phasorpy 0.1__cp311-cp311-win_arm64.whl → 0.3__cp311-cp311-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/_phasorpy.cp311-win_arm64.pyd +0 -0
- phasorpy/_phasorpy.pyx +388 -38
- phasorpy/_utils.py +27 -14
- phasorpy/datasets.py +20 -0
- phasorpy/io.py +156 -16
- phasorpy/phasor.py +521 -249
- phasorpy/plot.py +6 -2
- phasorpy/utils.py +301 -1
- phasorpy/version.py +1 -1
- {phasorpy-0.1.dist-info → phasorpy-0.3.dist-info}/METADATA +22 -22
- phasorpy-0.3.dist-info/RECORD +24 -0
- {phasorpy-0.1.dist-info → phasorpy-0.3.dist-info}/WHEEL +1 -1
- phasorpy-0.1.dist-info/RECORD +0 -24
- {phasorpy-0.1.dist-info → phasorpy-0.3.dist-info}/LICENSE.txt +0 -0
- {phasorpy-0.1.dist-info → phasorpy-0.3.dist-info}/entry_points.txt +0 -0
- {phasorpy-0.1.dist-info → phasorpy-0.3.dist-info}/top_level.txt +0 -0
Binary file
|
phasorpy/_phasorpy.pyx
CHANGED
@@ -61,12 +61,15 @@ 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,
|
67
69
|
const signal_t[:, :, ::1] signal,
|
68
70
|
const double[:, :, ::1] sincos,
|
69
|
-
const
|
71
|
+
const bint normalize,
|
72
|
+
const int num_threads,
|
70
73
|
):
|
71
74
|
"""Return phasor coordinates from signal along middle axis.
|
72
75
|
|
@@ -95,6 +98,8 @@ def _phasor_from_signal(
|
|
95
98
|
1. number samples
|
96
99
|
2. cos and sin
|
97
100
|
|
101
|
+
normalize : bool
|
102
|
+
Normalize phasor coordinates.
|
98
103
|
num_threads : int
|
99
104
|
Number of OpenMP threads to use for parallelization.
|
100
105
|
|
@@ -115,7 +120,7 @@ def _phasor_from_signal(
|
|
115
120
|
# https://numpy.org/devdocs/reference/c-api/iterator.html
|
116
121
|
|
117
122
|
if (
|
118
|
-
samples <
|
123
|
+
samples < 2
|
119
124
|
or harmonics > samples // 2
|
120
125
|
or phasor.shape[0] != harmonics * 2 + 1
|
121
126
|
or phasor.shape[1] != signal.shape[0]
|
@@ -143,14 +148,16 @@ def _phasor_from_signal(
|
|
143
148
|
dc = dc + sample
|
144
149
|
re = re + sample * sincos[h, k, 0]
|
145
150
|
im = im + sample * sincos[h, k, 1]
|
146
|
-
if
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
151
|
+
if normalize:
|
152
|
+
if dc != 0.0:
|
153
|
+
# includes isnan(dc)
|
154
|
+
re = re / dc
|
155
|
+
im = im / dc
|
156
|
+
dc = dc / samples
|
157
|
+
else:
|
158
|
+
# dc = 0.0
|
159
|
+
re = NAN if re == 0.0 else re * INFINITY
|
160
|
+
im = NAN if im == 0.0 else im * INFINITY
|
154
161
|
if h == 0:
|
155
162
|
mean[i, j] = <float_t> dc
|
156
163
|
real[h, i, j] = <float_t> re
|
@@ -171,14 +178,16 @@ def _phasor_from_signal(
|
|
171
178
|
dc = dc + sample
|
172
179
|
re = re + sample * sincos[h, k, 0]
|
173
180
|
im = im + sample * sincos[h, k, 1]
|
174
|
-
if
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
181
|
+
if normalize:
|
182
|
+
if dc != 0.0:
|
183
|
+
# includes isnan(dc)
|
184
|
+
re = re / dc
|
185
|
+
im = im / dc
|
186
|
+
dc = dc / samples
|
187
|
+
else:
|
188
|
+
# dc = 0.0
|
189
|
+
re = NAN if re == 0.0 else re * INFINITY
|
190
|
+
im = NAN if im == 0.0 else im * INFINITY
|
182
191
|
if h == 0:
|
183
192
|
mean[i, j] = <float_t> dc
|
184
193
|
real[h, i, j] = <float_t> re
|
@@ -199,14 +208,16 @@ def _phasor_from_signal(
|
|
199
208
|
dc += sample
|
200
209
|
re += sample * sincos[h, k, 0]
|
201
210
|
im += sample * sincos[h, k, 1]
|
202
|
-
if
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
211
|
+
if normalize:
|
212
|
+
if dc != 0.0:
|
213
|
+
# includes isnan(dc)
|
214
|
+
re /= dc
|
215
|
+
im /= dc
|
216
|
+
dc = dc / samples
|
217
|
+
else:
|
218
|
+
# dc = 0.0
|
219
|
+
re = NAN if re == 0.0 else re * INFINITY
|
220
|
+
im = NAN if im == 0.0 else im * INFINITY
|
210
221
|
if h == 0:
|
211
222
|
mean[i, j] = <float_t> dc
|
212
223
|
real[h, i, j] = <float_t> re
|
@@ -428,6 +439,7 @@ def _gaussian_signal(
|
|
428
439
|
###############################################################################
|
429
440
|
# FRET model
|
430
441
|
|
442
|
+
|
431
443
|
@cython.ufunc
|
432
444
|
cdef (double, double) _phasor_from_fret_donor(
|
433
445
|
double omega,
|
@@ -921,38 +933,48 @@ cdef (float_t, float_t) _phasor_at_harmonic(
|
|
921
933
|
|
922
934
|
@cython.ufunc
|
923
935
|
cdef (float_t, float_t) _phasor_multiply(
|
924
|
-
float_t
|
925
|
-
float_t
|
936
|
+
float_t real,
|
937
|
+
float_t imag,
|
926
938
|
float_t real2,
|
927
939
|
float_t imag2,
|
928
940
|
) noexcept nogil:
|
929
|
-
"""Return multiplication of two phasors."""
|
930
|
-
return
|
941
|
+
"""Return complex multiplication of two phasors."""
|
942
|
+
return (
|
943
|
+
real * real2 - imag * imag2,
|
944
|
+
real * imag2 + imag * real2
|
945
|
+
)
|
931
946
|
|
932
947
|
|
933
948
|
@cython.ufunc
|
934
949
|
cdef (float_t, float_t) _phasor_divide(
|
935
|
-
float_t
|
936
|
-
float_t
|
950
|
+
float_t real,
|
951
|
+
float_t imag,
|
937
952
|
float_t real2,
|
938
953
|
float_t imag2,
|
939
954
|
) noexcept nogil:
|
940
|
-
"""Return division of two phasors."""
|
955
|
+
"""Return complex division of two phasors."""
|
941
956
|
cdef:
|
942
|
-
float_t
|
957
|
+
float_t divisor = real2 * real2 + imag2 * imag2
|
943
958
|
|
944
|
-
if
|
945
|
-
|
959
|
+
if divisor != 0.0:
|
960
|
+
# includes isnan(divisor)
|
961
|
+
return (
|
962
|
+
(real * real2 + imag * imag2) / divisor,
|
963
|
+
(imag * real2 - real * imag2) / divisor
|
964
|
+
)
|
946
965
|
|
966
|
+
real = real * real2 + imag * imag2
|
967
|
+
imag = imag * real2 - real * imag2
|
947
968
|
return (
|
948
|
-
|
949
|
-
|
969
|
+
NAN if real == 0.0 else real * INFINITY,
|
970
|
+
NAN if imag == 0.0 else imag * INFINITY
|
950
971
|
)
|
951
972
|
|
952
973
|
|
953
974
|
###############################################################################
|
954
975
|
# Geometry ufuncs
|
955
976
|
|
977
|
+
|
956
978
|
@cython.ufunc
|
957
979
|
cdef short _is_inside_range(
|
958
980
|
float_t x, # point
|
@@ -1559,6 +1581,7 @@ cdef (double, double, double, double) _intersection_circle_line(
|
|
1559
1581
|
y + (-dd * dx - fabs(dy) * rdd) / dr,
|
1560
1582
|
)
|
1561
1583
|
|
1584
|
+
|
1562
1585
|
###############################################################################
|
1563
1586
|
# Blend ufuncs
|
1564
1587
|
|
@@ -1630,6 +1653,7 @@ cdef float_t _blend_lighten(
|
|
1630
1653
|
return a
|
1631
1654
|
return <float_t> max(a, b)
|
1632
1655
|
|
1656
|
+
|
1633
1657
|
###############################################################################
|
1634
1658
|
# Threshold ufuncs
|
1635
1659
|
|
@@ -1809,3 +1833,329 @@ cdef (double, double, double) _phasor_threshold_nan(
|
|
1809
1833
|
return <float_t> NAN, <float_t> NAN, <float_t> NAN
|
1810
1834
|
|
1811
1835
|
return mean, real, imag
|
1836
|
+
|
1837
|
+
|
1838
|
+
###############################################################################
|
1839
|
+
# Unary ufuncs
|
1840
|
+
|
1841
|
+
|
1842
|
+
@cython.ufunc
|
1843
|
+
cdef float_t _anscombe(
|
1844
|
+
float_t x,
|
1845
|
+
) noexcept nogil:
|
1846
|
+
"""Return anscombe variance stabilizing transformation."""
|
1847
|
+
if isnan(x):
|
1848
|
+
return <float_t> NAN
|
1849
|
+
|
1850
|
+
return <float_t> (2.0 * sqrt(<double> x + 0.375))
|
1851
|
+
|
1852
|
+
|
1853
|
+
@cython.ufunc
|
1854
|
+
cdef float_t _anscombe_inverse(
|
1855
|
+
float_t x,
|
1856
|
+
) noexcept nogil:
|
1857
|
+
"""Return inverse anscombe transformation."""
|
1858
|
+
if isnan(x):
|
1859
|
+
return <float_t> NAN
|
1860
|
+
|
1861
|
+
return <float_t> (x * x / 4.0 - 0.375) # 3/8
|
1862
|
+
|
1863
|
+
|
1864
|
+
@cython.ufunc
|
1865
|
+
cdef float_t _anscombe_inverse_approx(
|
1866
|
+
float_t x,
|
1867
|
+
) noexcept nogil:
|
1868
|
+
"""Return inverse anscombe transformation.
|
1869
|
+
|
1870
|
+
Using approximation of exact unbiased inverse.
|
1871
|
+
|
1872
|
+
"""
|
1873
|
+
if isnan(x):
|
1874
|
+
return <float_t> NAN
|
1875
|
+
|
1876
|
+
return <float_t> (
|
1877
|
+
0.25 * x * x # 1/4
|
1878
|
+
+ 0.30618621784789724 / x # 1/4 * sqrt(3/2)
|
1879
|
+
- 1.375 / (x * x) # 11/8
|
1880
|
+
+ 0.7654655446197431 / (x * x * x) # 5/8 * sqrt(3/2)
|
1881
|
+
- 0.125 # 1/8
|
1882
|
+
)
|
1883
|
+
|
1884
|
+
|
1885
|
+
###############################################################################
|
1886
|
+
# Denoising in spectral space
|
1887
|
+
|
1888
|
+
|
1889
|
+
def _phasor_from_signal_vector(
|
1890
|
+
float_t[:, ::1] phasor,
|
1891
|
+
const signal_t[:, ::1] signal,
|
1892
|
+
const double[:, :, ::1] sincos,
|
1893
|
+
const int num_threads
|
1894
|
+
):
|
1895
|
+
"""Calculate phasor coordinate vectors from signal along last axis.
|
1896
|
+
|
1897
|
+
Parameters
|
1898
|
+
----------
|
1899
|
+
phasor : 2D memoryview of float32 or float64
|
1900
|
+
Writable buffer of two dimensions where calculated phasor
|
1901
|
+
vectors are stored:
|
1902
|
+
|
1903
|
+
0. other dimensions flat
|
1904
|
+
1. real and imaginary components
|
1905
|
+
|
1906
|
+
signal : 2D memoryview of float32 or float64
|
1907
|
+
Buffer of two dimensions containing signal:
|
1908
|
+
|
1909
|
+
0. other dimensions flat
|
1910
|
+
1. dimension over which to compute FFT, number samples
|
1911
|
+
|
1912
|
+
sincos : 3D memoryview of float64
|
1913
|
+
Buffer of three dimensions containing sine and cosine terms to be
|
1914
|
+
multiplied with signal:
|
1915
|
+
|
1916
|
+
0. number harmonics
|
1917
|
+
1. number samples
|
1918
|
+
2. cos and sin
|
1919
|
+
|
1920
|
+
num_threads : int
|
1921
|
+
Number of OpenMP threads to use for parallelization.
|
1922
|
+
|
1923
|
+
Notes
|
1924
|
+
-----
|
1925
|
+
This implementation requires contiguous input arrays.
|
1926
|
+
|
1927
|
+
"""
|
1928
|
+
cdef:
|
1929
|
+
ssize_t size = signal.shape[0]
|
1930
|
+
ssize_t samples = signal.shape[1]
|
1931
|
+
ssize_t harmonics = sincos.shape[0]
|
1932
|
+
ssize_t i, j, k, h
|
1933
|
+
double dc, re, im, sample
|
1934
|
+
|
1935
|
+
if (
|
1936
|
+
samples < 2
|
1937
|
+
or harmonics > samples // 2
|
1938
|
+
or phasor.shape[0] != size
|
1939
|
+
or phasor.shape[1] != harmonics * 2
|
1940
|
+
):
|
1941
|
+
raise ValueError('invalid shape of phasor or signal')
|
1942
|
+
if sincos.shape[1] != samples or sincos.shape[2] != 2:
|
1943
|
+
raise ValueError('invalid shape of sincos')
|
1944
|
+
|
1945
|
+
with nogil, parallel(num_threads=num_threads):
|
1946
|
+
for i in prange(signal.shape[0]):
|
1947
|
+
j = 0
|
1948
|
+
for h in range(harmonics):
|
1949
|
+
dc = 0.0
|
1950
|
+
re = 0.0
|
1951
|
+
im = 0.0
|
1952
|
+
for k in range(samples):
|
1953
|
+
sample = <double> signal[i, k]
|
1954
|
+
dc = dc + sample
|
1955
|
+
re = re + sample * sincos[h, k, 0]
|
1956
|
+
im = im + sample * sincos[h, k, 1]
|
1957
|
+
if dc != 0.0:
|
1958
|
+
re = re / dc
|
1959
|
+
im = im / dc
|
1960
|
+
else:
|
1961
|
+
re = NAN if re == 0.0 else re * INFINITY
|
1962
|
+
im = NAN if im == 0.0 else im * INFINITY
|
1963
|
+
phasor[i, j] = <float_t> re
|
1964
|
+
j = j + 1
|
1965
|
+
phasor[i, j] = <float_t> im
|
1966
|
+
j = j + 1
|
1967
|
+
|
1968
|
+
|
1969
|
+
def _signal_denoise_vector(
|
1970
|
+
float_t[:, ::1] denoised,
|
1971
|
+
float_t[::1] integrated,
|
1972
|
+
const signal_t[:, ::1] signal,
|
1973
|
+
const float_t[:, ::1] spectral_vector,
|
1974
|
+
const double sigma,
|
1975
|
+
const double vmin,
|
1976
|
+
const int num_threads
|
1977
|
+
):
|
1978
|
+
"""Calculate denoised signal from spectral_vector."""
|
1979
|
+
cdef:
|
1980
|
+
ssize_t size = signal.shape[0]
|
1981
|
+
ssize_t samples = signal.shape[1]
|
1982
|
+
ssize_t dims = spectral_vector.shape[1]
|
1983
|
+
ssize_t i, j, m
|
1984
|
+
float_t n
|
1985
|
+
double weight, sum, t
|
1986
|
+
double sigma2 = -1.0 / (2.0 * sigma * sigma)
|
1987
|
+
double threshold = 9.0 * sigma * sigma
|
1988
|
+
|
1989
|
+
if denoised.shape[0] != size or denoised.shape[1] != samples:
|
1990
|
+
raise ValueError('signal and denoised shape mismatch')
|
1991
|
+
if integrated.shape[0] != size:
|
1992
|
+
raise ValueError('integrated.shape[0] != signal.shape[0]')
|
1993
|
+
if spectral_vector.shape[0] != size:
|
1994
|
+
raise ValueError('spectral_vector.shape[0] != signal.shape[0]')
|
1995
|
+
|
1996
|
+
with nogil, parallel(num_threads=num_threads):
|
1997
|
+
|
1998
|
+
# integrate channel intensities for each pixel
|
1999
|
+
# and filter low intensities
|
2000
|
+
for i in prange(size):
|
2001
|
+
sum = 0.0
|
2002
|
+
for m in range(samples):
|
2003
|
+
sum = sum + <double> signal[i, m]
|
2004
|
+
if sum < vmin:
|
2005
|
+
sum = NAN
|
2006
|
+
integrated[i] = <float_t> sum
|
2007
|
+
|
2008
|
+
# loop over all pixels
|
2009
|
+
for i in prange(size):
|
2010
|
+
|
2011
|
+
n = integrated[i]
|
2012
|
+
if not n > 0.0:
|
2013
|
+
# n is NaN or zero; cannot denoise; return original signal
|
2014
|
+
continue
|
2015
|
+
|
2016
|
+
for m in range(samples):
|
2017
|
+
denoised[i, m] /= n # weight = 1.0
|
2018
|
+
|
2019
|
+
# loop over other pixels
|
2020
|
+
for j in range(size):
|
2021
|
+
if i == j:
|
2022
|
+
# weight = 1.0 already accounted for
|
2023
|
+
continue
|
2024
|
+
|
2025
|
+
n = integrated[j]
|
2026
|
+
if not n > 0.0:
|
2027
|
+
# n is NaN or zero
|
2028
|
+
continue
|
2029
|
+
|
2030
|
+
# calculate weight from Euclidean distance of
|
2031
|
+
# pixels i and j in spectral vector space
|
2032
|
+
sum = 0.0
|
2033
|
+
for m in range(dims):
|
2034
|
+
t = spectral_vector[i, m] - spectral_vector[j, m]
|
2035
|
+
sum = sum + t * t
|
2036
|
+
if sum > threshold:
|
2037
|
+
sum = -1.0
|
2038
|
+
break
|
2039
|
+
if sum >= 0.0:
|
2040
|
+
weight = exp(sum * sigma2) / n
|
2041
|
+
else:
|
2042
|
+
# sum is NaN or greater than threshold
|
2043
|
+
continue
|
2044
|
+
|
2045
|
+
# add weighted signal[j] to denoised[i]
|
2046
|
+
for m in range(samples):
|
2047
|
+
denoised[i, m] += <float_t> (weight * signal[j, m])
|
2048
|
+
|
2049
|
+
# re-normalize to original intensity
|
2050
|
+
# sum cannot be zero because integrated == 0 was filtered
|
2051
|
+
sum = 0.0
|
2052
|
+
for m in range(samples):
|
2053
|
+
sum = sum + denoised[i, m]
|
2054
|
+
n = <float_t> (<double> integrated[i] / sum)
|
2055
|
+
for m in range(samples):
|
2056
|
+
denoised[i, m] *= n
|
2057
|
+
|
2058
|
+
|
2059
|
+
###############################################################################
|
2060
|
+
# Filtering functions
|
2061
|
+
|
2062
|
+
|
2063
|
+
cdef float_t _median(float_t *values, const ssize_t size) noexcept nogil:
|
2064
|
+
"""Return median of array values using Quickselect algorithm."""
|
2065
|
+
cdef:
|
2066
|
+
ssize_t i, pivot_index, pivot_index_new
|
2067
|
+
ssize_t left = 0
|
2068
|
+
ssize_t right = size - 1
|
2069
|
+
ssize_t middle = size // 2
|
2070
|
+
float_t pivot_value, temp
|
2071
|
+
|
2072
|
+
if size % 2 == 0:
|
2073
|
+
middle -= 1 # Quickselect sorts on right
|
2074
|
+
|
2075
|
+
while left <= right:
|
2076
|
+
pivot_index = left + (right - left) // 2
|
2077
|
+
pivot_value = values[pivot_index]
|
2078
|
+
temp = values[pivot_index]
|
2079
|
+
values[pivot_index] = values[right]
|
2080
|
+
values[right] = temp
|
2081
|
+
pivot_index_new = left
|
2082
|
+
for i in range(left, right):
|
2083
|
+
if values[i] < pivot_value:
|
2084
|
+
temp = values[i]
|
2085
|
+
values[i] = values[pivot_index_new]
|
2086
|
+
values[pivot_index_new] = temp
|
2087
|
+
pivot_index_new += 1
|
2088
|
+
temp = values[right]
|
2089
|
+
values[right] = values[pivot_index_new]
|
2090
|
+
values[pivot_index_new] = temp
|
2091
|
+
|
2092
|
+
if pivot_index_new == middle:
|
2093
|
+
if size % 2 == 0:
|
2094
|
+
return (values[middle] + values[middle + 1]) / <float_t> 2.0
|
2095
|
+
return values[middle]
|
2096
|
+
if pivot_index_new < middle:
|
2097
|
+
left = pivot_index_new + 1
|
2098
|
+
else:
|
2099
|
+
right = pivot_index_new - 1
|
2100
|
+
|
2101
|
+
return values[middle] # unreachable code?
|
2102
|
+
|
2103
|
+
|
2104
|
+
def _median_filter_2d(
|
2105
|
+
float_t[:, :] image,
|
2106
|
+
float_t[:, ::1] filtered_image,
|
2107
|
+
const ssize_t kernel_size,
|
2108
|
+
const int repeat=1,
|
2109
|
+
const int num_threads=1,
|
2110
|
+
):
|
2111
|
+
"""Apply 2D median filter ignoring NaN."""
|
2112
|
+
cdef:
|
2113
|
+
ssize_t rows = image.shape[0]
|
2114
|
+
ssize_t cols = image.shape[1]
|
2115
|
+
ssize_t k = kernel_size // 2
|
2116
|
+
ssize_t i, j, r, di, dj, ki, kj, valid_count
|
2117
|
+
float_t element
|
2118
|
+
float_t *kernel
|
2119
|
+
|
2120
|
+
if kernel_size <= 0:
|
2121
|
+
raise ValueError('kernel_size must be greater than 0')
|
2122
|
+
|
2123
|
+
with nogil, parallel(num_threads=num_threads):
|
2124
|
+
|
2125
|
+
kernel = <float_t *> malloc(
|
2126
|
+
kernel_size * kernel_size * sizeof(float_t)
|
2127
|
+
)
|
2128
|
+
if kernel == NULL:
|
2129
|
+
with gil:
|
2130
|
+
raise MemoryError('failed to allocate kernel')
|
2131
|
+
|
2132
|
+
for r in range(repeat):
|
2133
|
+
for i in prange(rows):
|
2134
|
+
for j in range(cols):
|
2135
|
+
if isnan(image[i, j]):
|
2136
|
+
filtered_image[i, j] = <float_t> NAN
|
2137
|
+
continue
|
2138
|
+
valid_count = 0
|
2139
|
+
for di in range(kernel_size):
|
2140
|
+
ki = i - k + di
|
2141
|
+
if ki < 0:
|
2142
|
+
ki = 0
|
2143
|
+
elif ki >= rows:
|
2144
|
+
ki = rows - 1
|
2145
|
+
for dj in range(kernel_size):
|
2146
|
+
kj = j - k + dj
|
2147
|
+
if kj < 0:
|
2148
|
+
kj = 0
|
2149
|
+
elif kj >= cols:
|
2150
|
+
kj = cols - 1
|
2151
|
+
element = image[ki, kj]
|
2152
|
+
if not isnan(element):
|
2153
|
+
kernel[valid_count] = element
|
2154
|
+
valid_count = valid_count + 1
|
2155
|
+
filtered_image[i, j] = _median(kernel, valid_count)
|
2156
|
+
|
2157
|
+
for i in prange(rows):
|
2158
|
+
for j in range(cols):
|
2159
|
+
image[i, j] = filtered_image[i, j]
|
2160
|
+
|
2161
|
+
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
|
|