phasorpy 0.4__cp311-cp311-win_arm64.whl → 0.5__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.py CHANGED
@@ -2,23 +2,10 @@
2
2
 
3
3
  The ``phasorpy.io`` module provides functions to:
4
4
 
5
- - write phasor coordinate images to OME-TIFF and SimFCS file formats:
6
-
7
- - :py:func:`phasor_to_ometiff`
8
- - :py:func:`phasor_to_simfcs_referenced`
9
-
10
- - read phasor coordinates and metadata from specialized file formats:
11
-
12
- - :py:func:`phasor_from_ometiff` - PhasorPy OME-TIFF
13
- - :py:func:`phasor_from_ifli` - ISS IFLI
14
- - :py:func:`phasor_from_lif` - Leica LIF
15
- - :py:func:`phasor_from_flimlabs_json` - FLIM LABS JSON
16
- - :py:func:`phasor_from_simfcs_referenced` - SimFCS REF and R64
17
-
18
5
  - read time-resolved and hyperspectral signals, as well as metadata from
19
6
  many file formats used in bio-imaging:
20
7
 
21
- - :py:func:`signal_from_lif` - Leica LIF
8
+ - :py:func:`signal_from_lif` - Leica LIF and XLEF
22
9
  - :py:func:`signal_from_lsm` - Zeiss LSM
23
10
  - :py:func:`signal_from_ptu` - PicoQuant PTU
24
11
  - :py:func:`signal_from_sdt` - Becker & Hickl SDT
@@ -31,6 +18,21 @@ The ``phasorpy.io`` module provides functions to:
31
18
  - :py:func:`signal_from_bhz` - SimFCS BHZ
32
19
  - :py:func:`signal_from_bh` - SimFCS B&H
33
20
 
21
+ - read phasor coordinates, lifetime images, and metadata from
22
+ specialized file formats:
23
+
24
+ - :py:func:`phasor_from_ometiff` - PhasorPy OME-TIFF
25
+ - :py:func:`phasor_from_ifli` - ISS IFLI
26
+ - :py:func:`phasor_from_lif` - Leica LIF and XLEF
27
+ - :py:func:`phasor_from_flimlabs_json` - FLIM LABS JSON
28
+ - :py:func:`phasor_from_simfcs_referenced` - SimFCS REF and R64
29
+ - :py:func:`lifetime_from_lif` - Leica LIF and XLEF
30
+
31
+ - write phasor coordinate images to OME-TIFF and SimFCS file formats:
32
+
33
+ - :py:func:`phasor_to_ometiff`
34
+ - :py:func:`phasor_to_simfcs_referenced`
35
+
34
36
  Support for other file formats is being considered:
35
37
 
36
38
  - OME-TIFF
@@ -60,7 +62,7 @@ where ``ext`` indicates the file format and ``kwargs`` are optional arguments
60
62
  passed to the underlying file reader library or used to select which data is
61
63
  returned. The returned `xarray.DataArray
62
64
  <https://docs.xarray.dev/en/stable/user-guide/data-structures.html>`_
63
- contains an n-dimensional array with labeled coordinates, dimensions, and
65
+ contains an N-dimensional array with labeled coordinates, dimensions, and
64
66
  attributes:
65
67
 
66
68
  - ``data`` or ``values`` (*array_like*)
@@ -96,7 +98,7 @@ Axes character codes from the OME model and tifffile library are used as
96
98
  - ``'Z'`` : depth (OME)
97
99
  - ``'S'`` : sample (color components or phasor coordinates)
98
100
  - ``'I'`` : sequence (of images, frames, or planes)
99
- - ``'T'`` : time (OME)
101
+ - ``'T'`` : time (OME)
100
102
  - ``'C'`` : channel (OME. Acquisition path or emission wavelength)
101
103
  - ``'A'`` : angle (OME)
102
104
  - ``'P'`` : phase (OME. In LSM, ``'P'`` maps to position)
@@ -116,6 +118,7 @@ Axes character codes from the OME model and tifffile library are used as
116
118
  from __future__ import annotations
117
119
 
118
120
  __all__ = [
121
+ 'lifetime_from_lif',
119
122
  'phasor_from_flimlabs_json',
120
123
  'phasor_from_ifli',
121
124
  'phasor_from_lif',
@@ -192,8 +195,9 @@ def phasor_to_ometiff(
192
195
 
193
196
  By default, write phasor coordinates as single precision floating point
194
197
  values to separate image series.
195
- Write images larger than (1024, 1024) as (256, 256) tiles, datasets
196
- larger than 2 GB as BigTIFF, and datasets larger than 8 KB zlib-compressed.
198
+ Write images larger than (1024, 1024) pixels as (256, 256) tiles, datasets
199
+ larger than 2 GB as BigTIFF, and datasets larger than 8 KB using
200
+ zlib compression.
197
201
 
198
202
  This file format is experimental and might be incompatible with future
199
203
  versions of this library. It is intended for temporarily exchanging
@@ -257,6 +261,9 @@ def phasor_to_ometiff(
257
261
  extension is used to store multi-harmonic phasor coordinates.
258
262
  The modulo type for the first, harmonic dimension is "other".
259
263
 
264
+ The implementation is based on the
265
+ `tifffile <https://github.com/cgohlke/tifffile/>`__ library.
266
+
260
267
  Examples
261
268
  --------
262
269
  >>> mean, real, imag = numpy.random.rand(3, 32, 32, 32)
@@ -377,7 +384,10 @@ def phasor_from_ometiff(
377
384
  *,
378
385
  harmonic: int | Sequence[int] | Literal['all'] | str | None = None,
379
386
  ) -> tuple[NDArray[Any], NDArray[Any], NDArray[Any], dict[str, Any]]:
380
- """Return phasor coordinates and metadata from PhasorPy OME-TIFF.
387
+ """Return phasor coordinates and metadata from PhasorPy OME-TIFF file.
388
+
389
+ PhasorPy OME-TIFF files contain phasor mean intensity, real and imaginary
390
+ components, along with frequency and harmonic information.
381
391
 
382
392
  Parameters
383
393
  ----------
@@ -436,6 +446,9 @@ def phasor_from_ometiff(
436
446
  returned as two-dimensional images (three-dimensional if multiple
437
447
  harmonics are present).
438
448
 
449
+ The implementation is based on the
450
+ `tifffile <https://github.com/cgohlke/tifffile/>`__ library.
451
+
439
452
  Examples
440
453
  --------
441
454
  >>> mean, real, imag = numpy.random.rand(3, 32, 32, 32)
@@ -664,7 +677,7 @@ def phasor_to_simfcs_referenced(
664
677
  if size is None:
665
678
  size = min(256, max(4, sizey, sizex))
666
679
  elif not 4 <= size <= 65535:
667
- raise ValueError(f'{size=} out of range [4..65535]')
680
+ raise ValueError(f'{size=} out of range [4, 65535]')
668
681
 
669
682
  harmonics_per_file = 2 # TODO: make this a parameter?
670
683
  chunk_shape = tuple(
@@ -690,7 +703,8 @@ def phasor_to_simfcs_referenced(
690
703
  rawdata.append(a.tobytes())
691
704
  elif sizey <= size and sizex <= size:
692
705
  chunk[:sizey, :sizex] = a[..., :sizey, :sizex]
693
- chunk[sizey:, sizex:] = numpy.nan
706
+ chunk[:sizey, sizex:] = numpy.nan
707
+ chunk[sizey:, :] = numpy.nan
694
708
  rawdata.append(chunk.tobytes())
695
709
  else:
696
710
  raise RuntimeError # should not be reached
@@ -760,6 +774,11 @@ def phasor_from_simfcs_referenced(
760
774
  --------
761
775
  phasorpy.io.phasor_to_simfcs_referenced
762
776
 
777
+ Notes
778
+ -----
779
+ The implementation is based on the
780
+ `lfdfiles <https://github.com/cgohlke/lfdfiles/>`__ library.
781
+
763
782
  Examples
764
783
  --------
765
784
  >>> phasor_to_simfcs_referenced(
@@ -859,6 +878,8 @@ def phasor_from_ifli(
859
878
  first axis.
860
879
  - ``'frequency'`` (float):
861
880
  Fundamental frequency of time-resolved phasor coordinates in MHz.
881
+ - ``'samples'`` (int):
882
+ Number of samples per frequency.
862
883
  - ``'ifli_header'`` (dict):
863
884
  Metadata from IFLI file header.
864
885
 
@@ -869,6 +890,11 @@ def phasor_from_ifli(
869
890
  IndexError
870
891
  Harmonic is not found in file.
871
892
 
893
+ Notes
894
+ -----
895
+ The implementation is based on the
896
+ `lfdfiles <https://github.com/cgohlke/lfdfiles/>`__ library.
897
+
872
898
  Examples
873
899
  --------
874
900
  >>> mean, real, imag, attr = phasor_from_ifli(
@@ -884,6 +910,8 @@ def phasor_from_ifli(
884
910
  [1, 2, 3, 5]
885
911
  >>> attr['frequency'] # doctest: +NUMBER
886
912
  80.33
913
+ >>> attr['samples']
914
+ 64
887
915
  >>> attr['ifli_header']
888
916
  {'Version': 16, ... 'ModFrequency': (...), 'RefLifetime': (2.5,), ...}
889
917
 
@@ -909,6 +937,7 @@ def phasor_from_ifli(
909
937
  dims = dims[:-2]
910
938
  del data
911
939
 
940
+ samples = header['HistogramResolution']
912
941
  frequencies = header['ModFrequency']
913
942
  frequency = frequencies[0]
914
943
  harmonic_stored = [
@@ -959,6 +988,7 @@ def phasor_from_ifli(
959
988
  'dims': tuple(dims),
960
989
  'harmonic': harmonic,
961
990
  'frequency': frequency * 1e-6,
991
+ 'samples': samples,
962
992
  'ifli_header': header,
963
993
  }
964
994
 
@@ -968,19 +998,19 @@ def phasor_from_ifli(
968
998
  def phasor_from_lif(
969
999
  filename: str | PathLike[Any],
970
1000
  /,
971
- series: str | None = None,
1001
+ image: str | None = None,
972
1002
  ) -> tuple[NDArray[Any], NDArray[Any], NDArray[Any], dict[str, Any]]:
973
- """Return phasor coordinates and metadata from Leica LIF file.
1003
+ """Return phasor coordinates and metadata from Leica image file.
974
1004
 
975
- LIF files may contain uncalibrated phasor coordinate images and metadata
976
- from the analysis of fluorescence lifetime imaging measurements.
1005
+ Leica image files may contain uncalibrated phasor coordinate images and
1006
+ metadata from the analysis of FLIM measurements.
977
1007
 
978
1008
  Parameters
979
1009
  ----------
980
1010
  filename : str or Path
981
- Name of Leica LIF file to read.
982
- series : str, optional
983
- Name of image containing phasor coordinates.
1011
+ Name of Leica image file to read.
1012
+ image : str, optional
1013
+ Name of parent image containing phasor coordinates.
984
1014
 
985
1015
  Returns
986
1016
  -------
@@ -997,16 +1027,24 @@ def phasor_from_lif(
997
1027
  :ref:`Axes codes <axes>` for `mean` image dimensions.
998
1028
  - ``'frequency'`` (float):
999
1029
  Fundamental frequency of time-resolved phasor coordinates in MHz.
1000
- May not be present in all LIF files.
1030
+ May not be present in all files.
1001
1031
  - ``'flim_rawdata'`` (dict):
1002
- Settings from LIF SingleMoleculeDetection/RawData XML element.
1032
+ Settings from SingleMoleculeDetection/RawData XML element.
1033
+ - ``'flim_phasor_channels'`` (list of dict):
1034
+ Settings from SingleMoleculeDetection/.../PhasorData/Channels XML
1035
+ elements.
1003
1036
 
1004
1037
  Raises
1005
1038
  ------
1006
1039
  liffile.LifFileError
1007
- File is not a Leica LIF file.
1040
+ File is not a Leica image file.
1008
1041
  ValueError
1009
- File does not contain FLIM phasor images and metadata.
1042
+ File or `image` do not contain phasor coordinates and metadata.
1043
+
1044
+ Notes
1045
+ -----
1046
+ The implementation is based on the
1047
+ `liffile <https://github.com/cgohlke/liffile/>`__ library.
1010
1048
 
1011
1049
  Examples
1012
1050
  --------
@@ -1020,46 +1058,163 @@ def phasor_from_lif(
1020
1058
 
1021
1059
  """
1022
1060
  # TODO: read harmonic from XML if possible
1023
- # TODO: get calibration settings from XML metadata or lifetime and/or
1061
+ # TODO: get calibration settings from XML metadata, lifetime, or
1024
1062
  # phasor plot images
1025
1063
  import liffile
1026
1064
 
1027
- if series is None:
1028
- series = ''
1029
- else:
1030
- series = f'.*{series}.*/'
1065
+ image = '' if image is None else f'.*{image}.*/'
1066
+ samples = 1
1031
1067
 
1032
1068
  with liffile.LifFile(filename) as lif:
1033
1069
  try:
1034
- image = lif.series[series + 'Phasor Intensity$']
1035
- dims = image.dims
1036
- coords = image.coords
1070
+ im = lif.images[image + 'Phasor Intensity$']
1071
+ dims = im.dims
1072
+ coords = im.coords
1037
1073
  # meta = image.attrs
1038
- mean = image.asarray()
1039
- real = lif.series[series + 'Phasor Real$'].asarray()
1040
- imag = lif.series[series + 'Phasor Imaginary$'].asarray()
1041
- # mask = lif.series[series + 'Phasor Mask$'].asarray()
1074
+ mean = im.asarray().astype(numpy.float32)
1075
+ real = lif.images[image + 'Phasor Real$'].asarray()
1076
+ imag = lif.images[image + 'Phasor Imaginary$'].asarray()
1077
+ # mask = lif.images[image + 'Phasor Mask$'].asarray()
1042
1078
  except Exception as exc:
1043
1079
  raise ValueError(
1044
- f'{lif.filename} does not contain Phasor images'
1080
+ f'{lif.filename!r} does not contain Phasor images'
1045
1081
  ) from exc
1046
1082
 
1047
- attrs: dict[str, Any] = {'dims': dims}
1048
- xml = image.xml_element_smd
1049
- if xml is not None:
1083
+ attrs: dict[str, Any] = {'dims': dims, 'coords': coords}
1084
+ flim = im.parent_image
1085
+ if flim is not None and isinstance(flim, liffile.LifFlimImage):
1086
+ xml = flim.parent.xml_element
1050
1087
  frequency = xml.find('.//Dataset/RawData/LaserPulseFrequency')
1051
1088
  if frequency is not None and frequency.text is not None:
1052
1089
  attrs['frequency'] = float(frequency.text) * 1e-6
1053
- attrs['coords'] = coords
1054
-
1090
+ clock_period = xml.find('.//Dataset/RawData/ClockPeriod')
1091
+ if clock_period is not None and clock_period.text is not None:
1092
+ tmp = float(clock_period.text) * float(frequency.text)
1093
+ samples = int(round(1.0 / tmp))
1094
+ attrs['samples'] = samples
1095
+ channels = []
1096
+ for channel in xml.findall(
1097
+ './/Dataset/FlimData/PhasorData/Channels'
1098
+ ):
1099
+ ch = liffile.xml2dict(channel)['Channels']
1100
+ ch.pop('PhasorPlotShapes', None)
1101
+ channels.append(ch)
1102
+ attrs['flim_phasor_channels'] = channels
1103
+ attrs['flim_rawdata'] = flim.attrs.get('RawData', {})
1104
+
1105
+ if samples > 1:
1106
+ mean /= samples
1055
1107
  return (
1056
- mean.astype(numpy.float32),
1108
+ mean,
1057
1109
  real.astype(numpy.float32),
1058
1110
  imag.astype(numpy.float32),
1059
1111
  attrs,
1060
1112
  )
1061
1113
 
1062
1114
 
1115
+ def lifetime_from_lif(
1116
+ filename: str | PathLike[Any],
1117
+ /,
1118
+ image: str | None = None,
1119
+ ) -> tuple[NDArray[Any], NDArray[Any], NDArray[Any], dict[str, Any]]:
1120
+ """Return lifetime image and metadata from Leica image file.
1121
+
1122
+ Leica image files may contain fluorescence lifetime images and metadata
1123
+ from the analysis of FLIM measurements.
1124
+
1125
+ Parameters
1126
+ ----------
1127
+ filename : str or Path
1128
+ Name of Leica image file to read.
1129
+ image : str, optional
1130
+ Name of parent image containing lifetime image.
1131
+
1132
+ Returns
1133
+ -------
1134
+ lifetime : ndarray
1135
+ Fluorescence lifetime image in ns.
1136
+ intensity : ndarray
1137
+ Fluorescence intensity image.
1138
+ stddev : ndarray
1139
+ Standard deviation of fluorescence lifetimes in ns.
1140
+ attrs : dict
1141
+ Select metadata:
1142
+
1143
+ - ``'dims'`` (tuple of str):
1144
+ :ref:`Axes codes <axes>` for `intensity` image dimensions.
1145
+ - ``'frequency'`` (float):
1146
+ Fundamental frequency of lifetimes in MHz.
1147
+ May not be present in all files.
1148
+ - ``'samples'`` (int):
1149
+ Number of bins in TCSPC histogram. May not be present in all files.
1150
+ - ``'flim_rawdata'`` (dict):
1151
+ Settings from SingleMoleculeDetection/RawData XML element.
1152
+
1153
+ Raises
1154
+ ------
1155
+ liffile.LifFileError
1156
+ File is not a Leica image file.
1157
+ ValueError
1158
+ File or `image` does not contain lifetime coordinates and metadata.
1159
+
1160
+ Notes
1161
+ -----
1162
+ The implementation is based on the
1163
+ `liffile <https://github.com/cgohlke/liffile/>`__ library.
1164
+
1165
+ Examples
1166
+ --------
1167
+ >>> lifetime, intensity, stddev, attrs = lifetime_from_lif(
1168
+ ... fetch('FLIM_testdata.lif')
1169
+ ... )
1170
+ >>> lifetime.shape
1171
+ (1024, 1024)
1172
+ >>> attrs['dims']
1173
+ ('Y', 'X')
1174
+ >>> attrs['frequency']
1175
+ 19.505
1176
+
1177
+ """
1178
+ import liffile
1179
+
1180
+ image = '' if image is None else f'.*{image}.*/'
1181
+
1182
+ with liffile.LifFile(filename) as lif:
1183
+ try:
1184
+ im = lif.images[image + 'Intensity$']
1185
+ dims = im.dims
1186
+ coords = im.coords
1187
+ # meta = im.attrs
1188
+ intensity = im.asarray()
1189
+ lifetime = lif.images[image + 'Fast Flim$'].asarray()
1190
+ stddev = lif.images[image + 'Standard Deviation$'].asarray()
1191
+ except Exception as exc:
1192
+ raise ValueError(
1193
+ f'{lif.filename!r} does not contain lifetime images'
1194
+ ) from exc
1195
+
1196
+ attrs: dict[str, Any] = {'dims': dims, 'coords': coords}
1197
+ flim = im.parent_image
1198
+ if flim is not None and isinstance(flim, liffile.LifFlimImage):
1199
+ xml = flim.parent.xml_element
1200
+ frequency = xml.find('.//Dataset/RawData/LaserPulseFrequency')
1201
+ if frequency is not None and frequency.text is not None:
1202
+ attrs['frequency'] = float(frequency.text) * 1e-6
1203
+ clock_period = xml.find('.//Dataset/RawData/ClockPeriod')
1204
+ if clock_period is not None and clock_period.text is not None:
1205
+ tmp = float(clock_period.text) * float(frequency.text)
1206
+ samples = int(round(1.0 / tmp))
1207
+ attrs['samples'] = samples
1208
+ attrs['flim_rawdata'] = flim.attrs.get('RawData', {})
1209
+
1210
+ return (
1211
+ lifetime.astype(numpy.float32),
1212
+ intensity.astype(numpy.float32),
1213
+ stddev.astype(numpy.float32),
1214
+ attrs,
1215
+ )
1216
+
1217
+
1063
1218
  def phasor_from_flimlabs_json(
1064
1219
  filename: str | PathLike[Any],
1065
1220
  /,
@@ -1068,13 +1223,10 @@ def phasor_from_flimlabs_json(
1068
1223
  ) -> tuple[NDArray[Any], NDArray[Any], NDArray[Any], dict[str, Any]]:
1069
1224
  """Return phasor coordinates and metadata from FLIM LABS JSON phasor file.
1070
1225
 
1071
- Some FLIM LABS JSON files contain uncalibrated phasor coordinates
1226
+ FLIM LABS JSON files may contain calibrated phasor coordinates
1072
1227
  (possibly for multiple channels and harmonics) and metadata from
1073
1228
  digital frequency-domain measurements.
1074
1229
 
1075
- The real and imaginary parts of the phasor coordinates are zero (not NaN)
1076
- if the intensity is zero.
1077
-
1078
1230
  Parameters
1079
1231
  ----------
1080
1232
  filename : str or Path
@@ -1126,16 +1278,16 @@ def phasor_from_flimlabs_json(
1126
1278
  Examples
1127
1279
  --------
1128
1280
  >>> mean, real, imag, attrs = phasor_from_flimlabs_json(
1129
- ... fetch('convallaria_2_1737113097_phasor_ch1.json'), harmonic='all'
1281
+ ... fetch('Convallaria_m2_1740751781_phasor_ch1.json'), harmonic='all'
1130
1282
  ... )
1131
1283
  >>> real.shape
1132
- (4, 256, 256)
1284
+ (3, 256, 256)
1133
1285
  >>> attrs['dims']
1134
1286
  ('Y', 'X')
1135
1287
  >>> attrs['harmonic']
1136
- [1, 2, 3, 4]
1288
+ [1, 2, 3]
1137
1289
  >>> attrs['frequency'] # doctest: +NUMBER
1138
- 79.51
1290
+ 40.00
1139
1291
 
1140
1292
  """
1141
1293
  import json
@@ -1160,12 +1312,8 @@ def phasor_from_flimlabs_json(
1160
1312
  header = data['header']
1161
1313
  phasor_data = data['phasors_data']
1162
1314
 
1163
- nchannels = len([c for c in header['channels'] if c])
1164
- if channel is not None and (channel < 0 or channel >= nchannels):
1165
- raise IndexError(f'{channel=}')
1166
-
1167
1315
  harmonics = []
1168
- channels = []
1316
+ channels = [] # 1-based
1169
1317
  for d in phasor_data:
1170
1318
  h = d['harmonic']
1171
1319
  if h not in harmonics:
@@ -1176,8 +1324,10 @@ def phasor_from_flimlabs_json(
1176
1324
  harmonics = sorted(harmonics)
1177
1325
  channels = sorted(channels)
1178
1326
 
1179
- if len(channels) != nchannels:
1180
- raise ValueError(f'{len(channels)=} != {nchannels=}')
1327
+ if channel is not None:
1328
+ if channel + 1 not in channels:
1329
+ raise IndexError(f'{channel=}')
1330
+ channel += 1 # 1-based index
1181
1331
 
1182
1332
  if isinstance(harmonic, str) and harmonic == 'all':
1183
1333
  harmonic = harmonics
@@ -1205,11 +1355,13 @@ def phasor_from_flimlabs_json(
1205
1355
  if h not in harmonic_index:
1206
1356
  continue
1207
1357
  h = harmonic_index[h]
1208
- c = channels.index(d['channel'])
1209
1358
  if channel is not None:
1210
- if c != channel:
1359
+ if d['channel'] != channel:
1211
1360
  continue
1212
1361
  c = 0
1362
+ else:
1363
+ c = channels.index(d['channel'])
1364
+
1213
1365
  real[h, c] = numpy.asarray(d['g_data'], dtype)
1214
1366
  imag[h, c] = numpy.asarray(d['s_data'], dtype)
1215
1367
 
@@ -1220,9 +1372,14 @@ def phasor_from_flimlabs_json(
1220
1372
  _flimlabs_mean(
1221
1373
  mean,
1222
1374
  data['intensities_data'],
1223
- -1 if channel is None else channel,
1375
+ -1 if channel is None else channels.index(channel),
1224
1376
  )
1225
1377
  mean.shape = shape[1:]
1378
+ # JSON cannot store NaN values
1379
+ nan_mask = mean == 0
1380
+ real[:, nan_mask] = numpy.nan
1381
+ imag[:, nan_mask] = numpy.nan
1382
+ del nan_mask
1226
1383
 
1227
1384
  if nchannels == 1:
1228
1385
  axes = axes[1:]
@@ -1255,7 +1412,7 @@ def signal_from_flimlabs_json(
1255
1412
  """Return TCSPC histogram and metadata from FLIM LABS JSON imaging file.
1256
1413
 
1257
1414
  FLIM LABS JSON imaging files contain encoded, multi-channel TCSPC
1258
- histogram images and metadata from digital frequency-domain measurements.
1415
+ histograms and metadata from digital frequency-domain measurements.
1259
1416
 
1260
1417
  Parameters
1261
1418
  ----------
@@ -1265,17 +1422,16 @@ def signal_from_flimlabs_json(
1265
1422
  channel : int, optional
1266
1423
  If None (default), return all channels, else return specified channel.
1267
1424
  dtype : dtype-like, optional, default: uint16
1268
- Unsigned integer type of image histogram array.
1425
+ Unsigned integer type of TCSPC histogram.
1269
1426
  Increase the bit-depth for high photon counts.
1270
1427
 
1271
1428
  Returns
1272
1429
  -------
1273
1430
  xarray.DataArray
1274
- TCSPC histogram image stack.
1275
- A 3 or 4-dimensional array of type `dtype` in dimension order
1276
- ``'CYXH'``.
1431
+ TCSPC histogram with :ref:`axes codes <axes>` ``'CYXH'`` and
1432
+ type specified in ``dtype``:
1277
1433
 
1278
- - ``coords['H']``: times of histogram bins in ns.
1434
+ - ``coords['H']``: delay-times of histogram bins in ns.
1279
1435
  - ``attrs['frequency']``: laser repetition frequency in MHz.
1280
1436
  - ``attrs['flimlabs_header']``: FLIM LABS file header.
1281
1437
 
@@ -1294,7 +1450,7 @@ def signal_from_flimlabs_json(
1294
1450
  Examples
1295
1451
  --------
1296
1452
  >>> signal = signal_from_flimlabs_json(
1297
- ... fetch('convallaria_2_1737113097_phasor_ch1.json')
1453
+ ... fetch('Convallaria_m2_1740751781_phasor_ch1.json')
1298
1454
  ... )
1299
1455
  >>> signal.values
1300
1456
  array(...)
@@ -1305,7 +1461,7 @@ def signal_from_flimlabs_json(
1305
1461
  >>> signal.coords['H'].data
1306
1462
  array(...)
1307
1463
  >>> signal.attrs['frequency'] # doctest: +NUMBER
1308
- 79.51
1464
+ 40.00
1309
1465
 
1310
1466
  """
1311
1467
  import json
@@ -1341,7 +1497,7 @@ def signal_from_flimlabs_json(
1341
1497
 
1342
1498
  if channel is not None:
1343
1499
  if channel >= nchannels or channel < 0:
1344
- raise IndexError(f'{channel=} not in range(0, {nchannels=})')
1500
+ raise IndexError(f'{channel=} out of range[0, {nchannels=}]')
1345
1501
  nchannels = 1
1346
1502
 
1347
1503
  if 'data' in data:
@@ -1390,23 +1546,21 @@ def signal_from_lif(
1390
1546
  filename: str | PathLike[Any],
1391
1547
  /,
1392
1548
  *,
1393
- series: int | str | None = None,
1549
+ image: int | str | None = None,
1394
1550
  dim: Literal['λ', 'Λ'] | str = 'λ',
1395
1551
  ) -> DataArray:
1396
- """Return hyperspectral image and metadata from Leica LIF file.
1552
+ """Return hyperspectral image and metadata from Leica image file.
1397
1553
 
1398
- LIF files may contain hyperspectral images and metadata from laser
1554
+ Leica image files may contain hyperspectral images and metadata from laser
1399
1555
  scanning microscopy measurements.
1400
- Reading of TCSPC histograms from FLIM measurements is not supported
1401
- because the compression scheme is patent-pending.
1402
1556
 
1403
1557
  Parameters
1404
1558
  ----------
1405
1559
  filename : str or Path
1406
- Name of Leica LIF file to read.
1407
- series : str or int, optional
1408
- Index or regex pattern of image series to return.
1409
- By default, return the first series containing hyperspectral data.
1560
+ Name of Leica image file to read.
1561
+ image : str or int, optional
1562
+ Index or regex pattern of image to return.
1563
+ By default, return the first image containing hyperspectral data.
1410
1564
  dim : str or None
1411
1565
  Character code of hyperspectral dimension.
1412
1566
  Either ``'λ'`` for emission (default) or ``'Λ'`` for excitation.
@@ -1422,9 +1576,17 @@ def signal_from_lif(
1422
1576
  Raises
1423
1577
  ------
1424
1578
  liffile.LifFileError
1425
- File is not a Leica LIF file.
1579
+ File is not a Leica image file.
1426
1580
  ValueError
1427
- File is not an LSM file or does not contain hyperspectral image.
1581
+ File is not a Leica image file or does not contain hyperspectral image.
1582
+
1583
+ Notes
1584
+ -----
1585
+ The implementation is based on the
1586
+ `liffile <https://github.com/cgohlke/liffile/>`__ library.
1587
+
1588
+ Reading of TCSPC histograms from FLIM measurements is not supported
1589
+ because the compression scheme is patent-pending.
1428
1590
 
1429
1591
  Examples
1430
1592
  --------
@@ -1442,33 +1604,33 @@ def signal_from_lif(
1442
1604
  import liffile
1443
1605
 
1444
1606
  with liffile.LifFile(filename) as lif:
1445
- if series is None:
1446
- # find series with excitation or emission dimension
1447
- for image in lif.series:
1448
- if dim in image.dims:
1607
+ if image is None:
1608
+ # find image with excitation or emission dimension
1609
+ for im in lif.images:
1610
+ if dim in im.dims:
1449
1611
  break
1450
1612
  else:
1451
1613
  raise ValueError(
1452
- f'{lif!r} does not contain hyperspectral image'
1614
+ f'{lif.filename!r} does not contain hyperspectral image'
1453
1615
  )
1454
1616
  else:
1455
- image = lif.series[series]
1617
+ im = lif.images[image]
1456
1618
 
1457
- if dim not in image.dims or image.sizes[dim] < 4:
1458
- raise ValueError(f'{image!r} does not contain spectral dimension')
1459
- if 'C' in image.dims:
1619
+ if dim not in im.dims or im.sizes[dim] < 4:
1620
+ raise ValueError(f'{im!r} does not contain spectral dimension')
1621
+ if 'C' in im.dims:
1460
1622
  raise ValueError(
1461
1623
  'hyperspectral image must not contain channel axis'
1462
1624
  )
1463
1625
 
1464
- data = image.asarray()
1626
+ data = im.asarray()
1465
1627
  coords: dict[str, Any] = {
1466
1628
  ('C' if k == dim else k): (v * 1e9 if k == dim else v)
1467
- for (k, v) in image.coords.items()
1629
+ for (k, v) in im.coords.items()
1468
1630
  }
1469
- dims = tuple(('C' if d == dim else d) for d in image.dims)
1631
+ dims = tuple(('C' if d == dim else d) for d in im.dims)
1470
1632
 
1471
- metadata = _metadata(dims, image.shape, filename, **coords)
1633
+ metadata = _metadata(dims, im.shape, filename, **coords)
1472
1634
 
1473
1635
  from xarray import DataArray
1474
1636
 
@@ -1505,6 +1667,11 @@ def signal_from_lsm(
1505
1667
  ValueError
1506
1668
  File is not an LSM file or does not contain hyperspectral image.
1507
1669
 
1670
+ Notes
1671
+ -----
1672
+ The implementation is based on the
1673
+ `tifffile <https://github.com/cgohlke/tifffile/>`__ library.
1674
+
1508
1675
  Examples
1509
1676
  --------
1510
1677
  >>> signal = signal_from_lsm(fetch('paramecium.lsm'))
@@ -1524,7 +1691,7 @@ def signal_from_lsm(
1524
1691
 
1525
1692
  with tifffile.TiffFile(filename) as tif:
1526
1693
  if not tif.is_lsm:
1527
- raise ValueError(f'{tif.filename} is not an LSM file')
1694
+ raise ValueError(f'{tif.filename!r} is not an LSM file')
1528
1695
 
1529
1696
  page = tif.pages.first
1530
1697
  lsminfo = tif.lsm_metadata
@@ -1532,7 +1699,7 @@ def signal_from_lsm(
1532
1699
 
1533
1700
  if channels < 4 or lsminfo is None or lsminfo['SpectralScan'] != 1:
1534
1701
  raise ValueError(
1535
- f'{tif.filename} does not contain hyperspectral image'
1702
+ f'{tif.filename!r} does not contain hyperspectral image'
1536
1703
  )
1537
1704
 
1538
1705
  # TODO: contribute this to tifffile
@@ -1545,14 +1712,14 @@ def signal_from_lsm(
1545
1712
  wavelengths = lsminfo['ChannelWavelength'].mean(axis=1)
1546
1713
  if wavelengths.size != data.shape[axis]:
1547
1714
  raise ValueError(
1548
- f'{tif.filename} wavelengths do not match channel axis'
1715
+ f'{tif.filename!r} wavelengths do not match channel axis'
1549
1716
  )
1550
1717
  # stack may contain non-wavelength frame
1551
1718
  indices = wavelengths > 0
1552
1719
  wavelengths = wavelengths[indices]
1553
1720
  if wavelengths.size < 3:
1554
1721
  raise ValueError(
1555
- f'{tif.filename} does not contain hyperspectral image'
1722
+ f'{tif.filename!r} does not contain hyperspectral image'
1556
1723
  )
1557
1724
  wavelengths *= 1e9
1558
1725
  data = data.take(indices.nonzero()[0], axis=axis)
@@ -1562,7 +1729,7 @@ def signal_from_lsm(
1562
1729
  coords['T'] = lsminfo['TimeStamps'] - lsminfo['TimeStamps'][0]
1563
1730
  if coords['T'].size != data.shape[dims.index('T')]:
1564
1731
  raise ValueError(
1565
- f'{tif.filename} timestamps do not match time axis'
1732
+ f'{tif.filename!r} timestamps do not match time axis'
1566
1733
  )
1567
1734
  # spatial coordinates
1568
1735
  for ax in 'ZYX':
@@ -1586,7 +1753,7 @@ def signal_from_imspector_tiff(
1586
1753
  filename: str | PathLike[Any],
1587
1754
  /,
1588
1755
  ) -> DataArray:
1589
- """Return FLIM image stack and metadata from ImSpector TIFF file.
1756
+ """Return TCSPC histogram and metadata from ImSpector TIFF file.
1590
1757
 
1591
1758
  Parameters
1592
1759
  ----------
@@ -1596,10 +1763,10 @@ def signal_from_imspector_tiff(
1596
1763
  Returns
1597
1764
  -------
1598
1765
  xarray.DataArray
1599
- TCSPC image stack.
1600
- Usually, a 3-to-5-dimensional array of type ``uint16``.
1766
+ TCSPC histogram with :ref:`axes codes <axes>` ``'HTZYX'`` and
1767
+ type ``uint16``.
1601
1768
 
1602
- - ``coords['H']``: times of histogram bins in ns.
1769
+ - ``coords['H']``: delay-times of histogram bins in ns.
1603
1770
  - ``attrs['frequency']``: repetition frequency in MHz.
1604
1771
 
1605
1772
  Raises
@@ -1609,6 +1776,11 @@ def signal_from_imspector_tiff(
1609
1776
  ValueError
1610
1777
  File is not an ImSpector FLIM TIFF file.
1611
1778
 
1779
+ Notes
1780
+ -----
1781
+ The implementation is based on the
1782
+ `tifffile <https://github.com/cgohlke/tifffile/>`__ library.
1783
+
1612
1784
  Examples
1613
1785
  --------
1614
1786
  >>> signal = signal_from_imspector_tiff(fetch('Embryo.tif'))
@@ -1641,7 +1813,7 @@ def signal_from_imspector_tiff(
1641
1813
  or len(tif.series) != 1
1642
1814
  or not tif.is_ome
1643
1815
  ):
1644
- raise ValueError(f'{tif.filename} is not an ImSpector TIFF file')
1816
+ raise ValueError(f'{tif.filename!r} is not an ImSpector TIFF file')
1645
1817
 
1646
1818
  series = tif.series[0]
1647
1819
  ndim = series.ndim
@@ -1650,7 +1822,7 @@ def signal_from_imspector_tiff(
1650
1822
 
1651
1823
  if ndim < 3 or not axes.endswith('YX'):
1652
1824
  raise ValueError(
1653
- f'{tif.filename} is not an ImSpector FLIM TIFF file'
1825
+ f'{tif.filename!r} is not an ImSpector FLIM TIFF file'
1654
1826
  )
1655
1827
 
1656
1828
  data = series.asarray()
@@ -1697,7 +1869,9 @@ def signal_from_imspector_tiff(
1697
1869
  or 'FirstAxis' not in axes_labels.attrib
1698
1870
  or 'SecondAxis' not in axes_labels.attrib
1699
1871
  ):
1700
- raise ValueError(f'{tif.filename} is not an ImSpector FLIM TIFF file')
1872
+ raise ValueError(
1873
+ f'{tif.filename!r} is not an ImSpector FLIM TIFF file'
1874
+ )
1701
1875
 
1702
1876
  if axes_labels.attrib['FirstAxis'] == 'lifetime' or axes_labels.attrib[
1703
1877
  'FirstAxis'
@@ -1711,7 +1885,9 @@ def signal_from_imspector_tiff(
1711
1885
  ax = axes[-4]
1712
1886
  assert axes_labels.attrib['SecondAxis-Unit'] == 'ns'
1713
1887
  else:
1714
- raise ValueError(f'{tif.filename} is not an ImSpector FLIM TIFF file')
1888
+ raise ValueError(
1889
+ f'{tif.filename!r} is not an ImSpector FLIM TIFF file'
1890
+ )
1715
1891
  axes = axes.replace(ax, 'H')
1716
1892
  coords['H'] = coords[ax]
1717
1893
  del coords[ax]
@@ -1731,7 +1907,7 @@ def signal_from_sdt(
1731
1907
  *,
1732
1908
  index: int = 0,
1733
1909
  ) -> DataArray:
1734
- """Return time-resolved image and metadata from Becker & Hickl SDT file.
1910
+ """Return TCSPC histogram and metadata from Becker & Hickl SDT file.
1735
1911
 
1736
1912
  SDT files contain TCSPC measurement data and instrumentation parameters.
1737
1913
 
@@ -1745,11 +1921,11 @@ def signal_from_sdt(
1745
1921
  Returns
1746
1922
  -------
1747
1923
  xarray.DataArray
1748
- Time correlated single photon counting image data with
1749
- :ref:`axes codes <axes>` ``'YXH'`` and type ``uint16``, ``uint32``,
1750
- or ``float32``.
1924
+ TCSPC histogram with :ref:`axes codes <axes>` ``'QCYXH'`` and
1925
+ type ``uint16``, ``uint32``, or ``float32``.
1926
+ Dimensions ``'Q'`` and ``'C'`` are optional detector channels.
1751
1927
 
1752
- - ``coords['H']``: times of histogram bins in ns.
1928
+ - ``coords['H']``: delay-times of histogram bins in ns.
1753
1929
  - ``attrs['frequency']``: repetition frequency in MHz.
1754
1930
 
1755
1931
  Raises
@@ -1757,6 +1933,11 @@ def signal_from_sdt(
1757
1933
  ValueError
1758
1934
  File is not a SDT file containing TCSPC histogram.
1759
1935
 
1936
+ Notes
1937
+ -----
1938
+ The implementation is based on the
1939
+ `sdtfile <https://github.com/cgohlke/sdtfile/>`__ library.
1940
+
1760
1941
  Examples
1761
1942
  --------
1762
1943
  >>> signal = signal_from_sdt(fetch('tcspc.sdt'))
@@ -1793,7 +1974,7 @@ def signal_from_sdt(
1793
1974
  times = sdt.times[index] * 1e9
1794
1975
 
1795
1976
  # TODO: get spatial coordinates from scanner settings?
1796
- metadata = _metadata('QYXH'[-data.ndim :], data.shape, filename, H=times)
1977
+ metadata = _metadata('QCYXH'[-data.ndim :], data.shape, filename, H=times)
1797
1978
  metadata['attrs']['frequency'] = 1e3 / float(times[-1] + times[1])
1798
1979
 
1799
1980
  from xarray import DataArray
@@ -1815,8 +1996,8 @@ def signal_from_ptu(
1815
1996
  ) -> DataArray:
1816
1997
  """Return TCSPC histogram and metadata from PicoQuant PTU T3 mode file.
1817
1998
 
1818
- PTU files contain time-correlated single photon counting measurement data
1819
- and instrumentation parameters.
1999
+ PTU files contain TCSPC measurement data and instrumentation parameters,
2000
+ which are decoded to a multi-dimensional TCSPC histogram.
1820
2001
 
1821
2002
  Parameters
1822
2003
  ----------
@@ -1835,7 +2016,7 @@ def signal_from_ptu(
1835
2016
  trimdims : str, optional, default: 'TCH'
1836
2017
  Axes to trim.
1837
2018
  dtype : dtype-like, optional, default: uint16
1838
- Unsigned integer type of image histogram array.
2019
+ Unsigned integer type of TCSPC histogram.
1839
2020
  Increase the bit depth to avoid overflows when integrating.
1840
2021
  frame : int, optional
1841
2022
  If < 0, integrate time axis, else return specified frame.
@@ -1844,9 +2025,9 @@ def signal_from_ptu(
1844
2025
  If < 0, integrate channel axis, else return specified channel.
1845
2026
  Overrides `selection` for axis ``C``.
1846
2027
  dtime : int, optional, default: 0
1847
- Specifies number of bins in image histogram.
2028
+ Specifies number of bins in TCSPC histogram.
1848
2029
  If 0 (default), return the number of bins in one period.
1849
- If < 0, integrate delay time axis (image mode only).
2030
+ If < 0, integrate delay-time axis (image mode only).
1850
2031
  If > 0, return up to specified bin.
1851
2032
  Overrides `selection` for axis ``H``.
1852
2033
  keepdims : bool, optional, default: True
@@ -1855,11 +2036,10 @@ def signal_from_ptu(
1855
2036
  Returns
1856
2037
  -------
1857
2038
  xarray.DataArray
1858
- Decoded TTTR T3 records as up to 5-dimensional image array
1859
- with :ref:`axes codes <axes>` ``'TYXCH'`` and type specified
1860
- in ``dtype``:
2039
+ TCSPC histogram with :ref:`axes codes <axes>` ``'TYXCH'`` and
2040
+ type specified in ``dtype``:
1861
2041
 
1862
- - ``coords['H']``: times of histogram bins in ns.
2042
+ - ``coords['H']``: delay-times of histogram bins in ns.
1863
2043
  - ``attrs['frequency']``: repetition frequency in MHz.
1864
2044
  - ``attrs['ptu_tags']``: metadata read from PTU file.
1865
2045
 
@@ -1873,6 +2053,11 @@ def signal_from_ptu(
1873
2053
  ValueError
1874
2054
  File is not a PicoQuant PTU T3 mode file containing TCSPC data.
1875
2055
 
2056
+ Notes
2057
+ -----
2058
+ The implementation is based on the
2059
+ `ptufile <https://github.com/cgohlke/ptufile/>`__ library.
2060
+
1876
2061
  Examples
1877
2062
  --------
1878
2063
  >>> signal = signal_from_ptu(fetch('hazelnut_FLIM_single_image.ptu'))
@@ -1940,9 +2125,9 @@ def signal_from_flif(
1940
2125
  filename: str | PathLike[Any],
1941
2126
  /,
1942
2127
  ) -> DataArray:
1943
- """Return frequency-domain image and metadata from FlimFast FLIF file.
2128
+ """Return phase images and metadata from FlimFast FLIF file.
1944
2129
 
1945
- FlimFast FLIF files contain camera images and metadata from
2130
+ FlimFast FLIF files contain phase images and metadata from full-field,
1946
2131
  frequency-domain fluorescence lifetime measurements.
1947
2132
 
1948
2133
  Parameters
@@ -1953,8 +2138,8 @@ def signal_from_flif(
1953
2138
  Returns
1954
2139
  -------
1955
2140
  xarray.DataArray
1956
- Frequency-domain phase images with :ref:`axes codes <axes>` ``'THYX'``
1957
- and type ``uint16``:
2141
+ Phase images with :ref:`axes codes <axes>` ``'THYX'`` and
2142
+ type ``uint16``:
1958
2143
 
1959
2144
  - ``coords['H']``: phases in radians.
1960
2145
  - ``attrs['frequency']``: repetition frequency in MHz.
@@ -1968,6 +2153,11 @@ def signal_from_flif(
1968
2153
  lfdfiles.LfdFileError
1969
2154
  File is not a FlimFast FLIF file.
1970
2155
 
2156
+ Notes
2157
+ -----
2158
+ The implementation is based on the
2159
+ `lfdfiles <https://github.com/cgohlke/lfdfiles/>`__ library.
2160
+
1971
2161
  Examples
1972
2162
  --------
1973
2163
  >>> signal = signal_from_flif(fetch('flimfast.flif'))
@@ -2024,10 +2214,10 @@ def signal_from_fbd(
2024
2214
  keepdims: bool = True,
2025
2215
  laser_factor: float = -1.0,
2026
2216
  ) -> DataArray:
2027
- """Return frequency-domain image and metadata from FLIMbox FBD file.
2217
+ """Return phase histogram and metadata from FLIMbox FBD file.
2028
2218
 
2029
- FDB files contain encoded data from the FLIMbox device, which can be
2030
- decoded to photon arrival windows, channels, and global times.
2219
+ FDB files contain encoded cross-correlation phase histograms from
2220
+ digital frequency-domain measurements using a FLIMbox device.
2031
2221
  The encoding scheme depends on the FLIMbox device's firmware.
2032
2222
  The FBD file format is undocumented.
2033
2223
 
@@ -2053,17 +2243,26 @@ def signal_from_fbd(
2053
2243
  Returns
2054
2244
  -------
2055
2245
  xarray.DataArray
2056
- Frequency-domain image histogram with :ref:`axes codes <axes>`
2057
- ``'TCYXH'`` and type ``uint16``:
2246
+ Phase histogram with :ref:`axes codes <axes>` ``'TCYXH'`` and
2247
+ type ``uint16``:
2058
2248
 
2059
- - ``coords['H']``: phases in radians.
2249
+ - ``coords['H']``: cross-correlation phases in radians.
2060
2250
  - ``attrs['frequency']``: repetition frequency in MHz.
2251
+ - ``attrs['harmonic']``: harmonic contained in phase histogram.
2252
+ - ``attrs['flimbox_header']``: FBD binary header, if any.
2253
+ - ``attrs['flimbox_firmware']``: FLIMbox firmware settings, if any.
2254
+ - ``attrs['flimbox_settings']``: Settings from FBS XML, if any.
2061
2255
 
2062
2256
  Raises
2063
2257
  ------
2064
2258
  lfdfiles.LfdFileError
2065
2259
  File is not a FLIMbox FBD file.
2066
2260
 
2261
+ Notes
2262
+ -----
2263
+ The implementation is based on the
2264
+ `lfdfiles <https://github.com/cgohlke/lfdfiles/>`__ library.
2265
+
2067
2266
  Examples
2068
2267
  --------
2069
2268
  >>> signal = signal_from_fbd(
@@ -2111,7 +2310,7 @@ def signal_from_fbd(
2111
2310
  data = data[0]
2112
2311
  axes = axes[1:]
2113
2312
  else:
2114
- if frame < 0 or frame > data.shape[0]:
2313
+ if frame < 0 or frame >= data.shape[0]:
2115
2314
  raise IndexError(f'{frame=} out of bounds')
2116
2315
  if keepdims:
2117
2316
  data = data[frame : frame + 1]
@@ -2128,6 +2327,13 @@ def signal_from_fbd(
2128
2327
  metadata = _metadata(axes, data.shape, H=phases)
2129
2328
  attrs = metadata['attrs']
2130
2329
  attrs['frequency'] = fbd.laser_frequency * 1e-6
2330
+ attrs['harmonic'] = fbd.harmonics
2331
+ if fbd.header is not None:
2332
+ attrs['flimbox_header'] = fbd.header
2333
+ if fbd.fbf is not None:
2334
+ attrs['flimbox_firmware'] = fbd.fbf
2335
+ if fbd.fbs is not None:
2336
+ attrs['flimbox_settings'] = fbd.fbs
2131
2337
 
2132
2338
  from xarray import DataArray
2133
2339
 
@@ -2151,7 +2357,7 @@ def signal_from_b64(
2151
2357
  Returns
2152
2358
  -------
2153
2359
  xarray.DataArray
2154
- Stack of square-sized intensity images of type ``int16``.
2360
+ Intensity image of type ``int16``.
2155
2361
 
2156
2362
  Raises
2157
2363
  ------
@@ -2160,6 +2366,11 @@ def signal_from_b64(
2160
2366
  ValueError
2161
2367
  File does not contain an image stack.
2162
2368
 
2369
+ Notes
2370
+ -----
2371
+ The implementation is based on the
2372
+ `lfdfiles <https://github.com/cgohlke/lfdfiles/>`__ library.
2373
+
2163
2374
  Examples
2164
2375
  --------
2165
2376
  >>> signal = signal_from_b64(fetch('simfcs.b64'))
@@ -2195,11 +2406,10 @@ def signal_from_z64(
2195
2406
  filename: str | PathLike[Any],
2196
2407
  /,
2197
2408
  ) -> DataArray:
2198
- """Return image and metadata from SimFCS Z64 file.
2409
+ """Return image stack and metadata from SimFCS Z64 file.
2199
2410
 
2200
- Z64 files contain stacks of square images such as intensity volumes
2201
- or time-domain fluorescence lifetime histograms acquired from
2202
- Becker & Hickl(r) TCSPC cards. Z64 files contain no metadata.
2411
+ Z64 files commonly contain stacks of square images, such as intensity
2412
+ volumes or TCSPC histograms. Z64 files contain no metadata.
2203
2413
 
2204
2414
  Parameters
2205
2415
  ----------
@@ -2209,13 +2419,18 @@ def signal_from_z64(
2209
2419
  Returns
2210
2420
  -------
2211
2421
  xarray.DataArray
2212
- Single or stack of square-sized images of type ``float32``.
2422
+ Image stack of type ``float32``.
2213
2423
 
2214
2424
  Raises
2215
2425
  ------
2216
2426
  lfdfiles.LfdFileError
2217
2427
  File is not a SimFCS Z64 file.
2218
2428
 
2429
+ Notes
2430
+ -----
2431
+ The implementation is based on the
2432
+ `lfdfiles <https://github.com/cgohlke/lfdfiles/>`__ library.
2433
+
2219
2434
  Examples
2220
2435
  --------
2221
2436
  >>> signal = signal_from_z64(fetch('simfcs.z64'))
@@ -2244,11 +2459,10 @@ def signal_from_bh(
2244
2459
  filename: str | PathLike[Any],
2245
2460
  /,
2246
2461
  ) -> DataArray:
2247
- """Return image and metadata from SimFCS B&H file.
2462
+ """Return TCSPC histogram and metadata from SimFCS B&H file.
2248
2463
 
2249
- B&H files contain time-domain fluorescence lifetime histogram data,
2250
- acquired from Becker & Hickl(r) TCSPC cards, or converted from other
2251
- data sources. B&H files contain no metadata.
2464
+ B&H files contain TCSPC histograms acquired from Becker & Hickl
2465
+ cards, or converted from other data sources. B&H files contain no metadata.
2252
2466
 
2253
2467
  Parameters
2254
2468
  ----------
@@ -2258,7 +2472,7 @@ def signal_from_bh(
2258
2472
  Returns
2259
2473
  -------
2260
2474
  xarray.DataArray
2261
- Time-domain fluorescence lifetime histogram with axes ``'HYX'``,
2475
+ TCSPC histogram with ref:`axes codes <axes>` ``'HYX'``,
2262
2476
  shape ``(256, 256, 256)``, and type ``float32``.
2263
2477
 
2264
2478
  Raises
@@ -2266,6 +2480,11 @@ def signal_from_bh(
2266
2480
  lfdfiles.LfdFileError
2267
2481
  File is not a SimFCS B&H file.
2268
2482
 
2483
+ Notes
2484
+ -----
2485
+ The implementation is based on the
2486
+ `lfdfiles <https://github.com/cgohlke/lfdfiles/>`__ library.
2487
+
2269
2488
  Examples
2270
2489
  --------
2271
2490
  >>> signal = signal_from_bh(fetch('simfcs.b&h'))
@@ -2295,11 +2514,10 @@ def signal_from_bhz(
2295
2514
  filename: str | PathLike[Any],
2296
2515
  /,
2297
2516
  ) -> DataArray:
2298
- """Return image and metadata from SimFCS BHZ file.
2517
+ """Return TCSPC histogram and metadata from SimFCS BHZ file.
2299
2518
 
2300
- BHZ files contain time-domain fluorescence lifetime histogram data,
2301
- acquired from Becker & Hickl(r) TCSPC cards, or converted from other
2302
- data sources. BHZ files contain no metadata.
2519
+ BHZ files contain TCSPC histograms acquired from Becker & Hickl
2520
+ cards, or converted from other data sources. BHZ files contain no metadata.
2303
2521
 
2304
2522
  Parameters
2305
2523
  ----------
@@ -2309,7 +2527,7 @@ def signal_from_bhz(
2309
2527
  Returns
2310
2528
  -------
2311
2529
  xarray.DataArray
2312
- Time-domain fluorescence lifetime histogram with axes ``'HYX'``,
2530
+ TCSPC histogram with ref:`axes codes <axes>` ``'HYX'``,
2313
2531
  shape ``(256, 256, 256)``, and type ``float32``.
2314
2532
 
2315
2533
  Raises
@@ -2317,6 +2535,11 @@ def signal_from_bhz(
2317
2535
  lfdfiles.LfdFileError
2318
2536
  File is not a SimFCS BHZ file.
2319
2537
 
2538
+ Notes
2539
+ -----
2540
+ The implementation is based on the
2541
+ `lfdfiles <https://github.com/cgohlke/lfdfiles/>`__ library.
2542
+
2320
2543
  Examples
2321
2544
  --------
2322
2545
  >>> signal = signal_from_bhz(fetch('simfcs.bhz'))
@@ -2379,7 +2602,8 @@ def _squeeze_dims(
2379
2602
  ) -> tuple[tuple[int, ...], tuple[str, ...], tuple[bool, ...]]:
2380
2603
  """Return shape and axes with length-1 dimensions removed.
2381
2604
 
2382
- Remove unused dimensions unless their axes are listed in `skip`.
2605
+ Remove unused dimensions unless their axes are listed in the `skip`
2606
+ parameter.
2383
2607
 
2384
2608
  Adapted from the tifffile library.
2385
2609