phasorpy 0.7__cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.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.
@@ -0,0 +1,310 @@
1
+ """Experimental functions.
2
+
3
+ The ``phasorpy.experimental`` module provides functions related to phasor
4
+ analysis for evaluation.
5
+ The functions may be removed or moved to other modules in future releases.
6
+
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ __all__ = [
12
+ 'anscombe_transform',
13
+ 'anscombe_transform_inverse',
14
+ 'spectral_vector_denoise',
15
+ ]
16
+
17
+ import math
18
+ from typing import TYPE_CHECKING
19
+
20
+ if TYPE_CHECKING:
21
+ from ._typing import Any, NDArray, ArrayLike, DTypeLike, Literal, Sequence
22
+
23
+ import numpy
24
+
25
+ from ._phasorpy import (
26
+ _anscombe,
27
+ _anscombe_inverse,
28
+ _anscombe_inverse_approx,
29
+ _phasor_from_signal_vector,
30
+ _signal_denoise_vector,
31
+ )
32
+ from ._utils import parse_harmonic
33
+ from .utils import number_threads
34
+
35
+
36
+ def anscombe_transform(
37
+ data: ArrayLike,
38
+ /,
39
+ **kwargs: Any,
40
+ ) -> NDArray[Any]:
41
+ r"""Return Anscombe variance-stabilizing transformation.
42
+
43
+ The Anscombe transformation normalizes the standard deviation of noisy,
44
+ Poisson-distributed data.
45
+ It can be used to transform un-normalized phasor coordinates to
46
+ approximate standard Gaussian distributions.
47
+
48
+ Parameters
49
+ ----------
50
+ data : array_like
51
+ Noisy Poisson-distributed data to be transformed.
52
+ **kwargs
53
+ Optional `arguments passed to numpy universal functions
54
+ <https://numpy.org/doc/stable/reference/ufuncs.html#ufuncs-kwargs>`_.
55
+
56
+ Returns
57
+ -------
58
+ ndarray
59
+ Anscombe-transformed data with variance of approximately 1.
60
+
61
+ Notes
62
+ -----
63
+ The Anscombe transformation according to [1]_:
64
+
65
+ .. math::
66
+
67
+ z = 2 \cdot \sqrt{x + 3 / 8}
68
+
69
+ References
70
+ ----------
71
+ .. [1] Anscombe FJ.
72
+ `The transformation of Poisson, binomial and negative-binomial data
73
+ <https://doi.org/10.2307/2332343>`_.
74
+ *Biometrika*, 35(3-4): 246-254 (1948)
75
+
76
+ Examples
77
+ --------
78
+
79
+ >>> z = anscombe_transform(numpy.random.poisson(10, 10000))
80
+ >>> numpy.allclose(numpy.std(z), 1.0, atol=0.1)
81
+ True
82
+
83
+ """
84
+ return _anscombe(data, **kwargs) # type: ignore[no-any-return]
85
+
86
+
87
+ def anscombe_transform_inverse(
88
+ data: ArrayLike,
89
+ /,
90
+ *,
91
+ approx: bool = False,
92
+ **kwargs: Any,
93
+ ) -> NDArray[Any]:
94
+ r"""Return inverse Anscombe transformation.
95
+
96
+ Parameters
97
+ ----------
98
+ data : array_like
99
+ Anscombe-transformed data.
100
+ approx : bool, default: False
101
+ If true, return approximation of exact unbiased inverse.
102
+ **kwargs
103
+ Optional `arguments passed to numpy universal functions
104
+ <https://numpy.org/doc/stable/reference/ufuncs.html#ufuncs-kwargs>`_.
105
+
106
+ Returns
107
+ -------
108
+ ndarray
109
+ Inverse Anscombe-transformed data.
110
+
111
+ Notes
112
+ -----
113
+ The inverse Anscombe transformation according to [1]_:
114
+
115
+ .. math::
116
+
117
+ x = (z / 2.0)^2 - 3 / 8
118
+
119
+ The approximate inverse Anscombe transformation according to [2]_ and [3]_:
120
+
121
+ .. math::
122
+
123
+ x = 1/4 \cdot z^2
124
+ + 1/4 \cdot \sqrt{3/2} \cdot z^{-1}
125
+ - 11/8 \cdot z^{-2}
126
+ + 5/8 \cdot \sqrt{3/2} \cdot z^{-3}
127
+ - 1/8
128
+
129
+ References
130
+ ----------
131
+ .. [2] Makitalo M, and Foi A.
132
+ `A closed-form approximation of the exact unbiased inverse of the
133
+ Anscombe variance-stabilizing transformation
134
+ <https://doi.org/10.1109/TIP.2011.2121085>`_.
135
+ *IEEE Trans Image Process*, 20(9): 2697-8 (2011)
136
+
137
+ .. [3] Makitalo M, and Foi A.
138
+ `Optimal inversion of the generalized Anscombe transformation for
139
+ Poisson-Gaussian noise
140
+ <https://doi.org/10.1109/TIP.2012.2202675>`_,
141
+ *IEEE Trans Image Process*, 22(1): 91-103 (2013)
142
+
143
+ Examples
144
+ --------
145
+
146
+ >>> x = numpy.random.poisson(10, 100)
147
+ >>> x2 = anscombe_transform_inverse(anscombe_transform(x))
148
+ >>> numpy.allclose(x, x2, atol=1e-3)
149
+ True
150
+
151
+ """
152
+ if approx:
153
+ return _anscombe_inverse_approx( # type: ignore[no-any-return]
154
+ data, **kwargs
155
+ )
156
+ 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
@@ -0,0 +1,138 @@
1
+ """Read and write time-resolved and hyperspectral image file formats.
2
+
3
+ The ``phasorpy.io`` module provides functions to:
4
+
5
+ - read time-resolved and hyperspectral signals, as well as metadata from
6
+ many file formats used in bio-imaging:
7
+
8
+ - :py:func:`signal_from_lif` - Leica LIF and XLEF
9
+ - :py:func:`signal_from_lsm` - Zeiss LSM
10
+ - :py:func:`signal_from_ptu` - PicoQuant PTU
11
+ - :py:func:`signal_from_pqbin` - PicoQuant BIN
12
+ - :py:func:`signal_from_sdt` - Becker & Hickl SDT
13
+ - :py:func:`signal_from_fbd` - FLIMbox FBD
14
+ - :py:func:`signal_from_flimlabs_json` - FLIM LABS JSON
15
+ - :py:func:`signal_from_imspector_tiff` - ImSpector FLIM TIFF
16
+ - :py:func:`signal_from_flif` - FlimFast FLIF
17
+ - :py:func:`signal_from_b64` - SimFCS B64
18
+ - :py:func:`signal_from_z64` - SimFCS Z64
19
+ - :py:func:`signal_from_bhz` - SimFCS BHZ
20
+ - :py:func:`signal_from_bh` - SimFCS B&H
21
+
22
+ - read phasor coordinates, lifetime images, and metadata from
23
+ specialized file formats:
24
+
25
+ - :py:func:`phasor_from_ometiff` - PhasorPy OME-TIFF
26
+ - :py:func:`phasor_from_ifli` - ISS IFLI
27
+ - :py:func:`phasor_from_lif` - Leica LIF and XLEF
28
+ - :py:func:`phasor_from_flimlabs_json` - FLIM LABS JSON
29
+ - :py:func:`phasor_from_simfcs_referenced` - SimFCS REF and R64
30
+ - :py:func:`lifetime_from_lif` - Leica LIF and XLEF
31
+
32
+ - write phasor coordinate images to OME-TIFF and SimFCS file formats:
33
+
34
+ - :py:func:`phasor_to_ometiff`
35
+ - :py:func:`phasor_to_simfcs_referenced`
36
+
37
+ Support for other file formats is being considered:
38
+
39
+ - OME-TIFF
40
+ - Zeiss CZI
41
+ - Nikon ND2
42
+ - Olympus OIB/OIF
43
+ - Olympus OIR
44
+
45
+ The functions are implemented as minimal wrappers around specialized
46
+ third-party file reader libraries, currently
47
+ `tifffile <https://github.com/cgohlke/tifffile>`_,
48
+ `ptufile <https://github.com/cgohlke/ptufile>`_,
49
+ `liffile <https://github.com/cgohlke/liffile>`_,
50
+ `sdtfile <https://github.com/cgohlke/sdtfile>`_, and
51
+ `lfdfiles <https://github.com/cgohlke/lfdfiles>`_.
52
+ For advanced or unsupported use cases, consider using these libraries directly.
53
+
54
+ The signal-reading functions typically have the following signature::
55
+
56
+ signal_from_ext(
57
+ filename: str | PathLike,
58
+ /,
59
+ **kwargs
60
+ ): -> xarray.DataArray
61
+
62
+ where ``ext`` indicates the file format and ``kwargs`` are optional arguments
63
+ passed to the underlying file reader library or used to select which data is
64
+ returned. The returned `xarray.DataArray
65
+ <https://docs.xarray.dev/en/stable/user-guide/data-structures.html>`_
66
+ contains an N-dimensional array with labeled coordinates, dimensions, and
67
+ attributes:
68
+
69
+ - ``data`` or ``values`` (*array_like*)
70
+
71
+ Numpy array or array-like holding the array's values.
72
+
73
+ - ``dims`` (*tuple of str*)
74
+
75
+ :ref:`Axes character codes <axes>` for each dimension in ``data``.
76
+ For example, ``('T', 'C', 'Y', 'X')`` defines the dimension order in a
77
+ 4-dimensional array of a time-series of multi-channel images.
78
+
79
+ - ``coords`` (*dict_like[str, array_like]*)
80
+
81
+ Coordinate arrays labelling each point in the data array.
82
+ The keys are :ref:`axes character codes <axes>`.
83
+ Values are 1-dimensional arrays of numbers or strings.
84
+ For example, ``coords['C']`` could be an array of emission wavelengths.
85
+
86
+ - ``attrs`` (*dict[str, Any]*)
87
+
88
+ Arbitrary metadata such as measurement or calibration parameters required to
89
+ interpret the data values.
90
+ For example, the laser repetition frequency of a time-resolved measurement.
91
+
92
+ .. _axes:
93
+
94
+ Axes character codes from the OME model and tifffile library are used as
95
+ ``dims`` items and ``coords`` keys:
96
+
97
+ - ``'X'`` : width (OME)
98
+ - ``'Y'`` : height (OME)
99
+ - ``'Z'`` : depth (OME)
100
+ - ``'S'`` : sample (color components or phasor coordinates)
101
+ - ``'I'`` : sequence (of images, frames, or planes)
102
+ - ``'T'`` : time (OME)
103
+ - ``'C'`` : channel (OME. Acquisition path or emission wavelength)
104
+ - ``'A'`` : angle (OME)
105
+ - ``'P'`` : phase (OME. In LSM, ``'P'`` maps to position)
106
+ - ``'R'`` : tile (OME. Region, position, or mosaic)
107
+ - ``'H'`` : lifetime histogram (OME)
108
+ - ``'E'`` : lambda (OME. Excitation wavelength)
109
+ - ``'F'`` : frequency (ISS)
110
+ - ``'Q'`` : other (OME. Harmonics in PhasorPy TIFF)
111
+ - ``'L'`` : exposure (FluoView)
112
+ - ``'V'`` : event (FluoView)
113
+ - ``'M'`` : mosaic (LSM 6)
114
+ - ``'J'`` : column (NDTiff)
115
+ - ``'K'`` : row (NDTiff)
116
+
117
+ """
118
+
119
+ from __future__ import annotations
120
+
121
+ __all__: list[str] = []
122
+
123
+ from .._utils import init_module
124
+ from ._flimlabs import *
125
+ from ._leica import *
126
+ from ._ometiff import *
127
+ from ._other import *
128
+ from ._simfcs import *
129
+
130
+ # The `init_module()` function dynamically populates the `__all__` list with
131
+ # all public symbols imported from submodules or defined in this module.
132
+ # Any name not starting with an underscore will be automatically exported
133
+ # when using "from phasorpy.io import *"
134
+
135
+ init_module(globals())
136
+ del init_module
137
+
138
+ # flake8: noqa: F401, F403