ctnet-hf / preprocessing.py
likan-blk's picture
Upload trained BNCI2014-001 CTNet checkpoint
a4df9bf verified
Raw
History Blame Contribute Delete
3.9 kB
"""Serializable preprocessing for trained CTNet checkpoints."""
from __future__ import annotations
from typing import Any, Optional
import numpy as np
import torch
from transformers.feature_extraction_utils import BatchFeature, FeatureExtractionMixin
class CtnetPreprocessor(FeatureExtractionMixin):
"""Apply the training-set normalization and validate the EEG layout."""
model_input_names = ["input_values"]
def __init__(
self,
*,
n_channels: int,
n_times: int,
sampling_rate: Optional[int] = None,
mean: Any,
std: Any,
channel_names: Optional[list[str]] = None,
dataset: Optional[str] = None,
subjects: Optional[list[int]] = None,
unit: str = "microvolts",
train_session: Optional[str] = None,
selection_session: Optional[str] = None,
**kwargs: Any,
) -> None:
if n_channels < 1 or n_times < 1:
raise ValueError("n_channels and n_times must be positive.")
if channel_names is not None and len(channel_names) != n_channels:
raise ValueError(
f"Expected {n_channels} channel names, got {len(channel_names)}."
)
self.n_channels = int(n_channels)
self.n_times = int(n_times)
self.sampling_rate = sampling_rate
self.mean = np.asarray(mean, dtype=np.float32)
self.std = np.asarray(std, dtype=np.float32)
if np.any(self.std <= 0):
raise ValueError("std must be positive.")
self.channel_names = channel_names
self.dataset = dataset
self.subjects = subjects
self.unit = unit
self.train_session = train_session
self.selection_session = selection_session
super().__init__(**kwargs)
def __call__(
self,
input_values: torch.Tensor | np.ndarray,
*,
return_tensors: str = "pt",
) -> BatchFeature:
"""Normalize one trial or a batch in ``(channels, time)`` layout."""
if isinstance(input_values, torch.Tensor):
values = input_values.detach().cpu().numpy()
else:
values = np.asarray(input_values)
if values.ndim == 2:
values = values[np.newaxis, ...]
if values.ndim != 3:
raise ValueError(
"Expected EEG input with shape (channels, time) or "
"(batch_size, channels, time)."
)
if values.shape[1] != self.n_channels:
raise ValueError(
f"Expected {self.n_channels} EEG channels but received "
f"{values.shape[1]}."
)
if values.shape[2] != self.n_times:
raise ValueError(
f"Expected {self.n_times} time samples but received "
f"{values.shape[2]}."
)
values = values.astype(np.float32, copy=True)
values = (values - self.mean) / self.std
if return_tensors not in {"np", "pt"}:
raise ValueError("return_tensors must be 'pt' or 'np'.")
return BatchFeature({"input_values": values}, tensor_type=return_tensors)
def to_dict(self) -> dict[str, Any]:
payload = super().to_dict()
payload.update(
{
"feature_extractor_type": self.__class__.__name__,
"n_channels": self.n_channels,
"n_times": self.n_times,
"sampling_rate": self.sampling_rate,
"mean": self.mean.tolist(),
"std": self.std.tolist(),
"channel_names": self.channel_names,
"dataset": self.dataset,
"subjects": self.subjects,
"unit": self.unit,
"train_session": self.train_session,
"selection_session": self.selection_session,
}
)
return payload