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
@@ -1,8 +1,8 @@
|
|
1
1
|
"""Select regions of interest (cursors) from phasor coordinates.
|
2
2
|
|
3
|
-
The ``phasorpy.
|
3
|
+
The ``phasorpy.cursor`` module provides functions to:
|
4
4
|
|
5
|
-
- create masks for regions of
|
5
|
+
- create masks for regions of interest in the phasor space:
|
6
6
|
|
7
7
|
- :py:func:`mask_from_circular_cursor`
|
8
8
|
- :py:func:`mask_from_elliptic_cursor`
|
@@ -26,10 +26,7 @@ __all__ = [
|
|
26
26
|
from typing import TYPE_CHECKING
|
27
27
|
|
28
28
|
if TYPE_CHECKING:
|
29
|
-
from ._typing import
|
30
|
-
ArrayLike,
|
31
|
-
NDArray,
|
32
|
-
)
|
29
|
+
from ._typing import ArrayLike, Literal, NDArray
|
33
30
|
|
34
31
|
import numpy
|
35
32
|
|
@@ -80,12 +77,12 @@ def mask_from_circular_cursor(
|
|
80
77
|
------
|
81
78
|
ValueError
|
82
79
|
The array shapes of `real` and `imag` do not match.
|
83
|
-
The array shapes of `
|
84
|
-
one dimension.
|
80
|
+
The array shapes of `center_real`, `center_imag`, or `radius` have
|
81
|
+
more than one dimension.
|
85
82
|
|
86
83
|
See Also
|
87
84
|
--------
|
88
|
-
:ref:`
|
85
|
+
:ref:`sphx_glr_tutorials_api_phasorpy_cursor.py`
|
89
86
|
|
90
87
|
Examples
|
91
88
|
--------
|
@@ -139,8 +136,7 @@ def mask_from_elliptic_cursor(
|
|
139
136
|
*,
|
140
137
|
radius: ArrayLike = 0.05,
|
141
138
|
radius_minor: ArrayLike | None = None,
|
142
|
-
angle: ArrayLike | None = None,
|
143
|
-
align_semicircle: bool = False,
|
139
|
+
angle: ArrayLike | Literal['phase', 'semicircle'] | str | None = None,
|
144
140
|
) -> NDArray[numpy.bool_]:
|
145
141
|
"""Return masks for elliptic cursors of phasor coordinates.
|
146
142
|
|
@@ -159,21 +155,18 @@ def mask_from_elliptic_cursor(
|
|
159
155
|
radius_minor : array_like, optional, shape (n,)
|
160
156
|
Radii of ellipses along semi-minor axis.
|
161
157
|
By default, the ellipses are circular.
|
162
|
-
angle : array_like,
|
163
|
-
Rotation
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
Determines orientation of ellipses if `angle` is not provided.
|
168
|
-
If true, align the minor axes of the ellipses with the closest tangent
|
169
|
-
on the universal semicircle, else the unit circle (default).
|
158
|
+
angle : array_like or {'phase', 'semicircle'}, optional
|
159
|
+
Rotation angle of semi-major axis of elliptic cursors in radians.
|
160
|
+
If None or 'phase', align the minor axes of the ellipses with
|
161
|
+
the closest tangent on the unit circle.
|
162
|
+
If 'semicircle', align the ellipses with the universal semicircle.
|
170
163
|
|
171
164
|
Returns
|
172
165
|
-------
|
173
166
|
masks : ndarray
|
174
167
|
Boolean array of shape `(n, *real.shape)`.
|
175
|
-
The first dimension is omitted if `
|
176
|
-
are scalars.
|
168
|
+
The first dimension is omitted if `center_real`, `center_imag`,
|
169
|
+
`radius`, `radius_minor`, and `angle` are scalars.
|
177
170
|
Values are True if phasor coordinates are inside elliptic cursor,
|
178
171
|
else False.
|
179
172
|
|
@@ -181,12 +174,12 @@ def mask_from_elliptic_cursor(
|
|
181
174
|
------
|
182
175
|
ValueError
|
183
176
|
The array shapes of `real` and `imag` do not match.
|
184
|
-
The array shapes of `
|
185
|
-
one dimension.
|
177
|
+
The array shapes of `center_real`, `center_imag`, `radius`,
|
178
|
+
`radius_minor`, or `angle` have more than one dimension.
|
186
179
|
|
187
180
|
See Also
|
188
181
|
--------
|
189
|
-
:ref:`
|
182
|
+
:ref:`sphx_glr_tutorials_api_phasorpy_cursor.py`
|
190
183
|
|
191
184
|
Examples
|
192
185
|
--------
|
@@ -220,12 +213,17 @@ def mask_from_elliptic_cursor(
|
|
220
213
|
angle = 0.0
|
221
214
|
else:
|
222
215
|
radius_b = numpy.asarray(radius_minor)
|
216
|
+
|
223
217
|
if angle is None:
|
224
|
-
|
225
|
-
|
218
|
+
angle = numpy.arctan2(center_imag, center_real)
|
219
|
+
elif isinstance(angle, str):
|
220
|
+
# TODO: vectorize str type
|
221
|
+
if angle == 'phase':
|
222
|
+
angle = numpy.arctan2(center_imag, center_real)
|
223
|
+
elif angle == 'semicircle':
|
226
224
|
angle = numpy.arctan2(center_imag, center_real - 0.5)
|
227
225
|
else:
|
228
|
-
|
226
|
+
raise ValueError(f'invalid {angle=}')
|
229
227
|
|
230
228
|
angle_sin = numpy.sin(angle)
|
231
229
|
angle_cos = numpy.cos(angle)
|
@@ -320,10 +318,10 @@ def mask_from_polar_cursor(
|
|
320
318
|
|
321
319
|
See Also
|
322
320
|
--------
|
323
|
-
:ref:`
|
321
|
+
:ref:`sphx_glr_tutorials_api_phasorpy_cursor.py`
|
324
322
|
|
325
|
-
|
326
|
-
|
323
|
+
Examples
|
324
|
+
--------
|
327
325
|
Create mask from a single polar cursor:
|
328
326
|
|
329
327
|
>>> mask_from_polar_cursor([0.2, 0.5], [0.4, 0.5], 1.1, 1.2, 0.4, 0.5)
|
@@ -423,10 +421,10 @@ def pseudo_color(
|
|
423
421
|
|
424
422
|
See Also
|
425
423
|
--------
|
426
|
-
:ref:`
|
424
|
+
:ref:`sphx_glr_tutorials_api_phasorpy_cursor.py`
|
427
425
|
|
428
|
-
|
429
|
-
|
426
|
+
Examples
|
427
|
+
--------
|
430
428
|
Create pseudo-color image from single mask:
|
431
429
|
|
432
430
|
>>> pseudo_color([True, False, True]) # doctest: +NUMBER
|
phasorpy/datasets.py
CHANGED
@@ -2,14 +2,13 @@
|
|
2
2
|
|
3
3
|
The ``phasorpy.datasets`` module provides a :py:func:`fetch` function to
|
4
4
|
download data files from remote repositories and cache them in a local
|
5
|
-
directory.
|
6
|
-
``PHASORPY_DATA_DIR`` environment variable.
|
5
|
+
directory.
|
7
6
|
|
8
7
|
Datasets from the following repositories are available:
|
9
8
|
|
10
|
-
- `PhasorPy tests <https://zenodo.org/
|
11
|
-
- `LFD Workshop <https://zenodo.org/
|
12
|
-
- `FLUTE <https://zenodo.org/
|
9
|
+
- `PhasorPy tests <https://zenodo.org/records/8417894>`_
|
10
|
+
- `LFD Workshop <https://zenodo.org/records/8411056>`_
|
11
|
+
- `FLUTE <https://zenodo.org/records/8046636>`_
|
13
12
|
- `napari-flim-phasor-plotter
|
14
13
|
<https://github.com/zoccoler/napari-flim-phasor-plotter/tree/0.0.6/src/napari_flim_phasor_plotter/data>`_
|
15
14
|
- `Phasor-based multi-harmonic unmixing for in-vivo hyperspectral imaging
|
@@ -19,10 +18,18 @@ Datasets from the following repositories are available:
|
|
19
18
|
<https://zenodo.org/records/14026720>`_
|
20
19
|
- `Convallaria FLIM dataset in FLIM LABS JSON format
|
21
20
|
<https://zenodo.org/records/15007900>`_
|
21
|
+
- `FLIM and spectral dataset for GSLab
|
22
|
+
<https://figshare.com/articles/dataset/FLIM_dataset_for_GSLab/28067108>`_
|
23
|
+
- `Hyperspectral and FLIM dataset of LAURDAN-stained NIH-3T3 cells
|
24
|
+
<https://zenodo.org/records/16894639>`_
|
22
25
|
|
23
26
|
The implementation is based on the `Pooch <https://www.fatiando.org/pooch>`_
|
24
27
|
library.
|
25
28
|
|
29
|
+
The default cache location is determined by the Pooch library, but can be
|
30
|
+
overridden by setting the ``PHASORPY_DATA_DIR`` environment variable to
|
31
|
+
a custom directory path.
|
32
|
+
|
26
33
|
"""
|
27
34
|
|
28
35
|
from __future__ import annotations
|
@@ -397,6 +404,60 @@ FLIMLABS = pooch.create(
|
|
397
404
|
},
|
398
405
|
)
|
399
406
|
|
407
|
+
FIGSHARE_28067108 = pooch.create(
|
408
|
+
path=pooch.os_cache('phasorpy'),
|
409
|
+
base_url=(
|
410
|
+
'https://github.com/phasorpy/phasorpy-data/raw/main/figshare_28067108'
|
411
|
+
),
|
412
|
+
env=ENV,
|
413
|
+
registry={
|
414
|
+
'38_Hoechst_Golgi_Mito_Lyso_CellMask_mixture.lsm': (
|
415
|
+
'sha256:'
|
416
|
+
'092ac050edf55e26dcda8cba10122408c6f1b81d19accf07214385d6eebfcf3e'
|
417
|
+
),
|
418
|
+
'38_Hoechst_Golgi_Mito_Lyso_CellMask_mixture.lsm.zip': (
|
419
|
+
'sha256:'
|
420
|
+
'19298d12b16a58c1de3f532398c154d89df3f2286a1a6ee38a29f1fbefac78fc'
|
421
|
+
),
|
422
|
+
'40x800fov19LP500p30_4dyes_a1_$CG0T_ch1_h1_h2.R64': (
|
423
|
+
'sha256:'
|
424
|
+
'ad1bae8c3624eecb8927283e333ab5ea3fb864d6c5bb0f155da83c7a8aa40df3'
|
425
|
+
),
|
426
|
+
'40x800fov19LP500p30_AcO_a4_$CG0T_ch1_h1_h2.R64': (
|
427
|
+
'sha256:'
|
428
|
+
'd1de46652fbed0626ae6ad6334c41baafb0a5ae9e0e15e49221eb540a666d7e7'
|
429
|
+
),
|
430
|
+
'40x800fov19LP500p30_EtBr_a5_$CG0T_ch1_h1_h2.R64': (
|
431
|
+
'sha256:'
|
432
|
+
'9f4dd2d328877d2b18e064a3fb20d2a5318c833caa5a3cea15bafbf8df5d588a'
|
433
|
+
),
|
434
|
+
'40x800fov19LP500p30_NucB_a6_$CG0T_ch1_h1_h2.R64': (
|
435
|
+
'sha256:'
|
436
|
+
'74e378ddae84f6532d7ac05f8639feb42541eecb278c842e9a18565c201242f8'
|
437
|
+
),
|
438
|
+
'40x800fov19LP500p30_RoseBx100_a1_$CG0T_ch1_h1_h2.R64': (
|
439
|
+
'sha256:'
|
440
|
+
'898e0e7e96f8b6c2ee83940f94026dd9006e6fc46bb7e56c62dd22855079e279'
|
441
|
+
),
|
442
|
+
'Coumarin6.txt': (
|
443
|
+
'sha256:'
|
444
|
+
'6a3ae0f886b512c26cea8bc4b4cd9027ee8d4b7dd437a1eeb9621947e19c3c88'
|
445
|
+
),
|
446
|
+
'Hoechst_Lyso_Golgi_MitoTracker_CellMask.csv': (
|
447
|
+
'sha256:'
|
448
|
+
'1d000ae6268e6d290ab1cfdf2d31d840f27183cba75b380f116e1049696bb3a4'
|
449
|
+
),
|
450
|
+
'Mosaic04_10x3_FOV600_z95_32A1_t06_uncalibrated.ref': (
|
451
|
+
'sha256:'
|
452
|
+
'3c23ca9a21a3ef762765fc81591dd0cf9bdaf3e77fa4faa3bf60da69e6ab1127'
|
453
|
+
),
|
454
|
+
'Mosaic04_10x3_FOV600_z95_32A1_t06_uncalibrated.ref.zip': (
|
455
|
+
'sha256:'
|
456
|
+
'001a472d81b3d348ac4680c73de877ee809c2f229f882ef015225e208a7273fb'
|
457
|
+
),
|
458
|
+
},
|
459
|
+
)
|
460
|
+
|
400
461
|
FIGSHARE_22336594 = pooch.create(
|
401
462
|
path=pooch.os_cache('phasorpy'),
|
402
463
|
base_url=(
|
@@ -447,6 +508,34 @@ FIGSHARE_22336594_EXPORTED = pooch.create(
|
|
447
508
|
},
|
448
509
|
)
|
449
510
|
|
511
|
+
ZENODO_16894639 = pooch.create(
|
512
|
+
path=pooch.os_cache('phasorpy'),
|
513
|
+
base_url=(
|
514
|
+
'https://github.com/phasorpy/phasorpy-data/raw/main/zenodo_16894639'
|
515
|
+
if DATA_ON_GITHUB
|
516
|
+
else 'doi:10.5281/zenodo.16894639'
|
517
|
+
),
|
518
|
+
env=ENV,
|
519
|
+
registry={
|
520
|
+
'04 NIH3T3LAURDAN8meanspectra.lsm': (
|
521
|
+
'sha256:'
|
522
|
+
'27173be5b57a1a0f40ad6e89df1fd1d6d20bdd1e0a80e469acb47cf0bc103a1c'
|
523
|
+
),
|
524
|
+
'04NIH3T3_LAURDAN_000$CC0Z.fbd': (
|
525
|
+
'sha256:'
|
526
|
+
'9c8e7d79ef6ce4cce7366c843d34f5b4544f4123fe88307b8df5954a1fe4a799'
|
527
|
+
),
|
528
|
+
'cumarinech1_780LAURDAN_000$CC0Z.fbd': (
|
529
|
+
'sha256:'
|
530
|
+
'1b7b513680973c79df697b9cefdf2104f5d32346e24c9071336f76a7b82e9313'
|
531
|
+
),
|
532
|
+
'cumarinech2_780LAURDAN_000$CC0Z.fbd': (
|
533
|
+
'sha256:'
|
534
|
+
'5c9cf9694652cc9c344e67b5650fd49683fa7fdcec39e56ccbf0bdd2f7dadf15'
|
535
|
+
),
|
536
|
+
},
|
537
|
+
)
|
538
|
+
|
450
539
|
MISC = pooch.create(
|
451
540
|
path=pooch.os_cache('phasorpy'),
|
452
541
|
base_url='https://github.com/phasorpy/phasorpy-data/raw/main/misc',
|
@@ -469,11 +558,18 @@ REPOSITORIES: dict[str, pooch.Pooch] = {
|
|
469
558
|
'zenodo-14976703': ZENODO_14976703,
|
470
559
|
'convallaria-fbd': CONVALLARIA_FBD,
|
471
560
|
'flimlabs': FLIMLABS,
|
561
|
+
'figshare_28067108': FIGSHARE_28067108,
|
472
562
|
'figshare_22336594': FIGSHARE_22336594,
|
473
563
|
'figshare_22336594_exported': FIGSHARE_22336594_EXPORTED,
|
564
|
+
'zenodo-16894639': ZENODO_16894639,
|
474
565
|
'misc': MISC,
|
475
566
|
}
|
476
|
-
"""Pooch
|
567
|
+
"""Dictionary mapping repository names to Pooch repository objects.
|
568
|
+
|
569
|
+
Each repository provides access to a specific collection of sample data files
|
570
|
+
that can be fetched using the :py:func:`fetch` function.
|
571
|
+
|
572
|
+
"""
|
477
573
|
|
478
574
|
|
479
575
|
def fetch(
|
@@ -489,21 +585,35 @@ def fetch(
|
|
489
585
|
|
490
586
|
Parameters
|
491
587
|
----------
|
492
|
-
*args : str
|
588
|
+
*args : str, iterable of str, or pooch.Pooch
|
493
589
|
Name(s) of file(s) or repositories to fetch from local storage.
|
590
|
+
Can be:
|
591
|
+
|
592
|
+
- File names (for example, 'simfcs.r64')
|
593
|
+
- Repository names (for example, 'tests', 'flute')
|
594
|
+
- Pooch repository objects
|
595
|
+
- Iterables of the above
|
596
|
+
|
494
597
|
If omitted, return files in all repositories.
|
495
598
|
extract_dir : str or None, optional
|
496
599
|
Path, relative to cache location, where ZIP files will be unpacked.
|
497
600
|
return_scalar : bool, optional
|
498
601
|
If true (default), return single path as string, else tuple of string.
|
499
602
|
**kwargs
|
500
|
-
|
603
|
+
Optional arguments passed to :py:func:`pooch.fetch`.
|
501
604
|
For example, ``progressbar=True``.
|
502
605
|
|
503
606
|
Returns
|
504
607
|
-------
|
505
608
|
str or tuple of str
|
506
609
|
Absolute path(s) of file(s) in local storage.
|
610
|
+
Returns a single string if only one file and `return_scalar=True`,
|
611
|
+
otherwise returns a tuple of strings.
|
612
|
+
|
613
|
+
Raises
|
614
|
+
------
|
615
|
+
ValueError
|
616
|
+
If specified file is not found in any repository.
|
507
617
|
|
508
618
|
Examples
|
509
619
|
--------
|
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
|
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(
|
@@ -68,7 +60,6 @@ def anscombe_transform(
|
|
68
60
|
|
69
61
|
References
|
70
62
|
----------
|
71
|
-
|
72
63
|
.. [1] Anscombe FJ.
|
73
64
|
`The transformation of Poisson, binomial and negative-binomial data
|
74
65
|
<https://doi.org/10.2307/2332343>`_.
|
@@ -124,19 +115,18 @@ def anscombe_transform_inverse(
|
|
124
115
|
x = 1/4 \cdot z^2
|
125
116
|
+ 1/4 \cdot \sqrt{3/2} \cdot z^{-1}
|
126
117
|
- 11/8 \cdot z^{-2}
|
127
|
-
+ 5/8 \cdot \sqrt
|
118
|
+
+ 5/8 \cdot \sqrt{3/2} \cdot z^{-3}
|
128
119
|
- 1/8
|
129
120
|
|
130
121
|
References
|
131
122
|
----------
|
132
|
-
|
133
123
|
.. [2] Makitalo M, and Foi A.
|
134
124
|
`A closed-form approximation of the exact unbiased inverse of the
|
135
125
|
Anscombe variance-stabilizing transformation
|
136
126
|
<https://doi.org/10.1109/TIP.2011.2121085>`_.
|
137
|
-
*IEEE Trans Image Process*, 20(9): 2697-8 (2011)
|
127
|
+
*IEEE Trans Image Process*, 20(9): 2697-8 (2011)
|
138
128
|
|
139
|
-
.. [3] Makitalo M, and Foi A
|
129
|
+
.. [3] Makitalo M, and Foi A.
|
140
130
|
`Optimal inversion of the generalized Anscombe transformation for
|
141
131
|
Poisson-Gaussian noise
|
142
132
|
<https://doi.org/10.1109/TIP.2012.2202675>`_,
|
@@ -156,157 +146,3 @@ def anscombe_transform_inverse(
|
|
156
146
|
data, **kwargs
|
157
147
|
)
|
158
148
|
return _anscombe_inverse(data, **kwargs) # type: ignore[no-any-return]
|
159
|
-
|
160
|
-
|
161
|
-
def spectral_vector_denoise(
|
162
|
-
signal: ArrayLike,
|
163
|
-
/,
|
164
|
-
spectral_vector: ArrayLike | None = None,
|
165
|
-
*,
|
166
|
-
axis: int = -1,
|
167
|
-
harmonic: int | Sequence[int] | Literal['all'] | str | None = None,
|
168
|
-
sigma: float = 0.05,
|
169
|
-
vmin: float | None = None,
|
170
|
-
dtype: DTypeLike | None = None,
|
171
|
-
num_threads: int | None = None,
|
172
|
-
) -> NDArray[Any]:
|
173
|
-
"""Return spectral-vector-denoised signal.
|
174
|
-
|
175
|
-
The spectral vector denoising algorithm is based on a Gaussian weighted
|
176
|
-
average calculation, with weights obtained in n-dimensional Chebyshev or
|
177
|
-
Fourier space [4]_.
|
178
|
-
|
179
|
-
Parameters
|
180
|
-
----------
|
181
|
-
signal : array_like
|
182
|
-
Hyperspectral data to be denoised.
|
183
|
-
A minimum of three samples are required along `axis`.
|
184
|
-
The samples must be uniformly spaced.
|
185
|
-
spectral_vector : array_like, optional
|
186
|
-
Spectral vector.
|
187
|
-
For example, phasor coordinates, PCA projected phasor coordinates,
|
188
|
-
or Chebyshev coefficients.
|
189
|
-
Must be of same shape as `signal` with `axis` removed and axis
|
190
|
-
containing spectral space appended.
|
191
|
-
If None (default), phasor coordinates are calculated at specified
|
192
|
-
`harmonic`.
|
193
|
-
axis : int, optional, default: -1
|
194
|
-
Axis over which `spectral_vector` is computed if not provided.
|
195
|
-
The default is the last axis (-1).
|
196
|
-
harmonic : int, sequence of int, or 'all', optional
|
197
|
-
Harmonics to include in calculating `spectral_vector`.
|
198
|
-
If `'all'`, include all harmonics for `signal` samples along `axis`.
|
199
|
-
Else, harmonics must be at least one and no larger than half the
|
200
|
-
number of `signal` samples along `axis`.
|
201
|
-
The default is the first harmonic (fundamental frequency).
|
202
|
-
A minimum of `harmonic * 2 + 1` samples are required along `axis`
|
203
|
-
to calculate correct phasor coordinates at `harmonic`.
|
204
|
-
sigma : float, default: 0.05
|
205
|
-
Width of Gaussian filter in spectral vector space.
|
206
|
-
Weighted averages are calculated using the spectra of signal items
|
207
|
-
within an spectral vector Euclidean distance of `3 * sigma` and
|
208
|
-
intensity above `vmin`.
|
209
|
-
vmin : float, optional
|
210
|
-
Signal intensity along `axis` below which not to include in denoising.
|
211
|
-
dtype : dtype_like, optional
|
212
|
-
Data type of output arrays. Either float32 or float64.
|
213
|
-
The default is float64 unless the `signal` is float32.
|
214
|
-
num_threads : int, optional
|
215
|
-
Number of OpenMP threads to use for parallelization.
|
216
|
-
By default, multi-threading is disabled.
|
217
|
-
If zero, up to half of logical CPUs are used.
|
218
|
-
OpenMP may not be available on all platforms.
|
219
|
-
|
220
|
-
Returns
|
221
|
-
-------
|
222
|
-
ndarray
|
223
|
-
Denoised signal of `dtype`.
|
224
|
-
Spectra with integrated intensity below `vmin` are unchanged.
|
225
|
-
|
226
|
-
References
|
227
|
-
----------
|
228
|
-
|
229
|
-
.. [4] Harman RC, Lang RT, Kercher EM, Leven P, and Spring BQ.
|
230
|
-
`Denoising multiplexed microscopy images in n-dimensional spectral space
|
231
|
-
<https://doi.org/10.1364/BOE.463979>`_.
|
232
|
-
*Biomed Opt Express*, 13(8): 4298-4309 (2022)
|
233
|
-
|
234
|
-
Examples
|
235
|
-
--------
|
236
|
-
Denoise a hyperspectral image with a Gaussian filter width of 0.1 in
|
237
|
-
spectral vector space using first and second harmonic:
|
238
|
-
|
239
|
-
>>> signal = numpy.random.randint(0, 255, (8, 16, 16))
|
240
|
-
>>> spectral_vector_denoise(signal, axis=0, sigma=0.1, harmonic=[1, 2])
|
241
|
-
array([[[...]]])
|
242
|
-
|
243
|
-
"""
|
244
|
-
num_threads = number_threads(num_threads)
|
245
|
-
|
246
|
-
signal = numpy.asarray(signal)
|
247
|
-
if axis == -1 or axis == signal.ndim - 1:
|
248
|
-
axis = -1
|
249
|
-
else:
|
250
|
-
signal = numpy.moveaxis(signal, axis, -1)
|
251
|
-
shape = signal.shape
|
252
|
-
samples = shape[-1]
|
253
|
-
|
254
|
-
if harmonic is None:
|
255
|
-
harmonic = 1
|
256
|
-
harmonic, _ = parse_harmonic(harmonic, samples // 2)
|
257
|
-
num_harmonics = len(harmonic)
|
258
|
-
|
259
|
-
if vmin is None or vmin < 0.0:
|
260
|
-
vmin = 0.0
|
261
|
-
|
262
|
-
sincos = numpy.empty((num_harmonics, samples, 2))
|
263
|
-
for i, h in enumerate(harmonic):
|
264
|
-
phase = numpy.linspace(
|
265
|
-
0,
|
266
|
-
h * math.pi * 2.0,
|
267
|
-
samples,
|
268
|
-
endpoint=False,
|
269
|
-
dtype=numpy.float64,
|
270
|
-
)
|
271
|
-
sincos[i, :, 0] = numpy.cos(phase)
|
272
|
-
sincos[i, :, 1] = numpy.sin(phase)
|
273
|
-
|
274
|
-
signal = numpy.ascontiguousarray(signal).reshape(-1, samples)
|
275
|
-
size = signal.shape[0]
|
276
|
-
|
277
|
-
if dtype is None:
|
278
|
-
if signal.dtype.char == 'f':
|
279
|
-
dtype = signal.dtype
|
280
|
-
else:
|
281
|
-
dtype = numpy.float64
|
282
|
-
dtype = numpy.dtype(dtype)
|
283
|
-
if dtype.char not in {'d', 'f'}:
|
284
|
-
raise ValueError('dtype is not floating point')
|
285
|
-
|
286
|
-
if spectral_vector is None:
|
287
|
-
spectral_vector = numpy.zeros((size, num_harmonics * 2), dtype=dtype)
|
288
|
-
_phasor_from_signal_vector(
|
289
|
-
spectral_vector, signal, sincos, num_threads
|
290
|
-
)
|
291
|
-
else:
|
292
|
-
spectral_vector = numpy.ascontiguousarray(spectral_vector, dtype=dtype)
|
293
|
-
if spectral_vector.shape[:-1] != shape[:-1]:
|
294
|
-
raise ValueError('signal and spectral_vector shape mismatch')
|
295
|
-
spectral_vector = spectral_vector.reshape(
|
296
|
-
-1, spectral_vector.shape[-1]
|
297
|
-
)
|
298
|
-
|
299
|
-
if dtype == signal.dtype:
|
300
|
-
denoised = signal.copy()
|
301
|
-
else:
|
302
|
-
denoised = numpy.zeros(signal.shape, dtype=dtype)
|
303
|
-
denoised[:] = signal
|
304
|
-
integrated = numpy.zeros(size, dtype=dtype)
|
305
|
-
_signal_denoise_vector(
|
306
|
-
denoised, integrated, signal, spectral_vector, sigma, vmin, num_threads
|
307
|
-
)
|
308
|
-
|
309
|
-
denoised = denoised.reshape(shape)
|
310
|
-
if axis != -1:
|
311
|
-
denoised = numpy.moveaxis(denoised, -1, axis)
|
312
|
-
return denoised
|