phasorpy 0.4__cp312-cp312-win_arm64.whl → 0.5__cp312-cp312-win_arm64.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.
- phasorpy/_io.py +382 -158
- phasorpy/_phasorpy.cp312-win_arm64.pyd +0 -0
- phasorpy/_phasorpy.pyx +54 -51
- phasorpy/_utils.py +89 -7
- phasorpy/cli.py +2 -0
- phasorpy/cluster.py +170 -0
- phasorpy/color.py +16 -11
- phasorpy/components.py +18 -18
- phasorpy/conftest.py +2 -0
- phasorpy/cursors.py +9 -9
- phasorpy/datasets.py +129 -51
- phasorpy/io.py +4 -0
- phasorpy/phasor.py +265 -96
- phasorpy/plot.py +251 -27
- phasorpy/utils.py +12 -7
- phasorpy/version.py +13 -5
- {phasorpy-0.4.dist-info → phasorpy-0.5.dist-info}/METADATA +10 -15
- phasorpy-0.5.dist-info/RECORD +26 -0
- {phasorpy-0.4.dist-info → phasorpy-0.5.dist-info}/WHEEL +1 -1
- phasorpy-0.4.dist-info/RECORD +0 -25
- {phasorpy-0.4.dist-info → phasorpy-0.5.dist-info}/entry_points.txt +0 -0
- {phasorpy-0.4.dist-info → phasorpy-0.5.dist-info/licenses}/LICENSE.txt +0 -0
- {phasorpy-0.4.dist-info → phasorpy-0.5.dist-info}/top_level.txt +0 -0
phasorpy/phasor.py
CHANGED
@@ -59,13 +59,14 @@ The ``phasorpy.phasor`` module provides functions to:
|
|
59
59
|
- :py:func:`lifetime_fraction_from_amplitude`
|
60
60
|
- :py:func:`lifetime_fraction_to_amplitude`
|
61
61
|
|
62
|
-
- calculate phasor coordinates on semicircle at other harmonics:
|
62
|
+
- calculate phasor coordinates on universal semicircle at other harmonics:
|
63
63
|
|
64
64
|
- :py:func:`phasor_at_harmonic`
|
65
65
|
|
66
66
|
- filter phasor coordinates:
|
67
67
|
|
68
68
|
- :py:func:`phasor_filter_median`
|
69
|
+
- :py:func:`phasor_filter_pawflim`
|
69
70
|
- :py:func:`phasor_threshold`
|
70
71
|
|
71
72
|
"""
|
@@ -83,6 +84,7 @@ __all__ = [
|
|
83
84
|
'phasor_center',
|
84
85
|
'phasor_divide',
|
85
86
|
'phasor_filter_median',
|
87
|
+
'phasor_filter_pawflim',
|
86
88
|
'phasor_from_apparent_lifetime',
|
87
89
|
'phasor_from_fret_acceptor',
|
88
90
|
'phasor_from_fret_donor',
|
@@ -149,7 +151,7 @@ from ._phasorpy import (
|
|
149
151
|
_polar_from_single_lifetime,
|
150
152
|
_polar_to_apparent_lifetime,
|
151
153
|
)
|
152
|
-
from ._utils import parse_harmonic, parse_signal_axis
|
154
|
+
from ._utils import parse_harmonic, parse_signal_axis, parse_skip_axis
|
153
155
|
from .utils import number_threads
|
154
156
|
|
155
157
|
|
@@ -502,7 +504,8 @@ def phasor_to_signal(
|
|
502
504
|
else:
|
503
505
|
keepdims = mean.ndim > 0 or real.ndim > 0
|
504
506
|
|
505
|
-
mean
|
507
|
+
mean = numpy.asarray(numpy.atleast_1d(mean))
|
508
|
+
real = numpy.asarray(numpy.atleast_1d(real))
|
506
509
|
|
507
510
|
if real.dtype.kind != 'f' or imag.dtype.kind != 'f':
|
508
511
|
raise ValueError(f'{real.dtype=} or {imag.dtype=} not floating point')
|
@@ -595,7 +598,7 @@ def lifetime_to_signal(
|
|
595
598
|
or `1` to synthesize a homodyne signal.
|
596
599
|
zero_phase : float, optional
|
597
600
|
Position of instrument response function in radians.
|
598
|
-
Must be in range 0
|
601
|
+
Must be in range [0, pi]. The default is the 8th sample.
|
599
602
|
zero_stdev : float, optional
|
600
603
|
Standard deviation of instrument response function in radians.
|
601
604
|
Must be at least 1.5 samples and no more than one tenth of samples
|
@@ -683,7 +686,7 @@ def lifetime_to_signal(
|
|
683
686
|
stdev = zero_stdev * scale # in sample units
|
684
687
|
|
685
688
|
if zero_phase < 0 or zero_phase > 2.0 * math.pi:
|
686
|
-
raise ValueError(f'{zero_phase=} out of range [0
|
689
|
+
raise ValueError(f'{zero_phase=} out of range [0, 2 pi]')
|
687
690
|
if stdev < 1.5:
|
688
691
|
raise ValueError(
|
689
692
|
f'{zero_stdev=} < {1.5 / scale} cannot be sampled sufficiently'
|
@@ -749,9 +752,9 @@ def phasor_semicircle(
|
|
749
752
|
Returns
|
750
753
|
-------
|
751
754
|
real : ndarray
|
752
|
-
Real component of
|
755
|
+
Real component of phasor coordinates on universal semicircle.
|
753
756
|
imag : ndarray
|
754
|
-
Imaginary component of
|
757
|
+
Imaginary component of phasor coordinates on universal semicircle.
|
755
758
|
|
756
759
|
Raises
|
757
760
|
------
|
@@ -1050,9 +1053,7 @@ def phasor_normalize(
|
|
1050
1053
|
else:
|
1051
1054
|
real = numpy.array(real_unnormalized, dtype, copy=True)
|
1052
1055
|
imag = numpy.array(imag_unnormalized, real.dtype, copy=True)
|
1053
|
-
mean = numpy.array(
|
1054
|
-
mean_unnormalized, real.dtype, copy=None if samples == 1 else True
|
1055
|
-
)
|
1056
|
+
mean = numpy.array(mean_unnormalized, real.dtype, copy=True)
|
1056
1057
|
|
1057
1058
|
with numpy.errstate(divide='ignore', invalid='ignore'):
|
1058
1059
|
numpy.divide(real, mean, out=real)
|
@@ -1158,7 +1159,8 @@ def phasor_calibrate(
|
|
1158
1159
|
ValueError
|
1159
1160
|
The array shapes of `real` and `imag`, or `reference_real` and
|
1160
1161
|
`reference_imag` do not match.
|
1161
|
-
Number of harmonics does not match the first axis
|
1162
|
+
Number of harmonics or frequencies does not match the first axis
|
1163
|
+
of `real` and `imag`.
|
1162
1164
|
|
1163
1165
|
See Also
|
1164
1166
|
--------
|
@@ -1257,13 +1259,24 @@ def phasor_calibrate(
|
|
1257
1259
|
),
|
1258
1260
|
)
|
1259
1261
|
|
1262
|
+
frequency = numpy.asarray(frequency)
|
1263
|
+
frequency = frequency * harmonic
|
1264
|
+
|
1260
1265
|
if has_harmonic_axis:
|
1261
1266
|
if real.ndim == 0:
|
1262
|
-
raise ValueError(
|
1263
|
-
|
1264
|
-
|
1265
|
-
if
|
1266
|
-
raise ValueError(
|
1267
|
+
raise ValueError(
|
1268
|
+
f'{real.shape=} != {len(frequency)} frequencies or harmonics'
|
1269
|
+
)
|
1270
|
+
if real.shape[0] != len(frequency):
|
1271
|
+
raise ValueError(
|
1272
|
+
f'{real.shape[0]=} != {len(frequency)} '
|
1273
|
+
'frequencies or harmonics'
|
1274
|
+
)
|
1275
|
+
if reference_real.shape[0] != len(frequency):
|
1276
|
+
raise ValueError(
|
1277
|
+
f'{reference_real.shape[0]=} != {len(frequency)} '
|
1278
|
+
'frequencies or harmonics'
|
1279
|
+
)
|
1267
1280
|
if reference_mean.shape != reference_real.shape[1:]:
|
1268
1281
|
raise ValueError(
|
1269
1282
|
f'{reference_mean.shape=} != {reference_real.shape[1:]=}'
|
@@ -1275,9 +1288,6 @@ def phasor_calibrate(
|
|
1275
1288
|
f'{reference_mean.shape=} does not have harmonic axis'
|
1276
1289
|
)
|
1277
1290
|
|
1278
|
-
frequency = numpy.asarray(frequency)
|
1279
|
-
frequency = frequency * harmonic
|
1280
|
-
|
1281
1291
|
_, measured_re, measured_im = phasor_center(
|
1282
1292
|
reference_mean,
|
1283
1293
|
reference_real,
|
@@ -1295,6 +1305,24 @@ def phasor_calibrate(
|
|
1295
1305
|
unit_conversion=unit_conversion,
|
1296
1306
|
)
|
1297
1307
|
|
1308
|
+
skip_axis, axis = parse_skip_axis(
|
1309
|
+
skip_axis, real.ndim - int(has_harmonic_axis), has_harmonic_axis
|
1310
|
+
)
|
1311
|
+
|
1312
|
+
if has_harmonic_axis and any(skip_axis):
|
1313
|
+
known_re = numpy.expand_dims(
|
1314
|
+
known_re, tuple(range(1, measured_re.ndim))
|
1315
|
+
)
|
1316
|
+
known_re = numpy.broadcast_to(
|
1317
|
+
known_re, (len(frequency), *measured_re.shape[1:])
|
1318
|
+
)
|
1319
|
+
known_im = numpy.expand_dims(
|
1320
|
+
known_im, tuple(range(1, measured_im.ndim))
|
1321
|
+
)
|
1322
|
+
known_im = numpy.broadcast_to(
|
1323
|
+
known_im, (len(frequency), *measured_im.shape[1:])
|
1324
|
+
)
|
1325
|
+
|
1298
1326
|
phi_zero, mod_zero = polar_from_reference_phasor(
|
1299
1327
|
measured_re, measured_im, known_re, known_im
|
1300
1328
|
)
|
@@ -1303,9 +1331,6 @@ def phasor_calibrate(
|
|
1303
1331
|
if reverse:
|
1304
1332
|
numpy.negative(phi_zero, out=phi_zero)
|
1305
1333
|
numpy.reciprocal(mod_zero, out=mod_zero)
|
1306
|
-
_, axis = _parse_skip_axis(
|
1307
|
-
skip_axis, real.ndim - int(has_harmonic_axis), has_harmonic_axis
|
1308
|
-
)
|
1309
1334
|
if axis is not None:
|
1310
1335
|
phi_zero = numpy.expand_dims(phi_zero, axis=axis)
|
1311
1336
|
mod_zero = numpy.expand_dims(mod_zero, axis=axis)
|
@@ -2433,7 +2458,7 @@ def phasor_from_fret_donor(
|
|
2433
2458
|
donor_lifetime: ArrayLike,
|
2434
2459
|
*,
|
2435
2460
|
fret_efficiency: ArrayLike = 0.0,
|
2436
|
-
|
2461
|
+
donor_fretting: ArrayLike = 1.0,
|
2437
2462
|
donor_background: ArrayLike = 0.0,
|
2438
2463
|
background_real: ArrayLike = 0.0,
|
2439
2464
|
background_imag: ArrayLike = 0.0,
|
@@ -2459,9 +2484,9 @@ def phasor_from_fret_donor(
|
|
2459
2484
|
donor_lifetime : array_like
|
2460
2485
|
Lifetime of donor without FRET in ns.
|
2461
2486
|
fret_efficiency : array_like, optional, default 0
|
2462
|
-
FRET efficiency in range [0
|
2463
|
-
|
2464
|
-
Fraction of donors participating in FRET. Range [0
|
2487
|
+
FRET efficiency in range [0, 1].
|
2488
|
+
donor_fretting : array_like, optional, default 1
|
2489
|
+
Fraction of donors participating in FRET. Range [0, 1].
|
2465
2490
|
donor_background : array_like, optional, default 0
|
2466
2491
|
Weight of background fluorescence in donor channel
|
2467
2492
|
relative to fluorescence of donor without FRET.
|
@@ -2502,7 +2527,7 @@ def phasor_from_fret_donor(
|
|
2502
2527
|
... frequency=80,
|
2503
2528
|
... donor_lifetime=4.2,
|
2504
2529
|
... fret_efficiency=[0.0, 0.3, 1.0],
|
2505
|
-
...
|
2530
|
+
... donor_fretting=0.9,
|
2506
2531
|
... donor_background=0.1,
|
2507
2532
|
... background_real=0.11,
|
2508
2533
|
... background_imag=0.12,
|
@@ -2516,7 +2541,7 @@ def phasor_from_fret_donor(
|
|
2516
2541
|
omega,
|
2517
2542
|
donor_lifetime,
|
2518
2543
|
fret_efficiency,
|
2519
|
-
|
2544
|
+
donor_fretting,
|
2520
2545
|
donor_background,
|
2521
2546
|
background_real,
|
2522
2547
|
background_imag,
|
@@ -2530,7 +2555,7 @@ def phasor_from_fret_acceptor(
|
|
2530
2555
|
acceptor_lifetime: ArrayLike,
|
2531
2556
|
*,
|
2532
2557
|
fret_efficiency: ArrayLike = 0.0,
|
2533
|
-
|
2558
|
+
donor_fretting: ArrayLike = 1.0,
|
2534
2559
|
donor_bleedthrough: ArrayLike = 0.0,
|
2535
2560
|
acceptor_bleedthrough: ArrayLike = 0.0,
|
2536
2561
|
acceptor_background: ArrayLike = 0.0,
|
@@ -2563,9 +2588,9 @@ def phasor_from_fret_acceptor(
|
|
2563
2588
|
acceptor_lifetime : array_like
|
2564
2589
|
Lifetime of acceptor in ns.
|
2565
2590
|
fret_efficiency : array_like, optional, default 0
|
2566
|
-
FRET efficiency in range [0
|
2567
|
-
|
2568
|
-
Fraction of donors participating in FRET. Range [0
|
2591
|
+
FRET efficiency in range [0, 1].
|
2592
|
+
donor_fretting : array_like, optional, default 1
|
2593
|
+
Fraction of donors participating in FRET. Range [0, 1].
|
2569
2594
|
donor_bleedthrough : array_like, optional, default 0
|
2570
2595
|
Weight of donor fluorescence in acceptor channel
|
2571
2596
|
relative to fluorescence of fully sensitized acceptor.
|
@@ -2618,7 +2643,7 @@ def phasor_from_fret_acceptor(
|
|
2618
2643
|
... donor_lifetime=4.2,
|
2619
2644
|
... acceptor_lifetime=3.0,
|
2620
2645
|
... fret_efficiency=[0.0, 0.3, 1.0],
|
2621
|
-
...
|
2646
|
+
... donor_fretting=0.9,
|
2622
2647
|
... donor_bleedthrough=0.1,
|
2623
2648
|
... acceptor_bleedthrough=0.1,
|
2624
2649
|
... acceptor_background=0.1,
|
@@ -2635,7 +2660,7 @@ def phasor_from_fret_acceptor(
|
|
2635
2660
|
donor_lifetime,
|
2636
2661
|
acceptor_lifetime,
|
2637
2662
|
fret_efficiency,
|
2638
|
-
|
2663
|
+
donor_fretting,
|
2639
2664
|
donor_bleedthrough,
|
2640
2665
|
acceptor_bleedthrough,
|
2641
2666
|
acceptor_background,
|
@@ -2720,7 +2745,7 @@ def phasor_to_principal_plane(
|
|
2720
2745
|
References
|
2721
2746
|
----------
|
2722
2747
|
|
2723
|
-
.. [1] Franssen WMJ, Vergeldt FJ, Bader AN, van Amerongen H,
|
2748
|
+
.. [1] Franssen WMJ, Vergeldt FJ, Bader AN, van Amerongen H, and Terenzi C.
|
2724
2749
|
`Full-harmonics phasor analysis: unravelling multiexponential trends
|
2725
2750
|
in magnetic resonance imaging data
|
2726
2751
|
<https://doi.org/10.1021/acs.jpclett.0c02319>`_.
|
@@ -2932,7 +2957,7 @@ def phasor_filter_median(
|
|
2932
2957
|
raise ValueError(f'{real.shape=} != {imag.shape=}')
|
2933
2958
|
|
2934
2959
|
prepend_axis = mean.ndim + 1 == real.ndim
|
2935
|
-
_, axes =
|
2960
|
+
_, axes = parse_skip_axis(skip_axis, mean.ndim, prepend_axis)
|
2936
2961
|
|
2937
2962
|
# in case mean is also filtered
|
2938
2963
|
# if prepend_axis:
|
@@ -3006,6 +3031,209 @@ def phasor_filter_median(
|
|
3006
3031
|
return mean, real, imag
|
3007
3032
|
|
3008
3033
|
|
3034
|
+
def phasor_filter_pawflim(
|
3035
|
+
mean: ArrayLike,
|
3036
|
+
real: ArrayLike,
|
3037
|
+
imag: ArrayLike,
|
3038
|
+
/,
|
3039
|
+
*,
|
3040
|
+
sigma: float = 2.0,
|
3041
|
+
levels: int = 1,
|
3042
|
+
harmonic: Sequence[int] | None = None,
|
3043
|
+
skip_axis: int | Sequence[int] | None = None,
|
3044
|
+
) -> tuple[NDArray[Any], NDArray[Any], NDArray[Any]]:
|
3045
|
+
"""Return pawFLIM wavelet-filtered phasor coordinates.
|
3046
|
+
|
3047
|
+
This function must only be used with calibrated, unprocessed phasor
|
3048
|
+
coordinates obtained from FLIM data. The coordinates must not be filtered,
|
3049
|
+
thresholded, or otherwise pre-processed.
|
3050
|
+
|
3051
|
+
The pawFLIM wavelet filter is described in [2]_.
|
3052
|
+
|
3053
|
+
Parameters
|
3054
|
+
----------
|
3055
|
+
mean : array_like
|
3056
|
+
Intensity of phasor coordinates.
|
3057
|
+
real : array_like
|
3058
|
+
Real component of phasor coordinates to be filtered.
|
3059
|
+
Must have at least two harmonics in the first axis.
|
3060
|
+
imag : array_like
|
3061
|
+
Imaginary component of phasor coordinates to be filtered.
|
3062
|
+
Must have at least two harmonics in the first axis.
|
3063
|
+
sigma : float, optional
|
3064
|
+
Significance level to test difference between two phasors.
|
3065
|
+
Given in terms of the equivalent 1D standard deviations.
|
3066
|
+
sigma=2 corresponds to ~95% (or 5%) significance.
|
3067
|
+
levels : int, optional
|
3068
|
+
Number of levels for wavelet decomposition.
|
3069
|
+
Controls the maximum averaging area, which has a length of
|
3070
|
+
:math:`2^level`.
|
3071
|
+
harmonic : sequence of int or None, optional
|
3072
|
+
Harmonics included in first axis of `real` and `imag`.
|
3073
|
+
If None (default), the first axis of `real` and `imag` contains lower
|
3074
|
+
harmonics starting at and increasing by one.
|
3075
|
+
All harmonics must have a corresponding half or double harmonic.
|
3076
|
+
skip_axis : int or sequence of int, optional
|
3077
|
+
Axes in `mean` to exclude from filter.
|
3078
|
+
By default, all axes except harmonics are included.
|
3079
|
+
|
3080
|
+
Returns
|
3081
|
+
-------
|
3082
|
+
mean : ndarray
|
3083
|
+
Unchanged intensity of phasor coordinates.
|
3084
|
+
real : ndarray
|
3085
|
+
Filtered real component of phasor coordinates.
|
3086
|
+
imag : ndarray
|
3087
|
+
Filtered imaginary component of phasor coordinates.
|
3088
|
+
|
3089
|
+
Raises
|
3090
|
+
------
|
3091
|
+
ValueError
|
3092
|
+
If `level` is less than 0.
|
3093
|
+
The array shapes of `mean`, `real`, and `imag` do not match.
|
3094
|
+
If `real` and `imag` have no harmonic axis.
|
3095
|
+
Number of harmonics in `harmonic` is less than 2 or does not match
|
3096
|
+
the first axis of `real` and `imag`.
|
3097
|
+
Not all harmonics in `harmonic` have a corresponding half
|
3098
|
+
or double harmonic.
|
3099
|
+
|
3100
|
+
References
|
3101
|
+
----------
|
3102
|
+
|
3103
|
+
.. [2] Silberberg M, and Grecco H. `pawFLIM: reducing bias and
|
3104
|
+
uncertainty to enable lower photon count in FLIM experiments
|
3105
|
+
<https://doi.org/10.1088/2050-6120/aa72ab>`_.
|
3106
|
+
*Methods Appl Fluoresc*, 5(2): 024016 (2017)
|
3107
|
+
|
3108
|
+
Examples
|
3109
|
+
--------
|
3110
|
+
Apply a pawFLIM wavelet filter with four significance levels (sigma)
|
3111
|
+
and three decomposition levels:
|
3112
|
+
|
3113
|
+
>>> mean, real, imag = phasor_filter_pawflim(
|
3114
|
+
... [[1, 1], [1, 1]],
|
3115
|
+
... [[[0.5, 0.8], [0.5, 0.8]], [[0.2, 0.4], [0.2, 0.4]]],
|
3116
|
+
... [[[0.5, 0.4], [0.5, 0.4]], [[0.4, 0.5], [0.4, 0.5]]],
|
3117
|
+
... sigma=4,
|
3118
|
+
... levels=3,
|
3119
|
+
... harmonic=[1, 2],
|
3120
|
+
... )
|
3121
|
+
>>> mean
|
3122
|
+
array([[1, 1],
|
3123
|
+
[1, 1]])
|
3124
|
+
>>> real
|
3125
|
+
array([[[0.65, 0.65],
|
3126
|
+
[0.65, 0.65]],
|
3127
|
+
[[0.3, 0.3],
|
3128
|
+
[0.3, 0.3]]])
|
3129
|
+
>>> imag
|
3130
|
+
array([[[0.45, 0.45],
|
3131
|
+
[0.45, 0.45]],
|
3132
|
+
[[0.45, 0.45],
|
3133
|
+
[0.45, 0.45]]])
|
3134
|
+
|
3135
|
+
"""
|
3136
|
+
from pawflim import pawflim # type: ignore[import-untyped]
|
3137
|
+
|
3138
|
+
mean = numpy.asarray(mean)
|
3139
|
+
real = numpy.asarray(real)
|
3140
|
+
imag = numpy.asarray(imag)
|
3141
|
+
|
3142
|
+
if levels < 0:
|
3143
|
+
raise ValueError(f'{levels=} < 0')
|
3144
|
+
if levels == 0:
|
3145
|
+
return mean, real, imag
|
3146
|
+
|
3147
|
+
if mean.shape != real.shape[-mean.ndim if mean.ndim else 1 :]:
|
3148
|
+
raise ValueError(f'{mean.shape=} != {real.shape=}')
|
3149
|
+
if real.shape != imag.shape:
|
3150
|
+
raise ValueError(f'{real.shape=} != {imag.shape=}')
|
3151
|
+
|
3152
|
+
has_harmonic_axis = mean.ndim + 1 == real.ndim
|
3153
|
+
if not has_harmonic_axis:
|
3154
|
+
raise ValueError('no harmonic axis')
|
3155
|
+
if harmonic is None:
|
3156
|
+
harmonics, _ = parse_harmonic('all', real.shape[0])
|
3157
|
+
else:
|
3158
|
+
harmonics, _ = parse_harmonic(harmonic, None)
|
3159
|
+
if len(harmonics) < 2:
|
3160
|
+
raise ValueError(
|
3161
|
+
'at least two harmonics required, ' f'got {len(harmonics)}'
|
3162
|
+
)
|
3163
|
+
if len(harmonics) != real.shape[0]:
|
3164
|
+
raise ValueError(
|
3165
|
+
'number of harmonics does not match first axis of real and imag'
|
3166
|
+
)
|
3167
|
+
|
3168
|
+
mean = numpy.asarray(numpy.nan_to_num(mean), dtype=float)
|
3169
|
+
real = numpy.asarray(numpy.nan_to_num(real * mean), dtype=float)
|
3170
|
+
imag = numpy.asarray(numpy.nan_to_num(imag * mean), dtype=float)
|
3171
|
+
|
3172
|
+
mean_expanded = numpy.broadcast_to(mean, real.shape).copy()
|
3173
|
+
original_mean_expanded = mean_expanded.copy()
|
3174
|
+
real_filtered = real.copy()
|
3175
|
+
imag_filtered = imag.copy()
|
3176
|
+
|
3177
|
+
_, axes = parse_skip_axis(skip_axis, mean.ndim, True)
|
3178
|
+
|
3179
|
+
for index in numpy.ndindex(
|
3180
|
+
*(
|
3181
|
+
real.shape[ax]
|
3182
|
+
for ax in range(real.ndim)
|
3183
|
+
if ax not in axes and ax != 0
|
3184
|
+
)
|
3185
|
+
):
|
3186
|
+
index_list: list[int | slice] = list(index)
|
3187
|
+
for ax in axes:
|
3188
|
+
index_list = index_list[:ax] + [slice(None)] + index_list[ax:]
|
3189
|
+
full_index = tuple(index_list)
|
3190
|
+
|
3191
|
+
processed_harmonics = set()
|
3192
|
+
|
3193
|
+
for h in harmonics:
|
3194
|
+
if h in processed_harmonics and (
|
3195
|
+
h * 4 in harmonics or h * 2 not in harmonics
|
3196
|
+
):
|
3197
|
+
continue
|
3198
|
+
if h * 2 not in harmonics:
|
3199
|
+
raise ValueError(
|
3200
|
+
f'harmonic {h} does not have a corresponding half '
|
3201
|
+
f'or double harmonic in {harmonics}'
|
3202
|
+
)
|
3203
|
+
n = harmonics.index(h)
|
3204
|
+
n2 = harmonics.index(h * 2)
|
3205
|
+
|
3206
|
+
complex_phasor = numpy.empty(
|
3207
|
+
(3, *original_mean_expanded[n][full_index].shape),
|
3208
|
+
dtype=complex,
|
3209
|
+
)
|
3210
|
+
complex_phasor[0] = original_mean_expanded[n][full_index]
|
3211
|
+
complex_phasor[1] = real[n][full_index] + 1j * imag[n][full_index]
|
3212
|
+
complex_phasor[2] = (
|
3213
|
+
real[n2][full_index] + 1j * imag[n2][full_index]
|
3214
|
+
)
|
3215
|
+
|
3216
|
+
complex_phasor = pawflim(
|
3217
|
+
complex_phasor, n_sigmas=sigma, levels=levels
|
3218
|
+
)
|
3219
|
+
|
3220
|
+
for i, idx in enumerate([n, n2]):
|
3221
|
+
if harmonics[idx] in processed_harmonics:
|
3222
|
+
continue
|
3223
|
+
mean_expanded[idx][full_index] = complex_phasor[0].real
|
3224
|
+
real_filtered[idx][full_index] = complex_phasor[i + 1].real
|
3225
|
+
imag_filtered[idx][full_index] = complex_phasor[i + 1].imag
|
3226
|
+
|
3227
|
+
processed_harmonics.add(h)
|
3228
|
+
processed_harmonics.add(h * 2)
|
3229
|
+
|
3230
|
+
with numpy.errstate(divide='ignore', invalid='ignore'):
|
3231
|
+
real = numpy.asarray(numpy.divide(real_filtered, mean_expanded))
|
3232
|
+
imag = numpy.asarray(numpy.divide(imag_filtered, mean_expanded))
|
3233
|
+
|
3234
|
+
return mean, real, imag
|
3235
|
+
|
3236
|
+
|
3009
3237
|
def phasor_threshold(
|
3010
3238
|
mean: ArrayLike,
|
3011
3239
|
real: ArrayLike,
|
@@ -3314,7 +3542,7 @@ def phasor_center(
|
|
3314
3542
|
raise ValueError(f'{mean.shape=} != {real.shape=}')
|
3315
3543
|
|
3316
3544
|
prepend_axis = mean.ndim + 1 == real.ndim
|
3317
|
-
_, axis =
|
3545
|
+
_, axis = parse_skip_axis(skip_axis, mean.ndim, prepend_axis)
|
3318
3546
|
if prepend_axis:
|
3319
3547
|
mean = numpy.expand_dims(mean, axis=0)
|
3320
3548
|
|
@@ -3358,62 +3586,3 @@ def _median(
|
|
3358
3586
|
numpy.nanmedian(real, **kwargs),
|
3359
3587
|
numpy.nanmedian(imag, **kwargs),
|
3360
3588
|
)
|
3361
|
-
|
3362
|
-
|
3363
|
-
def _parse_skip_axis(
|
3364
|
-
skip_axis: int | Sequence[int] | None,
|
3365
|
-
/,
|
3366
|
-
ndim: int,
|
3367
|
-
prepend_axis: bool = False,
|
3368
|
-
) -> tuple[tuple[int, ...], tuple[int, ...]]:
|
3369
|
-
"""Return axes to skip and not to skip.
|
3370
|
-
|
3371
|
-
This helper function is used to validate and parse `skip_axis`
|
3372
|
-
parameters.
|
3373
|
-
|
3374
|
-
Parameters
|
3375
|
-
----------
|
3376
|
-
skip_axis : int or sequence of int, optional
|
3377
|
-
Axes to skip. If None, no axes are skipped.
|
3378
|
-
ndim : int
|
3379
|
-
Dimensionality of array in which to skip axes.
|
3380
|
-
prepend_axis : bool, optional
|
3381
|
-
Prepend one dimension and include in `skip_axis`.
|
3382
|
-
|
3383
|
-
Returns
|
3384
|
-
-------
|
3385
|
-
skip_axis
|
3386
|
-
Ordered, positive values of `skip_axis`.
|
3387
|
-
other_axis
|
3388
|
-
Axes indices not included in `skip_axis`.
|
3389
|
-
|
3390
|
-
Raises
|
3391
|
-
------
|
3392
|
-
IndexError
|
3393
|
-
If any `skip_axis` value is out of bounds of `ndim`.
|
3394
|
-
|
3395
|
-
Examples
|
3396
|
-
--------
|
3397
|
-
>>> _parse_skip_axis((1, -2), 5)
|
3398
|
-
((1, 3), (0, 2, 4))
|
3399
|
-
|
3400
|
-
>>> _parse_skip_axis((1, -2), 5, True)
|
3401
|
-
((0, 2, 4), (1, 3, 5))
|
3402
|
-
|
3403
|
-
"""
|
3404
|
-
if ndim < 0:
|
3405
|
-
raise ValueError(f'invalid {ndim=}')
|
3406
|
-
if skip_axis is None:
|
3407
|
-
if prepend_axis:
|
3408
|
-
return (0,), tuple(range(1, ndim + 1))
|
3409
|
-
return (), tuple(range(ndim))
|
3410
|
-
if not isinstance(skip_axis, Sequence):
|
3411
|
-
skip_axis = (skip_axis,)
|
3412
|
-
if any(i >= ndim or i < -ndim for i in skip_axis):
|
3413
|
-
raise IndexError(f'skip_axis={skip_axis} out of range for {ndim=}')
|
3414
|
-
skip_axis = sorted(int(i % ndim) for i in skip_axis)
|
3415
|
-
if prepend_axis:
|
3416
|
-
skip_axis = [0] + [i + 1 for i in skip_axis]
|
3417
|
-
ndim += 1
|
3418
|
-
other_axis = tuple(i for i in range(ndim) if i not in skip_axis)
|
3419
|
-
return tuple(skip_axis), other_axis
|