@@ -80,6 +80,8 @@ def n_sources(self) -> int:
8080 def _interp_currents (self , t_ms , I , delay_ms , t_eval_ms ):
8181 if not np .all (np .diff (t_ms ) > 0 ):
8282 raise ValueError ("t_ms must be monotonically increasing" )
83+ if len (t_ms ) == 0 :
84+ return np .zeros ((len (t_eval_ms ), self .n_sources ))
8385
8486 t_eval_delayed = np .subtract (t_eval_ms , delay_ms )
8587 t_needed = (np .min (t_eval_delayed ), np .max (t_eval_delayed ))
@@ -99,7 +101,12 @@ def _interp_currents(self, t_ms, I, delay_ms, t_eval_ms):
99101 f"Needed [{ t_needed [0 ]} , { t_needed [1 ]} ] ms, "
100102 f"provided [{ t_provided [0 ]} , { t_provided [1 ]} ] ms."
101103 )
102- I_interp = PchipInterpolator (t_ms , I , extrapolate = False )(t_eval_delayed )
104+ if len (t_ms ) > 1 :
105+ interpolator = PchipInterpolator (t_ms , I , extrapolate = False )
106+ elif len (t_ms ) == 1 :
107+ interpolator = lambda t_eval : (t_eval == t_ms [0 ]) * I [0 :1 ]
108+
109+ I_interp = interpolator (t_eval_delayed )
103110 assert I_interp .shape == (len (t_eval_ms ), self .n_sources )
104111 I_interp [np .isnan (I_interp )] = 0
105112 return I_interp
@@ -112,6 +119,7 @@ def calculate(
112119 t_gaba_ms : np .ndarray ,
113120 I_gaba : np .ndarray ,
114121 normalize : bool = True ,
122+ wsum_mean_std_for_norm : tuple [np .ndarray , np .ndarray ] = None ,
115123 ) -> np .ndarray :
116124 """Calculate WSLFP at requested times for initialized coordinates given currents
117125
@@ -124,8 +132,12 @@ def calculate(
124132 in milliseconds
125133 I_gaba (np.ndarray): GABAergic currents, shape (len(t_ampa_ms), n_sources)
126134 normalize (bool, optional): Whether to normalize to mean of 0 and variance of 1.
127- The main reason not to normalize is if you are computing one time step at a time.
128- Defaults to True.
135+ The main reason not to normalize is if you are computing one time step at a time
136+ (see `notebooks/stepwise.ipynb`). Defaults to True.
137+ wsum_mean_std_for_norm (tuple[np.ndarray, np.ndarray], optional): If provided,
138+ the mean and standard deviation of the weighted sum term are used to normalize
139+ before a realistic amplitude is applied. (see `notebooks/stepwise.ipynb`).
140+ Defaults to None.
129141
130142 Returns:
131143 np.ndarray: (len(t_eval_ms), n_elec) array of WSLFP at requested times for
@@ -153,18 +165,33 @@ def calculate(
153165 assert wsum .shape == (len (t_eval_ms ), self .n_elec , self .n_sources )
154166 wsum = np .sum (wsum , axis = 2 )
155167 assert wsum .shape == (len (t_eval_ms ), self .n_elec )
156- if normalize :
157- wsum = wsum - np .mean (wsum , axis = 0 )
158- if len (t_eval_ms ) > 1 :
159- wsum /= np .std (wsum , axis = 0 )
160- assert wsum .shape == (len (t_eval_ms ), self .n_elec )
161-
162- assert np .allclose (wsum .mean (axis = 0 ), 0 )
163- if len (t_eval_ms ) > 1 :
164- assert np .allclose (wsum .std (axis = 0 ), 1 )
165168
166- wsum *= np .abs (self .amp_uV .mean (axis = 1 ))
167- return wsum
169+ if normalize :
170+ if wsum_mean_std_for_norm :
171+ wsum_mean , wsum_std = wsum_mean_std_for_norm
172+ else :
173+ wsum_mean = np .mean (wsum , axis = 0 )
174+ if len (t_eval_ms ) > 1 :
175+ wsum_std = np .std (wsum , axis = 0 )
176+ else :
177+ wsum_std = 1
178+
179+ lfp = (wsum - wsum_mean ) / wsum_std
180+ assert lfp .shape == (len (t_eval_ms ), self .n_elec )
181+
182+ if not wsum_mean_std_for_norm :
183+ assert np .allclose (lfp .mean (axis = 0 ), 0 )
184+ if len (t_eval_ms ) > 1 :
185+ assert np .allclose (lfp .std (axis = 0 ), 1 )
186+
187+ lfp *= np .abs (self .amp_uV .mean (axis = 1 ))
188+ else :
189+ lfp = wsum
190+
191+ if normalize and wsum_mean_std_for_norm :
192+ return lfp , wsum
193+ else :
194+ return lfp
168195
169196
170197def from_rec_radius_depth (
0 commit comments