rapidtide 3.0.7.1__py3-none-any.whl → 3.0.9__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.
Files changed (57) hide show
  1. rapidtide/RapidtideDataset.py +1 -1
  2. rapidtide/_version.py +3 -3
  3. rapidtide/calcnullsimfunc.py +1 -3
  4. rapidtide/data/examples/src/test_findmaxlag.py +1 -1
  5. rapidtide/data/examples/src/testfmri +19 -7
  6. rapidtide/data/examples/src/testnewrefine +0 -23
  7. rapidtide/fMRIData_class.py +29 -52
  8. rapidtide/fit.py +4 -4
  9. rapidtide/happy_supportfuncs.py +1 -1
  10. rapidtide/helper_classes.py +0 -1099
  11. rapidtide/linfitfiltpass.py +82 -4
  12. rapidtide/makelaggedtcs.py +10 -0
  13. rapidtide/refinedelay.py +11 -20
  14. rapidtide/refineregressor.py +1 -1
  15. rapidtide/resample.py +8 -8
  16. rapidtide/simFuncClasses.py +1132 -0
  17. rapidtide/simfuncfit.py +30 -30
  18. rapidtide/stats.py +5 -2
  19. rapidtide/tests/.coveragerc +6 -0
  20. rapidtide/tests/cleanposttest +1 -1
  21. rapidtide/tests/runlocaltest +2 -2
  22. rapidtide/tests/test_cleanregressor.py +3 -3
  23. rapidtide/tests/test_congrid.py +1 -1
  24. rapidtide/tests/test_corrpass.py +3 -3
  25. rapidtide/tests/test_delayestimation.py +9 -8
  26. rapidtide/tests/test_findmaxlag.py +2 -2
  27. rapidtide/tests/test_fullrunrapidtide_v3.py +2 -1
  28. rapidtide/tests/test_fullrunrapidtide_v8.py +66 -0
  29. rapidtide/tests/test_getparsers.py +14 -6
  30. rapidtide/tests/test_io.py +2 -6
  31. rapidtide/tests/test_nullcorr.py +3 -3
  32. rapidtide/tests/test_refinedelay.py +20 -5
  33. rapidtide/tidepoolTemplate_alt.py +1 -1
  34. rapidtide/util.py +7 -0
  35. rapidtide/voxelData.py +3 -6
  36. rapidtide/workflows/calcSimFuncMap.py +271 -0
  37. rapidtide/workflows/cleanregressor.py +2 -2
  38. rapidtide/workflows/delayvar.py +45 -59
  39. rapidtide/workflows/fitSimFuncMap.py +427 -0
  40. rapidtide/workflows/happy.py +1 -1
  41. rapidtide/workflows/rapidtide.py +499 -877
  42. rapidtide/workflows/rapidtide_parser.py +26 -38
  43. rapidtide/workflows/refineDelayMap.py +138 -0
  44. rapidtide/{RegressorRefiner.py → workflows/refineRegressor.py} +200 -28
  45. rapidtide/workflows/regressfrommaps.py +38 -30
  46. rapidtide/workflows/retrolagtcs.py +5 -6
  47. rapidtide/workflows/retroregress.py +73 -191
  48. rapidtide/workflows/showarbcorr.py +2 -2
  49. rapidtide/workflows/showxcorrx.py +5 -5
  50. rapidtide/workflows/tidepool.py +5 -5
  51. {rapidtide-3.0.7.1.dist-info → rapidtide-3.0.9.dist-info}/METADATA +2 -2
  52. {rapidtide-3.0.7.1.dist-info → rapidtide-3.0.9.dist-info}/RECORD +56 -52
  53. rapidtide/workflows/delayestimation.py +0 -483
  54. {rapidtide-3.0.7.1.dist-info → rapidtide-3.0.9.dist-info}/WHEEL +0 -0
  55. {rapidtide-3.0.7.1.dist-info → rapidtide-3.0.9.dist-info}/entry_points.txt +0 -0
  56. {rapidtide-3.0.7.1.dist-info → rapidtide-3.0.9.dist-info}/licenses/LICENSE +0 -0
  57. {rapidtide-3.0.7.1.dist-info → rapidtide-3.0.9.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1132 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ #
4
+ # Copyright 2016-2025 Blaise Frederick
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ #
19
+ import sys
20
+ import warnings
21
+
22
+ import matplotlib.pyplot as plt
23
+ import numpy as np
24
+ import scipy as sp
25
+ from numpy.polynomial import Polynomial
26
+ from scipy.optimize import curve_fit
27
+ from statsmodels.robust import mad
28
+
29
+ import rapidtide.correlate as tide_corr
30
+ import rapidtide.filter as tide_filt
31
+ import rapidtide.fit as tide_fit
32
+ import rapidtide.miscmath as tide_math
33
+ import rapidtide.util as tide_util
34
+
35
+
36
+ class SimilarityFunctionator:
37
+ reftc = None
38
+ prepreftc = None
39
+ testtc = None
40
+ preptesttc = None
41
+ timeaxis = None
42
+ similarityfunclen = 0
43
+ datavalid = False
44
+ timeaxisvalid = False
45
+ similarityfuncorigin = 0
46
+
47
+ def __init__(
48
+ self,
49
+ Fs=0.0,
50
+ similarityfuncorigin=0,
51
+ lagmininpts=0,
52
+ lagmaxinpts=0,
53
+ ncprefilter=None,
54
+ negativegradient=False,
55
+ reftc=None,
56
+ reftcstart=0.0,
57
+ detrendorder=1,
58
+ filterinputdata=True,
59
+ debug=False,
60
+ ):
61
+ self.setFs(Fs)
62
+ self.similarityfuncorigin = similarityfuncorigin
63
+ self.lagmininpts = lagmininpts + 0
64
+ self.lagmaxinpts = lagmaxinpts + 0
65
+ self.ncprefilter = ncprefilter
66
+ self.negativegradient = negativegradient
67
+ self.reftc = reftc
68
+ self.detrendorder = detrendorder
69
+ self.filterinputdata = filterinputdata
70
+ self.debug = debug
71
+ if self.reftc is not None:
72
+ self.setreftc(self.reftc)
73
+ self.reftcstart = reftcstart + 0.0
74
+
75
+ def setFs(self, Fs):
76
+ self.Fs = Fs
77
+
78
+ def preptc(self, thetc, isreftc=False):
79
+ # prepare timecourse by filtering, normalizing, detrending, and applying a window function
80
+ if isreftc:
81
+ thenormtc = tide_math.corrnormalize(
82
+ self.ncprefilter.apply(self.Fs, thetc),
83
+ detrendorder=self.detrendorder,
84
+ windowfunc=self.windowfunc,
85
+ )
86
+ else:
87
+ if self.negativegradient:
88
+ thenormtc = tide_math.corrnormalize(
89
+ -np.gradient(self.ncprefilter.apply(self.Fs, thetc)),
90
+ detrendorder=self.detrendorder,
91
+ windowfunc=self.windowfunc,
92
+ )
93
+ else:
94
+ if self.filterinputdata:
95
+ thenormtc = tide_math.corrnormalize(
96
+ self.ncprefilter.apply(self.Fs, thetc),
97
+ detrendorder=self.detrendorder,
98
+ windowfunc=self.windowfunc,
99
+ )
100
+ else:
101
+ thenormtc = tide_math.corrnormalize(
102
+ thetc,
103
+ detrendorder=self.detrendorder,
104
+ windowfunc=self.windowfunc,
105
+ )
106
+
107
+ return thenormtc
108
+
109
+ def trim(self, vector):
110
+ return vector[
111
+ self.similarityfuncorigin
112
+ - self.lagmininpts : self.similarityfuncorigin
113
+ + self.lagmaxinpts
114
+ ]
115
+
116
+ def getfunction(self, trim=True):
117
+ if self.datavalid:
118
+ if trim:
119
+ return (
120
+ self.trim(self.thesimfunc),
121
+ self.trim(self.timeaxis),
122
+ self.theglobalmax,
123
+ )
124
+ else:
125
+ return self.thesimfunc, self.timeaxis, self.theglobalmax
126
+ else:
127
+ if self.timeaxisvalid:
128
+ if trim:
129
+ return None, self.trim(self.timeaxis), None
130
+ else:
131
+ return None, self.timeaxis, None
132
+ else:
133
+ print("must calculate similarity function before fetching data")
134
+ return None, None, None
135
+
136
+
137
+ class MutualInformationator(SimilarityFunctionator):
138
+ def __init__(
139
+ self,
140
+ windowfunc="hamming",
141
+ norm=True,
142
+ madnorm=False,
143
+ smoothingtime=-1.0,
144
+ bins=20,
145
+ sigma=0.25,
146
+ *args,
147
+ **kwargs,
148
+ ):
149
+ self.windowfunc = windowfunc
150
+ self.norm = norm
151
+ self.madnorm = madnorm
152
+ self.bins = bins
153
+ self.sigma = sigma
154
+ self.smoothingtime = smoothingtime
155
+ self.smoothingfilter = tide_filt.NoncausalFilter(filtertype="arb")
156
+ self.mi_norm = 1.0
157
+ if self.smoothingtime > 0.0:
158
+ self.smoothingfilter.setfreqs(
159
+ 0.0, 0.0, 1.0 / self.smoothingtime, 1.0 / self.smoothingtime
160
+ )
161
+ super(MutualInformationator, self).__init__(*args, **kwargs)
162
+
163
+ def setlimits(self, lagmininpts, lagmaxinpts):
164
+ self.lagmininpts = lagmininpts
165
+ self.lagmaxinpts = lagmaxinpts
166
+ origpadtime = self.smoothingfilter.getpadtime()
167
+ timespan = self.timeaxis[-1] - self.timeaxis[0]
168
+ newpadtime = np.min([origpadtime, timespan])
169
+ if newpadtime < origpadtime:
170
+ print("lowering smoothing filter pad time to", newpadtime)
171
+ self.smoothingfilter.setpadtime(newpadtime)
172
+
173
+ def setbins(self, bins):
174
+ self.bins = bins
175
+
176
+ def setreftc(self, reftc, offset=0.0):
177
+ self.reftc = reftc + 0.0
178
+ self.prepreftc = self.preptc(self.reftc, isreftc=True)
179
+
180
+ self.timeaxis, self.automi, self.similarityfuncorigin = tide_corr.cross_mutual_info(
181
+ self.prepreftc,
182
+ self.prepreftc,
183
+ Fs=self.Fs,
184
+ fast=True,
185
+ negsteps=self.lagmininpts,
186
+ possteps=self.lagmaxinpts,
187
+ returnaxis=True,
188
+ )
189
+
190
+ self.timeaxis -= offset
191
+ self.similarityfunclen = len(self.timeaxis)
192
+ self.timeaxisvalid = True
193
+ self.datavalid = False
194
+ self.mi_norm = np.nan_to_num(1.0 / np.max(self.automi))
195
+ if self.debug:
196
+ print(f"MutualInformationator setreftc: {len(self.timeaxis)=}")
197
+ print(f"MutualInformationator setreftc: {self.timeaxis}")
198
+ print(f"MutualInformationator setreftc: {self.mi_norm=}")
199
+
200
+ def getnormfac(self):
201
+ return self.mi_norm
202
+
203
+ def run(self, thetc, locs=None, trim=True, gettimeaxis=True):
204
+ if len(thetc) != len(self.reftc):
205
+ print(
206
+ "MutualInformationator: timecourses are of different sizes:",
207
+ len(thetc),
208
+ "!=",
209
+ len(self.reftc),
210
+ "- exiting",
211
+ )
212
+ sys.exit()
213
+
214
+ self.testtc = thetc
215
+ self.preptesttc = self.preptc(self.testtc)
216
+
217
+ if locs is not None:
218
+ gettimeaxis = True
219
+
220
+ # now calculate the similarity function
221
+ if trim:
222
+ retvals = tide_corr.cross_mutual_info(
223
+ self.preptesttc,
224
+ self.prepreftc,
225
+ norm=self.norm,
226
+ negsteps=self.lagmininpts,
227
+ possteps=self.lagmaxinpts,
228
+ locs=locs,
229
+ madnorm=self.madnorm,
230
+ returnaxis=gettimeaxis,
231
+ fast=True,
232
+ Fs=self.Fs,
233
+ sigma=self.sigma,
234
+ bins=self.bins,
235
+ )
236
+ else:
237
+ retvals = tide_corr.cross_mutual_info(
238
+ self.preptesttc,
239
+ self.prepreftc,
240
+ norm=self.norm,
241
+ negsteps=-1,
242
+ possteps=-1,
243
+ locs=locs,
244
+ madnorm=self.madnorm,
245
+ returnaxis=gettimeaxis,
246
+ fast=True,
247
+ Fs=self.Fs,
248
+ sigma=self.sigma,
249
+ bins=self.bins,
250
+ )
251
+ if gettimeaxis:
252
+ self.timeaxis, self.thesimfunc, self.similarityfuncorigin = (
253
+ retvals[0],
254
+ retvals[1],
255
+ retvals[2],
256
+ )
257
+ self.timeaxisvalid = True
258
+ else:
259
+ self.thesimfunc = retvals[0]
260
+
261
+ # normalize
262
+ self.thesimfunc *= self.mi_norm
263
+
264
+ if locs is not None:
265
+ return self.thesimfunc
266
+
267
+ if self.smoothingtime > 0.0:
268
+ self.thesimfunc = self.smoothingfilter.apply(self.Fs, self.thesimfunc)
269
+
270
+ self.similarityfunclen = len(self.thesimfunc)
271
+ if trim:
272
+ self.similarityfuncorigin = self.lagmininpts + 1
273
+ else:
274
+ self.similarityfuncorigin = self.similarityfunclen // 2 + 1
275
+
276
+ # find the global maximum value
277
+ self.theglobalmax = np.argmax(self.thesimfunc)
278
+ self.datavalid = True
279
+
280
+ # make a dummy filtered baseline
281
+ self.filteredbaseline = self.thesimfunc * 0.0
282
+
283
+ if trim:
284
+ return (
285
+ self.trim(self.thesimfunc),
286
+ self.trim(self.timeaxis),
287
+ self.theglobalmax,
288
+ )
289
+ else:
290
+ return self.thesimfunc, self.timeaxis, self.theglobalmax
291
+
292
+
293
+ class Correlator(SimilarityFunctionator):
294
+ def __init__(
295
+ self,
296
+ windowfunc="hamming",
297
+ corrweighting="None",
298
+ corrpadding=0,
299
+ baselinefilter=None,
300
+ *args,
301
+ **kwargs,
302
+ ):
303
+ self.windowfunc = windowfunc
304
+ self.corrweighting = corrweighting
305
+ self.corrpadding = corrpadding
306
+ self.baselinefilter = baselinefilter
307
+ super(Correlator, self).__init__(*args, **kwargs)
308
+
309
+ def setlimits(self, lagmininpts, lagmaxinpts):
310
+ self.lagmininpts = lagmininpts
311
+ self.lagmaxinpts = lagmaxinpts
312
+
313
+ def setreftc(self, reftc, offset=0.0):
314
+ self.reftc = reftc + 0.0
315
+ self.prepreftc = self.preptc(self.reftc, isreftc=True)
316
+ self.similarityfunclen = len(self.reftc) * 2 - 1
317
+ self.similarityfuncorigin = self.similarityfunclen // 2 + 1
318
+
319
+ # make the reference time axis
320
+ self.timeaxis = (
321
+ np.arange(0.0, self.similarityfunclen) * (1.0 / self.Fs)
322
+ - ((self.similarityfunclen - 1) * (1.0 / self.Fs)) / 2.0
323
+ ) - offset
324
+ self.timeaxisvalid = True
325
+ self.datavalid = False
326
+
327
+ def run(self, thetc, trim=True):
328
+ if len(thetc) != len(self.reftc):
329
+ print(
330
+ "Correlator: timecourses are of different sizes:",
331
+ len(thetc),
332
+ "!=",
333
+ len(self.reftc),
334
+ "- exiting",
335
+ )
336
+ sys.exit()
337
+
338
+ self.testtc = thetc
339
+ self.preptesttc = self.preptc(self.testtc)
340
+
341
+ # now actually do the correlation
342
+ self.thesimfunc = tide_corr.fastcorrelate(
343
+ self.preptesttc,
344
+ self.prepreftc,
345
+ usefft=True,
346
+ weighting=self.corrweighting,
347
+ zeropadding=self.corrpadding,
348
+ debug=self.debug,
349
+ )
350
+ self.similarityfunclen = len(self.thesimfunc)
351
+ self.similarityfuncorigin = self.similarityfunclen // 2 + 1
352
+
353
+ if self.baselinefilter is not None:
354
+ self.filteredbaseline = self.baselinefilter.apply(self.Fs, self.thesimfunc)
355
+ else:
356
+ self.filteredbaseline = self.thesimfunc * 0.0
357
+
358
+ # find the global maximum value
359
+ self.theglobalmax = np.argmax(self.thesimfunc)
360
+ self.datavalid = True
361
+
362
+ if trim:
363
+ return (
364
+ self.trim(self.thesimfunc),
365
+ self.trim(self.timeaxis),
366
+ self.theglobalmax,
367
+ )
368
+ else:
369
+ return self.thesimfunc, self.timeaxis, self.theglobalmax
370
+
371
+
372
+ class SimilarityFunctionFitter:
373
+ corrtimeaxis = None
374
+ FML_NOERROR = np.uint32(0x0000)
375
+
376
+ FML_INITAMPLOW = np.uint32(0x0001)
377
+ FML_INITAMPHIGH = np.uint32(0x0002)
378
+ FML_INITWIDTHLOW = np.uint32(0x0004)
379
+ FML_INITWIDTHHIGH = np.uint32(0x0008)
380
+ FML_INITLAGLOW = np.uint32(0x0010)
381
+ FML_INITLAGHIGH = np.uint32(0x0020)
382
+ FML_INITFAIL = (
383
+ FML_INITAMPLOW
384
+ | FML_INITAMPHIGH
385
+ | FML_INITWIDTHLOW
386
+ | FML_INITWIDTHHIGH
387
+ | FML_INITLAGLOW
388
+ | FML_INITLAGHIGH
389
+ )
390
+
391
+ FML_FITAMPLOW = np.uint32(0x0100)
392
+ FML_FITAMPHIGH = np.uint32(0x0200)
393
+ FML_FITWIDTHLOW = np.uint32(0x0400)
394
+ FML_FITWIDTHHIGH = np.uint32(0x0800)
395
+ FML_FITLAGLOW = np.uint32(0x1000)
396
+ FML_FITLAGHIGH = np.uint32(0x2000)
397
+ FML_FITALGOFAIL = np.uint32(0x0400)
398
+ FML_FITFAIL = (
399
+ FML_FITAMPLOW
400
+ | FML_FITAMPHIGH
401
+ | FML_FITWIDTHLOW
402
+ | FML_FITWIDTHHIGH
403
+ | FML_FITLAGLOW
404
+ | FML_FITLAGHIGH
405
+ | FML_FITALGOFAIL
406
+ )
407
+
408
+ def __init__(
409
+ self,
410
+ corrtimeaxis=None,
411
+ lagmin=-30.0,
412
+ lagmax=30.0,
413
+ absmaxsigma=1000.0,
414
+ absminsigma=0.25,
415
+ hardlimit=True,
416
+ bipolar=False,
417
+ lthreshval=0.0,
418
+ uthreshval=1.0,
419
+ debug=False,
420
+ zerooutbadfit=True,
421
+ maxguess=0.0,
422
+ useguess=False,
423
+ searchfrac=0.5,
424
+ lagmod=1000.0,
425
+ enforcethresh=True,
426
+ allowhighfitamps=False,
427
+ displayplots=False,
428
+ functype="correlation",
429
+ peakfittype="gauss",
430
+ ):
431
+ r"""
432
+
433
+ Parameters
434
+ ----------
435
+ corrtimeaxis: 1D float array
436
+ The time axis of the correlation function
437
+ lagmin: float
438
+ The minimum allowed lag time in seconds
439
+ lagmax: float
440
+ The maximum allowed lag time in seconds
441
+ absmaxsigma: float
442
+ The maximum allowed peak halfwidth in seconds
443
+ hardlimit
444
+ bipolar: boolean
445
+ If true find the correlation peak with the maximum absolute value, regardless of sign
446
+ lthreshval
447
+ uthreshval
448
+ debug
449
+ zerooutbadfit
450
+ maxguess
451
+ useguess
452
+ searchfrac
453
+ lagmod
454
+ enforcethresh
455
+ displayplots
456
+
457
+ Returns
458
+ -------
459
+
460
+
461
+ Methods
462
+ -------
463
+ fit(corrfunc):
464
+ Fit the correlation function given in corrfunc and return the location of the peak in seconds, the maximum
465
+ correlation value, the peak width
466
+ setrange(lagmin, lagmax):
467
+ Specify the search range for lag peaks, in seconds
468
+ """
469
+ self.setcorrtimeaxis(corrtimeaxis)
470
+ self.lagmin = lagmin + 0.0
471
+ self.lagmax = lagmax + 0.0
472
+ self.absmaxsigma = absmaxsigma + 0.0
473
+ self.absminsigma = absminsigma + 0.0
474
+ self.hardlimit = hardlimit
475
+ self.bipolar = bipolar
476
+ self.lthreshval = lthreshval + 0.0
477
+ self.uthreshval = uthreshval + 0.0
478
+ self.debug = debug
479
+ if functype == "correlation" or functype == "mutualinfo":
480
+ self.functype = functype
481
+ else:
482
+ print("illegal functype")
483
+ sys.exit()
484
+ self.peakfittype = peakfittype
485
+ self.zerooutbadfit = zerooutbadfit
486
+ self.maxguess = maxguess + 0.0
487
+ self.useguess = useguess
488
+ self.searchfrac = searchfrac + 0.0
489
+ self.lagmod = lagmod + 0.0
490
+ self.enforcethresh = enforcethresh
491
+ self.allowhighfitamps = allowhighfitamps
492
+ self.displayplots = displayplots
493
+
494
+ def _maxindex_noedge(self, corrfunc):
495
+ """
496
+
497
+ Parameters
498
+ ----------
499
+ corrfunc
500
+
501
+ Returns
502
+ -------
503
+
504
+ """
505
+ lowerlim = 0
506
+ upperlim = len(self.corrtimeaxis) - 1
507
+ done = False
508
+ while not done:
509
+ flipfac = 1.0
510
+ done = True
511
+ maxindex = (np.argmax(corrfunc[lowerlim:upperlim]) + lowerlim).astype("int32")
512
+ if self.bipolar:
513
+ minindex = (np.argmax(-corrfunc[lowerlim:upperlim]) + lowerlim).astype("int32")
514
+ if np.fabs(corrfunc[minindex]) > np.fabs(corrfunc[maxindex]):
515
+ maxindex = minindex
516
+ flipfac = -1.0
517
+ if upperlim == lowerlim:
518
+ done = True
519
+ if maxindex == 0:
520
+ lowerlim += 1
521
+ done = False
522
+ if maxindex == upperlim:
523
+ upperlim -= 1
524
+ done = False
525
+ return maxindex, flipfac
526
+
527
+ def setfunctype(self, functype):
528
+ self.functype = functype
529
+
530
+ def setpeakfittype(self, peakfittype):
531
+ self.peakfittype = peakfittype
532
+
533
+ def setrange(self, lagmin, lagmax):
534
+ self.lagmin = lagmin
535
+ self.lagmax = lagmax
536
+
537
+ def setcorrtimeaxis(self, corrtimeaxis):
538
+ if corrtimeaxis is not None:
539
+ self.corrtimeaxis = corrtimeaxis + 0.0
540
+ else:
541
+ self.corrtimeaxis = corrtimeaxis
542
+
543
+ def setguess(self, useguess, maxguess=0.0):
544
+ self.useguess = useguess
545
+ self.maxguess = maxguess
546
+
547
+ def setlthresh(self, lthreshval):
548
+ self.lthreshval = lthreshval
549
+
550
+ def setuthresh(self, uthreshval):
551
+ self.uthreshval = uthreshval
552
+
553
+ def diagnosefail(self, failreason):
554
+ # define error values
555
+ reasons = []
556
+ if failreason.astype(np.uint32) & self.FML_INITAMPLOW:
557
+ reasons.append("Initial amplitude too low")
558
+ if failreason.astype(np.uint32) & self.FML_INITAMPHIGH:
559
+ reasons.append("Initial amplitude too high")
560
+ if failreason.astype(np.uint32) & self.FML_INITWIDTHLOW:
561
+ reasons.append("Initial width too low")
562
+ if failreason.astype(np.uint32) & self.FML_INITWIDTHHIGH:
563
+ reasons.append("Initial width too high")
564
+ if failreason.astype(np.uint32) & self.FML_INITLAGLOW:
565
+ reasons.append("Initial Lag too low")
566
+ if failreason.astype(np.uint32) & self.FML_INITLAGHIGH:
567
+ reasons.append("Initial Lag too high")
568
+
569
+ if failreason.astype(np.uint32) & self.FML_FITAMPLOW:
570
+ reasons.append("Fit amplitude too low")
571
+ if failreason.astype(np.uint32) & self.FML_FITAMPHIGH:
572
+ reasons.append("Fit amplitude too high")
573
+ if failreason.astype(np.uint32) & self.FML_FITWIDTHLOW:
574
+ reasons.append("Fit width too low")
575
+ if failreason.astype(np.uint32) & self.FML_FITWIDTHHIGH:
576
+ reasons.append("Fit width too high")
577
+ if failreason.astype(np.uint32) & self.FML_FITLAGLOW:
578
+ reasons.append("Fit Lag too low")
579
+ if failreason.astype(np.uint32) & self.FML_FITLAGHIGH:
580
+ reasons.append("Fit Lag too high")
581
+ if failreason.astype(np.uint32) & self.FML_FITALGOFAIL:
582
+ reasons.append("Nonlinear fit failed")
583
+
584
+ if len(reasons) > 0:
585
+ return ", ".join(reasons)
586
+ else:
587
+ return "No error"
588
+
589
+ def fit(self, incorrfunc):
590
+ # check to make sure xcorr_x and xcorr_y match
591
+ if self.corrtimeaxis is None:
592
+ print("Correlation time axis is not defined - exiting")
593
+ sys.exit()
594
+ if len(self.corrtimeaxis) != len(incorrfunc):
595
+ print(
596
+ "Correlation time axis and values do not match in length (",
597
+ len(self.corrtimeaxis),
598
+ "!=",
599
+ len(incorrfunc),
600
+ "- exiting",
601
+ )
602
+ sys.exit()
603
+ # set initial parameters
604
+ # absmaxsigma is in seconds
605
+ # maxsigma is in Hz
606
+ # maxlag is in seconds
607
+ warnings.filterwarnings("ignore", "Number*")
608
+ failreason = self.FML_NOERROR
609
+ maskval = np.uint16(1) # start out assuming the fit will succeed
610
+ binwidth = self.corrtimeaxis[1] - self.corrtimeaxis[0]
611
+
612
+ # set the search range
613
+ lowerlim = 0
614
+ upperlim = len(self.corrtimeaxis) - 1
615
+ if self.debug:
616
+ print(
617
+ "initial search indices are",
618
+ lowerlim,
619
+ "to",
620
+ upperlim,
621
+ "(",
622
+ self.corrtimeaxis[lowerlim],
623
+ self.corrtimeaxis[upperlim],
624
+ ")",
625
+ )
626
+
627
+ # make an initial guess at the fit parameters for the gaussian
628
+ # start with finding the maximum value and its location
629
+ flipfac = 1.0
630
+ corrfunc = incorrfunc + 0.0
631
+ if self.useguess:
632
+ maxindex = tide_util.valtoindex(self.corrtimeaxis, self.maxguess)
633
+ if (corrfunc[maxindex] < 0.0) and self.bipolar:
634
+ flipfac = -1.0
635
+ else:
636
+ maxindex, flipfac = self._maxindex_noedge(corrfunc)
637
+ corrfunc *= flipfac
638
+ maxlag_init = (1.0 * self.corrtimeaxis[maxindex]).astype("float64")
639
+ maxval_init = corrfunc[maxindex].astype("float64")
640
+ if self.debug:
641
+ print(
642
+ "maxindex, maxlag_init, maxval_init:",
643
+ maxindex,
644
+ maxlag_init,
645
+ maxval_init,
646
+ )
647
+
648
+ # set the baseline and baselinedev levels
649
+ if (self.functype == "correlation") or (self.functype == "hybrid"):
650
+ baseline = 0.0
651
+ baselinedev = 0.0
652
+ else:
653
+ # for mutual information, there is a nonzero baseline, so we want the difference from that.
654
+ baseline = np.median(corrfunc)
655
+ baselinedev = mad(corrfunc)
656
+ if self.debug:
657
+ print("baseline, baselinedev:", baseline, baselinedev)
658
+
659
+ # then calculate the width of the peak
660
+ if self.peakfittype == "fastquad" or self.peakfittype == "COM":
661
+ peakstart = np.max([1, maxindex - 2])
662
+ peakend = np.min([len(self.corrtimeaxis) - 2, maxindex + 2])
663
+ else:
664
+ # come here for peakfittype of None, quad, gauss, fastgauss
665
+ thegrad = np.gradient(corrfunc).astype(
666
+ "float64"
667
+ ) # the gradient of the correlation function
668
+ if (self.functype == "correlation") or (self.functype == "hybrid"):
669
+ if self.peakfittype == "quad":
670
+ peakpoints = np.where(
671
+ corrfunc > maxval_init - 0.05, 1, 0
672
+ ) # mask for places where correlation exceeds searchfrac*maxval_init
673
+ else:
674
+ peakpoints = np.where(
675
+ corrfunc > (baseline + self.searchfrac * (maxval_init - baseline)), 1, 0
676
+ ) # mask for places where correlation exceeds searchfrac*maxval_init
677
+ else:
678
+ # for mutual information, there is a flattish, nonzero baseline, so we want the difference from that.
679
+ peakpoints = np.where(
680
+ corrfunc > (baseline + self.searchfrac * (maxval_init - baseline)),
681
+ 1,
682
+ 0,
683
+ )
684
+
685
+ peakpoints[0] = 0
686
+ peakpoints[-1] = 0
687
+ peakstart = np.max([1, maxindex - 1])
688
+ peakend = np.min([len(self.corrtimeaxis) - 2, maxindex + 1])
689
+ if self.debug:
690
+ print("initial peakstart, peakend:", peakstart, peakend)
691
+ if self.functype == "mutualinfo":
692
+ while peakpoints[peakend + 1] == 1:
693
+ peakend += 1
694
+ while peakpoints[peakstart - 1] == 1:
695
+ peakstart -= 1
696
+ else:
697
+ while thegrad[peakend + 1] <= 0.0 and peakpoints[peakend + 1] == 1:
698
+ peakend += 1
699
+ while thegrad[peakstart - 1] >= 0.0 and peakpoints[peakstart - 1] == 1:
700
+ peakstart -= 1
701
+ if self.debug:
702
+ print("final peakstart, peakend:", peakstart, peakend)
703
+
704
+ # deal with flat peak top
705
+ while (
706
+ peakend < (len(self.corrtimeaxis) - 3)
707
+ and corrfunc[peakend] == corrfunc[peakend - 1]
708
+ ):
709
+ peakend += 1
710
+ while peakstart > 2 and corrfunc[peakstart] == corrfunc[peakstart + 1]:
711
+ peakstart -= 1
712
+ if self.debug:
713
+ print("peakstart, peakend after flattop correction:", peakstart, peakend)
714
+ print("\n")
715
+ for i in range(peakstart, peakend + 1):
716
+ print(self.corrtimeaxis[i], corrfunc[i])
717
+ print("\n")
718
+ fig = plt.figure()
719
+ ax = fig.add_subplot(111)
720
+ ax.set_title("Peak sent to fitting routine")
721
+ plt.plot(
722
+ self.corrtimeaxis[peakstart : peakend + 1],
723
+ corrfunc[peakstart : peakend + 1],
724
+ "r",
725
+ )
726
+ plt.show()
727
+
728
+ # This is calculated from first principles, but it's always big by a factor or ~1.4.
729
+ # Which makes me think I dropped a factor if sqrt(2). So fix that with a final division
730
+ maxsigma_init = np.float64(
731
+ ((peakend - peakstart + 1) * binwidth / (2.0 * np.sqrt(-np.log(self.searchfrac))))
732
+ / np.sqrt(2.0)
733
+ )
734
+ if self.debug:
735
+ print("maxsigma_init:", maxsigma_init)
736
+
737
+ # now check the values for errors
738
+ if self.hardlimit:
739
+ rangeextension = 0.0
740
+ else:
741
+ rangeextension = (self.lagmax - self.lagmin) * 0.75
742
+ if not (
743
+ (self.lagmin - rangeextension - binwidth)
744
+ <= maxlag_init
745
+ <= (self.lagmax + rangeextension + binwidth)
746
+ ):
747
+ if maxlag_init <= (self.lagmin - rangeextension - binwidth):
748
+ failreason |= self.FML_INITLAGLOW
749
+ maxlag_init = self.lagmin - rangeextension - binwidth
750
+ else:
751
+ failreason |= self.FML_INITLAGHIGH
752
+ maxlag_init = self.lagmax + rangeextension + binwidth
753
+ if self.debug:
754
+ print("bad initial")
755
+ if maxsigma_init > self.absmaxsigma:
756
+ failreason |= self.FML_INITWIDTHHIGH
757
+ maxsigma_init = self.absmaxsigma
758
+ if self.debug:
759
+ print("bad initial width - too high")
760
+ if peakend - peakstart < 2:
761
+ failreason |= self.FML_INITWIDTHLOW
762
+ maxsigma_init = np.float64(
763
+ ((2 + 1) * binwidth / (2.0 * np.sqrt(-np.log(self.searchfrac)))) / np.sqrt(2.0)
764
+ )
765
+ if self.debug:
766
+ print("bad initial width - too low")
767
+ if (self.functype == "correlation") or (self.functype == "hybrid"):
768
+ if not (self.lthreshval <= maxval_init <= self.uthreshval) and self.enforcethresh:
769
+ failreason |= self.FML_INITAMPLOW
770
+ if self.debug:
771
+ print(
772
+ "bad initial amp:",
773
+ maxval_init,
774
+ "is less than",
775
+ self.lthreshval,
776
+ )
777
+ if maxval_init < 0.0:
778
+ failreason |= self.FML_INITAMPLOW
779
+ maxval_init = 0.0
780
+ if self.debug:
781
+ print("bad initial amp:", maxval_init, "is less than 0.0")
782
+ if (maxval_init > 1.0) and self.enforcethresh:
783
+ failreason |= self.FML_INITAMPHIGH
784
+ maxval_init = 1.0
785
+ if self.debug:
786
+ print("bad initial amp:", maxval_init, "is greater than 1.0")
787
+ else:
788
+ # somewhat different rules for mutual information peaks
789
+ if ((maxval_init - baseline) < self.lthreshval * baselinedev) or (
790
+ maxval_init < baseline
791
+ ):
792
+ failreason |= self.FML_INITAMPLOW
793
+ maxval_init = 0.0
794
+ if self.debug:
795
+ print("bad initial amp:", maxval_init, "is less than 0.0")
796
+ if (failreason != self.FML_NOERROR) and self.zerooutbadfit:
797
+ maxval = np.float64(0.0)
798
+ maxlag = np.float64(0.0)
799
+ maxsigma = np.float64(0.0)
800
+ else:
801
+ maxval = np.float64(maxval_init)
802
+ maxlag = np.float64(maxlag_init)
803
+ maxsigma = np.float64(maxsigma_init)
804
+
805
+ # refine if necessary
806
+ if self.peakfittype != "None":
807
+ if self.peakfittype == "COM":
808
+ X = self.corrtimeaxis[peakstart : peakend + 1] - baseline
809
+ data = corrfunc[peakstart : peakend + 1]
810
+ maxval = maxval_init
811
+ maxlag = np.sum(X * data) / np.sum(data)
812
+ maxsigma = 10.0
813
+ elif self.peakfittype == "gauss":
814
+ X = self.corrtimeaxis[peakstart : peakend + 1] - baseline
815
+ data = corrfunc[peakstart : peakend + 1]
816
+ # do a least squares fit over the top of the peak
817
+ # p0 = np.array([maxval_init, np.fmod(maxlag_init, lagmod), maxsigma_init], dtype='float64')
818
+ p0 = np.array([maxval_init, maxlag_init, maxsigma_init], dtype="float64")
819
+ if self.debug:
820
+ print("fit input array:", p0)
821
+ try:
822
+ plsq, dummy = sp.optimize.leastsq(
823
+ tide_fit.gaussresiduals, p0, args=(data, X), maxfev=5000
824
+ )
825
+ maxval = plsq[0] + baseline
826
+ maxlag = np.fmod((1.0 * plsq[1]), self.lagmod)
827
+ maxsigma = plsq[2]
828
+ except:
829
+ failreason |= self.FML_FITALGOFAIL
830
+ maxval = np.float64(0.0)
831
+ maxlag = np.float64(0.0)
832
+ maxsigma = np.float64(0.0)
833
+ if self.debug:
834
+ print("fit output array:", [maxval, maxlag, maxsigma])
835
+ elif self.peakfittype == "gausscf":
836
+ X = self.corrtimeaxis[peakstart : peakend + 1] - baseline
837
+ data = corrfunc[peakstart : peakend + 1]
838
+ # do a least squares fit over the top of the peak
839
+ try:
840
+ plsq, pcov = curve_fit(
841
+ tide_fit.gaussfunc,
842
+ X,
843
+ data,
844
+ p0=[maxval_init, maxlag_init, maxsigma_init],
845
+ )
846
+ maxval = plsq[0] + baseline
847
+ maxlag = np.fmod((1.0 * plsq[1]), self.lagmod)
848
+ maxsigma = plsq[2]
849
+ except:
850
+ failreason |= self.FML_FITALGOFAIL
851
+ maxval = np.float64(0.0)
852
+ maxlag = np.float64(0.0)
853
+ maxsigma = np.float64(0.0)
854
+ if self.debug:
855
+ print("fit output array:", [maxval, maxlag, maxsigma])
856
+ elif self.peakfittype == "fastgauss":
857
+ X = self.corrtimeaxis[peakstart : peakend + 1] - baseline
858
+ data = corrfunc[peakstart : peakend + 1]
859
+ # do a non-iterative fit over the top of the peak
860
+ # 6/12/2015 This is just broken. Gives quantized maxima
861
+ maxlag = np.float64(1.0 * np.sum(X * data) / np.sum(data))
862
+ maxsigma = np.float64(
863
+ np.sqrt(np.abs(np.sum((X - maxlag) ** 2 * data) / np.sum(data)))
864
+ )
865
+ maxval = np.float64(data.max()) + baseline
866
+ elif self.peakfittype == "fastquad":
867
+ maxlag, maxval, maxsigma, ismax, badfit = tide_fit.refinepeak_quad(
868
+ self.corrtimeaxis, corrfunc, maxindex
869
+ )
870
+ elif self.peakfittype == "quad":
871
+ X = self.corrtimeaxis[peakstart : peakend + 1]
872
+ data = corrfunc[peakstart : peakend + 1]
873
+ try:
874
+ thecoffs = Polynomial.fit(X, data, 2).convert().coef[::-1]
875
+ a = thecoffs[0]
876
+ b = thecoffs[1]
877
+ c = thecoffs[2]
878
+ maxlag = -b / (2.0 * a)
879
+ maxval = a * maxlag * maxlag + b * maxlag + c
880
+ maxsigma = 1.0 / np.fabs(a)
881
+ if self.debug:
882
+ print("poly coffs:", a, b, c)
883
+ print("maxlag, maxval, maxsigma:", maxlag, maxval, maxsigma)
884
+ except np.lib.polynomial.RankWarning:
885
+ failreason |= self.FML_FITALGOFAIL
886
+ maxlag = 0.0
887
+ maxval = 0.0
888
+ maxsigma = 0.0
889
+ if self.debug:
890
+ print("\n")
891
+ for i in range(len(X)):
892
+ print(X[i], data[i])
893
+ print("\n")
894
+ fig = plt.figure()
895
+ ax = fig.add_subplot(111)
896
+ ax.set_title("Peak and fit")
897
+ plt.plot(X, data, "r")
898
+ plt.plot(X, c + b * X + a * X * X, "b")
899
+ plt.show()
900
+
901
+ else:
902
+ print("illegal peak refinement type")
903
+
904
+ # check for errors in fit
905
+ fitfail = False
906
+ if self.bipolar:
907
+ lowestcorrcoeff = -1.0
908
+ else:
909
+ lowestcorrcoeff = 0.0
910
+ if (self.functype == "correlation") or (self.functype == "hybrid"):
911
+ if maxval < lowestcorrcoeff:
912
+ failreason |= self.FML_FITAMPLOW
913
+ maxval = lowestcorrcoeff
914
+ if self.debug:
915
+ print("bad fit amp: maxval is lower than lower limit")
916
+ fitfail = True
917
+ if np.abs(maxval) > 1.0:
918
+ if not self.allowhighfitamps:
919
+ failreason |= self.FML_FITAMPHIGH
920
+ if self.debug:
921
+ print(
922
+ "bad fit amp: magnitude of",
923
+ maxval,
924
+ "is greater than 1.0",
925
+ )
926
+ fitfail = True
927
+ maxval = 1.0 * np.sign(maxval)
928
+ else:
929
+ # different rules for mutual information peaks
930
+ if ((maxval - baseline) < self.lthreshval * baselinedev) or (maxval < baseline):
931
+ failreason |= self.FML_FITAMPLOW
932
+ maxval_init = 0.0
933
+ if self.debug:
934
+ if (maxval - baseline) < self.lthreshval * baselinedev:
935
+ print(
936
+ "FITAMPLOW: maxval - baseline:",
937
+ maxval - baseline,
938
+ " < lthreshval * baselinedev:",
939
+ self.lthreshval * baselinedev,
940
+ )
941
+ if maxval < baseline:
942
+ print("FITAMPLOW: maxval < baseline:", maxval, baseline)
943
+ if self.debug:
944
+ print("bad fit amp: maxval is lower than lower limit")
945
+ if (self.lagmin > maxlag) or (maxlag > self.lagmax):
946
+ if self.debug:
947
+ print("bad lag after refinement")
948
+ if self.lagmin > maxlag:
949
+ failreason |= self.FML_FITLAGLOW
950
+ maxlag = self.lagmin
951
+ else:
952
+ failreason |= self.FML_FITLAGHIGH
953
+ maxlag = self.lagmax
954
+ fitfail = True
955
+ if maxsigma > self.absmaxsigma:
956
+ failreason |= self.FML_FITWIDTHHIGH
957
+ if self.debug:
958
+ print("bad width after refinement:", maxsigma, ">", self.absmaxsigma)
959
+ maxsigma = self.absmaxsigma
960
+ fitfail = True
961
+ if maxsigma < self.absminsigma:
962
+ failreason |= self.FML_FITWIDTHLOW
963
+ if self.debug:
964
+ print("bad width after refinement:", maxsigma, "<", self.absminsigma)
965
+ maxsigma = self.absminsigma
966
+ fitfail = True
967
+ if fitfail:
968
+ if self.debug:
969
+ print("fit fail")
970
+ if self.zerooutbadfit:
971
+ maxval = np.float64(0.0)
972
+ maxlag = np.float64(0.0)
973
+ maxsigma = np.float64(0.0)
974
+ maskval = np.uint16(0)
975
+ # print(maxlag_init, maxlag, maxval_init, maxval, maxsigma_init, maxsigma, maskval, failreason, fitfail)
976
+ else:
977
+ maxval = np.float64(maxval_init)
978
+ maxlag = np.float64(np.fmod(maxlag_init, self.lagmod))
979
+ maxsigma = np.float64(maxsigma_init)
980
+ if failreason != self.FML_NOERROR:
981
+ maskval = np.uint16(0)
982
+ else:
983
+ maskval = np.uint16(1)
984
+
985
+ if self.debug or self.displayplots:
986
+ print(
987
+ "init to final: maxval",
988
+ maxval_init,
989
+ maxval,
990
+ ", maxlag:",
991
+ maxlag_init,
992
+ maxlag,
993
+ ", width:",
994
+ maxsigma_init,
995
+ maxsigma,
996
+ )
997
+ if self.displayplots and (self.peakfittype != "None") and (maskval != 0.0):
998
+ fig = plt.figure()
999
+ ax = fig.add_subplot(111)
1000
+ ax.set_title("Data and fit")
1001
+ hiresx = np.arange(X[0], X[-1], (X[1] - X[0]) / 10.0)
1002
+ plt.plot(
1003
+ X,
1004
+ data,
1005
+ "ro",
1006
+ hiresx,
1007
+ tide_fit.gauss_eval(hiresx, np.array([maxval, maxlag, maxsigma])),
1008
+ "b-",
1009
+ )
1010
+ plt.show()
1011
+ return (
1012
+ maxindex,
1013
+ maxlag,
1014
+ flipfac * maxval,
1015
+ maxsigma,
1016
+ maskval,
1017
+ failreason,
1018
+ peakstart,
1019
+ peakend,
1020
+ )
1021
+
1022
+
1023
+ class FrequencyTracker:
1024
+ freqs = None
1025
+ times = None
1026
+
1027
+ def __init__(self, lowerlim=0.1, upperlim=0.6, nperseg=32, Q=10.0, debug=False):
1028
+ self.lowerlim = lowerlim
1029
+ self.upperlim = upperlim
1030
+ self.nperseg = nperseg
1031
+ self.Q = Q
1032
+ self.debug = debug
1033
+ self.nfft = self.nperseg
1034
+
1035
+ def track(self, x, fs):
1036
+ self.freqs, self.times, thespectrogram = sp.signal.spectrogram(
1037
+ np.concatenate(
1038
+ [np.zeros(int(self.nperseg // 2)), x, np.zeros(int(self.nperseg // 2))],
1039
+ axis=0,
1040
+ ),
1041
+ fs=fs,
1042
+ detrend="constant",
1043
+ scaling="spectrum",
1044
+ nfft=None,
1045
+ window=np.hamming(self.nfft),
1046
+ noverlap=(self.nperseg - 1),
1047
+ )
1048
+ lowerliminpts = tide_util.valtoindex(self.freqs, self.lowerlim)
1049
+ upperliminpts = tide_util.valtoindex(self.freqs, self.upperlim)
1050
+
1051
+ if self.debug:
1052
+ print(self.times.shape, self.freqs.shape, thespectrogram.shape)
1053
+ print(self.times)
1054
+
1055
+ # initialize the peak fitter
1056
+ thefitter = SimilarityFunctionFitter(
1057
+ corrtimeaxis=self.freqs,
1058
+ lagmin=self.lowerlim,
1059
+ lagmax=self.upperlim,
1060
+ absmaxsigma=10.0,
1061
+ absminsigma=0.1,
1062
+ debug=self.debug,
1063
+ peakfittype="fastquad",
1064
+ zerooutbadfit=False,
1065
+ useguess=False,
1066
+ )
1067
+
1068
+ peakfreqs = np.zeros((thespectrogram.shape[1] - 1), dtype=float)
1069
+ for i in range(0, thespectrogram.shape[1] - 1):
1070
+ (
1071
+ maxindex,
1072
+ peakfreqs[i],
1073
+ maxval,
1074
+ maxsigma,
1075
+ maskval,
1076
+ failreason,
1077
+ peakstart,
1078
+ peakend,
1079
+ ) = thefitter.fit(thespectrogram[:, i])
1080
+ if not (lowerliminpts <= maxindex <= upperliminpts):
1081
+ peakfreqs[i] = -1.0
1082
+
1083
+ return self.times[:-1], peakfreqs
1084
+
1085
+ def clean(self, x, fs, times, peakfreqs, numharmonics=2):
1086
+ nyquistfreq = 0.5 * fs
1087
+ y = x * 0.0
1088
+ halfwidth = int(self.nperseg // 2)
1089
+ padx = np.concatenate([np.zeros(halfwidth), x, np.zeros(halfwidth)], axis=0)
1090
+ pady = np.concatenate([np.zeros(halfwidth), y, np.zeros(halfwidth)], axis=0)
1091
+ padweight = padx * 0.0
1092
+ if self.debug:
1093
+ print(fs, len(times), len(peakfreqs))
1094
+ for i in range(0, len(times)):
1095
+ centerindex = int(times[i] * fs)
1096
+ xstart = centerindex - halfwidth
1097
+ xend = centerindex + halfwidth
1098
+ if peakfreqs[i] > 0.0:
1099
+ filtsignal = padx[xstart:xend]
1100
+ numharmonics = np.min([numharmonics, int((nyquistfreq // peakfreqs[i]) - 1)])
1101
+ if self.debug:
1102
+ print("numharmonics:", numharmonics, nyquistfreq // peakfreqs[i])
1103
+ for j in range(numharmonics + 1):
1104
+ workingfreq = (j + 1) * peakfreqs[i]
1105
+ if self.debug:
1106
+ print("workingfreq:", workingfreq)
1107
+ ws = [workingfreq * 0.95, workingfreq * 1.05]
1108
+ wp = [workingfreq * 0.9, workingfreq * 1.1]
1109
+ gpass = 1.0
1110
+ gstop = 40.0
1111
+ b, a = sp.signal.iirdesign(wp, ws, gpass, gstop, ftype="cheby2", fs=fs)
1112
+ if self.debug:
1113
+ print(
1114
+ i,
1115
+ j,
1116
+ times[i],
1117
+ centerindex,
1118
+ halfwidth,
1119
+ xstart,
1120
+ xend,
1121
+ xend - xstart,
1122
+ wp,
1123
+ ws,
1124
+ len(a),
1125
+ len(b),
1126
+ )
1127
+ filtsignal = sp.signal.filtfilt(b, a, sp.signal.filtfilt(b, a, filtsignal))
1128
+ pady[xstart:xend] += filtsignal
1129
+ else:
1130
+ pady[xstart:xend] += padx[xstart:xend]
1131
+ padweight[xstart:xend] += 1.0
1132
+ return (pady / padweight)[halfwidth:-halfwidth]