phasorpy 0.4__cp313-cp313-win_amd64.whl → 0.6__cp313-cp313-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/datasets.py CHANGED
@@ -14,8 +14,11 @@ Datasets from the following repositories are available:
14
14
  <https://github.com/zoccoler/napari-flim-phasor-plotter/tree/0.0.6/src/napari_flim_phasor_plotter/data>`_
15
15
  - `Phasor-based multi-harmonic unmixing for in-vivo hyperspectral imaging
16
16
  <https://zenodo.org/records/13625087>`_
17
+ (`second record <https://zenodo.org/records/14860228>`_)
17
18
  - `Convallaria slice acquired with time-resolved 2-photon microscope
18
19
  <https://zenodo.org/records/14026720>`_
20
+ - `Convallaria FLIM dataset in FLIM LABS JSON format
21
+ <https://zenodo.org/records/15007900>`_
19
22
 
20
23
  The implementation is based on the `Pooch <https://www.fatiando.org/pooch>`_
21
24
  library.
@@ -266,14 +269,15 @@ ZENODO_13625087 = pooch.create(
266
269
  base_url=(
267
270
  'https://github.com/phasorpy/phasorpy-data/raw/main/zenodo_13625087'
268
271
  # if DATA_ON_GITHUB
269
- # else 'doi:10.1088/2050-6120/ac9ae9'
272
+ # else 'doi:10.1088/2050-6120/ac9ae9' # TODO: not working with Pooch
270
273
  ),
271
274
  env=ENV,
272
275
  registry={
273
- '33_Hoechst_Golgi_Mito_Lyso_CellMAsk_404_488_561_633_SP.lsm': (
274
- 'sha256:'
275
- '68fcefcad4e750e9ec7068820e455258c986f6a9b724e66744a28bbbb689f986'
276
- ),
276
+ # part of ZENODO_14860228
277
+ # '33_Hoechst_Golgi_Mito_Lyso_CellMAsk_404_488_561_633_SP.lsm': (
278
+ # 'sha256:'
279
+ # '68fcefcad4e750e9ec7068820e455258c986f6a9b724e66744a28bbbb689f986'
280
+ # ),
277
281
  '34_Hoechst_Golgi_Mito_Lyso_CellMAsk_404_488_561_633_SP.lsm': (
278
282
  'sha256:'
279
283
  '5c0b7d76c274fd64891fca2507013b7c8c9979d8131ce282fac55fd24fbb38bd'
@@ -289,9 +293,65 @@ ZENODO_13625087 = pooch.create(
289
293
  },
290
294
  )
291
295
 
296
+ ZENODO_14860228 = pooch.create(
297
+ path=pooch.os_cache('phasorpy'),
298
+ base_url=(
299
+ 'https://github.com/phasorpy/phasorpy-data/raw/main/zenodo_14860228'
300
+ if DATA_ON_GITHUB
301
+ else 'doi:10.5281/zenodo.14860228'
302
+ ),
303
+ env=ENV,
304
+ registry={
305
+ '38_Hoechst_Golgi_Mito_Lyso_CellMAsk_404_488_561_633_SP.lsm': (
306
+ 'sha256:'
307
+ '092ac050edf55e26dcda8cba10122408c6f1b81d19accf07214385d6eebfcf3e'
308
+ ),
309
+ 'spectral cell mask.lsm': (
310
+ 'sha256:'
311
+ 'c4c2c567bd99ef4930d7278794d4e3daebaad0375c0852a5ab86a2ea056f4fe3'
312
+ ),
313
+ 'spectral golgi.lsm': (
314
+ 'sha256:'
315
+ 'd0a5079d9ed18b1248434f3f6d4b2b240fb034891121262cfe9dfec64d8429cd'
316
+ ),
317
+ 'spectral hoehst.lsm': (
318
+ 'sha256:'
319
+ '3ee44a7f9f125698bb5e34746d9723669f67c520ffbf21244757d7fc25dbbb88'
320
+ ),
321
+ 'spectral lyso tracker green.lsm': (
322
+ 'sha256:'
323
+ '0964448649e2c73a57f5ca0c705c86511fb4625c0a2af0d7850dfa39698fcbb9'
324
+ ),
325
+ 'spectral mito tracker.lsm': (
326
+ 'sha256:'
327
+ '99b9892b247256ebf8a9917c662bc7bb66a8daf3b5db950fbbb191de0cd35b37'
328
+ ),
329
+ },
330
+ )
331
+
332
+ ZENODO_14976703 = pooch.create(
333
+ path=pooch.os_cache('phasorpy'),
334
+ base_url=(
335
+ 'https://github.com/phasorpy/phasorpy-data/raw/main/zenodo_14976703'
336
+ if DATA_ON_GITHUB
337
+ else 'doi:10.5281/zenodo.14976703'
338
+ ),
339
+ env=ENV,
340
+ registry={
341
+ 'Convalaria_LambdaScan.lif': (
342
+ 'sha256:'
343
+ '27f1282cf02f87e11f8c7d3064066a4517ad4c9c769c796b32e459774f18c62a'
344
+ ),
345
+ },
346
+ )
347
+
292
348
  CONVALLARIA_FBD = pooch.create(
293
349
  path=pooch.os_cache('phasorpy'),
294
- base_url='doi:10.5281/zenodo.14026719',
350
+ base_url=(
351
+ 'https://github.com/phasorpy/phasorpy-data/raw/main/zenodo_14026720'
352
+ if DATA_ON_GITHUB
353
+ else 'doi:10.5281/zenodo.14026719'
354
+ ),
295
355
  env=ENV,
296
356
  registry={
297
357
  'Convallaria_$EI0S.fbd': (
@@ -307,80 +367,94 @@ CONVALLARIA_FBD = pooch.create(
307
367
 
308
368
  FLIMLABS = pooch.create(
309
369
  path=pooch.os_cache('phasorpy'),
310
- base_url='https://github.com/phasorpy/phasorpy-data/raw/main/flimlabs',
370
+ base_url=(
371
+ 'https://github.com/phasorpy/phasorpy-data/raw/main/flimlabs'
372
+ if DATA_ON_GITHUB
373
+ else 'doi:10.5281/zenodo.15007900'
374
+ ),
311
375
  env=ENV,
312
376
  registry={
313
- 'calibrator_2_5_1737112045_imaging.json': (
314
- 'sha256:'
315
- 'a34c7077e88d1e7272953a46b2bb4e3ab8adf5a2f61c824dfc27032d952b920e'
316
- ),
317
- 'calibrator_2_5_1737112045_imaging.json.zip': (
377
+ 'Convallaria_m2_1740751781_phasor_ch1.json': (
318
378
  'sha256:'
319
- 'fea791b28afd8365152018810cbbaaac1177cb72827578073587a1050d1af329'
379
+ 'a8bf0179f352ab2c6c78d0bd399545ab1fb6d5537f23dc06e5f12a7ef5af6615'
320
380
  ),
321
- 'calibrator_2_5_1737112045_imaging_calibration.json': (
381
+ 'Convallaria_m2_1740751781_phasor_ch1.json.zip': (
322
382
  'sha256:'
323
- '8f2ebe9b544fae9524dc13221c1a5ab1b57d9dfd40ec2eb06a7b1475fcd63057'
383
+ '9c5691f55e85778717ace13607d573bcd00c29e357e063c8db4f173340f72984'
324
384
  ),
325
- 'calibrator_2_5_bis_1737112494_imaging.json': (
385
+ 'Fluorescein_Calibration_m2_1740751189_imaging.json': (
326
386
  'sha256:'
327
- '0509c5aba066419b03f83264eba58acbf4aae470aa1057c52f45e60225e033a4'
387
+ 'aeebb074dbea6bff7578f409c7622b2f7f173bb23e5475d1436adedca7fc2eed'
328
388
  ),
329
- 'calibrator_2_5_bis_1737112494_imaging.json.zip': (
389
+ 'Fluorescein_Calibration_m2_1740751189_imaging.json.zip': (
330
390
  'sha256:'
331
- 'bdc5df2a3f08a64ec7b7bb57b36e21546de142e67c59c052318252dbb66d8abf'
391
+ '32960bc1dec85fd16ffc7dd74a3cd63041fb3b69054ee6582f913129b0847086'
332
392
  ),
333
- 'calibrator_2_5_bis_1737112494_imaging_calibration.json': (
393
+ 'Fluorescein_Calibration_m2_1740751189_imaging_calibration.json': (
334
394
  'sha256:'
335
- '9bb0e21b1e7c04add672aa8a78048b09908c860fcaf907ca33c0c87d161f6ebf'
395
+ '7fd1b9749789bd139c132602a771a127ea0c76f403d1750e9636cd657cce017a'
336
396
  ),
337
- 'convallaria_1_1737112980_phasor_ch1.json': (
338
- 'sha256:'
339
- '4a296a0d7898dc660a388e1bba5cf98b43c35fe12d94b7aba48d00245e37242d'
340
- ),
341
- 'convallaria_1_1737112980_phasor_ch1.json.zip': (
397
+ },
398
+ )
399
+
400
+ FIGSHARE_22336594 = pooch.create(
401
+ path=pooch.os_cache('phasorpy'),
402
+ base_url=(
403
+ 'https://github.com/phasorpy/phasorpy-data/raw/main/figshare_22336594'
404
+ if DATA_ON_GITHUB
405
+ else 'doi:10.6084/m9.figshare.22336594.v1'
406
+ ),
407
+ env=ENV,
408
+ registry={
409
+ 'FLIM_testdata.lif': (
342
410
  'sha256:'
343
- '79c416b9099c9f58d2092fe5b26ea6d0f695977b877784cf564d3ead896d9354'
411
+ '902d8fa6cd39da7cf062b32d43aab518fa2a851eab72b4bd8b8eca1bad591850'
344
412
  ),
345
- 'convallaria_2_1737113097_phasor_ch1.json': (
413
+ },
414
+ )
415
+
416
+ FIGSHARE_22336594_EXPORTED = pooch.create(
417
+ path=pooch.os_cache('phasorpy'),
418
+ base_url=(
419
+ 'https://github.com/phasorpy/phasorpy-data/raw/main/figshare_22336594'
420
+ ),
421
+ env=ENV,
422
+ registry={
423
+ 'FLIM_testdata.lif.ptu': (
346
424
  'sha256:'
347
- 'da549645ffd898238c26f7a1eac3aca4ffccec86653c0d241a6ece674dfce90d'
425
+ 'c85792b25d0b274f1484e490c99aa19052ab8e48e4e5022aabb1f218cd1123b6'
348
426
  ),
349
- 'convallaria_2_1737113097_phasor_ch1.json.zip': (
427
+ 'FLIM_testdata.lif.ptu.zip': (
350
428
  'sha256:'
351
- '8801bb14b457dceaef42e8b3bf6af770a2e14264cd2b282ba7e3d70b91ea954c'
429
+ 'c5134c470f6a3e5cb21eabd538cbd5061d9911dad96d58e3a4040cfddadaef33'
352
430
  ),
353
- 'data_2_calibrator_2_5_1737112409_phasor_ch1.json': (
431
+ 'FLIM_testdata.xlef': (
354
432
  'sha256:'
355
- 'ea8683892eb76f52231e5d6ceab64a3737454aa95fe73185366de8f758fd9b70'
433
+ '7860ef0847dc9f5534895a9c11b979bb446f67382b577fe63fb166e281e5dc5e'
356
434
  ),
357
- 'data_2_calibrator_2_5_1737112409_phasor_ch1.json.zip': (
435
+ 'FLIM_testdata.xlef.zip': (
358
436
  'sha256:'
359
- '40d2aa90b95fd8864a2392337c83a1a4f4931d7359cb30a486f65173f208de0a'
437
+ 'ad0ad6389f38dcba6f9809b54934ef3f19da975d9dabeb4c3a248692b959b9cf'
360
438
  ),
361
- 'data_calibrator_2_5_1737112133_phasor_ch1.json': (
439
+ 'FLIM_testdata.lif.filtered.ptu': (
362
440
  'sha256:'
363
- '6a8790212bc62014d597402ec5feb0e50ec6ae2aa62d63fae8cb62c6c5656268'
441
+ 'a00b84f626a39e79263322c60ae50b64163b36bb977ecc4dc54619097ba7f5b7'
364
442
  ),
365
- 'data_calibrator_2_5_1737112133_phasor_ch1.json.zip': (
443
+ 'FLIM_testdata.lif.filtered.ptu.zip': (
366
444
  'sha256:'
367
- 'c9ef343bdbd7a51d23fdf4082e379dcdb1ce9f3e2ba065289bf2925d68ef55ba'
445
+ '717366b231213bfd6e295c3efb7bf1bcd90e720eb28aab3223376087172e93e5'
368
446
  ),
369
447
  },
370
448
  )
371
449
 
372
- FIGSHARE_22336594 = pooch.create(
450
+ MISC = pooch.create(
373
451
  path=pooch.os_cache('phasorpy'),
374
- base_url=(
375
- 'https://github.com/phasorpy/phasorpy-data/raw/main/figshare_22336594'
376
- if DATA_ON_GITHUB
377
- else 'doi:10.6084/m9.figshare.22336594.v1'
378
- ),
452
+ base_url='https://github.com/phasorpy/phasorpy-data/raw/main/misc',
379
453
  env=ENV,
380
454
  registry={
381
- 'FLIM_testdata.lif': (
455
+ 'NADHandSHG.ifli': (
382
456
  'sha256:'
383
- '902d8fa6cd39da7cf062b32d43aab518fa2a851eab72b4bd8b8eca1bad591850'
457
+ 'dfa65952850b8a222258776a8a14eb1ab7e70ff5f62b58aa2214797c5921b4a3'
384
458
  ),
385
459
  },
386
460
  )
@@ -391,9 +465,13 @@ REPOSITORIES: dict[str, pooch.Pooch] = {
391
465
  'flute': FLUTE,
392
466
  'napari-flim-phasor-plotter': NAPARI_FLIM_PHASOR_PLOTTER,
393
467
  'zenodo-13625087': ZENODO_13625087,
468
+ 'zenodo-14860228': ZENODO_14860228,
469
+ 'zenodo-14976703': ZENODO_14976703,
394
470
  'convallaria-fbd': CONVALLARIA_FBD,
395
471
  'flimlabs': FLIMLABS,
396
472
  'figshare_22336594': FIGSHARE_22336594,
473
+ 'figshare_22336594_exported': FIGSHARE_22336594_EXPORTED,
474
+ 'misc': MISC,
397
475
  }
398
476
  """Pooch repositories."""
399
477
 
@@ -406,19 +484,19 @@ def fetch(
406
484
  ) -> Any: # str | tuple[str, ...]
407
485
  """Return absolute path(s) to sample file(s) in local storage.
408
486
 
409
- The files are downloaded from a remote repository if they do not already
410
- exist in the local storage.
487
+ The files are downloaded from a remote repository if not present in local
488
+ storage.
411
489
 
412
490
  Parameters
413
491
  ----------
414
- *args: str or iterable of str, optional
492
+ *args : str or iterable of str, optional
415
493
  Name(s) of file(s) or repositories to fetch from local storage.
416
494
  If omitted, return files in all repositories.
417
495
  extract_dir : str or None, optional
418
496
  Path, relative to cache location, where ZIP files will be unpacked.
419
497
  return_scalar : bool, optional
420
498
  If true (default), return single path as string, else tuple of string.
421
- **kwargs : optional
499
+ **kwargs
422
500
  Additional arguments passed to ``pooch.fetch()``.
423
501
  For example, ``progressbar=True``.
424
502
 
@@ -0,0 +1,312 @@
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
+
72
+ .. [1] Anscombe FJ.
73
+ `The transformation of Poisson, binomial and negative-binomial data
74
+ <https://doi.org/10.2307/2332343>`_.
75
+ *Biometrika*, 35(3-4): 246-254 (1948)
76
+
77
+ Examples
78
+ --------
79
+
80
+ >>> z = anscombe_transform(numpy.random.poisson(10, 10000))
81
+ >>> numpy.allclose(numpy.std(z), 1.0, atol=0.1)
82
+ True
83
+
84
+ """
85
+ return _anscombe(data, **kwargs) # type: ignore[no-any-return]
86
+
87
+
88
+ def anscombe_transform_inverse(
89
+ data: ArrayLike,
90
+ /,
91
+ *,
92
+ approx: bool = False,
93
+ **kwargs: Any,
94
+ ) -> NDArray[Any]:
95
+ r"""Return inverse Anscombe transformation.
96
+
97
+ Parameters
98
+ ----------
99
+ data : array_like
100
+ Anscombe-transformed data.
101
+ approx : bool, default: False
102
+ If true, return approximation of exact unbiased inverse.
103
+ **kwargs
104
+ Optional `arguments passed to numpy universal functions
105
+ <https://numpy.org/doc/stable/reference/ufuncs.html#ufuncs-kwargs>`_.
106
+
107
+ Returns
108
+ -------
109
+ ndarray
110
+ Inverse Anscombe-transformed data.
111
+
112
+ Notes
113
+ -----
114
+ The inverse Anscombe transformation according to [1]_:
115
+
116
+ .. math::
117
+
118
+ x = (z / 2.0)^2 - 3 / 8
119
+
120
+ The approximate inverse Anscombe transformation according to [2]_ and [3]_:
121
+
122
+ .. math::
123
+
124
+ x = 1/4 \cdot z^2
125
+ + 1/4 \cdot \sqrt{3/2} \cdot z^{-1}
126
+ - 11/8 \cdot z^{-2}
127
+ + 5/8 \cdot \sqrt(3/2) \cdot z^{-3}
128
+ - 1/8
129
+
130
+ References
131
+ ----------
132
+
133
+ .. [2] Makitalo M, and Foi A.
134
+ `A closed-form approximation of the exact unbiased inverse of the
135
+ Anscombe variance-stabilizing transformation
136
+ <https://doi.org/10.1109/TIP.2011.2121085>`_.
137
+ *IEEE Trans Image Process*, 20(9): 2697-8 (2011).
138
+
139
+ .. [3] Makitalo M, and Foi A
140
+ `Optimal inversion of the generalized Anscombe transformation for
141
+ Poisson-Gaussian noise
142
+ <https://doi.org/10.1109/TIP.2012.2202675>`_,
143
+ *IEEE Trans Image Process*, 22(1): 91-103 (2013)
144
+
145
+ Examples
146
+ --------
147
+
148
+ >>> x = numpy.random.poisson(10, 100)
149
+ >>> x2 = anscombe_transform_inverse(anscombe_transform(x))
150
+ >>> numpy.allclose(x, x2, atol=1e-3)
151
+ True
152
+
153
+ """
154
+ if approx:
155
+ return _anscombe_inverse_approx( # type: ignore[no-any-return]
156
+ data, **kwargs
157
+ )
158
+ 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