Skip to content

Commit

Permalink
add no psd feature normalization option to settings
Browse files Browse the repository at this point in the history
  • Loading branch information
timonmerk committed Nov 23, 2024
1 parent a50d357 commit 042d638
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 14 deletions.
1 change: 1 addition & 0 deletions py_neuromodulation/default_settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ postprocessing:
feature_normalization_settings:
normalization_time_s: 30
normalization_method: zscore # supported methods: mean, median, zscore, zscore-median, quantile, power, robust, minmax
normalize_psd: false
clip: 3

project_cortex_settings:
Expand Down
7 changes: 7 additions & 0 deletions py_neuromodulation/processing/data_preprocessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ def instantiate_preprocessor(
]

def process_data(self, data: "np.ndarray") -> "np.ndarray":
"""
Args:
data (np.ndarray): shape: (n_channels, n_samples)
Returns:
np.ndarray: shape: (n_channels, n_samples)
"""
for preprocessor in self.preprocessors:
data = preprocessor.process(data)
return data
33 changes: 24 additions & 9 deletions py_neuromodulation/processing/normalization.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,21 @@ class NormalizationSettings(NMBaseModel):
def list_normalization_methods() -> list[NormMethod]:
return list(get_args(NormMethod))

class FeatureNormalizationSettings(NormalizationSettings): normalize_psd: bool = False

class Normalizer(NMPreprocessor):
def __init__(
self,
sfreq: float,
settings: "NMSettings",
type: NormalizerType,
**kwargs,
) -> None:
self.type = type
self.settings: NormalizationSettings
if self.type == "raw":
self.settings: NormalizationSettings
else:
self.settings: FeatureNormalizationSettings

match self.type:
case "raw":
Expand Down Expand Up @@ -74,14 +79,24 @@ def __init__(
self.normalizer = NORM_FUNCTIONS[self.method]

def process(self, data: np.ndarray) -> np.ndarray:
# TODO: does feature normalization need to be transposed too?
if self.type == "raw":
data = data.T
"""Process normalization.
Note: raw data has to be internally transposed, s.t. raw and features
are normalized in the same way.
Args:
data (np.ndarray): shape (channels, n_samples)
Returns:
np.ndarray: (channels, n_samples)
"""

if self.previous.size == 0: # Check if empty
self.previous = data
return data if self.type == "raw" else data.T

if self.type == "raw":
self.previous = self.previous.T
return data
if self.type == "raw":
data = data.T
self.previous = np.vstack((self.previous, data[-self.add_samples :]))

data = self.normalizer(data, self.previous)
Expand All @@ -93,12 +108,12 @@ def process(self, data: np.ndarray) -> np.ndarray:

data = np.nan_to_num(data)

return data if self.type == "raw" else data.T
return data if self.type != "raw" else data.T


class RawNormalizer(Normalizer):
def __init__(self, sfreq: float, settings: "NMSettings") -> None:
super().__init__(sfreq, settings, "raw")
def __init__(self, sfreq: float, settings: "NMSettings", **kwargs,) -> None:
super().__init__(sfreq, settings, "raw", **kwargs)


class FeatureNormalizer(Normalizer):
Expand Down
27 changes: 24 additions & 3 deletions py_neuromodulation/stream/data_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ def __init__(
self.sfreq_raw: float = sfreq // 1
self.line_noise: float | None = line_noise
self.path_grids: _PathLike | None = path_grids
self.non_psd_indices: np.ndarray | None = None
self.verbose: bool = verbose

self.features_previous = None
Expand Down Expand Up @@ -255,9 +256,29 @@ def process(self, data: np.ndarray) -> dict[str, float]:

# normalize features
if self.settings.postprocessing.feature_normalization:
normed_features = self.feature_normalizer.process(
np.fromiter(features_dict.values(), dtype=np.float64)
)
if not self.settings.feature_normalization_settings.normalize_psd:
if self.non_psd_indices is None:
self.non_psd_indices = [
idx
for idx, key in enumerate(features_dict.keys())
if "psd" not in key
]
self.psd_indices = list(set(range(len(features_dict))) - set(
self.non_psd_indices
))
feature_values = np.fromiter(features_dict.values(), dtype=np.float64)
normed_features_non_psd = self.feature_normalizer.process(
feature_values[self.non_psd_indices]
)

# combine values in new array
normed_features = np.empty((feature_values.shape[0]))
normed_features[self.non_psd_indices] = normed_features_non_psd
normed_features[self.psd_indices] = feature_values[self.psd_indices]
else:
normed_features = self.feature_normalizer.process(
np.fromiter(features_dict.values(), dtype=np.float64)
)
features_dict = {
key: normed_features[idx]
for idx, key in enumerate(features_dict.keys())
Expand Down
4 changes: 2 additions & 2 deletions py_neuromodulation/stream/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
)

from py_neuromodulation.processing.filter_preprocessing import FilterSettings
from py_neuromodulation.processing.normalization import NormalizationSettings
from py_neuromodulation.processing.normalization import FeatureNormalizationSettings, NormalizationSettings
from py_neuromodulation.processing.resample import ResamplerSettings
from py_neuromodulation.processing.projection import ProjectionSettings

Expand Down Expand Up @@ -83,7 +83,7 @@ class NMSettings(NMBaseModel):

# Postprocessing settings
postprocessing: PostprocessingSettings = PostprocessingSettings()
feature_normalization_settings: NormalizationSettings = NormalizationSettings()
feature_normalization_settings: FeatureNormalizationSettings = FeatureNormalizationSettings()
project_cortex_settings: ProjectionSettings = ProjectionSettings(max_dist_mm=20)
project_subcortex_settings: ProjectionSettings = ProjectionSettings(max_dist_mm=5)

Expand Down

0 comments on commit 042d638

Please sign in to comment.