phasorpy 0.6__cp312-cp312-win_arm64.whl → 0.8__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/__init__.py +1 -1
- phasorpy/_phasorpy.cp312-win_arm64.pyd +0 -0
- phasorpy/_phasorpy.pyx +320 -10
- phasorpy/_utils.py +114 -33
- phasorpy/cli.py +19 -1
- phasorpy/cluster.py +12 -18
- phasorpy/color.py +11 -7
- phasorpy/{components.py → component.py} +263 -36
- phasorpy/{cursors.py → cursor.py} +31 -33
- phasorpy/datasets.py +118 -8
- phasorpy/experimental.py +4 -168
- phasorpy/filter.py +966 -0
- phasorpy/io/__init__.py +3 -1
- phasorpy/io/_flimlabs.py +26 -16
- phasorpy/io/_leica.py +38 -34
- phasorpy/io/_ometiff.py +10 -9
- phasorpy/io/_other.py +116 -8
- phasorpy/io/_simfcs.py +52 -24
- phasorpy/lifetime.py +2058 -0
- phasorpy/phasor.py +106 -2502
- phasorpy/plot/_functions.py +13 -7
- phasorpy/plot/_lifetime_plots.py +34 -24
- phasorpy/plot/_phasorplot.py +561 -176
- phasorpy/plot/_phasorplot_fret.py +12 -10
- phasorpy/utils.py +22 -10
- {phasorpy-0.6.dist-info → phasorpy-0.8.dist-info}/METADATA +8 -7
- phasorpy-0.8.dist-info/RECORD +36 -0
- phasorpy-0.6.dist-info/RECORD +0 -34
- {phasorpy-0.6.dist-info → phasorpy-0.8.dist-info}/WHEEL +0 -0
- {phasorpy-0.6.dist-info → phasorpy-0.8.dist-info}/entry_points.txt +0 -0
- {phasorpy-0.6.dist-info → phasorpy-0.8.dist-info}/licenses/LICENSE.txt +0 -0
- {phasorpy-0.6.dist-info → phasorpy-0.8.dist-info}/top_level.txt +0 -0
phasorpy/cli.py
CHANGED
@@ -86,6 +86,13 @@ def fret(hide: bool) -> None:
|
|
86
86
|
|
87
87
|
|
88
88
|
@main.command(help='Start interactive lifetime plots.')
|
89
|
+
@click.argument(
|
90
|
+
'number_lifetimes',
|
91
|
+
default=2,
|
92
|
+
type=click.IntRange(1, 5),
|
93
|
+
required=False,
|
94
|
+
# help='Number of preconfigured lifetimes.',
|
95
|
+
)
|
89
96
|
@click.option(
|
90
97
|
'-f',
|
91
98
|
'--frequency',
|
@@ -96,7 +103,7 @@ def fret(hide: bool) -> None:
|
|
96
103
|
@click.option(
|
97
104
|
'-l',
|
98
105
|
'--lifetime',
|
99
|
-
default=(4.0, 1.0),
|
106
|
+
# default=(4.0, 1.0),
|
100
107
|
type=float,
|
101
108
|
multiple=True,
|
102
109
|
required=False,
|
@@ -118,14 +125,25 @@ def fret(hide: bool) -> None:
|
|
118
125
|
help='Do not show interactive plot.',
|
119
126
|
)
|
120
127
|
def lifetime(
|
128
|
+
number_lifetimes: int,
|
121
129
|
frequency: float | None,
|
122
130
|
lifetime: tuple[float, ...],
|
123
131
|
fraction: tuple[float, ...],
|
124
132
|
hide: bool,
|
125
133
|
) -> None:
|
126
134
|
"""Lifetime command group."""
|
135
|
+
from .lifetime import phasor_semicircle, phasor_to_normal_lifetime
|
127
136
|
from .plot import LifetimePlots
|
128
137
|
|
138
|
+
if not lifetime:
|
139
|
+
if number_lifetimes == 2:
|
140
|
+
lifetime = (4.0, 1.0)
|
141
|
+
else:
|
142
|
+
real, imag = phasor_semicircle(number_lifetimes + 2)
|
143
|
+
lifetime = phasor_to_normal_lifetime(
|
144
|
+
real[1:-1], imag[1:-1], frequency if frequency else 80.0
|
145
|
+
) # type: ignore[assignment]
|
146
|
+
|
129
147
|
plot = LifetimePlots(
|
130
148
|
frequency,
|
131
149
|
lifetime,
|
phasorpy/cluster.py
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
"""Cluster phasor coordinates.
|
2
2
|
|
3
|
-
The
|
3
|
+
The ``phasorpy.cluster`` module provides functions to:
|
4
4
|
|
5
|
-
- fit elliptic clusters to phasor coordinates using
|
5
|
+
- fit elliptic clusters to phasor coordinates using a
|
6
6
|
Gaussian Mixture Model (GMM):
|
7
7
|
|
8
8
|
- :py:func:`phasor_cluster_gmm`
|
@@ -21,7 +21,6 @@ if TYPE_CHECKING:
|
|
21
21
|
import math
|
22
22
|
|
23
23
|
import numpy
|
24
|
-
from sklearn.mixture import GaussianMixture
|
25
24
|
|
26
25
|
|
27
26
|
def phasor_cluster_gmm(
|
@@ -52,14 +51,14 @@ def phasor_cluster_gmm(
|
|
52
51
|
Real component of phasor coordinates.
|
53
52
|
imag : array_like
|
54
53
|
Imaginary component of phasor coordinates.
|
55
|
-
sigma: float,
|
54
|
+
sigma : float, optional
|
56
55
|
Scaling factor for radii of major and minor axes.
|
57
|
-
Defaults to 2, which corresponds to the scaling of eigenvalues for
|
58
|
-
95% confidence ellipse.
|
56
|
+
Defaults to 2.0, which corresponds to the scaling of eigenvalues for
|
57
|
+
a 95% confidence ellipse.
|
59
58
|
clusters : int, optional
|
60
59
|
Number of Gaussian distributions to fit to phasor coordinates.
|
61
60
|
Defaults to 1.
|
62
|
-
sort: {'polar', 'phasor', 'area'}, optional
|
61
|
+
sort : {'polar', 'phasor', 'area'}, optional
|
63
62
|
Sorting method for output clusters. Defaults to 'polar'.
|
64
63
|
|
65
64
|
- 'polar': Sort by polar coordinates (phase, then modulation).
|
@@ -67,7 +66,7 @@ def phasor_cluster_gmm(
|
|
67
66
|
- 'area': Sort by inverse area of ellipse (-major * minor).
|
68
67
|
|
69
68
|
**kwargs
|
70
|
-
|
69
|
+
Optional arguments passed to
|
71
70
|
:py:class:`sklearn.mixture.GaussianMixture`.
|
72
71
|
|
73
72
|
Common options include:
|
@@ -89,19 +88,12 @@ def phasor_cluster_gmm(
|
|
89
88
|
angle : tuple of float
|
90
89
|
Rotation angles of major axes in radians, within range [0, pi].
|
91
90
|
|
92
|
-
Raises
|
93
|
-
------
|
94
|
-
ValueError
|
95
|
-
If the array shapes of `real` and `imag` do not match.
|
96
|
-
If `clusters` is not a positive integer.
|
97
|
-
|
98
|
-
|
99
91
|
References
|
100
92
|
----------
|
101
93
|
.. [1] Vallmitjana A, Torrado B, and Gratton E.
|
102
94
|
`Phasor-based image segmentation: machine learning clustering techniques
|
103
95
|
<https://doi.org/10.1364/BOE.422766>`_.
|
104
|
-
*Biomed Opt Express*, 12(6): 3410-3422 (2021)
|
96
|
+
*Biomed Opt Express*, 12(6): 3410-3422 (2021)
|
105
97
|
|
106
98
|
Examples
|
107
99
|
--------
|
@@ -119,11 +111,13 @@ def phasor_cluster_gmm(
|
|
119
111
|
>>> center_real, center_imag, radius_major, radius_minor, angle = (
|
120
112
|
... phasor_cluster_gmm(real, imag, clusters=2)
|
121
113
|
... )
|
122
|
-
>>>
|
114
|
+
>>> center_real # doctest: +SKIP
|
123
115
|
(0.2, 0.4)
|
124
116
|
|
125
117
|
"""
|
126
|
-
|
118
|
+
from sklearn.mixture import GaussianMixture
|
119
|
+
|
120
|
+
coords = numpy.stack([real, imag], axis=-1).reshape(-1, 2)
|
127
121
|
|
128
122
|
valid_data = ~numpy.isnan(coords).any(axis=1)
|
129
123
|
coords = coords[valid_data]
|
phasorpy/color.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
"""Color palettes and manipulation."""
|
1
|
+
"""Color palettes and color manipulation utilities."""
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
@@ -19,20 +19,22 @@ def wavelength2rgb(
|
|
19
19
|
) -> tuple[float, float, float] | NDArray[Any]:
|
20
20
|
"""Return approximate sRGB color components of visible wavelength(s).
|
21
21
|
|
22
|
-
Wavelengths are clipped to range [360, 750] nm, rounded, and used to
|
22
|
+
Wavelengths are clipped to the range [360, 750] nm, rounded, and used to
|
23
23
|
index the :py:attr:`SRGB_SPECTRUM` palette.
|
24
24
|
|
25
25
|
Parameters
|
26
26
|
----------
|
27
27
|
wavelength : array_like
|
28
28
|
Scalar or array of wavelengths in nm.
|
29
|
-
dtype :
|
29
|
+
dtype : dtype_like, optional
|
30
30
|
Data-type of return value. The default is ``float32``.
|
31
31
|
|
32
32
|
Returns
|
33
33
|
-------
|
34
|
-
ndarray or tuple
|
35
|
-
Approximate sRGB color components of visible wavelength.
|
34
|
+
ndarray or tuple of float
|
35
|
+
Approximate sRGB color components of visible wavelength(s).
|
36
|
+
If input is scalar, return tuple of three floats.
|
37
|
+
If input is array, return ndarray with shape (..., 3).
|
36
38
|
Floating-point values are in range [0.0, 1.0].
|
37
39
|
Integer values are scaled to the dtype's maximum value.
|
38
40
|
|
@@ -74,7 +76,7 @@ def float2int(
|
|
74
76
|
----------
|
75
77
|
rgb : array_like
|
76
78
|
Scalar or array of normalized floating-point color components.
|
77
|
-
dtype :
|
79
|
+
dtype : dtype_like, optional
|
78
80
|
Data type of return value. The default is ``uint8``.
|
79
81
|
|
80
82
|
Returns
|
@@ -169,7 +171,7 @@ CATEGORICAL: NDArray[numpy.float32] = numpy.array([
|
|
169
171
|
], dtype=numpy.float32)
|
170
172
|
"""Categorical sRGB color palette inspired by C. Glasbey.
|
171
173
|
|
172
|
-
Contains 64 maximally distinct
|
174
|
+
Contains 64 maximally distinct colors for visualization.
|
173
175
|
|
174
176
|
Generated using the `glasbey <https://glasbey.readthedocs.io>`_ package::
|
175
177
|
|
@@ -575,6 +577,8 @@ SRGB_SPECTRUM: NDArray[numpy.float32] = numpy.array([
|
|
575
577
|
], dtype=numpy.float32)
|
576
578
|
"""sRGB color components for wavelengths of visible light (360-750 nm).
|
577
579
|
|
580
|
+
Array of shape (391, 3) containing normalized sRGB color components
|
581
|
+
for wavelengths from 360 to 750 nm in 1 nm increments.
|
578
582
|
Based on the CIE 1931 2° Standard Observer.
|
579
583
|
|
580
584
|
Generated using the `colour <https://colour.readthedocs.io>`_ package::
|
@@ -1,6 +1,6 @@
|
|
1
|
-
"""
|
1
|
+
"""Analyze components in phasor coordinates.
|
2
2
|
|
3
|
-
The ``phasorpy.
|
3
|
+
The ``phasorpy.component`` module provides functions to:
|
4
4
|
|
5
5
|
- calculate fractions of two known components by projecting onto the
|
6
6
|
line between the components (:py:func:`phasor_component_fraction`)
|
@@ -14,9 +14,15 @@ The ``phasorpy.components`` module provides functions to:
|
|
14
14
|
- calculate fractions of two or three known components by resolving
|
15
15
|
graphically with histogram (:py:func:`phasor_component_graphical`)
|
16
16
|
|
17
|
+
- calculate mean value coordinates of phasor coordinates with respect
|
18
|
+
to three or more components (:py:func:`phasor_component_mvc`)
|
19
|
+
|
17
20
|
- blindly resolve fractions of multiple components by using harmonic
|
18
21
|
information (:py:func:`phasor_component_blind`, not implemented)
|
19
22
|
|
23
|
+
- calculate phasor coordinates from fractional intensities of
|
24
|
+
components (:py:func:`phasor_from_component`)
|
25
|
+
|
20
26
|
"""
|
21
27
|
|
22
28
|
from __future__ import annotations
|
@@ -26,13 +32,15 @@ __all__ = [
|
|
26
32
|
'phasor_component_fit',
|
27
33
|
'phasor_component_fraction',
|
28
34
|
'phasor_component_graphical',
|
35
|
+
'phasor_component_mvc',
|
36
|
+
'phasor_from_component',
|
29
37
|
]
|
30
38
|
|
31
39
|
import numbers
|
32
40
|
from typing import TYPE_CHECKING
|
33
41
|
|
34
42
|
if TYPE_CHECKING:
|
35
|
-
from ._typing import Any, ArrayLike, NDArray
|
43
|
+
from ._typing import Any, ArrayLike, DTypeLike, NDArray
|
36
44
|
|
37
45
|
import numpy
|
38
46
|
|
@@ -41,9 +49,92 @@ from ._phasorpy import (
|
|
41
49
|
_fraction_on_segment,
|
42
50
|
_is_inside_circle,
|
43
51
|
_is_inside_stadium,
|
52
|
+
_mean_value_coordinates,
|
44
53
|
_segment_direction_and_length,
|
45
54
|
)
|
46
|
-
from .
|
55
|
+
from ._utils import sort_coordinates
|
56
|
+
from .filter import phasor_threshold
|
57
|
+
from .utils import number_threads
|
58
|
+
|
59
|
+
|
60
|
+
def phasor_from_component(
|
61
|
+
component_real: ArrayLike,
|
62
|
+
component_imag: ArrayLike,
|
63
|
+
fraction: ArrayLike,
|
64
|
+
/,
|
65
|
+
axis: int = 0,
|
66
|
+
dtype: DTypeLike | None = None,
|
67
|
+
) -> tuple[NDArray[Any], NDArray[Any]]:
|
68
|
+
"""Return phasor coordinates from fractional intensities of components.
|
69
|
+
|
70
|
+
Return the dot products of the fractional intensities of components
|
71
|
+
with the real and imaginary phasor coordinates of the components.
|
72
|
+
|
73
|
+
Multi-dimensional component arrays are currently not supported.
|
74
|
+
|
75
|
+
Parameters
|
76
|
+
----------
|
77
|
+
component_real : array_like, shape (n,)
|
78
|
+
Real coordinates of components.
|
79
|
+
At least two components are required.
|
80
|
+
component_imag : array_like, shape (n,)
|
81
|
+
Imaginary coordinates of components.
|
82
|
+
fraction : array_like
|
83
|
+
Fractional intensities of components.
|
84
|
+
Fractions are normalized to sum to one along `axis`.
|
85
|
+
axis : int, optional, default: 0
|
86
|
+
Axis of components in `fraction`.
|
87
|
+
dtype : dtype_like, optional
|
88
|
+
Floating point data type used for calculation and output values.
|
89
|
+
Either `float32` or `float64`. The default is `float64`.
|
90
|
+
|
91
|
+
Returns
|
92
|
+
-------
|
93
|
+
real : ndarray
|
94
|
+
Real component of phasor coordinates.
|
95
|
+
imag : ndarray
|
96
|
+
Imaginary component of phasor coordinates.
|
97
|
+
|
98
|
+
See Also
|
99
|
+
--------
|
100
|
+
phasorpy.phasor.phasor_combine
|
101
|
+
|
102
|
+
Examples
|
103
|
+
--------
|
104
|
+
Calculate phasor coordinates from two components and their fractional
|
105
|
+
intensities:
|
106
|
+
|
107
|
+
>>> phasor_from_component(
|
108
|
+
... [0.6, 0.4], [0.3, 0.2], [[1.0, 0.2, 0.9], [0.0, 0.8, 0.1]]
|
109
|
+
... )
|
110
|
+
(array([0.6, 0.44, 0.58]), array([0.3, 0.22, 0.29]))
|
111
|
+
|
112
|
+
"""
|
113
|
+
dtype = numpy.dtype(dtype)
|
114
|
+
if dtype.char not in {'f', 'd'}:
|
115
|
+
raise ValueError(f'{dtype=} is not a floating point type')
|
116
|
+
|
117
|
+
fraction = numpy.asarray(fraction, dtype=dtype, copy=True)
|
118
|
+
if fraction.ndim < 1:
|
119
|
+
raise ValueError(f'{fraction.ndim=} < 1')
|
120
|
+
if fraction.shape[axis] < 2:
|
121
|
+
raise ValueError(f'{fraction.shape[axis]=} < 2')
|
122
|
+
with numpy.errstate(divide='ignore', invalid='ignore'):
|
123
|
+
fraction /= fraction.sum(axis=axis, keepdims=True)
|
124
|
+
|
125
|
+
component_real = numpy.asarray(component_real, dtype=dtype)
|
126
|
+
component_imag = numpy.asarray(component_imag, dtype=dtype)
|
127
|
+
if component_real.shape != component_imag.shape:
|
128
|
+
raise ValueError(f'{component_real.shape=} != {component_imag.shape=}')
|
129
|
+
if component_real.ndim != 1:
|
130
|
+
raise ValueError(f'{component_real.ndim=} != 1')
|
131
|
+
if component_real.size != fraction.shape[axis]:
|
132
|
+
raise ValueError(f'{component_real.size=} != {fraction.shape[axis]=}')
|
133
|
+
|
134
|
+
fraction = numpy.moveaxis(fraction, axis, -1)
|
135
|
+
real = numpy.dot(fraction, component_real)
|
136
|
+
imag = numpy.dot(fraction, component_imag)
|
137
|
+
return real, imag
|
47
138
|
|
48
139
|
|
49
140
|
def phasor_component_fraction(
|
@@ -83,7 +174,7 @@ def phasor_component_fraction(
|
|
83
174
|
|
84
175
|
See Also
|
85
176
|
--------
|
86
|
-
:ref:`
|
177
|
+
:ref:`sphx_glr_tutorials_api_phasorpy_component.py`
|
87
178
|
|
88
179
|
Notes
|
89
180
|
-----
|
@@ -97,7 +188,7 @@ def phasor_component_fraction(
|
|
97
188
|
--------
|
98
189
|
>>> phasor_component_fraction(
|
99
190
|
... [0.6, 0.5, 0.4], [0.4, 0.3, 0.2], [0.2, 0.9], [0.4, 0.3]
|
100
|
-
... )
|
191
|
+
... )
|
101
192
|
array([0.44, 0.56, 0.68])
|
102
193
|
|
103
194
|
"""
|
@@ -132,7 +223,7 @@ def phasor_component_graphical(
|
|
132
223
|
*,
|
133
224
|
radius: float = 0.05,
|
134
225
|
fractions: ArrayLike | None = None,
|
135
|
-
) ->
|
226
|
+
) -> NDArray[Any]:
|
136
227
|
r"""Return fractions of two or three components from phasor coordinates.
|
137
228
|
|
138
229
|
The graphical method is based on moving circular cursors along the line
|
@@ -162,9 +253,11 @@ def phasor_component_graphical(
|
|
162
253
|
|
163
254
|
Returns
|
164
255
|
-------
|
165
|
-
counts :
|
256
|
+
counts : ndarray
|
166
257
|
Counts along each line segment connecting components.
|
167
258
|
Ordered 0-1 (2 components) or 0-1, 0-2, 1-2 (3 components).
|
259
|
+
Shaped `(number fractions,)` (2 components) or
|
260
|
+
`(3, number fractions)` (3 components).
|
168
261
|
|
169
262
|
Raises
|
170
263
|
------
|
@@ -176,7 +269,7 @@ def phasor_component_graphical(
|
|
176
269
|
|
177
270
|
See Also
|
178
271
|
--------
|
179
|
-
:ref:`
|
272
|
+
:ref:`sphx_glr_tutorials_api_phasorpy_component.py`
|
180
273
|
|
181
274
|
Notes
|
182
275
|
-----
|
@@ -202,7 +295,6 @@ def phasor_component_graphical(
|
|
202
295
|
|
203
296
|
References
|
204
297
|
----------
|
205
|
-
|
206
298
|
.. [1] Ranjit S, Datta R, Dvornikov A, and Gratton E.
|
207
299
|
`Multicomponent analysis of phasor plot in a single pixel to
|
208
300
|
calculate changes of metabolic trajectory in biological systems
|
@@ -215,8 +307,8 @@ def phasor_component_graphical(
|
|
215
307
|
|
216
308
|
>>> phasor_component_graphical(
|
217
309
|
... [0.6, 0.3], [0.35, 0.38], [0.2, 0.9], [0.4, 0.3], fractions=6
|
218
|
-
... )
|
219
|
-
|
310
|
+
... )
|
311
|
+
array([0, 0, 1, 0, 1, 0], dtype=uint8)
|
220
312
|
|
221
313
|
Count the number of phasors between the combinations of three components:
|
222
314
|
|
@@ -226,10 +318,10 @@ def phasor_component_graphical(
|
|
226
318
|
... [0.0, 0.2, 0.9],
|
227
319
|
... [0.0, 0.4, 0.3],
|
228
320
|
... fractions=6,
|
229
|
-
... )
|
230
|
-
|
231
|
-
|
232
|
-
|
321
|
+
... )
|
322
|
+
array([[0, 1, 1, 1, 1, 0],
|
323
|
+
[0, 1, 0, 0, 0, 0],
|
324
|
+
[0, 1, 2, 0, 0, 0]], dtype=uint8)
|
233
325
|
|
234
326
|
"""
|
235
327
|
real = numpy.asarray(real)
|
@@ -272,7 +364,12 @@ def phasor_component_graphical(
|
|
272
364
|
if fractions.ndim != 1:
|
273
365
|
raise ValueError('fractions is not a one-dimensional array')
|
274
366
|
|
275
|
-
|
367
|
+
dtype = numpy.min_scalar_type(real.size)
|
368
|
+
counts = numpy.empty(
|
369
|
+
(1 if num_components == 2 else 3, fractions.size), dtype=dtype
|
370
|
+
)
|
371
|
+
|
372
|
+
c = 0
|
276
373
|
for i in range(num_components):
|
277
374
|
a_real = component_real[i]
|
278
375
|
a_imag = component_imag[i]
|
@@ -282,8 +379,7 @@ def phasor_component_graphical(
|
|
282
379
|
ab_real = a_real - b_real
|
283
380
|
ab_imag = a_imag - b_imag
|
284
381
|
|
285
|
-
|
286
|
-
for f in fractions:
|
382
|
+
for k, f in enumerate(fractions):
|
287
383
|
if f < 0.0 or f > 1.0:
|
288
384
|
raise ValueError(f'fraction {f} out of bounds [0.0, 1.0]')
|
289
385
|
if num_components == 2:
|
@@ -305,12 +401,10 @@ def phasor_component_graphical(
|
|
305
401
|
component_imag[3 - i - j], # c_imag
|
306
402
|
radius,
|
307
403
|
)
|
308
|
-
|
309
|
-
|
404
|
+
counts[c, k] = numpy.sum(mask, dtype=dtype)
|
405
|
+
c += 1
|
310
406
|
|
311
|
-
|
312
|
-
|
313
|
-
return tuple(counts)
|
407
|
+
return counts[0] if num_components == 2 else counts
|
314
408
|
|
315
409
|
|
316
410
|
def phasor_component_fit(
|
@@ -321,7 +415,7 @@ def phasor_component_fit(
|
|
321
415
|
component_imag: ArrayLike,
|
322
416
|
/,
|
323
417
|
**kwargs: Any,
|
324
|
-
) ->
|
418
|
+
) -> NDArray[Any]:
|
325
419
|
"""Return fractions of multiple components from phasor coordinates.
|
326
420
|
|
327
421
|
Component fractions are obtained from the least-squares solution of a
|
@@ -348,13 +442,13 @@ def phasor_component_fit(
|
|
348
442
|
component_imag : array_like
|
349
443
|
Imaginary coordinates of components.
|
350
444
|
Must be one or two-dimensional with harmonics in the first dimension.
|
351
|
-
**kwargs
|
352
|
-
|
445
|
+
**kwargs
|
446
|
+
Optional arguments passed to :py:func:`scipy.linalg.lstsq`.
|
353
447
|
|
354
448
|
Returns
|
355
449
|
-------
|
356
|
-
fractions :
|
357
|
-
Component fractions
|
450
|
+
fractions : ndarray
|
451
|
+
Component fractions.
|
358
452
|
Fractions may not exactly add up to 1.0.
|
359
453
|
|
360
454
|
Raises
|
@@ -369,7 +463,7 @@ def phasor_component_fit(
|
|
369
463
|
|
370
464
|
See Also
|
371
465
|
--------
|
372
|
-
:ref:`
|
466
|
+
:ref:`sphx_glr_tutorials_api_phasorpy_component.py`
|
373
467
|
:ref:`sphx_glr_tutorials_applications_phasorpy_component_fit.py`
|
374
468
|
|
375
469
|
Notes
|
@@ -392,12 +486,13 @@ def phasor_component_fit(
|
|
392
486
|
imaging <https://doi.org/10.1088/2050-6120/ac9ae9>`_.
|
393
487
|
*Methods Appl Fluoresc*, 11(1): 014001 (2022)
|
394
488
|
|
395
|
-
|
396
|
-
|
489
|
+
Examples
|
490
|
+
--------
|
397
491
|
>>> phasor_component_fit(
|
398
492
|
... [1, 1, 1], [0.6, 0.5, 0.4], [0.4, 0.3, 0.2], [0.2, 0.9], [0.4, 0.3]
|
399
|
-
... )
|
400
|
-
|
493
|
+
... )
|
494
|
+
array([[0.4644, 0.5356, 0.6068],
|
495
|
+
[0.5559, 0.4441, 0.3322]])
|
401
496
|
|
402
497
|
"""
|
403
498
|
from scipy.linalg import lstsq
|
@@ -464,7 +559,9 @@ def phasor_component_fit(
|
|
464
559
|
# [real coordinates (for each harmonic)] +
|
465
560
|
# [imaginary coordinates (for each harmonic)] +
|
466
561
|
# [ones for intensity constraint]
|
467
|
-
coords = numpy.ones(
|
562
|
+
coords = numpy.ones(
|
563
|
+
(2 * num_harmonics + 1,) + real.shape[1:] # type: ignore[union-attr]
|
564
|
+
)
|
468
565
|
coords[:num_harmonics] = real
|
469
566
|
coords[num_harmonics : 2 * num_harmonics] = imag
|
470
567
|
|
@@ -481,4 +578,134 @@ def phasor_component_fit(
|
|
481
578
|
# restore NaN values in fractions from mean
|
482
579
|
_blend_and(mean, fractions, out=fractions)
|
483
580
|
|
484
|
-
return
|
581
|
+
return numpy.asarray(fractions)
|
582
|
+
|
583
|
+
|
584
|
+
def phasor_component_mvc(
|
585
|
+
real: ArrayLike,
|
586
|
+
imag: ArrayLike,
|
587
|
+
component_real: ArrayLike,
|
588
|
+
component_imag: ArrayLike,
|
589
|
+
/,
|
590
|
+
*,
|
591
|
+
dtype: DTypeLike = None,
|
592
|
+
num_threads: int | None = None,
|
593
|
+
) -> NDArray[Any]:
|
594
|
+
"""Return mean value coordinates of phasor coordinates from components.
|
595
|
+
|
596
|
+
The mean value coordinates of phasor coordinates with respect to three or
|
597
|
+
more components spanning an arbitrary simple polygon are computed using
|
598
|
+
the stable method described in [3]_.
|
599
|
+
For three components, mean value coordinates are equivalent to
|
600
|
+
barycentric coordinates.
|
601
|
+
|
602
|
+
Parameters
|
603
|
+
----------
|
604
|
+
real : array_like
|
605
|
+
Real component of phasor coordinates.
|
606
|
+
imag : array_like
|
607
|
+
Imaginary component of phasor coordinates.
|
608
|
+
component_real : array_like
|
609
|
+
Real coordinates of at least three components.
|
610
|
+
component_imag : array_like
|
611
|
+
Imaginary coordinates of at least three components.
|
612
|
+
dtype : dtype_like, optional
|
613
|
+
Floating point data type used for calculation and output values.
|
614
|
+
Either `float32` or `float64`. The default is `float64`.
|
615
|
+
num_threads : int, optional
|
616
|
+
Number of OpenMP threads to use for parallelization.
|
617
|
+
By default, multi-threading is disabled.
|
618
|
+
If zero, up to half of logical CPUs are used.
|
619
|
+
OpenMP may not be available on all platforms.
|
620
|
+
|
621
|
+
Returns
|
622
|
+
-------
|
623
|
+
fractions : ndarray
|
624
|
+
Mean value coordinates for each phasor coordinate.
|
625
|
+
|
626
|
+
Raises
|
627
|
+
------
|
628
|
+
ValueError
|
629
|
+
The array shapes of `real` and `imag` do not match.
|
630
|
+
The array shapes of `component_real` and `component_imag` do not match.
|
631
|
+
|
632
|
+
Notes
|
633
|
+
-----
|
634
|
+
Calculation of mean value coordinates for different channels,
|
635
|
+
frequencies, or harmonics is not supported. Only one set of components
|
636
|
+
can be analyzed and is broadcast to all channels/frequencies/harmonics.
|
637
|
+
|
638
|
+
For three components, this function returns the same result as
|
639
|
+
:py:func:`phasor_component_fit`. For more than three components,
|
640
|
+
the system is underdetermined and the mean value coordinates represent
|
641
|
+
one of multiple solutions. However, the special properties of the mean
|
642
|
+
value coordinates make them particularly useful for interpolating and
|
643
|
+
visualizing multi-component data.
|
644
|
+
|
645
|
+
References
|
646
|
+
----------
|
647
|
+
.. [3] Fuda C and Hormann K.
|
648
|
+
`A new stable method to compute mean value coordinates
|
649
|
+
<https://doi.org/10.1016/j.cagd.2024.102310>`_.
|
650
|
+
*Computer Aided Geometric Design*, 111: 102310 (2024)
|
651
|
+
|
652
|
+
Examples
|
653
|
+
--------
|
654
|
+
Calculate the barycentric coordinates of a phasor coordinate
|
655
|
+
in a triangle defined by three components:
|
656
|
+
|
657
|
+
>>> phasor_component_mvc(0.6, 0.3, [0.0, 1.0, 0.0], [1.0, 0.0, 0.0])
|
658
|
+
array([0.3, 0.6, 0.1])
|
659
|
+
|
660
|
+
The barycentric coordinates of phasor coordinates outside the polygon
|
661
|
+
defined by the components may be outside the range [0.0, 1.0]:
|
662
|
+
|
663
|
+
>>> phasor_component_mvc(0.6, 0.6, [0.0, 1.0, 0.0], [1.0, 0.0, 0.0])
|
664
|
+
array([0.6, 0.6, -0.2])
|
665
|
+
|
666
|
+
"""
|
667
|
+
num_threads = number_threads(num_threads)
|
668
|
+
|
669
|
+
dtype = numpy.dtype(dtype)
|
670
|
+
if dtype.char not in {'f', 'd'}:
|
671
|
+
raise ValueError(f'{dtype=} is not a floating point type')
|
672
|
+
|
673
|
+
real = numpy.ascontiguousarray(real, dtype=dtype)
|
674
|
+
imag = numpy.ascontiguousarray(imag, dtype=dtype)
|
675
|
+
component_real = numpy.ascontiguousarray(component_real, dtype=dtype)
|
676
|
+
component_imag = numpy.ascontiguousarray(component_imag, dtype=dtype)
|
677
|
+
|
678
|
+
if real.shape != imag.shape:
|
679
|
+
raise ValueError(f'{real.shape=} != {imag.shape=}')
|
680
|
+
if component_real.shape != component_imag.shape:
|
681
|
+
raise ValueError(f'{component_real.shape=} != {component_imag.shape=}')
|
682
|
+
if component_real.ndim != 1 or component_real.size < 3:
|
683
|
+
raise ValueError('number of components must be three or more')
|
684
|
+
if numpy.isnan(component_real).any() or numpy.isnan(component_imag).any():
|
685
|
+
raise ValueError('component coordinates must not contain NaN values')
|
686
|
+
if numpy.isinf(component_real).any() or numpy.isinf(component_imag).any():
|
687
|
+
raise ValueError(
|
688
|
+
'component coordinates must not contain infinite values'
|
689
|
+
)
|
690
|
+
|
691
|
+
# TODO:: sorting not strictly required for three components?
|
692
|
+
component_real, component_imag, indices = sort_coordinates(
|
693
|
+
component_real, component_imag
|
694
|
+
)
|
695
|
+
|
696
|
+
shape = real.shape
|
697
|
+
real = real.reshape(-1)
|
698
|
+
imag = imag.reshape(-1)
|
699
|
+
fraction = numpy.zeros((component_real.size, real.size), dtype=dtype)
|
700
|
+
|
701
|
+
_mean_value_coordinates(
|
702
|
+
fraction,
|
703
|
+
indices,
|
704
|
+
real,
|
705
|
+
imag,
|
706
|
+
component_real,
|
707
|
+
component_imag,
|
708
|
+
num_threads,
|
709
|
+
)
|
710
|
+
|
711
|
+
return numpy.asarray(fraction.reshape((-1, *shape)).squeeze())
|