# -*- coding: utf-8 -*-
import itertools
import numpy as np
from ..signal import signal_binarize
[docs]def events_find(
event_channel,
threshold="auto",
threshold_keep="above",
start_at=0,
end_at=None,
duration_min=1,
duration_max=None,
inter_min=0,
discard_first=0,
discard_last=0,
event_labels=None,
event_conditions=None,
):
"""Find and select events in a continuous signal (e.g., from a photosensor).
Parameters
----------
event_channel : array or list
The channel containing the events.
threshold : str or float
The threshold value by which to select the events. If "auto", takes the value between the max
and the min.
threshold_keep : str
"above" or "below", define the events as above or under the threshold. For photosensors, a
white screen corresponds usually to higher values. Therefore, if your events are signaled by
a black colour, events values are the lower ones, and you should set the cut to "below".
start_at : int
Keep events which onset is after a particular time point.
end_at : int
Keep events which onset is before a particular time point.
duration_min : int
The minimum duration of an event to be considered as such (in time points).
duration_max : int
The maximum duration of an event to be considered as such (in time points).
inter_min : int
The minimum duration after an event for the subsequent event to be considered as such (in time
points). Useful when spurious consecutive events are created due to very high sampling rate.
discard_first : int
Discard first or last n events. Useful if the experiment starts with some spurious events.
If discard_first=0, no first event is removed.
discard_last : int
Discard first or last n events. Useful if the experiment ends with some spurious events.
If discard_last=0, no last event is removed.
event_labels : list
A list containing unique event identifiers. If `None`, will use the event index number.
event_conditions : list
An optional list containing, for each event, for example the trial category, group or
experimental conditions.
Returns
----------
dict
Dict containing 3 or 4 arrays, 'onset' for event onsets, 'duration' for event durations, 'label'
for the event identifiers and the optional 'conditions' passed to `event_conditions`.
See Also
--------
events_plot, events_to_mne
Example
----------
>>> import numpy as np
>>> import pandas as pd
>>> import neurokit2 as nk
>>>
>>> signal = nk.signal_simulate(duration=4)
>>> events = nk.events_find(signal)
>>> events #doctest: +ELLIPSIS
{'onset': array(...),
'duration': array(...),
'label': array(...)}
>>>
>>> nk.events_plot(events, signal) #doctest: +ELLIPSIS
<Figure ...>
"""
events = _events_find(event_channel, threshold=threshold, threshold_keep=threshold_keep)
# Warning when no events detected
if len(events["onset"]) == 0:
print(
"NeuroKit warning: events_find(): No events found. Check your "
"event_channel or adjust 'threhsold' or 'keep' arguments."
)
return events
# Remove based on duration
to_keep = np.full(len(events["onset"]), True)
to_keep[events["duration"] < duration_min] = False
if duration_max is not None:
to_keep[events["duration"] > duration_max] = False
events["onset"] = events["onset"][to_keep]
events["duration"] = events["duration"][to_keep]
# Remove based on index
if start_at > 0:
events["onset"] = events["onset"][events["onset"] >= start_at]
events["duration"] = events["duration"][events["onset"] >= start_at]
if end_at is not None:
events["onset"] = events["onset"][events["onset"] <= end_at]
events["duration"] = events["duration"][events["onset"] <= end_at]
# Remove based on interval min
if inter_min > 0:
inter = np.diff(events["onset"])
events["onset"] = np.concatenate([events["onset"][0:1], events["onset"][1::][inter >= inter_min]])
events["duration"] = np.concatenate([events["duration"][0:1], events["duration"][1::][inter >= inter_min]])
# Remove first and last n
if discard_first > 0:
events["onset"] = events["onset"][discard_first:]
events["duration"] = events["duration"][discard_first:]
if discard_last > 0:
events["onset"] = events["onset"][0 : -1 * discard_last]
events["duration"] = events["duration"][0 : -1 * discard_last]
events = _events_find_label(events, event_labels=event_labels, event_conditions=event_conditions)
return events
# =============================================================================
# Internals
# =============================================================================
def _events_find_label(events, event_labels=None, event_conditions=None, function_name="events_find"):
# Get n events
n = len(events["onset"])
# Labels
if event_labels is None:
event_labels = (np.arange(n) + 1).astype(np.str)
if len(list(set(event_labels))) != n:
raise ValueError(
"NeuroKit error: "
+ function_name
+ "(): oops, it seems like the `event_labels` that you provided "
+ "are not unique (all different). Please provide "
+ str(n)
+ " distinct labels."
)
if len(event_labels) != n:
raise ValueError(
"NeuroKit error: "
+ function_name
+ "(): oops, it seems like you provided "
+ str(n)
+ " `event_labels`, but "
+ str(n)
+ " events got detected :(. Check your event names or the event signal!"
)
events["label"] = event_labels
# Condition
if event_conditions is not None:
if len(event_conditions) != n:
raise ValueError(
"NeuroKit error: "
+ function_name
+ "(): oops, it seems like you provided "
+ str(n)
+ " `event_conditions`, but "
+ str(n)
+ " events got detected :(. Check your event conditions or the event signal!"
)
events["condition"] = event_conditions
return events
def _events_find(event_channel, threshold="auto", threshold_keep="above"):
binary = signal_binarize(event_channel, threshold=threshold)
if threshold_keep.lower() != "above":
binary = np.abs(binary - 1) # Reverse if events are below
# Initialize data
events = {"onset": [], "duration": []}
index = 0
for event, group in itertools.groupby(binary):
duration = len(list(group))
if event == 1:
events["onset"].append(index)
events["duration"].append(duration)
index += duration
# Convert to array
events["onset"] = np.array(events["onset"])
events["duration"] = np.array(events["duration"])
return events