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.
- cloud/gmscalc-HCPYA +1 -1
- cloud/mount-and-run +2 -0
- cloud/rapidtide-HCPYA +3 -3
- rapidtide/Colortables.py +538 -38
- rapidtide/OrthoImageItem.py +1094 -51
- rapidtide/RapidtideDataset.py +1709 -114
- rapidtide/__init__.py +0 -8
- rapidtide/_version.py +4 -4
- rapidtide/calccoherence.py +242 -97
- rapidtide/calcnullsimfunc.py +240 -140
- rapidtide/calcsimfunc.py +314 -129
- rapidtide/correlate.py +1211 -389
- rapidtide/data/examples/src/testLD +56 -0
- rapidtide/data/examples/src/test_findmaxlag.py +2 -2
- rapidtide/data/examples/src/test_mlregressallt.py +32 -17
- rapidtide/data/examples/src/testalign +1 -1
- rapidtide/data/examples/src/testatlasaverage +35 -7
- rapidtide/data/examples/src/testboth +21 -0
- rapidtide/data/examples/src/testcifti +11 -0
- rapidtide/data/examples/src/testdelayvar +13 -0
- rapidtide/data/examples/src/testdlfilt +25 -0
- rapidtide/data/examples/src/testfft +35 -0
- rapidtide/data/examples/src/testfileorfloat +37 -0
- rapidtide/data/examples/src/testfmri +92 -42
- rapidtide/data/examples/src/testfuncs +3 -3
- rapidtide/data/examples/src/testglmfilt +8 -6
- rapidtide/data/examples/src/testhappy +84 -51
- rapidtide/data/examples/src/testinitdelay +19 -0
- rapidtide/data/examples/src/testmodels +33 -0
- rapidtide/data/examples/src/testnewrefine +26 -0
- rapidtide/data/examples/src/testnoiseamp +2 -2
- rapidtide/data/examples/src/testppgproc +17 -0
- rapidtide/data/examples/src/testrefineonly +22 -0
- rapidtide/data/examples/src/testretro +26 -13
- rapidtide/data/examples/src/testretrolagtcs +16 -0
- rapidtide/data/examples/src/testrolloff +11 -0
- rapidtide/data/examples/src/testsimdata +45 -28
- rapidtide/data/models/model_cnn_pytorch/loss.png +0 -0
- rapidtide/data/models/model_cnn_pytorch/loss.txt +1 -0
- rapidtide/data/models/model_cnn_pytorch/model.pth +0 -0
- rapidtide/data/models/model_cnn_pytorch/model_meta.json +68 -0
- rapidtide/data/models/model_cnn_pytorch_fulldata/loss.png +0 -0
- rapidtide/data/models/model_cnn_pytorch_fulldata/loss.txt +1 -0
- rapidtide/data/models/model_cnn_pytorch_fulldata/model.pth +0 -0
- rapidtide/data/models/model_cnn_pytorch_fulldata/model_meta.json +80 -0
- rapidtide/data/models/model_cnnbp_pytorch_fullldata/loss.png +0 -0
- rapidtide/data/models/model_cnnbp_pytorch_fullldata/loss.txt +1 -0
- rapidtide/data/models/model_cnnbp_pytorch_fullldata/model.pth +0 -0
- rapidtide/data/models/model_cnnbp_pytorch_fullldata/model_meta.json +138 -0
- rapidtide/data/models/model_cnnfft_pytorch_fulldata/loss.png +0 -0
- rapidtide/data/models/model_cnnfft_pytorch_fulldata/loss.txt +1 -0
- rapidtide/data/models/model_cnnfft_pytorch_fulldata/model.pth +0 -0
- rapidtide/data/models/model_cnnfft_pytorch_fulldata/model_meta.json +128 -0
- rapidtide/data/models/model_ppgattention_pytorch_w128_fulldata/loss.png +0 -0
- rapidtide/data/models/model_ppgattention_pytorch_w128_fulldata/loss.txt +1 -0
- rapidtide/data/models/model_ppgattention_pytorch_w128_fulldata/model.pth +0 -0
- rapidtide/data/models/model_ppgattention_pytorch_w128_fulldata/model_meta.json +49 -0
- rapidtide/data/models/model_revised_tf2/model.keras +0 -0
- rapidtide/data/models/{model_serdar → model_revised_tf2}/model_meta.json +1 -1
- rapidtide/data/models/model_serdar2_tf2/model.keras +0 -0
- rapidtide/data/models/{model_serdar2 → model_serdar2_tf2}/model_meta.json +1 -1
- rapidtide/data/models/model_serdar_tf2/model.keras +0 -0
- rapidtide/data/models/{model_revised → model_serdar_tf2}/model_meta.json +1 -1
- rapidtide/data/reference/HCP1200v2_MTT_2mm.nii.gz +0 -0
- rapidtide/data/reference/HCP1200v2_binmask_2mm.nii.gz +0 -0
- rapidtide/data/reference/HCP1200v2_csf_2mm.nii.gz +0 -0
- rapidtide/data/reference/HCP1200v2_gray_2mm.nii.gz +0 -0
- rapidtide/data/reference/HCP1200v2_graylaghist.json +7 -0
- rapidtide/data/reference/HCP1200v2_graylaghist.tsv.gz +0 -0
- rapidtide/data/reference/HCP1200v2_laghist.json +7 -0
- rapidtide/data/reference/HCP1200v2_laghist.tsv.gz +0 -0
- rapidtide/data/reference/HCP1200v2_mask_2mm.nii.gz +0 -0
- rapidtide/data/reference/HCP1200v2_maxcorr_2mm.nii.gz +0 -0
- rapidtide/data/reference/HCP1200v2_maxtime_2mm.nii.gz +0 -0
- rapidtide/data/reference/HCP1200v2_maxwidth_2mm.nii.gz +0 -0
- rapidtide/data/reference/HCP1200v2_negmask_2mm.nii.gz +0 -0
- rapidtide/data/reference/HCP1200v2_timepercentile_2mm.nii.gz +0 -0
- rapidtide/data/reference/HCP1200v2_white_2mm.nii.gz +0 -0
- rapidtide/data/reference/HCP1200v2_whitelaghist.json +7 -0
- rapidtide/data/reference/HCP1200v2_whitelaghist.tsv.gz +0 -0
- rapidtide/data/reference/JHU-ArterialTerritoriesNoVent-LVL1-seg2.xml +131 -0
- rapidtide/data/reference/JHU-ArterialTerritoriesNoVent-LVL1-seg2_regions.txt +60 -0
- rapidtide/data/reference/JHU-ArterialTerritoriesNoVent-LVL1-seg2_space-MNI152NLin6Asym_2mm.nii.gz +0 -0
- rapidtide/data/reference/JHU-ArterialTerritoriesNoVent-LVL1_space-MNI152NLin2009cAsym_2mm.nii.gz +0 -0
- rapidtide/data/reference/JHU-ArterialTerritoriesNoVent-LVL1_space-MNI152NLin2009cAsym_2mm_mask.nii.gz +0 -0
- rapidtide/data/reference/JHU-ArterialTerritoriesNoVent-LVL1_space-MNI152NLin6Asym_2mm_mask.nii.gz +0 -0
- rapidtide/data/reference/JHU-ArterialTerritoriesNoVent-LVL2_space-MNI152NLin6Asym_2mm_mask.nii.gz +0 -0
- rapidtide/data/reference/MNI152_T1_1mm_Brain_FAST_seg.nii.gz +0 -0
- rapidtide/data/reference/MNI152_T1_1mm_Brain_Mask.nii.gz +0 -0
- rapidtide/data/reference/MNI152_T1_2mm_Brain_FAST_seg.nii.gz +0 -0
- rapidtide/data/reference/MNI152_T1_2mm_Brain_Mask.nii.gz +0 -0
- rapidtide/decorators.py +91 -0
- rapidtide/dlfilter.py +2553 -414
- rapidtide/dlfiltertorch.py +5201 -0
- rapidtide/externaltools.py +328 -13
- rapidtide/fMRIData_class.py +108 -92
- rapidtide/ffttools.py +168 -0
- rapidtide/filter.py +2704 -1462
- rapidtide/fit.py +2361 -579
- rapidtide/genericmultiproc.py +197 -0
- rapidtide/happy_supportfuncs.py +3255 -548
- rapidtide/helper_classes.py +587 -1116
- rapidtide/io.py +2569 -468
- rapidtide/linfitfiltpass.py +784 -0
- rapidtide/makelaggedtcs.py +267 -97
- rapidtide/maskutil.py +555 -25
- rapidtide/miscmath.py +835 -144
- rapidtide/multiproc.py +217 -44
- rapidtide/patchmatch.py +752 -0
- rapidtide/peakeval.py +32 -32
- rapidtide/ppgproc.py +2205 -0
- rapidtide/qualitycheck.py +353 -40
- rapidtide/refinedelay.py +854 -0
- rapidtide/refineregressor.py +939 -0
- rapidtide/resample.py +725 -204
- rapidtide/scripts/__init__.py +1 -0
- rapidtide/scripts/{adjustoffset → adjustoffset.py} +7 -2
- rapidtide/scripts/{aligntcs → aligntcs.py} +7 -2
- rapidtide/scripts/{applydlfilter → applydlfilter.py} +7 -2
- rapidtide/scripts/applyppgproc.py +28 -0
- rapidtide/scripts/{atlasaverage → atlasaverage.py} +7 -2
- rapidtide/scripts/{atlastool → atlastool.py} +7 -2
- rapidtide/scripts/{calcicc → calcicc.py} +7 -2
- rapidtide/scripts/{calctexticc → calctexticc.py} +7 -2
- rapidtide/scripts/{calcttest → calcttest.py} +7 -2
- rapidtide/scripts/{ccorrica → ccorrica.py} +7 -2
- rapidtide/scripts/delayvar.py +28 -0
- rapidtide/scripts/{diffrois → diffrois.py} +7 -2
- rapidtide/scripts/{endtidalproc → endtidalproc.py} +7 -2
- rapidtide/scripts/{fdica → fdica.py} +7 -2
- rapidtide/scripts/{filtnifti → filtnifti.py} +7 -2
- rapidtide/scripts/{filttc → filttc.py} +7 -2
- rapidtide/scripts/{fingerprint → fingerprint.py} +20 -16
- rapidtide/scripts/{fixtr → fixtr.py} +7 -2
- rapidtide/scripts/{gmscalc → gmscalc.py} +7 -2
- rapidtide/scripts/{happy → happy.py} +7 -2
- rapidtide/scripts/{happy2std → happy2std.py} +7 -2
- rapidtide/scripts/{happywarp → happywarp.py} +8 -4
- rapidtide/scripts/{histnifti → histnifti.py} +7 -2
- rapidtide/scripts/{histtc → histtc.py} +7 -2
- rapidtide/scripts/{glmfilt → linfitfilt.py} +7 -4
- rapidtide/scripts/{localflow → localflow.py} +7 -2
- rapidtide/scripts/{mergequality → mergequality.py} +7 -2
- rapidtide/scripts/{pairproc → pairproc.py} +7 -2
- rapidtide/scripts/{pairwisemergenifti → pairwisemergenifti.py} +7 -2
- rapidtide/scripts/{physiofreq → physiofreq.py} +7 -2
- rapidtide/scripts/{pixelcomp → pixelcomp.py} +7 -2
- rapidtide/scripts/{plethquality → plethquality.py} +7 -2
- rapidtide/scripts/{polyfitim → polyfitim.py} +7 -2
- rapidtide/scripts/{proj2flow → proj2flow.py} +7 -2
- rapidtide/scripts/{rankimage → rankimage.py} +7 -2
- rapidtide/scripts/{rapidtide → rapidtide.py} +7 -2
- rapidtide/scripts/{rapidtide2std → rapidtide2std.py} +7 -2
- rapidtide/scripts/{resamplenifti → resamplenifti.py} +7 -2
- rapidtide/scripts/{resampletc → resampletc.py} +7 -2
- rapidtide/scripts/retrolagtcs.py +28 -0
- rapidtide/scripts/retroregress.py +28 -0
- rapidtide/scripts/{roisummarize → roisummarize.py} +7 -2
- rapidtide/scripts/{runqualitycheck → runqualitycheck.py} +7 -2
- rapidtide/scripts/{showarbcorr → showarbcorr.py} +7 -2
- rapidtide/scripts/{showhist → showhist.py} +7 -2
- rapidtide/scripts/{showstxcorr → showstxcorr.py} +7 -2
- rapidtide/scripts/{showtc → showtc.py} +7 -2
- rapidtide/scripts/{showxcorr_legacy → showxcorr_legacy.py} +8 -8
- rapidtide/scripts/{showxcorrx → showxcorrx.py} +7 -2
- rapidtide/scripts/{showxy → showxy.py} +7 -2
- rapidtide/scripts/{simdata → simdata.py} +7 -2
- rapidtide/scripts/{spatialdecomp → spatialdecomp.py} +7 -2
- rapidtide/scripts/{spatialfit → spatialfit.py} +7 -2
- rapidtide/scripts/{spatialmi → spatialmi.py} +7 -2
- rapidtide/scripts/{spectrogram → spectrogram.py} +7 -2
- rapidtide/scripts/stupidramtricks.py +238 -0
- rapidtide/scripts/{synthASL → synthASL.py} +7 -2
- rapidtide/scripts/{tcfrom2col → tcfrom2col.py} +7 -2
- rapidtide/scripts/{tcfrom3col → tcfrom3col.py} +7 -2
- rapidtide/scripts/{temporaldecomp → temporaldecomp.py} +7 -2
- rapidtide/scripts/{testhrv → testhrv.py} +1 -1
- rapidtide/scripts/{threeD → threeD.py} +7 -2
- rapidtide/scripts/{tidepool → tidepool.py} +7 -2
- rapidtide/scripts/{variabilityizer → variabilityizer.py} +7 -2
- rapidtide/simFuncClasses.py +2113 -0
- rapidtide/simfuncfit.py +312 -108
- rapidtide/stats.py +579 -247
- rapidtide/tests/.coveragerc +27 -6
- rapidtide-2.9.6.data/scripts/fdica → rapidtide/tests/cleanposttest +4 -6
- rapidtide/tests/happycomp +9 -0
- rapidtide/tests/resethappytargets +1 -1
- rapidtide/tests/resetrapidtidetargets +1 -1
- rapidtide/tests/resettargets +1 -1
- rapidtide/tests/runlocaltest +3 -3
- rapidtide/tests/showkernels +1 -1
- rapidtide/tests/test_aliasedcorrelate.py +4 -4
- rapidtide/tests/test_aligntcs.py +1 -1
- rapidtide/tests/test_calcicc.py +1 -1
- rapidtide/tests/test_cleanregressor.py +184 -0
- rapidtide/tests/test_congrid.py +70 -81
- rapidtide/tests/test_correlate.py +1 -1
- rapidtide/tests/test_corrpass.py +4 -4
- rapidtide/tests/test_delayestimation.py +54 -59
- rapidtide/tests/test_dlfiltertorch.py +437 -0
- rapidtide/tests/test_doresample.py +2 -2
- rapidtide/tests/test_externaltools.py +69 -0
- rapidtide/tests/test_fastresampler.py +9 -5
- rapidtide/tests/test_filter.py +96 -57
- rapidtide/tests/test_findmaxlag.py +50 -19
- rapidtide/tests/test_fullrunhappy_v1.py +15 -10
- rapidtide/tests/test_fullrunhappy_v2.py +19 -13
- rapidtide/tests/test_fullrunhappy_v3.py +28 -13
- rapidtide/tests/test_fullrunhappy_v4.py +30 -11
- rapidtide/tests/test_fullrunhappy_v5.py +62 -0
- rapidtide/tests/test_fullrunrapidtide_v1.py +61 -7
- rapidtide/tests/test_fullrunrapidtide_v2.py +26 -14
- rapidtide/tests/test_fullrunrapidtide_v3.py +28 -8
- rapidtide/tests/test_fullrunrapidtide_v4.py +16 -8
- rapidtide/tests/test_fullrunrapidtide_v5.py +15 -6
- rapidtide/tests/test_fullrunrapidtide_v6.py +142 -0
- rapidtide/tests/test_fullrunrapidtide_v7.py +114 -0
- rapidtide/tests/test_fullrunrapidtide_v8.py +66 -0
- rapidtide/tests/test_getparsers.py +158 -0
- rapidtide/tests/test_io.py +59 -18
- rapidtide/tests/{test_glmpass.py → test_linfitfiltpass.py} +10 -10
- rapidtide/tests/test_mi.py +1 -1
- rapidtide/tests/test_miscmath.py +1 -1
- rapidtide/tests/test_motionregress.py +5 -5
- rapidtide/tests/test_nullcorr.py +6 -9
- rapidtide/tests/test_padvec.py +216 -0
- rapidtide/tests/test_parserfuncs.py +101 -0
- rapidtide/tests/test_phaseanalysis.py +1 -1
- rapidtide/tests/test_rapidtideparser.py +59 -53
- rapidtide/tests/test_refinedelay.py +296 -0
- rapidtide/tests/test_runmisc.py +5 -5
- rapidtide/tests/test_sharedmem.py +60 -0
- rapidtide/tests/test_simroundtrip.py +132 -0
- rapidtide/tests/test_simulate.py +1 -1
- rapidtide/tests/test_stcorrelate.py +4 -2
- rapidtide/tests/test_timeshift.py +2 -2
- rapidtide/tests/test_valtoindex.py +1 -1
- rapidtide/tests/test_zRapidtideDataset.py +5 -3
- rapidtide/tests/utils.py +10 -9
- rapidtide/tidepoolTemplate.py +88 -70
- rapidtide/tidepoolTemplate.ui +60 -46
- rapidtide/tidepoolTemplate_alt.py +88 -53
- rapidtide/tidepoolTemplate_alt.ui +62 -52
- rapidtide/tidepoolTemplate_alt_qt6.py +921 -0
- rapidtide/tidepoolTemplate_big.py +1125 -0
- rapidtide/tidepoolTemplate_big.ui +2386 -0
- rapidtide/tidepoolTemplate_big_qt6.py +1129 -0
- rapidtide/tidepoolTemplate_qt6.py +793 -0
- rapidtide/util.py +1389 -148
- rapidtide/voxelData.py +1048 -0
- rapidtide/wiener.py +138 -25
- rapidtide/wiener2.py +114 -8
- rapidtide/workflows/adjustoffset.py +107 -5
- rapidtide/workflows/aligntcs.py +86 -3
- rapidtide/workflows/applydlfilter.py +231 -89
- rapidtide/workflows/applyppgproc.py +540 -0
- rapidtide/workflows/atlasaverage.py +309 -48
- rapidtide/workflows/atlastool.py +130 -9
- rapidtide/workflows/calcSimFuncMap.py +490 -0
- rapidtide/workflows/calctexticc.py +202 -10
- rapidtide/workflows/ccorrica.py +123 -15
- rapidtide/workflows/cleanregressor.py +415 -0
- rapidtide/workflows/delayvar.py +1268 -0
- rapidtide/workflows/diffrois.py +84 -6
- rapidtide/workflows/endtidalproc.py +149 -9
- rapidtide/workflows/fdica.py +197 -17
- rapidtide/workflows/filtnifti.py +71 -4
- rapidtide/workflows/filttc.py +76 -5
- rapidtide/workflows/fitSimFuncMap.py +578 -0
- rapidtide/workflows/fixtr.py +74 -4
- rapidtide/workflows/gmscalc.py +116 -6
- rapidtide/workflows/happy.py +1242 -480
- rapidtide/workflows/happy2std.py +145 -13
- rapidtide/workflows/happy_parser.py +277 -59
- rapidtide/workflows/histnifti.py +120 -4
- rapidtide/workflows/histtc.py +85 -4
- rapidtide/workflows/{glmfilt.py → linfitfilt.py} +128 -14
- rapidtide/workflows/localflow.py +329 -29
- rapidtide/workflows/mergequality.py +80 -4
- rapidtide/workflows/niftidecomp.py +323 -19
- rapidtide/workflows/niftistats.py +178 -8
- rapidtide/workflows/pairproc.py +99 -5
- rapidtide/workflows/pairwisemergenifti.py +86 -3
- rapidtide/workflows/parser_funcs.py +1488 -56
- rapidtide/workflows/physiofreq.py +139 -12
- rapidtide/workflows/pixelcomp.py +211 -9
- rapidtide/workflows/plethquality.py +105 -23
- rapidtide/workflows/polyfitim.py +159 -19
- rapidtide/workflows/proj2flow.py +76 -3
- rapidtide/workflows/rankimage.py +115 -8
- rapidtide/workflows/rapidtide.py +1785 -1858
- rapidtide/workflows/rapidtide2std.py +101 -3
- rapidtide/workflows/rapidtide_parser.py +590 -389
- rapidtide/workflows/refineDelayMap.py +249 -0
- rapidtide/workflows/refineRegressor.py +1215 -0
- rapidtide/workflows/regressfrommaps.py +308 -0
- rapidtide/workflows/resamplenifti.py +86 -4
- rapidtide/workflows/resampletc.py +92 -4
- rapidtide/workflows/retrolagtcs.py +442 -0
- rapidtide/workflows/retroregress.py +1501 -0
- rapidtide/workflows/roisummarize.py +176 -7
- rapidtide/workflows/runqualitycheck.py +72 -7
- rapidtide/workflows/showarbcorr.py +172 -16
- rapidtide/workflows/showhist.py +87 -3
- rapidtide/workflows/showstxcorr.py +161 -4
- rapidtide/workflows/showtc.py +172 -10
- rapidtide/workflows/showxcorrx.py +250 -62
- rapidtide/workflows/showxy.py +186 -16
- rapidtide/workflows/simdata.py +418 -112
- rapidtide/workflows/spatialfit.py +83 -8
- rapidtide/workflows/spatialmi.py +252 -29
- rapidtide/workflows/spectrogram.py +306 -33
- rapidtide/workflows/synthASL.py +157 -6
- rapidtide/workflows/tcfrom2col.py +77 -3
- rapidtide/workflows/tcfrom3col.py +75 -3
- rapidtide/workflows/tidepool.py +3829 -666
- rapidtide/workflows/utils.py +45 -19
- rapidtide/workflows/utils_doc.py +293 -0
- rapidtide/workflows/variabilityizer.py +118 -5
- {rapidtide-2.9.6.dist-info → rapidtide-3.1.3.dist-info}/METADATA +30 -223
- rapidtide-3.1.3.dist-info/RECORD +393 -0
- {rapidtide-2.9.6.dist-info → rapidtide-3.1.3.dist-info}/WHEEL +1 -1
- rapidtide-3.1.3.dist-info/entry_points.txt +65 -0
- rapidtide-3.1.3.dist-info/top_level.txt +2 -0
- rapidtide/calcandfitcorrpairs.py +0 -262
- rapidtide/data/examples/src/testoutputsize +0 -45
- rapidtide/data/models/model_revised/model.h5 +0 -0
- rapidtide/data/models/model_serdar/model.h5 +0 -0
- rapidtide/data/models/model_serdar2/model.h5 +0 -0
- rapidtide/data/reference/ASPECTS_nlin_asym_09c_2mm.nii.gz +0 -0
- rapidtide/data/reference/ASPECTS_nlin_asym_09c_2mm_mask.nii.gz +0 -0
- rapidtide/data/reference/ATTbasedFlowTerritories_split_nlin_asym_09c_2mm.nii.gz +0 -0
- rapidtide/data/reference/ATTbasedFlowTerritories_split_nlin_asym_09c_2mm_mask.nii.gz +0 -0
- rapidtide/data/reference/HCP1200_binmask_2mm_2009c_asym.nii.gz +0 -0
- rapidtide/data/reference/HCP1200_lag_2mm_2009c_asym.nii.gz +0 -0
- rapidtide/data/reference/HCP1200_mask_2mm_2009c_asym.nii.gz +0 -0
- rapidtide/data/reference/HCP1200_negmask_2mm_2009c_asym.nii.gz +0 -0
- rapidtide/data/reference/HCP1200_sigma_2mm_2009c_asym.nii.gz +0 -0
- rapidtide/data/reference/HCP1200_strength_2mm_2009c_asym.nii.gz +0 -0
- rapidtide/glmpass.py +0 -434
- rapidtide/refine_factored.py +0 -641
- rapidtide/scripts/retroglm +0 -23
- rapidtide/workflows/glmfrommaps.py +0 -202
- rapidtide/workflows/retroglm.py +0 -643
- rapidtide-2.9.6.data/scripts/adjustoffset +0 -23
- rapidtide-2.9.6.data/scripts/aligntcs +0 -23
- rapidtide-2.9.6.data/scripts/applydlfilter +0 -23
- rapidtide-2.9.6.data/scripts/atlasaverage +0 -23
- rapidtide-2.9.6.data/scripts/atlastool +0 -23
- rapidtide-2.9.6.data/scripts/calcicc +0 -22
- rapidtide-2.9.6.data/scripts/calctexticc +0 -23
- rapidtide-2.9.6.data/scripts/calcttest +0 -22
- rapidtide-2.9.6.data/scripts/ccorrica +0 -23
- rapidtide-2.9.6.data/scripts/diffrois +0 -23
- rapidtide-2.9.6.data/scripts/endtidalproc +0 -23
- rapidtide-2.9.6.data/scripts/filtnifti +0 -23
- rapidtide-2.9.6.data/scripts/filttc +0 -23
- rapidtide-2.9.6.data/scripts/fingerprint +0 -593
- rapidtide-2.9.6.data/scripts/fixtr +0 -23
- rapidtide-2.9.6.data/scripts/glmfilt +0 -24
- rapidtide-2.9.6.data/scripts/gmscalc +0 -22
- rapidtide-2.9.6.data/scripts/happy +0 -25
- rapidtide-2.9.6.data/scripts/happy2std +0 -23
- rapidtide-2.9.6.data/scripts/happywarp +0 -350
- rapidtide-2.9.6.data/scripts/histnifti +0 -23
- rapidtide-2.9.6.data/scripts/histtc +0 -23
- rapidtide-2.9.6.data/scripts/localflow +0 -23
- rapidtide-2.9.6.data/scripts/mergequality +0 -23
- rapidtide-2.9.6.data/scripts/pairproc +0 -23
- rapidtide-2.9.6.data/scripts/pairwisemergenifti +0 -23
- rapidtide-2.9.6.data/scripts/physiofreq +0 -23
- rapidtide-2.9.6.data/scripts/pixelcomp +0 -23
- rapidtide-2.9.6.data/scripts/plethquality +0 -23
- rapidtide-2.9.6.data/scripts/polyfitim +0 -23
- rapidtide-2.9.6.data/scripts/proj2flow +0 -23
- rapidtide-2.9.6.data/scripts/rankimage +0 -23
- rapidtide-2.9.6.data/scripts/rapidtide +0 -23
- rapidtide-2.9.6.data/scripts/rapidtide2std +0 -23
- rapidtide-2.9.6.data/scripts/resamplenifti +0 -23
- rapidtide-2.9.6.data/scripts/resampletc +0 -23
- rapidtide-2.9.6.data/scripts/retroglm +0 -23
- rapidtide-2.9.6.data/scripts/roisummarize +0 -23
- rapidtide-2.9.6.data/scripts/runqualitycheck +0 -23
- rapidtide-2.9.6.data/scripts/showarbcorr +0 -23
- rapidtide-2.9.6.data/scripts/showhist +0 -23
- rapidtide-2.9.6.data/scripts/showstxcorr +0 -23
- rapidtide-2.9.6.data/scripts/showtc +0 -23
- rapidtide-2.9.6.data/scripts/showxcorr_legacy +0 -536
- rapidtide-2.9.6.data/scripts/showxcorrx +0 -23
- rapidtide-2.9.6.data/scripts/showxy +0 -23
- rapidtide-2.9.6.data/scripts/simdata +0 -23
- rapidtide-2.9.6.data/scripts/spatialdecomp +0 -23
- rapidtide-2.9.6.data/scripts/spatialfit +0 -23
- rapidtide-2.9.6.data/scripts/spatialmi +0 -23
- rapidtide-2.9.6.data/scripts/spectrogram +0 -23
- rapidtide-2.9.6.data/scripts/synthASL +0 -23
- rapidtide-2.9.6.data/scripts/tcfrom2col +0 -23
- rapidtide-2.9.6.data/scripts/tcfrom3col +0 -23
- rapidtide-2.9.6.data/scripts/temporaldecomp +0 -23
- rapidtide-2.9.6.data/scripts/threeD +0 -236
- rapidtide-2.9.6.data/scripts/tidepool +0 -23
- rapidtide-2.9.6.data/scripts/variabilityizer +0 -23
- rapidtide-2.9.6.dist-info/RECORD +0 -359
- rapidtide-2.9.6.dist-info/top_level.txt +0 -86
- {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-
|
|
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
|
-
#
|
|
47
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
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
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
79
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
95
|
-
The
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
:
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
109
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
176
|
-
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
430
|
+
def getpadtype(self):
|
|
431
|
+
"""
|
|
432
|
+
Return the padding type of the object.
|
|
187
433
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
453
|
+
def settransferfunc(self, transferfunc):
|
|
454
|
+
"""
|
|
455
|
+
Set the transfer function for the system.
|
|
195
456
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
:
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
222
|
-
|
|
223
|
-
|
|
561
|
+
def getfreqs(self):
|
|
562
|
+
"""
|
|
563
|
+
Return frequency boundaries for filter design.
|
|
224
564
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
589
|
+
def apply(self, Fs, data):
|
|
590
|
+
"""
|
|
591
|
+
Apply the filter to a dataset.
|
|
232
592
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
:
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
:
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
|
|
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.
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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 :
|
|
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(
|
|
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(
|
|
327
|
-
|
|
328
|
-
|
|
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
|
-
|
|
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.
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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
|
-
|
|
1228
|
+
Default is False.
|
|
363
1229
|
|
|
364
1230
|
Returns
|
|
365
1231
|
-------
|
|
366
|
-
filtereddata :
|
|
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(
|
|
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
|
-
|
|
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 :
|
|
396
|
-
Input data to be filtered
|
|
397
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) *
|
|
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(
|
|
454
|
-
|
|
455
|
-
|
|
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
|
-
|
|
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.
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
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
|
-
|
|
1400
|
+
Default is False.
|
|
482
1401
|
|
|
483
1402
|
Returns
|
|
484
1403
|
-------
|
|
485
|
-
|
|
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,
|
|
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(
|
|
497
|
-
|
|
498
|
-
|
|
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
|
-
|
|
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.
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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
|
-
|
|
1461
|
+
Default is False.
|
|
525
1462
|
|
|
526
1463
|
Returns
|
|
527
1464
|
-------
|
|
528
|
-
|
|
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,
|
|
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(
|
|
540
|
-
|
|
541
|
-
|
|
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
|
-
|
|
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.
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
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
|
-
|
|
1525
|
+
Default is False.
|
|
572
1526
|
|
|
573
1527
|
Returns
|
|
574
1528
|
-------
|
|
575
|
-
|
|
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,
|
|
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(
|
|
590
|
-
|
|
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
|
|
600
|
-
:param upperpass:
|
|
601
|
-
|
|
1575
|
+
Upper edge of the passband in Hz.
|
|
602
1576
|
upperstop : float
|
|
603
|
-
Lower
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
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
|
-
|
|
617
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1863
|
+
A plot of the transfer function will be displayed if debug is enabled.
|
|
765
1864
|
|
|
766
1865
|
Returns
|
|
767
1866
|
-------
|
|
768
|
-
|
|
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,
|
|
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 -
|
|
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
|
-
|
|
795
|
-
|
|
796
|
-
type="brickwall",
|
|
797
|
-
padlen=20,
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
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
|
-
|
|
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
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
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.
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
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
|
-
|
|
1940
|
+
Default is False.
|
|
833
1941
|
|
|
834
1942
|
Returns
|
|
835
1943
|
-------
|
|
836
|
-
filtereddata :
|
|
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,
|
|
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 -
|
|
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
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
upperstop=None,
|
|
868
|
-
type="brickwall",
|
|
869
|
-
padlen=20,
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
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
|
-
|
|
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
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
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.
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
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
|
-
|
|
2025
|
+
Default is False.
|
|
905
2026
|
|
|
906
2027
|
Returns
|
|
907
2028
|
-------
|
|
908
|
-
filtereddata :
|
|
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,
|
|
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(
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
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
|
-
|
|
1302
|
-
|
|
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
|
-
|
|
1305
|
-
|
|
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
|
-
|
|
1311
|
-
|
|
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
|
-
|
|
1314
|
-
|
|
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:
|
|
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
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
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
|
|
1332
|
-
Fs,
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
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
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
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
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
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
|
-
|
|
1393
|
-
|
|
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
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
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
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
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
|
-
|
|
1496
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
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
|
-
|
|
1591
|
-
|
|
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
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
2384
|
+
Parameters
|
|
2385
|
+
----------
|
|
2386
|
+
spectrum : NDArray
|
|
2387
|
+
Input spectrum array. Should contain non-negative values.
|
|
1599
2388
|
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
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
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
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
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
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
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
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
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
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
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
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
|
-
|
|
1694
|
-
|
|
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
|
-
|
|
1697
|
-
|
|
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
|
-
|
|
1700
|
-
|
|
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
|
-
|
|
1706
|
-
|
|
2560
|
+
def savgolsmooth(data: NDArray, smoothlen: int = 101, polyorder: int = 3) -> NDArray:
|
|
2561
|
+
"""
|
|
2562
|
+
Apply Savitzky-Golay filter to smooth data.
|
|
1707
2563
|
|
|
1708
|
-
|
|
1709
|
-
|
|
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
|
-
|
|
1712
|
-
|
|
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
|
-
|
|
1715
|
-
|
|
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
|
-
|
|
1755
|
-
|
|
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
|
-
|
|
1758
|
-
|
|
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
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
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
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
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
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
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
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
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
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
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
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
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
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
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
|
-
|
|
1890
|
-
|
|
2760
|
+
avlen=avlen,
|
|
2761
|
+
padtype=padtype,
|
|
2762
|
+
debug=debug,
|
|
1891
2763
|
)
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
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
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
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
|
-
|
|
1915
|
-
|
|
2773
|
+
avlen=avlen,
|
|
2774
|
+
padtype=padtype,
|
|
2775
|
+
debug=debug,
|
|
1916
2776
|
)
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
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
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
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
|
-
|
|
1940
|
-
|
|
2786
|
+
avlen=avlen,
|
|
2787
|
+
padtype=padtype,
|
|
2788
|
+
debug=debug,
|
|
1941
2789
|
)
|
|
1942
|
-
|
|
1943
|
-
return
|
|
2790
|
+
else:
|
|
2791
|
+
return dohptransfuncfilt(
|
|
1944
2792
|
Fs,
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
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
|
-
|
|
1954
|
-
|
|
2798
|
+
avlen=avlen,
|
|
2799
|
+
padtype=padtype,
|
|
2800
|
+
debug=debug,
|
|
1955
2801
|
)
|
|
1956
|
-
|
|
1957
|
-
|
|
2802
|
+
else:
|
|
2803
|
+
# set up for bandpass
|
|
2804
|
+
if transferfunc == "butterworth":
|
|
2805
|
+
return dohpfiltfilt(
|
|
1958
2806
|
Fs,
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
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
|
-
|
|
1968
|
-
|
|
2820
|
+
avlen=avlen,
|
|
2821
|
+
padtype=padtype,
|
|
2822
|
+
debug=debug,
|
|
1969
2823
|
)
|
|
1970
2824
|
else:
|
|
1971
|
-
|
|
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
|
-
|
|
1992
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3094
|
+
Default is False.
|
|
2003
3095
|
|
|
2004
3096
|
Returns
|
|
2005
3097
|
-------
|
|
2006
|
-
windowfunc :
|
|
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
|
-
|
|
2031
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3152
|
+
Default is False.
|
|
2042
3153
|
|
|
2043
3154
|
Returns
|
|
2044
3155
|
-------
|
|
2045
|
-
windowfunc :
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
2087
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
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
|
-
|
|
3351
|
+
Default is False.
|
|
2132
3352
|
|
|
2133
3353
|
Returns
|
|
2134
3354
|
-------
|
|
2135
|
-
windowfunc :
|
|
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)
|