diff --git a/toolbox/anatomy/tess_isosurface.m b/toolbox/anatomy/tess_isosurface.m index 28e34152e..5325ded5d 100644 --- a/toolbox/anatomy/tess_isosurface.m +++ b/toolbox/anatomy/tess_isosurface.m @@ -47,6 +47,7 @@ MeshFile = []; iSurface = []; isSave = true; +global GlobalData; % Parse inputs if (nargin < 3) || isempty(Comment) @@ -128,6 +129,7 @@ %% ===== CREATE SURFACE ===== % Compute isosurface +sMesh = db_template('LoadedSurface'); bst_progress('start', 'Generate thresholded isosurface from CT', 'Creating isosurface...'); [sMesh.Faces, sMesh.Vertices] = mri_isosurface(sMri.Cube, isoValue); bst_progress('inc', 10); @@ -145,6 +147,11 @@ sMesh.Vertices = bst_bsxfun(@times, sMesh.Vertices, sMri.Voxsize); % Convert to SCS sMesh.Vertices = cs_convert(sMri, 'mri', 'scs', sMesh.Vertices ./ 1000); +sMesh.Comment = sprintf('isoSurface (ISO_%d)', isoValue); +sMesh.VertConn = tess_vertconn(sMesh.Vertices, sMesh.Faces); +sMesh.VertNormals = tess_normals(sMesh.Vertices, sMesh.Faces, sMesh.VertConn); +[~, sMesh.VertArea] = tess_area(sMesh.Vertices, sMesh.Faces); +sMesh.SulciMap = tess_sulcimap(sMesh); %% ===== SAVE FILES ===== if isSave @@ -152,30 +159,52 @@ % Create output filenames ProtocolInfo = bst_get('ProtocolInfo'); SurfaceDir = bst_fullfile(ProtocolInfo.SUBJECTS, bst_fileparts(CtFile)); - % Get the mesh file + % Get the mesh file and MRI file MeshFile = bst_fullfile(SurfaceDir, 'tess_isosurface.mat'); - - % Replace existing isoSurface surface (tess_isosurface.mat) - [sSubjectTmp, iSubjectTmp, iSurfaceTmp] = bst_get('SurfaceFile', MeshFile); - if ~isempty(iSurfaceTmp) - file_delete(file_fullpath(MeshFile), 1); - sSubjectTmp.Surface(iSurfaceTmp) = []; - bst_set('Subject', iSubjectTmp, sSubjectTmp); - end - - % Save isosurface - sMesh.Comment = sprintf('isoSurface (ISO_%d)', isoValue); - sMesh = bst_history('add', sMesh, 'threshold_ct', 'CT thresholded isosurface generated with Brainstorm'); - bst_save(MeshFile, sMesh, 'v7'); - iSurface = db_add_surface(iSubject, MeshFile, sMesh.Comment); - % Display mesh with 3D orthogonal slices of the default MRI MriFile = sSubject.Anatomy(1).FileName; + % add details to sMesh structure + sMesh.FileName = file_short(MeshFile); + sMesh.Name = 'Other'; + % Get isoSurface (tess_isosurface.mat) + [~, ~, iSurface] = bst_get('SurfaceFile', MeshFile); + % Get 3D figure window hFig = bst_figures('GetFiguresByType', '3DViz'); - if isempty(hFig) - hFig = view_mri_3d(MriFile, [], 0.3, []); + + % if isosurface not created create it + if isempty(iSurface) + sMesh = bst_history('add', sMesh, 'threshold_ct', ['CT isosurface generated with isoValue threshold ' num2str(isoValue)]); + bst_save(MeshFile, sMesh, 'v7'); + iSurface = db_add_surface(iSubject, MeshFile, sMesh.Comment); + % open figure after saving + if isempty(hFig) + hFig = view_mri_3d(MriFile, [], 0.3, []); + view_surface(MeshFile, [], [], hFig, []); + end + else % else just update the isosurface surface patch with new computed values + % if surface present in database but figure not opened, open it + if isempty(hFig) + hFig = view_mri_3d(MriFile, [], 0.3, []); + view_surface(MeshFile, [], [], hFig, []); + end + % update the surface displayed in figure + TessInfo = getappdata(hFig, 'Surface'); + iSurfPatch = find(cellfun(@(x) ~isempty(regexp(x, '_isosurface', 'match')), {TessInfo.SurfaceFile})); + set(TessInfo(iSurfPatch(1)).hPatch, 'Vertices', sMesh.Vertices); + set(TessInfo(iSurfPatch(1)).hPatch, 'Faces', sMesh.Faces); + set(TessInfo(iSurfPatch(1)).hPatch, 'FaceVertexCData', ones(size(sMesh.Vertices))); + TessInfo(iSurfPatch(1)).nVertices = size(sMesh.Vertices, 1); + TessInfo(iSurfPatch(1)).nFaces = size(sMesh.Faces, 1); + setappdata(hFig, 'Surface', TessInfo); + % update the GlobalData with new surface + iSurfGlobal = find(file_compare({GlobalData.Surface.FileName}, sMesh.FileName)); + GlobalData.Surface(iSurfGlobal) = sMesh; + % update the surface in tess_isosurface.mat + sMesh = bst_history('add', sMesh, 'threshold_ct', ['CT isosurface updated with isovalue threshold ' num2str(isoValue)]); + sMesh.Curvature = tess_curvature(sMesh.Vertices, sMesh.VertConn, sMesh.VertNormals, .1); + bst_save(MeshFile, sMesh, 'v7'); + % reload the subject to reflect updated values + db_reload_subjects(iSubject); end - view_surface(MeshFile, 0.6, [], hFig, []); - panel_surface('SetIsoValue', isoValue); else % Return surface MeshFile = sMesh.Vertices; diff --git a/toolbox/gui/figure_3d.m b/toolbox/gui/figure_3d.m index a96a0d04b..dc47c67d7 100644 --- a/toolbox/gui/figure_3d.m +++ b/toolbox/gui/figure_3d.m @@ -993,6 +993,8 @@ function FigureKeyPressedCallback(hFig, keyEvent) % If figure is 2D is2D = ~strcmpi(FigureId.Type, '3DViz') && ~ismember(FigureId.SubType, {'3DSensorCap', '3DElectrodes', '3DOptodes'}); isRaw = strcmpi(GlobalData.DataSet(iDS).Measures.DataType, 'raw'); + % Get Surface + TessInfo = getappdata(hFig, 'Surface'); % ===== PROCESS BY CHARACTERS ===== switch (keyEvent.Character) @@ -1069,10 +1071,34 @@ function FigureKeyPressedCallback(hFig, keyEvent) end % === UP, DOWN, SPACE: Frequency or Tensor mode === case {'uparrow', 'downarrow'} - % If there are tensors displayed: update display + % Get figure handle Handles = bst_figures('GetFigureHandles', hFig); + % Check if there is SEEG isosurface displayed on the figure + iIsoSurf = find(cellfun(@(x) ~isempty(regexp(x, '_isosurface', 'match')), {TessInfo.SurfaceFile})); + % If there are tensors displayed: update display if isfield(Handles, 'TensorDisplay') && ~isempty(Handles.TensorDisplay) PlotTensorCut(hFig, [], [], [], keyEvent.Key, []); + % update isoValue threshold (Shift + Up/Down) + elseif ismember('shift', keyEvent.Modifier) && ~isempty(iIsoSurf) + % get the CT file and structure + SubjectFile = getappdata(hFig, 'SubjectFile'); + sSubject = bst_get('Subject', SubjectFile); + iCt = find(cellfun(@(x) ~isempty(regexp(x, '_volct', 'match')), {sSubject.Anatomy.FileName})); + CtFile = sSubject.Anatomy(iCt(1)).FileName; + sCt = bst_memory('LoadMri', CtFile); + % get the isosurface structure + sSurface = bst_memory('LoadSurface', TessInfo(iIsoSurf(1)).SurfaceFile); + % get the current isoValue + val = regexp(sSurface.Comment, '\d+', 'match'); + % increase/decrease isoValue by 300 for a coarse tuning by making sure it is in permissible range + if strcmpi(keyEvent.Key, 'uparrow') + newVal = str2double(val(1)) + 300; + else + newVal = str2double(val(1)) - 300; + end + if (newVal >= double(sCt.Histogram.whiteLevel)) && (newVal <= double(sCt.Histogram.intensityMax)) + tess_isosurface(CtFile, newVal); + end % Up/Down: Process by Freq panel else panel_freq('FreqKeyCallback', keyEvent); @@ -1556,6 +1582,8 @@ function ApplyViewToAllFigures(hSrcFig, isView, isSurfProp) %% ===== POPUP MENU ===== % Show a popup dialog about the target 3DViz figure function DisplayFigurePopup(hFig) + import java.awt.*; + import javax.swing.*; import java.awt.event.KeyEvent; import javax.swing.KeyStroke; import org.brainstorm.icon.*; @@ -1662,6 +1690,37 @@ function DisplayFigurePopup(hFig) end jPopup.addSeparator(); end + + % ==== MENU: ISOSURFACE ==== + % Create isosurface + iIsoSurf = find(cellfun(@(x)(~isempty(regexp(x, '_isosurface', 'match'))), {TessInfo.SurfaceFile})); + if ~isempty(iIsoSurf) + % get the CT file and its structure + SubjectFile = getappdata(hFig, 'SubjectFile'); + sSubject = bst_get('Subject', SubjectFile); + iCt = find(cellfun(@(x)(~isempty(regexp(x, '_volct', 'match'))), {sSubject.Anatomy.FileName})); + CtFile = sSubject.Anatomy(iCt(1)).FileName; + sCt = bst_memory('LoadMri', CtFile); + % get the isosurface structure + sSurface = bst_memory('LoadSurface', TessInfo(iIsoSurf(1)).SurfaceFile); + % panel operations + jPanel = gui_component('Panel'); + jPanel.setOpaque(0); + jPopup.add(jPanel); + % Title + jLabel = gui_component('label', [], '', 'isoValue threshold ', IconLoader.ICON_SURFACE); + jPanel.add(jLabel, BorderLayout.WEST); + % Spin button + % get the current isoValue + val = regexp(sSurface.Comment, '\d+', 'match'); + % increase/decrease isoValue by 50 for a fine tuning making sure it is in permissible range + spinmodel = SpinnerNumberModel(str2double(val(1)), double(sCt.Histogram.whiteLevel), double(sCt.Histogram.intensityMax), 50); + jSpinner = JSpinner(spinmodel); + jSpinner.setPreferredSize(Dimension(70,25)); + jSpinner.setToolTipText('isoValue thresholding'); + java_setcb(spinmodel, 'StateChangedCallback', @(h,ev)tess_isosurface(CtFile, jSpinner.getValue())); + jPanel.add(jSpinner, BorderLayout.EAST); + end % ==== MENU: 2DLAYOUT ==== if strcmpi(FigureType, 'Topography') && strcmpi(GlobalData.DataSet(iDS).Figure(iFig).Id.SubType, '2DLayout') @@ -1853,7 +1912,7 @@ function DisplayFigurePopup(hFig) jMenuMontage = gui_component('Menu', jPopup, [], 'Montage', IconLoader.ICON_TS_DISPLAY_MODE); panel_montage('CreateFigurePopupMenu', jMenuMontage, hFig); end - + % ==== MENU: COLORMAPS ==== % Create the colormaps menus bst_colormaps('CreateAllMenus', jPopup, hFig, 0); diff --git a/toolbox/gui/panel_surface.m b/toolbox/gui/panel_surface.m index 29d63044e..83bb23da6 100644 --- a/toolbox/gui/panel_surface.m +++ b/toolbox/gui/panel_surface.m @@ -107,20 +107,6 @@ % Quick preview java_setcb(jSliderSurfSmoothValue, 'StateChangedCallback', @(h,ev)SliderQuickPreview(jSliderSurfSmoothValue, jLabelSurfSmoothValue, 1)); - % Threshold title - jLabelSurfIsoValueTitle = gui_component('label', jPanelSurfaceOptions, 'br', 'Thresh.:'); - % Min size slider - jSliderSurfIsoValue = JSlider(1, GetIsoValueMaxRange(), 1); - jSliderSurfIsoValue.setPreferredSize(Dimension(SLIDER_WIDTH, DEFAULT_HEIGHT)); - jSliderSurfIsoValue.setToolTipText('isoSurface Threshold'); - java_setcb(jSliderSurfIsoValue, 'MouseReleasedCallback', @(h,ev)SliderCallback(h, ev, 'SurfIsoValue'), ... - 'KeyPressedCallback', @(h,ev)SliderCallback(h, ev, 'SurfIsoValue')); - jPanelSurfaceOptions.add('tab hfill', jSliderSurfIsoValue); - % IsoValue label - jLabelSurfIsoValue = gui_component('label', jPanelSurfaceOptions, [], ' 1', {JLabel.RIGHT, Dimension(LABEL_WIDTH, DEFAULT_HEIGHT)}); - % Quick preview - java_setcb(jSliderSurfIsoValue, 'StateChangedCallback', @(h,ev)SliderQuickPreview(jSliderSurfIsoValue, jLabelSurfIsoValue, 0)); - % Buttons jButtonSurfColor = gui_component('button', jPanelSurfaceOptions, 'br center', 'Color', {Dimension(BUTTON_WIDTH, DEFAULT_HEIGHT), Insets(0,0,0,0)}, 'Set surface color', @ButtonSurfColorCallback); jButtonSurfSulci = gui_component('toggle', jPanelSurfaceOptions, '', 'Sulci', {Dimension(BUTTON_WIDTH, DEFAULT_HEIGHT), Insets(0,0,0,0)}, 'Show/hide sulci map', @ButtonShowSulciCallback); @@ -228,9 +214,6 @@ 'jButtonSurfColor', jButtonSurfColor, ... 'jLabelSurfSmoothValue', jLabelSurfSmoothValue, ... 'jSliderSurfSmoothValue', jSliderSurfSmoothValue, ... - 'jLabelSurfIsoValueTitle',jLabelSurfIsoValueTitle, ... - 'jLabelSurfIsoValue', jLabelSurfIsoValue, ... - 'jSliderSurfIsoValue', jSliderSurfIsoValue, ... 'jButtonSurfSulci', jButtonSurfSulci, ... 'jButtonSurfEdge', jButtonSurfEdge, ... 'jSliderResectX', jSliderResectX, ... @@ -258,8 +241,6 @@ function SliderQuickPreview(jSlider, jText, isPercent) nVertices = str2num(char(jLabelNbVertices.getText())); sliderSizeVector = GetSliderSizeVector(nVertices); jText.setText(sprintf('%d', sliderSizeVector(double(jSlider.getValue())))); - elseif (jSlider == jSliderSurfIsoValue) - jText.setText(sprintf('%d', double(jSlider.getValue()))); elseif isPercent jText.setText(sprintf('%d%%', double(jSlider.getValue()))); else @@ -410,42 +391,6 @@ function SliderCallback(hObject, event, target) case 'SurfSmoothValue' SurfSmoothValue = jSlider.getValue() / 100; SetSurfaceSmooth(hFig, iSurface, SurfSmoothValue, 1); - - case 'SurfIsoValue' - % get the handles - hFig = bst_figures('GetFiguresByType', '3DViz'); - SubjectFile = getappdata(hFig, 'SubjectFile'); - if ~isempty(SubjectFile) - sSubject = bst_get('Subject', SubjectFile); - CtFile = []; - MeshFile = []; - for i=1:length(sSubject.Anatomy) - if ~isempty(regexp(sSubject.Anatomy(i).FileName, 'CT', 'match')) - CtFile = sSubject.Anatomy(i).FileName; - end - end - for i=1:length(sSubject.Surface) - if ~isempty(regexp(sSubject.Surface(i).FileName, 'tess_isosurface', 'match')) - MeshFile = sSubject.Surface(i).FileName; - end - end - end - - % ask user if they want to proceed - isProceed = java_dialog('confirm', 'Do you want to proceed generating mesh with new isoValue ?', 'Changing threshold'); - if ~isProceed - [sSubjectTmp, iSubjectTmp, iSurfaceTmp] = bst_get('SurfaceFile', MeshFile); - isoValue = regexp(sSubjectTmp.Surface(iSurfaceTmp).Comment, '\d*', 'match'); - SetIsoValue(str2double(isoValue{1})); - return; - end - - % get the iso value from slider - isoValue = jSlider.getValue(); - - % remove the old isosurface and generate and load the new one - ButtonRemoveSurfaceCallback(); - tess_isosurface(CtFile, isoValue); case 'DataAlpha' % Update value in Surface array @@ -528,42 +473,6 @@ function SliderCallback(hObject, event, target) end end -%% ===== GET SLIDER ISOVALUE ===== -function isoValue = GetIsoValueMaxRange() - % get the handles - hFig = bst_figures('GetFiguresByType', '3DViz'); - if ~isempty(hFig) - SubjectFile = getappdata(hFig, 'SubjectFile'); - if ~isempty(SubjectFile) - sSubject = bst_get('Subject', SubjectFile); - CtFile = []; - for i=1:length(sSubject.Anatomy) - if ~isempty(regexp(sSubject.Anatomy(i).FileName, 'CT', 'match')) - CtFile = sSubject.Anatomy(i).FileName; - end - end - end - - if ~isempty(CtFile) - sMri = bst_memory('LoadMri', CtFile); - isoValue = double(sMri.Histogram.intensityMax); - end - else - isoValue = 4500.0; - end -end - -%% ===== SET SLIDER ISOVALUE ===== -function SetIsoValue(isoValue) - % get panel controls - ctrl = bst_get('PanelControls', 'Surface'); - if isempty(ctrl) - return; - end - ctrl.jLabelSurfIsoValue.setText(sprintf('%d', isoValue)); - ctrl.jSliderSurfIsoValue.setValue(isoValue); -end - %% ===== SCROLL MRI CUTS ===== function ScrollMriCuts(hFig, direction, value) %#ok % Get Mri and figure Handles @@ -1132,11 +1041,6 @@ function UpdateSurfaceProperties() end % If surface is sliced MRI isAnatomy = strcmpi(TessInfo(iSurface).Name, 'Anatomy'); - if ~isempty(regexp(TessInfo(iSurface).SurfaceFile, 'isosurface', 'match')) - isIsoSurface = 1; - else - isIsoSurface = 0; - end % ==== Surface properties ==== % Number of vertices @@ -1152,15 +1056,6 @@ function UpdateSurfaceProperties() % Surface smoothing ALPHA ctrl.jSliderSurfSmoothValue.setValue(100 * TessInfo(iSurface).SurfSmoothValue); ctrl.jLabelSurfSmoothValue.setText(sprintf('%d%%', round(100 * TessInfo(iSurface).SurfSmoothValue))); - % Show/hide isoSurface thresholding - ctrl.jSliderSurfIsoValue.setVisible(isIsoSurface); - ctrl.jLabelSurfIsoValueTitle.setVisible(isIsoSurface); - ctrl.jLabelSurfIsoValue.setVisible(isIsoSurface); - if isIsoSurface - [sSubjectTmp, iSubjectTmp, iSurfaceTmp] = bst_get('SurfaceFile', TessInfo(iSurface).SurfaceFile); - isoValue = regexp(sSubjectTmp.Surface(iSurfaceTmp).Comment, '\d*', 'match'); - SetIsoValue(str2double(isoValue{1})); - end % Show sulci button ctrl.jButtonSurfSulci.setSelected(TessInfo(iSurface).SurfShowSulci); % Show surface edges button diff --git a/toolbox/tree/tree_callbacks.m b/toolbox/tree/tree_callbacks.m index 800f0d36f..b5e6aaf2a 100644 --- a/toolbox/tree/tree_callbacks.m +++ b/toolbox/tree/tree_callbacks.m @@ -226,12 +226,13 @@ % Get displayable modalities for this file [tmp, DisplayMod] = bst_get('ChannelModalities', filenameRelative); DisplayMod = intersect(DisplayMod, {'EEG','MEG','MEG GRAD','MEG MAG','ECOG','SEEG','ECOG+SEEG','NIRS'}); + hFig = bst_figures('GetFiguresByType','3DViz'); % If only one modality if ~isempty(DisplayMod) if strcmpi(DisplayMod{1}, 'ECOG+SEEG') || (length(DisplayMod) >= 2) && all(ismember({'SEEG','ECOG'}, DisplayMod)) DisplayChannels(bstNodes, 'ECOG+SEEG', 'cortex', 1); elseif strcmpi(DisplayMod{1}, 'SEEG') - DisplayChannels(bstNodes, DisplayMod{1}, 'anatomy', 1, 0); + DisplayChannels(bstNodes, DisplayMod{1}, 'anatomy', 1, 0, hFig); elseif strcmpi(DisplayMod{1}, 'ECOG') DisplayChannels(bstNodes, DisplayMod{1}, 'cortex', 1); elseif ismember(DisplayMod{1}, {'MEG','MEG GRAD','MEG MAG'}) @@ -835,6 +836,7 @@ % Get avaible modalities for this data file [AllMod, DisplayMod] = bst_get('ChannelModalities', filenameRelative); Device = bst_get('ChannelDevice', filenameRelative); + hFig = bst_figures('GetFiguresByType','3DViz'); % Replace SEEG+ECOG with iEEG if ~isempty(AllMod) && all(ismember({'SEEG','ECOG'}, AllMod)) AllMod = cat(2, {'ECOG+SEEG'}, setdiff(AllMod, {'SEEG','ECOG'})); @@ -923,7 +925,7 @@ end % === SEEG IMPLANTATION === if (length(bstNodes) == 1) && ((isempty(AllMod) && strcmpi(sStudy.Name, 'implantation')) || any(ismember({'SEEG','ECOG','ECOG+SEEG'}, AllMod))) - gui_component('MenuItem', jPopup, [], 'SEEG/ECOG implantation', IconLoader.ICON_SEEG_DEPTH, [], @(h,ev)DisplayChannels(bstNodes, 'SEEG', 'anatomy', 1, 0)); + gui_component('MenuItem', jPopup, [], 'SEEG/ECOG implantation', IconLoader.ICON_SEEG_DEPTH, [], @(h,ev)DisplayChannels(bstNodes, 'SEEG', 'anatomy', 1, 0, hFig)); end % === SEEG CONTACT LABELLING === if (length(bstNodes) == 1) && ~isempty(AllMod) && any(ismember({'SEEG','ECOG','ECOG+SEEG'}, AllMod))