phasorpy 0.4__cp312-cp312-win_arm64.whl → 0.5__cp312-cp312-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 +382 -158
- phasorpy/_phasorpy.cp312-win_arm64.pyd +0 -0
- phasorpy/_phasorpy.pyx +54 -51
- phasorpy/_utils.py +89 -7
- phasorpy/cli.py +2 -0
- phasorpy/cluster.py +170 -0
- phasorpy/color.py +16 -11
- phasorpy/components.py +18 -18
- phasorpy/conftest.py +2 -0
- phasorpy/cursors.py +9 -9
- phasorpy/datasets.py +129 -51
- phasorpy/io.py +4 -0
- phasorpy/phasor.py +265 -96
- phasorpy/plot.py +251 -27
- phasorpy/utils.py +12 -7
- phasorpy/version.py +13 -5
- {phasorpy-0.4.dist-info → phasorpy-0.5.dist-info}/METADATA +10 -15
- phasorpy-0.5.dist-info/RECORD +26 -0
- {phasorpy-0.4.dist-info → phasorpy-0.5.dist-info}/WHEEL +1 -1
- phasorpy-0.4.dist-info/RECORD +0 -25
- {phasorpy-0.4.dist-info → phasorpy-0.5.dist-info}/entry_points.txt +0 -0
- {phasorpy-0.4.dist-info → phasorpy-0.5.dist-info/licenses}/LICENSE.txt +0 -0
- {phasorpy-0.4.dist-info → phasorpy-0.5.dist-info}/top_level.txt +0 -0
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
|
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
|
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
|
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
|
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
|
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
|
-
|
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
|
1003
|
+
"""Return phasor coordinates and metadata from Leica image file.
|
974
1004
|
|
975
|
-
|
976
|
-
from the analysis of
|
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
|
982
|
-
|
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
|
1030
|
+
May not be present in all files.
|
1001
1031
|
- ``'flim_rawdata'`` (dict):
|
1002
|
-
Settings from
|
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
|
1040
|
+
File is not a Leica image file.
|
1008
1041
|
ValueError
|
1009
|
-
File
|
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
|
1061
|
+
# TODO: get calibration settings from XML metadata, lifetime, or
|
1024
1062
|
# phasor plot images
|
1025
1063
|
import liffile
|
1026
1064
|
|
1027
|
-
if
|
1028
|
-
|
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
|
-
|
1035
|
-
dims =
|
1036
|
-
coords =
|
1070
|
+
im = lif.images[image + 'Phasor Intensity$']
|
1071
|
+
dims = im.dims
|
1072
|
+
coords = im.coords
|
1037
1073
|
# meta = image.attrs
|
1038
|
-
mean =
|
1039
|
-
real = lif.
|
1040
|
-
imag = lif.
|
1041
|
-
# mask = lif.
|
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
|
-
|
1049
|
-
if
|
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
|
-
|
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
|
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
|
-
|
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('
|
1281
|
+
... fetch('Convallaria_m2_1740751781_phasor_ch1.json'), harmonic='all'
|
1130
1282
|
... )
|
1131
1283
|
>>> real.shape
|
1132
|
-
(
|
1284
|
+
(3, 256, 256)
|
1133
1285
|
>>> attrs['dims']
|
1134
1286
|
('Y', 'X')
|
1135
1287
|
>>> attrs['harmonic']
|
1136
|
-
[1, 2, 3
|
1288
|
+
[1, 2, 3]
|
1137
1289
|
>>> attrs['frequency'] # doctest: +NUMBER
|
1138
|
-
|
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
|
1180
|
-
|
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
|
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
|
-
|
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
|
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
|
1275
|
-
|
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('
|
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
|
-
|
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=}
|
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
|
-
|
1549
|
+
image: int | str | None = None,
|
1394
1550
|
dim: Literal['λ', 'Λ'] | str = 'λ',
|
1395
1551
|
) -> DataArray:
|
1396
|
-
"""Return hyperspectral image and metadata from Leica
|
1552
|
+
"""Return hyperspectral image and metadata from Leica image file.
|
1397
1553
|
|
1398
|
-
|
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
|
1407
|
-
|
1408
|
-
Index or regex pattern of image
|
1409
|
-
By default, return the first
|
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
|
1579
|
+
File is not a Leica image file.
|
1426
1580
|
ValueError
|
1427
|
-
File is not
|
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
|
1446
|
-
# find
|
1447
|
-
for
|
1448
|
-
if dim in
|
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
|
-
|
1617
|
+
im = lif.images[image]
|
1456
1618
|
|
1457
|
-
if dim not in
|
1458
|
-
raise ValueError(f'{
|
1459
|
-
if 'C' in
|
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 =
|
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
|
1629
|
+
for (k, v) in im.coords.items()
|
1468
1630
|
}
|
1469
|
-
dims = tuple(('C' if d == dim else d) for d in
|
1631
|
+
dims = tuple(('C' if d == dim else d) for d in im.dims)
|
1470
1632
|
|
1471
|
-
metadata = _metadata(dims,
|
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
|
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
|
1600
|
-
|
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(
|
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(
|
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
|
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
|
-
|
1749
|
-
|
1750
|
-
|
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('
|
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
|
1819
|
-
|
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
|
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
|
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
|
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
|
-
|
1859
|
-
|
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
|
2128
|
+
"""Return phase images and metadata from FlimFast FLIF file.
|
1944
2129
|
|
1945
|
-
FlimFast FLIF files contain
|
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
|
-
|
1957
|
-
|
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
|
2217
|
+
"""Return phase histogram and metadata from FLIMbox FBD file.
|
2028
2218
|
|
2029
|
-
FDB files contain encoded
|
2030
|
-
|
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
|
-
|
2057
|
-
|
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
|
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
|
-
|
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
|
2201
|
-
or
|
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
|
-
|
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
|
2462
|
+
"""Return TCSPC histogram and metadata from SimFCS B&H file.
|
2248
2463
|
|
2249
|
-
B&H files contain
|
2250
|
-
|
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
|
-
|
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
|
2517
|
+
"""Return TCSPC histogram and metadata from SimFCS BHZ file.
|
2299
2518
|
|
2300
|
-
BHZ files contain
|
2301
|
-
|
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
|
-
|
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
|
|