rapidtide 2.9.5__py3-none-any.whl → 3.1.3__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 (405) hide show
  1. cloud/gmscalc-HCPYA +1 -1
  2. cloud/mount-and-run +2 -0
  3. cloud/rapidtide-HCPYA +3 -3
  4. rapidtide/Colortables.py +538 -38
  5. rapidtide/OrthoImageItem.py +1094 -51
  6. rapidtide/RapidtideDataset.py +1709 -114
  7. rapidtide/__init__.py +0 -8
  8. rapidtide/_version.py +4 -4
  9. rapidtide/calccoherence.py +242 -97
  10. rapidtide/calcnullsimfunc.py +240 -140
  11. rapidtide/calcsimfunc.py +314 -129
  12. rapidtide/correlate.py +1211 -389
  13. rapidtide/data/examples/src/testLD +56 -0
  14. rapidtide/data/examples/src/test_findmaxlag.py +2 -2
  15. rapidtide/data/examples/src/test_mlregressallt.py +32 -17
  16. rapidtide/data/examples/src/testalign +1 -1
  17. rapidtide/data/examples/src/testatlasaverage +35 -7
  18. rapidtide/data/examples/src/testboth +21 -0
  19. rapidtide/data/examples/src/testcifti +11 -0
  20. rapidtide/data/examples/src/testdelayvar +13 -0
  21. rapidtide/data/examples/src/testdlfilt +25 -0
  22. rapidtide/data/examples/src/testfft +35 -0
  23. rapidtide/data/examples/src/testfileorfloat +37 -0
  24. rapidtide/data/examples/src/testfmri +94 -27
  25. rapidtide/data/examples/src/testfuncs +3 -3
  26. rapidtide/data/examples/src/testglmfilt +8 -6
  27. rapidtide/data/examples/src/testhappy +84 -51
  28. rapidtide/data/examples/src/testinitdelay +19 -0
  29. rapidtide/data/examples/src/testmodels +33 -0
  30. rapidtide/data/examples/src/testnewrefine +26 -0
  31. rapidtide/data/examples/src/testnoiseamp +21 -0
  32. rapidtide/data/examples/src/testppgproc +17 -0
  33. rapidtide/data/examples/src/testrefineonly +22 -0
  34. rapidtide/data/examples/src/testretro +26 -13
  35. rapidtide/data/examples/src/testretrolagtcs +16 -0
  36. rapidtide/data/examples/src/testrolloff +11 -0
  37. rapidtide/data/examples/src/testsimdata +45 -28
  38. rapidtide/data/models/model_cnn_pytorch/loss.png +0 -0
  39. rapidtide/data/models/model_cnn_pytorch/loss.txt +1 -0
  40. rapidtide/data/models/model_cnn_pytorch/model.pth +0 -0
  41. rapidtide/data/models/model_cnn_pytorch/model_meta.json +68 -0
  42. rapidtide/data/models/model_cnn_pytorch_fulldata/loss.png +0 -0
  43. rapidtide/data/models/model_cnn_pytorch_fulldata/loss.txt +1 -0
  44. rapidtide/data/models/model_cnn_pytorch_fulldata/model.pth +0 -0
  45. rapidtide/data/models/model_cnn_pytorch_fulldata/model_meta.json +80 -0
  46. rapidtide/data/models/model_cnnbp_pytorch_fullldata/loss.png +0 -0
  47. rapidtide/data/models/model_cnnbp_pytorch_fullldata/loss.txt +1 -0
  48. rapidtide/data/models/model_cnnbp_pytorch_fullldata/model.pth +0 -0
  49. rapidtide/data/models/model_cnnbp_pytorch_fullldata/model_meta.json +138 -0
  50. rapidtide/data/models/model_cnnfft_pytorch_fulldata/loss.png +0 -0
  51. rapidtide/data/models/model_cnnfft_pytorch_fulldata/loss.txt +1 -0
  52. rapidtide/data/models/model_cnnfft_pytorch_fulldata/model.pth +0 -0
  53. rapidtide/data/models/model_cnnfft_pytorch_fulldata/model_meta.json +128 -0
  54. rapidtide/data/models/model_ppgattention_pytorch_w128_fulldata/loss.png +0 -0
  55. rapidtide/data/models/model_ppgattention_pytorch_w128_fulldata/loss.txt +1 -0
  56. rapidtide/data/models/model_ppgattention_pytorch_w128_fulldata/model.pth +0 -0
  57. rapidtide/data/models/model_ppgattention_pytorch_w128_fulldata/model_meta.json +49 -0
  58. rapidtide/data/models/model_revised_tf2/model.keras +0 -0
  59. rapidtide/data/models/{model_serdar → model_revised_tf2}/model_meta.json +1 -1
  60. rapidtide/data/models/model_serdar2_tf2/model.keras +0 -0
  61. rapidtide/data/models/{model_serdar2 → model_serdar2_tf2}/model_meta.json +1 -1
  62. rapidtide/data/models/model_serdar_tf2/model.keras +0 -0
  63. rapidtide/data/models/{model_revised → model_serdar_tf2}/model_meta.json +1 -1
  64. rapidtide/data/reference/HCP1200v2_MTT_2mm.nii.gz +0 -0
  65. rapidtide/data/reference/HCP1200v2_binmask_2mm.nii.gz +0 -0
  66. rapidtide/data/reference/HCP1200v2_csf_2mm.nii.gz +0 -0
  67. rapidtide/data/reference/HCP1200v2_gray_2mm.nii.gz +0 -0
  68. rapidtide/data/reference/HCP1200v2_graylaghist.json +7 -0
  69. rapidtide/data/reference/HCP1200v2_graylaghist.tsv.gz +0 -0
  70. rapidtide/data/reference/HCP1200v2_laghist.json +7 -0
  71. rapidtide/data/reference/HCP1200v2_laghist.tsv.gz +0 -0
  72. rapidtide/data/reference/HCP1200v2_mask_2mm.nii.gz +0 -0
  73. rapidtide/data/reference/HCP1200v2_maxcorr_2mm.nii.gz +0 -0
  74. rapidtide/data/reference/HCP1200v2_maxtime_2mm.nii.gz +0 -0
  75. rapidtide/data/reference/HCP1200v2_maxwidth_2mm.nii.gz +0 -0
  76. rapidtide/data/reference/HCP1200v2_negmask_2mm.nii.gz +0 -0
  77. rapidtide/data/reference/HCP1200v2_timepercentile_2mm.nii.gz +0 -0
  78. rapidtide/data/reference/HCP1200v2_white_2mm.nii.gz +0 -0
  79. rapidtide/data/reference/HCP1200v2_whitelaghist.json +7 -0
  80. rapidtide/data/reference/HCP1200v2_whitelaghist.tsv.gz +0 -0
  81. rapidtide/data/reference/JHU-ArterialTerritoriesNoVent-LVL1-seg2.xml +131 -0
  82. rapidtide/data/reference/JHU-ArterialTerritoriesNoVent-LVL1-seg2_regions.txt +60 -0
  83. rapidtide/data/reference/JHU-ArterialTerritoriesNoVent-LVL1-seg2_space-MNI152NLin6Asym_2mm.nii.gz +0 -0
  84. rapidtide/data/reference/JHU-ArterialTerritoriesNoVent-LVL1_space-MNI152NLin2009cAsym_2mm.nii.gz +0 -0
  85. rapidtide/data/reference/JHU-ArterialTerritoriesNoVent-LVL1_space-MNI152NLin2009cAsym_2mm_mask.nii.gz +0 -0
  86. rapidtide/data/reference/JHU-ArterialTerritoriesNoVent-LVL1_space-MNI152NLin6Asym_2mm_mask.nii.gz +0 -0
  87. rapidtide/data/reference/JHU-ArterialTerritoriesNoVent-LVL2_space-MNI152NLin6Asym_2mm_mask.nii.gz +0 -0
  88. rapidtide/data/reference/MNI152_T1_1mm_Brain_FAST_seg.nii.gz +0 -0
  89. rapidtide/data/reference/MNI152_T1_1mm_Brain_Mask.nii.gz +0 -0
  90. rapidtide/data/reference/MNI152_T1_2mm_Brain_FAST_seg.nii.gz +0 -0
  91. rapidtide/data/reference/MNI152_T1_2mm_Brain_Mask.nii.gz +0 -0
  92. rapidtide/decorators.py +91 -0
  93. rapidtide/dlfilter.py +2553 -414
  94. rapidtide/dlfiltertorch.py +5201 -0
  95. rapidtide/externaltools.py +328 -13
  96. rapidtide/fMRIData_class.py +178 -0
  97. rapidtide/ffttools.py +168 -0
  98. rapidtide/filter.py +2704 -1462
  99. rapidtide/fit.py +2361 -579
  100. rapidtide/genericmultiproc.py +197 -0
  101. rapidtide/happy_supportfuncs.py +3255 -548
  102. rapidtide/helper_classes.py +590 -1181
  103. rapidtide/io.py +2569 -468
  104. rapidtide/linfitfiltpass.py +784 -0
  105. rapidtide/makelaggedtcs.py +267 -97
  106. rapidtide/maskutil.py +555 -25
  107. rapidtide/miscmath.py +867 -137
  108. rapidtide/multiproc.py +217 -44
  109. rapidtide/patchmatch.py +752 -0
  110. rapidtide/peakeval.py +32 -32
  111. rapidtide/ppgproc.py +2205 -0
  112. rapidtide/qualitycheck.py +353 -40
  113. rapidtide/refinedelay.py +854 -0
  114. rapidtide/refineregressor.py +939 -0
  115. rapidtide/resample.py +725 -204
  116. rapidtide/scripts/__init__.py +1 -0
  117. rapidtide/scripts/{adjustoffset → adjustoffset.py} +7 -2
  118. rapidtide/scripts/{aligntcs → aligntcs.py} +7 -2
  119. rapidtide/scripts/{applydlfilter → applydlfilter.py} +7 -2
  120. rapidtide/scripts/applyppgproc.py +28 -0
  121. rapidtide/scripts/{atlasaverage → atlasaverage.py} +7 -2
  122. rapidtide/scripts/{atlastool → atlastool.py} +7 -2
  123. rapidtide/scripts/{calcicc → calcicc.py} +7 -2
  124. rapidtide/scripts/{calctexticc → calctexticc.py} +7 -2
  125. rapidtide/scripts/{calcttest → calcttest.py} +7 -2
  126. rapidtide/scripts/{ccorrica → ccorrica.py} +7 -2
  127. rapidtide/scripts/delayvar.py +28 -0
  128. rapidtide/scripts/{diffrois → diffrois.py} +7 -2
  129. rapidtide/scripts/{endtidalproc → endtidalproc.py} +7 -2
  130. rapidtide/scripts/{fdica → fdica.py} +7 -2
  131. rapidtide/scripts/{filtnifti → filtnifti.py} +7 -2
  132. rapidtide/scripts/{filttc → filttc.py} +7 -2
  133. rapidtide/scripts/{fingerprint → fingerprint.py} +20 -16
  134. rapidtide/scripts/{fixtr → fixtr.py} +7 -2
  135. rapidtide/scripts/{gmscalc → gmscalc.py} +7 -2
  136. rapidtide/scripts/{happy → happy.py} +7 -2
  137. rapidtide/scripts/{happy2std → happy2std.py} +7 -2
  138. rapidtide/scripts/{happywarp → happywarp.py} +8 -4
  139. rapidtide/scripts/{histnifti → histnifti.py} +7 -2
  140. rapidtide/scripts/{histtc → histtc.py} +7 -2
  141. rapidtide/scripts/{glmfilt → linfitfilt.py} +7 -4
  142. rapidtide/scripts/{localflow → localflow.py} +7 -2
  143. rapidtide/scripts/{mergequality → mergequality.py} +7 -2
  144. rapidtide/scripts/{pairproc → pairproc.py} +7 -2
  145. rapidtide/scripts/{pairwisemergenifti → pairwisemergenifti.py} +7 -2
  146. rapidtide/scripts/{physiofreq → physiofreq.py} +7 -2
  147. rapidtide/scripts/{pixelcomp → pixelcomp.py} +7 -2
  148. rapidtide/scripts/{plethquality → plethquality.py} +7 -2
  149. rapidtide/scripts/{polyfitim → polyfitim.py} +7 -2
  150. rapidtide/scripts/{proj2flow → proj2flow.py} +7 -2
  151. rapidtide/scripts/{rankimage → rankimage.py} +7 -2
  152. rapidtide/scripts/{rapidtide → rapidtide.py} +7 -2
  153. rapidtide/scripts/{rapidtide2std → rapidtide2std.py} +7 -2
  154. rapidtide/scripts/{resamplenifti → resamplenifti.py} +7 -2
  155. rapidtide/scripts/{resampletc → resampletc.py} +7 -2
  156. rapidtide/scripts/retrolagtcs.py +28 -0
  157. rapidtide/scripts/retroregress.py +28 -0
  158. rapidtide/scripts/{roisummarize → roisummarize.py} +7 -2
  159. rapidtide/scripts/{runqualitycheck → runqualitycheck.py} +7 -2
  160. rapidtide/scripts/{showarbcorr → showarbcorr.py} +7 -2
  161. rapidtide/scripts/{showhist → showhist.py} +7 -2
  162. rapidtide/scripts/{showstxcorr → showstxcorr.py} +7 -2
  163. rapidtide/scripts/{showtc → showtc.py} +7 -2
  164. rapidtide/scripts/{showxcorr_legacy → showxcorr_legacy.py} +8 -8
  165. rapidtide/scripts/{showxcorrx → showxcorrx.py} +7 -2
  166. rapidtide/scripts/{showxy → showxy.py} +7 -2
  167. rapidtide/scripts/{simdata → simdata.py} +7 -2
  168. rapidtide/scripts/{spatialdecomp → spatialdecomp.py} +7 -2
  169. rapidtide/scripts/{spatialfit → spatialfit.py} +7 -2
  170. rapidtide/scripts/{spatialmi → spatialmi.py} +7 -2
  171. rapidtide/scripts/{spectrogram → spectrogram.py} +7 -2
  172. rapidtide/scripts/stupidramtricks.py +238 -0
  173. rapidtide/scripts/{synthASL → synthASL.py} +7 -2
  174. rapidtide/scripts/{tcfrom2col → tcfrom2col.py} +7 -2
  175. rapidtide/scripts/{tcfrom3col → tcfrom3col.py} +7 -2
  176. rapidtide/scripts/{temporaldecomp → temporaldecomp.py} +7 -2
  177. rapidtide/scripts/{testhrv → testhrv.py} +1 -1
  178. rapidtide/scripts/{threeD → threeD.py} +7 -2
  179. rapidtide/scripts/{tidepool → tidepool.py} +7 -2
  180. rapidtide/scripts/{variabilityizer → variabilityizer.py} +7 -2
  181. rapidtide/simFuncClasses.py +2113 -0
  182. rapidtide/simfuncfit.py +312 -108
  183. rapidtide/stats.py +579 -247
  184. rapidtide/tests/.coveragerc +27 -6
  185. rapidtide-2.9.5.data/scripts/fdica → rapidtide/tests/cleanposttest +4 -6
  186. rapidtide/tests/happycomp +9 -0
  187. rapidtide/tests/resethappytargets +1 -1
  188. rapidtide/tests/resetrapidtidetargets +1 -1
  189. rapidtide/tests/resettargets +1 -1
  190. rapidtide/tests/runlocaltest +3 -3
  191. rapidtide/tests/showkernels +1 -1
  192. rapidtide/tests/test_aliasedcorrelate.py +4 -4
  193. rapidtide/tests/test_aligntcs.py +1 -1
  194. rapidtide/tests/test_calcicc.py +1 -1
  195. rapidtide/tests/test_cleanregressor.py +184 -0
  196. rapidtide/tests/test_congrid.py +70 -81
  197. rapidtide/tests/test_correlate.py +1 -1
  198. rapidtide/tests/test_corrpass.py +4 -4
  199. rapidtide/tests/test_delayestimation.py +54 -59
  200. rapidtide/tests/test_dlfiltertorch.py +437 -0
  201. rapidtide/tests/test_doresample.py +2 -2
  202. rapidtide/tests/test_externaltools.py +69 -0
  203. rapidtide/tests/test_fastresampler.py +9 -5
  204. rapidtide/tests/test_filter.py +96 -57
  205. rapidtide/tests/test_findmaxlag.py +50 -19
  206. rapidtide/tests/test_fullrunhappy_v1.py +15 -10
  207. rapidtide/tests/test_fullrunhappy_v2.py +19 -13
  208. rapidtide/tests/test_fullrunhappy_v3.py +28 -13
  209. rapidtide/tests/test_fullrunhappy_v4.py +30 -11
  210. rapidtide/tests/test_fullrunhappy_v5.py +62 -0
  211. rapidtide/tests/test_fullrunrapidtide_v1.py +61 -7
  212. rapidtide/tests/test_fullrunrapidtide_v2.py +27 -15
  213. rapidtide/tests/test_fullrunrapidtide_v3.py +28 -8
  214. rapidtide/tests/test_fullrunrapidtide_v4.py +16 -8
  215. rapidtide/tests/test_fullrunrapidtide_v5.py +15 -6
  216. rapidtide/tests/test_fullrunrapidtide_v6.py +142 -0
  217. rapidtide/tests/test_fullrunrapidtide_v7.py +114 -0
  218. rapidtide/tests/test_fullrunrapidtide_v8.py +66 -0
  219. rapidtide/tests/test_getparsers.py +158 -0
  220. rapidtide/tests/test_io.py +59 -18
  221. rapidtide/tests/{test_glmpass.py → test_linfitfiltpass.py} +10 -10
  222. rapidtide/tests/test_mi.py +1 -1
  223. rapidtide/tests/test_miscmath.py +1 -1
  224. rapidtide/tests/test_motionregress.py +5 -5
  225. rapidtide/tests/test_nullcorr.py +6 -9
  226. rapidtide/tests/test_padvec.py +216 -0
  227. rapidtide/tests/test_parserfuncs.py +101 -0
  228. rapidtide/tests/test_phaseanalysis.py +1 -1
  229. rapidtide/tests/test_rapidtideparser.py +59 -53
  230. rapidtide/tests/test_refinedelay.py +296 -0
  231. rapidtide/tests/test_runmisc.py +5 -5
  232. rapidtide/tests/test_sharedmem.py +60 -0
  233. rapidtide/tests/test_simroundtrip.py +132 -0
  234. rapidtide/tests/test_simulate.py +1 -1
  235. rapidtide/tests/test_stcorrelate.py +4 -2
  236. rapidtide/tests/test_timeshift.py +2 -2
  237. rapidtide/tests/test_valtoindex.py +1 -1
  238. rapidtide/tests/test_zRapidtideDataset.py +5 -3
  239. rapidtide/tests/utils.py +10 -9
  240. rapidtide/tidepoolTemplate.py +88 -70
  241. rapidtide/tidepoolTemplate.ui +60 -46
  242. rapidtide/tidepoolTemplate_alt.py +88 -53
  243. rapidtide/tidepoolTemplate_alt.ui +62 -52
  244. rapidtide/tidepoolTemplate_alt_qt6.py +921 -0
  245. rapidtide/tidepoolTemplate_big.py +1125 -0
  246. rapidtide/tidepoolTemplate_big.ui +2386 -0
  247. rapidtide/tidepoolTemplate_big_qt6.py +1129 -0
  248. rapidtide/tidepoolTemplate_qt6.py +793 -0
  249. rapidtide/util.py +1389 -148
  250. rapidtide/voxelData.py +1048 -0
  251. rapidtide/wiener.py +138 -25
  252. rapidtide/wiener2.py +114 -8
  253. rapidtide/workflows/adjustoffset.py +107 -5
  254. rapidtide/workflows/aligntcs.py +86 -3
  255. rapidtide/workflows/applydlfilter.py +231 -89
  256. rapidtide/workflows/applyppgproc.py +540 -0
  257. rapidtide/workflows/atlasaverage.py +309 -48
  258. rapidtide/workflows/atlastool.py +130 -9
  259. rapidtide/workflows/calcSimFuncMap.py +490 -0
  260. rapidtide/workflows/calctexticc.py +202 -10
  261. rapidtide/workflows/ccorrica.py +123 -15
  262. rapidtide/workflows/cleanregressor.py +415 -0
  263. rapidtide/workflows/delayvar.py +1268 -0
  264. rapidtide/workflows/diffrois.py +84 -6
  265. rapidtide/workflows/endtidalproc.py +149 -9
  266. rapidtide/workflows/fdica.py +197 -17
  267. rapidtide/workflows/filtnifti.py +71 -4
  268. rapidtide/workflows/filttc.py +76 -5
  269. rapidtide/workflows/fitSimFuncMap.py +578 -0
  270. rapidtide/workflows/fixtr.py +74 -4
  271. rapidtide/workflows/gmscalc.py +116 -6
  272. rapidtide/workflows/happy.py +1242 -480
  273. rapidtide/workflows/happy2std.py +145 -13
  274. rapidtide/workflows/happy_parser.py +277 -59
  275. rapidtide/workflows/histnifti.py +120 -4
  276. rapidtide/workflows/histtc.py +85 -4
  277. rapidtide/workflows/{glmfilt.py → linfitfilt.py} +128 -14
  278. rapidtide/workflows/localflow.py +329 -29
  279. rapidtide/workflows/mergequality.py +80 -4
  280. rapidtide/workflows/niftidecomp.py +323 -19
  281. rapidtide/workflows/niftistats.py +178 -8
  282. rapidtide/workflows/pairproc.py +99 -5
  283. rapidtide/workflows/pairwisemergenifti.py +86 -3
  284. rapidtide/workflows/parser_funcs.py +1488 -56
  285. rapidtide/workflows/physiofreq.py +139 -12
  286. rapidtide/workflows/pixelcomp.py +211 -9
  287. rapidtide/workflows/plethquality.py +105 -23
  288. rapidtide/workflows/polyfitim.py +159 -19
  289. rapidtide/workflows/proj2flow.py +76 -3
  290. rapidtide/workflows/rankimage.py +115 -8
  291. rapidtide/workflows/rapidtide.py +1833 -1919
  292. rapidtide/workflows/rapidtide2std.py +101 -3
  293. rapidtide/workflows/rapidtide_parser.py +607 -372
  294. rapidtide/workflows/refineDelayMap.py +249 -0
  295. rapidtide/workflows/refineRegressor.py +1215 -0
  296. rapidtide/workflows/regressfrommaps.py +308 -0
  297. rapidtide/workflows/resamplenifti.py +86 -4
  298. rapidtide/workflows/resampletc.py +92 -4
  299. rapidtide/workflows/retrolagtcs.py +442 -0
  300. rapidtide/workflows/retroregress.py +1501 -0
  301. rapidtide/workflows/roisummarize.py +176 -7
  302. rapidtide/workflows/runqualitycheck.py +72 -7
  303. rapidtide/workflows/showarbcorr.py +172 -16
  304. rapidtide/workflows/showhist.py +87 -3
  305. rapidtide/workflows/showstxcorr.py +161 -4
  306. rapidtide/workflows/showtc.py +172 -10
  307. rapidtide/workflows/showxcorrx.py +250 -62
  308. rapidtide/workflows/showxy.py +186 -16
  309. rapidtide/workflows/simdata.py +418 -112
  310. rapidtide/workflows/spatialfit.py +83 -8
  311. rapidtide/workflows/spatialmi.py +252 -29
  312. rapidtide/workflows/spectrogram.py +306 -33
  313. rapidtide/workflows/synthASL.py +157 -6
  314. rapidtide/workflows/tcfrom2col.py +77 -3
  315. rapidtide/workflows/tcfrom3col.py +75 -3
  316. rapidtide/workflows/tidepool.py +3829 -666
  317. rapidtide/workflows/utils.py +45 -19
  318. rapidtide/workflows/utils_doc.py +293 -0
  319. rapidtide/workflows/variabilityizer.py +118 -5
  320. {rapidtide-2.9.5.dist-info → rapidtide-3.1.3.dist-info}/METADATA +30 -223
  321. rapidtide-3.1.3.dist-info/RECORD +393 -0
  322. {rapidtide-2.9.5.dist-info → rapidtide-3.1.3.dist-info}/WHEEL +1 -1
  323. rapidtide-3.1.3.dist-info/entry_points.txt +65 -0
  324. rapidtide-3.1.3.dist-info/top_level.txt +2 -0
  325. rapidtide/calcandfitcorrpairs.py +0 -262
  326. rapidtide/data/examples/src/testoutputsize +0 -45
  327. rapidtide/data/models/model_revised/model.h5 +0 -0
  328. rapidtide/data/models/model_serdar/model.h5 +0 -0
  329. rapidtide/data/models/model_serdar2/model.h5 +0 -0
  330. rapidtide/data/reference/ASPECTS_nlin_asym_09c_2mm.nii.gz +0 -0
  331. rapidtide/data/reference/ASPECTS_nlin_asym_09c_2mm_mask.nii.gz +0 -0
  332. rapidtide/data/reference/ATTbasedFlowTerritories_split_nlin_asym_09c_2mm.nii.gz +0 -0
  333. rapidtide/data/reference/ATTbasedFlowTerritories_split_nlin_asym_09c_2mm_mask.nii.gz +0 -0
  334. rapidtide/data/reference/HCP1200_binmask_2mm_2009c_asym.nii.gz +0 -0
  335. rapidtide/data/reference/HCP1200_lag_2mm_2009c_asym.nii.gz +0 -0
  336. rapidtide/data/reference/HCP1200_mask_2mm_2009c_asym.nii.gz +0 -0
  337. rapidtide/data/reference/HCP1200_negmask_2mm_2009c_asym.nii.gz +0 -0
  338. rapidtide/data/reference/HCP1200_sigma_2mm_2009c_asym.nii.gz +0 -0
  339. rapidtide/data/reference/HCP1200_strength_2mm_2009c_asym.nii.gz +0 -0
  340. rapidtide/glmpass.py +0 -434
  341. rapidtide/refine_factored.py +0 -641
  342. rapidtide/scripts/retroglm +0 -23
  343. rapidtide/workflows/glmfrommaps.py +0 -202
  344. rapidtide/workflows/retroglm.py +0 -643
  345. rapidtide-2.9.5.data/scripts/adjustoffset +0 -23
  346. rapidtide-2.9.5.data/scripts/aligntcs +0 -23
  347. rapidtide-2.9.5.data/scripts/applydlfilter +0 -23
  348. rapidtide-2.9.5.data/scripts/atlasaverage +0 -23
  349. rapidtide-2.9.5.data/scripts/atlastool +0 -23
  350. rapidtide-2.9.5.data/scripts/calcicc +0 -22
  351. rapidtide-2.9.5.data/scripts/calctexticc +0 -23
  352. rapidtide-2.9.5.data/scripts/calcttest +0 -22
  353. rapidtide-2.9.5.data/scripts/ccorrica +0 -23
  354. rapidtide-2.9.5.data/scripts/diffrois +0 -23
  355. rapidtide-2.9.5.data/scripts/endtidalproc +0 -23
  356. rapidtide-2.9.5.data/scripts/filtnifti +0 -23
  357. rapidtide-2.9.5.data/scripts/filttc +0 -23
  358. rapidtide-2.9.5.data/scripts/fingerprint +0 -593
  359. rapidtide-2.9.5.data/scripts/fixtr +0 -23
  360. rapidtide-2.9.5.data/scripts/glmfilt +0 -24
  361. rapidtide-2.9.5.data/scripts/gmscalc +0 -22
  362. rapidtide-2.9.5.data/scripts/happy +0 -25
  363. rapidtide-2.9.5.data/scripts/happy2std +0 -23
  364. rapidtide-2.9.5.data/scripts/happywarp +0 -350
  365. rapidtide-2.9.5.data/scripts/histnifti +0 -23
  366. rapidtide-2.9.5.data/scripts/histtc +0 -23
  367. rapidtide-2.9.5.data/scripts/localflow +0 -23
  368. rapidtide-2.9.5.data/scripts/mergequality +0 -23
  369. rapidtide-2.9.5.data/scripts/pairproc +0 -23
  370. rapidtide-2.9.5.data/scripts/pairwisemergenifti +0 -23
  371. rapidtide-2.9.5.data/scripts/physiofreq +0 -23
  372. rapidtide-2.9.5.data/scripts/pixelcomp +0 -23
  373. rapidtide-2.9.5.data/scripts/plethquality +0 -23
  374. rapidtide-2.9.5.data/scripts/polyfitim +0 -23
  375. rapidtide-2.9.5.data/scripts/proj2flow +0 -23
  376. rapidtide-2.9.5.data/scripts/rankimage +0 -23
  377. rapidtide-2.9.5.data/scripts/rapidtide +0 -23
  378. rapidtide-2.9.5.data/scripts/rapidtide2std +0 -23
  379. rapidtide-2.9.5.data/scripts/resamplenifti +0 -23
  380. rapidtide-2.9.5.data/scripts/resampletc +0 -23
  381. rapidtide-2.9.5.data/scripts/retroglm +0 -23
  382. rapidtide-2.9.5.data/scripts/roisummarize +0 -23
  383. rapidtide-2.9.5.data/scripts/runqualitycheck +0 -23
  384. rapidtide-2.9.5.data/scripts/showarbcorr +0 -23
  385. rapidtide-2.9.5.data/scripts/showhist +0 -23
  386. rapidtide-2.9.5.data/scripts/showstxcorr +0 -23
  387. rapidtide-2.9.5.data/scripts/showtc +0 -23
  388. rapidtide-2.9.5.data/scripts/showxcorr_legacy +0 -536
  389. rapidtide-2.9.5.data/scripts/showxcorrx +0 -23
  390. rapidtide-2.9.5.data/scripts/showxy +0 -23
  391. rapidtide-2.9.5.data/scripts/simdata +0 -23
  392. rapidtide-2.9.5.data/scripts/spatialdecomp +0 -23
  393. rapidtide-2.9.5.data/scripts/spatialfit +0 -23
  394. rapidtide-2.9.5.data/scripts/spatialmi +0 -23
  395. rapidtide-2.9.5.data/scripts/spectrogram +0 -23
  396. rapidtide-2.9.5.data/scripts/synthASL +0 -23
  397. rapidtide-2.9.5.data/scripts/tcfrom2col +0 -23
  398. rapidtide-2.9.5.data/scripts/tcfrom3col +0 -23
  399. rapidtide-2.9.5.data/scripts/temporaldecomp +0 -23
  400. rapidtide-2.9.5.data/scripts/threeD +0 -236
  401. rapidtide-2.9.5.data/scripts/tidepool +0 -23
  402. rapidtide-2.9.5.data/scripts/variabilityizer +0 -23
  403. rapidtide-2.9.5.dist-info/RECORD +0 -357
  404. rapidtide-2.9.5.dist-info/top_level.txt +0 -86
  405. {rapidtide-2.9.5.dist-info → rapidtide-3.1.3.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,2113 @@
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
+ from typing import Any
22
+
23
+ import matplotlib.pyplot as plt
24
+ import numpy as np
25
+ import scipy as sp
26
+ from numpy.polynomial import Polynomial
27
+ from numpy.typing import NDArray
28
+ from scipy.optimize import curve_fit
29
+ from statsmodels.robust import mad
30
+
31
+ import rapidtide.correlate as tide_corr
32
+ import rapidtide.filter as tide_filt
33
+ import rapidtide.fit as tide_fit
34
+ import rapidtide.miscmath as tide_math
35
+ import rapidtide.util as tide_util
36
+
37
+
38
+ class SimilarityFunctionator:
39
+ reftc = None
40
+ prepreftc = None
41
+ testtc = None
42
+ preptesttc = None
43
+ timeaxis = None
44
+ similarityfunclen = 0
45
+ datavalid = False
46
+ timeaxisvalid = False
47
+ similarityfuncorigin = 0
48
+
49
+ def __init__(
50
+ self,
51
+ Fs=0.0,
52
+ similarityfuncorigin=0,
53
+ lagmininpts=0,
54
+ lagmaxinpts=0,
55
+ ncprefilter=None,
56
+ negativegradient=False,
57
+ reftc=None,
58
+ reftcstart=0.0,
59
+ detrendorder=1,
60
+ filterinputdata=True,
61
+ debug=False,
62
+ ):
63
+ """
64
+ Initialize the similarity function analysis object.
65
+
66
+ Parameters
67
+ ----------
68
+ Fs : float, optional
69
+ Sampling frequency in Hz. Default is 0.0.
70
+ similarityfuncorigin : int, optional
71
+ Origin point for similarity function calculation. Default is 0.
72
+ lagmininpts : int, optional
73
+ Minimum lag in samples. Default is 0.
74
+ lagmaxinpts : int, optional
75
+ Maximum lag in samples. Default is 0.
76
+ ncprefilter : array-like, optional
77
+ Pre-filter for cross-correlation calculation. Default is None.
78
+ negativegradient : bool, optional
79
+ Flag to indicate if negative gradient should be used. Default is False.
80
+ reftc : array-like, optional
81
+ Reference time course for cross-correlation. Default is None.
82
+ reftcstart : float, optional
83
+ Start time for reference time course. Default is 0.0.
84
+ detrendorder : int, optional
85
+ Order of detrending to apply to data. Default is 1.
86
+ filterinputdata : bool, optional
87
+ Flag to indicate if input data should be filtered. Default is True.
88
+ debug : bool, optional
89
+ Flag to enable debug mode. Default is False.
90
+
91
+ Returns
92
+ -------
93
+ None
94
+ This method initializes the object attributes but does not return any value.
95
+
96
+ Notes
97
+ -----
98
+ The initialization sets up all necessary parameters for cross-correlation analysis.
99
+ If a reference time course is provided, it is set using the setreftc method.
100
+ All lag parameters are converted to integers by adding 0 to ensure proper type handling.
101
+
102
+ Examples
103
+ --------
104
+ >>> obj = CrossCorrelationAnalyzer(Fs=100.0, lagmininpts=-10, lagmaxinpts=10)
105
+ >>> obj = CrossCorrelationAnalyzer(reftc=reference_data, reftcstart=5.0)
106
+ """
107
+ self.setFs(Fs)
108
+ self.similarityfuncorigin = similarityfuncorigin
109
+ self.lagmininpts = lagmininpts + 0
110
+ self.lagmaxinpts = lagmaxinpts + 0
111
+ self.ncprefilter = ncprefilter
112
+ self.negativegradient = negativegradient
113
+ self.reftc = reftc
114
+ self.detrendorder = detrendorder
115
+ self.filterinputdata = filterinputdata
116
+ self.debug = debug
117
+ if self.reftc is not None:
118
+ self.setreftc(self.reftc)
119
+ self.reftcstart = reftcstart + 0.0
120
+
121
+ def setFs(self, Fs: float) -> None:
122
+ """Set the sampling frequency for the system.
123
+
124
+ Parameters
125
+ ----------
126
+ Fs : float
127
+ Sampling frequency in Hz. This parameter determines the rate at which
128
+ samples are taken from the continuous signal.
129
+
130
+ Returns
131
+ -------
132
+ None
133
+ This method does not return any value.
134
+
135
+ Notes
136
+ -----
137
+ The sampling frequency is a critical parameter that affects the
138
+ resolution and accuracy of digital signal processing operations.
139
+ It should be set appropriately based on the Nyquist criterion to
140
+ avoid aliasing.
141
+
142
+ Examples
143
+ --------
144
+ >>> obj = MyClass()
145
+ >>> obj.setFs(44100.0)
146
+ >>> print(obj.Fs)
147
+ 44100.0
148
+ """
149
+ self.Fs = Fs
150
+
151
+ def preptc(self, thetc: NDArray, isreftc: bool = False) -> NDArray:
152
+ """
153
+ Prepare timecourse by filtering, normalizing, detrending, and applying a window function.
154
+
155
+ This function applies a series of preprocessing steps to the input timecourse, including
156
+ optional filtering, normalization, detrending, and window function application. The specific
157
+ processing steps depend on the input parameters and class configuration.
158
+
159
+ Parameters
160
+ ----------
161
+ thetc : NDArray
162
+ Input timecourse data to be prepared
163
+ isreftc : bool, optional
164
+ Flag indicating whether the input is a reference timecourse. If True, the timecourse
165
+ is filtered using the class's prefilter and then normalized. Default is False.
166
+
167
+ Returns
168
+ -------
169
+ NDArray
170
+ Prepared and normalized timecourse data after filtering, normalization, detrending,
171
+ and window function application
172
+
173
+ Notes
174
+ -----
175
+ The preprocessing pipeline applies the following steps in order:
176
+ 1. Filtering (if applicable based on isreftc and filterinputdata flags)
177
+ 2. Gradient calculation (when negativegradient is True)
178
+ 3. Normalization with detrending and window function application
179
+
180
+ When isreftc is True, the input is filtered using self.ncprefilter.apply() before
181
+ normalization. When isreftc is False and negativegradient is True, the negative gradient
182
+ of the filtered timecourse is used. Otherwise, the filtering behavior depends on the
183
+ filterinputdata flag.
184
+
185
+ Examples
186
+ --------
187
+ >>> # Prepare a timecourse with default settings
188
+ >>> prepared_tc = processor.preptc(input_tc)
189
+ >>>
190
+ >>> # Prepare a reference timecourse
191
+ >>> ref_tc = processor.preptc(input_tc, isreftc=True)
192
+ """
193
+ # prepare timecourse by filtering, normalizing, detrending, and applying a window function
194
+ if isreftc:
195
+ thenormtc = tide_math.corrnormalize(
196
+ self.ncprefilter.apply(self.Fs, thetc),
197
+ detrendorder=self.detrendorder,
198
+ windowfunc=self.windowfunc,
199
+ )
200
+ else:
201
+ if self.negativegradient:
202
+ thenormtc = tide_math.corrnormalize(
203
+ -np.gradient(self.ncprefilter.apply(self.Fs, thetc)),
204
+ detrendorder=self.detrendorder,
205
+ windowfunc=self.windowfunc,
206
+ )
207
+ else:
208
+ if self.filterinputdata:
209
+ thenormtc = tide_math.corrnormalize(
210
+ self.ncprefilter.apply(self.Fs, thetc),
211
+ detrendorder=self.detrendorder,
212
+ windowfunc=self.windowfunc,
213
+ )
214
+ else:
215
+ thenormtc = tide_math.corrnormalize(
216
+ thetc,
217
+ detrendorder=self.detrendorder,
218
+ windowfunc=self.windowfunc,
219
+ )
220
+
221
+ return thenormtc
222
+
223
+ def trim(self, vector: NDArray) -> NDArray:
224
+ """
225
+ Trim vector based on similarity function origin and lag constraints.
226
+
227
+ Parameters
228
+ ----------
229
+ vector : NDArray
230
+ Input vector to be trimmed.
231
+
232
+ Returns
233
+ -------
234
+ NDArray
235
+ Trimmed vector containing elements from
236
+ `self.similarityfuncorigin - self.lagmininpts` to
237
+ `self.similarityfuncorigin + self.lagmaxinpts`.
238
+
239
+ Notes
240
+ -----
241
+ This function extracts a subset of the input vector based on the origin point
242
+ of the similarity function and the minimum/maximum lag constraints. The trimming
243
+ ensures that only relevant portions of the vector are considered for similarity
244
+ calculations.
245
+
246
+ Examples
247
+ --------
248
+ >>> # Assuming self.similarityfuncorigin = 10, self.lagmininpts = 2, self.lagmaxinpts = 3
249
+ >>> trimmed_vector = trim(vector)
250
+ >>> # Returns vector[8:13] where 8 = 10 - 2 and 13 = 10 + 3
251
+ """
252
+ return vector[
253
+ self.similarityfuncorigin
254
+ - self.lagmininpts : self.similarityfuncorigin
255
+ + self.lagmaxinpts
256
+ ]
257
+
258
+ def getfunction(self, trim: bool = True) -> tuple[NDArray | None, NDArray | None, int | None]:
259
+ """
260
+ Retrieve simulation function data with optional trimming.
261
+
262
+ This method returns the simulation function data and time axis, with optional
263
+ trimming based on the trim parameter. The method handles different validation
264
+ states of the data and returns appropriate tuples of (function, time_axis, max_value)
265
+ or None values depending on the data validity.
266
+
267
+ Parameters
268
+ ----------
269
+ trim : bool, optional
270
+ If True, trims the simulation function and time axis using the internal
271
+ trim method. If False, returns the raw data without trimming. Default is True.
272
+
273
+ Returns
274
+ -------
275
+ tuple
276
+ A tuple containing:
277
+ - NDArray or None: Trimmed or untrimmed simulation function data
278
+ - NDArray or None: Trimmed or untrimmed time axis data
279
+ - int or None: Global maximum value, or None if not available
280
+
281
+ Notes
282
+ -----
283
+ The method checks the validity of data through `self.datavalid` and `self.timeaxisvalid` attributes.
284
+ If `self.datavalid` is True, returns both function and time axis data.
285
+ If `self.datavalid` is False but `self.timeaxisvalid` is True, returns only time axis data.
286
+ If neither is valid, prints an error message and returns (None, None, None).
287
+
288
+ Examples
289
+ --------
290
+ >>> result = obj.getfunction(trim=True)
291
+ >>> func_data, time_data, max_val = result
292
+ >>>
293
+ >>> result = obj.getfunction(trim=False)
294
+ >>> func_data, time_data, max_val = result
295
+ """
296
+ if self.datavalid:
297
+ if trim:
298
+ return (
299
+ self.trim(self.thesimfunc),
300
+ self.trim(self.timeaxis),
301
+ self.theglobalmax,
302
+ )
303
+ else:
304
+ return self.thesimfunc, self.timeaxis, self.theglobalmax
305
+ else:
306
+ if self.timeaxisvalid:
307
+ if trim:
308
+ return None, self.trim(self.timeaxis), None
309
+ else:
310
+ return None, self.timeaxis, None
311
+ else:
312
+ print("must calculate similarity function before fetching data")
313
+ return None, None, None
314
+
315
+
316
+ class MutualInformationator(SimilarityFunctionator):
317
+ def __init__(
318
+ self,
319
+ windowfunc: str = "hamming",
320
+ norm: bool = True,
321
+ madnorm: bool = False,
322
+ smoothingtime: float = -1.0,
323
+ bins: int = 20,
324
+ sigma: float = 0.25,
325
+ *args: Any,
326
+ **kwargs: Any,
327
+ ) -> None:
328
+ """
329
+ Initialize the MutualInformationator object with specified parameters.
330
+
331
+ Parameters
332
+ ----------
333
+ windowfunc : str, optional
334
+ Window function to use for spectral analysis. Default is "hamming".
335
+ norm : bool, optional
336
+ Whether to normalize the data. Default is True.
337
+ madnorm : bool, optional
338
+ Whether to use median absolute deviation normalization. Default is False.
339
+ smoothingtime : float, optional
340
+ Time scale for smoothing filter. If > 0, a noncausal filter is set up.
341
+ Default is -1.0 (no smoothing).
342
+ bins : int, optional
343
+ Number of bins for histogram-based calculations. Default is 20.
344
+ sigma : float, optional
345
+ Standard deviation for Gaussian smoothing. Default is 0.25.
346
+ *args : Any
347
+ Additional positional arguments passed to parent class.
348
+ **kwargs : Any
349
+ Additional keyword arguments passed to parent class.
350
+
351
+ Returns
352
+ -------
353
+ None
354
+ This method initializes the object in-place and does not return anything.
355
+
356
+ Notes
357
+ -----
358
+ When `smoothingtime` is positive, a noncausal filter is initialized with
359
+ frequency settings based on the specified smoothing time scale.
360
+
361
+ Examples
362
+ --------
363
+ >>> mi = MutualInformationator(windowfunc="hanning", bins=30, smoothingtime=2.0)
364
+ >>> mi = MutualInformationator(norm=False, madnorm=True, sigma=0.5)
365
+ """
366
+ self.windowfunc = windowfunc
367
+ self.norm = norm
368
+ self.madnorm = madnorm
369
+ self.bins = bins
370
+ self.sigma = sigma
371
+ self.smoothingtime = smoothingtime
372
+ self.smoothingfilter = tide_filt.NoncausalFilter(filtertype="arb")
373
+ self.mi_norm = 1.0
374
+ if self.smoothingtime > 0.0:
375
+ self.smoothingfilter.setfreqs(
376
+ 0.0, 0.0, 1.0 / self.smoothingtime, 1.0 / self.smoothingtime
377
+ )
378
+ super(MutualInformationator, self).__init__(*args, **kwargs)
379
+
380
+ def setlimits(self, lagmininpts: int, lagmaxinpts: int) -> None:
381
+ """
382
+ Set the minimum and maximum lag limits for the analysis.
383
+
384
+ This function configures the lag limits based on the provided parameters and
385
+ adjusts the smoothing filter padding time if necessary to ensure proper
386
+ signal processing behavior.
387
+
388
+ Parameters
389
+ ----------
390
+ lagmininpts : int
391
+ The minimum lag value in terms of number of points.
392
+ lagmaxinpts : int
393
+ The maximum lag value in terms of number of points.
394
+
395
+ Returns
396
+ -------
397
+ None
398
+ This function does not return any value.
399
+
400
+ Notes
401
+ -----
402
+ The function automatically adjusts the smoothing filter padding time to be
403
+ no larger than the total time span of the data. If the adjustment is made,
404
+ a message is printed to indicate the new padding time value.
405
+
406
+ Examples
407
+ --------
408
+ >>> setlimits(10, 100)
409
+ >>> # Sets minimum lag to 10 points and maximum lag to 100 points
410
+ """
411
+ self.lagmininpts = lagmininpts
412
+ self.lagmaxinpts = lagmaxinpts
413
+ origpadtime = self.smoothingfilter.getpadtime()
414
+ timespan = self.timeaxis[-1] - self.timeaxis[0]
415
+ newpadtime = np.min([origpadtime, timespan])
416
+ if newpadtime < origpadtime:
417
+ print("lowering smoothing filter pad time to", newpadtime)
418
+ self.smoothingfilter.setpadtime(newpadtime)
419
+
420
+ def setbins(self, bins: int) -> None:
421
+ """
422
+ Set the number of bins for histogram calculation.
423
+
424
+ Parameters
425
+ ----------
426
+ bins : int
427
+ The number of bins to use for histogram calculation.
428
+
429
+ Returns
430
+ -------
431
+ None
432
+ This method does not return any value.
433
+
434
+ Notes
435
+ -----
436
+ This method assigns the specified number of bins to the instance variable
437
+ `self.bins`. The bins parameter determines the granularity of the histogram
438
+ distribution.
439
+
440
+ Examples
441
+ --------
442
+ >>> obj = MyClass()
443
+ >>> obj.setbins(10)
444
+ >>> print(obj.bins)
445
+ 10
446
+ """
447
+ self.bins = bins
448
+
449
+ def setreftc(self, reftc: NDArray, offset: float = 0.0) -> None:
450
+ """
451
+ Set reference time course and compute cross-mutual information.
452
+
453
+ This method initializes the reference time course and computes the cross-mutual
454
+ information between the reference time course and itself to determine the
455
+ optimal time alignment and similarity function.
456
+
457
+ Parameters
458
+ ----------
459
+ reftc : NDArray
460
+ Reference time course array to be set and processed.
461
+ offset : float, optional
462
+ Time offset to be applied to the time axis (default is 0.0).
463
+
464
+ Returns
465
+ -------
466
+ None
467
+ This method modifies the object's attributes in-place and does not return anything.
468
+
469
+ Notes
470
+ -----
471
+ The method performs the following operations:
472
+ 1. Stores a copy of the reference time course
473
+ 2. Pre-processes the reference time course using preptc method
474
+ 3. Computes cross-mutual information using tide_corr.cross_mutual_info
475
+ 4. Adjusts the time axis by the specified offset
476
+ 5. Sets various internal attributes including similarity function normalization
477
+
478
+ Examples
479
+ --------
480
+ >>> obj.setreftc(reference_data, offset=0.5)
481
+ >>> print(obj.timeaxis)
482
+ >>> print(obj.similarityfunclen)
483
+ """
484
+ self.reftc = reftc + 0.0
485
+ self.prepreftc = self.preptc(self.reftc, isreftc=True)
486
+
487
+ self.timeaxis, self.automi, self.similarityfuncorigin = tide_corr.cross_mutual_info(
488
+ self.prepreftc,
489
+ self.prepreftc,
490
+ Fs=self.Fs,
491
+ fast=True,
492
+ negsteps=self.lagmininpts,
493
+ possteps=self.lagmaxinpts,
494
+ returnaxis=True,
495
+ )
496
+
497
+ self.timeaxis -= offset
498
+ self.similarityfunclen = len(self.timeaxis)
499
+ self.timeaxisvalid = True
500
+ self.datavalid = False
501
+ self.mi_norm = np.nan_to_num(1.0 / np.max(self.automi))
502
+ if self.debug:
503
+ print(f"MutualInformationator setreftc: {len(self.timeaxis)=}")
504
+ print(f"MutualInformationator setreftc: {self.timeaxis}")
505
+ print(f"MutualInformationator setreftc: {self.mi_norm=}")
506
+
507
+ def getnormfac(self) -> float:
508
+ """
509
+ Return the normalization factor stored in the instance.
510
+
511
+ This method provides access to the normalization factor that has been
512
+ previously computed and stored in the instance variable `mi_norm`.
513
+
514
+ Returns
515
+ -------
516
+ float
517
+ The normalization factor value stored in `self.mi_norm`.
518
+
519
+ Notes
520
+ -----
521
+ The normalization factor is typically used to scale or normalize
522
+ data within the class. This value should be set before calling this
523
+ method to ensure meaningful results.
524
+
525
+ Examples
526
+ --------
527
+ >>> instance = MyClass()
528
+ >>> instance.mi_norm = 2.5
529
+ >>> norm_factor = instance.getnormfac()
530
+ >>> print(norm_factor)
531
+ 2.5
532
+ """
533
+ return self.mi_norm
534
+
535
+ def run(
536
+ self,
537
+ thetc: NDArray,
538
+ locs: NDArray | None = None,
539
+ trim: bool = True,
540
+ gettimeaxis: bool = True,
541
+ ) -> tuple[NDArray, NDArray, int] | NDArray:
542
+ """
543
+ Compute cross-mutual information between test and reference timecourses.
544
+
545
+ This function calculates the cross-mutual information between a test timecourse
546
+ and a reference timecourse, optionally applying preprocessing, trimming, and
547
+ smoothing. It supports both trimmed and untrimmed outputs, and can return
548
+ time axis information depending on the input parameters.
549
+
550
+ Parameters
551
+ ----------
552
+ thetc : NDArray
553
+ Test timecourse array of shape (n_times,).
554
+ locs : NDArray | None, optional
555
+ Locations to compute mutual information at; if provided, the function
556
+ will return only the similarity function values at these locations.
557
+ Default is None.
558
+ trim : bool, optional
559
+ If True, trim the output similarity function and time axis to the
560
+ valid range defined by `lagmininpts` and `lagmaxinpts`. If False,
561
+ the full similarity function is returned. Default is True.
562
+ gettimeaxis : bool, optional
563
+ If True, return the time axis along with the similarity function.
564
+ If False, only the similarity function is returned. Default is True.
565
+
566
+ Returns
567
+ -------
568
+ tuple[NDArray, NDArray, int] | NDArray
569
+ If `locs` is not None, returns the similarity function values at the
570
+ specified locations.
571
+ If `trim` is True and `gettimeaxis` is True, returns a tuple of:
572
+ - trimmed similarity function (NDArray)
573
+ - trimmed time axis (NDArray)
574
+ - index of the global maximum (int)
575
+ If `trim` is False and `gettimeaxis` is True, returns a tuple of:
576
+ - full similarity function (NDArray)
577
+ - full time axis (NDArray)
578
+ - index of the global maximum (int)
579
+ If `gettimeaxis` is False, returns only the similarity function (NDArray).
580
+
581
+ Notes
582
+ -----
583
+ This function uses `tide_corr.cross_mutual_info` for computing cross-mutual
584
+ information, and applies normalization and optional smoothing based on
585
+ instance attributes.
586
+
587
+ Examples
588
+ --------
589
+ >>> result = obj.run(test_tc, locs=None, trim=True, gettimeaxis=True)
590
+ >>> sim_func, time_axis, max_idx = result
591
+ """
592
+ if len(thetc) != len(self.reftc):
593
+ print(
594
+ "MutualInformationator: timecourses are of different sizes:",
595
+ len(thetc),
596
+ "!=",
597
+ len(self.reftc),
598
+ "- exiting",
599
+ )
600
+ sys.exit()
601
+
602
+ self.testtc = thetc
603
+ self.preptesttc = self.preptc(self.testtc)
604
+
605
+ if locs is not None:
606
+ gettimeaxis = True
607
+
608
+ # now calculate the similarity function
609
+ if trim:
610
+ retvals = tide_corr.cross_mutual_info(
611
+ self.preptesttc,
612
+ self.prepreftc,
613
+ norm=self.norm,
614
+ negsteps=self.lagmininpts,
615
+ possteps=self.lagmaxinpts,
616
+ locs=locs,
617
+ madnorm=self.madnorm,
618
+ returnaxis=gettimeaxis,
619
+ fast=True,
620
+ Fs=self.Fs,
621
+ sigma=self.sigma,
622
+ bins=self.bins,
623
+ )
624
+ else:
625
+ retvals = tide_corr.cross_mutual_info(
626
+ self.preptesttc,
627
+ self.prepreftc,
628
+ norm=self.norm,
629
+ negsteps=-1,
630
+ possteps=-1,
631
+ locs=locs,
632
+ madnorm=self.madnorm,
633
+ returnaxis=gettimeaxis,
634
+ fast=True,
635
+ Fs=self.Fs,
636
+ sigma=self.sigma,
637
+ bins=self.bins,
638
+ )
639
+ if gettimeaxis:
640
+ self.timeaxis, self.thesimfunc, self.similarityfuncorigin = (
641
+ retvals[0],
642
+ retvals[1],
643
+ retvals[2],
644
+ )
645
+ self.timeaxisvalid = True
646
+ else:
647
+ self.thesimfunc = retvals[0]
648
+
649
+ # normalize
650
+ self.thesimfunc *= self.mi_norm
651
+
652
+ if locs is not None:
653
+ return self.thesimfunc
654
+
655
+ if self.smoothingtime > 0.0:
656
+ self.thesimfunc = self.smoothingfilter.apply(self.Fs, self.thesimfunc)
657
+
658
+ self.similarityfunclen = len(self.thesimfunc)
659
+ if trim:
660
+ self.similarityfuncorigin = self.lagmininpts + 1
661
+ else:
662
+ self.similarityfuncorigin = self.similarityfunclen // 2 + 1
663
+
664
+ # find the global maximum value
665
+ self.theglobalmax = np.argmax(self.thesimfunc)
666
+ self.datavalid = True
667
+
668
+ # make a dummy filtered baseline
669
+ self.filteredbaseline = np.zeros_like(self.thesimfunc)
670
+
671
+ if trim:
672
+ return (
673
+ self.trim(self.thesimfunc),
674
+ self.trim(self.timeaxis),
675
+ self.theglobalmax,
676
+ )
677
+ else:
678
+ return self.thesimfunc, self.timeaxis, self.theglobalmax
679
+
680
+
681
+ class Correlator(SimilarityFunctionator):
682
+ def __init__(
683
+ self,
684
+ windowfunc: str = "hamming",
685
+ corrweighting: str = "None",
686
+ corrpadding: int = 0,
687
+ baselinefilter: Any | None = None,
688
+ *args: Any,
689
+ **kwargs: Any,
690
+ ) -> None:
691
+ """
692
+ Initialize the Correlator with specified parameters.
693
+
694
+ Parameters
695
+ ----------
696
+ windowfunc : str, default="hamming"
697
+ Window function to apply during correlation. Common options include
698
+ 'hamming', 'hanning', 'blackman', etc.
699
+ corrweighting : str, default="None"
700
+ Correlation weighting method. Can be 'None' or other weighting schemes
701
+ depending on implementation.
702
+ corrpadding : int, default=0
703
+ Padding size to apply during correlation operations.
704
+ baselinefilter : Any | None, default=None
705
+ Baseline filtering method or object to apply. Can be None to skip filtering.
706
+ *args : Any
707
+ Additional positional arguments passed to parent class.
708
+ **kwargs : Any
709
+ Additional keyword arguments passed to parent class.
710
+
711
+ Returns
712
+ -------
713
+ None
714
+ This method initializes the instance and does not return any value.
715
+
716
+ Notes
717
+ -----
718
+ The Correlator class inherits from a parent class, and this initialization
719
+ method sets up the correlation parameters before calling the parent's
720
+ initialization method.
721
+
722
+ Examples
723
+ --------
724
+ >>> correlator = Correlator(windowfunc="hanning", corrpadding=10)
725
+ >>> correlator = Correlator(baselinefilter=my_filter_object)
726
+ """
727
+ self.windowfunc = windowfunc
728
+ self.corrweighting = corrweighting
729
+ self.corrpadding = corrpadding
730
+ self.baselinefilter = baselinefilter
731
+ super(Correlator, self).__init__(*args, **kwargs)
732
+
733
+ def setlimits(self, lagmininpts: int, lagmaxinpts: int) -> None:
734
+ """
735
+ Set the minimum and maximum lag limits for the analysis.
736
+
737
+ Parameters
738
+ ----------
739
+ lagmininpts : int
740
+ The minimum lag value in points for the analysis.
741
+ lagmaxinpts : int
742
+ The maximum lag value in points for the analysis.
743
+
744
+ Returns
745
+ -------
746
+ None
747
+ This method does not return any value.
748
+
749
+ Notes
750
+ -----
751
+ This method assigns the provided lag limits to instance variables
752
+ `self.lagmininpts` and `self.lagmaxinpts`. The lag limits define the
753
+ range of lags to be considered in the subsequent analysis operations.
754
+
755
+ Examples
756
+ --------
757
+ >>> obj = MyClass()
758
+ >>> obj.setlimits(5, 20)
759
+ >>> print(obj.lagmininpts)
760
+ 5
761
+ >>> print(obj.lagmaxinpts)
762
+ 20
763
+ """
764
+ self.lagmininpts = lagmininpts
765
+ self.lagmaxinpts = lagmaxinpts
766
+
767
+ def setreftc(self, reftc: NDArray, offset: float = 0.0) -> None:
768
+ """
769
+ Set reference time course and initialize related attributes.
770
+
771
+ This function sets the reference time course, computes related parameters,
772
+ and initializes the time axis for similarity function calculations.
773
+
774
+ Parameters
775
+ ----------
776
+ reftc : NDArray
777
+ Reference time course array used for similarity calculations.
778
+ offset : float, optional
779
+ Time offset to apply to the reference time axis (default is 0.0).
780
+
781
+ Returns
782
+ -------
783
+ None
784
+ This function modifies instance attributes in-place and does not return anything.
785
+
786
+ Notes
787
+ -----
788
+ This function performs the following operations:
789
+ 1. Creates a copy of the reference time course
790
+ 2. Computes preprocessed reference time course using preptc method
791
+ 3. Calculates similarity function length and origin
792
+ 4. Constructs time axis based on sampling frequency and offset
793
+
794
+ The time axis is centered around zero with the specified offset applied.
795
+
796
+ Examples
797
+ --------
798
+ >>> setreftc(reftc_array, offset=0.5)
799
+ >>> print(self.timeaxis)
800
+ >>> print(self.similarityfunclen)
801
+ """
802
+ self.reftc = reftc + 0.0
803
+ self.prepreftc = self.preptc(self.reftc, isreftc=True)
804
+ self.similarityfunclen = len(self.reftc) * 2 - 1
805
+ self.similarityfuncorigin = self.similarityfunclen // 2 + 1
806
+
807
+ # make the reference time axis
808
+ self.timeaxis = (
809
+ np.arange(0.0, self.similarityfunclen) * (1.0 / self.Fs)
810
+ - ((self.similarityfunclen - 1) * (1.0 / self.Fs)) / 2.0
811
+ ) - offset
812
+ self.timeaxisvalid = True
813
+ self.datavalid = False
814
+
815
+ def run(self, thetc: NDArray, trim: bool = True) -> tuple[NDArray, NDArray, int]:
816
+ """
817
+ Compute the correlation between test and reference timecourses.
818
+
819
+ This function performs correlation analysis between a test timecourse and a reference
820
+ timecourse, applying preprocessing and optional filtering. It returns the similarity
821
+ function, time axis, and the index of the global maximum.
822
+
823
+ Parameters
824
+ ----------
825
+ thetc : ndarray
826
+ Test timecourse to be correlated with the reference timecourse.
827
+ trim : bool, optional
828
+ If True, trims the similarity function and time axis to remove zero-padding
829
+ effects. Default is True.
830
+
831
+ Returns
832
+ -------
833
+ tuple of (ndarray, ndarray, int)
834
+ A tuple containing:
835
+ - similarity function (ndarray)
836
+ - time axis (ndarray)
837
+ - index of the global maximum (int)
838
+
839
+ Notes
840
+ -----
841
+ The function exits with status code 1 if the lengths of `thetc` and `self.reftc`
842
+ do not match.
843
+
844
+ Examples
845
+ --------
846
+ >>> result = correlator.run(test_timecourse, trim=True)
847
+ >>> similarity_func, time_axis, max_index = result
848
+ """
849
+ if len(thetc) != len(self.reftc):
850
+ print(
851
+ "Correlator: timecourses are of different sizes:",
852
+ len(thetc),
853
+ "!=",
854
+ len(self.reftc),
855
+ "- exiting",
856
+ )
857
+ sys.exit()
858
+
859
+ self.testtc = thetc
860
+ self.preptesttc = self.preptc(self.testtc)
861
+
862
+ # now actually do the correlation
863
+ self.thesimfunc = tide_corr.fastcorrelate(
864
+ self.preptesttc,
865
+ self.prepreftc,
866
+ usefft=True,
867
+ weighting=self.corrweighting,
868
+ zeropadding=self.corrpadding,
869
+ debug=self.debug,
870
+ )
871
+ self.similarityfunclen = len(self.thesimfunc)
872
+ self.similarityfuncorigin = self.similarityfunclen // 2 + 1
873
+
874
+ if self.baselinefilter is not None:
875
+ self.filteredbaseline = self.baselinefilter.apply(self.Fs, self.thesimfunc)
876
+ else:
877
+ self.filteredbaseline = np.zeros_like(self.thesimfunc)
878
+
879
+ # find the global maximum value
880
+ self.theglobalmax = np.argmax(self.thesimfunc)
881
+ self.datavalid = True
882
+
883
+ if trim:
884
+ return (
885
+ self.trim(self.thesimfunc),
886
+ self.trim(self.timeaxis),
887
+ self.theglobalmax,
888
+ )
889
+ else:
890
+ return self.thesimfunc, self.timeaxis, self.theglobalmax
891
+
892
+
893
+ class SimilarityFunctionFitter:
894
+ corrtimeaxis = None
895
+ FML_NOERROR = np.uint32(0x0000)
896
+
897
+ FML_INITAMPLOW = np.uint32(0x0001)
898
+ FML_INITAMPHIGH = np.uint32(0x0002)
899
+ FML_INITWIDTHLOW = np.uint32(0x0004)
900
+ FML_INITWIDTHHIGH = np.uint32(0x0008)
901
+ FML_INITLAGLOW = np.uint32(0x0010)
902
+ FML_INITLAGHIGH = np.uint32(0x0020)
903
+ FML_INITFAIL = (
904
+ FML_INITAMPLOW
905
+ | FML_INITAMPHIGH
906
+ | FML_INITWIDTHLOW
907
+ | FML_INITWIDTHHIGH
908
+ | FML_INITLAGLOW
909
+ | FML_INITLAGHIGH
910
+ )
911
+
912
+ FML_FITAMPLOW = np.uint32(0x0100)
913
+ FML_FITAMPHIGH = np.uint32(0x0200)
914
+ FML_FITWIDTHLOW = np.uint32(0x0400)
915
+ FML_FITWIDTHHIGH = np.uint32(0x0800)
916
+ FML_FITLAGLOW = np.uint32(0x1000)
917
+ FML_FITLAGHIGH = np.uint32(0x2000)
918
+ FML_FITALGOFAIL = np.uint32(0x0400)
919
+ FML_FITFAIL = (
920
+ FML_FITAMPLOW
921
+ | FML_FITAMPHIGH
922
+ | FML_FITWIDTHLOW
923
+ | FML_FITWIDTHHIGH
924
+ | FML_FITLAGLOW
925
+ | FML_FITLAGHIGH
926
+ | FML_FITALGOFAIL
927
+ )
928
+
929
+ def __init__(
930
+ self,
931
+ corrtimeaxis=None,
932
+ lagmin=-30.0,
933
+ lagmax=30.0,
934
+ absmaxsigma=1000.0,
935
+ absminsigma=0.25,
936
+ hardlimit=True,
937
+ bipolar=False,
938
+ lthreshval=0.0,
939
+ uthreshval=1.0,
940
+ debug=False,
941
+ zerooutbadfit=True,
942
+ maxguess=0.0,
943
+ useguess=False,
944
+ searchfrac=0.5,
945
+ lagmod=1000.0,
946
+ enforcethresh=True,
947
+ allowhighfitamps=False,
948
+ displayplots=False,
949
+ functype="correlation",
950
+ peakfittype="gauss",
951
+ ):
952
+ """
953
+ Initialize a correlation peak finder.
954
+
955
+ This constructor sets up the parameters for fitting and searching correlation
956
+ functions to find peak locations, amplitudes, and widths.
957
+
958
+ Parameters
959
+ ----------
960
+ corrtimeaxis : 1D float array, optional
961
+ The time axis of the correlation function. Default is None.
962
+ lagmin : float, optional
963
+ The minimum allowed lag time in seconds. Default is -30.0.
964
+ lagmax : float, optional
965
+ The maximum allowed lag time in seconds. Default is 30.0.
966
+ absmaxsigma : float, optional
967
+ The maximum allowed peak halfwidth in seconds. Default is 1000.0.
968
+ absminsigma : float, optional
969
+ The minimum allowed peak halfwidth in seconds. Default is 0.25.
970
+ hardlimit : bool, optional
971
+ If True, enforce hard limits on peak fitting. Default is True.
972
+ bipolar : bool, optional
973
+ If True, find the correlation peak with the maximum absolute value,
974
+ regardless of sign. Default is False.
975
+ lthreshval : float, optional
976
+ Lower threshold value for correlation function. Default is 0.0.
977
+ uthreshval : float, optional
978
+ Upper threshold value for correlation function. Default is 1.0.
979
+ debug : bool, optional
980
+ If True, enable debug output. Default is False.
981
+ zerooutbadfit : bool, optional
982
+ If True, set bad fits to zero. Default is True.
983
+ maxguess : float, optional
984
+ Maximum guess for peak fitting. Default is 0.0.
985
+ useguess : bool, optional
986
+ If True, use initial guess for peak fitting. Default is False.
987
+ searchfrac : float, optional
988
+ Fraction of the search range to consider for peak fitting. Default is 0.5.
989
+ lagmod : float, optional
990
+ Modulus for lag values. Default is 1000.0.
991
+ enforcethresh : bool, optional
992
+ If True, enforce threshold constraints. Default is True.
993
+ allowhighfitamps : bool, optional
994
+ If True, allow high amplitude fits. Default is False.
995
+ displayplots : bool, optional
996
+ If True, display plots during fitting. Default is False.
997
+ functype : str, optional
998
+ Type of function to fit. Either "correlation" or "mutualinfo". Default is "correlation".
999
+ peakfittype : str, optional
1000
+ Type of peak fit to use. Default is "gauss".
1001
+
1002
+ Returns
1003
+ -------
1004
+ None
1005
+ This method initializes the object and does not return any value.
1006
+
1007
+ Notes
1008
+ -----
1009
+ The `corrtimeaxis` must be provided before calling `fit()` method.
1010
+ The `functype` parameter determines whether to fit a correlation or mutual information function.
1011
+
1012
+ Examples
1013
+ --------
1014
+ >>> peakfinder = PeakFinder(corrtimeaxis=time_axis, lagmin=-20, lagmax=20)
1015
+ >>> peak_location, peak_value, peak_width = peakfinder.fit(correlation_data)
1016
+ """
1017
+ self.setcorrtimeaxis(corrtimeaxis)
1018
+ self.lagmin = lagmin + 0.0
1019
+ self.lagmax = lagmax + 0.0
1020
+ self.absmaxsigma = absmaxsigma + 0.0
1021
+ self.absminsigma = absminsigma + 0.0
1022
+ self.hardlimit = hardlimit
1023
+ self.bipolar = bipolar
1024
+ self.lthreshval = lthreshval + 0.0
1025
+ self.uthreshval = uthreshval + 0.0
1026
+ self.debug = debug
1027
+ if functype == "correlation" or functype == "mutualinfo":
1028
+ self.functype = functype
1029
+ else:
1030
+ print("illegal functype")
1031
+ sys.exit()
1032
+ self.peakfittype = peakfittype
1033
+ self.zerooutbadfit = zerooutbadfit
1034
+ self.maxguess = maxguess + 0.0
1035
+ self.useguess = useguess
1036
+ self.searchfrac = searchfrac + 0.0
1037
+ self.lagmod = lagmod + 0.0
1038
+ self.enforcethresh = enforcethresh
1039
+ self.allowhighfitamps = allowhighfitamps
1040
+ self.displayplots = displayplots
1041
+
1042
+ def _maxindex_noedge(self, corrfunc: NDArray) -> tuple[int, float]:
1043
+ """
1044
+ Find the index of the maximum value in correlation function, avoiding edge effects.
1045
+
1046
+ This function searches for the maximum value in the correlation function while
1047
+ avoiding the edges of the data. It handles bipolar correlation functions by
1048
+ considering both positive and negative peaks, returning the one with the larger
1049
+ absolute value. The function also accounts for edge effects by adjusting the
1050
+ search boundaries when the maximum is found at the edge.
1051
+
1052
+ Parameters
1053
+ ----------
1054
+ corrfunc : NDArray
1055
+ Correlation function array to search for maximum value
1056
+
1057
+ Returns
1058
+ -------
1059
+ tuple[int, float]
1060
+ Tuple containing:
1061
+ - maxindex: Index of the maximum value in the correlation function
1062
+ - flipfac: Flipping factor (-1.0 if minimum was selected, 1.0 otherwise)
1063
+
1064
+ Notes
1065
+ -----
1066
+ The function adjusts search boundaries to avoid edge effects:
1067
+ - If maximum is at index 0, lowerlim is incremented
1068
+ - If maximum is at upper limit, upperlim is decremented
1069
+ - For bipolar correlation functions, both positive and negative peaks are considered
1070
+ - The search continues until no edge effects are detected
1071
+
1072
+ Examples
1073
+ --------
1074
+ >>> max_index, flip_factor = obj._maxindex_noedge(corrfunc)
1075
+ >>> print(f"Maximum at index {max_index} with flip factor {flip_factor}")
1076
+ """
1077
+ lowerlim = 0
1078
+ upperlim = len(self.corrtimeaxis) - 1
1079
+ done = False
1080
+ while not done:
1081
+ flipfac = 1.0
1082
+ done = True
1083
+ maxindex = (np.argmax(corrfunc[lowerlim:upperlim]) + lowerlim).astype("int32")
1084
+ if self.bipolar:
1085
+ minindex = (np.argmax(-corrfunc[lowerlim:upperlim]) + lowerlim).astype("int32")
1086
+ if np.fabs(corrfunc[minindex]) > np.fabs(corrfunc[maxindex]):
1087
+ maxindex = minindex
1088
+ flipfac = -1.0
1089
+ if upperlim == lowerlim:
1090
+ done = True
1091
+ if maxindex == 0:
1092
+ lowerlim += 1
1093
+ done = False
1094
+ if maxindex == upperlim:
1095
+ upperlim -= 1
1096
+ done = False
1097
+ return maxindex, flipfac
1098
+
1099
+ def setfunctype(self, functype: str) -> None:
1100
+ """
1101
+ Set the function type for the object.
1102
+
1103
+ Parameters
1104
+ ----------
1105
+ functype : str
1106
+ The function type to be set. This should be a string identifier
1107
+ that defines the type of function this object represents.
1108
+
1109
+ Returns
1110
+ -------
1111
+ None
1112
+ This method does not return any value.
1113
+
1114
+ Notes
1115
+ -----
1116
+ This method directly assigns the provided function type to the
1117
+ internal `functype` attribute of the object.
1118
+
1119
+ Examples
1120
+ --------
1121
+ >>> obj = MyClass()
1122
+ >>> obj.setfunctype('linear')
1123
+ >>> obj.functype
1124
+ 'linear'
1125
+ """
1126
+ self.functype = functype
1127
+
1128
+ def setpeakfittype(self, peakfittype: str) -> None:
1129
+ """
1130
+ Set the peak fitting type for the analysis.
1131
+
1132
+ Parameters
1133
+ ----------
1134
+ peakfittype : str
1135
+ The type of peak fitting to be used. This parameter determines the
1136
+ mathematical model and fitting algorithm applied to the peak data.
1137
+
1138
+ Returns
1139
+ -------
1140
+ None
1141
+ This method does not return any value.
1142
+
1143
+ Notes
1144
+ -----
1145
+ This method directly assigns the provided peak fitting type to the
1146
+ instance variable `self.peakfittype`. The valid values for peakfittype
1147
+ depend on the specific implementation of the peak fitting algorithms
1148
+ available in the class.
1149
+
1150
+ Examples
1151
+ --------
1152
+ >>> analyzer = PeakAnalyzer()
1153
+ >>> analyzer.setpeakfittype('gaussian')
1154
+ >>> print(analyzer.peakfittype)
1155
+ 'gaussian'
1156
+ """
1157
+ self.peakfittype = peakfittype
1158
+
1159
+ def setrange(self, lagmin: float, lagmax: float) -> None:
1160
+ """
1161
+ Set the range of lags for the analysis.
1162
+
1163
+ Parameters
1164
+ ----------
1165
+ lagmin : float
1166
+ The minimum lag value for the analysis range.
1167
+ lagmax : float
1168
+ The maximum lag value for the analysis range.
1169
+
1170
+ Returns
1171
+ -------
1172
+ None
1173
+ This method does not return any value.
1174
+
1175
+ Notes
1176
+ -----
1177
+ This method updates the internal lag range parameters of the object.
1178
+ The lagmin value should be less than or equal to the lagmax value.
1179
+
1180
+ Examples
1181
+ --------
1182
+ >>> obj = MyClass()
1183
+ >>> obj.setrange(0.0, 10.0)
1184
+ >>> print(obj.lagmin)
1185
+ 0.0
1186
+ >>> print(obj.lagmax)
1187
+ 10.0
1188
+ """
1189
+ self.lagmin = lagmin
1190
+ self.lagmax = lagmax
1191
+
1192
+ def setcorrtimeaxis(self, corrtimeaxis: NDArray | None) -> None:
1193
+ """
1194
+ Set the correlation time axis for the object.
1195
+
1196
+ This method assigns the provided correlation time axis to the object's
1197
+ `corrtimeaxis` attribute. If the input is not None, a copy of the array is
1198
+ created to avoid modifying the original data.
1199
+
1200
+ Parameters
1201
+ ----------
1202
+ corrtimeaxis : NDArray | None
1203
+ The correlation time axis array to be set. If None, the attribute will
1204
+ be set to None. If an array is provided, a copy will be created to
1205
+ prevent modification of the original array.
1206
+
1207
+ Returns
1208
+ -------
1209
+ None
1210
+ This method does not return any value.
1211
+
1212
+ Notes
1213
+ -----
1214
+ When a numpy array is passed, the method creates a copy using `+ 0.0`
1215
+ to ensure that modifications to the original array do not affect the
1216
+ object's internal state.
1217
+
1218
+ Examples
1219
+ --------
1220
+ >>> obj.setcorrtimeaxis(np.array([1, 2, 3, 4]))
1221
+ >>> obj.setcorrtimeaxis(None)
1222
+ """
1223
+ if corrtimeaxis is not None:
1224
+ self.corrtimeaxis = corrtimeaxis + 0.0
1225
+ else:
1226
+ self.corrtimeaxis = corrtimeaxis
1227
+
1228
+ def setguess(self, useguess: bool, maxguess: float = 0.0) -> None:
1229
+ """
1230
+ Set the guess parameters for the optimization process.
1231
+
1232
+ This method configures whether to use a guess value and sets the maximum
1233
+ guess value for optimization algorithms.
1234
+
1235
+ Parameters
1236
+ ----------
1237
+ useguess : bool
1238
+ Flag indicating whether to use a guess value in the optimization process.
1239
+ If True, the algorithm will attempt to use the provided guess value.
1240
+ If False, no guess value will be used.
1241
+ maxguess : float, optional
1242
+ Maximum guess value to be used in the optimization process. Default is 0.0.
1243
+ This parameter is only relevant when useguess is True.
1244
+
1245
+ Returns
1246
+ -------
1247
+ None
1248
+ This method does not return any value.
1249
+
1250
+ Notes
1251
+ -----
1252
+ The maxguess parameter is typically used to constrain the search space
1253
+ during optimization. When useguess is False, the maxguess parameter has
1254
+ no effect on the optimization process.
1255
+
1256
+ Examples
1257
+ --------
1258
+ >>> optimizer = Optimizer()
1259
+ >>> optimizer.setguess(True, 10.0)
1260
+ >>> optimizer.setguess(False)
1261
+ """
1262
+ self.useguess = useguess
1263
+ self.maxguess = maxguess
1264
+
1265
+ def setlthresh(self, lthreshval: float) -> None:
1266
+ """
1267
+ Set the lower threshold value for the object.
1268
+
1269
+ Parameters
1270
+ ----------
1271
+ lthreshval : float
1272
+ The lower threshold value to be set. This value will be assigned to
1273
+ the instance attribute `lthreshval`.
1274
+
1275
+ Returns
1276
+ -------
1277
+ None
1278
+ This method does not return any value.
1279
+
1280
+ Notes
1281
+ -----
1282
+ This method assigns the provided threshold value to the instance attribute
1283
+ `lthreshval`. The threshold value is typically used for filtering or
1284
+ processing operations where values below this threshold are treated
1285
+ differently.
1286
+
1287
+ Examples
1288
+ --------
1289
+ >>> obj = MyClass()
1290
+ >>> obj.setlthresh(0.5)
1291
+ >>> print(obj.lthreshval)
1292
+ 0.5
1293
+ """
1294
+ self.lthreshval = lthreshval
1295
+
1296
+ def setuthresh(self, uthreshval: float) -> None:
1297
+ """
1298
+ Set the upper threshold value for the object.
1299
+
1300
+ Parameters
1301
+ ----------
1302
+ uthreshval : float
1303
+ The upper threshold value to be set. This value will be assigned to
1304
+ the object's internal `uthreshval` attribute.
1305
+
1306
+ Returns
1307
+ -------
1308
+ None
1309
+ This method does not return any value.
1310
+
1311
+ Notes
1312
+ -----
1313
+ This method directly assigns the provided threshold value to the object's
1314
+ internal attribute. No validation or processing is performed on the input value.
1315
+
1316
+ Examples
1317
+ --------
1318
+ >>> obj = MyClass()
1319
+ >>> obj.setuthresh(0.5)
1320
+ >>> print(obj.uthreshval)
1321
+ 0.5
1322
+ """
1323
+ self.uthreshval = uthreshval
1324
+
1325
+ def diagnosefail(self, failreason: Any) -> str:
1326
+ """
1327
+ Diagnose the cause of a failure based on bitwise flags.
1328
+
1329
+ This function takes a failure reason encoded as a bitwise flag and returns
1330
+ a human-readable string describing the cause(s) of the failure. Each flag
1331
+ corresponds to a specific condition that may have led to the failure.
1332
+
1333
+ Parameters
1334
+ ----------
1335
+ failreason : Any
1336
+ A value representing the failure reason, typically an integer or array
1337
+ of integers. It is cast to `np.uint32` for bitwise operations.
1338
+
1339
+ Returns
1340
+ -------
1341
+ str
1342
+ A comma-separated string listing the reasons for the failure. If no
1343
+ reasons are found, returns "No error".
1344
+
1345
+ Notes
1346
+ -----
1347
+ The function checks the following flags:
1348
+ - ``FML_INITAMPLOW``, ``FML_INITAMPHIGH``, ``FML_INITWIDTHLOW``,
1349
+ ``FML_INITWIDTHHIGH``, ``FML_INITLAGLOW``, ``FML_INITLAGHIGH``:
1350
+ Initial parameter values are out of bounds.
1351
+ - ``FML_FITAMPLOW``, ``FML_FITAMPHIGH``, ``FML_FITWIDTHLOW``,
1352
+ ``FML_FITWIDTHHIGH``, ``FML_FITLAGLOW``, ``FML_FITLAGHIGH``:
1353
+ Fit parameter values are out of bounds.
1354
+ - ``FML_FITALGOFAIL``: Nonlinear fitting algorithm failed.
1355
+
1356
+ Examples
1357
+ --------
1358
+ >>> diagnosis = obj.diagnosefail(0x0001)
1359
+ >>> print(diagnosis)
1360
+ 'Initial amplitude too low'
1361
+ """
1362
+ # define error values
1363
+ reasons = []
1364
+ if failreason.astype(np.uint32) & self.FML_INITAMPLOW:
1365
+ reasons.append("Initial amplitude too low")
1366
+ if failreason.astype(np.uint32) & self.FML_INITAMPHIGH:
1367
+ reasons.append("Initial amplitude too high")
1368
+ if failreason.astype(np.uint32) & self.FML_INITWIDTHLOW:
1369
+ reasons.append("Initial width too low")
1370
+ if failreason.astype(np.uint32) & self.FML_INITWIDTHHIGH:
1371
+ reasons.append("Initial width too high")
1372
+ if failreason.astype(np.uint32) & self.FML_INITLAGLOW:
1373
+ reasons.append("Initial Lag too low")
1374
+ if failreason.astype(np.uint32) & self.FML_INITLAGHIGH:
1375
+ reasons.append("Initial Lag too high")
1376
+
1377
+ if failreason.astype(np.uint32) & self.FML_FITAMPLOW:
1378
+ reasons.append("Fit amplitude too low")
1379
+ if failreason.astype(np.uint32) & self.FML_FITAMPHIGH:
1380
+ reasons.append("Fit amplitude too high")
1381
+ if failreason.astype(np.uint32) & self.FML_FITWIDTHLOW:
1382
+ reasons.append("Fit width too low")
1383
+ if failreason.astype(np.uint32) & self.FML_FITWIDTHHIGH:
1384
+ reasons.append("Fit width too high")
1385
+ if failreason.astype(np.uint32) & self.FML_FITLAGLOW:
1386
+ reasons.append("Fit Lag too low")
1387
+ if failreason.astype(np.uint32) & self.FML_FITLAGHIGH:
1388
+ reasons.append("Fit Lag too high")
1389
+ if failreason.astype(np.uint32) & self.FML_FITALGOFAIL:
1390
+ reasons.append("Nonlinear fit failed")
1391
+
1392
+ if len(reasons) > 0:
1393
+ return ", ".join(reasons)
1394
+ else:
1395
+ return "No error"
1396
+
1397
+ def fit(self, incorrfunc: NDArray) -> tuple[int, float, float, float, int, Any, int, int]:
1398
+ """
1399
+ Fit a correlation function to determine peak parameters including lag, amplitude, and width.
1400
+
1401
+ This function performs a fit on the provided correlation function to extract key parameters
1402
+ such as the peak lag, amplitude, and width. It supports multiple fitting methods and handles
1403
+ various edge cases including invalid inputs, out-of-bounds values, and fitting failures.
1404
+
1405
+ Parameters
1406
+ ----------
1407
+ incorrfunc : ndarray
1408
+ The input correlation function to be fitted. Must match the length of `self.corrtimeaxis`.
1409
+
1410
+ Returns
1411
+ -------
1412
+ tuple[int, float, float, float, int, Any, int, int]
1413
+ A tuple containing:
1414
+
1415
+ - `maxindex` (int): Index of the maximum value in the correlation function.
1416
+ - `maxlag` (float): The lag corresponding to the peak, in seconds.
1417
+ - `maxval` (float): The amplitude of the peak, adjusted for flip factor.
1418
+ - `maxsigma` (float): The width of the peak, in seconds.
1419
+ - `maskval` (int): A flag indicating fit success (1) or failure (0).
1420
+ - `failreason` (Any): A bitmask indicating the reason for fit failure, if any.
1421
+ - `peakstart` (int): Start index of the peak region used in fitting.
1422
+ - `peakend` (int): End index of the peak region used in fitting.
1423
+
1424
+ Notes
1425
+ -----
1426
+ The function performs several checks:
1427
+
1428
+ - Ensures `self.corrtimeaxis` is defined and matches the input length.
1429
+ - Handles bipolar correlation functions and adjusts signs accordingly.
1430
+ - Applies initial parameter estimation based on the input data.
1431
+ - Supports multiple fitting algorithms including Gaussian, quadratic, and center-of-mass.
1432
+ - Applies bounds checking for lag, amplitude, and width to ensure physical validity.
1433
+ - Outputs debugging information if `self.debug` is set to True.
1434
+
1435
+ Examples
1436
+ --------
1437
+ >>> # Assuming `fit_instance` is an instance of the class containing this method
1438
+ >>> corr_func = np.array([0.1, 0.5, 1.0, 0.5, 0.1])
1439
+ >>> result = fit_instance.fit(corr_func)
1440
+ >>> print(result)
1441
+ (2, 1.0, 1.0, 0.5, 1, 0, 1, 3)
1442
+ """
1443
+ # check to make sure xcorr_x and xcorr_y match
1444
+ if self.corrtimeaxis is None:
1445
+ print("Correlation time axis is not defined - exiting")
1446
+ sys.exit()
1447
+ if len(self.corrtimeaxis) != len(incorrfunc):
1448
+ print(
1449
+ "Correlation time axis and values do not match in length (",
1450
+ len(self.corrtimeaxis),
1451
+ "!=",
1452
+ len(incorrfunc),
1453
+ "- exiting",
1454
+ )
1455
+ sys.exit()
1456
+ # set initial parameters
1457
+ # absmaxsigma is in seconds
1458
+ # maxsigma is in Hz
1459
+ # maxlag is in seconds
1460
+ warnings.filterwarnings("ignore", "Number*")
1461
+ failreason = self.FML_NOERROR
1462
+ maskval = np.uint16(1) # start out assuming the fit will succeed
1463
+ binwidth = self.corrtimeaxis[1] - self.corrtimeaxis[0]
1464
+
1465
+ # set the search range
1466
+ lowerlim = 0
1467
+ upperlim = len(self.corrtimeaxis) - 1
1468
+ if self.debug:
1469
+ print(
1470
+ "initial search indices are",
1471
+ lowerlim,
1472
+ "to",
1473
+ upperlim,
1474
+ "(",
1475
+ self.corrtimeaxis[lowerlim],
1476
+ self.corrtimeaxis[upperlim],
1477
+ ")",
1478
+ )
1479
+
1480
+ # make an initial guess at the fit parameters for the gaussian
1481
+ # start with finding the maximum value and its location
1482
+ flipfac = 1.0
1483
+ corrfunc = incorrfunc + 0.0
1484
+ if self.useguess:
1485
+ maxindex = tide_util.valtoindex(self.corrtimeaxis, self.maxguess)
1486
+ if (corrfunc[maxindex] < 0.0) and self.bipolar:
1487
+ flipfac = -1.0
1488
+ else:
1489
+ maxindex, flipfac = self._maxindex_noedge(corrfunc)
1490
+ corrfunc *= flipfac
1491
+ maxlag_init = (1.0 * self.corrtimeaxis[maxindex]).astype("float64")
1492
+ maxval_init = corrfunc[maxindex].astype("float64")
1493
+ if self.debug:
1494
+ print(
1495
+ "maxindex, maxlag_init, maxval_init:",
1496
+ maxindex,
1497
+ maxlag_init,
1498
+ maxval_init,
1499
+ )
1500
+
1501
+ # set the baseline and baselinedev levels
1502
+ if (self.functype == "correlation") or (self.functype == "hybrid"):
1503
+ baseline = 0.0
1504
+ baselinedev = 0.0
1505
+ else:
1506
+ # for mutual information, there is a nonzero baseline, so we want the difference from that.
1507
+ baseline = np.median(corrfunc)
1508
+ baselinedev = mad(corrfunc)
1509
+ if self.debug:
1510
+ print("baseline, baselinedev:", baseline, baselinedev)
1511
+
1512
+ # then calculate the width of the peak
1513
+ if self.peakfittype == "fastquad" or self.peakfittype == "COM":
1514
+ peakstart = np.max([1, maxindex - 2])
1515
+ peakend = np.min([len(self.corrtimeaxis) - 2, maxindex + 2])
1516
+ else:
1517
+ # come here for peakfittype of None, quad, gauss, fastgauss
1518
+ thegrad = np.gradient(corrfunc).astype(
1519
+ "float64"
1520
+ ) # the gradient of the correlation function
1521
+ if (self.functype == "correlation") or (self.functype == "hybrid"):
1522
+ if self.peakfittype == "quad":
1523
+ peakpoints = np.where(
1524
+ corrfunc > maxval_init - 0.05, 1, 0
1525
+ ) # mask for places where correlation exceeds searchfrac*maxval_init
1526
+ else:
1527
+ peakpoints = np.where(
1528
+ corrfunc > (baseline + self.searchfrac * (maxval_init - baseline)), 1, 0
1529
+ ) # mask for places where correlation exceeds searchfrac*maxval_init
1530
+ else:
1531
+ # for mutual information, there is a flattish, nonzero baseline, so we want the difference from that.
1532
+ peakpoints = np.where(
1533
+ corrfunc > (baseline + self.searchfrac * (maxval_init - baseline)),
1534
+ 1,
1535
+ 0,
1536
+ )
1537
+
1538
+ peakpoints[0] = 0
1539
+ peakpoints[-1] = 0
1540
+ peakstart = np.max([1, maxindex - 1])
1541
+ peakend = np.min([len(self.corrtimeaxis) - 2, maxindex + 1])
1542
+ if self.debug:
1543
+ print("initial peakstart, peakend:", peakstart, peakend)
1544
+ if self.functype == "mutualinfo":
1545
+ while peakpoints[peakend + 1] == 1:
1546
+ peakend += 1
1547
+ while peakpoints[peakstart - 1] == 1:
1548
+ peakstart -= 1
1549
+ else:
1550
+ while (
1551
+ thegrad[peakend + 1] <= 0.0
1552
+ and peakpoints[peakend + 1] == 1
1553
+ and peakend < len(self.corrtimeaxis) - 2
1554
+ ):
1555
+ peakend += 1
1556
+ while (
1557
+ thegrad[peakstart - 1] >= 0.0
1558
+ and peakpoints[peakstart - 1] == 1
1559
+ and peakstart >= 1
1560
+ ):
1561
+ peakstart -= 1
1562
+ if self.debug:
1563
+ print("final peakstart, peakend:", peakstart, peakend)
1564
+
1565
+ # deal with flat peak top
1566
+ while (
1567
+ peakend < (len(self.corrtimeaxis) - 3)
1568
+ and corrfunc[peakend] == corrfunc[peakend - 1]
1569
+ ):
1570
+ peakend += 1
1571
+ while peakstart > 2 and corrfunc[peakstart] == corrfunc[peakstart + 1]:
1572
+ peakstart -= 1
1573
+ if self.debug:
1574
+ print("peakstart, peakend after flattop correction:", peakstart, peakend)
1575
+ print("\n")
1576
+ for i in range(peakstart, peakend + 1):
1577
+ print(self.corrtimeaxis[i], corrfunc[i])
1578
+ print("\n")
1579
+ fig = plt.figure()
1580
+ ax = fig.add_subplot(111)
1581
+ ax.set_title("Peak sent to fitting routine")
1582
+ plt.plot(
1583
+ self.corrtimeaxis[peakstart : peakend + 1],
1584
+ corrfunc[peakstart : peakend + 1],
1585
+ "r",
1586
+ )
1587
+ plt.show()
1588
+
1589
+ # This is calculated from first principles, but it's always big by a factor or ~1.4.
1590
+ # Which makes me think I dropped a factor if sqrt(2). So fix that with a final division
1591
+ maxsigma_init = np.float64(
1592
+ ((peakend - peakstart + 1) * binwidth / (2.0 * np.sqrt(-np.log(self.searchfrac))))
1593
+ / np.sqrt(2.0)
1594
+ )
1595
+ if self.debug:
1596
+ print("maxsigma_init:", maxsigma_init)
1597
+
1598
+ # now check the values for errors
1599
+ if self.hardlimit:
1600
+ rangeextension = 0.0
1601
+ else:
1602
+ rangeextension = (self.lagmax - self.lagmin) * 0.75
1603
+ if not (
1604
+ (self.lagmin - rangeextension - binwidth)
1605
+ <= maxlag_init
1606
+ <= (self.lagmax + rangeextension + binwidth)
1607
+ ):
1608
+ if maxlag_init <= (self.lagmin - rangeextension - binwidth):
1609
+ failreason |= self.FML_INITLAGLOW
1610
+ maxlag_init = self.lagmin - rangeextension - binwidth
1611
+ else:
1612
+ failreason |= self.FML_INITLAGHIGH
1613
+ maxlag_init = self.lagmax + rangeextension + binwidth
1614
+ if self.debug:
1615
+ print("bad initial")
1616
+ if maxsigma_init > self.absmaxsigma:
1617
+ failreason |= self.FML_INITWIDTHHIGH
1618
+ maxsigma_init = self.absmaxsigma
1619
+ if self.debug:
1620
+ print("bad initial width - too high")
1621
+ if peakend - peakstart < 2:
1622
+ failreason |= self.FML_INITWIDTHLOW
1623
+ maxsigma_init = np.float64(
1624
+ ((2 + 1) * binwidth / (2.0 * np.sqrt(-np.log(self.searchfrac)))) / np.sqrt(2.0)
1625
+ )
1626
+ if self.debug:
1627
+ print("bad initial width - too low")
1628
+ if (self.functype == "correlation") or (self.functype == "hybrid"):
1629
+ if not (self.lthreshval <= maxval_init <= self.uthreshval) and self.enforcethresh:
1630
+ failreason |= self.FML_INITAMPLOW
1631
+ if self.debug:
1632
+ print(
1633
+ "bad initial amp:",
1634
+ maxval_init,
1635
+ "is less than",
1636
+ self.lthreshval,
1637
+ )
1638
+ if maxval_init < 0.0:
1639
+ failreason |= self.FML_INITAMPLOW
1640
+ maxval_init = 0.0
1641
+ if self.debug:
1642
+ print("bad initial amp:", maxval_init, "is less than 0.0")
1643
+ if (maxval_init > 1.0) and self.enforcethresh:
1644
+ failreason |= self.FML_INITAMPHIGH
1645
+ maxval_init = 1.0
1646
+ if self.debug:
1647
+ print("bad initial amp:", maxval_init, "is greater than 1.0")
1648
+ else:
1649
+ # somewhat different rules for mutual information peaks
1650
+ if ((maxval_init - baseline) < self.lthreshval * baselinedev) or (
1651
+ maxval_init < baseline
1652
+ ):
1653
+ failreason |= self.FML_INITAMPLOW
1654
+ maxval_init = 0.0
1655
+ if self.debug:
1656
+ print("bad initial amp:", maxval_init, "is less than 0.0")
1657
+ if (failreason != self.FML_NOERROR) and self.zerooutbadfit:
1658
+ maxval = np.float64(0.0)
1659
+ maxlag = np.float64(0.0)
1660
+ maxsigma = np.float64(0.0)
1661
+ else:
1662
+ maxval = np.float64(maxval_init)
1663
+ maxlag = np.float64(maxlag_init)
1664
+ maxsigma = np.float64(maxsigma_init)
1665
+
1666
+ # refine if necessary
1667
+ if self.peakfittype != "None":
1668
+ if self.peakfittype == "COM":
1669
+ X = self.corrtimeaxis[peakstart : peakend + 1] - baseline
1670
+ data = corrfunc[peakstart : peakend + 1]
1671
+ maxval = maxval_init
1672
+ maxlag = np.sum(X * data) / np.sum(data)
1673
+ maxsigma = 10.0
1674
+ elif self.peakfittype == "gauss":
1675
+ X = self.corrtimeaxis[peakstart : peakend + 1] - baseline
1676
+ data = corrfunc[peakstart : peakend + 1]
1677
+ # do a least squares fit over the top of the peak
1678
+ # p0 = np.array([maxval_init, np.fmod(maxlag_init, lagmod), maxsigma_init], dtype='float64')
1679
+ p0 = np.array([maxval_init, maxlag_init, maxsigma_init], dtype="float64")
1680
+ if self.debug:
1681
+ print("fit input array:", p0)
1682
+ try:
1683
+ plsq, ier = sp.optimize.leastsq(
1684
+ tide_fit.gaussresiduals, p0, args=(data, X), maxfev=5000
1685
+ )
1686
+ if ier not in [1, 2, 3, 4]: # Check for successful convergence
1687
+ failreason |= self.FML_FITALGOFAIL
1688
+ maxval = np.float64(0.0)
1689
+ maxlag = np.float64(0.0)
1690
+ maxsigma = np.float64(0.0)
1691
+ else:
1692
+ maxval = plsq[0] + baseline
1693
+ maxlag = np.fmod((1.0 * plsq[1]), self.lagmod)
1694
+ maxsigma = plsq[2]
1695
+ except:
1696
+ failreason |= self.FML_FITALGOFAIL
1697
+ maxval = np.float64(0.0)
1698
+ maxlag = np.float64(0.0)
1699
+ maxsigma = np.float64(0.0)
1700
+ if self.debug:
1701
+ print("fit output array:", [maxval, maxlag, maxsigma])
1702
+ elif self.peakfittype == "gausscf":
1703
+ X = self.corrtimeaxis[peakstart : peakend + 1] - baseline
1704
+ data = corrfunc[peakstart : peakend + 1]
1705
+ # do a least squares fit over the top of the peak
1706
+ try:
1707
+ plsq, pcov = curve_fit(
1708
+ tide_fit.gaussfunc,
1709
+ X,
1710
+ data,
1711
+ p0=[maxval_init, maxlag_init, maxsigma_init],
1712
+ )
1713
+ maxval = plsq[0] + baseline
1714
+ maxlag = np.fmod((1.0 * plsq[1]), self.lagmod)
1715
+ maxsigma = plsq[2]
1716
+ except:
1717
+ failreason |= self.FML_FITALGOFAIL
1718
+ maxval = np.float64(0.0)
1719
+ maxlag = np.float64(0.0)
1720
+ maxsigma = np.float64(0.0)
1721
+ if self.debug:
1722
+ print("fit output array:", [maxval, maxlag, maxsigma])
1723
+ elif self.peakfittype == "fastgauss":
1724
+ X = self.corrtimeaxis[peakstart : peakend + 1] - baseline
1725
+ data = corrfunc[peakstart : peakend + 1]
1726
+ # do a non-iterative fit over the top of the peak
1727
+ # 6/12/2015 This is just broken. Gives quantized maxima
1728
+ maxlag = np.float64(1.0 * np.sum(X * data) / np.sum(data))
1729
+ maxsigma = np.float64(
1730
+ np.sqrt(np.abs(np.sum((X - maxlag) ** 2 * data) / np.sum(data)))
1731
+ )
1732
+ maxval = np.float64(data.max()) + baseline
1733
+ elif self.peakfittype == "fastquad":
1734
+ maxlag, maxval, maxsigma, ismax, badfit = tide_fit.refinepeak_quad(
1735
+ self.corrtimeaxis, corrfunc, maxindex
1736
+ )
1737
+ elif self.peakfittype == "quad":
1738
+ X = self.corrtimeaxis[peakstart : peakend + 1]
1739
+ data = corrfunc[peakstart : peakend + 1]
1740
+ try:
1741
+ thecoffs = Polynomial.fit(X, data, 2).convert().coef[::-1]
1742
+ a = thecoffs[0]
1743
+ b = thecoffs[1]
1744
+ c = thecoffs[2]
1745
+ maxlag = -b / (2.0 * a)
1746
+ maxval = a * maxlag * maxlag + b * maxlag + c
1747
+ maxsigma = 1.0 / np.fabs(a)
1748
+ if self.debug:
1749
+ print("poly coffs:", a, b, c)
1750
+ print("maxlag, maxval, maxsigma:", maxlag, maxval, maxsigma)
1751
+ except np.exceptions.RankWarning:
1752
+ failreason |= self.FML_FITALGOFAIL
1753
+ maxlag = 0.0
1754
+ maxval = 0.0
1755
+ maxsigma = 0.0
1756
+ if self.debug:
1757
+ print("\n")
1758
+ for i in range(len(X)):
1759
+ print(X[i], data[i])
1760
+ print("\n")
1761
+ fig = plt.figure()
1762
+ ax = fig.add_subplot(111)
1763
+ ax.set_title("Peak and fit")
1764
+ plt.plot(X, data, "r")
1765
+ plt.plot(X, c + b * X + a * X * X, "b")
1766
+ plt.show()
1767
+
1768
+ else:
1769
+ print("illegal peak refinement type")
1770
+
1771
+ # check for errors in fit
1772
+ fitfail = False
1773
+ if self.bipolar:
1774
+ lowestcorrcoeff = -1.0
1775
+ else:
1776
+ lowestcorrcoeff = 0.0
1777
+ if (self.functype == "correlation") or (self.functype == "hybrid"):
1778
+ if maxval < lowestcorrcoeff:
1779
+ failreason |= self.FML_FITAMPLOW
1780
+ maxval = lowestcorrcoeff
1781
+ if self.debug:
1782
+ print("bad fit amp: maxval is lower than lower limit")
1783
+ fitfail = True
1784
+ if np.abs(maxval) > 1.0:
1785
+ if not self.allowhighfitamps:
1786
+ failreason |= self.FML_FITAMPHIGH
1787
+ if self.debug:
1788
+ print(
1789
+ "bad fit amp: magnitude of",
1790
+ maxval,
1791
+ "is greater than 1.0",
1792
+ )
1793
+ fitfail = True
1794
+ maxval = 1.0 * np.sign(maxval)
1795
+ else:
1796
+ # different rules for mutual information peaks
1797
+ if ((maxval - baseline) < self.lthreshval * baselinedev) or (maxval < baseline):
1798
+ failreason |= self.FML_FITAMPLOW
1799
+ maxval = 0.0
1800
+ if self.debug:
1801
+ if (maxval - baseline) < self.lthreshval * baselinedev:
1802
+ print(
1803
+ "FITAMPLOW: maxval - baseline:",
1804
+ maxval - baseline,
1805
+ " < lthreshval * baselinedev:",
1806
+ self.lthreshval * baselinedev,
1807
+ )
1808
+ if maxval < baseline:
1809
+ print("FITAMPLOW: maxval < baseline:", maxval, baseline)
1810
+ if self.debug:
1811
+ print("bad fit amp: maxval is lower than lower limit")
1812
+ if (self.lagmin > maxlag) or (maxlag > self.lagmax):
1813
+ if self.debug:
1814
+ print("bad lag after refinement")
1815
+ if self.lagmin > maxlag:
1816
+ failreason |= self.FML_FITLAGLOW
1817
+ maxlag = self.lagmin
1818
+ else:
1819
+ failreason |= self.FML_FITLAGHIGH
1820
+ maxlag = self.lagmax
1821
+ fitfail = True
1822
+ if maxsigma > self.absmaxsigma:
1823
+ failreason |= self.FML_FITWIDTHHIGH
1824
+ if self.debug:
1825
+ print("bad width after refinement:", maxsigma, ">", self.absmaxsigma)
1826
+ maxsigma = self.absmaxsigma
1827
+ fitfail = True
1828
+ if maxsigma < self.absminsigma:
1829
+ failreason |= self.FML_FITWIDTHLOW
1830
+ if self.debug:
1831
+ print("bad width after refinement:", maxsigma, "<", self.absminsigma)
1832
+ maxsigma = self.absminsigma
1833
+ fitfail = True
1834
+ if fitfail:
1835
+ if self.debug:
1836
+ print("fit fail")
1837
+ if self.zerooutbadfit:
1838
+ maxval = np.float64(0.0)
1839
+ maxlag = np.float64(0.0)
1840
+ maxsigma = np.float64(0.0)
1841
+ maskval = np.uint16(0)
1842
+ # print(maxlag_init, maxlag, maxval_init, maxval, maxsigma_init, maxsigma, maskval, failreason, fitfail)
1843
+ else:
1844
+ maxval = np.float64(maxval_init)
1845
+ maxlag = np.float64(np.fmod(maxlag_init, self.lagmod))
1846
+ maxsigma = np.float64(maxsigma_init)
1847
+ if failreason != self.FML_NOERROR:
1848
+ maskval = np.uint16(0)
1849
+ else:
1850
+ maskval = np.uint16(1)
1851
+
1852
+ if self.debug or self.displayplots:
1853
+ print(
1854
+ "init to final: maxval",
1855
+ maxval_init,
1856
+ maxval,
1857
+ ", maxlag:",
1858
+ maxlag_init,
1859
+ maxlag,
1860
+ ", width:",
1861
+ maxsigma_init,
1862
+ maxsigma,
1863
+ )
1864
+ if self.displayplots and (self.peakfittype != "None") and (maskval != 0.0):
1865
+ fig = plt.figure()
1866
+ ax = fig.add_subplot(111)
1867
+ ax.set_title("Data and fit")
1868
+ hiresx = np.arange(X[0], X[-1], (X[1] - X[0]) / 10.0)
1869
+ plt.plot(
1870
+ X,
1871
+ data,
1872
+ "ro",
1873
+ hiresx,
1874
+ tide_fit.gauss_eval(hiresx, np.array([maxval, maxlag, maxsigma])),
1875
+ "b-",
1876
+ )
1877
+ plt.show()
1878
+ return (
1879
+ maxindex,
1880
+ maxlag,
1881
+ flipfac * maxval,
1882
+ maxsigma,
1883
+ maskval,
1884
+ failreason,
1885
+ peakstart,
1886
+ peakend,
1887
+ )
1888
+
1889
+
1890
+ class FrequencyTracker:
1891
+ freqs = None
1892
+ times = None
1893
+
1894
+ def __init__(
1895
+ self,
1896
+ lowerlim: float = 0.1,
1897
+ upperlim: float = 0.6,
1898
+ nperseg: int = 32,
1899
+ Q: float = 10.0,
1900
+ debug: bool = False,
1901
+ ) -> None:
1902
+ """
1903
+ Initialize the object with spectral analysis parameters.
1904
+
1905
+ Parameters
1906
+ ----------
1907
+ lowerlim : float, optional
1908
+ Lower frequency limit for spectral analysis, default is 0.1
1909
+ upperlim : float, optional
1910
+ Upper frequency limit for spectral analysis, default is 0.6
1911
+ nperseg : int, optional
1912
+ Number of samples per segment for spectral analysis, default is 32
1913
+ Q : float, optional
1914
+ Quality factor for spectral analysis, default is 10.0
1915
+ debug : bool, optional
1916
+ Debug flag for verbose output, default is False
1917
+
1918
+ Returns
1919
+ -------
1920
+ None
1921
+ This method initializes the object attributes and does not return any value.
1922
+
1923
+ Notes
1924
+ -----
1925
+ The ``nfft`` attribute is set equal to ``nperseg`` during initialization.
1926
+
1927
+ Examples
1928
+ --------
1929
+ >>> obj = MyClass(lowerlim=0.2, upperlim=0.8, nperseg=64)
1930
+ >>> print(obj.lowerlim)
1931
+ 0.2
1932
+ """
1933
+ self.lowerlim = lowerlim
1934
+ self.upperlim = upperlim
1935
+ self.nperseg = nperseg
1936
+ self.Q = Q
1937
+ self.debug = debug
1938
+ self.nfft = self.nperseg
1939
+
1940
+ def track(self, x: NDArray, fs: float) -> tuple[NDArray, NDArray]:
1941
+ """
1942
+ Track peak frequencies in a signal using spectrogram analysis and peak fitting.
1943
+
1944
+ This function computes the spectrogram of the input signal, then tracks the
1945
+ dominant frequency component over time by fitting peaks in each time segment.
1946
+ The result is a tuple of time indices and corresponding peak frequencies.
1947
+
1948
+ Parameters
1949
+ ----------
1950
+ x : NDArray
1951
+ Input signal array to be analyzed.
1952
+ fs : float
1953
+ Sampling frequency of the input signal in Hz.
1954
+
1955
+ Returns
1956
+ -------
1957
+ tuple[NDArray, NDArray]
1958
+ A tuple containing:
1959
+ - times : NDArray
1960
+ Time indices corresponding to the tracked peaks (excluding the last time bin).
1961
+ - peakfreqs : NDArray
1962
+ Array of peak frequencies corresponding to each time segment.
1963
+ If no valid peak is found within the specified frequency range,
1964
+ the value is set to -1.0.
1965
+
1966
+ Notes
1967
+ -----
1968
+ - The input signal is padded with zeros at both ends to reduce edge effects.
1969
+ - The spectrogram is computed using a Hamming window and no overlap between segments.
1970
+ - Peak fitting is performed using a fast quadratic method.
1971
+ - Frequencies outside the range defined by `self.lowerlim` and `self.upperlim`
1972
+ are marked as invalid (set to -1.0).
1973
+
1974
+ Examples
1975
+ --------
1976
+ >>> times, peakfreqs = obj.track(signal, fs)
1977
+ >>> print(f"Peak frequencies: {peakfreqs}")
1978
+ >>> print(f"Time indices: {times}")
1979
+ """
1980
+ self.freqs, self.times, thespectrogram = sp.signal.spectrogram(
1981
+ np.concatenate(
1982
+ [np.zeros(int(self.nperseg // 2)), x, np.zeros(int(self.nperseg // 2))],
1983
+ axis=0,
1984
+ ),
1985
+ fs=fs,
1986
+ detrend="constant",
1987
+ scaling="spectrum",
1988
+ nfft=None,
1989
+ window=np.hamming(self.nfft),
1990
+ noverlap=(self.nperseg - 1),
1991
+ )
1992
+ lowerliminpts = tide_util.valtoindex(self.freqs, self.lowerlim)
1993
+ upperliminpts = tide_util.valtoindex(self.freqs, self.upperlim)
1994
+
1995
+ if self.debug:
1996
+ print(self.times.shape, self.freqs.shape, thespectrogram.shape)
1997
+ print(self.times)
1998
+
1999
+ # initialize the peak fitter
2000
+ thefitter = SimilarityFunctionFitter(
2001
+ corrtimeaxis=self.freqs,
2002
+ lagmin=self.lowerlim,
2003
+ lagmax=self.upperlim,
2004
+ absmaxsigma=10.0,
2005
+ absminsigma=0.1,
2006
+ debug=self.debug,
2007
+ peakfittype="fastquad",
2008
+ zerooutbadfit=False,
2009
+ useguess=False,
2010
+ )
2011
+
2012
+ peakfreqs = np.zeros((thespectrogram.shape[1] - 1), dtype=float)
2013
+ for i in range(0, thespectrogram.shape[1] - 1):
2014
+ (
2015
+ maxindex,
2016
+ peakfreqs[i],
2017
+ maxval,
2018
+ maxsigma,
2019
+ maskval,
2020
+ failreason,
2021
+ peakstart,
2022
+ peakend,
2023
+ ) = thefitter.fit(thespectrogram[:, i])
2024
+ if not (lowerliminpts <= maxindex <= upperliminpts):
2025
+ peakfreqs[i] = -1.0
2026
+
2027
+ return self.times[:-1], peakfreqs
2028
+
2029
+ def clean(
2030
+ self, x: NDArray, fs: float, times: NDArray, peakfreqs: NDArray, numharmonics: int = 2
2031
+ ) -> NDArray:
2032
+ """
2033
+ Apply harmonic cleaning to a signal based on detected peak frequencies and their harmonics.
2034
+
2035
+ This function cleans a signal by applying bandpass filtering to specific time intervals
2036
+ centered at given peak frequencies. It supports filtering of harmonics up to a specified
2037
+ number and handles edge effects through padding.
2038
+
2039
+ Parameters
2040
+ ----------
2041
+ x : ndarray
2042
+ Input signal to be cleaned.
2043
+ fs : float
2044
+ Sampling frequency of the signal in Hz.
2045
+ times : ndarray
2046
+ Array of time indices (in seconds) where cleaning is applied.
2047
+ peakfreqs : ndarray
2048
+ Array of peak frequencies (in Hz) corresponding to each time index.
2049
+ numharmonics : int, optional
2050
+ Maximum number of harmonics to filter (default is 2).
2051
+
2052
+ Returns
2053
+ -------
2054
+ ndarray
2055
+ Cleaned signal with harmonics filtered out.
2056
+
2057
+ Notes
2058
+ -----
2059
+ - The function uses Chebyshev type II filter design for each harmonic.
2060
+ - Harmonics are filtered using `scipy.signal.filtfilt` for zero-phase filtering.
2061
+ - Edge effects are mitigated by padding the input signal with zeros.
2062
+
2063
+ Examples
2064
+ --------
2065
+ >>> cleaned_signal = obj.clean(x, fs=1000.0, times=[0.1, 0.5], peakfreqs=[50.0, 100.0])
2066
+ """
2067
+ nyquistfreq = 0.5 * fs
2068
+ y = np.zeros_like(x)
2069
+ halfwidth = int(self.nperseg // 2)
2070
+ padx = np.concatenate([np.zeros(halfwidth), x, np.zeros(halfwidth)], axis=0)
2071
+ pady = np.concatenate([np.zeros(halfwidth), y, np.zeros(halfwidth)], axis=0)
2072
+ padweight = np.zeros_like(padx)
2073
+ if self.debug:
2074
+ print(fs, len(times), len(peakfreqs))
2075
+ for i in range(0, len(times)):
2076
+ centerindex = int(times[i] * fs)
2077
+ xstart = centerindex - halfwidth
2078
+ xend = centerindex + halfwidth
2079
+ if peakfreqs[i] > 0.0:
2080
+ filtsignal = padx[xstart:xend]
2081
+ numharmonics = np.min([numharmonics, int((nyquistfreq // peakfreqs[i]) - 1)])
2082
+ if self.debug:
2083
+ print("numharmonics:", numharmonics, nyquistfreq // peakfreqs[i])
2084
+ for j in range(numharmonics + 1):
2085
+ workingfreq = (j + 1) * peakfreqs[i]
2086
+ if self.debug:
2087
+ print("workingfreq:", workingfreq)
2088
+ ws = [workingfreq * 0.95, workingfreq * 1.05]
2089
+ wp = [workingfreq * 0.9, workingfreq * 1.1]
2090
+ gpass = 1.0
2091
+ gstop = 40.0
2092
+ b, a = sp.signal.iirdesign(wp, ws, gpass, gstop, ftype="cheby2", fs=fs)
2093
+ if self.debug:
2094
+ print(
2095
+ i,
2096
+ j,
2097
+ times[i],
2098
+ centerindex,
2099
+ halfwidth,
2100
+ xstart,
2101
+ xend,
2102
+ xend - xstart,
2103
+ wp,
2104
+ ws,
2105
+ len(a),
2106
+ len(b),
2107
+ )
2108
+ filtsignal = sp.signal.filtfilt(b, a, sp.signal.filtfilt(b, a, filtsignal))
2109
+ pady[xstart:xend] += filtsignal
2110
+ else:
2111
+ pady[xstart:xend] += padx[xstart:xend]
2112
+ padweight[xstart:xend] += 1.0
2113
+ return (pady / padweight)[halfwidth:-halfwidth]