From 6c646d2248c877a481dcf1e7308ff5287992a676 Mon Sep 17 00:00:00 2001 From: Chinmay Chinara Date: Wed, 6 Nov 2024 15:27:56 -0800 Subject: [PATCH] Automatic EEG localization and labelling using Revopoint 3D (#716) * 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 3066842789e93a8f5c33d793fe42d73312d9dd36 * '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 1b3e2c8d041e7e470024c7d245fada46956b98e2 * 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: 3d77e537 * Refactor to avoid unnecessary reads to db: `bst_get('Subject')` See 1c2c2b03 * Not apparent reason to have a different panel name See 8d6bc3bd * 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 53f43dde11cbd2ea28e7a2bb0477bdac0fa7e7d8. * Better handling of calls to `Start` function recommended here: https://github.com/brainstorm-tools/brainstorm3/pull/716#issuecomment-2347192014 * 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 --- .../eeg/Colin27/channel_ANT_Waveguard_65.mat | Bin 0 -> 5825 bytes .../channel_BrainProducts_ActiCap_68.mat | Bin 0 -> 4564 bytes .../channel_WearableSensing_DSI_24.mat | Bin 0 -> 2942 bytes .../eeg/ICBM152/channel_ANT_Waveguard_65.mat | Bin 0 -> 5817 bytes .../channel_BrainProducts_ActiCap_68.mat | Bin 0 -> 4643 bytes .../channel_WearableSensing_DSI_24.mat | Bin 0 -> 3146 bytes external/piotr_toolbox/LICENCE.txt | 26 + external/piotr_toolbox/tpsGetWarp.m | 77 ++ external/piotr_toolbox/tpsInterpolate.m | 52 + toolbox/anatomy/tess_deface.m | 62 + toolbox/anatomy/tess_downsize.m | 39 +- toolbox/core/bst_get.m | 7 +- toolbox/core/bst_memory.m | 1 + toolbox/db/db_template.m | 2 + toolbox/gui/figure_3d.m | 33 +- toolbox/gui/gui_brainstorm.m | 4 +- toolbox/gui/menu_default_eegcaps.m | 127 ++ toolbox/gui/panel_surface.m | 7 +- toolbox/gui/view_headpoints.m | 15 +- toolbox/gui/view_surface_matrix.m | 51 +- toolbox/io/import_surfaces.m | 9 +- toolbox/io/in_tess.m | 17 +- toolbox/io/in_tess_bst.m | 5 + toolbox/io/in_tess_wftobj.m | 200 ++++ toolbox/sensors/channel_detect_eegcap_auto.m | 241 ++++ toolbox/sensors/channel_fixunits.m | 71 +- toolbox/sensors/panel_digitize.m | 1055 +++++++++++++---- toolbox/sensors/panel_digitize_2024.m | 915 ++++++++++---- toolbox/sensors/private/bst_beep.wav | Bin 0 -> 17632 bytes toolbox/sensors/private/bst_beep_wav.mat | Bin 160194 -> 0 bytes toolbox/tree/tree_callbacks.m | 95 +- 31 files changed, 2534 insertions(+), 577 deletions(-) create mode 100644 defaults/eeg/Colin27/channel_ANT_Waveguard_65.mat create mode 100644 defaults/eeg/Colin27/channel_BrainProducts_ActiCap_68.mat create mode 100644 defaults/eeg/Colin27/channel_WearableSensing_DSI_24.mat create mode 100644 defaults/eeg/ICBM152/channel_ANT_Waveguard_65.mat create mode 100644 defaults/eeg/ICBM152/channel_BrainProducts_ActiCap_68.mat create mode 100644 defaults/eeg/ICBM152/channel_WearableSensing_DSI_24.mat create mode 100644 external/piotr_toolbox/LICENCE.txt create mode 100644 external/piotr_toolbox/tpsGetWarp.m create mode 100644 external/piotr_toolbox/tpsInterpolate.m create mode 100644 toolbox/anatomy/tess_deface.m create mode 100644 toolbox/gui/menu_default_eegcaps.m create mode 100644 toolbox/io/in_tess_wftobj.m create mode 100644 toolbox/sensors/channel_detect_eegcap_auto.m create mode 100644 toolbox/sensors/private/bst_beep.wav delete mode 100644 toolbox/sensors/private/bst_beep_wav.mat diff --git a/defaults/eeg/Colin27/channel_ANT_Waveguard_65.mat b/defaults/eeg/Colin27/channel_ANT_Waveguard_65.mat new file mode 100644 index 0000000000000000000000000000000000000000..3071f5b6bfef3089bf3d071049d22762586a61bc GIT binary patch literal 5825 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 fi&_)~a4q8lYh;>!w*CVFY>Hy&4VEumqK8Wi4 literal 0 HcmV?d00001 diff --git a/defaults/eeg/Colin27/channel_WearableSensing_DSI_24.mat b/defaults/eeg/Colin27/channel_WearableSensing_DSI_24.mat new file mode 100644 index 0000000000000000000000000000000000000000..5b54295d13aa31739c52d7f0c78a24d554d0b962 GIT binary patch literal 2942 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 T@3|F3)Fo0U3WWecCItR3c~WD+ literal 0 HcmV?d00001 diff --git a/defaults/eeg/ICBM152/channel_ANT_Waveguard_65.mat b/defaults/eeg/ICBM152/channel_ANT_Waveguard_65.mat new file mode 100644 index 0000000000000000000000000000000000000000..48603e510fdd7e17816eb2c676e1602dee890f8d GIT binary patch literal 5817 zcma)g2UJtbw|?kFny4r!5Q>0E2PslQ5d@UpRC@132uc7UbOeziUPS4LfYQ6vfPf&O zBQ;bhp@oE&2!Z6mz5nZ5Z~fN)tvBn;IcKeXzS( z10>Xg<6b76Ff+hehEn1j^yCl7?QamkL4capE_%;C?BYv>aQR#z(Cs^o{BYXx#OUzr z!eFMZ!K22;&w)1?SX|UXxspv7T#g0UTq!goAFw{qebo^ZDZM}1$qH%r0=$a9Ont6F zw^`q8hBo2zWkzRD#&HUkjf_ zt41`x4|@doexJA4nF)Z!am}1tV*baH;s3DY&m=rO{+$H3VSgl9UJli(P&IxCRqn}b zSIcPEjp&t={QRFQZ~9M^icjq1m;o!K8x7~9qiD|edi!0lVX7BgF|GPn4jvvleAGM36wgl)#Uf1} zn!2LuW|W&{RZWf;6l=B#3xn8!OsDxDMAU=IT2?~h_zQHvVvP;1 zpFv%OH&Yv{zG1X`%KrQZ3xU<}nhKCbYLrGYYRe)?yoFpl0c%&$(7<==!7s<7fM+YH zpD8I(14u$k+huL=8#mnX2r`{fRrDiLILkHsX2Bd9Y06NQAz-65xRO;jRI5P=QHx6iAUr7@mZ zG&=rpP~rbN3$Y=UG8`At`xO-5c`Bq-gMVMf^4zAm*r_q}!pt~F7^_D&3%f#}Z1$2y z1(*r3HcsWVD!V8tX~TH_QCnceR0D8gkBcX$jtE+M5UQnkt#mNJ@>pQ^eC0$#x zYklbtj-vOLz*kC5Cak-2!SyFsxnIAy7T@-8r_zlaJu!6W=j#WHB_)Rsc$@c9bY%mx z-soSldgI|-G;>A$Bn~(3qCwupxYY0FzPD82u+-pd?P_E@)UnYbtYXD{$#h=e=Esw2 z46mG^W<-oG@3cfEy$U2gs6R%XmVF91uB{)AieXqS{efzrOMP5fJSS^7djtps?aLG@ra#$o1(TL*6Bvv~h-pkgTZoDjtrsIEJ9l}MByARU zviJbM^Glsh7%qJN+*_nL&DLJ=xdf+$_KfM71MU7*1Tp$O)^RX$S@!trZRq=3b?j2o z``-7*26?t3W2XBZ)oYjUJ4fI5A)j*btNzaTS6+TeYq?nLZgWZwaX7% zdifhN+mf=JwWUl2ASab1T5*kOwtK*_)THzt5kYv)tqA3SSxZ-%DB3}zfRkbOc=g~- z=NKhVRnx4UKH&;2_gVSImU1g?_pdbx`b_}Qve^f##nj=iUcfUmRf2>jNf3>8%}0ob zjh#v+uc=k)o@SUjKx~kjHv6M7)CP75SJ$mgx4%W>!A^@<{OM zl@F_5_B#ZFamw7bD$rV)Lzn2u@bR!z(qto*P@VZo7jhuBY!q({k}M{hd3vEieT|pi z@XFzh31Lc1;drx1^k1sxtVz#k_H%PPHgdyBe+mIx(6AQ>1cyJ=r_qYzk4e^O%+tzltVC|8id+? zO#Q9nxkVmpMi-ybX4)_0=yDJTDtDj9{`JZ0zjA{NZe}CaI?J5+K1$Mv(R0JD^kw&k zTd(o)PWwteY^lzDqhrM%`T{bY>%|phWRX_&MUO%J0B$OCh|1-gRk@%{(ESm`D#=4#j`RNy~MZ`&w(jJubK=Q=tEk4V&GdJ zWge*~#z6y8Re(A9uwMor=$3v)JwfIghH-fMXPcLaXqIJLUTVT&?dm0epCZ|*So{<^ zpEuXDq?s!QAFWpMR2*7lFLTJ=V`Fsll#0@hcc_#@q%gMHFR@!&NE;ddVzP{U%yW`_ zZ({tVP?VY1>Xps;_io+3%1rwoh2M^Ts91htN&Bw+O2sxOim!Up%gq=>GuZuXwlvh7 z{1Wd%_QZ1)2ls>MpGi**kuh^w_6u*@w_5XxPJF=H1Jl=yA-M0D;Ftq3TO4Qwal!-p zymIe?Y{v?(gu)n=?7HX&=H_(nEP9P#4r}r!sRa zlM;_+Mg2y4 zq?}zi82~^cEuXQo%Kt~S*#0J3?0H^V#a{t$F&r0JS7-~4v<{OtxyM0YU@tdm0InAE zo9F31hVDGw|2hLFzdXC0)0+M+09mP_3LlH2y8CnB^Fi%?#Df8{zMA$`(Xc6TNx3Tp z;sw=!hlCZu$bnt)zJ~+1$uh=1 zfJ0^N$s!@;q8cPmJA;>-s=0|Y za?>RP$#qGtkDSGK6>vvELufv}mXdYudBvRFmlcOo4y5ny<2z#?(g@%RBy^{9zV%G5 zcP2l*Js@y;+WMm{!YB|SoHkCNuxe!Ue=#E~^$i(8Tq>Ib)z!ETP!o<{!tw&8GH~L- z&)2h(&{z?;*0wPpC-!0=mWjS-_4j%j+V%Lul6Rob(GblF8{ldvtrs!Ck4lLai z+d!O*Y&bZ1HzmEb?ltoayia^wKX9M(a*S(WgpKVn$0)qbltrqfw^iPmv@?KE_Ce1( zVKT9m@W>iZk~S?SnQYzmi5{_ESgPuKo4@`z-_ox#oRYyB>PeFr`^61pAmI3&H5a2I zJZ*d?AhTCb=cu8BNH{EME$rv}2wzqLoFa7eBft}xB{f5DDlMNVb%w%S9@_VP)Qr{A zf-Uc9*Nd=wE2XjO((pv z#B+aD3BdWprWl11I7UD%AkN3)#nO(Op*=^1bU2R;e1L9-u&C4yXBzs_!BYrRE({hr zF~8DK^2h~kks!n7KQZM156sG)$)d`Lbw2*i06xZ}YDD(dZVqHNz&zIlcW>>y+FrP` zHJ_0NeHs-dcS_i=;rb*pcp@D@swn4}sHJ;_N2Zrb?qhR9pcH2jMlW77K-qR|fED@5a zDniy(!hc|EYLHb$8bhx$OEFsFZf8pViqoF*GUz2qgkeFxM6JpDLs%E}s&{cb?U^mm zeEt?|s zmRtzxtM4H(j)rgOnRY_*fwvk7>$5R&`~_L*N1_kex`s*6BXCDqrM^D`Z&WgX51wAY zR^$gm3D_6e7lR-O;&RWjxwVpKElHyX9ZZm_XGUPP3>Jtn2L=lW*NR~lWQ#XxHNMke z$R^X1RQD85`r%dbw{L`e4sf#lf*C;*CW!>w!?*_!Qhod-QeXUJLIPGc9|rY&4_b#w zJ0e5%#_L90^7nCYSKD4W(ny~@`~3RS+PaHA9y{OA0G?wCN8`8@s1xoll-A=}TG+EL zH919{1^-+pVP!A?;e*H2!df=#R|~Q(py4kJ#oqMJ$*6xrM(r#OxV6E39Ov{2Io+t{ zh^t~Vn6&PCnY%9vq8g~?I)kIv>ci-ck?8CBZJ&L8%cgD1m+!pR352Ezt{;BNxOKXP z*w|t-|ALuvTexEO#mKn2L#0*2!0q&u@$B%R$vx$4wUX4g+jk+vL6AOI(gv#Fb=m{n zuYHGlX)$DO%!M^DD>Iy1&FPw}Uo;_V4Kpe7;pX$-xSf=X^DYO>n2IIcZqy*SHp!-5 zXtgJjkuV-DOIuXSG_*&lJUg4YvA~ttwpqF~H~s*9bVd%ih+hZicO#FohqJBu_**tW zHlHwul3VuMegsgN<$KLnXgtCoO=X(P?{CLPE?f@)Fu(EHAS#m(Uyaq7Qg!_htrU=CaZIA{S~ zB1VBgz>30kDG4mu^m(k9^+afLOUJ9YP+M38RH`uZhj_Ui+XdMj%ZEQAf4N_V(yur& z^CYbg&xw}5W1Ai__*(m~(UpwZaUM#6$pJS?yo8hM&k?UIvpA9r^4mY+F!4JX{$XIm0Riaib?4O$>E zVB2^2Lb=^xS~~EhrdWI`))tjXv9`T%bo+4iSn)b;=;70&-nTMJV{$=9oOZgTy=IIB zE?`o;1P@mV@Eerre2tUR`V}Fb2IgR2QU;(q=K5~k22+Axlwa2{G7H#1bw zod1YtXhh04y|(Z$r{>@8%#$alH*hJ__w^H#v0P!e+*FiL!u$T6-#P^FEO%Lx zVyOE8R>WYqQ?z~I?aorjo+-kivn&ITDBkmud0+RfO=xvIEmcUNpYs=%3t!C+nc` z*ie+i*!nG^dBq#A5F!y>&-D}8*;nH%Un-ofu0JbguxRY+G>uVJ2V*w|cKC9mi zGH^BbZNx>|>SjVwj~f&2B-}oq8YmD*y>IiDjM_g4y*|xm1s47`5le8e-EnI zGy*$m7AUElc_;X+v*|{F{ z+aUv_#N{4kZ;9vEX6$1*7cO=gEAXJ>mjJ??Ua{eJsRCtgd#qP^8=pHjpL|6dN zW;WHc-zQrN^Ch}tGRN{MM(U}QtbM=I>n`! z0rDVE`TJxvhp~H#QF$YL)XK8I9ipou^NL&}Lqa|ktTl2z>MrNyywAi3_xqLXj zLvChf^ZCLk+ozvHzRfj^CKoMuKwu5iLLct^o31qwWyR3&n!Ri;WvQ?0Y#^in=IV7G8wq>`j;WB5}P z&DgiGFIloLGnnli{ojAzdCz;k?|jcW&pE$ye!u&=?%(rV*L|Pc!qEDXp%Ga9j5655 z&{`4h>Eo^dw(@bl14jh_ECvh%Fl_EHI_$y+Kyvfk+s9MsX%z$v+~MDqP>ou%jG)74{%pwQ{E4{@h9l^zmt&torH75p$A%# znoQ2q>NPSr&UX)EBE?T3_*(G0H8rJoPw)u94bhU1uJOS4WQ9F|=cCL8&CL>Iqic9B(|d?2Gl;d5Lmu!b1PhJvtE0FqM9S+z?5CVukE?DN?E z;pzM@otB} zMC_yQ_pM6;c)|>P8YAAv@&1q$g++H?0>0!2-1p-a+XoNh-k04JQ+5+=9?T>9A8>&> z1J7V^Fy6GI-TVj3_sYgu#fU*A6^$b=30hDM4fXP9sh1xB9Vn&mN{WDP{oQXcBrQ?Z zu$yPHG;2Z3Ae&zv&Ln02C9T8iC>vXZ?q^ox`+G1zAwO$d1);C+nKVWB(N;y_Le$WK zo>s0FK?B`ZU!o6*Qnys;Vi)eMV1K*3(c{;&>fIu}TAj`x#wZR2eXa#pH-jLK!;ik0 z!b56~i1Jc)Ds=a9wJ3x$p%frB4Hv91f-93!yMgGNNCL?UVIV5iS%>@Ml%xLY+^E(@IxK+}^TXo4#DLn++ZLB^>}5C3;}s>0XaiX5KN zLh&T;lpdw8_Z)T0My__thQ=1BjoagNJ|YZ9!q*un5^wU3iWq&p687{yRlg^h!YdJauuRCb3GW#O<#3n8E1(Or&f0b>R>$r@%fYEjMAHj^}YmdN{3EgI7ZCe6&Z4> z*LA<>L5Y+Sgf`r?dIAOk?0+YYK4=derAeB^iTZ%!loMRU}c;dtSL^J>iMp z3E&f@Pje`M?7Vz_<$T5%j_xU*AZQSALlDU9rRfPDC)clpH4s!2-r4(^=(bMBNgMvIJn66O%k7CTMgJuT}JRj-v%Y^&f z-pee!2iwz(yw3VS4r7m^7WGfjbZ-PB`?nQ`%ax4_qgPv5Lhqmoa^hC_WMxnjJ*{mK zi+r-+#6yYtcQ6p?U6v^XoP7=eN%$+#n!+xP&QA3im3h}ebfUPu?K<^Xk+O^^+S@H3 zQDL1Ui_`avG9Qqt%NXU6 zlwp0fsba64Rze+Om9E(_T%F(aemJKGUH2@4(eW;RfW-8~zUGnNPjt8-=&`hGBuoJ6 z8P*H8V~uyu5;m4$T<2lzXvsas2H#*;c1&9Ft9^;{WXO!yUHhKK?m>%^bY!7FWomvY zA*nt3$Ju2k`l5O7=9CR7c?|jTAzBHPz8;l_q4LII=vt)|=fn|*xG->(y=x=x8YO?qkq#VoXC)aluel5v8qhhG;111#lgVEB~1AY=m z*CDKX#Y2JFjADw<2fHq$7gpK%UHBA6{O2)f+}E8FBIi6Kny!|mUF?=dOPa4dA~-O& zg7Y5eS41?RI=}4q0S3c`*&$)9E+*`p0)UN?4pGsa2p=FA4s_uI!y~^}8tp1abaJpn zZ$G1H-#&l^H*E#5Rr*xd`QaZijhp=yfrQ+DF#NqLhP=R$04zfbEo z3Gt=XH;=Gyciab<0BKABlYy73TmA|k@{9JKv=0U}(@Q;~lGCs#{^T95{o_CWxu6<1 zc>7a$&dsXXZY2j2NF@YI8>ThcH&hhtWrmTXZ2H)Sz70&JlK*z z$|1duXT$yB= z&_t`&9(oRk2ZdL!J{K5^VSM~nQ+n=fSQi>E=ixn|?)To3JesA5yLWpFlM8$u8&2(Q zT`<)zxmBOuiHcu{2|-6N49pEetD$C_t!MgVNVKVb#{6Zs!(MY|D$fCobbi-Vn8D$>pm^i7n-Z-EX9hj*Cnu|?Da zFv$CRouelsnxC}{&MhupejF|t^49^l$R&`RfH@#wc7*oqfJ6>iV&z_?x8>&2!8;JJ?F73Oa$5 zeeFvmfT@a8(SreXI3C;%VaQkP@ABb+nS1&$yYnrjcqDIZ*z2=D=EBsjP|EYdREz@i z&PFooYC5Q!uV`TpeY*0}>{e04s*0h$@Y}smKYaQVgfQEZ>xCYUDd4oJR9j#2%6j^) zyyjLVLohV6MkDz42CgB{-?Sy|1#zuBZmWuLuVgpo%Zr)JRC1xdG3ALEAZkKw3*l6<3Lmhd%}KTDR7@>DboY_bYxrpCX>%*Plk`)w0gKc$`{ocC)F03lx(QH$GhYHk30} z@kXz8AQ96|-V7z-%5BNI1PXTbZr$E?sbot1f~&_Xr(f$K!L)eE{IsjzrtrjO@|f;B z=a$JiCxx{&7s0Fu=+RZ<0K?jGYD#)tS82!;JjZH)*x z?Wi_)MM3y_`w68F+wH1KFB_k2gst7`FQauVQGM2oV8L`qh~h*r-C;83GWj^`;vJ|o zXPBL?$vVYkc`vbPdIEKGW4QKw-OTyMTR-Q1(S7DJ@K-T>Mh?*OwJ;r=s4f;AOsUw` z9e>AA+|X_;b;lJuZ(f>d^B$cmrbIqrd`fPm{XDPRuPZsRd!9iLETfg&-Q4uEZX&9O zl%)A698^y(KC+%%v=b{bF5@LWwHWkvnjTjjwA&jzX7MAmHB+R@G;Os;;~ClbJQZ?W zvg*`1$Fc9yIk(2o;a}~+9LO&mMLz4(Y!1mK?OwsukWbbqy0Om)vCjpFCAw%H4HGis zxrgmRr<$qHe^*G`g=q@jl`I~Ri!t3mOCe;9Z5$H|&1zd|QMUde>0Iae^cU_$vjzqR z+^x2 zFxbm2nV{_xd6qqKLi2&#h9NN{0SCy~rt`GeEq8m_@$O7hEd2|JsW}E?Io-m%MOh&B zbV-O{N}k)px)O78LEK_dU4KeqdJrP`r0Zc{u!UKw`*!{kL4D|x{JPa4Hh#W(E(EJ3 zJW~q%Qq$(+qlAgW#bEE(oysTGOj#}l0@Hi`lD}BO=dtpsalXC0-o0S?T;R7t3j8a^$G!O>=iLr!4=bvmM#j5*_D= z#<%hf+msa=Rh+|*OSgtDPg>~ZZ9+WHF2_#<=yEr&$xyE=6{t*?U&ucr+=QZVj#2Q0 zBiujggp8muG1L8eBjzc;s46e7dVdl#)B$m$l`|N$T_SBa88dw>!TpWFzG35-`|rZ= zXMX=@^p$`>*mO96U+hH(3tdzVmgYoxuC5%O;Cy}`oQ}ByzXz8bl+Xv0Une2||tge<3aa}4^oA_)ClC>&9(rXOuw#l=W+4{gu Q7N}XD)uaZ13V^i!1^dbPCjbBd literal 0 HcmV?d00001 diff --git a/defaults/eeg/ICBM152/channel_WearableSensing_DSI_24.mat b/defaults/eeg/ICBM152/channel_WearableSensing_DSI_24.mat new file mode 100644 index 0000000000000000000000000000000000000000..04240e1e3ffa5a40fb996499cfd4592e4c66a6a6 GIT binary patch literal 3146 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 literal 0 HcmV?d00001 diff --git a/external/piotr_toolbox/LICENCE.txt b/external/piotr_toolbox/LICENCE.txt new file mode 100644 index 000000000..4b692e467 --- /dev/null +++ b/external/piotr_toolbox/LICENCE.txt @@ -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. \ No newline at end of file diff --git a/external/piotr_toolbox/tpsGetWarp.m b/external/piotr_toolbox/tpsGetWarp.m new file mode 100644 index 000000000..a88563b82 --- /dev/null +++ b/external/piotr_toolbox/tpsGetWarp.m @@ -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; \ No newline at end of file diff --git a/external/piotr_toolbox/tpsInterpolate.m b/external/piotr_toolbox/tpsInterpolate.m new file mode 100644 index 000000000..2c75ab4fd --- /dev/null +++ b/external/piotr_toolbox/tpsInterpolate.m @@ -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); diff --git a/toolbox/anatomy/tess_deface.m b/toolbox/anatomy/tess_deface.m new file mode 100644 index 000000000..e16c0ce4c --- /dev/null +++ b/toolbox/anatomy/tess_deface.m @@ -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 \ No newline at end of file diff --git a/toolbox/anatomy/tess_downsize.m b/toolbox/anatomy/tess_downsize.m index 620e66562..629c8e8a6 100644 --- a/toolbox/anatomy/tess_downsize.m +++ b/toolbox/anatomy/tess_downsize.m @@ -84,21 +84,28 @@ % Ask for resampling method if isempty(Method) + % Downsize methods strings + methods_str = {['Matlab''s reducepatch:
' ... + '   | - Inhomogeneous mesh: large faces at the top of the gyri
' ... + '   | - Keeps the atlases and the subjects co-registration'], ... + ['Matlab''s reducepatch + subdivide large faces:
' ... + '   | - The large faces at the top of the gyri are subdivided in three
' ... + '   | - Deletes the atlases and the subjects co-registration'], ... + ['iso2mesh/CGAL library:
' ... + '   | - Homogeneous mesh: all the faces have similar sizes
' ... + '   | - Deletes the atlases and the subject co-registration
' ... + '   | - If the downsample looks dark, right-click > Swap faces']}; + % ['iso2mesh/CGAL + project on the original surface:
' ... + % '   | - Homogeneous mesh but possible topological problems
' ... + % '   | - Damages 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', [], ... - {['Matlab''s reducepatch:
' ... - '   | - Inhomogeneous mesh: large faces at the top of the gyri
' ... - '   | - Keeps the atlases and the subjects co-registration'], ... - ['Matlab''s reducepatch + subdivide large faces:
' ... - '   | - The large faces at the top of the gyri are subdivided in three
' ... - '   | - Deletes the atlases and the subjects co-registration'], ... - ['iso2mesh/CGAL library:
' ... - '   | - Homogeneous mesh: all the faces have similar sizes
' ... - '   | - Deletes the atlases and the subject co-registration
' ... - '   | - If the downsample looks dark, right-click > Swap faces']}, 1); -% ['iso2mesh/CGAL + project on the original surface:
' ... -% '   | - Homogeneous mesh but possible topological problems
' ... -% '   | - Damages 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 @@ -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); @@ -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); diff --git a/toolbox/core/bst_get.m b/toolbox/core/bst_get.m index 62bfe6b67..9e758c0f8 100644 --- a/toolbox/core/bst_get.m +++ b/toolbox/core/bst_get.m @@ -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); @@ -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'; ... diff --git a/toolbox/core/bst_memory.m b/toolbox/core/bst_memory.m index b4995b66a..e99fd8d8e 100644 --- a/toolbox/core/bst_memory.m +++ b/toolbox/core/bst_memory.m @@ -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); diff --git a/toolbox/db/db_template.m b/toolbox/db/db_template.m index 4625ac982..2166c7774 100644 --- a/toolbox/db/db_template.m +++ b/toolbox/db/db_template.m @@ -102,6 +102,7 @@ 'Comment', '', ... 'Vertices', [], ... 'Faces', [], ... + 'Color', [], ... 'VertConn', [], ... 'VertNormals', [], ... 'Curvature', [], ... @@ -673,6 +674,7 @@ 'Comment', '', ... 'Vertices', [], ... 'Faces', [], ... + 'Color', [], ... 'VertConn', [], ... 'VertNormals', [], ... 'VertArea', [], ... diff --git a/toolbox/gui/figure_3d.m b/toolbox/gui/figure_3d.m index a96a0d04b..72967a597 100644 --- a/toolbox/gui/figure_3d.m +++ b/toolbox/gui/figure_3d.m @@ -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'); @@ -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) @@ -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'; diff --git a/toolbox/gui/gui_brainstorm.m b/toolbox/gui/gui_brainstorm.m index 50ccf9364..244ae04a2 100644 --- a/toolbox/gui/gui_brainstorm.m +++ b/toolbox/gui/gui_brainstorm.m @@ -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 === diff --git a/toolbox/gui/menu_default_eegcaps.m b/toolbox/gui/menu_default_eegcaps.m new file mode 100644 index 000000000..28c8772e1 --- /dev/null +++ b/toolbox/gui/menu_default_eegcaps.m @@ -0,0 +1,127 @@ +function menu_default_eegcaps(jMenu, iAllStudies, isAddLoc) +% MENU_DEFAULT_EEGCAPS: Generate Brainstorm available EEG caps menu +% +% USAGE: menu_default_eegcaps(jMenu, iAllStudies, isAddLoc) +% +% PARAMETERS: +% - jMenu : The handle for the parent menu where this menu will be added +% - iAllStudies : All studies in the protocol +% - isAddLoc : if 1 (SEEG/ECOG) or 2 (EEG), call 'channel_add_loc' +% if 0 call 'db_set_channel' + +% @============================================================================= +% 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: Raymundo Cassani, 2024 +% Chinmay Chinara, 2024 + +import org.brainstorm.icon.*; + +%% ===== PARSE INPUTS ===== +if (nargin < 1) || isempty(jMenu) + bst_error('Incorrect usage, first parameter ''jMenu'' is required', 'Menu EEG caps', 0); + return +end +if (nargin < 2) || isempty(iAllStudies) + iAllStudies = []; +end +if (nargin < 3) || isempty(isAddLoc) + isAddLoc = []; +end + +% Get the digitize options +DigitizeOptions = bst_get('DigitizeOptions'); +% Get registered Brainstorm EEG defaults +bstDefaults = bst_get('EegDefaults'); +if ~isempty(bstDefaults) + % Add a directory per template block available + for iDir = 1:length(bstDefaults) + jMenuDir = gui_component('Menu', jMenu, [], bstDefaults(iDir).name, IconLoader.ICON_FOLDER_CLOSE, [], []); + isMni = strcmpi(bstDefaults(iDir).name, 'ICBM152'); + % Create subfolder for cap manufacturer + jMenuOther = gui_component('Menu', [], [], 'Generic', IconLoader.ICON_FOLDER_CLOSE, [], []); + jMenuAnt = gui_component('Menu', [], [], 'ANT', IconLoader.ICON_FOLDER_CLOSE, [], []); + jMenuBs = gui_component('Menu', [], [], 'BioSemi', IconLoader.ICON_FOLDER_CLOSE, [], []); + jMenuBp = gui_component('Menu', [], [], 'BrainProducts', IconLoader.ICON_FOLDER_CLOSE, [], []); + jMenuEgi = gui_component('Menu', [], [], 'EGI', IconLoader.ICON_FOLDER_CLOSE, [], []); + jMenuNs = gui_component('Menu', [], [], 'NeuroScan', IconLoader.ICON_FOLDER_CLOSE, [], []); + jMenuWs = gui_component('Menu', [], [], 'WearableSensing', IconLoader.ICON_FOLDER_CLOSE, [], []); + % Add an item per Template available + fList = bstDefaults(iDir).contents; + % Sort in natural order + [tmp,I] = sort_nat({fList.name}); + fList = fList(I); + % Create an entry for each default + for iFile = 1:length(fList) + % Define callback function + if isempty(isAddLoc) + panel_fun = @panel_digitize; + if isfield(DigitizeOptions, 'Version') && strcmpi(DigitizeOptions.Version, '2024') + panel_fun = @panel_digitize_2024; + end + fcnCallback = @(h,ev)panel_fun('AddMontage', fList(iFile).fullpath); + else + if isAddLoc + fcnCallback = @(h,ev)channel_add_loc(iAllStudies, fList(iFile).fullpath, 1, isMni); + else + fcnCallback = @(h,ev)db_set_channel(iAllStudies, fList(iFile).fullpath, 1, 0); + end + end + % Find corresponding submenu + if ~isempty(strfind(fList(iFile).name, 'ANT')) + jMenuType = jMenuAnt; + elseif ~isempty(strfind(fList(iFile).name, 'BioSemi')) + jMenuType = jMenuBs; + elseif ~isempty(strfind(fList(iFile).name, 'BrainProducts')) + jMenuType = jMenuBp; + elseif ~isempty(strfind(fList(iFile).name, 'GSN')) || ~isempty(strfind(fList(iFile).name, 'U562')) + jMenuType = jMenuEgi; + elseif ~isempty(strfind(fList(iFile).name, 'Neuroscan')) + jMenuType = jMenuNs; + elseif ~isempty(strfind(fList(iFile).name, 'WearableSensing')) + jMenuType = jMenuWs; + else + jMenuType = jMenuOther; + end + % Create item + gui_component('MenuItem', jMenuType, [], fList(iFile).name, IconLoader.ICON_CHANNEL, [], fcnCallback); + end + % Add if not empty + if (jMenuOther.getMenuComponentCount() > 0) + jMenuDir.add(jMenuOther); + end + if (jMenuAnt.getMenuComponentCount() > 0) + jMenuDir.add(jMenuAnt); + end + if (jMenuBs.getMenuComponentCount() > 0) + jMenuDir.add(jMenuBs); + end + if (jMenuBp.getMenuComponentCount() > 0) + jMenuDir.add(jMenuBp); + end + if (jMenuEgi.getMenuComponentCount() > 0) + jMenuDir.add(jMenuEgi); + end + if (jMenuNs.getMenuComponentCount() > 0) + jMenuDir.add(jMenuNs); + end + if (jMenuWs.getMenuComponentCount() > 0) + jMenuDir.add(jMenuWs); + end + end +end diff --git a/toolbox/gui/panel_surface.m b/toolbox/gui/panel_surface.m index 91f1540f3..e7b0bf2c8 100644 --- a/toolbox/gui/panel_surface.m +++ b/toolbox/gui/panel_surface.m @@ -1278,6 +1278,11 @@ function UpdateSurfaceProperties() TessInfo(iTess).Name = sSurface.Name; TessInfo(iTess).nVertices = size(sSurface.Vertices, 1); TessInfo(iTess).nFaces = size(sSurface.Faces, 1); + if isempty(sSurface.Color) + sSurface.Color = TessInfo(iTess).AnatomyColor(2,:); + else + TessInfo(iTess).AnatomyColor = [.75 .* sSurface.Color; sSurface.Color]; + end % === PLOT SURFACE === switch (FigureId.Type) @@ -1288,7 +1293,7 @@ function UpdateSurfaceProperties() [hFig, TessInfo(iTess).hPatch] = figure_3d('PlotSurface', hFig, ... sSurface.Faces, ... sSurface.Vertices, ... - TessInfo(iTess).AnatomyColor(2,:), ... + sSurface.Color, ... TessInfo(iTess).SurfAlpha); end % Update figure's surfaces list and current surface pointer diff --git a/toolbox/gui/view_headpoints.m b/toolbox/gui/view_headpoints.m index 1832242dd..d7dcd0603 100644 --- a/toolbox/gui/view_headpoints.m +++ b/toolbox/gui/view_headpoints.m @@ -30,7 +30,7 @@ % % Authors: Francois Tadel, 2010-2022 -global GlobalData; +global GlobalData Digitize % Default: no color for the distance between the scalp and the points if (nargin < 4) || isempty(isColorDist) @@ -68,10 +68,15 @@ % Load full channel file ChannelMat = in_bst_channel(ChannelFile); -% View scalp surface if available -[hFig, iFig, iDS] = bst_figures('GetFigureWithSurface', file_short(ScalpFile)); -if isempty(hFig) - [hFig, iDS, iFig] = view_surface(ScalpFile, .2); +% Head points for digitizer +if gui_brainstorm('isTabVisible', 'Digitize') && strcmpi(Digitize.Type, '3DScanner') + [hFig, iFig, iDS] = bst_figures('GetCurrentFigure', '3D'); +else + % View on figure with scalp surface if available + [hFig, iFig, iDS] = bst_figures('GetFigureWithSurface', file_short(ScalpFile)); + if isempty(hFig) + [hFig, iDS, iFig] = view_surface(ScalpFile, .2); + end end figure_3d('SetStandardView', hFig, 'front'); diff --git a/toolbox/gui/view_surface_matrix.m b/toolbox/gui/view_surface_matrix.m index 21dd1df23..54ad43fc1 100644 --- a/toolbox/gui/view_surface_matrix.m +++ b/toolbox/gui/view_surface_matrix.m @@ -40,9 +40,12 @@ % =============================================================================@ % % Authors: Francois Tadel, 2008-2019 +% Chinmay Chinara, 2024 %% ===== PARSE INPUTS ===== -global GlobalData; +global GlobalData + +iDS = []; % If full surface structure is passed if isstruct(Vertices) sSurf = Vertices; @@ -76,14 +79,44 @@ SurfaceFile = []; end +% ===== If surface file is defined ===== +if ~isempty(SurfaceFile) + % Get Subject that holds this surface + sSubject = bst_get('SurfaceFile', SurfaceFile); + % If this surface does not belong to any subject + if isempty(iDS) + if isempty(sSubject) + % Check that the SurfaceFile really exist as an absolute file path + if ~file_exist(SurfaceFile) + bst_error(['File not found : "', SurfaceFile, '"'], 'Display surface'); + return + end + % Create an empty DataSet + SubjectFile = ''; + iDS = bst_memory('GetDataSetEmpty'); + else + % Get GlobalData DataSet associated with subjectfile (create if does not exist) + SubjectFile = sSubject.FileName; + iDS = bst_memory('GetDataSetSubject', SubjectFile, 1); + end + iDS = iDS(1); + else + SubjectFile = sSubject.FileName; + end +end + + % ===== Create new 3DViz figure ===== isProgress = ~bst_progress('isVisible'); if isProgress bst_progress('start', 'View surface', 'Loading surface file...'); end + if isempty(hFig) - % Create a new empty DataSet - iDS = bst_memory('GetDataSetEmpty'); + if isempty(SurfaceFile) + % Create a new empty DataSet + iDS = bst_memory('GetDataSetEmpty'); + end % Prepare FigureId structure FigureId = db_template('FigureId'); FigureId.Type = '3DViz'; @@ -100,6 +133,12 @@ isNewFig = 0; end +if ~isempty(SurfaceFile) + % Set application data + setappdata(hFig, 'SubjectFile', SubjectFile); +end + + % ===== Create a pseudo-surface ===== % Surface type if isFem @@ -117,6 +156,9 @@ sLoadedSurf.Comment = 'User_surface'; sLoadedSurf.Vertices = Vertices; sLoadedSurf.Faces = Faces; +if ~isempty(SurfColor) + sLoadedSurf.Color = SurfColor; +end if ~isempty(sSurf) sLoadedSurf.VertConn = sSurf.VertConn; sLoadedSurf.VertNormals = sSurf.VertNormals; @@ -142,7 +184,7 @@ end % Register in the GUI GlobalData.Surface(end + 1) = sLoadedSurf; - + % ===== Add target surface ===== % Get figure appdata (surfaces configuration) TessInfo = getappdata(hFig, 'Surface'); @@ -209,5 +251,4 @@ gui_brainstorm('SetSelectedTab', 'Surface'); end - end diff --git a/toolbox/io/import_surfaces.m b/toolbox/io/import_surfaces.m index 428e0228d..f36e7b66c 100644 --- a/toolbox/io/import_surfaces.m +++ b/toolbox/io/import_surfaces.m @@ -172,6 +172,9 @@ if isfield(Tess, 'Faces') % Volume meshes do not have Faces field NewTess.Faces = Tess(1).Faces; end + if isfield(Tess, 'Color') % Not all meshes have color + NewTess.Color = Tess(1).Color; + end % Volume FEM mesh else NewTess = Tess; @@ -204,7 +207,11 @@ NewTess = bst_history('add', NewTess, 'import', ['Import from: ' TessFile]); % Produce a default surface filename (surface of volume mesh) if isfield(NewTess, 'Faces') - BstTessFile = bst_fullfile(ProtocolInfo.SUBJECTS, subjectSubDir, ['tess_' importedBaseName '.mat']); + BaseTessFile = ['tess_' importedBaseName '.mat']; + if ~isempty(NewTess.Color) + BaseTessFile = regexprep(BaseTessFile, '^tess_', 'tess_textured_'); + end + BstTessFile = bst_fullfile(ProtocolInfo.SUBJECTS, subjectSubDir, BaseTessFile); else BstTessFile = bst_fullfile(ProtocolInfo.SUBJECTS, subjectSubDir, ['tess_fem_' importedBaseName '.mat']); end diff --git a/toolbox/io/in_tess.m b/toolbox/io/in_tess.m index 3b69c61d7..7dc222b8b 100644 --- a/toolbox/io/in_tess.m +++ b/toolbox/io/in_tess.m @@ -13,7 +13,8 @@ % OUTPUT: % - TessMat: Brainstorm tesselation structure with fields: % |- Vertices : {[3 x nbVertices] double}, in millimeters -% |- Faces : {[nbFaces x 3] double} +% |- Faces : {[nbFaces x 3] double} (optional, volume meshes do not have 'Faces') +% |- Color : {[nColors x 3] double}, normalized between 0-1 (optional, not all surfaces have color info) % |- Comment : {information string} % @============================================================================= @@ -217,7 +218,11 @@ T = sMri.Header.info.mat(1:3,4)' - 1; TessMat.Vertices = bst_bsxfun(@minus, TessMat.Vertices, T / 1000); end - + + case 'WFTOBJ' + TessMat = in_tess_wftobj(TessFile); + isConvertScs = 0; + case 'MRI-MASK' [TessMat, Labels] = in_tess_mrimask(TessFile, 0, SelLabels); @@ -272,6 +277,14 @@ %% ===== COMMENT ===== % Add a comment field to the TessMat structure. + +if ~isempty(sMri) + % Get the current subject + sSubject = bst_get('MriFile', sMri.FileName); + % Unique comment + fileBase = file_unique(fileBase, {sSubject.Surface.Comment}); +end + % If various tesselations were loaded from one file if (length(TessMat) > 1) for iTess = 1:length(TessMat) diff --git a/toolbox/io/in_tess_bst.m b/toolbox/io/in_tess_bst.m index 3c8efe1fc..7188ee4eb 100644 --- a/toolbox/io/in_tess_bst.m +++ b/toolbox/io/in_tess_bst.m @@ -52,6 +52,7 @@ % - Remove cells: Old Brainstorm surface files contained more than one tesselation, now one tesselation = file % - Check matrix orientations % - Convert to double +% - Add Color field UpdateFile = 0; if isfield(TessMat, 'Faces') TessMat.Faces = double(TessMat.Faces); @@ -87,6 +88,10 @@ TessMat.Curvature = TessMat.Curvature{1}; UpdateFile = 1; end +if ~isfield(TessMat, 'Color') + TessMat.Color = []; + UpdateFile = 1; +end % ===== ATLASES ===== diff --git a/toolbox/io/in_tess_wftobj.m b/toolbox/io/in_tess_wftobj.m new file mode 100644 index 000000000..f4bdd82ab --- /dev/null +++ b/toolbox/io/in_tess_wftobj.m @@ -0,0 +1,200 @@ +function TessMat = in_tess_wftobj(TessFile) +% IN_TESS_WFTOBJ: Load a WAVEFRONT OBJ mesh file. +% +% USAGE: TessMat = in_tess_wftobj(TessFile, FileType); +% +% INPUT: +% - TessFile : full path to a tesselation file (*.obj) +% +% OUTPUT: +% - TessMat: 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 +% |- Comment : {information string} +% +% @============================================================================= +% 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 +% Raymundo Cassani, 2024 + +%% ===== PARSE INPUTS ===== +% Check inputs +if (nargin < 1) + bst_error('Invalid call. Please specify the mesh file to be loaded.', 'Importing tesselation', 1); +end + +%% ===== PARSE THE OBJ FILE: SET UP IMPORT OPTIONS AND IMPORT THE DATA ===== +if bst_get('MatlabVersion') < 901 + % MATLAB < R2016b + % Read entire .wobj file + fid = fopen(TessFile, 'r'); + txtStr = fread(fid, '*char')'; + fclose(fid); + % Keep relevant lines from file content + allData = regexp(txtStr, '(\w)+ ([^\n])*\n', 'tokens'); % (\w) ignores comments (#) + allData = cat(1,allData{:}); + % Read data for each element type + elementTags = {'v', 'vt', 'f'}; % Vertices, Texture, Faces + elementData = cell(1, length(elementTags)); + % Parse element data + for iElement = 1: length(elementTags) + iLines = strcmp(elementTags{iElement}, allData(:,1)); + elementTmp = regexp(allData(iLines, 2), '([e|\-|\.|\d])*', 'match')'; + elementTmp = cat(1, elementTmp{:}); + elementSize = size(elementTmp); + elementTmp = sscanf(sprintf(' %s', elementTmp{:}), '%f'); % Faster than str2double + elementData{iElement} = reshape(elementTmp, elementSize); + end + vertices = elementData{1, 1}(:, 1:3); % Use only the first 3 + texture = elementData{2}; + faces = elementData{1, 3}(:, [3,6,9]); + textureIdx = elementData{1, 3}(:, [2,5,8]); +else + % MATLAB R2016b to R2018a had 'DelimitedTextImportOptions' + if(bst_get('MatlabVersion') >= 901) && (bst_get('MatlabVersion') <= 904) + opts = matlab.io.text.DelimitedTextImportOptions(); + else + opts = delimitedTextImportOptions('NumVariables', 10); + end + + opts.Delimiter = {' ', '/'}; + opts.VariableNames = {'type', 'c1', 'c2', 'c3', 'c4', 'c5', 'c6', 'c7', 'c8', 'c9'}; + opts.VariableTypes = {'categorical', 'double', 'double', 'double', 'double', 'double', 'double', 'double', 'double', 'double'}; + + % Specify file level properties + opts.ExtraColumnsRule = 'ignore'; + opts.EmptyLineRule = 'read'; + + % Import the data + objtbl = readtable(TessFile, opts); + + obj = struct; + obj.Vertices = objtbl{objtbl.type=='v', 2:4}; + obj.VertexNormals = objtbl{objtbl.type=='vn', 2:4}; + obj.Faces = objtbl{objtbl.type=='f', [2,5,8]}; + obj.TextCoords = objtbl{objtbl.type=='vt', 2:3}; + obj.TextIndices = objtbl{objtbl.type=='f', [3,6,9]}; + % For some OBJ's exported from 3D softwares like Maya and Blender, when parsed using + % 'readtable', the vertex coordinates start from the 3rd column + if isnan(obj.Vertices(:,1)) + obj.Vertices = objtbl{objtbl.type=='v', 3:5}; + end + vertices = obj.Vertices; + faces = obj.Faces; + texture = obj.TextCoords; + textureIdx = obj.TextIndices; +end + +%% ===== REFINE FACES, MESH AND GENERATE COLOR MATRIX ===== +% Check if there exists a .jpg file of 'TessFile' +[pathstr, name] = fileparts(TessFile); +if exist(fullfile(pathstr, [name, '.jpg']), 'file') + image = fullfile(pathstr, [name, '.jpg']); + hasimage = true; +elseif exist(fullfile(pathstr,[name,'.png']), 'file') + image = fullfile(pathstr,[name,'.png']); + hasimage = true; +else + hasimage = false; +end + +% Check if the texture is defined per vertex, in which case the texture can be refined below +if size(texture, 1)==size(vertices, 1) + texture_per_vert = true; +else + texture_per_vert = false; +end + +% Remove the faces with 0's first +allzeros = sum(faces==0,2)==3; +faces(allzeros, :) = []; +textureIdx(allzeros, :) = []; + +% Check whether all vertices belong to a face. If not, prune the vertices and keep the faces consistent. +ufacesIdx = unique(faces(:)); +remove = setdiff((1:size(vertices, 1))', ufacesIdx); +if ~isempty(remove) + [vertices, faces] = tess_remove_vert(vertices, faces, remove); + if texture_per_vert + % Also remove the removed vertices from the texture + texture(remove, :) = []; + end +end + +color = []; +if hasimage + % If true then there is an image/texture with color information + if texture_per_vert + picture = imread(image); + color = zeros(size(vertices, 1), 3); + for i = 1:size(vertices, 1) + color(i,1:3) = picture(floor((1-texture(i,2))*length(picture)),1+floor(texture(i,1)*length(picture)),1:3); + end + else + % Do the texture to color mapping in a different way, without additional refinement + picture = flip(imread(image),1); + [sy, sx, sz] = size(picture); + picture = reshape(picture, sy*sx, sz); + + % Make image 3D if grayscale + if sz == 1 + picture = repmat(picture, 1, 3); + end + [~, ix] = unique(faces); + textureIdx = textureIdx(ix); + + % Get the indices into the image + x = abs(round(texture(:,1)*(sx-1)))+1; + y = abs(round(texture(:,2)*(sy-1)))+1; + + % Eliminates points out of bounds + if any(x > sx) + texture(x > sx,:) = 1; + x(x > sx) = sx; + end + + if any(find(y > sy)) + texture(y > sy,:) = 1; + y(y > sy) = sy; + end + + xy = sub2ind([sy sx], y, x); + sel = xy(textureIdx); + color = double(picture(sel,:))/255; + end + + % If color is specified as 0-255 rather than 0-1 correct by dividing by 255 + if range(color(:)) > 1 + color = color./255; + end +end + +% Centering vertices +vertices = vertices - repmat(mean(vertices,1), [size(vertices, 1),1]); + +% Convert vertices' unit as locations in Brainstorm are saved in 'meters' +vertices = channel_fixunits(vertices, 'mm', 1, 1); + +%% ===== BRAINSTORM SURFACE STRUCTURE ===== +TessMat = struct('Faces', faces, ... + 'Vertices', vertices, ... + 'Color', color, ... + 'Comment', ''); diff --git a/toolbox/sensors/channel_detect_eegcap_auto.m b/toolbox/sensors/channel_detect_eegcap_auto.m new file mode 100644 index 000000000..9f15d99f0 --- /dev/null +++ b/toolbox/sensors/channel_detect_eegcap_auto.m @@ -0,0 +1,241 @@ +function varargout = channel_detect_eegcap_auto(varargin) +% CHANNEL_DETECT_EEGCAP_AUTO: Automatic electrode detection and labelling of 3D Scanner acquired mesh +% +% USAGE: [capCenters2d, capImg2d, surface3dscannerUv] = channel_detect_eegcap_auto('FindElectrodesEegCap', surface3dscanner, isWhiteCap) +% channel_detect_eegcap_auto('WarpLayout2Mesh', capCenters2d, capImg2d, surface3dscannerUv, channelRef, eegPoints) +% eegCapLandmarkLabels = channel_detect_eegcap_auto('GetEegCapLandmarkLabels', eegCapName) +% +% PARAMETERS: +% - surface3dscanner : The 3D mesh surface obtained from the 3d Scanner loaded into brainstorm +% - isWhiteCap : Set if the 3D mesh surface correspongs to a white EEG cap +% - surface3dscannerUv : 'surface3dscanner' above along with the UV texture information of the surface +% - capImg2d : Flattend 2D grayscale image of the mesh +% - capCenters2d : The ceters of the various electrodes detected in the flattened 2D image of the mesh +% - channelRef : The channel file containing all the layout information of the cap +% - eegCapName : Name of the EEG cap +% - eegCapLandmarkLabels : The manually chosen list of labels of the electrodes to be used as initilization for automation +% - nLandmarkLabels : The count for the number of chosen electrode labels above +% +% @============================================================================= +% 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: Anand A. Joshi, 2024 +% Chinmay Chinara, 2024 +% Raymundo Cassani, 2024 + +eval(macro_method); +end + +%% ===== FIND ELECTRODES ON THE EEG CAP ===== +function [capCenters2d, capImg2d, surface3dscannerUv] = FindElectrodesEegCap(surface3dscanner, isWhiteCap) + % Hyperparameters for circle detection + % NOTE: these values can vary for new caps + minRadius = 1; + maxRadius = 25; + + % create a copy of the input mesh to add UV texture information to it as well + surface3dscannerUv = surface3dscanner; + + % Flatten the 3D mesh to 2D space + [surface3dscannerUv.u, surface3dscannerUv.v] = bst_project_2d(surface3dscanner.Vertices(:,1), surface3dscanner.Vertices(:,2), surface3dscanner.Vertices(:,3), '2dcap'); + + % Perform image processing to detect the electrode locations + % Convert to grayscale + grayness = surface3dscanner.Color*[1;1;1]/sqrt(3); + + % Interpolate and fit flattended mesh image to a 512x512 grid + % NOTE: Should work with any flattened cap mesh but needs more testing + ll=linspace(-1,1,512); + [X,Y]=meshgrid(ll,ll); + capImg2d = 0*X; + warning('off','MATLAB:scatteredInterpolant:DupPtsAvValuesWarnId'); + capImg2d(:) = griddata(surface3dscannerUv.u(1:end),surface3dscannerUv.v(1:end),grayness,X(:),Y(:),'linear'); + warning('on','MATLAB:scatteredInterpolant:DupPtsAvValuesWarnId'); + + % For white caps + if isWhiteCap + capImg2d = imcomplement(capImg2d); + end + + % Detect the centers of the electrodes which appear as circles in the flattened image whose radii are in the range below + warning('off','images:imfindcircles:warnForSmallRadius'); + warning('off','images:imfindcircles:warnForLargeRadiusRange'); + capCenters2d = imfindcircles(capImg2d, [minRadius maxRadius]); + warning('on','images:imfindcircles:warnForSmallRadius'); + warning('on','images:imfindcircles:warnForLargeRadiusRange'); + +end + +%% ===== WARP ELECTRODE LOCATIONS FROM EEG CAP MANUFACTURER LAYOUT AVAILABLE IN BRAINSTORM TO THE MESH ===== +function capPoints = WarpLayout2Mesh(capCenters2d, capImg2d, surface3dscannerUv, channelRef, eegPoints) + capPoints = struct(); + % Hyperparameters for warping and interpolation + % NOTE: these values can vary for new caps + % Number of iterations to run warp-interpolation on + numIters = 1000; + % Defines the rigidity of the warping (check the 'tpsGetWarp' function for more details) + lambda = 100000; + % Dimension of the flattened cap from mesh + capImgDim = length(capImg2d); + % Threshold for ignoring some border pixels that might be bad detections + ignorePix = 15; + + % Get current montage + DigitizeOptions = bst_get('DigitizeOptions'); + panel_fun = @panel_digitize; + eegPointsLabel = eegPoints.Label; + if isfield(DigitizeOptions, 'Version') && strcmpi(DigitizeOptions.Version, '2024') + panel_fun = @panel_digitize_2024; + eegPointsLabel = {eegPoints.Label}; + end + curMontage = panel_fun('GetCurrentMontage'); + % Get EEG cap landmark labels used for initialization + capLandmarkLabels = GetEegCapLandmarkLabels(curMontage.Name); + + % Check that all landmarks are acquired + if ~all(ismember([capLandmarkLabels], eegPointsLabel)) + bst_error('Not all EEG landmarks are provided', 'Auto electrode location', 1); + return + end + + % Convert EEG cap manufacturer layout from 3D to 2D + capLayoutPts3d = [channelRef.Loc]'; + [X1, Y1] = bst_project_2d(capLayoutPts3d(:,1), capLayoutPts3d(:,2), capLayoutPts3d(:,3), '2dcap'); + capLayoutPts2d = [X1 Y1]; + capLayoutNames = {channelRef.Name}; + + % Indices for capLayoutPts2dSorted for points to compute warp + [~, iwarp] = ismember(eegPointsLabel, capLayoutNames); + + % Warping EEG cap layout electrodes to mesh + % Get 2D projected landmark points to be used for initialization + capLayoutPts2dInit = capLayoutPts2d(iwarp, :); + % Get 2D projected points of the 3D points selected by the user on the mesh + % Check if using new version + if isfield(DigitizeOptions, 'Version') && strcmpi(DigitizeOptions.Version, '2024') + eegPointsLoc = cat(1, eegPoints.Loc); + else + eegPointsLoc = cat(1, eegPoints.EEG); + end + [x2, y2] = bst_project_2d(eegPointsLoc(:,1), eegPointsLoc(:,2), eegPointsLoc(:,3), '2dcap'); + % Reprojection into the space of the flattened mesh dimensions + capUserSelectPts2d = ([x2 y2]+1) * capImgDim/2; + + % Delete the manual electrodes selected in figure to update it with the automatic detected ones + for i=1 : length(eegPoints) + panel_fun('DeletePoint_Callback'); + end + + % Do the warping and interpolation + warp = tpsGetWarp(10, capLayoutPts2dInit(:,1)', capLayoutPts2dInit(:,2)', capUserSelectPts2d(:,1)', capUserSelectPts2d(:,2)' ); + [xsR,ysR] = tpsInterpolate(warp, capLayoutPts2d(:,1)', capLayoutPts2d(:,2)', 0); + capLayoutPts2d(:,1) = xsR; + capLayoutPts2d(:,2) = ysR; + % 'ignorePix' is just a hyperparameter. It is because if some point is detected near the border then it is + % too close to the border; it moves it inside. It leaves a margin of 'ignorePix' pixels around the border + capLayoutPts2d = max(min(capLayoutPts2d,capImgDim-ignorePix),ignorePix); + + bst_progress('start', '3Dscanner', 'Automatic labelling of EEG sensors...', 0, 100); + % Warp and interpolate to get the best point fitting + for numIter=1:numIters + % Show progress + progressPrc = round(100 .* numIter ./ numIters); + if progressPrc > 0 && ~mod(progressPrc, 5) + bst_progress('set', progressPrc); + end + % Nearest point search between the layout and detected circle centers from the 2D flattened mesh + % 'k' is an index into points from the available layout + k = dsearchn(capLayoutPts2d, capCenters2d); + [vecLayoutPts,ind] = unique(k); + + % distance between the layout and detected circle centers from the 2D flattened mesh + vecLayout2Mesh = capCenters2d(ind,:)-capLayoutPts2d(vecLayoutPts,:); + dist = sqrt(vecLayout2Mesh(:,1).^2+vecLayout2Mesh(:,2).^2); + + % Identify outliers with 3*scaled_MAD from median and remove them + % Use 'rmoutliers' for Matlab >= R2018b + if bst_get('MatlabVersion') >= 905 + [~, isoutlier] = rmoutliers(dist); + % Implementation + else + mad = median(abs(dist-median(dist))); + c = -1/(sqrt(2) * erfcinv(3/2)) * 2; + scaled_mad = c * mad; + isoutlier = find(abs(dist-median(dist)) > 3*scaled_mad); + end + ind(isoutlier) = []; + vecLayoutPts(isoutlier) = []; + + % Perform warping and interpolation to fit the points + warp = tpsGetWarp(lambda, capLayoutPts2d(vecLayoutPts,1)', capLayoutPts2d(vecLayoutPts,2)', capCenters2d(ind,1)', capCenters2d(ind,2)' ); + [xsR,ysR] = tpsInterpolate(warp, capLayoutPts2d(:,1)', capLayoutPts2d(:,2)', 0); + + % Perform gradual warping for half the iterations and fast warping for the rest of the iterations + if numIter 'vertices', 'channel' -> 'surface' +% +% OUTPUT: +% - ChannelMat : Same type as ChannelMat input % @============================================================================= % This function is part of the Brainstorm software: @@ -28,25 +33,32 @@ % =============================================================================@ % % Authors: Francois Tadel, 2017 +% Raymundo Cassani, 2024 % Parse inputs +if (nargin < 4) || isempty(isVertices) + isVertices = 0; +end if (nargin < 3) || isempty(isConfirmFix) isConfirmFix = 1; end -% Get EEG channels -iEEG = good_channel(ChannelMat.Channel, [], {'EEG','SEEG','ECOG','Fiducial'}); -% If not enough channels: nothing to do -if (length(iEEG) <= 8) && (length(iEEG) ~= length(ChannelMat.Channel)) - return; -end - -% Get all EEG locations -eegLoc = []; -for k = 1:length(iEEG) - if ~isempty(ChannelMat.Channel(iEEG(k)).Loc) && ~isequal(ChannelMat.Channel(iEEG(k)).Loc, [0;0;0]) - eegLoc = [eegLoc, ChannelMat.Channel(iEEG(k)).Loc(:,1)]; +if isstruct(ChannelMat) + % Get EEG channels + iEEG = good_channel(ChannelMat.Channel, [], {'EEG','SEEG','ECOG','Fiducial'}); + % If not enough channels: nothing to do + if (length(iEEG) <= 8) && (length(iEEG) ~= length(ChannelMat.Channel)) + return; end + % Get all EEG locations + eegLoc = []; + for k = 1:length(iEEG) + if ~isempty(ChannelMat.Channel(iEEG(k)).Loc) && ~isequal(ChannelMat.Channel(iEEG(k)).Loc, [0;0;0]) + eegLoc = [eegLoc, ChannelMat.Channel(iEEG(k)).Loc(:,1)]; + end + end +else + eegLoc = ChannelMat'; end if isempty(eegLoc) return; @@ -64,22 +76,33 @@ strFactor = num2str(FactorTest(iFactor)); % Ask user if we should scale the distances if isConfirmFix + % Strings for GUI message + locType = 'EEG electrodes'; + fileType = 'channel'; + if isVertices + locType = 'vertices'; + fileType = 'surface'; + end strFactor = java_dialog('question', ... - ['Warning: The EEG electrodes locations might not be in the expected units (' FileUnits ').' 10 ... - 'Please select a scaling factor for the units (suggested: ' strFactor '):' 10 10], 'Import channel file', ... + ['Warning: The ' locType ' locations might not be in the expected units (' FileUnits ').' 10 ... + 'Please select a scaling factor for the units (suggested: ' strFactor '):' 10 10], ['Import ' fileType ' file'], ... [], {'0.001', '0.01', '0.1', '1', '10', '100' '1000'}, strFactor); end % If user accepted to scale if ~isempty(strFactor) && ~isequal(strFactor, '1') Factor = str2num(strFactor); % Apply correction to location values - for k = 1:length(iEEG) - ChannelMat.Channel(iEEG(k)).Loc = ChannelMat.Channel(iEEG(k)).Loc .* Factor; - end - % Apply correction to head points - isHeadPoints = isfield(ChannelMat, 'HeadPoints') && ~isempty(ChannelMat.HeadPoints.Loc); - if isHeadPoints - ChannelMat.HeadPoints.Loc = ChannelMat.HeadPoints.Loc .* Factor; + if isstruct(ChannelMat) + for k = 1:length(iEEG) + ChannelMat.Channel(iEEG(k)).Loc = ChannelMat.Channel(iEEG(k)).Loc .* Factor; + end + % Apply correction to head points + isHeadPoints = isfield(ChannelMat, 'HeadPoints') && ~isempty(ChannelMat.HeadPoints.Loc); + if isHeadPoints + ChannelMat.HeadPoints.Loc = ChannelMat.HeadPoints.Loc .* Factor; + end + else + ChannelMat = eegLoc' .* Factor; end end end diff --git a/toolbox/sensors/panel_digitize.m b/toolbox/sensors/panel_digitize.m index e142eeab9..b8e2cec45 100644 --- a/toolbox/sensors/panel_digitize.m +++ b/toolbox/sensors/panel_digitize.m @@ -26,6 +26,7 @@ % =============================================================================@ % % Authors: Elizabeth Bock & Francois Tadel, 2012-2017 +% Chinmay Chinara, 2024 eval(macro_method); end @@ -39,57 +40,22 @@ %#function icinterface %% ===== START ===== -function Start() %#ok - % Get Digitize options - DigitizeOptions = bst_get('DigitizeOptions'); - % Check if using new version - if isfield(DigitizeOptions, 'Version') && strcmpi(DigitizeOptions.Version, '2024') - bst_call(@panel_digitize_2024, 'Start'); - return; - end - - global Digitize; - % ===== PREPARE DATABASE ===== - % If no protocol: exit - if (bst_get('iProtocol') <= 0) - bst_error('Please create a protocol first.', 'Digitize', 0); - return; - end - % Get subject - SubjectName = 'Digitize'; - [sSubject, iSubject] = bst_get('Subject', SubjectName); - % Create if subject doesnt exist - if isempty(iSubject) - % Default anat / one channel file per subject - UseDefaultAnat = 1; - UseDefaultChannel = 0; - [sSubject, iSubject] = db_add_subject(SubjectName, iSubject, UseDefaultAnat, UseDefaultChannel); - % Update tree - panel_protocols('UpdateTree'); - end - - % ===== PATIENT ID ===== - % Ask for subject id - PatientId = java_dialog('input', 'Please, enter subject ID:', 'Digitize', [], DigitizeOptions.PatientId); - if isempty(PatientId) - return; - end - % Save the new default patient id - DigitizeOptions.PatientId = PatientId; - bst_set('DigitizeOptions', DigitizeOptions); - +function Start(varargin) %#ok + global Digitize % ===== INITIALIZE CONNECTION ===== % Intialize global variable Digitize = struct(... + 'Type' , [], ... 'SerialConnection', [], ... 'Mode', 0, ... 'hFig', [], ... 'iDS', [], ... 'FidSets', 2, ... 'EEGlabels', [], ... - 'SubjectName', SubjectName, ... + 'SubjectName', [], ... 'ConditionName', [], ... 'BeepWav', [], ... + 'isEditPts', 0, ... 'Points', struct(... 'nasion', [], ... 'LPA', [], ... @@ -98,10 +64,104 @@ function Start() %#ok 'hpiL', [], ... 'hpiR', [], ... 'EEG', [], ... + 'Label', [], ... 'headshape',[], ... 'trans', [])); + + % ===== PARSE INPUT ===== + DigitizerType = 'Digitize'; + sSubject = []; + iSubject = []; + surfaceFile = []; + if nargin > 0 && ~isempty(varargin{1}) + DigitizerType = varargin{1}; + end + if nargin > 1 && ~isempty(varargin{2}) + sSubject = varargin{2}; + end + if nargin > 2 && ~isempty(varargin{3}) + iSubject = varargin{3}; + end + if nargin > 3 && ~isempty(varargin{4}) + surfaceFile = varargin{4}; + end + Digitize.Type = DigitizerType; + switch DigitizerType + case 'Digitize' + % Do nothing + case '3DScanner' + % Simulate + SetSimulate(1); + otherwise + bst_error(sprintf('DigitizerType : "%s" is not supported', DigitizerType)); + return + end + + % Get Digitize options + DigitizeOptions = bst_get('DigitizeOptions'); + % Check if using new version + if isfield(DigitizeOptions, 'Version') && strcmpi(DigitizeOptions.Version, '2024') + if strcmpi(Digitize.Type, '3DScanner') + bst_call(@panel_digitize_2024, 'Start', varargin{:}); + else + bst_call(@panel_digitize_2024, 'Start'); + end + return; + end + + % ===== PREPARE DATABASE ===== + % If no protocol: exit + if (bst_get('iProtocol') <= 0) + bst_error('Please create a protocol first.', Digitize.Type, 0); + return; + end + + % ===== PATIENT ID ===== + if isempty(sSubject) + % Get Digitize options + DigitizeOptions = bst_get('DigitizeOptions'); + % Ask for subject id + PatientId = java_dialog('input', 'Please, enter subject ID:', Digitize.Type, [], DigitizeOptions.PatientId); + if isempty(PatientId) + return; + end + % Save the new default patient id + DigitizeOptions.PatientId = PatientId; + bst_set('DigitizeOptions', DigitizeOptions); + + % ===== GET SUBJECT ===== + if strcmpi(Digitize.Type, '3DScanner') + SubjectName = [Digitize.Type, '_', PatientId]; + else + SubjectName = Digitize.Type; + end + + [sSubject, iSubject] = bst_get('Subject', SubjectName); + else + SubjectName = sSubject.Name; + end + + % Save the new SubjectName + Digitize.SubjectName = SubjectName; + + % Create if subject doesnt exist + if isempty(iSubject) + % Default anat / one channel file per subject + if strcmpi(Digitize.Type, '3DScanner') + [sSubject, iSubject] = db_add_subject(SubjectName, iSubject); + sTemplates = bst_get('AnatomyDefaults'); + db_set_template(iSubject, sTemplates(1), 1); + else + UseDefaultAnat = 1; + UseDefaultChannel = 0; + [sSubject, iSubject] = db_add_subject(SubjectName, iSubject, UseDefaultAnat, UseDefaultChannel); + end + % Update tree + panel_protocols('UpdateTree'); + end + % Start Serial Connection - if ~CreateSerialConnection(); + if ~CreateSerialConnection() return; end @@ -113,7 +173,7 @@ function Start() %#ok % Generate new condition name ConditionName = sprintf('%s_%02d%02d%02d_%02d', DigitizeOptions.PatientId, c(1), c(2), c(3), i); % Get condition - [sStudy, iStudy] = bst_get('StudyWithCondition', [SubjectName '/' ConditionName]); + sStudy = bst_get('StudyWithCondition', [SubjectName '/' ConditionName]); % If condition doesn't exist: ok, keep this one if isempty(sStudy) break; @@ -132,10 +192,58 @@ function Start() %#ok db_reload_studies(iStudy); % Save condition name Digitize.ConditionName = ConditionName; + + if strcmpi(Digitize.Type, '3DScanner') + if isempty(surfaceFile) + % Import surface + iSurface = find(cellfun(@(x)~isempty(regexp(x, 'tess_textured', 'match')), {sSubject.Surface.FileName})); + if isempty(iSurface) + [~, surfaceFiles] = import_surfaces(iSubject); + if isempty(surfaceFiles) + return + end + surfaceFile = file_short(surfaceFiles{end}); + else + [res, isCancel] = java_dialog('question', ['There is already scanned mesh available for this subject.' 10 10 ... + 'What do you want to do?'], ... + 'Import surface', [], {'Use existing', 'Add new', 'Cancel'}, 'Use existing'); + if strcmpi(res, 'cancel') || isCancel + return + elseif strcmpi(res, 'use existing') + % If more than one surface present, user can choose + if length(iSurface) > 1 + texSurfComment = java_dialog('combo', 'Select the textured surface:

', 'Choose textured surface', [], {sSubject.Surface(iSurface).Comment}); + texSurfComment = strrep(texSurfComment, '_defaced', ''); + if isempty(texSurfComment) + return + end + iSurfFile = find(cellfun(@(x)~isempty(regexp(x, [texSurfComment '.mat'], 'match')), {sSubject.Surface.FileName})); + surfaceFile = sSubject.Surface(iSurfFile).FileName; + % If only one surface is present, then load it directly + else + surfaceFile = sSubject.Surface(iSurface(end)).FileName; + end + elseif strcmpi(res, 'add new') + % Import a new textured mesh and append it to the list + [~, surfaceFiles] = import_surfaces(iSubject); + if isempty(surfaceFiles) + return + end + surfaceFile = file_short(surfaceFiles{end}); + end + end + end + bst_progress('start', Digitize.Type, 'Loading surface file...'); + Digitize.surfaceFile = surfaceFile; + sSurf = bst_memory('LoadSurface', Digitize.surfaceFile); + % Display surface + view_surface_matrix(sSurf.Vertices, sSurf.Faces, [], sSurf.Color, [], [], Digitize.surfaceFile); + end % ===== DISPLAY DIGITIZE WINDOW ===== % Display panel - panelContainer = gui_show('panel_digitize', 'JavaWindow', 'Digitize', [], [], [], []); + % Set window title to Digitize.Type + panelContainer = gui_show('panel_digitize', 'JavaWindow', Digitize.Type, [], [], [], []); % Hide Brainstorm window jBstFrame = bst_get('BstFrame'); jBstFrame.setVisible(0); @@ -148,11 +256,8 @@ function Start() %#ok ResetDataCollection(); % Load beep sound - if bst_iscompiled() - wavfile = bst_fullfile(bst_get('BrainstormHomeDir'), 'toolbox', 'sensors', 'private', 'bst_beep_wav.mat'); - filemat = load(wavfile, 'wav'); - Digitize.BeepWav = filemat.wav; - end + wavfile = bst_fullfile(bst_get('BrainstormHomeDir'), 'toolbox', 'sensors', 'private', 'bst_beep.wav'); + [Digitize.BeepWav.data, Digitize.BeepWav.fs] = audioread(wavfile); end @@ -162,6 +267,7 @@ function Start() %#ok %% ===== CREATE PANEL ===== function [bstPanelNew, panelName] = CreatePanel() %#ok + global Digitize % Constants panelName = 'Digitize'; % Java initializations @@ -188,36 +294,38 @@ function Start() %#ok jLabelNews.setOpaque(true); jLabelNews.setBackground(java.awt.Color.yellow); - % File menu + % ===== FILE MENU ===== jMenu = gui_component('Menu', jMenuBar, [], 'File', [], [], [], []); gui_component('MenuItem', jMenu, [], 'Start over', IconLoader.ICON_RELOAD, [], @(h,ev)bst_call(@ResetDataCollection, 1), []); gui_component('MenuItem', jMenu, [], 'Save as...', IconLoader.ICON_SAVE, [], @(h,ev)bst_call(@Save_Callback), []); jMenu.addSeparator(); gui_component('MenuItem', jMenu, [], 'Edit settings...', IconLoader.ICON_EDIT, [], @(h,ev)bst_call(@EditSettings), []); gui_component('MenuItem', jMenu, [], 'Switch to Digitize "2024"', [], [], @(h,ev)bst_call(@SwitchVersion), []); - gui_component('MenuItem', jMenu, [], 'Reset serial connection', IconLoader.ICON_FLIP, [], @(h,ev)bst_call(@CreateSerialConnection), []); + if ~strcmpi(Digitize.Type, '3DScanner') + gui_component('MenuItem', jMenu, [], 'Reset serial connection', IconLoader.ICON_FLIP, [], @(h,ev)bst_call(@CreateSerialConnection), []); + end jMenu.addSeparator(); - if exist('bst_headtracking', 'file') - gui_component('MenuItem', jMenu, [], 'Start head tracking', IconLoader.ICON_ALIGN_CHANNELS, [], @(h,ev)bst_call(@(h,ev)bst_headtracking([],1,1)), []); + if exist('bst_headtracking', 'file') && ~strcmpi(Digitize.Type, '3DScanner') + gui_component('MenuItem', jMenu, [], 'Start head tracking', IconLoader.ICON_ALIGN_CHANNELS, [], @(h,ev)bst_call(@(h,ev)bst_headtracking([],1,1)), []); jMenu.addSeparator(); end gui_component('MenuItem', jMenu, [], 'Save and exit', IconLoader.ICON_RESET, [], @(h,ev)bst_call(@Close_Callback), []); - % EEG Montage menu + % ===== EEG MONTAGE MENU ===== jMenuEeg = gui_component('Menu', jMenuBar, [], 'EEG montage', [], [], [], []); CreateMontageMenu(jMenuEeg); - % Help menu + % ===== HELP MENU ===== jMenuHelp = gui_component('Menu', jMenuBar, [], 'Help', [], [], [], []); gui_component('MenuItem', jMenuHelp, [], 'Digitize tutorial', [], [], @(h,ev)web('https://neuroimage.usc.edu/brainstorm/Tutorials/TutDigitizeLegacy', '-browser'), []); jPanelNew.add(jPanelMenu, BorderLayout.NORTH); - % ===== Control Panel ===== + % ===== CONTROL PANEL ===== jPanelControl = java_create('javax.swing.JPanel'); jPanelControl.setLayout(BoxLayout(jPanelControl, BoxLayout.Y_AXIS)); jPanelControl.setBorder(BorderFactory.createEmptyBorder(7,7,7,7)); modeButtonGroup = javax.swing.ButtonGroup(); - % ===== Coils panel ===== + % ===== COILS PANEL ===== jPanelCoils = gui_river([5,4], [10,10,10,10], 'Head Localization Coils'); % Fiducials jButtonhpiN = gui_component('toggle', jPanelCoils, [], 'HPI-N', {modeButtonGroup}, 'Center Coil', @(h,ev)SwitchToNewMode(1), largeFontSize); @@ -239,7 +347,7 @@ function Start() %#ok jPanelControl.add(jPanelCoils); jPanelControl.add(Box.createVerticalStrut(20)); - % ===== Fiducials panel ===== + % ===== FIDUCIALS PANEL ===== jPanelHeadCoord = gui_river([5,4], [10,10,10,10], 'Anatomical fiducials'); % Fiducials jButtonNasion = gui_component('toggle', jPanelHeadCoord, [], 'Nasion', {modeButtonGroup}, 'Nasion', @(h,ev)SwitchToNewMode(4), largeFontSize); @@ -264,15 +372,23 @@ function Start() %#ok jPanelControl.add(jPanelHeadCoord); jPanelControl.add(Box.createVerticalStrut(20)); - % ===== EEG panel ===== + % ===== EEG PANEL ===== jPanelEEG = gui_river([5,4], [10,10,10,10], 'EEG electrodes coordinates'); % Start EEG coord collection jButtonEEGStart = gui_component('toggle', jPanelEEG, [], 'EEG', {modeButtonGroup}, 'Start/Restart EEG digitization', @(h,ev)SwitchToNewMode(7), largeFontSize); newButtonSize = Dimension(initButtonSize.getWidth()*1.5, initButtonSize.getHeight()*1.5); jButtonEEGStart.setPreferredSize(newButtonSize); jButtonEEGStart.setFocusable(0); - % Separator - gui_component('label', jPanelEEG, 'hfill', ''); + + if strcmpi(Digitize.Type, '3DScanner') + % Auto EEG cap electrodes detection button + jButtonEEGAutoDetectElectrodes = gui_component('button', jPanelEEG, [], 'Auto', [], GenerateTooltipTextAuto(), @EEGAutoDetectElectrodes, largeFontSize); + jButtonEEGAutoDetectElectrodes.setPreferredSize(newButtonSize); + else + % Separator + jButtonEEGAutoDetectElectrodes = gui_component('label', jPanelEEG, 'hfill', ''); + end + % Number jTextFieldEEG = gui_component('text',jPanelEEG, [], '1', [], 'EEG Sensor # to be digitized', @EEGChangePoint_Callback, largeFontSize); jTextFieldEEG.setPreferredSize(newButtonSize) @@ -280,14 +396,22 @@ function Start() %#ok jPanelControl.add(jPanelEEG); jPanelControl.add(Box.createVerticalStrut(20)); - % ===== Extra points panel ===== + % ===== EXTRA POINTS PANEL ===== jPanelExtra = gui_river([5,4], [10,10,10,10], 'Head shape coordinates'); % Start Extra coord collection jButtonExtraStart = gui_component('toggle',jPanelExtra, [], 'Shape', {modeButtonGroup}, 'Start/Restart head shape digitization', @(h,ev)SwitchToNewMode(8), largeFontSize); jButtonExtraStart.setPreferredSize(newButtonSize); jButtonExtraStart.setFocusable(0); - % Separator - gui_component('label', jPanelExtra, 'hfill', ''); + + if strcmpi(Digitize.Type, '3DScanner') + % Add 150 random head shape points generation button + jButtonRandomHeadPts = gui_component('button', jPanelExtra, [], 'Random', [], 'Collect 150 head shape points from mesh', @CollectRandomHeadPts_Callback, largeFontSize); + jButtonRandomHeadPts.setPreferredSize(newButtonSize); + else + % Separator + jButtonRandomHeadPts = gui_component('label', jPanelExtra, 'hfill', ''); + end + % Number jTextFieldExtra = gui_component('text',jPanelExtra, [], '1',[], 'Head shape point to be digitized', @ExtraChangePoint_Callback, largeFontSize); jTextFieldExtra.setPreferredSize(newButtonSize) @@ -295,21 +419,26 @@ function Start() %#ok jPanelControl.add(jPanelExtra); jPanelControl.add(Box.createVerticalStrut(20)); - % ===== Extra buttons ===== + % ===== EXTRA BUTTONS ===== jPanelMisc = gui_river([5,4], [2,4,4,0]); - gui_component('button', jPanelMisc, [], 'Collect point', [], [], @ManualCollect_Callback); + if ~strcmpi(Digitize.Type, '3DScanner') + gui_component('button', jPanelMisc, [], 'Collect point', [], [], @(h,ev)bst_call(@ManualCollect_Callback)); + end jButtonDeletePoint = gui_component('button', jPanelMisc, 'hfill', 'Delete last point', [], [], @DeletePoint_Callback); gui_component('Button', jPanelMisc, [], 'Save as...', [], [], @Save_Callback); jPanelControl.add(jPanelMisc); jPanelControl.add(Box.createVerticalStrut(20)); jPanelNew.add(jPanelControl, BorderLayout.WEST); - % ===== Coordinate Display Panel ===== + % ===== COORDINATE DISPLAY PANEL ===== jPanelDisplay = gui_component('Panel'); jPanelDisplay.setBorder(java_scaled('titledborder', 'Coordinates (cm)')); % List of coordinates jListCoord = JList(largeFontSize); jListCoord.setCellRenderer(BstStringListRenderer(fontSize)); + java_setcb(jListCoord, ... + 'KeyTypedCallback', @(h,ev)bst_call(@CoordListKeyTyped_Callback,h,ev), ... + 'MouseClickedCallback', @(h,ev)bst_call(@CoordListClick_Callback,h,ev)); % Size jPanelScrollList = JScrollPane(jListCoord); jPanelScrollList.getLayout.getViewport.setView(jListCoord); @@ -319,27 +448,88 @@ function Start() %#ok jPanelDisplay.add(jPanelScrollList, BorderLayout.CENTER); jPanelNew.add(jPanelDisplay, BorderLayout.CENTER); - % create the controls structure - ctrl = struct('jMenuEeg', jMenuEeg, ... - 'jButtonNasion', jButtonNasion, ... - 'jButtonLPA', jButtonLPA, ... - 'jButtonRPA', jButtonRPA, ... - 'jLabelCoilMessage', jLabelCoilMessage, ... - 'jLabelFidMessage', jLabelFidMessage, ... - 'jButtonhpiN', jButtonhpiN, ... - 'jButtonhpiL', jButtonhpiL, ... - 'jButtonhpiR', jButtonhpiR, ... - 'jListCoord', jListCoord, ... - 'jButtonEEGStart', jButtonEEGStart, ... - 'jTextFieldEEG', jTextFieldEEG, ... - 'jButtonExtraStart', jButtonExtraStart, ... - 'jTextFieldExtra', jTextFieldExtra, ... - 'jButtonDeletePoint', jButtonDeletePoint); + % Create the controls structure + ctrl = struct('jMenuEeg', jMenuEeg, ... + 'jButtonNasion', jButtonNasion, ... + 'jButtonLPA', jButtonLPA, ... + 'jButtonRPA', jButtonRPA, ... + 'jLabelCoilMessage', jLabelCoilMessage, ... + 'jLabelFidMessage', jLabelFidMessage, ... + 'jButtonhpiN', jButtonhpiN, ... + 'jButtonhpiL', jButtonhpiL, ... + 'jButtonhpiR', jButtonhpiR, ... + 'jListCoord', jListCoord, ... + 'jButtonEEGStart', jButtonEEGStart, ... + 'jButtonEEGAutoDetectElectrodes', jButtonEEGAutoDetectElectrodes, ... + 'jTextFieldEEG', jTextFieldEEG, ... + 'jButtonExtraStart', jButtonExtraStart, ... + 'jButtonRandomHeadPts', jButtonRandomHeadPts, ... + 'jTextFieldExtra', jTextFieldExtra, ... + 'jButtonDeletePoint', jButtonDeletePoint); bstPanelNew = BstPanel(panelName, jPanelNew, ctrl); + + %% ================================================================================= + % === INTERNAL CALLBACKS ========================================================= + % ================================================================================= + %% ===== COORDINATE LIST KEY TYPED CALLBACK ===== + function CoordListKeyTyped_Callback(h, ev) + switch(uint8(ev.getKeyChar())) + % Delete + case {ev.VK_DELETE, ev.VK_BACK_SPACE} + ctrl = bst_get('PanelControls', 'Digitize'); + % If contact list rendering is blank in panel then dont't proceed + if ctrl.jListCoord.isSelectionEmpty() + return; + end + + [sCoordName, iSelCoord] = GetSelectedCoord(); + spl = regexp(sCoordName,'\s+','split'); + nameFinal = spl{1}; + if (~strcmpi(nameFinal, 'Nasion') &&... + ~strcmpi(nameFinal, 'LPA') &&... + ~strcmpi(nameFinal, 'RPA')) + listModel = ctrl.jListCoord.getModel(); + listModel.setElementAt(nameFinal, iSelCoord-1); + RemoveCoordinates('EEG', iSelCoord-3); + Digitize.isEditPts = 1; + SwitchToNewMode(7); + end + end + end + + %% ===== COORDINATE LIST CLICK CALLBACK ===== + function CoordListClick_Callback(h, ev) + % If single click + if (ev.getClickCount() == 1) + ctrl = bst_get('PanelControls', 'Digitize'); + % If contact list rendering is blank in panel then dont't proceed + if ctrl.jListCoord.isSelectionEmpty() + return; + end + + [sCoordName, ~] = GetSelectedCoord(); + spl = regexp(sCoordName,'\s+','split'); + nameFinal = spl{1}; + bst_figures('SetSelectedRows', nameFinal); + end + end end +%% ===== GET SELECTED ELECTRODE ===== +function [sCoordName, iSelCoord] = GetSelectedCoord() + % Get panel handles + ctrl = bst_get('PanelControls', 'Digitize'); + if isempty(ctrl) + return; + end + + % Get JList selected indices + iSelCoord = uint16(ctrl.jListCoord.getSelectedIndices())' + 1; + listModel = ctrl.jListCoord.getModel(); + sCoordName = listModel.getElementAt(iSelCoord-1); +end -%% ===== SWITCH to 2024 version ===== +%% ===== SWITCH TO 2024 VERSION ===== function SwitchVersion() % Always confirm this switch. if ~java_dialog('confirm', ['Switch to new (2024) version of the Digitize panel?
', ... @@ -362,7 +552,8 @@ function Close_Callback() %% ===== HIDING CALLBACK ===== function isAccepted = PanelHidingCallback() %#ok - global Digitize; + global Digitize + % If Brainstorm window was hidden before showing the Digitizer if bst_get('isGUI') % Get Brainstorm frame @@ -402,51 +593,89 @@ function Close_Callback() %% ===== EDIT SETTINGS ===== function isOk = EditSettings() + global Digitize + isOk = 0; % Get options DigitizeOptions = bst_get('DigitizeOptions'); + % Ask for new options - [res, isCancel] = java_dialog('input', ... - {'Serial connection settings

Serial port name (COM1):', ... - 'Unit Type (Fastrak or Patriot):', ... - '
Collection settings

Digitize MEG HPI coils (0=no, 1=yes):', ... - 'How many times do you want to collect
the three fiducials (NAS,LPA,RPA):', ... - 'Beep when collecting point (0=no, 1=yes):'}, ... - 'Digitizer configuration', [], ... - {DigitizeOptions.ComPort, ... - DigitizeOptions.UnitType, ... - num2str(DigitizeOptions.isMEG), ... - num2str(DigitizeOptions.nFidSets), ... - num2str(DigitizeOptions.isBeep)}); - if isempty(res) || isCancel + options_all = {'Serial connection settings

Serial port name (COM1):', ... + DigitizeOptions.ComPort; + 'Unit Type (Fastrak or Patriot):', ... + DigitizeOptions.UnitType; + '
Collection settings

Digitize MEG HPI coils (0=no, 1=yes):', ... + num2str(DigitizeOptions.isMEG); + 'How many times do you want to collect
the three fiducials (NAS,LPA,RPA):', ... + num2str(DigitizeOptions.nFidSets); + 'Beep when collecting point (0=no, 1=yes):', ... + num2str(DigitizeOptions.isBeep)}; + + % Options to show for each type + switch lower(Digitize.Type) + case 'digitize' + iOptionsType = [1:5]; + case '3dscanner' + iOptionsType = [3:5]; + end + + % Ask options + [resType, isCancel] = java_dialog('input', options_all(iOptionsType,1), [Digitize.Type ' configuration'], [], options_all(iOptionsType,2)); + if isempty(resType) || isCancel return end + + % Results from options asked to user + options_all(iOptionsType, 3) = resType; + % Results from options not asked to user + iOptionsKeep = setdiff([1:length(options_all)], iOptionsType); + options_all(iOptionsKeep, 3) = options_all(iOptionsKeep,2); + res = options_all(:,3); + % Check values - if (length(res) < 5) || isempty(res{1}) || isempty(res{2}) || ~ismember(str2double(res{3}), [0 1]) || isnan(str2double(res{4})) || ~ismember(str2double(res{5}), [0 1]) - bst_error('Invalid values.', 'Digitize', 0); + if length(resType) < length(iOptionsType) || (ismember(1, iOptionsType) && isempty(res{1})) || ... + (ismember(2, iOptionsType) && isempty(res{2})) || ... + (ismember(3, iOptionsType) && ~ismember(str2double(res{3}), [0 1])) || ... + (ismember(4, iOptionsType) && isnan(str2double(res{4}))) || ... + (ismember(5, iOptionsType) && ~ismember(str2double(res{5}), [0 1])) + bst_error('Invalid values.', 'Digitize', 0); return; end - % Get entered values - DigitizeOptions.ComPort = res{1}; - DigitizeOptions.UnitType = lower(res{2}); - DigitizeOptions.isMEG = str2double(res{3}); - DigitizeOptions.nFidSets = str2double(res{4}); - DigitizeOptions.isBeep = str2double(res{5}); - - if strcmp(DigitizeOptions.UnitType,'fastrak') + % Digitizer: COM port + DigitizeOptions.ComPort = res{1}; + % Digitizer: Type + DigitizeOptions.UnitType = lower(res{2}); + % Digitizer: COM properties + if isempty(DigitizeOptions.UnitType) + % Do nothing + elseif strcmp(DigitizeOptions.UnitType,'fastrak') DigitizeOptions.ComRate = 9600; DigitizeOptions.ComByteCount = 94; elseif strcmp(DigitizeOptions.UnitType,'patriot') DigitizeOptions.ComRate = 115200; DigitizeOptions.ComByteCount = 120; else - bst_error('Incorrect unit type.', 'Digitize', 0); + bst_error('Incorrect unit type.', Digitize.Type, 0); return; end + + % Common: Digitize MEG HPI coils + if ~isempty(res{3}) + DigitizeOptions.isMEG = str2double(res{3}); + end + % Common: Number of fiducial sets + if ~isempty(res{4}) + DigitizeOptions.nFidSets = str2double(res{4}); + end + % Common: Beep + if ~isempty(res{5}) + DigitizeOptions.isBeep = str2double(res{5}); + end % Save values bst_set('DigitizeOptions', DigitizeOptions); - %ResetDataCollection(); + % ResetDataCollection(); + UpdateList(); isOk = 1; end @@ -470,7 +699,8 @@ function SetSimulate(isSimulate) %#ok %% ===== RESET DATA COLLECTION ===== function ResetDataCollection(isResetSerial) global Digitize - bst_progress('start', 'Digitize', 'Initializing...'); + + bst_progress('start', Digitize.Type, 'Initializing...'); % Reset serial? if (nargin == 1) && isequal(isResetSerial, 1) CreateSerialConnection(); @@ -482,10 +712,11 @@ function ResetDataCollection(isResetSerial) 'nasion', [], ... 'LPA', [], ... 'RPA', [], ... - 'hpiN', [], ... - 'hpiL', [], ... - 'hpiR', [], ... + 'hpiN', [], ... + 'hpiL', [], ... + 'hpiR', [], ... 'EEG', [], ... + 'Label', [], ... 'headshape', [], ... 'trans', []); % Reset counters @@ -493,10 +724,17 @@ function ResetDataCollection(isResetSerial) ctrl.jTextFieldEEG.setText(java.lang.String.valueOf(int16(1))); ctrl.jTextFieldExtra.setText(java.lang.String.valueOf(int16(1))); end - % Reset figure - if isfield(Digitize, 'hFig') && ~isempty(Digitize.hFig) && ishandle(Digitize.hFig) - %close(Digitize.hFig); + % Reset figure (also unloads in global data) + if ~isempty(Digitize.hFig) && ishandle(Digitize.hFig) bst_figures('DeleteFigure', Digitize.hFig, []); + + % For 3D Scanner reload the surface + if strcmpi(Digitize.Type, '3DScanner') + % load the surface + sSurf = bst_memory('LoadSurface', Digitize.surfaceFile); + % Display surface + view_surface_matrix(sSurf.Vertices, sSurf.Faces, [], sSurf.Color, [], [], Digitize.surfaceFile); + end end Digitize.iDS = []; @@ -522,6 +760,7 @@ function ResetDataCollection(isResetSerial) % - Mode 8 = Headshape function SwitchToNewMode(mode) global Digitize + % Get controls ctrl = bst_get('PanelControls', 'Digitize'); % Get options @@ -542,7 +781,8 @@ function SwitchToNewMode(mode) ctrl.jButtonLPA.setEnabled(0); ctrl.jButtonRPA.setEnabled(0); ctrl.jButtonDeletePoint.setEnabled(0); - % always switch to next mode to start with the nasion + ctrl.jButtonEEGAutoDetectElectrodes.setEnabled(0); + % Always switch to next mode to start with the nasion SwitchToNewMode(1); else ctrl.jButtonhpiN.setEnabled(0); @@ -552,13 +792,15 @@ function SwitchToNewMode(mode) ctrl.jButtonLPA.setEnabled(1); ctrl.jButtonRPA.setEnabled(1); ctrl.jButtonDeletePoint.setEnabled(0); - % always switch to next mode to start with the nasion + ctrl.jButtonEEGAutoDetectElectrodes.setEnabled(0); + % Always switch to next mode to start with the nasion SwitchToNewMode(4); end ctrl.jButtonEEGStart.setEnabled(0); ctrl.jTextFieldEEG.setEnabled(0); ctrl.jButtonExtraStart.setEnabled(0); + ctrl.jButtonRandomHeadPts.setEnabled(0); ctrl.jTextFieldExtra.setEnabled(0); @@ -620,6 +862,10 @@ function SwitchToNewMode(mode) % Shape case 8 ctrl.jButtonExtraStart.setEnabled(1); + if strcmpi(Digitize.Type, '3DScanner') + ctrl.jButtonEEGAutoDetectElectrodes.setEnabled(0); + ctrl.jButtonRandomHeadPts.setEnabled(1); + end ctrl.jTextFieldExtra.setEnabled(1); SetSelectedButton(8); Digitize.Mode = 8; @@ -629,7 +875,8 @@ function SwitchToNewMode(mode) %% ===== UPDATE LIST ===== function UpdateList() - global Digitize; + global Digitize + % Get controls ctrl = bst_get('PanelControls', 'Digitize'); % Define the model @@ -739,6 +986,11 @@ function UpdateList() else ctrl.jListCoord.ensureIndexIsVisible(lastIndex-1); % 0-indexed, -1 works even if 0 end + + % Update tooltip text for 'Auto' button + if strcmpi(Digitize.Type, '3DScanner') + ctrl.jButtonEEGAutoDetectElectrodes.setToolTipText(GenerateTooltipTextAuto()); + end end @@ -773,15 +1025,89 @@ function SetSelectedButton(iButton) end end +%% 3DSCANNER: AUTOMATICALLY DETECT AND LABEL EEG CAP ELECTRODES +function EEGAutoDetectElectrodes(h, ev) + global Digitize + + % Add disclaimer to users that 'Auto' feature is experimental + if ~java_dialog('confirm', [' Automatic detection of EEG sensors is an experimental feature.
' ... + 'Please verify the results carefully.

' ... + 'Do you want to continue?'], 'Auto detect EEG electrodes') + return + end + % Get controls + ctrl = bst_get('PanelControls', 'Digitize'); + % Disable Auto button + ctrl.jButtonEEGAutoDetectElectrodes.setEnabled(0); + % Progress bar + bst_progress('start', Digitize.Type, 'Automatic labelling of EEG sensors...'); + + % Get current montage + curMontage = GetCurrentMontage(); + isWhiteCap = 0; + % For white caps change the color space by inverting the colors + % NOTE: only 'Acticap' is the tested white cap (needs work on finding a better aprrooach) + if ~isempty(regexp(curMontage.Name, 'ActiCap', 'match')) + isWhiteCap = 1; + end + + % Get the cap surface from 3D scanner + hFig = bst_figures('GetCurrentFigure','3D'); + TessInfo = getappdata(hFig, 'Surface'); + sSurf = bst_memory('LoadSurface', TessInfo.SurfaceFile); + + % Automatically find electrodes locations on EEG cap + [capCenters2d, capImg2d, surface3dscannerUv] = channel_detect_eegcap_auto('FindElectrodesEegCap', sSurf, isWhiteCap); + DigitizeOptions = bst_get('DigitizeOptions'); + if isempty(DigitizeOptions.Montages(DigitizeOptions.iMontage).ChannelFile) + bst_error('EEG cap layout not selected. Go to EEG', Digitize.Type, 1); + bst_progress('stop'); + return; + else + ChannelMat = in_bst_channel(DigitizeOptions.Montages(DigitizeOptions.iMontage).ChannelFile); + end + + + % Warp points from layout to mesh + capPoints3d = channel_detect_eegcap_auto('WarpLayout2Mesh', capCenters2d, capImg2d, surface3dscannerUv, ChannelMat.Channel, Digitize.Points); + + % Plot the electrodes and their labels + for i= 1:size(capPoints3d.EEG, 1) + % Get current montage + [curMontage, nEEG] = GetCurrentMontage(); + % Find found point in current montage + [~, iPoint] = ismember(capPoints3d.Label{i}, [curMontage.Labels]); + % Transform coordinate + Digitize.Points.EEG(iPoint,:) = capPoints3d.EEG(i, :); + % Add the point to the display + Digitize.Points.Label{iPoint} = cell2mat(capPoints3d.Label{i}); + PlotCoordinate(Digitize.Points.EEG(iPoint,:), Digitize.Points.Label{iPoint}, 'EEG', iPoint) + % Update text field counter to the next point in the list + nextPoint = max(size(Digitize.Points.EEG,1)+1, 1); + if nextPoint > nEEG + % All EEG points have been collected, switch to next mode + ctrl.jTextFieldEEG.setText(java.lang.String.valueOf(int16(nEEG))); + SwitchToNewMode(8); + else + ctrl.jTextFieldEEG.setText(java.lang.String.valueOf(int16(nextPoint))); + end + end + + UpdateList(); + % Enable Random button + ctrl.jButtonRandomHeadPts.setEnabled(1); + bst_progress('stop'); +end %% ===== MANUAL COLLECT CALLBACK ====== -function ManualCollect_Callback(h, ev) +function ManualCollect_Callback() global Digitize + % Get Digitize options DigitizeOptions = bst_get('DigitizeOptions'); % Simulation: call the callback directly if DigitizeOptions.isSimulate - BytesAvailable_Callback(h, ev); + BytesAvailable_Callback(); % Else: Send a collection request to the Polhemus else % User clicked the button, collect a point @@ -790,14 +1116,97 @@ function ManualCollect_Callback(h, ev) end end +%% ===== COLLECT RANDOM HEADPOINTS ===== +function CollectRandomHeadPts_Callback(h, ev) + global Digitize; + % Get controls + ctrl = bst_get('PanelControls', 'Digitize'); + % Disable Random button + ctrl.jButtonRandomHeadPts.setEnabled(0); + % Progress bar + bst_progress('start', Digitize.Type, 'Plotting 150 random head shape points...'); + + hFig = bst_figures('GetCurrentFigure','3D'); + TessInfo = getappdata(hFig, 'Surface'); + TessMat = bst_memory('LoadSurface', TessInfo.SurfaceFile); + + % Brainstorm recommends to collect approximately 100-150 points from the head + % 5-10 points from the boney part of the nose + PlotHeadShapePoints(TessMat.Vertices, 'nose', 10); + % 10-20 points across the left eyebrow + PlotHeadShapePoints(TessMat.Vertices, 'leyebrow', 20); + % 10-20 points across the right eyebrow + PlotHeadShapePoints(TessMat.Vertices, 'reyebrow', 20); + % 100 points on the scalp + PlotHeadShapePoints(TessMat.Vertices, 'scalp', 100); + + UpdateList(); + bst_progress('stop'); +end + +%% ===== PLOT HEAD SHAPE POINTS ===== +function PlotHeadShapePoints(Vertices, plotRegion, nPoints) + global Digitize + % Get controls + ctrl = bst_get('PanelControls', 'Digitize'); + + % Get the plotting parameters based on the region in the head + switch plotRegion + case 'nose' + nosePoint = Digitize.Points.nasion; + % Get 600 nearest points to the 'nosePoint' and choose 'nPoints' from it + nearPointsIdx = bst_nearest(Vertices, nosePoint, 600, 0, []); + range = length(nearPointsIdx); + stepFactor = range/nPoints; + case 'leyebrow' + lEyebrowPoint = (1.25 * Digitize.Points.nasion) + (0.5 * Digitize.Points.LPA); + % Get 400 nearest points to the 'lEyebrowPoint' and choose 'nPoints' from it + nearPointsIdx = bst_nearest(Vertices, lEyebrowPoint, 400, 0, []); + range = length(nearPointsIdx); + stepFactor = range/nPoints; + case 'reyebrow' + rEyebrowPoint = (1.25 * Digitize.Points.nasion) + (0.5 * Digitize.Points.RPA); + % Get 400 nearest points to the 'rEyebrowPoint' and choose 'nPoints' from it + nearPointsIdx = bst_nearest(Vertices, rEyebrowPoint, 400, 0, []); + range = length(nearPointsIdx); + stepFactor = range/nPoints; + case 'scalp' + range = length(Vertices); + stepFactor = ceil(range/nPoints); + otherwise + bst_error([plotRegion 'is invalid'], 'Plot Head Shape Points', 0); + bst_progress(stop); + return + end + + % Plot the head shape points + for i= 1:stepFactor:range + if strcmpi(plotRegion, 'scalp') + pointCoord = Vertices(i, :); + else + pointCoord = Vertices(nearPointsIdx(i), :); + end + % Find the index for the current point in the headshape points + iPoint = str2double(ctrl.jTextFieldExtra.getText()); + % Transformed points_pen from original points_pen + Digitize.Points.headshape(iPoint,:) = pointCoord; + % Add the point to the display (in cm) + PlotCoordinate(Digitize.Points.headshape(iPoint,:), 'EXTRA', 'EXTRA', iPoint) + % Update text field counter to the next point in the list + nextPoint = iPoint+1; + ctrl.jTextFieldExtra.setText(java.lang.String.valueOf(int16(nextPoint))); + end +end + %% ===== DELETE POINT CALLBACK ===== function DeletePoint_Callback(h, ev) %#ok global Digitize + % Get controls ctrl = bst_get('PanelControls', 'Digitize'); DigitizeOptions = bst_get('DigitizeOptions'); - % only remove cardinal points when MEG coils are used for the + % Only remove cardinal points when MEG coils are used for the % transformation. if ismember(Digitize.Mode, [4 5 6]) && DigitizeOptions.isMEG % Remove the last cardinal point that was collected @@ -810,30 +1219,30 @@ function DeletePoint_Callback(h, ev) %#ok end if Digitize.Mode == 7 - % find the last EEG point collected + % Find the last EEG point collected iPoint = str2double(ctrl.jTextFieldEEG.getText()) - 1; if iPoint == 0 - % if no EEG points are collected, delete the last cardinal point + % If no EEG points are collected, delete the last cardinal point iPoint = size(Digitize.Points.nasion,1); coordInd = (iPoint-1)*3; point_type = 'cardinal'; else - % delete last EEG point + % Delete last EEG point point_type = 'eeg'; end elseif Digitize.Mode == 8 - % headshape point + % Headshape point iPoint = str2double(ctrl.jTextFieldExtra.getText()) - 1; if iPoint == 0 % If no headpoints are collected: [tmp, nEEG] = GetCurrentMontage(); if nEEG > 0 - % check for EEG, then delete the last point + % Check for EEG, then delete the last point iPoint = str2double(ctrl.jTextFieldEEG.getText()); point_type = 'eeg'; else - % delete the last cardinal point + % Delete the last cardinal point iPoint = size(Digitize.Points.nasion,1); coordInd = (iPoint-1)*3; point_type = 'cardinal'; @@ -867,6 +1276,7 @@ function DeletePoint_Callback(h, ev) %#ok end case 'eeg' Digitize.Points.EEG(iPoint,:) = []; + Digitize.Points.Label{iPoint} = {}; RemoveCoordinates('EEG', iPoint); ctrl.jTextFieldEEG.setText(java.lang.String.valueOf(int16(iPoint))); SwitchToNewMode(7) @@ -877,25 +1287,23 @@ function DeletePoint_Callback(h, ev) %#ok SwitchToNewMode(8) end - - % Update coordinates list UpdateList(); end - %% ===== COMPUTE TRANFORMATION ===== function ComputeTransform() global Digitize + % Get controls ctrl = bst_get('PanelControls', 'Digitize'); % Get options DigitizeOptions = bst_get('DigitizeOptions'); - % if MEG coils are used, these will determine the coodinate system + % If MEG coils are used, these will determine the coodinate system if DigitizeOptions.isMEG - % find the difference between the first two collections to determine error + % Find the difference between the first two collections to determine error if (size(Digitize.Points.hpiN, 1) > 1) diffPoint = Digitize.Points.hpiN(1,:) - Digitize.Points.hpiN(2,:); normPoint(1) = sum(sqrt(diffPoint(1)^2+diffPoint(2)^2+diffPoint(3)^2)); @@ -907,14 +1315,13 @@ function ComputeTransform() ctrl.jLabelCoilMessage.setText('Difference error exceeds 5 mm'); end end - % find the average across collections to compute the transformation + % Find the average across collections to compute the transformation na = mean(Digitize.Points.hpiN,1); % these values are in meters la = mean(Digitize.Points.hpiL,1); ra = mean(Digitize.Points.hpiR,1); else - % if only EEG is used, the cardinal points will determine the - % coordinate system + % If only EEG is used, the cardinal points will determine the coordinate system % find the difference between the first two collections to determine error if (size(Digitize.Points.nasion, 1) > 1) @@ -998,7 +1405,8 @@ function ComputeTransform() %% ===== CREATE FIGURE ===== function CreateHeadpointsFigure() - global Digitize + global Digitize + if isempty(Digitize.hFig) || ~ishandle(Digitize.hFig) || isempty(Digitize.iDS) % Get study sStudy = bst_get('StudyWithCondition', [Digitize.SubjectName '/' Digitize.ConditionName]); @@ -1007,10 +1415,10 @@ function CreateHeadpointsFigure() % Hide head surface panel_surface('SetSurfaceTransparency', hFig, 1, 0.8); % Get Digitizer JFrame - bstContainer = get(bst_get('Panel','Digitize'), 'container'); + bstContainer = get(bst_get('Panel', 'Digitize'), 'container'); % Get maximum figure position decorationSize = bst_get('DecorationSize'); - [jBstArea, FigArea] = gui_layout('GetScreenBrainstormAreas', bstContainer.handle{1}); + [~, FigArea] = gui_layout('GetScreenBrainstormAreas', bstContainer.handle{1}); FigPos = FigArea(1,:) + [decorationSize(1), decorationSize(4), - decorationSize(1) - decorationSize(3), - decorationSize(2) - decorationSize(4)]; if (FigPos(3) > 0) && (FigPos(4) > 0) set(hFig, 'Position', FigPos); @@ -1020,12 +1428,56 @@ function CreateHeadpointsFigure() % Save handles in global variable Digitize.hFig = hFig; Digitize.iDS = iDS; - end + else + % Hide figure + set(Digitize.hFig, 'Visible', 'off'); + % Get study + sStudy = bst_get('StudyWithCondition', [Digitize.SubjectName '/' Digitize.ConditionName]); + % Plot head points + [hFig, iDS] = view_headpoints(file_fullpath(sStudy.Channel.FileName)); + % Get the surface + sSurf = bst_memory('LoadSurface', Digitize.surfaceFile); + % Apply the transformation + sSurf.Vertices = (Digitize.Points.trans * [sSurf.Vertices ones(size(sSurf.Vertices, 1),1)]')'; + % Remove the surface + panel_surface('RemoveSurface', hFig, 1); + % Deface the surface + if isempty(regexp(sSurf.Comment, 'defaced', 'match')) + sSurf = tess_deface(sSurf); + end + % Save the surface and update the node + ProtocolInfo = bst_get('ProtocolInfo'); + surfaceFile = bst_fullfile(ProtocolInfo.SUBJECTS, Digitize.surfaceFile); + bst_save(surfaceFile, sSurf, 'v7'); + [~, iSubject] = bst_get('Subject', Digitize.SubjectName); + db_reload_subjects(iSubject); + % Hide head surface + if ~strcmpi(Digitize.Type, '3DScanner') + panel_surface('SetSurfaceTransparency', hFig, 1, 0.8); + end + % Get Digitizer JFrame + bstContainer = get(bst_get('Panel', 'Digitize'), 'container'); + % Get maximum figure position + decorationSize = bst_get('DecorationSize'); + [~, FigArea] = gui_layout('GetScreenBrainstormAreas', bstContainer.handle{1}); + FigPos = FigArea(1,:) + [decorationSize(1), decorationSize(4), - decorationSize(1) - decorationSize(3), - decorationSize(2) - decorationSize(4)]; + % Display updated surface + view_surface_matrix(sSurf.Vertices, sSurf.Faces, [], sSurf.Color, hFig, [], Digitize.surfaceFile); + if (FigPos(3) > 0) && (FigPos(4) > 0) + set(hFig, 'Position', FigPos); + end + % Remove the close handle function + set(hFig, 'CloseRequestFcn', []); + % Save handles in global variable + Digitize.hFig = hFig; + Digitize.iDS = iDS; + end end %% ===== PLOT POINTS ===== function PlotCoordinate(Loc, Label, Type, iPoint) global Digitize GlobalData + sStudy = bst_get('StudyWithCondition', [Digitize.SubjectName '/' Digitize.ConditionName]); ChannelFile = file_fullpath(sStudy.Channel.FileName); ChannelMat = load(ChannelFile); @@ -1033,7 +1485,7 @@ function PlotCoordinate(Loc, Label, Type, iPoint) % Add EEG sensor locations to channel stucture if strcmp(Type, 'EEG') if isempty(ChannelMat.Channel) - % first point in the list + % First point in the list ChannelMat.Channel = db_template('channeldesc'); end ChannelMat.Channel(iPoint).Name = Label; @@ -1059,19 +1511,22 @@ function PlotCoordinate(Loc, Label, Type, iPoint) figure_3d('ViewHeadPoints', Digitize.hFig, 1); figure_3d('ViewSensors',Digitize.hFig, 1, 1, 0,'EEG'); % Hide head surface - panel_surface('SetSurfaceTransparency', Digitize.hFig, 1, 1); + if ~strcmpi(Digitize.Type, '3DScanner') + panel_surface('SetSurfaceTransparency', Digitize.hFig, 1, 1); + end end %% ===== EEG CHANGE POINT CALLBACK ===== function EEGChangePoint_Callback(h, ev) %#ok global Digitize + % Get controls ctrl = bst_get('PanelControls', 'Digitize'); % Get digitize options DigitizeOptions = bst_get('DigitizeOptions'); initPoint = str2num(ctrl.jTextFieldEEG.getText()); - % restrict to a maximum of points collected or defined max points and minimum of '1' + % Restrict to a maximum of points collected or defined max points and minimum of '1' [curMontage, nEEG] = GetCurrentMontage(); newPoint = max(min(initPoint, min(length(Digitize.Points.EEG)+1, nEEG)), 1); ctrl.jTextFieldEEG.setText(java.lang.String.valueOf(int16(newPoint))); @@ -1080,11 +1535,12 @@ function EEGChangePoint_Callback(h, ev) %#ok %% ===== EXTRA CHANGE POINT CALLBACK ===== function ExtraChangePoint_Callback(h, ev) %#ok global Digitize + % Get controls ctrl = bst_get('PanelControls', 'Digitize'); initPoint = str2num(ctrl.jTextFieldExtra.getText()); %#ok<*ST2NM> - % restrict to a maximum of points collected and minimum of '1' + % Restrict to a maximum of points collected and minimum of '1' newPoint = max(min(initPoint, length(Digitize.Points.headshape)+1), 1); ctrl.jTextFieldExtra.setText(java.lang.String.valueOf(int16(newPoint))); end @@ -1092,6 +1548,7 @@ function ExtraChangePoint_Callback(h, ev) %#ok %% ===== SAVE CALLBACK ===== function Save_Callback(h, ev) %#ok global Digitize + sStudy = bst_get('StudyWithCondition', [Digitize.SubjectName '/' Digitize.ConditionName]); ChannelFile = file_fullpath(sStudy.Channel.FileName); export_channel( ChannelFile ); @@ -1099,6 +1556,9 @@ function Save_Callback(h, ev) %#ok %% ===== CREATE MONTAGE MENU ===== function CreateMontageMenu(jMenu) + import org.brainstorm.icon.*; + global Digitize + % Get menu pointer if not in argument if (nargin < 1) || isempty(jMenu) ctrl = bst_get('PanelControls', 'Digitize'); @@ -1122,13 +1582,24 @@ function CreateMontageMenu(jMenu) end % Add new montage / reset list jMenu.addSeparator(); - gui_component('MenuItem', jMenu, [], 'Add EEG montage...', [], [], @(h,ev)bst_call(@AddMontage), []); + + if strcmpi(Digitize.Type, '3DScanner') + jMenuAddMontage = gui_component('Menu', jMenu, [], 'Add EEG montage...', [], [], [], []); + gui_component('MenuItem', jMenuAddMontage, [], 'From file...', [], [], @(h,ev)bst_call(@AddMontage), []); + % Creating montages from EEG cap layout mat files (only for 3DScanner) + jMenuEegCaps = gui_component('Menu', jMenuAddMontage, [], 'From default EEG cap', IconLoader.ICON_CHANNEL, [], [], []); + % Use default channel file + menu_default_eegcaps(jMenuEegCaps); + else % if not 3DScanner + gui_component('MenuItem', jMenu, [], 'Add EEG montage...', [], [], @(h,ev)bst_call(@AddMontage), []); + end gui_component('MenuItem', jMenu, [], 'Unload all montages', [], [], @(h,ev)bst_call(@UnloadAllMontages), []); end - %% ===== SELECT MONTAGE ===== function SelectMontage(iMontage) + global Digitize + % Get Digitize options DigitizeOptions = bst_get('DigitizeOptions'); % Default montage: ask for number of channels @@ -1167,6 +1638,20 @@ function SelectMontage(iMontage) ResetDataCollection(); end +%% ===== TOOLTIP TEXT FOR AUTO BUTTON ===== +function autoButtonTooltip = GenerateTooltipTextAuto() + % Get current montage + curMontage = GetCurrentMontage(); + % Get cap landmark labels for selected montage + eegCapLandmarkLabels = channel_detect_eegcap_auto('GetEegCapLandmarkLabels', curMontage.Name); + autoButtonTooltip = 'Auto localization of EEG sensor is not suported for this cap.'; + if ~isempty(eegCapLandmarkLabels) + strSensors = sprintf('%s, ',eegCapLandmarkLabels{:}); + strSensors = strSensors(1:end-2); + autoButtonTooltip = ['Set at least sensors: [' strSensors '] to enable.']; + end +end + %% ===== GET CURRENT MONTAGE ===== function [curMontage, nEEG] = GetCurrentMontage() % Get Digitize options @@ -1177,46 +1662,69 @@ function SelectMontage(iMontage) end %% ===== ADD EEG MONTAGE ===== -function AddMontage() - % Get recently used folders - LastUsedDirs = bst_get('LastUsedDirs'); - % Open file - MontageFile = java_getfile('open', 'Select montage file...', LastUsedDirs.ImportChannel, 'single', 'files', ... - {{'*.txt'}, 'Text files', 'TXT'}, 0); - if isempty(MontageFile) - return; - end - % Get filename - [MontageDir, MontageName] = bst_fileparts(MontageFile); - % Intialize new montage - newMontage.Name = MontageName; - newMontage.Labels = {}; - - % Open file - fid = fopen(MontageFile,'r'); - if (fid == -1) - error('Cannot open file.'); - end - % Read file - while (1) - tline = fgetl(fid); - if ~ischar(tline) - break; +function AddMontage(ChannelFile) + global Digitize + + % Add Montage from text file + if nargin<1 + % Get recently used folders + LastUsedDirs = bst_get('LastUsedDirs'); + % Open file + MontageFile = java_getfile('open', 'Select montage file...', LastUsedDirs.ImportChannel, 'single', 'files', ... + {{'*.txt'}, 'Text files', 'TXT'}, 0); + if isempty(MontageFile) + return; end - spl = regexp(tline,'\s+','split'); - if (length(spl) >= 2) - newMontage.Labels{end+1} = spl{2}; + % Get filename + [MontageDir, MontageName] = bst_fileparts(MontageFile); + % Intialize new montage + newMontage.Name = MontageName; + newMontage.Labels = {}; + + % Open file + fid = fopen(MontageFile,'r'); + if (fid == -1) + error('Cannot open file.'); end + % Read file + while (1) + tline = fgetl(fid); + if ~ischar(tline) + break; + end + spl = regexp(tline,'\s+','split'); + if (length(spl) >= 2) + newMontage.Labels{end+1} = spl{2}; + end + end + % Close file + fclose(fid); + % If no labels were read: exit + if isempty(newMontage.Labels) + return + end + % Save last dir + LastUsedDirs.ImportChannel = MontageDir; + bst_set('LastUsedDirs', LastUsedDirs); + % Add Montage from mat file of EEG caps + else + % Load existing file + ChannelMat = in_bst_channel(ChannelFile); + + % Intialize new montage + newMontage.Name = ChannelMat.Comment; + newMontage.Labels = {}; + newMontage.ChannelFile = ChannelFile; + + % Get cap landmark labels + eegCapLandmarkLabels = channel_detect_eegcap_auto('GetEegCapLandmarkLabels', newMontage.Name); + + % Sort as per the initialization landmark labels of EEG Cap + nonLandmarkLabelsIdx = find(~ismember({ChannelMat.Channel.Name},eegCapLandmarkLabels)); + allLabels = {ChannelMat.Channel.Name}; + newMontage.Labels = cat(2, eegCapLandmarkLabels, allLabels(nonLandmarkLabelsIdx)); end - % Close file - fclose(fid); - % If no labels were read: exit - if isempty(newMontage.Labels) - return - end - % Save last dir - LastUsedDirs.ImportChannel = MontageDir; - bst_set('LastUsedDirs', LastUsedDirs); + % Get Digitize options DigitizeOptions = bst_get('DigitizeOptions'); @@ -1236,8 +1744,13 @@ function AddMontage() bst_set('DigitizeOptions', DigitizeOptions); % Reload Menu CreateMontageMenu(); - % Restart acquisition - ResetDataCollection(); + if nargin<1 + % Restart acquisition + ResetDataCollection(); + else + % Update List + UpdateList(); + end end %% ===== UNLOAD ALL MONTAGES ===== @@ -1247,15 +1760,19 @@ function UnloadAllMontages() % Remove all montages DigitizeOptions.Montages = [... struct('Name', 'No EEG', ... - 'Labels', []), ... + 'Labels', [], ... + 'ChannelFile', []), ... struct('Name', 'Default', ... - 'Labels', [])]; + 'Labels', [], ... + 'ChannelFile', [])]; % Reset to "No EEG" DigitizeOptions.iMontage = 1; % Save Digitize options bst_set('DigitizeOptions', DigitizeOptions); % Reload menu bar CreateMontageMenu(); + % Update List + UpdateList(); end @@ -1270,37 +1787,42 @@ function RemoveCoordinates(type, iPoint) ChannelFile = file_fullpath(sStudy.Channel.FileName); ChannelMat = load(ChannelFile); if isempty(type) - % remove all points + % Remove all points ChannelMat.HeadPoints = []; ChannelMat.Channel = []; % Save file back save(ChannelFile, '-struct', 'ChannelMat'); - % close the figure + % Close the figure if ishandle(Digitize.hFig) - %close(Digitize.hFig); + % close(Digitize.hFig); bst_figures('DeleteFigure', Digitize.hFig, []); end else % find group and remove selected point if isempty(iPoint) - % create a mask of points to keep that exclude the specified + % Create a mask of points to keep that exclude the specified % type mask = cellfun(@isempty,regexp([ChannelMat.HeadPoints.Type], type)); ChannelMat.HeadPoints.Loc = ChannelMat.HeadPoints.Loc(:,mask); ChannelMat.HeadPoints.Label = ChannelMat.HeadPoints.Label(mask); ChannelMat.HeadPoints.Type = ChannelMat.HeadPoints.Type(mask); if strcmp(type, 'EEG') - % remove all EEG channels from channel struct + % Remove all EEG channels from channel struct ChannelMat.Channel = []; end else - % find the point in the type and create a mask of points to keep + % Find the point in the type and create a mask of points to keep % that excludes the specified point if strcmp(type, 'EEG') - % remove specific EEG channel - ChannelMat.Channel(iPoint) = []; + if strcmpi(Digitize.Type, '3DScanner') + % Remove only location + ChannelMat.Channel(iPoint).Loc = []; + else + % Remove specific EEG channel + ChannelMat.Channel(iPoint) = []; + end else - % all other types + % All other types iType = find(~cellfun(@isempty,regexp([ChannelMat.HeadPoints.Type], type))); mask = true(1,size(ChannelMat.HeadPoints.Type,2)); iDelete = iType(iPoint); @@ -1311,7 +1833,7 @@ function RemoveCoordinates(type, iPoint) end end - % save changes + % Save changes save(ChannelFile, '-struct', 'ChannelMat'); GlobalData.DataSet(Digitize.iDS).HeadPoints = ChannelMat.HeadPoints; GlobalData.DataSet(Digitize.iDS).Channel = ChannelMat.Channel; @@ -1326,7 +1848,7 @@ function RemoveCoordinates(type, iPoint) % View headpoints figure_3d('ViewHeadPoints', Digitize.hFig, 1); - % manually remove any remaining EEG markers if the channel file is + % Manually remove any remaining EEG markers if the channel file is % empty. if isempty(ChannelMat.Channel) hSensorMarkers = findobj(hAxes, 'Tag', 'SensorsMarkers'); @@ -1335,7 +1857,7 @@ function RemoveCoordinates(type, iPoint) delete(hSensorLabels); end - % view EEG sensors + % View EEG sensors figure_3d('ViewSensors',Digitize.hFig, 1, 1, 0,'EEG'); end end @@ -1348,6 +1870,7 @@ function RemoveCoordinates(type, iPoint) %% ===== CREATE SERIAL COLLECTION ===== function isOk = CreateSerialConnection(h, ev) %#ok global Digitize + isOk = 0; while ~isOk % Get COM port options @@ -1367,7 +1890,7 @@ function RemoveCoordinates(type, iPoint) if strcmp(DigitizeOptions.UnitType,'patriot') SerialConnection.terminator = 'CR'; end - % set up the Bytes Available function and open the connection (if needed) + % Set up the Bytes Available function and open the connection (if needed) SerialConnection.BytesAvailableFcnCount = DigitizeOptions.ComByteCount; SerialConnection.BytesAvailableFcnMode = 'byte'; SerialConnection.BytesAvailableFcn = @BytesAvailable_Callback; @@ -1389,7 +1912,7 @@ function RemoveCoordinates(type, iPoint) pause(0.2); catch %#ok % If the connection cannot be established: error message - bst_error(['Cannot open serial connection.' 10 10 'Please check the serial port configuration.' 10], 'Digitize', 0); + bst_error(['Cannot open serial connection.' 10 10 'Please check the serial port configuration.' 10], Digitize.Type, 0); % Ask user to edit the port options isChanged = EditSettings(); % If edit was canceled: exit @@ -1410,8 +1933,9 @@ function RemoveCoordinates(type, iPoint) %% ===== BYTES AVAILABLE CALLBACK ===== -function BytesAvailable_Callback(h, ev) %#ok +function BytesAvailable_Callback() global Digitize rawpoints + % Get controls ctrl = bst_get('PanelControls', 'Digitize'); % Get digitizer options @@ -1419,14 +1943,34 @@ function BytesAvailable_Callback(h, ev) %#ok % Simulate: Generate random points if DigitizeOptions.isSimulate - switch (Digitize.Mode) - case 1, pointCoord = [.08 0 -.01]; - case 2, pointCoord = [-.01 .07 0]; - case 3, pointCoord = [-.01 -.07 0]; - case 4, pointCoord = [.08 0 0]; - case 5, pointCoord = [0 .07 0]; - case 6, pointCoord = [0 -.07 0]; - otherwise, pointCoord = rand(1,3) * .15 - .075; + if strcmpi(Digitize.Type, '3DScanner') + % Get current 3D figure + [hFig,~,iDS] = bst_figures('GetCurrentFigure', '3D'); + if isempty(hFig) + return + end + % Get current selected point + CoordinatesSelector = getappdata(hFig, 'CoordinatesSelector'); + isSelectingCoordinates = getappdata(hFig, 'isSelectingCoordinates'); + if isempty(CoordinatesSelector) || isempty(CoordinatesSelector.MRI) + return; + else + if isSelectingCoordinates + pointCoord = CoordinatesSelector.SCS; + end + end + Digitize.hFig = hFig; + Digitize.iDS = iDS; + else + switch (Digitize.Mode) + case 1, pointCoord = [.08 0 -.01]; + case 2, pointCoord = [-.01 .07 0]; + case 3, pointCoord = [-.01 -.07 0]; + case 4, pointCoord = [.08 0 0]; + case 5, pointCoord = [0 .07 0]; + case 6, pointCoord = [0 -.07 0]; + otherwise, pointCoord = rand(1,3) * .15 - .075; + end end % Else: Get digitized point coordinates else @@ -1473,16 +2017,10 @@ function BytesAvailable_Callback(h, ev) %#ok end % Beep at each click AND not for headshape points if DigitizeOptions.isBeep - % Beep not working in compiled version, replacing with this: - if bst_iscompiled() && (Digitize.Mode ~= 8) - sound(Digitize.BeepWav(6000:2:16000,1), 22000); - else - beep on; - beep(); - end + sound(Digitize.BeepWav.data, Digitize.BeepWav.fs); end - % check for change in Mode (click at least 1 meter away from transmitter) - if any(abs(pointCoord) > 1.5) + % Check for change in Mode (click at least 1 meter away from transmitter) + if ~strcmpi(Digitize.Type, '3DScanner') && any(abs(pointCoord) > 1.5) newMode = Digitize.Mode +1; SwitchToNewMode(newMode); return; @@ -1501,7 +2039,7 @@ function BytesAvailable_Callback(h, ev) %#ok Digitize.Points.hpiN(iPoint,:) = (Digitize.Points.trans * [pointCoord 1]')'; PlotCoordinate(Digitize.Points.hpiN(iPoint,:), 'HPI-N', 'HPI', iPoint); else - % used to compute transform + % Used to compute transform Digitize.Points.hpiN(iPoint,:) = pointCoord; end SwitchToNewMode(2); @@ -1513,7 +2051,7 @@ function BytesAvailable_Callback(h, ev) %#ok Digitize.Points.hpiL(iPoint,:) = (Digitize.Points.trans * [pointCoord 1]')'; PlotCoordinate(Digitize.Points.hpiL(iPoint,:), 'HPI-L', 'HPI', iPoint); else - % used to compute transform + % Used to compute transform Digitize.Points.hpiL(iPoint,:) = pointCoord; end SwitchToNewMode(3); @@ -1525,7 +2063,7 @@ function BytesAvailable_Callback(h, ev) %#ok Digitize.Points.hpiR(iPoint,:) = (Digitize.Points.trans * [pointCoord 1]')'; PlotCoordinate(Digitize.Points.hpiR(iPoint,:), 'HPI-R', 'HPI', iPoint); else - % used to compute transform + % Used to compute transform Digitize.Points.hpiR(iPoint,:) = pointCoord; end % Check for multiple fiducial measurements @@ -1596,43 +2134,75 @@ function BytesAvailable_Callback(h, ev) %#ok % === EEG === case 7 - % find the index for the current point - iPoint = str2double(ctrl.jTextFieldEEG.getText()); - % Transform coordinate - Digitize.Points.EEG(iPoint,:) = (Digitize.Points.trans * [pointCoord 1]')'; + % Find the index for the current point + % ADD A CONDITION HERE THAT EDITS EXISTING EEG POINTS + if Digitize.isEditPts + [~, iSelCoord] = GetSelectedCoord(); + iPoint = iSelCoord - 3; + else + % ELSE DOES THE BELOW OF ADDING NEW POINTS + iPoint = str2double(ctrl.jTextFieldEEG.getText()); + end + if strcmpi(Digitize.Type, '3DScanner') + Digitize.Points.EEG(iPoint,:) = pointCoord; + else + % Transform coordinate + Digitize.Points.EEG(iPoint,:) = (Digitize.Points.trans * [pointCoord 1]')'; + end % Add the point to the display % Get current montage [curMontage, nEEG] = GetCurrentMontage(); - PlotCoordinate(Digitize.Points.EEG(iPoint,:), curMontage.Labels{iPoint}, 'EEG', iPoint) - % update text field counter to the next point in the list - nextPoint = max(size(Digitize.Points.EEG,1)+1, 1); - if nextPoint > nEEG - % all EEG points have been collected, switch to next mode - ctrl.jTextFieldEEG.setText(java.lang.String.valueOf(int16(nEEG))); - SwitchToNewMode(8); + Digitize.Points.Label{iPoint} = curMontage.Labels{iPoint}; + PlotCoordinate(Digitize.Points.EEG(iPoint,:), Digitize.Points.Label{iPoint}, 'EEG', iPoint) + + if Digitize.isEditPts + Digitize.isEditPts = 0; else - ctrl.jTextFieldEEG.setText(java.lang.String.valueOf(int16(nextPoint))); + % Update text field counter to the next point in the list + nextPoint = max(size(Digitize.Points.EEG,1)+1, 1); + if nextPoint > nEEG + % All EEG points have been collected, switch to next mode + ctrl.jTextFieldEEG.setText(java.lang.String.valueOf(int16(nEEG))); + SwitchToNewMode(8); + else + ctrl.jTextFieldEEG.setText(java.lang.String.valueOf(int16(nextPoint))); + end end % === EXTRA === case 8 - % find the index for the current point in the headshape points + % Find the index for the current point in the headshape points iPoint = str2double(ctrl.jTextFieldExtra.getText()); - % Transformed points_pen from original points_pen - Digitize.Points.headshape(iPoint,:) = (Digitize.Points.trans * [pointCoord 1]')'; - % add the point to the display (in cm) + if strcmpi(Digitize.Type, '3DScanner') + Digitize.Points.headshape(iPoint,:) = pointCoord; + else + % Transformed points_pen from original points_pen + Digitize.Points.headshape(iPoint,:) = (Digitize.Points.trans * [pointCoord 1]')'; + end + % Add the point to the display (in cm) PlotCoordinate(Digitize.Points.headshape(iPoint,:), 'EXTRA', 'EXTRA', iPoint) - % update text field counter to the next point in the list + % Update text field counter to the next point in the list nextPoint = iPoint+1; ctrl.jTextFieldExtra.setText(java.lang.String.valueOf(int16(nextPoint))); end % Update coordinates list UpdateList(); + % Enable 'Auto' button IFF all landmark fiducials have been acquired + if strcmpi(Digitize.Type, '3DScanner') && (Digitize.Mode ~= 8) + curMontage = GetCurrentMontage(); + eegCapLandmarkLabels = channel_detect_eegcap_auto('GetEegCapLandmarkLabels', curMontage.Name); + if ~isempty(eegCapLandmarkLabels) && ~isempty(Digitize.Points.EEG) + acqPointLabels = Digitize.Points.Label(1 : size(Digitize.Points.EEG, 1)); + if all(ismember([eegCapLandmarkLabels], acqPointLabels)) + ctrl.jButtonEEGAutoDetectElectrodes.setEnabled(1); + end + end + end end %% ===== MOTION COMPENSATION ===== function newPT = DoMotionCompensation(sensors) - % use sensor one and its orientation vectors as the new coordinate system + % Use sensor one and its orientation vectors as the new coordinate system % Define the origin as the position of sensor attached to the glasses. WAND = 1; REMOTE1 = 2; @@ -1673,7 +2243,7 @@ function BytesAvailable_Callback(h, ev) %#ok rotMat(4, 1:4) = 0; - %Translate and rotate the WAND into new coordinate system + % Translate and rotate the WAND into new coordinate system pt(1) = sensors(WAND,2) - C(1); pt(2) = sensors(WAND,3) - C(2); pt(3) = sensors(WAND,4) - C(3); @@ -1683,6 +2253,3 @@ function BytesAvailable_Callback(h, ev) %#ok newPT(3) = pt(1) * rotMat(3, 1) + pt(2) * rotMat(3, 2) + pt(3) * rotMat(3, 3)'+ rotMat(3, 4); end - - - diff --git a/toolbox/sensors/panel_digitize_2024.m b/toolbox/sensors/panel_digitize_2024.m index 15b802600..6777aef1a 100644 --- a/toolbox/sensors/panel_digitize_2024.m +++ b/toolbox/sensors/panel_digitize_2024.m @@ -1,5 +1,5 @@ function varargout = panel_digitize_2024(varargin) -% PANEL_DIGITIZE: Digitize EEG sensors and head shape. +% PANEL_DIGITIZE_2024: Digitize EEG sensors and head shape. % % USAGE: panel_digitize_2024('Start') % panel_digitize_2024('CreateSerialConnection') @@ -25,7 +25,9 @@ % For more information type "brainstorm license" at command prompt. % =============================================================================@ % -% Authors: Elizabeth Bock & Francois Tadel, 2012-2017, Marc Lalancette 2024 +% Authors: Elizabeth Bock & Francois Tadel, 2012-2017 +% Marc Lalancette, 2024 +% Chinmay Chinara, 2024 eval(macro_method); end @@ -36,51 +38,106 @@ % ======================================================================== %% ===== START ===== -function Start() - global Digitize; - % If no protocol: exit - if (bst_get('iProtocol') <= 0) - bst_error('Please create a protocol first.', 'Digitize', 0); - return; - end - % Get subject - SubjectName = 'Digitize'; - [sSubject, iSubject] = bst_get('Subject', SubjectName); - % Create if subject doesn't exist - if isempty(iSubject) - % Default anat / one channel file per subject - UseDefaultAnat = 1; - UseDefaultChannel = 0; - [sSubject, iSubject] = db_add_subject(SubjectName, iSubject, UseDefaultAnat, UseDefaultChannel); - % Update tree - panel_protocols('UpdateTree'); - end - +function Start(varargin) + global Digitize % Intialize global variable Digitize = struct(... 'Options', bst_get('DigitizeOptions'), ... + 'Type' , [], ... 'SerialConnection', [], ... 'Mode', 0, ... 'hFig', [], ... 'iDS', [], ... - 'SubjectName', SubjectName, ... + 'SubjectName', [], ... 'ConditionName', [], ... 'iStudy', [], ... 'BeepWav', [], ... + 'isEditPts', 0, ... % for correcting a wrongly detected point manually 'Points', struct(... 'Label', [], ... 'Type', [], ... 'Loc', []), ... 'iPoint', 0, ... 'Transf', []); + + % Fix old structure (bef 2024) for Digitize.Options.Montages + if length(Digitize.Options.Montages) > 1 && ~isfield(Digitize.Options.Montages, 'ChannelFile') + Digitize.Options.Montages(end).ChannelFile = []; + end - % Ask for subject id - Digitize.Options.PatientId = java_dialog('input', 'Please, enter subject ID:', 'Digitize', [], Digitize.Options.PatientId); - if isempty(Digitize.Options.PatientId) + % ===== PARSE INPUT ===== + DigitizerType = 'Digitize'; + sSubject = []; + iSubject = []; + surfaceFile = []; + if nargin > 0 && ~isempty(varargin{1}) + DigitizerType = varargin{1}; + end + if nargin > 1 && ~isempty(varargin{2}) + sSubject = varargin{2}; + end + if nargin > 2 && ~isempty(varargin{3}) + iSubject = varargin{3}; + end + if nargin > 3 && ~isempty(varargin{4}) + surfaceFile = varargin{4}; + end + Digitize.Type = DigitizerType; + switch DigitizerType + case 'Digitize' + % Do nothing + case '3DScanner' + % Simulate + SetSimulate(1); + otherwise + bst_error(sprintf('DigitizerType : "%s" is not supported', DigitizerType)); + return + end + + % ===== PREPARE DATABASE ===== + % If no protocol: exit + if (bst_get('iProtocol') <= 0) + bst_error('Please create a protocol first.', Digitize.Type, 0); return; end - % Save new ID - bst_set('DigitizeOptions', Digitize.Options); + + % Ask for subject id + if isempty(sSubject) + Digitize.Options.PatientId = java_dialog('input', 'Please, enter subject ID:', Digitize.Type, [], Digitize.Options.PatientId); + if isempty(Digitize.Options.PatientId) + return; + end + % Save new ID + bst_set('DigitizeOptions', Digitize.Options); + + % ===== GET SUBJECT ===== + % Save the new SubjectName + if strcmpi(Digitize.Type, '3DScanner') + Digitize.SubjectName = [Digitize.Type, '_', Digitize.Options.PatientId]; + else + Digitize.SubjectName = Digitize.Type; + end + + [sSubject, iSubject] = bst_get('Subject', Digitize.SubjectName); + else + Digitize.SubjectName = sSubject.Name; + end + + % Create if subject doesnt exist + if isempty(iSubject) + % Default anat / one channel file per subject + if strcmpi(Digitize.Type, '3DScanner') + [sSubject, iSubject] = db_add_subject(Digitize.SubjectName, iSubject); + sTemplates = bst_get('AnatomyDefaults'); + db_set_template(iSubject, sTemplates(1), 1); + else + UseDefaultAnat = 1; + UseDefaultChannel = 0; + [sSubject, iSubject] = db_add_subject(Digitize.SubjectName, iSubject, UseDefaultAnat, UseDefaultChannel); + end + % Update tree + panel_protocols('UpdateTree'); + end % ===== INITIALIZE CONNECTION ===== % Start Serial Connection @@ -89,21 +146,22 @@ function Start() end % ===== CREATE CONDITION ===== - % Get current date - CurrentDate = char(datetime('now'), 'yyyyMMdd'); + % Get current date/time +% CurrentDate = char(datetime('now'), 'yyyyMMdd'); + c = clock; % Condition name: PatientId_Date_Run for i = 1:99 % Generate new condition name - Digitize.ConditionName = sprintf('%s_%s_%02d', Digitize.Options.PatientId, CurrentDate, i); + Digitize.ConditionName = sprintf('%s_%02d%02d%02d_%02d', Digitize.Options.PatientId, c(1), c(2), c(3), i); % Get condition - sStudy = bst_get('StudyWithCondition', [SubjectName '/' Digitize.ConditionName]); + sStudy = bst_get('StudyWithCondition', [Digitize.SubjectName '/' Digitize.ConditionName]); % If condition doesn't exist: ok, keep this one if isempty(sStudy) break; end end % Create condition - Digitize.iStudy = db_add_condition(SubjectName, Digitize.ConditionName); + Digitize.iStudy = db_add_condition(Digitize.SubjectName, Digitize.ConditionName); sStudy = bst_get('Study', Digitize.iStudy); % Create an empty channel file in there ChannelMat = db_template('channelmat'); @@ -111,27 +169,77 @@ function Start() % Save new channel file ChannelFile = bst_fullfile(bst_fileparts(file_fullpath(sStudy.FileName)), ['channel_' Digitize.ConditionName '.mat']); bst_save(ChannelFile, ChannelMat, 'v7'); - % Reload condition (why?) + % Reload condition to update the functional nodes db_reload_studies(Digitize.iStudy); + if strcmpi(Digitize.Type, '3DScanner') + if isempty(surfaceFile) + % Import surface + iSurface = find(cellfun(@(x)~isempty(regexp(x, 'tess_textured', 'match')), {sSubject.Surface.FileName})); + if isempty(iSurface) + [~, surfaceFiles] = import_surfaces(iSubject); + if isempty(surfaceFiles) + return + end + surfaceFile = file_short(surfaceFiles{end}); + else + [res, isCancel] = java_dialog('question', ['There is already scanned mesh available for this subject.' 10 10 ... + 'What do you want to do?'], ... + 'Import surface', [], {'Use existing', 'Add new', 'Cancel'}, 'Use existing'); + if strcmpi(res, 'cancel') || isCancel + return + elseif strcmpi(res, 'use existing') + % If more than one surface present, user can choose + if length(iSurface) > 1 + texSurfComment = java_dialog('combo', 'Select the textured surface:

', 'Choose textured surface', [], {sSubject.Surface(iSurface).Comment}); + texSurfComment = strrep(texSurfComment, '_defaced', ''); + if isempty(texSurfComment) + return + end + iSurfFile = find(cellfun(@(x)~isempty(regexp(x, [texSurfComment '.mat'], 'match')), {sSubject.Surface.FileName})); + surfaceFile = sSubject.Surface(iSurfFile).FileName; + % If only one surface is present, then load it directly + else + surfaceFile = sSubject.Surface(iSurface(end)).FileName; + end + elseif strcmpi(res, 'add new') + % Import a new textured mesh and append it to the list + [~, surfaceFiles] = import_surfaces(iSubject); + if isempty(surfaceFiles) + return + end + surfaceFile = file_short(surfaceFiles{end}); + end + end + end + bst_progress('start', Digitize.Type, 'Loading surface file...'); + Digitize.surfaceFile = surfaceFile; + sSurf = bst_memory('LoadSurface', Digitize.surfaceFile); + % Display surface + view_surface_matrix(sSurf.Vertices, sSurf.Faces, [], sSurf.Color, [], [], Digitize.surfaceFile); + end + % ===== DISPLAY DIGITIZE WINDOW ===== % Display panel % Set the window to the position of the main Bst window, which is then hidden - panelContainer = gui_show('panel_digitize_2024', 'JavaWindow', 'Digitize', [], [], [], [], [0,0]); - % hard-coded size for now - panelContainer.handle{1}.setSize(600, 600); - + % Set window title to Digitize.Type + panelContainer = gui_show('panel_digitize_2024', 'JavaWindow', Digitize.Type, [], [], [], [], [0,0]); + % Hide Brainstorm window jBstFrame = bst_get('BstFrame'); jBstFrame.setVisible(0); drawnow; + % Hard-coded window size for now + panelContainer.handle{1}.setSize(600, 600); + % Set the window to the left of the screen + loc = panelContainer.handle{1}.getLocation(); + loc.x = 0; + panelContainer.handle{1}.setLocation(loc); + % Load beep sound - if bst_iscompiled() - wavfile = bst_fullfile(bst_get('BrainstormHomeDir'), 'toolbox', 'sensors', 'private', 'bst_beep_wav.mat'); - filemat = load(wavfile, 'wav'); - Digitize.BeepWav = filemat.wav; - end + wavfile = bst_fullfile(bst_get('BrainstormHomeDir'), 'toolbox', 'sensors', 'private', 'bst_beep.wav'); + [Digitize.BeepWav.data, Digitize.BeepWav.fs] = audioread(wavfile); % Reset collection ResetDataCollection(); @@ -144,6 +252,8 @@ function Start() %% ===== CREATE PANEL ===== function [bstPanelNew, panelName] = CreatePanel() + global Digitize + % Constants panelName = 'Digitize'; % Java initializations @@ -171,41 +281,50 @@ function Start() jLabelNews.setOpaque(true); jLabelNews.setBackground(java.awt.Color.yellow); - % File menu + % ===== FILE MENU ===== jMenu = gui_component('Menu', jMenuBar, [], 'File', [], [], [], []); gui_component('MenuItem', jMenu, [], 'Start over', IconLoader.ICON_RELOAD, [], @(h,ev)bst_call(@ResetDataCollection, 1), []); gui_component('MenuItem', jMenu, [], 'Edit settings...', IconLoader.ICON_EDIT, [], @(h,ev)bst_call(@EditSettings), []); gui_component('MenuItem', jMenu, [], 'Switch to Digitize "legacy"', [], [], @(h,ev)bst_call(@SwitchVersion), []); - gui_component('MenuItem', jMenu, [], 'Reset serial connection', IconLoader.ICON_FLIP, [], @(h,ev)bst_call(@CreateSerialConnection), []); + if ~strcmpi(Digitize.Type, '3DScanner') + gui_component('MenuItem', jMenu, [], 'Reset serial connection', IconLoader.ICON_FLIP, [], @(h,ev)bst_call(@CreateSerialConnection), []); + end jMenu.addSeparator(); - if exist('bst_headtracking', 'file') + if exist('bst_headtracking', 'file') && ~strcmpi(Digitize.Type, '3DScanner') gui_component('MenuItem', jMenu, [], 'Start head tracking', IconLoader.ICON_ALIGN_CHANNELS, [], @(h,ev)bst_call(@(h,ev)bst_headtracking([],1,1)), []); jMenu.addSeparator(); end gui_component('MenuItem', jMenu, [], 'Save as...', IconLoader.ICON_SAVE, [], @(h,ev)bst_call(@Save_Callback), []); gui_component('MenuItem', jMenu, [], 'Save in database and exit', IconLoader.ICON_RESET, [], @(h,ev)bst_call(@Close_Callback), []); - % EEG Montage menu + % ===== EEG MONTAGE MENU ===== jMenuEeg = gui_component('Menu', jMenuBar, [], 'EEG montage', [], [], [], []); CreateMontageMenu(jMenuEeg); - % Help menu + % ===== HELP MENU ===== jMenuHelp = gui_component('Menu', jMenuBar, [], 'Help', [], [], [], []); gui_component('MenuItem', jMenuHelp, [], 'Digitize tutorial', [], [], @(h,ev)web('https://neuroimage.usc.edu/brainstorm/Tutorials/TutDigitize', '-browser'), []); jPanelNew.add(jPanelMenu, BorderLayout.NORTH); - % ===== Control Panel ===== + % ===== CONTROL PANEL ===== jPanelControl = gui_component('panel'); jPanelControl.setBorder(BorderFactory.createEmptyBorder(0,0,7,0)); - % ===== Next point Panel ===== + % ===== NEXT POINT PANEL ===== jPanelNext = gui_river([5,4], [4,4,4,4], 'Next point'); % Next point label jLabelNextPoint = gui_component('label', jPanelNext, [], '', [], [], [], veryLargeFontSize); jButtonFids = gui_component('button', jPanelNext, 'br', 'Add fiducials', [], 'Add set of fiducials to digitize', @(h,ev)bst_call(@Fiducials_Callback)); jButtonFids.setEnabled(0); + if strcmpi(Digitize.Type, '3DScanner') + jButtonEEGAutoDetectElectrodes = gui_component('button', jPanelNext, [], 'Auto', [], GenerateTooltipTextAuto(), @(h,ev)bst_call(@EEGAutoDetectElectrodes)); + else + % Separator + jButtonEEGAutoDetectElectrodes = gui_component('label', jPanelNext, 'hfill', ''); + end + jButtonEEGAutoDetectElectrodes.setEnabled(0); jPanelControl.add(jPanelNext, BorderLayout.NORTH); - % ===== Info Panel ===== + % ===== INFO PANEL ===== jPanelInfo = gui_river([5,4], [10,10,10,10], ''); % Message label jLabelWarning = gui_component('label', jPanelInfo, 'br', ' ', [], [], [], largeFontSize); @@ -215,11 +334,24 @@ function Start() jTextFieldExtra = gui_component('text', jPanelInfo, [], '0', [], 'Head shape points digitized', @(h,ev)bst_call(@ExtraChangePoint_Callback), largeFontSize); initSize = jTextFieldExtra.getPreferredSize(); jTextFieldExtra.setPreferredSize(Dimension(initSize.getWidth()*1.5, initSize.getHeight()*1.5)) + if strcmpi(Digitize.Type, '3DScanner') + % Add 150 random head shape points generation button + jButtonRandomHeadPts = gui_component('button', jPanelInfo, [], 'Random', [], 'Collect 150 head shape points from mesh', @(h,ev)bst_call(@CollectRandomHeadPts_Callback), largeFontSize); + jButtonRandomHeadPts.setPreferredSize(Dimension(initSize.getWidth()*2.2, initSize.getHeight()*1.7)); + else + % Separator + jButtonRandomHeadPts = gui_component('label', jPanelInfo, 'hfill', ''); + end + jButtonRandomHeadPts.setEnabled(0); jPanelControl.add(jPanelInfo, BorderLayout.CENTER); - % ===== Other buttons ===== + % ===== OTHER BUTTONS ===== jPanelMisc = gui_river([5,4], [10,4,4,4]); - jButtonCollectPoint = gui_component('button', jPanelMisc, 'br', 'Collect point', [], [], @(h,ev)bst_call(@ManualCollect_Callback)); + if ~strcmpi(Digitize.Type, '3DScanner') + jButtonCollectPoint = gui_component('button', jPanelMisc, 'br', 'Collect point', [], [], @(h,ev)bst_call(@ManualCollect_Callback)); + else + jButtonCollectPoint = gui_component('label', jPanelMisc, 'hfill', ''); % spacing + end % Until initial fids are collected and figure displayed, "delete" button is used to "restart". jButtonDeletePoint = gui_component('button', jPanelMisc, [], 'Start over', [], [], @(h,ev)bst_call(@ResetDataCollection, 1)); gui_component('label', jPanelMisc, 'hfill', ''); % spacing @@ -227,12 +359,15 @@ function Start() jPanelControl.add(jPanelMisc, BorderLayout.SOUTH); jPanelNew.add(jPanelControl, BorderLayout.WEST); - % ===== Coordinate Display Panel ===== + % ===== COORDINATE DISPLAY PANEL ===== jPanelDisplay = gui_component('Panel'); jPanelDisplay.setBorder(java_scaled('titledborder', 'Coordinates (cm)')); % List of coordinates jListCoord = JList(fontSize); jListCoord.setCellRenderer(BstStringListRenderer(fontSize)); + java_setcb(jListCoord, ... + 'KeyTypedCallback', @(h,ev)bst_call(@CoordListKeyTyped_Callback,h,ev), ... + 'MouseClickedCallback', @(h,ev)bst_call(@CoordListClick_Callback,h,ev)); jPanelScrollList = JScrollPane(jListCoord); jPanelScrollList.setViewportView(jListCoord); jPanelScrollList.setHorizontalScrollBarPolicy(jPanelScrollList.HORIZONTAL_SCROLLBAR_NEVER); @@ -241,19 +376,82 @@ function Start() jPanelDisplay.add(jPanelScrollList, BorderLayout.CENTER); jPanelNew.add(jPanelDisplay, BorderLayout.CENTER); - % create the controls structure - ctrl = struct('jMenuEeg', jMenuEeg, ... - 'jButtonFids', jButtonFids, ... - 'jLabelNextPoint', jLabelNextPoint, ... - 'jLabelWarning', jLabelWarning, ... - 'jListCoord', jListCoord, ... - 'jTextFieldExtra', jTextFieldExtra, ... - 'jButtonCollectPoint', jButtonCollectPoint, ... - 'jButtonDeletePoint', jButtonDeletePoint); + % Create the controls structure + ctrl = struct('jMenuEeg', jMenuEeg, ... + 'jButtonFids', jButtonFids, ... + 'jLabelNextPoint', jLabelNextPoint, ... + 'jLabelWarning', jLabelWarning, ... + 'jListCoord', jListCoord, ... + 'jButtonEEGAutoDetectElectrodes', jButtonEEGAutoDetectElectrodes, ... + 'jButtonRandomHeadPts', jButtonRandomHeadPts, ... + 'jTextFieldExtra', jTextFieldExtra, ... + 'jButtonCollectPoint', jButtonCollectPoint, ... + 'jButtonDeletePoint', jButtonDeletePoint); bstPanelNew = BstPanel(panelName, jPanelNew, ctrl); + + %% ================================================================================= + % === INTERNAL CALLBACKS ========================================================= + % ================================================================================= + %% ===== COORDINATE LIST KEY TYPED CALLBACK ===== + function CoordListKeyTyped_Callback(h, ev) + switch(uint8(ev.getKeyChar())) + % Delete + case {ev.VK_DELETE, ev.VK_BACK_SPACE} + ctrl = bst_get('PanelControls', 'Digitize'); + % If contact list rendering is blank in panel then dont't proceed + if ctrl.jListCoord.isSelectionEmpty() + return; + end + + [sCoordName, iSelCoord] = GetSelectedCoord(); + spl = regexp(sCoordName,'\s+','split'); + nameFinal = spl{1}; + if (~strcmpi(nameFinal, 'NAS') &&... + ~strcmpi(nameFinal, 'LPA') &&... + ~strcmpi(nameFinal, 'RPA')) + listModel = ctrl.jListCoord.getModel(); + listModel.setElementAt(nameFinal, iSelCoord-1); + Digitize.iPoint = iSelCoord; + Digitize.isEditPts = 1; + DeletePoint_Callback(); + end + end + end + + %% ===== COORDINATE LIST CLICK CALLBACK ===== + function CoordListClick_Callback(h, ev) + % If single click + if (ev.getClickCount() == 1) + ctrl = bst_get('PanelControls', 'Digitize'); + % If contact list rendering is blank in panel then dont't proceed + if ctrl.jListCoord.isSelectionEmpty() + return; + end + + [sCoordName, ~] = GetSelectedCoord(); + spl = regexp(sCoordName,'\s+','split'); + nameFinal = spl{1}; + bst_figures('SetSelectedRows', nameFinal); + end + end end -%% ===== SWITCH to old version ===== +%% ===== GET SELECTED ELECTRODE ===== +function [sCoordName, iSelCoord] = GetSelectedCoord() + global Digitize + % Get panel handles + ctrl = bst_get('PanelControls', 'Digitize'); + if isempty(ctrl) + return; + end + + % Get JList selected indices + iSelCoord = uint16(ctrl.jListCoord.getSelectedIndices())' + 1; + listModel = ctrl.jListCoord.getModel(); + sCoordName = listModel.getElementAt(iSelCoord-1); +end + +%% ===== SWITCH TO OLD VERSION ===== function SwitchVersion() % Always confirm this switch. if ~java_dialog('confirm', ['Switch to legacy version of the Digitize panel?
', ... @@ -315,7 +513,7 @@ function Close_Callback() %% ===== EDIT SETTINGS ===== function isOk = EditSettings() global Digitize - %Digitize.Options = bst_get('DigitizeOptions'); + isOk = 0; % Ask for new options if isfield(Digitize.Options, 'Fids') && iscell(Digitize.Options.Fids) @@ -331,66 +529,95 @@ function Close_Callback() else ConfigString = ''; end - [res, isCancel] = java_dialog('input', ... - {'Serial connection settings

Serial port name (COM1):', ... - 'Unit Type (Fastrak or Patriot):', ... - 'Additional device configuration commands, separated by ";"
(see device documentation, e.g. H1,0,0,-1;H2,0,0,-1):', ... - '
Collection settings

List anatomy and possibly MEG fiducials, in desired order
(NAS, LPA, RPA, HPI-N, HPI-L, HPI-R, HPI-X):', ... - 'How many times do you want to localize
these fiducials at the start:', ... - 'Distance threshold for repeated measure of fiducial locations (mm):', ... - 'Beep when collecting point (0=no, 1=yes):'}, ... - 'Digitizer configuration', [], ... - {Digitize.Options.ComPort, ... - Digitize.Options.UnitType, ... - ConfigString, ... - FidsString, ... - num2str(Digitize.Options.nFidSets), ... - num2str(Digitize.Options.DistThresh * 1000), ... % m to mm - num2str(Digitize.Options.isBeep)}); - if isempty(res) || isCancel + + % GUI all potential options: {Description, default values} + options_all = {'Serial connection settings

Serial port name (COM1):', ... + Digitize.Options.ComPort; + 'Unit Type (Fastrak or Patriot):', ... + Digitize.Options.UnitType; ... + 'Additional device configuration commands, separated by ";"
(see device documentation, e.g. H1,0,0,-1;H2,0,0,-1):', ... + ConfigString; ... + '
Collection settings

List anatomy and possibly MEG fiducials, in desired order
(NAS, LPA, RPA, HPI-N, HPI-L, HPI-R, HPI-X):', ... + FidsString; ... + 'How many times do you want to localize
these fiducials at the start:', ... + num2str(Digitize.Options.nFidSets); ... + 'Distance threshold for repeated measure of fiducial locations (mm):', ... + num2str(Digitize.Options.DistThresh * 1000); ... % m to mm + 'Beep when collecting point (0=no, 1=yes):', ...; + num2str(Digitize.Options.isBeep)}; + + % Options to show for each type + switch lower(Digitize.Type) + case 'digitize' + iOptionsType = [1:7]; + case '3dscanner' + iOptionsType = [4:7]; + end + + % Ask options + [resType, isCancel] = java_dialog('input', options_all(iOptionsType,1), [Digitize.Type ' configuration'], [], options_all(iOptionsType,2)); + if isempty(resType) || isCancel return end + + % Results from options asked to user + options_all(iOptionsType, 3) = resType; + % Results from options not asked to user + iOptionsKeep = setdiff([1:length(options_all)], iOptionsType); + options_all(iOptionsKeep, 3) = options_all(iOptionsKeep,2); + res = options_all(:,3); + % Check values - if (length(res) < 6) || isempty(res{1}) || isempty(res{2}) || isnan(str2double(res{5})) || isnan(str2double(res{6})) || ~ismember(str2double(res{7}), [0 1]) - bst_error('Invalid values.', 'Digitize', 0); + if length(resType) < length(iOptionsType) || (ismember(1, iOptionsType) && isempty(res{1})) || ... + (ismember(2, iOptionsType) && isempty(res{2})) || ... + (ismember(5, iOptionsType) && isnan(str2double(res{5}))) || ... + (ismember(6, iOptionsType) && isnan(str2double(res{6}))) || ... + (ismember(7, iOptionsType) && ~ismember(str2double(res{7}), [0 1])) + bst_error('Invalid values.', 'Digitize', 0); return; end - % Get entered values, keep defaults for some if empty + % Digitizer: COM port Digitize.Options.ComPort = res{1}; + % Digitizer: Type Digitize.Options.UnitType = lower(res{2}); - if ~isempty(res{5}) - Digitize.Options.nFidSets = str2double(res{5}); - end - if ~isempty(res{6}) - Digitize.Options.DistThresh = str2double(res{6}) / 1000; % mm to m - end - if ~isempty(res{7}) - Digitize.Options.isBeep = str2double(res{7}); - end - % Parse device configuration commands. Remove all spaces, and split at ";" - Digitize.Options.ConfigCommands = str_split(strrep(res{3}, ' ', ''), ';', true); % remove empty - % Device type - if strcmp(Digitize.Options.UnitType,'fastrak') + % Digitizer: COM properties + if isempty(Digitize.Options.UnitType) + % Do nothing + elseif strcmp(Digitize.Options.UnitType,'fastrak') Digitize.Options.ComRate = 9600; Digitize.Options.ComByteCount = 94; elseif strcmp(Digitize.Options.UnitType,'patriot') Digitize.Options.ComRate = 115200; Digitize.Options.ComByteCount = 120; else - bst_error('Incorrect unit type.', 'Digitize', 0); + bst_error('Incorrect unit type.', Digitize.Type, 0); return; end - - % Parse and validate fiducials. - Digitize.Options.Fids = str_split(res{4}, '()[],;"'' ', true); % remove empty + % Digitizer: Parse device configuration commands. Remove all spaces, and split at ";" + Digitize.Options.ConfigCommands = str_split(strrep(res{3}, ' ', ''), ';', true); % remove empty + % Common: Parse fiducials. + Digitize.Options.Fids = str_split(res{4}, '()[],;"'' ', true); % remove empty if isempty(Digitize.Options.Fids) || ~iscell(Digitize.Options.Fids) || numel(Digitize.Options.Fids) < 3 - bst_error('At least 3 anatomy fiducials are required, e.g. NAS, LPA, RPA.', 'Digitize', 0); + bst_error('At least 3 anatomy fiducials are required, e.g. NAS, LPA, RPA.', Digitize.Type, 0); Digitize.Options.Fids = {'NAS', 'LPA', 'RPA'}; return; end + % Common: Number of fiducial sets + if ~isempty(res{5}) + Digitize.Options.nFidSets = str2double(res{5}); + end + % Common: Threshold for distance in fiducial sets + if ~isempty(res{6}) + Digitize.Options.DistThresh = str2double(res{6}) / 1000; % mm to m + end + % Common: Beep + if ~isempty(res{7}) + Digitize.Options.isBeep = str2double(res{7}); + end + for iFid = 1:numel(Digitize.Options.Fids) switch lower(Digitize.Options.Fids{iFid}) - % possible names copied from channel_detect_type + % Possible names copied from channel_detect_type case {'nas', 'nasion', 'nz', 'fidnas', 'fidnz', 'n', 'na'} Digitize.Options.Fids{iFid} = 'NAS'; case {'lpa', 'pal', 'og', 'left', 'fidt9', 'leftear', 'l'} @@ -399,7 +626,7 @@ function Close_Callback() Digitize.Options.Fids{iFid} = 'RPA'; otherwise if ~strfind(lower(Digitize.Options.Fids{iFid}), 'hpi') - bst_error(sprintf('Unrecognized fiducial: %s', Digitize.Options.Fids{iFid}), 'Digitize', 0); + bst_error(sprintf('Unrecognized fiducial: %s', Digitize.Options.Fids{iFid}), Digitize.Type, 0); return; end Digitize.Options.Fids{iFid} = upper(Digitize.Options.Fids{iFid}); @@ -435,7 +662,7 @@ function SetSimulate(isSimulate) %% ===== RESET DATA COLLECTION ===== function ResetDataCollection(isResetSerial) global Digitize - bst_progress('start', 'Digitize', 'Initializing...'); + bst_progress('start', Digitize.Type, 'Initializing...'); % Reset serial? if (nargin == 1) && isequal(isResetSerial, 1) CreateSerialConnection(); @@ -449,8 +676,15 @@ function ResetDataCollection(isResetSerial) Digitize.Transf = []; % Reset figure (also unloads in global data) if ~isempty(Digitize.hFig) && ishandle(Digitize.hFig) - %close(Digitize.hFig); bst_figures('DeleteFigure', Digitize.hFig, []); + + % For 3D Scanner reload the surface + if strcmpi(Digitize.Type, '3DScanner') + % Load the surface + sSurf = bst_memory('LoadSurface', Digitize.surfaceFile); + % Display surface + view_surface_matrix(sSurf.Vertices, sSurf.Faces, [], sSurf.Color, [], [], Digitize.surfaceFile); + end end Digitize.iDS = []; @@ -482,12 +716,13 @@ function ResetDataCollection(isResetSerial) % Display list in text box UpdateList(); + % Close progress bar bst_progress('stop'); end -%% ===== UPDATE LIST of points in text box ===== +%% ===== UPDATE LIST OF POINTS IN TEXT BOX ===== function UpdateList() global Digitize; % Get controls @@ -500,7 +735,7 @@ function UpdateList() for iP = 1:numel(Digitize.Points) if ~isempty(Digitize.Points(iP).Label) listModel.addElement(sprintf('%s %3.3f %3.3f %3.3f', Digitize.Points(iP).Label, Digitize.Points(iP).Loc .* 100)); - else % head points + else % Head points iHeadPoints = iHeadPoints + 1; listModel.addElement(sprintf('%03d %3.3f %3.3f %3.3f', iHeadPoints, Digitize.Points(iP).Loc .* 100)); end @@ -522,8 +757,77 @@ function UpdateList() else ctrl.jLabelNextPoint.setText(num2str(iHeadPoints + 1)); end + + % Update tooltip text for 'Auto' button + if strcmpi(Digitize.Type, '3DScanner') + ctrl.jButtonEEGAutoDetectElectrodes.setToolTipText(GenerateTooltipTextAuto()); + end end +%% ===== 3DSCANNER: AUTOMATICALLY DETECT AND LABEL EEG CAP ELECTRODES ===== +function EEGAutoDetectElectrodes() + global Digitize GlobalData + + % Add disclaimer to users that 'Auto' feature is experimental + if ~java_dialog('confirm', [' Automatic detection of EEG sensors is an experimental feature.
' ... + 'Please verify the results carefully.

' ... + 'Do you want to continue?'], 'Auto detect EEG electrodes') + return + end + % Get controls + ctrl = bst_get('PanelControls', 'Digitize'); + % Disable Auto button + ctrl.jButtonEEGAutoDetectElectrodes.setEnabled(0); + % Progress bar + bst_progress('start', Digitize.Type, 'Automatic labelling of EEG sensors...'); + + % Get current montage + curMontage = GetCurrentMontage(); + isWhiteCap = 0; + % For white caps change the color space by inverting the colors + % NOTE: only 'Acticap' is the tested white cap (needs work on finding a better aprrooach) + if ~isempty(regexp(curMontage.Name, 'ActiCap', 'match')) + isWhiteCap = 1; + end + + % Get the cap surface from 3D scanner + hFig = bst_figures('GetCurrentFigure','3D'); + TessInfo = getappdata(hFig, 'Surface'); + sSurf = bst_memory('LoadSurface', TessInfo.SurfaceFile); + + % Automatically find electrodes locations on EEG cap + [capCenters2d, capImg2d, surface3dscannerUv] = channel_detect_eegcap_auto('FindElectrodesEegCap', sSurf, isWhiteCap); + if isempty(Digitize.Options.Montages(Digitize.Options.iMontage).ChannelFile) + bst_error('EEG cap layout not selected. Go to EEG', Digitize.Type, 1); + bst_progress('stop'); + return; + else + ChannelMat = in_bst_channel(Digitize.Options.Montages(Digitize.Options.iMontage).ChannelFile); + end + + % Get acquired EEG points + iEeg = and(cellfun(@(x) ~isempty(regexp(x, 'EEG', 'match')), {Digitize.Points.Type}), ~cellfun(@isempty, {Digitize.Points.Loc})); + pointsEEG = Digitize.Points(iEeg); + + % Warp points from layout to mesh + capPoints3d = channel_detect_eegcap_auto('WarpLayout2Mesh', capCenters2d, capImg2d, surface3dscannerUv, ChannelMat.Channel, pointsEEG); + + % Plot the electrodes and their labels + for iPoint= 1:length(capPoints3d) + % Find found point in current montage and set it in global + [~, Digitize.iPoint] = ismember(capPoints3d(iPoint).Label, {Digitize.Points.Label}); + Digitize.Points(Digitize.iPoint).Loc = capPoints3d(iPoint).Loc; + Digitize.Points(Digitize.iPoint).Type = 'EEG'; + % Add the point to the display (in cm) + PlotCoordinate(); + end + + UpdateList(); + % Enable Random button + ctrl.jButtonRandomHeadPts.setEnabled(1); + bst_progress('stop'); + +end %% ===== MANUAL COLLECT CALLBACK ====== function ManualCollect_Callback() @@ -542,6 +846,88 @@ function ManualCollect_Callback() ctrl.jButtonCollectPoint.setEnabled(1); end +%% ===== COLLECT RANDOM HEADPOINTS ===== +function CollectRandomHeadPts_Callback() + global Digitize + % Get controls + ctrl = bst_get('PanelControls', 'Digitize'); + % Disable Random button + ctrl.jButtonRandomHeadPts.setEnabled(0); + % Progress bar + bst_progress('start', Digitize.Type, 'Plotting 150 random head shape points...'); + + hFig = bst_figures('GetCurrentFigure','3D'); + TessInfo = getappdata(hFig, 'Surface'); + TessMat = bst_memory('LoadSurface', TessInfo.SurfaceFile); + + % Brainstorm recommends to collect approximately 100-150 points from the head + % 5-10 points from the boney part of the nose + PlotHeadShapePoints(TessMat.Vertices, 'nose', 10); + % 10-20 points across the left eyebrow + PlotHeadShapePoints(TessMat.Vertices, 'leyebrow', 20); + % 10-20 points across the right eyebrow + PlotHeadShapePoints(TessMat.Vertices, 'reyebrow', 20); + % 100 points on the scalp + PlotHeadShapePoints(TessMat.Vertices, 'scalp', 100); + + UpdateList(); + bst_progress('stop'); +end + +%% ===== PLOT HEAD SHAPE POINTS ===== +function PlotHeadShapePoints(Vertices, plotRegion, nPoints) + global Digitize + % Get controls + ctrl = bst_get('PanelControls', 'Digitize'); + + % Get the plotting parameters based on the region in the head + switch plotRegion + case 'nose' + nosePoint = Digitize.Points(1).Loc; + % Get 600 nearest points to the 'nosePoint' and choose 'nPoints' from it + nearPointsIdx = bst_nearest(Vertices, nosePoint, 600, 0, []); + range = length(nearPointsIdx); + stepFactor = range/nPoints; + case 'leyebrow' + lEyebrowPoint = (1.25 * Digitize.Points(1).Loc) + (0.5 * Digitize.Points(2).Loc); + % Get 400 nearest points to the 'lEyebrowPoint' and choose 'nPoints' from it + nearPointsIdx = bst_nearest(Vertices, lEyebrowPoint, 400, 0, []); + range = length(nearPointsIdx); + stepFactor = range/nPoints; + case 'reyebrow' + rEyebrowPoint = (1.25 * Digitize.Points(1).Loc) + (0.5 * Digitize.Points(3).Loc); + % Get 400 nearest points to the 'rEyebrowPoint' and choose 'nPoints' from it + nearPointsIdx = bst_nearest(Vertices, rEyebrowPoint, 400, 0, []); + range = length(nearPointsIdx); + stepFactor = range/nPoints; + case 'scalp' + range = length(Vertices); + stepFactor = ceil(range/nPoints); + otherwise + bst_error([plotRegion 'is invalid for plotting head shape'], Digitize.Type, 0); + bst_progress('stop'); + return + end + + % Plot the head shape points + for i= 1:stepFactor:range + % Increment current point index + Digitize.iPoint = Digitize.iPoint + 1; + % Update the coordinate and Type + if strcmpi(plotRegion, 'scalp') + Digitize.Points(Digitize.iPoint).Loc = Vertices(i, :); + else + Digitize.Points(Digitize.iPoint).Loc = Vertices(nearPointsIdx(i), :); + end + Digitize.Points(Digitize.iPoint).Type = 'EXTRA'; + % Add the point to the display (in cm) + PlotCoordinate(); + % Update text field counter to the next point in the list + iCount = str2double(ctrl.jTextFieldExtra.getText()); + ctrl.jTextFieldExtra.setText(num2str(iCount + 1)); + end +end + %% ===== DELETE POINT CALLBACK ===== function DeletePoint_Callback() global Digitize @@ -579,10 +965,9 @@ function DeletePoint_Callback() % Update coordinates list UpdateList(); - end -%% ===== Check fiducials: add set to digitize now ===== +%% ===== CHECK FIDUCIALS: ADD SET TO DIGITIZE NOW ===== function Fiducials_Callback() global Digitize nRemaining = numel(Digitize.Points) - Digitize.iPoint; @@ -609,11 +994,47 @@ function CreateHeadpointsFigure() % Hide head surface panel_surface('SetSurfaceTransparency', Digitize.hFig, 1, 0.8); % Get Digitizer JFrame - bstContainer = get(bst_get('Panel','Digitize'), 'container'); + bstContainer = get(bst_get('Panel', 'Digitize'), 'container'); + % Get maximum figure position + decorationSize = bst_get('DecorationSize'); + [~, FigArea] = gui_layout('GetScreenBrainstormAreas', bstContainer.handle{1}); + FigPos = FigArea(1,:) + [decorationSize(1), decorationSize(4), - decorationSize(1) - decorationSize(3), - decorationSize(2) - decorationSize(4)]; + if (FigPos(3) > 0) && (FigPos(4) > 0) + set(Digitize.hFig, 'Position', FigPos); + end + % Remove the close handle function + set(Digitize.hFig, 'CloseRequestFcn', []); + else + % Hide figure + set(Digitize.hFig, 'Visible', 'off'); + % Get study + sStudy = bst_get('StudyWithCondition', [Digitize.SubjectName '/' Digitize.ConditionName]); + % Plot head points and save handles in global variable + [Digitize.hFig, Digitize.iDS] = view_headpoints(file_fullpath(sStudy.Channel.FileName)); + % Get the surface + sSurf = bst_memory('LoadSurface', Digitize.surfaceFile); + % Apply the transformation + sSurf.Vertices = [sSurf.Vertices ones(size(sSurf.Vertices,1),1)] * Digitize.Transf'; + % Remove the surface + panel_surface('RemoveSurface', Digitize.hFig, 1); + % Deface the surface + if isempty(regexp(sSurf.Comment, 'defaced', 'match')) + sSurf = tess_deface(sSurf); + end + % Save the surface and update the node + ProtocolInfo = bst_get('ProtocolInfo'); + surfaceFile = bst_fullfile(ProtocolInfo.SUBJECTS, Digitize.surfaceFile); + bst_save(surfaceFile, sSurf, 'v7'); + [~, iSubject] = bst_get('Subject', Digitize.SubjectName); + db_reload_subjects(iSubject); + % Get Digitizer JFrame + bstContainer = get(bst_get('Panel', 'Digitize'), 'container'); % Get maximum figure position decorationSize = bst_get('DecorationSize'); - [jBstArea, FigArea] = gui_layout('GetScreenBrainstormAreas', bstContainer.handle{1}); + [~, FigArea] = gui_layout('GetScreenBrainstormAreas', bstContainer.handle{1}); FigPos = FigArea(1,:) + [decorationSize(1), decorationSize(4), - decorationSize(1) - decorationSize(3), - decorationSize(2) - decorationSize(4)]; + % Display updated surface + view_surface_matrix(sSurf.Vertices, sSurf.Faces, [], sSurf.Color, Digitize.hFig, [], Digitize.surfaceFile); if (FigPos(3) > 0) && (FigPos(4) > 0) set(Digitize.hFig, 'Position', FigPos); end @@ -622,8 +1043,8 @@ function CreateHeadpointsFigure() end end -%% ===== PLOT next point, or remove last ===== -function PlotCoordinate(isAdd) %(Loc, Label, Type, iPoint) +%% ===== PLOT NEXT POINT, OR REMOVE LAST OR REMOVE SELECTED POINT ===== +function PlotCoordinate(isAdd) if nargin < 1 || isempty(isAdd) isAdd = true; end @@ -639,26 +1060,42 @@ function PlotCoordinate(isAdd) %(Loc, Label, Type, iPoint) % Overwrite empty channel created by template. iP = 1; else - iP = numel(GlobalData.DataSet(Digitize.iDS).Channel) + 1; + if Digitize.isEditPts + % 'iP' points to the 'GlobalData's Channel' which just contains + % EEG data and not the fiducials so an offset is required + % from 'Digitize.iPoint' to exclude the fiducials + if isAdd + iP = Digitize.iPoint - 3; + else + iP = Digitize.iPoint - 2; + end + else + iP = numel(GlobalData.DataSet(Digitize.iDS).Channel) + 1; + end end - if isAdd + + if isAdd GlobalData.DataSet(Digitize.iDS).Channel(iP).Name = Digitize.Points(Digitize.iPoint).Label; GlobalData.DataSet(Digitize.iDS).Channel(iP).Type = Digitize.Points(Digitize.iPoint).Type; % 'EEG' GlobalData.DataSet(Digitize.iDS).Channel(iP).Loc = Digitize.Points(Digitize.iPoint).Loc'; - else % Remove last point + else % Remove last point or a selected point iP = iP - 1; if iP > 0 - GlobalData.DataSet(Digitize.iDS).Channel(iP) = []; + if Digitize.isEditPts % remove selected point + % Keep point in list, but remove location + GlobalData.DataSet(Digitize.iDS).Channel(iP).Loc = []; + else % Remove last point + GlobalData.DataSet(Digitize.iDS).Channel(iP) = []; + end end end - else % fids or head points + else % FIDs or head points iP = size(GlobalData.DataSet(Digitize.iDS).HeadPoints.Loc, 2) + 1; if isAdd GlobalData.DataSet(Digitize.iDS).HeadPoints.Label{iP} = Digitize.Points(Digitize.iPoint).Label; GlobalData.DataSet(Digitize.iDS).HeadPoints.Type{iP} = Digitize.Points(Digitize.iPoint).Type; % 'CARDINAL' or 'EXTRA' GlobalData.DataSet(Digitize.iDS).HeadPoints.Loc(:,iP) = Digitize.Points(Digitize.iPoint).Loc'; else - iP = iP - 1; if iP > 0 GlobalData.DataSet(Digitize.iDS).HeadPoints.Label(iP) = []; GlobalData.DataSet(Digitize.iDS).HeadPoints.Type(iP) = []; @@ -687,7 +1124,9 @@ function PlotCoordinate(isAdd) %(Loc, Label, Type, iPoint) figure_3d('ViewSensors', Digitize.hFig, 1, 1, 0, 'EEG'); end % Hide template head surface - panel_surface('SetSurfaceTransparency', Digitize.hFig, 1, 1); + if ~strcmpi(Digitize.Type, '3DScanner') + panel_surface('SetSurfaceTransparency', Digitize.hFig, 1, 1); + end end %% ===== SAVE CALLBACK ===== @@ -710,7 +1149,7 @@ function Save_Callback(OutFile) end end -%% ===== Save channel file with contents of points list ===== +%% ===== SAVE CHANNEL FILE WITH CONTENTS OF POINTS LIST ===== function SaveDigitizeChannelFile() global Digitize sStudy = bst_get('StudyWithCondition', [Digitize.SubjectName '/' Digitize.ConditionName]); @@ -736,7 +1175,7 @@ function SaveDigitizeChannelFile() ChannelMat.Channel(iChan).Name = Digitize.Points(iP).Label; ChannelMat.Channel(iChan).Type = Digitize.Points(iP).Type; ChannelMat.Channel(:,iChan).Loc = Digitize.Points(iP).Loc'; - else % head points, including fiducials + else % Head points, including fiducials iHead = iHead + 1; ChannelMat.HeadPoints.Loc(:,iHead) = Digitize.Points(iP).Loc'; ChannelMat.HeadPoints.Label{iHead} = Digitize.Points(iP).Label; @@ -748,7 +1187,9 @@ function SaveDigitizeChannelFile() %% ===== CREATE MONTAGE MENU ===== function CreateMontageMenu(jMenu) + import org.brainstorm.icon.*; global Digitize + % Get menu pointer if not in argument if (nargin < 1) || isempty(jMenu) ctrl = bst_get('PanelControls', 'Digitize'); @@ -770,11 +1211,20 @@ function CreateMontageMenu(jMenu) end % Add new montage / reset list jMenu.addSeparator(); - gui_component('MenuItem', jMenu, [], 'Add EEG montage...', [], [], @(h,ev)bst_call(@AddMontage), []); + + if strcmpi(Digitize.Type, '3DScanner') + jMenuAddMontage = gui_component('Menu', jMenu, [], 'Add EEG montage...', [], [], [], []); + gui_component('MenuItem', jMenuAddMontage, [], 'From file...', [], [], @(h,ev)bst_call(@AddMontage), []); + % Creating montages from EEG cap layout mat files (only for 3DScanner) + jMenuEegCaps = gui_component('Menu', jMenuAddMontage, [], 'From default EEG cap', IconLoader.ICON_CHANNEL, [], [], []); + % Use default channel file + menu_default_eegcaps(jMenuEegCaps); + else % If not 3DScanner + gui_component('MenuItem', jMenu, [], 'Add EEG montage...', [], [], @(h,ev)bst_call(@AddMontage), []); + end gui_component('MenuItem', jMenu, [], 'Unload all montages', [], [], @(h,ev)bst_call(@UnloadAllMontages), []); end - %% ===== SELECT MONTAGE ===== function SelectMontage(iMontage) global Digitize @@ -814,6 +1264,19 @@ function SelectMontage(iMontage) ResetDataCollection(); end +%% ===== TOOLTIP TEXT FOR AUTO BUTTON ===== +function autoButtonTooltip = GenerateTooltipTextAuto() + global Digitize + % Get cap landmark labels for selected montage + eegCapLandmarkLabels = channel_detect_eegcap_auto('GetEegCapLandmarkLabels', Digitize.Options.Montages(Digitize.Options.iMontage).Name); + autoButtonTooltip = 'Auto localization of EEG sensor is not suported for this cap.'; + if ~isempty(eegCapLandmarkLabels) + strSensors = sprintf('%s, ',eegCapLandmarkLabels{:}); + strSensors = strSensors(1:end-2); + autoButtonTooltip = ['Set at least sensors: [' strSensors '] to enable.']; + end +end + %% ===== GET CURRENT MONTAGE ===== function [curMontage, nEEG] = GetCurrentMontage() global Digitize @@ -823,47 +1286,66 @@ function SelectMontage(iMontage) end %% ===== ADD EEG MONTAGE ===== -function AddMontage() +function AddMontage(ChannelFile) global Digitize - % Get recently used folders - LastUsedDirs = bst_get('LastUsedDirs'); - % Open file - MontageFile = java_getfile('open', 'Select montage file...', LastUsedDirs.ImportChannel, 'single', 'files', ... - {{'*.txt'}, 'Text files', 'TXT'}, 0); - if isempty(MontageFile) - return; - end - % Get filename - [MontageDir, MontageName] = bst_fileparts(MontageFile); - % Intialize new montage - newMontage.Name = MontageName; - newMontage.Labels = {}; - - % Open file - fid = fopen(MontageFile,'r'); - if (fid == -1) - error('Cannot open file.'); - end - % Read file - while (1) - tline = fgetl(fid); - if ~ischar(tline) - break; + % Add Montage from text file + if nargin<1 + % Get recently used folders + LastUsedDirs = bst_get('LastUsedDirs'); + % Open file + MontageFile = java_getfile('open', 'Select montage file...', LastUsedDirs.ImportChannel, 'single', 'files', ... + {{'*.txt'}, 'Text files', 'TXT'}, 0); + if isempty(MontageFile) + return; end - spl = regexp(tline,'\s+','split'); - if (length(spl) >= 2) - newMontage.Labels{end+1} = spl{2}; + % Get filename + [MontageDir, MontageName] = bst_fileparts(MontageFile); + % Intialize new montage + newMontage.Name = MontageName; + newMontage.Labels = {}; + + % Open file + fid = fopen(MontageFile,'r'); + if (fid == -1) + error('Cannot open file.'); end + % Read file + while (1) + tline = fgetl(fid); + if ~ischar(tline) + break; + end + spl = regexp(tline,'\s+','split'); + if (length(spl) >= 2) + newMontage.Labels{end+1} = spl{2}; + end + end + % Close file + fclose(fid); + % If no labels were read: exit + if isempty(newMontage.Labels) + return + end + % Save last dir + LastUsedDirs.ImportChannel = MontageDir; + bst_set('LastUsedDirs', LastUsedDirs); + else % Add Montage from mat file of EEG caps + % Load existing file + ChannelMat = in_bst_channel(ChannelFile); + + % Intialize new montage + newMontage.Name = ChannelMat.Comment; + newMontage.Labels = {}; + newMontage.ChannelFile = ChannelFile; + + % Get cap landmark labels + eegCapLandmarkLabels = channel_detect_eegcap_auto('GetEegCapLandmarkLabels', newMontage.Name); + + % Sort as per the initialization landmark labels of EEG Cap + nonLandmarkLabelsIdx = find(~ismember({ChannelMat.Channel.Name},eegCapLandmarkLabels)); + allLabels = {ChannelMat.Channel.Name}; + newMontage.Labels = cat(2, eegCapLandmarkLabels, allLabels(nonLandmarkLabelsIdx)); end - % Close file - fclose(fid); - % If no labels were read: exit - if isempty(newMontage.Labels) - return - end - % Save last dir - LastUsedDirs.ImportChannel = MontageDir; - bst_set('LastUsedDirs', LastUsedDirs); % Get existing montage with the same name iMontage = find(strcmpi({Digitize.Options.Montages.Name}, newMontage.Name)); @@ -891,15 +1373,19 @@ function UnloadAllMontages() % Remove all montages Digitize.Options.Montages = [... struct('Name', 'No EEG', ... - 'Labels', []), ... + 'Labels', [], ... + 'ChannelFile', []), ... struct('Name', 'Default', ... - 'Labels', [])]; + 'Labels', [], ... + 'ChannelFile', [])]; % Reset to "No EEG" Digitize.Options.iMontage = 1; % Save Digitize options bst_set('DigitizeOptions', Digitize.Options); % Reload menu bar CreateMontageMenu(); + % Reset list + ResetDataCollection(); end @@ -933,7 +1419,7 @@ function UnloadAllMontages() flush(Digitize.SerialConnection); end - % set up the Bytes Available function + % Set up the Bytes Available function configureCallback(Digitize.SerialConnection, 'byte', Digitize.Options.ComByteCount, @BytesAvailable_Callback); if strcmp(Digitize.Options.UnitType, 'fastrak') %'c' - Disable Continuous Printing @@ -988,7 +1474,7 @@ function UnloadAllMontages() writeline(Digitize.SerialConnection, Digitize.Options.ConfigCommands{iCmd}); end elseif strcmp(Digitize.Options.UnitType,'patriot') - % request input from stylus + % Request input from stylus writeline(Digitize.SerialConnection,'L1,1\r'); % Set units to centimeters writeline(Digitize.SerialConnection,'U1\r'); @@ -996,7 +1482,7 @@ function UnloadAllMontages() pause(0.2); catch %#ok % If the connection cannot be established: error message - bst_error(['Cannot open serial connection.' 10 10 'Please check the serial port configuration.' 10], 'Digitize', 0); + bst_error(['Cannot open serial connection.' 10 10 'Please check the serial port configuration.' 10], Digitize.Type, 0); % Ask user to edit the port options isChanged = EditSettings(); % If edit was canceled: exit @@ -1014,11 +1500,11 @@ function UnloadAllMontages() %% ===== BYTES AVAILABLE CALLBACK ===== -function BytesAvailable_Callback(h, ev) - global Digitize % rawpoints +function BytesAvailable_Callback() %#ok + global Digitize % Get controls ctrl = bst_get('PanelControls', 'Digitize'); - + % Simulate: Generate random points if Digitize.Options.isSimulate % Increment current point index @@ -1026,7 +1512,26 @@ function BytesAvailable_Callback(h, ev) if Digitize.iPoint > numel(Digitize.Points) Digitize.Points(Digitize.iPoint).Type = 'EXTRA'; end - Digitize.Points(Digitize.iPoint).Loc = rand(1,3) * .15 - .075; + if strcmpi(Digitize.Type, '3DScanner') + % Get current 3D figure + [Digitize.hFig,~,Digitize.iDS] = bst_figures('GetCurrentFigure', '3D'); + if isempty(Digitize.hFig) + return + end + % Get current selected point + CoordinatesSelector = getappdata(Digitize.hFig, 'CoordinatesSelector'); + isSelectingCoordinates = getappdata(Digitize.hFig, 'isSelectingCoordinates'); + if isempty(CoordinatesSelector) || isempty(CoordinatesSelector.MRI) + return; + else + if isSelectingCoordinates + Digitize.Points(Digitize.iPoint).Loc = CoordinatesSelector.SCS; + end + end + else + Digitize.Points(Digitize.iPoint).Loc = rand(1,3) * .15 - .075; + end + % Else: Get digitized point coordinates else vals = zeros(1,7); % header, x, y, z, azimuth, elevation, roll @@ -1074,22 +1579,19 @@ function BytesAvailable_Callback(h, ev) Digitize.Points(Digitize.iPoint).Loc = DoMotionCompensation(rawpoints) ./100; % cm => meters end % Beep at each click - if Digitize.Options.isBeep - % Beep not working in compiled version, replacing with this: - if bst_iscompiled() - sound(Digitize.BeepWav(6000:2:16000,1), 22000); - else - beep on; - beep(); - end + if Digitize.Options.isBeep + sound(Digitize.BeepWav.data, Digitize.BeepWav.fs); end % Transform coordinates - if ~isempty(Digitize.Transf) + if ~isempty(Digitize.Transf) && ~strcmpi(Digitize.Type, '3DScanner') Digitize.Points(Digitize.iPoint).Loc = [Digitize.Points(Digitize.iPoint).Loc 1] * Digitize.Transf'; end - % Update coordinates list - UpdateList(); + % Update coordinates list only when there is no updating of selected point + % for which the updating happens at the end + if ~Digitize.isEditPts + UpdateList(); + end % Update counters switch upper(Digitize.Points(Digitize.iPoint).Type) @@ -1098,7 +1600,7 @@ function BytesAvailable_Callback(h, ev) ctrl.jTextFieldExtra.setText(num2str(iCount + 1)); end - if ~isempty(Digitize.hFig) && ishandle(Digitize.hFig) + if ~isempty(Digitize.hFig) && ishandle(Digitize.hFig) && ~strcmpi(Digitize.Points(Digitize.iPoint).Type, 'CARDINAL') % Add this point to the figure % Saves in GlobalData, but NOT in actual channel file PlotCoordinate(); @@ -1117,14 +1619,8 @@ function BytesAvailable_Callback(h, ev) ctrl.jLabelWarning.setOpaque(true); ctrl.jLabelWarning.setBackground(java.awt.Color.red); % Extra beep for large distances - % Beep not working in compiled version, replacing with this: - if bst_iscompiled() - sound(Digitize.BeepWav(6000:2:16000,1), 22000); - else - beep on; - pause(0.25); % maybe to help, sometimes it didn't do this 2nd beep - beep(); - end + pause(0.15); + sound(Digitize.BeepWav.data, Digitize.BeepWav.fs*.08); end end @@ -1150,7 +1646,7 @@ function BytesAvailable_Callback(h, ev) UpdateList(); % Update the channel file to save these essential points, and possibly needed for creating figure. SaveDigitizeChannelFile(); - + % Create figure, store hFig & iDS CreateHeadpointsFigure(); % Enable fids button @@ -1160,13 +1656,34 @@ function BytesAvailable_Callback(h, ev) java_setcb(ctrl.jButtonDeletePoint, 'ActionPerformedCallback', @(h,ev)bst_call(@DeletePoint_Callback)); ctrl.jButtonDeletePoint.setText('Delete last point'); end + + % Update coordinate list after the updating the selected point + if Digitize.isEditPts + % Reset global variable required for updating + Digitize.isEditPts = 0; + % Update the Digitize.iPoint + iNotEmptyLoc = find(cellfun(@(x)~isempty(x), {Digitize.Points.Loc})); + Digitize.iPoint = length(iNotEmptyLoc); + % Update the coordinate list + UpdateList(); + end + % Enable 'Auto' button IFF all landmark fiducials have been acquired + if strcmpi(Digitize.Type, '3DScanner') && ~strcmpi(Digitize.Points(Digitize.iPoint).Type, 'EXTRA') + eegCapLandmarkLabels = channel_detect_eegcap_auto('GetEegCapLandmarkLabels', Digitize.Options.Montages(Digitize.Options.iMontage).Name); + if ~isempty(eegCapLandmarkLabels) + acqPoints = Digitize.Points(~cellfun(@isempty, {Digitize.Points.Loc})); + if all(ismember([eegCapLandmarkLabels], {acqPoints.Label})) + ctrl.jButtonEEGAutoDetectElectrodes.setEnabled(1); + end + end + end end %% ===== MOTION COMPENSATION ===== function newPT = DoMotionCompensation(sensors) - % use sensor one and its orientation vectors as the new coordinate system - % Define the origin as the position of sensor attached to the glasses. + % Use sensor one and its orientation vectors as the new coordinate system + % Define the origin as the position of sensor attached to the glasses WAND = 1; REMOTE1 = 2; @@ -1190,8 +1707,7 @@ function BytesAvailable_Callback(h, ev) CE = cos(beta); CR = cos(gamma); - % Convert Euler angles to directional cosines - % using formulae in Polhemus manual. + % Convert Euler angles to directional cosines using formulae in Polhemus manual rotMat(1, 1) = CA * CE; rotMat(1, 2) = SA * CE; rotMat(1, 3) = -SE; @@ -1206,7 +1722,7 @@ function BytesAvailable_Callback(h, ev) rotMat(4, 1:4) = 0; - %Translate and rotate the WAND into new coordinate system + % Translate and rotate the WAND into new coordinate system pt(1) = sensors(WAND,2) - C(1); pt(2) = sensors(WAND,3) - C(2); pt(3) = sensors(WAND,4) - C(3); @@ -1216,6 +1732,3 @@ function BytesAvailable_Callback(h, ev) newPT(3) = pt(1) * rotMat(3, 1) + pt(2) * rotMat(3, 2) + pt(3) * rotMat(3, 3)'+ rotMat(3, 4); end - - - diff --git a/toolbox/sensors/private/bst_beep.wav b/toolbox/sensors/private/bst_beep.wav new file mode 100644 index 0000000000000000000000000000000000000000..946c0485cd72ef68abbd890601b55df92c66bf8c GIT binary patch literal 17632 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 literal 0 HcmV?d00001 diff --git a/toolbox/sensors/private/bst_beep_wav.mat b/toolbox/sensors/private/bst_beep_wav.mat deleted file mode 100644 index d6e8cbe9cd8ccd8feb15789e8a19b1d5c6bfc518..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 160194 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 z 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 2) if ~bst_get('ReadOnly') @@ -2934,72 +2948,8 @@ function fcnPopupImportChannel(bstNodes, jMenu, isAddLoc) gui_component('MenuItem', jMenu, [], 'Import channel file', IconLoader.ICON_CHANNEL, [], @(h,ev)bst_call(@ImportChannelCheck, iAllStudies)); jMenu = gui_component('Menu', jMenu, [], 'Use default EEG cap', IconLoader.ICON_CHANNEL, [], []); end - % === USE DEFAULT CHANNEL FILE === - % Get registered Brainstorm EEG defaults - bstDefaults = bst_get('EegDefaults'); - if ~isempty(bstDefaults) - % Add a directory per template block available - for iDir = 1:length(bstDefaults) - jMenuDir = gui_component('Menu', jMenu, [], bstDefaults(iDir).name, IconLoader.ICON_FOLDER_CLOSE, [], []); - isMni = strcmpi(bstDefaults(iDir).name, 'ICBM152'); - % Create subfolder for cap manufacturer - jMenuOther = gui_component('Menu', [], [], 'Generic', IconLoader.ICON_FOLDER_CLOSE, [], []); - jMenuAnt = gui_component('Menu', [], [], 'ANT', IconLoader.ICON_FOLDER_CLOSE, [], []); - jMenuBs = gui_component('Menu', [], [], 'BioSemi', IconLoader.ICON_FOLDER_CLOSE, [], []); - jMenuBp = gui_component('Menu', [], [], 'BrainProducts', IconLoader.ICON_FOLDER_CLOSE, [], []); - jMenuEgi = gui_component('Menu', [], [], 'EGI', IconLoader.ICON_FOLDER_CLOSE, [], []); - jMenuNs = gui_component('Menu', [], [], 'NeuroScan', IconLoader.ICON_FOLDER_CLOSE, [], []); - % Add an item per Template available - fList = bstDefaults(iDir).contents; - % Sort in natural order - [tmp,I] = sort_nat({fList.name}); - fList = fList(I); - % Create an entry for each default - for iFile = 1:length(fList) - % Define callback function - if isAddLoc - fcnCallback = @(h,ev)channel_add_loc(iAllStudies, fList(iFile).fullpath, 1, isMni); - else - fcnCallback = @(h,ev)db_set_channel(iAllStudies, fList(iFile).fullpath, 1, 0); - end - % Find corresponding submenu - if ~isempty(strfind(fList(iFile).name, 'ANT')) - jMenuType = jMenuAnt; - elseif ~isempty(strfind(fList(iFile).name, 'BioSemi')) - jMenuType = jMenuBs; - elseif ~isempty(strfind(fList(iFile).name, 'BrainProducts')) - jMenuType = jMenuBp; - elseif ~isempty(strfind(fList(iFile).name, 'GSN')) || ~isempty(strfind(fList(iFile).name, 'U562')) - jMenuType = jMenuEgi; - elseif ~isempty(strfind(fList(iFile).name, 'Neuroscan')) - jMenuType = jMenuNs; - else - jMenuType = jMenuOther; - end - % Create item - gui_component('MenuItem', jMenuType, [], fList(iFile).name, IconLoader.ICON_CHANNEL, [], fcnCallback); - end - % Add if not empty - if (jMenuOther.getMenuComponentCount() > 0) - jMenuDir.add(jMenuOther); - end - if (jMenuAnt.getMenuComponentCount() > 0) - jMenuDir.add(jMenuAnt); - end - if (jMenuBs.getMenuComponentCount() > 0) - jMenuDir.add(jMenuBs); - end - if (jMenuBp.getMenuComponentCount() > 0) - jMenuDir.add(jMenuBp); - end - if (jMenuEgi.getMenuComponentCount() > 0) - jMenuDir.add(jMenuEgi); - end - if (jMenuNs.getMenuComponentCount() > 0) - jMenuDir.add(jMenuNs); - end - end - end + % Use default channel file + menu_default_eegcaps(jMenu, iAllStudies, isAddLoc); end %% ===== EDIT NODE ===== @@ -3851,4 +3801,11 @@ function MriReslice(MriFileSrc, MriFileRef, TransfSrc, TransfRef) if isempty(MriFileReg) || ~isempty(errMsg) bst_error(['Could not reslice volume.', 10, 10, errMsg], 'MRI reslice', 0); end -end \ No newline at end of file +end + + +%% ===== DISPLAY TEXTURED SURFACE ===== +function ViewTexturedSurface(filenameRelative) + sSurf = bst_memory('LoadSurface', filenameRelative); + view_surface_matrix(sSurf.Vertices, sSurf.Faces, [], sSurf.Color, [], [], filenameRelative); +end