phasorpy 0.7__cp312-cp312-macosx_11_0_arm64.whl → 0.8__cp312-cp312-macosx_11_0_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 CHANGED
@@ -5,5 +5,5 @@ from __future__ import annotations
5
5
  __all__ = ['__version__']
6
6
 
7
7
 
8
- __version__ = '0.7'
8
+ __version__ = '0.8'
9
9
  """PhasorPy version string."""
Binary file
phasorpy/_phasorpy.pyx CHANGED
@@ -6,7 +6,13 @@
6
6
  # cython: nonecheck = False
7
7
  # cython: freethreading_compatible = True
8
8
 
9
- """Cython implementation of low-level functions for the PhasorPy library."""
9
+ """Private functions implemented in Cython for performance.
10
+
11
+ .. note::
12
+ This module and its functions are not part of the public interface.
13
+ They are intended to facilitate the development of the PhasorPy library.
14
+
15
+ """
10
16
 
11
17
  cimport cython
12
18
 
@@ -1005,6 +1011,38 @@ cdef (float_t, float_t) _phasor_divide(
1005
1011
  )
1006
1012
 
1007
1013
 
1014
+ @cython.ufunc
1015
+ cdef (float_t, float_t, float_t) _phasor_combine(
1016
+ float_t int0,
1017
+ float_t real0,
1018
+ float_t imag0,
1019
+ float_t int1,
1020
+ float_t real1,
1021
+ float_t imag1,
1022
+ float_t fraction0,
1023
+ float_t fraction1,
1024
+ ) noexcept nogil:
1025
+ """Return linear combination of two phasor coordinates."""
1026
+ cdef:
1027
+ float_t intensity
1028
+
1029
+ fraction1 += fraction0
1030
+ if fraction1 == 0.0:
1031
+ return <float_t> 0.0, <float_t> NAN, <float_t> NAN
1032
+ fraction0 /= fraction1
1033
+
1034
+ int0 *= fraction0
1035
+ int1 *= <float_t> 1.0 - fraction0
1036
+ intensity = int0 + int1
1037
+
1038
+ if intensity == 0.0:
1039
+ return <float_t> 0.0, <float_t> NAN, <float_t> NAN
1040
+
1041
+ int0 /= intensity
1042
+ int1 /= intensity
1043
+ return intensity, int0 * real0 + int1 * real1, int0 * imag0 + int1 * imag1
1044
+
1045
+
1008
1046
  ###############################################################################
1009
1047
  # Geometry ufuncs
1010
1048
 
phasorpy/_utils.py CHANGED
@@ -1,4 +1,10 @@
1
- """Private auxiliary and convenience functions."""
1
+ """Private auxiliary and convenience functions.
2
+
3
+ .. note::
4
+ This module and its functions are not part of the public interface.
5
+ They are intended to facilitate the development of the PhasorPy library.
6
+
7
+ """
2
8
 
3
9
  from __future__ import annotations
4
10
 
@@ -667,7 +673,7 @@ def chunk_iter(
667
673
 
668
674
 
669
675
  def init_module(globs: dict[str, Any], /) -> None:
670
- """Add names in module to ``__all__`` and set ``__module__`` attributes.
676
+ """Add names in module to ``__all__`` attribute.
671
677
 
672
678
  Parameters
673
679
  ----------
@@ -690,9 +696,11 @@ def init_module(globs: dict[str, Any], /) -> None:
690
696
  }:
691
697
  continue
692
698
  names.append(name)
693
- obj = getattr(module, name)
694
- if hasattr(obj, '__module__'):
695
- obj.__module__ = module_name
699
+ # do not change __module__ attributes because that may interfere
700
+ # with introspection and pickling
701
+ # obj = getattr(module, name)
702
+ # if hasattr(obj, '__module__'):
703
+ # obj.__module__ = module_name
696
704
  globs['__all__'] = sorted(set(names))
697
705
 
698
706
 
phasorpy/cluster.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """Cluster phasor coordinates.
2
2
 
3
- The `phasorpy.cluster` module provides functions to:
3
+ The ``phasorpy.cluster`` module provides functions to:
4
4
 
5
5
  - fit elliptic clusters to phasor coordinates using a
6
6
  Gaussian Mixture Model (GMM):
@@ -66,7 +66,7 @@ def phasor_cluster_gmm(
66
66
  - 'area': Sort by inverse area of ellipse (-major * minor).
67
67
 
68
68
  **kwargs
69
- Additional keyword arguments passed to
69
+ Optional arguments passed to
70
70
  :py:class:`sklearn.mixture.GaussianMixture`.
71
71
 
72
72
  Common options include:
phasorpy/component.py CHANGED
@@ -1,4 +1,4 @@
1
- """Component analysis of phasor coordinates.
1
+ """Analyze components in phasor coordinates.
2
2
 
3
3
  The ``phasorpy.component`` module provides functions to:
4
4
 
@@ -53,7 +53,7 @@ from ._phasorpy import (
53
53
  _segment_direction_and_length,
54
54
  )
55
55
  from ._utils import sort_coordinates
56
- from .phasor import phasor_threshold
56
+ from .filter import phasor_threshold
57
57
  from .utils import number_threads
58
58
 
59
59
 
@@ -95,6 +95,10 @@ def phasor_from_component(
95
95
  imag : ndarray
96
96
  Imaginary component of phasor coordinates.
97
97
 
98
+ See Also
99
+ --------
100
+ phasorpy.phasor.phasor_combine
101
+
98
102
  Examples
99
103
  --------
100
104
  Calculate phasor coordinates from two components and their fractional
@@ -110,7 +114,7 @@ def phasor_from_component(
110
114
  if dtype.char not in {'f', 'd'}:
111
115
  raise ValueError(f'{dtype=} is not a floating point type')
112
116
 
113
- fraction = numpy.array(fraction, dtype=dtype, copy=True)
117
+ fraction = numpy.asarray(fraction, dtype=dtype, copy=True)
114
118
  if fraction.ndim < 1:
115
119
  raise ValueError(f'{fraction.ndim=} < 1')
116
120
  if fraction.shape[axis] < 2:
@@ -362,7 +366,7 @@ def phasor_component_graphical(
362
366
 
363
367
  dtype = numpy.min_scalar_type(real.size)
364
368
  counts = numpy.empty(
365
- (1 if num_components == 2 else 3, fractions.size), dtype
369
+ (1 if num_components == 2 else 3, fractions.size), dtype=dtype
366
370
  )
367
371
 
368
372
  c = 0
@@ -438,8 +442,8 @@ def phasor_component_fit(
438
442
  component_imag : array_like
439
443
  Imaginary coordinates of components.
440
444
  Must be one or two-dimensional with harmonics in the first dimension.
441
- **kwargs : optional
442
- Additional arguments passed to :py:func:`scipy.linalg.lstsq()`.
445
+ **kwargs
446
+ Optional arguments passed to :py:func:`scipy.linalg.lstsq`.
443
447
 
444
448
  Returns
445
449
  -------
phasorpy/datasets.py CHANGED
@@ -600,7 +600,7 @@ def fetch(
600
600
  return_scalar : bool, optional
601
601
  If true (default), return single path as string, else tuple of string.
602
602
  **kwargs
603
- Additional arguments passed to ``pooch.fetch()``.
603
+ Optional arguments passed to :py:func:`pooch.fetch`.
604
604
  For example, ``progressbar=True``.
605
605
 
606
606
  Returns
phasorpy/experimental.py CHANGED
@@ -11,26 +11,18 @@ from __future__ import annotations
11
11
  __all__ = [
12
12
  'anscombe_transform',
13
13
  'anscombe_transform_inverse',
14
- 'spectral_vector_denoise',
15
14
  ]
16
15
 
17
- import math
18
16
  from typing import TYPE_CHECKING
19
17
 
20
18
  if TYPE_CHECKING:
21
- from ._typing import Any, NDArray, ArrayLike, DTypeLike, Literal, Sequence
22
-
23
- import numpy
19
+ from ._typing import Any, NDArray, ArrayLike
24
20
 
25
21
  from ._phasorpy import (
26
22
  _anscombe,
27
23
  _anscombe_inverse,
28
24
  _anscombe_inverse_approx,
29
- _phasor_from_signal_vector,
30
- _signal_denoise_vector,
31
25
  )
32
- from ._utils import parse_harmonic
33
- from .utils import number_threads
34
26
 
35
27
 
36
28
  def anscombe_transform(
@@ -154,157 +146,3 @@ def anscombe_transform_inverse(
154
146
  data, **kwargs
155
147
  )
156
148
  return _anscombe_inverse(data, **kwargs) # type: ignore[no-any-return]
157
-
158
-
159
- def spectral_vector_denoise(
160
- signal: ArrayLike,
161
- /,
162
- spectral_vector: ArrayLike | None = None,
163
- *,
164
- axis: int = -1,
165
- harmonic: int | Sequence[int] | Literal['all'] | str | None = None,
166
- sigma: float = 0.05,
167
- vmin: float | None = None,
168
- dtype: DTypeLike | None = None,
169
- num_threads: int | None = None,
170
- ) -> NDArray[Any]:
171
- """Return spectral-vector-denoised signal.
172
-
173
- The spectral vector denoising algorithm is based on a Gaussian weighted
174
- average calculation, with weights obtained in n-dimensional Chebyshev or
175
- Fourier space [4]_.
176
-
177
- Parameters
178
- ----------
179
- signal : array_like
180
- Hyperspectral data to be denoised.
181
- A minimum of three samples are required along `axis`.
182
- The samples must be uniformly spaced.
183
- spectral_vector : array_like, optional
184
- Spectral vector.
185
- For example, phasor coordinates, PCA projected phasor coordinates,
186
- or Chebyshev coefficients.
187
- Must be of the same shape as `signal` with `axis` removed and an axis
188
- containing spectral space appended.
189
- If None (default), phasor coordinates are calculated at specified
190
- `harmonic`.
191
- axis : int, optional, default: -1
192
- Axis over which `spectral_vector` is computed if not provided.
193
- The default is the last axis (-1).
194
- harmonic : int, sequence of int, or 'all', optional
195
- Harmonics to include in calculating `spectral_vector`.
196
- If `'all'`, include all harmonics for `signal` samples along `axis`.
197
- Else, harmonics must be at least one and no larger than half the
198
- number of `signal` samples along `axis`.
199
- The default is the first harmonic (fundamental frequency).
200
- A minimum of `harmonic * 2 + 1` samples are required along `axis`
201
- to calculate correct phasor coordinates at `harmonic`.
202
- sigma : float, default: 0.05
203
- Width of Gaussian filter in spectral vector space.
204
- Weighted averages are calculated using the spectra of signal items
205
- within a spectral vector Euclidean distance of `3 * sigma` and
206
- intensity above `vmin`.
207
- vmin : float, optional
208
- Signal intensity along `axis` below which spectra are excluded from
209
- denoising.
210
- dtype : dtype_like, optional
211
- Data type of output arrays. Either float32 or float64.
212
- The default is float64 unless the `signal` is float32.
213
- num_threads : int, optional
214
- Number of OpenMP threads to use for parallelization.
215
- By default, multi-threading is disabled.
216
- If zero, up to half of logical CPUs are used.
217
- OpenMP may not be available on all platforms.
218
-
219
- Returns
220
- -------
221
- ndarray
222
- Denoised signal of `dtype`.
223
- Spectra with integrated intensity below `vmin` are unchanged.
224
-
225
- References
226
- ----------
227
- .. [4] Harman RC, Lang RT, Kercher EM, Leven P, and Spring BQ.
228
- `Denoising multiplexed microscopy images in n-dimensional spectral space
229
- <https://doi.org/10.1364/BOE.463979>`_.
230
- *Biomed Opt Express*, 13(8): 4298-4309 (2022)
231
-
232
- Examples
233
- --------
234
- Denoise a hyperspectral image with a Gaussian filter width of 0.1 in
235
- spectral vector space using first and second harmonic:
236
-
237
- >>> signal = numpy.random.randint(0, 255, (8, 16, 16))
238
- >>> spectral_vector_denoise(signal, axis=0, sigma=0.1, harmonic=[1, 2])
239
- array([[[...]]])
240
-
241
- """
242
- num_threads = number_threads(num_threads)
243
-
244
- signal = numpy.asarray(signal)
245
- if axis == -1 or axis == signal.ndim - 1:
246
- axis = -1
247
- else:
248
- signal = numpy.moveaxis(signal, axis, -1)
249
- shape = signal.shape
250
- samples = shape[-1]
251
-
252
- if harmonic is None:
253
- harmonic = 1
254
- harmonic, _ = parse_harmonic(harmonic, samples // 2)
255
- num_harmonics = len(harmonic)
256
-
257
- if vmin is None or vmin < 0.0:
258
- vmin = 0.0
259
-
260
- sincos = numpy.empty((num_harmonics, samples, 2))
261
- for i, h in enumerate(harmonic):
262
- phase = numpy.linspace(
263
- 0,
264
- h * math.pi * 2.0,
265
- samples,
266
- endpoint=False,
267
- dtype=numpy.float64,
268
- )
269
- sincos[i, :, 0] = numpy.cos(phase)
270
- sincos[i, :, 1] = numpy.sin(phase)
271
-
272
- signal = numpy.ascontiguousarray(signal).reshape(-1, samples)
273
- size = signal.shape[0]
274
-
275
- if dtype is None:
276
- if signal.dtype.char == 'f':
277
- dtype = signal.dtype
278
- else:
279
- dtype = numpy.float64
280
- dtype = numpy.dtype(dtype)
281
- if dtype.char not in {'d', 'f'}:
282
- raise ValueError('dtype is not floating point')
283
-
284
- if spectral_vector is None:
285
- spectral_vector = numpy.zeros((size, num_harmonics * 2), dtype=dtype)
286
- _phasor_from_signal_vector(
287
- spectral_vector, signal, sincos, num_threads
288
- )
289
- else:
290
- spectral_vector = numpy.ascontiguousarray(spectral_vector, dtype=dtype)
291
- if spectral_vector.shape[:-1] != shape[:-1]:
292
- raise ValueError('signal and spectral_vector shape mismatch')
293
- spectral_vector = spectral_vector.reshape(
294
- -1, spectral_vector.shape[-1]
295
- )
296
-
297
- if dtype == signal.dtype:
298
- denoised = signal.copy()
299
- else:
300
- denoised = numpy.zeros(signal.shape, dtype=dtype)
301
- denoised[:] = signal
302
- integrated = numpy.zeros(size, dtype=dtype)
303
- _signal_denoise_vector(
304
- denoised, integrated, signal, spectral_vector, sigma, vmin, num_threads
305
- )
306
-
307
- denoised = denoised.reshape(shape) # type: ignore[assignment]
308
- if axis != -1:
309
- denoised = numpy.moveaxis(denoised, -1, axis)
310
- return denoised