21
21
from ..bem import ConductorModel , _bem_find_surface , read_bem_solution
22
22
from ..source_estimate import VolSourceEstimate
23
23
from ..source_space ._source_space import (
24
+ SourceSpaces ,
24
25
_complete_vol_src ,
25
26
_ensure_src ,
26
27
_filter_source_spaces ,
27
28
_make_discrete_source_space ,
28
29
)
29
- from ..surface import _CheckInside , _normalize_vectors
30
+ from ..surface import _CheckInside , _CheckInsideSphere , _normalize_vectors
30
31
from ..transforms import (
31
32
Transform ,
32
33
_coord_frame_name ,
35
36
_print_coord_trans ,
36
37
apply_trans ,
37
38
invert_transform ,
38
- transform_surface_to ,
39
39
)
40
40
from ..utils import _check_fname , _pl , _validate_type , logger , verbose , warn
41
- from ._compute_forward import _compute_forwards
41
+ from ._compute_forward import (
42
+ _compute_forwards ,
43
+ _compute_forwards_meeg ,
44
+ _prep_field_computation ,
45
+ )
42
46
from .forward import _FWD_ORDER , Forward , _merge_fwds , convert_forward_solution
43
47
44
48
_accuracy_dict = dict (
@@ -459,7 +463,7 @@ def _prepare_for_forward(
459
463
# let's make a copy in case we modify something
460
464
src = _ensure_src (src ).copy ()
461
465
nsource = sum (s ["nuse" ] for s in src )
462
- if nsource == 0 :
466
+ if len ( src ) and nsource == 0 :
463
467
raise RuntimeError (
464
468
"No sources are active in these source spaces. "
465
469
'"do_all" option should be used.'
@@ -517,11 +521,12 @@ def _prepare_for_forward(
517
521
518
522
# Transform the source spaces into the appropriate coordinates
519
523
# (will either be HEAD or MRI)
520
- for s in src :
521
- transform_surface_to (s , "head" , mri_head_t )
522
- logger .info (
523
- f"Source spaces are now in { _coord_frame_name (s ['coord_frame' ])} coordinates."
524
- )
524
+ src ._transform_to ("head" , mri_head_t )
525
+ if len (src ):
526
+ logger .info (
527
+ f"Source spaces are now in { _coord_frame_name (src [0 ]['coord_frame' ])} "
528
+ "coordinates."
529
+ )
525
530
526
531
# Prepare the BEM model
527
532
eegnames = sensors .get ("eeg" , dict ()).get ("ch_names" , [])
@@ -533,48 +538,50 @@ def _prepare_for_forward(
533
538
# Circumvent numerical problems by excluding points too close to the skull,
534
539
# and check that sensors are not inside any BEM surface
535
540
if bem is not None :
541
+ kwargs = dict (limit = mindist , mri_head_t = mri_head_t , src = src )
536
542
if not bem ["is_sphere" ]:
537
543
check_surface = "inner skull surface"
538
- inner_skull = _bem_find_surface (bem , "inner_skull" )
539
- check_inside = _filter_source_spaces (
540
- inner_skull , mindist , mri_head_t , src , n_jobs
541
- )
544
+ check_inside_brain = _CheckInside (_bem_find_surface (bem , "inner_skull" ))
542
545
logger .info ("" )
543
546
if len (bem ["surfs" ]) == 3 :
544
547
check_surface = "scalp surface"
545
- check_inside = _CheckInside (_bem_find_surface (bem , "head" ))
548
+ check_inside_head = _CheckInside (_bem_find_surface (bem , "head" ))
549
+ else :
550
+ check_inside_head = check_inside_brain
546
551
else :
547
552
check_surface = "outermost sphere shell"
548
- if len (bem ["layers" ]) == 0 :
553
+ check_inside_brain = _CheckInsideSphere (bem )
554
+ if bem .radius is not None :
555
+ check_inside_head = _CheckInsideSphere (bem , check = "outer" )
556
+ else :
549
557
550
- def check_inside (x ):
558
+ def check_inside_head (x ):
551
559
return np .zeros (len (x ), bool )
552
560
553
- else :
554
-
555
- def check_inside (x ):
556
- r0 = apply_trans (invert_transform (mri_head_t ), bem ["r0" ])
557
- return np .linalg .norm (x - r0 , axis = 1 ) < bem ["layers" ][- 1 ]["rad" ]
561
+ if len (src ):
562
+ _filter_source_spaces (check_inside_brain , ** kwargs )
558
563
559
564
if "meg" in sensors :
560
- meg_loc = apply_trans (
561
- invert_transform (mri_head_t ),
562
- np .array ([coil ["r0" ] for coil in sensors ["meg" ]["defs" ]]),
563
- )
564
- n_inside = check_inside (meg_loc ).sum ()
565
+ meg_loc = np .array ([coil ["r0" ] for coil in sensors ["meg" ]["defs" ]])
566
+ if not bem ["is_sphere" ]:
567
+ meg_loc = apply_trans (invert_transform (mri_head_t ), meg_loc )
568
+ n_inside = check_inside_head (meg_loc ).sum ()
565
569
if n_inside :
566
570
raise RuntimeError (
567
571
f"Found { n_inside } MEG sensor{ _pl (n_inside )} inside the "
568
572
f"{ check_surface } , perhaps coordinate frames and/or "
569
573
"coregistration must be incorrect"
570
574
)
571
575
572
- rr = np .concatenate ([s ["rr" ][s ["vertno" ]] for s in src ])
573
- if len (rr ) < 1 :
574
- raise RuntimeError (
575
- "No points left in source space after excluding "
576
- "points close to inner skull."
577
- )
576
+ if len (src ):
577
+ rr = np .concatenate ([s ["rr" ][s ["vertno" ]] for s in src ])
578
+ if len (rr ) < 1 :
579
+ raise RuntimeError (
580
+ "No points left in source space after excluding "
581
+ "points close to inner skull."
582
+ )
583
+ else :
584
+ rr = np .zeros ((0 , 3 ))
578
585
579
586
# deal with free orientations:
580
587
source_nn = np .tile (np .eye (3 ), (len (rr ), 1 ))
@@ -934,3 +941,75 @@ def use_coil_def(fname):
934
941
yield
935
942
finally :
936
943
_extra_coil_def_fname = None
944
+
945
+
946
+ class _ForwardModeler :
947
+ """Optimized incremental fitting using the same sensors and BEM."""
948
+
949
+ @verbose
950
+ def __init__ (
951
+ self ,
952
+ info ,
953
+ trans ,
954
+ bem ,
955
+ * ,
956
+ mindist = 0.0 ,
957
+ n_jobs = 1 ,
958
+ verbose = None ,
959
+ ):
960
+ self .mri_head_t , _ = _get_trans (trans )
961
+ self .mindist = mindist
962
+ self .n_jobs = n_jobs
963
+ src = SourceSpaces ([])
964
+ self .sensors , _ , _ , _ , self .bem = _prepare_for_forward (
965
+ src ,
966
+ self .mri_head_t ,
967
+ info ,
968
+ bem ,
969
+ mindist ,
970
+ n_jobs ,
971
+ bem_extra = "" ,
972
+ trans = "" ,
973
+ info_extra = "" ,
974
+ meg = True ,
975
+ eeg = True ,
976
+ ignore_ref = False ,
977
+ )
978
+ self .fwd_data = _prep_field_computation (
979
+ sensors = self .sensors ,
980
+ bem = self .bem ,
981
+ n_jobs = self .n_jobs ,
982
+ )
983
+ if self .bem ["is_sphere" ]:
984
+ self .check_inside = _CheckInsideSphere (self .bem )
985
+ else :
986
+ self .check_inside = _CheckInside (_bem_find_surface (self .bem , "inner_skull" ))
987
+
988
+ def compute (self , src ):
989
+ src = _ensure_src (src ).copy ()
990
+ src ._transform_to ("head" , self .mri_head_t )
991
+ kwargs = dict (limit = self .mindist , mri_head_t = self .mri_head_t , src = src )
992
+ _filter_source_spaces (self .check_inside , n_jobs = self .n_jobs , ** kwargs )
993
+ rr = np .concatenate ([s ["rr" ][s ["vertno" ]] for s in src ])
994
+ if len (rr ) < 1 :
995
+ raise RuntimeError (
996
+ "No points left in source space after excluding "
997
+ "points close to inner skull."
998
+ )
999
+
1000
+ sensors = deepcopy (self .sensors )
1001
+ fwd_data = deepcopy (self .fwd_data )
1002
+ fwds = _compute_forwards_meeg (
1003
+ rr ,
1004
+ sensors = sensors ,
1005
+ fwd_data = fwd_data ,
1006
+ n_jobs = self .n_jobs ,
1007
+ )
1008
+ fwds = {
1009
+ key : _to_forward_dict (fwds [key ], sensors [key ]["ch_names" ])
1010
+ for key in _FWD_ORDER
1011
+ if key in fwds
1012
+ }
1013
+ fwd = _merge_fwds (fwds , verbose = False )
1014
+ del fwds
1015
+ return fwd
0 commit comments