@@ -1040,90 +1040,90 @@ def _check_sphere(sphere, info=None, sphere_units="m"):
10401040 sphere = "auto"
10411041
10421042 if isinstance (sphere , str ):
1043- if sphere not in ("auto" , "eeglab" ):
1044- raise ValueError (
1045- f'sphere, if str, must be "auto" or "eeglab", got { sphere } '
1046- )
1043+ _check_option (
1044+ "sphere" , sphere , ("auto" , "eeglab" , "extra" , "eeg" , "cardinal" , "hpi" )
1045+ )
1046+
1047+ if isinstance (sphere , str ) and sphere == "eeglab" :
10471048 assert info is not None
10481049
1049- if sphere == "auto" :
1050- R , r0 , _ = fit_sphere_to_headshape (
1051- info , verbose = _verbose_safe_false (), units = "m"
1050+ # We need coordinates for the 2D plane formed by
1051+ # Fpz<->Oz and T7<->T8, as this plane will be the horizon (i.e. it
1052+ # will determine the location of the head circle).
1053+ #
1054+ # We implement some special-handling in case Fpz is missing, as this seems to be
1055+ # a quite common situation in numerous EEG labs.
1056+ montage = info .get_montage ()
1057+ if montage is None :
1058+ raise ValueError (
1059+ 'No montage was set on your data, but sphere="eeglab" can only work if '
1060+ "digitization points for the EEG channels are available. Consider "
1061+ "calling set_montage() to apply a montage."
10521062 )
1053- sphere = tuple (r0 ) + (R ,)
1054- sphere_units = "m"
1055- elif sphere == "eeglab" :
1056- # We need coordinates for the 2D plane formed by
1057- # Fpz<->Oz and T7<->T8, as this plane will be the horizon (i.e. it
1058- # will determine the location of the head circle).
1059- #
1060- # We implement some special-handling in case Fpz is missing, as
1061- # this seems to be a quite common situation in numerous EEG labs.
1062- montage = info .get_montage ()
1063- if montage is None :
1064- raise ValueError (
1065- 'No montage was set on your data, but sphere="eeglab" '
1066- "can only work if digitization points for the EEG "
1067- "channels are available. Consider calling set_montage() "
1068- "to apply a montage."
1069- )
1070- ch_pos = montage .get_positions ()["ch_pos" ]
1071- horizon_ch_names = ("Fpz" , "Oz" , "T7" , "T8" )
1072-
1073- if "FPz" in ch_pos : # "fix" naming
1074- ch_pos ["Fpz" ] = ch_pos ["FPz" ]
1075- del ch_pos ["FPz" ]
1076- elif "Fpz" not in ch_pos and "Oz" in ch_pos :
1077- logger .info (
1078- "Approximating Fpz location by mirroring Oz along the X and Y axes."
1063+ ch_pos = montage .get_positions ()["ch_pos" ]
1064+ horizon_ch_names = ("Fpz" , "Oz" , "T7" , "T8" )
1065+
1066+ if "FPz" in ch_pos : # "fix" naming
1067+ ch_pos ["Fpz" ] = ch_pos ["FPz" ]
1068+ del ch_pos ["FPz" ]
1069+ elif "Fpz" not in ch_pos and "Oz" in ch_pos :
1070+ logger .info (
1071+ "Approximating Fpz location by mirroring Oz along the X and Y axes."
1072+ )
1073+ # This assumes Fpz and Oz have the same Z coordinate
1074+ ch_pos ["Fpz" ] = ch_pos ["Oz" ] * [- 1 , - 1 , 1 ]
1075+
1076+ for ch_name in horizon_ch_names :
1077+ if ch_name not in ch_pos :
1078+ msg = (
1079+ f'sphere="eeglab" requires digitization points of the following '
1080+ f"electrode locations in the data: { ', ' .join (horizon_ch_names )} , "
1081+ f"but could not find: { ch_name } "
10791082 )
1080- # This assumes Fpz and Oz have the same Z coordinate
1081- ch_pos ["Fpz" ] = ch_pos ["Oz" ] * [- 1 , - 1 , 1 ]
1082-
1083- for ch_name in horizon_ch_names :
1084- if ch_name not in ch_pos :
1085- msg = (
1086- f'sphere="eeglab" requires digitization points of '
1087- f"the following electrode locations in the data: "
1088- f"{ ', ' .join (horizon_ch_names )} , but could not find: "
1089- f"{ ch_name } "
1090- )
1091- if ch_name == "Fpz" :
1092- msg += ", and was unable to approximate its location from Oz"
1093- raise ValueError (msg )
1094-
1095- # Calculate the radius from: T7<->T8, Fpz<->Oz
1096- radius = np .abs (
1083+ if ch_name == "Fpz" :
1084+ msg += ", and was unable to approximate its location from Oz"
1085+ raise ValueError (msg )
1086+
1087+ # Calculate the radius from: T7<->T8, Fpz<->Oz
1088+ radius = np .abs (
1089+ [
1090+ ch_pos ["T7" ][0 ], # X axis
1091+ ch_pos ["T8" ][0 ], # X axis
1092+ ch_pos ["Fpz" ][1 ], # Y axis
1093+ ch_pos ["Oz" ][1 ], # Y axis
1094+ ]
1095+ ).mean ()
1096+
1097+ # Calculate the center of the head sphere.
1098+ # Use 4 digpoints for each of the 3 axes to hopefully get a better approximation
1099+ # than when using just 2 digpoints.
1100+ sphere_locs = dict ()
1101+ for idx , axis in enumerate (("X" , "Y" , "Z" )):
1102+ sphere_locs [axis ] = np .mean (
10971103 [
1098- ch_pos ["T7" ][0 ], # X axis
1099- ch_pos ["T8" ][0 ], # X axis
1100- ch_pos ["Fpz" ][1 ], # Y axis
1101- ch_pos ["Oz" ][1 ], # Y axis
1104+ ch_pos ["T7" ][idx ],
1105+ ch_pos ["T8" ][idx ],
1106+ ch_pos ["Fpz" ][idx ],
1107+ ch_pos ["Oz" ][idx ],
11021108 ]
1103- ).mean ()
1104-
1105- # Calculate the center of the head sphere
1106- # Use 4 digpoints for each of the 3 axes to hopefully get a better
1107- # approximation than when using just 2 digpoints.
1108- sphere_locs = dict ()
1109- for idx , axis in enumerate (("X" , "Y" , "Z" )):
1110- sphere_locs [axis ] = np .mean (
1111- [
1112- ch_pos ["T7" ][idx ],
1113- ch_pos ["T8" ][idx ],
1114- ch_pos ["Fpz" ][idx ],
1115- ch_pos ["Oz" ][idx ],
1116- ]
1117- )
1118- sphere = (sphere_locs ["X" ], sphere_locs ["Y" ], sphere_locs ["Z" ], radius )
1119- sphere_units = "m"
1120- del sphere_locs , radius , montage , ch_pos
1109+ )
1110+ sphere = (sphere_locs ["X" ], sphere_locs ["Y" ], sphere_locs ["Z" ], radius )
1111+ sphere_units = "m"
1112+ del sphere_locs , radius , montage , ch_pos
1113+ elif isinstance (sphere , str ) or (
1114+ isinstance (sphere , list ) and all (isinstance (s , str ) for s in sphere )
1115+ ):
1116+ # Fit a sphere to the head points.
1117+ R , r0 , _ = fit_sphere_to_headshape (
1118+ info , dig_kinds = sphere , verbose = _verbose_safe_false (), units = "m"
1119+ )
1120+ sphere = tuple (r0 ) + (R ,)
1121+ sphere_units = "m"
11211122 elif isinstance (sphere , ConductorModel ):
11221123 if not sphere ["is_sphere" ] or len (sphere ["layers" ]) == 0 :
11231124 raise ValueError (
1124- "sphere, if a ConductorModel, must be spherical "
1125- "with multiple layers, not a BEM or single-layer "
1126- f"sphere (got { sphere } )"
1125+ "sphere, if a ConductorModel, must be spherical with multiple layers, "
1126+ f"not a BEM or single-layer sphere (got { sphere } )"
11271127 )
11281128 sphere = tuple (sphere ["r0" ]) + (sphere ["layers" ][0 ]["rad" ],)
11291129 sphere_units = "m"
@@ -1132,8 +1132,8 @@ def _check_sphere(sphere, info=None, sphere_units="m"):
11321132 sphere = np .concatenate ([[0.0 ] * 3 , [sphere ]])
11331133 if sphere .shape != (4 ,):
11341134 raise ValueError (
1135- "sphere must be float or 1D array of shape (4,), got "
1136- f"array-like of shape { sphere .shape } "
1135+ "sphere must be float or 1D array of shape (4,), "
1136+ f"got array-like of shape { sphere .shape } "
11371137 )
11381138 _check_option ("sphere_units" , sphere_units , ("m" , "mm" ))
11391139 if sphere_units == "mm" :
0 commit comments