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))