rapidtide 3.0.7__py3-none-any.whl → 3.0.8__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 (50) hide show
  1. rapidtide/_version.py +3 -3
  2. rapidtide/calcnullsimfunc.py +1 -3
  3. rapidtide/data/examples/src/test_findmaxlag.py +1 -1
  4. rapidtide/data/examples/src/testfmri +22 -16
  5. rapidtide/data/examples/src/testnewrefine +0 -23
  6. rapidtide/fMRIData_class.py +29 -52
  7. rapidtide/fit.py +4 -4
  8. rapidtide/happy_supportfuncs.py +1 -1
  9. rapidtide/helper_classes.py +0 -1099
  10. rapidtide/linfitfiltpass.py +59 -0
  11. rapidtide/makelaggedtcs.py +10 -0
  12. rapidtide/refinedelay.py +10 -19
  13. rapidtide/simFuncClasses.py +1132 -0
  14. rapidtide/simfuncfit.py +30 -30
  15. rapidtide/stats.py +5 -2
  16. rapidtide/tests/.coveragerc +6 -0
  17. rapidtide/tests/cleanposttest +1 -1
  18. rapidtide/tests/runlocaltest +2 -2
  19. rapidtide/tests/test_cleanregressor.py +3 -3
  20. rapidtide/tests/test_congrid.py +1 -1
  21. rapidtide/tests/test_corrpass.py +3 -3
  22. rapidtide/tests/test_delayestimation.py +8 -7
  23. rapidtide/tests/test_findmaxlag.py +2 -2
  24. rapidtide/tests/test_fullrunrapidtide_v3.py +2 -1
  25. rapidtide/tests/test_getparsers.py +14 -6
  26. rapidtide/tests/test_io.py +2 -6
  27. rapidtide/tests/test_nullcorr.py +3 -3
  28. rapidtide/tests/test_refinedelay.py +20 -5
  29. rapidtide/tidepoolTemplate_alt.py +1 -1
  30. rapidtide/util.py +7 -0
  31. rapidtide/voxelData.py +3 -6
  32. rapidtide/workflows/cleanregressor.py +2 -2
  33. rapidtide/workflows/delayvar.py +44 -58
  34. rapidtide/workflows/{delayestimation.py → estimateDelayMap.py} +84 -31
  35. rapidtide/workflows/rapidtide.py +361 -865
  36. rapidtide/workflows/rapidtide_parser.py +8 -41
  37. rapidtide/workflows/refineDelayMap.py +138 -0
  38. rapidtide/{RegressorRefiner.py → workflows/refineRegressor.py} +200 -28
  39. rapidtide/workflows/regressfrommaps.py +35 -27
  40. rapidtide/workflows/retrolagtcs.py +5 -6
  41. rapidtide/workflows/retroregress.py +93 -193
  42. rapidtide/workflows/showarbcorr.py +2 -2
  43. rapidtide/workflows/showxcorrx.py +5 -5
  44. rapidtide/workflows/tidepool.py +5 -5
  45. {rapidtide-3.0.7.dist-info → rapidtide-3.0.8.dist-info}/METADATA +2 -2
  46. {rapidtide-3.0.7.dist-info → rapidtide-3.0.8.dist-info}/RECORD +50 -48
  47. {rapidtide-3.0.7.dist-info → rapidtide-3.0.8.dist-info}/WHEEL +0 -0
  48. {rapidtide-3.0.7.dist-info → rapidtide-3.0.8.dist-info}/entry_points.txt +0 -0
  49. {rapidtide-3.0.7.dist-info → rapidtide-3.0.8.dist-info}/licenses/LICENSE +0 -0
  50. {rapidtide-3.0.7.dist-info → rapidtide-3.0.8.dist-info}/top_level.txt +0 -0
@@ -140,342 +140,6 @@ class ProbeRegressor:
140
140
  )
141
141
 
142
142
 
143
- class SimilarityFunctionator:
144
- reftc = None
145
- prepreftc = None
146
- testtc = None
147
- preptesttc = None
148
- timeaxis = None
149
- similarityfunclen = 0
150
- datavalid = False
151
- timeaxisvalid = False
152
- similarityfuncorigin = 0
153
-
154
- def __init__(
155
- self,
156
- Fs=0.0,
157
- similarityfuncorigin=0,
158
- lagmininpts=0,
159
- lagmaxinpts=0,
160
- ncprefilter=None,
161
- negativegradient=False,
162
- reftc=None,
163
- reftcstart=0.0,
164
- detrendorder=1,
165
- filterinputdata=True,
166
- debug=False,
167
- ):
168
- self.setFs(Fs)
169
- self.similarityfuncorigin = similarityfuncorigin
170
- self.lagmininpts = lagmininpts
171
- self.lagmaxinpts = lagmaxinpts
172
- self.ncprefilter = ncprefilter
173
- self.negativegradient = negativegradient
174
- self.reftc = reftc
175
- self.detrendorder = detrendorder
176
- self.filterinputdata = filterinputdata
177
- self.debug = debug
178
- if self.reftc is not None:
179
- self.setreftc(self.reftc)
180
- self.reftcstart = reftcstart
181
-
182
- def setFs(self, Fs):
183
- self.Fs = Fs
184
-
185
- def preptc(self, thetc, isreftc=False):
186
- # prepare timecourse by filtering, normalizing, detrending, and applying a window function
187
- if isreftc:
188
- thenormtc = tide_math.corrnormalize(
189
- self.ncprefilter.apply(self.Fs, thetc),
190
- detrendorder=self.detrendorder,
191
- windowfunc=self.windowfunc,
192
- )
193
- else:
194
- if self.negativegradient:
195
- thenormtc = tide_math.corrnormalize(
196
- -np.gradient(self.ncprefilter.apply(self.Fs, thetc)),
197
- detrendorder=self.detrendorder,
198
- windowfunc=self.windowfunc,
199
- )
200
- else:
201
- if self.filterinputdata:
202
- thenormtc = tide_math.corrnormalize(
203
- self.ncprefilter.apply(self.Fs, thetc),
204
- detrendorder=self.detrendorder,
205
- windowfunc=self.windowfunc,
206
- )
207
- else:
208
- thenormtc = tide_math.corrnormalize(
209
- thetc,
210
- detrendorder=self.detrendorder,
211
- windowfunc=self.windowfunc,
212
- )
213
-
214
- return thenormtc
215
-
216
- def trim(self, vector):
217
- return vector[
218
- self.similarityfuncorigin
219
- - self.lagmininpts : self.similarityfuncorigin
220
- + self.lagmaxinpts
221
- ]
222
-
223
- def getfunction(self, trim=True):
224
- if self.datavalid:
225
- if trim:
226
- return (
227
- self.trim(self.thesimfunc),
228
- self.trim(self.timeaxis),
229
- self.theglobalmax,
230
- )
231
- else:
232
- return self.thesimfunc, self.timeaxis, self.theglobalmax
233
- else:
234
- if self.timeaxisvalid:
235
- if trim:
236
- return None, self.trim(self.timeaxis), None
237
- else:
238
- return None, self.timeaxis, None
239
- else:
240
- print("must calculate similarity function before fetching data")
241
- return None, None, None
242
-
243
-
244
- class MutualInformationator(SimilarityFunctionator):
245
- def __init__(
246
- self,
247
- windowfunc="hamming",
248
- norm=True,
249
- madnorm=False,
250
- smoothingtime=-1.0,
251
- bins=20,
252
- sigma=0.25,
253
- *args,
254
- **kwargs,
255
- ):
256
- self.windowfunc = windowfunc
257
- self.norm = norm
258
- self.madnorm = madnorm
259
- self.bins = bins
260
- self.sigma = sigma
261
- self.smoothingtime = smoothingtime
262
- self.smoothingfilter = tide_filt.NoncausalFilter(filtertype="arb")
263
- self.mi_norm = 1.0
264
- if self.smoothingtime > 0.0:
265
- self.smoothingfilter.setfreqs(
266
- 0.0, 0.0, 1.0 / self.smoothingtime, 1.0 / self.smoothingtime
267
- )
268
- super(MutualInformationator, self).__init__(*args, **kwargs)
269
-
270
- def setlimits(self, lagmininpts, lagmaxinpts):
271
- self.lagmininpts = lagmininpts
272
- self.lagmaxinpts = lagmaxinpts
273
- origpadtime = self.smoothingfilter.getpadtime()
274
- timespan = self.timeaxis[-1] - self.timeaxis[0]
275
- newpadtime = np.min([origpadtime, timespan])
276
- if newpadtime < origpadtime:
277
- print("lowering smoothing filter pad time to", newpadtime)
278
- self.smoothingfilter.setpadtime(newpadtime)
279
-
280
- def setbins(self, bins):
281
- self.bins = bins
282
-
283
- def setreftc(self, reftc, offset=0.0):
284
- self.reftc = reftc + 0.0
285
- self.prepreftc = self.preptc(self.reftc, isreftc=True)
286
-
287
- self.timeaxis, self.automi, self.similarityfuncorigin = tide_corr.cross_mutual_info(
288
- self.prepreftc,
289
- self.prepreftc,
290
- Fs=self.Fs,
291
- fast=True,
292
- negsteps=self.lagmininpts,
293
- possteps=self.lagmaxinpts,
294
- returnaxis=True,
295
- )
296
-
297
- self.timeaxis -= offset
298
- self.similarityfunclen = len(self.timeaxis)
299
- self.timeaxisvalid = True
300
- self.datavalid = False
301
- self.mi_norm = np.nan_to_num(1.0 / np.max(self.automi))
302
- if self.debug:
303
- print(f"MutualInformationator setreftc: {len(self.timeaxis)=}")
304
- print(f"MutualInformationator setreftc: {self.timeaxis}")
305
- print(f"MutualInformationator setreftc: {self.mi_norm=}")
306
-
307
- def getnormfac(self):
308
- return self.mi_norm
309
-
310
- def run(self, thetc, locs=None, trim=True, gettimeaxis=True):
311
- if len(thetc) != len(self.reftc):
312
- print(
313
- "MutualInformationator: timecourses are of different sizes:",
314
- len(thetc),
315
- "!=",
316
- len(self.reftc),
317
- "- exiting",
318
- )
319
- sys.exit()
320
-
321
- self.testtc = thetc
322
- self.preptesttc = self.preptc(self.testtc)
323
-
324
- if locs is not None:
325
- gettimeaxis = True
326
-
327
- # now calculate the similarity function
328
- if trim:
329
- retvals = tide_corr.cross_mutual_info(
330
- self.preptesttc,
331
- self.prepreftc,
332
- norm=self.norm,
333
- negsteps=self.lagmininpts,
334
- possteps=self.lagmaxinpts,
335
- locs=locs,
336
- madnorm=self.madnorm,
337
- returnaxis=gettimeaxis,
338
- fast=True,
339
- Fs=self.Fs,
340
- sigma=self.sigma,
341
- bins=self.bins,
342
- )
343
- else:
344
- retvals = tide_corr.cross_mutual_info(
345
- self.preptesttc,
346
- self.prepreftc,
347
- norm=self.norm,
348
- negsteps=-1,
349
- possteps=-1,
350
- locs=locs,
351
- madnorm=self.madnorm,
352
- returnaxis=gettimeaxis,
353
- fast=True,
354
- Fs=self.Fs,
355
- sigma=self.sigma,
356
- bins=self.bins,
357
- )
358
- if gettimeaxis:
359
- self.timeaxis, self.thesimfunc, self.similarityfuncorigin = (
360
- retvals[0],
361
- retvals[1],
362
- retvals[2],
363
- )
364
- self.timeaxisvalid = True
365
- else:
366
- self.thesimfunc = retvals[0]
367
-
368
- # normalize
369
- self.thesimfunc *= self.mi_norm
370
-
371
- if locs is not None:
372
- return self.thesimfunc
373
-
374
- if self.smoothingtime > 0.0:
375
- self.thesimfunc = self.smoothingfilter.apply(self.Fs, self.thesimfunc)
376
-
377
- self.similarityfunclen = len(self.thesimfunc)
378
- if trim:
379
- self.similarityfuncorigin = self.lagmininpts + 1
380
- else:
381
- self.similarityfuncorigin = self.similarityfunclen // 2 + 1
382
-
383
- # find the global maximum value
384
- self.theglobalmax = np.argmax(self.thesimfunc)
385
- self.datavalid = True
386
-
387
- # make a dummy filtered baseline
388
- self.filteredbaseline = self.thesimfunc * 0.0
389
-
390
- if trim:
391
- return (
392
- self.trim(self.thesimfunc),
393
- self.trim(self.timeaxis),
394
- self.theglobalmax,
395
- )
396
- else:
397
- return self.thesimfunc, self.timeaxis, self.theglobalmax
398
-
399
-
400
- class Correlator(SimilarityFunctionator):
401
- def __init__(
402
- self,
403
- windowfunc="hamming",
404
- corrweighting="None",
405
- corrpadding=0,
406
- baselinefilter=None,
407
- *args,
408
- **kwargs,
409
- ):
410
- self.windowfunc = windowfunc
411
- self.corrweighting = corrweighting
412
- self.corrpadding = corrpadding
413
- self.baselinefilter = baselinefilter
414
- super(Correlator, self).__init__(*args, **kwargs)
415
-
416
- def setlimits(self, lagmininpts, lagmaxinpts):
417
- self.lagmininpts = lagmininpts
418
- self.lagmaxinpts = lagmaxinpts
419
-
420
- def setreftc(self, reftc, offset=0.0):
421
- self.reftc = reftc + 0.0
422
- self.prepreftc = self.preptc(self.reftc, isreftc=True)
423
- self.similarityfunclen = len(self.reftc) * 2 - 1
424
- self.similarityfuncorigin = self.similarityfunclen // 2 + 1
425
-
426
- # make the reference time axis
427
- self.timeaxis = (
428
- np.arange(0.0, self.similarityfunclen) * (1.0 / self.Fs)
429
- - ((self.similarityfunclen - 1) * (1.0 / self.Fs)) / 2.0
430
- ) - offset
431
- self.timeaxisvalid = True
432
- self.datavalid = False
433
-
434
- def run(self, thetc, trim=True):
435
- if len(thetc) != len(self.reftc):
436
- print(
437
- "Correlator: timecourses are of different sizes:",
438
- len(thetc),
439
- "!=",
440
- len(self.reftc),
441
- "- exiting",
442
- )
443
- sys.exit()
444
-
445
- self.testtc = thetc
446
- self.preptesttc = self.preptc(self.testtc)
447
-
448
- # now actually do the correlation
449
- self.thesimfunc = tide_corr.fastcorrelate(
450
- self.preptesttc,
451
- self.prepreftc,
452
- usefft=True,
453
- weighting=self.corrweighting,
454
- zeropadding=self.corrpadding,
455
- debug=self.debug,
456
- )
457
- self.similarityfunclen = len(self.thesimfunc)
458
- self.similarityfuncorigin = self.similarityfunclen // 2 + 1
459
-
460
- if self.baselinefilter is not None:
461
- self.filteredbaseline = self.baselinefilter.apply(self.Fs, self.thesimfunc)
462
- else:
463
- self.filteredbaseline = self.thesimfunc * 0.0
464
-
465
- # find the global maximum value
466
- self.theglobalmax = np.argmax(self.thesimfunc)
467
- self.datavalid = True
468
-
469
- if trim:
470
- return (
471
- self.trim(self.thesimfunc),
472
- self.trim(self.timeaxis),
473
- self.theglobalmax,
474
- )
475
- else:
476
- return self.thesimfunc, self.timeaxis, self.theglobalmax
477
-
478
-
479
143
  class Coherer:
480
144
  reftc = None
481
145
  prepreftc = None
@@ -664,766 +328,3 @@ class Coherer:
664
328
  else:
665
329
  self.themax = np.argmax(self.thecoherence)
666
330
  return self.thecoherence, self.freqaxis, self.themax
667
-
668
-
669
- class SimilarityFunctionFitter:
670
- corrtimeaxis = None
671
- FML_NOERROR = np.uint32(0x0000)
672
-
673
- FML_INITAMPLOW = np.uint32(0x0001)
674
- FML_INITAMPHIGH = np.uint32(0x0002)
675
- FML_INITWIDTHLOW = np.uint32(0x0004)
676
- FML_INITWIDTHHIGH = np.uint32(0x0008)
677
- FML_INITLAGLOW = np.uint32(0x0010)
678
- FML_INITLAGHIGH = np.uint32(0x0020)
679
- FML_INITFAIL = (
680
- FML_INITAMPLOW
681
- | FML_INITAMPHIGH
682
- | FML_INITWIDTHLOW
683
- | FML_INITWIDTHHIGH
684
- | FML_INITLAGLOW
685
- | FML_INITLAGHIGH
686
- )
687
-
688
- FML_FITAMPLOW = np.uint32(0x0100)
689
- FML_FITAMPHIGH = np.uint32(0x0200)
690
- FML_FITWIDTHLOW = np.uint32(0x0400)
691
- FML_FITWIDTHHIGH = np.uint32(0x0800)
692
- FML_FITLAGLOW = np.uint32(0x1000)
693
- FML_FITLAGHIGH = np.uint32(0x2000)
694
- FML_FITALGOFAIL = np.uint32(0x0400)
695
- FML_FITFAIL = (
696
- FML_FITAMPLOW
697
- | FML_FITAMPHIGH
698
- | FML_FITWIDTHLOW
699
- | FML_FITWIDTHHIGH
700
- | FML_FITLAGLOW
701
- | FML_FITLAGHIGH
702
- | FML_FITALGOFAIL
703
- )
704
-
705
- def __init__(
706
- self,
707
- corrtimeaxis=None,
708
- lagmin=-30.0,
709
- lagmax=30.0,
710
- absmaxsigma=1000.0,
711
- absminsigma=0.25,
712
- hardlimit=True,
713
- bipolar=False,
714
- lthreshval=0.0,
715
- uthreshval=1.0,
716
- debug=False,
717
- zerooutbadfit=True,
718
- maxguess=0.0,
719
- useguess=False,
720
- searchfrac=0.5,
721
- lagmod=1000.0,
722
- enforcethresh=True,
723
- allowhighfitamps=False,
724
- displayplots=False,
725
- functype="correlation",
726
- peakfittype="gauss",
727
- ):
728
- r"""
729
-
730
- Parameters
731
- ----------
732
- corrtimeaxis: 1D float array
733
- The time axis of the correlation function
734
- lagmin: float
735
- The minimum allowed lag time in seconds
736
- lagmax: float
737
- The maximum allowed lag time in seconds
738
- absmaxsigma: float
739
- The maximum allowed peak halfwidth in seconds
740
- hardlimit
741
- bipolar: boolean
742
- If true find the correlation peak with the maximum absolute value, regardless of sign
743
- threshval
744
- uthreshval
745
- debug
746
- zerooutbadfit
747
- maxguess
748
- useguess
749
- searchfrac
750
- lagmod
751
- enforcethresh
752
- displayplots
753
-
754
- Returns
755
- -------
756
-
757
-
758
- Methods
759
- -------
760
- fit(corrfunc):
761
- Fit the correlation function given in corrfunc and return the location of the peak in seconds, the maximum
762
- correlation value, the peak width
763
- setrange(lagmin, lagmax):
764
- Specify the search range for lag peaks, in seconds
765
- """
766
- self.setcorrtimeaxis(corrtimeaxis)
767
- self.lagmin = lagmin
768
- self.lagmax = lagmax
769
- self.absmaxsigma = absmaxsigma
770
- self.absminsigma = absminsigma
771
- self.hardlimit = hardlimit
772
- self.bipolar = bipolar
773
- self.lthreshval = lthreshval
774
- self.uthreshval = uthreshval
775
- self.debug = debug
776
- if functype == "correlation" or functype == "mutualinfo":
777
- self.functype = functype
778
- else:
779
- print("illegal functype")
780
- sys.exit()
781
- self.peakfittype = peakfittype
782
- self.zerooutbadfit = zerooutbadfit
783
- self.maxguess = maxguess
784
- self.useguess = useguess
785
- self.searchfrac = searchfrac
786
- self.lagmod = lagmod
787
- self.enforcethresh = enforcethresh
788
- self.allowhighfitamps = allowhighfitamps
789
- self.displayplots = displayplots
790
-
791
- def _maxindex_noedge(self, corrfunc):
792
- """
793
-
794
- Parameters
795
- ----------
796
- corrfunc
797
-
798
- Returns
799
- -------
800
-
801
- """
802
- lowerlim = 0
803
- upperlim = len(self.corrtimeaxis) - 1
804
- done = False
805
- while not done:
806
- flipfac = 1.0
807
- done = True
808
- maxindex = (np.argmax(corrfunc[lowerlim:upperlim]) + lowerlim).astype("int32")
809
- if self.bipolar:
810
- minindex = (np.argmax(-corrfunc[lowerlim:upperlim]) + lowerlim).astype("int32")
811
- if np.fabs(corrfunc[minindex]) > np.fabs(corrfunc[maxindex]):
812
- maxindex = minindex
813
- flipfac = -1.0
814
- if upperlim == lowerlim:
815
- done = True
816
- if maxindex == 0:
817
- lowerlim += 1
818
- done = False
819
- if maxindex == upperlim:
820
- upperlim -= 1
821
- done = False
822
- return maxindex, flipfac
823
-
824
- def setfunctype(self, functype):
825
- self.functype = functype
826
-
827
- def setpeakfittype(self, peakfittype):
828
- self.peakfittype = peakfittype
829
-
830
- def setrange(self, lagmin, lagmax):
831
- self.lagmin = lagmin
832
- self.lagmax = lagmax
833
-
834
- def setcorrtimeaxis(self, corrtimeaxis):
835
- if corrtimeaxis is not None:
836
- self.corrtimeaxis = corrtimeaxis + 0.0
837
- else:
838
- self.corrtimeaxis = corrtimeaxis
839
-
840
- def setguess(self, useguess, maxguess=0.0):
841
- self.useguess = useguess
842
- self.maxguess = maxguess
843
-
844
- def setlthresh(self, lthreshval):
845
- self.lthreshval = lthreshval
846
-
847
- def setuthresh(self, uthreshval):
848
- self.uthreshval = uthreshval
849
-
850
- def diagnosefail(self, failreason):
851
- # define error values
852
- reasons = []
853
- if failreason.astype(np.uint32) & self.FML_INITAMPLOW:
854
- reasons.append("Initial amplitude too low")
855
- if failreason.astype(np.uint32) & self.FML_INITAMPHIGH:
856
- reasons.append("Initial amplitude too high")
857
- if failreason.astype(np.uint32) & self.FML_INITWIDTHLOW:
858
- reasons.append("Initial width too low")
859
- if failreason.astype(np.uint32) & self.FML_INITWIDTHHIGH:
860
- reasons.append("Initial width too high")
861
- if failreason.astype(np.uint32) & self.FML_INITLAGLOW:
862
- reasons.append("Initial Lag too low")
863
- if failreason.astype(np.uint32) & self.FML_INITLAGHIGH:
864
- reasons.append("Initial Lag too high")
865
-
866
- if failreason.astype(np.uint32) & self.FML_FITAMPLOW:
867
- reasons.append("Fit amplitude too low")
868
- if failreason.astype(np.uint32) & self.FML_FITAMPHIGH:
869
- reasons.append("Fit amplitude too high")
870
- if failreason.astype(np.uint32) & self.FML_FITWIDTHLOW:
871
- reasons.append("Fit width too low")
872
- if failreason.astype(np.uint32) & self.FML_FITWIDTHHIGH:
873
- reasons.append("Fit width too high")
874
- if failreason.astype(np.uint32) & self.FML_FITLAGLOW:
875
- reasons.append("Fit Lag too low")
876
- if failreason.astype(np.uint32) & self.FML_FITLAGHIGH:
877
- reasons.append("Fit Lag too high")
878
- if failreason.astype(np.uint32) & self.FML_FITALGOFAIL:
879
- reasons.append("Nonlinear fit failed")
880
-
881
- if len(reasons) > 0:
882
- return ", ".join(reasons)
883
- else:
884
- return "No error"
885
-
886
- def fit(self, incorrfunc):
887
- # check to make sure xcorr_x and xcorr_y match
888
- if self.corrtimeaxis is None:
889
- print("Correlation time axis is not defined - exiting")
890
- sys.exit()
891
- if len(self.corrtimeaxis) != len(incorrfunc):
892
- print(
893
- "Correlation time axis and values do not match in length (",
894
- len(self.corrtimeaxis),
895
- "!=",
896
- len(incorrfunc),
897
- "- exiting",
898
- )
899
- sys.exit()
900
- # set initial parameters
901
- # absmaxsigma is in seconds
902
- # maxsigma is in Hz
903
- # maxlag is in seconds
904
- warnings.filterwarnings("ignore", "Number*")
905
- failreason = self.FML_NOERROR
906
- maskval = np.uint16(1) # start out assuming the fit will succeed
907
- binwidth = self.corrtimeaxis[1] - self.corrtimeaxis[0]
908
-
909
- # set the search range
910
- lowerlim = 0
911
- upperlim = len(self.corrtimeaxis) - 1
912
- if self.debug:
913
- print(
914
- "initial search indices are",
915
- lowerlim,
916
- "to",
917
- upperlim,
918
- "(",
919
- self.corrtimeaxis[lowerlim],
920
- self.corrtimeaxis[upperlim],
921
- ")",
922
- )
923
-
924
- # make an initial guess at the fit parameters for the gaussian
925
- # start with finding the maximum value and its location
926
- flipfac = 1.0
927
- corrfunc = incorrfunc + 0.0
928
- if self.useguess:
929
- maxindex = tide_util.valtoindex(self.corrtimeaxis, self.maxguess)
930
- if (corrfunc[maxindex] < 0.0) and self.bipolar:
931
- flipfac = -1.0
932
- else:
933
- maxindex, flipfac = self._maxindex_noedge(corrfunc)
934
- corrfunc *= flipfac
935
- maxlag_init = (1.0 * self.corrtimeaxis[maxindex]).astype("float64")
936
- maxval_init = corrfunc[maxindex].astype("float64")
937
- if self.debug:
938
- print(
939
- "maxindex, maxlag_init, maxval_init:",
940
- maxindex,
941
- maxlag_init,
942
- maxval_init,
943
- )
944
-
945
- # set the baseline and baselinedev levels
946
- if (self.functype == "correlation") or (self.functype == "hybrid"):
947
- baseline = 0.0
948
- baselinedev = 0.0
949
- else:
950
- # for mutual information, there is a nonzero baseline, so we want the difference from that.
951
- baseline = np.median(corrfunc)
952
- baselinedev = mad(corrfunc)
953
- if self.debug:
954
- print("baseline, baselinedev:", baseline, baselinedev)
955
-
956
- # then calculate the width of the peak
957
- if self.peakfittype == "fastquad" or self.peakfittype == "COM":
958
- peakstart = np.max([1, maxindex - 2])
959
- peakend = np.min([len(self.corrtimeaxis) - 2, maxindex + 2])
960
- else:
961
- # come here for peakfittype of None, quad, gauss, fastgauss
962
- thegrad = np.gradient(corrfunc).astype(
963
- "float64"
964
- ) # the gradient of the correlation function
965
- if (self.functype == "correlation") or (self.functype == "hybrid"):
966
- if self.peakfittype == "quad":
967
- peakpoints = np.where(
968
- corrfunc > maxval_init - 0.05, 1, 0
969
- ) # mask for places where correlation exceeds searchfrac*maxval_init
970
- else:
971
- peakpoints = np.where(
972
- corrfunc > (baseline + self.searchfrac * (maxval_init - baseline)), 1, 0
973
- ) # mask for places where correlation exceeds searchfrac*maxval_init
974
- else:
975
- # for mutual information, there is a flattish, nonzero baseline, so we want the difference from that.
976
- peakpoints = np.where(
977
- corrfunc > (baseline + self.searchfrac * (maxval_init - baseline)),
978
- 1,
979
- 0,
980
- )
981
-
982
- peakpoints[0] = 0
983
- peakpoints[-1] = 0
984
- peakstart = np.max([1, maxindex - 1])
985
- peakend = np.min([len(self.corrtimeaxis) - 2, maxindex + 1])
986
- if self.debug:
987
- print("initial peakstart, peakend:", peakstart, peakend)
988
- if self.functype == "mutualinfo":
989
- while peakpoints[peakend + 1] == 1:
990
- peakend += 1
991
- while peakpoints[peakstart - 1] == 1:
992
- peakstart -= 1
993
- else:
994
- while thegrad[peakend + 1] <= 0.0 and peakpoints[peakend + 1] == 1:
995
- peakend += 1
996
- while thegrad[peakstart - 1] >= 0.0 and peakpoints[peakstart - 1] == 1:
997
- peakstart -= 1
998
- if self.debug:
999
- print("final peakstart, peakend:", peakstart, peakend)
1000
-
1001
- # deal with flat peak top
1002
- while (
1003
- peakend < (len(self.corrtimeaxis) - 3)
1004
- and corrfunc[peakend] == corrfunc[peakend - 1]
1005
- ):
1006
- peakend += 1
1007
- while peakstart > 2 and corrfunc[peakstart] == corrfunc[peakstart + 1]:
1008
- peakstart -= 1
1009
- if self.debug:
1010
- print("peakstart, peakend after flattop correction:", peakstart, peakend)
1011
- print("\n")
1012
- for i in range(peakstart, peakend + 1):
1013
- print(self.corrtimeaxis[i], corrfunc[i])
1014
- print("\n")
1015
- fig = plt.figure()
1016
- ax = fig.add_subplot(111)
1017
- ax.set_title("Peak sent to fitting routine")
1018
- plt.plot(
1019
- self.corrtimeaxis[peakstart : peakend + 1],
1020
- corrfunc[peakstart : peakend + 1],
1021
- "r",
1022
- )
1023
- plt.show()
1024
-
1025
- # This is calculated from first principles, but it's always big by a factor or ~1.4.
1026
- # Which makes me think I dropped a factor if sqrt(2). So fix that with a final division
1027
- maxsigma_init = np.float64(
1028
- ((peakend - peakstart + 1) * binwidth / (2.0 * np.sqrt(-np.log(self.searchfrac))))
1029
- / np.sqrt(2.0)
1030
- )
1031
- if self.debug:
1032
- print("maxsigma_init:", maxsigma_init)
1033
-
1034
- # now check the values for errors
1035
- if self.hardlimit:
1036
- rangeextension = 0.0
1037
- else:
1038
- rangeextension = (self.lagmax - self.lagmin) * 0.75
1039
- if not (
1040
- (self.lagmin - rangeextension - binwidth)
1041
- <= maxlag_init
1042
- <= (self.lagmax + rangeextension + binwidth)
1043
- ):
1044
- if maxlag_init <= (self.lagmin - rangeextension - binwidth):
1045
- failreason |= self.FML_INITLAGLOW
1046
- maxlag_init = self.lagmin - rangeextension - binwidth
1047
- else:
1048
- failreason |= self.FML_INITLAGHIGH
1049
- maxlag_init = self.lagmax + rangeextension + binwidth
1050
- if self.debug:
1051
- print("bad initial")
1052
- if maxsigma_init > self.absmaxsigma:
1053
- failreason |= self.FML_INITWIDTHHIGH
1054
- maxsigma_init = self.absmaxsigma
1055
- if self.debug:
1056
- print("bad initial width - too high")
1057
- if peakend - peakstart < 2:
1058
- failreason |= self.FML_INITWIDTHLOW
1059
- maxsigma_init = np.float64(
1060
- ((2 + 1) * binwidth / (2.0 * np.sqrt(-np.log(self.searchfrac)))) / np.sqrt(2.0)
1061
- )
1062
- if self.debug:
1063
- print("bad initial width - too low")
1064
- if (self.functype == "correlation") or (self.functype == "hybrid"):
1065
- if not (self.lthreshval <= maxval_init <= self.uthreshval) and self.enforcethresh:
1066
- failreason |= self.FML_INITAMPLOW
1067
- if self.debug:
1068
- print(
1069
- "bad initial amp:",
1070
- maxval_init,
1071
- "is less than",
1072
- self.lthreshval,
1073
- )
1074
- if maxval_init < 0.0:
1075
- failreason |= self.FML_INITAMPLOW
1076
- maxval_init = 0.0
1077
- if self.debug:
1078
- print("bad initial amp:", maxval_init, "is less than 0.0")
1079
- if (maxval_init > 1.0) and self.enforcethresh:
1080
- failreason |= self.FML_INITAMPHIGH
1081
- maxval_init = 1.0
1082
- if self.debug:
1083
- print("bad initial amp:", maxval_init, "is greater than 1.0")
1084
- else:
1085
- # somewhat different rules for mutual information peaks
1086
- if ((maxval_init - baseline) < self.lthreshval * baselinedev) or (
1087
- maxval_init < baseline
1088
- ):
1089
- failreason |= self.FML_INITAMPLOW
1090
- maxval_init = 0.0
1091
- if self.debug:
1092
- print("bad initial amp:", maxval_init, "is less than 0.0")
1093
- if (failreason != self.FML_NOERROR) and self.zerooutbadfit:
1094
- maxval = np.float64(0.0)
1095
- maxlag = np.float64(0.0)
1096
- maxsigma = np.float64(0.0)
1097
- else:
1098
- maxval = np.float64(maxval_init)
1099
- maxlag = np.float64(maxlag_init)
1100
- maxsigma = np.float64(maxsigma_init)
1101
-
1102
- # refine if necessary
1103
- if self.peakfittype != "None":
1104
- if self.peakfittype == "COM":
1105
- X = self.corrtimeaxis[peakstart : peakend + 1] - baseline
1106
- data = corrfunc[peakstart : peakend + 1]
1107
- maxval = maxval_init
1108
- maxlag = np.sum(X * data) / np.sum(data)
1109
- maxsigma = 10.0
1110
- elif self.peakfittype == "gauss":
1111
- X = self.corrtimeaxis[peakstart : peakend + 1] - baseline
1112
- data = corrfunc[peakstart : peakend + 1]
1113
- # do a least squares fit over the top of the peak
1114
- # p0 = np.array([maxval_init, np.fmod(maxlag_init, lagmod), maxsigma_init], dtype='float64')
1115
- p0 = np.array([maxval_init, maxlag_init, maxsigma_init], dtype="float64")
1116
- if self.debug:
1117
- print("fit input array:", p0)
1118
- try:
1119
- plsq, dummy = sp.optimize.leastsq(
1120
- tide_fit.gaussresiduals, p0, args=(data, X), maxfev=5000
1121
- )
1122
- maxval = plsq[0] + baseline
1123
- maxlag = np.fmod((1.0 * plsq[1]), self.lagmod)
1124
- maxsigma = plsq[2]
1125
- except:
1126
- failreason |= self.FML_FITALGOFAIL
1127
- maxval = np.float64(0.0)
1128
- maxlag = np.float64(0.0)
1129
- maxsigma = np.float64(0.0)
1130
- if self.debug:
1131
- print("fit output array:", [maxval, maxlag, maxsigma])
1132
- elif self.peakfittype == "gausscf":
1133
- X = self.corrtimeaxis[peakstart : peakend + 1] - baseline
1134
- data = corrfunc[peakstart : peakend + 1]
1135
- # do a least squares fit over the top of the peak
1136
- try:
1137
- plsq, pcov = curve_fit(
1138
- tide_fit.gaussfunc,
1139
- X,
1140
- data,
1141
- p0=[maxval_init, maxlag_init, maxsigma_init],
1142
- )
1143
- maxval = plsq[0] + baseline
1144
- maxlag = np.fmod((1.0 * plsq[1]), self.lagmod)
1145
- maxsigma = plsq[2]
1146
- except:
1147
- failreason |= self.FML_FITALGOFAIL
1148
- maxval = np.float64(0.0)
1149
- maxlag = np.float64(0.0)
1150
- maxsigma = np.float64(0.0)
1151
- if self.debug:
1152
- print("fit output array:", [maxval, maxlag, maxsigma])
1153
- elif self.peakfittype == "fastgauss":
1154
- X = self.corrtimeaxis[peakstart : peakend + 1] - baseline
1155
- data = corrfunc[peakstart : peakend + 1]
1156
- # do a non-iterative fit over the top of the peak
1157
- # 6/12/2015 This is just broken. Gives quantized maxima
1158
- maxlag = np.float64(1.0 * np.sum(X * data) / np.sum(data))
1159
- maxsigma = np.float64(
1160
- np.sqrt(np.abs(np.sum((X - maxlag) ** 2 * data) / np.sum(data)))
1161
- )
1162
- maxval = np.float64(data.max()) + baseline
1163
- elif self.peakfittype == "fastquad":
1164
- maxlag, maxval, maxsigma, ismax, badfit = tide_fit.refinepeak_quad(
1165
- self.corrtimeaxis, corrfunc, maxindex
1166
- )
1167
- elif self.peakfittype == "quad":
1168
- X = self.corrtimeaxis[peakstart : peakend + 1]
1169
- data = corrfunc[peakstart : peakend + 1]
1170
- try:
1171
- thecoffs = Polynomial.fit(X, data, 2).convert().coef[::-1]
1172
- a = thecoffs[0]
1173
- b = thecoffs[1]
1174
- c = thecoffs[2]
1175
- maxlag = -b / (2.0 * a)
1176
- maxval = a * maxlag * maxlag + b * maxlag + c
1177
- maxsigma = 1.0 / np.fabs(a)
1178
- if self.debug:
1179
- print("poly coffs:", a, b, c)
1180
- print("maxlag, maxval, maxsigma:", maxlag, maxval, maxsigma)
1181
- except np.lib.polynomial.RankWarning:
1182
- failreason |= self.FML_FITALGOFAIL
1183
- maxlag = 0.0
1184
- maxval = 0.0
1185
- maxsigma = 0.0
1186
- if self.debug:
1187
- print("\n")
1188
- for i in range(len(X)):
1189
- print(X[i], data[i])
1190
- print("\n")
1191
- fig = plt.figure()
1192
- ax = fig.add_subplot(111)
1193
- ax.set_title("Peak and fit")
1194
- plt.plot(X, data, "r")
1195
- plt.plot(X, c + b * X + a * X * X, "b")
1196
- plt.show()
1197
-
1198
- else:
1199
- print("illegal peak refinement type")
1200
-
1201
- # check for errors in fit
1202
- fitfail = False
1203
- if self.bipolar:
1204
- lowestcorrcoeff = -1.0
1205
- else:
1206
- lowestcorrcoeff = 0.0
1207
- if (self.functype == "correlation") or (self.functype == "hybrid"):
1208
- if maxval < lowestcorrcoeff:
1209
- failreason |= self.FML_FITAMPLOW
1210
- maxval = lowestcorrcoeff
1211
- if self.debug:
1212
- print("bad fit amp: maxval is lower than lower limit")
1213
- fitfail = True
1214
- if np.abs(maxval) > 1.0:
1215
- if not self.allowhighfitamps:
1216
- failreason |= self.FML_FITAMPHIGH
1217
- if self.debug:
1218
- print(
1219
- "bad fit amp: magnitude of",
1220
- maxval,
1221
- "is greater than 1.0",
1222
- )
1223
- fitfail = True
1224
- maxval = 1.0 * np.sign(maxval)
1225
- else:
1226
- # different rules for mutual information peaks
1227
- if ((maxval - baseline) < self.lthreshval * baselinedev) or (maxval < baseline):
1228
- failreason |= self.FML_FITAMPLOW
1229
- maxval_init = 0.0
1230
- if self.debug:
1231
- if (maxval - baseline) < self.lthreshval * baselinedev:
1232
- print(
1233
- "FITAMPLOW: maxval - baseline:",
1234
- maxval - baseline,
1235
- " < lthreshval * baselinedev:",
1236
- self.lthreshval * baselinedev,
1237
- )
1238
- if maxval < baseline:
1239
- print("FITAMPLOW: maxval < baseline:", maxval, baseline)
1240
- if self.debug:
1241
- print("bad fit amp: maxval is lower than lower limit")
1242
- if (self.lagmin > maxlag) or (maxlag > self.lagmax):
1243
- if self.debug:
1244
- print("bad lag after refinement")
1245
- if self.lagmin > maxlag:
1246
- failreason |= self.FML_FITLAGLOW
1247
- maxlag = self.lagmin
1248
- else:
1249
- failreason |= self.FML_FITLAGHIGH
1250
- maxlag = self.lagmax
1251
- fitfail = True
1252
- if maxsigma > self.absmaxsigma:
1253
- failreason |= self.FML_FITWIDTHHIGH
1254
- if self.debug:
1255
- print("bad width after refinement:", maxsigma, ">", self.absmaxsigma)
1256
- maxsigma = self.absmaxsigma
1257
- fitfail = True
1258
- if maxsigma < self.absminsigma:
1259
- failreason |= self.FML_FITWIDTHLOW
1260
- if self.debug:
1261
- print("bad width after refinement:", maxsigma, "<", self.absminsigma)
1262
- maxsigma = self.absminsigma
1263
- fitfail = True
1264
- if fitfail:
1265
- if self.debug:
1266
- print("fit fail")
1267
- if self.zerooutbadfit:
1268
- maxval = np.float64(0.0)
1269
- maxlag = np.float64(0.0)
1270
- maxsigma = np.float64(0.0)
1271
- maskval = np.uint16(0)
1272
- # print(maxlag_init, maxlag, maxval_init, maxval, maxsigma_init, maxsigma, maskval, failreason, fitfail)
1273
- else:
1274
- maxval = np.float64(maxval_init)
1275
- maxlag = np.float64(np.fmod(maxlag_init, self.lagmod))
1276
- maxsigma = np.float64(maxsigma_init)
1277
- if failreason != self.FML_NOERROR:
1278
- maskval = np.uint16(0)
1279
- else:
1280
- maskval = np.uint16(1)
1281
-
1282
- if self.debug or self.displayplots:
1283
- print(
1284
- "init to final: maxval",
1285
- maxval_init,
1286
- maxval,
1287
- ", maxlag:",
1288
- maxlag_init,
1289
- maxlag,
1290
- ", width:",
1291
- maxsigma_init,
1292
- maxsigma,
1293
- )
1294
- if self.displayplots and (self.peakfittype != "None") and (maskval != 0.0):
1295
- fig = plt.figure()
1296
- ax = fig.add_subplot(111)
1297
- ax.set_title("Data and fit")
1298
- hiresx = np.arange(X[0], X[-1], (X[1] - X[0]) / 10.0)
1299
- plt.plot(
1300
- X,
1301
- data,
1302
- "ro",
1303
- hiresx,
1304
- tide_fit.gauss_eval(hiresx, np.array([maxval, maxlag, maxsigma])),
1305
- "b-",
1306
- )
1307
- plt.show()
1308
- return (
1309
- maxindex,
1310
- maxlag,
1311
- flipfac * maxval,
1312
- maxsigma,
1313
- maskval,
1314
- failreason,
1315
- peakstart,
1316
- peakend,
1317
- )
1318
-
1319
-
1320
- class FrequencyTracker:
1321
- freqs = None
1322
- times = None
1323
-
1324
- def __init__(self, lowerlim=0.1, upperlim=0.6, nperseg=32, Q=10.0, debug=False):
1325
- self.lowerlim = lowerlim
1326
- self.upperlim = upperlim
1327
- self.nperseg = nperseg
1328
- self.Q = Q
1329
- self.debug = debug
1330
- self.nfft = self.nperseg
1331
-
1332
- def track(self, x, fs):
1333
- self.freqs, self.times, thespectrogram = sp.signal.spectrogram(
1334
- np.concatenate(
1335
- [np.zeros(int(self.nperseg // 2)), x, np.zeros(int(self.nperseg // 2))],
1336
- axis=0,
1337
- ),
1338
- fs=fs,
1339
- detrend="constant",
1340
- scaling="spectrum",
1341
- nfft=None,
1342
- window=np.hamming(self.nfft),
1343
- noverlap=(self.nperseg - 1),
1344
- )
1345
- lowerliminpts = tide_util.valtoindex(self.freqs, self.lowerlim)
1346
- upperliminpts = tide_util.valtoindex(self.freqs, self.upperlim)
1347
-
1348
- if self.debug:
1349
- print(self.times.shape, self.freqs.shape, thespectrogram.shape)
1350
- print(self.times)
1351
-
1352
- # initialize the peak fitter
1353
- thefitter = SimilarityFunctionFitter(
1354
- corrtimeaxis=self.freqs,
1355
- lagmin=self.lowerlim,
1356
- lagmax=self.upperlim,
1357
- absmaxsigma=10.0,
1358
- absminsigma=0.1,
1359
- debug=self.debug,
1360
- peakfittype="fastquad",
1361
- zerooutbadfit=False,
1362
- useguess=False,
1363
- )
1364
-
1365
- peakfreqs = np.zeros((thespectrogram.shape[1] - 1), dtype=float)
1366
- for i in range(0, thespectrogram.shape[1] - 1):
1367
- (
1368
- maxindex,
1369
- peakfreqs[i],
1370
- maxval,
1371
- maxsigma,
1372
- maskval,
1373
- failreason,
1374
- peakstart,
1375
- peakend,
1376
- ) = thefitter.fit(thespectrogram[:, i])
1377
- if not (lowerliminpts <= maxindex <= upperliminpts):
1378
- peakfreqs[i] = -1.0
1379
-
1380
- return self.times[:-1], peakfreqs
1381
-
1382
- def clean(self, x, fs, times, peakfreqs, numharmonics=2):
1383
- nyquistfreq = 0.5 * fs
1384
- y = x * 0.0
1385
- halfwidth = int(self.nperseg // 2)
1386
- padx = np.concatenate([np.zeros(halfwidth), x, np.zeros(halfwidth)], axis=0)
1387
- pady = np.concatenate([np.zeros(halfwidth), y, np.zeros(halfwidth)], axis=0)
1388
- padweight = padx * 0.0
1389
- if self.debug:
1390
- print(fs, len(times), len(peakfreqs))
1391
- for i in range(0, len(times)):
1392
- centerindex = int(times[i] * fs)
1393
- xstart = centerindex - halfwidth
1394
- xend = centerindex + halfwidth
1395
- if peakfreqs[i] > 0.0:
1396
- filtsignal = padx[xstart:xend]
1397
- numharmonics = np.min([numharmonics, int((nyquistfreq // peakfreqs[i]) - 1)])
1398
- if self.debug:
1399
- print("numharmonics:", numharmonics, nyquistfreq // peakfreqs[i])
1400
- for j in range(numharmonics + 1):
1401
- workingfreq = (j + 1) * peakfreqs[i]
1402
- if self.debug:
1403
- print("workingfreq:", workingfreq)
1404
- ws = [workingfreq * 0.95, workingfreq * 1.05]
1405
- wp = [workingfreq * 0.9, workingfreq * 1.1]
1406
- gpass = 1.0
1407
- gstop = 40.0
1408
- b, a = sp.signal.iirdesign(wp, ws, gpass, gstop, ftype="cheby2", fs=fs)
1409
- if self.debug:
1410
- print(
1411
- i,
1412
- j,
1413
- times[i],
1414
- centerindex,
1415
- halfwidth,
1416
- xstart,
1417
- xend,
1418
- xend - xstart,
1419
- wp,
1420
- ws,
1421
- len(a),
1422
- len(b),
1423
- )
1424
- filtsignal = sp.signal.filtfilt(b, a, sp.signal.filtfilt(b, a, filtsignal))
1425
- pady[xstart:xend] += filtsignal
1426
- else:
1427
- pady[xstart:xend] += padx[xstart:xend]
1428
- padweight[xstart:xend] += 1.0
1429
- return (pady / padweight)[halfwidth:-halfwidth]