2121from ..bem import ConductorModel , _bem_find_surface , read_bem_solution
2222from ..source_estimate import VolSourceEstimate
2323from ..source_space ._source_space import (
24+ SourceSpaces ,
2425 _complete_vol_src ,
2526 _ensure_src ,
2627 _filter_source_spaces ,
2728 _make_discrete_source_space ,
2829)
29- from ..surface import _CheckInside , _normalize_vectors
30+ from ..surface import _CheckInside , _CheckInsideSphere , _normalize_vectors
3031from ..transforms import (
3132 Transform ,
3233 _coord_frame_name ,
3536 _print_coord_trans ,
3637 apply_trans ,
3738 invert_transform ,
38- transform_surface_to ,
3939)
4040from ..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+ )
4246from .forward import _FWD_ORDER , Forward , _merge_fwds , convert_forward_solution
4347
4448_accuracy_dict = dict (
@@ -459,7 +463,7 @@ def _prepare_for_forward(
459463 # let's make a copy in case we modify something
460464 src = _ensure_src (src ).copy ()
461465 nsource = sum (s ["nuse" ] for s in src )
462- if nsource == 0 :
466+ if len ( src ) and nsource == 0 :
463467 raise RuntimeError (
464468 "No sources are active in these source spaces. "
465469 '"do_all" option should be used.'
@@ -517,11 +521,12 @@ def _prepare_for_forward(
517521
518522 # Transform the source spaces into the appropriate coordinates
519523 # (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+ )
525530
526531 # Prepare the BEM model
527532 eegnames = sensors .get ("eeg" , dict ()).get ("ch_names" , [])
@@ -533,48 +538,50 @@ def _prepare_for_forward(
533538 # Circumvent numerical problems by excluding points too close to the skull,
534539 # and check that sensors are not inside any BEM surface
535540 if bem is not None :
541+ kwargs = dict (limit = mindist , mri_head_t = mri_head_t , src = src )
536542 if not bem ["is_sphere" ]:
537543 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" ))
542545 logger .info ("" )
543546 if len (bem ["surfs" ]) == 3 :
544547 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
546551 else :
547552 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 :
549557
550- def check_inside (x ):
558+ def check_inside_head (x ):
551559 return np .zeros (len (x ), bool )
552560
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 )
558563
559564 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 ()
565569 if n_inside :
566570 raise RuntimeError (
567571 f"Found { n_inside } MEG sensor{ _pl (n_inside )} inside the "
568572 f"{ check_surface } , perhaps coordinate frames and/or "
569573 "coregistration must be incorrect"
570574 )
571575
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 ))
578585
579586 # deal with free orientations:
580587 source_nn = np .tile (np .eye (3 ), (len (rr ), 1 ))
@@ -934,3 +941,75 @@ def use_coil_def(fname):
934941 yield
935942 finally :
936943 _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