phasorpy 0.1__cp312-cp312-win_amd64.whl → 0.2__cp312-cp312-win_amd64.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/_phasorpy.cp312-win_amd64.pyd +0 -0
- phasorpy/_phasorpy.pyx +333 -1
- phasorpy/_utils.py +27 -14
- phasorpy/datasets.py +20 -0
- phasorpy/io.py +7 -9
- phasorpy/phasor.py +227 -51
- phasorpy/utils.py +301 -1
- phasorpy/version.py +1 -1
- {phasorpy-0.1.dist-info → phasorpy-0.2.dist-info}/METADATA +22 -22
- phasorpy-0.2.dist-info/RECORD +24 -0
- {phasorpy-0.1.dist-info → phasorpy-0.2.dist-info}/WHEEL +1 -1
- phasorpy-0.1.dist-info/RECORD +0 -24
- {phasorpy-0.1.dist-info → phasorpy-0.2.dist-info}/LICENSE.txt +0 -0
- {phasorpy-0.1.dist-info → phasorpy-0.2.dist-info}/entry_points.txt +0 -0
- {phasorpy-0.1.dist-info → phasorpy-0.2.dist-info}/top_level.txt +0 -0
phasorpy/phasor.py
CHANGED
@@ -104,7 +104,6 @@ __all__ = [
|
|
104
104
|
]
|
105
105
|
|
106
106
|
import math
|
107
|
-
import numbers
|
108
107
|
from collections.abc import Sequence
|
109
108
|
from typing import TYPE_CHECKING
|
110
109
|
|
@@ -122,6 +121,7 @@ import numpy
|
|
122
121
|
|
123
122
|
from ._phasorpy import (
|
124
123
|
_gaussian_signal,
|
124
|
+
_median_filter_2d,
|
125
125
|
_phasor_at_harmonic,
|
126
126
|
_phasor_divide,
|
127
127
|
_phasor_from_apparent_lifetime,
|
@@ -180,6 +180,8 @@ def phasor_from_signal(
|
|
180
180
|
Else, harmonics must be at least one and no larger than half the
|
181
181
|
number of `signal` samples along `axis`.
|
182
182
|
The default is the first harmonic (fundamental frequency).
|
183
|
+
A minimum of `harmonic * 2 + 1` samples are required along `axis`
|
184
|
+
to calculate correct phasor coordinates at `harmonic`.
|
183
185
|
sample_phase : array_like, optional
|
184
186
|
Phase values (in radians) of `signal` samples along `axis`.
|
185
187
|
If None (default), samples are assumed to be uniformly spaced along
|
@@ -285,7 +287,7 @@ def phasor_from_signal(
|
|
285
287
|
if dtype.kind != 'f':
|
286
288
|
raise TypeError(f'{dtype=} not supported')
|
287
289
|
|
288
|
-
harmonic, keepdims = parse_harmonic(harmonic, samples)
|
290
|
+
harmonic, keepdims = parse_harmonic(harmonic, samples // 2)
|
289
291
|
num_harmonics = len(harmonic)
|
290
292
|
|
291
293
|
if sample_phase is not None:
|
@@ -303,7 +305,7 @@ def phasor_from_signal(
|
|
303
305
|
use_fft = sample_phase is None and (
|
304
306
|
rfft is not None
|
305
307
|
or num_harmonics > 7
|
306
|
-
or num_harmonics
|
308
|
+
or num_harmonics >= samples // 2
|
307
309
|
)
|
308
310
|
|
309
311
|
if use_fft:
|
@@ -413,7 +415,7 @@ def phasor_to_signal(
|
|
413
415
|
coordinates (most commonly, lower harmonics are present if the number
|
414
416
|
of dimensions of `mean` is one less than `real`).
|
415
417
|
If `'all'`, the harmonics in the first axis of phasor coordinates are
|
416
|
-
the lower harmonics
|
418
|
+
the lower harmonics necessary to synthesize `samples`.
|
417
419
|
Else, harmonics must be at least one and no larger than half of
|
418
420
|
`samples`.
|
419
421
|
The phasor coordinates of missing harmonics are zeroed
|
@@ -462,18 +464,15 @@ def phasor_to_signal(
|
|
462
464
|
array([2.2, 2.486, 0.8566, -0.4365, 0.3938])
|
463
465
|
|
464
466
|
"""
|
467
|
+
if samples < 3:
|
468
|
+
raise ValueError(f'{samples=} < 3')
|
469
|
+
|
465
470
|
mean = numpy.array(mean, ndmin=0, copy=True)
|
466
471
|
real = numpy.array(real, ndmin=0, copy=True)
|
467
472
|
imag = numpy.array(imag, ndmin=1, copy=True)
|
468
473
|
|
469
|
-
if isinstance(harmonic, (int, numbers.Integral)) and harmonic == 0:
|
470
|
-
# harmonics are expected in the first axes of real and imag
|
471
|
-
samples_ = 2 * imag.shape[0]
|
472
|
-
else:
|
473
|
-
samples_ = samples
|
474
|
-
|
475
474
|
harmonic_ = harmonic
|
476
|
-
harmonic, has_harmonic_axis = parse_harmonic(harmonic,
|
475
|
+
harmonic, has_harmonic_axis = parse_harmonic(harmonic, samples // 2)
|
477
476
|
|
478
477
|
if real.ndim == 1 and len(harmonic) > 1 and real.shape[0] == len(harmonic):
|
479
478
|
# single axis contains harmonic
|
@@ -641,7 +640,7 @@ def lifetime_to_signal(
|
|
641
640
|
if harmonic is None:
|
642
641
|
harmonic = 'all'
|
643
642
|
all_hamonics = harmonic == 'all'
|
644
|
-
harmonic, _ = parse_harmonic(harmonic, samples)
|
643
|
+
harmonic, _ = parse_harmonic(harmonic, samples // 2)
|
645
644
|
|
646
645
|
if samples < 16:
|
647
646
|
raise ValueError(f'{samples=} < 16')
|
@@ -962,12 +961,13 @@ def phasor_calibrate(
|
|
962
961
|
frequency: ArrayLike,
|
963
962
|
lifetime: ArrayLike,
|
964
963
|
*,
|
964
|
+
harmonic: int | Sequence[int] | Literal['all'] | str | None = None,
|
965
|
+
skip_axis: int | Sequence[int] | None = None,
|
965
966
|
fraction: ArrayLike | None = None,
|
966
967
|
preexponential: bool = False,
|
967
968
|
unit_conversion: float = 1e-3,
|
968
969
|
reverse: bool = False,
|
969
970
|
method: Literal['mean', 'median'] = 'mean',
|
970
|
-
skip_axis: int | Sequence[int] | None = None,
|
971
971
|
) -> tuple[NDArray[Any], NDArray[Any]]:
|
972
972
|
"""
|
973
973
|
Return calibrated/referenced phasor coordinates.
|
@@ -992,10 +992,22 @@ def phasor_calibrate(
|
|
992
992
|
Must be measured with the same instrument setting as the phasor
|
993
993
|
coordinates to be calibrated.
|
994
994
|
frequency : array_like
|
995
|
-
|
996
|
-
A scalar or one-dimensional sequence.
|
995
|
+
Fundamental laser pulse or modulation frequency in MHz.
|
997
996
|
lifetime : array_like
|
998
|
-
Lifetime components in ns. Must be scalar or one
|
997
|
+
Lifetime components in ns. Must be scalar or one-dimensional.
|
998
|
+
harmonic : int, sequence of int, or 'all', default: 1
|
999
|
+
Harmonics included in `real` and `imag`.
|
1000
|
+
If an integer, the harmonics at which `real` and `imag` were acquired
|
1001
|
+
or calculated.
|
1002
|
+
If a sequence, the harmonics included in the first axis of `real` and
|
1003
|
+
`imag`.
|
1004
|
+
If `'all'`, the first axis of `real` and `imag` contains lower
|
1005
|
+
harmonics.
|
1006
|
+
The default is the first harmonic (fundamental frequency).
|
1007
|
+
skip_axis : int or sequence of int, optional
|
1008
|
+
Axes to be excluded during center calculation. If None, all
|
1009
|
+
axes are considered, except for the first axis if multiple harmonics
|
1010
|
+
are specified.
|
999
1011
|
fraction : array_like, optional
|
1000
1012
|
Fractional intensities or pre-exponential amplitudes of the lifetime
|
1001
1013
|
components. Fractions are normalized to sum to 1.
|
@@ -1015,9 +1027,6 @@ def phasor_calibrate(
|
|
1015
1027
|
|
1016
1028
|
- ``'mean'``: Arithmetic mean of phasor coordinates.
|
1017
1029
|
- ``'median'``: Spatial median of phasor coordinates.
|
1018
|
-
skip_axis : int or sequence of int, optional
|
1019
|
-
Axes to be excluded during center calculation. If None, all
|
1020
|
-
axes are considered.
|
1021
1030
|
|
1022
1031
|
Returns
|
1023
1032
|
-------
|
@@ -1031,6 +1040,7 @@ def phasor_calibrate(
|
|
1031
1040
|
ValueError
|
1032
1041
|
The array shapes of `real` and `imag`, or `reference_real` and
|
1033
1042
|
`reference_imag` do not match.
|
1043
|
+
Number of harmonics does not match the first axis of `real` and `imag`.
|
1034
1044
|
|
1035
1045
|
See Also
|
1036
1046
|
--------
|
@@ -1113,6 +1123,22 @@ def phasor_calibrate(
|
|
1113
1123
|
f'reference_real.shape={ref_re.shape} '
|
1114
1124
|
f'!= reference_imag.shape{ref_im.shape}'
|
1115
1125
|
)
|
1126
|
+
|
1127
|
+
if harmonic == 'all' and re.ndim > 0:
|
1128
|
+
harmonic, has_harmonic_axis = parse_harmonic(harmonic, re.shape[0])
|
1129
|
+
else:
|
1130
|
+
harmonic, has_harmonic_axis = parse_harmonic(harmonic)
|
1131
|
+
if has_harmonic_axis and len(harmonic) != re.shape[0]:
|
1132
|
+
raise ValueError(f'{len(harmonic)=} != real.shape[0]={re.shape[0]}')
|
1133
|
+
|
1134
|
+
frequency = numpy.asarray(frequency)
|
1135
|
+
frequency = frequency * harmonic
|
1136
|
+
|
1137
|
+
skip_axis, axis = _parse_skip_axis(skip_axis, re.ndim)
|
1138
|
+
if has_harmonic_axis:
|
1139
|
+
skip_axis = (0,) + skip_axis if 0 not in skip_axis else skip_axis
|
1140
|
+
skip_axis, axis = _parse_skip_axis(skip_axis, re.ndim)
|
1141
|
+
|
1116
1142
|
measured_re, measured_im = phasor_center(
|
1117
1143
|
reference_real, reference_imag, skip_axis=skip_axis, method=method
|
1118
1144
|
)
|
@@ -1130,7 +1156,6 @@ def phasor_calibrate(
|
|
1130
1156
|
if reverse:
|
1131
1157
|
numpy.negative(phi_zero, out=phi_zero)
|
1132
1158
|
numpy.reciprocal(mod_zero, out=mod_zero)
|
1133
|
-
_, axis = _parse_skip_axis(skip_axis, re.ndim)
|
1134
1159
|
if axis is not None:
|
1135
1160
|
phi_zero = numpy.expand_dims(
|
1136
1161
|
phi_zero,
|
@@ -2648,14 +2673,17 @@ def phasor_filter(
|
|
2648
2673
|
imag: ArrayLike,
|
2649
2674
|
/,
|
2650
2675
|
*,
|
2651
|
-
method: Literal['median'] = 'median',
|
2676
|
+
method: Literal['median', 'median_scipy'] = 'median',
|
2652
2677
|
repeat: int = 1,
|
2678
|
+
size: int = 3,
|
2679
|
+
skip_axis: int | Sequence[int] | None = None,
|
2680
|
+
num_threads: int | None = None,
|
2653
2681
|
**kwargs: Any,
|
2654
2682
|
) -> tuple[NDArray[Any], NDArray[Any]]:
|
2655
2683
|
"""Return filtered phasor coordinates.
|
2656
2684
|
|
2657
|
-
By default, a median filter is applied to the real and
|
2658
|
-
components of phasor coordinates once with a kernel size of 3
|
2685
|
+
By default, a median filter is applied independently to the real and
|
2686
|
+
imaginary components of phasor coordinates once with a kernel size of 3
|
2659
2687
|
multiplied by the number of dimensions of the input arrays.
|
2660
2688
|
|
2661
2689
|
Parameters
|
@@ -2668,9 +2696,21 @@ def phasor_filter(
|
|
2668
2696
|
Method used for filtering:
|
2669
2697
|
|
2670
2698
|
- ``'median'``: Spatial median of phasor coordinates.
|
2699
|
+
- ``'median_scipy'``: Spatial median of phasor coordinates
|
2700
|
+
based on :py:func:`scipy.ndimage.median_filter`.
|
2671
2701
|
|
2672
2702
|
repeat : int, optional
|
2673
2703
|
Number of times to apply filter. The default is 1.
|
2704
|
+
size : int, optional
|
2705
|
+
Size of filter kernel. The default is 3.
|
2706
|
+
skip_axis : int or sequence of int, optional
|
2707
|
+
Axis or axes to skip filtering. By default all axes are filtered.
|
2708
|
+
num_threads : int, optional
|
2709
|
+
Number of OpenMP threads to use for parallelization.
|
2710
|
+
Applies to filtering in two dimensions with the `median` method only.
|
2711
|
+
By default, multi-threading is disabled.
|
2712
|
+
If zero, up to half of logical CPUs are used.
|
2713
|
+
OpenMP may not be available on all platforms.
|
2674
2714
|
**kwargs
|
2675
2715
|
Optional arguments passed to :py:func:`scipy.ndimage.median_filter`.
|
2676
2716
|
|
@@ -2685,26 +2725,33 @@ def phasor_filter(
|
|
2685
2725
|
------
|
2686
2726
|
ValueError
|
2687
2727
|
If the specified method is not supported.
|
2728
|
+
If `repeat` is less than 0.
|
2729
|
+
If `size` is less than 1.
|
2688
2730
|
The array shapes of `real` and `imag` do not match.
|
2689
|
-
If `repeat` is less than 1.
|
2690
2731
|
|
2691
2732
|
Notes
|
2692
2733
|
-----
|
2693
|
-
For now, only the median filter method is implemented.
|
2694
2734
|
Additional filtering methods may be added in the future.
|
2695
2735
|
|
2696
|
-
The
|
2736
|
+
The `median` method ignores `NaN` values. If the kernel contains an even
|
2737
|
+
number of elements, the median is the average of the two middle elements.
|
2738
|
+
|
2739
|
+
The implementation of the `median_scipy` method is based on
|
2697
2740
|
:py:func:`scipy.ndimage.median_filter`,
|
2698
2741
|
which has undefined behavior if the input arrays contain `NaN` values.
|
2699
2742
|
See `issue #87 <https://github.com/phasorpy/phasorpy/issues/87>`_.
|
2700
2743
|
|
2744
|
+
When filtering in more than two dimensions, the `median` method is
|
2745
|
+
slower than the `median_scipy` method. When filtering in two
|
2746
|
+
dimensions, both methods have similar performance.
|
2747
|
+
|
2701
2748
|
Examples
|
2702
2749
|
--------
|
2703
2750
|
Apply three times a median filter with a kernel size of three:
|
2704
2751
|
|
2705
2752
|
>>> phasor_filter(
|
2706
2753
|
... [[0, 0, 0], [5, 5, 5], [2, 2, 2]],
|
2707
|
-
... [[3, 3, 3], [6,
|
2754
|
+
... [[3, 3, 3], [6, math.nan, 6], [4, 4, 4]],
|
2708
2755
|
... size=3,
|
2709
2756
|
... repeat=3,
|
2710
2757
|
... )
|
@@ -2712,25 +2759,42 @@ def phasor_filter(
|
|
2712
2759
|
[2, 2, 2],
|
2713
2760
|
[2, 2, 2]]),
|
2714
2761
|
array([[3, 3, 3],
|
2715
|
-
[4,
|
2762
|
+
[4, nan, 4],
|
2716
2763
|
[4, 4, 4]]))
|
2717
2764
|
|
2718
2765
|
"""
|
2719
|
-
methods = {
|
2766
|
+
methods: dict[str, Callable[..., Any]] = {
|
2767
|
+
'median': _median_filter,
|
2768
|
+
'median_scipy': _median_filter_scipy,
|
2769
|
+
}
|
2720
2770
|
if method not in methods:
|
2721
2771
|
raise ValueError(
|
2722
|
-
f
|
2772
|
+
f'Method not supported, supported methods are: '
|
2723
2773
|
f"{', '.join(methods)}"
|
2724
2774
|
)
|
2775
|
+
if repeat == 0 or size == 1:
|
2776
|
+
return numpy.asarray(real), numpy.asarray(imag)
|
2777
|
+
if repeat < 0:
|
2778
|
+
raise ValueError(f'{repeat=} < 0')
|
2779
|
+
if size < 1:
|
2780
|
+
raise ValueError(f'{size=} < 1')
|
2781
|
+
|
2725
2782
|
real = numpy.asarray(real)
|
2726
2783
|
imag = numpy.asarray(imag)
|
2727
2784
|
|
2728
2785
|
if real.shape != imag.shape:
|
2729
2786
|
raise ValueError(f'{real.shape=} != {imag.shape=}')
|
2730
|
-
if repeat < 1:
|
2731
|
-
raise ValueError(f'{repeat=} < 1')
|
2732
2787
|
|
2733
|
-
|
2788
|
+
_, axes = _parse_skip_axis(skip_axis, real.ndim)
|
2789
|
+
|
2790
|
+
if 'axes' in kwargs and method == 'median_scipy':
|
2791
|
+
axes = kwargs.pop('axes')
|
2792
|
+
if method == 'median':
|
2793
|
+
kwargs['num_threads'] = num_threads
|
2794
|
+
|
2795
|
+
return methods[method]( # type: ignore[no-any-return]
|
2796
|
+
real, imag, axes, repeat=repeat, size=size, **kwargs
|
2797
|
+
)
|
2734
2798
|
|
2735
2799
|
|
2736
2800
|
def phasor_threshold(
|
@@ -2973,7 +3037,7 @@ def phasor_center(
|
|
2973
3037
|
}
|
2974
3038
|
if method not in methods:
|
2975
3039
|
raise ValueError(
|
2976
|
-
f
|
3040
|
+
f'Method not supported, supported methods are: '
|
2977
3041
|
f"{', '.join(methods)}"
|
2978
3042
|
)
|
2979
3043
|
real = numpy.asarray(real)
|
@@ -3024,18 +3088,18 @@ def _median(
|
|
3024
3088
|
Parameters
|
3025
3089
|
----------
|
3026
3090
|
real : ndarray
|
3027
|
-
Real components of
|
3091
|
+
Real components of phasor coordinates.
|
3028
3092
|
imag : ndarray
|
3029
|
-
Imaginary components of
|
3093
|
+
Imaginary components of phasor coordinates.
|
3030
3094
|
**kwargs
|
3031
3095
|
Optional arguments passed to :py:func:`numpy.nanmedian`.
|
3032
3096
|
|
3033
3097
|
Returns
|
3034
3098
|
-------
|
3035
3099
|
real_center : ndarray
|
3036
|
-
Spatial median center
|
3100
|
+
Spatial median center of real coordinates.
|
3037
3101
|
imag_center : ndarray
|
3038
|
-
Spatial median center
|
3102
|
+
Spatial median center of imaginary coordinates.
|
3039
3103
|
|
3040
3104
|
Examples
|
3041
3105
|
--------
|
@@ -3047,42 +3111,154 @@ def _median(
|
|
3047
3111
|
|
3048
3112
|
|
3049
3113
|
def _median_filter(
|
3050
|
-
real:
|
3051
|
-
imag:
|
3114
|
+
real: NDArray[Any],
|
3115
|
+
imag: NDArray[Any],
|
3116
|
+
axes: Sequence[int],
|
3117
|
+
/,
|
3118
|
+
*,
|
3119
|
+
repeat: int = 1,
|
3120
|
+
size: int = 3,
|
3121
|
+
num_threads: int | None = None,
|
3122
|
+
) -> tuple[NDArray[Any], NDArray[Any]]:
|
3123
|
+
"""Return median-filtered phasor coordinates, ignoring NaN values.
|
3124
|
+
|
3125
|
+
Parameters
|
3126
|
+
----------
|
3127
|
+
real : ndarray
|
3128
|
+
Real components of phasor coordinates.
|
3129
|
+
imag : ndarray
|
3130
|
+
Imaginary components of phasor coordinates.
|
3131
|
+
axes : sequence of int
|
3132
|
+
Axes along which to apply median filter.
|
3133
|
+
repeat : int, optional
|
3134
|
+
Number of times to apply filter. The default is 1.
|
3135
|
+
size : int, optional
|
3136
|
+
Size of median filter kernel. The default is 3.
|
3137
|
+
num_threads : int, optional
|
3138
|
+
Number of OpenMP threads to use for parallelization.
|
3139
|
+
By default, multi-threading is disabled.
|
3140
|
+
If zero, up to half of logical CPUs are used.
|
3141
|
+
OpenMP may not be available on all platforms.
|
3142
|
+
|
3143
|
+
Returns
|
3144
|
+
-------
|
3145
|
+
real : ndarray
|
3146
|
+
Median-filtered real component of phasor coordinates.
|
3147
|
+
imag : ndarray
|
3148
|
+
Median-filtered imaginary component of phasor coordinates.
|
3149
|
+
|
3150
|
+
"""
|
3151
|
+
real = numpy.asarray(real)
|
3152
|
+
if real.dtype == numpy.float32:
|
3153
|
+
real = real.copy()
|
3154
|
+
else:
|
3155
|
+
real = real.astype(float)
|
3156
|
+
|
3157
|
+
imag = numpy.asarray(imag)
|
3158
|
+
if imag.dtype == numpy.float32:
|
3159
|
+
imag = imag.copy()
|
3160
|
+
else:
|
3161
|
+
imag = imag.astype(float)
|
3162
|
+
|
3163
|
+
if len(axes) != 2:
|
3164
|
+
# n-dimensional median filter using numpy
|
3165
|
+
from numpy.lib.stride_tricks import sliding_window_view
|
3166
|
+
|
3167
|
+
kernel_shape = tuple(
|
3168
|
+
size if i in axes else 1 for i in range(real.ndim)
|
3169
|
+
)
|
3170
|
+
pad_width = [
|
3171
|
+
(s // 2, s // 2) if s > 1 else (0, 0) for s in kernel_shape
|
3172
|
+
]
|
3173
|
+
axis = tuple(range(-real.ndim, 0))
|
3174
|
+
|
3175
|
+
nan_mask = numpy.isnan(real)
|
3176
|
+
for _ in range(repeat):
|
3177
|
+
real = numpy.pad(real, pad_width, mode='edge')
|
3178
|
+
real = sliding_window_view(real, kernel_shape)
|
3179
|
+
real = numpy.nanmedian(real, axis=axis)
|
3180
|
+
real = numpy.where(nan_mask, numpy.nan, real)
|
3181
|
+
|
3182
|
+
nan_mask = numpy.isnan(imag)
|
3183
|
+
for _ in range(repeat):
|
3184
|
+
imag = numpy.pad(imag, pad_width, mode='edge')
|
3185
|
+
imag = sliding_window_view(imag, kernel_shape)
|
3186
|
+
imag = numpy.nanmedian(imag, axis=axis)
|
3187
|
+
imag = numpy.where(nan_mask, numpy.nan, imag)
|
3188
|
+
|
3189
|
+
return real, imag
|
3190
|
+
|
3191
|
+
# 2-dimensional median filter using optimized Cython implementation
|
3192
|
+
num_threads = number_threads(num_threads)
|
3193
|
+
|
3194
|
+
filtered_slice = numpy.empty(
|
3195
|
+
tuple(real.shape[axis] for axis in axes), dtype=real.dtype
|
3196
|
+
)
|
3197
|
+
|
3198
|
+
for index in numpy.ndindex(
|
3199
|
+
*[real.shape[ax] for ax in range(real.ndim) if ax not in axes]
|
3200
|
+
):
|
3201
|
+
index_list: list[int | slice] = list(index)
|
3202
|
+
for ax in axes:
|
3203
|
+
index_list = index_list[:ax] + [slice(None)] + index_list[ax:]
|
3204
|
+
full_index = tuple(index_list)
|
3205
|
+
|
3206
|
+
_median_filter_2d(
|
3207
|
+
real[full_index], filtered_slice, size, repeat, num_threads
|
3208
|
+
)
|
3209
|
+
|
3210
|
+
_median_filter_2d(
|
3211
|
+
imag[full_index], filtered_slice, size, repeat, num_threads
|
3212
|
+
)
|
3213
|
+
|
3214
|
+
return real, imag
|
3215
|
+
|
3216
|
+
|
3217
|
+
def _median_filter_scipy(
|
3218
|
+
real: NDArray[Any],
|
3219
|
+
imag: NDArray[Any],
|
3220
|
+
axes: Sequence[int],
|
3221
|
+
/,
|
3222
|
+
*,
|
3052
3223
|
repeat: int = 1,
|
3053
|
-
size: int
|
3224
|
+
size: int = 3,
|
3054
3225
|
**kwargs: Any,
|
3055
3226
|
) -> tuple[NDArray[Any], NDArray[Any]]:
|
3056
|
-
"""Return
|
3227
|
+
"""Return median-filtered phasor coordinates.
|
3057
3228
|
|
3058
3229
|
Convenience wrapper around :py:func:`scipy.ndimage.median_filter`.
|
3059
3230
|
|
3060
3231
|
Parameters
|
3061
3232
|
----------
|
3062
3233
|
real : ndarray
|
3063
|
-
Real components of
|
3234
|
+
Real components of phasor coordinates.
|
3064
3235
|
imag : ndarray
|
3065
|
-
Imaginary components of
|
3236
|
+
Imaginary components of phasor coordinates.
|
3237
|
+
axes : sequence of int
|
3238
|
+
Axes along which to apply median filter.
|
3066
3239
|
repeat : int, optional
|
3067
3240
|
Number of times to apply filter. The default is 1.
|
3068
|
-
size : int
|
3069
|
-
|
3241
|
+
size : int, optional
|
3242
|
+
Size of median filter kernel. The default is 3.
|
3070
3243
|
**kwargs
|
3071
3244
|
Optional arguments passed to :py:func:`scipy.ndimage.median_filter`.
|
3072
3245
|
|
3073
3246
|
Returns
|
3074
3247
|
-------
|
3075
3248
|
real : ndarray
|
3076
|
-
|
3249
|
+
Median-filtered real component of phasor coordinates.
|
3077
3250
|
imag : ndarray
|
3078
|
-
|
3251
|
+
Median-filtered imaginary component of phasor coordinates.
|
3079
3252
|
|
3080
3253
|
"""
|
3081
3254
|
from scipy.ndimage import median_filter
|
3082
3255
|
|
3256
|
+
real = numpy.asarray(real)
|
3257
|
+
imag = numpy.asarray(imag)
|
3258
|
+
|
3083
3259
|
for _ in range(repeat):
|
3084
|
-
real = median_filter(real, size=size, **kwargs)
|
3085
|
-
imag = median_filter(imag, size=size, **kwargs)
|
3260
|
+
real = median_filter(real, size=size, axes=axes, **kwargs)
|
3261
|
+
imag = median_filter(imag, size=size, axes=axes, **kwargs)
|
3086
3262
|
|
3087
3263
|
return numpy.asarray(real), numpy.asarray(imag)
|
3088
3264
|
|
@@ -3129,7 +3305,7 @@ def _parse_skip_axis(
|
|
3129
3305
|
if not isinstance(skip_axis, Sequence):
|
3130
3306
|
skip_axis = (skip_axis,)
|
3131
3307
|
if any(i >= ndim or i < -ndim for i in skip_axis):
|
3132
|
-
raise IndexError(f
|
3308
|
+
raise IndexError(f'skip_axis={skip_axis} out of range for {ndim=}')
|
3133
3309
|
skip_axis = tuple(sorted(int(i % ndim) for i in skip_axis))
|
3134
3310
|
other_axis = tuple(i for i in range(ndim) if i not in skip_axis)
|
3135
3311
|
return skip_axis, other_axis
|