Skip to content

Commit

Permalink
- add option in nm_stream_offline to pass data
Browse files Browse the repository at this point in the history
 - add option in nm_stream to add feature_sampling_frequency_hz
 - fix path in examples to data is loaded by absolute path
 - add test for running all examples
  • Loading branch information
timonmerk committed Aug 14, 2023
1 parent 1dc1ea2 commit ae2edab
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 34 deletions.
2 changes: 1 addition & 1 deletion examples/plot_example_BIDS.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@

RUN_NAME = "sub-000_ses-right_task-force_run-3_ieeg"

PATH_BIDS = Path.cwd() / "data"
PATH_BIDS = Path(__file__).absolute().parent / "data"

PATH_RUN = PATH_BIDS / "sub-000" / "sess-right" / "ieeg" / (RUN_NAME + ".vhdr")

Expand Down
2 changes: 1 addition & 1 deletion examples/plot_example_gridPointProjection.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
# %%
RUN_NAME = "sub-000_ses-right_task-force_run-3_ieeg"

PATH_BIDS = Path.cwd() / "data"
PATH_BIDS = Path(__file__).absolute().parent / "data"

PATH_RUN = PATH_BIDS / "sub-000" / "sess-right" / "ieeg" / (RUN_NAME + ".vhdr")

Expand Down
2 changes: 1 addition & 1 deletion examples/plot_example_sharpwave_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@

RUN_NAME = "sub-000_ses-right_task-force_run-3_ieeg"

PATH_BIDS = Path.cwd() / "data"
PATH_BIDS = Path(__file__).absolute().parent / "data"

PATH_RUN = PATH_BIDS / "sub-000" / "sess-right" / "ieeg" / (RUN_NAME + ".vhdr")

Expand Down
1 change: 0 additions & 1 deletion examples/plot_first_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ def generate_random_walk(NUM_CHANNELS, TIME_DATA_SAMPLES):
plt.xlabel("Time [s]")
plt.ylabel("Amplitude")
plt.title("Example random walk data")
plt.show()

# %%
# Now let’s define the necessary setup files will be use for data
Expand Down
55 changes: 29 additions & 26 deletions py_neuromodulation/nm_stream_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class PNStream(ABC):
features: nm_features.Features
coords: dict
sfreq: int | float
sfreq_feature: int | float = None
path_grids: _PathLike | None
model: base.BaseEstimator | None
sess_right: bool | None
Expand All @@ -39,22 +40,45 @@ def __init__(
nm_channels: pd.DataFrame | _PathLike,
settings: dict | _PathLike | None = None,
line_noise: int | float | None = 50,
sampling_rate_features_hz: int | float | None = None,
path_grids: _PathLike | None = None,
coords: dict | None = None,
coord_names: list | None = None,
coord_list: list | None = None,
verbose: bool = True,
) -> None:
"""Stream initialization
Parameters
----------
sfreq : int | float
sampling frequency of data in Hertz
nm_channels : pd.DataFrame | _PathLike
parametrization of channels (see nm_define_channels.py for initialization)
settings : dict | _PathLike | None, optional
features settings can be a dictionary or path to the nm_settings.json, by default the py_neuromodulation/nm_settings.json are read
line_noise : int | float | None, optional
line noise, by default 50
sampling_rate_features_hz : int | float | None, optional
feature sampling rate, by default None
path_grids : _PathLike | None, optional
path to grid_cortex.tsv and/or gird_subcortex.tsv, by default Non
coord_names : list | None, optional
coordinate name in the form [coord_1_name, coord_2_name, etc], by default None
coord_list : list | None, optional
coordinates in the form [[coord_1_x, coord_1_y, coord_1_z], [coord_2_x, coord_2_y, coord_2_z],], by default None
verbose : bool, optional
print out stream computation time information, by default True
"""
self.settings = self._load_settings(settings)

if sampling_rate_features_hz is not None:
self.settings["sampling_rate_features_hz"] = sampling_rate_features_hz

self.nm_channels = self._load_nm_channels(nm_channels)
if path_grids is None:
path_grids = pathlib.Path(__file__).parent.resolve()
self.path_grids = path_grids
self.verbose = verbose
if coords is None:
self.coords = {}
else:
self.coords = coords
self.sfreq = sfreq
self.sess_right = None
self.projection = None
Expand Down Expand Up @@ -119,27 +143,6 @@ def load_model(self, model_name: _PathLike) -> None:
with open(model_name, "rb") as fid:
self.model = cPickle.load(fid)

def plot_cortical_projection(self) -> None:
"""plot projection of cortical grid electrodes on cortex"""
ecog_strip = None
if self.projection is not None:
ecog_strip = self.projection.ecog_strip

grid_cortex = None
if self.projection is not None:
grid_cortex = self.projection.grid_cortex

sess_right = None
if self.projection is not None:
sess_right = self.projection.sess_right

nmplotter = nm_plots.NM_Plot(
ecog_strip=ecog_strip,
grid_cortex=grid_cortex,
sess_right=sess_right,
)
nmplotter.plot_cortex(set_clim=False)

def save_after_stream(
self,
out_path_root: _PathLike | None = None,
Expand Down
84 changes: 80 additions & 4 deletions py_neuromodulation/nm_stream_offline.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,20 @@
import numpy as np
import pandas as pd

from py_neuromodulation import nm_generator, nm_IO, nm_stream_abc
from py_neuromodulation import nm_generator, nm_IO, nm_stream_abc, nm_define_nmchannels

_PathLike = str | os.PathLike


class _OfflineStream(nm_stream_abc.PNStream):
"""Offline stream base class.
This class can be inhereted for different types of offline streams, e.g. epoch-based or continuous.
Parameters
----------
nm_stream_abc : nm_stream_abc.PNStream
"""

def _add_labels(
self, features: pd.DataFrame, data: np.ndarray
) -> pd.DataFrame:
Expand Down Expand Up @@ -105,13 +113,74 @@ def _run_offline(


class Stream(_OfflineStream):
def __init__(
self,
sfreq: int | float,
data: np.ndarray | pd.DataFrame = None,
nm_channels: pd.DataFrame | _PathLike = None,
settings: dict | _PathLike | None = None,
sampling_rate_features_hz: float = None,
line_noise: int | float | None = 50,
path_grids: _PathLike | None = None,
coord_names: list | None = None,
coord_list: list | None = None,
verbose: bool = True,
) -> None:
"""Stream initialization
Parameters
----------
sfreq : int | float
sampling frequency of data in Hertz
data : np.ndarray | pd.DataFrame | None, optional
data to be streamed with shape (n_channels, n_time), by default None
nm_channels : pd.DataFrame | _PathLike
parametrization of channels (see nm_define_channels.py for initialization)
settings : dict | _PathLike | None, optional
features settings can be a dictionary or path to the nm_settings.json, by default the py_neuromodulation/nm_settings.json are read
line_noise : int | float | None, optional
line noise, by default 50
sampling_rate_features_hz : int | float | None, optional
feature sampling rate, by default None
path_grids : _PathLike | None, optional
path to grid_cortex.tsv and/or gird_subcortex.tsv, by default Non
coord_names : list | None, optional
coordinate name in the form [coord_1_name, coord_2_name, etc], by default None
coord_list : list | None, optional
coordinates in the form [[coord_1_x, coord_1_y, coord_1_z], [coord_2_x, coord_2_y, coord_2_z],], by default None
verbose : bool, optional
print out stream computation time information, by default True
"""

if nm_channels is None and data is not None:
nm_channels = nm_define_nmchannels.get_default_channels_from_data(data)

if nm_channels is None and data is None:
raise ValueError(
"Either `nm_channels` or `data` must be passed to `Stream`."
)

super().__init__(
sfreq,
nm_channels,
settings,
line_noise,
sampling_rate_features_hz,
path_grids,
coord_names,
coord_list,
verbose,
)

self.data = data

def run(
self,
data: np.ndarray | pd.DataFrame,
data: np.ndarray | pd.DataFrame = None,
out_path_root: _PathLike | None = None,
folder_name: str = "sub",
) -> pd.DataFrame:
"""Call run function for offline stream.
"""Call run function for offline stream.
Parameters
----------
Expand All @@ -129,6 +198,13 @@ def run(
feature DataFrame
"""

data = self._handle_data(data)
if self.data is not None:
data = self._handle_data(self.data)
elif data is not None:
data = self._handle_data(data)
elif self.data is None and data is None:
raise ValueError(
"No data passed to run function."
)

return self._run_offline(data, out_path_root, folder_name)
8 changes: 8 additions & 0 deletions tests/test_all_examples.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from pathlib import Path
import pytest
import subprocess

@pytest.mark.parametrize("example_filename", Path('examples').glob('*.py'))
def test_run_through_all_test(example_filename):
print(f'Running {example_filename}')
subprocess.run(['python', example_filename], check=True)
14 changes: 14 additions & 0 deletions tests/test_initalization_offline_stream.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import numpy as np

import py_neuromodulation as nm


def test_stream_init():
"""Test if stream initialization with passed data will setup nm_channels correctly
"""
data = np.random.random((10, 1000))
sfreq = 100
stream = nm.Stream(sfreq=sfreq, data=data, sampling_rate_features_hz=11)

assert stream.nm_channels.shape[0] == 10
assert stream.settings["sampling_rate_features_hz"] == 11

0 comments on commit ae2edab

Please sign in to comment.