Source code for neuroconv.datainterfaces.events.npm_events.npmeventsdatainterface

from typing import Literal

import pandas as pd
from pydantic import FilePath, validate_call

from ..csv_events.csveventsdatainterface import CSVEventsInterface


[docs] class NPMEventsInterface(CSVEventsInterface): """Data Interface for converting discrete events from Neurophotometrics (NPM) files. NPM stores discrete events in a raw, headerless two-column stimuli CSV: the first column holds the event onset time (in the recording's raw time base) and the second column holds the event type label (e.g. ``whitenoise``, ``pinknoise``, a boolean ``True``/``False`` annotation, or a numeric code). This is exactly a headerless CSV with a timestamp column and an event-type column, so this interface is a thin :class:`.CSVEventsInterface` that fixes those two columns. Each distinct label becomes its own ``pynwb.event.EventsTable`` (onset timestamps only) in ``nwbfile.events``. Notes ----- Note that we *assume* the second column is the event **type**. Each distinct value becomes its own event type/table rather than a per-event value/payload column. The raw onset times are scaled to seconds by ``time_unit`` (see :class:`.CSVEventsInterface`) but are otherwise written as-is: they remain in the recording's raw time base. NPM recordings carry no embedded recording-start timestamp, so :meth:`get_metadata` does NOT populate ``NWBFile/session_start_time``; the user must supply it via editable metadata. This interface targets the standalone Bonsai stimuli CSV *only*. NPM can also embed discrete events directly in the photometry/signal CSV, alongside the fluorescence columns: older firmware writes each digital I/O line (e.g. ``Stimulation``, ``Output0``/``Output1``, ``Input0``/``Input1``) as its own 0/1-per-frame column, while newer firmware bit-packs those same lines into the ``Flags``/``LedState`` column. This interface's fixed headerless two-column layout does not fit that photometry CSV; use :class:`.CSVEventsInterface` directly to select the relevant columns from it. """ keywords = ("events", "Neurophotometrics") display_name = "NPMEvents" info = "Data Interface for converting discrete events from Neurophotometrics files." associated_suffixes = ("csv",) @validate_call def __init__( self, file_path: FilePath, *, time_unit: Literal["seconds", "milliseconds", "microseconds"] = "seconds", metadata_key: str | None = None, verbose: bool = False, ): """Initialize the NPMEventsInterface. Parameters ---------- file_path : FilePath The path to the raw NPM event/stimuli CSV file: a headerless two-column CSV whose first column is the event onset time and whose second column is the event type label. time_unit : {"seconds", "milliseconds", "microseconds"}, optional The unit of the raw onset-time column, default = "seconds". Onset times are divided by the corresponding factor to convert them to seconds. metadata_key : str, optional The key under ``metadata["Events"]`` that namespaces this interface's events metadata. If None (default), the file stem is used (inherited from :class:`.CSVEventsInterface`). verbose : bool, optional Whether to print status messages, default = False. """ # We assume column 1 is the event *type* (one table per distinct value), not a per-event value/ # payload column. Every NPM stimuli file we have seen behaves this way, but the vendor manual # never pins the two-column format down -- it documents a KeyDown time/label example and a # separate 5-column Digital IOs log, without specifying that the second column is the type, not a # value. This is a convention matched to our examples, not a documented guarantee; do not treat # it as gospel. number_of_columns = pd.read_csv(file_path, header=None, nrows=1).shape[1] if number_of_columns > 2: raise ValueError( f"NPMEventsInterface expects a headerless two-column CSV (onset time, event type), but " f"this file has {number_of_columns} columns. Neurophotometrics also produces richer event " f"files (e.g. the 5-column Digital IOs log), which this interface does not support. Please " f"open an issue at https://github.com/catalystneuro/neuroconv/issues if you need it." ) super().__init__( file_path=file_path, timestamps_column=0, event_type_column=1, time_unit=time_unit, metadata_key=metadata_key, verbose=verbose, )