diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/core/processes/base.py b/core/processes/base.py index 9dadb0470eab4de33f56538e9008382496415fda..142bd6c3fa2c5ef0fc578d3d09bfa47530482619 100644 --- a/core/processes/base.py +++ b/core/processes/base.py @@ -1,54 +1 @@ -from pathlib import Path, PosixPath -from typing import Union -from abc import ABC, abstractmethod - -from yaml import safe_load, dump - - -class ParametersABC(ABC): - """ - Base class to add yaml functionality to parameters - - """ - - def to_dict(self): - return self.__dict__ - - @classmethod - def from_dict(cls, d: dict): - return cls(**d) - - def to_yaml(self, path: Union[PosixPath, str] = None): - if path: - with open(Path(path), "w") as f: - dump(self.to_dict(), f) - return dump(self.to_dict()) - - @classmethod - def from_yaml(cls, path: Union[PosixPath, str]): - with open(Path(file)) as f: - params = safe_load(f) - return cls(**params) - - @classmethod - @abstractmethod - def default(cls): - pass - - -class ProcessABC(ABC): - "Base class for processes" - - def __init__(self, parameters): - self._parameters = parameters - - for k, v in parameters.to_dict().items(): # access parameters directly - setattr(self, k, v) - - @property - def parameters(self): - return self._parameters - - @abstractmethod - def run(self): - pass +from agora.base import ParametersABC, ProcessABC \ No newline at end of file diff --git a/core/processes/dsignal.py b/core/processes/dsignal.py index c596075721934a2517c4a20c648deabce8db1453..484ad59944c12cd12005c02b9470b84d8f2f19d7 100644 --- a/core/processes/dsignal.py +++ b/core/processes/dsignal.py @@ -1,7 +1,6 @@ import pandas as pd from postprocessor.core.processes.base import ParametersABC, ProcessABC -from postprocessor.core.functions.tracks import clean_tracks, merge_tracks, join_tracks class dsignalParameters(ParametersABC): diff --git a/core/processes/gpsignal.py b/core/processes/gpsignal.py new file mode 100644 index 0000000000000000000000000000000000000000..f6cdb45fbd1416630382246fb3152c9d99184076 --- /dev/null +++ b/core/processes/gpsignal.py @@ -0,0 +1,97 @@ +"""Gaussian process fit of a Signal.""" +import logging + +from postprocessor.core.processes.base import ParametersABC, ProcessABC +import numpy as np +import pandas as pd + +import gaussian_processes.gaussianprocess as gp + +def estimate_gr(volume, dt, noruns, bounds, verbose): + """ + Parameters + ---------- + + volume : pd.Series + The volume series of a given cell + dt : float + The time interval in hours + noruns : int + The number of runs for optimisation + bounds : dict + The hyperparameter bounds used for optimisation + verbose : bool + If True, prints results + + Returns + ------- + """ + volume = volume.values + n = len(volume) + idx = np.arange(n) + t = idx * dt + y = volume[volume > 0] + x = t[volume > 0] + idx = idx[volume > 0] + # Fit the GP + mg = gp.maternGP(bounds, x, y) + mg.findhyperparameters(noruns=noruns) + if verbose: + mg.results() # Prints out the hyperparameters + mg.predict(x, derivs=2) # Saves the predictions to object + # Save the predictions to a csv file so they can be reused + results = dict(time=mg.x, volume=mg.y, fit_time=mg.xnew, fit_volume=mg.f, + growth_rate=mg.df, d_growth_rate=mg.ddf, + volume_var=mg.fvar, growth_rate_var=mg.dfvar, + d_growth_rate_var=mg.ddfvar) + for name, value in results.items(): + results[name] = np.full((n, ), np.nan) + results[name][idx] = value + return results + +# Give that to a writer: NOTE the writer needs to learn how to write the +# output of a process that returns multiple signals like this one does. + +class gpParameters(ParametersABC): + default_dict = dict(dt=5, + noruns=5, + bounds={0: (-2, 3), + 1: (-2, 1), + 2: (-4, -1)}, + verbose=False) + def __init__(self, dt, noruns, bounds, verbose): + """ + Parameters + ---------- + dt : float + The time step between time points, in minutes + noruns : int + The number of times the optimisation is tried + bounds : dict + Hyperparameter bounds for the Matern Kernel + verbose : bool + Determines whether to print hyperparameter results + """ + self.dt = dt + self.noruns = noruns + self.bounds = bounds + self.verbose = verbose + + @classmethod + def default(cls): + return cls.from_dict(cls.default_dict) + + +class GPSignal(ProcessABC): + """Gaussian process fit of a Signal. + """ + def __init__(self, parameters: gpParameters): + super().__init__(parameters) + + def run(self, signal: pd.DataFrame): + results = signal.apply(lambda x: estimate_gr(x, + **self.parameters.to_dict()), + result_type='expand').T + multi_signal = {name: pd.DataFrame(np.vstack(results[name])) + for name in results.columns} + return multi_signal diff --git a/core/processes/merger.py b/core/processes/merger.py index 1fa07b9191f7d2e0b2370c54b0364581b00b8de4..f7e90a5cb0b2fe50be7542c642ebd986d8f63152 100644 --- a/core/processes/merger.py +++ b/core/processes/merger.py @@ -1,4 +1,4 @@ -from postprocessor.core.processes.base import ParametersABC, ProcessABC +from agora.base import ParametersABC, ProcessABC from postprocessor.core.functions.tracks import get_joinable diff --git a/core/processes/picker.py b/core/processes/picker.py index 2c057df93afd40a1a109f2866191e8d2c0451a02..3668e92291d9889cd7def4a04d8302c2296bfa98 100644 --- a/core/processes/picker.py +++ b/core/processes/picker.py @@ -6,7 +6,7 @@ import pandas as pd from core.cells import CellsHDF -from postprocessor.core.processes.base import ParametersABC, ProcessABC +from agora.base import ParametersABC, ProcessABC from postprocessor.core.functions.tracks import max_ntps, max_nonstop_ntps @@ -106,7 +106,7 @@ class picker(ProcessABC): ): threshold_asint = _as_int(threshold, signals.shape[1]) case_mgr = { - "present": signals.apply(max_ntps, axis=1) > threshold_asint, + "present": signals.notna().sum(axis=1) > threshold_asint, "nonstoply_present": signals.apply(max_nonstop_ntps, axis=1) > threshold_asint, "quantile": [np.quantile(signals.values[signals.notna()], threshold)], diff --git a/core/processor.py b/core/processor.py index e5eb499525991fcb7d8aed774a08472400e37176..239edd99314d004c5f4301060c2ce074a0f67936 100644 --- a/core/processor.py +++ b/core/processor.py @@ -5,13 +5,13 @@ from pydoc import locate import numpy as np import pandas as pd -from postprocessor.core.processes.base import ParametersABC -from postprocessor.core.processes.merger import mergerParameters, merger -from postprocessor.core.processes.picker import pickerParameters, picker +from agora.base import ParametersABC from core.io.writer import Writer from core.io.signal import Signal from core.cells import Cells +from postprocessor.core.processes.merger import mergerParameters, merger +from postprocessor.core.processes.picker import pickerParameters, picker class PostProcessorParameters(ParametersABC): @@ -165,9 +165,17 @@ class PostProcessor: else: raise ("Outpath not defined", type(dataset)) - self.write_result( - "/postprocessing/" + process + "/" + outpath, result, metadata={} - ) + if isinstance(result, dict): # Multiple Signals as output + for k, v in result: + self.write_result( + "/postprocessing/" + process + "/" + outpath + + f'/{k}', + v, metadata={} + ) + else: + self.write_result( + "/postprocessing/" + process + "/" + outpath, result, metadata={} + ) def write_result( self, path: str, result: Union[List, pd.DataFrame, np.ndarray], metadata: Dict