phasorpy 0.1__cp313-cp313-win_amd64.whl → 0.3__cp313-cp313-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- phasorpy/_phasorpy.cp313-win_amd64.pyd +0 -0
- phasorpy/_phasorpy.pyx +388 -38
- phasorpy/_utils.py +27 -14
- phasorpy/datasets.py +20 -0
- phasorpy/io.py +156 -16
- phasorpy/phasor.py +521 -249
- phasorpy/plot.py +6 -2
- phasorpy/utils.py +301 -1
- phasorpy/version.py +1 -1
- {phasorpy-0.1.dist-info → phasorpy-0.3.dist-info}/METADATA +22 -22
- phasorpy-0.3.dist-info/RECORD +24 -0
- {phasorpy-0.1.dist-info → phasorpy-0.3.dist-info}/WHEEL +1 -1
- phasorpy-0.1.dist-info/RECORD +0 -24
- {phasorpy-0.1.dist-info → phasorpy-0.3.dist-info}/LICENSE.txt +0 -0
- {phasorpy-0.1.dist-info → phasorpy-0.3.dist-info}/entry_points.txt +0 -0
- {phasorpy-0.1.dist-info → phasorpy-0.3.dist-info}/top_level.txt +0 -0
phasorpy/io.py
CHANGED
@@ -16,6 +16,7 @@ The ``phasorpy.io`` module provides functions to:
|
|
16
16
|
- read time-resolved and hyperspectral image data and metadata (as relevant
|
17
17
|
to phasor analysis) from many file formats used in bio-imaging:
|
18
18
|
|
19
|
+
- :py:func:`read_imspector_tiff` - ImSpector FLIM TIFF
|
19
20
|
- :py:func:`read_lsm` - Zeiss LSM
|
20
21
|
- :py:func:`read_ifli` - ISS IFLI
|
21
22
|
- :py:func:`read_sdt` - Becker & Hickl SDT
|
@@ -123,6 +124,7 @@ __all__ = [
|
|
123
124
|
'read_fbd',
|
124
125
|
'read_flif',
|
125
126
|
'read_ifli',
|
127
|
+
'read_imspector_tiff',
|
126
128
|
# 'read_lif',
|
127
129
|
'read_lsm',
|
128
130
|
# 'read_nd2',
|
@@ -290,11 +292,9 @@ def phasor_to_ometiff(
|
|
290
292
|
imag = imag.reshape(1, -1)
|
291
293
|
|
292
294
|
if harmonic is not None:
|
293
|
-
|
294
|
-
if
|
295
|
+
harmonic, _ = parse_harmonic(harmonic)
|
296
|
+
if len(harmonic) != nharmonic:
|
295
297
|
raise ValueError('invalid harmonic')
|
296
|
-
samples = int(harmonic_array.max()) * 2 + 1
|
297
|
-
harmonic, _ = parse_harmonic(harmonic, samples)
|
298
298
|
|
299
299
|
if frequency is not None:
|
300
300
|
frequency_array = numpy.atleast_2d(frequency).astype(numpy.float64)
|
@@ -488,7 +488,7 @@ def phasor_from_ometiff(
|
|
488
488
|
|
489
489
|
has_harmonic_dim = tif.series[1].ndim > tif.series[0].ndim
|
490
490
|
nharmonics = tif.series[1].shape[0] if has_harmonic_dim else 1
|
491
|
-
|
491
|
+
harmonic_max = nharmonics
|
492
492
|
for i in (3, 4):
|
493
493
|
if len(tif.series) < i + 1:
|
494
494
|
break
|
@@ -499,10 +499,10 @@ def phasor_from_ometiff(
|
|
499
499
|
elif series.name == 'Phasor harmonic':
|
500
500
|
if not has_harmonic_dim and data.size == 1:
|
501
501
|
attrs['harmonic'] = int(data.item(0))
|
502
|
-
|
502
|
+
harmonic_max = attrs['harmonic']
|
503
503
|
elif has_harmonic_dim and data.size == nharmonics:
|
504
504
|
attrs['harmonic'] = data.tolist()
|
505
|
-
|
505
|
+
harmonic_max = max(attrs['harmonic'])
|
506
506
|
else:
|
507
507
|
logger.warning(
|
508
508
|
f'harmonic={data} does not match phasor '
|
@@ -535,7 +535,7 @@ def phasor_from_ometiff(
|
|
535
535
|
imag = tif.series[2].asarray()
|
536
536
|
else:
|
537
537
|
# specified harmonics
|
538
|
-
harmonic, keepdims = parse_harmonic(harmonic,
|
538
|
+
harmonic, keepdims = parse_harmonic(harmonic, harmonic_max)
|
539
539
|
try:
|
540
540
|
if isinstance(harmonic_stored, list):
|
541
541
|
index = [harmonic_stored.index(h) for h in harmonic]
|
@@ -769,7 +769,7 @@ def phasor_from_simfcs_referenced(
|
|
769
769
|
else:
|
770
770
|
raise ValueError(f'file extension must be .ref or .r64, not {ext!r}')
|
771
771
|
|
772
|
-
harmonic, keep_harmonic_dim = parse_harmonic(harmonic, data.shape[0])
|
772
|
+
harmonic, keep_harmonic_dim = parse_harmonic(harmonic, data.shape[0] // 2)
|
773
773
|
|
774
774
|
mean = data[0].copy()
|
775
775
|
real = numpy.empty((len(harmonic),) + mean.shape, numpy.float32)
|
@@ -889,6 +889,146 @@ def read_lsm(
|
|
889
889
|
return DataArray(data, **metadata)
|
890
890
|
|
891
891
|
|
892
|
+
def read_imspector_tiff(
|
893
|
+
filename: str | PathLike[Any],
|
894
|
+
/,
|
895
|
+
) -> DataArray:
|
896
|
+
"""Return FLIM image stack and metadata from ImSpector TIFF file.
|
897
|
+
|
898
|
+
Parameters
|
899
|
+
----------
|
900
|
+
filename : str or Path
|
901
|
+
Name of ImSpector FLIM TIFF file to read.
|
902
|
+
|
903
|
+
Returns
|
904
|
+
-------
|
905
|
+
xarray.DataArray
|
906
|
+
TCSPC image stack.
|
907
|
+
Usually, a 3-to-5-dimensional array of type ``uint16``.
|
908
|
+
|
909
|
+
- ``coords['H']``: times of histogram bins.
|
910
|
+
- ``attrs['frequency']``: repetition frequency in MHz.
|
911
|
+
|
912
|
+
Raises
|
913
|
+
------
|
914
|
+
tifffile.TiffFileError
|
915
|
+
File is not a TIFF file.
|
916
|
+
ValueError
|
917
|
+
File is not an ImSpector FLIM TIFF file.
|
918
|
+
|
919
|
+
Examples
|
920
|
+
--------
|
921
|
+
>>> data = read_imspector_tiff(fetch('Embryo.tif'))
|
922
|
+
>>> data.values
|
923
|
+
array(...)
|
924
|
+
>>> data.dtype
|
925
|
+
dtype('uint16')
|
926
|
+
>>> data.shape
|
927
|
+
(56, 512, 512)
|
928
|
+
>>> data.dims
|
929
|
+
('H', 'Y', 'X')
|
930
|
+
>>> data.coords['H'].data # dtime bins
|
931
|
+
array(...)
|
932
|
+
>>> data.attrs['frequency'] # doctest: +NUMBER
|
933
|
+
80.109
|
934
|
+
|
935
|
+
"""
|
936
|
+
from xml.etree import ElementTree
|
937
|
+
|
938
|
+
import tifffile
|
939
|
+
|
940
|
+
with tifffile.TiffFile(filename) as tif:
|
941
|
+
tags = tif.pages.first.tags
|
942
|
+
omexml = tags.valueof(270, '')
|
943
|
+
make = tags.valueof(271, '')
|
944
|
+
|
945
|
+
if (
|
946
|
+
make != 'ImSpector'
|
947
|
+
or not omexml.startswith('<?xml version')
|
948
|
+
or len(tif.series) != 1
|
949
|
+
or not tif.is_ome
|
950
|
+
):
|
951
|
+
raise ValueError(f'{tif.filename} is not an ImSpector TIFF file')
|
952
|
+
|
953
|
+
series = tif.series[0]
|
954
|
+
ndim = series.ndim
|
955
|
+
axes = series.axes
|
956
|
+
shape = series.shape
|
957
|
+
|
958
|
+
if ndim < 3 or not axes.endswith('YX'):
|
959
|
+
raise ValueError(
|
960
|
+
f'{tif.filename} is not an ImSpector FLIM TIFF file'
|
961
|
+
)
|
962
|
+
|
963
|
+
data = series.asarray()
|
964
|
+
|
965
|
+
attrs: dict[str, Any] = {}
|
966
|
+
coords = {}
|
967
|
+
physical_size = {}
|
968
|
+
|
969
|
+
root = ElementTree.fromstring(omexml)
|
970
|
+
ns = {
|
971
|
+
'': 'http://www.openmicroscopy.org/Schemas/OME/2008-02',
|
972
|
+
'ca': 'http://www.openmicroscopy.org/Schemas/CA/2008-02',
|
973
|
+
}
|
974
|
+
|
975
|
+
description = root.find('.//Description', ns)
|
976
|
+
if (
|
977
|
+
description is not None
|
978
|
+
and description.text
|
979
|
+
and description.text != 'not_specified'
|
980
|
+
):
|
981
|
+
attrs['description'] = description.text
|
982
|
+
|
983
|
+
pixels = root.find('.//Image/Pixels', ns)
|
984
|
+
assert pixels is not None
|
985
|
+
for ax in 'TZYX':
|
986
|
+
attrib = 'TimeIncrement' if ax == 'T' else f'PhysicalSize{ax}'
|
987
|
+
if ax not in axes or attrib not in pixels.attrib:
|
988
|
+
continue
|
989
|
+
size = float(pixels.attrib[attrib])
|
990
|
+
physical_size[ax] = size
|
991
|
+
coords[ax] = numpy.linspace(
|
992
|
+
0.0,
|
993
|
+
size,
|
994
|
+
shape[axes.index(ax)],
|
995
|
+
endpoint=False,
|
996
|
+
dtype=numpy.float64,
|
997
|
+
)
|
998
|
+
|
999
|
+
axes_labels = root.find('.//ca:CustomAttributes/AxesLabels', ns)
|
1000
|
+
if (
|
1001
|
+
axes_labels is None
|
1002
|
+
or 'X' not in axes_labels.attrib
|
1003
|
+
or 'TCSPC' not in axes_labels.attrib['X']
|
1004
|
+
or 'FirstAxis' not in axes_labels.attrib
|
1005
|
+
or 'SecondAxis' not in axes_labels.attrib
|
1006
|
+
):
|
1007
|
+
raise ValueError(f'{tif.filename} is not an ImSpector FLIM TIFF file')
|
1008
|
+
|
1009
|
+
if axes_labels.attrib['FirstAxis'].endswith('TCSPC T'):
|
1010
|
+
ax = axes[-3]
|
1011
|
+
assert axes_labels.attrib['FirstAxis-Unit'] == 'ns'
|
1012
|
+
elif axes_labels.attrib['SecondAxis'].endswith('TCSPC T') and ndim > 3:
|
1013
|
+
ax = axes[-4]
|
1014
|
+
assert axes_labels.attrib['SecondAxis-Unit'] == 'ns'
|
1015
|
+
else:
|
1016
|
+
raise ValueError(f'{tif.filename} is not an ImSpector FLIM TIFF file')
|
1017
|
+
axes = axes.replace(ax, 'H')
|
1018
|
+
coords['H'] = coords[ax]
|
1019
|
+
del coords[ax]
|
1020
|
+
|
1021
|
+
attrs['frequency'] = float(
|
1022
|
+
1000.0 / (shape[axes.index('H')] * physical_size[ax])
|
1023
|
+
)
|
1024
|
+
|
1025
|
+
metadata = _metadata(axes, shape, filename, attrs=attrs, **coords)
|
1026
|
+
|
1027
|
+
from xarray import DataArray
|
1028
|
+
|
1029
|
+
return DataArray(data, **metadata)
|
1030
|
+
|
1031
|
+
|
892
1032
|
def read_ifli(
|
893
1033
|
filename: str | PathLike[Any],
|
894
1034
|
/,
|
@@ -944,8 +1084,8 @@ def read_ifli(
|
|
944
1084
|
(256, 256, 4, 3)
|
945
1085
|
>>> data.dims
|
946
1086
|
('Y', 'X', 'F', 'S')
|
947
|
-
>>> data.coords['F'].data
|
948
|
-
array([8.
|
1087
|
+
>>> data.coords['F'].data # doctest: +NUMBER
|
1088
|
+
array([8.033e+07, 1.607e+08, 2.41e+08, 4.017e+08])
|
949
1089
|
>>> data.coords['S'].data
|
950
1090
|
array(['mean', 'real', 'imag'], dtype='<U4')
|
951
1091
|
>>> data.attrs
|
@@ -1037,8 +1177,8 @@ def read_sdt(
|
|
1037
1177
|
('Y', 'X', 'H')
|
1038
1178
|
>>> data.coords['H'].data
|
1039
1179
|
array(...)
|
1040
|
-
>>> data.attrs['frequency']
|
1041
|
-
79
|
1180
|
+
>>> data.attrs['frequency'] # doctest: +NUMBER
|
1181
|
+
79.99
|
1042
1182
|
|
1043
1183
|
"""
|
1044
1184
|
import sdtfile
|
@@ -1150,7 +1290,7 @@ def read_ptu(
|
|
1150
1290
|
('T', 'Y', 'X', 'C', 'H')
|
1151
1291
|
>>> data.coords['H'].data
|
1152
1292
|
array(...)
|
1153
|
-
>>> data.attrs['frequency']
|
1293
|
+
>>> data.attrs['frequency'] # doctest: +NUMBER
|
1154
1294
|
78.02
|
1155
1295
|
|
1156
1296
|
"""
|
@@ -1223,8 +1363,8 @@ def read_flif(
|
|
1223
1363
|
('H', 'Y', 'X')
|
1224
1364
|
>>> data.coords['H'].data
|
1225
1365
|
array(...)
|
1226
|
-
>>> data.attrs['frequency']
|
1227
|
-
80.65
|
1366
|
+
>>> data.attrs['frequency'] # doctest: +NUMBER
|
1367
|
+
80.65
|
1228
1368
|
|
1229
1369
|
"""
|
1230
1370
|
import lfdfiles
|