phasorpy 0.1__cp313-cp313-macosx_11_0_arm64.whl → 0.2__cp313-cp313-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/_phasorpy.cpython-313-darwin.so +0 -0
- phasorpy/_phasorpy.pyx +333 -1
- phasorpy/_utils.py +27 -14
- phasorpy/datasets.py +20 -0
- phasorpy/io.py +7 -9
- phasorpy/phasor.py +227 -51
- phasorpy/utils.py +301 -1
- phasorpy/version.py +1 -1
- {phasorpy-0.1.dist-info → phasorpy-0.2.dist-info}/METADATA +22 -22
- phasorpy-0.2.dist-info/RECORD +24 -0
- {phasorpy-0.1.dist-info → phasorpy-0.2.dist-info}/WHEEL +1 -1
- phasorpy-0.1.dist-info/RECORD +0 -24
- {phasorpy-0.1.dist-info → phasorpy-0.2.dist-info}/LICENSE.txt +0 -0
- {phasorpy-0.1.dist-info → phasorpy-0.2.dist-info}/entry_points.txt +0 -0
- {phasorpy-0.1.dist-info → phasorpy-0.2.dist-info}/top_level.txt +0 -0
Binary file
|
phasorpy/_phasorpy.pyx
CHANGED
@@ -61,6 +61,8 @@ ctypedef fused signal_t:
|
|
61
61
|
float
|
62
62
|
double
|
63
63
|
|
64
|
+
from libc.stdlib cimport free, malloc
|
65
|
+
|
64
66
|
|
65
67
|
def _phasor_from_signal(
|
66
68
|
float_t[:, :, ::1] phasor,
|
@@ -115,7 +117,7 @@ def _phasor_from_signal(
|
|
115
117
|
# https://numpy.org/devdocs/reference/c-api/iterator.html
|
116
118
|
|
117
119
|
if (
|
118
|
-
samples <
|
120
|
+
samples < 2
|
119
121
|
or harmonics > samples // 2
|
120
122
|
or phasor.shape[0] != harmonics * 2 + 1
|
121
123
|
or phasor.shape[1] != signal.shape[0]
|
@@ -428,6 +430,7 @@ def _gaussian_signal(
|
|
428
430
|
###############################################################################
|
429
431
|
# FRET model
|
430
432
|
|
433
|
+
|
431
434
|
@cython.ufunc
|
432
435
|
cdef (double, double) _phasor_from_fret_donor(
|
433
436
|
double omega,
|
@@ -953,6 +956,7 @@ cdef (float_t, float_t) _phasor_divide(
|
|
953
956
|
###############################################################################
|
954
957
|
# Geometry ufuncs
|
955
958
|
|
959
|
+
|
956
960
|
@cython.ufunc
|
957
961
|
cdef short _is_inside_range(
|
958
962
|
float_t x, # point
|
@@ -1559,6 +1563,7 @@ cdef (double, double, double, double) _intersection_circle_line(
|
|
1559
1563
|
y + (-dd * dx - fabs(dy) * rdd) / dr,
|
1560
1564
|
)
|
1561
1565
|
|
1566
|
+
|
1562
1567
|
###############################################################################
|
1563
1568
|
# Blend ufuncs
|
1564
1569
|
|
@@ -1630,6 +1635,7 @@ cdef float_t _blend_lighten(
|
|
1630
1635
|
return a
|
1631
1636
|
return <float_t> max(a, b)
|
1632
1637
|
|
1638
|
+
|
1633
1639
|
###############################################################################
|
1634
1640
|
# Threshold ufuncs
|
1635
1641
|
|
@@ -1809,3 +1815,329 @@ cdef (double, double, double) _phasor_threshold_nan(
|
|
1809
1815
|
return <float_t> NAN, <float_t> NAN, <float_t> NAN
|
1810
1816
|
|
1811
1817
|
return mean, real, imag
|
1818
|
+
|
1819
|
+
|
1820
|
+
###############################################################################
|
1821
|
+
# Unary ufuncs
|
1822
|
+
|
1823
|
+
|
1824
|
+
@cython.ufunc
|
1825
|
+
cdef float_t _anscombe(
|
1826
|
+
float_t x,
|
1827
|
+
) noexcept nogil:
|
1828
|
+
"""Return anscombe variance stabilizing transformation."""
|
1829
|
+
if isnan(x):
|
1830
|
+
return <float_t> NAN
|
1831
|
+
|
1832
|
+
return <float_t> (2.0 * sqrt(<double> x + 0.375))
|
1833
|
+
|
1834
|
+
|
1835
|
+
@cython.ufunc
|
1836
|
+
cdef float_t _anscombe_inverse(
|
1837
|
+
float_t x,
|
1838
|
+
) noexcept nogil:
|
1839
|
+
"""Return inverse anscombe transformation."""
|
1840
|
+
if isnan(x):
|
1841
|
+
return <float_t> NAN
|
1842
|
+
|
1843
|
+
return <float_t> (x * x / 4.0 - 0.375) # 3/8
|
1844
|
+
|
1845
|
+
|
1846
|
+
@cython.ufunc
|
1847
|
+
cdef float_t _anscombe_inverse_approx(
|
1848
|
+
float_t x,
|
1849
|
+
) noexcept nogil:
|
1850
|
+
"""Return inverse anscombe transformation.
|
1851
|
+
|
1852
|
+
Using approximation of exact unbiased inverse.
|
1853
|
+
|
1854
|
+
"""
|
1855
|
+
if isnan(x):
|
1856
|
+
return <float_t> NAN
|
1857
|
+
|
1858
|
+
return <float_t> (
|
1859
|
+
0.25 * x * x # 1/4
|
1860
|
+
+ 0.30618621784789724 / x # 1/4 * sqrt(3/2)
|
1861
|
+
- 1.375 / (x * x) # 11/8
|
1862
|
+
+ 0.7654655446197431 / (x * x * x) # 5/8 * sqrt(3/2)
|
1863
|
+
- 0.125 # 1/8
|
1864
|
+
)
|
1865
|
+
|
1866
|
+
|
1867
|
+
###############################################################################
|
1868
|
+
# Denoising in spectral space
|
1869
|
+
|
1870
|
+
|
1871
|
+
def _phasor_from_signal_vector(
|
1872
|
+
float_t[:, ::1] phasor,
|
1873
|
+
const signal_t[:, ::1] signal,
|
1874
|
+
const double[:, :, ::1] sincos,
|
1875
|
+
const int num_threads
|
1876
|
+
):
|
1877
|
+
"""Calculate phasor coordinate vectors from signal along last axis.
|
1878
|
+
|
1879
|
+
Parameters
|
1880
|
+
----------
|
1881
|
+
phasor : 2D memoryview of float32 or float64
|
1882
|
+
Writable buffer of two dimensions where calculated phasor
|
1883
|
+
vectors are stored:
|
1884
|
+
|
1885
|
+
0. other dimensions flat
|
1886
|
+
1. real and imaginary components
|
1887
|
+
|
1888
|
+
signal : 2D memoryview of float32 or float64
|
1889
|
+
Buffer of two dimensions containing signal:
|
1890
|
+
|
1891
|
+
0. other dimensions flat
|
1892
|
+
1. dimension over which to compute FFT, number samples
|
1893
|
+
|
1894
|
+
sincos : 3D memoryview of float64
|
1895
|
+
Buffer of three dimensions containing sine and cosine terms to be
|
1896
|
+
multiplied with signal:
|
1897
|
+
|
1898
|
+
0. number harmonics
|
1899
|
+
1. number samples
|
1900
|
+
2. cos and sin
|
1901
|
+
|
1902
|
+
num_threads : int
|
1903
|
+
Number of OpenMP threads to use for parallelization.
|
1904
|
+
|
1905
|
+
Notes
|
1906
|
+
-----
|
1907
|
+
This implementation requires contiguous input arrays.
|
1908
|
+
|
1909
|
+
"""
|
1910
|
+
cdef:
|
1911
|
+
ssize_t size = signal.shape[0]
|
1912
|
+
ssize_t samples = signal.shape[1]
|
1913
|
+
ssize_t harmonics = sincos.shape[0]
|
1914
|
+
ssize_t i, j, k, h
|
1915
|
+
double dc, re, im, sample
|
1916
|
+
|
1917
|
+
if (
|
1918
|
+
samples < 2
|
1919
|
+
or harmonics > samples // 2
|
1920
|
+
or phasor.shape[0] != size
|
1921
|
+
or phasor.shape[1] != harmonics * 2
|
1922
|
+
):
|
1923
|
+
raise ValueError('invalid shape of phasor or signal')
|
1924
|
+
if sincos.shape[1] != samples or sincos.shape[2] != 2:
|
1925
|
+
raise ValueError('invalid shape of sincos')
|
1926
|
+
|
1927
|
+
with nogil, parallel(num_threads=num_threads):
|
1928
|
+
for i in prange(signal.shape[0]):
|
1929
|
+
j = 0
|
1930
|
+
for h in range(harmonics):
|
1931
|
+
dc = 0.0
|
1932
|
+
re = 0.0
|
1933
|
+
im = 0.0
|
1934
|
+
for k in range(samples):
|
1935
|
+
sample = <double> signal[i, k]
|
1936
|
+
dc = dc + sample
|
1937
|
+
re = re + sample * sincos[h, k, 0]
|
1938
|
+
im = im + sample * sincos[h, k, 1]
|
1939
|
+
if dc != 0.0:
|
1940
|
+
re = re / dc
|
1941
|
+
im = im / dc
|
1942
|
+
else:
|
1943
|
+
re = NAN if re == 0.0 else re * INFINITY
|
1944
|
+
im = NAN if im == 0.0 else im * INFINITY
|
1945
|
+
phasor[i, j] = <float_t> re
|
1946
|
+
j = j + 1
|
1947
|
+
phasor[i, j] = <float_t> im
|
1948
|
+
j = j + 1
|
1949
|
+
|
1950
|
+
|
1951
|
+
def _signal_denoise_vector(
|
1952
|
+
float_t[:, ::1] denoised,
|
1953
|
+
float_t[::1] integrated,
|
1954
|
+
const signal_t[:, ::1] signal,
|
1955
|
+
const float_t[:, ::1] spectral_vector,
|
1956
|
+
const double sigma,
|
1957
|
+
const double vmin,
|
1958
|
+
const int num_threads
|
1959
|
+
):
|
1960
|
+
"""Calculate denoised signal from spectral_vector."""
|
1961
|
+
cdef:
|
1962
|
+
ssize_t size = signal.shape[0]
|
1963
|
+
ssize_t samples = signal.shape[1]
|
1964
|
+
ssize_t dims = spectral_vector.shape[1]
|
1965
|
+
ssize_t i, j, m
|
1966
|
+
float_t n
|
1967
|
+
double weight, sum, t
|
1968
|
+
double sigma2 = -1.0 / (2.0 * sigma * sigma)
|
1969
|
+
double threshold = 9.0 * sigma * sigma
|
1970
|
+
|
1971
|
+
if denoised.shape[0] != size or denoised.shape[1] != samples:
|
1972
|
+
raise ValueError('signal and denoised shape mismatch')
|
1973
|
+
if integrated.shape[0] != size:
|
1974
|
+
raise ValueError('integrated.shape[0] != signal.shape[0]')
|
1975
|
+
if spectral_vector.shape[0] != size:
|
1976
|
+
raise ValueError('spectral_vector.shape[0] != signal.shape[0]')
|
1977
|
+
|
1978
|
+
with nogil, parallel(num_threads=num_threads):
|
1979
|
+
|
1980
|
+
# integrate channel intensities for each pixel
|
1981
|
+
# and filter low intensities
|
1982
|
+
for i in prange(size):
|
1983
|
+
sum = 0.0
|
1984
|
+
for m in range(samples):
|
1985
|
+
sum = sum + <double> signal[i, m]
|
1986
|
+
if sum < vmin:
|
1987
|
+
sum = NAN
|
1988
|
+
integrated[i] = <float_t> sum
|
1989
|
+
|
1990
|
+
# loop over all pixels
|
1991
|
+
for i in prange(size):
|
1992
|
+
|
1993
|
+
n = integrated[i]
|
1994
|
+
if not n > 0.0:
|
1995
|
+
# n is NaN or zero; cannot denoise; return original signal
|
1996
|
+
continue
|
1997
|
+
|
1998
|
+
for m in range(samples):
|
1999
|
+
denoised[i, m] /= n # weight = 1.0
|
2000
|
+
|
2001
|
+
# loop over other pixels
|
2002
|
+
for j in range(size):
|
2003
|
+
if i == j:
|
2004
|
+
# weight = 1.0 already accounted for
|
2005
|
+
continue
|
2006
|
+
|
2007
|
+
n = integrated[j]
|
2008
|
+
if not n > 0.0:
|
2009
|
+
# n is NaN or zero
|
2010
|
+
continue
|
2011
|
+
|
2012
|
+
# calculate weight from Euclidean distance of
|
2013
|
+
# pixels i and j in spectral vector space
|
2014
|
+
sum = 0.0
|
2015
|
+
for m in range(dims):
|
2016
|
+
t = spectral_vector[i, m] - spectral_vector[j, m]
|
2017
|
+
sum = sum + t * t
|
2018
|
+
if sum > threshold:
|
2019
|
+
sum = -1.0
|
2020
|
+
break
|
2021
|
+
if sum >= 0.0:
|
2022
|
+
weight = exp(sum * sigma2) / n
|
2023
|
+
else:
|
2024
|
+
# sum is NaN or greater than threshold
|
2025
|
+
continue
|
2026
|
+
|
2027
|
+
# add weighted signal[j] to denoised[i]
|
2028
|
+
for m in range(samples):
|
2029
|
+
denoised[i, m] += <float_t> (weight * signal[j, m])
|
2030
|
+
|
2031
|
+
# re-normalize to original intensity
|
2032
|
+
# sum cannot be zero because integrated == 0 was filtered
|
2033
|
+
sum = 0.0
|
2034
|
+
for m in range(samples):
|
2035
|
+
sum = sum + denoised[i, m]
|
2036
|
+
n = <float_t> (<double> integrated[i] / sum)
|
2037
|
+
for m in range(samples):
|
2038
|
+
denoised[i, m] *= n
|
2039
|
+
|
2040
|
+
|
2041
|
+
###############################################################################
|
2042
|
+
# Filtering functions
|
2043
|
+
|
2044
|
+
|
2045
|
+
cdef float_t _median(float_t *values, const ssize_t size) noexcept nogil:
|
2046
|
+
"""Return median of array values using Quickselect algorithm."""
|
2047
|
+
cdef:
|
2048
|
+
ssize_t i, pivot_index, pivot_index_new
|
2049
|
+
ssize_t left = 0
|
2050
|
+
ssize_t right = size - 1
|
2051
|
+
ssize_t middle = size // 2
|
2052
|
+
float_t pivot_value, temp
|
2053
|
+
|
2054
|
+
if size % 2 == 0:
|
2055
|
+
middle -= 1 # Quickselect sorts on right
|
2056
|
+
|
2057
|
+
while left <= right:
|
2058
|
+
pivot_index = left + (right - left) // 2
|
2059
|
+
pivot_value = values[pivot_index]
|
2060
|
+
temp = values[pivot_index]
|
2061
|
+
values[pivot_index] = values[right]
|
2062
|
+
values[right] = temp
|
2063
|
+
pivot_index_new = left
|
2064
|
+
for i in range(left, right):
|
2065
|
+
if values[i] < pivot_value:
|
2066
|
+
temp = values[i]
|
2067
|
+
values[i] = values[pivot_index_new]
|
2068
|
+
values[pivot_index_new] = temp
|
2069
|
+
pivot_index_new += 1
|
2070
|
+
temp = values[right]
|
2071
|
+
values[right] = values[pivot_index_new]
|
2072
|
+
values[pivot_index_new] = temp
|
2073
|
+
|
2074
|
+
if pivot_index_new == middle:
|
2075
|
+
if size % 2 == 0:
|
2076
|
+
return (values[middle] + values[middle + 1]) / <float_t> 2.0
|
2077
|
+
return values[middle]
|
2078
|
+
if pivot_index_new < middle:
|
2079
|
+
left = pivot_index_new + 1
|
2080
|
+
else:
|
2081
|
+
right = pivot_index_new - 1
|
2082
|
+
|
2083
|
+
return values[middle] # unreachable code?
|
2084
|
+
|
2085
|
+
|
2086
|
+
def _median_filter_2d(
|
2087
|
+
float_t[:, :] image,
|
2088
|
+
float_t[:, ::1] filtered_image,
|
2089
|
+
const ssize_t kernel_size,
|
2090
|
+
const int repeat=1,
|
2091
|
+
const int num_threads=1,
|
2092
|
+
):
|
2093
|
+
"""Apply 2D median filter ignoring NaN."""
|
2094
|
+
cdef:
|
2095
|
+
ssize_t rows = image.shape[0]
|
2096
|
+
ssize_t cols = image.shape[1]
|
2097
|
+
ssize_t k = kernel_size // 2
|
2098
|
+
ssize_t i, j, r, di, dj, ki, kj, valid_count
|
2099
|
+
float_t element
|
2100
|
+
float_t *kernel
|
2101
|
+
|
2102
|
+
if kernel_size <= 0:
|
2103
|
+
raise ValueError('kernel_size must be greater than 0')
|
2104
|
+
|
2105
|
+
with nogil, parallel(num_threads=num_threads):
|
2106
|
+
|
2107
|
+
kernel = <float_t *> malloc(
|
2108
|
+
kernel_size * kernel_size * sizeof(float_t)
|
2109
|
+
)
|
2110
|
+
if kernel == NULL:
|
2111
|
+
with gil:
|
2112
|
+
raise MemoryError('failed to allocate kernel')
|
2113
|
+
|
2114
|
+
for r in range(repeat):
|
2115
|
+
for i in prange(rows):
|
2116
|
+
for j in range(cols):
|
2117
|
+
if isnan(image[i, j]):
|
2118
|
+
filtered_image[i, j] = <float_t> NAN
|
2119
|
+
continue
|
2120
|
+
valid_count = 0
|
2121
|
+
for di in range(kernel_size):
|
2122
|
+
ki = i - k + di
|
2123
|
+
if ki < 0:
|
2124
|
+
ki = 0
|
2125
|
+
elif ki >= rows:
|
2126
|
+
ki = rows - 1
|
2127
|
+
for dj in range(kernel_size):
|
2128
|
+
kj = j - k + dj
|
2129
|
+
if kj < 0:
|
2130
|
+
kj = 0
|
2131
|
+
elif kj >= cols:
|
2132
|
+
kj = cols - 1
|
2133
|
+
element = image[ki, kj]
|
2134
|
+
if not isnan(element):
|
2135
|
+
kernel[valid_count] = element
|
2136
|
+
valid_count = valid_count + 1
|
2137
|
+
filtered_image[i, j] = _median(kernel, valid_count)
|
2138
|
+
|
2139
|
+
for i in prange(rows):
|
2140
|
+
for j in range(cols):
|
2141
|
+
image[i, j] = filtered_image[i, j]
|
2142
|
+
|
2143
|
+
free(kernel)
|
phasorpy/_utils.py
CHANGED
@@ -249,52 +249,65 @@ def phasor_from_polar_scalar(
|
|
249
249
|
|
250
250
|
def parse_harmonic(
|
251
251
|
harmonic: int | Sequence[int] | Literal['all'] | str | None,
|
252
|
-
|
252
|
+
harmonic_max: int | None = None,
|
253
253
|
/,
|
254
254
|
) -> tuple[list[int], bool]:
|
255
255
|
"""Return parsed harmonic parameter.
|
256
256
|
|
257
|
+
This function performs common, but not necessarily all, verifications
|
258
|
+
of user-provided `harmonic` parameter.
|
259
|
+
|
257
260
|
Parameters
|
258
261
|
----------
|
259
262
|
harmonic : int, list of int, 'all', or None
|
260
263
|
Harmonic parameter to parse.
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
+
harmonic_max : int, optional
|
265
|
+
Maximum value allowed in `hamonic`. Must be one or greater.
|
266
|
+
To verify against known number of signal samples,
|
267
|
+
pass ``samples // 2``.
|
268
|
+
If `harmonic='all'`, a range of harmonics from one to `harmonic_max`
|
269
|
+
(included) is returned.
|
264
270
|
|
265
271
|
Returns
|
266
272
|
-------
|
267
273
|
harmonic : list of int
|
268
274
|
Parsed list of harmonics.
|
269
275
|
has_harmonic_axis : bool
|
270
|
-
|
276
|
+
False if `harmonic` input parameter is a scalar integer.
|
271
277
|
|
272
278
|
Raises
|
273
279
|
------
|
274
280
|
IndexError
|
275
|
-
Any element is out of range [1..
|
281
|
+
Any element is out of range `[1..harmonic_max]`.
|
276
282
|
ValueError
|
277
283
|
Elements are not unique.
|
278
284
|
Harmonic is empty.
|
279
285
|
String input is not 'all'.
|
286
|
+
`harmonic_max` is smaller than 1.
|
280
287
|
TypeError
|
281
288
|
Any element is not an integer.
|
289
|
+
`harmonic` is `'all'` and `harmonic_max` is None.
|
282
290
|
|
283
291
|
"""
|
284
|
-
if
|
285
|
-
raise ValueError(f'{
|
292
|
+
if harmonic_max is not None and harmonic_max < 1:
|
293
|
+
raise ValueError(f'{harmonic_max=} < 1')
|
286
294
|
|
287
295
|
if harmonic is None:
|
288
296
|
return [1], False
|
289
297
|
|
290
|
-
harmonic_max = samples // 2
|
291
298
|
if isinstance(harmonic, (int, numbers.Integral)):
|
292
|
-
if harmonic < 1 or
|
299
|
+
if harmonic < 1 or (
|
300
|
+
harmonic_max is not None and harmonic > harmonic_max
|
301
|
+
):
|
293
302
|
raise IndexError(f'{harmonic=} out of range [1..{harmonic_max}]')
|
294
303
|
return [int(harmonic)], False
|
295
304
|
|
296
305
|
if isinstance(harmonic, str):
|
297
306
|
if harmonic == 'all':
|
307
|
+
if harmonic_max is None:
|
308
|
+
raise TypeError(
|
309
|
+
f'maximum harmonic must be specified for {harmonic=!r}'
|
310
|
+
)
|
298
311
|
return list(range(1, harmonic_max + 1)), True
|
299
312
|
raise ValueError(f'{harmonic=!r} is not a valid harmonic')
|
300
313
|
|
@@ -303,10 +316,10 @@ def parse_harmonic(
|
|
303
316
|
raise ValueError(f'{harmonic=} is empty')
|
304
317
|
if h.dtype.kind not in 'iu' or h.ndim != 1:
|
305
318
|
raise TypeError(f'{harmonic=} element not an integer')
|
306
|
-
if numpy.any(h < 1)
|
307
|
-
raise IndexError(
|
308
|
-
|
309
|
-
)
|
319
|
+
if numpy.any(h < 1):
|
320
|
+
raise IndexError(f'{harmonic=} element < 1')
|
321
|
+
if harmonic_max is not None and numpy.any(h > harmonic_max):
|
322
|
+
raise IndexError(f'{harmonic=} element > {harmonic_max}]')
|
310
323
|
if numpy.unique(h).size != h.size:
|
311
324
|
raise ValueError(f'{harmonic=} elements must be unique')
|
312
325
|
return h.tolist(), True
|
phasorpy/datasets.py
CHANGED
@@ -14,6 +14,8 @@ 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
|
+
- `Convallaria slice acquired with time-resolved 2-photon microscope
|
18
|
+
<https://zenodo.org/records/14026720>`_
|
17
19
|
|
18
20
|
The implementation is based on the `Pooch <https://www.fatiando.org/pooch>`_
|
19
21
|
library.
|
@@ -287,12 +289,30 @@ ZENODO_13625087 = pooch.create(
|
|
287
289
|
},
|
288
290
|
)
|
289
291
|
|
292
|
+
CONVALLARIA_FBD = pooch.create(
|
293
|
+
path=pooch.os_cache('phasorpy'),
|
294
|
+
base_url='doi:10.5281/zenodo.14026719',
|
295
|
+
env=ENV,
|
296
|
+
registry={
|
297
|
+
'Convallaria_$EI0S.fbd': (
|
298
|
+
'sha256:'
|
299
|
+
'3751891b02e3095fedd53a09688d8a22ff2a0083544dd5c0726b9267d11df1bc'
|
300
|
+
),
|
301
|
+
'Calibration_Rhodamine110_$EI0S.fbd': (
|
302
|
+
'sha256:'
|
303
|
+
'd745cbcdd4a10dbaed83ee9f1b150f0c7ddd313031e18233293582cdf10e4691'
|
304
|
+
),
|
305
|
+
},
|
306
|
+
)
|
307
|
+
|
308
|
+
|
290
309
|
REPOSITORIES: dict[str, pooch.Pooch] = {
|
291
310
|
'tests': TESTS,
|
292
311
|
'lfd-workshop': LFD_WORKSHOP,
|
293
312
|
'flute': FLUTE,
|
294
313
|
'napari-flim-phasor-plotter': NAPARI_FLIM_PHASOR_PLOTTER,
|
295
314
|
'zenodo-13625087': ZENODO_13625087,
|
315
|
+
'convallaria-fbd': CONVALLARIA_FBD,
|
296
316
|
}
|
297
317
|
"""Pooch repositories."""
|
298
318
|
|
phasorpy/io.py
CHANGED
@@ -290,11 +290,9 @@ def phasor_to_ometiff(
|
|
290
290
|
imag = imag.reshape(1, -1)
|
291
291
|
|
292
292
|
if harmonic is not None:
|
293
|
-
|
294
|
-
if
|
293
|
+
harmonic, _ = parse_harmonic(harmonic)
|
294
|
+
if len(harmonic) != nharmonic:
|
295
295
|
raise ValueError('invalid harmonic')
|
296
|
-
samples = int(harmonic_array.max()) * 2 + 1
|
297
|
-
harmonic, _ = parse_harmonic(harmonic, samples)
|
298
296
|
|
299
297
|
if frequency is not None:
|
300
298
|
frequency_array = numpy.atleast_2d(frequency).astype(numpy.float64)
|
@@ -488,7 +486,7 @@ def phasor_from_ometiff(
|
|
488
486
|
|
489
487
|
has_harmonic_dim = tif.series[1].ndim > tif.series[0].ndim
|
490
488
|
nharmonics = tif.series[1].shape[0] if has_harmonic_dim else 1
|
491
|
-
|
489
|
+
harmonic_max = nharmonics
|
492
490
|
for i in (3, 4):
|
493
491
|
if len(tif.series) < i + 1:
|
494
492
|
break
|
@@ -499,10 +497,10 @@ def phasor_from_ometiff(
|
|
499
497
|
elif series.name == 'Phasor harmonic':
|
500
498
|
if not has_harmonic_dim and data.size == 1:
|
501
499
|
attrs['harmonic'] = int(data.item(0))
|
502
|
-
|
500
|
+
harmonic_max = attrs['harmonic']
|
503
501
|
elif has_harmonic_dim and data.size == nharmonics:
|
504
502
|
attrs['harmonic'] = data.tolist()
|
505
|
-
|
503
|
+
harmonic_max = max(attrs['harmonic'])
|
506
504
|
else:
|
507
505
|
logger.warning(
|
508
506
|
f'harmonic={data} does not match phasor '
|
@@ -535,7 +533,7 @@ def phasor_from_ometiff(
|
|
535
533
|
imag = tif.series[2].asarray()
|
536
534
|
else:
|
537
535
|
# specified harmonics
|
538
|
-
harmonic, keepdims = parse_harmonic(harmonic,
|
536
|
+
harmonic, keepdims = parse_harmonic(harmonic, harmonic_max)
|
539
537
|
try:
|
540
538
|
if isinstance(harmonic_stored, list):
|
541
539
|
index = [harmonic_stored.index(h) for h in harmonic]
|
@@ -769,7 +767,7 @@ def phasor_from_simfcs_referenced(
|
|
769
767
|
else:
|
770
768
|
raise ValueError(f'file extension must be .ref or .r64, not {ext!r}')
|
771
769
|
|
772
|
-
harmonic, keep_harmonic_dim = parse_harmonic(harmonic, data.shape[0])
|
770
|
+
harmonic, keep_harmonic_dim = parse_harmonic(harmonic, data.shape[0] // 2)
|
773
771
|
|
774
772
|
mean = data[0].copy()
|
775
773
|
real = numpy.empty((len(harmonic),) + mean.shape, numpy.float32)
|