phasorpy 0.6__cp311-cp311-win_arm64.whl → 0.7__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/io/_other.py CHANGED
@@ -8,11 +8,13 @@ __all__ = [
8
8
  'phasor_from_ifli',
9
9
  'signal_from_imspector_tiff',
10
10
  'signal_from_lsm',
11
+ 'signal_from_pqbin',
11
12
  'signal_from_ptu',
12
13
  'signal_from_sdt',
13
14
  ]
14
15
 
15
16
  import os
17
+ import struct
16
18
  from typing import TYPE_CHECKING
17
19
  from xml.etree import ElementTree
18
20
 
@@ -48,7 +50,7 @@ def signal_from_sdt(
48
50
  filename : str or Path
49
51
  Name of Becker & Hickl SDT file to read.
50
52
  index : int, optional, default: 0
51
- Index of dataset to read in case the file contains multiple datasets.
53
+ Index of dataset to read if the file contains multiple datasets.
52
54
 
53
55
  Returns
54
56
  -------
@@ -63,7 +65,7 @@ def signal_from_sdt(
63
65
  Raises
64
66
  ------
65
67
  ValueError
66
- File is not a SDT file containing TCSPC histogram.
68
+ File is not an SDT file containing TCSPC histogram.
67
69
 
68
70
  Notes
69
71
  -----
@@ -127,6 +129,7 @@ def signal_from_ptu(
127
129
  channel: int | None = 0,
128
130
  dtime: int | None = 0,
129
131
  keepdims: bool = False,
132
+ **kwargs: Any,
130
133
  ) -> DataArray:
131
134
  """Return TCSPC histogram and metadata from PicoQuant PTU T3 mode file.
132
135
 
@@ -141,7 +144,7 @@ def signal_from_ptu(
141
144
  Indices for all dimensions of image mode files:
142
145
 
143
146
  - ``None``: return all items along axis (default).
144
- - ``Ellipsis``: return all items along multiple axes.
147
+ - ``Ellipsis`` (``...``): return all items along multiple axes.
145
148
  - ``int``: return single item along axis.
146
149
  - ``slice``: return chunk of axis.
147
150
  ``slice.step`` is a binning factor.
@@ -149,7 +152,7 @@ def signal_from_ptu(
149
152
 
150
153
  trimdims : str, optional, default: 'TCH'
151
154
  Axes to trim.
152
- dtype : dtype-like, optional, default: uint16
155
+ dtype : dtype_like, optional, default: uint16
153
156
  Unsigned integer type of TCSPC histogram.
154
157
  Increase the bit depth to avoid overflows when integrating.
155
158
  frame : int, optional
@@ -168,6 +171,9 @@ def signal_from_ptu(
168
171
  Overrides `selection` for axis ``H``.
169
172
  keepdims : bool, optional, default: False
170
173
  If true, return reduced axes as size-one dimensions.
174
+ **kwargs
175
+ Additional arguments passed to :py:meth:`PtuFile.decode_image`
176
+ or :py:meth:`PtuFile.decode_histogram`.
171
177
 
172
178
  Returns
173
179
  -------
@@ -214,6 +220,8 @@ def signal_from_ptu(
214
220
  import ptufile
215
221
  from xarray import DataArray
216
222
 
223
+ kwargs.pop('records', None)
224
+
217
225
  with ptufile.PtuFile(filename, trimdims=trimdims) as ptu:
218
226
  if not ptu.is_t3:
219
227
  raise ValueError(f'{ptu.filename!r} is not a T3 mode PTU file')
@@ -226,6 +234,7 @@ def signal_from_ptu(
226
234
  dtime=dtime,
227
235
  keepdims=keepdims,
228
236
  asxarray=True,
237
+ **kwargs,
229
238
  )
230
239
  assert isinstance(data, DataArray)
231
240
  elif ptu.measurement_submode == 1:
@@ -233,7 +242,7 @@ def signal_from_ptu(
233
242
  if dtime == -1:
234
243
  raise ValueError(f'{dtime=} not supported for point mode')
235
244
  data = ptu.decode_histogram(
236
- dtype=dtype, dtime=dtime, asxarray=True
245
+ dtype=dtype, dtime=dtime, asxarray=True, **kwargs
237
246
  )
238
247
  assert isinstance(data, DataArray)
239
248
  if channel is not None:
@@ -263,7 +272,7 @@ def signal_from_lsm(
263
272
  ) -> DataArray:
264
273
  """Return hyperspectral image and metadata from Zeiss LSM file.
265
274
 
266
- LSM files contain multi-dimensional images and metadata from laser
275
+ Zeiss LSM files contain multi-dimensional images and metadata from laser
267
276
  scanning microscopy measurements. The file format is based on TIFF.
268
277
 
269
278
  Parameters
@@ -542,7 +551,7 @@ def phasor_from_ifli(
542
551
  Index of channel to return.
543
552
  By default, return the first channel.
544
553
  If None, return all channels.
545
- harmonic : int, sequence of int, or 'all', optional
554
+ harmonic : int, sequence of int, 'any', or 'all', optional
546
555
  Harmonic(s) to return from file.
547
556
  If None (default), return the first harmonic stored in file.
548
557
  If `'all'`, return all harmonics of first frequency stored in file.
@@ -780,3 +789,102 @@ def signal_from_flif(
780
789
  from xarray import DataArray
781
790
 
782
791
  return DataArray(data, **metadata)
792
+
793
+
794
+ def signal_from_pqbin(
795
+ filename: str | PathLike[Any],
796
+ /,
797
+ ) -> DataArray:
798
+ """Return TCSPC histogram and metadata from PicoQuant BIN file.
799
+
800
+ PicoQuant BIN files contain TCSPC histograms with limited metadata.
801
+
802
+ Parameters
803
+ ----------
804
+ filename : str or Path
805
+ Name of PicoQuant BIN file to read.
806
+
807
+ Returns
808
+ -------
809
+ xarray.DataArray
810
+ TCSPC histogram with :ref:`axes codes <axes>` ``'YXH'``,
811
+ and type ``uint32``.
812
+
813
+ - ``coords['H']``: delay-times of histogram bins in ns.
814
+ - ``attrs['frequency']``: repetition frequency in MHz.
815
+ This assumes that the histogram contains exactly one period.
816
+
817
+ Raises
818
+ ------
819
+ ValueError
820
+ File is not a PicoQuant BIN file.
821
+
822
+ Examples
823
+ --------
824
+ >>> signal = signal_from_pqbin('picoquant.bin') # doctest: +SKIP
825
+ >>> signal.values # doctest: +SKIP
826
+ array(...)
827
+ >>> signal.dtype # doctest: +SKIP
828
+ dtype('uint32')
829
+ >>> signal.shape # doctest: +SKIP
830
+ (256, 256, 2000)
831
+ >>> signal.dims # doctest: +SKIP
832
+ ('Y', 'X', 'H')
833
+ >>> signal.coords['H'].data # doctest: +SKIP
834
+ array([0, ..., 49.975])
835
+ >>> signal.attrs['frequency'] # doctest: +SKIP
836
+ 19.99
837
+
838
+ """
839
+ with open(filename, 'rb') as fh:
840
+ header = fh.read(20)
841
+ if len(header) != 20:
842
+ raise ValueError(
843
+ f'invalid PicoQuant BIN header length {len(header)} != 20'
844
+ )
845
+ (size_x, size_y, pixel_resolution, size_h, tcspc_resolution) = (
846
+ struct.unpack('<IIfIf', header)
847
+ )
848
+ size = size_y * size_x * size_h * 4
849
+
850
+ # check the header values against arbitrary but reasonable limits
851
+ # to detect invalid files and prevent memory errors
852
+ if (
853
+ size <= 0
854
+ or size > 2**35 - 1 # 32 GiB
855
+ or size_x > 16384
856
+ or size_y > 16384
857
+ or size_h > 16384
858
+ or pixel_resolution < 0.0
859
+ or pixel_resolution > 1.0
860
+ or tcspc_resolution <= 0.0
861
+ or tcspc_resolution > 1.0
862
+ ):
863
+ raise ValueError('invalid PicoQuant BIN file header')
864
+
865
+ shape = size_y, size_x, size_h
866
+ data = numpy.empty(shape, '<u4')
867
+ if fh.readinto(data) != size:
868
+ raise ValueError('invalid PicoQuant BIN data size')
869
+
870
+ metadata = xarray_metadata(
871
+ ('Y', 'X', 'H'),
872
+ shape,
873
+ filename,
874
+ attrs={
875
+ 'frequency': 1000.0 / (size_h * tcspc_resolution), # MHz
876
+ 'pixel_resolution': pixel_resolution, # um
877
+ 'tcspc_resolution': tcspc_resolution, # ns
878
+ },
879
+ Y=numpy.linspace(
880
+ 0, size_y * pixel_resolution * 1e-6, size_y, endpoint=False
881
+ ),
882
+ X=numpy.linspace(
883
+ 0, size_x * pixel_resolution * 1e-6, size_x, endpoint=False
884
+ ),
885
+ H=numpy.linspace(0, size_h * tcspc_resolution, size_h, endpoint=False),
886
+ )
887
+
888
+ from xarray import DataArray
889
+
890
+ return DataArray(data, **metadata)
phasorpy/io/_simfcs.py CHANGED
@@ -12,6 +12,7 @@ __all__ = [
12
12
  'signal_from_z64',
13
13
  ]
14
14
 
15
+ import math
15
16
  import os
16
17
  import struct
17
18
  import zlib
@@ -75,9 +76,11 @@ def phasor_to_simfcs_referenced(
75
76
  Harmonics must be starting at and increasing by one.
76
77
  size : int, optional
77
78
  Size of X and Y dimensions of square-sized images stored in file.
79
+ Must be in range [4, 65535].
78
80
  By default, ``size = min(256, max(4, sizey, sizex))``.
79
81
  dims : sequence of str, optional
80
82
  Character codes for `mean` dimensions used to format file names.
83
+ Only used when chunking multi-dimensional data into multiple files.
81
84
 
82
85
  See Also
83
86
  --------
@@ -180,17 +183,21 @@ def phasor_from_simfcs_referenced(
180
183
  ) -> tuple[NDArray[Any], NDArray[Any], NDArray[Any], dict[str, Any]]:
181
184
  """Return phasor coordinates and metadata from SimFCS REF or R64 file.
182
185
 
183
- SimFCS referenced REF and R64 files contain phasor coordinate images
184
- (encoded as phase and modulation) for two harmonics.
186
+ SimFCS referenced REF and R64 files contain square-shaped phasor
187
+ coordinate images (encoded as phase and modulation) for two harmonics.
185
188
  Phasor coordinates from lifetime-resolved signals are calibrated.
189
+ Variants of referenced files (RE<n>) written by other software may
190
+ contain up to eight harmonics and may be uncalibrated.
186
191
 
187
192
  Parameters
188
193
  ----------
189
194
  filename : str or Path
190
- Name of SimFCS REF or R64 file to read.
191
- harmonic : int or sequence of int, optional
195
+ Name of SimFCS REF, R64, or RE<n> file to read.
196
+ harmonic : int, sequence of int, or 'all', optional
192
197
  Harmonic(s) to include in returned phasor coordinates.
193
198
  By default, only the first harmonic is returned.
199
+ If 'all', return all available harmonics.
200
+ If int or sequence, return specified harmonic(s).
194
201
 
195
202
  Returns
196
203
  -------
@@ -203,7 +210,7 @@ def phasor_from_simfcs_referenced(
203
210
  Image of imaginary component of phasor coordinates.
204
211
  Multiple harmonics, if any, are in the first axis.
205
212
  attrs : dict
206
- Select metadata:
213
+ Select metadata containing:
207
214
 
208
215
  - ``'dims'`` (tuple of str):
209
216
  :ref:`Axes codes <axes>` for `mean` image dimensions.
@@ -211,7 +218,7 @@ def phasor_from_simfcs_referenced(
211
218
  Raises
212
219
  ------
213
220
  lfdfiles.LfdfileError
214
- File is not a SimFCS REF or R64 file.
221
+ File is not a SimFCS REF, R64, or RE<n> file.
215
222
 
216
223
  See Also
217
224
  --------
@@ -219,7 +226,7 @@ def phasor_from_simfcs_referenced(
219
226
 
220
227
  Notes
221
228
  -----
222
- The implementation is based on the
229
+ The implementation for reading R64 files is based on the
223
230
  `lfdfiles <https://github.com/cgohlke/lfdfiles/>`__ library.
224
231
 
225
232
  Examples
@@ -232,17 +239,35 @@ def phasor_from_simfcs_referenced(
232
239
  array([[...]], dtype=float32)
233
240
 
234
241
  """
235
- import lfdfiles
236
-
237
242
  ext = os.path.splitext(filename)[-1].lower()
238
243
  if ext == '.r64':
244
+ import lfdfiles
245
+
239
246
  with lfdfiles.SimfcsR64(filename) as r64:
240
247
  data = r64.asarray()
241
- elif ext == '.ref':
242
- with lfdfiles.SimfcsRef(filename) as ref:
243
- data = ref.asarray()
248
+ elif ext.startswith('.re') and len(ext) == 4:
249
+ if ext[-1] == 'f':
250
+ num_images = 5
251
+ elif ext[-1].isdigit():
252
+ # non-SimFCS referenced files containing other number of harmonics
253
+ num_images = int(ext[-1]) * 2 + 1
254
+ else:
255
+ raise ValueError(
256
+ f'file extension must be .ref, .r64, or .re<n>, not {ext!r}'
257
+ )
258
+ size = os.path.getsize(filename)
259
+ if (
260
+ size > 4294967295
261
+ or size % (num_images * 4)
262
+ or not math.sqrt(size // (num_images * 4)).is_integer()
263
+ ):
264
+ raise ValueError(f'{filename!r} is not a valid referenced file')
265
+ size = int(math.sqrt(size // (num_images * 4)))
266
+ data = numpy.fromfile(filename, dtype='<f4').reshape((-1, size, size))
244
267
  else:
245
- raise ValueError(f'file extension must be .ref or .r64, not {ext!r}')
268
+ raise ValueError(
269
+ f'file extension must be .ref, .r64, or .re<n>, not {ext!r}'
270
+ )
246
271
 
247
272
  harmonic, keep_harmonic_dim = parse_harmonic(harmonic, data.shape[0] // 2)
248
273
 
@@ -289,7 +314,7 @@ def signal_from_fbd(
289
314
  frame : int, optional
290
315
  If None (default), return all frames.
291
316
  If < 0, integrate time axis, else return specified frame.
292
- channel : int, optional
317
+ channel : int or None, optional
293
318
  Index of channel to return.
294
319
  By default, return the first channel.
295
320
  If None, return all channels.
@@ -530,7 +555,7 @@ def signal_from_bh(
530
555
  Returns
531
556
  -------
532
557
  xarray.DataArray
533
- TCSPC histogram with ref:`axes codes <axes>` ``'HYX'``,
558
+ TCSPC histogram with :ref:`axes codes <axes>` ``'HYX'``,
534
559
  shape ``(256, 256, 256)``, and type ``float32``.
535
560
 
536
561
  Raises
@@ -587,7 +612,7 @@ def signal_from_bhz(
587
612
  Returns
588
613
  -------
589
614
  xarray.DataArray
590
- TCSPC histogram with ref:`axes codes <axes>` ``'HYX'``,
615
+ TCSPC histogram with :ref:`axes codes <axes>` ``'HYX'``,
591
616
  shape ``(256, 256, 256)``, and type ``float32``.
592
617
 
593
618
  Raises