CSV Fiber Photometry data conversion#
Install NeuroConv with the additional dependencies necessary for reading CSV Fiber Photometry data.
pip install "neuroconv[csv_fp]"
This is a general-purpose CSV reader: point it at one CSV file and name the column holding the
timestamps in seconds (timestamps_column) and the data column(s) whose fluorescence samples form
the series (data_columns). Columns are addressed by name (for a CSV with a header row) or by
0-based positional index (for a header-less CSV).
The interface writes a single FiberPhotometryResponseSeries; its channels are the data_columns
read from the file, in column order, column-stacked into one series. This covers a narrow one-column
file (the GuPPy acquisition format’s <stream>.csv with timestamps and data) and a wide
file with several data columns. To aggregate several per-channel CSV files (e.g. GuPPy’s per-region
files) into one series, use MultiFileCSVFiberPhotometryInterface, described below. To write
several separate series (e.g. a signal and an isosbestic control) sharing one
FiberPhotometryTable, combine one interface per series (with distinct metadata_key values) in
a converter.
Interleaved (multiplexed) files#
Some CSVs interleave the excitation channels frame-by-frame down the rows rather than giving each
channel its own column, so one row is one channel at one timepoint. Pass a demux_configuration to read a
single channel out of such a file. There are two shapes, chosen by by:
{"by": "column", "column": ..., "values": ...}when a column labels each row’s channel (e.g. a NeurophotometricsLedState): reads the rows carrying this channel’s label. One channel can be named by more than one label, and a list selects the rows carrying any of them. For example, an NPMLedStatepacks the digital input lines into the same integer as the excitation LED, so a single LED is written as several distinct codes. A startup frame with a label of its own is excluded for free, by carrying no channel’s label; when its label does belong to a channel (an NPM initialization frame sets every excitation bit, so it carries every wavelength’s label), pass"skip_rows": nto drop thenleading rows before the labels are consulted.{"by": "stride", "channels": k, "index": i, "skip_rows": n}when a header-less file cycles the channels in a fixed order with no label column: reads everyk-th row from offsetiafter droppingnleading calibration rows.
The interface stays single-series, so instantiate one interface per channel (with distinct
metadata_key values) and combine them in a converter:
# A LedState column labels each row's excitation channel; one interface per channel.
signal = CSVFiberPhotometryInterface(
file_path=interleaved_path, data_columns="Region0G", timestamps_column="Timestamp",
demux_configuration={"by": "column", "column": "LedState", "values": 2}, metadata_key="signal",
)
isosbestic = CSVFiberPhotometryInterface(
file_path=interleaved_path, data_columns="Region0G", timestamps_column="Timestamp",
demux_configuration={"by": "column", "column": "LedState", "values": 1}, metadata_key="isosbestic",
)
Convert CSV Fiber Photometry data to NWB#
Convert CSV Fiber Photometry data to NWB using
CSVFiberPhotometryInterface.
CSV recordings carry no embedded recording-start timestamp, so session_start_time must be
supplied explicitly in the metadata.
Here we use a small example signal-channel CSV (Sample_Signal_Channel.csv) with timestamps
and data columns:
timestamps,data
0.0,0.1
0.01,0.106
0.02,0.112
...
>>> from datetime import datetime
>>> from zoneinfo import ZoneInfo
>>> from neuroconv.datainterfaces import CSVFiberPhotometryInterface
>>> # Inspect the file's column headers (callable before construction)
>>> available_columns = CSVFiberPhotometryInterface.get_available_columns(file_path=csv_signal_channel_path)
>>> interface = CSVFiberPhotometryInterface(file_path=csv_signal_channel_path, data_columns="data", timestamps_column="timestamps", metadata_key="calcium_signal", verbose=False)
>>> metadata = interface.get_metadata()
>>> # CSV recordings have no embedded start time, so it must be set explicitly.
>>> metadata["NWBFile"]["session_start_time"] = datetime.now(tz=ZoneInfo("US/Pacific"))
>>> # Add subject information (required for DANDI upload)
>>> metadata["Subject"] = dict(subject_id="subject1", species="Mus musculus", sex="M", age="P30D")
>>> # Choose a path for saving the nwb file and run the conversion
>>> nwbfile_path = f"{path_to_save_nwbfile}"
>>> interface.run_conversion(nwbfile_path=nwbfile_path, metadata=metadata, overwrite=True)
One file per channel#
Some acquisition formats write one CSV file per channel/region rather than one wide CSV. GuPPy, for
instance, stores each region in its own file whose channel identity lives in the filename. Use
MultiFileCSVFiberPhotometryInterface
for these. It reads data_columns from each file, in file-then-column order, and column-stacks
them into a single FiberPhotometryResponseSeries. The channels share one time axis, taken from
the first file’s timestamps_column.
Because only channels on a common timebase can share one series, the first file must contain the
timestamps_column. Secondary files may omit it (their timestamps would be redundant); when a
secondary file does contain it, the interface asserts it matches the first file’s timestamps, so
files that do not share a timebase fail loudly instead of producing a silently mis-timed series.
Here we use two per-channel CSVs (Sample_Signal_Channel.csv and Sample_Control_Channel.csv),
each with timestamps and data columns on a common timebase:
# Sample_Signal_Channel.csv # Sample_Control_Channel.csv
timestamps,data timestamps,data
0.0,0.1 0.0,0.05
0.01,0.106 0.01,0.053
0.02,0.112 0.02,0.056
... ...
>>> from neuroconv.datainterfaces import MultiFileCSVFiberPhotometryInterface
>>> interface = MultiFileCSVFiberPhotometryInterface(file_paths=[csv_signal_channel_path, csv_control_channel_path], data_columns="data", timestamps_column="timestamps", metadata_key="calcium_signal", verbose=False)
>>> metadata = interface.get_metadata()
>>> # CSV recordings have no embedded start time, so it must be set explicitly.
>>> metadata["NWBFile"]["session_start_time"] = datetime.now(tz=ZoneInfo("US/Pacific"))
>>> # Add subject information (required for DANDI upload)
>>> metadata["Subject"] = dict(subject_id="subject1", species="Mus musculus", sex="M", age="P30D")
>>> # Choose a path for saving the nwb file and run the conversion
>>> nwbfile_path = f"{path_to_save_nwbfile}"
>>> interface.run_conversion(nwbfile_path=nwbfile_path, metadata=metadata, overwrite=True)
NeuroConv aims to automatically add all the metadata annotations that are present in the source format. It is often the case that crucial information is not available there, such as the anatomical location, the meaning of the values, or a semantically meaningful description of the data. Follow the fiber photometry how-to for a modality-relevant guide to adding this extra metadata, which makes the data more useful for future users and for the community as a whole. Its section on templates starts from scratch, and the reference template lists every element the metadata accepts.