phasorpy 0.6__cp312-cp312-win_amd64.whl → 0.7__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/__init__.py +1 -1
- phasorpy/_phasorpy.cp312-win_amd64.pyd +0 -0
- phasorpy/_phasorpy.pyx +281 -9
- phasorpy/_utils.py +101 -28
- phasorpy/cli.py +19 -1
- phasorpy/cluster.py +10 -16
- phasorpy/color.py +11 -7
- phasorpy/{components.py → component.py} +255 -32
- phasorpy/{cursors.py → cursor.py} +31 -33
- phasorpy/datasets.py +117 -7
- phasorpy/experimental.py +8 -10
- phasorpy/io/__init__.py +1 -0
- phasorpy/io/_flimlabs.py +20 -10
- phasorpy/io/_leica.py +3 -1
- phasorpy/io/_ometiff.py +2 -3
- phasorpy/io/_other.py +115 -7
- phasorpy/io/_simfcs.py +41 -16
- phasorpy/lifetime.py +2058 -0
- phasorpy/phasor.py +71 -1947
- phasorpy/plot/_functions.py +8 -2
- phasorpy/plot/_lifetime_plots.py +33 -23
- phasorpy/plot/_phasorplot.py +547 -159
- phasorpy/plot/_phasorplot_fret.py +11 -9
- phasorpy/utils.py +21 -10
- {phasorpy-0.6.dist-info → phasorpy-0.7.dist-info}/METADATA +2 -2
- phasorpy-0.7.dist-info/RECORD +35 -0
- phasorpy-0.6.dist-info/RECORD +0 -34
- {phasorpy-0.6.dist-info → phasorpy-0.7.dist-info}/WHEEL +0 -0
- {phasorpy-0.6.dist-info → phasorpy-0.7.dist-info}/entry_points.txt +0 -0
- {phasorpy-0.6.dist-info → phasorpy-0.7.dist-info}/licenses/LICENSE.txt +0 -0
- {phasorpy-0.6.dist-info → phasorpy-0.7.dist-info}/top_level.txt +0 -0
phasorpy/cluster.py
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
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).
|
@@ -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
|
"""Component analysis of 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,88 @@ 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
|
)
|
55
|
+
from ._utils import sort_coordinates
|
46
56
|
from .phasor 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
|
+
Examples
|
99
|
+
--------
|
100
|
+
Calculate phasor coordinates from two components and their fractional
|
101
|
+
intensities:
|
102
|
+
|
103
|
+
>>> phasor_from_component(
|
104
|
+
... [0.6, 0.4], [0.3, 0.2], [[1.0, 0.2, 0.9], [0.0, 0.8, 0.1]]
|
105
|
+
... )
|
106
|
+
(array([0.6, 0.44, 0.58]), array([0.3, 0.22, 0.29]))
|
107
|
+
|
108
|
+
"""
|
109
|
+
dtype = numpy.dtype(dtype)
|
110
|
+
if dtype.char not in {'f', 'd'}:
|
111
|
+
raise ValueError(f'{dtype=} is not a floating point type')
|
112
|
+
|
113
|
+
fraction = numpy.array(fraction, dtype=dtype, copy=True)
|
114
|
+
if fraction.ndim < 1:
|
115
|
+
raise ValueError(f'{fraction.ndim=} < 1')
|
116
|
+
if fraction.shape[axis] < 2:
|
117
|
+
raise ValueError(f'{fraction.shape[axis]=} < 2')
|
118
|
+
with numpy.errstate(divide='ignore', invalid='ignore'):
|
119
|
+
fraction /= fraction.sum(axis=axis, keepdims=True)
|
120
|
+
|
121
|
+
component_real = numpy.asarray(component_real, dtype=dtype)
|
122
|
+
component_imag = numpy.asarray(component_imag, dtype=dtype)
|
123
|
+
if component_real.shape != component_imag.shape:
|
124
|
+
raise ValueError(f'{component_real.shape=} != {component_imag.shape=}')
|
125
|
+
if component_real.ndim != 1:
|
126
|
+
raise ValueError(f'{component_real.ndim=} != 1')
|
127
|
+
if component_real.size != fraction.shape[axis]:
|
128
|
+
raise ValueError(f'{component_real.size=} != {fraction.shape[axis]=}')
|
129
|
+
|
130
|
+
fraction = numpy.moveaxis(fraction, axis, -1)
|
131
|
+
real = numpy.dot(fraction, component_real)
|
132
|
+
imag = numpy.dot(fraction, component_imag)
|
133
|
+
return real, imag
|
47
134
|
|
48
135
|
|
49
136
|
def phasor_component_fraction(
|
@@ -83,7 +170,7 @@ def phasor_component_fraction(
|
|
83
170
|
|
84
171
|
See Also
|
85
172
|
--------
|
86
|
-
:ref:`
|
173
|
+
:ref:`sphx_glr_tutorials_api_phasorpy_component.py`
|
87
174
|
|
88
175
|
Notes
|
89
176
|
-----
|
@@ -97,7 +184,7 @@ def phasor_component_fraction(
|
|
97
184
|
--------
|
98
185
|
>>> phasor_component_fraction(
|
99
186
|
... [0.6, 0.5, 0.4], [0.4, 0.3, 0.2], [0.2, 0.9], [0.4, 0.3]
|
100
|
-
... )
|
187
|
+
... )
|
101
188
|
array([0.44, 0.56, 0.68])
|
102
189
|
|
103
190
|
"""
|
@@ -132,7 +219,7 @@ def phasor_component_graphical(
|
|
132
219
|
*,
|
133
220
|
radius: float = 0.05,
|
134
221
|
fractions: ArrayLike | None = None,
|
135
|
-
) ->
|
222
|
+
) -> NDArray[Any]:
|
136
223
|
r"""Return fractions of two or three components from phasor coordinates.
|
137
224
|
|
138
225
|
The graphical method is based on moving circular cursors along the line
|
@@ -162,9 +249,11 @@ def phasor_component_graphical(
|
|
162
249
|
|
163
250
|
Returns
|
164
251
|
-------
|
165
|
-
counts :
|
252
|
+
counts : ndarray
|
166
253
|
Counts along each line segment connecting components.
|
167
254
|
Ordered 0-1 (2 components) or 0-1, 0-2, 1-2 (3 components).
|
255
|
+
Shaped `(number fractions,)` (2 components) or
|
256
|
+
`(3, number fractions)` (3 components).
|
168
257
|
|
169
258
|
Raises
|
170
259
|
------
|
@@ -176,7 +265,7 @@ def phasor_component_graphical(
|
|
176
265
|
|
177
266
|
See Also
|
178
267
|
--------
|
179
|
-
:ref:`
|
268
|
+
:ref:`sphx_glr_tutorials_api_phasorpy_component.py`
|
180
269
|
|
181
270
|
Notes
|
182
271
|
-----
|
@@ -202,7 +291,6 @@ def phasor_component_graphical(
|
|
202
291
|
|
203
292
|
References
|
204
293
|
----------
|
205
|
-
|
206
294
|
.. [1] Ranjit S, Datta R, Dvornikov A, and Gratton E.
|
207
295
|
`Multicomponent analysis of phasor plot in a single pixel to
|
208
296
|
calculate changes of metabolic trajectory in biological systems
|
@@ -215,8 +303,8 @@ def phasor_component_graphical(
|
|
215
303
|
|
216
304
|
>>> phasor_component_graphical(
|
217
305
|
... [0.6, 0.3], [0.35, 0.38], [0.2, 0.9], [0.4, 0.3], fractions=6
|
218
|
-
... )
|
219
|
-
|
306
|
+
... )
|
307
|
+
array([0, 0, 1, 0, 1, 0], dtype=uint8)
|
220
308
|
|
221
309
|
Count the number of phasors between the combinations of three components:
|
222
310
|
|
@@ -226,10 +314,10 @@ def phasor_component_graphical(
|
|
226
314
|
... [0.0, 0.2, 0.9],
|
227
315
|
... [0.0, 0.4, 0.3],
|
228
316
|
... fractions=6,
|
229
|
-
... )
|
230
|
-
|
231
|
-
|
232
|
-
|
317
|
+
... )
|
318
|
+
array([[0, 1, 1, 1, 1, 0],
|
319
|
+
[0, 1, 0, 0, 0, 0],
|
320
|
+
[0, 1, 2, 0, 0, 0]], dtype=uint8)
|
233
321
|
|
234
322
|
"""
|
235
323
|
real = numpy.asarray(real)
|
@@ -272,7 +360,12 @@ def phasor_component_graphical(
|
|
272
360
|
if fractions.ndim != 1:
|
273
361
|
raise ValueError('fractions is not a one-dimensional array')
|
274
362
|
|
275
|
-
|
363
|
+
dtype = numpy.min_scalar_type(real.size)
|
364
|
+
counts = numpy.empty(
|
365
|
+
(1 if num_components == 2 else 3, fractions.size), dtype
|
366
|
+
)
|
367
|
+
|
368
|
+
c = 0
|
276
369
|
for i in range(num_components):
|
277
370
|
a_real = component_real[i]
|
278
371
|
a_imag = component_imag[i]
|
@@ -282,8 +375,7 @@ def phasor_component_graphical(
|
|
282
375
|
ab_real = a_real - b_real
|
283
376
|
ab_imag = a_imag - b_imag
|
284
377
|
|
285
|
-
|
286
|
-
for f in fractions:
|
378
|
+
for k, f in enumerate(fractions):
|
287
379
|
if f < 0.0 or f > 1.0:
|
288
380
|
raise ValueError(f'fraction {f} out of bounds [0.0, 1.0]')
|
289
381
|
if num_components == 2:
|
@@ -305,12 +397,10 @@ def phasor_component_graphical(
|
|
305
397
|
component_imag[3 - i - j], # c_imag
|
306
398
|
radius,
|
307
399
|
)
|
308
|
-
|
309
|
-
|
400
|
+
counts[c, k] = numpy.sum(mask, dtype=dtype)
|
401
|
+
c += 1
|
310
402
|
|
311
|
-
|
312
|
-
|
313
|
-
return tuple(counts)
|
403
|
+
return counts[0] if num_components == 2 else counts
|
314
404
|
|
315
405
|
|
316
406
|
def phasor_component_fit(
|
@@ -321,7 +411,7 @@ def phasor_component_fit(
|
|
321
411
|
component_imag: ArrayLike,
|
322
412
|
/,
|
323
413
|
**kwargs: Any,
|
324
|
-
) ->
|
414
|
+
) -> NDArray[Any]:
|
325
415
|
"""Return fractions of multiple components from phasor coordinates.
|
326
416
|
|
327
417
|
Component fractions are obtained from the least-squares solution of a
|
@@ -353,8 +443,8 @@ def phasor_component_fit(
|
|
353
443
|
|
354
444
|
Returns
|
355
445
|
-------
|
356
|
-
fractions :
|
357
|
-
Component fractions
|
446
|
+
fractions : ndarray
|
447
|
+
Component fractions.
|
358
448
|
Fractions may not exactly add up to 1.0.
|
359
449
|
|
360
450
|
Raises
|
@@ -369,7 +459,7 @@ def phasor_component_fit(
|
|
369
459
|
|
370
460
|
See Also
|
371
461
|
--------
|
372
|
-
:ref:`
|
462
|
+
:ref:`sphx_glr_tutorials_api_phasorpy_component.py`
|
373
463
|
:ref:`sphx_glr_tutorials_applications_phasorpy_component_fit.py`
|
374
464
|
|
375
465
|
Notes
|
@@ -392,12 +482,13 @@ def phasor_component_fit(
|
|
392
482
|
imaging <https://doi.org/10.1088/2050-6120/ac9ae9>`_.
|
393
483
|
*Methods Appl Fluoresc*, 11(1): 014001 (2022)
|
394
484
|
|
395
|
-
|
396
|
-
|
485
|
+
Examples
|
486
|
+
--------
|
397
487
|
>>> phasor_component_fit(
|
398
488
|
... [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
|
-
|
489
|
+
... )
|
490
|
+
array([[0.4644, 0.5356, 0.6068],
|
491
|
+
[0.5559, 0.4441, 0.3322]])
|
401
492
|
|
402
493
|
"""
|
403
494
|
from scipy.linalg import lstsq
|
@@ -464,7 +555,9 @@ def phasor_component_fit(
|
|
464
555
|
# [real coordinates (for each harmonic)] +
|
465
556
|
# [imaginary coordinates (for each harmonic)] +
|
466
557
|
# [ones for intensity constraint]
|
467
|
-
coords = numpy.ones(
|
558
|
+
coords = numpy.ones(
|
559
|
+
(2 * num_harmonics + 1,) + real.shape[1:] # type: ignore[union-attr]
|
560
|
+
)
|
468
561
|
coords[:num_harmonics] = real
|
469
562
|
coords[num_harmonics : 2 * num_harmonics] = imag
|
470
563
|
|
@@ -481,4 +574,134 @@ def phasor_component_fit(
|
|
481
574
|
# restore NaN values in fractions from mean
|
482
575
|
_blend_and(mean, fractions, out=fractions)
|
483
576
|
|
484
|
-
return
|
577
|
+
return numpy.asarray(fractions)
|
578
|
+
|
579
|
+
|
580
|
+
def phasor_component_mvc(
|
581
|
+
real: ArrayLike,
|
582
|
+
imag: ArrayLike,
|
583
|
+
component_real: ArrayLike,
|
584
|
+
component_imag: ArrayLike,
|
585
|
+
/,
|
586
|
+
*,
|
587
|
+
dtype: DTypeLike = None,
|
588
|
+
num_threads: int | None = None,
|
589
|
+
) -> NDArray[Any]:
|
590
|
+
"""Return mean value coordinates of phasor coordinates from components.
|
591
|
+
|
592
|
+
The mean value coordinates of phasor coordinates with respect to three or
|
593
|
+
more components spanning an arbitrary simple polygon are computed using
|
594
|
+
the stable method described in [3]_.
|
595
|
+
For three components, mean value coordinates are equivalent to
|
596
|
+
barycentric coordinates.
|
597
|
+
|
598
|
+
Parameters
|
599
|
+
----------
|
600
|
+
real : array_like
|
601
|
+
Real component of phasor coordinates.
|
602
|
+
imag : array_like
|
603
|
+
Imaginary component of phasor coordinates.
|
604
|
+
component_real : array_like
|
605
|
+
Real coordinates of at least three components.
|
606
|
+
component_imag : array_like
|
607
|
+
Imaginary coordinates of at least three components.
|
608
|
+
dtype : dtype_like, optional
|
609
|
+
Floating point data type used for calculation and output values.
|
610
|
+
Either `float32` or `float64`. The default is `float64`.
|
611
|
+
num_threads : int, optional
|
612
|
+
Number of OpenMP threads to use for parallelization.
|
613
|
+
By default, multi-threading is disabled.
|
614
|
+
If zero, up to half of logical CPUs are used.
|
615
|
+
OpenMP may not be available on all platforms.
|
616
|
+
|
617
|
+
Returns
|
618
|
+
-------
|
619
|
+
fractions : ndarray
|
620
|
+
Mean value coordinates for each phasor coordinate.
|
621
|
+
|
622
|
+
Raises
|
623
|
+
------
|
624
|
+
ValueError
|
625
|
+
The array shapes of `real` and `imag` do not match.
|
626
|
+
The array shapes of `component_real` and `component_imag` do not match.
|
627
|
+
|
628
|
+
Notes
|
629
|
+
-----
|
630
|
+
Calculation of mean value coordinates for different channels,
|
631
|
+
frequencies, or harmonics is not supported. Only one set of components
|
632
|
+
can be analyzed and is broadcast to all channels/frequencies/harmonics.
|
633
|
+
|
634
|
+
For three components, this function returns the same result as
|
635
|
+
:py:func:`phasor_component_fit`. For more than three components,
|
636
|
+
the system is underdetermined and the mean value coordinates represent
|
637
|
+
one of multiple solutions. However, the special properties of the mean
|
638
|
+
value coordinates make them particularly useful for interpolating and
|
639
|
+
visualizing multi-component data.
|
640
|
+
|
641
|
+
References
|
642
|
+
----------
|
643
|
+
.. [3] Fuda C and Hormann K.
|
644
|
+
`A new stable method to compute mean value coordinates
|
645
|
+
<https://doi.org/10.1016/j.cagd.2024.102310>`_.
|
646
|
+
*Computer Aided Geometric Design*, 111: 102310 (2024)
|
647
|
+
|
648
|
+
Examples
|
649
|
+
--------
|
650
|
+
Calculate the barycentric coordinates of a phasor coordinate
|
651
|
+
in a triangle defined by three components:
|
652
|
+
|
653
|
+
>>> phasor_component_mvc(0.6, 0.3, [0.0, 1.0, 0.0], [1.0, 0.0, 0.0])
|
654
|
+
array([0.3, 0.6, 0.1])
|
655
|
+
|
656
|
+
The barycentric coordinates of phasor coordinates outside the polygon
|
657
|
+
defined by the components may be outside the range [0.0, 1.0]:
|
658
|
+
|
659
|
+
>>> phasor_component_mvc(0.6, 0.6, [0.0, 1.0, 0.0], [1.0, 0.0, 0.0])
|
660
|
+
array([0.6, 0.6, -0.2])
|
661
|
+
|
662
|
+
"""
|
663
|
+
num_threads = number_threads(num_threads)
|
664
|
+
|
665
|
+
dtype = numpy.dtype(dtype)
|
666
|
+
if dtype.char not in {'f', 'd'}:
|
667
|
+
raise ValueError(f'{dtype=} is not a floating point type')
|
668
|
+
|
669
|
+
real = numpy.ascontiguousarray(real, dtype=dtype)
|
670
|
+
imag = numpy.ascontiguousarray(imag, dtype=dtype)
|
671
|
+
component_real = numpy.ascontiguousarray(component_real, dtype=dtype)
|
672
|
+
component_imag = numpy.ascontiguousarray(component_imag, dtype=dtype)
|
673
|
+
|
674
|
+
if real.shape != imag.shape:
|
675
|
+
raise ValueError(f'{real.shape=} != {imag.shape=}')
|
676
|
+
if component_real.shape != component_imag.shape:
|
677
|
+
raise ValueError(f'{component_real.shape=} != {component_imag.shape=}')
|
678
|
+
if component_real.ndim != 1 or component_real.size < 3:
|
679
|
+
raise ValueError('number of components must be three or more')
|
680
|
+
if numpy.isnan(component_real).any() or numpy.isnan(component_imag).any():
|
681
|
+
raise ValueError('component coordinates must not contain NaN values')
|
682
|
+
if numpy.isinf(component_real).any() or numpy.isinf(component_imag).any():
|
683
|
+
raise ValueError(
|
684
|
+
'component coordinates must not contain infinite values'
|
685
|
+
)
|
686
|
+
|
687
|
+
# TODO:: sorting not strictly required for three components?
|
688
|
+
component_real, component_imag, indices = sort_coordinates(
|
689
|
+
component_real, component_imag
|
690
|
+
)
|
691
|
+
|
692
|
+
shape = real.shape
|
693
|
+
real = real.reshape(-1)
|
694
|
+
imag = imag.reshape(-1)
|
695
|
+
fraction = numpy.zeros((component_real.size, real.size), dtype=dtype)
|
696
|
+
|
697
|
+
_mean_value_coordinates(
|
698
|
+
fraction,
|
699
|
+
indices,
|
700
|
+
real,
|
701
|
+
imag,
|
702
|
+
component_real,
|
703
|
+
component_imag,
|
704
|
+
num_threads,
|
705
|
+
)
|
706
|
+
|
707
|
+
return numpy.asarray(fraction.reshape((-1, *shape)).squeeze())
|