Source code for osl_ephys.utils.spmio._events
"""Classes relating to the format and storage of MEEG events and trials."""
from ._spmmeeg_utils import empty_to_zero
from logging import getLogger
import numpy as np
[docs]_logger = getLogger("py_spm")
[docs]class Trial:
## This works for continuous data but not epoched.
# Epoched info is stored in list with dict per trial
# Need to account for both
# Should populate events all the time anyway
# Need to populate trial info when present
def __init__(self, label, events, onset, bad, tag, repl, sample_frequency=None):
[docs] self.events = [Event.from_dict(event_dict) for event_dict in events]
if sample_frequency is not None:
self.calculate_samples(sample_frequency)
[docs] def calculate_samples(self, sample_frequency):
for event in self.events:
event.sample = np.floor(event.time * sample_frequency).astype(int)
event.end_sample = np.floor(event.end_time * sample_frequency).astype(int)
[docs] def _event_property(self, property_):
return np.array([getattr(event, property_) for event in self.events])
[docs] def _set_event_property(self, property_, values):
if len(self.events) != len(values):
_logger.warning(
f"{len(self.events)} events, but {len(values)} values given."
)
for event, value in zip(self.events, values):
setattr(event, property_, value)
@property
[docs] def types(self):
return self._event_property("type")
@property
[docs] def values(self):
return self._event_property("value")
@property
[docs] def durations(self):
return self._event_property("duration")
@property
[docs] def times(self):
return self._event_property("time")
@property
[docs] def offsets(self):
return self._event_property("offset")
@property
[docs] def end_times(self):
return self._event_property("end_time")
@property
[docs] def samples(self):
return self._event_property("sample")
@property
[docs] def end_samples(self):
return self._event_property("end_sample")
@property
[docs] def good_samples(self):
return self._event_property("good_sample")
@good_samples.setter
def good_samples(self, values):
self._set_event_property("good_sample", values)
@property
[docs] def good_end_samples(self):
return self._event_property("good_end_sample")
@good_end_samples.setter
def good_end_samples(self, values):
return self._set_event_property("good_end_sample", values)
@property
[docs] def trial_starts(self):
return self._event_property("trial_start")
@trial_starts.setter
def trial_starts(self, values):
return self._set_event_property("trial_start", values)
#%% -------------------------------------------------------------
[docs]class Event:
def __init__(self, type_, value, duration, time, offset):
[docs] self.duration = empty_to_zero(duration)
if "artefact" in self.type.lower():
self.duration *= 1000
[docs] self.end_time = time + self.duration / 1000
[docs] self.good_end_sample = -1
@classmethod
[docs] def from_dict(cls, event_dict):
if "type" in event_dict:
event_dict["type_"] = event_dict.pop("type")
return cls(**event_dict)
[docs] def to_dict(self):
return {
"type_": self.type,
"value": self.value,
"duration": self.duration,
"time": self.time,
"offset": self.offset,
}
[docs] def __repr__(self):
return (
f"{self.__class__.__name__}(type_='{self.type}', "
f"value={self.value}, "
f"duration={self.duration}, "
f"time={self.time}, "
f"offset={self.offset})"
)
#%% -------------------------------------------------------------