phasorpy 0.2__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 +55 -37
- phasorpy/io.py +149 -7
- phasorpy/phasor.py +464 -368
- phasorpy/plot.py +6 -2
- phasorpy/version.py +1 -1
- {phasorpy-0.2.dist-info → phasorpy-0.3.dist-info}/METADATA +1 -1
- {phasorpy-0.2.dist-info → phasorpy-0.3.dist-info}/RECORD +12 -12
- {phasorpy-0.2.dist-info → phasorpy-0.3.dist-info}/LICENSE.txt +0 -0
- {phasorpy-0.2.dist-info → phasorpy-0.3.dist-info}/WHEEL +0 -0
- {phasorpy-0.2.dist-info → phasorpy-0.3.dist-info}/entry_points.txt +0 -0
- {phasorpy-0.2.dist-info → phasorpy-0.3.dist-info}/top_level.txt +0 -0
Binary file
|
phasorpy/_phasorpy.pyx
CHANGED
@@ -68,7 +68,8 @@ def _phasor_from_signal(
|
|
68
68
|
float_t[:, :, ::1] phasor,
|
69
69
|
const signal_t[:, :, ::1] signal,
|
70
70
|
const double[:, :, ::1] sincos,
|
71
|
-
const
|
71
|
+
const bint normalize,
|
72
|
+
const int num_threads,
|
72
73
|
):
|
73
74
|
"""Return phasor coordinates from signal along middle axis.
|
74
75
|
|
@@ -97,6 +98,8 @@ def _phasor_from_signal(
|
|
97
98
|
1. number samples
|
98
99
|
2. cos and sin
|
99
100
|
|
101
|
+
normalize : bool
|
102
|
+
Normalize phasor coordinates.
|
100
103
|
num_threads : int
|
101
104
|
Number of OpenMP threads to use for parallelization.
|
102
105
|
|
@@ -145,14 +148,16 @@ def _phasor_from_signal(
|
|
145
148
|
dc = dc + sample
|
146
149
|
re = re + sample * sincos[h, k, 0]
|
147
150
|
im = im + sample * sincos[h, k, 1]
|
148
|
-
if
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
151
|
+
if normalize:
|
152
|
+
if dc != 0.0:
|
153
|
+
# includes isnan(dc)
|
154
|
+
re = re / dc
|
155
|
+
im = im / dc
|
156
|
+
dc = dc / samples
|
157
|
+
else:
|
158
|
+
# dc = 0.0
|
159
|
+
re = NAN if re == 0.0 else re * INFINITY
|
160
|
+
im = NAN if im == 0.0 else im * INFINITY
|
156
161
|
if h == 0:
|
157
162
|
mean[i, j] = <float_t> dc
|
158
163
|
real[h, i, j] = <float_t> re
|
@@ -173,14 +178,16 @@ def _phasor_from_signal(
|
|
173
178
|
dc = dc + sample
|
174
179
|
re = re + sample * sincos[h, k, 0]
|
175
180
|
im = im + sample * sincos[h, k, 1]
|
176
|
-
if
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
181
|
+
if normalize:
|
182
|
+
if dc != 0.0:
|
183
|
+
# includes isnan(dc)
|
184
|
+
re = re / dc
|
185
|
+
im = im / dc
|
186
|
+
dc = dc / samples
|
187
|
+
else:
|
188
|
+
# dc = 0.0
|
189
|
+
re = NAN if re == 0.0 else re * INFINITY
|
190
|
+
im = NAN if im == 0.0 else im * INFINITY
|
184
191
|
if h == 0:
|
185
192
|
mean[i, j] = <float_t> dc
|
186
193
|
real[h, i, j] = <float_t> re
|
@@ -201,14 +208,16 @@ def _phasor_from_signal(
|
|
201
208
|
dc += sample
|
202
209
|
re += sample * sincos[h, k, 0]
|
203
210
|
im += sample * sincos[h, k, 1]
|
204
|
-
if
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
211
|
+
if normalize:
|
212
|
+
if dc != 0.0:
|
213
|
+
# includes isnan(dc)
|
214
|
+
re /= dc
|
215
|
+
im /= dc
|
216
|
+
dc = dc / samples
|
217
|
+
else:
|
218
|
+
# dc = 0.0
|
219
|
+
re = NAN if re == 0.0 else re * INFINITY
|
220
|
+
im = NAN if im == 0.0 else im * INFINITY
|
212
221
|
if h == 0:
|
213
222
|
mean[i, j] = <float_t> dc
|
214
223
|
real[h, i, j] = <float_t> re
|
@@ -924,32 +933,41 @@ cdef (float_t, float_t) _phasor_at_harmonic(
|
|
924
933
|
|
925
934
|
@cython.ufunc
|
926
935
|
cdef (float_t, float_t) _phasor_multiply(
|
927
|
-
float_t
|
928
|
-
float_t
|
936
|
+
float_t real,
|
937
|
+
float_t imag,
|
929
938
|
float_t real2,
|
930
939
|
float_t imag2,
|
931
940
|
) noexcept nogil:
|
932
|
-
"""Return multiplication of two phasors."""
|
933
|
-
return
|
941
|
+
"""Return complex multiplication of two phasors."""
|
942
|
+
return (
|
943
|
+
real * real2 - imag * imag2,
|
944
|
+
real * imag2 + imag * real2
|
945
|
+
)
|
934
946
|
|
935
947
|
|
936
948
|
@cython.ufunc
|
937
949
|
cdef (float_t, float_t) _phasor_divide(
|
938
|
-
float_t
|
939
|
-
float_t
|
950
|
+
float_t real,
|
951
|
+
float_t imag,
|
940
952
|
float_t real2,
|
941
953
|
float_t imag2,
|
942
954
|
) noexcept nogil:
|
943
|
-
"""Return division of two phasors."""
|
955
|
+
"""Return complex division of two phasors."""
|
944
956
|
cdef:
|
945
|
-
float_t
|
957
|
+
float_t divisor = real2 * real2 + imag2 * imag2
|
946
958
|
|
947
|
-
if
|
948
|
-
|
959
|
+
if divisor != 0.0:
|
960
|
+
# includes isnan(divisor)
|
961
|
+
return (
|
962
|
+
(real * real2 + imag * imag2) / divisor,
|
963
|
+
(imag * real2 - real * imag2) / divisor
|
964
|
+
)
|
949
965
|
|
966
|
+
real = real * real2 + imag * imag2
|
967
|
+
imag = imag * real2 - real * imag2
|
950
968
|
return (
|
951
|
-
|
952
|
-
|
969
|
+
NAN if real == 0.0 else real * INFINITY,
|
970
|
+
NAN if imag == 0.0 else imag * INFINITY
|
953
971
|
)
|
954
972
|
|
955
973
|
|
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',
|
@@ -887,6 +889,146 @@ def read_lsm(
|
|
887
889
|
return DataArray(data, **metadata)
|
888
890
|
|
889
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
|
+
|
890
1032
|
def read_ifli(
|
891
1033
|
filename: str | PathLike[Any],
|
892
1034
|
/,
|
@@ -942,8 +1084,8 @@ def read_ifli(
|
|
942
1084
|
(256, 256, 4, 3)
|
943
1085
|
>>> data.dims
|
944
1086
|
('Y', 'X', 'F', 'S')
|
945
|
-
>>> data.coords['F'].data
|
946
|
-
array([8.
|
1087
|
+
>>> data.coords['F'].data # doctest: +NUMBER
|
1088
|
+
array([8.033e+07, 1.607e+08, 2.41e+08, 4.017e+08])
|
947
1089
|
>>> data.coords['S'].data
|
948
1090
|
array(['mean', 'real', 'imag'], dtype='<U4')
|
949
1091
|
>>> data.attrs
|
@@ -1035,8 +1177,8 @@ def read_sdt(
|
|
1035
1177
|
('Y', 'X', 'H')
|
1036
1178
|
>>> data.coords['H'].data
|
1037
1179
|
array(...)
|
1038
|
-
>>> data.attrs['frequency']
|
1039
|
-
79
|
1180
|
+
>>> data.attrs['frequency'] # doctest: +NUMBER
|
1181
|
+
79.99
|
1040
1182
|
|
1041
1183
|
"""
|
1042
1184
|
import sdtfile
|
@@ -1148,7 +1290,7 @@ def read_ptu(
|
|
1148
1290
|
('T', 'Y', 'X', 'C', 'H')
|
1149
1291
|
>>> data.coords['H'].data
|
1150
1292
|
array(...)
|
1151
|
-
>>> data.attrs['frequency']
|
1293
|
+
>>> data.attrs['frequency'] # doctest: +NUMBER
|
1152
1294
|
78.02
|
1153
1295
|
|
1154
1296
|
"""
|
@@ -1221,8 +1363,8 @@ def read_flif(
|
|
1221
1363
|
('H', 'Y', 'X')
|
1222
1364
|
>>> data.coords['H'].data
|
1223
1365
|
array(...)
|
1224
|
-
>>> data.attrs['frequency']
|
1225
|
-
80.65
|
1366
|
+
>>> data.attrs['frequency'] # doctest: +NUMBER
|
1367
|
+
80.65
|
1226
1368
|
|
1227
1369
|
"""
|
1228
1370
|
import lfdfiles
|