Skip to content

Commit 57c45be

Browse files
Some tweaks to dF/F code
1 parent d84ba5e commit 57c45be

File tree

6 files changed

+322
-76
lines changed

6 files changed

+322
-76
lines changed

siffpy/core/flim/flimparams.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,6 +1022,36 @@ def sample(
10221022

10231023
return np.histogram(samples, bins = x_range)[0]
10241024

1025+
def fraction_to_empirical(self, fractions : np.ndarray) -> np.ndarray:
1026+
"""
1027+
Converts the `fractions` argument into the equivalent empirical lifetime
1028+
using the exponential parameter fits.
1029+
1030+
## Arguments
1031+
1032+
- `fractions : np.ndarray`
1033+
1034+
An array of fractions of the total photons in each bin. Should be
1035+
dimensions (..., self.n_exps - 1), with the presumption that the final
1036+
bin adds up to 1 (i.e. fractions.sum(axis=-1) <= 1)
1037+
1038+
## Returns
1039+
1040+
- `empirical : np.ndarray`
1041+
1042+
An array of the empirical lifetimes corresponding to the fractions
1043+
in the input array.
1044+
"""
1045+
if self.n_exp == 2:
1046+
fractions = np.expand_dims(fractions, axis=-1)
1047+
1048+
return (
1049+
fractions @ np.array([exp.tau for exp in self.exps[:-1]])
1050+
+ ((1-fractions.sum(axis=-1)) * self.exps[-1].tau)
1051+
)
1052+
1053+
1054+
10251055
def __eq__(self, other)->bool:
10261056
"""
10271057
Two FLIMParams objects are equal if they have the same parameters

siffpy/core/siffreader.py

Lines changed: 61 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -192,10 +192,12 @@ def open(
192192
self._time_axis_epoch = None
193193

194194
#self.events = io.find_events(self.im_params, self.get_frames_metadata())
195-
flim_params = io.load_flim_params(filename)
196-
if any(flim_params):
197-
self._flim_params = flim_params
198-
#print("Finished opening and reading file")
195+
try:
196+
flim_params = io.load_flim_params(filename)
197+
if any(flim_params):
198+
self._flim_params = flim_params
199+
except Exception as e:
200+
print(f"Failed to load FLIM parameters with error: {e}")
199201

200202
def close(self) -> None:
201203
""" Closes opened file """
@@ -518,16 +520,16 @@ def sec_to_frames(self, seconds : float, base : str = 'volume')->int:
518520
519521
# Arguments
520522
521-
* seconds : float
522-
Time in seconds to convert
523+
* `seconds` : *float*
524+
* Time in seconds to convert
523525
524-
* base : str
525-
Base to convert to. Can be 'volume' or 'frame'. Defaults to 'volume'.
526+
* `base` : *str*
527+
* Base to convert to. Can be 'volume' or 'frame'. Defaults to 'volume'.
526528
527529
# Returns
528530
529-
* frames : int
530-
Number of frames that time represents.
531+
* `frames` : *int*
532+
* Number of frames that time represents.
531533
532534
# Example
533535
@@ -544,7 +546,7 @@ def sec_to_frames(self, seconds : float, base : str = 'volume')->int:
544546
>> 10
545547
```
546548
"""
547-
549+
base = base.lower()
548550
if base == 'volume':
549551
return int(seconds/self.dt_volume)
550552
if base == 'frame':
@@ -558,6 +560,20 @@ def get_frames_metadata(self, frames : Optional[List[int]] = None) -> List[io.Fr
558560
return [io.FrameMetaData(meta_dict)
559561
for meta_dict in io.frame_metadata_to_dict(self.siffio.get_frame_metadata(frames=frames))
560562
]
563+
564+
def epoch_time_to_volume_index(self, epoch_time : int) -> int:
565+
"""
566+
Returns the index of the volume containing the epoch time provided.
567+
"""
568+
return np.searchsorted(self.t_axis(reference_time = 'epoch'), epoch_time)
569+
570+
def epoch_time_to_frame_index(self, epoch_time : int, correct_flyback : bool = False) -> int:
571+
"""
572+
Returns the index of the frame containing the epoch time provided. Note:
573+
by default frames are numbered by _frame triggers_,
574+
meaning they include flyback frames in their indexing!
575+
"""
576+
return np.searchsorted(self.get_time(reference_time = 'epoch'), epoch_time)
561577

562578
def epoch_to_frame_time(self, epoch_time : int) -> float:
563579
""" Converts epoch time to frame time for this experiment (returned in seconds) """
@@ -1326,11 +1342,6 @@ def get_frames_flim(
13261342

13271343
method = FlimMethod(method)
13281344

1329-
if method != FlimMethod.EMPIRICAL:
1330-
raise NotImplementedError(
1331-
"Only empirical lifetime method is implemented in `siffio` backend so far"
1332-
)
1333-
13341345
registration_dict = self.registration_dict if (
13351346
registration_dict is None
13361347
and hasattr(self, 'registration_dict')
@@ -1354,8 +1365,6 @@ def get_frames_flim(
13541365
method = method.value,
13551366
units = 'countbins',
13561367
)
1357-
1358-
13591368

13601369
def _get_frames_flim_srm(
13611370
self,
@@ -1382,10 +1391,6 @@ def _get_frames_flim_srm(
13821391
frames = list(range(self.im_params.num_frames))
13831392

13841393
method = FlimMethod(method)
1385-
if method != FlimMethod.EMPIRICAL:
1386-
raise NotImplementedError(
1387-
"Only empirical lifetime method is implemented in `SiffIO` backend so far"
1388-
)
13891394

13901395
registration_dict = self.registration_dict if (
13911396
registration_dict is None
@@ -1471,6 +1476,10 @@ def sum_mask_flim(
14711476
* `return_framewise` : bool
14721477
If True, does not sum across timepoints, and returns a flat array
14731478
corresponding to the frames.
1479+
1480+
* `flim_method` : Union[str, FlimMethod]
1481+
The method to use for the FLIM analysis. Default is 'empirical'.
1482+
Options are any of the `FlimMethod` enum values (`siffpy.siffmath.flim.FlimMethod`).
14741483
14751484
# Returns
14761485
@@ -1501,10 +1510,6 @@ def sum_mask_flim(
15011510
)
15021511

15031512
flim_method = FlimMethod(flim_method)
1504-
if flim_method != FlimMethod.EMPIRICAL:
1505-
raise NotImplementedError(
1506-
"Only empirical lifetime method is implemented in `siffio` backend so far"
1507-
)
15081513

15091514
timepoint_end = (
15101515
self.im_params.num_timepoints
@@ -1545,7 +1550,7 @@ def sum_mask_flim(
15451550
summed_flim_data,
15461551
intensity = summed_intensity_data,
15471552
FLIMParams = params,
1548-
method = 'empirical lifetime',
1553+
method = flim_method.value,
15491554
info_string = "ROI",
15501555
units = FlimUnits.COUNTBINS,
15511556
)
@@ -1554,7 +1559,7 @@ def sum_mask_flim(
15541559
summed_flim_data,
15551560
intensity = summed_intensity_data,
15561561
FLIMParams = params,
1557-
method = 'empirical lifetime',
1562+
method = flim_method.value,
15581563
info_string = "ROI",
15591564
units = FlimUnits.COUNTBINS,
15601565
).reshape(
@@ -1701,6 +1706,25 @@ def sum_masks_flim(
17011706
If True, returns a flat array of all frames, rather than summing across
17021707
timepoints.
17031708
1709+
* `flim_method` : Union[str, FlimMethod]
1710+
The method to use for the FLIM analysis. Default is 'empirical'.
1711+
Options are any of the `FlimMethod` enum values (`siffpy.siffmath.flim.FlimMethod`).
1712+
1713+
# Returns
1714+
1715+
* FlimTrace
1716+
TODO :DOCUMENT
1717+
1718+
# Example
1719+
1720+
TODO
1721+
```python
1722+
1723+
from siffpy import SiffReader
1724+
reader = SiffReader('example.siff')
1725+
1726+
```
1727+
17041728
"""
17051729
if self.backend == 'siffreadermodule':
17061730
return self._sum_masks_flim_srm(
@@ -1715,10 +1739,10 @@ def sum_masks_flim(
17151739
)
17161740

17171741
flim_method = FlimMethod(flim_method)
1718-
if flim_method != FlimMethod.EMPIRICAL:
1719-
raise NotImplementedError(
1720-
"Only empirical lifetime method is implemented in `siffio` backend so far"
1721-
)
1742+
# if flim_method != FlimMethod.EMPIRICAL:
1743+
# raise NotImplementedError(
1744+
# "Only empirical lifetime method is implemented in `siffio` backend so far"
1745+
# )
17221746

17231747
if isinstance(masks, list):
17241748
masks = np.array(masks).squeeze()
@@ -1765,7 +1789,7 @@ def sum_masks_flim(
17651789
flim_summed,
17661790
intensity = intensity_summed,
17671791
FLIMParams = params,
1768-
method = 'empirical lifetime',
1792+
method = flim_method.value,
17691793
info_string = "Multi-ROIs",
17701794
units = FlimUnits.COUNTBINS,
17711795
)
@@ -1776,7 +1800,7 @@ def sum_masks_flim(
17761800
flim_summed,
17771801
intensity = intensity_summed,
17781802
FLIMParams = params,
1779-
method = 'empirical lifetime',
1803+
method = flim_method.value,
17801804
info_string = "Multi-ROIs",
17811805
units = FlimUnits.COUNTBINS,
17821806
).reshape(
@@ -1985,4 +2009,6 @@ def _rinfo_safe_convert(registration_info : Union[RegistrationInfo, Dict]) -> Di
19852009
if isinstance(registration_info, RegistrationInfo):
19862010
return registration_info.yx_shifts
19872011
if isinstance(registration_info, dict) and 'yx_shifts' in registration_info:
1988-
return registration_info['yx_shifts']
2012+
return registration_info['yx_shifts']
2013+
if isinstance(registration_info, dict):
2014+
return registration_info

siffpy/siffmath/flim/phasor.py

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
"""
22
Generic functions for dealing with phasors.
33
"""
4-
from typing import Any, Union
4+
from typing import Any, Union, Tuple
55
import numpy as np
66

77
from siffpy.core.flim import FLIMParams
88

9-
def tau_to_phasor(tau : Union[np.ndarray,float], rep_period: float) -> Union[np.ndarray,complex]:
9+
def tau_to_phasor(
10+
tau : Union[np.ndarray,float],
11+
rep_period: float,
12+
) -> Union[np.ndarray,complex]:
1013
"""
1114
Returns the corresponding phasor value for a single exponential
1215
with the given lifetime and laser repetition period. Tau
@@ -47,7 +50,10 @@ def tau_to_phasor(tau : Union[np.ndarray,float], rep_period: float) -> Union[np.
4750
s = (2*np.pi*tau/rep_period)/(1 + (2*np.pi*tau/rep_period)**2)
4851
return g + 1j*s
4952

50-
def phasor_to_tau(phasor : Union[np.ndarray,complex], rep_period : float) -> Union[np.ndarray,float]:
53+
def phasor_to_tau(
54+
phasor : Union[np.ndarray,complex],
55+
rep_period : float
56+
) -> Union[np.ndarray,float]:
5157
"""
5258
Converts a phasor to a tau value, assuming it corresponds to a
5359
single exponential with the given laser repetition period.
@@ -70,6 +76,43 @@ def phasor_to_tau(phasor : Union[np.ndarray,complex], rep_period : float) -> Uni
7076
s = phasor.imag
7177
return rep_period*s/(g*2*np.pi)
7278

79+
def phasor_to_lifetimes(
80+
phasor : Union[np.ndarray, complex],
81+
rep_period : float,
82+
) -> Tuple[Union[np.ndarray,float], Union[np.ndarray,float]]:
83+
"""
84+
Returns the modulation and phase lifetime values for a given
85+
phasor using the relations:
86+
- tau_m = sqrt[(1/M)^2 - 1]/(2*pi*f)
87+
- tau_phi = tan(phi)/(2*pi*f)
88+
89+
where M = np.abs(phasor) and phi = np.angle(phasor) and f
90+
is the frequency of laser pulses, with the phasor
91+
value defined as
92+
93+
phasor = M * exp( 1j * phi )
94+
95+
# Arguments
96+
97+
- `phasor : Union[np.ndarray, complex]`
98+
The phasors to transform (as an array or a single value)
99+
100+
- `rep_period : float`
101+
The repetition rate of the laser pulses (in Hz)
102+
103+
# Returns
104+
105+
- `lifetimes : (tau_m : float, tau_phi : float)`
106+
A tuple of the modulation and phase lifetimes in seconds.
107+
108+
"""
109+
m = np.abs(phasor)
110+
phi = np.angle(phasor)
111+
return (
112+
np.sqrt(m**(-2) - 1)*rep_period/(2*np.pi),
113+
np.tan(phi)*rep_period/(2*np.pi)
114+
)
115+
73116
def phasor_to_fraction(
74117
phasor : Union[np.ndarray[Any, np.complex128], complex],
75118
params : FLIMParams,
@@ -101,7 +144,7 @@ def phasor_to_fraction(
101144
102145
`np.ndarray[Any, np.float64]`
103146
The fraction in each exponential state corresponding to the given phasor.
104-
Shape is size(input_phasor) x (params.n_exps - 1), with the last state in
147+
Shape is (params.n_exps - 1) x size(input_phasor), with the last state in
105148
the `FLIMParams` object's `exps` attribute being implicit (1 - sum(fractions)).
106149
"""
107150

@@ -116,7 +159,7 @@ def phasor_to_fraction(
116159
return np.array([
117160
((phasor - startpt)/(endpt - startpt)).real
118161
for endpt, startpt in zip(bounds[:-1], bounds[1:])
119-
]).T.squeeze()
162+
]).T.squeeze().T
120163

121164
def universal_circle() -> np.ndarray[Any, np.complex128]:
122165
"""
@@ -195,6 +238,7 @@ def correct_phasor(
195238
phasor : np.ndarray[Any, np.complex128],
196239
params : 'FLIMParams',
197240
hist_length : int,
241+
rotate_by_offset : bool = True,
198242
subtract_noise : bool = False,
199243
) -> np.ndarray[Any, np.complex128]:
200244
"""
@@ -220,6 +264,11 @@ def correct_phasor(
220264
The length of the histogram used to compute the phasor -- i.e., the
221265
number of histogram bins between each laser pulse.
222266
267+
`rotate_by_offset : bool = True`
268+
Whether or not to rotate the phasor by the offset in the `FLIMParams`
269+
object. If `False`, the phasor will not be corrected for the instrument
270+
response function.
271+
223272
`subtract_noise : bool = False`
224273
Whether or not to additionally project the phasor onto the line
225274
connecting the exponentials of the `FLIMParams` object.
@@ -265,9 +314,11 @@ def correct_phasor(
265314
>>> [0.660594+0.36399072j]
266315
```
267316
"""
317+
rotated_phasor = phasor
268318
with params.as_units('countbins'):
269-
offset = params.tau_offset
270-
rotated_phasor = phasor*np.exp(-1j*2*np.pi*offset/hist_length)
319+
if rotate_by_offset:
320+
offset = params.tau_offset
321+
rotated_phasor *= np.exp(-1j*2*np.pi*offset/hist_length)
271322

272323
if subtract_noise:
273324
if len(params.exps) > 2:

0 commit comments

Comments
 (0)