Skip to content

Commit

Permalink
Automatic EEG localization and labelling using Revopoint 3D (#716)
Browse files Browse the repository at this point in the history
* initial commit: enable importing wavefront obj files into brainstorm

* refactoring: add input checks, remove unwanted code

* add 'Color' field to the surfacemat structure templates

* add check for 'Color' field

* Update menu: 'Digitizer' and 'Revopoint' under the bracket 'Digitize'

* Refactor 'panel_digitize'  to add conditions for 'Revopoint'

* support visualizing per vertex color for imported OBJ mesh in BST

* continued: enable importing wavefront obj files into brainstorm

* External added: Piotr Image and Video Toolbox (https://github.com/pdollar/toolbox)

required for warping and interpolating points on EEG cap

* deface a 3D mesh

* Tag imported mesh with color as 'textured'

* Handle displaying a 'textured' mesh

* Key 'c' as shortcut for collecting points from Revopoint mesh

* Refactor importing wafefront obj

- it is always going to be a meshed OBJ so 'FileType' parameter is unnecessary
- renamed 'tri' as 'faces'
- renamed 'pos' as 'vertices'

* handle mesh without texture information

* Update comment

* intial commit: automatic detection and labelling of EEG cap electrodes

* custom layout EEG caps

- ANT 65 is a modeifed version of ANT Waveguard 64
- Acticap 66 is a modified version of Acticap 65

* configure 'Edit Settings' options for Revopoint

* Option to generate 100 random headpoints

to be used for registration to MRI

* fix typo bugs introduced in 3066842

* 'AddMontage' function modified to handle adding Channel File as montage

* WIP: Handle ANT Waveguard 65 and Acticap 66 EEG caps

Needs work on a more generic approach to handle different EEG caps

* updated comment (file description)

* updated cap type

* typo in file name

* Update Color for 'textured' mesh when downsized/downsampled

* Update comments on ways to add montage

* Update panel_digitize.m

- Limit display of 'Auto' and 'Random' buttons for Revopoint only
- Correct display order of EEG labels based on the initialization labels required as per the EEG cap
- Removed unwanted code for sketch points (was used for debugging)
- Creating montages from EEG cap layout mat files (only for Revopoint)

* load the latest (last updated) mesh to work on

* add to 1b3e2c8

* adding montage from text files only available for Polhemus

* better handling of saving ChannelFile info to DigitizeOptions

* removing 'Color' fields from template

To be added only when needed

* 'Color' field added to surface when needed

* remove commented code

* Handle updating surface color for 'Views' menu option on 3D figure

* making sure old naming in UI is intact for 'Digitize'

* handle 'Edit Settings' for Revopoint

* mask unwanted menu items for Revopoint

* 'DelimitedTextImportOptions' support for R2016b to R2018a

* code cleanup and reduce ambiguity

* ignore 'rmoutliers' step for < R2018b

* remove default menu size 12 for EEG cap montages

* support new panel calls in the legacy panel; some code cleanup

* adding revopoint changes to the new panel (1) and its dependencies

* manual point collection in new panel for revopoint

* new panel: collecting 100 random head points

* legacy panel: reverting adding montage from text files

* legacy panel: refactor montage selection in menu for Revopoint

* new panel: montage menu refactoring for Revopoint

* new panel: revopoint automation for detecting and labeling electrodes

* new panel: refactor 'Edit_Settings' menu to handle Revopoint options

* new panel: allow updating a specific EEG electrode from the coordinate list manually

* new panel: remove unused variable

* new panel: handle enable/disable of the 'Auto' and 'Random' buttons

* new panel: User warning if 'Auto' button pressed without initialization points

* new panel: use `Digitize.Type` to define the mode used

* legacy panel: handle 'Auto' and 'Random' visibility; some code cleanup

* new and legacy panel: rename `Revopoint` to `3DScanner` for better generalization

* Bugfix: support old `Digitize.Options.Montages` struct

* GUI menu name updated: '3DScaner' to '3D scanner'

* Bugfix: Two entries in `GlobalData.Surface` were created instead of one

* Implement `rmoutliers` without arguments for older Matlab versions

* Ignore unused output arguments

* Refactor to avoid unnecessary reads to db: `bst_get('Subject')`

* Gentle exit if user cancels "Import surfaces..."

* Clean code. Avoid copy-paste

* Cleaner

* Add `Color` field for all surface files and loaded surfaces

- Avoid different versions of surfaces files and loaded surfaces
- Avoid many checks for `Color` field

* Treecallback for Display texture surface

- Right-click > Display has same behaviour as double-click
- Do not load surface on right-click, that slows the popup menu display

* Bugfix typo

* Avoid copy-paste same method call

* Parse input in `panel_digitize_2024('Start')`

* Not apparent reason to have a different panel name

* Add progress bar to 'EEGAutoDetectElectrodes'

* Parse input in `panel_digitize('Start')`

See: 3d77e53

* Refactor to avoid unnecessary reads to db: `bst_get('Subject')`

See 1c2c2b0

* Not apparent reason to have a different panel name

See 8d6bc3b

* Code easier to read

* Bugfix

* Remove unnecessary FOR loops

* handle `Digitize` as input to `DigitizerType` parameter

* Revert "handle `Digitize` as input to `DigitizerType` parameter"

This reverts commit 53f43dd.

* Better handling of calls to `Start` function

recommended here: #716 (comment)

* No scaling needed as OBJ vertices are unitless

* new panel: avoid copy pasting; add comments

* function to compartmentalize automatic detection and labelling

* clean code; call compartmentalized function

* legacy panel: avoid copy paste

sync with changes as above for the new panel

* bugfix: missed some call checks

* code cleanup; comments added

* avoid copy/paste for `Edit settings`

* add comments; clean code and calls `auto_3dscanner`

* changed surface description

avoid using 'head' keyword because if renamed having `head` word in it, the `node_rename` function converts the type to `Scalp`.

* remove comment; `in_tess` handles node naming

* handle textured surface import and selection

* do not delete `3D scanner` figure when resetting

* textured surface handling: allow user to choose from options

The options:

1. Subject has no textured mesh or subject does not exist at all:
 - Import surface GUI pops up, user chooses the surface and it gets loaded
2. Subject already has 1 or more than 1 textured mesh in it then user is given an option:
 - Use existing - if only one mesh present then it just loads that directly; if more than one present then user is given an option to choose which one he wants to use
 - Add New - Import surface GUI pops up, user chooses the surface and it gets loaded
 - Cancel / Press (X) at any instant - cancels the process and exits

* `Digitize (3D scanner)` option on right click pop up for textured surface

* Better way to get the open surface details

* make textured surface comments unique; show comments as choice to users to choose from

- Unique comments for same textured mesh makes it easy for the user to distinguish one from the other
- When user clicks 'Use existing' the list of textured surface comments pop up for the users to choose from. This is more intuitive for the user than having to choose from the filename which sits in the database.

* add comments

* collect 100 points without `reducepatch`

* bugfix: display correct label text on deleting-updating landmark coordinates

* Avoid copy-paste same method call

* Fixes 128c9b0

* Clean code

* for `Start over`, close and reset figure for `3D Scanner`

* bugfix: Right click on subject > `Import surfaces`

* better way to access the current surface file

* import head surface obj with units using fieldtrip's `ft_convert_units`

* legacy: better way to access the current surface file

* update the correct caps based on the protocol

* update landmark initialization points

* add all other surface property calculations

required as the updated surface will be saved to database

* save the surface and update the node

* check spm12 installation for `ft_convert_units`

`ft_convert_units` is part of spm12/external/fieldtrip

* bugfix

* replace `erase` with `strrep` for backward compatibility

* improve obj parsing (see description)

- updated the code reference from where this was inspired
- for some obj, obtained from 3D software like Maya and Blender, when parsed using  'readtable', the vertex coordinates start from the 3rd column
- `refine` is a FT function to improve resolution. Using this creates a processing and loading delay which can be avoided as the scans would already be in a good resolution.

* similar to `ft_convert_units`

inspired by `channel_fixunits.m`

* remove `fieldtrip` dependency

* typo fix

* generalize checking for initialization points

* adding `Wearable Sensing DS-24` EEG cap layout

It follows the 10-20 system with 'Fpz' as GND and 'A1, A2' as  REF.

* Update `channel_fixunits` to accept vertices

Remove unnecessary copies of code

* Simpler creation of struct (`in_tess_wftobj`)

* GUI: Add feedback that surface is loading...

* Simplify using already defined function handle

* Simpler and meaningful variable name

* Use content rather than string in filename

* Cleaner diff

* Unify "EditSettings" options, default values and user inputs

* Correct file tag

* EEG caps with landmark labels must be usable, as they allow manual loc

* Add info in auto button tooltip

* Enable auto button for valid caps IFF all landmarks are acquired

* Update function names in `auto_3dscanner`

* Move "whiteCap" out of `FindElectrodesEegCap`

* Bugfix: Consider that user acquires more points than landmarks

* Bugfix: Setting proper tag for textured surface file

* Improve warping by using all EEG points acquired by user

* Remove unnecessary sorting

Landmarks and extra points are identified by name.
In output the locations for EEG electrodes is get also by name

* Change dots in console with progress bar

* Suppress expected warnings

* Remove unnecessary output: `auto_3dscanner('GetEegCapLandmarkLabels')`

* Reduce perceived lag in opening figures after fiducials are set

* Unify sound of clicks across platforms for source and compiled BST

* Avoid copy/pasting for default EEG caps menu

* for <2016b: no importing WFTOBJ and `3D scanner` option

* rename `auto_3dscanner` to `channel_detect_eegcap_auto`

* update description; expected units `mm`

* Correct case in comments

* remove unnecessary indents for cleaner diff

* bugfix: `iOptionsType` was not initialized; use `num2str` for `ComPort`

* bugfix: set global point index with found index

* Correct case in comments

* changes in `panel_digitize_2024` made to `panel_digitize`

* collect random head shape points based on Brainstorm recommended way

https://neuroimage.usc.edu/brainstorm/Tutorials/TutDigitize3dScanner#Collect_the_desired_number_of_head_shape_points

* explicitly mention `digitize` in condition

* revert `ComPort` type conversion

mistook it for a number

* `menu_default_eegcaps`:  separate function for generating BST available EEG caps menu

* extend renaming of surfaces with same filename (comments) while importing to all surfaces

* Add first draft of parsing of `.wobj` text file

* proper columns assigned for `vertices`, `faces` and `textureIdx`

* use `regexp` to spoort older versions; only check based on `Comment` for renaming

* revert back importing obj support for < R2016b

* Fix indentation

* Use functions in Brainstorm to:

- Retrieve sSubject from MRI filename
- Generate unique comment

* Bugfix: return same orientation as input

* Cleaner indexing

* Update regexp to handle scientific notation (using e notation)

* Digitize panels, do not clear settings that are not asked to the user

* Allow displaying texture surfaces with "Add surface" in surface panel

* use `clock` instead of `datetime` (support older MATLAB versions)

* better way to get the surface file structure in  `hFig`

tested in R2014a:  `TessInfo.hPatch` does not have the structure definitions

* supress more warnings

tested in R2014a: Large Radius warnings were showing up

* add disclaimer to users that 'Auto' feature is experimental

* Bugfix: No check required while doing manual head shape points collection

* Bugfix: Update tooltip for 'Auto' button

* Allow to cancel start of autodetection

* Bugfix: Disable Auto button, when EXTRA are being acquired.

* Bugfix: Wrong logic: `~cellfun(@isempty, ...)` results in `1` or `0`

---------

Co-authored-by: rcassani <[email protected]>
  • Loading branch information
chinmaychinara91 and rcassani authored Nov 6, 2024
1 parent 0b8f184 commit 6c646d2
Show file tree
Hide file tree
Showing 31 changed files with 2,534 additions and 577 deletions.
Binary file added defaults/eeg/Colin27/channel_ANT_Waveguard_65.mat
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added defaults/eeg/ICBM152/channel_ANT_Waveguard_65.mat
Binary file not shown.
Binary file not shown.
Binary file not shown.
26 changes: 26 additions & 0 deletions external/piotr_toolbox/LICENCE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
Copyright (c) 2012, Piotr Dollar
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

The views and conclusions contained in the software and documentation are those
of the authors and should not be interpreted as representing official policies,
either expressed or implied, of the FreeBSD Project.
77 changes: 77 additions & 0 deletions external/piotr_toolbox/tpsGetWarp.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
function [warp,L,LnInv,bendE] = tpsGetWarp( lambda, xsS, ysS, xsD, ysD )
% Given two sets of corresponding points, calculates warp between them.
%
% Uses booksteins PAMI89 method. Can then apply warp to a new set of
% points (tpsInterpolate), or even an image (tpsInterpolateIm).
% "Principal Warps: Thin-Plate Splines and the Decomposition of
% Deformations". Bookstein. PAMI 1989.
%
% USAGE
% [warp,L,LnInv,bendE] = tpsGetWarp( lambda, xsS, ysS, xsD, ysD )
%
% INPUTS
% lambda - rigidity of warp (inf means warp becomes affine)
% xsS, ysS - [1xn] correspondence points from source image
% xsD, ysD - [1xn] correspondence points from destination image
%
% OUTPUTS
% warp - bookstein warping parameters
% L, LnInv - see bookstein
% bendE - bending energy
%
% EXAMPLE - 1
% xsS=[0 -1 0 1]; ysS=[1 0 -1 0]; xsD=xsS; ysD=[3/4 1/4 -5/4 1/4];
% warp = tpsGetWarp( 0, xsS, ysS, xsD, ysD );
% [gxs, gys] = meshgrid(-1.25:.25:1.25,-1.25:.25:1.25);
% tpsInterpolate( warp, gxs, gys, 1 );
%
% EXAMPLE - 2
% xsS = [3.6929 6.5827 6.7756 4.8189 5.6969];
% ysS = [10.3819 8.8386 12.0866 11.2047 10.0748];
% xsD = [3.9724 6.6969 6.5394 5.4016 5.7756];
% ysD = [6.5354 4.1181 7.2362 6.4528 5.1142];
% warp = tpsGetWarp( 0, xsS, ysS, xsD, ysD );
% [gxs, gys] = meshgrid(3.5:.25:7, 8.5:.25: 12.5);
% tpsInterpolate( warp, gxs, gys, 1 );
%
% See also TPSINTERPOLATE, TPSINTERPOLATEIM, TPSRANDOM
%
% Piotr's Computer Vision Matlab Toolbox Version 2.0
% Copyright 2014 Piotr Dollar. [pdollar-at-gmail.com]
% Licensed under the Simplified BSD License [see https://github.com/pdollar/toolbox/blob/master/external/bsd.txt]

dim = size( xsS );
if( all(size(xsS)~=dim) || all(size(ysS)~=dim) || all(size(xsD)~=dim))
error( 'argument sizes do not match' );
end

% get L
n = size(xsS,2);
deltaXs = xsS'*ones(1,n) - ones(n,1) * xsS;
deltaYs = ysS'*ones(1,n) - ones(n,1) * ysS;
Rsq = (deltaXs .* deltaXs + deltaYs .* deltaYs);
Rsq = Rsq+eye(n); K = Rsq .* log( Rsq ); K( isnan(K) )=0;
K = K + lambda * eye( n );
P = [ ones(n,1), xsS', ysS' ];
L = [ K, P; P', zeros(3,3) ];
LInv = L^(-1);
LnInv = LInv(1:n,1:n);

% recover W's
wx = LInv * [xsD 0 0 0]';
affinex = wx(n+1:n+3);
wx = wx(1:n);
wy = LInv * [ysD 0 0 0]';
affiney = wy(n+1:n+3);
wy = wy(1:n);

% record warp
warp.wx = wx; warp.affinex = affinex;
warp.wy = wy; warp.affiney = affiney;
warp.xsS = xsS; warp.ysS = ysS;
warp.xsD = xsD; warp.ysD = ysD;

% get bending energy (without regularization)
w = [wx'; wy'];
K = K - lambda * eye( n );
bendE = trace(w*K*w')/2;
52 changes: 52 additions & 0 deletions external/piotr_toolbox/tpsInterpolate.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
function [xsR,ysR] = tpsInterpolate( warp, xs, ys, show )
% Apply warp (obtained by tpsGetWarp) to a set of new points.
%
% USAGE
% [xsR,ysR] = tpsInterpolate( warp, xs, ys, [show] )
%
% INPUTS
% warp - [see tpsGetWarp] bookstein warping parameters
% xs, ys - points to apply warp to
% show - [1] will display results in figure(show)
%
% OUTPUTS
% xsR, ysR - result of warp applied to xs, ys
%
% EXAMPLE
%
% See also TPSGETWARP
%
% Piotr's Computer Vision Matlab Toolbox Version 2.0
% Copyright 2014 Piotr Dollar. [pdollar-at-gmail.com]
% Licensed under the Simplified BSD License [see https://github.com/pdollar/toolbox/blob/master/external/bsd.txt]

if( nargin<4 || isempty(show)); show = 1; end

wx = warp.wx; affinex = warp.affinex;
wy = warp.wy; affiney = warp.affiney;
xsS = warp.xsS; ysS = warp.ysS;
xsD = warp.xsD; ysD = warp.ysD;

% interpolate points (xs,ys)
xsR = f( wx, affinex, xsS, ysS, xs(:)', ys(:)' );
ysR = f( wy, affiney, xsS, ysS, xs(:)', ys(:)' );

% optionally show points (xsR, ysR)
if( show )
figure(show);
subplot(2,1,1); plot( xs, ys, '.', 'color', [0 0 1] );
hold('on'); plot( xsS, ysS, '+' ); hold('off');
subplot(2,1,2); plot( xsR, ysR, '.' );
hold('on'); plot( xsD, ysD, '+' ); hold('off');
end

function zs = f( w, aff, xsS, ysS, xs, ys )
% find f(x,y) for xs and ys given W and original points
n = size(w,1); ns = size(xs,2);
delXs = xs'*ones(1,n) - ones(ns,1)*xsS;
delYs = ys'*ones(1,n) - ones(ns,1)*ysS;
distSq = (delXs .* delXs + delYs .* delYs);
distSq = distSq + eye(size(distSq)) + eps;
U = distSq .* log( distSq ); U( isnan(U) )=0;
zs = aff(1)*ones(ns,1)+aff(2)*xs'+aff(3)*ys';
zs = zs + sum((U.*(ones(ns,1)*w')),2);
62 changes: 62 additions & 0 deletions toolbox/anatomy/tess_deface.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
function [head_surface] = tess_deface(head_surface)
% TESS_DEFACE: Removing non-essential vertices (bottom half of the subject's face in mesh) to deface the 3D mesh
%
% USAGE: [head_surface] = tess_deface(head_surface);
%
% INPUT:
% - head_surface: Brainstorm tesselation structure with fields:
% |- Vertices : {[nVertices x 3] double}, in millimeters
% |- Faces : {[nFaces x 3] double}
% |- Color : {[nColors x 3] double}, normalized between 0-1 (optional)
%
% OUTPUT:
% - head_surface: Brainstorm tesselation structure with fields:
% |- Vertices : {[nVertices x 3] double}, in millimeters
% |- Faces : {[nFaces x 3] double}
% |- Color : {[nColors x 3] double}, normalized between 0-1 (optional)
%
% @=============================================================================
% This function is part of the Brainstorm software:
% https://neuroimage.usc.edu/brainstorm
%
% Copyright (c) University of Southern California & McGill University
% This software is distributed under the terms of the GNU General Public License
% as published by the Free Software Foundation. Further details on the GPLv3
% license can be found at http://www.gnu.org/copyleft/gpl.html.
%
% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE
% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY
% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY
% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE.
%
% For more information type "brainstorm license" at command prompt.
% =============================================================================@
%
% Authors: Yash Shashank Vakilna, 2024
% Chinmay Chinara, 2024

% Identify vertices to remove from the surface mesh
% Spherical coordinates
[TH,PHI,R] = cart2sph(head_surface.Vertices(:,1), head_surface.Vertices(:,2), head_surface.Vertices(:,3));
% Flat projection
R = 1 - PHI ./ pi*2;

% Remove the identified vertices from the surface mesh
iRemoveVert = find(R > 1.1);
if ~isempty(iRemoveVert)
[head_surface.Vertices, head_surface.Faces] = tess_remove_vert(head_surface.Vertices, head_surface.Faces, iRemoveVert);
if isfield(head_surface, 'Color')
head_surface.Color(iRemoveVert, :) = [];
end
end

head_surface.VertConn = tess_vertconn(head_surface.Vertices, head_surface.Faces);
head_surface.VertNormals = tess_normals(head_surface.Vertices, head_surface.Faces, head_surface.VertConn);
head_surface.Curvature = tess_curvature(head_surface.Vertices, head_surface.VertConn, head_surface.VertNormals, .1);
[~, head_surface.VertArea] = tess_area(head_surface.Vertices, head_surface.Faces);
head_surface.SulciMap = tess_sulcimap(head_surface);
head_surface.Comment = [head_surface.Comment '_defaced'];
head_surface = bst_history('add', head_surface, 'deface_mesh', 'mesh defaced');

end
39 changes: 25 additions & 14 deletions toolbox/anatomy/tess_downsize.m
Original file line number Diff line number Diff line change
Expand Up @@ -84,21 +84,28 @@

% Ask for resampling method
if isempty(Method)
% Downsize methods strings
methods_str = {['<HTML><B><U>Matlab''s reducepatch:</U></B><BR>' ...
'&nbsp;&nbsp;&nbsp;| - Inhomogeneous mesh: large faces at the top of the gyri<BR>' ...
'&nbsp;&nbsp;&nbsp;| - Keeps the atlases and the subjects co-registration'], ...
['<HTML><B>Matlab''s reducepatch + subdivide large faces:</B><BR>' ...
'&nbsp;&nbsp;&nbsp;| - The large faces at the top of the gyri are subdivided in three<BR>' ...
'&nbsp;&nbsp;&nbsp;| - <U>Deletes</U> the atlases and the subjects co-registration'], ...
['<HTML><B>iso2mesh/CGAL library:</B><BR>' ...
'&nbsp;&nbsp;&nbsp;| - Homogeneous mesh: all the faces have similar sizes<BR>' ...
'&nbsp;&nbsp;&nbsp;| - <U>Deletes</U> the atlases and the subject co-registration<BR>' ...
'&nbsp;&nbsp;&nbsp;| - If the downsample looks dark, right-click > Swap faces']};
% ['<HTML><B>iso2mesh/CGAL + project on the original surface:</B><BR>' ...
% '&nbsp;&nbsp;&nbsp;| - Homogeneous mesh but possible <U>topological problems</U><BR>' ...
% '&nbsp;&nbsp;&nbsp;| - <U>Damages</U> the atlases and the subject co-registration']},

% Identify textured surfaces (color info is present) and show available methods for them
VarInfo = whos('-file',file_fullpath(TessFile), 'Color');
if all(VarInfo.size ~= 0)
methods_str = methods_str(1); % Inhomogeneous mesh
end
% Ask method
ind = java_dialog('radio', 'Select the resampling method:', 'Resample surface', [], ...
{['<HTML><B><U>Matlab''s reducepatch:</U></B><BR>' ...
'&nbsp;&nbsp;&nbsp;| - Inhomogeneous mesh: large faces at the top of the gyri<BR>' ...
'&nbsp;&nbsp;&nbsp;| - Keeps the atlases and the subjects co-registration'], ...
['<HTML><B>Matlab''s reducepatch + subdivide large faces:</B><BR>' ...
'&nbsp;&nbsp;&nbsp;| - The large faces at the top of the gyri are subdivided in three<BR>' ...
'&nbsp;&nbsp;&nbsp;| - <U>Deletes</U> the atlases and the subjects co-registration'], ...
['<HTML><B>iso2mesh/CGAL library:</B><BR>' ...
'&nbsp;&nbsp;&nbsp;| - Homogeneous mesh: all the faces have similar sizes<BR>' ...
'&nbsp;&nbsp;&nbsp;| - <U>Deletes</U> the atlases and the subject co-registration<BR>' ...
'&nbsp;&nbsp;&nbsp;| - If the downsample looks dark, right-click > Swap faces']}, 1);
% ['<HTML><B>iso2mesh/CGAL + project on the original surface:</B><BR>' ...
% '&nbsp;&nbsp;&nbsp;| - Homogeneous mesh but possible <U>topological problems</U><BR>' ...
% '&nbsp;&nbsp;&nbsp;| - <U>Damages</U> the atlases and the subject co-registration']}, 1);
ind = java_dialog('radio', 'Select the resampling method:', 'Resample surface', [], methods_str, 1);
if isempty(ind)
return
end
Expand Down Expand Up @@ -128,6 +135,7 @@
% Prepare variables
TessMat.Faces = double(TessMat.Faces);
TessMat.Vertices = double(TessMat.Vertices);
TessMat.Color = double(TessMat.Color);
dsFactor = newNbVertices / size(TessMat.Vertices, 1);


Expand All @@ -145,6 +153,9 @@
% Re-order the vertices so that they are in the same order in the output surface
[I, iSort] = sort(I);
NewTessMat.Vertices = TessMat.Vertices(I,:);
if ~isempty(TessMat.Color)
NewTessMat.Color = TessMat.Color(I,:);
end
J = J(iSort);
% Re-order the vertices in the faces
iSortFaces(J) = 1:length(J);
Expand Down
7 changes: 5 additions & 2 deletions toolbox/core/bst_get.m
Original file line number Diff line number Diff line change
Expand Up @@ -3474,9 +3474,11 @@
'isSimulate', 0, ...
'Montages', [...
struct('Name', 'No EEG', ...
'Labels', []), ...
'Labels', [], ...
'ChannelFile', []), ...
struct('Name', 'Default', ...
'Labels', [])], ...
'Labels', [], ...
'ChannelFile', [])], ...
'iMontage', 1, ...
'Version', '2024'); % Version of the Digitize panel: 'legacy' or '2024'
argout1 = FillMissingFields(contextName, defPref);
Expand Down Expand Up @@ -3633,6 +3635,7 @@
{'.gii'}, 'GIfTI / World coordinates (*.gii)', 'GII-WORLD'; ...
{'.fif'}, 'MNE (*.fif)', 'FIF'; ...
{'.obj'}, 'MNI OBJ (*.obj)', 'MNIOBJ'; ...
{'.obj'}, 'Wavefront OBJ (*.obj)', 'WFTOBJ'; ...
{'.msh'}, 'SimNIBS3/headreco Gmsh4 (*.msh)', 'SIMNIBS3'; ...
{'.msh'}, 'SimNIBS4/charm Gmsh4 (*.msh)', 'SIMNIBS4'; ...
{'.tri'}, 'TRI (*.tri)', 'TRI'; ...
Expand Down
1 change: 1 addition & 0 deletions toolbox/core/bst_memory.m
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@
sSurf.Comment = surfMat.Comment;
sSurf.Faces = double(surfMat.Faces);
sSurf.Vertices = double(surfMat.Vertices);
sSurf.Color = double(surfMat.Color);
sSurf.VertConn = surfMat.VertConn;
sSurf.VertNormals = surfMat.VertNormals;
[tmp, sSurf.VertArea] = tess_area(surfMat.Vertices, surfMat.Faces);
Expand Down
2 changes: 2 additions & 0 deletions toolbox/db/db_template.m
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
'Comment', '', ...
'Vertices', [], ...
'Faces', [], ...
'Color', [], ...
'VertConn', [], ...
'VertNormals', [], ...
'Curvature', [], ...
Expand Down Expand Up @@ -673,6 +674,7 @@
'Comment', '', ...
'Vertices', [], ...
'Faces', [], ...
'Color', [], ...
'VertConn', [], ...
'VertNormals', [], ...
'VertArea', [], ...
Expand Down
33 changes: 25 additions & 8 deletions toolbox/gui/figure_3d.m
Original file line number Diff line number Diff line change
Expand Up @@ -973,7 +973,7 @@ function FigureMouseWheelCallback(hFig, event, target)

%% ===== KEYBOARD CALLBACK =====
function FigureKeyPressedCallback(hFig, keyEvent)
global GlobalData TimeSliderMutex;
global GlobalData TimeSliderMutex Digitize;
% Prevent multiple executions
hAxes = findobj(hFig, '-depth', 1, 'Tag', 'Axes3D');
set([hFig hAxes], 'BusyAction', 'cancel');
Expand Down Expand Up @@ -1094,7 +1094,19 @@ function FigureKeyPressedCallback(hFig, keyEvent)
case 'a'
if ismember('control', keyEvent.Modifier)
ViewAxis(hFig);
end
end
% C : Collect point
case 'c'
% for 3DScanner
if gui_brainstorm('isTabVisible', 'Digitize') && strcmpi(Digitize.Type, '3DScanner')
% Get Digitize options
DigitizeOptions = bst_get('DigitizeOptions');
panel_fun = @panel_digitize;
if isfield(DigitizeOptions, 'Version') && strcmpi(DigitizeOptions.Version, '2024')
panel_fun = @panel_digitize_2024;
end
panel_fun('ManualCollect_Callback');
end
% CTRL+D : Dock figure
case 'd'
if ismember('control', keyEvent.Modifier)
Expand Down Expand Up @@ -2808,12 +2820,17 @@ function UpdateSurfaceColor(hFig, iTess)
SulciMap = zeros(TessInfo(iTess).nVertices, 1);
end
% Compute RGB values
FaceVertexCdata = BlendAnatomyData(SulciMap, ... % Anatomy: Sulci map
TessInfo(iTess).AnatomyColor([1,end], :), ... % Anatomy: color
DataSurf, ... % Data: values map
TessInfo(iTess).DataLimitValue, ... % Data: limit value
TessInfo(iTess).DataAlpha,... % Data: transparency
sColormap); % Colormap
if ~isempty(regexp(TessInfo(iTess).SurfaceFile, 'tess_textured', 'match'))
FaceVertexCdata = TessInfo(iTess).AnatomyColor(TessInfo(iTess).nVertices+1:end, :);
else
FaceVertexCdata = BlendAnatomyData(SulciMap, ... % Anatomy: Sulci map
TessInfo(iTess).AnatomyColor([1,end], :), ... % Anatomy: color
DataSurf, ... % Data: values map
TessInfo(iTess).DataLimitValue, ... % Data: limit value
TessInfo(iTess).DataAlpha,... % Data: transparency
sColormap); % Colormap
end

% Edge display : on/off
if ~TessInfo(iTess).SurfShowEdges
EdgeColor = 'none';
Expand Down
4 changes: 3 additions & 1 deletion toolbox/gui/gui_brainstorm.m
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,9 @@
jMenuFile.addSeparator();
end
% === DIGITIZE ===
gui_component('MenuItem', jMenuFile, [], 'Digitize', IconLoader.ICON_CHANNEL, [], @(h,ev)bst_call(@panel_digitize, 'Start'), fontSize);
jSubMenu = gui_component('Menu', jMenuFile, [], 'Digitize', IconLoader.ICON_CHANNEL,[],[], fontSize);
gui_component('MenuItem', jSubMenu, [], 'Digitizer', IconLoader.ICON_CHANNEL, [], @(h,ev)bst_call(@panel_digitize, 'Start'), fontSize);
gui_component('MenuItem', jSubMenu, [], '3D scanner', IconLoader.ICON_SNAPSHOT, [], @(h,ev)bst_call(@panel_digitize, 'Start', '3DScanner'), fontSize);
gui_component('MenuItem', jMenuFile, [], 'Batch MRI fiducials', IconLoader.ICON_LOBE, [], @(h,ev)bst_call(@bst_batch_fiducials), fontSize);
jMenuFile.addSeparator();
% === QUIT ===
Expand Down
Loading

0 comments on commit 6c646d2

Please sign in to comment.