Skip to content

Commit

Permalink
completely removed pynapple (#32)
Browse files Browse the repository at this point in the history
* completely removed pynapple

* and removed pynapple from the requirements as well

* move tests in iblphotometry_tests package

---------

Co-authored-by: owinter <olivier.winter@hotmail.fr>
  • Loading branch information
grg2rsr and oliche authored Dec 4, 2024
1 parent 06728b6 commit 9c4568a
Show file tree
Hide file tree
Showing 23 changed files with 259 additions and 310 deletions.
3 changes: 1 addition & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,4 @@ numpy
matplotlib
pytest
scipy
pandera
pynapple
pandera
32 changes: 17 additions & 15 deletions src/iblphotometry/bleach_corrections.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from abc import ABC, abstractmethod
import warnings
import numpy as np
import pynapple as nap
import pandas as pd
from scipy.optimize import minimize
from scipy.stats.distributions import norm
from scipy.stats import gaussian_kde
Expand All @@ -11,14 +11,16 @@
from inspect import signature


def correct(signal: nap.Tsd, reference: nap.Tsd, mode: str = 'subtract') -> nap.Tsd:
def correct(
signal: pd.Series, reference: pd.Series, mode: str = 'subtract'
) -> pd.Series:
if mode == 'subtract':
signal_corrected = signal.values - reference.values
if mode == 'divide':
signal_corrected = signal.values / reference.values
if mode == 'subtract-divide':
signal_corrected = (signal.values - reference.values) / reference.values
return nap.Tsd(t=signal.times(), d=signal_corrected)
return pd.Series(signal_corrected, index=signal.index.values)


def mse_loss(p, x, y, fun):
Expand Down Expand Up @@ -240,8 +242,8 @@ def predict(self, x: np.ndarray, return_type='numpy'):
y_hat = self.model.eq(x, *self.popt)
if return_type == 'numpy':
return y_hat
if return_type == 'pynapple':
return nap.Tsd(t=x, d=y_hat)
if return_type == 'pandas':
return pd.Series(y_hat, index=x)


class BleachCorrection:
Expand All @@ -258,9 +260,9 @@ def __init__(
)
self.correction_method = correction_method

def correct(self, F: nap.Tsd):
self.regression.fit(F.times(), F.values)
ref = self.regression.predict(F.times(), return_type='pynapple')
def correct(self, F: pd.Series):
self.regression.fit(F.index.values, F.values)
ref = self.regression.predict(F.index.values, return_type='pandas')
return correct(F, ref, mode=self.correction_method)


Expand All @@ -282,14 +284,14 @@ def __init__(

def correct(
self,
F_ca: nap.Tsd,
F_iso: nap.Tsd,
F_ca: pd.Series,
F_iso: pd.Series,
):
if self.lowpass_isosbestic is not None:
F_iso = filt(F_iso, **self.lowpass_isosbestic)

self.reg.fit(F_iso.values, F_ca.values)
F_iso_fit = self.reg.predict(F_iso.values, return_type='pynapple')
F_iso_fit = self.reg.predict(F_iso.values, return_type='pandas')

return correct(F_ca, F_iso_fit, mode=self.correction_method)

Expand All @@ -303,23 +305,23 @@ def __init__(
self.filter_params = filter_params
self.correction_method = correction_method

def correct(self, F: nap.Tsd):
def correct(self, F: pd.Series):
F_filt = filt(F, **self.filter_params)
return correct(F, F_filt, mode=self.correction_method)


# convenience functions for pipelines
def lowpass_bleachcorrect(F: nap.Tsd, **kwargs):
def lowpass_bleachcorrect(F: pd.Series, **kwargs):
bc = LowpassBleachCorrection(**kwargs)
return bc.correct(F)


def exponential_bleachcorrect(F: nap.Tsd, **kwargs):
def exponential_bleachcorrect(F: pd.Series, **kwargs):
model = DoubleExponDecay()
ec = BleachCorrection(model, **kwargs)
return ec.correct(F)


def isosbestic_correct(F_sig: nap.TsdFrame, F_ref: nap.TsdFrame, **kwargs):
def isosbestic_correct(F_sig: pd.DataFrame, F_ref: pd.DataFrame, **kwargs):
ic = IsosbesticCorrection(**kwargs)
return ic.correct(F_sig, F_ref)
20 changes: 10 additions & 10 deletions src/iblphotometry/helpers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import numpy as np
from scipy import signal
import pynapple as nap
import pandas as pd
from ibldsp.utils import WindowGenerator
from iblutil.numerical import rcoeff

Expand Down Expand Up @@ -37,13 +37,13 @@ def mad(A: np.array):


# TODO move to processing
def madscore(F: nap.Tsd):
y, t = F.values, F.times()
return nap.Tsd(t=t, d=mad(y))
def madscore(F: pd.Series):
y, t = F.values, F.index.values
return pd.Series(mad(y), index=t)


# TODO move to processing
def zscore(F: nap.Tsd, mode='classic'):
def zscore(F: pd.Series, mode='classic'):
"""pynapple friendly zscore
Args:
Expand All @@ -52,13 +52,13 @@ def zscore(F: nap.Tsd, mode='classic'):
Returns:
_type_: z-scored nap.Tsd
"""
y, t = F.values, F.times()
y, t = F.values, F.index.values
# mu, sig = np.average(y), np.std(y)
return nap.Tsd(t=t, d=z(y, mode=mode))
return pd.Series(z(y, mode=mode), index=t)


# TODO move to processing
def filt(F: nap.Tsd, N: int, Wn: float, fs: float = None, btype='low'):
def filt(F: pd.Series, N: int, Wn: float, fs: float = None, btype='low'):
"""a pynapple friendly wrapper for scipy.signal.butter and sosfiltfilt
Args:
Expand All @@ -71,12 +71,12 @@ def filt(F: nap.Tsd, N: int, Wn: float, fs: float = None, btype='low'):
Returns:
_type_: _description_
"""
y, t = F.values, F.times()
y, t = F.values, F.index.values
if fs is None:
fs = 1 / np.median(np.diff(t))
sos = signal.butter(N, Wn, btype, fs=fs, output='sos')
y_filt = signal.sosfiltfilt(sos, y)
return nap.Tsd(t=t, d=y_filt)
return pd.Series(y_filt, index=t)


def sliding_rcoeff(signal_a, signal_b, nswin, overlap=0):
Expand Down
17 changes: 5 additions & 12 deletions src/iblphotometry/io.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# %%
import numpy as np
import pandas as pd
import pynapple as nap
from pathlib import Path
import warnings
import pandera
Expand All @@ -14,8 +13,8 @@

def from_array(
times: np.array, data: np.array, channel_names: list[str] = None
) -> nap.TsdFrame:
return nap.TsdFrame(t=times, d=data, columns=channel_names)
) -> pd.DataFrame:
return pd.DataFrame(data, index=times, columns=channel_names)


def from_dataframe(
Expand Down Expand Up @@ -62,23 +61,17 @@ def from_dataframe(
to_drop = ['None', '']
channel_names = [ch for ch in channel_names if ch not in to_drop]

raw_tfs = {}
raw_dfs = {}
for channel in channel_names:
# TODO include the use of raw_df['include'] to set the time_support of the pynapple object
# requires conversion of boolen to nap.IntervalSet (have done this somewhere already. find code)

# TODO check pynappe PR#343 https://github.com/pynapple-org/pynapple/pull/343 for future
# inclusion of locations_df as metadata

# get the data for the band
df = raw_df.groupby(channel_column).get_group(channel)
# if rename dict is passed, rename Region0X to the corresponding brain region
if rename is not None:
df = df.rename(columns=rename)
data_columns = rename.values()
raw_tfs[channel] = nap.TsdFrame(df.set_index(time_column)[data_columns])
raw_dfs[channel] = df.set_index(time_column)[data_columns]

return raw_tfs
return raw_dfs


def from_pqt(
Expand Down
55 changes: 9 additions & 46 deletions src/iblphotometry/loaders.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import pynapple as nap
import pandas as pd
from pathlib import Path
from iblphotometry import io
Expand Down Expand Up @@ -28,31 +27,15 @@ def _load_data_from_eid(self, eid, rename=True) -> nap.TsdFrame:
data_columns=list(locations_df.index),
rename=locations_df['brain_region'].to_dict() if rename else None,
)
raw_tfs = io.from_dataframe(raw_photometry_df, **read_config)
raw_dfs = io.from_dataframe(raw_photometry_df, **read_config)

signal_band_names = list(raw_tfs.keys())
col_names = list(raw_tfs[signal_band_names[0]].columns)
signal_band_names = list(raw_dfs.keys())
col_names = list(raw_dfs[signal_band_names[0]].columns)
if self.verbose:
print(f'available signal bands: {signal_band_names}')
print(f'available brain regions: {col_names}')

return raw_tfs
# if return_regions:
# return raw_tfs, cols
# else:
# return raw_tfs

# def _load_data_from_pid(self, pid=None, signal=None) -> nap.Tsd:
# eid, pname = self.one.pid2eid(pid)
# locations = self._load_locations(eid)
# roi_name = dict(zip(locations['fiber'], locations.index))[pname]
# return self._load_data_from_eid(eid, signal=signal)[roi_name]

# def pid2eid(self, pid: str) -> tuple[str, str]:
# return self.one.pid2eid(pid)

# def eid2pid(self, eid: str):
# return self.one.eid2pid(eid)
return raw_dfs


class KceniaLoader(PhotometryLoader):
Expand All @@ -69,39 +52,19 @@ def _load_data_from_eid(self, eid: str, rename=True):
signal_bands = ['raw_calcium', 'raw_isosbestic'] # HARDCODED but fine

# flipping the data representation
raw_tfs = {}
raw_dfs = {}
for band in signal_bands:
df = pd.DataFrame([raw_dfs[pname][band].values for pname in pnames]).T
df.columns = pnames
df.index = raw_dfs[pname][band].index
raw_tfs[band] = nap.TsdFrame(df)
raw_dfs[band] = df

if self.verbose:
print(f'available signal bands: {list(raw_tfs.keys())}')
cols = list(raw_tfs[list(raw_tfs.keys())[0]].columns)
print(f'available signal bands: {list(raw_dfs.keys())}')
cols = list(raw_dfs[list(raw_dfs.keys())[0]].columns)
print(f'available brain regions: {cols}')

# if return_regions:
# return raw_tfs, pnames
# else:
return raw_tfs

# def _load_data_from_eid(self, eid, signal=None):
# raise NotImplementedError

# def get_mappable(self, eid):
# raise NotImplementedError

# def get_mapping(self, eid, key=None, value=None):
# raise NotImplementedError

# def pid2eid(self, pid: str) -> tuple[str, str]:
# return pid.split('_')

# def eid2pid(self, eid):
# pnames = self._eid2pnames(eid)
# pids = [f'{eid}_{pname}' for pname in pnames]
# return (pids, pnames)
return raw_dfs

def _eid2pnames(self, eid: str):
session_path = self.one.eid2path(eid)
Expand Down
Loading

0 comments on commit 9c4568a

Please sign in to comment.