phasorpy 0.7__cp314-cp314-win_amd64.whl → 0.8__cp314-cp314-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/io/__init__.py CHANGED
@@ -47,7 +47,8 @@ third-party file reader libraries, currently
47
47
  `tifffile <https://github.com/cgohlke/tifffile>`_,
48
48
  `ptufile <https://github.com/cgohlke/ptufile>`_,
49
49
  `liffile <https://github.com/cgohlke/liffile>`_,
50
- `sdtfile <https://github.com/cgohlke/sdtfile>`_, and
50
+ `sdtfile <https://github.com/cgohlke/sdtfile>`_,
51
+ `fbdfile <https://github.com/cgohlke/fbdfile>`_, and
51
52
  `lfdfiles <https://github.com/cgohlke/lfdfiles>`_.
52
53
  For advanced or unsupported use cases, consider using these libraries directly.
53
54
 
phasorpy/io/_flimlabs.py CHANGED
@@ -157,9 +157,9 @@ def phasor_from_flimlabs_json(
157
157
 
158
158
  shape: tuple[int, ...] = nharmonics, nchannels, height, width
159
159
  axes: str = 'CYX'
160
- mean = numpy.zeros(shape[1:], dtype)
161
- real = numpy.zeros(shape, dtype)
162
- imag = numpy.zeros(shape, dtype)
160
+ mean = numpy.zeros(shape[1:], dtype=dtype)
161
+ real = numpy.zeros(shape, dtype=dtype)
162
+ imag = numpy.zeros(shape, dtype=dtype)
163
163
 
164
164
  for d in phasor_data:
165
165
  h = d['harmonic']
@@ -173,8 +173,8 @@ def phasor_from_flimlabs_json(
173
173
  else:
174
174
  c = channels.index(d['channel'])
175
175
 
176
- real[h, c] = numpy.asarray(d['g_data'], dtype)
177
- imag[h, c] = numpy.asarray(d['s_data'], dtype)
176
+ real[h, c] = numpy.asarray(d['g_data'], dtype=dtype)
177
+ imag[h, c] = numpy.asarray(d['s_data'], dtype=dtype)
178
178
 
179
179
  if 'intensities_data' in data:
180
180
  from .._phasorpy import _flimlabs_mean
@@ -327,7 +327,7 @@ def signal_from_flimlabs_json(
327
327
 
328
328
  from .._phasorpy import _flimlabs_signal
329
329
 
330
- signal = numpy.zeros((nchannels, height * width, 256), dtype)
330
+ signal = numpy.zeros((nchannels, height * width, 256), dtype=dtype)
331
331
  _flimlabs_signal(
332
332
  signal,
333
333
  intensities_data,
phasorpy/io/_leica.py CHANGED
@@ -82,7 +82,6 @@ def phasor_from_lif(
82
82
  import liffile
83
83
 
84
84
  image = '' if image is None else f'.*{image}.*/'
85
- samples = 1
86
85
 
87
86
  with liffile.LifFile(filename) as lif:
88
87
  try:
@@ -100,27 +99,9 @@ def phasor_from_lif(
100
99
  ) from exc
101
100
 
102
101
  attrs: dict[str, Any] = {'dims': dims, 'coords': coords}
103
- flim = im.parent_image
104
- if flim is not None and isinstance(flim, liffile.LifFlimImage):
105
- xml = flim.parent.xml_element
106
- frequency = xml.find('.//Dataset/RawData/LaserPulseFrequency')
107
- if frequency is not None and frequency.text is not None:
108
- attrs['frequency'] = float(frequency.text) * 1e-6
109
- clock_period = xml.find('.//Dataset/RawData/ClockPeriod')
110
- if clock_period is not None and clock_period.text is not None:
111
- tmp = float(clock_period.text) * float(frequency.text)
112
- samples = int(round(1.0 / tmp))
113
- attrs['samples'] = samples
114
- channels = []
115
- for channel in xml.findall(
116
- './/Dataset/FlimData/PhasorData/Channels'
117
- ):
118
- ch = liffile.xml2dict(channel)['Channels']
119
- ch.pop('PhasorPlotShapes', None)
120
- channels.append(ch)
121
- attrs['flim_phasor_channels'] = channels
122
- attrs['flim_rawdata'] = flim.attrs.get('RawData', {})
102
+ _flim_metadata(im.parent_image, attrs)
123
103
 
104
+ samples = attrs.get('samples', 1)
124
105
  if samples > 1:
125
106
  mean /= samples
126
107
  return (
@@ -141,7 +122,8 @@ def lifetime_from_lif(
141
122
  Leica image files may contain fluorescence lifetime images and metadata
142
123
  from the analysis of FLIM measurements.
143
124
  The lifetimes are average photon arrival times ("Fast FLIM") according to
144
- the LAS X FLIM/FCS documentation.
125
+ the LAS X FLIM/FCS documentation. They are not corrected for the IRF
126
+ position.
145
127
 
146
128
  Parameters
147
129
  ----------
@@ -185,6 +167,8 @@ def lifetime_from_lif(
185
167
 
186
168
  Examples
187
169
  --------
170
+ Read Fast FLIM lifetime and related images from a Leica image file:
171
+
188
172
  >>> lifetime, intensity, stddev, attrs = lifetime_from_lif(
189
173
  ... fetch('FLIM_testdata.lif')
190
174
  ... )
@@ -195,6 +179,12 @@ def lifetime_from_lif(
195
179
  >>> attrs['frequency']
196
180
  19.505
197
181
 
182
+ Calibrate the Fast FLIM lifetime for IRF position:
183
+
184
+ >>> frequency = attrs['frequency']
185
+ >>> reference = attrs['flim_phasor_channels'][0]['AutomaticReferencePhase']
186
+ >>> lifetime -= math.radians(reference) / (2 * math.pi) / frequency * 1000
187
+
198
188
  """
199
189
  import liffile
200
190
 
@@ -215,18 +205,7 @@ def lifetime_from_lif(
215
205
  ) from exc
216
206
 
217
207
  attrs: dict[str, Any] = {'dims': dims, 'coords': coords}
218
- flim = im.parent_image
219
- if flim is not None and isinstance(flim, liffile.LifFlimImage):
220
- xml = flim.parent.xml_element
221
- frequency = xml.find('.//Dataset/RawData/LaserPulseFrequency')
222
- if frequency is not None and frequency.text is not None:
223
- attrs['frequency'] = float(frequency.text) * 1e-6
224
- clock_period = xml.find('.//Dataset/RawData/ClockPeriod')
225
- if clock_period is not None and clock_period.text is not None:
226
- tmp = float(clock_period.text) * float(frequency.text)
227
- samples = int(round(1.0 / tmp))
228
- attrs['samples'] = samples
229
- attrs['flim_rawdata'] = flim.attrs.get('RawData', {})
208
+ _flim_metadata(im.parent_image, attrs)
230
209
 
231
210
  return (
232
211
  lifetime.astype(numpy.float32),
@@ -236,6 +215,29 @@ def lifetime_from_lif(
236
215
  )
237
216
 
238
217
 
218
+ def _flim_metadata(flim: Any | None, attrs: dict[str, Any]) -> None:
219
+ """Add FLIM metadata to attrs."""
220
+ import liffile
221
+
222
+ if flim is not None and isinstance(flim, liffile.LifFlimImage):
223
+ xml = flim.parent.xml_element
224
+ frequency = xml.find('.//Dataset/RawData/LaserPulseFrequency')
225
+ if frequency is not None and frequency.text is not None:
226
+ attrs['frequency'] = float(frequency.text) * 1e-6
227
+ clock_period = xml.find('.//Dataset/RawData/ClockPeriod')
228
+ if clock_period is not None and clock_period.text is not None:
229
+ tmp = float(clock_period.text) * float(frequency.text)
230
+ samples = int(round(1.0 / tmp))
231
+ attrs['samples'] = samples
232
+ channels = []
233
+ for channel in xml.findall('.//Dataset/FlimData/PhasorData/Channels'):
234
+ ch = liffile.xml2dict(channel)['Channels']
235
+ ch.pop('PhasorPlotShapes', None)
236
+ channels.append(ch)
237
+ attrs['flim_phasor_channels'] = channels
238
+ attrs['flim_rawdata'] = flim.attrs.get('RawData', {})
239
+
240
+
239
241
  def signal_from_lif(
240
242
  filename: str | PathLike[Any],
241
243
  /,
phasorpy/io/_ometiff.py CHANGED
@@ -95,7 +95,7 @@ def phasor_to_ometiff(
95
95
  description : str, optional
96
96
  Plain-text description of dataset. Write as OME dataset description.
97
97
  **kwargs
98
- Additional arguments passed to :py:class:`tifffile.TiffWriter` and
98
+ Optional arguments passed to :py:class:`tifffile.TiffWriter` and
99
99
  :py:meth:`tifffile.TiffWriter.write`.
100
100
  For example, ``compression=None`` writes image data uncompressed.
101
101
 
@@ -135,9 +135,9 @@ def phasor_to_ometiff(
135
135
  if dtype.kind != 'f':
136
136
  raise ValueError(f'{dtype=} not a floating point type')
137
137
 
138
- mean = numpy.asarray(mean, dtype)
139
- real = numpy.asarray(real, dtype)
140
- imag = numpy.asarray(imag, dtype)
138
+ mean = numpy.asarray(mean, dtype=dtype)
139
+ real = numpy.asarray(real, dtype=dtype)
140
+ imag = numpy.asarray(imag, dtype=dtype)
141
141
  datasize = mean.nbytes + real.nbytes + imag.nbytes
142
142
 
143
143
  if real.shape != imag.shape:
@@ -166,7 +166,9 @@ def phasor_to_ometiff(
166
166
  raise ValueError('invalid harmonic')
167
167
 
168
168
  if frequency is not None:
169
- frequency_array = numpy.atleast_2d(frequency).astype(numpy.float64)
169
+ frequency_array = numpy.array(
170
+ frequency, dtype=numpy.float64, ndmin=2, copy=None
171
+ )
170
172
  if frequency_array.size > 1:
171
173
  raise ValueError('frequency must be scalar')
172
174
 
@@ -226,7 +228,7 @@ def phasor_to_ometiff(
226
228
 
227
229
  if harmonic is not None:
228
230
  tif.write(
229
- numpy.atleast_2d(harmonic).astype(numpy.uint32),
231
+ numpy.array(harmonic, dtype=numpy.uint32, ndmin=2),
230
232
  metadata={'Name': 'Phasor harmonic'},
231
233
  )
232
234
 
phasorpy/io/_other.py CHANGED
@@ -172,7 +172,7 @@ def signal_from_ptu(
172
172
  keepdims : bool, optional, default: False
173
173
  If true, return reduced axes as size-one dimensions.
174
174
  **kwargs
175
- Additional arguments passed to :py:meth:`PtuFile.decode_image`
175
+ Optional arguments passed to :py:meth:`PtuFile.decode_image`
176
176
  or :py:meth:`PtuFile.decode_histogram`.
177
177
 
178
178
  Returns
@@ -562,7 +562,7 @@ def phasor_from_ifli(
562
562
  If an integer, the returned `real` and `imag` arrays are single
563
563
  harmonic and have the same shape as `mean`.
564
564
  **kwargs
565
- Additional arguments passed to :py:meth:`lfdfiles.VistaIfli.asarray`,
565
+ Optional arguments passed to :py:meth:`lfdfiles.VistaIfli.asarray`,
566
566
  for example ``memmap=True``.
567
567
 
568
568
  Returns
@@ -863,7 +863,7 @@ def signal_from_pqbin(
863
863
  raise ValueError('invalid PicoQuant BIN file header')
864
864
 
865
865
  shape = size_y, size_x, size_h
866
- data = numpy.empty(shape, '<u4')
866
+ data = numpy.empty(shape, dtype='<u4')
867
867
  if fh.readinto(data) != size:
868
868
  raise ValueError('invalid PicoQuant BIN data size')
869
869
 
phasorpy/io/_simfcs.py CHANGED
@@ -97,7 +97,7 @@ def phasor_to_simfcs_referenced(
97
97
  raise ValueError(f'file extension {ext} != .r64')
98
98
 
99
99
  # TODO: delay conversions to numpy arrays to inner loop
100
- mean = numpy.asarray(mean, numpy.float32)
100
+ mean = numpy.asarray(mean, dtype=numpy.float32)
101
101
  phi, mod = phasor_to_polar(real, imag, dtype=numpy.float32)
102
102
  del real
103
103
  del imag
@@ -272,7 +272,7 @@ def phasor_from_simfcs_referenced(
272
272
  harmonic, keep_harmonic_dim = parse_harmonic(harmonic, data.shape[0] // 2)
273
273
 
274
274
  mean = data[0].copy()
275
- real = numpy.empty((len(harmonic),) + mean.shape, numpy.float32)
275
+ real = numpy.empty((len(harmonic),) + mean.shape, dtype=numpy.float32)
276
276
  imag = numpy.empty_like(real)
277
277
  for i, h in enumerate(harmonic):
278
278
  h = (h - 1) * 2 + 1
@@ -294,10 +294,11 @@ def signal_from_fbd(
294
294
  channel: int | None = 0,
295
295
  keepdims: bool = False,
296
296
  laser_factor: float = -1.0,
297
+ **kwargs: Any,
297
298
  ) -> DataArray:
298
299
  """Return phase histogram and metadata from FLIMbox FBD file.
299
300
 
300
- FDB files contain encoded cross-correlation phase histograms from
301
+ FBD files contain encoded cross-correlation phase histograms from
301
302
  digital frequency-domain measurements using a FLIMbox device.
302
303
  The encoding scheme depends on the FLIMbox device's firmware.
303
304
  The FBD file format is undocumented.
@@ -322,6 +323,8 @@ def signal_from_fbd(
322
323
  If true, return reduced axes as size-one dimensions.
323
324
  laser_factor : float, optional
324
325
  Factor to correct dwell_time/laser_frequency.
326
+ **kwargs
327
+ Optional arguments passed to :py:class:`fbdfile.FbdFile`.
325
328
 
326
329
  Returns
327
330
  -------
@@ -338,13 +341,13 @@ def signal_from_fbd(
338
341
 
339
342
  Raises
340
343
  ------
341
- lfdfiles.LfdFileError
344
+ ValueError
342
345
  File is not a FLIMbox FBD file.
343
346
 
344
347
  Notes
345
348
  -----
346
349
  The implementation is based on the
347
- `lfdfiles <https://github.com/cgohlke/lfdfiles/>`__ library.
350
+ `fbdfile <https://github.com/cgohlke/fbdfile/>`__ library.
348
351
 
349
352
  Examples
350
353
  --------
@@ -365,12 +368,12 @@ def signal_from_fbd(
365
368
  40.0
366
369
 
367
370
  """
368
- import lfdfiles
371
+ import fbdfile
369
372
 
370
373
  integrate_frames = 0 if frame is None or frame >= 0 else 1
371
374
 
372
- with lfdfiles.FlimboxFbd(filename, laser_factor=laser_factor) as fbd:
373
- data = fbd.asimage(None, None, integrate_frames=integrate_frames)
375
+ with fbdfile.FbdFile(filename, laser_factor=laser_factor, **kwargs) as fbd:
376
+ data = fbd.asimage(integrate_frames=integrate_frames)
374
377
  if integrate_frames:
375
378
  frame = None
376
379
  copy = False
phasorpy/lifetime.py CHANGED
@@ -276,13 +276,13 @@ def phasor_from_lifetime(
276
276
  """
277
277
  if unit_conversion < 1e-16:
278
278
  raise ValueError(f'{unit_conversion=} < 1e-16')
279
- frequency = numpy.atleast_1d(
280
- numpy.ascontiguousarray(frequency, dtype=numpy.float64)
279
+ frequency = numpy.array(
280
+ frequency, dtype=numpy.float64, ndmin=1, order='C', copy=None
281
281
  )
282
282
  if frequency.ndim != 1:
283
283
  raise ValueError('frequency is not one-dimensional array')
284
- lifetime = numpy.atleast_1d(
285
- numpy.ascontiguousarray(lifetime, dtype=numpy.float64)
284
+ lifetime = numpy.array(
285
+ lifetime, dtype=numpy.float64, ndmin=1, order='C', copy=None
286
286
  )
287
287
  if lifetime.ndim > 2:
288
288
  raise ValueError('lifetime must be one- or two-dimensional array')
@@ -296,8 +296,8 @@ def phasor_from_lifetime(
296
296
  lifetime = lifetime.reshape(-1, 1) # move components to last axis
297
297
  fraction = numpy.ones_like(lifetime) # not really used
298
298
  else:
299
- fraction = numpy.atleast_1d(
300
- numpy.ascontiguousarray(fraction, dtype=numpy.float64)
299
+ fraction = numpy.array(
300
+ fraction, dtype=numpy.float64, ndmin=1, order='C', copy=None
301
301
  )
302
302
  if fraction.ndim > 2:
303
303
  raise ValueError('fraction must be one- or two-dimensional array')
@@ -1021,8 +1021,8 @@ def phasor_to_lifetime_search(
1021
1021
  if dtype.char not in {'f', 'd'}:
1022
1022
  raise ValueError(f'{dtype=} is not a floating point type')
1023
1023
 
1024
- real = numpy.ascontiguousarray(real, dtype)
1025
- imag = numpy.ascontiguousarray(imag, dtype)
1024
+ real = numpy.ascontiguousarray(real, dtype=dtype)
1025
+ imag = numpy.ascontiguousarray(imag, dtype=dtype)
1026
1026
 
1027
1027
  if real.shape != imag.shape:
1028
1028
  raise ValueError(f'{real.shape=} != {imag.shape=}')
@@ -1134,7 +1134,7 @@ def phasor_to_apparent_lifetime(
1134
1134
  (array([inf, 0]), array([inf, 0]))
1135
1135
 
1136
1136
  """
1137
- omega = numpy.array(frequency, dtype=numpy.float64) # makes copy
1137
+ omega = numpy.asarray(frequency, dtype=numpy.float64, copy=True)
1138
1138
  omega *= math.pi * 2.0 * unit_conversion
1139
1139
  return _phasor_to_apparent_lifetime( # type: ignore[no-any-return]
1140
1140
  real, imag, omega, **kwargs
@@ -1218,7 +1218,7 @@ def phasor_from_apparent_lifetime(
1218
1218
  (array([1, 0.0]), array([0, 0.0]))
1219
1219
 
1220
1220
  """
1221
- omega = numpy.array(frequency, dtype=numpy.float64) # makes copy
1221
+ omega = numpy.asarray(frequency, dtype=numpy.float64, copy=True)
1222
1222
  omega *= math.pi * 2.0 * unit_conversion
1223
1223
  if modulation_lifetime is None:
1224
1224
  return _phasor_from_single_lifetime( # type: ignore[no-any-return]
@@ -1302,7 +1302,7 @@ def phasor_to_normal_lifetime(
1302
1302
  array([1.989, 1.989])
1303
1303
 
1304
1304
  """
1305
- omega = numpy.array(frequency, dtype=numpy.float64) # makes copy
1305
+ omega = numpy.asarray(frequency, dtype=numpy.float64, copy=True)
1306
1306
  omega *= math.pi * 2.0 * unit_conversion
1307
1307
  return _phasor_to_normal_lifetime( # type: ignore[no-any-return]
1308
1308
  real, imag, omega, **kwargs
@@ -1445,7 +1445,7 @@ def lifetime_fraction_to_amplitude(
1445
1445
  array([0.2, 0.2])
1446
1446
 
1447
1447
  """
1448
- t = numpy.array(fraction, dtype=numpy.float64) # makes copy
1448
+ t = numpy.asarray(fraction, dtype=numpy.float64, copy=True)
1449
1449
  t /= numpy.sum(t, axis=axis, keepdims=True)
1450
1450
  numpy.true_divide(t, lifetime, out=t)
1451
1451
  return t
@@ -1757,7 +1757,7 @@ def polar_to_apparent_lifetime(
1757
1757
  (array([1.989, 1.989]), array([1.989, 2.411]))
1758
1758
 
1759
1759
  """
1760
- omega = numpy.array(frequency, dtype=numpy.float64) # makes copy
1760
+ omega = numpy.asarray(frequency, dtype=numpy.float64, copy=True)
1761
1761
  omega *= math.pi * 2.0 * unit_conversion
1762
1762
  return _polar_to_apparent_lifetime( # type: ignore[no-any-return]
1763
1763
  phase, modulation, omega, **kwargs
@@ -1829,7 +1829,7 @@ def polar_from_apparent_lifetime(
1829
1829
  (array([0.7854, 0.7854]), array([0.7071, 0.6364]))
1830
1830
 
1831
1831
  """
1832
- omega = numpy.array(frequency, dtype=numpy.float64) # makes copy
1832
+ omega = numpy.asarray(frequency, dtype=numpy.float64, copy=True)
1833
1833
  omega *= math.pi * 2.0 * unit_conversion
1834
1834
  if modulation_lifetime is None:
1835
1835
  return _polar_from_single_lifetime( # type: ignore[no-any-return]
@@ -1923,7 +1923,7 @@ def phasor_from_fret_donor(
1923
1923
  (array([0.1766, 0.2737, 0.1466]), array([0.3626, 0.4134, 0.2534]))
1924
1924
 
1925
1925
  """
1926
- omega = numpy.array(frequency, dtype=numpy.float64) # makes copy
1926
+ omega = numpy.asarray(frequency, dtype=numpy.float64, copy=True)
1927
1927
  omega *= math.pi * 2.0 * unit_conversion
1928
1928
  return _phasor_from_fret_donor( # type: ignore[no-any-return]
1929
1929
  omega,
@@ -2041,7 +2041,7 @@ def phasor_from_fret_acceptor(
2041
2041
  (array([0.1996, 0.05772, 0.2867]), array([0.3225, 0.3103, 0.4292]))
2042
2042
 
2043
2043
  """
2044
- omega = numpy.array(frequency, dtype=numpy.float64) # makes copy
2044
+ omega = numpy.asarray(frequency, dtype=numpy.float64, copy=True)
2045
2045
  omega *= math.pi * 2.0 * unit_conversion
2046
2046
  return _phasor_from_fret_acceptor( # type: ignore[no-any-return]
2047
2047
  omega,