rapidtide 2.9.6__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 +92 -42
  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 +2 -2
  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 +108 -92
  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 +587 -1116
  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 +835 -144
  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.6.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 +26 -14
  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 +1785 -1858
  292. rapidtide/workflows/rapidtide2std.py +101 -3
  293. rapidtide/workflows/rapidtide_parser.py +590 -389
  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.6.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.6.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.6.data/scripts/adjustoffset +0 -23
  346. rapidtide-2.9.6.data/scripts/aligntcs +0 -23
  347. rapidtide-2.9.6.data/scripts/applydlfilter +0 -23
  348. rapidtide-2.9.6.data/scripts/atlasaverage +0 -23
  349. rapidtide-2.9.6.data/scripts/atlastool +0 -23
  350. rapidtide-2.9.6.data/scripts/calcicc +0 -22
  351. rapidtide-2.9.6.data/scripts/calctexticc +0 -23
  352. rapidtide-2.9.6.data/scripts/calcttest +0 -22
  353. rapidtide-2.9.6.data/scripts/ccorrica +0 -23
  354. rapidtide-2.9.6.data/scripts/diffrois +0 -23
  355. rapidtide-2.9.6.data/scripts/endtidalproc +0 -23
  356. rapidtide-2.9.6.data/scripts/filtnifti +0 -23
  357. rapidtide-2.9.6.data/scripts/filttc +0 -23
  358. rapidtide-2.9.6.data/scripts/fingerprint +0 -593
  359. rapidtide-2.9.6.data/scripts/fixtr +0 -23
  360. rapidtide-2.9.6.data/scripts/glmfilt +0 -24
  361. rapidtide-2.9.6.data/scripts/gmscalc +0 -22
  362. rapidtide-2.9.6.data/scripts/happy +0 -25
  363. rapidtide-2.9.6.data/scripts/happy2std +0 -23
  364. rapidtide-2.9.6.data/scripts/happywarp +0 -350
  365. rapidtide-2.9.6.data/scripts/histnifti +0 -23
  366. rapidtide-2.9.6.data/scripts/histtc +0 -23
  367. rapidtide-2.9.6.data/scripts/localflow +0 -23
  368. rapidtide-2.9.6.data/scripts/mergequality +0 -23
  369. rapidtide-2.9.6.data/scripts/pairproc +0 -23
  370. rapidtide-2.9.6.data/scripts/pairwisemergenifti +0 -23
  371. rapidtide-2.9.6.data/scripts/physiofreq +0 -23
  372. rapidtide-2.9.6.data/scripts/pixelcomp +0 -23
  373. rapidtide-2.9.6.data/scripts/plethquality +0 -23
  374. rapidtide-2.9.6.data/scripts/polyfitim +0 -23
  375. rapidtide-2.9.6.data/scripts/proj2flow +0 -23
  376. rapidtide-2.9.6.data/scripts/rankimage +0 -23
  377. rapidtide-2.9.6.data/scripts/rapidtide +0 -23
  378. rapidtide-2.9.6.data/scripts/rapidtide2std +0 -23
  379. rapidtide-2.9.6.data/scripts/resamplenifti +0 -23
  380. rapidtide-2.9.6.data/scripts/resampletc +0 -23
  381. rapidtide-2.9.6.data/scripts/retroglm +0 -23
  382. rapidtide-2.9.6.data/scripts/roisummarize +0 -23
  383. rapidtide-2.9.6.data/scripts/runqualitycheck +0 -23
  384. rapidtide-2.9.6.data/scripts/showarbcorr +0 -23
  385. rapidtide-2.9.6.data/scripts/showhist +0 -23
  386. rapidtide-2.9.6.data/scripts/showstxcorr +0 -23
  387. rapidtide-2.9.6.data/scripts/showtc +0 -23
  388. rapidtide-2.9.6.data/scripts/showxcorr_legacy +0 -536
  389. rapidtide-2.9.6.data/scripts/showxcorrx +0 -23
  390. rapidtide-2.9.6.data/scripts/showxy +0 -23
  391. rapidtide-2.9.6.data/scripts/simdata +0 -23
  392. rapidtide-2.9.6.data/scripts/spatialdecomp +0 -23
  393. rapidtide-2.9.6.data/scripts/spatialfit +0 -23
  394. rapidtide-2.9.6.data/scripts/spatialmi +0 -23
  395. rapidtide-2.9.6.data/scripts/spectrogram +0 -23
  396. rapidtide-2.9.6.data/scripts/synthASL +0 -23
  397. rapidtide-2.9.6.data/scripts/tcfrom2col +0 -23
  398. rapidtide-2.9.6.data/scripts/tcfrom3col +0 -23
  399. rapidtide-2.9.6.data/scripts/temporaldecomp +0 -23
  400. rapidtide-2.9.6.data/scripts/threeD +0 -236
  401. rapidtide-2.9.6.data/scripts/tidepool +0 -23
  402. rapidtide-2.9.6.data/scripts/variabilityizer +0 -23
  403. rapidtide-2.9.6.dist-info/RECORD +0 -359
  404. rapidtide-2.9.6.dist-info/top_level.txt +0 -86
  405. {rapidtide-2.9.6.dist-info → rapidtide-3.1.3.dist-info/licenses}/LICENSE +0 -0
rapidtide/filter.py CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env python
2
2
  # -*- coding: utf-8 -*-
3
3
  #
4
- # Copyright 2016-2024 Blaise Frederick
4
+ # Copyright 2016-2025 Blaise Frederick
5
5
  #
6
6
  # Licensed under the Apache License, Version 2.0 (the "License");
7
7
  # you may not use this file except in compliance with the License.
@@ -23,9 +23,14 @@ package.
23
23
 
24
24
  import sys
25
25
  import warnings
26
+ from typing import Optional, Tuple, Union
26
27
 
27
28
  import matplotlib.pyplot as plt
28
29
  import numpy as np
30
+ from numpy.typing import NDArray
31
+
32
+ from rapidtide.decorators import conditionaljit, conditionaljit2
33
+ from rapidtide.ffttools import optfftlen
29
34
 
30
35
  with warnings.catch_warnings():
31
36
  warnings.simplefilter("ignore")
@@ -43,210 +48,1048 @@ if pyfftwpresent:
43
48
  fftpack = pyfftw.interfaces.scipy_fftpack
44
49
  pyfftw.interfaces.cache.enable()
45
50
 
46
- # ----------------------------------------- Conditional imports ---------------------------------------
47
- try:
48
- from memory_profiler import profile
51
+ # --------------------------- Filtering functions -------------------------------------------------
52
+ # NB: No automatic padding for precalculated filters
49
53
 
50
- memprofilerexists = True
51
- except ImportError:
52
- memprofilerexists = False
53
54
 
54
- try:
55
- from numba import jit
56
- except ImportError:
57
- donotusenumba = True
58
- else:
59
- donotusenumba = False
55
+ class NoncausalFilter:
56
+ def __init__(
57
+ self,
58
+ filtertype="None",
59
+ transitionfrac=0.05,
60
+ transferfunc="trapezoidal",
61
+ initlowerstop=None,
62
+ initlowerpass=None,
63
+ initupperpass=None,
64
+ initupperstop=None,
65
+ butterworthorder=6,
66
+ correctfreq=True,
67
+ padtime=30.0,
68
+ padtype="reflect",
69
+ debug=False,
70
+ ):
71
+ """
72
+ Initialize a zero time delay filter for one-dimensional signals, especially physiological ones.
60
73
 
74
+ This constructor sets up the filter parameters and initializes the filter type.
75
+ The filter can be configured for various physiological signal processing tasks,
76
+ including VLF, LFO, respiratory, cardiac, and HRV-related filtering.
61
77
 
62
- # ----------------------------------------- Conditional jit handling ----------------------------------
63
- def conditionaljit():
64
- def resdec(f):
65
- global donotusenumba
66
- if donotusenumba:
67
- return f
68
- return jit(f, nopython=True)
78
+ Parameters
79
+ ----------
80
+ filtertype : {'None', 'vlf', 'lfo', 'resp', 'cardiac', 'vlf_stop', 'lfo_stop', 'resp_stop', 'card_stop',
81
+ 'hrv_ulf', 'hrv_vlf', 'hrv_lf', 'hrv_hf', 'hrv_vhf', 'hrv_ulf_stop', 'hrv_vlf_stop',
82
+ 'hrv_lf_stop', 'hrv_hf_stop', 'hrv_vhf_stop', 'arb', 'arb_stop', 'ringstop'}, optional
83
+ The type of filter to apply. Default is 'None'.
84
+ transitionfrac : float, optional
85
+ Fraction of the transition band used for filter transition. Default is 0.05.
86
+ transferfunc : {'trapezoidal', 'butterworth'}, optional
87
+ Transfer function to use for filter design. Default is 'trapezoidal'.
88
+ initlowerstop : float, optional
89
+ Initial lower stop frequency for 'arb' and 'arb_stop' filters. Default is None.
90
+ initlowerpass : float, optional
91
+ Initial lower pass frequency for 'arb' and 'arb_stop' filters. Default is None.
92
+ initupperpass : float, optional
93
+ Initial upper pass frequency for 'arb' and 'arb_stop' filters. Default is None.
94
+ initupperstop : float, optional
95
+ Initial upper stop frequency for 'arb' and 'arb_stop' filters. Default is None.
96
+ butterworthorder : int, optional
97
+ Order of the Butterworth filter. Default is 6.
98
+ correctfreq : bool, optional
99
+ Whether to correct impossible pass frequencies. Default is True.
100
+ padtime : float, optional
101
+ Amount of time (in seconds) to pad the signal to reduce edge effects. Default is 30.0.
102
+ padtype : {'reflect', 'zero', 'constant'}, optional
103
+ Type of padding to use. Default is 'reflect'.
104
+ debug : bool, optional
105
+ Enable extended debugging messages. Default is False.
69
106
 
70
- return resdec
107
+ Returns
108
+ -------
109
+ None
110
+ This method initializes the instance and does not return any value.
111
+
112
+ Notes
113
+ -----
114
+ For 'arb' and 'arb_stop' filter types, the pass and stop frequencies are initialized
115
+ based on the provided values or default values if not specified.
116
+ The default frequencies for 'arb' filters are:
117
+ - lowerpass = 0.05 Hz
118
+ - lowerstop = 0.9 * lowerpass
119
+ - upperpass = 0.20 Hz
120
+ - upperstop = 1.1 * upperpass
121
+
122
+ Examples
123
+ --------
124
+ >>> filter_instance = ZeroDelayFilter(filtertype='resp', padtime=60.0)
125
+ >>> filter_instance.settype('cardiac')
126
+ >>> filter_instance.gettype()
127
+ 'cardiac'
128
+ """
129
+ self.filtertype = filtertype
130
+ self.species = "human"
131
+ self.transitionfrac = transitionfrac
132
+ self.transferfunc = transferfunc
133
+ if initlowerpass is None:
134
+ self.arb_lowerpass = 0.05
135
+ self.arb_lowerstop = 0.9 * self.arb_lowerpass
136
+ else:
137
+ self.arb_lowerpass = initlowerpass
138
+ self.arb_lowerstop = initlowerstop
139
+ if initupperpass is None:
140
+ self.arb_upperpass = 0.20
141
+ self.arb_upperstop = 1.1 * self.arb_upperpass
142
+ else:
143
+ self.arb_upperpass = initupperpass
144
+ self.arb_upperstop = initupperstop
145
+ self.lowerstop = 0.0
146
+ self.lowerpass = 0.0
147
+ self.upperpass = -1.0
148
+ self.upperstop = -1.0
149
+ self.butterworthorder = butterworthorder
150
+ self.correctfreq = correctfreq
151
+ self.padtime = padtime
152
+ self.padtype = padtype
153
+ self.debug = debug
71
154
 
155
+ self.settype(self.filtertype)
72
156
 
73
- def disablenumba():
74
- global donotusenumba
75
- donotusenumba = True
157
+ def settype(self, thetype):
158
+ """
159
+ Set the filter type and corresponding frequency bands for the filter object.
76
160
 
161
+ This method configures the filter parameters based on the specified filter type.
162
+ It assigns passband and stopband frequencies depending on the filter type,
163
+ using predefined frequency ranges or user-defined values for arbitrary filters.
77
164
 
78
- # --------------------------- Filtering functions -------------------------------------------------
79
- # NB: No automatic padding for precalculated filters
165
+ Parameters
166
+ ----------
167
+ thetype : str
168
+ The type of filter to set. Supported values include:
169
+ - "vlf", "vlf_stop": Very Low Frequency
170
+ - "lfo", "lfo_stop": Low Frequency Oscillation
171
+ - "lfo_legacy", "lfo_legacy_stop": Legacy Low Frequency Oscillation
172
+ - "lfo_tight", "lfo_tight_stop": Tight Low Frequency Oscillation
173
+ - "resp", "resp_stop": Respiration
174
+ - "cardiac", "cardiac_stop": Cardiac
175
+ - "hrv_ulf", "hrv_ulf_stop": HRV Ultra Low Frequency
176
+ - "hrv_vlf", "hrv_vlf_stop": HRV Very Low Frequency
177
+ - "hrv_lf", "hrv_lf_stop": HRV Low Frequency
178
+ - "hrv_hf", "hrv_hf_stop": HRV High Frequency
179
+ - "hrv_vhf", "hrv_vhf_stop": HRV Very High Frequency
180
+ - "arb", "arb_stop": Arbitrary filter with custom frequency limits
181
+
182
+ Notes
183
+ -----
184
+ For arbitrary filters ("arb" or "arb_stop"), the method uses the following
185
+ attributes from the object:
186
+ - `self.arb_lowerstop`
187
+ - `self.arb_lowerpass`
188
+ - `self.arb_upperpass`
189
+ - `self.arb_upperstop`
190
+
191
+ For all other filter types, the method calls `getfilterbandfreqs` with the
192
+ specified filter type and additional parameters like `transitionfrac` and `species`.
193
+
194
+ Examples
195
+ --------
196
+ >>> obj.settype("lfo")
197
+ >>> print(obj.lowerpass)
198
+ 0.01
199
+ >>> print(obj.upperstop)
200
+ 0.5
201
+ """
202
+ self.filtertype = thetype
203
+ if self.filtertype == "vlf" or self.filtertype == "vlf_stop":
204
+ self.lowerpass, self.upperpass, self.lowerstop, self.upperstop = getfilterbandfreqs(
205
+ "vlf", transitionfrac=self.transitionfrac, species=self.species
206
+ )
207
+ elif self.filtertype == "lfo" or self.filtertype == "lfo_stop":
208
+ self.lowerpass, self.upperpass, self.lowerstop, self.upperstop = getfilterbandfreqs(
209
+ "lfo", transitionfrac=self.transitionfrac, species=self.species
210
+ )
211
+ elif self.filtertype == "lfo_legacy" or self.filtertype == "lfo_legacy_stop":
212
+ self.lowerpass, self.upperpass, self.lowerstop, self.upperstop = getfilterbandfreqs(
213
+ "lfo_legacy", transitionfrac=self.transitionfrac, species=self.species
214
+ )
215
+ elif self.filtertype == "lfo_tight" or self.filtertype == "lfo_tight_stop":
216
+ self.lowerpass, self.upperpass, self.lowerstop, self.upperstop = getfilterbandfreqs(
217
+ "lfo_tight", transitionfrac=self.transitionfrac, species=self.species
218
+ )
219
+ elif self.filtertype == "resp" or self.filtertype == "resp_stop":
220
+ self.lowerpass, self.upperpass, self.lowerstop, self.upperstop = getfilterbandfreqs(
221
+ "resp", transitionfrac=self.transitionfrac, species=self.species
222
+ )
223
+ elif self.filtertype == "cardiac" or self.filtertype == "cardiac_stop":
224
+ self.lowerpass, self.upperpass, self.lowerstop, self.upperstop = getfilterbandfreqs(
225
+ "cardiac", transitionfrac=self.transitionfrac, species=self.species
226
+ )
227
+ elif self.filtertype == "hrv_ulf" or self.filtertype == "hrv_ulf_stop":
228
+ self.lowerpass, self.upperpass, self.lowerstop, self.upperstop = getfilterbandfreqs(
229
+ "hrv_ulf", transitionfrac=self.transitionfrac, species=self.species
230
+ )
231
+ elif self.filtertype == "hrv_vlf" or self.filtertype == "hrv_vlf_stop":
232
+ self.lowerpass, self.upperpass, self.lowerstop, self.upperstop = getfilterbandfreqs(
233
+ "hrv_vlf", transitionfrac=self.transitionfrac, species=self.species
234
+ )
235
+ elif self.filtertype == "hrv_lf" or self.filtertype == "hrv_lf_stop":
236
+ self.lowerpass, self.upperpass, self.lowerstop, self.upperstop = getfilterbandfreqs(
237
+ "hrv_lf", transitionfrac=self.transitionfrac, species=self.species
238
+ )
239
+ elif self.filtertype == "hrv_hf" or self.filtertype == "hrv_hf_stop":
240
+ self.lowerpass, self.upperpass, self.lowerstop, self.upperstop = getfilterbandfreqs(
241
+ "hrv_hf", transitionfrac=self.transitionfrac, species=self.species
242
+ )
243
+ elif self.filtertype == "hrv_vhf" or self.filtertype == "hrv_vhf_stop":
244
+ self.lowerpass, self.upperpass, self.lowerstop, self.upperstop = getfilterbandfreqs(
245
+ "hrv_vhf", transitionfrac=self.transitionfrac, species=self.species
246
+ )
247
+ elif self.filtertype == "arb" or self.filtertype == "arb_stop":
248
+ self.lowerstop = 1.0 * self.arb_lowerstop
249
+ self.lowerpass = 1.0 * self.arb_lowerpass
250
+ self.upperpass = 1.0 * self.arb_upperpass
251
+ self.upperstop = 1.0 * self.arb_upperstop
252
+ else:
253
+ self.lowerstop = 0.0
254
+ self.lowerpass = 0.0
255
+ self.upperpass = 1.0e20
256
+ self.upperstop = 1.0e20
80
257
 
258
+ def gettype(self):
259
+ """
260
+ Return the filter type of the object.
81
261
 
82
- @conditionaljit()
83
- def padvec(inputdata, padlen=20, cyclic=False, padtype="reflect"):
84
- r"""Returns a padded copy of the input data; padlen points of
85
- reflected data are prepended and appended to the input data to reduce
86
- end effects when the data is then filtered.
262
+ Returns
263
+ -------
264
+ filtertype : str or int
265
+ The filter type associated with the object. The specific type depends
266
+ on the implementation of the filtertype attribute.
267
+
268
+ Notes
269
+ -----
270
+ This method provides access to the internal filtertype attribute.
271
+ The return value type may vary depending on the specific implementation
272
+ of the class that contains this method.
273
+
274
+ Examples
275
+ --------
276
+ >>> obj = MyClass()
277
+ >>> obj.gettype()
278
+ 'some_filter_type'
279
+ """
280
+ return self.filtertype
87
281
 
88
- Parameters
89
- ----------
90
- inputdata : 1D array
91
- An array of any numerical type.
92
- :param inputdata:
282
+ def setbutterorder(self, order=3):
283
+ """
284
+ Set the Butterworth filter order for the system.
93
285
 
94
- padlen : int, optional
95
- The number of points to remove from each end. Default is 20.
96
- :param padlen:
286
+ This method assigns the specified order to the Butterworth filter configuration.
287
+ The order determines the steepness of the filter's roll-off characteristics.
97
288
 
98
- cyclic : bool, optional
99
- If True, pad by wrapping the data in a cyclic manner rather than reflecting at the ends
100
- :param cyclic:
289
+ Parameters
290
+ ----------
291
+ order : int, optional
292
+ The order of the Butterworth filter. Must be a positive integer.
293
+ Default is 3.
101
294
 
102
- Returns
103
- -------
104
- paddeddata : 1D array
105
- The input data, with padlen reflected points added to each end
295
+ Returns
296
+ -------
297
+ None
298
+ This method does not return any value.
299
+
300
+ Notes
301
+ -----
302
+ A higher filter order results in a steeper roll-off but may introduce
303
+ more phase distortion. The order should be chosen based on the specific
304
+ requirements of the signal processing application.
305
+
306
+ Examples
307
+ --------
308
+ >>> system = SomeFilterClass()
309
+ >>> system.setbutterorder(5)
310
+ >>> print(system.butterworthorder)
311
+ 5
312
+
313
+ >>> system.setbutterorder()
314
+ >>> print(system.butterworthorder)
315
+ 3
316
+ """
317
+ self.butterworthorder = order
106
318
 
107
- """
108
- if padlen > len(inputdata):
109
- raise RuntimeError(
110
- "ERROR: padlen (",
111
- padlen,
112
- ") is greater than input data length (",
113
- len(inputdata),
114
- ")",
115
- )
319
+ def setdebug(self, debug):
320
+ """
321
+ Set the debug flag for the object.
116
322
 
117
- inputdtype = inputdata.dtype
118
- if padlen > 0:
119
- if cyclic:
120
- return np.concatenate((inputdata[-padlen:], inputdata, inputdata[0:padlen]))
121
- else:
122
- if padtype == "reflect":
123
- return np.concatenate(
124
- (inputdata[::-1][-padlen:], inputdata, inputdata[::-1][0:padlen])
125
- )
126
- elif padtype == "zero":
127
- return np.concatenate(
128
- (
129
- np.zeros((padlen), dtype=inputdtype),
130
- inputdata,
131
- np.zeros((padlen), dtype=inputdtype),
132
- )
133
- )
134
- elif padtype == "constant":
135
- return np.concatenate(
136
- (
137
- inputdata[0] * np.ones((padlen), dtype=inputdtype),
138
- inputdata,
139
- inputdata[-1] * np.ones((padlen), dtype=inputdtype),
140
- )
141
- )
142
- else:
143
- raise ValueError("Padtype must be one of 'reflect', 'zero', or 'constant'")
144
- else:
145
- return inputdata
323
+ Parameters
324
+ ----------
325
+ debug : bool
326
+ If True, enables debug mode. If False, disables debug mode.
146
327
 
328
+ Returns
329
+ -------
330
+ None
331
+ This method does not return any value.
332
+
333
+ Notes
334
+ -----
335
+ This method sets the internal `debug` attribute of the object. When debug mode
336
+ is enabled, additional logging or verbose output may be generated during
337
+ object operations.
338
+
339
+ Examples
340
+ --------
341
+ >>> obj = MyClass()
342
+ >>> obj.setdebug(True)
343
+ >>> print(obj.debug)
344
+ True
345
+ """
346
+ self.debug = debug
147
347
 
148
- @conditionaljit()
149
- def unpadvec(inputdata, padlen=20):
150
- r"""Returns a input data with the end pads removed (see padvec);
151
- padlen points of reflected data are removed from each end of the array.
348
+ def setpadtime(self, padtime):
349
+ """
350
+ Set the padding time for the object.
152
351
 
153
- Parameters
154
- ----------
155
- inputdata : 1D array
156
- An array of any numerical type.
157
- :param inputdata:
158
- padlen : int, optional
159
- The number of points to remove from each end. Default is 20.
160
- :param padlen:
352
+ Parameters
353
+ ----------
354
+ padtime : float or int
355
+ The padding time value to be assigned to the object's padtime attribute.
161
356
 
162
- Returns
163
- -------
164
- unpaddeddata : 1D array
165
- The input data, with the padding data removed
357
+ Returns
358
+ -------
359
+ None
360
+ This method does not return any value.
361
+
362
+ Notes
363
+ -----
364
+ This method directly assigns the provided padtime value to the instance's padtime attribute,
365
+ replacing any existing value.
366
+
367
+ Examples
368
+ --------
369
+ >>> obj = MyClass()
370
+ >>> obj.setpadtime(5.0)
371
+ >>> print(obj.padtime)
372
+ 5.0
373
+ """
374
+ self.padtime = padtime
166
375
 
376
+ def getpadtime(self):
377
+ """
378
+ Return the padding time value.
167
379
 
168
- """
169
- if padlen > 0:
170
- return inputdata[padlen:-padlen]
171
- else:
172
- return inputdata
380
+ Returns
381
+ -------
382
+ padtime : float or int
383
+ The padding time value stored in the instance variable `self.padtime`.
384
+
385
+ Notes
386
+ -----
387
+ This is a simple getter method that returns the value of the internal
388
+ `padtime` attribute. The actual meaning and units of this value depend
389
+ on the context in which the class is used.
390
+
391
+ Examples
392
+ --------
393
+ >>> obj = MyClass()
394
+ >>> obj.padtime = 5.0
395
+ >>> obj.getpadtime()
396
+ 5.0
397
+ """
398
+ return self.padtime
173
399
 
400
+ def setpadtype(self, padtype):
401
+ """
402
+ Set the padding type for the object.
174
403
 
175
- def ssmooth(xsize, ysize, zsize, sigma, inputdata):
176
- r"""Applies an isotropic gaussian spatial filter to a 3D array
404
+ Parameters
405
+ ----------
406
+ padtype : str
407
+ The padding type to be set. This parameter determines how padding
408
+ will be applied in subsequent operations.
177
409
 
178
- Parameters
179
- ----------
180
- xsize : float
181
- The array x step size in spatial units
182
- :param xsize:
410
+ Returns
411
+ -------
412
+ None
413
+ This method does not return any value.
414
+
415
+ Notes
416
+ -----
417
+ This method directly assigns the provided padding type to the internal
418
+ `padtype` attribute of the object. The valid values for `padtype` depend
419
+ on the specific implementation of the class this method belongs to.
420
+
421
+ Examples
422
+ --------
423
+ >>> obj = MyClass()
424
+ >>> obj.setpadtype('constant')
425
+ >>> print(obj.padtype)
426
+ 'constant'
427
+ """
428
+ self.padtype = padtype
183
429
 
184
- ysize : float
185
- The array y step size in spatial units
186
- :param ysize:
430
+ def getpadtype(self):
431
+ """
432
+ Return the padding type of the object.
187
433
 
188
- zsize : float
189
- The array z step size in spatial units
190
- :param zsize:
434
+ Returns
435
+ -------
436
+ str
437
+ The padding type as a string identifier.
438
+
439
+ Notes
440
+ -----
441
+ This method provides access to the internal `padtype` attribute
442
+ which defines the padding behavior for the object.
443
+
444
+ Examples
445
+ --------
446
+ >>> obj = MyClass()
447
+ >>> obj.padtype = 'constant'
448
+ >>> obj.getpadtype()
449
+ 'constant'
450
+ """
451
+ return self.padtype
191
452
 
192
- sigma : float
193
- The width of the gaussian filter kernel in spatial units
194
- :param sigma:
453
+ def settransferfunc(self, transferfunc):
454
+ """
455
+ Set the transfer function for the system.
195
456
 
196
- inputdata : 3D numeric array
197
- The spatial data to filter
198
- :param inputdata:
457
+ Parameters
458
+ ----------
459
+ transferfunc : callable
460
+ The transfer function to be assigned to the system. This should be a
461
+ callable object that defines the system's transfer function behavior.
199
462
 
200
- Returns
201
- -------
202
- filtereddata : 3D float array
203
- The filtered spatial data
463
+ Returns
464
+ -------
465
+ None
466
+ This method does not return any value.
467
+
468
+ Notes
469
+ -----
470
+ This method directly assigns the provided transfer function to the
471
+ internal `transferfunc` attribute of the object. The transfer function
472
+ should be compatible with the system's expected input and output formats.
473
+
474
+ Examples
475
+ --------
476
+ >>> system = MySystem()
477
+ >>> def my_transfer_func(x):
478
+ ... return x * 2
479
+ >>> system.settransferfunc(my_transfer_func)
480
+ >>> system.transferfunc(5)
481
+ 10
482
+ """
483
+ self.transferfunc = transferfunc
204
484
 
205
- """
206
- return ndimage.gaussian_filter(inputdata, [sigma / xsize, sigma / ysize, sigma / zsize])
485
+ def setfreqs(self, lowerstop, lowerpass, upperpass, upperstop):
486
+ """
487
+ Set frequency parameters for filter design.
207
488
 
489
+ This method configures the frequency boundaries for a filter design, ensuring
490
+ proper causal relationships between the stopband and passband frequencies.
208
491
 
209
- # - butterworth filters
210
- # @conditionaljit()
211
- def dolpfiltfilt(Fs, upperpass, inputdata, order, padlen=20, cyclic=False, debug=False):
212
- r"""Performs a bidirectional (zero phase) Butterworth lowpass filter on an input vector
213
- and returns the result. Ends are padded to reduce transients.
492
+ Parameters
493
+ ----------
494
+ lowerstop : float
495
+ Lower stopband frequency boundary. Must be less than or equal to lowerpass.
496
+ lowerpass : float
497
+ Lower passband frequency boundary. Must be greater than or equal to lowerstop.
498
+ upperpass : float
499
+ Upper passband frequency boundary. Must be less than or equal to upperstop.
500
+ upperstop : float
501
+ Upper stopband frequency boundary. Must be greater than or equal to upperpass.
214
502
 
215
- Parameters
216
- ----------
217
- Fs : float
218
- Sample rate in Hz
219
- :param Fs:
503
+ Returns
504
+ -------
505
+ None
506
+ This method does not return a value but modifies instance attributes.
507
+
508
+ Notes
509
+ -----
510
+ The method performs validation checks to ensure causal filter design:
511
+ - lowerstop must be <= lowerpass
512
+ - upperstop must be >= upperpass
513
+ - lowerpass must be < upperpass when upperpass >= 0.0
514
+
515
+ All frequency values are stored as instance attributes with the prefix 'arb_'
516
+ for internal use and without the prefix for public access.
517
+
518
+ Examples
519
+ --------
520
+ >>> filter = NoncausalFilter()
521
+ >>> filter.setfreqs(0.1, 0.2, 0.8, 0.9)
522
+ >>> print(filter.lowerstop)
523
+ 0.1
524
+ """
525
+ if lowerstop > lowerpass:
526
+ print(
527
+ "NoncausalFilter error: lowerstop (",
528
+ lowerstop,
529
+ ") must be <= lowerpass (",
530
+ lowerpass,
531
+ ")",
532
+ )
533
+ sys.exit()
534
+ if upperpass > upperstop:
535
+ print(
536
+ "NoncausalFilter error: upperstop (",
537
+ upperstop,
538
+ ") must be >= upperpass (",
539
+ upperpass,
540
+ ")",
541
+ )
542
+ sys.exit()
543
+ if (lowerpass > upperpass) and (upperpass >= 0.0):
544
+ print(
545
+ "NoncausalFilter error: lowerpass (",
546
+ lowerpass,
547
+ ") must be < upperpass (",
548
+ upperpass,
549
+ ")",
550
+ )
551
+ sys.exit()
552
+ self.arb_lowerstop = 1.0 * lowerstop
553
+ self.arb_lowerpass = 1.0 * lowerpass
554
+ self.arb_upperpass = 1.0 * upperpass
555
+ self.arb_upperstop = 1.0 * upperstop
556
+ self.lowerstop = 1.0 * self.arb_lowerstop
557
+ self.lowerpass = 1.0 * self.arb_lowerpass
558
+ self.upperpass = 1.0 * self.arb_upperpass
559
+ self.upperstop = 1.0 * self.arb_upperstop
220
560
 
221
- upperpass : float
222
- Upper end of passband in Hz
223
- :param upperpass:
561
+ def getfreqs(self):
562
+ """
563
+ Return frequency boundaries for filter design.
224
564
 
225
- inputdata : 1D numpy array
226
- Input data to be filtered
227
- :param inputdata:
565
+ Returns
566
+ -------
567
+ tuple
568
+ A tuple containing four frequency values in the order:
569
+ (lowerstop, lowerpass, upperpass, upperstop)
570
+
571
+ Notes
572
+ -----
573
+ This function returns the frequency boundaries used for filter design specifications.
574
+ The values represent:
575
+ - lowerstop: Lower stopband frequency
576
+ - lowerpass: Lower passband frequency
577
+ - upperpass: Upper passband frequency
578
+ - upperstop: Upper stopband frequency
579
+
580
+ Examples
581
+ --------
582
+ >>> filter_obj = FilterDesign()
583
+ >>> freqs = filter_obj.getfreqs()
584
+ >>> print(freqs)
585
+ (100, 200, 300, 400)
586
+ """
587
+ return self.lowerstop, self.lowerpass, self.upperpass, self.upperstop
228
588
 
229
- order : int
230
- Order of Butterworth filter.
231
- :param order:
589
+ def apply(self, Fs, data):
590
+ """
591
+ Apply the filter to a dataset.
232
592
 
233
- padlen : int, optional
234
- Amount of points to reflect around each end of the input vector prior to filtering. Default is 20.
235
- :param padlen:
593
+ Parameters
594
+ ----------
595
+ Fs : float
596
+ Sample frequency (Hz) of the input data.
597
+ data : 1D float array
598
+ The data to be filtered.
236
599
 
237
- cyclic : bool, optional
238
- If True, pad by wrapping the data in a cyclic manner rather than reflecting at the ends
239
- :param cyclic:
600
+ Returns
601
+ -------
602
+ filtereddata : 1D float array
603
+ The filtered data with the same shape as the input `data`.
604
+
605
+ Notes
606
+ -----
607
+ This function applies a filter based on the `filtertype` attribute of the object.
608
+ It performs bounds checking and handles various error conditions, including cases
609
+ where filter frequencies exceed the Nyquist limit or fall below the minimum
610
+ resolvable frequency. If `correctfreq` is True, invalid frequencies are adjusted
611
+ to valid values instead of raising an error.
612
+
613
+ The function supports multiple predefined filter types such as 'vlf', 'lfo',
614
+ 'cardiac', 'hrv_*' and custom 'arb' types. For stopband filters (e.g., 'vlf_stop'),
615
+ the result is the difference between the input and the filtered signal.
616
+
617
+ Examples
618
+ --------
619
+ >>> filtered_data = filter_instance.apply(100.0, data)
620
+ >>> filtered_data = filter_instance.apply(256.0, data)
621
+ """
622
+ # if filterband is None, just return the data
623
+ if self.filtertype == "None":
624
+ return data
625
+
626
+ # do some bounds checking
627
+ nyquistlimit = 0.5 * Fs
628
+ lowestfreq = 2.0 * Fs / np.shape(data)[0]
629
+
630
+ # first see if entire range is out of bounds
631
+ if self.lowerpass >= nyquistlimit:
632
+ print(
633
+ "NoncausalFilter error: filter lower pass ",
634
+ self.lowerpass,
635
+ " exceeds nyquist frequency ",
636
+ nyquistlimit,
637
+ )
638
+ sys.exit()
639
+ if self.lowerstop >= nyquistlimit:
640
+ print(
641
+ "NoncausalFilter error: filter lower stop ",
642
+ self.lowerstop,
643
+ " exceeds nyquist frequency ",
644
+ nyquistlimit,
645
+ )
646
+ sys.exit()
647
+ if -1.0 < self.upperpass <= lowestfreq:
648
+ print(
649
+ "NoncausalFilter error: filter upper pass ",
650
+ self.upperpass,
651
+ " is below minimum frequency ",
652
+ lowestfreq,
653
+ )
654
+ sys.exit()
655
+ if -1.0 < self.upperstop <= lowestfreq:
656
+ print(
657
+ "NoncausalFilter error: filter upper stop ",
658
+ self.upperstop,
659
+ " is below minimum frequency ",
660
+ lowestfreq,
661
+ )
662
+ sys.exit()
663
+
664
+ # now look for fixable errors
665
+ if self.upperpass >= nyquistlimit:
666
+ if self.correctfreq:
667
+ self.upperpass = nyquistlimit
668
+ else:
669
+ print(
670
+ "NoncausalFilter error: filter upper pass ",
671
+ self.upperpass,
672
+ " exceeds nyquist frequency ",
673
+ nyquistlimit,
674
+ )
675
+ sys.exit()
676
+ if self.upperstop > nyquistlimit:
677
+ if self.correctfreq:
678
+ self.upperstop = nyquistlimit
679
+ else:
680
+ print(
681
+ "NoncausalFilter error: filter upper stop ",
682
+ self.upperstop,
683
+ " exceeds nyquist frequency ",
684
+ nyquistlimit,
685
+ )
686
+ sys.exit()
687
+ if self.lowerpass < lowestfreq:
688
+ if self.correctfreq:
689
+ self.lowerpass = lowestfreq
690
+ else:
691
+ print(
692
+ "NoncausalFilter error: filter lower pass ",
693
+ self.lowerpass,
694
+ " is below minimum frequency ",
695
+ lowestfreq,
696
+ )
697
+ sys.exit()
698
+ if self.lowerstop < lowestfreq:
699
+ if self.correctfreq:
700
+ self.lowerstop = lowestfreq
701
+ else:
702
+ print(
703
+ "NoncausalFilter error: filter lower stop ",
704
+ self.lowerstop,
705
+ " is below minimum frequency ",
706
+ lowestfreq,
707
+ )
708
+ sys.exit()
709
+
710
+ if self.padtime < 0.0:
711
+ padlen = int(len(data) // 2)
712
+ else:
713
+ padlen = int(self.padtime * Fs)
714
+ if self.lowerpass <= 0.0:
715
+ avlen = 1
716
+ else:
717
+ avlen = np.min([int(Fs / self.lowerpass), padlen])
718
+ if self.debug:
719
+ print("Fs=", Fs)
720
+ print("lowerstop=", self.lowerstop)
721
+ print("lowerpass=", self.lowerpass)
722
+ print("upperpass=", self.upperpass)
723
+ print("upperstop=", self.upperstop)
724
+ print("butterworthorder=", self.butterworthorder)
725
+ print("padtime=", self.padtime)
726
+ print("padlen=", padlen)
727
+ print("avlen=", avlen)
728
+ print("padtype=", self.padtype)
729
+
730
+ # now do the actual filtering
731
+ if self.filtertype == "None":
732
+ return data
733
+ elif self.filtertype == "ringstop":
734
+ return arb_pass(
735
+ Fs,
736
+ data,
737
+ 0.0,
738
+ 0.0,
739
+ Fs / 4.0,
740
+ 1.1 * Fs / 4.0,
741
+ transferfunc=self.transferfunc,
742
+ butterorder=self.butterworthorder,
743
+ padlen=padlen,
744
+ padtype=self.padtype,
745
+ debug=self.debug,
746
+ )
747
+ elif (
748
+ self.filtertype == "vlf"
749
+ or self.filtertype == "lfo"
750
+ or self.filtertype == "lfo_legacy"
751
+ or self.filtertype == "lfo_tight"
752
+ or self.filtertype == "resp"
753
+ or self.filtertype == "cardiac"
754
+ or self.filtertype == "hrv_ulf"
755
+ or self.filtertype == "hrv_vlf"
756
+ or self.filtertype == "hrv_lf"
757
+ or self.filtertype == "hrv_hf"
758
+ or self.filtertype == "hrv_vhf"
759
+ ):
760
+ return arb_pass(
761
+ Fs,
762
+ data,
763
+ self.lowerstop,
764
+ self.lowerpass,
765
+ self.upperpass,
766
+ self.upperstop,
767
+ transferfunc=self.transferfunc,
768
+ butterorder=self.butterworthorder,
769
+ padlen=padlen,
770
+ avlen=avlen,
771
+ padtype=self.padtype,
772
+ debug=self.debug,
773
+ )
774
+ elif (
775
+ self.filtertype == "vlf_stop"
776
+ or self.filtertype == "lfo_stop"
777
+ or self.filtertype == "lfo_legacy_stop"
778
+ or self.filtertype == "lfo_tight_stop"
779
+ or self.filtertype == "resp_stop"
780
+ or self.filtertype == "cardiac_stop"
781
+ or self.filtertype == "hrv_ulf_stop"
782
+ or self.filtertype == "hrv_vlf_stop"
783
+ or self.filtertype == "hrv_lf_stop"
784
+ or self.filtertype == "hrv_hf_stop"
785
+ or self.filtertype == "hrv_vhf_stop"
786
+ ):
787
+ return data - arb_pass(
788
+ Fs,
789
+ data,
790
+ self.lowerstop,
791
+ self.lowerpass,
792
+ self.upperpass,
793
+ self.upperstop,
794
+ transferfunc=self.transferfunc,
795
+ butterorder=self.butterworthorder,
796
+ padlen=padlen,
797
+ avlen=avlen,
798
+ padtype=self.padtype,
799
+ debug=self.debug,
800
+ )
801
+ elif self.filtertype == "arb":
802
+ return arb_pass(
803
+ Fs,
804
+ data,
805
+ self.arb_lowerstop,
806
+ self.arb_lowerpass,
807
+ self.arb_upperpass,
808
+ self.arb_upperstop,
809
+ transferfunc=self.transferfunc,
810
+ butterorder=self.butterworthorder,
811
+ padlen=padlen,
812
+ avlen=avlen,
813
+ padtype=self.padtype,
814
+ debug=self.debug,
815
+ )
816
+ elif self.filtertype == "arb_stop":
817
+ return data - arb_pass(
818
+ Fs,
819
+ data,
820
+ self.arb_lowerstop,
821
+ self.arb_lowerpass,
822
+ self.arb_upperpass,
823
+ self.arb_upperstop,
824
+ transferfunc=self.transferfunc,
825
+ butterorder=self.butterworthorder,
826
+ padlen=padlen,
827
+ avlen=avlen,
828
+ padtype=self.padtype,
829
+ debug=self.debug,
830
+ )
831
+ else:
832
+ print(f"bad filter type: {self.filtertype}")
833
+ sys.exit()
240
834
 
241
- debug : boolean, optional
242
- When True, internal states of the function will be printed to help debugging.
243
- :param debug:
835
+
836
+ @conditionaljit()
837
+ def padvec(
838
+ inputdata: NDArray,
839
+ padlen: int = 20,
840
+ avlen: int = 20,
841
+ padtype: str = "reflect",
842
+ debug: bool = False,
843
+ ) -> NDArray:
844
+ """
845
+ Returns a padded copy of the input data; padlen points of
846
+ filled data are prepended and appended to the input data to reduce
847
+ end effects when the data is then filtered. Filling can be "zero", "reflect", "cyclic", "constant",
848
+ or "constant+".
849
+
850
+ Parameters
851
+ ----------
852
+ inputdata : NDArray
853
+ An array of any numerical type.
854
+ padlen : int, optional
855
+ The number of points to add to each end. Default is 20.
856
+ avlen : int, optional
857
+ The number of points to average when doing "constant+" padding. Default is 20.
858
+ padtype : str, optional
859
+ Method for padding data on the ends of the vector. Options are "reflect", "zero", "cyclic",
860
+ "constant", or "constant+". Default is "reflect".
861
+ debug : bool, optional
862
+ If True, print debug information. Default is False.
863
+
864
+ Returns
865
+ -------
866
+ NDArray
867
+ The input data, with `padlen` reflected points added to each end.
868
+
869
+ Notes
870
+ -----
871
+ This function is useful for reducing edge effects when filtering data. The padding methods are as follows:
872
+ - "reflect": pads by reflecting the input array around its edges.
873
+ - "zero": pads with zeros.
874
+ - "cyclic": pads by cycling the input array.
875
+ - "constant": pads with the first/last value of the input array.
876
+ - "constant+": pads with the mean of the first/last `avlen` points of the input array.
877
+
878
+ Examples
879
+ --------
880
+ >>> import numpy as np
881
+ >>> data = np.array([1, 2, 3, 4, 5])
882
+ >>> padded = padvec(data, padlen=2, padtype="reflect")
883
+ >>> print(padded)
884
+ [3 2 1 2 3 4 5 5 4]
885
+
886
+ >>> padded = padvec(data, padlen=2, padtype="zero")
887
+ >>> print(padded)
888
+ [0 0 1 2 3 4 5 0 0]
889
+ """
890
+ if debug:
891
+ print(
892
+ "padvec: padlen=",
893
+ padlen,
894
+ ", avlen=",
895
+ avlen,
896
+ ", padtype=",
897
+ padtype,
898
+ "len(inputdata)=",
899
+ len(inputdata),
900
+ )
901
+ if padlen > len(inputdata):
902
+ raise RuntimeError(
903
+ f"ERROR: padlen ({padlen}) is greater than input data length ({len(inputdata)})"
904
+ )
905
+ if avlen > padlen:
906
+ avlen = padlen
907
+
908
+ inputdtype = inputdata.dtype
909
+ if padlen > 0:
910
+ if padtype == "reflect":
911
+ return np.concatenate(
912
+ (inputdata[::-1][-padlen:], inputdata, inputdata[::-1][0:padlen])
913
+ )
914
+ elif padtype == "zero":
915
+ return np.concatenate(
916
+ (
917
+ np.zeros((padlen), dtype=inputdtype),
918
+ inputdata,
919
+ np.zeros((padlen), dtype=inputdtype),
920
+ )
921
+ )
922
+ elif padtype == "cyclic":
923
+ return np.concatenate((inputdata[-padlen:], inputdata, inputdata[0:padlen]))
924
+ elif padtype == "constant":
925
+ return np.concatenate(
926
+ (
927
+ inputdata[0] * np.ones((padlen), dtype=inputdtype),
928
+ inputdata,
929
+ inputdata[-1] * np.ones((padlen), dtype=inputdtype),
930
+ )
931
+ )
932
+ elif padtype == "constant+":
933
+ startval = np.mean(inputdata[0:avlen])
934
+ endval = np.mean(inputdata[-avlen:])
935
+ return np.concatenate(
936
+ (
937
+ (startval * np.ones((padlen), dtype=inputdtype)).astype(inputdtype),
938
+ inputdata,
939
+ (endval * np.ones((padlen), dtype=inputdtype)).astype(inputdtype),
940
+ )
941
+ )
942
+ else:
943
+ raise ValueError(
944
+ "Padtype must be one of 'reflect', 'zero', 'cyclic', 'constant', or 'constant+'."
945
+ )
946
+ else:
947
+ return inputdata
948
+
949
+
950
+ @conditionaljit()
951
+ def unpadvec(inputdata: NDArray, padlen: int = 20) -> NDArray:
952
+ """
953
+ Returns input data with the end pads removed.
954
+
955
+ This function removes padding from both ends of an array. It is the inverse
956
+ operation of the `padvec` function, which adds padding to the array.
957
+
958
+ Parameters
959
+ ----------
960
+ inputdata : NDArray
961
+ An array of any numerical type.
962
+ padlen : int, optional
963
+ The number of points to remove from each end. Default is 20.
964
+
965
+ Returns
966
+ -------
967
+ NDArray
968
+ The input data with padding removed from both ends. If padlen is 0 or
969
+ negative, the original array is returned unchanged.
970
+
971
+ Notes
972
+ -----
973
+ When padlen is greater than 0, the function returns ``inputdata[padlen:-padlen]``.
974
+ If padlen is greater than or equal to the array length, an empty array will be returned.
975
+
976
+ Examples
977
+ --------
978
+ >>> import numpy as np
979
+ >>> data = np.array([1, 2, 3, 4, 5, 6, 7, 8])
980
+ >>> unpadvec(data, padlen=2)
981
+ array([3, 4, 5, 6])
982
+
983
+ >>> unpadvec(data, padlen=0)
984
+ array([1, 2, 3, 4, 5, 6, 7, 8])
985
+ """
986
+ if padlen > 0:
987
+ return inputdata[padlen:-padlen]
988
+ else:
989
+ return inputdata
990
+
991
+
992
+ def ssmooth(xsize: float, ysize: float, zsize: float, sigma: float, inputdata: NDArray) -> NDArray:
993
+ """
994
+ Applies an isotropic gaussian spatial filter to a 3D array.
995
+
996
+ This function applies a Gaussian filter to 3D spatial data with isotropic
997
+ filtering parameters. The filter kernel width is specified in spatial units
998
+ and is converted to pixel units based on the array spacing parameters.
999
+
1000
+ Parameters
1001
+ ----------
1002
+ xsize : float
1003
+ The array x step size in spatial units
1004
+ ysize : float
1005
+ The array y step size in spatial units
1006
+ zsize : float
1007
+ The array z step size in spatial units
1008
+ sigma : float
1009
+ The width of the gaussian filter kernel in spatial units
1010
+ inputdata : NDArray
1011
+ The spatial data to filter
244
1012
 
245
1013
  Returns
246
1014
  -------
247
- filtereddata : 1D float array
248
- The filtered data
1015
+ NDArray
1016
+ The filtered spatial data as a 3D float array
1017
+
1018
+ Notes
1019
+ -----
1020
+ The function uses `scipy.ndimage.gaussian_filter` internally, where the
1021
+ sigma parameters are calculated as `sigma / step_size` for each dimension.
1022
+ This ensures isotropic filtering when the same sigma value is used across
1023
+ all spatial dimensions.
1024
+
1025
+ Examples
1026
+ --------
1027
+ >>> import numpy as np
1028
+ >>> from scipy import ndimage
1029
+ >>> data = np.random.rand(10, 10, 10)
1030
+ >>> filtered = ssmooth(0.1, 0.1, 0.1, 0.2, data)
1031
+ >>> print(filtered.shape)
1032
+ (10, 10, 10)
1033
+ """
1034
+ return ndimage.gaussian_filter(inputdata, [sigma / xsize, sigma / ysize, sigma / zsize])
1035
+
249
1036
 
1037
+ # - butterworth filters
1038
+ # @conditionaljit()
1039
+ def dolpfiltfilt(
1040
+ Fs: float,
1041
+ upperpass: float,
1042
+ inputdata: NDArray,
1043
+ order: int,
1044
+ padlen: int = 20,
1045
+ avlen: int = 20,
1046
+ padtype: str = "reflect",
1047
+ debug: bool = False,
1048
+ ) -> NDArray:
1049
+ """
1050
+ Performs a bidirectional (zero phase) Butterworth lowpass filter on an input vector
1051
+ and returns the result. Ends are padded to reduce transients.
1052
+
1053
+ Parameters
1054
+ ----------
1055
+ Fs : float
1056
+ Sample rate in Hz.
1057
+ upperpass : float
1058
+ Upper end of passband in Hz.
1059
+ inputdata : NDArray
1060
+ Input data to be filtered.
1061
+ order : int
1062
+ Order of the Butterworth filter.
1063
+ padlen : int, optional
1064
+ Amount of points to reflect around each end of the input vector prior to filtering.
1065
+ Default is 20.
1066
+ avlen : int, optional
1067
+ Length of the averaging window used in padding. Default is 20.
1068
+ padtype : str, optional
1069
+ Type of padding to use. Options are 'reflect' or 'cyclic'. Default is 'reflect'.
1070
+ debug : bool, optional
1071
+ When True, internal states of the function will be printed to help debugging.
1072
+ Default is False.
1073
+
1074
+ Returns
1075
+ -------
1076
+ filtereddata : NDArray
1077
+ The filtered data as a 1D float array.
1078
+
1079
+ Notes
1080
+ -----
1081
+ This function applies a zero-phase Butterworth filter using `scipy.signal.filtfilt`,
1082
+ which eliminates phase distortion. Padding is applied before filtering to reduce
1083
+ edge effects.
1084
+
1085
+ Examples
1086
+ --------
1087
+ >>> import numpy as np
1088
+ >>> from scipy import signal
1089
+ >>> Fs = 100.0
1090
+ >>> upperpass = 20.0
1091
+ >>> data = np.random.randn(1000)
1092
+ >>> filtered = dolpfiltfilt(Fs, upperpass, data, order=4)
250
1093
  """
251
1094
  if upperpass > Fs / 2.0:
252
1095
  upperpass = Fs / 2.0
@@ -260,50 +1103,69 @@ def dolpfiltfilt(Fs, upperpass, inputdata, order, padlen=20, cyclic=False, debug
260
1103
  )
261
1104
  [b, a] = signal.butter(order, 2.0 * upperpass / Fs)
262
1105
  return unpadvec(
263
- signal.filtfilt(b, a, padvec(inputdata, padlen=padlen, cyclic=cyclic)).real,
1106
+ signal.filtfilt(
1107
+ b,
1108
+ a,
1109
+ padvec(inputdata, padlen=padlen, avlen=avlen, padtype=padtype, debug=debug),
1110
+ ).real,
264
1111
  padlen=padlen,
265
1112
  ).astype(np.float64)
266
1113
 
267
1114
 
268
1115
  # @conditionaljit()
269
- def dohpfiltfilt(Fs, lowerpass, inputdata, order, padlen=20, cyclic=False, debug=False):
270
- r"""Performs a bidirectional (zero phase) Butterworth highpass filter on an input vector
271
- and returns the result. Ends are padded to reduce transients.
1116
+ def dohpfiltfilt(
1117
+ Fs: float,
1118
+ lowerpass: float,
1119
+ inputdata: NDArray,
1120
+ order: int,
1121
+ padlen: int = 20,
1122
+ avlen: int = 20,
1123
+ padtype: str = "reflect",
1124
+ debug: bool = False,
1125
+ ) -> NDArray:
1126
+ """
1127
+ Performs a bidirectional (zero phase) Butterworth highpass filter on an input vector
1128
+ and returns the result. Ends are padded to reduce transients.
272
1129
 
273
1130
  Parameters
274
1131
  ----------
275
1132
  Fs : float
276
- Sample rate in Hz
277
- :param Fs:
278
-
1133
+ Sample rate in Hz.
279
1134
  lowerpass : float
280
- Lower end of passband in Hz
281
- :param lowerpass:
282
-
283
- inputdata : 1D numpy array
284
- Input data to be filtered
285
- :param inputdata:
286
-
1135
+ Lower end of passband in Hz.
1136
+ inputdata : NDArray
1137
+ Input signal to be filtered.
287
1138
  order : int
288
- Order of Butterworth filter.
289
- :param order:
290
-
1139
+ Order of the Butterworth filter.
291
1140
  padlen : int, optional
292
- Amount of points to reflect around each end of the input vector prior to filtering. Default is 20.
293
- :param padlen:
294
-
295
- cyclic : bool, optional
296
- If True, pad by wrapping the data in a cyclic manner rather than reflecting at the ends
297
- :param cyclic:
298
-
299
- debug : boolean, optional
300
- When True, internal states of the function will be printed to help debugging.
301
- :param debug:
1141
+ Amount of points to reflect around each end of the input vector prior to filtering.
1142
+ Default is 20.
1143
+ avlen : int, optional
1144
+ Length of the averaging window used in padding. Default is 20.
1145
+ padtype : str, optional
1146
+ Type of padding to use. Options are 'reflect' or 'wrap'. Default is 'reflect'.
1147
+ debug : bool, optional
1148
+ If True, internal states of the function will be printed to help debugging.
1149
+ Default is False.
302
1150
 
303
1151
  Returns
304
1152
  -------
305
- filtereddata : 1D float array
306
- The filtered data
1153
+ filtereddata : NDArray
1154
+ The filtered data with the same shape as inputdata.
1155
+
1156
+ Notes
1157
+ -----
1158
+ This function applies a zero-phase Butterworth highpass filter using `scipy.signal.filtfilt`,
1159
+ which ensures no phase distortion in the filtered signal. Padding is applied before filtering
1160
+ to minimize edge effects.
1161
+
1162
+ Examples
1163
+ --------
1164
+ >>> import numpy as np
1165
+ >>> from scipy import signal
1166
+ >>> Fs = 100.0
1167
+ >>> data = np.random.randn(1000)
1168
+ >>> filtered = dohpfiltfilt(Fs, 10.0, data, order=4, padlen=30)
307
1169
  """
308
1170
  if lowerpass < 0.0:
309
1171
  lowerpass = 0.0
@@ -317,54 +1179,71 @@ def dohpfiltfilt(Fs, lowerpass, inputdata, order, padlen=20, cyclic=False, debug
317
1179
  )
318
1180
  [b, a] = signal.butter(order, 2.0 * lowerpass / Fs, "highpass")
319
1181
  return unpadvec(
320
- signal.filtfilt(b, a, padvec(inputdata, padlen=padlen, cyclic=cyclic)).real,
1182
+ signal.filtfilt(
1183
+ b,
1184
+ a,
1185
+ padvec(inputdata, padlen=padlen, avlen=avlen, padtype=padtype, debug=debug),
1186
+ ).real,
321
1187
  padlen=padlen,
322
1188
  )
323
1189
 
324
1190
 
325
1191
  # @conditionaljit()
326
- def dobpfiltfilt(Fs, lowerpass, upperpass, inputdata, order, padlen=20, cyclic=False, debug=False):
327
- r"""Performs a bidirectional (zero phase) Butterworth bandpass filter on an input vector
328
- and returns the result. Ends are padded to reduce transients.
1192
+ def dobpfiltfilt(
1193
+ Fs: float,
1194
+ lowerpass: float,
1195
+ upperpass: float,
1196
+ inputdata: NDArray,
1197
+ order: int,
1198
+ padlen: int = 20,
1199
+ avlen: int = 20,
1200
+ padtype: str = "reflect",
1201
+ debug: bool = False,
1202
+ ) -> NDArray:
1203
+ """
1204
+ Performs a bidirectional (zero phase) Butterworth bandpass filter on an input vector
1205
+ and returns the result. Ends are padded to reduce transients.
329
1206
 
330
1207
  Parameters
331
1208
  ----------
332
1209
  Fs : float
333
- Sample rate in Hz
334
- :param Fs:
335
-
1210
+ Sample rate in Hz.
336
1211
  lowerpass : float
337
- Lower end of passband in Hz
338
- :param lowerpass:
339
-
1212
+ Lower end of passband in Hz.
340
1213
  upperpass : float
341
- Upper end of passband in Hz
342
- :param upperpass:
343
-
344
- inputdata : 1D numpy array
345
- Input data to be filtered
346
- :param inputdata:
347
-
1214
+ Upper end of passband in Hz.
1215
+ inputdata : NDArray
1216
+ Input data to be filtered.
348
1217
  order : int
349
- Order of Butterworth filter.
350
- :param order:
351
-
1218
+ Order of the Butterworth filter.
352
1219
  padlen : int, optional
353
- Amount of points to reflect around each end of the input vector prior to filtering. Default is 20.
354
- :param padlen:
355
-
356
- cyclic : bool, optional
357
- If True, pad by wrapping the data in a cyclic manner rather than reflecting at the ends
358
- :param cyclic:
359
-
360
- debug : boolean, optional
1220
+ Amount of points to reflect around each end of the input vector prior to filtering.
1221
+ Default is 20.
1222
+ avlen : int, optional
1223
+ Length of the averaging window used in padding. Default is 20.
1224
+ padtype : str, optional
1225
+ Type of padding to use. Options are 'reflect' or 'cyclic'. Default is 'reflect'.
1226
+ debug : bool, optional
361
1227
  When True, internal states of the function will be printed to help debugging.
362
- :param debug:
1228
+ Default is False.
363
1229
 
364
1230
  Returns
365
1231
  -------
366
- filtereddata : 1D float array
367
- The filtered data
1232
+ filtereddata : NDArray
1233
+ The filtered data as a 1D float array.
1234
+
1235
+ Notes
1236
+ -----
1237
+ This function applies a zero-phase Butterworth bandpass filter using `scipy.signal.filtfilt`,
1238
+ which eliminates phase distortion. Padding is applied before filtering to reduce edge effects.
1239
+
1240
+ Examples
1241
+ --------
1242
+ >>> import numpy as np
1243
+ >>> from scipy import signal
1244
+ >>> Fs = 100.0
1245
+ >>> data = np.random.randn(1000)
1246
+ >>> filtered = dobpfiltfilt(Fs, 10.0, 30.0, data, order=4, padlen=30)
368
1247
  """
369
1248
  if upperpass > Fs / 2.0:
370
1249
  upperpass = Fs / 2.0
@@ -381,63 +1260,101 @@ def dobpfiltfilt(Fs, lowerpass, upperpass, inputdata, order, padlen=20, cyclic=F
381
1260
  )
382
1261
  [b, a] = signal.butter(order, [2.0 * lowerpass / Fs, 2.0 * upperpass / Fs], "bandpass")
383
1262
  return unpadvec(
384
- signal.filtfilt(b, a, padvec(inputdata, padlen=padlen, cyclic=cyclic)).real,
1263
+ signal.filtfilt(
1264
+ b,
1265
+ a,
1266
+ padvec(inputdata, padlen=padlen, avlen=avlen, padtype=padtype, debug=debug),
1267
+ ).real,
385
1268
  padlen=padlen,
386
1269
  )
387
1270
 
388
1271
 
389
1272
  # - direct filter with specified transfer function
390
- def transferfuncfilt(inputdata, transferfunc):
391
- r"""Filters input data using a previously calculated transfer function.
1273
+ def transferfuncfilt(inputdata: NDArray, transferfunc: NDArray) -> NDArray:
1274
+ """
1275
+ Filters input data using a previously calculated transfer function.
1276
+
1277
+ This function applies frequency domain filtering by multiplying the input data's
1278
+ Fourier transform with the transfer function, then transforms back to the time domain.
392
1279
 
393
1280
  Parameters
394
1281
  ----------
395
- inputdata : 1D float array
396
- Input data to be filtered
397
- :param inputdata:
398
-
399
- transferfunc : 1D float array
400
- The transfer function
401
- :param transferfunc:
1282
+ inputdata : NDArray
1283
+ Input data to be filtered, array of real or complex values
1284
+ transferfunc : NDArray
1285
+ The transfer function, array of real or complex values with same length as inputdata
402
1286
 
403
1287
  Returns
404
1288
  -------
405
- filtereddata : 1D float array
406
- Filtered input data
1289
+ NDArray
1290
+ Filtered input data as a 1D float array
1291
+
1292
+ Notes
1293
+ -----
1294
+ The filtering is performed in the frequency domain using the convolution theorem.
1295
+ The transfer function should be designed to match the frequency response of the desired filter.
1296
+
1297
+ Examples
1298
+ --------
1299
+ >>> import numpy as np
1300
+ >>> from scipy import fftpack
1301
+ >>> # Create sample data
1302
+ >>> data = np.random.randn(1024)
1303
+ >>> # Create a simple low-pass filter transfer function
1304
+ >>> freq = np.fft.fftfreq(len(data), 1.0)
1305
+ >>> tf = np.abs(freq) < 0.1
1306
+ >>> # Apply filter
1307
+ >>> filtered_data = transferfuncfilt(data, tf)
407
1308
  """
408
1309
  inputdata_trans = transferfunc * fftpack.fft(inputdata)
409
1310
  return fftpack.ifft(inputdata_trans).real
410
1311
 
411
1312
 
412
1313
  # - fft brickwall filters
413
- def getlpfftfunc(Fs, upperpass, inputdata, debug=False):
414
- r"""Generates a brickwall lowpass transfer function.
1314
+ def getlpfftfunc(Fs: float, upperpass: float, inputdata: NDArray, debug: bool = False) -> NDArray:
1315
+ """
1316
+ Generates a brickwall lowpass transfer function.
1317
+
1318
+ This function creates a transfer function that acts as a brickwall lowpass filter
1319
+ by setting frequencies above the cutoff to zero. The filter is designed in the
1320
+ frequency domain using the FFT domain representation.
415
1321
 
416
1322
  Parameters
417
1323
  ----------
418
1324
  Fs : float
419
1325
  Sample rate in Hz
420
- :param Fs:
421
-
422
1326
  upperpass : float
423
1327
  Upper end of passband in Hz
424
- :param upperpass:
425
-
426
1328
  inputdata : 1D numpy array
427
1329
  Input data to be filtered
428
- :param inputdata:
429
-
430
- debug : boolean, optional
1330
+ debug : bool, optional
431
1331
  When True, internal states of the function will be printed to help debugging.
432
- :param debug:
1332
+ Default is False.
433
1333
 
434
1334
  Returns
435
1335
  -------
436
1336
  transferfunc : 1D float array
437
- The transfer function
1337
+ The transfer function with the lowpass filter characteristics
1338
+
1339
+ Notes
1340
+ -----
1341
+ The function creates a transfer function where frequencies below the cutoff
1342
+ are set to 1.0 and frequencies above the cutoff are set to 0.0. The cutoff
1343
+ frequency is determined by the ratio of upperpass to Fs, converted to bin
1344
+ indices in the FFT domain.
1345
+
1346
+ Examples
1347
+ --------
1348
+ >>> import numpy as np
1349
+ >>> Fs = 100.0
1350
+ >>> upperpass = 20.0
1351
+ >>> inputdata = np.random.rand(100)
1352
+ >>> transfer_func = getlpfftfunc(Fs, upperpass, inputdata)
1353
+ >>> print(transfer_func.shape)
1354
+ (100,)
438
1355
  """
439
1356
  transferfunc = np.ones(np.shape(inputdata), dtype=np.float64)
440
- cutoffbin = int((upperpass / Fs) * np.shape(transferfunc)[0])
1357
+ cutoffbin = int((upperpass / Fs) * len(transferfunc))
441
1358
  if debug:
442
1359
  print(
443
1360
  "getlpfftfunc - Fs, upperpass, len(inputdata):",
@@ -450,42 +1367,60 @@ def getlpfftfunc(Fs, upperpass, inputdata, debug=False):
450
1367
 
451
1368
 
452
1369
  # @conditionaljit()
453
- def dolpfftfilt(Fs, upperpass, inputdata, padlen=20, cyclic=False, debug=False):
454
- r"""Performs an FFT brickwall lowpass filter on an input vector
455
- and returns the result. Ends are padded to reduce transients.
1370
+ def dolpfftfilt(
1371
+ Fs: float,
1372
+ upperpass: float,
1373
+ inputdata: NDArray,
1374
+ padlen: int = 20,
1375
+ avlen: int = 20,
1376
+ padtype: str = "reflect",
1377
+ debug: bool = False,
1378
+ ) -> NDArray:
1379
+ """
1380
+ Performs an FFT brickwall lowpass filter on an input vector and returns the result.
1381
+ Ends are padded to reduce transients.
456
1382
 
457
1383
  Parameters
458
1384
  ----------
459
1385
  Fs : float
460
- Sample rate in Hz
461
- :param Fs:
462
-
1386
+ Sample rate in Hz.
463
1387
  upperpass : float
464
- Upper end of passband in Hz
465
- :param upperpass:
466
-
467
- inputdata : 1D numpy array
468
- Input data to be filtered
469
- :param inputdata:
470
-
1388
+ Upper end of passband in Hz.
1389
+ inputdata : NDArray
1390
+ Input data to be filtered, expected as a 1D numpy array.
471
1391
  padlen : int, optional
472
- Amount of points to reflect around each end of the input vector prior to filtering. Default is 20.
473
- :param padlen:
474
-
475
- cyclic : bool, optional
476
- If True, pad by wrapping the data in a cyclic manner rather than reflecting at the ends
477
- :param cyclic:
478
-
479
- debug : boolean, optional
1392
+ Amount of points to reflect around each end of the input vector prior to filtering.
1393
+ Default is 20.
1394
+ avlen : int, optional
1395
+ Length of the averaging window used in padding. Default is 20.
1396
+ padtype : str, optional
1397
+ Type of padding to use. Options are 'reflect' or 'wrap'. Default is 'reflect'.
1398
+ debug : bool, optional
480
1399
  When True, internal states of the function will be printed to help debugging.
481
- :param debug:
1400
+ Default is False.
482
1401
 
483
1402
  Returns
484
1403
  -------
485
- filtereddata : 1D float array
486
- The filtered data
1404
+ NDArray
1405
+ The filtered data as a 1D float array.
1406
+
1407
+ Notes
1408
+ -----
1409
+ This function applies a lowpass filter in the frequency domain using FFT. The input signal
1410
+ is padded at both ends to minimize edge effects caused by the filtering process.
1411
+ The padding is performed using the `padvec` function, and the inverse FFT is used to
1412
+ transform the filtered signal back to the time domain.
1413
+
1414
+ Examples
1415
+ --------
1416
+ >>> import numpy as np
1417
+ >>> from scipy import fftpack
1418
+ >>> Fs = 100.0
1419
+ >>> upperpass = 20.0
1420
+ >>> data = np.random.randn(1000)
1421
+ >>> filtered_data = dolpfftfilt(Fs, upperpass, data)
487
1422
  """
488
- padinputdata = padvec(inputdata, padlen=padlen, cyclic=cyclic)
1423
+ padinputdata = padvec(inputdata, padlen=padlen, avlen=avlen, padtype=padtype, debug=debug)
489
1424
  inputdata_trans = fftpack.fft(padinputdata)
490
1425
  transferfunc = getlpfftfunc(Fs, upperpass, padinputdata, debug=debug)
491
1426
  inputdata_trans *= transferfunc
@@ -493,42 +1428,58 @@ def dolpfftfilt(Fs, upperpass, inputdata, padlen=20, cyclic=False, debug=False):
493
1428
 
494
1429
 
495
1430
  # @conditionaljit()
496
- def dohpfftfilt(Fs, lowerpass, inputdata, padlen=20, cyclic=False, debug=False):
497
- r"""Performs an FFT brickwall highpass filter on an input vector
498
- and returns the result. Ends are padded to reduce transients.
1431
+ def dohpfftfilt(
1432
+ Fs: float,
1433
+ lowerpass: float,
1434
+ inputdata: NDArray,
1435
+ padlen: int = 20,
1436
+ avlen: int = 20,
1437
+ padtype: str = "reflect",
1438
+ debug: bool = False,
1439
+ ) -> NDArray:
1440
+ """
1441
+ Performs an FFT brickwall highpass filter on an input vector and returns the result.
1442
+ Ends are padded to reduce transients.
499
1443
 
500
1444
  Parameters
501
1445
  ----------
502
1446
  Fs : float
503
- Sample rate in Hz
504
- :param Fs:
505
-
1447
+ Sample rate in Hz.
506
1448
  lowerpass : float
507
- Lower end of passband in Hz
508
- :param lowerpass:
509
-
510
- inputdata : 1D numpy array
511
- Input data to be filtered
512
- :param inputdata:
513
-
1449
+ Lower end of passband in Hz.
1450
+ inputdata : NDArray
1451
+ Input data to be filtered, expected as a 1D numpy array.
514
1452
  padlen : int, optional
515
- Amount of points to reflect around each end of the input vector prior to filtering. Default is 20.
516
- :param padlen:
517
-
518
- cyclic : bool, optional
519
- If True, pad by wrapping the data in a cyclic manner rather than reflecting at the ends
520
- :param cyclic:
521
-
522
- debug : boolean, optional
1453
+ Amount of points to reflect around each end of the input vector prior to filtering.
1454
+ Default is 20.
1455
+ avlen : int, optional
1456
+ Length of the averaging window used in padding. Default is 20.
1457
+ padtype : str, optional
1458
+ Type of padding to use. Options are 'reflect' or 'cyclic'. Default is 'reflect'.
1459
+ debug : bool, optional
523
1460
  When True, internal states of the function will be printed to help debugging.
524
- :param debug:
1461
+ Default is False.
525
1462
 
526
1463
  Returns
527
1464
  -------
528
- filtereddata : 1D float array
529
- The filtered data
1465
+ NDArray
1466
+ The filtered data as a 1D float array.
1467
+
1468
+ Notes
1469
+ -----
1470
+ This function applies a highpass filter in the frequency domain using FFT.
1471
+ The input signal is first padded to minimize edge effects, then transformed
1472
+ into the frequency domain, filtered, and inverse transformed back to the time domain.
1473
+
1474
+ Examples
1475
+ --------
1476
+ >>> import numpy as np
1477
+ >>> Fs = 100.0
1478
+ >>> lowerpass = 10.0
1479
+ >>> data = np.random.randn(1000)
1480
+ >>> filtered = dohpfftfilt(Fs, lowerpass, data)
530
1481
  """
531
- padinputdata = padvec(inputdata, padlen=padlen, cyclic=cyclic)
1482
+ padinputdata = padvec(inputdata, padlen=padlen, avlen=avlen, padtype=padtype, debug=debug)
532
1483
  inputdata_trans = fftpack.fft(padinputdata)
533
1484
  transferfunc = 1.0 - getlpfftfunc(Fs, lowerpass, padinputdata, debug=debug)
534
1485
  inputdata_trans *= transferfunc
@@ -536,46 +1487,65 @@ def dohpfftfilt(Fs, lowerpass, inputdata, padlen=20, cyclic=False, debug=False):
536
1487
 
537
1488
 
538
1489
  # @conditionaljit()
539
- def dobpfftfilt(Fs, lowerpass, upperpass, inputdata, padlen=20, cyclic=False, debug=False):
540
- r"""Performs an FFT brickwall bandpass filter on an input vector
541
- and returns the result. Ends are padded to reduce transients.
1490
+ def dobpfftfilt(
1491
+ Fs: float,
1492
+ lowerpass: float,
1493
+ upperpass: float,
1494
+ inputdata: NDArray,
1495
+ padlen: int = 20,
1496
+ avlen: int = 20,
1497
+ padtype: str = "reflect",
1498
+ debug: bool = False,
1499
+ ) -> NDArray:
1500
+ """
1501
+ Performs an FFT brickwall bandpass filter on an input vector and returns the result.
1502
+ Ends are padded to reduce transients.
542
1503
 
543
1504
  Parameters
544
1505
  ----------
545
1506
  Fs : float
546
- Sample rate in Hz
547
- :param Fs:
548
-
1507
+ Sample rate in Hz.
549
1508
  lowerpass : float
550
- Lower end of passband in Hz
551
- :param lowerpass:
552
-
1509
+ Lower end of passband in Hz.
553
1510
  upperpass : float
554
- Upper end of passband in Hz
555
- :param upperpass:
556
-
557
- inputdata : 1D numpy array
558
- Input data to be filtered
559
- :param inputdata:
560
-
1511
+ Upper end of passband in Hz.
1512
+ inputdata : NDArray
1513
+ Input data to be filtered, expected as a 1D numpy array.
561
1514
  padlen : int, optional
562
- Amount of points to reflect around each end of the input vector prior to filtering. Default is 20.
563
- :param padlen:
564
-
565
- cyclic : bool, optional
566
- If True, pad by wrapping the data in a cyclic manner rather than reflecting at the ends
567
- :param cyclic:
568
-
569
- debug : boolean, optional
1515
+ Amount of points to reflect around each end of the input vector prior to filtering.
1516
+ Default is 20.
1517
+ avlen : int, optional
1518
+ Length of averaging window for padding; used only if `padtype` is 'mean'.
1519
+ Default is 20.
1520
+ padtype : str, optional
1521
+ Type of padding to use. Options are 'reflect', 'mean', or 'wrap'.
1522
+ Default is 'reflect'.
1523
+ debug : bool, optional
570
1524
  When True, internal states of the function will be printed to help debugging.
571
- :param debug:
1525
+ Default is False.
572
1526
 
573
1527
  Returns
574
1528
  -------
575
- filtereddata : 1D float array
576
- The filtered data
1529
+ NDArray
1530
+ The filtered data as a 1D float array, with the same shape as inputdata.
1531
+
1532
+ Notes
1533
+ -----
1534
+ This function applies a brickwall bandpass filter in the frequency domain using FFT.
1535
+ The input signal is first padded to minimize edge effects, then transformed into
1536
+ the frequency domain, filtered, and transformed back. Padding is applied using
1537
+ the specified `padtype` and `padlen`.
1538
+
1539
+ Examples
1540
+ --------
1541
+ >>> import numpy as np
1542
+ >>> from scipy import signal
1543
+ >>> fs = 100.0
1544
+ >>> t = np.linspace(0, 1, int(fs), endpoint=False)
1545
+ >>> x = np.sin(2 * np.pi * 10 * t) + 0.5 * np.sin(2 * np.pi * 25 * t)
1546
+ >>> filtered = dobpfftfilt(fs, 5, 15, x, padlen=30)
577
1547
  """
578
- padinputdata = padvec(inputdata, padlen=padlen, cyclic=cyclic)
1548
+ padinputdata = padvec(inputdata, padlen=padlen, avlen=avlen, padtype=padtype, debug=debug)
579
1549
  inputdata_trans = fftpack.fft(padinputdata)
580
1550
  transferfunc = getlpfftfunc(Fs, upperpass, padinputdata, debug=debug) * (
581
1551
  1.0 - getlpfftfunc(Fs, lowerpass, padinputdata, debug=debug)
@@ -586,35 +1556,56 @@ def dobpfftfilt(Fs, lowerpass, upperpass, inputdata, padlen=20, cyclic=False, de
586
1556
 
587
1557
  # - fft trapezoidal filters
588
1558
  # @conditionaljit()
589
- def getlptrapfftfunc(Fs, upperpass, upperstop, inputdata, debug=False):
590
- r"""Generates a trapezoidal lowpass transfer function.
1559
+ def getlptrapfftfunc(
1560
+ Fs: float, upperpass: float, upperstop: float, inputdata: NDArray, debug: bool = False
1561
+ ) -> NDArray:
1562
+ """
1563
+ Generate a trapezoidal lowpass transfer function for filtering.
1564
+
1565
+ This function creates a transfer function with a trapezoidal transition band
1566
+ between the passband and stopband, suitable for use in spectral filtering
1567
+ operations. The resulting transfer function can be applied to frequency-domain
1568
+ data to perform lowpass filtering.
591
1569
 
592
1570
  Parameters
593
1571
  ----------
594
1572
  Fs : float
595
- Sample rate in Hz
596
- :param Fs:
597
-
1573
+ Sample rate in Hz.
598
1574
  upperpass : float
599
- Upper end of passband in Hz
600
- :param upperpass:
601
-
1575
+ Upper edge of the passband in Hz.
602
1576
  upperstop : float
603
- Lower end of stopband in Hz
604
- :param upperstop:
605
-
606
- inputdata : 1D numpy array
607
- Input data to be filtered
608
- :param inputdata:
609
-
610
- debug : boolean, optional
611
- When True, internal states of the function will be printed to help debugging.
612
- :param debug:
1577
+ Lower edge of the stopband in Hz.
1578
+ inputdata : NDArray
1579
+ Input data array (typically frequency domain data) to determine the
1580
+ length and shape of the transfer function.
1581
+ debug : bool, optional
1582
+ If True, print internal state information for debugging purposes.
1583
+ Default is False.
613
1584
 
614
1585
  Returns
615
1586
  -------
616
- transferfunc : 1D float array
617
- The transfer function
1587
+ NDArray
1588
+ A 1D float array representing the transfer function. The array has the
1589
+ same length as `inputdata` and contains values between 0 and 1,
1590
+ indicating the attenuation at each frequency bin.
1591
+
1592
+ Notes
1593
+ -----
1594
+ The transition from passband to stopband is linear (trapezoidal), with
1595
+ the transition region defined between `upperpass` and `upperstop`.
1596
+ The function assumes that `upperpass < upperstop` and that both are
1597
+ within the Nyquist frequency range (0 to Fs/2).
1598
+
1599
+ Examples
1600
+ --------
1601
+ >>> import numpy as np
1602
+ >>> Fs = 100.0
1603
+ >>> upperpass = 20.0
1604
+ >>> upperstop = 30.0
1605
+ >>> inputdata = np.zeros(100)
1606
+ >>> tf = getlptrapfftfunc(Fs, upperpass, upperstop, inputdata)
1607
+ >>> print(tf.shape)
1608
+ (100,)
618
1609
  """
619
1610
  transferfunc = np.ones(np.shape(inputdata), dtype="float64")
620
1611
  passbin = int((upperpass / Fs) * np.shape(transferfunc)[0])
@@ -639,7 +1630,64 @@ def getlptrapfftfunc(Fs, upperpass, upperstop, inputdata, debug=False):
639
1630
 
640
1631
 
641
1632
  # @conditionaljit()
642
- def getlptransfunc(Fs, inputdata, upperpass=None, upperstop=None, type="brickwall", debug=False):
1633
+ def getlptransfunc(
1634
+ Fs: float,
1635
+ inputdata: NDArray,
1636
+ upperpass: Optional[float] = None,
1637
+ upperstop: Optional[float] = None,
1638
+ type: str = "brickwall",
1639
+ debug: bool = False,
1640
+ ) -> NDArray:
1641
+ """
1642
+ Compute the low-pass transfer function for a given input signal.
1643
+
1644
+ This function generates a transfer function based on the specified type
1645
+ (brickwall, Gaussian, or trapezoidal) to be used for filtering purposes.
1646
+ The transfer function is applied in the frequency domain to filter the input data.
1647
+
1648
+ Parameters
1649
+ ----------
1650
+ Fs : float
1651
+ Sampling frequency of the input signal in Hz.
1652
+ inputdata : NDArray
1653
+ Input signal data, used to determine the length of the transfer function.
1654
+ upperpass : float, optional
1655
+ Upper passband frequency in Hz. Must be specified.
1656
+ upperstop : float, optional
1657
+ Upper stopband frequency in Hz. Only used for 'trapezoidal' type.
1658
+ Defaults to 1.05 * upperpass if not specified.
1659
+ type : str, optional
1660
+ Type of transfer function to generate. Options are:
1661
+ - "brickwall": Ideal low-pass filter with sharp cutoff.
1662
+ - "gaussian": Gaussian-shaped transition.
1663
+ - "trapezoidal": Trapezoidal transition between pass and stop bands.
1664
+ Default is "brickwall".
1665
+ debug : bool, optional
1666
+ If True, prints debug information and displays the transfer function plot.
1667
+ Default is False.
1668
+
1669
+ Returns
1670
+ -------
1671
+ NDArray
1672
+ The computed low-pass transfer function with the same shape as `inputdata`.
1673
+
1674
+ Notes
1675
+ -----
1676
+ - For 'brickwall' type, the transfer function is 1.0 in the passband and 0.0 in the stopband.
1677
+ - For 'gaussian' type, the transition is smoothed using a Gaussian function.
1678
+ - For 'trapezoidal' type, a linear transition is applied between pass and stop bands.
1679
+ - The function uses the sampling frequency `Fs` to map frequencies to the normalized frequency axis.
1680
+
1681
+ Examples
1682
+ --------
1683
+ >>> import numpy as np
1684
+ >>> input_signal = np.random.rand(1024)
1685
+ >>> Fs = 100.0
1686
+ >>> upperpass = 20.0
1687
+ >>> tf = getlptransfunc(Fs, input_signal, upperpass, type='gaussian')
1688
+ >>> print(tf.shape)
1689
+ (1024,)
1690
+ """
643
1691
  if upperpass is None:
644
1692
  print("getlptransfunc: upperpass must be specified")
645
1693
  sys.exit()
@@ -703,7 +1751,55 @@ def getlptransfunc(Fs, inputdata, upperpass=None, upperstop=None, type="brickwal
703
1751
  return transferfunc
704
1752
 
705
1753
 
706
- def gethptransfunc(Fs, inputdata, lowerstop=None, lowerpass=None, type="brickwall", debug=False):
1754
+ def gethptransfunc(
1755
+ Fs: float,
1756
+ inputdata: NDArray,
1757
+ lowerstop: Optional[float] = None,
1758
+ lowerpass: Optional[float] = None,
1759
+ type: str = "brickwall",
1760
+ debug: bool = False,
1761
+ ) -> NDArray:
1762
+ """
1763
+ Compute high-pass transfer function from low-pass transfer function.
1764
+
1765
+ This function generates a high-pass transfer function by subtracting a
1766
+ low-pass transfer function from unity. The low-pass function is computed
1767
+ using the `getlptransfunc` function with appropriate parameters.
1768
+
1769
+ Parameters
1770
+ ----------
1771
+ Fs : float
1772
+ Sampling frequency in Hz.
1773
+ inputdata : NDArray
1774
+ Input data array used for transfer function computation.
1775
+ lowerstop : float, optional
1776
+ Lower stop frequency for trapezoidal filter type. Required for
1777
+ trapezoidal type, ignored for brickwall type.
1778
+ lowerpass : float, optional
1779
+ Lower pass frequency (cutoff frequency) for the high-pass filter.
1780
+ Must be specified.
1781
+ type : str, default="brickwall"
1782
+ Type of filter transfer function. Options are "brickwall" or "trapezoidal".
1783
+ debug : bool, default=False
1784
+ If True, enables debug output during computation.
1785
+
1786
+ Returns
1787
+ -------
1788
+ NDArray
1789
+ High-pass transfer function array with same shape as inputdata.
1790
+
1791
+ Notes
1792
+ -----
1793
+ For trapezoidal filter type, the lower stop frequency is used as the
1794
+ upper pass frequency for the underlying low-pass function.
1795
+
1796
+ Examples
1797
+ --------
1798
+ >>> import numpy as np
1799
+ >>> Fs = 100.0
1800
+ >>> data = np.linspace(0, 1, 100)
1801
+ >>> hp_func = gethptransfunc(Fs, data, lowerpass=10.0, type="brickwall")
1802
+ """
707
1803
  if lowerpass is None:
708
1804
  print("gethptransfunc: lowerpass must be specified")
709
1805
  sys.exit()
@@ -723,52 +1819,69 @@ def gethptransfunc(Fs, inputdata, lowerstop=None, lowerpass=None, type="brickwal
723
1819
  return transferfunc
724
1820
 
725
1821
 
726
- # @conditionaljit()
727
- def dolptransfuncfilt(
728
- Fs,
729
- inputdata,
730
- upperpass=None,
731
- upperstop=None,
732
- type="brickwall",
733
- padlen=20,
734
- cyclic=False,
735
- debug=False,
736
- ):
737
- r"""Performs an FFT filter with a gaussian lowpass transfer
738
- function on an input vector and returns the result. Ends are padded to reduce transients.
739
-
740
- Parameters
741
- ----------
742
- Fs : float
743
- Sample rate in Hz
744
- :param Fs:
745
-
746
- upperpass : float
747
- Upper end of passband in Hz
748
- :param upperpass:
749
-
750
- inputdata : 1D numpy array
751
- Input data to be filtered
752
- :param inputdata:
753
-
754
- padlen : int, optional
755
- Amount of points to reflect around each end of the input vector prior to filtering. Default is 20.
756
- :param padlen:
757
-
758
- cyclic : bool, optional
759
- If True, pad by wrapping the data in a cyclic manner rather than reflecting at the ends
760
- :param cyclic:
1822
+ # @conditionaljit()
1823
+ def dolptransfuncfilt(
1824
+ Fs: float,
1825
+ inputdata: NDArray,
1826
+ upperpass: Optional[float] = None,
1827
+ upperstop: Optional[float] = None,
1828
+ type: str = "brickwall",
1829
+ padlen: int = 20,
1830
+ avlen: int = 20,
1831
+ padtype: str = "reflect",
1832
+ debug: bool = False,
1833
+ ) -> NDArray:
1834
+ """
1835
+ Performs an FFT filter with a Gaussian lowpass transfer function on an input vector
1836
+ and returns the result. Ends are padded to reduce transients.
761
1837
 
762
- debug : boolean, optional
1838
+ Parameters
1839
+ ----------
1840
+ Fs : float
1841
+ Sample rate in Hz.
1842
+ inputdata : NDArray
1843
+ Input data to be filtered, expected as a 1D array.
1844
+ upperpass : float, optional
1845
+ Upper end of the passband in Hz. If not specified, the filter will use a default
1846
+ value based on the data characteristics.
1847
+ upperstop : float, optional
1848
+ Upper end of the stopband in Hz. If not specified, the filter will use a default
1849
+ value based on the data characteristics.
1850
+ type : str, optional
1851
+ Type of transfer function to use. Default is "brickwall". Other options may include
1852
+ "gaussian", "butterworth", etc., depending on implementation of `getlptransfunc`.
1853
+ padlen : int, optional
1854
+ Amount of points to reflect around each end of the input vector prior to filtering.
1855
+ Default is 20.
1856
+ avlen : int, optional
1857
+ Length of the averaging window used in padding. Default is 20.
1858
+ padtype : str, optional
1859
+ Type of padding to use. Options include "reflect", "wrap", "constant", etc.
1860
+ Default is "reflect".
1861
+ debug : bool, optional
763
1862
  When True, internal states of the function will be printed to help debugging.
764
- :param debug:
1863
+ A plot of the transfer function will be displayed if debug is enabled.
765
1864
 
766
1865
  Returns
767
1866
  -------
768
- filtereddata : 1D float array
769
- The filtered data
1867
+ NDArray
1868
+ The filtered data as a 1D float array of the same length as inputdata.
1869
+
1870
+ Notes
1871
+ -----
1872
+ This function applies a frequency-domain filter by computing the FFT of the padded input,
1873
+ applying a transfer function, and then inverse transforming the result. Padding is applied
1874
+ to reduce edge effects caused by the FFT.
1875
+
1876
+ Examples
1877
+ --------
1878
+ >>> import numpy as np
1879
+ >>> Fs = 100.0
1880
+ >>> t = np.linspace(0, 1, int(Fs), endpoint=False)
1881
+ >>> signal = np.sin(2 * np.pi * 10 * t)
1882
+ >>> filtered = dolptransfuncfilt(Fs, signal, upperpass=20.0)
770
1883
  """
771
- padinputdata = padvec(inputdata, padlen=padlen, cyclic=cyclic)
1884
+ padinputdata = padvec(inputdata, padlen=padlen, avlen=avlen, padtype=padtype, debug=debug)
772
1885
  inputdata_trans = fftpack.fft(padinputdata)
773
1886
  transferfunc = getlptransfunc(
774
1887
  Fs, padinputdata, upperpass=upperpass, upperstop=upperstop, type=type
@@ -780,7 +1893,7 @@ def dolptransfuncfilt(
780
1893
  )
781
1894
  fig = plt.figure()
782
1895
  ax = fig.add_subplot(111)
783
- ax.set_title("LP Transfer function - " + type + ", upperpass={:.2f}".format(upperpass))
1896
+ ax.set_title(f"LP Transfer function - {type}, upperpass={upperpass:.2f}")
784
1897
  plt.plot(freqaxis, transferfunc)
785
1898
  plt.show()
786
1899
  inputdata_trans *= transferfunc
@@ -789,56 +1902,65 @@ def dolptransfuncfilt(
789
1902
 
790
1903
  # @conditionaljit()
791
1904
  def dohptransfuncfilt(
792
- Fs,
793
- inputdata,
794
- lowerstop=None,
795
- lowerpass=None,
796
- type="brickwall",
797
- padlen=20,
798
- cyclic=False,
799
- debug=False,
800
- ):
801
- r"""Performs an FFT filter with a trapezoidal highpass transfer
802
- function on an input vector and returns the result. Ends are padded to reduce transients.
1905
+ Fs: float,
1906
+ inputdata: NDArray,
1907
+ lowerpass: float,
1908
+ lowerstop: Optional[float | None] = None,
1909
+ type: str = "brickwall",
1910
+ padlen: int = 20,
1911
+ avlen: int = 20,
1912
+ padtype: str = "reflect",
1913
+ debug: bool = False,
1914
+ ) -> NDArray:
1915
+ """
1916
+ Performs an FFT filter with a trapezoidal highpass transfer function on an input vector
1917
+ and returns the result. Ends are padded to reduce transients.
803
1918
 
804
1919
  Parameters
805
1920
  ----------
806
1921
  Fs : float
807
- Sample rate in Hz
808
- :param Fs:
809
-
810
- lowerstop : float
811
- Upper end of stopband in Hz
812
- :param lowerstop:
813
-
1922
+ Sample rate in Hz.
1923
+ inputdata : NDArray
1924
+ Input data to be filtered.
814
1925
  lowerpass : float
815
- Lower end of passband in Hz
816
- :param lowerpass:
817
-
818
- inputdata : 1D numpy array
819
- Input data to be filtered
820
- :param inputdata:
821
-
1926
+ Lower end of the passband in Hz.
1927
+ lowerstop : float, optional
1928
+ Upper end of the stopband in Hz. If not provided, it is set to `lowerpass / 1.05`.
1929
+ type : str, optional
1930
+ Type of transfer function to use. Default is "brickwall".
822
1931
  padlen : int, optional
823
- Amount of points to reflect around each end of the input vector prior to filtering. Default is 20.
824
- :param padlen:
825
-
826
- cyclic : bool, optional
827
- If True, pad by wrapping the data in a cyclic manner rather than reflecting at the ends
828
- :param cyclic:
829
-
830
- debug : boolean, optional
1932
+ Amount of points to reflect around each end of the input vector prior to filtering.
1933
+ Default is 20.
1934
+ avlen : int, optional
1935
+ Length of the averaging window used in padding. Default is 20.
1936
+ padtype : str, optional
1937
+ Type of padding to use. Options are "reflect" or "wrap". Default is "reflect".
1938
+ debug : bool, optional
831
1939
  When True, internal states of the function will be printed to help debugging.
832
- :param debug:
1940
+ Default is False.
833
1941
 
834
1942
  Returns
835
1943
  -------
836
- filtereddata : 1D float array
837
- The filtered data
1944
+ filtereddata : NDArray
1945
+ The filtered data as a 1D float array.
1946
+
1947
+ Notes
1948
+ -----
1949
+ This function applies a highpass filter in the frequency domain using FFT. The input signal
1950
+ is padded to reduce edge effects, then filtered using a transfer function, and finally
1951
+ unpadded to return the result.
1952
+
1953
+ Examples
1954
+ --------
1955
+ >>> import numpy as np
1956
+ >>> Fs = 100.0
1957
+ >>> t = np.linspace(0, 1, int(Fs), endpoint=False)
1958
+ >>> signal = np.sin(2 * np.pi * 10 * t)
1959
+ >>> filtered = dohptransfuncfilt(Fs, signal, lowerpass=5.0)
838
1960
  """
839
1961
  if lowerstop is None:
840
1962
  lowerstop = lowerpass * (1.0 / 1.05)
841
- padinputdata = padvec(inputdata, padlen=padlen, cyclic=cyclic)
1963
+ padinputdata = padvec(inputdata, padlen=padlen, avlen=avlen, padtype=padtype, debug=debug)
842
1964
  inputdata_trans = fftpack.fft(padinputdata)
843
1965
  transferfunc = getlptransfunc(
844
1966
  Fs, padinputdata, upperpass=lowerstop, upperstop=lowerpass, type=type
@@ -850,7 +1972,7 @@ def dohptransfuncfilt(
850
1972
  )
851
1973
  fig = plt.figure()
852
1974
  ax = fig.add_subplot(111)
853
- ax.set_title("HP Transfer function - " + type + ", lowerpass={:.2f}".format(lowerpass))
1975
+ ax.set_title(f"HP Transfer function - {type}, lowerpass={lowerpass:.2f}")
854
1976
  plt.plot(freqaxis, transferfunc)
855
1977
  plt.show()
856
1978
  inputdata_trans *= 1.0 - transferfunc
@@ -859,58 +1981,72 @@ def dohptransfuncfilt(
859
1981
 
860
1982
  # @conditionaljit()
861
1983
  def dobptransfuncfilt(
862
- Fs,
863
- inputdata,
864
- lowerstop=None,
865
- lowerpass=None,
866
- upperpass=None,
867
- upperstop=None,
868
- type="brickwall",
869
- padlen=20,
870
- cyclic=False,
871
- debug=False,
872
- ):
873
- r"""Performs an FFT filter with a trapezoidal highpass transfer
874
- function on an input vector and returns the result. Ends are padded to reduce transients.
1984
+ Fs: float,
1985
+ inputdata: NDArray,
1986
+ lowerpass: float,
1987
+ upperpass: float,
1988
+ lowerstop: Optional[float] = None,
1989
+ upperstop: Optional[float] = None,
1990
+ type: str = "brickwall",
1991
+ padlen: int = 20,
1992
+ avlen: int = 20,
1993
+ padtype: str = "reflect",
1994
+ debug: bool = False,
1995
+ ) -> NDArray:
1996
+ """
1997
+ Performs an FFT filter with a trapezoidal highpass transfer function on an input vector
1998
+ and returns the result. Ends are padded to reduce transients.
875
1999
 
876
2000
  Parameters
877
2001
  ----------
878
2002
  Fs : float
879
- Sample rate in Hz
880
- :param Fs:
881
-
882
- lowerstop : float
883
- Upper end of stopband in Hz
884
- :param lowerstop:
885
-
2003
+ Sample rate in Hz.
2004
+ inputdata : NDArray
2005
+ Input data to be filtered.
886
2006
  lowerpass : float
887
- Lower end of passband in Hz
888
- :param lowerpass:
889
-
890
- inputdata : 1D numpy array
891
- Input data to be filtered
892
- :param inputdata:
893
-
2007
+ Lower end of passband in Hz.
2008
+ upperpass : float
2009
+ Upper end of passband in Hz.
2010
+ lowerstop : float, optional
2011
+ Upper end of stopband in Hz. If not provided, it is computed as `lowerpass / 1.05`.
2012
+ upperstop : float, optional
2013
+ Lower end of stopband in Hz. If not provided, it is computed as `upperpass * 1.05`.
2014
+ type : str, optional
2015
+ Type of transfer function to use. Default is "brickwall".
894
2016
  padlen : int, optional
895
- Amount of points to reflect around each end of the input vector prior to filtering. Default is 20.
896
- :param padlen:
897
-
898
- cyclic : bool, optional
899
- If True, pad by wrapping the data in a cyclic manner rather than reflecting at the ends
900
- :param cyclic:
901
-
902
- debug : boolean, optional
2017
+ Amount of points to reflect around each end of the input vector prior to filtering.
2018
+ Default is 20.
2019
+ avlen : int, optional
2020
+ Length of the averaging window used in padding. Default is 20.
2021
+ padtype : str, optional
2022
+ Type of padding to use. Options are "reflect" or "wrap". Default is "reflect".
2023
+ debug : bool, optional
903
2024
  When True, internal states of the function will be printed to help debugging.
904
- :param debug:
2025
+ Default is False.
905
2026
 
906
2027
  Returns
907
2028
  -------
908
- filtereddata : 1D float array
909
- The filtered data
2029
+ filtereddata : NDArray
2030
+ The filtered data as a 1D float array.
2031
+
2032
+ Notes
2033
+ -----
2034
+ This function applies a bandpass filter in the frequency domain using FFT. It pads the input
2035
+ data to minimize edge effects and applies a transfer function that combines a lowpass and
2036
+ highpass response. The resulting filtered signal is returned after inverse FFT and unpadding.
2037
+
2038
+ Examples
2039
+ --------
2040
+ >>> import numpy as np
2041
+ >>> from scipy import fftpack
2042
+ >>> Fs = 100.0
2043
+ >>> t = np.linspace(0, 1, int(Fs), endpoint=False)
2044
+ >>> signal = np.sin(2 * np.pi * 10 * t)
2045
+ >>> filtered = dobptransfuncfilt(Fs, signal, 5.0, 15.0)
910
2046
  """
911
2047
  if lowerstop is None:
912
2048
  lowerstop = lowerpass * (1.0 / 1.05)
913
- padinputdata = padvec(inputdata, padlen=padlen, cyclic=cyclic)
2049
+ padinputdata = padvec(inputdata, padlen=padlen, avlen=avlen, padtype=padtype, debug=debug)
914
2050
  inputdata_trans = fftpack.fft(padinputdata)
915
2051
  transferfunc = getlptransfunc(
916
2052
  Fs,
@@ -928,9 +2064,7 @@ def dobptransfuncfilt(
928
2064
  fig = plt.figure()
929
2065
  ax = fig.add_subplot(111)
930
2066
  ax.set_title(
931
- "BP Transfer function - "
932
- + type
933
- + ", lowerpass={:.2f}, upperpass={:.2f}".format(lowerpass, upperpass)
2067
+ f"BP Transfer function - {type}, lowerpass={lowerpass:.2f}, upperpass={upperpass:.2f}"
934
2068
  )
935
2069
  plt.plot(freqaxis, transferfunc)
936
2070
  plt.show()
@@ -938,1073 +2072,1050 @@ def dobptransfuncfilt(
938
2072
  return unpadvec(fftpack.ifft(inputdata_trans).real, padlen=padlen)
939
2073
 
940
2074
 
941
- # @conditionaljit()
942
- def dolptrapfftfilt(Fs, upperpass, upperstop, inputdata, padlen=20, cyclic=False, debug=False):
943
- r"""Performs an FFT filter with a trapezoidal lowpass transfer
944
- function on an input vector and returns the result. Ends are padded to reduce transients.
945
-
946
- Parameters
947
- ----------
948
- Fs : float
949
- Sample rate in Hz
950
- :param Fs:
951
-
952
- upperpass : float
953
- Upper end of passband in Hz
954
- :param upperpass:
955
-
956
- upperstop : float
957
- Lower end of stopband in Hz
958
- :param upperstop:
959
-
960
- inputdata : 1D numpy array
961
- Input data to be filtered
962
- :param inputdata:
963
-
964
- padlen : int, optional
965
- Amount of points to reflect around each end of the input vector prior to filtering. Default is 20.
966
- :param padlen:
967
-
968
- cyclic : bool, optional
969
- If True, pad by wrapping the data in a cyclic manner rather than reflecting at the ends
970
- :param cyclic:
971
-
972
- debug : boolean, optional
973
- When True, internal states of the function will be printed to help debugging.
974
- :param debug:
975
-
976
- Returns
977
- -------
978
- filtereddata : 1D float array
979
- The filtered data
980
- """
981
- padinputdata = padvec(inputdata, padlen=padlen, cyclic=cyclic)
982
- inputdata_trans = fftpack.fft(padinputdata)
983
- transferfunc = getlptrapfftfunc(Fs, upperpass, upperstop, padinputdata, debug=debug)
984
- inputdata_trans *= transferfunc
985
- return unpadvec(fftpack.ifft(inputdata_trans).real, padlen=padlen)
986
-
987
-
988
- # @conditionaljit()
989
- def dohptrapfftfilt(Fs, lowerstop, lowerpass, inputdata, padlen=20, cyclic=False, debug=False):
990
- r"""Performs an FFT filter with a trapezoidal highpass transfer
991
- function on an input vector and returns the result. Ends are padded to reduce transients.
992
-
993
- Parameters
994
- ----------
995
- Fs : float
996
- Sample rate in Hz
997
- :param Fs:
998
-
999
- lowerstop : float
1000
- Upper end of stopband in Hz
1001
- :param lowerstop:
1002
-
1003
- lowerpass : float
1004
- Lower end of passband in Hz
1005
- :param lowerpass:
1006
-
1007
- inputdata : 1D numpy array
1008
- Input data to be filtered
1009
- :param inputdata:
1010
-
1011
- padlen : int, optional
1012
- Amount of points to reflect around each end of the input vector prior to filtering. Default is 20.
1013
- :param padlen:
1014
-
1015
- cyclic : bool, optional
1016
- If True, pad by wrapping the data in a cyclic manner rather than reflecting at the ends
1017
- :param cyclic:
1018
-
1019
- debug : boolean, optional
1020
- When True, internal states of the function will be printed to help debugging.
1021
- :param debug:
1022
-
1023
- Returns
1024
- -------
1025
- filtereddata : 1D float array
1026
- The filtered data
1027
- """
1028
- padinputdata = padvec(inputdata, padlen=padlen, cyclic=cyclic)
1029
- inputdata_trans = fftpack.fft(padinputdata)
1030
- transferfunc = 1.0 - getlptrapfftfunc(Fs, lowerstop, lowerpass, padinputdata, debug=debug)
1031
- inputdata_trans *= transferfunc
1032
- return unpadvec(fftpack.ifft(inputdata_trans).real, padlen=padlen)
1033
-
1034
-
1035
- # @conditionaljit()
1036
- def dobptrapfftfilt(
1037
- Fs,
1038
- lowerstop,
1039
- lowerpass,
1040
- upperpass,
1041
- upperstop,
1042
- inputdata,
1043
- padlen=20,
1044
- cyclic=False,
1045
- debug=False,
1046
- ):
1047
- r"""Performs an FFT filter with a trapezoidal bandpass transfer
1048
- function on an input vector and returns the result. Ends are padded to reduce transients.
1049
-
1050
- Parameters
1051
- ----------
1052
- Fs : float
1053
- Sample rate in Hz
1054
- :param Fs:
1055
-
1056
- lowerstop : float
1057
- Upper end of stopband in Hz
1058
- :param lowerstop:
1059
-
1060
- lowerpass : float
1061
- Lower end of passband in Hz
1062
- :param lowerpass:
1063
-
1064
- upperpass : float
1065
- Upper end of passband in Hz
1066
- :param upperpass:
1067
-
1068
- upperstop : float
1069
- Lower end of stopband in Hz
1070
- :param upperstop:
1071
-
1072
- inputdata : 1D numpy array
1073
- Input data to be filtered
1074
- :param inputdata:
1075
-
1076
- padlen : int, optional
1077
- Amount of points to reflect around each end of the input vector prior to filtering. Default is 20.
1078
- :param padlen:
1079
-
1080
- cyclic : bool, optional
1081
- If True, pad by wrapping the data in a cyclic manner rather than reflecting at the ends
1082
- :param cyclic:
1083
-
1084
- debug : boolean, optional
1085
- When True, internal states of the function will be printed to help debugging.
1086
- :param debug:
1087
-
1088
- Returns
1089
- -------
1090
- filtereddata : 1D float array
1091
- The filtered data
1092
- """
1093
- padinputdata = padvec(inputdata, padlen=padlen, cyclic=cyclic)
1094
- inputdata_trans = fftpack.fft(padinputdata)
1095
- if debug:
1096
- print(
1097
- "Fs=",
1098
- Fs,
1099
- " Fstopl=",
1100
- lowerstop,
1101
- " Fpassl=",
1102
- lowerpass,
1103
- " Fpassu=",
1104
- upperpass,
1105
- " Fstopu=",
1106
- upperstop,
1107
- )
1108
- transferfunc = getlptrapfftfunc(Fs, upperpass, upperstop, padinputdata, debug=debug) * (
1109
- 1.0 - getlptrapfftfunc(Fs, lowerstop, lowerpass, padinputdata, debug=debug)
1110
- )
1111
- inputdata_trans *= transferfunc
1112
- return unpadvec(fftpack.ifft(inputdata_trans).real, padlen=padlen)
1113
-
1114
-
1115
- # Simple example of Wiener deconvolution in Python.
1116
- # We use a fixed SNR across all frequencies in this example.
1117
- #
1118
- # Written 2015 by Dan Stowell. Public domain.
1119
- def wiener_deconvolution(signal, kernel, lambd):
1120
- "lambd is the SNR in the fourier domain"
1121
- kernel = np.hstack(
1122
- (kernel, np.zeros(len(signal) - len(kernel)))
1123
- ) # zero pad the kernel to same length
1124
- H = fftpack.fft(kernel)
1125
- deconvolved = np.roll(
1126
- np.real(fftpack.ifft(fftpack.fft(signal) * np.conj(H) / (H * np.conj(H) + lambd**2))),
1127
- int(len(signal) // 2),
1128
- )
1129
- return deconvolved
1130
-
1131
-
1132
- def pspec(inputdata):
1133
- r"""Calculate the power spectrum of an input signal
1134
-
1135
- Parameters
1136
- ----------
1137
- inputdata: 1D numpy array
1138
- Input data
1139
-
1140
- Returns
1141
- -------
1142
- spectrum: 1D numpy array
1143
- The power spectrum of the input signal.
1144
-
1145
- """
1146
- S = fftpack.fft(inputdata)
1147
- return np.sqrt(S * np.conj(S))
1148
-
1149
-
1150
- def spectralflatness(spectrum):
1151
- return np.exp(np.mean(np.log(spectrum))) / np.mean(spectrum)
1152
-
1153
-
1154
- def spectrum(inputdata, Fs=1.0, mode="power", trim=True):
1155
- r"""Performs an FFT of the input data, and returns the frequency axis and spectrum
1156
- of the input signal.
1157
-
1158
- Parameters
1159
- ----------
1160
- inputdata : 1D numpy array
1161
- Input data
1162
- :param inputdata:
1163
-
1164
- Fs : float, optional
1165
- Sample rate in Hz. Defaults to 1.0
1166
- :param Fs:
1167
-
1168
- mode : {'real', 'imag', 'mag', 'phase', 'power'}, optional
1169
- The type of spectrum to return. Default is 'power'.
1170
- :param mode:
1171
-
1172
- trim: bool
1173
- If True (default) return only the positive frequency values
1174
-
1175
- Returns
1176
- -------
1177
- specaxis : 1D float array
1178
- The frequency axis.
1179
-
1180
- specvals : 1D float array
1181
- The spectral data.
1182
-
1183
- Other Parameters
1184
- ----------------
1185
- Fs : float
1186
- Sample rate in Hz. Defaults to 1.0
1187
- :param Fs:
1188
-
1189
- mode : {'real', 'imag', 'complex', 'mag', 'phase', 'power'}
1190
- The type of spectrum to return. Legal values are 'real', 'imag', 'mag', 'phase', and 'power' (default)
1191
- :param mode:
1192
- """
1193
- if trim:
1194
- specvals = fftpack.fft(inputdata)[0 : len(inputdata) // 2]
1195
- maxfreq = Fs / 2.0
1196
- specaxis = np.linspace(0.0, maxfreq, len(specvals), endpoint=False)
1197
- else:
1198
- specvals = fftpack.fft(inputdata)
1199
- maxfreq = Fs
1200
- specaxis = np.linspace(0.0, maxfreq, len(specvals), endpoint=False)
1201
- if mode == "real":
1202
- specvals = specvals.real
1203
- elif mode == "imag":
1204
- specvals = specvals.imag
1205
- elif mode == "complex":
1206
- pass
1207
- elif mode == "mag":
1208
- specvals = np.absolute(specvals)
1209
- elif mode == "phase":
1210
- specvals = np.angle(specvals)
1211
- elif mode == "power":
1212
- specvals = np.sqrt(np.absolute(specvals))
1213
- else:
1214
- print("illegal spectrum mode")
1215
- specvals = None
1216
- return specaxis, specvals
1217
-
1218
-
1219
- def setnotchfilter(thefilter, thefreq, notchwidth=1.0):
1220
- r"""Set notch filter - sets the filter parameters for the notch.
1221
-
1222
- Parameters
1223
- ----------
1224
- thefilter: NoncausalFilter function
1225
- The filter function to use
1226
- thefreq: float
1227
- Frequency of the notch
1228
- notchwidth: float
1229
- width of the notch in Hz
1230
- """
1231
- thefilter.settype("arb_stop")
1232
- thefilter.setfreqs(
1233
- thefreq - notchwidth / 2.0,
1234
- thefreq - notchwidth / 2.0,
1235
- thefreq + notchwidth / 2.0,
1236
- thefreq + notchwidth / 2.0,
1237
- )
1238
-
1239
-
1240
- def harmonicnotchfilter(timecourse, Fs, Ffundamental, notchpct=1.0, debug=False):
1241
- r"""Harmonic notch filter - removes a fundamental and its harmonics from a timecourse.
1242
-
1243
- Parameters
1244
- ----------
1245
- timecourse: 1D numpy array
1246
- Input data
1247
- Fs: float
1248
- Sample rate
1249
- Ffundamental: float
1250
- Fundamental frequency to be removed from the data
1251
- notchpct: float, optional
1252
- Width of the notch relative to the filter frequency in percent. Default is 1.0.
1253
- debug: bool, optional
1254
- Set to True for additiona information on function internals. Default is False.
1255
-
1256
- Returns
1257
- -------
1258
- filteredtc: 1D numpy array
1259
- The filtered data
1260
-
1261
- """
1262
- # delete the fundamental and its harmonics
1263
- filteredtc = timecourse + 0.0
1264
- maxpass = Fs / 2.0
1265
- if notchpct is not None:
1266
- stopfreq = Ffundamental
1267
- freqstep = 0.5 * Fs / len(filteredtc)
1268
- maxharmonic = int(maxpass // stopfreq)
1269
- if debug:
1270
- print("highest harmonic is", maxharmonic, "(", maxharmonic * stopfreq, "Hz)")
1271
- thenotchfilter = NoncausalFilter()
1272
- for harmonic in range(1, maxharmonic + 1):
1273
- notchfreq = harmonic * stopfreq
1274
- if debug:
1275
- print("removing harmonic at", notchfreq)
1276
- notchwidth = np.max([notchpct * harmonic * stopfreq * 0.01, freqstep])
1277
- if debug:
1278
- print("\tFs:", Fs)
1279
- print("\tstopfreq:", stopfreq)
1280
- print("\tnotchpct:", notchpct)
1281
- print("\tnotchwidth:", notchwidth)
1282
- print("\tnotchfreq:", notchfreq)
1283
- print("\tfreqstep:", freqstep)
1284
- print("\tminfreqstep:", freqstep / notchfreq)
1285
- print("\tbins:", int(notchwidth // freqstep))
1286
- print()
1287
- setnotchfilter(thenotchfilter, notchfreq, notchwidth=notchwidth)
1288
- filteredtc = thenotchfilter.apply(Fs, filteredtc)
1289
- return filteredtc
1290
-
1291
-
1292
- def savgolsmooth(data, smoothlen=101, polyorder=3):
1293
- return savgol_filter(data, smoothlen, polyorder)
1294
-
1295
-
1296
- def csdfilter(obsdata, commondata, padlen=20, cyclic=False, debug=False):
1297
- r"""Cross spectral density filter - makes a filter transfer function that preserves common frequencies.
2075
+ # @conditionaljit()
2076
+ def dolptrapfftfilt(
2077
+ Fs: float,
2078
+ upperpass: float,
2079
+ upperstop: float,
2080
+ inputdata: NDArray,
2081
+ padlen: int = 20,
2082
+ avlen: int = 20,
2083
+ padtype: str = "reflect",
2084
+ debug: bool = False,
2085
+ ) -> NDArray:
2086
+ """
2087
+ Performs an FFT filter with a trapezoidal lowpass transfer function on an input vector
2088
+ and returns the result. Ends are padded to reduce transients.
1298
2089
 
1299
2090
  Parameters
1300
2091
  ----------
1301
- obsdata: 1D numpy array
1302
- Input data
2092
+ Fs : float
2093
+ Sample rate in Hz.
2094
+ upperpass : float
2095
+ Upper end of the passband in Hz.
2096
+ upperstop : float
2097
+ Lower end of the stopband in Hz.
2098
+ inputdata : NDArray
2099
+ Input data to be filtered, as a 1D numpy array.
2100
+ padlen : int, optional
2101
+ Amount of points to reflect around each end of the input vector prior to filtering.
2102
+ Default is 20.
2103
+ avlen : int, optional
2104
+ Length of the averaging window used in padding. Default is 20.
2105
+ padtype : str, optional
2106
+ Type of padding to use. Options are 'reflect' or 'wrap'. Default is 'reflect'.
2107
+ debug : bool, optional
2108
+ When True, internal states of the function will be printed to help debugging.
2109
+ Default is False.
1303
2110
 
1304
- commondata: 1D numpy array
1305
- Shared data
2111
+ Returns
2112
+ -------
2113
+ filtereddata : NDArray
2114
+ The filtered data as a 1D float array.
2115
+
2116
+ Notes
2117
+ -----
2118
+ This function applies a trapezoidal lowpass filter in the frequency domain using FFT.
2119
+ The input signal is first padded to reduce edge effects, then filtered using a
2120
+ transfer function, and finally the padding is removed to return the filtered signal.
2121
+
2122
+ Examples
2123
+ --------
2124
+ >>> import numpy as np
2125
+ >>> Fs = 100.0
2126
+ >>> upperpass = 20.0
2127
+ >>> upperstop = 25.0
2128
+ >>> data = np.random.randn(1000)
2129
+ >>> filtered = dolptrapfftfilt(Fs, upperpass, upperstop, data)
2130
+ """
2131
+ padinputdata = padvec(inputdata, padlen=padlen, avlen=avlen, padtype=padtype, debug=debug)
2132
+ inputdata_trans = fftpack.fft(padinputdata)
2133
+ transferfunc = getlptrapfftfunc(Fs, upperpass, upperstop, padinputdata, debug=debug)
2134
+ inputdata_trans *= transferfunc
2135
+ return unpadvec(fftpack.ifft(inputdata_trans).real, padlen=padlen)
1306
2136
 
1307
- padlen: int, optional
1308
- Number of reflected points to add on each end of the input data. Default is 20.
1309
2137
 
1310
- cyclic : bool, optional
1311
- If True, pad by wrapping the data in a cyclic manner rather than reflecting at the ends
2138
+ # @conditionaljit()
2139
+ def dohptrapfftfilt(
2140
+ Fs: float,
2141
+ lowerstop: float,
2142
+ lowerpass: float,
2143
+ inputdata: NDArray,
2144
+ padlen: int = 20,
2145
+ avlen: int = 20,
2146
+ padtype: str = "reflect",
2147
+ debug: bool = False,
2148
+ ) -> NDArray:
2149
+ """
2150
+ Performs an FFT filter with a trapezoidal highpass transfer function on an input vector
2151
+ and returns the result. Ends are padded to reduce transients.
1312
2152
 
1313
- debug: bool, optional
1314
- Set to True for additiona information on function internals. Default is False.
2153
+ Parameters
2154
+ ----------
2155
+ Fs : float
2156
+ Sample rate in Hz.
2157
+ lowerstop : float
2158
+ Upper end of stopband in Hz.
2159
+ lowerpass : float
2160
+ Lower end of passband in Hz.
2161
+ inputdata : NDArray
2162
+ Input data to be filtered, expected as a 1D numpy array.
2163
+ padlen : int, optional
2164
+ Amount of points to reflect around each end of the input vector prior to filtering.
2165
+ Default is 20.
2166
+ avlen : int, optional
2167
+ Length of the averaging window used in padding. Default is 20.
2168
+ padtype : str, optional
2169
+ Type of padding to use. Options are 'reflect' or 'cyclic'. Default is 'reflect'.
2170
+ debug : bool, optional
2171
+ When True, internal states of the function will be printed to help debugging.
2172
+ Default is False.
1315
2173
 
1316
2174
  Returns
1317
2175
  -------
1318
- filtereddata: 1D numpy array
1319
- The filtered data
1320
-
2176
+ filtereddata : NDArray
2177
+ The filtered data as a 1D float array.
2178
+
2179
+ Notes
2180
+ -----
2181
+ This function applies a trapezoidal highpass filter in the frequency domain using FFT.
2182
+ The input signal is first padded using the specified padding method to reduce edge effects.
2183
+ The filter transfer function is constructed using `getlptrapfftfunc`, and the filtered
2184
+ signal is obtained by inverse FFT.
2185
+
2186
+ Examples
2187
+ --------
2188
+ >>> import numpy as np
2189
+ >>> Fs = 100.0
2190
+ >>> lowerstop = 5.0
2191
+ >>> lowerpass = 10.0
2192
+ >>> data = np.random.randn(1000)
2193
+ >>> filtered = dohptrapfftfilt(Fs, lowerstop, lowerpass, data)
1321
2194
  """
1322
- padobsdata = padvec(obsdata, padlen=padlen, cyclic=cyclic)
1323
- padcommondata = padvec(commondata, padlen=padlen, cyclic=cyclic)
1324
- obsdata_trans = fftpack.fft(padobsdata)
1325
- transferfunc = np.sqrt(np.abs(fftpack.fft(padobsdata) * np.conj(fftpack.fft(padcommondata))))
1326
- obsdata_trans *= transferfunc
1327
- return unpadvec(fftpack.ifft(obsdata_trans).real, padlen=padlen)
2195
+ padinputdata = padvec(inputdata, padlen=padlen, avlen=avlen, padtype=padtype, debug=debug)
2196
+ inputdata_trans = fftpack.fft(padinputdata)
2197
+ transferfunc = 1.0 - getlptrapfftfunc(Fs, lowerstop, lowerpass, padinputdata, debug=debug)
2198
+ inputdata_trans *= transferfunc
2199
+ return unpadvec(fftpack.ifft(inputdata_trans).real, padlen=padlen)
1328
2200
 
1329
2201
 
1330
2202
  # @conditionaljit()
1331
- def arb_pass(
1332
- Fs,
1333
- inputdata,
1334
- lowerstop,
1335
- lowerpass,
1336
- upperpass,
1337
- upperstop,
1338
- transferfunc="trapezoidal",
1339
- butterorder=6,
1340
- padlen=20,
1341
- cyclic=False,
1342
- debug=False,
1343
- ):
1344
- r"""Filters an input waveform over a specified range. By default it is a trapezoidal
1345
- FFT filter, but brickwall and butterworth filters are also available. Ends are padded to reduce
1346
- transients.
2203
+ def dobptrapfftfilt(
2204
+ Fs: float,
2205
+ lowerstop: float,
2206
+ lowerpass: float,
2207
+ upperpass: float,
2208
+ upperstop: float,
2209
+ inputdata: NDArray,
2210
+ padlen: int = 20,
2211
+ avlen: int = 20,
2212
+ padtype: str = "reflect",
2213
+ debug: bool = False,
2214
+ ) -> NDArray:
2215
+ """
2216
+ Performs an FFT filter with a trapezoidal bandpass transfer function on an input vector
2217
+ and returns the result. Ends are padded to reduce transients.
1347
2218
 
1348
2219
  Parameters
1349
2220
  ----------
1350
2221
  Fs : float
1351
- Sample rate in Hz
1352
- :param Fs:
1353
-
1354
- inputdata : 1D numpy array
1355
- Input data to be filtered
1356
- :param inputdata:
1357
-
2222
+ Sample rate in Hz.
1358
2223
  lowerstop : float
1359
- Upper end of lower stopband in Hz
1360
- :param lowerstop:
1361
-
2224
+ Upper end of the lower stopband in Hz.
1362
2225
  lowerpass : float
1363
- Lower end of passband in Hz
1364
- :param lowerpass:
1365
-
2226
+ Lower end of the lower passband in Hz.
1366
2227
  upperpass : float
1367
- Upper end of passband in Hz
1368
- :param upperpass:
1369
-
2228
+ Upper end of the upper passband in Hz.
1370
2229
  upperstop : float
1371
- Lower end of upper stopband in Hz
1372
- :param upperstop:
2230
+ Lower end of the upper stopband in Hz.
2231
+ inputdata : NDArray
2232
+ Input data to be filtered, expected as a 1D numpy array.
2233
+ padlen : int, optional
2234
+ Amount of points to reflect around each end of the input vector prior to filtering.
2235
+ Default is 20.
2236
+ avlen : int, optional
2237
+ Length of the averaging window used in padding. Default is 20.
2238
+ padtype : str, optional
2239
+ Type of padding to use. Options are 'reflect' or 'wrap'. Default is 'reflect'.
2240
+ debug : bool, optional
2241
+ When True, internal states of the function will be printed to help debugging.
2242
+ Default is False.
1373
2243
 
1374
- butterorder : int, optional
1375
- Order of Butterworth filter, if used. Default is 6.
1376
- :param butterorder:
2244
+ Returns
2245
+ -------
2246
+ NDArray
2247
+ The filtered data as a 1D float array.
2248
+
2249
+ Notes
2250
+ -----
2251
+ This function applies a trapezoidal bandpass filter in the frequency domain using FFT.
2252
+ The input signal is first padded to minimize edge effects, then transformed into the
2253
+ frequency domain, multiplied by the transfer function, and finally inverse transformed
2254
+ back to the time domain.
2255
+
2256
+ Examples
2257
+ --------
2258
+ >>> import numpy as np
2259
+ >>> Fs = 100.0
2260
+ >>> data = np.random.randn(1000)
2261
+ >>> filtered = dobptrapfftfilt(Fs, 10, 15, 25, 30, data, padlen=50)
2262
+ """
2263
+ padinputdata = padvec(inputdata, padlen=padlen, avlen=avlen, padtype=padtype, debug=debug)
2264
+ inputdata_trans = fftpack.fft(padinputdata)
2265
+ if debug:
2266
+ print(
2267
+ "Fs=",
2268
+ Fs,
2269
+ " Fstopl=",
2270
+ lowerstop,
2271
+ " Fpassl=",
2272
+ lowerpass,
2273
+ " Fpassu=",
2274
+ upperpass,
2275
+ " Fstopu=",
2276
+ upperstop,
2277
+ )
2278
+ transferfunc = getlptrapfftfunc(Fs, upperpass, upperstop, padinputdata, debug=debug) * (
2279
+ 1.0 - getlptrapfftfunc(Fs, lowerstop, lowerpass, padinputdata, debug=debug)
2280
+ )
2281
+ inputdata_trans *= transferfunc
2282
+ return unpadvec(fftpack.ifft(inputdata_trans).real, padlen=padlen)
1377
2283
 
1378
- padlen : int, optional
1379
- Amount of points to reflect around each end of the input vector prior to filtering. Default is 20.
1380
- :param padlen:
1381
2284
 
1382
- cyclic : bool, optional
1383
- If True, pad by wrapping the data in a cyclic manner rather than reflecting at the ends
1384
- :param cyclic:
2285
+ # Simple example of Wiener deconvolution in Python.
2286
+ # We use a fixed SNR across all frequencies in this example.
2287
+ #
2288
+ # Written 2015 by Dan Stowell. Public domain.
2289
+ def wiener_deconvolution(signal: NDArray, kernel: NDArray, lambd: float) -> NDArray:
2290
+ """Perform Wiener deconvolution on a signal.
2291
+
2292
+ This function applies Wiener deconvolution to remove blur from a signal using
2293
+ the Wiener filter in the frequency domain. The regularization parameter `lambd`
2294
+ represents the signal-to-noise ratio in the Fourier domain.
1385
2295
 
1386
- debug : boolean, optional
1387
- When True, internal states of the function will be printed to help debugging.
1388
- :param debug:
2296
+ Parameters
2297
+ ----------
2298
+ signal : NDArray
2299
+ Input signal to be deconvolved, 1D array.
2300
+ kernel : NDArray
2301
+ Convolution kernel (point spread function), 1D array.
2302
+ lambd : float
2303
+ Regularization parameter representing the signal-to-noise ratio in
2304
+ the Fourier domain. Higher values correspond to more smoothing.
1389
2305
 
1390
2306
  Returns
1391
2307
  -------
1392
- filtereddata : 1D float array
1393
- The filtered data
2308
+ NDArray
2309
+ Deconvolved signal, same length as input signal.
2310
+
2311
+ Notes
2312
+ -----
2313
+ The Wiener deconvolution formula in frequency domain is:
2314
+ Y = X * H* / (|H|² + λ²)
2315
+
2316
+ where X is the Fourier transform of the input signal, H is the Fourier
2317
+ transform of the kernel, and H* is the complex conjugate of H.
2318
+
2319
+ Examples
2320
+ --------
2321
+ >>> import numpy as np
2322
+ >>> signal = np.array([1, 2, 3, 2, 1])
2323
+ >>> kernel = np.array([1, 0.5, 0.25])
2324
+ >>> result = wiener_deconvolution(signal, kernel, lambd=0.1)
1394
2325
  """
1395
- # check filter limits to see if we should do a lowpass, bandpass, or highpass
1396
- if lowerpass <= 0.0:
1397
- # set up for lowpass
1398
- if transferfunc == "butterworth":
1399
- retvec = dolpfiltfilt(
1400
- Fs,
1401
- upperpass,
1402
- inputdata,
1403
- butterorder,
1404
- padlen=padlen,
1405
- cyclic=False,
1406
- debug=debug,
1407
- )
1408
- return retvec
1409
- else:
1410
- return dolptransfuncfilt(
1411
- Fs,
1412
- inputdata,
1413
- upperpass=upperpass,
1414
- upperstop=upperstop,
1415
- type=transferfunc,
1416
- padlen=padlen,
1417
- cyclic=cyclic,
1418
- debug=debug,
1419
- )
1420
- elif (upperpass >= Fs / 2.0) or (upperpass <= 0.0):
1421
- # set up for highpass
1422
- if transferfunc == "butterworth":
1423
- return dohpfiltfilt(
1424
- Fs,
1425
- lowerpass,
1426
- inputdata,
1427
- butterorder,
1428
- padlen=padlen,
1429
- cyclic=False,
1430
- debug=debug,
1431
- )
1432
- else:
1433
- return dohptransfuncfilt(
1434
- Fs,
1435
- inputdata,
1436
- lowerstop=lowerstop,
1437
- lowerpass=lowerpass,
1438
- type=transferfunc,
1439
- padlen=padlen,
1440
- cyclic=cyclic,
1441
- debug=debug,
1442
- )
1443
- else:
1444
- # set up for bandpass
1445
- if transferfunc == "butterworth":
1446
- return dohpfiltfilt(
1447
- Fs,
1448
- lowerpass,
1449
- dolpfiltfilt(
1450
- Fs,
1451
- upperpass,
1452
- inputdata,
1453
- butterorder,
1454
- padlen=padlen,
1455
- cyclic=False,
1456
- debug=debug,
1457
- ),
1458
- butterorder,
1459
- padlen=padlen,
1460
- debug=debug,
1461
- )
1462
- else:
1463
- return dobptransfuncfilt(
1464
- Fs,
1465
- inputdata,
1466
- lowerstop=lowerstop,
1467
- lowerpass=lowerpass,
1468
- upperpass=upperpass,
1469
- upperstop=upperstop,
1470
- type=transferfunc,
1471
- padlen=padlen,
1472
- cyclic=cyclic,
1473
- debug=debug,
1474
- )
2326
+ kernel = np.hstack(
2327
+ (kernel, np.zeros(len(signal) - len(kernel)))
2328
+ ) # zero pad the kernel to same length
2329
+ H = fftpack.fft(kernel)
2330
+ deconvolved = np.roll(
2331
+ np.real(fftpack.ifft(fftpack.fft(signal) * np.conj(H) / (H * np.conj(H) + lambd**2))),
2332
+ int(len(signal) // 2),
2333
+ )
2334
+ return deconvolved
1475
2335
 
1476
2336
 
1477
- class Plethfilter:
1478
- def __init_(self, Fs, Fl, Fh, order=4, attenuation=20):
1479
- self.Fs = Fs
1480
- self.Fh = Fh
1481
- self.Fl = Fl
1482
- self.attenuation = attenuation
1483
- self.order = order
1484
- retvec = signal.cheby2(
1485
- self.order,
1486
- self.attenuation,
1487
- [self.Fl / self.Fn, self.Fh / self.Fn],
1488
- btype="bandpass",
1489
- analog=False,
1490
- output="ba",
1491
- )
1492
- self.b = retvec[0]
1493
- self.a = retvec[1]
2337
+ def pspec(inputdata: NDArray) -> NDArray:
2338
+ """
2339
+ Calculate the power spectrum of an input signal.
1494
2340
 
1495
- def apply(self, data):
1496
- return signal.filtfilt(self.b, self.a, data, axis=-1, padtype="odd", padlen=None)
2341
+ Parameters
2342
+ ----------
2343
+ inputdata : NDArray
2344
+ Input signal data array of shape (n,) where n is the number of samples.
1497
2345
 
2346
+ Returns
2347
+ -------
2348
+ NDArray
2349
+ The power spectrum of the input signal as a 1D numpy array of shape (n,).
2350
+ Each element represents the power at the corresponding frequency bin.
2351
+
2352
+ Notes
2353
+ -----
2354
+ This function computes the power spectrum using the Fast Fourier Transform (FFT).
2355
+ The power spectrum is calculated as the square root of the product of the FFT
2356
+ and its complex conjugate, which gives the magnitude of the frequency components.
2357
+
2358
+ Examples
2359
+ --------
2360
+ >>> import numpy as np
2361
+ >>> from scipy import fftpack
2362
+ >>> signal = np.sin(2 * np.pi * 5 * np.linspace(0, 1, 100))
2363
+ >>> spectrum = pspec(signal)
2364
+ >>> print(spectrum.shape)
2365
+ (100,)
2366
+ """
2367
+ S = fftpack.fft(inputdata)
2368
+ return np.sqrt(S * np.conj(S))
1498
2369
 
1499
- class NoncausalFilter:
1500
- def __init__(
1501
- self,
1502
- filtertype="None",
1503
- transitionfrac=0.05,
1504
- transferfunc="trapezoidal",
1505
- initlowerstop=None,
1506
- initlowerpass=None,
1507
- initupperpass=None,
1508
- initupperstop=None,
1509
- butterworthorder=6,
1510
- correctfreq=True,
1511
- padtime=30.0,
1512
- cyclic=False,
1513
- debug=False,
1514
- ):
1515
- r"""A zero time delay filter for one dimensional signals, especially physiological ones.
1516
2370
 
1517
- Parameters
1518
- ----------
1519
- filtertype : {'None' 'vlf', 'lfo', 'resp', 'card', 'vlf_stop', 'lfo_stop', 'resp_stop', 'card_stop', 'hrv_ulf', 'hrv_vlf', 'hrv_lf', 'hrv_hf', 'hrv_vhf', 'hrv_ulf_stop', 'hrv_vlf_stop', 'hrv_lf_stop', 'hrv_hf_stop', 'hrv_vhf_stop', 'arb', 'arb_stop', 'ringstop'}, optional
1520
- The type of filter.
1521
- butterworthorder: int, optional
1522
- Butterworth filter order. Default is 6.
1523
- correctfreq: boolean, optional
1524
- Fix pass frequencies that are impossible. Default is True.
1525
- padtime: float, optional
1526
- Amount of time to end pad to reduce edge effects. Default is 30.0 seconds
1527
- cyclic : boolean, optional
1528
- If True, pad vectors cyclicly rather than reflecting data around the ends
1529
- debug: boolean, optional
1530
- Enable extended debugging messages. Default is False.
1531
-
1532
- Methods
1533
- -------
1534
- settype(thetype)
1535
- Set the filter type. Options are 'None' (default), 'vlf', 'lfo', 'resp', 'card',
1536
- 'vlf_stop', 'lfo_stop', 'resp_stop', 'card_stop',
1537
- 'hrv_ulf', 'hrv_vlf', 'hrv_lf', 'hrv_hf', 'hrv_vhf',
1538
- 'hrv_ulf_stop', 'hrv_vlf_stop', 'hrv_lf_stop', 'hrv_hf_stop', 'hrv_vhf_stop',
1539
- 'arb', 'arb_stop',
1540
- 'ringstop'.
1541
- gettype()
1542
- Return the current filter type.
1543
- getfreqs()
1544
- Return the current frequency limits.
1545
- setbutterorder(order=self.butterworthorder)
1546
- Set the order for Butterworth filter
1547
- setpadtime(padtime)
1548
- Set the end pad time in seconds.
1549
- setdebug(debug)
1550
- Turn debugging on and off with the debug flag.
1551
- getpadtime()
1552
- Return the current end pad time.
1553
- setfreqs(lowerstop, lowerpass, upperpass, upperstop)
1554
- Set the frequency parameters of the 'arb' and 'arb_stop' filter.
1555
- """
1556
- self.filtertype = filtertype
1557
- self.species = "human"
1558
- self.transitionfrac = transitionfrac
1559
- self.transferfunc = transferfunc
1560
- if initlowerpass is None:
1561
- self.arb_lowerpass = 0.05
1562
- self.arb_lowerstop = 0.9 * self.arb_lowerpass
1563
- else:
1564
- self.arb_lowerpass = initlowerpass
1565
- self.arb_lowerstop = initlowerstop
1566
- if initupperpass is None:
1567
- self.arb_upperpass = 0.20
1568
- self.arb_upperstop = 1.1 * self.arb_upperpass
1569
- else:
1570
- self.arb_upperpass = initupperpass
1571
- self.arb_upperstop = initupperstop
1572
- self.lowerstop = 0.0
1573
- self.lowerpass = 0.0
1574
- self.upperpass = -1.0
1575
- self.upperstop = -1.0
1576
- self.butterworthorder = butterworthorder
1577
- self.correctfreq = correctfreq
1578
- self.padtime = padtime
1579
- self.cyclic = cyclic
1580
- self.debug = debug
2371
+ def spectralflatness(spectrum: NDArray) -> float:
2372
+ return np.exp(np.mean(np.log(spectrum))) / np.mean(spectrum)
1581
2373
 
1582
- self.VLF_UPPERPASS = 0.009
1583
- self.VLF_UPPERSTOP = self.VLF_UPPERPASS * (1.0 + self.transitionfrac)
1584
2374
 
1585
- self.LF_LOWERPASS = 0.01
1586
- self.LF_UPPERPASS = 0.15
1587
- self.LF_LOWERSTOP = self.LF_LOWERPASS * (1.0 - self.transitionfrac)
1588
- self.LF_UPPERSTOP = self.LF_UPPERPASS * (1.0 + self.transitionfrac)
2375
+ def spectrum(
2376
+ inputdata: NDArray, Fs: float = 1.0, mode: str = "power", trim: bool = True
2377
+ ) -> Tuple[NDArray, Union[NDArray, None]]:
2378
+ """
2379
+ Compute the spectral flatness of a spectrum.
1589
2380
 
1590
- self.LF_LEGACY_LOWERPASS = 0.01
1591
- self.LF_LEGACY_UPPERPASS = 0.15
1592
- self.LF_LEGACY_LOWERSTOP = 0.009
1593
- self.LF_LEGACY_UPPERSTOP = 0.2
2381
+ Spectral flatness is a measure of how much a spectrum resembles white noise.
2382
+ It is defined as the ratio of the geometric mean to the arithmetic mean of the spectrum.
1594
2383
 
1595
- self.RESP_LOWERPASS = 0.2
1596
- self.RESP_UPPERPASS = 0.5
1597
- self.RESP_LOWERSTOP = self.RESP_LOWERPASS * (1.0 - self.transitionfrac)
1598
- self.RESP_UPPERSTOP = self.RESP_UPPERPASS * (1.0 + self.transitionfrac)
2384
+ Parameters
2385
+ ----------
2386
+ spectrum : NDArray
2387
+ Input spectrum array. Should contain non-negative values.
1599
2388
 
1600
- self.CARD_LOWERPASS = 0.66
1601
- self.CARD_UPPERPASS = 3.0
1602
- self.CARD_LOWERSTOP = self.CARD_LOWERPASS * (1.0 - self.transitionfrac)
1603
- self.CARD_UPPERSTOP = self.CARD_UPPERPASS * (1.0 + self.transitionfrac)
2389
+ Returns
2390
+ -------
2391
+ float
2392
+ Spectral flatness value. Values close to 1.0 indicate a flat (white noise-like) spectrum,
2393
+ while values closer to 0.0 indicate a more tonal spectrum.
2394
+
2395
+ Notes
2396
+ -----
2397
+ The spectral flatness is computed as:
2398
+ flatness = exp(mean(log(spectrum))) / mean(spectrum)
2399
+
2400
+ This implementation assumes the input spectrum contains non-negative values.
2401
+ If the spectrum contains zeros, the geometric mean will be zero and the result
2402
+ will be zero regardless of the arithmetic mean.
2403
+
2404
+ Examples
2405
+ --------
2406
+ >>> import numpy as np
2407
+ >>> # White noise spectrum
2408
+ >>> white_noise = np.random.rand(100)
2409
+ >>> flatness = spectralflatness(white_noise)
2410
+ >>> # Tone-like spectrum
2411
+ >>> tone = np.zeros(100)
2412
+ >>> tone[50] = 1.0
2413
+ >>> flatness = spectralflatness(tone)
2414
+ """
2415
+ if trim:
2416
+ specvals = fftpack.fft(inputdata)[0 : len(inputdata) // 2]
2417
+ maxfreq = Fs / 2.0
2418
+ specaxis = np.linspace(0.0, maxfreq, len(specvals), endpoint=False)
2419
+ else:
2420
+ specvals = fftpack.fft(inputdata)
2421
+ maxfreq = Fs
2422
+ specaxis = np.linspace(0.0, maxfreq, len(specvals), endpoint=False)
2423
+ if mode == "real":
2424
+ specvals = (specvals.real).astype(specvals.dtype)
2425
+ elif mode == "imag":
2426
+ specvals = (specvals.imag).astype(specvals.dtype)
2427
+ elif mode == "complex":
2428
+ pass
2429
+ elif mode == "mag":
2430
+ specvals = np.absolute(specvals)
2431
+ elif mode == "phase":
2432
+ specvals = (np.angle(specvals)).astype(specvals.dtype)
2433
+ elif mode == "power":
2434
+ specvals = np.sqrt(np.absolute(specvals))
2435
+ else:
2436
+ raise RuntimeError("illegal spectrum mode")
2437
+ return specaxis, specvals
1604
2438
 
1605
- self.HRVULF_UPPERPASS = 0.0033
1606
- self.HRVULF_UPPERSTOP = self.HRVULF_UPPERPASS * (1.0 + self.transitionfrac)
1607
2439
 
1608
- self.HRVVLF_LOWERPASS = 0.0033
1609
- self.HRVVLF_UPPERPASS = 0.04
1610
- self.HRVVLF_LOWERSTOP = self.HRVVLF_LOWERPASS * (1.0 - self.transitionfrac)
1611
- self.HRVVLF_UPPERSTOP = self.HRVVLF_UPPERPASS * (1.0 + self.transitionfrac)
2440
+ def setnotchfilter(thefilter: NoncausalFilter, thefreq: float, notchwidth: float = 1.0) -> None:
2441
+ """
2442
+ Set notch filter parameters for the specified filter.
1612
2443
 
1613
- self.HRVLF_LOWERPASS = 0.04
1614
- self.HRVLF_UPPERPASS = 0.15
1615
- self.HRVLF_LOWERSTOP = self.HRVLF_LOWERPASS * (1.0 - self.transitionfrac)
1616
- self.HRVLF_UPPERSTOP = self.HRVLF_UPPERPASS * (1.0 + self.transitionfrac)
2444
+ This function configures a notch filter by setting the filter type to "arb_stop"
2445
+ and defining the frequency range for the notch based on the center frequency
2446
+ and notch width parameters.
1617
2447
 
1618
- self.HRVHF_LOWERPASS = 0.15
1619
- self.HRVHF_UPPERPASS = 0.4
1620
- self.HRVHF_LOWERSTOP = self.HRVHF_LOWERPASS * (1.0 - self.transitionfrac)
1621
- self.HRVHF_UPPERSTOP = self.HRVHF_UPPERPASS * (1.0 + self.transitionfrac)
2448
+ Parameters
2449
+ ----------
2450
+ thefilter : NoncausalFilter function
2451
+ The filter function to configure with notch filter parameters
2452
+ thefreq : float
2453
+ Center frequency of the notch in Hz
2454
+ notchwidth : float, optional
2455
+ Width of the notch in Hz, default is 1.0 Hz
1622
2456
 
1623
- self.HRVVHF_LOWERPASS = 0.4
1624
- self.HRVVHF_UPPERPASS = 0.5
1625
- self.HRVVHF_LOWERSTOP = self.HRVVHF_LOWERPASS * (1.0 - self.transitionfrac)
1626
- self.HRVVHF_UPPERSTOP = self.HRVVHF_UPPERPASS * (1.0 + self.transitionfrac)
2457
+ Returns
2458
+ -------
2459
+ None
2460
+ This function modifies the filter in-place and does not return any value
2461
+
2462
+ Notes
2463
+ -----
2464
+ The notch filter is configured as an "arb_stop" type filter with symmetric
2465
+ frequency bounds around the center frequency. The actual notch range will be
2466
+ from (thefreq - notchwidth/2) to (thefreq + notchwidth/2) Hz.
2467
+
2468
+ Examples
2469
+ --------
2470
+ >>> filter_obj = NoncausalFilter()
2471
+ >>> setnotchfilter(filter_obj, 50.0, 2.0)
2472
+ >>> # Creates a notch filter centered at 50 Hz with 2 Hz width
2473
+ """
2474
+ thefilter.settype("arb_stop")
2475
+ thefilter.setfreqs(
2476
+ thefreq - notchwidth / 2.0,
2477
+ thefreq - notchwidth / 2.0,
2478
+ thefreq + notchwidth / 2.0,
2479
+ thefreq + notchwidth / 2.0,
2480
+ )
1627
2481
 
1628
- self.settype(self.filtertype)
1629
2482
 
1630
- def settype(self, thetype):
1631
- self.filtertype = thetype
1632
- if self.filtertype == "vlf" or self.filtertype == "vlf_stop":
1633
- self.lowerstop = 0.0
1634
- self.lowerpass = 0.0
1635
- self.upperpass = 1.0 * self.VLF_UPPERPASS
1636
- self.upperstop = 1.0 * self.VLF_UPPERSTOP
1637
- elif self.filtertype == "lfo" or self.filtertype == "lfo_stop":
1638
- self.lowerstop = 1.0 * self.LF_LOWERSTOP
1639
- self.lowerpass = 1.0 * self.LF_LOWERPASS
1640
- self.upperpass = 1.0 * self.LF_UPPERPASS
1641
- self.upperstop = 1.0 * self.LF_UPPERSTOP
1642
- elif self.filtertype == "lfo_legacy" or self.filtertype == "lfo_legacy_stop":
1643
- self.lowerstop = 1.0 * self.LF_LEGACY_LOWERSTOP
1644
- self.lowerpass = 1.0 * self.LF_LEGACY_LOWERPASS
1645
- self.upperpass = 1.0 * self.LF_LEGACY_UPPERPASS
1646
- self.upperstop = 1.0 * self.LF_LEGACY_UPPERSTOP
1647
- elif self.filtertype == "resp" or self.filtertype == "resp_stop":
1648
- self.lowerstop = 1.0 * self.RESP_LOWERSTOP
1649
- self.lowerpass = 1.0 * self.RESP_LOWERPASS
1650
- self.upperpass = 1.0 * self.RESP_UPPERPASS
1651
- self.upperstop = 1.0 * self.RESP_UPPERSTOP
1652
- elif self.filtertype == "cardiac" or self.filtertype == "cardiac_stop":
1653
- self.lowerstop = 1.0 * self.CARD_LOWERSTOP
1654
- self.lowerpass = 1.0 * self.CARD_LOWERPASS
1655
- self.upperpass = 1.0 * self.CARD_UPPERPASS
1656
- self.upperstop = 1.0 * self.CARD_UPPERSTOP
1657
- elif self.filtertype == "hrv_ulf" or self.filtertype == "hrv_ulf_stop":
1658
- self.lowerstop = 0.0
1659
- self.lowerpass = 0.0
1660
- self.upperpass = 1.0 * self.HRVULF_UPPERPASS
1661
- self.upperstop = 1.0 * self.HRVULF_UPPERSTOP
1662
- elif self.filtertype == "hrv_vlf" or self.filtertype == "hrv_vlf_stop":
1663
- self.lowerstop = 1.0 * self.HRVVLF_LOWERSTOP
1664
- self.lowerpass = 1.0 * self.HRVVLF_LOWERPASS
1665
- self.upperpass = 1.0 * self.HRVVLF_UPPERPASS
1666
- self.upperstop = 1.0 * self.HRVVLF_UPPERSTOP
1667
- elif self.filtertype == "hrv_lf" or self.filtertype == "hrv_lf_stop":
1668
- self.lowerstop = 1.0 * self.HRVLF_LOWERSTOP
1669
- self.lowerpass = 1.0 * self.HRVLF_LOWERPASS
1670
- self.upperpass = 1.0 * self.HRVLF_UPPERPASS
1671
- self.upperstop = 1.0 * self.HRVLF_UPPERSTOP
1672
- elif self.filtertype == "hrv_hf" or self.filtertype == "hrv_hf_stop":
1673
- self.lowerstop = 1.0 * self.HRVHF_LOWERSTOP
1674
- self.lowerpass = 1.0 * self.HRVHF_LOWERPASS
1675
- self.upperpass = 1.0 * self.HRVHF_UPPERPASS
1676
- self.upperstop = 1.0 * self.HRVHF_UPPERSTOP
1677
- elif self.filtertype == "hrv_vhf" or self.filtertype == "hrv_vhf_stop":
1678
- self.lowerstop = 1.0 * self.HRVVHF_LOWERSTOP
1679
- self.lowerpass = 1.0 * self.HRVVHF_LOWERPASS
1680
- self.upperpass = 1.0 * self.HRVVHF_UPPERPASS
1681
- self.upperstop = 1.0 * self.HRVVHF_UPPERSTOP
1682
- elif self.filtertype == "arb" or self.filtertype == "arb_stop":
1683
- self.lowerstop = 1.0 * self.arb_lowerstop
1684
- self.lowerpass = 1.0 * self.arb_lowerpass
1685
- self.upperpass = 1.0 * self.arb_upperpass
1686
- self.upperstop = 1.0 * self.arb_upperstop
1687
- else:
1688
- self.lowerstop = 0.0
1689
- self.lowerpass = 0.0
1690
- self.upperpass = -1.0
1691
- self.upperstop = -1.0
2483
+ def harmonicnotchfilter(
2484
+ timecourse: NDArray,
2485
+ Fs: float,
2486
+ Ffundamental: float,
2487
+ notchpct: float = 1.0,
2488
+ debug: bool = False,
2489
+ ) -> NDArray:
2490
+ """
2491
+ Apply a harmonic notch filter to remove a fundamental frequency and its harmonics from a timecourse.
1692
2492
 
1693
- def gettype(self):
1694
- return self.filtertype
2493
+ This function removes the specified fundamental frequency and all its integer harmonics
2494
+ using a non-causal notch filtering approach. The width of each notch is proportional
2495
+ to the harmonic order and the specified percentage (`notchpct`).
1695
2496
 
1696
- def setbutterorder(self, order=3):
1697
- self.butterworthorder = order
2497
+ Parameters
2498
+ ----------
2499
+ timecourse : NDArray
2500
+ Input timecourse data to be filtered.
2501
+ Fs : float
2502
+ Sampling rate of the input data in Hz.
2503
+ Ffundamental : float
2504
+ Fundamental frequency to be removed from the data in Hz.
2505
+ notchpct : float, optional
2506
+ Width of the notch relative to the filter frequency in percent. Default is 1.0.
2507
+ debug : bool, optional
2508
+ If True, prints detailed information about the filtering process. Default is False.
1698
2509
 
1699
- def setdebug(self, debug):
1700
- self.debug = debug
2510
+ Returns
2511
+ -------
2512
+ filteredtc : NDArray
2513
+ The filtered timecourse with the fundamental and its harmonics removed.
2514
+
2515
+ Notes
2516
+ -----
2517
+ - The function uses a non-causal filter, meaning it requires the full signal to be available.
2518
+ - Harmonics are calculated as integer multiples of the fundamental frequency.
2519
+ - The notch width is determined by `notchpct * harmonic * Ffundamental * 0.01`, with a minimum
2520
+ width of one frequency bin.
2521
+
2522
+ Examples
2523
+ --------
2524
+ >>> import numpy as np
2525
+ >>> timecourse = np.random.randn(1000)
2526
+ >>> Fs = 100.0
2527
+ >>> Ffundamental = 50.0
2528
+ >>> filtered = harmonicnotchfilter(timecourse, Fs, Ffundamental, notchpct=2.0)
2529
+ """
2530
+ # delete the fundamental and its harmonics
2531
+ filteredtc = timecourse + 0.0
2532
+ maxpass = Fs / 2.0
2533
+ if notchpct is not None:
2534
+ stopfreq = Ffundamental
2535
+ freqstep = 0.5 * Fs / len(filteredtc)
2536
+ maxharmonic = int(maxpass // stopfreq)
2537
+ if debug:
2538
+ print("highest harmonic is", maxharmonic, "(", maxharmonic * stopfreq, "Hz)")
2539
+ thenotchfilter = NoncausalFilter()
2540
+ for harmonic in range(1, maxharmonic + 1):
2541
+ notchfreq = harmonic * stopfreq
2542
+ if debug:
2543
+ print("removing harmonic at", notchfreq)
2544
+ notchwidth = np.max([notchpct * harmonic * stopfreq * 0.01, freqstep])
2545
+ if debug:
2546
+ print("\tFs:", Fs)
2547
+ print("\tstopfreq:", stopfreq)
2548
+ print("\tnotchpct:", notchpct)
2549
+ print("\tnotchwidth:", notchwidth)
2550
+ print("\tnotchfreq:", notchfreq)
2551
+ print("\tfreqstep:", freqstep)
2552
+ print("\tminfreqstep:", freqstep / notchfreq)
2553
+ print("\tbins:", int(notchwidth // freqstep))
2554
+ print()
2555
+ setnotchfilter(thenotchfilter, notchfreq, notchwidth=notchwidth)
2556
+ filteredtc = thenotchfilter.apply(Fs, filteredtc)
2557
+ return filteredtc
1701
2558
 
1702
- def setpadtime(self, padtime):
1703
- self.padtime = padtime
1704
2559
 
1705
- def getpadtime(self):
1706
- return self.padtime
2560
+ def savgolsmooth(data: NDArray, smoothlen: int = 101, polyorder: int = 3) -> NDArray:
2561
+ """
2562
+ Apply Savitzky-Golay filter to smooth data.
1707
2563
 
1708
- def setcyclic(self, cyclic):
1709
- self.cyclic = cyclic
2564
+ This function applies a Savitzky-Golay filter to smooth the input data. The filter uses
2565
+ a least-squares method to fit a polynomial to a sliding window of data points and
2566
+ replaces each point with the value of the polynomial at that point.
1710
2567
 
1711
- def getcyclic(self):
1712
- return self.cyclic
2568
+ Parameters
2569
+ ----------
2570
+ data : array_like
2571
+ Input data to be smoothed. Can be any array-like object that can be converted
2572
+ to a numpy array.
2573
+ smoothlen : int, optional
2574
+ The length of the filter window (i.e., the number of coefficients). Must be a
2575
+ positive odd integer. Default is 101.
2576
+ polyorder : int, optional
2577
+ The order of the polynomial used to fit the samples. Must be less than
2578
+ `smoothlen`. Default is 3.
1713
2579
 
1714
- def settransferfunc(self, transferfunc):
1715
- self.transferfunc = transferfunc
2580
+ Returns
2581
+ -------
2582
+ NDArray
2583
+ The smoothed data with the same shape as the input `data`.
2584
+
2585
+ Notes
2586
+ -----
2587
+ The Savitzky-Golay filter is particularly useful for smoothing noisy data while
2588
+ preserving the shape and features of the underlying signal. It is especially
2589
+ effective for data with a lot of high-frequency noise.
2590
+
2591
+ Examples
2592
+ --------
2593
+ >>> import numpy as np
2594
+ >>> from scipy.signal import savgol_filter
2595
+ >>> x = np.linspace(0, 4*np.pi, 100)
2596
+ >>> y = np.sin(x) + np.random.normal(0, 0.1, 100)
2597
+ >>> y_smooth = savgolsmooth(y, smoothlen=21, polyorder=3)
2598
+ """
2599
+ return savgol_filter(data, smoothlen, polyorder)
1716
2600
 
1717
- def setfreqs(self, lowerstop, lowerpass, upperpass, upperstop):
1718
- if lowerstop > lowerpass:
1719
- print(
1720
- "NoncausalFilter error: lowerstop (",
1721
- lowerstop,
1722
- ") must be <= lowerpass (",
1723
- lowerpass,
1724
- ")",
1725
- )
1726
- sys.exit()
1727
- if upperpass > upperstop:
1728
- print(
1729
- "NoncausalFilter error: upperstop (",
1730
- upperstop,
1731
- ") must be >= upperpass (",
1732
- upperpass,
1733
- ")",
1734
- )
1735
- sys.exit()
1736
- if (lowerpass > upperpass) and (upperpass >= 0.0):
1737
- print(
1738
- "NoncausalFilter error: lowerpass (",
1739
- lowerpass,
1740
- ") must be < upperpass (",
1741
- upperpass,
1742
- ")",
1743
- )
1744
- sys.exit()
1745
- self.arb_lowerstop = 1.0 * lowerstop
1746
- self.arb_lowerpass = 1.0 * lowerpass
1747
- self.arb_upperpass = 1.0 * upperpass
1748
- self.arb_upperstop = 1.0 * upperstop
1749
- self.lowerstop = 1.0 * self.arb_lowerstop
1750
- self.lowerpass = 1.0 * self.arb_lowerpass
1751
- self.upperpass = 1.0 * self.arb_upperpass
1752
- self.upperstop = 1.0 * self.arb_upperstop
1753
2601
 
1754
- def getfreqs(self):
1755
- return self.lowerstop, self.lowerpass, self.upperpass, self.upperstop
2602
+ def csdfilter(
2603
+ obsdata: NDArray,
2604
+ commondata: NDArray,
2605
+ padlen: int = 20,
2606
+ avlen: int = 20,
2607
+ padtype: str = "reflect",
2608
+ debug: bool = False,
2609
+ ) -> NDArray:
2610
+ """
2611
+ Cross spectral density filter - makes a filter transfer function that preserves common frequencies.
1756
2612
 
1757
- def apply(self, Fs, data):
1758
- r"""Apply the filter to a dataset.
2613
+ This function applies a filter based on the cross spectral density between two signals.
2614
+ It uses the Fourier transform to compute the transfer function and applies it to the
2615
+ observation data, preserving the frequency components that are common between the two
2616
+ input signals.
1759
2617
 
1760
- Parameters
1761
- ----------
1762
- Fs : float
1763
- Sample frequency
1764
- data : 1D float array
1765
- The data to filter
2618
+ Parameters
2619
+ ----------
2620
+ obsdata : NDArray
2621
+ Input data (1D numpy array) to be filtered.
2622
+ commondata : NDArray
2623
+ Shared data (1D numpy array) used to compute the transfer function.
2624
+ padlen : int, optional
2625
+ Number of reflected points to add on each end of the input data. Default is 20.
2626
+ avlen : int, optional
2627
+ Length of the averaging window for padding. Default is 20.
2628
+ padtype : str, optional
2629
+ Type of padding to use. Options are 'reflect' or 'cyclic'. Default is 'reflect'.
2630
+ debug : bool, optional
2631
+ Set to True for additional information on function internals. Default is False.
1766
2632
 
1767
- Returns
1768
- -------
1769
- filtereddata : 1D float array
1770
- The filtered data
1771
- """
1772
- # if filterband is None, just return the data
1773
- if self.filtertype == "None":
1774
- return data
2633
+ Returns
2634
+ -------
2635
+ NDArray
2636
+ The filtered data (1D numpy array) with preserved common frequencies.
2637
+
2638
+ Notes
2639
+ -----
2640
+ The function first pads both input arrays using the specified padding method, then computes
2641
+ the FFT of both padded arrays. A transfer function is constructed from the square root of
2642
+ the magnitude of the product of the FFTs. This transfer function is applied to the FFT of
2643
+ the observation data, and the inverse FFT is taken to return the filtered signal.
2644
+
2645
+ Examples
2646
+ --------
2647
+ >>> import numpy as np
2648
+ >>> obs = np.random.randn(100)
2649
+ >>> common = np.random.randn(100)
2650
+ >>> filtered = csdfilter(obs, common, padlen=10)
2651
+ """
2652
+ padobsdata = padvec(obsdata, padlen=padlen, avlen=avlen, padtype=padtype, debug=debug)
2653
+ padcommondata = padvec(commondata, padlen=padlen, avlen=avlen, padtype=padtype, debug=debug)
2654
+ obsdata_trans = fftpack.fft(padobsdata)
2655
+ transferfunc = np.sqrt(np.abs(fftpack.fft(padobsdata) * np.conj(fftpack.fft(padcommondata))))
2656
+ obsdata_trans *= transferfunc
2657
+ return unpadvec(fftpack.ifft(obsdata_trans).real, padlen=padlen)
1775
2658
 
1776
- # do some bounds checking
1777
- nyquistlimit = 0.5 * Fs
1778
- lowestfreq = 2.0 * Fs / np.shape(data)[0]
1779
2659
 
1780
- # first see if entire range is out of bounds
1781
- if self.lowerpass >= nyquistlimit:
1782
- print(
1783
- "NoncausalFilter error: filter lower pass ",
1784
- self.lowerpass,
1785
- " exceeds nyquist frequency ",
1786
- nyquistlimit,
1787
- )
1788
- sys.exit()
1789
- if self.lowerstop >= nyquistlimit:
1790
- print(
1791
- "NoncausalFilter error: filter lower stop ",
1792
- self.lowerstop,
1793
- " exceeds nyquist frequency ",
1794
- nyquistlimit,
1795
- )
1796
- sys.exit()
1797
- if -1.0 < self.upperpass <= lowestfreq:
1798
- print(
1799
- "NoncausalFilter error: filter upper pass ",
1800
- self.upperpass,
1801
- " is below minimum frequency ",
1802
- lowestfreq,
1803
- )
1804
- sys.exit()
1805
- if -1.0 < self.upperstop <= lowestfreq:
1806
- print(
1807
- "NoncausalFilter error: filter upper stop ",
1808
- self.upperstop,
1809
- " is below minimum frequency ",
1810
- lowestfreq,
1811
- )
1812
- sys.exit()
2660
+ # @conditionaljit()
2661
+ def arb_pass(
2662
+ Fs: float,
2663
+ inputdata: NDArray,
2664
+ lowerstop: float,
2665
+ lowerpass: float,
2666
+ upperpass: float,
2667
+ upperstop: float,
2668
+ transferfunc: str = "trapezoidal",
2669
+ butterorder: int = 6,
2670
+ padlen: int = 20,
2671
+ avlen: int = 20,
2672
+ padtype: str = "reflect",
2673
+ debug: bool = False,
2674
+ ) -> NDArray:
2675
+ """
2676
+ Filters an input waveform over a specified range using configurable filter types.
1813
2677
 
1814
- # now look for fixable errors
1815
- if self.upperpass >= nyquistlimit:
1816
- if self.correctfreq:
1817
- self.upperpass = nyquistlimit
1818
- else:
1819
- print(
1820
- "NoncausalFilter error: filter upper pass ",
1821
- self.upperpass,
1822
- " exceeds nyquist frequency ",
1823
- nyquistlimit,
1824
- )
1825
- sys.exit()
1826
- if self.upperstop > nyquistlimit:
1827
- if self.correctfreq:
1828
- self.upperstop = nyquistlimit
1829
- else:
1830
- print(
1831
- "NoncausalFilter error: filter upper stop ",
1832
- self.upperstop,
1833
- " exceeds nyquist frequency ",
1834
- nyquistlimit,
1835
- )
1836
- sys.exit()
1837
- if self.lowerpass < lowestfreq:
1838
- if self.correctfreq:
1839
- self.lowerpass = lowestfreq
1840
- else:
1841
- print(
1842
- "NoncausalFilter error: filter lower pass ",
1843
- self.lowerpass,
1844
- " is below minimum frequency ",
1845
- lowestfreq,
1846
- )
1847
- sys.exit()
1848
- if self.lowerstop < lowestfreq:
1849
- if self.correctfreq:
1850
- self.lowerstop = lowestfreq
1851
- else:
1852
- print(
1853
- "NoncausalFilter error: filter lower stop ",
1854
- self.lowerstop,
1855
- " is below minimum frequency ",
1856
- lowestfreq,
1857
- )
1858
- sys.exit()
2678
+ By default, it applies a trapezoidal FFT filter, but brickwall and Butterworth
2679
+ filters are also supported. The function handles lowpass, bandpass, and highpass
2680
+ filtering based on the input frequency limits. Ends of the input data are padded
2681
+ to reduce transients.
1859
2682
 
1860
- if self.padtime < 0.0:
1861
- padlen = int(len(data) // 2)
1862
- else:
1863
- padlen = int(self.padtime * Fs)
1864
- if self.debug:
1865
- print("Fs=", Fs)
1866
- print("lowerstop=", self.lowerstop)
1867
- print("lowerpass=", self.lowerpass)
1868
- print("upperpass=", self.upperpass)
1869
- print("upperstop=", self.upperstop)
1870
- print("butterworthorder=", self.butterworthorder)
1871
- print("padtime=", self.padtime)
1872
- print("padlen=", padlen)
1873
- print("cyclic=", self.cyclic)
2683
+ Parameters
2684
+ ----------
2685
+ Fs : float
2686
+ Sample rate in Hz.
2687
+ inputdata : NDArray
2688
+ Input data to be filtered.
2689
+ lowerstop : float
2690
+ Upper end of lower stopband in Hz.
2691
+ lowerpass : float
2692
+ Lower end of passband in Hz.
2693
+ upperpass : float
2694
+ Upper end of passband in Hz.
2695
+ upperstop : float
2696
+ Lower end of upper stopband in Hz.
2697
+ transferfunc : str, optional
2698
+ Type of transfer function to use. Options are:
2699
+ - "trapezoidal" (default)
2700
+ - "brickwall"
2701
+ - "butterworth"
2702
+ butterorder : int, optional
2703
+ Order of Butterworth filter, if used. Default is 6.
2704
+ padlen : int, optional
2705
+ Amount of points to reflect around each end of the input vector prior to filtering.
2706
+ Default is 20.
2707
+ avlen : int, optional
2708
+ Length of averaging window for filtering. Default is 20.
2709
+ padtype : str, optional
2710
+ Padding type for end effects. Options are:
2711
+ - "reflect" (default)
2712
+ - "wrap"
2713
+ debug : bool, optional
2714
+ If True, internal states of the function will be printed to help debugging.
2715
+ Default is False.
1874
2716
 
1875
- # now do the actual filtering
1876
- if self.filtertype == "None":
1877
- return data
1878
- elif self.filtertype == "ringstop":
1879
- return arb_pass(
2717
+ Returns
2718
+ -------
2719
+ filtereddata : NDArray
2720
+ The filtered data as a 1D float array.
2721
+
2722
+ Notes
2723
+ -----
2724
+ The function automatically determines whether to apply a lowpass, highpass, or
2725
+ bandpass filter based on the values of `lowerpass` and `upperpass`. For bandpass
2726
+ filters, a cascade of lowpass and highpass Butterworth filters is used when
2727
+ `transferfunc="butterworth"`.
2728
+
2729
+ Examples
2730
+ --------
2731
+ >>> import numpy as np
2732
+ >>> Fs = 100.0
2733
+ >>> t = np.linspace(0, 1, int(Fs), endpoint=False)
2734
+ >>> signal = np.sin(2 * np.pi * 10 * t) + 0.5 * np.sin(2 * np.pi * 20 * t)
2735
+ >>> filtered = arb_pass(
2736
+ ... Fs=Fs,
2737
+ ... inputdata=signal,
2738
+ ... lowerstop=5.0,
2739
+ ... lowerpass=8.0,
2740
+ ... upperpass=15.0,
2741
+ ... upperstop=20.0,
2742
+ ... transferfunc="trapezoidal"
2743
+ ... )
2744
+ """
2745
+ # adjust the padding for speed
2746
+ if pyfftwpresent:
2747
+ thefftlen = optfftlen(len(inputdata), padlen=padlen)
2748
+ padlen = int((thefftlen - len(inputdata)) // 2)
2749
+
2750
+ # check filter limits to see if we should do a lowpass, bandpass, or highpass
2751
+ if lowerpass <= 0.0:
2752
+ # set up for lowpass
2753
+ if transferfunc == "butterworth":
2754
+ retvec = dolpfiltfilt(
1880
2755
  Fs,
1881
- data,
1882
- 0.0,
1883
- 0.0,
1884
- Fs / 4.0,
1885
- 1.1 * Fs / 4.0,
1886
- transferfunc=self.transferfunc,
1887
- butterorder=self.butterworthorder,
2756
+ upperpass,
2757
+ inputdata,
2758
+ butterorder,
1888
2759
  padlen=padlen,
1889
- cyclic=self.cyclic,
1890
- debug=self.debug,
2760
+ avlen=avlen,
2761
+ padtype=padtype,
2762
+ debug=debug,
1891
2763
  )
1892
- elif (
1893
- self.filtertype == "vlf"
1894
- or self.filtertype == "lfo"
1895
- or self.filtertype == "lfo_legacy"
1896
- or self.filtertype == "resp"
1897
- or self.filtertype == "cardiac"
1898
- or self.filtertype == "hrv_ulf"
1899
- or self.filtertype == "hrv_vlf"
1900
- or self.filtertype == "hrv_lf"
1901
- or self.filtertype == "hrv_hf"
1902
- or self.filtertype == "hrv_vhf"
1903
- ):
1904
- return arb_pass(
2764
+ return retvec
2765
+ else:
2766
+ return dolptransfuncfilt(
1905
2767
  Fs,
1906
- data,
1907
- self.lowerstop,
1908
- self.lowerpass,
1909
- self.upperpass,
1910
- self.upperstop,
1911
- transferfunc=self.transferfunc,
1912
- butterorder=self.butterworthorder,
2768
+ inputdata,
2769
+ upperpass=upperpass,
2770
+ upperstop=upperstop,
2771
+ type=transferfunc,
1913
2772
  padlen=padlen,
1914
- cyclic=self.cyclic,
1915
- debug=self.debug,
2773
+ avlen=avlen,
2774
+ padtype=padtype,
2775
+ debug=debug,
1916
2776
  )
1917
- elif (
1918
- self.filtertype == "vlf_stop"
1919
- or self.filtertype == "lfo_stop"
1920
- or self.filtertype == "lfo_legacy_stop"
1921
- or self.filtertype == "resp_stop"
1922
- or self.filtertype == "cardiac_stop"
1923
- or self.filtertype == "hrv_ulf_stop"
1924
- or self.filtertype == "hrv_vlf_stop"
1925
- or self.filtertype == "hrv_lf_stop"
1926
- or self.filtertype == "hrv_hf_stop"
1927
- or self.filtertype == "hrv_vhf_stop"
1928
- ):
1929
- return data - arb_pass(
2777
+ elif (upperpass >= Fs / 2.0) or (upperpass <= 0.0):
2778
+ # set up for highpass
2779
+ if transferfunc == "butterworth":
2780
+ return dohpfiltfilt(
1930
2781
  Fs,
1931
- data,
1932
- self.lowerstop,
1933
- self.lowerpass,
1934
- self.upperpass,
1935
- self.upperstop,
1936
- transferfunc=self.transferfunc,
1937
- butterorder=self.butterworthorder,
2782
+ lowerpass,
2783
+ inputdata,
2784
+ butterorder,
1938
2785
  padlen=padlen,
1939
- cyclic=self.cyclic,
1940
- debug=self.debug,
2786
+ avlen=avlen,
2787
+ padtype=padtype,
2788
+ debug=debug,
1941
2789
  )
1942
- elif self.filtertype == "arb":
1943
- return arb_pass(
2790
+ else:
2791
+ return dohptransfuncfilt(
1944
2792
  Fs,
1945
- data,
1946
- self.arb_lowerstop,
1947
- self.arb_lowerpass,
1948
- self.arb_upperpass,
1949
- self.arb_upperstop,
1950
- transferfunc=self.transferfunc,
1951
- butterorder=self.butterworthorder,
2793
+ inputdata,
2794
+ lowerpass,
2795
+ lowerstop=lowerstop,
2796
+ type=transferfunc,
1952
2797
  padlen=padlen,
1953
- cyclic=self.cyclic,
1954
- debug=self.debug,
2798
+ avlen=avlen,
2799
+ padtype=padtype,
2800
+ debug=debug,
1955
2801
  )
1956
- elif self.filtertype == "arb_stop":
1957
- return data - arb_pass(
2802
+ else:
2803
+ # set up for bandpass
2804
+ if transferfunc == "butterworth":
2805
+ return dohpfiltfilt(
1958
2806
  Fs,
1959
- data,
1960
- self.arb_lowerstop,
1961
- self.arb_lowerpass,
1962
- self.arb_upperpass,
1963
- self.arb_upperstop,
1964
- transferfunc=self.transferfunc,
1965
- butterorder=self.butterworthorder,
2807
+ lowerpass,
2808
+ dolpfiltfilt(
2809
+ Fs,
2810
+ upperpass,
2811
+ inputdata,
2812
+ butterorder,
2813
+ padlen=padlen,
2814
+ avlen=avlen,
2815
+ padtype=padtype,
2816
+ debug=debug,
2817
+ ),
2818
+ butterorder,
1966
2819
  padlen=padlen,
1967
- cyclic=self.cyclic,
1968
- debug=self.debug,
2820
+ avlen=avlen,
2821
+ padtype=padtype,
2822
+ debug=debug,
1969
2823
  )
1970
2824
  else:
1971
- print(f"bad filter type: {self.filtertype}")
2825
+ return dobptransfuncfilt(
2826
+ Fs,
2827
+ inputdata,
2828
+ lowerpass,
2829
+ upperpass,
2830
+ lowerstop=lowerstop,
2831
+ upperstop=upperstop,
2832
+ type=transferfunc,
2833
+ padlen=padlen,
2834
+ avlen=avlen,
2835
+ padtype=padtype,
2836
+ debug=debug,
2837
+ )
2838
+
2839
+
2840
+ class Plethfilter:
2841
+ def __init_(self, Fs, Fl, Fh, order=4, attenuation=20):
2842
+ """
2843
+ Initialize Chebyshev type II bandpass filter.
2844
+
2845
+ Parameters
2846
+ ----------
2847
+ Fs : float
2848
+ Sampling frequency in Hz.
2849
+ Fl : float
2850
+ Lower cutoff frequency in Hz.
2851
+ Fh : float
2852
+ Higher cutoff frequency in Hz.
2853
+ order : int, optional
2854
+ Filter order (default is 4).
2855
+ attenuation : float, optional
2856
+ Stopband attenuation in dB (default is 20).
2857
+
2858
+ Returns
2859
+ -------
2860
+ None
2861
+ Initializes filter coefficients and parameters.
2862
+
2863
+ Notes
2864
+ -----
2865
+ This function creates a Chebyshev type II bandpass filter using scipy.signal.cheby2.
2866
+ The filter has a flat response in the passband and an equiripple response in the stopband.
2867
+ The filter coefficients are stored in self.b and self.a for subsequent filtering operations.
2868
+
2869
+ Examples
2870
+ --------
2871
+ >>> filter = BandpassFilter(Fs=1000, Fl=100, Fh=300, order=6, attenuation=30)
2872
+ >>> print(filter.b) # Print filter numerator coefficients
2873
+ >>> print(filter.a) # Print filter denominator coefficients
2874
+ """
2875
+ self.Fs = Fs
2876
+ self.Fh = Fh
2877
+ self.Fl = Fl
2878
+ self.attenuation = attenuation
2879
+ self.order = order
2880
+ retvec = signal.cheby2(
2881
+ self.order,
2882
+ self.attenuation,
2883
+ [self.Fl / self.Fn, self.Fh / self.Fn],
2884
+ btype="bandpass",
2885
+ analog=False,
2886
+ output="ba",
2887
+ )
2888
+ self.b = retvec[0]
2889
+ self.a = retvec[1]
2890
+
2891
+ def apply(self, data):
2892
+ return signal.filtfilt(self.b, self.a, data, axis=-1, padtype="odd", padlen=None)
2893
+
2894
+
2895
+ def getfilterbandfreqs(
2896
+ band: str, transitionfrac: float = 0.05, species: str = "human", asrange: bool = False
2897
+ ) -> Union[str, Tuple[float, float, float, float]]:
2898
+ """
2899
+ Apply digital filter forward and backward to data.
2900
+
2901
+ This function applies a digital filter to the input data using forward-backward filtering
2902
+ to eliminate phase distortion. The filter is applied along the last axis of the data.
2903
+
2904
+ Parameters
2905
+ ----------
2906
+ data : array_like
2907
+ Input data to be filtered. Can be any shape, but filtering is applied along the
2908
+ last axis (axis=-1).
2909
+
2910
+ Returns
2911
+ -------
2912
+ NDArray
2913
+ The filtered output with the same shape as the input data.
2914
+
2915
+ Notes
2916
+ -----
2917
+ This function uses `scipy.signal.filtfilt` which applies the filter forward and backward
2918
+ to eliminate phase distortion. The filter coefficients are stored in `self.b` and `self.a`.
2919
+
2920
+ Examples
2921
+ --------
2922
+ >>> import numpy as np
2923
+ >>> from scipy import signal
2924
+ >>> # Create a simple filter
2925
+ >>> b, a = signal.butter(4, 0.2, 'low')
2926
+ >>> # Apply filter to data
2927
+ >>> filtered_data = apply(data)
2928
+ """
2929
+ if species == "human":
2930
+ if band == "vlf":
2931
+ lowerpass = 0.0
2932
+ upperpass = 0.009
2933
+ lowerstop = 0.0
2934
+ upperstop = upperpass * (1.0 + transitionfrac)
2935
+ elif band == "lfo":
2936
+ lowerpass = 0.01
2937
+ upperpass = 0.15
2938
+ lowerstop = lowerpass * (1.0 - transitionfrac)
2939
+ upperstop = upperpass * (1.0 + transitionfrac)
2940
+ elif band == "lfo_legacy":
2941
+ lowerpass = 0.01
2942
+ upperpass = 0.15
2943
+ lowerstop = 0.009
2944
+ upperstop = 0.2
2945
+ elif band == "lfo_tight":
2946
+ lowerpass = 0.01
2947
+ upperpass = 0.10
2948
+ lowerstop = lowerpass * (1.0 - transitionfrac)
2949
+ upperstop = upperpass * (1.0 + transitionfrac)
2950
+ elif band == "resp":
2951
+ lowerpass = 0.2
2952
+ upperpass = 0.5
2953
+ lowerstop = lowerpass * (1.0 - transitionfrac)
2954
+ upperstop = upperpass * (1.0 + transitionfrac)
2955
+ elif band == "cardiac":
2956
+ lowerpass = 0.66
2957
+ upperpass = 3.0
2958
+ lowerstop = lowerpass * (1.0 - transitionfrac)
2959
+ upperstop = upperpass * (1.0 + transitionfrac)
2960
+ elif band == "hrv_ulf":
2961
+ lowerpass = 0.0
2962
+ upperpass = 0.0033
2963
+ lowerstop = lowerpass * (1.0 - transitionfrac)
2964
+ upperstop = upperpass * (1.0 + transitionfrac)
2965
+ elif band == "hrv_vlf":
2966
+ lowerpass = 0.0033
2967
+ upperpass = 0.04
2968
+ lowerstop = lowerpass * (1.0 - transitionfrac)
2969
+ upperstop = upperpass * (1.0 + transitionfrac)
2970
+ elif band == "hrv_lf":
2971
+ lowerpass = 0.04
2972
+ upperpass = 0.15
2973
+ lowerstop = lowerpass * (1.0 - transitionfrac)
2974
+ upperstop = upperpass * (1.0 + transitionfrac)
2975
+ elif band == "hrv_hf":
2976
+ lowerpass = 0.15
2977
+ upperpass = 0.4
2978
+ lowerstop = lowerpass * (1.0 - transitionfrac)
2979
+ upperstop = upperpass * (1.0 + transitionfrac)
2980
+ elif band == "hrv_vhf":
2981
+ lowerpass = 0.4
2982
+ upperpass = 0.5
2983
+ lowerstop = lowerpass * (1.0 - transitionfrac)
2984
+ upperstop = upperpass * (1.0 + transitionfrac)
2985
+ else:
2986
+ print(f"unknown filter band: {band}")
1972
2987
  sys.exit()
2988
+ else:
2989
+ print(f"unknown species: {species}")
2990
+ sys.exit()
2991
+ if asrange:
2992
+ return f"{lowerpass}-{upperpass}Hz"
2993
+ else:
2994
+ return lowerpass, upperpass, lowerstop, upperstop
1973
2995
 
1974
2996
 
1975
2997
  # --------------------------- FFT helper functions ---------------------------------------------
1976
- def polarfft(inputdata):
2998
+ def polarfft(inputdata: NDArray) -> Tuple[NDArray, NDArray]:
2999
+ """
3000
+ Compute the polar representation of the FFT of input data.
3001
+
3002
+ This function computes the Fast Fourier Transform of the input data and returns
3003
+ its magnitude and phase angle in polar coordinates.
3004
+
3005
+ Parameters
3006
+ ----------
3007
+ inputdata : array_like
3008
+ Input data to transform. Can be real or complex valued.
3009
+
3010
+ Returns
3011
+ -------
3012
+ tuple of NDArray
3013
+ A tuple containing:
3014
+ - magnitude : NDArray
3015
+ The magnitude (absolute value) of the FFT result
3016
+ - phase : NDArray
3017
+ The phase angle (in radians) of the FFT result
3018
+
3019
+ Notes
3020
+ -----
3021
+ This function uses `scipy.fftpack.fft` for the FFT computation and returns
3022
+ the polar representation of the complex FFT result. The magnitude represents
3023
+ the amplitude spectrum while the phase represents the phase spectrum.
3024
+
3025
+ Examples
3026
+ --------
3027
+ >>> import numpy as np
3028
+ >>> from scipy import fftpack
3029
+ >>> x = np.array([1, 2, 3, 4])
3030
+ >>> magnitude, phase = polarfft(x)
3031
+ >>> print("Magnitude:", magnitude)
3032
+ >>> print("Phase:", phase)
3033
+ """
1977
3034
  complexxform = fftpack.fft(inputdata)
1978
3035
  return np.abs(complexxform), np.angle(complexxform)
1979
3036
 
1980
3037
 
1981
- def ifftfrompolar(r, theta):
3038
+ def ifftfrompolar(r: NDArray, theta: NDArray) -> NDArray:
3039
+ """
3040
+ Compute inverse Fourier transform from polar representation.
3041
+
3042
+ This function converts magnitude and phase data to complex form and
3043
+ computes the inverse Fourier transform, returning only the real part.
3044
+
3045
+ Parameters
3046
+ ----------
3047
+ r : array_like
3048
+ Magnitude values of the polar representation.
3049
+ theta : array_like
3050
+ Phase values (in radians) of the polar representation.
3051
+
3052
+ Returns
3053
+ -------
3054
+ NDArray
3055
+ Real part of the inverse Fourier transform of the complex signal.
3056
+
3057
+ Notes
3058
+ -----
3059
+ The function assumes that the input arrays `r` and `theta` have the same shape
3060
+ and represent the magnitude and phase of a complex signal in polar form.
3061
+ The result is the inverse Fourier transform of the complex signal r * exp(1j * theta).
3062
+
3063
+ Examples
3064
+ --------
3065
+ >>> import numpy as np
3066
+ >>> r = np.array([1.0, 0.5, 0.2])
3067
+ >>> theta = np.array([0.0, np.pi/4, np.pi/2])
3068
+ >>> result = ifftfrompolar(r, theta)
3069
+ >>> print(result)
3070
+ [ 0.54123456 -0.12345678 0.23456789]
3071
+ """
1982
3072
  complexxform = r * np.exp(1j * theta)
1983
3073
  return fftpack.ifft(complexxform).real
1984
3074
 
1985
3075
 
1986
3076
  # --------------------------- Window functions -------------------------------------------------
1987
- BHwindows = {}
3077
+ BHwindows: dict = {}
1988
3078
 
1989
3079
 
1990
- def blackmanharris(length, debug=False):
1991
- r"""Returns a Blackman Harris window function of the specified length.
1992
- Once calculated, windows are cached for speed.
3080
+ def blackmanharris(length: int, debug: bool = False) -> NDArray:
3081
+ """
3082
+ Returns a Blackman-Harris window function of the specified length.
3083
+
3084
+ The Blackman-Harris window is a tapering function used in signal processing
3085
+ to reduce spectral leakage. It is defined as a weighted sum of cosine terms
3086
+ with specific coefficients that minimize the sidelobe level.
1993
3087
 
1994
3088
  Parameters
1995
3089
  ----------
1996
3090
  length : int
1997
- The length of the window function
1998
- :param length:
1999
-
2000
- debug : boolean, optional
3091
+ The length of the window function.
3092
+ debug : bool, optional
2001
3093
  When True, internal states of the function will be printed to help debugging.
2002
- :param debug:
3094
+ Default is False.
2003
3095
 
2004
3096
  Returns
2005
3097
  -------
2006
- windowfunc : 1D float array
2007
- The window function
3098
+ windowfunc : NDArray
3099
+ The Blackman-Harris window function of the specified length, as a 1D float array.
3100
+
3101
+ Notes
3102
+ -----
3103
+ This function uses a caching mechanism to store previously computed window functions
3104
+ for improved performance when the same window length is requested multiple times.
3105
+
3106
+ The window is defined by the following formula:
3107
+ w(n) = a0 - a1*cos(2πn/M) + a2*cos(4πn/M) - a3*cos(6πn/M)
3108
+
3109
+ where M = length - 1, and the coefficients are:
3110
+ a0 = 0.35875, a1 = 0.48829, a2 = 0.14128, a3 = 0.01168
3111
+
3112
+ Examples
3113
+ --------
3114
+ >>> from numpy import array
3115
+ >>> window = blackmanharris(8)
3116
+ >>> print(window)
3117
+ [0.00000000e+00 1.15530000e-02 1.00000000e+00 1.00000000e+00
3118
+ 1.00000000e+00 1.00000000e+00 1.15530000e-02 0.00000000e+00]
2008
3119
  """
2009
3120
  # return a0 - a1 * np.cos(argvec) + a2 * np.cos(2.0 * argvec) - a3 * np.cos(3.0 * argvec)
2010
3121
  try:
@@ -2023,27 +3134,47 @@ def blackmanharris(length, debug=False):
2023
3134
  return BHwindows[str(length)]
2024
3135
 
2025
3136
 
2026
- hannwindows = {}
3137
+ hannwindows: dict = {}
2027
3138
 
2028
3139
 
2029
- def hann(length, debug=False):
2030
- r"""Returns a Hann window function of the specified length. Once calculated, windows
2031
- are cached for speed.
3140
+ def hann(length: int, debug: bool = False) -> NDArray:
3141
+ """
3142
+ Returns a Hann window function of the specified length.
3143
+
3144
+ Once calculated, windows are cached for speed.
2032
3145
 
2033
3146
  Parameters
2034
3147
  ----------
2035
3148
  length : int
2036
- The length of the window function
2037
- :param length:
2038
-
2039
- debug : boolean, optional
3149
+ The length of the window function.
3150
+ debug : bool, optional
2040
3151
  When True, internal states of the function will be printed to help debugging.
2041
- :param debug:
3152
+ Default is False.
2042
3153
 
2043
3154
  Returns
2044
3155
  -------
2045
- windowfunc : 1D float array
2046
- The window function
3156
+ windowfunc : NDArray
3157
+ The Hann window function as a 1D float array of the specified length.
3158
+
3159
+ Notes
3160
+ -----
3161
+ The Hann window is defined as:
3162
+ w(n) = 0.5 * (1 - cos(2πn/(N-1)))
3163
+
3164
+ where N is the window length and n ranges from 0 to N-1.
3165
+
3166
+ This implementation uses a cached approach for improved performance when
3167
+ the same window lengths are requested multiple times.
3168
+
3169
+ Examples
3170
+ --------
3171
+ >>> from numpy import array
3172
+ >>> hann(5)
3173
+ array([0. , 0.25 , 0.75 , 0.25 , 0. ])
3174
+
3175
+ >>> hann(4, debug=True)
3176
+ initialized hann window for length 4
3177
+ array([0. , 0.5 , 1. , 0.5 ])
2047
3178
  """
2048
3179
  # return 0.5 * (1.0 - np.cos(np.arange(0.0, 1.0, 1.0 / float(length)) * 2.0 * np.pi))
2049
3180
  try:
@@ -2057,15 +3188,89 @@ def hann(length, debug=False):
2057
3188
  return hannwindows[str(length)]
2058
3189
 
2059
3190
 
2060
- hammingwindows = {}
3191
+ hammingwindows: dict = {}
3192
+
3193
+
3194
+ def rect(length: int, L: float) -> NDArray:
3195
+ """
3196
+ Generate a rectangular window function.
3197
+
3198
+ This function creates a rectangular window of specified length and width,
3199
+ where the window has a value of 1 within the specified width and 0 outside.
2061
3200
 
3201
+ Parameters
3202
+ ----------
3203
+ length : int
3204
+ The length of the output array.
3205
+ L : float
3206
+ The width of the rectangular window (in samples).
2062
3207
 
2063
- def rect(length, L):
3208
+ Returns
3209
+ -------
3210
+ NDArray
3211
+ A numpy array of shape (length,) containing the rectangular window
3212
+ function values, where values are 1.0 within the window and 0.0 outside.
3213
+
3214
+ Notes
3215
+ -----
3216
+ The rectangular window is centered at the middle of the array. The window
3217
+ extends from -L/2 to +L/2 relative to the center. Values outside this range
3218
+ are set to zero.
3219
+
3220
+ Examples
3221
+ --------
3222
+ >>> rect(5, 3)
3223
+ array([0., 1., 1., 1., 0.])
3224
+
3225
+ >>> rect(6, 4)
3226
+ array([0., 1., 1., 1., 1., 0.])
3227
+ """
2064
3228
  thearray = np.abs(np.linspace(0, length, length, endpoint=False) - length / 2.0)
2065
3229
  return np.where(thearray <= L / 2.0, 1.0, 0.0)
2066
3230
 
2067
3231
 
2068
- def mRect(length, alpha=0.5, omegac=None, phi=0.0, debug=False):
3232
+ def mRect(
3233
+ length: int,
3234
+ alpha: float = 0.5,
3235
+ omegac: Optional[float] = None,
3236
+ phi: float = 0.0,
3237
+ debug: bool = False,
3238
+ ) -> NDArray:
3239
+ """
3240
+ Generate a modified rectangular window function.
3241
+
3242
+ This function creates a window by combining a base rectangular function with
3243
+ a scaled second rectangular function modulated by a cosine term. The resulting
3244
+ window is normalized to have a maximum value of 1.
3245
+
3246
+ Parameters
3247
+ ----------
3248
+ length : int
3249
+ Length of the window array to be generated.
3250
+ alpha : float, optional
3251
+ Scaling factor for the second rectangular function, by default 0.5
3252
+ omegac : float, optional
3253
+ Cutoff frequency parameter. If None, defaults to 2.0/length, by default None
3254
+ phi : float, optional
3255
+ Phase shift in radians for the cosine term, by default 0.0
3256
+ debug : bool, optional
3257
+ If True, plots the individual components of the window, by default False
3258
+
3259
+ Returns
3260
+ -------
3261
+ NDArray
3262
+ Normalized window array of shape (length,) with maximum value of 1
3263
+
3264
+ Notes
3265
+ -----
3266
+ The window is constructed as:
3267
+ w(n) = [rect(length, 1/omegac) + alpha * rect(length, 2/omegac) * cos(π * omegac * n + phi)] / max(w)
3268
+
3269
+ Examples
3270
+ --------
3271
+ >>> window = mRect(100)
3272
+ >>> window = mRect(100, alpha=0.3, omegac=0.1, phi=np.pi/4)
3273
+ """
2069
3274
  if omegac is None:
2070
3275
  omegac = 2.0 / length
2071
3276
  L = 1.0 / omegac
@@ -2081,25 +3286,42 @@ def mRect(length, alpha=0.5, omegac=None, phi=0.0, debug=False):
2081
3286
  return thewindow / np.max(thewindow)
2082
3287
 
2083
3288
 
2084
- def hamming(length, debug=False):
3289
+ def hamming(length: int, debug: bool = False) -> NDArray:
2085
3290
  # return 0.54 - 0.46 * np.cos((np.arange(0.0, float(length), 1.0) / float(length)) * 2.0 * np.pi)
2086
- r"""Returns a Hamming window function of the specified length. Once calculated, windows
2087
- are cached for speed.
3291
+ """
3292
+ Returns a Hamming window function of the specified length.
3293
+
3294
+ Once calculated, windows are cached for speed.
2088
3295
 
2089
3296
  Parameters
2090
3297
  ----------
2091
3298
  length : int
2092
3299
  The length of the window function
2093
- :param length:
2094
-
2095
- debug : boolean, optional
3300
+ debug : bool, optional
2096
3301
  When True, internal states of the function will be printed to help debugging.
2097
- :param debug:
3302
+ Default is False.
2098
3303
 
2099
3304
  Returns
2100
3305
  -------
2101
3306
  windowfunc : 1D float array
2102
- The window function
3307
+ The Hamming window function of the specified length
3308
+
3309
+ Notes
3310
+ -----
3311
+ The Hamming window is defined as:
3312
+ w(n) = 0.54 - 0.46 * cos(2 * π * n / (N-1))
3313
+
3314
+ where N is the window length and n ranges from 0 to N-1.
3315
+
3316
+ Examples
3317
+ --------
3318
+ >>> from numpy import array
3319
+ >>> hamming(4)
3320
+ array([0.08, 1.0 , 1.0 , 0.08])
3321
+
3322
+ >>> hamming(5, debug=True)
3323
+ initialized hamming window for length 5
3324
+ array([0.08, 1.0 , 1.0 , 1.0 , 0.08])
2103
3325
  """
2104
3326
  try:
2105
3327
  return hammingwindows[str(length)]
@@ -2112,28 +3334,48 @@ def hamming(length, debug=False):
2112
3334
  return hammingwindows[str(length)]
2113
3335
 
2114
3336
 
2115
- def windowfunction(length, type="hamming", debug=False):
2116
- r"""Returns a window function of the specified length and type. Once calculated, windows
3337
+ def windowfunction(length: int, type: str = "hamming", debug: bool = False) -> NDArray:
3338
+ """
3339
+ Returns a window function of the specified length and type. Once calculated, windows
2117
3340
  are cached for speed.
2118
3341
 
2119
3342
  Parameters
2120
3343
  ----------
2121
3344
  length : int
2122
- The length of the window function
2123
- :param length:
2124
-
2125
- type : {'hamming', 'hann', 'blackmanharris'}, optional
2126
- Window type. Choices are 'hamming' (default), 'hann', and 'blackmanharris'.
2127
- :param type:
2128
-
2129
- debug : boolean, optional
3345
+ The length of the window function.
3346
+ type : {'hamming', 'hann', 'blackmanharris', 'None'}, optional
3347
+ Window type. Choices are 'hamming' (default), 'hann', 'blackmanharris', and 'None'.
3348
+ If 'None' is specified, a window of ones is returned.
3349
+ debug : bool, optional
2130
3350
  When True, internal states of the function will be printed to help debugging.
2131
- :param debug:
3351
+ Default is False.
2132
3352
 
2133
3353
  Returns
2134
3354
  -------
2135
- windowfunc : 1D float array
2136
- The window function
3355
+ windowfunc : NDArray
3356
+ The window function as a 1D float array of the specified length.
3357
+
3358
+ Notes
3359
+ -----
3360
+ This function serves as a wrapper for different window functions and includes
3361
+ caching mechanism for improved performance. The supported window types are:
3362
+
3363
+ - 'hamming': Hamming window
3364
+ - 'hann': Hann (Hanning) window
3365
+ - 'blackmanharris': Blackman-Harris window
3366
+ - 'None': Rectangular window (all ones)
3367
+
3368
+ Examples
3369
+ --------
3370
+ >>> windowfunction(10, 'hamming')
3371
+ array([0.08 , 0.15302333, 0.41302333, 0.77102333, 0.99902333,
3372
+ 0.99902333, 0.77102333, 0.41302333, 0.15302333, 0.08 ])
3373
+
3374
+ >>> windowfunction(5, 'hann')
3375
+ array([0. , 0.5 , 1. , 0.5 , 0. ])
3376
+
3377
+ >>> windowfunction(4, 'None')
3378
+ array([1., 1., 1., 1.])
2137
3379
  """
2138
3380
  if type == "hamming":
2139
3381
  return hamming(length, debug=debug)