phasorpy 0.4__cp313-cp313-win_amd64.whl → 0.6__cp313-cp313-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/__init__.py +2 -3
- phasorpy/_phasorpy.cp313-win_amd64.pyd +0 -0
- phasorpy/_phasorpy.pyx +237 -51
- phasorpy/_utils.py +201 -7
- phasorpy/cli.py +58 -3
- phasorpy/cluster.py +206 -0
- phasorpy/color.py +16 -11
- phasorpy/components.py +240 -69
- phasorpy/conftest.py +2 -0
- phasorpy/cursors.py +9 -9
- phasorpy/datasets.py +129 -51
- phasorpy/experimental.py +312 -0
- phasorpy/io/__init__.py +137 -0
- phasorpy/io/_flimlabs.py +350 -0
- phasorpy/io/_leica.py +329 -0
- phasorpy/io/_ometiff.py +445 -0
- phasorpy/io/_other.py +782 -0
- phasorpy/io/_simfcs.py +627 -0
- phasorpy/phasor.py +572 -97
- phasorpy/plot/__init__.py +27 -0
- phasorpy/plot/_functions.py +717 -0
- phasorpy/plot/_lifetime_plots.py +553 -0
- phasorpy/plot/_phasorplot.py +1119 -0
- phasorpy/plot/_phasorplot_fret.py +559 -0
- phasorpy/utils.py +90 -297
- {phasorpy-0.4.dist-info → phasorpy-0.6.dist-info}/METADATA +11 -16
- phasorpy-0.6.dist-info/RECORD +34 -0
- {phasorpy-0.4.dist-info → phasorpy-0.6.dist-info}/WHEEL +1 -1
- phasorpy/_io.py +0 -2431
- phasorpy/io.py +0 -5
- phasorpy/plot.py +0 -2094
- phasorpy/version.py +0 -72
- phasorpy-0.4.dist-info/RECORD +0 -25
- {phasorpy-0.4.dist-info → phasorpy-0.6.dist-info}/entry_points.txt +0 -0
- {phasorpy-0.4.dist-info → phasorpy-0.6.dist-info/licenses}/LICENSE.txt +0 -0
- {phasorpy-0.4.dist-info → phasorpy-0.6.dist-info}/top_level.txt +0 -0
phasorpy/phasor.py
CHANGED
@@ -17,6 +17,7 @@ The ``phasorpy.phasor`` module provides functions to:
|
|
17
17
|
- :py:func:`phasor_from_lifetime`
|
18
18
|
- :py:func:`phasor_from_apparent_lifetime`
|
19
19
|
- :py:func:`phasor_to_apparent_lifetime`
|
20
|
+
- :py:func:`phasor_to_normal_lifetime`
|
20
21
|
|
21
22
|
- convert to and from polar coordinates (phase and modulation):
|
22
23
|
|
@@ -59,15 +60,20 @@ The ``phasorpy.phasor`` module provides functions to:
|
|
59
60
|
- :py:func:`lifetime_fraction_from_amplitude`
|
60
61
|
- :py:func:`lifetime_fraction_to_amplitude`
|
61
62
|
|
62
|
-
- calculate phasor coordinates on semicircle at other harmonics:
|
63
|
+
- calculate phasor coordinates on universal semicircle at other harmonics:
|
63
64
|
|
64
65
|
- :py:func:`phasor_at_harmonic`
|
65
66
|
|
66
67
|
- filter phasor coordinates:
|
67
68
|
|
68
69
|
- :py:func:`phasor_filter_median`
|
70
|
+
- :py:func:`phasor_filter_pawflim`
|
69
71
|
- :py:func:`phasor_threshold`
|
70
72
|
|
73
|
+
- find nearest neighbor phasor coordinates from another set of phasor coordinates:
|
74
|
+
|
75
|
+
- :py:func:`phasor_nearest_neighbor`
|
76
|
+
|
71
77
|
"""
|
72
78
|
|
73
79
|
from __future__ import annotations
|
@@ -83,6 +89,7 @@ __all__ = [
|
|
83
89
|
'phasor_center',
|
84
90
|
'phasor_divide',
|
85
91
|
'phasor_filter_median',
|
92
|
+
'phasor_filter_pawflim',
|
86
93
|
'phasor_from_apparent_lifetime',
|
87
94
|
'phasor_from_fret_acceptor',
|
88
95
|
'phasor_from_fret_donor',
|
@@ -90,11 +97,14 @@ __all__ = [
|
|
90
97
|
'phasor_from_polar',
|
91
98
|
'phasor_from_signal',
|
92
99
|
'phasor_multiply',
|
100
|
+
'phasor_nearest_neighbor',
|
93
101
|
'phasor_normalize',
|
94
102
|
'phasor_semicircle',
|
103
|
+
'phasor_semicircle_intersect',
|
95
104
|
'phasor_threshold',
|
96
105
|
'phasor_to_apparent_lifetime',
|
97
106
|
'phasor_to_complex',
|
107
|
+
'phasor_to_normal_lifetime',
|
98
108
|
'phasor_to_polar',
|
99
109
|
'phasor_to_principal_plane',
|
100
110
|
'phasor_to_signal',
|
@@ -123,7 +133,9 @@ import numpy
|
|
123
133
|
|
124
134
|
from ._phasorpy import (
|
125
135
|
_gaussian_signal,
|
136
|
+
_intersect_semicircle_line,
|
126
137
|
_median_filter_2d,
|
138
|
+
_nearest_neighbor_2d,
|
127
139
|
_phasor_at_harmonic,
|
128
140
|
_phasor_divide,
|
129
141
|
_phasor_from_apparent_lifetime,
|
@@ -140,6 +152,7 @@ from ._phasorpy import (
|
|
140
152
|
_phasor_threshold_nan,
|
141
153
|
_phasor_threshold_open,
|
142
154
|
_phasor_to_apparent_lifetime,
|
155
|
+
_phasor_to_normal_lifetime,
|
143
156
|
_phasor_to_polar,
|
144
157
|
_phasor_transform,
|
145
158
|
_phasor_transform_const,
|
@@ -149,7 +162,7 @@ from ._phasorpy import (
|
|
149
162
|
_polar_from_single_lifetime,
|
150
163
|
_polar_to_apparent_lifetime,
|
151
164
|
)
|
152
|
-
from ._utils import parse_harmonic, parse_signal_axis
|
165
|
+
from ._utils import parse_harmonic, parse_signal_axis, parse_skip_axis
|
153
166
|
from .utils import number_threads
|
154
167
|
|
155
168
|
|
@@ -502,7 +515,8 @@ def phasor_to_signal(
|
|
502
515
|
else:
|
503
516
|
keepdims = mean.ndim > 0 or real.ndim > 0
|
504
517
|
|
505
|
-
mean
|
518
|
+
mean = numpy.asarray(numpy.atleast_1d(mean))
|
519
|
+
real = numpy.asarray(numpy.atleast_1d(real))
|
506
520
|
|
507
521
|
if real.dtype.kind != 'f' or imag.dtype.kind != 'f':
|
508
522
|
raise ValueError(f'{real.dtype=} or {imag.dtype=} not floating point')
|
@@ -595,7 +609,7 @@ def lifetime_to_signal(
|
|
595
609
|
or `1` to synthesize a homodyne signal.
|
596
610
|
zero_phase : float, optional
|
597
611
|
Position of instrument response function in radians.
|
598
|
-
Must be in range 0
|
612
|
+
Must be in range [0, pi]. The default is the 8th sample.
|
599
613
|
zero_stdev : float, optional
|
600
614
|
Standard deviation of instrument response function in radians.
|
601
615
|
Must be at least 1.5 samples and no more than one tenth of samples
|
@@ -671,7 +685,7 @@ def lifetime_to_signal(
|
|
671
685
|
mean = 1.0
|
672
686
|
mean = numpy.asarray(mean)
|
673
687
|
mean -= background
|
674
|
-
if numpy.any(mean
|
688
|
+
if numpy.any(mean < 0.0):
|
675
689
|
raise ValueError('mean - background must not be less than zero')
|
676
690
|
|
677
691
|
scale = samples / (2.0 * math.pi)
|
@@ -683,7 +697,7 @@ def lifetime_to_signal(
|
|
683
697
|
stdev = zero_stdev * scale # in sample units
|
684
698
|
|
685
699
|
if zero_phase < 0 or zero_phase > 2.0 * math.pi:
|
686
|
-
raise ValueError(f'{zero_phase=} out of range [0
|
700
|
+
raise ValueError(f'{zero_phase=} out of range [0, 2 pi]')
|
687
701
|
if stdev < 1.5:
|
688
702
|
raise ValueError(
|
689
703
|
f'{zero_stdev=} < {1.5 / scale} cannot be sampled sufficiently'
|
@@ -749,9 +763,9 @@ def phasor_semicircle(
|
|
749
763
|
Returns
|
750
764
|
-------
|
751
765
|
real : ndarray
|
752
|
-
Real component of
|
766
|
+
Real component of phasor coordinates on universal semicircle.
|
753
767
|
imag : ndarray
|
754
|
-
Imaginary component of
|
768
|
+
Imaginary component of phasor coordinates on universal semicircle.
|
755
769
|
|
756
770
|
Raises
|
757
771
|
------
|
@@ -791,6 +805,65 @@ def phasor_semicircle(
|
|
791
805
|
return real, imag
|
792
806
|
|
793
807
|
|
808
|
+
def phasor_semicircle_intersect(
|
809
|
+
real0: ArrayLike,
|
810
|
+
imag0: ArrayLike,
|
811
|
+
real1: ArrayLike,
|
812
|
+
imag1: ArrayLike,
|
813
|
+
/,
|
814
|
+
**kwargs: Any,
|
815
|
+
) -> tuple[NDArray[Any], NDArray[Any], NDArray[Any], NDArray[Any]]:
|
816
|
+
"""Return intersection of line through phasors with universal semicircle.
|
817
|
+
|
818
|
+
Return the phasor coordinates of two intersections of the universal
|
819
|
+
semicircle with the line between two phasor coordinates.
|
820
|
+
Return NaN if the line does not intersect the semicircle.
|
821
|
+
|
822
|
+
Parameters
|
823
|
+
----------
|
824
|
+
real0 : array_like
|
825
|
+
Real component of first set of phasor coordinates.
|
826
|
+
imag0 : array_like
|
827
|
+
Imaginary component of first set of phasor coordinates.
|
828
|
+
real1 : array_like
|
829
|
+
Real component of second set of phasor coordinates.
|
830
|
+
imag1 : array_like
|
831
|
+
Imaginary component of second set of phasor coordinates.
|
832
|
+
**kwargs
|
833
|
+
Optional `arguments passed to numpy universal functions
|
834
|
+
<https://numpy.org/doc/stable/reference/ufuncs.html#ufuncs-kwargs>`_.
|
835
|
+
|
836
|
+
Returns
|
837
|
+
-------
|
838
|
+
real0 : ndarray
|
839
|
+
Real component of first intersect of phasors with semicircle.
|
840
|
+
imag0 : ndarray
|
841
|
+
Imaginary component of first intersect of phasors with semicircle.
|
842
|
+
real1 : ndarray
|
843
|
+
Real component of second intersect of phasors with semicircle.
|
844
|
+
imag1 : ndarray
|
845
|
+
Imaginary component of second intersect of phasors with semicircle.
|
846
|
+
|
847
|
+
Examples
|
848
|
+
--------
|
849
|
+
Calculate two intersects of a line through two phasor coordinates
|
850
|
+
with the universal semicircle:
|
851
|
+
|
852
|
+
>>> phasor_semicircle_intersect(0.2, 0.25, 0.6, 0.25) # doctest: +NUMBER
|
853
|
+
(0.066, 0.25, 0.933, 0.25)
|
854
|
+
|
855
|
+
The line between two phasor coordinates may not intersect the semicircle
|
856
|
+
at two points:
|
857
|
+
|
858
|
+
>>> phasor_semicircle_intersect(0.2, 0.0, 0.6, 0.25) # doctest: +NUMBER
|
859
|
+
(nan, nan, 0.817, 0.386)
|
860
|
+
|
861
|
+
"""
|
862
|
+
return _intersect_semicircle_line( # type: ignore[no-any-return]
|
863
|
+
real0, imag0, real1, imag1, **kwargs
|
864
|
+
)
|
865
|
+
|
866
|
+
|
794
867
|
def phasor_to_complex(
|
795
868
|
real: ArrayLike,
|
796
869
|
imag: ArrayLike,
|
@@ -1050,9 +1123,7 @@ def phasor_normalize(
|
|
1050
1123
|
else:
|
1051
1124
|
real = numpy.array(real_unnormalized, dtype, copy=True)
|
1052
1125
|
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
|
-
)
|
1126
|
+
mean = numpy.array(mean_unnormalized, real.dtype, copy=True)
|
1056
1127
|
|
1057
1128
|
with numpy.errstate(divide='ignore', invalid='ignore'):
|
1058
1129
|
numpy.divide(real, mean, out=real)
|
@@ -1158,7 +1229,8 @@ def phasor_calibrate(
|
|
1158
1229
|
ValueError
|
1159
1230
|
The array shapes of `real` and `imag`, or `reference_real` and
|
1160
1231
|
`reference_imag` do not match.
|
1161
|
-
Number of harmonics does not match the first axis
|
1232
|
+
Number of harmonics or frequencies does not match the first axis
|
1233
|
+
of `real` and `imag`.
|
1162
1234
|
|
1163
1235
|
See Also
|
1164
1236
|
--------
|
@@ -1257,13 +1329,24 @@ def phasor_calibrate(
|
|
1257
1329
|
),
|
1258
1330
|
)
|
1259
1331
|
|
1332
|
+
frequency = numpy.asarray(frequency)
|
1333
|
+
frequency = frequency * harmonic
|
1334
|
+
|
1260
1335
|
if has_harmonic_axis:
|
1261
1336
|
if real.ndim == 0:
|
1262
|
-
raise ValueError(
|
1263
|
-
|
1264
|
-
|
1265
|
-
if
|
1266
|
-
raise ValueError(
|
1337
|
+
raise ValueError(
|
1338
|
+
f'{real.shape=} != {len(frequency)} frequencies or harmonics'
|
1339
|
+
)
|
1340
|
+
if real.shape[0] != len(frequency):
|
1341
|
+
raise ValueError(
|
1342
|
+
f'{real.shape[0]=} != {len(frequency)} '
|
1343
|
+
'frequencies or harmonics'
|
1344
|
+
)
|
1345
|
+
if reference_real.shape[0] != len(frequency):
|
1346
|
+
raise ValueError(
|
1347
|
+
f'{reference_real.shape[0]=} != {len(frequency)} '
|
1348
|
+
'frequencies or harmonics'
|
1349
|
+
)
|
1267
1350
|
if reference_mean.shape != reference_real.shape[1:]:
|
1268
1351
|
raise ValueError(
|
1269
1352
|
f'{reference_mean.shape=} != {reference_real.shape[1:]=}'
|
@@ -1275,9 +1358,6 @@ def phasor_calibrate(
|
|
1275
1358
|
f'{reference_mean.shape=} does not have harmonic axis'
|
1276
1359
|
)
|
1277
1360
|
|
1278
|
-
frequency = numpy.asarray(frequency)
|
1279
|
-
frequency = frequency * harmonic
|
1280
|
-
|
1281
1361
|
_, measured_re, measured_im = phasor_center(
|
1282
1362
|
reference_mean,
|
1283
1363
|
reference_real,
|
@@ -1295,6 +1375,24 @@ def phasor_calibrate(
|
|
1295
1375
|
unit_conversion=unit_conversion,
|
1296
1376
|
)
|
1297
1377
|
|
1378
|
+
skip_axis, axis = parse_skip_axis(
|
1379
|
+
skip_axis, real.ndim - int(has_harmonic_axis), has_harmonic_axis
|
1380
|
+
)
|
1381
|
+
|
1382
|
+
if has_harmonic_axis and any(skip_axis):
|
1383
|
+
known_re = numpy.expand_dims(
|
1384
|
+
known_re, tuple(range(1, measured_re.ndim))
|
1385
|
+
)
|
1386
|
+
known_re = numpy.broadcast_to(
|
1387
|
+
known_re, (len(frequency), *measured_re.shape[1:])
|
1388
|
+
)
|
1389
|
+
known_im = numpy.expand_dims(
|
1390
|
+
known_im, tuple(range(1, measured_im.ndim))
|
1391
|
+
)
|
1392
|
+
known_im = numpy.broadcast_to(
|
1393
|
+
known_im, (len(frequency), *measured_im.shape[1:])
|
1394
|
+
)
|
1395
|
+
|
1298
1396
|
phi_zero, mod_zero = polar_from_reference_phasor(
|
1299
1397
|
measured_re, measured_im, known_re, known_im
|
1300
1398
|
)
|
@@ -1303,9 +1401,6 @@ def phasor_calibrate(
|
|
1303
1401
|
if reverse:
|
1304
1402
|
numpy.negative(phi_zero, out=phi_zero)
|
1305
1403
|
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
1404
|
if axis is not None:
|
1310
1405
|
phi_zero = numpy.expand_dims(phi_zero, axis=axis)
|
1311
1406
|
mod_zero = numpy.expand_dims(mod_zero, axis=axis)
|
@@ -1552,6 +1647,7 @@ def phasor_to_polar(
|
|
1552
1647
|
See Also
|
1553
1648
|
--------
|
1554
1649
|
phasorpy.phasor.phasor_from_polar
|
1650
|
+
:ref:`sphx_glr_tutorials_phasorpy_lifetime_geometry.py`
|
1555
1651
|
|
1556
1652
|
Examples
|
1557
1653
|
--------
|
@@ -1659,6 +1755,7 @@ def phasor_to_apparent_lifetime(
|
|
1659
1755
|
See Also
|
1660
1756
|
--------
|
1661
1757
|
phasorpy.phasor.phasor_from_apparent_lifetime
|
1758
|
+
:ref:`sphx_glr_tutorials_phasorpy_lifetime_geometry.py`
|
1662
1759
|
|
1663
1760
|
Notes
|
1664
1761
|
-----
|
@@ -1794,6 +1891,86 @@ def phasor_from_apparent_lifetime(
|
|
1794
1891
|
)
|
1795
1892
|
|
1796
1893
|
|
1894
|
+
def phasor_to_normal_lifetime(
|
1895
|
+
real: ArrayLike,
|
1896
|
+
imag: ArrayLike,
|
1897
|
+
/,
|
1898
|
+
frequency: ArrayLike,
|
1899
|
+
*,
|
1900
|
+
unit_conversion: float = 1e-3,
|
1901
|
+
**kwargs: Any,
|
1902
|
+
) -> NDArray[Any]:
|
1903
|
+
r"""Return normal lifetimes from phasor coordinates.
|
1904
|
+
|
1905
|
+
The normal lifetime of phasor coordinates represents the single lifetime
|
1906
|
+
equivalent corresponding to the perpendicular projection of the coordinates
|
1907
|
+
onto the universal semicircle, as defined in [3]_.
|
1908
|
+
|
1909
|
+
Parameters
|
1910
|
+
----------
|
1911
|
+
real : array_like
|
1912
|
+
Real component of phasor coordinates.
|
1913
|
+
imag : array_like
|
1914
|
+
Imaginary component of phasor coordinates.
|
1915
|
+
frequency : array_like
|
1916
|
+
Laser pulse or modulation frequency in MHz.
|
1917
|
+
unit_conversion : float, optional
|
1918
|
+
Product of `frequency` and returned `lifetime` units' prefix factors.
|
1919
|
+
The default is 1e-3 for MHz and ns, or Hz and ms.
|
1920
|
+
Use 1.0 for Hz and s.
|
1921
|
+
**kwargs
|
1922
|
+
Optional `arguments passed to numpy universal functions
|
1923
|
+
<https://numpy.org/doc/stable/reference/ufuncs.html#ufuncs-kwargs>`_.
|
1924
|
+
|
1925
|
+
Returns
|
1926
|
+
-------
|
1927
|
+
normal_lifetime : ndarray
|
1928
|
+
Normal lifetime from of phasor coordinates.
|
1929
|
+
|
1930
|
+
See Also
|
1931
|
+
--------
|
1932
|
+
:ref:`sphx_glr_tutorials_phasorpy_lifetime_geometry.py`
|
1933
|
+
|
1934
|
+
Notes
|
1935
|
+
-----
|
1936
|
+
The phasor coordinates `real` (:math:`G`) and `imag` (:math:`S`)
|
1937
|
+
are converted to normal lifetimes `normal_lifetime` (:math:`\tau_{N}`)
|
1938
|
+
at frequency :math:`f` according to:
|
1939
|
+
|
1940
|
+
.. math::
|
1941
|
+
|
1942
|
+
\omega &= 2 \pi f
|
1943
|
+
|
1944
|
+
G_{N} &= 0.5 \cdot (1 + \cos{\arctan{\frac{S}{G - 0.5}}})
|
1945
|
+
|
1946
|
+
\tau_{N} &= \sqrt{\frac{1 - G_{N}}{\omega^{2} \cdot G_{N}}}
|
1947
|
+
|
1948
|
+
References
|
1949
|
+
----------
|
1950
|
+
|
1951
|
+
.. [3] Silberberg M, and Grecco H. `pawFLIM: reducing bias and
|
1952
|
+
uncertainty to enable lower photon count in FLIM experiments
|
1953
|
+
<https://doi.org/10.1088/2050-6120/aa72ab>`_.
|
1954
|
+
*Methods Appl Fluoresc*, 5(2): 024016 (2017)
|
1955
|
+
|
1956
|
+
Examples
|
1957
|
+
--------
|
1958
|
+
The normal lifetimes of phasor coordinates with a real component of 0.5
|
1959
|
+
are independent of the imaginary component:
|
1960
|
+
|
1961
|
+
>>> phasor_to_normal_lifetime(
|
1962
|
+
... 0.5, [0.5, 0.45], frequency=80
|
1963
|
+
... ) # doctest: +NUMBER
|
1964
|
+
array([1.989, 1.989])
|
1965
|
+
|
1966
|
+
"""
|
1967
|
+
omega = numpy.array(frequency, dtype=numpy.float64) # makes copy
|
1968
|
+
omega *= math.pi * 2.0 * unit_conversion
|
1969
|
+
return _phasor_to_normal_lifetime( # type: ignore[no-any-return]
|
1970
|
+
real, imag, omega, **kwargs
|
1971
|
+
)
|
1972
|
+
|
1973
|
+
|
1797
1974
|
def lifetime_to_frequency(
|
1798
1975
|
lifetime: ArrayLike,
|
1799
1976
|
*,
|
@@ -2113,6 +2290,11 @@ def phasor_from_lifetime(
|
|
2113
2290
|
ValueError
|
2114
2291
|
Input arrays exceed their allowed dimensionality or do not match.
|
2115
2292
|
|
2293
|
+
See Also
|
2294
|
+
--------
|
2295
|
+
:ref:`sphx_glr_tutorials_api_phasorpy_phasor_from_lifetime.py`
|
2296
|
+
:ref:`sphx_glr_tutorials_phasorpy_lifetime_geometry.py`
|
2297
|
+
|
2116
2298
|
Notes
|
2117
2299
|
-----
|
2118
2300
|
The phasor coordinates :math:`G` (`real`) and :math:`S` (`imag`) for
|
@@ -2433,7 +2615,7 @@ def phasor_from_fret_donor(
|
|
2433
2615
|
donor_lifetime: ArrayLike,
|
2434
2616
|
*,
|
2435
2617
|
fret_efficiency: ArrayLike = 0.0,
|
2436
|
-
|
2618
|
+
donor_fretting: ArrayLike = 1.0,
|
2437
2619
|
donor_background: ArrayLike = 0.0,
|
2438
2620
|
background_real: ArrayLike = 0.0,
|
2439
2621
|
background_imag: ArrayLike = 0.0,
|
@@ -2459,9 +2641,9 @@ def phasor_from_fret_donor(
|
|
2459
2641
|
donor_lifetime : array_like
|
2460
2642
|
Lifetime of donor without FRET in ns.
|
2461
2643
|
fret_efficiency : array_like, optional, default 0
|
2462
|
-
FRET efficiency in range [0
|
2463
|
-
|
2464
|
-
Fraction of donors participating in FRET. Range [0
|
2644
|
+
FRET efficiency in range [0, 1].
|
2645
|
+
donor_fretting : array_like, optional, default 1
|
2646
|
+
Fraction of donors participating in FRET. Range [0, 1].
|
2465
2647
|
donor_background : array_like, optional, default 0
|
2466
2648
|
Weight of background fluorescence in donor channel
|
2467
2649
|
relative to fluorescence of donor without FRET.
|
@@ -2492,6 +2674,7 @@ def phasor_from_fret_donor(
|
|
2492
2674
|
--------
|
2493
2675
|
phasorpy.phasor.phasor_from_fret_acceptor
|
2494
2676
|
:ref:`sphx_glr_tutorials_api_phasorpy_fret.py`
|
2677
|
+
:ref:`sphx_glr_tutorials_applications_phasorpy_fret_efficiency.py`
|
2495
2678
|
|
2496
2679
|
Examples
|
2497
2680
|
--------
|
@@ -2502,7 +2685,7 @@ def phasor_from_fret_donor(
|
|
2502
2685
|
... frequency=80,
|
2503
2686
|
... donor_lifetime=4.2,
|
2504
2687
|
... fret_efficiency=[0.0, 0.3, 1.0],
|
2505
|
-
...
|
2688
|
+
... donor_fretting=0.9,
|
2506
2689
|
... donor_background=0.1,
|
2507
2690
|
... background_real=0.11,
|
2508
2691
|
... background_imag=0.12,
|
@@ -2516,7 +2699,7 @@ def phasor_from_fret_donor(
|
|
2516
2699
|
omega,
|
2517
2700
|
donor_lifetime,
|
2518
2701
|
fret_efficiency,
|
2519
|
-
|
2702
|
+
donor_fretting,
|
2520
2703
|
donor_background,
|
2521
2704
|
background_real,
|
2522
2705
|
background_imag,
|
@@ -2530,7 +2713,7 @@ def phasor_from_fret_acceptor(
|
|
2530
2713
|
acceptor_lifetime: ArrayLike,
|
2531
2714
|
*,
|
2532
2715
|
fret_efficiency: ArrayLike = 0.0,
|
2533
|
-
|
2716
|
+
donor_fretting: ArrayLike = 1.0,
|
2534
2717
|
donor_bleedthrough: ArrayLike = 0.0,
|
2535
2718
|
acceptor_bleedthrough: ArrayLike = 0.0,
|
2536
2719
|
acceptor_background: ArrayLike = 0.0,
|
@@ -2563,9 +2746,9 @@ def phasor_from_fret_acceptor(
|
|
2563
2746
|
acceptor_lifetime : array_like
|
2564
2747
|
Lifetime of acceptor in ns.
|
2565
2748
|
fret_efficiency : array_like, optional, default 0
|
2566
|
-
FRET efficiency in range [0
|
2567
|
-
|
2568
|
-
Fraction of donors participating in FRET. Range [0
|
2749
|
+
FRET efficiency in range [0, 1].
|
2750
|
+
donor_fretting : array_like, optional, default 1
|
2751
|
+
Fraction of donors participating in FRET. Range [0, 1].
|
2569
2752
|
donor_bleedthrough : array_like, optional, default 0
|
2570
2753
|
Weight of donor fluorescence in acceptor channel
|
2571
2754
|
relative to fluorescence of fully sensitized acceptor.
|
@@ -2618,7 +2801,7 @@ def phasor_from_fret_acceptor(
|
|
2618
2801
|
... donor_lifetime=4.2,
|
2619
2802
|
... acceptor_lifetime=3.0,
|
2620
2803
|
... fret_efficiency=[0.0, 0.3, 1.0],
|
2621
|
-
...
|
2804
|
+
... donor_fretting=0.9,
|
2622
2805
|
... donor_bleedthrough=0.1,
|
2623
2806
|
... acceptor_bleedthrough=0.1,
|
2624
2807
|
... acceptor_background=0.1,
|
@@ -2635,7 +2818,7 @@ def phasor_from_fret_acceptor(
|
|
2635
2818
|
donor_lifetime,
|
2636
2819
|
acceptor_lifetime,
|
2637
2820
|
fret_efficiency,
|
2638
|
-
|
2821
|
+
donor_fretting,
|
2639
2822
|
donor_bleedthrough,
|
2640
2823
|
acceptor_bleedthrough,
|
2641
2824
|
acceptor_background,
|
@@ -2720,7 +2903,7 @@ def phasor_to_principal_plane(
|
|
2720
2903
|
References
|
2721
2904
|
----------
|
2722
2905
|
|
2723
|
-
.. [1] Franssen WMJ, Vergeldt FJ, Bader AN, van Amerongen H,
|
2906
|
+
.. [1] Franssen WMJ, Vergeldt FJ, Bader AN, van Amerongen H, and Terenzi C.
|
2724
2907
|
`Full-harmonics phasor analysis: unravelling multiexponential trends
|
2725
2908
|
in magnetic resonance imaging data
|
2726
2909
|
<https://doi.org/10.1021/acs.jpclett.0c02319>`_.
|
@@ -2932,7 +3115,7 @@ def phasor_filter_median(
|
|
2932
3115
|
raise ValueError(f'{real.shape=} != {imag.shape=}')
|
2933
3116
|
|
2934
3117
|
prepend_axis = mean.ndim + 1 == real.ndim
|
2935
|
-
_, axes =
|
3118
|
+
_, axes = parse_skip_axis(skip_axis, mean.ndim, prepend_axis)
|
2936
3119
|
|
2937
3120
|
# in case mean is also filtered
|
2938
3121
|
# if prepend_axis:
|
@@ -3006,6 +3189,209 @@ def phasor_filter_median(
|
|
3006
3189
|
return mean, real, imag
|
3007
3190
|
|
3008
3191
|
|
3192
|
+
def phasor_filter_pawflim(
|
3193
|
+
mean: ArrayLike,
|
3194
|
+
real: ArrayLike,
|
3195
|
+
imag: ArrayLike,
|
3196
|
+
/,
|
3197
|
+
*,
|
3198
|
+
sigma: float = 2.0,
|
3199
|
+
levels: int = 1,
|
3200
|
+
harmonic: Sequence[int] | None = None,
|
3201
|
+
skip_axis: int | Sequence[int] | None = None,
|
3202
|
+
) -> tuple[NDArray[Any], NDArray[Any], NDArray[Any]]:
|
3203
|
+
"""Return pawFLIM wavelet-filtered phasor coordinates.
|
3204
|
+
|
3205
|
+
This function must only be used with calibrated, unprocessed phasor
|
3206
|
+
coordinates obtained from FLIM data. The coordinates must not be filtered,
|
3207
|
+
thresholded, or otherwise pre-processed.
|
3208
|
+
|
3209
|
+
The pawFLIM wavelet filter is described in [2]_.
|
3210
|
+
|
3211
|
+
Parameters
|
3212
|
+
----------
|
3213
|
+
mean : array_like
|
3214
|
+
Intensity of phasor coordinates.
|
3215
|
+
real : array_like
|
3216
|
+
Real component of phasor coordinates to be filtered.
|
3217
|
+
Must have at least two harmonics in the first axis.
|
3218
|
+
imag : array_like
|
3219
|
+
Imaginary component of phasor coordinates to be filtered.
|
3220
|
+
Must have at least two harmonics in the first axis.
|
3221
|
+
sigma : float, optional
|
3222
|
+
Significance level to test difference between two phasors.
|
3223
|
+
Given in terms of the equivalent 1D standard deviations.
|
3224
|
+
sigma=2 corresponds to ~95% (or 5%) significance.
|
3225
|
+
levels : int, optional
|
3226
|
+
Number of levels for wavelet decomposition.
|
3227
|
+
Controls the maximum averaging area, which has a length of
|
3228
|
+
:math:`2^level`.
|
3229
|
+
harmonic : sequence of int or None, optional
|
3230
|
+
Harmonics included in first axis of `real` and `imag`.
|
3231
|
+
If None (default), the first axis of `real` and `imag` contains lower
|
3232
|
+
harmonics starting at and increasing by one.
|
3233
|
+
All harmonics must have a corresponding half or double harmonic.
|
3234
|
+
skip_axis : int or sequence of int, optional
|
3235
|
+
Axes in `mean` to exclude from filter.
|
3236
|
+
By default, all axes except harmonics are included.
|
3237
|
+
|
3238
|
+
Returns
|
3239
|
+
-------
|
3240
|
+
mean : ndarray
|
3241
|
+
Unchanged intensity of phasor coordinates.
|
3242
|
+
real : ndarray
|
3243
|
+
Filtered real component of phasor coordinates.
|
3244
|
+
imag : ndarray
|
3245
|
+
Filtered imaginary component of phasor coordinates.
|
3246
|
+
|
3247
|
+
Raises
|
3248
|
+
------
|
3249
|
+
ValueError
|
3250
|
+
If `level` is less than 0.
|
3251
|
+
The array shapes of `mean`, `real`, and `imag` do not match.
|
3252
|
+
If `real` and `imag` have no harmonic axis.
|
3253
|
+
Number of harmonics in `harmonic` is less than 2 or does not match
|
3254
|
+
the first axis of `real` and `imag`.
|
3255
|
+
Not all harmonics in `harmonic` have a corresponding half
|
3256
|
+
or double harmonic.
|
3257
|
+
|
3258
|
+
References
|
3259
|
+
----------
|
3260
|
+
|
3261
|
+
.. [2] Silberberg M, and Grecco H. `pawFLIM: reducing bias and
|
3262
|
+
uncertainty to enable lower photon count in FLIM experiments
|
3263
|
+
<https://doi.org/10.1088/2050-6120/aa72ab>`_.
|
3264
|
+
*Methods Appl Fluoresc*, 5(2): 024016 (2017)
|
3265
|
+
|
3266
|
+
Examples
|
3267
|
+
--------
|
3268
|
+
Apply a pawFLIM wavelet filter with four significance levels (sigma)
|
3269
|
+
and three decomposition levels:
|
3270
|
+
|
3271
|
+
>>> mean, real, imag = phasor_filter_pawflim(
|
3272
|
+
... [[1, 1], [1, 1]],
|
3273
|
+
... [[[0.5, 0.8], [0.5, 0.8]], [[0.2, 0.4], [0.2, 0.4]]],
|
3274
|
+
... [[[0.5, 0.4], [0.5, 0.4]], [[0.4, 0.5], [0.4, 0.5]]],
|
3275
|
+
... sigma=4,
|
3276
|
+
... levels=3,
|
3277
|
+
... harmonic=[1, 2],
|
3278
|
+
... )
|
3279
|
+
>>> mean
|
3280
|
+
array([[1, 1],
|
3281
|
+
[1, 1]])
|
3282
|
+
>>> real
|
3283
|
+
array([[[0.65, 0.65],
|
3284
|
+
[0.65, 0.65]],
|
3285
|
+
[[0.3, 0.3],
|
3286
|
+
[0.3, 0.3]]])
|
3287
|
+
>>> imag
|
3288
|
+
array([[[0.45, 0.45],
|
3289
|
+
[0.45, 0.45]],
|
3290
|
+
[[0.45, 0.45],
|
3291
|
+
[0.45, 0.45]]])
|
3292
|
+
|
3293
|
+
"""
|
3294
|
+
from pawflim import pawflim # type: ignore[import-untyped]
|
3295
|
+
|
3296
|
+
mean = numpy.asarray(mean)
|
3297
|
+
real = numpy.asarray(real)
|
3298
|
+
imag = numpy.asarray(imag)
|
3299
|
+
|
3300
|
+
if levels < 0:
|
3301
|
+
raise ValueError(f'{levels=} < 0')
|
3302
|
+
if levels == 0:
|
3303
|
+
return mean, real, imag
|
3304
|
+
|
3305
|
+
if mean.shape != real.shape[-mean.ndim if mean.ndim else 1 :]:
|
3306
|
+
raise ValueError(f'{mean.shape=} != {real.shape=}')
|
3307
|
+
if real.shape != imag.shape:
|
3308
|
+
raise ValueError(f'{real.shape=} != {imag.shape=}')
|
3309
|
+
|
3310
|
+
has_harmonic_axis = mean.ndim + 1 == real.ndim
|
3311
|
+
if not has_harmonic_axis:
|
3312
|
+
raise ValueError('no harmonic axis')
|
3313
|
+
if harmonic is None:
|
3314
|
+
harmonics, _ = parse_harmonic('all', real.shape[0])
|
3315
|
+
else:
|
3316
|
+
harmonics, _ = parse_harmonic(harmonic, None)
|
3317
|
+
if len(harmonics) < 2:
|
3318
|
+
raise ValueError(
|
3319
|
+
'at least two harmonics required, ' f'got {len(harmonics)}'
|
3320
|
+
)
|
3321
|
+
if len(harmonics) != real.shape[0]:
|
3322
|
+
raise ValueError(
|
3323
|
+
'number of harmonics does not match first axis of real and imag'
|
3324
|
+
)
|
3325
|
+
|
3326
|
+
mean = numpy.asarray(numpy.nan_to_num(mean), dtype=float)
|
3327
|
+
real = numpy.asarray(numpy.nan_to_num(real * mean), dtype=float)
|
3328
|
+
imag = numpy.asarray(numpy.nan_to_num(imag * mean), dtype=float)
|
3329
|
+
|
3330
|
+
mean_expanded = numpy.broadcast_to(mean, real.shape).copy()
|
3331
|
+
original_mean_expanded = mean_expanded.copy()
|
3332
|
+
real_filtered = real.copy()
|
3333
|
+
imag_filtered = imag.copy()
|
3334
|
+
|
3335
|
+
_, axes = parse_skip_axis(skip_axis, mean.ndim, True)
|
3336
|
+
|
3337
|
+
for index in numpy.ndindex(
|
3338
|
+
*(
|
3339
|
+
real.shape[ax]
|
3340
|
+
for ax in range(real.ndim)
|
3341
|
+
if ax not in axes and ax != 0
|
3342
|
+
)
|
3343
|
+
):
|
3344
|
+
index_list: list[int | slice] = list(index)
|
3345
|
+
for ax in axes:
|
3346
|
+
index_list = index_list[:ax] + [slice(None)] + index_list[ax:]
|
3347
|
+
full_index = tuple(index_list)
|
3348
|
+
|
3349
|
+
processed_harmonics = set()
|
3350
|
+
|
3351
|
+
for h in harmonics:
|
3352
|
+
if h in processed_harmonics and (
|
3353
|
+
h * 4 in harmonics or h * 2 not in harmonics
|
3354
|
+
):
|
3355
|
+
continue
|
3356
|
+
if h * 2 not in harmonics:
|
3357
|
+
raise ValueError(
|
3358
|
+
f'harmonic {h} does not have a corresponding half '
|
3359
|
+
f'or double harmonic in {harmonics}'
|
3360
|
+
)
|
3361
|
+
n = harmonics.index(h)
|
3362
|
+
n2 = harmonics.index(h * 2)
|
3363
|
+
|
3364
|
+
complex_phasor = numpy.empty(
|
3365
|
+
(3, *original_mean_expanded[n][full_index].shape),
|
3366
|
+
dtype=complex,
|
3367
|
+
)
|
3368
|
+
complex_phasor[0] = original_mean_expanded[n][full_index]
|
3369
|
+
complex_phasor[1] = real[n][full_index] + 1j * imag[n][full_index]
|
3370
|
+
complex_phasor[2] = (
|
3371
|
+
real[n2][full_index] + 1j * imag[n2][full_index]
|
3372
|
+
)
|
3373
|
+
|
3374
|
+
complex_phasor = pawflim(
|
3375
|
+
complex_phasor, n_sigmas=sigma, levels=levels
|
3376
|
+
)
|
3377
|
+
|
3378
|
+
for i, idx in enumerate([n, n2]):
|
3379
|
+
if harmonics[idx] in processed_harmonics:
|
3380
|
+
continue
|
3381
|
+
mean_expanded[idx][full_index] = complex_phasor[0].real
|
3382
|
+
real_filtered[idx][full_index] = complex_phasor[i + 1].real
|
3383
|
+
imag_filtered[idx][full_index] = complex_phasor[i + 1].imag
|
3384
|
+
|
3385
|
+
processed_harmonics.add(h)
|
3386
|
+
processed_harmonics.add(h * 2)
|
3387
|
+
|
3388
|
+
with numpy.errstate(divide='ignore', invalid='ignore'):
|
3389
|
+
real = numpy.asarray(numpy.divide(real_filtered, mean_expanded))
|
3390
|
+
imag = numpy.asarray(numpy.divide(imag_filtered, mean_expanded))
|
3391
|
+
|
3392
|
+
return mean, real, imag
|
3393
|
+
|
3394
|
+
|
3009
3395
|
def phasor_threshold(
|
3010
3396
|
mean: ArrayLike,
|
3011
3397
|
real: ArrayLike,
|
@@ -3224,6 +3610,154 @@ def phasor_threshold(
|
|
3224
3610
|
return mean, real, imag
|
3225
3611
|
|
3226
3612
|
|
3613
|
+
def phasor_nearest_neighbor(
|
3614
|
+
real: ArrayLike,
|
3615
|
+
imag: ArrayLike,
|
3616
|
+
neighbor_real: ArrayLike,
|
3617
|
+
neighbor_imag: ArrayLike,
|
3618
|
+
/,
|
3619
|
+
*,
|
3620
|
+
values: ArrayLike | None = None,
|
3621
|
+
dtype: DTypeLike | None = None,
|
3622
|
+
distance_max: float | None = None,
|
3623
|
+
num_threads: int | None = None,
|
3624
|
+
) -> NDArray[Any]:
|
3625
|
+
"""Return indices or values of nearest neighbor from other coordinates.
|
3626
|
+
|
3627
|
+
For each phasor coordinate, find the nearest neighbor in another set of
|
3628
|
+
phasor coordinates and return its flat index. If more than one neighbor
|
3629
|
+
has the same distance, return the smallest index.
|
3630
|
+
|
3631
|
+
For phasor coordinates that are NaN, or have a distance to the nearest
|
3632
|
+
neighbor that is larger than `distance_max`, return an index of -1.
|
3633
|
+
|
3634
|
+
If `values` are provided, return the values corresponding to the nearest
|
3635
|
+
neighbor coordinates instead of indices. Return NaN values for indices
|
3636
|
+
that are -1.
|
3637
|
+
|
3638
|
+
This function does not support multi-harmonic, multi-channel, or
|
3639
|
+
multi-frequency phasor coordinates.
|
3640
|
+
|
3641
|
+
Parameters
|
3642
|
+
----------
|
3643
|
+
real : array_like
|
3644
|
+
Real component of phasor coordinates.
|
3645
|
+
imag : array_like
|
3646
|
+
Imaginary component of phasor coordinates.
|
3647
|
+
neighbor_real : array_like
|
3648
|
+
Real component of neighbor phasor coordinates.
|
3649
|
+
neighbor_imag : array_like
|
3650
|
+
Imaginary component of neighbor phasor coordinates.
|
3651
|
+
values : array_like, optional
|
3652
|
+
Array of values corresponding to neighbor coordinates.
|
3653
|
+
If provided, return the values corresponding to the nearest
|
3654
|
+
neighbor coordinates.
|
3655
|
+
distance_max : float, optional
|
3656
|
+
Maximum Euclidean distance to consider a neighbor valid.
|
3657
|
+
By default, all neighbors are considered.
|
3658
|
+
dtype : dtype_like, optional
|
3659
|
+
Floating point data type used for calculation and output values.
|
3660
|
+
Either `float32` or `float64`. The default is `float64`.
|
3661
|
+
num_threads : int, optional
|
3662
|
+
Number of OpenMP threads to use for parallelization.
|
3663
|
+
By default, multi-threading is disabled.
|
3664
|
+
If zero, up to half of logical CPUs are used.
|
3665
|
+
OpenMP may not be available on all platforms.
|
3666
|
+
|
3667
|
+
Returns
|
3668
|
+
-------
|
3669
|
+
nearest : ndarray
|
3670
|
+
Flat indices (or the corresponding values if provided) of the nearest
|
3671
|
+
neighbor coordinates.
|
3672
|
+
|
3673
|
+
Raises
|
3674
|
+
------
|
3675
|
+
ValueError
|
3676
|
+
If the shapes of `real`, and `imag` do not match.
|
3677
|
+
If the shapes of `neighbor_real` and `neighbor_imag` do not match.
|
3678
|
+
If the shapes of `values` and `neighbor_real` do not match.
|
3679
|
+
If `distance_max` is less than or equal to zero.
|
3680
|
+
|
3681
|
+
See Also
|
3682
|
+
--------
|
3683
|
+
:ref:`sphx_glr_tutorials_applications_phasorpy_fret_efficiency.py`
|
3684
|
+
|
3685
|
+
Notes
|
3686
|
+
-----
|
3687
|
+
This function uses linear search, which is inefficient for large
|
3688
|
+
number of coordinates or neighbors.
|
3689
|
+
``scipy.spatial.KDTree.query()`` would be more efficient in those cases.
|
3690
|
+
However, KDTree is known to return non-deterministic results in case of
|
3691
|
+
multiple neighbors with the same distance.
|
3692
|
+
|
3693
|
+
Examples
|
3694
|
+
--------
|
3695
|
+
>>> phasor_nearest_neighbor(
|
3696
|
+
... [0.1, 0.5, numpy.nan],
|
3697
|
+
... [0.1, 0.5, numpy.nan],
|
3698
|
+
... [0, 0.4],
|
3699
|
+
... [0, 0.4],
|
3700
|
+
... values=[10, 20],
|
3701
|
+
... )
|
3702
|
+
array([10, 20, nan])
|
3703
|
+
|
3704
|
+
"""
|
3705
|
+
dtype = numpy.dtype(dtype)
|
3706
|
+
if dtype.char not in {'f', 'd'}:
|
3707
|
+
raise ValueError(f'{dtype=} is not a floating point type')
|
3708
|
+
|
3709
|
+
real = numpy.ascontiguousarray(real, dtype=dtype)
|
3710
|
+
imag = numpy.ascontiguousarray(imag, dtype=dtype)
|
3711
|
+
neighbor_real = numpy.ascontiguousarray(neighbor_real, dtype=dtype)
|
3712
|
+
neighbor_imag = numpy.ascontiguousarray(neighbor_imag, dtype=dtype)
|
3713
|
+
|
3714
|
+
if real.shape != imag.shape:
|
3715
|
+
raise ValueError(f'{real.shape=} != {imag.shape=}')
|
3716
|
+
if neighbor_real.shape != neighbor_imag.shape:
|
3717
|
+
raise ValueError(f'{neighbor_real.shape=} != {neighbor_imag.shape=}')
|
3718
|
+
|
3719
|
+
shape = real.shape
|
3720
|
+
real = real.ravel()
|
3721
|
+
imag = imag.ravel()
|
3722
|
+
neighbor_real = neighbor_real.ravel()
|
3723
|
+
neighbor_imag = neighbor_imag.ravel()
|
3724
|
+
|
3725
|
+
indices = numpy.empty(
|
3726
|
+
real.shape, numpy.min_scalar_type(-neighbor_real.size)
|
3727
|
+
)
|
3728
|
+
|
3729
|
+
if distance_max is None:
|
3730
|
+
distance_max = numpy.inf
|
3731
|
+
else:
|
3732
|
+
distance_max = float(distance_max)
|
3733
|
+
if distance_max <= 0:
|
3734
|
+
raise ValueError(f'{distance_max=} <= 0')
|
3735
|
+
|
3736
|
+
num_threads = number_threads(num_threads)
|
3737
|
+
|
3738
|
+
_nearest_neighbor_2d(
|
3739
|
+
indices,
|
3740
|
+
real,
|
3741
|
+
imag,
|
3742
|
+
neighbor_real,
|
3743
|
+
neighbor_imag,
|
3744
|
+
distance_max,
|
3745
|
+
num_threads,
|
3746
|
+
)
|
3747
|
+
|
3748
|
+
if values is None:
|
3749
|
+
return numpy.asarray(indices.reshape(shape))
|
3750
|
+
|
3751
|
+
values = numpy.ascontiguousarray(values, dtype=dtype).ravel()
|
3752
|
+
if values.shape != neighbor_real.shape:
|
3753
|
+
raise ValueError(f'{values.shape=} != {neighbor_real.shape=}')
|
3754
|
+
|
3755
|
+
nearest_values = values[indices]
|
3756
|
+
nearest_values[indices == -1] = numpy.nan
|
3757
|
+
|
3758
|
+
return numpy.asarray(nearest_values.reshape(shape))
|
3759
|
+
|
3760
|
+
|
3227
3761
|
def phasor_center(
|
3228
3762
|
mean: ArrayLike,
|
3229
3763
|
real: ArrayLike,
|
@@ -3314,7 +3848,7 @@ def phasor_center(
|
|
3314
3848
|
raise ValueError(f'{mean.shape=} != {real.shape=}')
|
3315
3849
|
|
3316
3850
|
prepend_axis = mean.ndim + 1 == real.ndim
|
3317
|
-
_, axis =
|
3851
|
+
_, axis = parse_skip_axis(skip_axis, mean.ndim, prepend_axis)
|
3318
3852
|
if prepend_axis:
|
3319
3853
|
mean = numpy.expand_dims(mean, axis=0)
|
3320
3854
|
|
@@ -3358,62 +3892,3 @@ def _median(
|
|
3358
3892
|
numpy.nanmedian(real, **kwargs),
|
3359
3893
|
numpy.nanmedian(imag, **kwargs),
|
3360
3894
|
)
|
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
|