diff --git a/io/signal.py b/io/signal.py index 3b5eac5943b1cf2b1e956adbced115e65fcd9081..673b13047b9d864294ae36fd9eebc96b365d2004 100644 --- a/io/signal.py +++ b/io/signal.py @@ -1,6 +1,6 @@ +import typing as t from copy import copy from pathlib import PosixPath -from typing import Union import h5py import numpy as np @@ -13,14 +13,22 @@ from agora.io.bridge import BridgeH5 class Signal(BridgeH5): """ Class that fetches data from the hdf5 storage for post-processing + + Signal is works under the assumption that metadata and data are + accessible, to perform time-adjustments and apply previously-recorded + postprocesses. """ - def __init__(self, file: Union[str, PosixPath]): + def __init__(self, file: t.Union[str, PosixPath]): super().__init__(file, flag=None) self.names = ["experiment", "position", "trap"] - def __getitem__(self, dsets): + def __getitem__(self, dsets: t.Union[str, t.Collection]): + + assert isinstance( + dsets, (str, t.Collection) + ), "Incorrect type for dset" if isinstance(dsets, str) and dsets.endswith("imBackground"): df = self.get_raw(dsets) @@ -64,9 +72,10 @@ class Signal(BridgeH5): return f["extraction/general/None/area/timepoint"][-1] + 1 @property - def tinterval(self): + def tinterval(self) -> int: + tinterval_location = "time_settings/timeinterval" with h5py.File(self.filename, "r") as f: - return f.attrs["time_settings/timeinterval"] + return f.attrs[tinterval_location][0] @staticmethod def get_retained(df, cutoff): @@ -79,9 +88,9 @@ class Signal(BridgeH5): return self.get_retained(df, cutoff) elif isinstance(df, list): - return [self.get_retained(d) for d in df] + return [self.get_retained(d, cutoff=cutoff) for d in df] - def apply_prepost(self, dataset: str, skip_pick: bool = None): + def apply_prepost(self, dataset: str, skip_pick: t.Optional[bool] = None): """ Apply modifier operations (picker, merger) to a given dataframe. """ @@ -249,6 +258,10 @@ class Signal(BridgeH5): return df + @property + def stem(self): + return self.filename.stem + @staticmethod def dataset_to_df(f: h5py.File, path: str): @@ -299,3 +312,46 @@ class Signal(BridgeH5): end = find_1st(target.values[::-1], 0, cmp_larger) tgt_copy.iloc[-end:] = source.iloc[-end:].values return tgt_copy + + # TODO FUTURE add stages support to fluigent system + @property + def ntps(self) -> int: + # Return number of time-points according to the metadata + return self.meta_h5["time_settings/ntimepoints"][0] + + @property + def stages(self) -> t.List[str]: + """ + Return the contents of the pump with highest flow rate + at each stage. + """ + flowrate_name = "pumpinit/flowrate" + pumprate_name = "pumprate" + main_pump_id = np.concatenate( + ( + (np.argmax(self.meta_h5[flowrate_name]),), + np.argmax(self.meta_h5[pumprate_name], axis=0), + ) + ) + return [self.meta_h5["pumpinit/contents"][i] for i in main_pump_id] + + @property + def nstages(self) -> int: + switchtimes_name = "switchtimes" + return self.meta_h5[switchtimes_name] + 1 + + @property + def max_span(self) -> int: + return int(self.tinterval * self.ntps / 60) + + @property + def stages_span(self) -> t.Tuple[t.Tuple[str, int], ...]: + # Return consecutive stages and their corresponding number of time-points + switchtimes_name = "switchtimes" + transition_tps = (0, *self.meta_h5[switchtimes_name]) + spans = [ + end - start + for start, end in zip(transition_tps[:-1], transition_tps[1:]) + if end <= self.max_span + ] + return tuple((stage, ntps) for stage, ntps in zip(self.stages, spans))