rapidtide 3.0.10__py3-none-any.whl → 3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rapidtide/Colortables.py +492 -27
- rapidtide/OrthoImageItem.py +1053 -47
- rapidtide/RapidtideDataset.py +1533 -86
- rapidtide/_version.py +3 -3
- rapidtide/calccoherence.py +196 -29
- rapidtide/calcnullsimfunc.py +191 -40
- rapidtide/calcsimfunc.py +245 -42
- rapidtide/correlate.py +1210 -393
- rapidtide/data/examples/src/testLD +56 -0
- rapidtide/data/examples/src/testalign +1 -1
- rapidtide/data/examples/src/testdelayvar +0 -1
- rapidtide/data/examples/src/testfmri +19 -1
- rapidtide/data/examples/src/testglmfilt +5 -5
- rapidtide/data/examples/src/testhappy +30 -1
- rapidtide/data/examples/src/testppgproc +17 -0
- rapidtide/data/examples/src/testrolloff +11 -0
- rapidtide/data/models/model_cnn_pytorch/best_model.pth +0 -0
- rapidtide/data/models/model_cnn_pytorch/loss.png +0 -0
- rapidtide/data/models/model_cnn_pytorch/loss.txt +1 -0
- rapidtide/data/models/model_cnn_pytorch/model.pth +0 -0
- rapidtide/data/models/model_cnn_pytorch/model_meta.json +68 -0
- rapidtide/data/reference/JHU-ArterialTerritoriesNoVent-LVL1_space-MNI152NLin2009cAsym_2mm.nii.gz +0 -0
- rapidtide/data/reference/JHU-ArterialTerritoriesNoVent-LVL1_space-MNI152NLin2009cAsym_2mm_mask.nii.gz +0 -0
- rapidtide/decorators.py +91 -0
- rapidtide/dlfilter.py +2225 -108
- rapidtide/dlfiltertorch.py +4843 -0
- rapidtide/externaltools.py +327 -12
- rapidtide/fMRIData_class.py +79 -40
- rapidtide/filter.py +1899 -810
- rapidtide/fit.py +2004 -574
- rapidtide/genericmultiproc.py +93 -18
- rapidtide/happy_supportfuncs.py +2044 -171
- rapidtide/helper_classes.py +584 -43
- rapidtide/io.py +2363 -370
- rapidtide/linfitfiltpass.py +341 -75
- rapidtide/makelaggedtcs.py +211 -20
- rapidtide/maskutil.py +423 -53
- rapidtide/miscmath.py +827 -121
- rapidtide/multiproc.py +210 -22
- rapidtide/patchmatch.py +234 -33
- rapidtide/peakeval.py +32 -30
- rapidtide/ppgproc.py +2203 -0
- rapidtide/qualitycheck.py +352 -39
- rapidtide/refinedelay.py +422 -57
- rapidtide/refineregressor.py +498 -184
- rapidtide/resample.py +671 -185
- rapidtide/scripts/applyppgproc.py +28 -0
- rapidtide/simFuncClasses.py +1052 -77
- rapidtide/simfuncfit.py +260 -46
- rapidtide/stats.py +540 -238
- rapidtide/tests/happycomp +9 -0
- rapidtide/tests/test_dlfiltertorch.py +627 -0
- rapidtide/tests/test_findmaxlag.py +24 -8
- rapidtide/tests/test_fullrunhappy_v1.py +0 -2
- rapidtide/tests/test_fullrunhappy_v2.py +0 -2
- rapidtide/tests/test_fullrunhappy_v3.py +1 -0
- rapidtide/tests/test_fullrunhappy_v4.py +2 -2
- rapidtide/tests/test_fullrunrapidtide_v7.py +1 -1
- rapidtide/tests/test_simroundtrip.py +8 -8
- rapidtide/tests/utils.py +9 -8
- rapidtide/tidepoolTemplate.py +142 -38
- rapidtide/tidepoolTemplate_alt.py +165 -44
- rapidtide/tidepoolTemplate_big.py +189 -52
- rapidtide/util.py +1217 -118
- rapidtide/voxelData.py +684 -37
- rapidtide/wiener.py +19 -12
- rapidtide/wiener2.py +113 -7
- rapidtide/wiener_doc.py +255 -0
- rapidtide/workflows/adjustoffset.py +105 -3
- rapidtide/workflows/aligntcs.py +85 -2
- rapidtide/workflows/applydlfilter.py +87 -10
- rapidtide/workflows/applyppgproc.py +522 -0
- rapidtide/workflows/atlasaverage.py +210 -47
- rapidtide/workflows/atlastool.py +100 -3
- rapidtide/workflows/calcSimFuncMap.py +294 -64
- rapidtide/workflows/calctexticc.py +201 -9
- rapidtide/workflows/ccorrica.py +97 -4
- rapidtide/workflows/cleanregressor.py +168 -29
- rapidtide/workflows/delayvar.py +163 -10
- rapidtide/workflows/diffrois.py +81 -3
- rapidtide/workflows/endtidalproc.py +144 -4
- rapidtide/workflows/fdica.py +195 -15
- rapidtide/workflows/filtnifti.py +70 -3
- rapidtide/workflows/filttc.py +74 -3
- rapidtide/workflows/fitSimFuncMap.py +206 -48
- rapidtide/workflows/fixtr.py +73 -3
- rapidtide/workflows/gmscalc.py +113 -3
- rapidtide/workflows/happy.py +813 -201
- rapidtide/workflows/happy2std.py +144 -12
- rapidtide/workflows/happy_parser.py +149 -8
- rapidtide/workflows/histnifti.py +118 -2
- rapidtide/workflows/histtc.py +84 -3
- rapidtide/workflows/linfitfilt.py +117 -4
- rapidtide/workflows/localflow.py +328 -28
- rapidtide/workflows/mergequality.py +79 -3
- rapidtide/workflows/niftidecomp.py +322 -18
- rapidtide/workflows/niftistats.py +174 -4
- rapidtide/workflows/pairproc.py +88 -2
- rapidtide/workflows/pairwisemergenifti.py +85 -2
- rapidtide/workflows/parser_funcs.py +1421 -40
- rapidtide/workflows/physiofreq.py +137 -11
- rapidtide/workflows/pixelcomp.py +208 -5
- rapidtide/workflows/plethquality.py +103 -21
- rapidtide/workflows/polyfitim.py +151 -11
- rapidtide/workflows/proj2flow.py +75 -2
- rapidtide/workflows/rankimage.py +111 -4
- rapidtide/workflows/rapidtide.py +272 -15
- rapidtide/workflows/rapidtide2std.py +98 -2
- rapidtide/workflows/rapidtide_parser.py +109 -9
- rapidtide/workflows/refineDelayMap.py +143 -33
- rapidtide/workflows/refineRegressor.py +682 -93
- rapidtide/workflows/regressfrommaps.py +152 -31
- rapidtide/workflows/resamplenifti.py +85 -3
- rapidtide/workflows/resampletc.py +91 -3
- rapidtide/workflows/retrolagtcs.py +98 -6
- rapidtide/workflows/retroregress.py +165 -9
- rapidtide/workflows/roisummarize.py +173 -5
- rapidtide/workflows/runqualitycheck.py +71 -3
- rapidtide/workflows/showarbcorr.py +147 -4
- rapidtide/workflows/showhist.py +86 -2
- rapidtide/workflows/showstxcorr.py +160 -3
- rapidtide/workflows/showtc.py +159 -3
- rapidtide/workflows/showxcorrx.py +184 -4
- rapidtide/workflows/showxy.py +185 -15
- rapidtide/workflows/simdata.py +262 -36
- rapidtide/workflows/spatialfit.py +77 -2
- rapidtide/workflows/spatialmi.py +251 -27
- rapidtide/workflows/spectrogram.py +305 -32
- rapidtide/workflows/synthASL.py +154 -3
- rapidtide/workflows/tcfrom2col.py +76 -2
- rapidtide/workflows/tcfrom3col.py +74 -2
- rapidtide/workflows/tidepool.py +2972 -133
- rapidtide/workflows/utils.py +19 -14
- rapidtide/workflows/utils_doc.py +293 -0
- rapidtide/workflows/variabilityizer.py +116 -3
- {rapidtide-3.0.10.dist-info → rapidtide-3.1.dist-info}/METADATA +10 -9
- {rapidtide-3.0.10.dist-info → rapidtide-3.1.dist-info}/RECORD +141 -122
- {rapidtide-3.0.10.dist-info → rapidtide-3.1.dist-info}/entry_points.txt +1 -0
- {rapidtide-3.0.10.dist-info → rapidtide-3.1.dist-info}/WHEEL +0 -0
- {rapidtide-3.0.10.dist-info → rapidtide-3.1.dist-info}/licenses/LICENSE +0 -0
- {rapidtide-3.0.10.dist-info → rapidtide-3.1.dist-info}/top_level.txt +0 -0
rapidtide/fit.py
CHANGED
|
@@ -18,175 +18,273 @@
|
|
|
18
18
|
#
|
|
19
19
|
import sys
|
|
20
20
|
import warnings
|
|
21
|
+
from typing import Any, Callable, Optional, Tuple, Union
|
|
21
22
|
|
|
22
23
|
import matplotlib.pyplot as plt
|
|
23
24
|
import numpy as np
|
|
24
25
|
import scipy as sp
|
|
25
26
|
import scipy.special as sps
|
|
26
27
|
import statsmodels.api as sm
|
|
27
|
-
import tqdm
|
|
28
28
|
from numpy.polynomial import Polynomial
|
|
29
|
+
from numpy.typing import ArrayLike, NDArray
|
|
30
|
+
from scipy import signal
|
|
29
31
|
from scipy.optimize import curve_fit
|
|
30
32
|
from scipy.signal import find_peaks, hilbert
|
|
31
33
|
from scipy.stats import entropy, moment
|
|
32
34
|
from sklearn.linear_model import LinearRegression
|
|
33
35
|
from statsmodels.robust import mad
|
|
36
|
+
from statsmodels.tsa.ar_model import AutoReg, ar_select_order
|
|
37
|
+
from tqdm import tqdm
|
|
34
38
|
|
|
39
|
+
import rapidtide.miscmath as tide_math
|
|
35
40
|
import rapidtide.util as tide_util
|
|
41
|
+
from rapidtide.decorators import conditionaljit, conditionaljit2
|
|
36
42
|
|
|
37
43
|
# ---------------------------------------- Global constants -------------------------------------------
|
|
38
44
|
defaultbutterorder = 6
|
|
39
45
|
MAXLINES = 10000000
|
|
40
|
-
donotbeaggressive = True
|
|
41
|
-
|
|
42
|
-
# ----------------------------------------- Conditional imports ---------------------------------------
|
|
43
|
-
try:
|
|
44
|
-
from numba import jit
|
|
45
|
-
except ImportError:
|
|
46
|
-
donotusenumba = True
|
|
47
|
-
else:
|
|
48
|
-
donotusenumba = False
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def conditionaljit():
|
|
52
|
-
def resdec(f):
|
|
53
|
-
if donotusenumba:
|
|
54
|
-
return f
|
|
55
|
-
return jit(f, nopython=True)
|
|
56
|
-
|
|
57
|
-
return resdec
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def conditionaljit2():
|
|
61
|
-
def resdec(f):
|
|
62
|
-
if donotusenumba or donotbeaggressive:
|
|
63
|
-
return f
|
|
64
|
-
return jit(f, nopython=True)
|
|
65
|
-
|
|
66
|
-
return resdec
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def disablenumba():
|
|
70
|
-
global donotusenumba
|
|
71
|
-
donotusenumba = True
|
|
72
46
|
|
|
73
47
|
|
|
74
48
|
# --------------------------- Fitting functions -------------------------------------------------
|
|
75
|
-
def
|
|
49
|
+
def gaussskresiduals(p: NDArray, y: NDArray, x: NDArray) -> NDArray:
|
|
76
50
|
"""
|
|
51
|
+
Calculate residuals for skewed Gaussian fit.
|
|
77
52
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
y
|
|
82
|
-
x
|
|
83
|
-
|
|
84
|
-
Returns
|
|
85
|
-
-------
|
|
86
|
-
|
|
87
|
-
"""
|
|
88
|
-
err = y - gausssk_eval(x, p)
|
|
89
|
-
return err
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def gaussskresiduals(p, y, x):
|
|
93
|
-
"""
|
|
53
|
+
This function computes the residuals (observed values minus fitted values)
|
|
54
|
+
for a skewed Gaussian model. The residuals are used to assess the quality
|
|
55
|
+
of the fit and are commonly used in optimization routines.
|
|
94
56
|
|
|
95
57
|
Parameters
|
|
96
58
|
----------
|
|
97
|
-
p
|
|
98
|
-
|
|
99
|
-
|
|
59
|
+
p : NDArray
|
|
60
|
+
Skewed Gaussian parameters [amplitude, center, width, skewness]
|
|
61
|
+
y : NDArray
|
|
62
|
+
Observed y values
|
|
63
|
+
x : NDArray
|
|
64
|
+
x values
|
|
100
65
|
|
|
101
66
|
Returns
|
|
102
67
|
-------
|
|
68
|
+
residuals : NDArray
|
|
69
|
+
Residuals (y - fitted values) for the skewed Gaussian model
|
|
70
|
+
|
|
71
|
+
Notes
|
|
72
|
+
-----
|
|
73
|
+
The function relies on the `gausssk_eval` function to compute the fitted
|
|
74
|
+
values of the skewed Gaussian model given the parameters and x values.
|
|
103
75
|
|
|
76
|
+
Examples
|
|
77
|
+
--------
|
|
78
|
+
>>> import numpy as np
|
|
79
|
+
>>> x = np.linspace(-5, 5, 100)
|
|
80
|
+
>>> p = np.array([1.0, 0.0, 1.0, 0.5]) # amplitude, center, width, skewness
|
|
81
|
+
>>> y = gausssk_eval(x, p) + np.random.normal(0, 0.1, len(x))
|
|
82
|
+
>>> residuals = gaussskresiduals(p, y, x)
|
|
83
|
+
>>> print(f"Mean residual: {np.mean(residuals):.6f}")
|
|
104
84
|
"""
|
|
105
85
|
return y - gausssk_eval(x, p)
|
|
106
86
|
|
|
107
87
|
|
|
108
88
|
@conditionaljit()
|
|
109
|
-
def gaussresiduals(p, y, x):
|
|
89
|
+
def gaussresiduals(p: NDArray, y: NDArray, x: NDArray) -> NDArray:
|
|
110
90
|
"""
|
|
91
|
+
Calculate residuals for Gaussian fit.
|
|
92
|
+
|
|
93
|
+
This function computes the residuals (observed values minus fitted values)
|
|
94
|
+
for a Gaussian function with parameters [amplitude, center, width].
|
|
111
95
|
|
|
112
96
|
Parameters
|
|
113
97
|
----------
|
|
114
|
-
p
|
|
115
|
-
|
|
116
|
-
|
|
98
|
+
p : NDArray
|
|
99
|
+
Gaussian parameters [amplitude, center, width]
|
|
100
|
+
y : NDArray
|
|
101
|
+
Observed y values
|
|
102
|
+
x : NDArray
|
|
103
|
+
x values
|
|
117
104
|
|
|
118
105
|
Returns
|
|
119
106
|
-------
|
|
107
|
+
NDArray
|
|
108
|
+
Residuals (y - fitted values) where fitted values are calculated as:
|
|
109
|
+
y_fit = amplitude * exp(-((x - center) ** 2) / (2 * width ** 2))
|
|
110
|
+
|
|
111
|
+
Notes
|
|
112
|
+
-----
|
|
113
|
+
The Gaussian function is defined as:
|
|
114
|
+
f(x) = amplitude * exp(-((x - center) ** 2) / (2 * width ** 2))
|
|
120
115
|
|
|
116
|
+
Examples
|
|
117
|
+
--------
|
|
118
|
+
>>> import numpy as np
|
|
119
|
+
>>> p = np.array([1.0, 0.0, 0.5]) # amplitude=1.0, center=0.0, width=0.5
|
|
120
|
+
>>> y = np.array([0.5, 0.8, 1.0, 0.8, 0.5])
|
|
121
|
+
>>> x = np.linspace(-2, 2, 5)
|
|
122
|
+
>>> residuals = gaussresiduals(p, y, x)
|
|
123
|
+
>>> print(residuals)
|
|
121
124
|
"""
|
|
122
125
|
return y - p[0] * np.exp(-((x - p[1]) ** 2) / (2.0 * p[2] * p[2]))
|
|
123
126
|
|
|
124
127
|
|
|
125
|
-
def trapezoidresiduals(p, y, x, toplength):
|
|
128
|
+
def trapezoidresiduals(p: NDArray, y: NDArray, x: NDArray, toplength: float) -> NDArray:
|
|
126
129
|
"""
|
|
130
|
+
Calculate residuals for trapezoid fit.
|
|
131
|
+
|
|
132
|
+
This function computes the residuals (observed values minus fitted values) for a trapezoid
|
|
133
|
+
function fit. The trapezoid is defined by amplitude, center, and width parameters, with
|
|
134
|
+
a specified flat top length.
|
|
127
135
|
|
|
128
136
|
Parameters
|
|
129
137
|
----------
|
|
130
|
-
p
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
138
|
+
p : NDArray
|
|
139
|
+
Trapezoid parameters [amplitude, center, width]
|
|
140
|
+
y : NDArray
|
|
141
|
+
Observed y values
|
|
142
|
+
x : NDArray
|
|
143
|
+
x values
|
|
144
|
+
toplength : float
|
|
145
|
+
Length of the flat top of the trapezoid
|
|
134
146
|
|
|
135
147
|
Returns
|
|
136
148
|
-------
|
|
149
|
+
residuals : NDArray
|
|
150
|
+
Residuals (y - fitted values)
|
|
137
151
|
|
|
152
|
+
Notes
|
|
153
|
+
-----
|
|
154
|
+
The function uses `trapezoid_eval_loop` to evaluate the trapezoid function with the
|
|
155
|
+
given parameters and returns the difference between observed and predicted values.
|
|
156
|
+
|
|
157
|
+
Examples
|
|
158
|
+
--------
|
|
159
|
+
>>> import numpy as np
|
|
160
|
+
>>> x = np.linspace(0, 10, 100)
|
|
161
|
+
>>> y = trapezoid_eval_loop(x, 2.0, [1.0, 5.0, 3.0]) + np.random.normal(0, 0.1, 100)
|
|
162
|
+
>>> p = [1.0, 5.0, 3.0]
|
|
163
|
+
>>> residuals = trapezoidresiduals(p, y, x, 2.0)
|
|
138
164
|
"""
|
|
139
165
|
return y - trapezoid_eval_loop(x, toplength, p)
|
|
140
166
|
|
|
141
167
|
|
|
142
|
-
def risetimeresiduals(p, y, x):
|
|
168
|
+
def risetimeresiduals(p: NDArray, y: NDArray, x: NDArray) -> NDArray:
|
|
143
169
|
"""
|
|
170
|
+
Calculate residuals for rise time fit.
|
|
171
|
+
|
|
172
|
+
This function computes the residuals between observed data and fitted rise time model
|
|
173
|
+
by subtracting the evaluated model from the observed values.
|
|
144
174
|
|
|
145
175
|
Parameters
|
|
146
176
|
----------
|
|
147
|
-
p
|
|
148
|
-
|
|
149
|
-
|
|
177
|
+
p : NDArray
|
|
178
|
+
Rise time parameters [amplitude, start, rise time] where:
|
|
179
|
+
- amplitude: peak value of the rise time curve
|
|
180
|
+
- start: starting time of the rise
|
|
181
|
+
- rise time: time constant for the rise process
|
|
182
|
+
y : NDArray
|
|
183
|
+
Observed y values (dependent variable)
|
|
184
|
+
x : NDArray
|
|
185
|
+
x values (independent variable, typically time)
|
|
150
186
|
|
|
151
187
|
Returns
|
|
152
188
|
-------
|
|
189
|
+
residuals : NDArray
|
|
190
|
+
Residuals (y - fitted values) representing the difference between
|
|
191
|
+
observed data and model predictions
|
|
192
|
+
|
|
193
|
+
Notes
|
|
194
|
+
-----
|
|
195
|
+
This function assumes the existence of a `risetime_eval_loop` function that
|
|
196
|
+
evaluates the rise time model at given x values with parameters p.
|
|
153
197
|
|
|
198
|
+
Examples
|
|
199
|
+
--------
|
|
200
|
+
>>> import numpy as np
|
|
201
|
+
>>> p = np.array([1.0, 0.0, 0.5])
|
|
202
|
+
>>> y = np.array([0.1, 0.3, 0.7, 0.9])
|
|
203
|
+
>>> x = np.array([0.0, 0.2, 0.4, 0.6])
|
|
204
|
+
>>> residuals = risetimeresiduals(p, y, x)
|
|
205
|
+
>>> print(residuals)
|
|
154
206
|
"""
|
|
155
207
|
return y - risetime_eval_loop(x, p)
|
|
156
208
|
|
|
157
209
|
|
|
158
|
-
def gausssk_eval(x, p):
|
|
210
|
+
def gausssk_eval(x: NDArray, p: NDArray) -> NDArray:
|
|
159
211
|
"""
|
|
212
|
+
Evaluate a skewed Gaussian function.
|
|
213
|
+
|
|
214
|
+
This function computes a skewed Gaussian distribution using the method described
|
|
215
|
+
by Azzalini and Dacunha (1996) for generating skewed normal distributions.
|
|
160
216
|
|
|
161
217
|
Parameters
|
|
162
218
|
----------
|
|
163
|
-
x
|
|
164
|
-
|
|
219
|
+
x : NDArray
|
|
220
|
+
x values at which to evaluate the function
|
|
221
|
+
p : NDArray
|
|
222
|
+
Skewed Gaussian parameters [amplitude, center, width, skewness]
|
|
223
|
+
- amplitude: scaling factor for the peak height
|
|
224
|
+
- center: location parameter (mean of the underlying normal distribution)
|
|
225
|
+
- width: scale parameter (standard deviation of the underlying normal distribution)
|
|
226
|
+
- skewness: skewness parameter (controls the asymmetry of the distribution)
|
|
165
227
|
|
|
166
228
|
Returns
|
|
167
229
|
-------
|
|
230
|
+
y : NDArray
|
|
231
|
+
Evaluated skewed Gaussian values
|
|
168
232
|
|
|
233
|
+
Notes
|
|
234
|
+
-----
|
|
235
|
+
The skewed Gaussian is defined as:
|
|
236
|
+
f(x) = amplitude * φ((x-center)/width) * Φ(skewness * (x-center)/width)
|
|
237
|
+
where φ is the standard normal PDF and Φ is the standard normal CDF.
|
|
238
|
+
|
|
239
|
+
Examples
|
|
240
|
+
--------
|
|
241
|
+
>>> import numpy as np
|
|
242
|
+
>>> x = np.linspace(-5, 5, 100)
|
|
243
|
+
>>> params = [1.0, 0.0, 1.0, 2.0] # amplitude, center, width, skewness
|
|
244
|
+
>>> y = gausssk_eval(x, params)
|
|
169
245
|
"""
|
|
170
246
|
t = (x - p[1]) / p[2]
|
|
171
247
|
return p[0] * sp.stats.norm.pdf(t) * sp.stats.norm.cdf(p[3] * t)
|
|
172
248
|
|
|
173
249
|
|
|
174
250
|
# @conditionaljit()
|
|
175
|
-
def kaiserbessel_eval(x, p):
|
|
251
|
+
def kaiserbessel_eval(x: NDArray, p: NDArray) -> NDArray:
|
|
176
252
|
"""
|
|
177
253
|
|
|
254
|
+
Evaluate the Kaiser-Bessel window function.
|
|
255
|
+
|
|
256
|
+
This function computes the Kaiser-Bessel window function, which is commonly used in
|
|
257
|
+
signal processing and medical imaging applications for gridding and convolution operations.
|
|
258
|
+
The window is defined by parameters alpha (or beta) and tau (or W/2).
|
|
259
|
+
|
|
178
260
|
Parameters
|
|
179
261
|
----------
|
|
180
|
-
x:
|
|
181
|
-
|
|
182
|
-
p:
|
|
262
|
+
x : NDArray
|
|
263
|
+
Arguments to the KB function, typically representing spatial or frequency coordinates
|
|
264
|
+
p : NDArray
|
|
183
265
|
The Kaiser-Bessel window parameters [alpha, tau] (wikipedia) or [beta, W/2] (Jackson, J. I., Meyer, C. H.,
|
|
184
266
|
Nishimura, D. G. & Macovski, A. Selection of a convolution function for Fourier inversion using gridding
|
|
185
267
|
[computerised tomography application]. IEEE Trans. Med. Imaging 10, 473–478 (1991))
|
|
186
268
|
|
|
187
269
|
Returns
|
|
188
270
|
-------
|
|
271
|
+
NDArray
|
|
272
|
+
The evaluated Kaiser-Bessel window function values corresponding to input x
|
|
273
|
+
|
|
274
|
+
Notes
|
|
275
|
+
-----
|
|
276
|
+
The Kaiser-Bessel window is defined as:
|
|
277
|
+
KB(x) = I0(α√(1-(x/τ)²)) / (τ * I0(α)) for |x| ≤ τ
|
|
278
|
+
KB(x) = 0 for |x| > τ
|
|
189
279
|
|
|
280
|
+
where I0 is the zeroth-order modified Bessel function of the first kind.
|
|
281
|
+
|
|
282
|
+
Examples
|
|
283
|
+
--------
|
|
284
|
+
>>> import numpy as np
|
|
285
|
+
>>> x = np.linspace(-1, 1, 100)
|
|
286
|
+
>>> p = np.array([4.0, 0.5]) # alpha=4.0, tau=0.5
|
|
287
|
+
>>> result = kaiserbessel_eval(x, p)
|
|
190
288
|
"""
|
|
191
289
|
normfac = sps.i0(p[0] * np.sqrt(1.0 - np.square((0.0 / p[1])))) / p[1]
|
|
192
290
|
sqrtargs = 1.0 - np.square((x / p[1]))
|
|
@@ -199,33 +297,83 @@ def kaiserbessel_eval(x, p):
|
|
|
199
297
|
|
|
200
298
|
|
|
201
299
|
@conditionaljit()
|
|
202
|
-
def gauss_eval(
|
|
300
|
+
def gauss_eval(
|
|
301
|
+
x: NDArray[np.floating[Any]], p: NDArray[np.floating[Any]]
|
|
302
|
+
) -> NDArray[np.floating[Any]]:
|
|
203
303
|
"""
|
|
304
|
+
Evaluate a Gaussian function.
|
|
305
|
+
|
|
306
|
+
This function computes the values of a Gaussian (normal) distribution
|
|
307
|
+
at given x points with specified parameters.
|
|
204
308
|
|
|
205
309
|
Parameters
|
|
206
310
|
----------
|
|
207
|
-
x
|
|
208
|
-
|
|
311
|
+
x : NDArray[np.floating[Any]]
|
|
312
|
+
x values at which to evaluate the Gaussian function
|
|
313
|
+
p : NDArray[np.floating[Any]]
|
|
314
|
+
Gaussian parameters [amplitude, center, width] where:
|
|
315
|
+
- amplitude: peak height of the Gaussian
|
|
316
|
+
- center: x-value of the Gaussian center
|
|
317
|
+
- width: standard deviation of the Gaussian
|
|
209
318
|
|
|
210
319
|
Returns
|
|
211
320
|
-------
|
|
321
|
+
y : NDArray[np.floating[Any]]
|
|
322
|
+
Evaluated Gaussian values with the same shape as x
|
|
212
323
|
|
|
324
|
+
Notes
|
|
325
|
+
-----
|
|
326
|
+
The Gaussian function is defined as:
|
|
327
|
+
f(x) = amplitude * exp(-((x - center)^2) / (2 * width^2))
|
|
328
|
+
|
|
329
|
+
Examples
|
|
330
|
+
--------
|
|
331
|
+
>>> import numpy as np
|
|
332
|
+
>>> x = np.linspace(-5, 5, 100)
|
|
333
|
+
>>> params = np.array([1.0, 0.0, 1.0]) # amplitude=1, center=0, width=1
|
|
334
|
+
>>> y = gauss_eval(x, params)
|
|
335
|
+
>>> print(y.shape)
|
|
336
|
+
(100,)
|
|
213
337
|
"""
|
|
214
338
|
return p[0] * np.exp(-((x - p[1]) ** 2) / (2.0 * p[2] * p[2]))
|
|
215
339
|
|
|
216
340
|
|
|
217
|
-
def trapezoid_eval_loop(x, toplength, p):
|
|
341
|
+
def trapezoid_eval_loop(x: NDArray, toplength: float, p: NDArray) -> NDArray:
|
|
218
342
|
"""
|
|
343
|
+
Evaluate a trapezoid function at multiple points using a loop.
|
|
344
|
+
|
|
345
|
+
This function evaluates a trapezoid-shaped function at given x values. The trapezoid
|
|
346
|
+
is defined by its amplitude, center, and total width, with the flat top length
|
|
347
|
+
specified separately.
|
|
219
348
|
|
|
220
349
|
Parameters
|
|
221
350
|
----------
|
|
222
|
-
x
|
|
223
|
-
|
|
224
|
-
|
|
351
|
+
x : NDArray
|
|
352
|
+
x values at which to evaluate the function
|
|
353
|
+
toplength : float
|
|
354
|
+
Length of the flat top of the trapezoid
|
|
355
|
+
p : NDArray
|
|
356
|
+
Trapezoid parameters [amplitude, center, width]
|
|
225
357
|
|
|
226
358
|
Returns
|
|
227
359
|
-------
|
|
360
|
+
y : NDArray
|
|
361
|
+
Evaluated trapezoid values
|
|
362
|
+
|
|
363
|
+
Notes
|
|
364
|
+
-----
|
|
365
|
+
The trapezoid function is defined as:
|
|
366
|
+
- Zero outside the range [center - width/2, center + width/2]
|
|
367
|
+
- Linearly increasing from 0 to amplitude in the range [center - width/2, center - width/2 + toplength/2]
|
|
368
|
+
- Constant at amplitude in the range [center - width/2 + toplength/2, center + width/2 - toplength/2]
|
|
369
|
+
- Linearly decreasing from amplitude to 0 in the range [center + width/2 - toplength/2, center + width/2]
|
|
228
370
|
|
|
371
|
+
Examples
|
|
372
|
+
--------
|
|
373
|
+
>>> import numpy as np
|
|
374
|
+
>>> x = np.linspace(0, 10, 100)
|
|
375
|
+
>>> p = [1.0, 5.0, 4.0] # amplitude=1.0, center=5.0, width=4.0
|
|
376
|
+
>>> result = trapezoid_eval_loop(x, 2.0, p)
|
|
229
377
|
"""
|
|
230
378
|
r = np.zeros(len(x), dtype="float64")
|
|
231
379
|
for i in range(0, len(x)):
|
|
@@ -233,17 +381,40 @@ def trapezoid_eval_loop(x, toplength, p):
|
|
|
233
381
|
return r
|
|
234
382
|
|
|
235
383
|
|
|
236
|
-
def risetime_eval_loop(x, p):
|
|
384
|
+
def risetime_eval_loop(x: NDArray, p: NDArray) -> NDArray:
|
|
237
385
|
"""
|
|
386
|
+
Evaluate a rise time function.
|
|
387
|
+
|
|
388
|
+
This function evaluates a rise time function for a given set of x values and parameters.
|
|
389
|
+
It iterates through each x value and applies the risetime_eval function to compute
|
|
390
|
+
the corresponding y values.
|
|
238
391
|
|
|
239
392
|
Parameters
|
|
240
393
|
----------
|
|
241
|
-
x
|
|
242
|
-
|
|
394
|
+
x : NDArray
|
|
395
|
+
x values at which to evaluate the function
|
|
396
|
+
p : NDArray
|
|
397
|
+
Rise time parameters [amplitude, start, rise time]
|
|
243
398
|
|
|
244
399
|
Returns
|
|
245
400
|
-------
|
|
401
|
+
y : NDArray
|
|
402
|
+
Evaluated rise time function values
|
|
403
|
+
|
|
404
|
+
Notes
|
|
405
|
+
-----
|
|
406
|
+
This function uses a loop-based approach for evaluating the rise time function.
|
|
407
|
+
For better performance with large arrays, consider using vectorized operations
|
|
408
|
+
instead of this loop-based implementation.
|
|
246
409
|
|
|
410
|
+
Examples
|
|
411
|
+
--------
|
|
412
|
+
>>> import numpy as np
|
|
413
|
+
>>> x = np.array([0, 1, 2, 3, 4])
|
|
414
|
+
>>> p = np.array([1.0, 0.0, 1.0])
|
|
415
|
+
>>> result = risetime_eval_loop(x, p)
|
|
416
|
+
>>> print(result)
|
|
417
|
+
[0. 0.63212056 0.86466472 0.95021293 0.98168436]
|
|
247
418
|
"""
|
|
248
419
|
r = np.zeros(len(x), dtype="float64")
|
|
249
420
|
for i in range(0, len(x)):
|
|
@@ -252,40 +423,51 @@ def risetime_eval_loop(x, p):
|
|
|
252
423
|
|
|
253
424
|
|
|
254
425
|
@conditionaljit()
|
|
255
|
-
def trapezoid_eval(
|
|
426
|
+
def trapezoid_eval(
|
|
427
|
+
x: Union[float, NDArray], toplength: float, p: NDArray
|
|
428
|
+
) -> Union[float, NDArray]:
|
|
256
429
|
"""
|
|
257
|
-
|
|
430
|
+
Evaluate the trapezoidal function at given points.
|
|
258
431
|
|
|
259
432
|
The trapezoidal function is defined as:
|
|
260
433
|
|
|
261
|
-
f(x) = A
|
|
434
|
+
f(x) = A * (1 - exp(-x / tau)) if 0 <= x < L
|
|
262
435
|
|
|
263
|
-
|
|
436
|
+
f(x) = A * exp(-(x - L) / gamma) if x >= L
|
|
264
437
|
|
|
265
|
-
and
|
|
266
|
-
|
|
267
|
-
f(x) = A * exp(-(x - L) / gamma)
|
|
268
|
-
|
|
269
|
-
if x >= L
|
|
270
|
-
|
|
271
|
-
where A, tau, and gamma are parameters.
|
|
438
|
+
where A, tau, and gamma are parameters, and L is the length of the top plateau.
|
|
272
439
|
|
|
273
440
|
Parameters
|
|
274
441
|
----------
|
|
275
|
-
x: float or
|
|
276
|
-
The point
|
|
277
|
-
toplength: float
|
|
442
|
+
x : float or NDArray
|
|
443
|
+
The point or vector at which to evaluate the trapezoidal function.
|
|
444
|
+
toplength : float
|
|
278
445
|
The length of the top plateau of the trapezoid.
|
|
279
|
-
p:
|
|
280
|
-
A list of four values [A, tau, gamma, L]
|
|
446
|
+
p : NDArray
|
|
447
|
+
A list or tuple of four values [A, tau, gamma, L] where:
|
|
448
|
+
- A is the amplitude,
|
|
449
|
+
- tau is the time constant for the rising edge,
|
|
450
|
+
- gamma is the time constant for the falling edge,
|
|
451
|
+
- L is the length of the top plateau.
|
|
452
|
+
|
|
281
453
|
Returns
|
|
282
454
|
-------
|
|
283
|
-
float or
|
|
284
|
-
The value of the trapezoidal function at x.
|
|
455
|
+
float or NDArray
|
|
456
|
+
The value of the trapezoidal function at x. Returns a scalar if x is scalar,
|
|
457
|
+
or an array if x is an array.
|
|
285
458
|
|
|
286
459
|
Notes
|
|
287
460
|
-----
|
|
288
461
|
This function is vectorized and can handle arrays of input points.
|
|
462
|
+
|
|
463
|
+
Examples
|
|
464
|
+
--------
|
|
465
|
+
>>> import numpy as np
|
|
466
|
+
>>> p = [1.0, 2.0, 3.0, 4.0] # A=1.0, tau=2.0, gamma=3.0, L=4.0
|
|
467
|
+
>>> trapezoid_eval(2.0, 4.0, p)
|
|
468
|
+
0.3934693402873665
|
|
469
|
+
>>> trapezoid_eval(np.array([1.0, 2.0, 5.0]), 4.0, p)
|
|
470
|
+
array([0.39346934, 0.63212056, 0.22313016])
|
|
289
471
|
"""
|
|
290
472
|
corrx = x - p[0]
|
|
291
473
|
if corrx < 0.0:
|
|
@@ -297,7 +479,9 @@ def trapezoid_eval(x, toplength, p):
|
|
|
297
479
|
|
|
298
480
|
|
|
299
481
|
@conditionaljit()
|
|
300
|
-
def risetime_eval(
|
|
482
|
+
def risetime_eval(
|
|
483
|
+
x: Union[float, NDArray[np.floating[Any]]], p: NDArray[np.floating[Any]]
|
|
484
|
+
) -> Union[float, NDArray[np.floating[Any]]]:
|
|
301
485
|
"""
|
|
302
486
|
Evaluates the rise time function at a given point.
|
|
303
487
|
|
|
@@ -309,18 +493,33 @@ def risetime_eval(x, p):
|
|
|
309
493
|
|
|
310
494
|
Parameters
|
|
311
495
|
----------
|
|
312
|
-
x: float or
|
|
496
|
+
x : float or NDArray
|
|
313
497
|
The point at which to evaluate the rise time function.
|
|
314
|
-
p:
|
|
315
|
-
|
|
498
|
+
p : NDArray
|
|
499
|
+
An array of three values [x0, A, tau] where:
|
|
500
|
+
- x0: offset parameter
|
|
501
|
+
- A: amplitude parameter
|
|
502
|
+
- tau: time constant parameter
|
|
503
|
+
|
|
316
504
|
Returns
|
|
317
505
|
-------
|
|
318
|
-
float or
|
|
319
|
-
The value of the rise time function at x.
|
|
506
|
+
float or NDArray
|
|
507
|
+
The value of the rise time function at x. Returns 0.0 if x < x0.
|
|
320
508
|
|
|
321
509
|
Notes
|
|
322
510
|
-----
|
|
323
511
|
This function is vectorized and can handle arrays of input points.
|
|
512
|
+
The function implements a shifted exponential rise function commonly used
|
|
513
|
+
in signal processing and physics applications.
|
|
514
|
+
|
|
515
|
+
Examples
|
|
516
|
+
--------
|
|
517
|
+
>>> import numpy as np
|
|
518
|
+
>>> p = [1.0, 2.0, 0.5] # x0=1.0, A=2.0, tau=0.5
|
|
519
|
+
>>> risetime_eval(2.0, p)
|
|
520
|
+
1.2642411176571153
|
|
521
|
+
>>> risetime_eval(np.array([0.5, 1.5, 2.5]), p)
|
|
522
|
+
array([0. , 0.63212056, 1.26424112])
|
|
324
523
|
"""
|
|
325
524
|
corrx = x - p[0]
|
|
326
525
|
if corrx < 0.0:
|
|
@@ -330,22 +529,71 @@ def risetime_eval(x, p):
|
|
|
330
529
|
|
|
331
530
|
|
|
332
531
|
def gasboxcar(
|
|
333
|
-
data,
|
|
334
|
-
samplerate,
|
|
335
|
-
firstpeakstart,
|
|
336
|
-
firstpeakend,
|
|
337
|
-
secondpeakstart,
|
|
338
|
-
secondpeakend,
|
|
339
|
-
risetime=3.0,
|
|
340
|
-
falltime=3.0,
|
|
341
|
-
):
|
|
532
|
+
data: NDArray[np.floating[Any]],
|
|
533
|
+
samplerate: float,
|
|
534
|
+
firstpeakstart: float,
|
|
535
|
+
firstpeakend: float,
|
|
536
|
+
secondpeakstart: float,
|
|
537
|
+
secondpeakend: float,
|
|
538
|
+
risetime: float = 3.0,
|
|
539
|
+
falltime: float = 3.0,
|
|
540
|
+
) -> None:
|
|
541
|
+
"""
|
|
542
|
+
Apply gas boxcar filtering to the input data.
|
|
543
|
+
|
|
544
|
+
This function applies a gas boxcar filtering operation to the provided data array,
|
|
545
|
+
which is commonly used in gas detection and analysis applications to smooth and
|
|
546
|
+
enhance specific signal features.
|
|
547
|
+
|
|
548
|
+
Parameters
|
|
549
|
+
----------
|
|
550
|
+
data : NDArray
|
|
551
|
+
Input data array to be filtered
|
|
552
|
+
samplerate : float
|
|
553
|
+
Sampling rate of the input data in Hz
|
|
554
|
+
firstpeakstart : float
|
|
555
|
+
Start time of the first peak in seconds
|
|
556
|
+
firstpeakend : float
|
|
557
|
+
End time of the first peak in seconds
|
|
558
|
+
secondpeakstart : float
|
|
559
|
+
Start time of the second peak in seconds
|
|
560
|
+
secondpeakend : float
|
|
561
|
+
End time of the second peak in seconds
|
|
562
|
+
risetime : float, optional
|
|
563
|
+
Rise time parameter for the boxcar filter in seconds, default is 3.0
|
|
564
|
+
falltime : float, optional
|
|
565
|
+
Fall time parameter for the boxcar filter in seconds, default is 3.0
|
|
566
|
+
|
|
567
|
+
Returns
|
|
568
|
+
-------
|
|
569
|
+
None
|
|
570
|
+
This function modifies the input data in-place and returns None
|
|
571
|
+
|
|
572
|
+
Notes
|
|
573
|
+
-----
|
|
574
|
+
The gas boxcar filtering operation is designed to enhance gas detection signals
|
|
575
|
+
by applying specific filtering parameters based on the peak timing information.
|
|
576
|
+
The function assumes that the input data is properly formatted and that the
|
|
577
|
+
time parameters are within the valid range of the data.
|
|
578
|
+
|
|
579
|
+
Examples
|
|
580
|
+
--------
|
|
581
|
+
>>> import numpy as np
|
|
582
|
+
>>> data = np.random.rand(1000)
|
|
583
|
+
>>> gasboxcar(data, samplerate=100.0, firstpeakstart=10.0,
|
|
584
|
+
... firstpeakend=15.0, secondpeakstart=20.0,
|
|
585
|
+
... secondpeakend=25.0, risetime=2.0, falltime=2.0)
|
|
586
|
+
"""
|
|
342
587
|
return None
|
|
343
588
|
|
|
344
589
|
|
|
345
590
|
# generate the polynomial fit timecourse from the coefficients
|
|
346
591
|
@conditionaljit()
|
|
347
|
-
def trendgen(
|
|
348
|
-
|
|
592
|
+
def trendgen(
|
|
593
|
+
thexvals: NDArray[np.floating[Any]], thefitcoffs: NDArray[np.floating[Any]], demean: bool
|
|
594
|
+
) -> NDArray[np.floating[Any]]:
|
|
595
|
+
"""
|
|
596
|
+
Generate a polynomial trend based on input x-values and coefficients.
|
|
349
597
|
|
|
350
598
|
This function constructs a polynomial trend using the provided x-values and
|
|
351
599
|
a set of polynomial coefficients. The order of the polynomial is determined
|
|
@@ -354,10 +602,10 @@ def trendgen(thexvals, thefitcoffs, demean):
|
|
|
354
602
|
|
|
355
603
|
Parameters
|
|
356
604
|
----------
|
|
357
|
-
thexvals :
|
|
605
|
+
thexvals : NDArray[np.floating[Any]]
|
|
358
606
|
The x-values (independent variable) at which to evaluate the polynomial trend.
|
|
359
607
|
Expected to be a numpy array or similar.
|
|
360
|
-
thefitcoffs :
|
|
608
|
+
thefitcoffs : NDArray[np.floating[Any]]
|
|
361
609
|
A 1D array of polynomial coefficients. The length of this array minus one
|
|
362
610
|
determines the order of the polynomial. Coefficients are expected to be
|
|
363
611
|
ordered from the highest power of x down to the constant term (e.g.,
|
|
@@ -370,7 +618,7 @@ def trendgen(thexvals, thefitcoffs, demean):
|
|
|
370
618
|
|
|
371
619
|
Returns
|
|
372
620
|
-------
|
|
373
|
-
|
|
621
|
+
NDArray[np.floating[Any]]
|
|
374
622
|
A numpy array containing the calculated polynomial trend, with the same
|
|
375
623
|
shape as `thexvals`.
|
|
376
624
|
|
|
@@ -378,6 +626,16 @@ def trendgen(thexvals, thefitcoffs, demean):
|
|
|
378
626
|
-----
|
|
379
627
|
This function implicitly assumes that `thexvals` is a numpy array or
|
|
380
628
|
behaves similarly for element-wise multiplication (`np.multiply`).
|
|
629
|
+
|
|
630
|
+
Examples
|
|
631
|
+
--------
|
|
632
|
+
>>> import numpy as np
|
|
633
|
+
>>> x = np.linspace(0, 1, 5)
|
|
634
|
+
>>> coeffs = np.array([1, 0, 1]) # x^2 + 1
|
|
635
|
+
>>> trendgen(x, coeffs, demean=True)
|
|
636
|
+
array([1. , 1.0625, 1.25 , 1.5625, 2. ])
|
|
637
|
+
>>> trendgen(x, coeffs, demean=False)
|
|
638
|
+
array([-0. , -0.0625, -0.25 , -0.5625, -1. ])
|
|
381
639
|
"""
|
|
382
640
|
theshape = thefitcoffs.shape
|
|
383
641
|
order = theshape[0] - 1
|
|
@@ -393,93 +651,209 @@ def trendgen(thexvals, thefitcoffs, demean):
|
|
|
393
651
|
|
|
394
652
|
|
|
395
653
|
# @conditionaljit()
|
|
396
|
-
def detrend(
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
654
|
+
def detrend(
|
|
655
|
+
inputdata: NDArray[np.floating[Any]], order: int = 1, demean: bool = False
|
|
656
|
+
) -> NDArray[np.floating[Any]]:
|
|
657
|
+
"""
|
|
658
|
+
Estimates and removes a polynomial trend timecourse.
|
|
659
|
+
|
|
660
|
+
This routine calculates a polynomial defined by a set of coefficients
|
|
661
|
+
at specified time points to create a trend timecourse, and subtracts it
|
|
662
|
+
from the input signal. Optionally, it can remove the mean of the input
|
|
663
|
+
data as well.
|
|
664
|
+
|
|
665
|
+
Parameters
|
|
666
|
+
----------
|
|
667
|
+
inputdata : NDArray[np.floating[Any]]
|
|
668
|
+
A 1D NumPy array of input data from which the trend will be removed.
|
|
669
|
+
order : int, optional
|
|
670
|
+
The order of the polynomial to fit to the data. Default is 1 (linear).
|
|
671
|
+
demean : bool, optional
|
|
672
|
+
If True, the mean of the input data is subtracted before fitting the
|
|
673
|
+
polynomial trend. Default is False.
|
|
674
|
+
|
|
675
|
+
Returns
|
|
676
|
+
-------
|
|
677
|
+
NDArray[np.floating[Any]]
|
|
678
|
+
A 1D NumPy array of the detrended data, with the polynomial trend removed.
|
|
679
|
+
|
|
680
|
+
Notes
|
|
681
|
+
-----
|
|
682
|
+
- This function uses `numpy.polynomial.Polynomial.fit` to fit a polynomial
|
|
683
|
+
to the input data and then evaluates it using `trendgen`.
|
|
684
|
+
- If a `RankWarning` is raised during fitting (e.g., due to insufficient
|
|
685
|
+
data or poor conditioning), the function defaults to a zero-order
|
|
686
|
+
polynomial (constant trend).
|
|
687
|
+
- The time points are centered around zero, ranging from -N/2 to N/2,
|
|
688
|
+
where N is the length of the input data.
|
|
689
|
+
|
|
690
|
+
Examples
|
|
691
|
+
--------
|
|
692
|
+
>>> import numpy as np
|
|
693
|
+
>>> data = np.array([1, 2, 3, 4, 5])
|
|
694
|
+
>>> detrended = detrend(data, order=1)
|
|
695
|
+
>>> print(detrended)
|
|
696
|
+
[0. 0. 0. 0. 0.]
|
|
697
|
+
"""
|
|
425
698
|
thetimepoints = np.arange(0.0, len(inputdata), 1.0) - len(inputdata) / 2.0
|
|
426
699
|
try:
|
|
427
700
|
thecoffs = Polynomial.fit(thetimepoints, inputdata, order).convert().coef[::-1]
|
|
428
|
-
except np.
|
|
429
|
-
thecoffs = [0.0, 0.0]
|
|
701
|
+
except np.exceptions.RankWarning:
|
|
702
|
+
thecoffs = np.array([0.0, 0.0])
|
|
430
703
|
thefittc = trendgen(thetimepoints, thecoffs, demean)
|
|
431
704
|
return inputdata - thefittc
|
|
432
705
|
|
|
433
706
|
|
|
434
|
-
|
|
435
|
-
|
|
707
|
+
def prewhiten(
|
|
708
|
+
series: NDArray[np.floating[Any]], nlags: Optional[int] = None, debug: bool = False
|
|
709
|
+
) -> NDArray[np.floating[Any]]:
|
|
436
710
|
"""
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
This function iterates through the input array `theyvals` and returns the index of the
|
|
440
|
-
first element that is greater than or equal to `thevalue`. If no such element exists,
|
|
441
|
-
it returns the length of the array.
|
|
711
|
+
Prewhiten a time series using an AR model estimated via statsmodels.
|
|
712
|
+
The resulting series has the same length as the input.
|
|
442
713
|
|
|
443
714
|
Parameters
|
|
444
715
|
----------
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
716
|
+
series : NDArray[np.floating[Any]]
|
|
717
|
+
Input 1D time series data.
|
|
718
|
+
nlags : int, optional
|
|
719
|
+
Order of the autoregressive model. If None, automatically chosen via AIC.
|
|
720
|
+
Default is None.
|
|
721
|
+
debug : bool, optional
|
|
722
|
+
If True, additional debug information may be printed. Default is False.
|
|
723
|
+
|
|
449
724
|
Returns
|
|
450
725
|
-------
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
726
|
+
whitened : NDArray[np.floating[Any]]
|
|
727
|
+
Prewhitened series of the same length as input. The prewhitening removes
|
|
728
|
+
the autoregressive structure from the data, leaving only the residuals.
|
|
729
|
+
|
|
730
|
+
Notes
|
|
731
|
+
-----
|
|
732
|
+
This function fits an AR(p) model to the input series using `statsmodels.tsa.ARIMA`
|
|
733
|
+
and applies the inverse AR filter to prewhiten the data. If `nlags` is not provided,
|
|
734
|
+
the function automatically selects the best model order based on the Akaike Information Criterion (AIC).
|
|
454
735
|
|
|
455
736
|
Examples
|
|
456
737
|
--------
|
|
457
|
-
>>>
|
|
458
|
-
|
|
459
|
-
>>>
|
|
460
|
-
|
|
738
|
+
>>> import numpy as np
|
|
739
|
+
>>> from statsmodels.tsa.arima.model import ARIMA
|
|
740
|
+
>>> series = np.random.randn(100)
|
|
741
|
+
>>> whitened = prewhiten(series)
|
|
742
|
+
>>> print(whitened.shape)
|
|
743
|
+
(100,)
|
|
744
|
+
"""
|
|
745
|
+
series = np.asarray(series)
|
|
746
|
+
|
|
747
|
+
# Fit AR(p) model using ARIMA
|
|
748
|
+
if nlags is None:
|
|
749
|
+
best_aic, best_model, best_p = np.inf, None, None
|
|
750
|
+
for p in range(1, min(10, len(series) // 5)):
|
|
751
|
+
try:
|
|
752
|
+
model = sm.tsa.ARIMA(series, order=(p, 0, 0)).fit()
|
|
753
|
+
if model.aic < best_aic:
|
|
754
|
+
best_aic, best_model, best_p = model.aic, model, p
|
|
755
|
+
except Exception:
|
|
756
|
+
continue
|
|
757
|
+
model = best_model
|
|
758
|
+
if model is None:
|
|
759
|
+
raise RuntimeError("Failed to fit any AR model.")
|
|
760
|
+
else:
|
|
761
|
+
model = sm.tsa.ARIMA(series, order=(nlags, 0, 0)).fit()
|
|
762
|
+
|
|
763
|
+
# Extract AR coefficients and apply filter
|
|
764
|
+
ar_params = model.arparams
|
|
765
|
+
b = np.array([1.0]) # numerator (no MA component)
|
|
766
|
+
a = np.r_[1.0, -ar_params] # denominator (AR polynomial)
|
|
767
|
+
|
|
768
|
+
# Apply the inverse AR filter (prewhitening)
|
|
769
|
+
whitened = signal.lfilter(b, a, series)
|
|
770
|
+
|
|
771
|
+
# return whitened, model
|
|
772
|
+
return whitened
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
def prewhiten2(
|
|
776
|
+
timecourse: NDArray[np.floating[Any]], nlags: int, debug: bool = False, sel: bool = False
|
|
777
|
+
) -> NDArray[np.floating[Any]]:
|
|
461
778
|
"""
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
779
|
+
Prewhiten a time course using autoregressive modeling.
|
|
780
|
+
|
|
781
|
+
This function applies prewhitening to a time course by fitting an autoregressive
|
|
782
|
+
model and then applying the corresponding filter to remove temporal autocorrelation.
|
|
783
|
+
|
|
784
|
+
Parameters
|
|
785
|
+
----------
|
|
786
|
+
timecourse : NDArray[np.floating[Any]]
|
|
787
|
+
Input time course to be prewhitened, shape (n_times,)
|
|
788
|
+
nlags : int
|
|
789
|
+
Number of lags to use for the autoregressive model
|
|
790
|
+
debug : bool, optional
|
|
791
|
+
If True, print model summary and display diagnostic plots, by default False
|
|
792
|
+
sel : bool, optional
|
|
793
|
+
If True, use automatic lag selection, by default False
|
|
794
|
+
|
|
795
|
+
Returns
|
|
796
|
+
-------
|
|
797
|
+
NDArray[np.floating[Any]]
|
|
798
|
+
Prewhitened time course with standardized normalization applied
|
|
799
|
+
|
|
800
|
+
Notes
|
|
801
|
+
-----
|
|
802
|
+
The prewhitening process involves:
|
|
803
|
+
1. Fitting an autoregressive model to the input time course
|
|
804
|
+
2. Computing filter coefficients from the model parameters
|
|
805
|
+
3. Applying the filter using scipy.signal.lfilter
|
|
806
|
+
4. Standardizing the result using tide_math.stdnormalize
|
|
807
|
+
|
|
808
|
+
When `sel=True`, the function uses `ar_select_order` for automatic lag selection
|
|
809
|
+
instead of using the fixed number of lags specified by `nlags`.
|
|
810
|
+
|
|
811
|
+
Examples
|
|
812
|
+
--------
|
|
813
|
+
>>> import numpy as np
|
|
814
|
+
>>> timecourse = np.random.randn(100)
|
|
815
|
+
>>> whitened = prewhiten2(timecourse, nlags=3)
|
|
816
|
+
>>> # With debugging enabled
|
|
817
|
+
>>> whitened = prewhiten2(timecourse, nlags=3, debug=True)
|
|
818
|
+
"""
|
|
819
|
+
if not sel:
|
|
820
|
+
ar_model = AutoReg(timecourse, lags=nlags)
|
|
821
|
+
ar_fit = ar_model.fit()
|
|
822
|
+
else:
|
|
823
|
+
ar_model = ar_select_order(timecourse, nlags)
|
|
824
|
+
ar_model.ar_lags
|
|
825
|
+
ar_fit = ar_model.model.fit()
|
|
826
|
+
if debug:
|
|
827
|
+
print(ar_fit.summary())
|
|
828
|
+
fig = plt.figure(figsize=(16, 9))
|
|
829
|
+
fig = ar_fit.plot_diagnostics(fig=fig, lags=nlags)
|
|
830
|
+
plt.show()
|
|
831
|
+
ar_params = ar_fit.params
|
|
832
|
+
|
|
833
|
+
# The prewhitening filter coefficients are 1 for the numerator and
|
|
834
|
+
# (1, -ar_params[1]) for the denominator
|
|
835
|
+
b = [1]
|
|
836
|
+
a = np.insert(-ar_params[1:], 0, 1)
|
|
837
|
+
|
|
838
|
+
# Apply the filter to prewhiten the signal
|
|
839
|
+
return tide_math.stdnormalize(signal.lfilter(b, a, timecourse))
|
|
466
840
|
|
|
467
841
|
|
|
468
842
|
def findtrapezoidfunc(
|
|
469
|
-
thexvals,
|
|
470
|
-
theyvals,
|
|
471
|
-
thetoplength,
|
|
472
|
-
initguess=None,
|
|
473
|
-
debug=False,
|
|
474
|
-
minrise=0.0,
|
|
475
|
-
maxrise=200.0,
|
|
476
|
-
minfall=0.0,
|
|
477
|
-
maxfall=200.0,
|
|
478
|
-
minstart
|
|
479
|
-
maxstart=100.0,
|
|
480
|
-
refine=False,
|
|
481
|
-
displayplots=False,
|
|
482
|
-
):
|
|
843
|
+
thexvals: NDArray[np.floating[Any]],
|
|
844
|
+
theyvals: NDArray[np.floating[Any]],
|
|
845
|
+
thetoplength: float,
|
|
846
|
+
initguess: NDArray[np.floating[Any]] | None = None,
|
|
847
|
+
debug: bool = False,
|
|
848
|
+
minrise: float = 0.0,
|
|
849
|
+
maxrise: float = 200.0,
|
|
850
|
+
minfall: float = 0.0,
|
|
851
|
+
maxfall: float = 200.0,
|
|
852
|
+
minstart: float = -100.0,
|
|
853
|
+
maxstart: float = 100.0,
|
|
854
|
+
refine: bool = False,
|
|
855
|
+
displayplots: bool = False,
|
|
856
|
+
) -> Tuple[float, float, float, float, int]:
|
|
483
857
|
"""
|
|
484
858
|
Find the best-fitting trapezoidal function parameters to a data set.
|
|
485
859
|
|
|
@@ -489,13 +863,13 @@ def findtrapezoidfunc(
|
|
|
489
863
|
|
|
490
864
|
Parameters
|
|
491
865
|
----------
|
|
492
|
-
thexvals :
|
|
866
|
+
thexvals : NDArray[np.floating[Any]]
|
|
493
867
|
Independent variable values (time points) for the data.
|
|
494
|
-
theyvals :
|
|
868
|
+
theyvals : NDArray[np.floating[Any]]
|
|
495
869
|
Dependent variable values (signal intensity) corresponding to `thexvals`.
|
|
496
870
|
thetoplength : float
|
|
497
871
|
The length of the top plateau of the trapezoid function.
|
|
498
|
-
initguess :
|
|
872
|
+
initguess : NDArray[np.floating[Any]], optional
|
|
499
873
|
Initial guess for [start, amplitude, risetime, falltime].
|
|
500
874
|
If None, uses defaults based on data statistics.
|
|
501
875
|
debug : bool, optional
|
|
@@ -516,12 +890,30 @@ def findtrapezoidfunc(
|
|
|
516
890
|
If True, perform additional refinement steps (not implemented in this version).
|
|
517
891
|
displayplots : bool, optional
|
|
518
892
|
If True, display plots during computation (not implemented in this version).
|
|
893
|
+
|
|
519
894
|
Returns
|
|
520
895
|
-------
|
|
521
896
|
tuple of floats
|
|
522
897
|
The fitted parameters [start, amplitude, risetime, falltime] if successful,
|
|
523
898
|
or [0.0, 0.0, 0.0, 0.0] if the solution is outside the valid parameter bounds.
|
|
524
899
|
A fifth value (integer) indicating success (1) or failure (0).
|
|
900
|
+
|
|
901
|
+
Notes
|
|
902
|
+
-----
|
|
903
|
+
The optimization is performed using `scipy.optimize.leastsq` with a residual
|
|
904
|
+
function `trapezoidresiduals`. The function returns a tuple of five elements:
|
|
905
|
+
(start, amplitude, risetime, falltime, success_flag), where success_flag is 1
|
|
906
|
+
if all parameters are within the specified bounds, and 0 otherwise.
|
|
907
|
+
|
|
908
|
+
Examples
|
|
909
|
+
--------
|
|
910
|
+
>>> import numpy as np
|
|
911
|
+
>>> x = np.linspace(0, 10, 100)
|
|
912
|
+
>>> y = trapezoid_eval(x, start=2, amplitude=5, risetime=1, falltime=1, top_length=4)
|
|
913
|
+
>>> y += np.random.normal(0, 0.1, len(y)) # Add noise
|
|
914
|
+
>>> params = findtrapezoidfunc(x, y, thetoplength=4)
|
|
915
|
+
>>> print(params)
|
|
916
|
+
(2.05, 4.98, 1.02, 1.01, 1)
|
|
525
917
|
"""
|
|
526
918
|
# guess at parameters: risestart, riseamplitude, risetime
|
|
527
919
|
if initguess is None:
|
|
@@ -555,35 +947,68 @@ def findtrapezoidfunc(
|
|
|
555
947
|
|
|
556
948
|
|
|
557
949
|
def findrisetimefunc(
|
|
558
|
-
thexvals,
|
|
559
|
-
theyvals,
|
|
560
|
-
initguess=None,
|
|
561
|
-
debug=False,
|
|
562
|
-
minrise=0.0,
|
|
563
|
-
maxrise=200.0,
|
|
564
|
-
minstart
|
|
565
|
-
maxstart=100.0,
|
|
566
|
-
refine=False,
|
|
567
|
-
displayplots=False,
|
|
568
|
-
):
|
|
569
|
-
"""
|
|
950
|
+
thexvals: NDArray[np.floating[Any]],
|
|
951
|
+
theyvals: NDArray[np.floating[Any]],
|
|
952
|
+
initguess: NDArray[np.floating[Any]] | None = None,
|
|
953
|
+
debug: bool = False,
|
|
954
|
+
minrise: float = 0.0,
|
|
955
|
+
maxrise: float = 200.0,
|
|
956
|
+
minstart: float = -100.0,
|
|
957
|
+
maxstart: float = 100.0,
|
|
958
|
+
refine: bool = False,
|
|
959
|
+
displayplots: bool = False,
|
|
960
|
+
) -> Tuple[float, float, float, int]:
|
|
961
|
+
"""
|
|
962
|
+
Find the rise time of a signal by fitting a model to the data.
|
|
963
|
+
|
|
964
|
+
This function fits a rise time model to the provided signal data using least squares
|
|
965
|
+
optimization. It returns the estimated start time, amplitude, and rise time of the signal,
|
|
966
|
+
along with a success flag indicating whether the fit is within specified bounds.
|
|
570
967
|
|
|
571
968
|
Parameters
|
|
572
969
|
----------
|
|
573
|
-
thexvals
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
970
|
+
thexvals : NDArray[np.floating[Any]]
|
|
971
|
+
Array of x-axis values (time or independent variable).
|
|
972
|
+
theyvals : NDArray[np.floating[Any]]
|
|
973
|
+
Array of y-axis values (signal or dependent variable).
|
|
974
|
+
initguess : NDArray[np.floating[Any]] | None, optional
|
|
975
|
+
Initial guess for [start_time, amplitude, rise_time]. If None, defaults are used.
|
|
976
|
+
debug : bool, optional
|
|
977
|
+
If True, prints the x and y values during processing (default is False).
|
|
978
|
+
minrise : float, optional
|
|
979
|
+
Minimum allowed rise time (default is 0.0).
|
|
980
|
+
maxrise : float, optional
|
|
981
|
+
Maximum allowed rise time (default is 200.0).
|
|
982
|
+
minstart : float, optional
|
|
983
|
+
Minimum allowed start time (default is -100.0).
|
|
984
|
+
maxstart : float, optional
|
|
985
|
+
Maximum allowed start time (default is 100.0).
|
|
986
|
+
refine : bool, optional
|
|
987
|
+
Placeholder for future refinement logic (default is False).
|
|
988
|
+
displayplots : bool, optional
|
|
989
|
+
Placeholder for future plotting logic (default is False).
|
|
583
990
|
|
|
584
991
|
Returns
|
|
585
992
|
-------
|
|
993
|
+
Tuple[float, float, float, int]
|
|
994
|
+
A tuple containing:
|
|
995
|
+
- start_time: Estimated start time of the rise.
|
|
996
|
+
- amplitude: Estimated amplitude of the rise.
|
|
997
|
+
- rise_time: Estimated rise time.
|
|
998
|
+
- success: 1 if the fit is within bounds, 0 otherwise.
|
|
586
999
|
|
|
1000
|
+
Notes
|
|
1001
|
+
-----
|
|
1002
|
+
The function uses `scipy.optimize.leastsq` to perform the fitting. The model being fitted
|
|
1003
|
+
is defined in the `risetimeresiduals` function, which must be defined elsewhere in the code.
|
|
1004
|
+
|
|
1005
|
+
Examples
|
|
1006
|
+
--------
|
|
1007
|
+
>>> import numpy as np
|
|
1008
|
+
>>> x = np.linspace(0, 10, 100)
|
|
1009
|
+
>>> y = np.exp(-x / 2) * np.sin(x)
|
|
1010
|
+
>>> start, amp, rise, success = findrisetimefunc(x, y)
|
|
1011
|
+
>>> print(f"Start: {start}, Amplitude: {amp}, Rise Time: {rise}, Success: {success}")
|
|
587
1012
|
"""
|
|
588
1013
|
# guess at parameters: risestart, riseamplitude, risetime
|
|
589
1014
|
if initguess is None:
|
|
@@ -611,8 +1036,14 @@ def findrisetimefunc(
|
|
|
611
1036
|
|
|
612
1037
|
|
|
613
1038
|
def territorydecomp(
|
|
614
|
-
inputmap
|
|
615
|
-
|
|
1039
|
+
inputmap: NDArray[np.floating[Any]],
|
|
1040
|
+
template: NDArray,
|
|
1041
|
+
atlas: NDArray,
|
|
1042
|
+
inputmask: Optional[NDArray] = None,
|
|
1043
|
+
intercept: bool = True,
|
|
1044
|
+
fitorder: int = 1,
|
|
1045
|
+
debug: bool = False,
|
|
1046
|
+
) -> Tuple[NDArray, NDArray, NDArray]:
|
|
616
1047
|
"""
|
|
617
1048
|
Decompose an input map into territories defined by an atlas using polynomial regression.
|
|
618
1049
|
|
|
@@ -623,7 +1054,7 @@ def territorydecomp(
|
|
|
623
1054
|
|
|
624
1055
|
Parameters
|
|
625
1056
|
----------
|
|
626
|
-
inputmap :
|
|
1057
|
+
inputmap : NDArray[np.floating[Any]]
|
|
627
1058
|
Input data to be decomposed. Can be 3D or 4D (e.g., time series).
|
|
628
1059
|
template : numpy.ndarray
|
|
629
1060
|
Template values corresponding to the spatial locations in `inputmap`.
|
|
@@ -640,6 +1071,7 @@ def territorydecomp(
|
|
|
640
1071
|
The order of the polynomial to fit for each territory (default: 1).
|
|
641
1072
|
debug : bool, optional
|
|
642
1073
|
If True, print debugging information during computation (default: False).
|
|
1074
|
+
|
|
643
1075
|
Returns
|
|
644
1076
|
-------
|
|
645
1077
|
tuple of numpy.ndarray
|
|
@@ -658,6 +1090,14 @@ def territorydecomp(
|
|
|
658
1090
|
- If `inputmask` is not provided, all voxels are considered valid.
|
|
659
1091
|
- The number of territories is determined by the maximum value in `atlas`.
|
|
660
1092
|
- For each territory, a polynomial regression is performed using the template values as predictors.
|
|
1093
|
+
|
|
1094
|
+
Examples
|
|
1095
|
+
--------
|
|
1096
|
+
>>> import numpy as np
|
|
1097
|
+
>>> inputmap = np.random.rand(10, 10, 10)
|
|
1098
|
+
>>> template = np.random.rand(10, 10, 10)
|
|
1099
|
+
>>> atlas = np.ones((10, 10, 10), dtype=int)
|
|
1100
|
+
>>> fitmap, coeffs, r2s = territorydecomp(inputmap, template, atlas)
|
|
661
1101
|
"""
|
|
662
1102
|
datadims = len(inputmap.shape)
|
|
663
1103
|
if datadims > 3:
|
|
@@ -711,7 +1151,9 @@ def territorydecomp(
|
|
|
711
1151
|
evs = []
|
|
712
1152
|
for order in range(1, fitorder + 1):
|
|
713
1153
|
evs.append(np.power(template[maskedvoxels], order))
|
|
714
|
-
thefit, R2 = mlregress(
|
|
1154
|
+
thefit, R2 = mlregress(
|
|
1155
|
+
np.asarray(evs), thismap[maskedvoxels], intercept=intercept
|
|
1156
|
+
)
|
|
715
1157
|
thecoffs[whichmap, i - 1, :] = np.asarray(thefit[0]).reshape((-1))
|
|
716
1158
|
theR2s[whichmap, i - 1] = 1.0 * R2
|
|
717
1159
|
thisfit[maskedvoxels] = mlproject(thecoffs[whichmap, i - 1, :], evs, intercept)
|
|
@@ -724,20 +1166,72 @@ def territorydecomp(
|
|
|
724
1166
|
|
|
725
1167
|
|
|
726
1168
|
def territorystats(
|
|
727
|
-
inputmap
|
|
728
|
-
|
|
1169
|
+
inputmap: NDArray[np.floating[Any]],
|
|
1170
|
+
atlas: NDArray,
|
|
1171
|
+
inputmask: NDArray | None = None,
|
|
1172
|
+
entropybins: int = 101,
|
|
1173
|
+
entropyrange: Tuple[float, float] | None = None,
|
|
1174
|
+
debug: bool = False,
|
|
1175
|
+
) -> Tuple[NDArray, NDArray, NDArray, NDArray, NDArray, NDArray, NDArray, NDArray, NDArray]:
|
|
729
1176
|
"""
|
|
1177
|
+
Compute descriptive statistics for regions defined by an atlas within a multi-dimensional input map.
|
|
1178
|
+
|
|
1179
|
+
This function calculates various statistical measures (mean, standard deviation, median, etc.)
|
|
1180
|
+
for each region (territory) defined in the `atlas` array, based on the data in `inputmap`.
|
|
1181
|
+
It supports both single and multi-map inputs, and optionally uses a mask to define valid regions.
|
|
730
1182
|
|
|
731
1183
|
Parameters
|
|
732
1184
|
----------
|
|
733
|
-
inputmap
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
1185
|
+
inputmap : NDArray[np.floating[Any]]
|
|
1186
|
+
Input data array of shape (X, Y, Z) or (X, Y, Z, N), where N is the number of maps.
|
|
1187
|
+
atlas : ndarray
|
|
1188
|
+
Atlas array defining regions of interest, with each region labeled by an integer.
|
|
1189
|
+
Must be the same spatial dimensions as `inputmap`.
|
|
1190
|
+
inputmask : ndarray, optional
|
|
1191
|
+
Boolean or binary mask array of the same shape as `inputmap`. If None, all voxels are considered valid.
|
|
1192
|
+
entropybins : int, default=101
|
|
1193
|
+
Number of bins to use when computing entropy.
|
|
1194
|
+
entropyrange : tuple of float, optional
|
|
1195
|
+
Range (min, max) for histogram binning when computing entropy. If None, uses the full range of data.
|
|
1196
|
+
debug : bool, default=False
|
|
1197
|
+
If True, prints debug information during computation.
|
|
737
1198
|
|
|
738
1199
|
Returns
|
|
739
1200
|
-------
|
|
1201
|
+
tuple of ndarray
|
|
1202
|
+
A tuple containing:
|
|
1203
|
+
- statsmap : ndarray
|
|
1204
|
+
Zero-initialized array of the same shape as `inputmap`, used for storing statistics.
|
|
1205
|
+
- themeans : ndarray
|
|
1206
|
+
Array of shape (N, max(atlas)) containing the mean values for each region in each map.
|
|
1207
|
+
- thestds : ndarray
|
|
1208
|
+
Array of shape (N, max(atlas)) containing the standard deviations for each region in each map.
|
|
1209
|
+
- themedians : ndarray
|
|
1210
|
+
Array of shape (N, max(atlas)) containing the median values for each region in each map.
|
|
1211
|
+
- themads : ndarray
|
|
1212
|
+
Array of shape (N, max(atlas)) containing the median absolute deviations for each region in each map.
|
|
1213
|
+
- thevariances : ndarray
|
|
1214
|
+
Array of shape (N, max(atlas)) containing the variance values for each region in each map.
|
|
1215
|
+
- theskewnesses : ndarray
|
|
1216
|
+
Array of shape (N, max(atlas)) containing the skewness values for each region in each map.
|
|
1217
|
+
- thekurtoses : ndarray
|
|
1218
|
+
Array of shape (N, max(atlas)) containing the kurtosis values for each region in each map.
|
|
1219
|
+
- theentropies : ndarray
|
|
1220
|
+
Array of shape (N, max(atlas)) containing the entropy values for each region in each map.
|
|
740
1221
|
|
|
1222
|
+
Notes
|
|
1223
|
+
-----
|
|
1224
|
+
- The function supports both 3D and 4D input arrays. For 4D arrays, each map is processed separately.
|
|
1225
|
+
- Entropy is computed using the probability distribution from a histogram of voxel values.
|
|
1226
|
+
- If `inputmask` is not provided, all voxels are considered valid.
|
|
1227
|
+
- The `atlas` labels are expected to start from 1, and regions are indexed accordingly.
|
|
1228
|
+
|
|
1229
|
+
Examples
|
|
1230
|
+
--------
|
|
1231
|
+
>>> import numpy as np
|
|
1232
|
+
>>> inputmap = np.random.rand(10, 10, 10)
|
|
1233
|
+
>>> atlas = np.ones((10, 10, 10), dtype=int)
|
|
1234
|
+
>>> statsmap, means, stds, medians, mads, variances, skewnesses, kurtoses, entropies = territorystats(inputmap, atlas)
|
|
741
1235
|
"""
|
|
742
1236
|
datadims = len(inputmap.shape)
|
|
743
1237
|
if datadims > 3:
|
|
@@ -774,7 +1268,7 @@ def territorystats(
|
|
|
774
1268
|
thevoxels = inputmap[np.where(inputmask > 0.0)]
|
|
775
1269
|
else:
|
|
776
1270
|
thevoxels = inputmap
|
|
777
|
-
entropyrange =
|
|
1271
|
+
entropyrange = (np.min(thevoxels), np.max(thevoxels))
|
|
778
1272
|
if debug:
|
|
779
1273
|
print(f"entropy bins: {entropybins}")
|
|
780
1274
|
print(f"entropy range: {entropyrange}")
|
|
@@ -801,9 +1295,9 @@ def territorystats(
|
|
|
801
1295
|
thestds[whichmap, i - 1] = np.std(thismap[maskedvoxels])
|
|
802
1296
|
themedians[whichmap, i - 1] = np.median(thismap[maskedvoxels])
|
|
803
1297
|
themads[whichmap, i - 1] = mad(thismap[maskedvoxels])
|
|
804
|
-
thevariances[whichmap, i - 1] = moment(thismap[maskedvoxels],
|
|
805
|
-
theskewnesses[whichmap, i - 1] = moment(thismap[maskedvoxels],
|
|
806
|
-
thekurtoses[whichmap, i - 1] = moment(thismap[maskedvoxels],
|
|
1298
|
+
thevariances[whichmap, i - 1] = moment(thismap[maskedvoxels], order=2)
|
|
1299
|
+
theskewnesses[whichmap, i - 1] = moment(thismap[maskedvoxels], order=3)
|
|
1300
|
+
thekurtoses[whichmap, i - 1] = moment(thismap[maskedvoxels], order=4)
|
|
807
1301
|
theentropies[whichmap, i - 1] = entropy(
|
|
808
1302
|
np.histogram(
|
|
809
1303
|
thismap[maskedvoxels], bins=entropybins, range=entropyrange, density=True
|
|
@@ -824,7 +1318,9 @@ def territorystats(
|
|
|
824
1318
|
|
|
825
1319
|
|
|
826
1320
|
@conditionaljit()
|
|
827
|
-
def refinepeak_quad(
|
|
1321
|
+
def refinepeak_quad(
|
|
1322
|
+
x: NDArray[np.floating[Any]], y: NDArray[np.floating[Any]], peakindex: int, stride: int = 1
|
|
1323
|
+
) -> Tuple[float, float, float, Optional[bool], bool]:
|
|
828
1324
|
"""
|
|
829
1325
|
Refine the location and properties of a peak using quadratic interpolation.
|
|
830
1326
|
|
|
@@ -834,9 +1330,9 @@ def refinepeak_quad(x, y, peakindex, stride=1):
|
|
|
834
1330
|
|
|
835
1331
|
Parameters
|
|
836
1332
|
----------
|
|
837
|
-
x :
|
|
1333
|
+
x : NDArray[np.floating[Any]]
|
|
838
1334
|
Independent variable values (e.g., time points).
|
|
839
|
-
y :
|
|
1335
|
+
y : NDArray[np.floating[Any]]
|
|
840
1336
|
Dependent variable values (e.g., signal intensity) corresponding to `x`.
|
|
841
1337
|
peakindex : int
|
|
842
1338
|
Index of the peak in the arrays `x` and `y`.
|
|
@@ -866,6 +1362,14 @@ def refinepeak_quad(x, y, peakindex, stride=1):
|
|
|
866
1362
|
The function uses a quadratic fit to estimate peak properties. It checks for
|
|
867
1363
|
valid conditions before performing the fit, including ensuring that the peak
|
|
868
1364
|
is not at the edge of the data and that it's either a local maximum or minimum.
|
|
1365
|
+
|
|
1366
|
+
Examples
|
|
1367
|
+
--------
|
|
1368
|
+
>>> import numpy as np
|
|
1369
|
+
>>> x = np.linspace(0, 10, 100)
|
|
1370
|
+
>>> y = np.exp(-0.5 * (x - 5)**2) + 0.1 * np.random.random(100)
|
|
1371
|
+
>>> peakloc, peakval, peakwidth, ismax, badfit = refinepeak_quad(x, y, 50, stride=2)
|
|
1372
|
+
>>> print(f"Peak location: {peakloc:.2f}, Peak value: {peakval:.2f}")
|
|
869
1373
|
"""
|
|
870
1374
|
# first make sure this actually is a peak
|
|
871
1375
|
ismax = None
|
|
@@ -897,28 +1401,138 @@ def refinepeak_quad(x, y, peakindex, stride=1):
|
|
|
897
1401
|
|
|
898
1402
|
@conditionaljit2()
|
|
899
1403
|
def findmaxlag_gauss(
|
|
900
|
-
thexcorr_x,
|
|
901
|
-
thexcorr_y,
|
|
902
|
-
lagmin,
|
|
903
|
-
lagmax,
|
|
904
|
-
widthmax,
|
|
905
|
-
edgebufferfrac=0.0,
|
|
906
|
-
threshval=0.0,
|
|
907
|
-
uthreshval=30.0,
|
|
908
|
-
debug=False,
|
|
909
|
-
tweaklims=True,
|
|
910
|
-
zerooutbadfit=True,
|
|
911
|
-
refine=False,
|
|
912
|
-
maxguess=0.0,
|
|
913
|
-
useguess=False,
|
|
914
|
-
searchfrac=0.5,
|
|
915
|
-
fastgauss=False,
|
|
916
|
-
lagmod=1000.0,
|
|
917
|
-
enforcethresh=True,
|
|
918
|
-
absmaxsigma=1000.0,
|
|
919
|
-
absminsigma=0.1,
|
|
920
|
-
displayplots=False,
|
|
921
|
-
):
|
|
1404
|
+
thexcorr_x: NDArray[np.floating[Any]],
|
|
1405
|
+
thexcorr_y: NDArray[np.floating[Any]],
|
|
1406
|
+
lagmin: float,
|
|
1407
|
+
lagmax: float,
|
|
1408
|
+
widthmax: float,
|
|
1409
|
+
edgebufferfrac: float = 0.0,
|
|
1410
|
+
threshval: float = 0.0,
|
|
1411
|
+
uthreshval: float = 30.0,
|
|
1412
|
+
debug: bool = False,
|
|
1413
|
+
tweaklims: bool = True,
|
|
1414
|
+
zerooutbadfit: bool = True,
|
|
1415
|
+
refine: bool = False,
|
|
1416
|
+
maxguess: float = 0.0,
|
|
1417
|
+
useguess: bool = False,
|
|
1418
|
+
searchfrac: float = 0.5,
|
|
1419
|
+
fastgauss: bool = False,
|
|
1420
|
+
lagmod: float = 1000.0,
|
|
1421
|
+
enforcethresh: bool = True,
|
|
1422
|
+
absmaxsigma: float = 1000.0,
|
|
1423
|
+
absminsigma: float = 0.1,
|
|
1424
|
+
displayplots: bool = False,
|
|
1425
|
+
) -> Tuple[int, np.float64, np.float64, np.float64, np.uint16, np.uint16, int, int]:
|
|
1426
|
+
"""
|
|
1427
|
+
Find the maximum lag in a cross-correlation function by fitting a Gaussian curve to the peak.
|
|
1428
|
+
|
|
1429
|
+
This function locates the peak in a cross-correlation function and optionally fits a Gaussian
|
|
1430
|
+
curve to determine the precise lag time, amplitude, and width. It includes extensive error
|
|
1431
|
+
checking and validation to ensure robust results.
|
|
1432
|
+
|
|
1433
|
+
Parameters
|
|
1434
|
+
----------
|
|
1435
|
+
thexcorr_x : NDArray[np.floating[Any]]
|
|
1436
|
+
X-axis values (lag times) of the cross-correlation function.
|
|
1437
|
+
thexcorr_y : NDArray[np.floating[Any]]
|
|
1438
|
+
Y-axis values (correlation coefficients) of the cross-correlation function.
|
|
1439
|
+
lagmin : float
|
|
1440
|
+
Minimum allowable lag value in seconds.
|
|
1441
|
+
lagmax : float
|
|
1442
|
+
Maximum allowable lag value in seconds.
|
|
1443
|
+
widthmax : float
|
|
1444
|
+
Maximum allowable width of the Gaussian peak in seconds.
|
|
1445
|
+
edgebufferfrac : float, optional
|
|
1446
|
+
Fraction of array length to exclude from each edge during search. Default is 0.0.
|
|
1447
|
+
threshval : float, optional
|
|
1448
|
+
Minimum correlation threshold for a valid peak. Default is 0.0.
|
|
1449
|
+
uthreshval : float, optional
|
|
1450
|
+
Upper threshold value (currently unused). Default is 30.0.
|
|
1451
|
+
debug : bool, optional
|
|
1452
|
+
Enable debug output showing initial vs final parameter values. Default is False.
|
|
1453
|
+
tweaklims : bool, optional
|
|
1454
|
+
Automatically adjust search limits to avoid edge artifacts. Default is True.
|
|
1455
|
+
zerooutbadfit : bool, optional
|
|
1456
|
+
Set output to zero when fit fails rather than using initial guess. Default is True.
|
|
1457
|
+
refine : bool, optional
|
|
1458
|
+
Perform least-squares refinement of the Gaussian fit. Default is False.
|
|
1459
|
+
maxguess : float, optional
|
|
1460
|
+
Initial guess for maximum lag position. Used when useguess=True. Default is 0.0.
|
|
1461
|
+
useguess : bool, optional
|
|
1462
|
+
Use the provided maxguess instead of finding peak automatically. Default is False.
|
|
1463
|
+
searchfrac : float, optional
|
|
1464
|
+
Fraction of peak height used to determine initial width estimate. Default is 0.5.
|
|
1465
|
+
fastgauss : bool, optional
|
|
1466
|
+
Use fast non-iterative Gaussian fitting (less accurate). Default is False.
|
|
1467
|
+
lagmod : float, optional
|
|
1468
|
+
Modulus for lag values to handle wraparound. Default is 1000.0.
|
|
1469
|
+
enforcethresh : bool, optional
|
|
1470
|
+
Enforce minimum threshold requirements. Default is True.
|
|
1471
|
+
absmaxsigma : float, optional
|
|
1472
|
+
Absolute maximum allowed sigma (width) value. Default is 1000.0.
|
|
1473
|
+
absminsigma : float, optional
|
|
1474
|
+
Absolute minimum allowed sigma (width) value. Default is 0.1.
|
|
1475
|
+
displayplots : bool, optional
|
|
1476
|
+
Show matplotlib plots of data and fitted curve. Default is False.
|
|
1477
|
+
|
|
1478
|
+
Returns
|
|
1479
|
+
-------
|
|
1480
|
+
maxindex : int
|
|
1481
|
+
Array index of the maximum correlation value.
|
|
1482
|
+
maxlag : numpy.float64
|
|
1483
|
+
Time lag at maximum correlation in seconds.
|
|
1484
|
+
maxval : numpy.float64
|
|
1485
|
+
Maximum correlation coefficient value.
|
|
1486
|
+
maxsigma : numpy.float64
|
|
1487
|
+
Width (sigma) of the fitted Gaussian peak.
|
|
1488
|
+
maskval : numpy.uint16
|
|
1489
|
+
Validity mask (1 = valid fit, 0 = invalid fit).
|
|
1490
|
+
failreason : numpy.uint16
|
|
1491
|
+
Bitwise failure reason code. Possible values:
|
|
1492
|
+
- 0x01: Correlation amplitude below threshold
|
|
1493
|
+
- 0x02: Correlation amplitude above maximum (>1.0)
|
|
1494
|
+
- 0x04: Search window too narrow (<3 points)
|
|
1495
|
+
- 0x08: Fitted width exceeds widthmax
|
|
1496
|
+
- 0x10: Fitted lag outside [lagmin, lagmax] range
|
|
1497
|
+
- 0x20: Peak found at edge of search range
|
|
1498
|
+
- 0x40: Fitting procedure failed
|
|
1499
|
+
- 0x80: Initial parameter estimation failed
|
|
1500
|
+
fitstart : int
|
|
1501
|
+
Starting index used for fitting.
|
|
1502
|
+
fitend : int
|
|
1503
|
+
Ending index used for fitting.
|
|
1504
|
+
|
|
1505
|
+
Notes
|
|
1506
|
+
-----
|
|
1507
|
+
- The function assumes cross-correlation data where Y-values represent correlation
|
|
1508
|
+
coefficients (typically in range [-1, 1]).
|
|
1509
|
+
- When refine=False, uses simple peak-finding based on maximum value.
|
|
1510
|
+
- When refine=True, performs least-squares Gaussian fit for sub-bin precision.
|
|
1511
|
+
- All time-related parameters (lagmin, lagmax, widthmax) should be in the same
|
|
1512
|
+
units as thexcorr_x.
|
|
1513
|
+
- The fastgauss option provides faster but less accurate non-iterative fitting.
|
|
1514
|
+
|
|
1515
|
+
Examples
|
|
1516
|
+
--------
|
|
1517
|
+
Basic usage without refinement:
|
|
1518
|
+
|
|
1519
|
+
>>> maxindex, maxlag, maxval, maxsigma, maskval, failreason, fitstart, fitend = \\
|
|
1520
|
+
... findmaxlag_gauss(lag_times, correlations, -10.0, 10.0, 5.0)
|
|
1521
|
+
>>> if maskval == 1:
|
|
1522
|
+
... print(f"Peak found at lag: {maxlag:.3f} s, correlation: {maxval:.3f}")
|
|
1523
|
+
|
|
1524
|
+
Advanced usage with refinement:
|
|
1525
|
+
|
|
1526
|
+
>>> maxindex, maxlag, maxval, maxsigma, maskval, failreason, fitstart, fitend = \\
|
|
1527
|
+
... findmaxlag_gauss(lag_times, correlations, -5.0, 5.0, 2.0,
|
|
1528
|
+
... refine=True, threshval=0.1, displayplots=True)
|
|
1529
|
+
|
|
1530
|
+
Using an initial guess:
|
|
1531
|
+
|
|
1532
|
+
>>> maxindex, maxlag, maxval, maxsigma, maskval, failreason, fitstart, fitend = \\
|
|
1533
|
+
... findmaxlag_gauss(lag_times, correlations, -10.0, 10.0, 3.0,
|
|
1534
|
+
... useguess=True, maxguess=2.5, refine=True)
|
|
1535
|
+
"""
|
|
922
1536
|
"""
|
|
923
1537
|
Find the maximum lag in a cross-correlation function by fitting a Gaussian curve to the peak.
|
|
924
1538
|
|
|
@@ -928,9 +1542,9 @@ def findmaxlag_gauss(
|
|
|
928
1542
|
|
|
929
1543
|
Parameters
|
|
930
1544
|
----------
|
|
931
|
-
thexcorr_x :
|
|
1545
|
+
thexcorr_x : NDArray
|
|
932
1546
|
X-axis values (lag times) of the cross-correlation function.
|
|
933
|
-
thexcorr_y :
|
|
1547
|
+
thexcorr_y : NDArray
|
|
934
1548
|
Y-axis values (correlation coefficients) of the cross-correlation function.
|
|
935
1549
|
lagmin : float
|
|
936
1550
|
Minimum allowable lag value in seconds.
|
|
@@ -1127,15 +1741,15 @@ def findmaxlag_gauss(
|
|
|
1127
1741
|
)
|
|
1128
1742
|
if maxsigma_init > widthmax:
|
|
1129
1743
|
failreason += FML_BADWIDTH
|
|
1130
|
-
maxsigma_init = widthmax
|
|
1744
|
+
maxsigma_init = np.float64(widthmax)
|
|
1131
1745
|
if (maxval_init < threshval) and enforcethresh:
|
|
1132
1746
|
failreason += FML_BADAMPLOW
|
|
1133
1747
|
if maxval_init < 0.0:
|
|
1134
1748
|
failreason += FML_BADAMPLOW
|
|
1135
|
-
maxval_init = 0.0
|
|
1749
|
+
maxval_init = np.float64(0.0)
|
|
1136
1750
|
if maxval_init > 1.0:
|
|
1137
1751
|
failreason |= FML_BADAMPHIGH
|
|
1138
|
-
maxval_init = 1.0
|
|
1752
|
+
maxval_init = np.float64(1.0)
|
|
1139
1753
|
if failreason > 0:
|
|
1140
1754
|
maskval = np.uint16(0)
|
|
1141
1755
|
if failreason > 0 and zerooutbadfit:
|
|
@@ -1196,9 +1810,9 @@ def findmaxlag_gauss(
|
|
|
1196
1810
|
maskval = np.uint16(0)
|
|
1197
1811
|
else:
|
|
1198
1812
|
if maxsigma > absmaxsigma:
|
|
1199
|
-
maxsigma = absmaxsigma
|
|
1813
|
+
maxsigma = np.float64(absmaxsigma)
|
|
1200
1814
|
else:
|
|
1201
|
-
maxsigma = absminsigma
|
|
1815
|
+
maxsigma = np.float64(absminsigma)
|
|
1202
1816
|
|
|
1203
1817
|
else:
|
|
1204
1818
|
maxval = np.float64(maxval_init)
|
|
@@ -1244,18 +1858,52 @@ def findmaxlag_gauss(
|
|
|
1244
1858
|
|
|
1245
1859
|
|
|
1246
1860
|
@conditionaljit2()
|
|
1247
|
-
def maxindex_noedge(
|
|
1861
|
+
def maxindex_noedge(
|
|
1862
|
+
thexcorr_x: NDArray, thexcorr_y: NDArray, bipolar: bool = False
|
|
1863
|
+
) -> Tuple[int, float]:
|
|
1248
1864
|
"""
|
|
1865
|
+
Find the index of the maximum value in cross-correlation data, avoiding edge effects.
|
|
1866
|
+
|
|
1867
|
+
This function searches for the maximum value in the cross-correlation data while
|
|
1868
|
+
ensuring that the result is not located at the edges of the data array. It handles
|
|
1869
|
+
both unipolar and bipolar cases, returning the index and a flip factor for bipolar
|
|
1870
|
+
cases where the minimum absolute value might be larger than the maximum.
|
|
1249
1871
|
|
|
1250
1872
|
Parameters
|
|
1251
1873
|
----------
|
|
1252
|
-
thexcorr_x
|
|
1253
|
-
|
|
1254
|
-
|
|
1874
|
+
thexcorr_x : NDArray
|
|
1875
|
+
Array containing the x-coordinates of the cross-correlation data
|
|
1876
|
+
thexcorr_y : NDArray
|
|
1877
|
+
Array containing the y-coordinates (cross-correlation values) of the data
|
|
1878
|
+
bipolar : bool, optional
|
|
1879
|
+
If True, considers both positive and negative values when finding the maximum.
|
|
1880
|
+
If False, only considers positive values. Default is False.
|
|
1255
1881
|
|
|
1256
1882
|
Returns
|
|
1257
1883
|
-------
|
|
1884
|
+
Tuple[int, float]
|
|
1885
|
+
A tuple containing:
|
|
1886
|
+
- int: The index of the maximum value in the cross-correlation data
|
|
1887
|
+
- float: Flip factor (-1.0 if bipolar case and minimum absolute value is larger,
|
|
1888
|
+
1.0 otherwise)
|
|
1889
|
+
|
|
1890
|
+
Notes
|
|
1891
|
+
-----
|
|
1892
|
+
The function iteratively adjusts the search range to avoid edge effects by
|
|
1893
|
+
incrementing lowerlim when maxindex is 0, and decrementing upperlim when
|
|
1894
|
+
maxindex equals upperlim. This ensures the returned index is not at the boundaries
|
|
1895
|
+
of the input arrays.
|
|
1258
1896
|
|
|
1897
|
+
Examples
|
|
1898
|
+
--------
|
|
1899
|
+
>>> import numpy as np
|
|
1900
|
+
>>> x = np.array([0, 1, 2, 3, 4])
|
|
1901
|
+
>>> y = np.array([0.1, 0.5, 0.8, 0.3, 0.2])
|
|
1902
|
+
>>> index, flip = maxindex_noedge(x, y)
|
|
1903
|
+
>>> print(index)
|
|
1904
|
+
2
|
|
1905
|
+
>>> print(flip)
|
|
1906
|
+
1.0
|
|
1259
1907
|
"""
|
|
1260
1908
|
lowerlim = 0
|
|
1261
1909
|
upperlim = len(thexcorr_x) - 1
|
|
@@ -1284,24 +1932,53 @@ def maxindex_noedge(thexcorr_x, thexcorr_y, bipolar=False):
|
|
|
1284
1932
|
return maxindex, flipfac
|
|
1285
1933
|
|
|
1286
1934
|
|
|
1287
|
-
def gaussfitsk(
|
|
1935
|
+
def gaussfitsk(
|
|
1936
|
+
height: float, loc: float, width: float, skewness: float, xvals: ArrayLike, yvals: ArrayLike
|
|
1937
|
+
) -> NDArray:
|
|
1288
1938
|
"""
|
|
1939
|
+
Fit a skewed Gaussian function to data using least squares optimization.
|
|
1940
|
+
|
|
1941
|
+
This function performs least squares fitting of a skewed Gaussian model to the
|
|
1942
|
+
provided data points. The model includes parameters for height, location, width,
|
|
1943
|
+
and skewness of the Gaussian distribution.
|
|
1289
1944
|
|
|
1290
1945
|
Parameters
|
|
1291
1946
|
----------
|
|
1292
|
-
height
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1947
|
+
height : float
|
|
1948
|
+
The amplitude or height of the Gaussian peak.
|
|
1949
|
+
loc : float
|
|
1950
|
+
The location (mean) of the Gaussian peak.
|
|
1951
|
+
width : float
|
|
1952
|
+
The width (standard deviation) of the Gaussian peak.
|
|
1953
|
+
skewness : float
|
|
1954
|
+
The skewness parameter that controls the asymmetry of the Gaussian.
|
|
1955
|
+
xvals : array-like
|
|
1956
|
+
The x-coordinates of the data points to be fitted.
|
|
1957
|
+
yvals : array-like
|
|
1958
|
+
The y-coordinates of the data points to be fitted.
|
|
1298
1959
|
|
|
1299
1960
|
Returns
|
|
1300
1961
|
-------
|
|
1962
|
+
ndarray
|
|
1963
|
+
Array containing the optimized parameters [height, loc, width, skewness] that
|
|
1964
|
+
best fit the data according to the least squares method.
|
|
1301
1965
|
|
|
1966
|
+
Notes
|
|
1967
|
+
-----
|
|
1968
|
+
This function uses `scipy.optimize.leastsq` internally for the optimization
|
|
1969
|
+
process. The fitting is performed using the `gaussskresiduals` residual function
|
|
1970
|
+
which should be defined elsewhere in the codebase.
|
|
1971
|
+
|
|
1972
|
+
Examples
|
|
1973
|
+
--------
|
|
1974
|
+
>>> import numpy as np
|
|
1975
|
+
>>> x = np.linspace(-5, 5, 100)
|
|
1976
|
+
>>> y = gaussfitsk(1.0, 0.0, 1.0, 0.0, x, y_data)
|
|
1977
|
+
>>> print(y)
|
|
1978
|
+
[height_opt, loc_opt, width_opt, skewness_opt]
|
|
1302
1979
|
"""
|
|
1303
1980
|
plsq, dummy = sp.optimize.leastsq(
|
|
1304
|
-
|
|
1981
|
+
gaussskresiduals,
|
|
1305
1982
|
np.array([height, loc, width, skewness]),
|
|
1306
1983
|
args=(yvals, xvals),
|
|
1307
1984
|
maxfev=5000,
|
|
@@ -1309,72 +1986,243 @@ def gaussfitsk(height, loc, width, skewness, xvals, yvals):
|
|
|
1309
1986
|
return plsq
|
|
1310
1987
|
|
|
1311
1988
|
|
|
1312
|
-
def gaussfunc(x, height, loc, FWHM):
|
|
1989
|
+
def gaussfunc(x: NDArray, height: float, loc: float, FWHM: float) -> NDArray:
|
|
1990
|
+
"""
|
|
1991
|
+
Calculate a Gaussian function.
|
|
1992
|
+
|
|
1993
|
+
This function computes a Gaussian (normal) distribution with specified height,
|
|
1994
|
+
location, and Full Width at Half Maximum (FWHM).
|
|
1995
|
+
|
|
1996
|
+
Parameters
|
|
1997
|
+
----------
|
|
1998
|
+
x : NDArray
|
|
1999
|
+
Array of values at which to evaluate the Gaussian function.
|
|
2000
|
+
height : float
|
|
2001
|
+
The maximum height of the Gaussian curve.
|
|
2002
|
+
loc : float
|
|
2003
|
+
The location (mean) of the Gaussian curve.
|
|
2004
|
+
FWHM : float
|
|
2005
|
+
The Full Width at Half Maximum of the Gaussian curve.
|
|
2006
|
+
|
|
2007
|
+
Returns
|
|
2008
|
+
-------
|
|
2009
|
+
NDArray
|
|
2010
|
+
Array of Gaussian function values evaluated at x.
|
|
2011
|
+
|
|
2012
|
+
Notes
|
|
2013
|
+
-----
|
|
2014
|
+
The Gaussian function is defined as:
|
|
2015
|
+
f(x) = height * exp(-((x - loc) ** 2) / (2 * (FWHM / 2.355) ** 2))
|
|
2016
|
+
|
|
2017
|
+
The conversion from FWHM to standard deviation (sigma) uses the relationship:
|
|
2018
|
+
sigma = FWHM / (2 * sqrt(2 * log(2))) ≈ FWHM / 2.355
|
|
2019
|
+
|
|
2020
|
+
Examples
|
|
2021
|
+
--------
|
|
2022
|
+
>>> import numpy as np
|
|
2023
|
+
>>> x = np.linspace(-5, 5, 100)
|
|
2024
|
+
>>> y = gaussfunc(x, height=1.0, loc=0.0, FWHM=2.0)
|
|
2025
|
+
>>> print(y.shape)
|
|
2026
|
+
(100,)
|
|
2027
|
+
"""
|
|
1313
2028
|
return height * np.exp(-((x - loc) ** 2) / (2 * (FWHM / 2.355) ** 2))
|
|
1314
2029
|
|
|
1315
2030
|
|
|
1316
|
-
def gaussfit2(
|
|
2031
|
+
def gaussfit2(
|
|
2032
|
+
height: float, loc: float, width: float, xvals: NDArray, yvals: NDArray
|
|
2033
|
+
) -> Tuple[float, float, float]:
|
|
2034
|
+
"""
|
|
2035
|
+
Calculate a Gaussian function.
|
|
2036
|
+
|
|
2037
|
+
This function computes a Gaussian (normal) distribution with specified height,
|
|
2038
|
+
location, and Full Width at Half Maximum (FWHM).
|
|
2039
|
+
|
|
2040
|
+
Parameters
|
|
2041
|
+
----------
|
|
2042
|
+
x : array_like
|
|
2043
|
+
Input values for which to compute the Gaussian function
|
|
2044
|
+
height : float
|
|
2045
|
+
Height (amplitude) of the Gaussian peak
|
|
2046
|
+
loc : float
|
|
2047
|
+
Location (mean) of the Gaussian peak
|
|
2048
|
+
FWHM : float
|
|
2049
|
+
Full Width at Half Maximum of the Gaussian peak
|
|
2050
|
+
|
|
2051
|
+
Returns
|
|
2052
|
+
-------
|
|
2053
|
+
ndarray
|
|
2054
|
+
Array of Gaussian function values computed at input x values
|
|
2055
|
+
|
|
2056
|
+
Notes
|
|
2057
|
+
-----
|
|
2058
|
+
The Gaussian function is computed using the formula:
|
|
2059
|
+
f(x) = height * exp(-((x - loc)^2) / (2 * (FWHM / 2.355)^2))
|
|
2060
|
+
|
|
2061
|
+
The conversion from FWHM to sigma (standard deviation) uses the relationship:
|
|
2062
|
+
sigma = FWHM / 2.355
|
|
2063
|
+
|
|
2064
|
+
Examples
|
|
2065
|
+
--------
|
|
2066
|
+
>>> import numpy as np
|
|
2067
|
+
>>> x = np.linspace(-5, 5, 100)
|
|
2068
|
+
>>> y = gaussfunc(x, height=1.0, loc=0.0, FWHM=2.0)
|
|
2069
|
+
>>> print(y.shape)
|
|
2070
|
+
(100,)
|
|
2071
|
+
"""
|
|
1317
2072
|
popt, pcov = curve_fit(gaussfunc, xvals, yvals, p0=[height, loc, width])
|
|
1318
2073
|
return popt[0], popt[1], popt[2]
|
|
1319
2074
|
|
|
1320
2075
|
|
|
1321
|
-
def sincfunc(x, height, loc, FWHM, baseline):
|
|
2076
|
+
def sincfunc(x: NDArray, height: float, loc: float, FWHM: float, baseline: float) -> NDArray:
|
|
2077
|
+
"""
|
|
2078
|
+
Compute a scaled and shifted sinc function.
|
|
2079
|
+
|
|
2080
|
+
This function evaluates a sinc function with specified height, location,
|
|
2081
|
+
full width at half maximum, and baseline offset. The sinc function is
|
|
2082
|
+
scaled by a factor that relates the FWHM to the sinc function's natural
|
|
2083
|
+
scaling.
|
|
2084
|
+
|
|
2085
|
+
Parameters
|
|
2086
|
+
----------
|
|
2087
|
+
x : NDArray
|
|
2088
|
+
Input array of values where the function is evaluated.
|
|
2089
|
+
height : float
|
|
2090
|
+
Height of the sinc function peak.
|
|
2091
|
+
loc : float
|
|
2092
|
+
Location (center) of the sinc function peak.
|
|
2093
|
+
FWHM : float
|
|
2094
|
+
Full width at half maximum of the sinc function.
|
|
2095
|
+
baseline : float
|
|
2096
|
+
Baseline offset added to the sinc function values.
|
|
2097
|
+
|
|
2098
|
+
Returns
|
|
2099
|
+
-------
|
|
2100
|
+
NDArray
|
|
2101
|
+
Array of sinc function values with the same shape as input `x`.
|
|
2102
|
+
|
|
2103
|
+
Notes
|
|
2104
|
+
-----
|
|
2105
|
+
The sinc function is defined as sin(πx)/(πx) with the convention that
|
|
2106
|
+
sinc(0) = 1. The scaling factor 3.79098852 is chosen to relate the FWHM
|
|
2107
|
+
to the natural sinc function properties.
|
|
2108
|
+
|
|
2109
|
+
Examples
|
|
2110
|
+
--------
|
|
2111
|
+
>>> import numpy as np
|
|
2112
|
+
>>> x = np.linspace(-5, 5, 100)
|
|
2113
|
+
>>> y = sincfunc(x, height=2.0, loc=0.0, FWHM=1.0, baseline=1.0)
|
|
2114
|
+
>>> print(y.shape)
|
|
2115
|
+
(100,)
|
|
2116
|
+
"""
|
|
1322
2117
|
return height * np.sinc((3.79098852 / (FWHM * np.pi)) * (x - loc)) + baseline
|
|
1323
2118
|
|
|
1324
2119
|
|
|
1325
2120
|
# found this sinc fitting routine (and optimization) here:
|
|
1326
2121
|
# https://stackoverflow.com/questions/49676116/why-cant-scipy-optimize-curve-fit-fit-my-data-using-a-numpy-sinc-function
|
|
1327
|
-
def sincfit(
|
|
2122
|
+
def sincfit(
|
|
2123
|
+
height: float, loc: float, width: float, baseline: float, xvals: NDArray, yvals: NDArray
|
|
2124
|
+
) -> Tuple[NDArray, NDArray]:
|
|
2125
|
+
"""
|
|
2126
|
+
Sinc function for fitting and modeling.
|
|
2127
|
+
|
|
2128
|
+
This function implements a scaled and shifted sinc function commonly used in
|
|
2129
|
+
signal processing and data fitting applications.
|
|
2130
|
+
|
|
2131
|
+
Parameters
|
|
2132
|
+
----------
|
|
2133
|
+
x : ndarray
|
|
2134
|
+
Array of x-values where the function is evaluated.
|
|
2135
|
+
height : float
|
|
2136
|
+
Height of the sinc function peak.
|
|
2137
|
+
loc : float
|
|
2138
|
+
Location (center) of the sinc function peak.
|
|
2139
|
+
FWHM : float
|
|
2140
|
+
Full Width at Half Maximum of the sinc function.
|
|
2141
|
+
baseline : float
|
|
2142
|
+
Baseline offset added to the sinc function.
|
|
2143
|
+
|
|
2144
|
+
Returns
|
|
2145
|
+
-------
|
|
2146
|
+
ndarray
|
|
2147
|
+
Array of sinc function values evaluated at x.
|
|
2148
|
+
|
|
2149
|
+
Notes
|
|
2150
|
+
-----
|
|
2151
|
+
The sinc function is defined as sin(πx)/(πx) with the convention that sinc(0) = 1.
|
|
2152
|
+
This implementation uses a scaled version with the specified FWHM parameter.
|
|
2153
|
+
|
|
2154
|
+
Examples
|
|
2155
|
+
--------
|
|
2156
|
+
>>> import numpy as np
|
|
2157
|
+
>>> x = np.linspace(-5, 5, 100)
|
|
2158
|
+
>>> y = sincfunc(x, height=1.0, loc=0.0, FWHM=2.0, baseline=0.0)
|
|
2159
|
+
>>> print(y.shape)
|
|
2160
|
+
(100,)
|
|
2161
|
+
"""
|
|
1328
2162
|
popt, pcov = curve_fit(sincfunc, xvals, yvals, p0=[height, loc, width, baseline])
|
|
1329
2163
|
return popt, pcov
|
|
1330
2164
|
|
|
1331
2165
|
|
|
1332
|
-
def gaussfit(
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
2166
|
+
def gaussfit(
|
|
2167
|
+
height: float, loc: float, width: float, xvals: NDArray, yvals: NDArray
|
|
2168
|
+
) -> Tuple[float, float, float]:
|
|
2169
|
+
"""
|
|
2170
|
+
Performs a non-linear least squares fit of a Gaussian function to data.
|
|
2171
|
+
|
|
2172
|
+
This routine uses `scipy.optimize.leastsq` to find the optimal parameters
|
|
2173
|
+
(height, location, and width) that best describe a Gaussian curve fitted
|
|
2174
|
+
to the provided `yvals` data against `xvals`. It requires an external
|
|
2175
|
+
`gaussresiduals` function to compute the residuals.
|
|
2176
|
+
|
|
2177
|
+
Parameters
|
|
2178
|
+
----------
|
|
2179
|
+
height : float
|
|
2180
|
+
Initial guess for the amplitude or peak height of the Gaussian.
|
|
2181
|
+
loc : float
|
|
2182
|
+
Initial guess for the mean (center) of the Gaussian.
|
|
2183
|
+
width : float
|
|
2184
|
+
Initial guess for the standard deviation (width) of the Gaussian.
|
|
2185
|
+
xvals : NDArray
|
|
2186
|
+
The independent variable data points.
|
|
2187
|
+
yvals : NDArray
|
|
2188
|
+
The dependent variable data points to which the Gaussian will be fitted.
|
|
2189
|
+
|
|
2190
|
+
Returns
|
|
2191
|
+
-------
|
|
2192
|
+
tuple of float
|
|
2193
|
+
A tuple containing the fitted parameters:
|
|
2194
|
+
- height: The fitted height (amplitude) of the Gaussian.
|
|
2195
|
+
- loc: The fitted location (mean) of the Gaussian.
|
|
2196
|
+
- width: The fitted width (standard deviation) of the Gaussian.
|
|
2197
|
+
|
|
2198
|
+
Notes
|
|
2199
|
+
-----
|
|
2200
|
+
- This function relies on an external function `gaussresiduals(params, y, x)`
|
|
2201
|
+
which should calculate the difference between the observed `y` values and
|
|
2202
|
+
the Gaussian function evaluated at `x` with the given `params` (height, loc, width).
|
|
2203
|
+
- `scipy.optimize.leastsq` is used for the optimization, which requires
|
|
2204
|
+
`scipy` and `numpy` to be imported (e.g., `import scipy.optimize as sp`
|
|
2205
|
+
and `import numpy as np`).
|
|
2206
|
+
- The optimization may fail if initial guesses are too far from the true values
|
|
2207
|
+
or if the data does not well-support a Gaussian fit.
|
|
2208
|
+
|
|
2209
|
+
Examples
|
|
2210
|
+
--------
|
|
2211
|
+
>>> import numpy as np
|
|
2212
|
+
>>> x = np.linspace(-5, 5, 100)
|
|
2213
|
+
>>> y = 2 * np.exp(-0.5 * ((x - 1) / 0.5)**2) + np.random.normal(0, 0.1, 100)
|
|
2214
|
+
>>> height, loc, width = gaussfit(1.0, 0.0, 1.0, x, y)
|
|
2215
|
+
>>> print(f"Fitted height: {height:.2f}, location: {loc:.2f}, width: {width:.2f}")
|
|
2216
|
+
"""
|
|
1370
2217
|
plsq, dummy = sp.optimize.leastsq(
|
|
1371
2218
|
gaussresiduals, np.array([height, loc, width]), args=(yvals, xvals), maxfev=5000
|
|
1372
2219
|
)
|
|
1373
2220
|
return plsq[0], plsq[1], plsq[2]
|
|
1374
2221
|
|
|
1375
2222
|
|
|
1376
|
-
def gram_schmidt(theregressors, debug=False):
|
|
1377
|
-
|
|
2223
|
+
def gram_schmidt(theregressors: NDArray, debug: bool = False) -> NDArray:
|
|
2224
|
+
"""
|
|
2225
|
+
Performs Gram-Schmidt orthogonalization on a set of vectors.
|
|
1378
2226
|
|
|
1379
2227
|
This routine takes a set of input vectors (rows of a 2D array) and
|
|
1380
2228
|
transforms them into an orthonormal basis using the Gram-Schmidt process.
|
|
@@ -1382,29 +2230,45 @@ def gram_schmidt(theregressors, debug=False):
|
|
|
1382
2230
|
have a unit norm. Linearly dependent vectors are effectively skipped
|
|
1383
2231
|
if their orthogonal component is negligible.
|
|
1384
2232
|
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
2233
|
+
Parameters
|
|
2234
|
+
----------
|
|
2235
|
+
theregressors : numpy.ndarray
|
|
2236
|
+
A 2D NumPy array where each row represents a vector to be orthogonalized.
|
|
2237
|
+
debug : bool, optional
|
|
2238
|
+
If True, prints debug information about input and output dimensions.
|
|
2239
|
+
Default is False.
|
|
2240
|
+
|
|
2241
|
+
Returns
|
|
2242
|
+
-------
|
|
2243
|
+
numpy.ndarray
|
|
2244
|
+
A 2D NumPy array representing the orthonormal basis. Each row is an
|
|
2245
|
+
orthonormal vector. The number of rows may be less than the input if
|
|
2246
|
+
some vectors were linearly dependent.
|
|
1390
2247
|
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
2248
|
+
Notes
|
|
2249
|
+
-----
|
|
2250
|
+
- The function normalizes each orthogonalized vector to unit length.
|
|
2251
|
+
- A small tolerance (1e-10) is used to check if a vector's orthogonal
|
|
2252
|
+
component is effectively zero, indicating linear dependence.
|
|
2253
|
+
- Requires the `numpy` library for array operations and linear algebra.
|
|
1395
2254
|
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
2255
|
+
Examples
|
|
2256
|
+
--------
|
|
2257
|
+
>>> import numpy as np
|
|
2258
|
+
>>> vectors = np.array([[2, 1], [3, 4]])
|
|
2259
|
+
>>> basis = gram_schmidt(vectors)
|
|
2260
|
+
>>> print(basis)
|
|
2261
|
+
[[0.89442719 0.4472136 ]
|
|
2262
|
+
[-0.4472136 0.89442719]]
|
|
1401
2263
|
"""
|
|
1402
2264
|
|
|
1403
2265
|
if debug:
|
|
1404
2266
|
print("gram_schmidt, input dimensions:", theregressors.shape)
|
|
1405
|
-
basis = []
|
|
2267
|
+
basis: list[float] = []
|
|
1406
2268
|
for i in range(theregressors.shape[0]):
|
|
1407
|
-
w = theregressors[i, :] - np.sum(
|
|
2269
|
+
w = theregressors[i, :] - np.sum(
|
|
2270
|
+
np.array(np.dot(theregressors[i, :], b) * b) for b in basis
|
|
2271
|
+
)
|
|
1408
2272
|
if (np.fabs(w) > 1e-10).any():
|
|
1409
2273
|
basis.append(w / np.linalg.norm(w))
|
|
1410
2274
|
outputbasis = np.array(basis)
|
|
@@ -1413,36 +2277,51 @@ def gram_schmidt(theregressors, debug=False):
|
|
|
1413
2277
|
return outputbasis
|
|
1414
2278
|
|
|
1415
2279
|
|
|
1416
|
-
def mlproject(thefit, theevs, intercept):
|
|
1417
|
-
|
|
2280
|
+
def mlproject(thefit: NDArray, theevs: list, intercept: bool) -> NDArray:
|
|
2281
|
+
"""
|
|
2282
|
+
Calculates a linear combination (weighted sum) of explanatory variables.
|
|
1418
2283
|
|
|
1419
2284
|
This routine computes a predicted output by multiplying a set of
|
|
1420
2285
|
explanatory variables by corresponding coefficients and summing the results.
|
|
1421
2286
|
It can optionally include an intercept term. This is a common operation
|
|
1422
2287
|
in linear regression and other statistical models.
|
|
1423
2288
|
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
2289
|
+
Parameters
|
|
2290
|
+
----------
|
|
2291
|
+
thefit : NDArray
|
|
2292
|
+
A 1D array or list of coefficients (weights) to be applied to the
|
|
2293
|
+
explanatory variables. If `intercept` is True, the first element of
|
|
2294
|
+
`thefit` is treated as the intercept.
|
|
2295
|
+
theevs : list of numpy.ndarray
|
|
2296
|
+
A list where each element is a 1D NumPy array representing an
|
|
2297
|
+
explanatory variable (feature time series). The length of `theevs`
|
|
2298
|
+
should match the number of non-intercept coefficients in `thefit`.
|
|
2299
|
+
intercept : bool
|
|
2300
|
+
If True, the first element of `thefit` is used as an intercept term,
|
|
2301
|
+
and the remaining elements of `thefit` are applied to `theevs`. If False,
|
|
2302
|
+
no intercept is added, and all elements of `thefit` are applied to
|
|
2303
|
+
`theevs` starting from the first element.
|
|
2304
|
+
|
|
2305
|
+
Returns
|
|
2306
|
+
-------
|
|
2307
|
+
NDArray
|
|
2308
|
+
A 1D NumPy array representing the calculated linear combination.
|
|
2309
|
+
Its length will be the same as the explanatory variables.
|
|
2310
|
+
|
|
2311
|
+
Notes
|
|
2312
|
+
-----
|
|
2313
|
+
The calculation performed is conceptually equivalent to:
|
|
2314
|
+
`output = intercept_term + (coefficient_1 * ev_1) + (coefficient_2 * ev_2) + ...`
|
|
2315
|
+
where `intercept_term` is `thefit[0]` if `intercept` is True, otherwise 0.
|
|
2316
|
+
|
|
2317
|
+
Examples
|
|
2318
|
+
--------
|
|
2319
|
+
>>> import numpy as np
|
|
2320
|
+
>>> thefit = np.array([1.0, 2.0, 3.0])
|
|
2321
|
+
>>> theevs = [np.array([1, 2, 3]), np.array([4, 5, 6])]
|
|
2322
|
+
>>> result = mlproject(thefit, theevs, intercept=True)
|
|
2323
|
+
>>> print(result)
|
|
2324
|
+
[ 9. 14. 19.]
|
|
1446
2325
|
"""
|
|
1447
2326
|
|
|
1448
2327
|
thedest = theevs[0] * 0.0
|
|
@@ -1456,29 +2335,46 @@ def mlproject(thefit, theevs, intercept):
|
|
|
1456
2335
|
return thedest
|
|
1457
2336
|
|
|
1458
2337
|
|
|
1459
|
-
def olsregress(
|
|
2338
|
+
def olsregress(
|
|
2339
|
+
X: ArrayLike, y: ArrayLike, intercept: bool = True, debug: bool = False
|
|
2340
|
+
) -> Tuple[NDArray, float]:
|
|
1460
2341
|
"""
|
|
2342
|
+
Perform ordinary least squares regression.
|
|
1461
2343
|
|
|
1462
2344
|
Parameters
|
|
1463
2345
|
----------
|
|
1464
|
-
X
|
|
1465
|
-
|
|
1466
|
-
|
|
2346
|
+
X : array-like
|
|
2347
|
+
Independent variables (features) matrix of shape (n_samples, n_features).
|
|
2348
|
+
y : array-like
|
|
2349
|
+
Dependent variable (target) vector of shape (n_samples,).
|
|
2350
|
+
intercept : bool, optional
|
|
2351
|
+
Whether to add a constant term (intercept) to the model. Default is True.
|
|
2352
|
+
debug : bool, optional
|
|
2353
|
+
Whether to enable debug mode. Default is False.
|
|
1467
2354
|
|
|
1468
2355
|
Returns
|
|
1469
2356
|
-------
|
|
2357
|
+
tuple
|
|
2358
|
+
A tuple containing:
|
|
2359
|
+
- params : ndarray
|
|
2360
|
+
Estimated regression coefficients (including intercept if specified)
|
|
2361
|
+
- rsquared : float
|
|
2362
|
+
Square root of the coefficient of determination (R-squared)
|
|
1470
2363
|
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
intercept: Specifies whether or not the slope intercept should be considered.
|
|
1477
|
-
|
|
1478
|
-
The routine computes the coefficients (b_0, b_1, ..., b_p) from the data (x,y) under
|
|
1479
|
-
the assumption that y = b0 + b_1 * x_1 + b_2 * x_2 + ... + b_p * x_p.
|
|
2364
|
+
Notes
|
|
2365
|
+
-----
|
|
2366
|
+
This function uses statsmodels OLS regression to fit a linear model.
|
|
2367
|
+
If intercept is True, a constant term is added to the design matrix.
|
|
2368
|
+
The function returns the regression parameters and the square root of R-squared.
|
|
1480
2369
|
|
|
1481
|
-
|
|
2370
|
+
Examples
|
|
2371
|
+
--------
|
|
2372
|
+
>>> import numpy as np
|
|
2373
|
+
>>> X = np.array([[1, 2], [3, 4], [5, 6]])
|
|
2374
|
+
>>> y = np.array([1, 2, 3])
|
|
2375
|
+
>>> params, r_squared = olsregress(X, y)
|
|
2376
|
+
>>> print(params)
|
|
2377
|
+
[0.1 0.4 0.2]
|
|
1482
2378
|
"""
|
|
1483
2379
|
if intercept:
|
|
1484
2380
|
X = sm.add_constant(X, prepend=True)
|
|
@@ -1487,29 +2383,59 @@ def olsregress(X, y, intercept=True, debug=False):
|
|
|
1487
2383
|
return thefit.params, np.sqrt(thefit.rsquared)
|
|
1488
2384
|
|
|
1489
2385
|
|
|
1490
|
-
|
|
2386
|
+
# @conditionaljit()
|
|
2387
|
+
def mlregress(
|
|
2388
|
+
X: NDArray[np.floating[Any]],
|
|
2389
|
+
y: NDArray[np.floating[Any]],
|
|
2390
|
+
intercept: bool = True,
|
|
2391
|
+
debug: bool = False,
|
|
2392
|
+
) -> Tuple[NDArray[np.floating[Any]], float]:
|
|
1491
2393
|
"""
|
|
2394
|
+
Perform multiple linear regression and return coefficients and R-squared value.
|
|
2395
|
+
|
|
2396
|
+
This function fits a multiple linear regression model to the input data and
|
|
2397
|
+
returns the regression coefficients (including intercept if specified) along
|
|
2398
|
+
with the coefficient of determination (R-squared).
|
|
1492
2399
|
|
|
1493
2400
|
Parameters
|
|
1494
2401
|
----------
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
2402
|
+
X : NDArray[np.floating[Any]]
|
|
2403
|
+
Input feature matrix of shape (n_samples, n_features) or (n_samples,)
|
|
2404
|
+
If 1D array is provided, it will be treated as a single feature.
|
|
2405
|
+
y : NDArray[np.floating[Any]]
|
|
2406
|
+
Target values of shape (n_samples,) or (n_samples, 1)
|
|
2407
|
+
If 1D array is provided, it will be treated as a single target.
|
|
2408
|
+
intercept : bool, optional
|
|
2409
|
+
Whether to calculate and include intercept term in the model.
|
|
2410
|
+
Default is True.
|
|
2411
|
+
debug : bool, optional
|
|
2412
|
+
If True, print debug information about the input shapes and processing steps.
|
|
2413
|
+
Default is False.
|
|
1498
2414
|
|
|
1499
2415
|
Returns
|
|
1500
2416
|
-------
|
|
2417
|
+
Tuple[NDArray[np.floating[Any]], float]
|
|
2418
|
+
A tuple containing:
|
|
2419
|
+
- coefficients : NDArray[np.floating[Any]] of shape (n_features + 1, 1) where the first
|
|
2420
|
+
element is the intercept (if intercept=True) and subsequent elements
|
|
2421
|
+
are the regression coefficients for each feature
|
|
2422
|
+
- R2 : float, the coefficient of determination (R-squared) of the fitted model
|
|
1501
2423
|
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
The routine computes the coefficients (b_0, b_1, ..., b_p) from the data (x,y) under
|
|
1510
|
-
the assumption that y = b0 + b_1 * x_1 + b_2 * x_2 + ... + b_p * x_p.
|
|
2424
|
+
Notes
|
|
2425
|
+
-----
|
|
2426
|
+
The function automatically handles shape adjustments for input arrays,
|
|
2427
|
+
ensuring that the number of samples in X matches the number of target values in y.
|
|
2428
|
+
If the input X is 1D, it will be converted to 2D. If the shapes don't match initially,
|
|
2429
|
+
the function will attempt to transpose X to match the number of samples in y.
|
|
1511
2430
|
|
|
1512
|
-
|
|
2431
|
+
Examples
|
|
2432
|
+
--------
|
|
2433
|
+
>>> import numpy as np
|
|
2434
|
+
>>> X = np.array([[1, 2], [3, 4], [5, 6]])
|
|
2435
|
+
>>> y = np.array([3, 7, 11])
|
|
2436
|
+
>>> coeffs, r2 = mlregress(X, y)
|
|
2437
|
+
>>> print(f"Coefficients: {coeffs.flatten()}")
|
|
2438
|
+
>>> print(f"R-squared: {r2}")
|
|
1513
2439
|
"""
|
|
1514
2440
|
if debug:
|
|
1515
2441
|
print(f"mlregress initial: {X.shape=}, {y.shape=}")
|
|
@@ -1542,51 +2468,77 @@ def mlregress(X, y, intercept=True, debug=False):
|
|
|
1542
2468
|
|
|
1543
2469
|
|
|
1544
2470
|
def calcexpandedregressors(
|
|
1545
|
-
confounddict
|
|
1546
|
-
|
|
1547
|
-
|
|
2471
|
+
confounddict: dict,
|
|
2472
|
+
labels: Optional[list] = None,
|
|
2473
|
+
start: int = 0,
|
|
2474
|
+
end: int = -1,
|
|
2475
|
+
deriv: bool = True,
|
|
2476
|
+
order: int = 1,
|
|
2477
|
+
debug: bool = False,
|
|
2478
|
+
) -> Tuple[NDArray, list]:
|
|
2479
|
+
"""
|
|
2480
|
+
Calculate expanded regressors from a dictionary of confound vectors.
|
|
1548
2481
|
|
|
1549
2482
|
This routine generates a comprehensive set of motion-related regressors by
|
|
1550
2483
|
including higher-order polynomial terms and derivatives of the original
|
|
1551
2484
|
confound timecourses. It is commonly used in neuroimaging analysis to
|
|
1552
2485
|
account for subject movement.
|
|
1553
2486
|
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
2487
|
+
Parameters
|
|
2488
|
+
----------
|
|
2489
|
+
confounddict : dict
|
|
2490
|
+
A dictionary where keys are labels (e.g., 'rot_x', 'trans_y') and values
|
|
2491
|
+
are the corresponding 1D time series (NumPy arrays or lists).
|
|
2492
|
+
labels : list, optional
|
|
2493
|
+
A list of specific confound labels from `confounddict` to process. If None,
|
|
2494
|
+
all labels in `confounddict` will be used. Default is None.
|
|
2495
|
+
start : int, optional
|
|
2496
|
+
The starting index (inclusive) for slicing the timecourses. Default is 0.
|
|
2497
|
+
end : int, optional
|
|
2498
|
+
The ending index (exclusive) for slicing the timecourses. If -1, slicing
|
|
2499
|
+
continues to the end of the timecourse. Default is -1.
|
|
2500
|
+
deriv : bool, optional
|
|
2501
|
+
If True, the first derivative of each selected timecourse (and its
|
|
2502
|
+
polynomial expansions) is calculated and included as a regressor.
|
|
2503
|
+
Default is True.
|
|
2504
|
+
order : int, optional
|
|
2505
|
+
The polynomial order for expansion. If `order > 1`, terms like `label^2`,
|
|
2506
|
+
`label^3`, up to `label^order` will be included. Default is 1 (no
|
|
2507
|
+
polynomial expansion).
|
|
2508
|
+
debug : bool, optional
|
|
2509
|
+
If True, prints debug information during processing. Default is False.
|
|
2510
|
+
|
|
2511
|
+
Returns
|
|
2512
|
+
-------
|
|
2513
|
+
tuple of (numpy.ndarray, list)
|
|
2514
|
+
A tuple containing:
|
|
2515
|
+
- outputregressors : numpy.ndarray
|
|
2516
|
+
A 2D NumPy array where each row represents a generated regressor
|
|
2517
|
+
(original, polynomial, or derivative) and columns represent time points.
|
|
2518
|
+
- outlabels : list of str
|
|
2519
|
+
A list of strings providing the labels for each row in `outputregressors`,
|
|
2520
|
+
indicating what each regressor represents (e.g., 'rot_x', 'rot_x^2',
|
|
2521
|
+
'rot_x_deriv').
|
|
2522
|
+
|
|
2523
|
+
Notes
|
|
2524
|
+
-----
|
|
2525
|
+
- The derivatives are calculated using `numpy.gradient`.
|
|
2526
|
+
- The function handles slicing of the timecourses based on `start` and `end`
|
|
2527
|
+
parameters.
|
|
2528
|
+
- The output regressors are concatenated horizontally to form the final
|
|
2529
|
+
`outputregressors` array.
|
|
2530
|
+
|
|
2531
|
+
Examples
|
|
2532
|
+
--------
|
|
2533
|
+
>>> confounddict = {
|
|
2534
|
+
... 'rot_x': [0.1, 0.2, 0.3],
|
|
2535
|
+
... 'trans_y': [0.05, 0.1, 0.15]
|
|
2536
|
+
... }
|
|
2537
|
+
>>> regressors, labels = calcexpandedregressors(confounddict, order=2, deriv=True)
|
|
2538
|
+
>>> print(regressors.shape)
|
|
2539
|
+
(4, 3)
|
|
2540
|
+
>>> print(labels)
|
|
2541
|
+
['rot_x', 'trans_y', 'rot_x^2', 'trans_y^2', 'rot_x_deriv', 'trans_y_deriv']
|
|
1590
2542
|
"""
|
|
1591
2543
|
if labels is None:
|
|
1592
2544
|
localconfounddict = confounddict.copy()
|
|
@@ -1635,30 +2587,60 @@ def calcexpandedregressors(
|
|
|
1635
2587
|
activecolumn += 1
|
|
1636
2588
|
return outputregressors, outlabels
|
|
1637
2589
|
|
|
2590
|
+
@conditionaljit()
|
|
2591
|
+
def derivativelinfitfilt(
|
|
2592
|
+
thedata: NDArray, theevs: NDArray, nderivs: int = 1, debug: bool = False
|
|
2593
|
+
) -> Tuple[NDArray, NDArray, NDArray, float, NDArray]:
|
|
2594
|
+
"""
|
|
2595
|
+
Perform multicomponent expansion on explanatory variables and fit the data using linear regression.
|
|
1638
2596
|
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
in thenewevs and return the result.
|
|
2597
|
+
First, each explanatory variable is expanded into multiple components by taking
|
|
2598
|
+
successive derivatives (or powers, in the case of scalar inputs). Then, a linear
|
|
2599
|
+
fit is performed on the input data using the expanded set of explanatory variables.
|
|
1643
2600
|
|
|
1644
2601
|
Parameters
|
|
1645
2602
|
----------
|
|
1646
|
-
thedata :
|
|
1647
|
-
Input data of length N to be filtered
|
|
1648
|
-
|
|
2603
|
+
thedata : NDArray
|
|
2604
|
+
Input data of length N to be filtered.
|
|
2605
|
+
theevs : NDArray
|
|
2606
|
+
NxP array of explanatory variables to be fit. If 1D, it is treated as a single
|
|
2607
|
+
explanatory variable.
|
|
2608
|
+
nderivs : int, optional
|
|
2609
|
+
Number of derivative components to compute for each explanatory variable.
|
|
2610
|
+
Default is 1. For each input variable, this creates a sequence of
|
|
2611
|
+
derivatives: original, first derivative, second derivative, etc.
|
|
2612
|
+
debug : bool, optional
|
|
2613
|
+
Flag to toggle debugging output. Default is False.
|
|
1649
2614
|
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
2615
|
+
Returns
|
|
2616
|
+
-------
|
|
2617
|
+
tuple
|
|
2618
|
+
A tuple containing:
|
|
2619
|
+
- filtered : ndarray
|
|
2620
|
+
The filtered version of `thedata` after fitting.
|
|
2621
|
+
- thenewevs : ndarray
|
|
2622
|
+
The expanded set of explanatory variables (original + derivatives).
|
|
2623
|
+
- datatoremove : ndarray
|
|
2624
|
+
The part of the data that was removed during fitting.
|
|
2625
|
+
- R : float
|
|
2626
|
+
The coefficient of determination (R²) of the fit.
|
|
2627
|
+
- coffs : ndarray
|
|
2628
|
+
The coefficients of the linear fit.
|
|
1653
2629
|
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
2630
|
+
Notes
|
|
2631
|
+
-----
|
|
2632
|
+
This function is useful for filtering data when the underlying signal is expected
|
|
2633
|
+
to have smooth variations, and derivative information can improve the fit.
|
|
2634
|
+
The expansion of each variable into its derivatives allows for better modeling
|
|
2635
|
+
of local trends in the data.
|
|
1658
2636
|
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
2637
|
+
Examples
|
|
2638
|
+
--------
|
|
2639
|
+
>>> import numpy as np
|
|
2640
|
+
>>> from typing import Tuple
|
|
2641
|
+
>>> thedata = np.array([1, 2, 3, 4, 5])
|
|
2642
|
+
>>> theevs = np.array([[1, 2], [2, 3], [3, 4], [4, 5], [5, 6]])
|
|
2643
|
+
>>> filtered, expanded_ev, removed, R, coeffs = derivativelinfitfilt(thedata, theevs, nderivs=2)
|
|
1662
2644
|
"""
|
|
1663
2645
|
if debug:
|
|
1664
2646
|
print(f"{thedata.shape=}")
|
|
@@ -1682,36 +2664,65 @@ def derivativelinfitfilt(thedata, theevs, nderivs=1, debug=False):
|
|
|
1682
2664
|
if debug:
|
|
1683
2665
|
print(f"{nderivs=}")
|
|
1684
2666
|
print(f"{thenewevs.shape=}")
|
|
1685
|
-
filtered, datatoremove, R, coffs = linfitfilt(thedata, thenewevs, debug=debug)
|
|
2667
|
+
filtered, datatoremove, R, coffs, dummy = linfitfilt(thedata, thenewevs, debug=debug)
|
|
1686
2668
|
if debug:
|
|
1687
2669
|
print(f"{R=}")
|
|
1688
2670
|
|
|
1689
2671
|
return filtered, thenewevs, datatoremove, R, coffs
|
|
1690
2672
|
|
|
2673
|
+
@conditionaljit()
|
|
2674
|
+
def expandedlinfitfilt(
|
|
2675
|
+
thedata: NDArray, theevs: NDArray, ncomps: int = 1, debug: bool = False
|
|
2676
|
+
) -> Tuple[NDArray, NDArray, NDArray, float, NDArray]:
|
|
2677
|
+
"""
|
|
2678
|
+
Perform multicomponent expansion on explanatory variables and fit a linear model.
|
|
1691
2679
|
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
2680
|
+
First, perform multicomponent expansion on the explanatory variables (`theevs`),
|
|
2681
|
+
where each variable is replaced by itself, its square, its cube, etc., up to `ncomps`
|
|
2682
|
+
components. Then, perform a multiple regression fit of `thedata` using the expanded
|
|
2683
|
+
explanatory variables and return the filtered data, the fitted model components,
|
|
2684
|
+
the residual sum of squares, and the coefficients.
|
|
1696
2685
|
|
|
1697
2686
|
Parameters
|
|
1698
2687
|
----------
|
|
1699
|
-
thedata :
|
|
1700
|
-
Input data of length N to be filtered
|
|
1701
|
-
|
|
2688
|
+
thedata : NDArray
|
|
2689
|
+
Input data of length N to be filtered.
|
|
2690
|
+
theevs : array_like
|
|
2691
|
+
NxP array of explanatory variables to be fit.
|
|
2692
|
+
ncomps : int, optional
|
|
2693
|
+
Number of components to use for each ev. Each successive component is a
|
|
2694
|
+
higher power of the initial ev (initial, square, cube, etc.). Default is 1.
|
|
2695
|
+
debug : bool, optional
|
|
2696
|
+
Flag to toggle debugging output. Default is False.
|
|
1702
2697
|
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
2698
|
+
Returns
|
|
2699
|
+
-------
|
|
2700
|
+
filtered : ndarray
|
|
2701
|
+
The filtered version of `thedata` after fitting and removing the linear model.
|
|
2702
|
+
thenewevs : ndarray
|
|
2703
|
+
The expanded explanatory variables used in the fit.
|
|
2704
|
+
datatoremove : ndarray
|
|
2705
|
+
The portion of `thedata` that was removed during the fitting process.
|
|
2706
|
+
R : float
|
|
2707
|
+
Residual sum of squares from the linear fit.
|
|
2708
|
+
coffs : ndarray
|
|
2709
|
+
The coefficients of the linear fit.
|
|
1706
2710
|
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
2711
|
+
Notes
|
|
2712
|
+
-----
|
|
2713
|
+
If `ncomps` is 1, no expansion is performed and `theevs` is used directly.
|
|
2714
|
+
For each column in `theevs`, the expanded columns are created by taking powers
|
|
2715
|
+
of the original column (1st, 2nd, ..., ncomps-th power).
|
|
1711
2716
|
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
2717
|
+
Examples
|
|
2718
|
+
--------
|
|
2719
|
+
>>> import numpy as np
|
|
2720
|
+
>>> from typing import Tuple
|
|
2721
|
+
>>> thedata = np.array([1, 2, 3, 4, 5])
|
|
2722
|
+
>>> theevs = np.array([[1, 2], [2, 3], [3, 4], [4, 5], [5, 6]])
|
|
2723
|
+
>>> filtered, expanded_ev, removed, R, coeffs = expandedlinfitfilt(thedata, theevs, ncomps=2)
|
|
2724
|
+
>>> print(filtered)
|
|
2725
|
+
[0. 0. 0. 0. 0.]
|
|
1715
2726
|
"""
|
|
1716
2727
|
if debug:
|
|
1717
2728
|
print(f"{thedata.shape=}")
|
|
@@ -1735,30 +2746,63 @@ def expandedlinfitfilt(thedata, theevs, ncomps=1, debug=False):
|
|
|
1735
2746
|
if debug:
|
|
1736
2747
|
print(f"{ncomps=}")
|
|
1737
2748
|
print(f"{thenewevs.shape=}")
|
|
1738
|
-
filtered, datatoremove, R, coffs = linfitfilt(thedata, thenewevs, debug=debug)
|
|
2749
|
+
filtered, datatoremove, R, coffs, dummy = linfitfilt(thedata, thenewevs, debug=debug)
|
|
1739
2750
|
if debug:
|
|
1740
2751
|
print(f"{R=}")
|
|
1741
2752
|
|
|
1742
2753
|
return filtered, thenewevs, datatoremove, R, coffs
|
|
1743
2754
|
|
|
1744
|
-
|
|
1745
|
-
def linfitfilt(
|
|
1746
|
-
|
|
2755
|
+
@conditionaljit()
|
|
2756
|
+
def linfitfilt(
|
|
2757
|
+
thedata: NDArray, theevs: NDArray, debug: bool = False
|
|
2758
|
+
) -> Tuple[NDArray, NDArray, float, NDArray, float]:
|
|
2759
|
+
"""
|
|
2760
|
+
Performs a multiple regression fit of thedata using the vectors in theevs
|
|
1747
2761
|
and returns the result.
|
|
1748
2762
|
|
|
2763
|
+
This function fits a linear model to the input data using the explanatory
|
|
2764
|
+
variables provided in `theevs`, then removes the fitted component from the
|
|
2765
|
+
original data to produce a filtered version.
|
|
2766
|
+
|
|
1749
2767
|
Parameters
|
|
1750
2768
|
----------
|
|
1751
|
-
thedata :
|
|
1752
|
-
Input data of length N to be filtered
|
|
1753
|
-
|
|
2769
|
+
thedata : NDArray
|
|
2770
|
+
Input data of length N to be filtered.
|
|
2771
|
+
theevs : NDArray
|
|
2772
|
+
NxP array of explanatory variables to be fit. If 1D, treated as a single
|
|
2773
|
+
explanatory variable.
|
|
2774
|
+
returnintercept : bool, optional
|
|
2775
|
+
If True, also return the intercept term from the regression. Default is False.
|
|
2776
|
+
debug : bool, optional
|
|
2777
|
+
If True, print debugging information during execution. Default is False.
|
|
2778
|
+
|
|
2779
|
+
Returns
|
|
2780
|
+
-------
|
|
2781
|
+
filtered : ndarray
|
|
2782
|
+
The filtered data, i.e., the original data with the fitted component removed.
|
|
2783
|
+
datatoremove : ndarray
|
|
2784
|
+
The component of thedata that was removed during filtering.
|
|
2785
|
+
R2 : float
|
|
2786
|
+
The coefficient of determination (R-squared) of the regression.
|
|
2787
|
+
retcoffs : ndarray
|
|
2788
|
+
The regression coefficients (excluding intercept) for each explanatory variable.
|
|
2789
|
+
theintercept : float, optional
|
|
2790
|
+
The intercept term from the regression. Only returned if `returnintercept=True`.
|
|
1754
2791
|
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
2792
|
+
Notes
|
|
2793
|
+
-----
|
|
2794
|
+
This function uses `mlregress` internally to perform the linear regression.
|
|
2795
|
+
The intercept is always included in the model, but only returned if explicitly
|
|
2796
|
+
requested via `returnintercept`.
|
|
1758
2797
|
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
2798
|
+
Examples
|
|
2799
|
+
--------
|
|
2800
|
+
>>> import numpy as np
|
|
2801
|
+
>>> thedata = np.array([1, 2, 3, 4, 5])
|
|
2802
|
+
>>> theevs = np.array([[1, 1], [1, 2], [1, 3], [1, 4], [1, 5]])
|
|
2803
|
+
>>> filtered, datatoremove, R2, retcoffs, dummy = linfitfilt(thedata, theevs)
|
|
2804
|
+
>>> print(filtered)
|
|
2805
|
+
[0. 0. 0. 0. 0.]
|
|
1762
2806
|
"""
|
|
1763
2807
|
|
|
1764
2808
|
if debug:
|
|
@@ -1790,35 +2834,59 @@ def linfitfilt(thedata, theevs, returnintercept=False, debug=False):
|
|
|
1790
2834
|
filtered = thedata - datatoremove
|
|
1791
2835
|
if debug:
|
|
1792
2836
|
print(f"{retcoffs=}")
|
|
1793
|
-
|
|
1794
|
-
return filtered, datatoremove, R2, retcoffs, theintercept
|
|
1795
|
-
else:
|
|
1796
|
-
return filtered, datatoremove, R2, retcoffs
|
|
2837
|
+
return filtered, datatoremove, R2, retcoffs, theintercept
|
|
1797
2838
|
|
|
1798
2839
|
|
|
2840
|
+
@conditionaljit()
|
|
1799
2841
|
def confoundregress(
|
|
1800
|
-
data,
|
|
1801
|
-
regressors,
|
|
1802
|
-
debug=False,
|
|
1803
|
-
showprogressbar=True,
|
|
1804
|
-
rt_floatset=np.float64,
|
|
1805
|
-
rt_floattype="float64",
|
|
1806
|
-
):
|
|
1807
|
-
|
|
2842
|
+
data: NDArray,
|
|
2843
|
+
regressors: NDArray,
|
|
2844
|
+
debug: bool = False,
|
|
2845
|
+
showprogressbar: bool = True,
|
|
2846
|
+
rt_floatset: type = np.float64,
|
|
2847
|
+
rt_floattype: str = "float64",
|
|
2848
|
+
) -> Tuple[NDArray, NDArray]:
|
|
2849
|
+
"""
|
|
2850
|
+
Filters multiple regressors out of an array of data using linear regression.
|
|
2851
|
+
|
|
2852
|
+
This function removes the effect of nuisance regressors from each voxel's timecourse
|
|
2853
|
+
by fitting a linear model and subtracting the predicted signal.
|
|
1808
2854
|
|
|
1809
2855
|
Parameters
|
|
1810
2856
|
----------
|
|
1811
2857
|
data : 2d numpy array
|
|
1812
|
-
A data array
|
|
1813
|
-
|
|
1814
|
-
regressors: 2d numpy array
|
|
1815
|
-
The set of regressors to filter out of each timecourse.
|
|
1816
|
-
|
|
1817
|
-
debug :
|
|
1818
|
-
Print additional diagnostic information if True
|
|
2858
|
+
A data array where the first index is the spatial dimension (e.g., voxels),
|
|
2859
|
+
and the second index is the time (filtering) dimension.
|
|
2860
|
+
regressors : 2d numpy array
|
|
2861
|
+
The set of regressors to filter out of each timecourse. The first dimension
|
|
2862
|
+
is the regressor number, and the second is the time (filtering) dimension.
|
|
2863
|
+
debug : bool, optional
|
|
2864
|
+
Print additional diagnostic information if True. Default is False.
|
|
2865
|
+
showprogressbar : bool, optional
|
|
2866
|
+
Show progress bar during processing. Default is True.
|
|
2867
|
+
rt_floatset : type, optional
|
|
2868
|
+
The data type used for floating-point calculations. Default is np.float64.
|
|
2869
|
+
rt_floattype : str, optional
|
|
2870
|
+
The string representation of the floating-point data type. Default is "float64".
|
|
1819
2871
|
|
|
1820
2872
|
Returns
|
|
1821
2873
|
-------
|
|
2874
|
+
filtereddata : 2d numpy array
|
|
2875
|
+
The data with regressors removed, same shape as input `data`.
|
|
2876
|
+
r2value : 1d numpy array
|
|
2877
|
+
The R-squared value for each voxel's regression fit, shape (data.shape[0],).
|
|
2878
|
+
|
|
2879
|
+
Notes
|
|
2880
|
+
-----
|
|
2881
|
+
This function uses `mlregress` internally to perform the linear regression for each voxel.
|
|
2882
|
+
The regressors are applied in the order they appear in the input array.
|
|
2883
|
+
|
|
2884
|
+
Examples
|
|
2885
|
+
--------
|
|
2886
|
+
>>> import numpy as np
|
|
2887
|
+
>>> data = np.random.rand(100, 1000)
|
|
2888
|
+
>>> regressors = np.random.rand(3, 1000)
|
|
2889
|
+
>>> filtered_data, r2_values = confoundregress(data, regressors, debug=True)
|
|
1822
2890
|
"""
|
|
1823
2891
|
if debug:
|
|
1824
2892
|
print("data shape:", data.shape)
|
|
@@ -1850,7 +2918,56 @@ def confoundregress(
|
|
|
1850
2918
|
# You can redistribute it and/or modify it under the terms of the Do What The
|
|
1851
2919
|
# Fuck You Want To Public License, Version 2, as published by Sam Hocevar. See
|
|
1852
2920
|
# http://www.wtfpl.net/ for more details.
|
|
1853
|
-
def getpeaks(
|
|
2921
|
+
def getpeaks(
|
|
2922
|
+
xvals: NDArray,
|
|
2923
|
+
yvals: NDArray,
|
|
2924
|
+
xrange: Optional[Tuple[float, float]] = None,
|
|
2925
|
+
bipolar: bool = False,
|
|
2926
|
+
displayplots: bool = False,
|
|
2927
|
+
) -> list:
|
|
2928
|
+
"""
|
|
2929
|
+
Find peaks in y-values within a specified range and optionally display results.
|
|
2930
|
+
|
|
2931
|
+
This function identifies local maxima (and optionally minima) in the input
|
|
2932
|
+
y-values and returns their coordinates along with an offset from the origin.
|
|
2933
|
+
It supports filtering by a range of x-values and can handle both unipolar and
|
|
2934
|
+
bipolar peak detection.
|
|
2935
|
+
|
|
2936
|
+
Parameters
|
|
2937
|
+
----------
|
|
2938
|
+
xvals : NDArray
|
|
2939
|
+
X-axis values corresponding to the y-values.
|
|
2940
|
+
yvals : NDArray
|
|
2941
|
+
Y-axis values where peaks are to be detected.
|
|
2942
|
+
xrange : tuple of float, optional
|
|
2943
|
+
A tuple (min, max) specifying the range of x-values to consider.
|
|
2944
|
+
If None, the full range is used.
|
|
2945
|
+
bipolar : bool, optional
|
|
2946
|
+
If True, detect both positive and negative peaks (minima and maxima).
|
|
2947
|
+
If False, only detect positive peaks.
|
|
2948
|
+
displayplots : bool, optional
|
|
2949
|
+
If True, display a plot showing the data and detected peaks.
|
|
2950
|
+
|
|
2951
|
+
Returns
|
|
2952
|
+
-------
|
|
2953
|
+
list of lists
|
|
2954
|
+
A list of peaks, each represented as [x_value, y_value, offset_from_origin].
|
|
2955
|
+
The offset is calculated using `tide_util.valtoindex` relative to x=0.
|
|
2956
|
+
|
|
2957
|
+
Notes
|
|
2958
|
+
-----
|
|
2959
|
+
- The function uses `scipy.signal.find_peaks` to detect peaks.
|
|
2960
|
+
- If `bipolar` is True, both positive and negative peaks are included.
|
|
2961
|
+
- The `displayplots` option requires `matplotlib.pyplot` to be imported as `plt`.
|
|
2962
|
+
|
|
2963
|
+
Examples
|
|
2964
|
+
--------
|
|
2965
|
+
>>> x = np.linspace(-10, 10, 100)
|
|
2966
|
+
>>> y = np.sin(x)
|
|
2967
|
+
>>> peaks = getpeaks(x, y, xrange=(-5, 5), bipolar=True)
|
|
2968
|
+
>>> print(peaks)
|
|
2969
|
+
[[-1.5707963267948966, 1.0, -25], [1.5707963267948966, 1.0, 25]]
|
|
2970
|
+
"""
|
|
1854
2971
|
peaks, dummy = find_peaks(yvals, height=0)
|
|
1855
2972
|
if bipolar:
|
|
1856
2973
|
negpeaks, dummy = find_peaks(-yvals, height=0)
|
|
@@ -1898,19 +3015,44 @@ def getpeaks(xvals, yvals, xrange=None, bipolar=False, displayplots=False):
|
|
|
1898
3015
|
return procpeaks
|
|
1899
3016
|
|
|
1900
3017
|
|
|
1901
|
-
def parabfit(x_axis, y_axis, peakloc, points):
|
|
3018
|
+
def parabfit(x_axis: NDArray, y_axis: NDArray, peakloc: int, points: int) -> Tuple[float, float]:
|
|
1902
3019
|
"""
|
|
3020
|
+
Fit a parabola to a localized region around a peak and return the peak coordinates.
|
|
3021
|
+
|
|
3022
|
+
This function performs a quadratic curve fitting on a subset of data surrounding
|
|
3023
|
+
a specified peak location. It uses a parabolic model of the form a*(x-tau)^2 + c
|
|
3024
|
+
to estimate the precise peak position and amplitude.
|
|
1903
3025
|
|
|
1904
3026
|
Parameters
|
|
1905
3027
|
----------
|
|
1906
|
-
x_axis
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
3028
|
+
x_axis : NDArray
|
|
3029
|
+
Array of x-axis values (typically time or frequency).
|
|
3030
|
+
y_axis : NDArray
|
|
3031
|
+
Array of y-axis values (typically signal amplitude).
|
|
3032
|
+
peakloc : int
|
|
3033
|
+
Index location of the peak in the data arrays.
|
|
3034
|
+
points : int
|
|
3035
|
+
Number of points to include in the local fit around the peak.
|
|
1910
3036
|
|
|
1911
3037
|
Returns
|
|
1912
3038
|
-------
|
|
3039
|
+
Tuple[float, float]
|
|
3040
|
+
A tuple containing (x_peak, y_peak) - the fitted peak coordinates.
|
|
3041
|
+
|
|
3042
|
+
Notes
|
|
3043
|
+
-----
|
|
3044
|
+
The function uses a least-squares fitting approach with scipy.optimize.curve_fit.
|
|
3045
|
+
Initial parameter estimates are derived analytically based on the peak location
|
|
3046
|
+
and a distance calculation. The parabolic model assumes the peak has a symmetric
|
|
3047
|
+
quadratic shape.
|
|
1913
3048
|
|
|
3049
|
+
Examples
|
|
3050
|
+
--------
|
|
3051
|
+
>>> import numpy as np
|
|
3052
|
+
>>> x = np.linspace(0, 10, 100)
|
|
3053
|
+
>>> y = 2 * (x - 5)**2 + 1
|
|
3054
|
+
>>> peak_x, peak_y = parabfit(x, y, 50, 10)
|
|
3055
|
+
>>> print(f"Peak at x={peak_x:.2f}, y={peak_y:.2f}")
|
|
1914
3056
|
"""
|
|
1915
3057
|
func = lambda x, a, tau, c: a * ((x - tau) ** 2) + c
|
|
1916
3058
|
distance = abs(x_axis[peakloc[1][0]] - x_axis[peakloc[0][0]]) / 4
|
|
@@ -1935,20 +3077,48 @@ def parabfit(x_axis, y_axis, peakloc, points):
|
|
|
1935
3077
|
return x, y
|
|
1936
3078
|
|
|
1937
3079
|
|
|
1938
|
-
def _datacheck_peakdetect(x_axis, y_axis):
|
|
3080
|
+
def _datacheck_peakdetect(x_axis: Optional[NDArray], y_axis: NDArray) -> Tuple[NDArray, NDArray]:
|
|
1939
3081
|
"""
|
|
3082
|
+
Validate and convert input arrays for peak detection.
|
|
1940
3083
|
|
|
1941
3084
|
Parameters
|
|
1942
3085
|
----------
|
|
1943
|
-
x_axis
|
|
1944
|
-
|
|
3086
|
+
x_axis : NDArray, optional
|
|
3087
|
+
X-axis values. If None, range(len(y_axis)) is used.
|
|
3088
|
+
y_axis : NDArray
|
|
3089
|
+
Y-axis values to be processed.
|
|
1945
3090
|
|
|
1946
3091
|
Returns
|
|
1947
3092
|
-------
|
|
3093
|
+
tuple of ndarray
|
|
3094
|
+
Tuple containing (x_axis, y_axis) as numpy arrays.
|
|
3095
|
+
|
|
3096
|
+
Raises
|
|
3097
|
+
------
|
|
3098
|
+
ValueError
|
|
3099
|
+
If input vectors y_axis and x_axis have different lengths.
|
|
3100
|
+
|
|
3101
|
+
Notes
|
|
3102
|
+
-----
|
|
3103
|
+
This function ensures that both input arrays are converted to numpy arrays
|
|
3104
|
+
and have matching shapes. If x_axis is None, it defaults to a range
|
|
3105
|
+
corresponding to the length of y_axis.
|
|
1948
3106
|
|
|
3107
|
+
Examples
|
|
3108
|
+
--------
|
|
3109
|
+
>>> import numpy as np
|
|
3110
|
+
>>> x, y = _datacheck_peakdetect([1, 2, 3], [4, 5, 6])
|
|
3111
|
+
>>> print(x)
|
|
3112
|
+
[1 2 3]
|
|
3113
|
+
>>> print(y)
|
|
3114
|
+
[4 5 6]
|
|
3115
|
+
|
|
3116
|
+
>>> x, y = _datacheck_peakdetect(None, [4, 5, 6])
|
|
3117
|
+
>>> print(x)
|
|
3118
|
+
[0 1 2]
|
|
1949
3119
|
"""
|
|
1950
3120
|
if x_axis is None:
|
|
1951
|
-
x_axis =
|
|
3121
|
+
x_axis = np.arange(0, len(y_axis))
|
|
1952
3122
|
|
|
1953
3123
|
if np.shape(y_axis) != np.shape(x_axis):
|
|
1954
3124
|
raise ValueError("Input vectors y_axis and x_axis must have same length")
|
|
@@ -1959,43 +3129,58 @@ def _datacheck_peakdetect(x_axis, y_axis):
|
|
|
1959
3129
|
return x_axis, y_axis
|
|
1960
3130
|
|
|
1961
3131
|
|
|
1962
|
-
def peakdetect(
|
|
3132
|
+
def peakdetect(
|
|
3133
|
+
y_axis: NDArray[np.floating[Any]],
|
|
3134
|
+
x_axis: Optional[NDArray[np.floating[Any]]] = None,
|
|
3135
|
+
lookahead: int = 200,
|
|
3136
|
+
delta: float = 0.0,
|
|
3137
|
+
) -> list:
|
|
1963
3138
|
"""
|
|
1964
|
-
|
|
1965
|
-
http://billauer.co.il/peakdet.html
|
|
1966
|
-
|
|
1967
|
-
function for detecting local maxima and minima in a signal.
|
|
1968
|
-
Discovers peaks by searching for values which are surrounded by lower
|
|
1969
|
-
or larger values for maxima and minima respectively
|
|
3139
|
+
Detect local maxima and minima in a signal.
|
|
1970
3140
|
|
|
1971
|
-
|
|
1972
|
-
|
|
3141
|
+
This function is based on a MATLAB script by Billauer, and identifies peaks
|
|
3142
|
+
by searching for values that are surrounded by lower (for maxima) or larger
|
|
3143
|
+
(for minima) values. It uses a lookahead window to confirm that a candidate
|
|
3144
|
+
is indeed a peak and not noise or jitter.
|
|
1973
3145
|
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
3146
|
+
Parameters
|
|
3147
|
+
----------
|
|
3148
|
+
y_axis : NDArray[np.floating[Any]]
|
|
3149
|
+
A list or array containing the signal over which to find peaks.
|
|
3150
|
+
x_axis : NDArray[np.floating[Any]], optional
|
|
3151
|
+
An x-axis whose values correspond to the y_axis list. If omitted,
|
|
3152
|
+
an index of the y_axis is used. Default is None.
|
|
3153
|
+
lookahead : int, optional
|
|
3154
|
+
Distance to look ahead from a peak candidate to determine if it is
|
|
3155
|
+
the actual peak. Default is 200.
|
|
3156
|
+
delta : float, optional
|
|
3157
|
+
Minimum difference between a peak and the following points. If set,
|
|
3158
|
+
this helps avoid false peaks towards the end of the signal. Default is 0.0.
|
|
1983
3159
|
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
(
|
|
1989
|
-
|
|
1990
|
-
|
|
3160
|
+
Returns
|
|
3161
|
+
-------
|
|
3162
|
+
list of lists
|
|
3163
|
+
A list containing two sublists: ``[max_peaks, min_peaks]``.
|
|
3164
|
+
Each sublist contains tuples of the form ``(position, peak_value)``.
|
|
3165
|
+
For example, to unpack maxima into x and y coordinates:
|
|
3166
|
+
``x, y = zip(*max_peaks)``.
|
|
1991
3167
|
|
|
3168
|
+
Notes
|
|
3169
|
+
-----
|
|
3170
|
+
- The function assumes that the input signal is sampled at regular intervals.
|
|
3171
|
+
- If ``delta`` is not provided, the function runs slower but may detect more
|
|
3172
|
+
peaks.
|
|
3173
|
+
- When ``delta`` is correctly specified (e.g., as 5 * RMS noise), it can
|
|
3174
|
+
significantly improve performance.
|
|
1992
3175
|
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
3176
|
+
Examples
|
|
3177
|
+
--------
|
|
3178
|
+
>>> import numpy as np
|
|
3179
|
+
>>> x = np.linspace(0, 10, 100)
|
|
3180
|
+
>>> y = np.sin(x) + 0.5 * np.sin(3 * x)
|
|
3181
|
+
>>> max_peaks, min_peaks = peakdetect(y, x, lookahead=10, delta=0.1)
|
|
3182
|
+
>>> print("Max peaks:", max_peaks)
|
|
3183
|
+
>>> print("Min peaks:", min_peaks)
|
|
1999
3184
|
"""
|
|
2000
3185
|
max_peaks = []
|
|
2001
3186
|
min_peaks = []
|
|
@@ -2009,7 +3194,7 @@ def peakdetect(y_axis, x_axis=None, lookahead=200, delta=0.0):
|
|
|
2009
3194
|
# perform some checks
|
|
2010
3195
|
if lookahead < 1:
|
|
2011
3196
|
raise ValueError("Lookahead must be '1' or above in value")
|
|
2012
|
-
if not (np.isscalar(delta) and delta >= 0):
|
|
3197
|
+
if not (np.isscalar(delta) and (delta >= 0.0)):
|
|
2013
3198
|
raise ValueError("delta must be a positive number")
|
|
2014
3199
|
|
|
2015
3200
|
# maxima and minima candidates are temporarily stored in
|
|
@@ -2044,7 +3229,7 @@ def peakdetect(y_axis, x_axis=None, lookahead=200, delta=0.0):
|
|
|
2044
3229
|
# mxpos = x_axis[np.where(y_axis[index:index+lookahead]==mx)]
|
|
2045
3230
|
|
|
2046
3231
|
####look for min####
|
|
2047
|
-
if y > mn + delta and mn != -np.inf:
|
|
3232
|
+
if (y > mn + delta) and (mn != -np.inf):
|
|
2048
3233
|
# Minima peak candidate found
|
|
2049
3234
|
# look ahead in signal to ensure that this is a peak and not jitter
|
|
2050
3235
|
if y_axis[index : index + lookahead].min() > mn:
|
|
@@ -2074,7 +3259,46 @@ def peakdetect(y_axis, x_axis=None, lookahead=200, delta=0.0):
|
|
|
2074
3259
|
return [max_peaks, min_peaks]
|
|
2075
3260
|
|
|
2076
3261
|
|
|
2077
|
-
def ocscreetest(eigenvals, debug=False, displayplots=False):
|
|
3262
|
+
def ocscreetest(eigenvals: NDArray, debug: bool = False, displayplots: bool = False) -> int:
|
|
3263
|
+
"""
|
|
3264
|
+
Perform eigenvalue screening using the OCSCREE test to determine the number of retained components.
|
|
3265
|
+
|
|
3266
|
+
This function implements a variant of the scree test for determining the number of significant
|
|
3267
|
+
eigenvalues in a dataset. It uses a linear regression approach to model the eigenvalue decay
|
|
3268
|
+
and identifies the point where the observed eigenvalues fall below the predicted values.
|
|
3269
|
+
|
|
3270
|
+
Parameters
|
|
3271
|
+
----------
|
|
3272
|
+
eigenvals : NDArray
|
|
3273
|
+
Array of eigenvalues, typically sorted in descending order.
|
|
3274
|
+
debug : bool, optional
|
|
3275
|
+
If True, print intermediate calculations for debugging purposes. Default is False.
|
|
3276
|
+
displayplots : bool, optional
|
|
3277
|
+
If True, display plots of the original eigenvalues, regression coefficients (a and b),
|
|
3278
|
+
and the predicted eigenvalue curve. Default is False.
|
|
3279
|
+
|
|
3280
|
+
Returns
|
|
3281
|
+
-------
|
|
3282
|
+
int
|
|
3283
|
+
The index of the last retained component based on the OCSCREE criterion.
|
|
3284
|
+
|
|
3285
|
+
Notes
|
|
3286
|
+
-----
|
|
3287
|
+
The function performs the following steps:
|
|
3288
|
+
1. Initialize arrays for regression coefficients 'a' and 'b'.
|
|
3289
|
+
2. Compute regression coefficients from the eigenvalues.
|
|
3290
|
+
3. Predict eigenvalues using the regression model.
|
|
3291
|
+
4. Identify the point where the actual eigenvalues drop below the predicted values.
|
|
3292
|
+
5. Optionally display diagnostic plots.
|
|
3293
|
+
|
|
3294
|
+
Examples
|
|
3295
|
+
--------
|
|
3296
|
+
>>> import numpy as np
|
|
3297
|
+
>>> eigenvals = np.array([3.5, 2.1, 1.8, 1.2, 0.9, 0.5])
|
|
3298
|
+
>>> result = ocscreetest(eigenvals)
|
|
3299
|
+
>>> print(result)
|
|
3300
|
+
3
|
|
3301
|
+
"""
|
|
2078
3302
|
num = len(eigenvals)
|
|
2079
3303
|
a = eigenvals * 0.0
|
|
2080
3304
|
b = eigenvals * 0.0
|
|
@@ -2111,7 +3335,45 @@ def ocscreetest(eigenvals, debug=False, displayplots=False):
|
|
|
2111
3335
|
return i
|
|
2112
3336
|
|
|
2113
3337
|
|
|
2114
|
-
def afscreetest(eigenvals, displayplots=False):
|
|
3338
|
+
def afscreetest(eigenvals: NDArray, displayplots: bool = False) -> int:
|
|
3339
|
+
"""
|
|
3340
|
+
Detect the optimal number of components using the second derivative of eigenvalues.
|
|
3341
|
+
|
|
3342
|
+
This function applies a second derivative analysis to the eigenvalues to identify
|
|
3343
|
+
the point where the rate of change of eigenvalues begins to decrease significantly,
|
|
3344
|
+
which typically indicates the optimal number of components to retain.
|
|
3345
|
+
|
|
3346
|
+
Parameters
|
|
3347
|
+
----------
|
|
3348
|
+
eigenvals : NDArray
|
|
3349
|
+
Array of eigenvalues, typically from a PCA or similar decomposition.
|
|
3350
|
+
Should be sorted in descending order.
|
|
3351
|
+
displayplots : bool, optional
|
|
3352
|
+
If True, display plots showing the original eigenvalues, first derivative,
|
|
3353
|
+
and second derivative (default is False).
|
|
3354
|
+
|
|
3355
|
+
Returns
|
|
3356
|
+
-------
|
|
3357
|
+
int
|
|
3358
|
+
The index of the optimal number of components, adjusted by subtracting 1
|
|
3359
|
+
from the location of maximum second derivative.
|
|
3360
|
+
|
|
3361
|
+
Notes
|
|
3362
|
+
-----
|
|
3363
|
+
The method works by:
|
|
3364
|
+
1. Computing the first derivative of eigenvalues
|
|
3365
|
+
2. Computing the second derivative of the first derivative
|
|
3366
|
+
3. Finding the maximum of the second derivative
|
|
3367
|
+
4. Returning the index of this maximum minus 1
|
|
3368
|
+
|
|
3369
|
+
Examples
|
|
3370
|
+
--------
|
|
3371
|
+
>>> import numpy as np
|
|
3372
|
+
>>> eigenvals = np.array([5.0, 3.0, 1.5, 0.8, 0.2])
|
|
3373
|
+
>>> optimal_components = afscreetest(eigenvals)
|
|
3374
|
+
>>> print(optimal_components)
|
|
3375
|
+
1
|
|
3376
|
+
"""
|
|
2115
3377
|
num = len(eigenvals)
|
|
2116
3378
|
firstderiv = np.gradient(eigenvals, edge_order=2)
|
|
2117
3379
|
secondderiv = np.gradient(firstderiv, edge_order=2)
|
|
@@ -2129,10 +3391,56 @@ def afscreetest(eigenvals, displayplots=False):
|
|
|
2129
3391
|
ax3.set_title("Second derivative")
|
|
2130
3392
|
plt.plot(secondderiv, color="g")
|
|
2131
3393
|
plt.show()
|
|
2132
|
-
return maxaccloc - 1
|
|
3394
|
+
return int(maxaccloc - 1)
|
|
3395
|
+
|
|
3396
|
+
|
|
3397
|
+
def phaseanalysis(
|
|
3398
|
+
firstharmonic: NDArray, displayplots: bool = False
|
|
3399
|
+
) -> Tuple[NDArray, NDArray, NDArray]:
|
|
3400
|
+
"""
|
|
3401
|
+
Perform phase analysis on a signal using analytic signal representation.
|
|
3402
|
+
|
|
3403
|
+
This function computes the analytic signal of the input signal using the Hilbert transform,
|
|
3404
|
+
and extracts the instantaneous phase and amplitude envelope. Optionally displays plots
|
|
3405
|
+
of the analytic signal, phase, and amplitude.
|
|
3406
|
+
|
|
3407
|
+
Parameters
|
|
3408
|
+
----------
|
|
3409
|
+
firstharmonic : NDArray
|
|
3410
|
+
Input signal to analyze. Should be a 1D NDArray object.
|
|
3411
|
+
displayplots : bool, optional
|
|
3412
|
+
If True, displays plots of the analytic signal, phase, and amplitude.
|
|
3413
|
+
Default is False.
|
|
3414
|
+
|
|
3415
|
+
Returns
|
|
3416
|
+
-------
|
|
3417
|
+
tuple of ndarray
|
|
3418
|
+
A tuple containing:
|
|
3419
|
+
- instantaneous_phase : ndarray
|
|
3420
|
+
The unwrapped instantaneous phase of the signal
|
|
3421
|
+
- amplitude_envelope : ndarray
|
|
3422
|
+
The amplitude envelope of the signal
|
|
3423
|
+
- analytic_signal : ndarray
|
|
3424
|
+
The analytic signal (complex-valued)
|
|
3425
|
+
|
|
3426
|
+
Notes
|
|
3427
|
+
-----
|
|
3428
|
+
The function uses `scipy.signal.hilbert` to compute the analytic signal,
|
|
3429
|
+
which is defined as: :math:`x_a(t) = x(t) + j\\hat{x}(t)` where :math:`\\hat{x}(t)`
|
|
3430
|
+
is the Hilbert transform of :math:`x(t)`.
|
|
2133
3431
|
|
|
3432
|
+
The instantaneous phase is computed as the angle of the analytic signal and is
|
|
3433
|
+
unwrapped to remove discontinuities.
|
|
2134
3434
|
|
|
2135
|
-
|
|
3435
|
+
Examples
|
|
3436
|
+
--------
|
|
3437
|
+
>>> import numpy as np
|
|
3438
|
+
>>> from scipy.signal import hilbert
|
|
3439
|
+
>>> signal = np.sin(2 * np.pi * 5 * np.linspace(0, 1, 100))
|
|
3440
|
+
>>> phase, amp, analytic = phaseanalysis(signal)
|
|
3441
|
+
>>> print(f"Phase shape: {phase.shape}")
|
|
3442
|
+
Phase shape: (100,)
|
|
3443
|
+
"""
|
|
2136
3444
|
print("entering phaseanalysis")
|
|
2137
3445
|
analytic_signal = hilbert(firstharmonic)
|
|
2138
3446
|
amplitude_envelope = np.abs(analytic_signal)
|
|
@@ -2190,28 +3498,119 @@ FML_FITFAIL = (
|
|
|
2190
3498
|
|
|
2191
3499
|
|
|
2192
3500
|
def simfuncpeakfit(
|
|
2193
|
-
incorrfunc,
|
|
2194
|
-
corrtimeaxis,
|
|
2195
|
-
useguess=False,
|
|
2196
|
-
maxguess=0.0,
|
|
2197
|
-
displayplots=False,
|
|
2198
|
-
functype="correlation",
|
|
2199
|
-
peakfittype="gauss",
|
|
2200
|
-
searchfrac=0.5,
|
|
2201
|
-
lagmod=1000.0,
|
|
2202
|
-
enforcethresh=True,
|
|
2203
|
-
allowhighfitamps=False,
|
|
2204
|
-
lagmin
|
|
2205
|
-
lagmax=30.0,
|
|
2206
|
-
absmaxsigma=1000.0,
|
|
2207
|
-
absminsigma=0.25,
|
|
2208
|
-
hardlimit=True,
|
|
2209
|
-
bipolar=False,
|
|
2210
|
-
lthreshval=0.0,
|
|
2211
|
-
uthreshval=1.0,
|
|
2212
|
-
zerooutbadfit=True,
|
|
2213
|
-
debug=False,
|
|
2214
|
-
):
|
|
3501
|
+
incorrfunc: NDArray,
|
|
3502
|
+
corrtimeaxis: NDArray,
|
|
3503
|
+
useguess: bool = False,
|
|
3504
|
+
maxguess: float = 0.0,
|
|
3505
|
+
displayplots: bool = False,
|
|
3506
|
+
functype: str = "correlation",
|
|
3507
|
+
peakfittype: str = "gauss",
|
|
3508
|
+
searchfrac: float = 0.5,
|
|
3509
|
+
lagmod: float = 1000.0,
|
|
3510
|
+
enforcethresh: bool = True,
|
|
3511
|
+
allowhighfitamps: bool = False,
|
|
3512
|
+
lagmin: float = -30.0,
|
|
3513
|
+
lagmax: float = 30.0,
|
|
3514
|
+
absmaxsigma: float = 1000.0,
|
|
3515
|
+
absminsigma: float = 0.25,
|
|
3516
|
+
hardlimit: bool = True,
|
|
3517
|
+
bipolar: bool = False,
|
|
3518
|
+
lthreshval: float = 0.0,
|
|
3519
|
+
uthreshval: float = 1.0,
|
|
3520
|
+
zerooutbadfit: bool = True,
|
|
3521
|
+
debug: bool = False,
|
|
3522
|
+
) -> Tuple[int, np.float64, np.float64, np.float64, np.uint16, np.uint32, int, int]:
|
|
3523
|
+
"""
|
|
3524
|
+
Fit a peak in a correlation or mutual information function.
|
|
3525
|
+
|
|
3526
|
+
This function performs peak fitting on a correlation or mutual information
|
|
3527
|
+
function to extract peak parameters such as location, amplitude, and width.
|
|
3528
|
+
It supports various fitting methods and includes error handling and
|
|
3529
|
+
validation for fit parameters.
|
|
3530
|
+
|
|
3531
|
+
Parameters
|
|
3532
|
+
----------
|
|
3533
|
+
incorrfunc : NDArray
|
|
3534
|
+
Input correlation or mutual information function values.
|
|
3535
|
+
corrtimeaxis : NDArray
|
|
3536
|
+
Time axis corresponding to the correlation function.
|
|
3537
|
+
useguess : bool, optional
|
|
3538
|
+
If True, use `maxguess` as an initial guess for the peak location.
|
|
3539
|
+
Default is False.
|
|
3540
|
+
maxguess : float, optional
|
|
3541
|
+
Initial guess for the peak location in seconds. Used only if `useguess` is True.
|
|
3542
|
+
Default is 0.0.
|
|
3543
|
+
displayplots : bool, optional
|
|
3544
|
+
If True, display plots of the peak and fit. Default is False.
|
|
3545
|
+
functype : str, optional
|
|
3546
|
+
Type of function to fit. Options are 'correlation', 'mutualinfo', or 'hybrid'.
|
|
3547
|
+
Default is 'correlation'.
|
|
3548
|
+
peakfittype : str, optional
|
|
3549
|
+
Type of peak fitting to perform. Options are 'gauss', 'fastgauss', 'quad',
|
|
3550
|
+
'fastquad', 'COM', or 'None'. Default is 'gauss'.
|
|
3551
|
+
searchfrac : float, optional
|
|
3552
|
+
Fraction of the peak maximum to define the search range for peak width.
|
|
3553
|
+
Default is 0.5.
|
|
3554
|
+
lagmod : float, optional
|
|
3555
|
+
Modulus for lag values, used to wrap around the lag values.
|
|
3556
|
+
Default is 1000.0.
|
|
3557
|
+
enforcethresh : bool, optional
|
|
3558
|
+
If True, enforce amplitude thresholds. Default is True.
|
|
3559
|
+
allowhighfitamps : bool, optional
|
|
3560
|
+
If True, allow fit amplitudes to exceed 1.0. Default is False.
|
|
3561
|
+
lagmin : float, optional
|
|
3562
|
+
Minimum allowed lag value in seconds. Default is -30.0.
|
|
3563
|
+
lagmax : float, optional
|
|
3564
|
+
Maximum allowed lag value in seconds. Default is 30.0.
|
|
3565
|
+
absmaxsigma : float, optional
|
|
3566
|
+
Maximum allowed sigma value in seconds. Default is 1000.0.
|
|
3567
|
+
absminsigma : float, optional
|
|
3568
|
+
Minimum allowed sigma value in seconds. Default is 0.25.
|
|
3569
|
+
hardlimit : bool, optional
|
|
3570
|
+
If True, enforce hard limits on lag values. Default is True.
|
|
3571
|
+
bipolar : bool, optional
|
|
3572
|
+
If True, allow negative correlation values. Default is False.
|
|
3573
|
+
lthreshval : float, optional
|
|
3574
|
+
Lower threshold for amplitude validation. Default is 0.0.
|
|
3575
|
+
uthreshval : float, optional
|
|
3576
|
+
Upper threshold for amplitude validation. Default is 1.0.
|
|
3577
|
+
zerooutbadfit : bool, optional
|
|
3578
|
+
If True, set fit results to zero if fit fails. Default is True.
|
|
3579
|
+
debug : bool, optional
|
|
3580
|
+
If True, print debug information. Default is False.
|
|
3581
|
+
|
|
3582
|
+
Returns
|
|
3583
|
+
-------
|
|
3584
|
+
tuple of int, float, float, float, int, int, int, int
|
|
3585
|
+
A tuple containing:
|
|
3586
|
+
- maxindex: Index of the peak maximum.
|
|
3587
|
+
- maxlag: Fitted peak lag in seconds.
|
|
3588
|
+
- maxval: Fitted peak amplitude.
|
|
3589
|
+
- maxsigma: Fitted peak width (sigma) in seconds.
|
|
3590
|
+
- maskval: Mask indicating fit success (1 for success, 0 for failure).
|
|
3591
|
+
- failreason: Reason for fit failure (bitmask).
|
|
3592
|
+
- peakstart: Start index of the peak region used for fitting.
|
|
3593
|
+
- peakend: End index of the peak region used for fitting.
|
|
3594
|
+
|
|
3595
|
+
Notes
|
|
3596
|
+
-----
|
|
3597
|
+
- The function automatically handles different types of correlation functions
|
|
3598
|
+
and mutual information functions with appropriate baseline corrections.
|
|
3599
|
+
- Various fitting methods are supported, each with its own strengths and
|
|
3600
|
+
trade-offs in terms of speed and accuracy.
|
|
3601
|
+
- Fit results are validated against physical constraints and thresholds.
|
|
3602
|
+
|
|
3603
|
+
Examples
|
|
3604
|
+
--------
|
|
3605
|
+
>>> import numpy as np
|
|
3606
|
+
>>> from scipy import signal
|
|
3607
|
+
>>> # Create sample data
|
|
3608
|
+
>>> t = np.linspace(-50, 50, 1000)
|
|
3609
|
+
>>> corr = np.exp(-0.5 * (t / 2)**2) + 0.1 * np.random.randn(1000)
|
|
3610
|
+
>>> maxindex, maxlag, maxval, maxsigma, maskval, failreason, peakstart, peakend = \
|
|
3611
|
+
... simfuncpeakfit(corr, t, peakfittype='gauss')
|
|
3612
|
+
>>> print(f"Peak lag: {maxlag:.2f} s, Amplitude: {maxval:.2f}, Width: {maxsigma:.2f} s")
|
|
3613
|
+
"""
|
|
2215
3614
|
# check to make sure xcorr_x and xcorr_y match
|
|
2216
3615
|
if corrtimeaxis is None:
|
|
2217
3616
|
print("Correlation time axis is not defined - exiting")
|
|
@@ -2276,7 +3675,7 @@ def simfuncpeakfit(
|
|
|
2276
3675
|
baselinedev = 0.0
|
|
2277
3676
|
else:
|
|
2278
3677
|
# for mutual information, there is a nonzero baseline, so we want the difference from that.
|
|
2279
|
-
baseline = np.median(corrfunc)
|
|
3678
|
+
baseline = float(np.median(corrfunc))
|
|
2280
3679
|
baselinedev = mad(corrfunc)
|
|
2281
3680
|
if debug:
|
|
2282
3681
|
print("baseline, baselinedev:", baseline, baselinedev)
|
|
@@ -2309,8 +3708,8 @@ def simfuncpeakfit(
|
|
|
2309
3708
|
|
|
2310
3709
|
peakpoints[0] = 0
|
|
2311
3710
|
peakpoints[-1] = 0
|
|
2312
|
-
peakstart = np.max([1, maxindex - 1])
|
|
2313
|
-
peakend = np.min([len(corrtimeaxis) - 2, maxindex + 1])
|
|
3711
|
+
peakstart = int(np.max([1, maxindex - 1]))
|
|
3712
|
+
peakend = int(np.min([len(corrtimeaxis) - 2, maxindex + 1]))
|
|
2314
3713
|
if debug:
|
|
2315
3714
|
print("initial peakstart, peakend:", peakstart, peakend)
|
|
2316
3715
|
if functype == "mutualinfo":
|
|
@@ -2376,7 +3775,7 @@ def simfuncpeakfit(
|
|
|
2376
3775
|
print("bad initial")
|
|
2377
3776
|
if maxsigma_init > absmaxsigma:
|
|
2378
3777
|
failreason |= FML_INITWIDTHHIGH
|
|
2379
|
-
maxsigma_init = absmaxsigma
|
|
3778
|
+
maxsigma_init = np.float64(absmaxsigma)
|
|
2380
3779
|
if debug:
|
|
2381
3780
|
print("bad initial width - too high")
|
|
2382
3781
|
if peakend - peakstart < 2:
|
|
@@ -2429,7 +3828,7 @@ def simfuncpeakfit(
|
|
|
2429
3828
|
data = corrfunc[peakstart : peakend + 1]
|
|
2430
3829
|
maxval = maxval_init
|
|
2431
3830
|
maxlag = np.sum(X * data) / np.sum(data)
|
|
2432
|
-
maxsigma = 10.0
|
|
3831
|
+
maxsigma = np.float64(10.0)
|
|
2433
3832
|
elif peakfittype == "gauss":
|
|
2434
3833
|
X = corrtimeaxis[peakstart : peakend + 1] - baseline
|
|
2435
3834
|
data = corrfunc[peakstart : peakend + 1]
|
|
@@ -2480,10 +3879,10 @@ def simfuncpeakfit(
|
|
|
2480
3879
|
if debug:
|
|
2481
3880
|
print("poly coffs:", a, b, c)
|
|
2482
3881
|
print("maxlag, maxval, maxsigma:", maxlag, maxval, maxsigma)
|
|
2483
|
-
except np.
|
|
2484
|
-
maxlag = 0.0
|
|
2485
|
-
maxval = 0.0
|
|
2486
|
-
maxsigma = 0.0
|
|
3882
|
+
except np.exceptions.RankWarning:
|
|
3883
|
+
maxlag = np.float64(0.0)
|
|
3884
|
+
maxval = np.float64(0.0)
|
|
3885
|
+
maxsigma = np.float64(0.0)
|
|
2487
3886
|
if debug:
|
|
2488
3887
|
print("\n")
|
|
2489
3888
|
for i in range(len(X)):
|
|
@@ -2508,7 +3907,7 @@ def simfuncpeakfit(
|
|
|
2508
3907
|
if (functype == "correlation") or (functype == "hybrid"):
|
|
2509
3908
|
if maxval < lowestcorrcoeff:
|
|
2510
3909
|
failreason |= FML_FITAMPLOW
|
|
2511
|
-
maxval = lowestcorrcoeff
|
|
3910
|
+
maxval = np.float64(lowestcorrcoeff)
|
|
2512
3911
|
if debug:
|
|
2513
3912
|
print("bad fit amp: maxval is lower than lower limit")
|
|
2514
3913
|
fitfail = True
|
|
@@ -2545,22 +3944,22 @@ def simfuncpeakfit(
|
|
|
2545
3944
|
print("bad lag after refinement")
|
|
2546
3945
|
if lagmin > maxlag:
|
|
2547
3946
|
failreason |= FML_FITLAGLOW
|
|
2548
|
-
maxlag = lagmin
|
|
3947
|
+
maxlag = np.float64(lagmin)
|
|
2549
3948
|
else:
|
|
2550
3949
|
failreason |= FML_FITLAGHIGH
|
|
2551
|
-
maxlag = lagmax
|
|
3950
|
+
maxlag = np.float64(lagmax)
|
|
2552
3951
|
fitfail = True
|
|
2553
3952
|
if maxsigma > absmaxsigma:
|
|
2554
3953
|
failreason |= FML_FITWIDTHHIGH
|
|
2555
3954
|
if debug:
|
|
2556
3955
|
print("bad width after refinement:", maxsigma, ">", absmaxsigma)
|
|
2557
|
-
maxsigma = absmaxsigma
|
|
3956
|
+
maxsigma = np.float64(absmaxsigma)
|
|
2558
3957
|
fitfail = True
|
|
2559
3958
|
if maxsigma < absminsigma:
|
|
2560
3959
|
failreason |= FML_FITWIDTHLOW
|
|
2561
3960
|
if debug:
|
|
2562
3961
|
print("bad width after refinement:", maxsigma, "<", absminsigma)
|
|
2563
|
-
maxsigma = absminsigma
|
|
3962
|
+
maxsigma = np.float64(absminsigma)
|
|
2564
3963
|
fitfail = True
|
|
2565
3964
|
if fitfail:
|
|
2566
3965
|
if debug:
|
|
@@ -2616,16 +4015,47 @@ def simfuncpeakfit(
|
|
|
2616
4015
|
)
|
|
2617
4016
|
|
|
2618
4017
|
|
|
2619
|
-
def _maxindex_noedge(
|
|
4018
|
+
def _maxindex_noedge(
|
|
4019
|
+
corrfunc: NDArray, corrtimeaxis: NDArray, bipolar: bool = False
|
|
4020
|
+
) -> Tuple[int, float]:
|
|
2620
4021
|
"""
|
|
4022
|
+
Find the index of the maximum correlation value, avoiding edge effects.
|
|
4023
|
+
|
|
4024
|
+
This function locates the maximum (or minimum, if bipolar=True) correlation value
|
|
4025
|
+
within the given time axis range, while avoiding edge effects by progressively
|
|
4026
|
+
narrowing the search window.
|
|
2621
4027
|
|
|
2622
4028
|
Parameters
|
|
2623
4029
|
----------
|
|
2624
|
-
corrfunc
|
|
4030
|
+
corrfunc : NDArray
|
|
4031
|
+
Correlation function values to search for maximum.
|
|
4032
|
+
corrtimeaxis : NDArray
|
|
4033
|
+
Time axis corresponding to the correlation function.
|
|
4034
|
+
bipolar : bool, optional
|
|
4035
|
+
If True, considers both positive and negative correlation values.
|
|
4036
|
+
Default is False.
|
|
2625
4037
|
|
|
2626
4038
|
Returns
|
|
2627
4039
|
-------
|
|
4040
|
+
Tuple[int, float]
|
|
4041
|
+
A tuple containing:
|
|
4042
|
+
- int: Index of the maximum correlation value
|
|
4043
|
+
- float: Flip factor (-1.0 if minimum was selected, 1.0 otherwise)
|
|
2628
4044
|
|
|
4045
|
+
Notes
|
|
4046
|
+
-----
|
|
4047
|
+
The function iteratively narrows the search range by excluding edges
|
|
4048
|
+
where the maximum was found. This helps avoid edge effects in correlation
|
|
4049
|
+
analysis. When bipolar=True, the function compares both maximum and minimum
|
|
4050
|
+
absolute values to determine the optimal selection.
|
|
4051
|
+
|
|
4052
|
+
Examples
|
|
4053
|
+
--------
|
|
4054
|
+
>>> corrfunc = np.array([0.1, 0.5, 0.3, 0.8, 0.2])
|
|
4055
|
+
>>> corrtimeaxis = np.array([0, 1, 2, 3, 4])
|
|
4056
|
+
>>> index, flip = _maxindex_noedge(corrfunc, corrtimeaxis)
|
|
4057
|
+
>>> print(index)
|
|
4058
|
+
3
|
|
2629
4059
|
"""
|
|
2630
4060
|
lowerlim = 0
|
|
2631
4061
|
upperlim = len(corrtimeaxis) - 1
|