Intervals#
- class pypillometry.Intervals(intervals, units=UNSET, label=None, event_labels=None, event_onsets=None, event_meta=None)[source]#
Bases:
StatefulContainer for intervals with metadata.
This class wraps a list of (start, end) intervals and provides additional metadata such as units, labels, and associated event information. Intervals are end-exclusive:
(start, end)covers the samples withstart <= t < end.Usually, Intervals objects are not created directly but returned by methods of the eye-data classes, e.g.,
get_intervals()(spans between two events; windows around single events areEpochsfromget_epochs(), seeto_intervals()),get_blinks()ormask_as_intervals(). Detection methods such asd.pupil.blinks_detect()store their results as Intervals. Several Intervals can be combined with+orconcat_intervals()and subtracted with-.Intervals in sample indices are only valid for the data object they came from. Converting between sample indices and time needs that data object (its time vector), e.g.,
iv.to_units("ms", d)oriv.to_mask(d); conversions between time units work without it.- Parameters:
intervals (list of tuples, np.ndarray or Quantity) – Intervals as list of (start, end) tuples or 2D array; can carry units, e.g.
[(0, 100), (200, 300)] * u.msor[("1 min", "2 min")]units (str or Unit) – Units of the intervals (“ms”, “sec”, “min”, “h”, or “samples” for sample indices). Can be omitted if intervals carry units; otherwise, intervals with units are converted to units.
label (str, optional) – Descriptive label for the interval collection
event_labels (list, optional) – Event labels corresponding to each interval
event_onsets (list or np.ndarray, optional) – Original event onset times (in same units as intervals)
event_meta (dict, optional) – Metadata of the event corresponding to each interval (column name -> array with one value per interval; see
Events)
Examples
>>> from pypillometry import u >>> intervals = Intervals([(0, 100), (200, 300)], units="ms", label="stimulus") >>> intervals = Intervals([(0, 100), (200, 300)] * u.ms, label="stimulus") >>> len(intervals) 2 >>> for start, end in intervals: ... print(f"{start}-{end}") 0.0-100.0 200.0-300.0
Methods
Represent intervals as a
pandas.DataFrame.Return the periods of the recording not covered by the intervals.
Filter intervals and return a new Intervals object.
Create Intervals from a binary mask array.
Merge overlapping or close intervals.
Add padding to the left (start) and right (end) of each interval.
Plot intervals as horizontal lines on a timeline.
Get statistics about interval durations.
Convert intervals to a binary mask array for the samples of data.
Convert intervals to different time units or indices.
Details
- classmethod from_mask(mask, data=None, units='samples', label=None)[source]#
Create Intervals from a binary mask array.
Each contiguous run of non-zero values becomes one interval.
- Parameters:
mask (
ndarray) – Binary array where non-zero values indicate interval membership, one entry per sample.data (EyeData, optional) – data object the mask belongs to (anything with tx and fs); needed for time units.
units (str, optional) – Units for the intervals. Default is “samples” (sample indices).
label (
str) – Label for the intervals.
- Return type:
- Returns:
New Intervals object created from the mask.
Examples
>>> mask = np.array([0, 0, 1, 1, 1, 0, 0, 1, 1, 0]) >>> intervals = Intervals.from_mask(mask) >>> intervals.intervals [(2, 5), (7, 9)]
- as_pandas()[source]#
Represent intervals as a
pandas.DataFrame.- Return type:
- Returns:
DataFrame with columns
start,end,durationandunits. When metadata is available, the columnslabelandonset(from event_labels and event_onsets, named as inEpochs.as_pandas()andEvents.to_table()) are included, and the metadata of the events (event_meta) are added as columns.- Raises:
ImportError – If pandas is not installed.
- complement(data)[source]#
Return the periods of the recording not covered by the intervals.
These are the gaps between the intervals and before the first and after the last one, within the whole recording. Overlapping intervals are merged first. Event information (labels, onsets) is not kept.
- Parameters:
data (EyeData) – data object the intervals belong to; the complement is taken within the whole recording, from the first sample to the end of the last one (one sample past it, as intervals are end-exclusive)
- Return type:
- Returns:
the periods not covered, in the units of this object
Examples
>>> import pypillometry as pp >>> d = pp.EyeData(left_pupil=np.ones(100), sampling_rate=100) >>> iv = Intervals([(10, 20), (50, 60)], units="samples") >>> iv.complement(d).intervals [(0, 10), (20, 50), (60, 100)]
- filter(selector=None, min_duration=None, max_duration=None, time_range=None)[source]#
Filter intervals and return a new Intervals object.
This method supports multiple filtering criteria that can be combined: - Custom function: applied to each interval - Duration constraints: minimum and/or maximum duration - Time range: intervals must overlap with the specified range
All specified criteria are combined with AND logic.
- Parameters:
selector (
callable) – Function called asselector(start, end)for each interval that returns bool: True to keep the interval, False to discard it.min_duration (
float) – Minimum duration of intervals to keep (in current units, unless it carries units, e.g."50 ms")max_duration (
float) – Maximum duration of intervals to keep (as min_duration)time_range (
tuple) – (start, end) time range (in current units, unless it carries units, e.g.(1, 2) * u.min). Only keeps intervals that overlap with this range. An interval overlaps if its end > range_start AND its start < range_end.
- Return type:
- Returns:
New Intervals object containing only the filtered intervals
Examples
>>> intervals = Intervals([(0, 100), (200, 400), (500, 550)], units="ms")
>>> # Filter by duration >>> short = intervals.filter(max_duration=100) >>> len(short) 2
>>> # Filter by minimum duration >>> long = intervals.filter(min_duration=150) >>> len(long) 1
>>> # Filter by time range (keeps intervals overlapping with range) >>> middle = intervals.filter(time_range=(150, 450))
>>> # Filter by custom function >>> starts_at_multiple_of_100 = intervals.filter( ... selector=lambda start, end: start % 100 == 0 ... )
>>> # Combine multiple criteria >>> filtered = intervals.filter( ... min_duration=50, ... max_duration=200, ... time_range=(0, 500) ... )
- merge(merge_sep='_', distance=0)[source]#
Merge overlapping or close intervals.
When intervals are merged, metadata is handled as follows: - event_labels: Labels of merged intervals are joined with merge_sep - event_onsets: First onset of merged group is kept - event_meta: Metadata of the first interval of merged group is kept
- Parameters:
merge_sep (str, optional) – Separator to use when joining event labels of merged intervals. Default is ‘_’.
distance (
float) –Maximum gap between intervals to still merge them, in the units of the Intervals object unless it carries units (e.g.,
50 * u.ms; for intervals in sample indices, convert them to time units first or give the distance in samples). If two intervals are separated by less than or equal to this distance, they will be merged. Default is 0 (only merge overlapping intervals).Example: If distance=2 and intervals are (10, 15) and (17, 22), they will be merged into (10, 22) because the gap (17-15=2) is <= distance.
- Return type:
- Returns:
New Intervals object with merged intervals and updated metadata.
- Raises:
ValueError – If distance has time units and the intervals are in sample indices (or the other way round).
- pad(left=0, right=0)[source]#
Add padding to the left (start) and right (end) of each interval.
The padding is in the current units of the Intervals object, unless it carries units (e.g.,
left=50 * u.ms). Padding is applied independently to each interval without merging. Use .merge() after padding if you want to merge overlapping intervals. The intervals are not clipped to the data (e.g., a padded interval in sample indices may start before 0); the methods that read the data handle the parts outside of it.- Parameters:
- Return type:
- Returns:
New Intervals object with padded intervals.
Notes
Negative padding values will shrink intervals (left adds to start, right subtracts from end); a ValueError is raised if an interval would end before it starts
Examples
>>> intervals = Intervals([(100, 200), (300, 400)], units="ms") >>> padded = intervals.pad(left=10, right=20) >>> padded.intervals [(90, 220), (290, 420)]
>>> # Pad and then merge overlapping intervals >>> intervals.pad(left=50, right=50).merge().intervals [(50, 450)]
- plot(show_labels=True, title=None, ax=None, **kwargs)[source]#
Plot intervals as horizontal lines on a timeline.
Each interval is shown as a horizontal line at a different y-level.
- Parameters:
show_labels (
bool) – Whether to display event labels for each interval (default True)title (
str) – title of the plot; default: the label of the intervalsax (matplotlib.axes.Axes, optional) – axes to plot on; default: the current axes
**kwargs (dict) – Additional keyword arguments passed to
matplotlib.axes.Axes.plot()
- Return type:
- Returns:
the axes of the plot
Examples
>>> import pypillometry as pp >>> from pypillometry import u >>> import matplotlib.pyplot as plt >>> d = pp.get_example_data("rlmw_002_short") >>> intervals = d.get_epochs("S_", (-500, 2500) * u.ms).to_intervals("sec") >>> fig, ax = plt.subplots(figsize=(8, 3)) >>> ax = intervals.plot(ax=ax)
- stats()[source]#
Get statistics about interval durations.
- Return type:
- Returns:
Dictionary with summary statistics (n, mean, sd, min, max, total_duration, units); for empty Intervals, mean/sd/min/max are NaN and total_duration is 0
- to_mask(data)[source]#
Convert intervals to a binary mask array for the samples of data.
- Parameters:
data (EyeData) – data object the intervals belong to (anything with a time vector tx in ms and a sampling rate fs); the mask has one entry per sample of data
- Return type:
- Returns:
Binary array with 1s where intervals exist, 0s elsewhere.
Examples
>>> import pypillometry as pp >>> d = pp.get_example_data("rlmw_002_short").pupil.blinks_detect() >>> blinks = d.get_blinks("left", "pupil")["left_pupil"] >>> mask = blinks.to_mask(d) >>> len(mask) == len(d) True
- to_units(target_units, data=None)[source]#
Convert intervals to different time units or indices.
Conversions between time units (“ms”, “sec”, “min”, “h”) only rescale the values. Conversions between sample indices and time use the time vector of data, the data object the intervals belong to: a sample index stands for the time of that sample, and a time is converted to the first sample at or after it (so
(start, end)covers the samples withstart <= t < end).- Parameters:
target_units (
str) – Target units: “ms”, “sec”, “min”, “h”, or “samples” for sample indices (aliases such as “seconds” or “indices” work, too).data (EyeData, optional) – data object the intervals belong to (anything with a time vector tx in ms and a sampling rate fs); needed to convert between sample indices and time
- Return type:
- Returns:
New Intervals object with converted units
- Raises:
ValueError – If the conversion involves sample indices and data is not given
Examples
>>> import pypillometry as pp >>> d = pp.get_example_data("rlmw_002_short") >>> intervals = d.get_intervals(start="F_", end="R_", units="ms") >>> intervals_sec = intervals.to_units("sec") >>> intervals_seconds = intervals.to_units("seconds") # alias works too
Convert between indices and time units (needs the data object):
>>> d = d.pupil.blinks_detect() >>> blinks = d.get_blinks("left", "pupil")["left_pupil"] >>> blinks_idx = blinks.to_units("samples", d) >>> blinks_ms = blinks_idx.to_units("ms", d)