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.
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 int num_threads
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 < 3
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 dc != 0.0:
147
- re = re / dc
148
- im = im / dc
149
- dc = dc /samples
150
- else:
151
- dc = 0.0
152
- re = NAN if re == 0.0 else re * INFINITY
153
- im = NAN if im == 0.0 else im * INFINITY
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 dc != 0.0:
175
- re = re / dc
176
- im = im / dc
177
- dc = dc /samples
178
- else:
179
- dc = 0.0
180
- re = NAN if re == 0.0 else re * INFINITY
181
- im = NAN if im == 0.0 else im * INFINITY
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 dc != 0.0:
203
- re /= dc
204
- im /= dc
205
- dc /= samples
206
- else:
207
- dc = 0.0
208
- re = NAN if re == 0.0 else re * INFINITY
209
- im = NAN if im == 0.0 else im * INFINITY
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 real1,
925
- float_t imag1,
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 real1 * real2 - imag1 * imag2, real1 * imag2 + imag1 * real2
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 real1,
936
- float_t imag1,
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 denom = real2 * real2 + imag2 * imag2
957
+ float_t divisor = real2 * real2 + imag2 * imag2
943
958
 
944
- if isnan(denom) or denom == 0.0:
945
- return <float_t> NAN, <float_t> NAN
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
- (real1 * real2 + imag1 * imag2) / denom,
949
- (imag1 * real2 - real1 * imag2) / denom
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
- samples: int,
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
- samples : int
262
- Number of samples in signal.
263
- Used to verify harmonic values and set maximum harmonic value.
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
- If true, `harmonic` input parameter is an integer, else a list.
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..samples // 2].
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 samples < 3:
285
- raise ValueError(f'{samples=} < 3')
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 harmonic > harmonic_max:
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) or numpy.any(h > harmonic_max):
307
- raise IndexError(
308
- f'{harmonic=} element out of range [1..{harmonic_max}]'
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