You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
v0.10.0 (August 18, 2026)
Removals, Deprecations and Changes
Deprecated the es_key argument of the ecephys recording and LFP interfaces, to be removed on or after February 2027. Use metadata_key instead: it is the same concept, the key addressing this interface's entry in the metadata, and the name written to the file comes from that entry's name field rather than from the key. es_key now defaults to None and the interface's own default is used when it is not stated, so the warning reaches callers who pass it and not the interfaces and converters that pass it internally. PR #1941
The dict-based metadata format is now the default: get_metadata() returns it on all 47 public get_metadata and get_metadata_schema surfaces, use_new_metadata_format=False still returns the old shape, and old list-based metadata passed in is converted where it enters the library behind a once-per-process FutureWarning naming August 2027 as the earliest removal. Files change where the two paths disagree: a device nothing links to is dropped, df/F traces join the Fluorescence container, ROICentroids is gone, ROI masks keep the extractor's native format, and a conversion passing no metadata stops writing invented filler (location="", templated electrode-group descriptions, the MEArec device description) while MaxOneRecordingInterface gains a named MaxOne device and an unnamed imaging series is written as MicroscopySeries rather than TwoPhotonSeries or OnePhotonSeries. PR #1934PR #1935PR #1874PR #1853PR #1928PR #1690PR #1694PR #1702PR #1703PR #1704PR #1705PR #1721PR #1723PR #1731
The fiber photometry installation extras are grouped as their own modality: csv_fp, doric_fp, npm_fp, tdt_fp and guppy resolve through a new fiber_photometry_minimal extra holding ndx-fiber-photometry, and neuroconv[fiber_photometry], which used to be that bare dependency, is now the aggregate over all five. Nothing breaks in this release, but neuroconv[ophys] will stop installing them on or after August 2027, so a photometry conversion should ask for neuroconv[fiber_photometry] or for the single format extra it uses. PR #1933
Deprecated AbfInterface, and with it the BaseIcephysInterface it is the only subclass of, to be removed on or after August 2027. Use AxonIntracellularInterface, which reads the same Axon Binary Format files, takes the clamp mode explicitly instead of inferring it from unreliable ABF metadata, and writes one continuous series per electrode rather than one per sweep; where AbfInterface took a list of files, use one interface per electrode per file and combine them with AxonIntracellularConverter. The removal will also drop the ndx-dandi-icephys dependency, of whose twenty-five fields NeuroConv only ever wrote four (cell_id, slice_id, targeted_layer, inferred_layer), none of them read from the file. PR #1932
Deprecated the reference_frame and confidence_definition conversion options of LightningPoseDataInterface.add_to_nwbfile, to be removed on or after August 2027. No other pose interface exposes them as conversion options; set them per series under metadata["Pose"]["PoseEstimations"][metadata_key]["PoseEstimationSeries"] instead. Passing them still works and applies the value to every series. PR #1927
Removed the tolerance_decimals parameter from calculate_regular_series_rate, with no deprecation path, as the rounding it configured no longer happens and its value has no faithful translation into the new tolerance. PR #1925
Bumped minimum versions: hdmf to >=6.1.0, pynwb to >=4.0.0, spikeinterface to >=0.104.7 and neo to >=0.14.5, the last removing the previous neo<0.14.5 cap now that released SpikeInterface no longer passes load_sync_channel to neo's SpikeGLXRawIO. The spikeinterface floor also renames the template metrics used in unit property descriptions (halfwidth to trough_half_width/peak_half_width, peak_to_valley to peak_to_trough_duration). PR #1924PR #1769PR #1697PR #1766
run_conversion no longer accepts a backend_configuration together with an in-memory nwbfile or with append_on_disk_nwbfile=True; both raise ValueError naming what to call instead, since both paths add the conversion's own data after the point where a caller could have derived a configuration. To customize it, build the file with create_nwbfile, derive the configuration from it and write it with configure_and_write_nwbfile; to append with defaults, pass backend. run_conversion(nwbfile_path=..., backend_configuration=...) is unchanged. PR #1910
SpikeGLXRecordingInterface keys its probe device by the probe's serial number instead of its position, so a script editing metadata["Devices"]["neuropixels_imec0"] must now edit metadata["Devices"]["neuropixels_18194809281"]; there is no deprecation path, as the new key is derived from the data. The device name is unchanged and its description loses the JSON blob of probe annotations, replaced by the DeviceModel link, and SpikeGLXSyncChannelInterface stops emitting a device, since it writes a plain TimeSeries that nothing ever linked to one. PR #1895
IntanRecordingInterface names its electrode groups after the headstage port the channels were recorded on (A, B, C) instead of the port's position in a sorted list (0, 1, 2), and writes the port per channel as a port column replacing the stray group_names column nothing downstream read, so a script addressing a group by name, nwbfile.electrode_groups["0"], needs updating. The port is kept as a channel property as well as a group name because attaching a probe regroups the channels and overwrites the group name, while the port stays true of every channel afterwards. A recording whose group_name property disagrees with its group property now reports the conflicting values and says to restate group_name or delete it. PR #1897
A device_metadata_key or device_model_metadata_key that names nothing in its registry now raises one curated ValueError naming the field, the value written, the registry it resolves into and the keys that registry actually holds, from _add_device_to_nwbfile, which every modality resolves through. The only change for a caller is that the video interfaces raise ValueError where they used to raise KeyError. PR #1893
AxonIntracellularInterface reports a device only where the telegraph header names the amplifier, since an ABF v1 file has no telegraph block and a manually set instrument may not be Axon hardware at all; those recordings now write the PlaceholderIntracellularDevice that tools/icephys.py supplies at write time instead of a generic AxonAmplifier. The placeholder states no instrument class on purpose, because across published icephys files on DANDI that link holds a digitizer about half the time, an amplifier a fifth of the time, and a rig, the acquisition software or the pipette for the rest. A file whose telegraph does name the model is unchanged. PR #1893
The device a write path invents when nothing describes the hardware is now built where the object requiring it is created, in ecephys, ophys and icephys alike, and is named PlaceholderElectrodeDevice, PlaceholderMicroscope or PlaceholderIntracellularDevice in place of Device and Microscope, carrying nothing but a name. Two behaviors change with it: a metadata["Devices"]["default_metadata_key"] entry is no longer picked up by an electrode group or imaging plane that names no device of its own, so state that link with the entry's own device_metadata_key, and a device the user named Device or Microscope no longer collides with the placeholder. An electrode group whose device names a model with device_model_metadata_key also resolves it now, where the device writer used to be handed a private dictionary holding only the Devices registry. PR #1893
SpikeGLXNIDQInterface addresses a channel by the board's own name ("XA0", "XD0"), which is what ~snsChanMap, the SpikeGLX user interface and CatGT all show, so get_channel_names(), analog_channel_groups, detection_configuration and get_event_times_from_ttl all take them rather than neo's stream-qualified ids. The prefixed spelling ("nidq#XA0") is still accepted everywhere a channel is named, behind a FutureWarning, and will be removed on or after August 2027. PR #1842
Renamed the default metadata_key on the analog and sync interfaces to snake_case, so a key is a dict handle everywhere and CamelCase is reserved for a neurodata type's name: IntanStimInterface"TimeSeriesIntanStim" -> "intan_stim", IntanAnalogInterface"TimeSeriesAnalogIntan" -> "intan_analog", SpikeGLXSyncChannelInterface"SpikeGLXSync" -> "spikeglx_sync", SpikeGLXNIDQInterface"SpikeGLXNIDQ" -> "spikeglx_nidq", EDFAnalogInterface"analog_edf_metadata_key" -> "edf_analog", IntanConverter's routing keys drop their object-type prefix ("time_series_intan_dc" -> "intan_dc", likewise for the auxiliary, ADC input, ADC output and stim streams), and SpikeGLXConverterPipe keys each probe's sync interface as f"spikeglx_sync_{probe}". This is a breaking change with no deprecation path, so a script that edits metadata["TimeSeries"]["SpikeGLXNIDQ"] must be updated to the new key; the written objects' names are unchanged. PR #1870
The analog and sync interfaces now emit metadata["Devices"] as a keyed registry rather than a list, so a script reading metadata["Devices"][0] from any of them must read it by key instead: IntanAnalogInterface and IntanStimInterface under "intan_device", SpikeGLXNIDQInterface under "spikeglx_nidq_device", and SpikeGLXSyncChannelInterface under f"neuropixels_imec{n}". The Intan and SpikeGLX keys are deliberately the ones their recording interfaces already use, because one Intan system is one device however many streams it produced and a probe's sync channel is the same probe as its AP and LF streams. These were the last interfaces emitting the legacy list shape, so it is no longer produced anywhere in NeuroConv, and the two interfaces that read the registry back raise a message naming the key an entry now belongs under when handed the old list. PR #1870
add_recording_as_time_series_to_nwbfile's metadata_key now defaults to None instead of "TimeSeries", which was both the name of the metadata block and the default name of the written object, so the default call looked up metadata["TimeSeries"]["TimeSeries"]. A key is now required exactly when the metadata carries a "TimeSeries" block, and it must resolve: an unaddressed block or an unresolvable key raises instead of silently writing defaults over the caller's edits, matching what the ElectricalSeries path already did. Calling with no metadata is unchanged and still writes from the recording's own properties. PR #1870
OpenEphysBinaryAnalogInterface gained a metadata_key parameter (default "open_ephys_analog"), which had been fused with time_series_name: the same string served as both the metadata key and the written object's name. time_series_name keeps naming the object. PR #1870
The shared test mixins dropped test_run_conversion_with_backend_configuration, test_run_conversion_with_backend, test_configure_backend_for_equivalent_nwbfiles and the check_run_conversion_in_nwbconverter_* helpers; test_all_conversion_checks now writes the interface once per backend with run_conversion(backend=...), reads each file back through check_read_nwb, and no longer builds an NWBConverter. check_read_nwb now runs for the zarr file as well as the hdf5 one, so a downstream override that opens the file with NWBHDF5IO instead of pynwb.read_nwb fails on the zarr pass; set check_read_nwb_backends = ("hdf5",) on the test class to opt out, as ImagingExtractorInterfaceTestMixin does. test_metadata and check_extracted_metadata now name the dict-based format and the old-format pair is test_metadata_old_list_format and check_extracted_metadata_old_list_format, so a downstream class asserting the old shape has to rename it. PR #1866PR #1862PR #1928
The stimulus_type column on the intracellular-recordings table is now optional, so an interface whose format carries no protocol section stops writing a column holding nothing but a placeholder on every row; _build_icephys_hierarchical_tables still supplies "not described" at the SequentialRecordings level, where NWB requires it, and MockIcephysInterface accepts stimulus_type=None. It also decides which recordings rows were simultaneous from the sweep's resolved time rather than from its (start_index, count) range, which had collapsed the rows of an interface writing one series per sweep into a single SimultaneousRecordings entry claiming one electrode recorded several things at once. AxonIntracellularInterface is unchanged. PR #1852
Renamed the container-selection parameter write_as to parent_container (now keyword-only) across the audio, spatial-series, sorting, and recording functions, so a single keyword selects the destination NWB container everywhere. The old write_as keyword still works but emits a FutureWarning and will be removed on or after February 2027. For the recording functions the values are now {"acquisition", "processing/LFP", "processing/FilteredEphys"} (the deprecated write_as maps "raw" -> "acquisition", "lfp" -> "processing/LFP", "processed" -> "processing/FilteredEphys"). PR #1760
Migrated TDTFiberPhotometryInterface to a single-series interface that selects input streams via the new stream_names argument. Constructing without stream_names is deprecated and will be removed on or after February 2027. PR #1778
Migrated ExternalVideoInterface and InternalVideoInterface to the unified dict-based metadata pattern: added a metadata_key registry key and moved the camera Device to top-level metadata["Devices"] referenced by device_metadata_key. The nested device=dict(...) form is deprecated and will be removed on or after February 2027. PR #1767
Removed the deprecated external_mode parameter from LightningPoseConverter.add_to_nwbfile and migrated the converter to ExternalVideoInterface. Videos are now always written as external ImageSeries with the ExternalVideos dict-based metadata structure (the old Videos list structure is no longer accepted). PR #1734
Removed the deprecated iterator_opts parameter throughout, from add_recording_to_nwbfile, add_recording_as_time_series_to_nwbfile, write_recording_to_nwbfile, BaseRecordingExtractorInterface.add_to_nwbfile, BaseLFPExtractorInterface.add_to_nwbfile, CellExplorerLFPInterface.add_to_nwbfile, EDFAnalogInterface.add_to_nwbfile, IntanAnalogInterface.add_to_nwbfile, OpenEphysBinaryAnalogInterface.add_to_nwbfile and SpikeGLXNIDQInterface.add_to_nwbfile. Use iterator_options instead. PR #1730PR #1689PR #1681
Removed the deprecated es_key parameter from SpikeGLXNIDQInterface.__init__. The parameter had no effect since the NIDQ interface writes analog data as TimeSeries and digital data as LabeledEvents, not ElectricalSeries. PR #1730
Made nwbfile_path a required parameter in write_recording_to_nwbfile, write_sorting_to_nwbfile, and write_sorting_analyzer_to_nwbfile. To add data to an in-memory NWBFile, use add_recording_to_nwbfile, add_sorting_to_nwbfile, or add_sorting_analyzer_to_nwbfile instead. PR #1689
write_recording_to_nwbfile, write_sorting_to_nwbfile, and write_sorting_analyzer_to_nwbfile now return None in append mode (append_on_disk_nwbfile=True) instead of the NWBFile object. PR #1689
Removed the deprecated plane_name and fallback_sampling_frequency parameters from ScanImageImagingInterface. Use plane_index instead of plane_name. PR #1688
Changed the default value of interleave_slice_samples in ScanImageImagingInterface from True to False. PR #1688
Removed the deprecated extractor property and get_extractor() method from BaseExtractorInterface. Use get_extractor_class() instead. PR #1681
Removed the deprecated iterator_type='v1' option from _imaging_frames_to_hdmf_iterator. Use iterator_type='v2' (default) instead. PR #1679
Removed support for passing rate in trace metadata for fluorescence traces. The rate is now always calculated automatically from the segmentation extractor's timestamps or sampling frequency. PR #1679
Removed the deprecated stub_frames parameter from ophys interfaces (BaseImagingExtractorInterface, BaseSegmentationExtractorInterface, BrukerTiffMultiPlaneConverter, BrukerTiffSinglePlaneConverter, MiniscopeConverter, Suite2pSegmentationInterface, MinianSegmentationInterface, MiniscopeImagingDataInterface). Use stub_samples instead. PR #1676
Added *args positional argument deprecation to add_imaging_to_nwbfile and add_segmentation_to_nwbfile to enforce keyword-only arguments. Will be enforced on or after February 2027. PR #1680PR #1687
Deprecated the legacy folder discovery mode of MiniscopeConverter (when user_configuration_file_path is not passed). Will be removed on or after February 2027. PR #1706
Deprecated plane_segmentation_name on Suite2pSegmentationInterface (both __init__ and add_to_nwbfile), together with the matching plane_segmentation_name and background_plane_segmentation_name kwargs on the public add_segmentation_to_nwbfile. Use metadata_key for pattern discovery and edit metadata["Ophys"]["PlaneSegmentations"][metadata_key]["name"] directly for custom names; these will be removed when the old list-based metadata format is. PR #1716
Removed three deprecated parameters: staging from automatic_dandi_upload (use sandbox), container_name from ImageInterface.add_to_nwbfile and DeepLabCutInterface.add_to_nwbfile (use metadata_key in __init__), and time_series_name from add_recording_as_time_series_to_nwbfile (use metadata_key). PR #1678
Deprecated add_electrodes_to_nwbfile, whose implementation is now private as _add_electrodes_to_nwbfile, because calling it standalone can produce missing or spurious device and electrode-group metadata. Use add_recording_metadata_to_nwbfile to add devices, electrode groups and electrodes together; the public wrapper emits a FutureWarning and will be removed on or after February 2027. PR #1719
Deprecated BrukerTiffSinglePlaneImagingInterface, BrukerTiffMultiPlaneImagingInterface, BrukerTiffSinglePlaneConverter, and BrukerTiffMultiPlaneConverter. Use the unified BrukerTiffImagingInterface (or BrukerTiffConverter for multi-channel and multi-plane folders) instead. Will be removed on or after February 2027. PR #1773
Deprecated the public add_devices_to_nwbfile (ecephys), which now emits a FutureWarning and only supports the old list-based metadata["Ecephys"]["Device"] format. Pass dict-based metadata (metadata["Devices"]) to add_recording_to_nwbfile, or call _add_device_to_nwbfile from tools.nwb_helpers directly for a single device; removal on or after February 2027. PR #1743
Stopped calling the removed get_accepted_list and get_rejected_list methods on segmentation extractors; ROI acceptance is now written automatically as a column on the PlaneSegmentation table from the extractor's property system (is_accepted, is_rejected). The include_roi_acceptance parameter on add_segmentation_to_nwbfile, write_segmentation_to_nwbfile, BaseSegmentationExtractorInterface.add_to_nwbfile, Suite2pSegmentationInterface.add_to_nwbfile and MinianSegmentationInterface.add_to_nwbfile is now a deprecated no-op and will be removed on or after February 2027. PR #1736
Updated the URL for EMBER with sandbox=True for the automatic_dandi_upload helper function. Also updated the testing suite to use a new sandbox Dandiset. PR #1777PR #1818
The removal deadlines for the deprecations announced in this release moved from December 2026 to February 2027, and the warning messages and docstrings state the later date. PR #1936
Bug Fixes
Fixed the Inscopix segmentation gallery page not showing its install command: the .. code-block:: bash directive had no blank line before its content, so docutils dropped the block and pip install "neuroconv[inscopix]" appeared nowhere on the rendered page. PR #1945
Fixed DeepLabCutInterface writing the row index into the pose series as if it were seconds, so a conversion that stated no timestamps produced a file timed at 1 Hz. Neither the .h5/.csv nor the project config records a frame rate, so it has to come from the caller through the new sampling_frequency argument or set_aligned_timestamps, and writing with neither now raises. Files already written this way carry wrong timing rather than missing timing and need re-converting. PR #1937
Fixed EDFRecordingInterface discarding the subject fields it read from the header and writing experimenter as a string instead of the list the schema types it as, and added the patient field's sex. PR #1929
Fixed the three spikeinterface writers discarding the rate they measured from a recording's timestamps and writing recording.get_sampling_frequency() in its place. A recording only reaches that branch when it carries a timestamps vector, which is what an alignment or a device's own clock produces, so the disagreement between the measured and the nominal rate was exactly the information being thrown away: on the Open Ephys v0.6 test data the two differ by 94 ppm, which displaces samples by 339 ms per hour. The other nineteen call sites already wrote the measured rate. PR #1926
Fixed calculate_regular_series_rate accepting a series that has drifted arbitrarily far from a straight line. It tested whether the consecutive differences all rounded to the same microsecond, which bounds the gap between neighbours but not the departure those gaps accumulate into, so a slowly drifting series was written as starting_time plus rate with samples near its middle hundreds of sampling periods from where the interface placed them. It now accepts a series only if replacing it displaces no sample by more than a microsecond, and measures the rate across the whole series rather than off the first difference, so the rate also changes slightly on series that were already being accepted. PR #1925
Fixed every dataset added to an NWB file after nwbfile.objects was first read being written with no compression and no chunking: objects is a cache pynwb builds on first access and never invalidates, so anything a later interface added was invisible to the backend configuration. All six call sites now walk the file with all_children() instead. One call order changes with it: building a backend configuration, adding a container, then calling configure_backend now raises ValueError: The number of default configurations (N) does not match the number of specified configurations (M)! where it used to silently leave the added dataset unconfigured. PR #1910
Fixed ScanImageImagingInterface.get_available_channels and get_available_planes raising AttributeError, and get_metadata raising AttributeError: 'NoneType' object has no attribute 'replace' on a single-channel file opened without naming its channel, which is the documented way to open one. The two discovery methods called methods roiextractors had renamed before the roiextractors>=0.9.0 we require, so neither had worked for any file since that pin and nothing in the test suite called either one; they now read through get_available_channel_names and get_available_num_planes, and get_available_planes keeps returning the list of plane names it documents rather than the count the extractor now reports. On the metadata side only the old list-based branch interpolated the channel unguarded, and the two branches now agree that an unnamed channel leaves the objects with the plain names (ImagingPlane, TwoPhotonSeries); the photon series description no longer reads for None either. PR #1908
Fixed add_imaging_to_nwbfile(..., iterator_type=None) raising ValueError: axes don't match array on volumetric data. The non-iterative branch transposed with a fixed three-axis permutation, which does not apply to the 4D series a volumetric extractor returns, so opting out of the iterator was broken for every volumetric format rather than any one of them. It now picks the permutation from the number of axes, the same rule the default iterator already applied. PR #1908
Fixed configure_and_write_nwbfile failing with ValueError: Could not find location '...' in builder on an NWB file read from disk whose extension the reading process never imported. The builder used to size each dataset was built by the global manager from pynwb.get_manager(), which only knows the extensions this process imported, so a container from an unimported extension was built without any of its datasets. A read file is now built by a fresh manager carrying the type map of the manager that read it, which holds the namespaces cached in the file. PR #1904
Fixed OpenEphysBinaryAnalogInterface discarding the caller's TimeSeries metadata. Its entry was built inside add_to_nwbfile and assigned unconditionally, after the metadata argument had already been accepted, so any edit to that entry's name or description was silently overwritten by the interface's own defaults. The entry now comes out of get_metadata like every other analog interface, so it can be edited before the write rather than being replaced by it. PR #1870
Fixed SpikeGLXNIDQInterface writing its digital events on a different clock than its analog channels: event times came from the reader's event extractor, which counts from zero at the start of the file and ignores the stream's firstSample, while the analog TimeSeries from the same board starts at firstSample / niSampRate. One physical instant was therefore recorded at two times in the same file, 11.9 s apart on the DigitalChannelTest_g0 fixture, and misaligned against the IMEC probes in a SpikeGLXConverter run as well; the events are now read against the recording's own clock. PR #1842
Fixed MiniscopeConverter silently dropping the behavior video whenever a User Config file is passed: the camera folders are now discovered from devices[cameras] in that config, the way the imaging folders already were, instead of globbing the lab-specific BehavCam* name at a fixed depth where config-driven layouts never sit. Each camera folder is written as an ExternalVideoInterface with the timestamps of its own timeStamps.csv and a Device of its own, aligned to the same session start time as the imaging interfaces. The legacy discovery mode, with no config file, is unchanged. PR #1851
Fixed MiniscopeConverter raising AttributeError: 'list' object has no attribute 'keys' on a User Config that declares its devices in the legacy list form. The DAQ's own config schema allows devices[miniscopes] and devices[cameras] either as an object keyed by device name, which is what the software writes today and what neuroconv reads, or as an array of devices each carrying its own deviceName. No recording in the legacy form has reached us, so rather than write discovery that cannot be tested against a real config, the converter now raises a NotImplementedError naming the shape and asking for the file through an issue. PR #1855
Fixed appending rows to a units or electrodes table when a column of the existing table gets no value from the new data. Units without electrode indices failed with TypeError: 'NoneType' object is not subscriptable from hdmf, and the same was true of any other ragged column; units without waveforms, or with waveforms where the table had none, failed with a shape mismatch from numpy. Ragged columns now take an empty row and columns whose rows are arrays take an array of the matching shape filled with NaN. PR #1832
Replaced the two reads of the private recording._recording_segments[segment_index].t_start with the public recording.get_start_time(segment_index=...) when writing an ElectricalSeries or a TimeSeries from a recording. SpikeInterface is making the segment attributes private, which would break the starting time of every recording written through these two paths; the public method has been available since 0.104.0, well below the current minimum pin. PR #1712
Fixed fill_defaults raising KeyError: 'properties' on schema nodes validated only by additionalProperties (e.g. an object keyed by a dynamic metadata_key, as in TDTEventsInterface). Such nodes have no named properties to attach defaults to and are now skipped, so wrapping these interfaces in an NWBConverter/ConverterPipe and calling get_metadata_schema() no longer crashes. PR #1765
Fixed InscopixImagingInterface falsely rejecting single-plane recordings as multiplane. The previous check flagged any file containing the substring "multiplane", which every modern Inscopix file carries as UI configuration state regardless of plane count; detection now reads the microscope.multiplane.enabled flag from the file's JSON metadata. PR #1735
Fixed ImageInterface writing all images as float64 regardless of source dtype. SingleImageIterator.dtype now reports the dtype derived from the PIL image mode (uint8 for L/RGB/RGBA/LA, uint16 for I;16), so on-disk datasets match the source. PR #1744
Fixed MiniscopeBehaviorInterface.get_metadata() reporting session_start_time off by up to 1 ms because the msec field from metaData.json was treated as microseconds rather than milliseconds. PR #1725
Fixed RuntimeWarning: divide by zero in calculate_regular_series_rate when all timestamps are identical (zero time-step). The function now emits a UserWarning and returns None early instead of performing an invalid division. PR #1701
Fixed compatibility with upcoming roiextractors changes (roiextractors PR #562) by removing calls to the deprecated get_num_channels and get_channel_names methods on imaging extractors, and making the volumetric chunking test dtype-aware. PR #1696
Fixed NotImplementedError when using ndx-events>=0.2.2 with configure_and_write_nwbfile. The version check for ndx-events Events types was too restrictive (<= 0.2.1), causing files with Events objects to fail during backend configuration. PR #1682
Fixed get_json_schema_from_method_signature to resolve PEP 563 string annotations (from from __future__ import annotations) before passing them to pydantic. This affected any interface defined in a module with deferred annotations (e.g. MiniscopeConverter, or external subclasses from SpikeInterface). PR #1670
Fixed DeepDict.copy() raising TypeError: dict expected at most 1 argument, got 2: DeepDict subclasses defaultdict, whose copy protocol reconstructs via type(self)(default_factory, self), but DeepDict.__init__ hardcodes its own factory and forwards the extra arguments. This surfaced running a dict-based-metadata imaging interface through a ConverterPipe (BrukerTiffConverter on multi-channel data), where the metadata merged by dict_deep_update contains DeepDict leaves that the ophys add path copies. PR #1773
Fixed NWB image datasets (GrayscaleImage, RGBImage, RGBAImage) being written uncompressed and unchunked. These types inherit from NWBData rather than NWBContainer, so get_default_dataset_io_configurations never produced a configuration for them, and a shadowed name-mangled attribute in pynwb made set_data_io a silent no-op on them even when one was supplied. Images written by ImageInterface and the ophys summary images now get the same default chunking and gzip compression as every other dataset. PR #1835
Fixed the spike2 extra not installing sonpy: its python_version=='3.9' marker can never be satisfied under requires-python = ">=3.10". The extra now targets the platform/Python combinations where sonpy publishes a usable wheel (Windows, or Python 3.14+). PR #1855
Features
The imaging, segmentation and fiber photometry interfaces gained get_metadata_template(), the counterpart to get_metadata(): it returns the source-derived values wrapped in the full structure the writer expects, with the *_metadata_key cross-references resolved and every field only the experimenter can supply left None, sized to what the interface writes at one entry per trace and per summary image for segmentation and one FiberPhotometryTable row per trace for photometry. The blanks are the checklist, since what comes back None is exactly what the source could not tell us, and the new Metadata Templates user guide page carries the same structures as YAML and JSON files to fill in by hand. A FiberPhotometryTable row's notes also reaches the file now, where the column had always been in ndx-fiber-photometry and the writer never passed it through. PR #1939PR #1911
Added BaseEventsInterface, a shared base for events interfaces that writes native pynwb 4.0 EventsTable objects into nwbfile.events, with MockEventsInterface for exercising the writer across payload shapes without acquisition data. interface.alignment.shift_times(delta) offsets every event time by delta seconds at write for gross temporal alignment, leaving durations and the source times untouched, and get_event_times(event_type_source_id) reads one event type's onsets back on the interface's current clock so a line used as a clock can align another stream, with get_event_type_source_ids listing the handles it takes. An event type with zero occurrences is written as a zero-row table rather than forbidden, so a source that enumerates its event types from a header (an Intan digital word) can write an enabled-but-idle line faithfully. PR #1774PR #1787PR #1798PR #1795PR #1931PR #1813
ExternalVideoInterface computes starting_frames from the frame count of each video file when the caller does not pass it, instead of raising. That argument holds the cumulative frame boundaries of the files being written, which the interface already opens to sum num_samples and to read the frame rate, so stating them by hand asked for a number that is read off the data anyway. Passing starting_frames explicitly still overrides the computed value. PR #1924
Added parent_container: Literal["acquisition", "processing/ophys"] = "acquisition" to BaseFiberPhotometryInterface.add_to_nwbfile, so processed fiber photometry signals (e.g. dF/F, motion-corrected traces) can be written to the processing/ophys module rather than acquisition. The parameter defaults to "acquisition" to preserve existing behavior; passing "processing/ophys" creates the module if it does not exist, using the same get_module helper the imaging interfaces already use for the same purpose. PR #1915
Added ScanImageConverter and ThorConverter, which write every channel of an acquisition in one call, building the channel set from the file itself as one ImagingPlane and one TwoPhotonSeries per channel over a single shared device, the way BrukerTiffConverter already does. A ScanImage acquisition spread over several files is followed from its first file, and a volumetric one is written as a 4D series per channel. ThorConverter.get_available_channels reads the names from the acquisition's Experiment.xml, whose Wavelengths block is what the extractor accepts, rather than from the OME-XML, which carries no channel names for this format. PR #1913
The brukertiff, micromanagertiff, scanimage, thor and tiff extras now install tifffile[codecs], without which a compressed TIFF raises <COMPRESSION.LZW: 5> requires the 'imagecodecs' package from inside tifffile mid-read, as though the file were corrupt. ThorImageLS writes LZW-compressed TIFFs whenever the operator leaves compression on, and compression is a property of the file a user was handed rather than a choice they made, so they cannot know to install it in advance. This is meant to be temporary, and the comment in pyproject.toml states the condition for taking it back out. PR #1913
Added Suite2pConverter, which writes every plane and channel of a Suite2p output folder in one call as one PlaneSegmentation, ImagingPlane and set of traces per pair, instead of instantiating one Suite2pSegmentationInterface per pair by hand. Channels are discovered per plane rather than for the folder as a whole, so a session whose red channel was segmented for only some of its planes writes those and skips the rest. The combined folder Suite2p writes for a multi-plane session is skipped, since its ROIs are the per-plane ones concatenated. PR #1902
SpikeGLXRecordingInterface and OpenEphysBinaryRecordingInterface now write the attached Neuropixels probe's identity as a DeviceModel carrying the catalogue manufacturer and model_number (imec, NP1110) and a Device carrying the unit's serial_number, so probeinterface.get_probe(manufacturer, model_number) rebuilds the probe from what is in the file. add_recording_to_nwbfile reads the same identity off the recording's probe, so an electrode group naming no device of its own links to the probe rather than the placeholder even when no metadata is passed. This only fires for a single-probe recording whose probe names a model_name, so a probe carrying only a manufacturer (Biocam, Maxwell) keeps PlaceholderElectrodeDevice, and a group naming its own device_metadata_key still wins. PR #1895
EDFRecordingInterface and EDFAnalogInterface now take a stream_name, and get_stream_names lists the ones a file offers. EDF groups its channels into one stream per sampling rate, so a recording that samples some of its signals at a different rate than the rest carries more than one, and until now those files could not be opened at all: the extractor raised asking for an argument the interfaces had no way to accept. get_available_channel_ids now reads the names from the file's header rather than through a stream, so it answers for the whole file and no longer raises on those recordings; the channels of the stream an interface holds are a subset of what it returns. PR #1892
Added BaseRecordingExtractorInterface.remove_channels to drop channels from the recording an interface already holds. It is the generic equivalent of the construction-time selections (EDF's channels_to_skip, the analog interfaces' channels_to_include) for channels only known to be wrong afterwards. PR #1890
Added GuppyInterface and GuppyConverter for converting GuPPy processed fiber photometry outputs, depending on the ndx-guppy extension; the converter bundles a session's raw acquisition, raw events and GuPPy-derived outputs into one conversion, with the recording format selected by acquisition_format ("tdt", "csv", "doric" or "npm", all four installed by neuroconv[guppy]) and acquisition series grouped by excitation wavelength rather than written one per store. events_folder_path is scanned for the one-column timestamps CSVs GuPPy's custom-event import writes, so an event store with one of its own is read from it while every other store falls to acquisition_format, and each event type gets its own EventsTable in nwbfile.events rather than being merged into one BehavioralEvents table. GuppyInterface is public, so a session GuPPy processed out of an existing NWB file is converted by handing that file to add_to_nwbfile, and session_start_time is no longer derived from timeRecStart, a clock origin rather than a recording start, which had reported 1970-01-01 for CSV, NPM and most Doric sessions. PR #965PR #1783PR #1879PR #1880PR #1840
The modality metadata blocks are now described by get_metadata_schema() in the dict-based format, each an object keyed by metadata_key rather than an array: Ecephys.ElectrodeGroups and Ecephys.ElectricalSeries on the recording base, Electrodes and UnitProperties on the sorting base, Ophys.ImagingPlanes and MicroscopySeries on the imaging base, plus PlaneSegmentations, RoiResponses and SegmentationImages on the segmentation base. Entries stay permissive, since each is handed to a pynwb constructor, but their shape is now checked, so an edit written against the old format that lands in a block whose name exists in both (metadata['Ecephys']['ElectricalSeries']['name'] = ...) is rejected at validation instead of being silently ignored at write time. The old list-based format is validated exactly as before, and the local Devices and DeviceModels declarations in ExternalVideoInterface, InternalVideoInterface and BaseFiberPhotometryInterface are gone. PR #1873
Added IntanDigitalInterface for converting Intan digital TTL lines into discrete events, written as native pynwb.event.EventsTable objects, with lines selected and edge-detected through a detection_configuration keyed by the header's own name for each line so one interface covers both digital words; coded multi-bit words are deferred. IntanConverter routes the digital input and output words to it automatically, so those lines are converted with no extra wiring. It also takes saved_files_are_split, the last Intan interface to gain it, reading the rotated files as one continuous recording before edge detection, so a pulse that rises in one file and falls in the next reads as the single pulse it is and carries the time it has on the session's clock. PR #1812PR #1819PR #1891
SpikeGLXNIDQInterface now writes its digital events as native pynwb.event.EventsTable objects into nwbfile.events rather than as ndx-eventsLabeledEvents into acquisition, and ndx-events is no longer a dependency of neuroconv[spikeglx]. Which lines to read and how is set through a new detection_configuration on the shared signal-encoded grammar, addressing a line as word plus bit ({"XD0": [{"signal_conditioning": {"bits": [0]}, "detection": "high_period"}]}) the way ~snsChanMap and CatGT do, with the analog channels inventoried alongside so they can be cut into events with thresholds; by default every line the header declares is read as a lossless high_period. The digital_channel_groups argument is deprecated for removal on or after August 2027 and is now translated onto the new grammar, so a group still yields one object holding every edge, with labels_map naming the two edges through the table's event_type column instead of an index into a labels list. PR #1842
Dict-based metadata can now be passed to run_conversion: validate_metadata on both BaseDataInterface and NWBConverter picks the schema that matches the metadata it is given, so dict-based metadata is checked against the base schema instead of being rejected by a modality schema that describes only the old shape ('Device' is a required property). The base schema now declares the top-level Devices and DeviceModels registries, which no schema described until now, so every interface that emits them stops declaring its own and MiniscopeConverter drops its local workaround. One consequence worth knowing: an interface that emits the legacy list-valued metadata["Devices"] has that list validated for the first time against a required name with no slash in it, which every such interface already satisfies. PR #1868
The ecephys interfaces and converters adopted the dict-based metadata format, each gaining a metadata_key that keys its ElectricalSeries entry: SpikeGLXRecordingInterface registers the Neuropixels probe in the top-level Devices registry keyed per probe, IntanRecordingInterface its Intan device, NeuralynxRecordingInterface the header's AcquisitionSystem, AxonaRecordingInterface the DacqUSB software version, AxonRecordingInterface its Axon Instruments device, MaxOneRecordingInterface a named MaxOne device carrying the Maxwell version, and MEArecRecordingInterface the electrode template name, each linked from its electrode groups through device_metadata_key, while NeuroScopeRecordingInterface and NeuroScopeLFPInterface claim no device because the .xml describes only the shank structure. Sixteen more (AlphaOmega, Blackrock, EDF, Plexon, PlexonLFP, Plexon2, CellExplorer, CellExplorerLFP, OpenEphysBinary, OpenEphysLegacy, SpikeGadgets, Biocam, MCSRaw, Spike2, Tdt and WhiteMatter) read no device or electrode groups out of their sources, so the argument and its snake_case default (alpha_omega_recording, blackrock_recording, edf_recording, plexon_recording, plexon_lfp, plexon2_recording, cell_explorer_recording, cell_explorer_lfp, open_ephys_recording, spikegadgets_recording, biocam_recording, mcs_raw_recording, spike2_recording, tdt_recording, white_matter_recording, and neuroscope_recording, neuroscope_lfp, axon_recording, maxone_recording, mearec_recording, neuralynx_recording and axona_recording for the ones above) are the whole of their dict-based support, with the last six of that list previously raising TypeError on it and PlexonLFPInterface and CellExplorerLFPInterface naming their own series (ElectricalSeriesLF and ElectricalSeriesLFP). OpenEphysBinaryConverter gives each neural stream its own metadata_key derived from the stream name and names each stream's ElectricalSeries, which the interfaces cannot do themselves since each only knows it is "the" Open Ephys recording, and IntanConverter passes its routing table's key through as metadata_key rather than translating it into es_key. PR #1859PR #1858PR #1856PR #1850PR #1848PR #1844PR #1843PR #1816PR #1815
Added a data_representation write option ("digital_counts", the default, or "physical_units") to add_recording_to_nwbfile and BaseRecordingExtractorInterface.add_to_nwbfile. "physical_units" folds each channel's gain and offset into float data, so channels with heterogeneous per-channel offsets can be written to a single ElectricalSeries. PR #1814PR #1894
Subject metadata no longer requires subject_id, sex and species, none of which NWB requires, so an interface that reads only part of a file's subject information can report just that instead of inventing the rest. The Inscopix interfaces no longer fill subject_id with "Unknown" or species with "Unknown species", leaving NWB Inspector to report the omission; sex still falls back to "U", NWB's own term for unknown. PR #1845
Added BrukerTiffImagingInterface, a unified interface for Bruker Prairie View OME-TIFF data backed by the new unified BrukerTiffImagingExtractor, and BrukerTiffConverter, which auto-enumerates a folder's channels and builds one interface per channel, together replacing the deprecated BrukerTiffSinglePlaneConverter and BrukerTiffMultiPlaneConverter. Single-plane, volumetric and multi-channel cases go through one class with channels selected by channel_name ("Ch1", "Ch2"), and plane_index on the interface plus plane_separation_type on the converter write volumetric data either as one 4D TwoPhotonSeries per channel ("contiguous") or one 2D series per depth plane ("disjoint"), each imaging plane carrying its own focal depth from the active zDevice so origin_coords, grid_spacing and field_of_view are right. The microscope belongs to the folder rather than to a channel or a plane, so every interface over a folder shares one device entry, now keyed bruker_device_<system number> from the .xml's SystemIDs and carrying the machine-unique identifier as its serial_number, where a constant key used to merge two different Bruker systems into one entry; files predating SystemIDs keep the old key and carry no serial number. PR #1804PR #1773PR #1860
Migrated the Miniscope interfaces to the dict-based metadata format: Miniscope is a registered device type built from a single config-to-device mapping, so gain, led0, frameRate, framesPerFile, compression and ROI reach the device instead of only its name, values are coerced to the dtype the ndx-miniscope schema declares (gain: 3.5 and frameRate: 50 are real values that raised TypeError), settings the schema has no field for (ewl, the sensor offsets of the ROI) are named in the device description rather than dropped, and the deviceType the DAQ reports becomes the DeviceModel pynwb 4 asks for. Devices are keyed by the Miniscope they describe rather than by the interface's metadata_key, so MiniscopeConverter's User Config mode writes one Devices and one ImagingPlanes entry per Miniscope, shared by all of its recordings, and one MicroscopySeries entry per recording. A device keeps only the settings all of its recordings agree on, with the ones that vary reported on each MicroscopySeries rather than left on whichever recording was merged last, and folder discovery is sorted; the deprecated folder-discovery mode keeps the list-based format until its removal. PR #1847PR #1849
Added NPMFiberPhotometryInterface for converting raw Neurophotometrics (NPM) fiber photometry data. PR #1756
Added DoricEventsInterface and DoricCSVEventsInterface for converting discrete events from Doric Neuroscience Studio .doric (HDF5) digital IO and from its CSV exports. Both the modern .doric layout (root group DataAcquisition), including its durative events, and the legacy "EPConsole" layout (Traces/<console>/<stream>/<stream> with DI--O-* digital lines) are read, auto-detected from the file. PR #1805PR #1817PR #1821PR #1823
Added CSVFiberPhotometryInterface for converting a raw fiber photometry recording from a CSV file, parsed into the ndx-fiber-photometry format, and MultiFileCSVFiberPhotometryInterface for aggregating several per-channel CSV files (GuPPy's per-region files, for instance) into one fiber photometry response series. An interleaved recording, where excitation channels are multiplexed frame-by-frame down the rows, is demultiplexed through a demux_configuration reading one channel per interface: its column selector takes values, a single label value or a list of them for label values that denote the same channel, and a skip_rows count that drops leading rows before the label is consulted, for a startup frame whose label would otherwise select it into a channel. Both interfaces accept a time_unit argument (seconds, milliseconds or microseconds) that scales the timestamps column to seconds. PR #1754PR #1808PR #1830PR #1820
Added CSVEventsInterface for converting discrete events from a CSV file into native pyNWB 4.0 EventsTable objects in nwbfile.events, with per-column roles (event_type_column, value_columns, durations_column) and a time_unit argument (seconds, milliseconds or microseconds) that scales the timestamp and duration columns to seconds. PR #1755PR #1785PR #1809
Added NPMEventsInterface for converting discrete events from raw Neurophotometrics (NPM) headerless two-column stimuli CSVs. Built on CSVEventsInterface, the rows are split by unique event-type label and each label is written as its own pynwb.event.EventsTable into nwbfile.events. PR #1757
Added TDTEventsInterface for converting discrete events (epocs) from a TDT tank folder, including events that have a duration, whose duration is written to the EventsTableduration column instead of raising. PR #1751PR #1775PR #1781
Added BaseFiberPhotometryInterface, a base class for fiber photometry interfaces. Its get_metadata() returns only the response-series entry rather than a placeholder device/indicator/table scaffold ("PLACEHOLDER" strings and NaN wavelengths), so an interface writes a bare FiberPhotometryResponseSeries unless the user supplies the full provenance chain, and the two fields the source never provides no longer raise KeyError before writing: the optical fiber's device model is attached only when device_model_metadata_key is supplied, matching ndx-ophys-devices where model is optional, and the response series' DynamicTableRegion description defaults at construction, as ecephys does with its hardcoded "electrode_table_region", instead of requiring fiber_photometry_table_region_description. PR #1778PR #1800PR #1807
Added support for device models via the top-level metadata["DeviceModels"] and metadata["Devices"] registries, resolved by device_metadata_key and device_model_metadata_key everywhere a device is written; the caller's registries now reach the device writer whole rather than being rebuilt per modality, so a DeviceModels entry is no longer dropped on the way to an imaging plane or a camera. A model entry may omit the NWB-required manufacturer, which an acquisition file rarely records, and two Devices or DeviceModels keys that resolve to the same NWB name now raise before the object is added. Fiber photometry writes only the devices a FiberPhotometryTable row references rather than every entry of the converter-wide registries, so an entry naming no type is no longer written out as a plain Device. PR #1780PR #1824PR #1883
Added DoricFiberPhotometryInterface for converting fiber photometry data from Doric Neuroscience Studio .doric HDF5 files, built on BaseFiberPhotometryInterface. It also reads Doric's CSV format and the legacy "EPConsole" .doric HDF5 layout (Traces/<console>/<stream>/<stream>), auto-detected alongside the newer DataAcquisition-based layout. PR #1727PR #1779
Added MockFiberPhotometryInterface, a synthetic acquisition fiber photometry interface for testing and demos. It is constructed from excitation_wavelengths_in_nm and num_fibers, one source stream per wavelength carrying one column per fiber, which is the organization ndx-fiber-photometry recommends: one series per excitation/emission wavelength. PR #1788PR #1889
Use SpikeInterface metric descriptions to populate property_descriptions in add_sorting_analyzer_to_nwbfilePR #1717
Added VameInterface for converting VAME behavioral segmentation data, writing the faithful ndx-vame series (per-frame motif labels as MotifSeries, optional LatentSpaceSeries and CommunitySeries, and the project config serialized as JSON) and, for each motif run, a curated ndx-ethogram product run-length-encoding the MotifSeries into an EthogramBouts timeline with an Ethogram catalogue, one row per motif id with the modal community in category. The bouts link back to the producing MotifSeries (source), the upstream PoseEstimation (source_pose) and, when a project sets video_metadata_key, the video ImageSeries (source_video). A data_to_write option ("algorithm_output", "ethogram" or "both", default "both") selects which outputs are written, with "ethogram" dropping the MotifSeries and its back-link; requires the ndx-vame and ndx-ethogram extensions. PR #1737PR #1770PR #1792PR #1803
Added AxonIntracellularInterface for converting intracellular electrophysiology recorded in Axon Binary Format (.abf), one electrode in one file per instance, writing its response as a single continuous PatchClampSeries (with an optional paired stimulus taken from a recorded monitor channel or a reconstructed protocol command) and one IntracellularRecordings row per sweep, addressed by (start_index, count) ranges and tagged with the run's sequence and stimulus_type columns. The clamp mode (voltage_clamp, current_clamp or izero) is selected explicitly because Axon clamp-mode metadata is unreliable, and the interface uses the dict-based metadata format keyed by metadata_key. It writes only the per-sweep recordings rows; the upper icephys hierarchy tables are assembled separately, once the full set of channels and files is known. PR #1746
Added AxonIntracellularConverter, which combines several AxonIntracellularInterface instances (dual-patch channels in one file, or one cell across several protocol files) into a single NWB icephys table, aligning multi-file recordings on one timeline by their header start times (rec_datetime, ABF version 2 only), disambiguating runs that share a Clampex-assigned name (0000.abf), and building the upper hierarchy tables (SimultaneousRecordings, SequentialRecordings, Repetitions, ExperimentalConditions) through a format-agnostic aggregator in tools.icephys. Optional repetition and condition labels on the interface group runs into the repetitions and experimental-conditions levels, and a sweepsTimeIntervals table holds every sweep's start and stop time, so tools that read NWB intervals surface them with no icephys-specific code. MockIcephysInterface writes a PatchClampSeries and one recordings row per sweep for testing the format-independent machinery without acquisition files, and the converter's grouping-level and electrode-sharing tests run through it rather than downloading ABF files. PR #1761PR #1833
Added the shared machinery for the signal-encoded events interfaces, which derive events from a sampled signal, together with MockSignalEncodedEventsInterface in neuroconv.tools.testing to exercise it. Every detection spec now carries a signal_conditioning saying how its signal becomes a two-valued line, holding exactly one of bits (name a wire inside a packed word) or binarize (cut a magnitude, at a number you give or at "midpoint", derived from the data). A signal that is already a line takes {"binarize": "midpoint"}, whose cut falls between the two levels whatever they are, so it reads a 0/1 line and a line at 48 and 64 alike; DoricEventsInterface and DoricCSVEventsInterface are the first users. PR #1825
Added XClustSortingInterface for converting XClust (.CEL) spike sorting data, using the XClustSortingExtractor from SpikeInterface. PR #1691
Added IntanConverter for one-call conversion of multi-stream Intan recordings, parsing the .rhd/.rhs header via neo to auto-discover which streams are present and route each to IntanRecordingInterface, IntanAnalogInterface, IntanStimInterface or IntanDigitalInterface. IntanStimInterface is new, writing RHS2000 stimulation channels (one per amplifier channel, named {channel}_STIM) as a TimeSeries with unit="A", the conversion factor derived automatically from stim_step_size in the header. A saved_files_are_split parameter on the recording, analog and stim interfaces concatenates all sibling .rhd/.rhs files in filename order for sessions saved with RHX's "create a new save file every N minutes" option, warning when it is off and rotation files sit next to the one the user pointed at; the converter forwards it to every sub-interface. PR #1711PR #1724PR #1739
Added OpenEphysBinaryConverter for automatic multi-stream OpenEphys binary conversion, following the SpikeGLXConverterPipe pattern. Auto-discovers streams and routes neural data to OpenEphysBinaryRecordingInterface and analog (ADC/NI-DAQ) data to OpenEphysBinaryAnalogInterface. PR #1686, PR #1740
Added the dict-based metadata pipeline for ophys in roiextractors.py, covering imaging (MicroscopySeries, ImagingPlanes and Devices keyed by metadata_key) and segmentation (_add_plane_segmentation_to_nwbfile, _add_roi_response_traces_to_nwbfile, _add_summary_images_to_nwbfile), with dual routing in add_imaging_to_nwbfile and add_segmentation_to_nwbfile and the old functions kept under an _old_list_format suffix. Masks are written in the extractor's native format, all traces go into a single Fluorescence container, summary images go into a shared SegmentationImages container in the ophys processing module configurable through metadata["Ophys"]["SegmentationImages"], and BaseImagingExtractorInterface.get_metadata names the series it writes in metadata["Ophys"]["MicroscopySeries"], where the name used to materialise inside the write path and so could not be edited beforehand. metadata_key and use_new_metadata_format reached the segmentation interfaces (BaseSegmentationExtractorInterface, Caiman, Cnmfe, Extract, Sima, Suite2p, Minian and Inscopix), with Suite2p surfacing the source-provided imaging_rate and populating RoiResponses and SegmentationImages, and MicroManagerTiffImagingInterface gaining an ImagingPlanes entry linked from its MicroscopySeries through imaging_plane_metadata_key. PR #1677PR #1692PR #1695PR #1708PR #1716PR #1722PR #1726PR #1865
Driving the dict-based ophys path on the interfaces that had never seen it fixed several things. Six interfaces registered a Devices entry with no name, and SbxImagingInterface, FemtonicsImagingInterface and the two Inscopix interfaces registered a device no imaging plane referenced, so the writer substituted its placeholder and the description the source recorded was lost; the registries are now keyed by the microscope rather than by the interface (scan_image_microscope, thor_microscope, scanbox_microscope, femtonics_microscope, and inscopix_{serial} where the file records one), ScanImageImagingInterface and ThorImagingInterface name their imaging plane and series after the channel and plane so two channels no longer overwrite each other, Thor's optical channel takes the placeholder description rather than an empty string, MicroManagerTiffImagingInterface keeps the unit and format the source states, and Inscopix's acquisition settings move into the imaging plane description. A PlaneSegmentations entry that names no segmentation of its own now defaults to PlaneSegmentation where the object is built, so a second entry colliding on a defaulted name raises rather than silently dropping its ROIs while sharing one by a caller-stated name stays allowed, and ROI property columns carry descriptions, with snr, r_values and cnn_preds taking the same ones the old path gave them. PR #1872PR #1865
Added MdaSortingInterface for converting MountainSort v4 and earlier firings.mda sorting output. PR #1203
Added the dict-based metadata pipeline for ecephys in spikeinterface.py, supporting the new top-level Devices format and a metadata_key kwarg on add_recording_to_nwbfile and on BaseRecordingExtractorInterface (defaulting to the value of es_key), together with use_new_metadata_format on get_metadata(). The shape is detected by _is_dict_based_metadata and routed by the dispatcher in add_recording_metadata_to_nwbfile, dict-based through _add_electrode_groups_to_nwbfile and _get_ecephys_metadata_placeholders, old list-based through _add_electrode_groups_to_nwbfile_old_list_format. The three decision points on the write path each ask about the block they are about to write rather than about the dictionary's overall shape, which is what a converter needs: an ordinary camera-plus-ephys conversion used to route the recording to the dict path and raise ValueError: metadata['Ecephys']['ElectricalSeries'] does not contain key 'ElectricalSeries', and the electrode groups also stop writing a placeholder Device over the one the metadata names. PR #1743PR #1747PR #1867
The pose interfaces adopted the dict-based metadata format: DeepLabCutInterface, LightningPoseDataInterface, LightningPoseConverter and MockPoseEstimationInterface take a metadata_key (default "lightning_pose" on the Lightning Pose pair) and a use_new_metadata_format argument on get_metadata()/get_metadata_schema(), emitting top-level metadata["Devices"] and metadata["Pose"] holding Skeletons and PoseEstimations linked via device_metadata_key and skeleton_metadata_key. add_to_nwbfile writes either shape, auto-detected from the metadata, so default behavior is unchanged; pose_estimation_metadata_key is deprecated in favor of metadata_key and will be removed on or after February 2027. PR #1750PR #1745PR #1927
Added BrukerVoltageRecordingInterface and BrukerVoltageRecordingConverter for the intracellular recordings Bruker PrairieView writes alongside two-photon imaging, closing issue #379: PrairieView writes one CSV/XML pair per cycle, a cycle being one sweep, and the interface takes an explicit list of them for one electrode and concatenates them into a single PatchClampSeries placed on one timeline by each cycle's DateTime. The clamp mode is read off the unit of the amplifier's Primary output (mV for current clamp, pA for voltage clamp) rather than being required, and the Multiplier/Divisor scaling is carried in the series' conversion so the samples stay exactly what PrairieView wrote. Column identity comes from the XML's Enabled flags positionally rather than from the CSV header, whose names are shifted on real published recordings, where believing them writes the membrane potential as a current. PR #1854
Improvements
BaseRecordingExtractorInterface.__init__ no longer renames a channel_names property to channel_name. The shim was written against SpikeInterface 0.101.0 and carried a TODO to remove it once that release was out; the minimum is now spikeinterface>=0.104.7 and no extractor sets a channel_names property any more, so the branch was unreachable. Nothing else changes, as every consumer of the name already reads the singular and falls back to the channel ids when it is absent. PR #1943
Added docs/how_to/extract_events_from_signals.rst, documenting the detection_configuration argument the signal-encoded events interfaces take: which signals are read, how each becomes a line with bits or binarize, which of its transitions become events, and the failure a parameter name cannot state, that a configuration is a selection and drops the signals it does not name. The grammar had shipped user-facing and undocumented, taught from scratch on the SpikeGLX and Intan recording gallery pages and never mentioned on the Doric events page although both Doric interfaces take it. Those gallery pages now point at it and keep only what is specific to their own hardware, and docs/how_to/annotate_events_metadata.rst gained a pointer as well. PR #1942
Writing an EventsTable, a PlaneSegmentation, an ethogram bouts table, a GuPPy events table or an icephys sweeps table is now linear in the number of rows rather than quadratic, because DynamicTable.add_row calls hdmf's is_ragged on the entire column after appending each value and none of these tables can hold a ragged cell. They now pass check_ragged=False, which hdmf documents for exactly this case. A twelve-minute fiber photometry recording whose digital line pulses at 30 Hz writes its 21,330 events in 0.9 s instead of 201 s, and a segmentation with 8,000 image-mask ROIs writes in 0.5 s instead of 22 s; the written files are unchanged. PR #1920
The YAML specifications printed in docs/user_guide/docker_demo.rst and docs/user_guide/aws_demo.rst run again: both still passed file_path to SpikeGLXRecordingInterface, which has taken folder_path and stream_id since v0.9.0, and the AWS one asked for a DANDI upload without giving its sessions the session_id that requires. Neither was caught because the Docker CI test runs the specification maintained under tests/, not the one the page prints, and that page also no longer promises console output the command stopped printing when the converter stopped being constructed with verbose=True. Added docs/user_guide/converting_multiple_sessions.rst, which walks the loop over LocalPathExpander results that converts one session at a time and then the two dataset-wide steps over the resulting folder, uploading to a Dandiset with automatic_dandi_upload and reorganizing into a BIDS layout with nwb2bids. PR #1906PR #1838
docs/developer_guide/metadata_principles.rst gained a "Placeholders for required links" section beside the existing one on required fields. The page said what to do when a metadata entry omits a field NWB requires and nothing about when it omits the object an entry links to, which is the same question and the one a new interface hits first. It now states that a placeholder object is created when the schema requires the link and nothing is written when it does not, that the requirement is read off the schema rather than the docval (pynwb gives ImagingPlane.device a default while the schema requires it, so the constructor signature answers that one wrong), that the placeholder is built where the object needing it is created instead of being pre-filled into metadata["Devices"] so an ordinary lookup resolves, and why the placeholder devices carry a Placeholder prefix in their names. PR #1896PR #1801
The heterogeneous-offset errors now print which channels carry which offset and link a new how-to guide that says which of the two fixes applies. The TimeSeries warning for heterogeneous units now says that the physical values will not be recoverable from the file. PR #1890
Added a how-to guide for annotating fiber photometry metadata (docs/how_to/annotate_fiber_photometry_metadata.rst). It builds a FiberPhotometryTable one step at a time, starting from a conversion with no metadata at all and adding the row, the indicator, the devices and their models in turn, then covers the two ways a table grows: one fiber recorded at a signal and an isosbestic wavelength, and several fibers in different brain regions. The dict-based format itself is documented in docs/developer_guide/fiber_photometry_metadata_structure.rst. PR #1806
Writing the IntracellularRecordings rows and their run-level columns (sequence, stimulus_type, repetition, condition) moved out of the individual icephys interfaces into _add_intracellular_recordings_to_nwbfile, beside the _build_icephys_hierarchical_tables that reads those columns back, so the two halves of that contract cannot drift. A run-level column now belongs to the table rather than to the interface that introduced it, which makes an optional value representable when runs disagree about having one: combining a run that states a stimulus_type with one that does not writes an empty cell for the latter and the readable placeholder at the sequential level, instead of failing inside hdmf mid-write with an error that named neither the argument nor the run and that changed with declaration order. AxonIntracellularInterface always states a stimulus type, so its output is unchanged. PR #1854
MiniscopeBehaviorInterface no longer depends on ndx-miniscope for raw format parsing; ndx-miniscope is used only for NWB construction. Legacy V3 Miniscope folders now raise a clear error on both Miniscope interfaces pointing users to the issue tracker so V3 support can be added against real fixtures. PR #1725
Added a conversion gallery example for aligning ScanImage imaging data with external DAQ sync pulses, demonstrating use of get_original_frame_indices and slice_samples from ScanImageImagingExtractor. PR #1709
Added array-like protocol methods (shape, ndim, __len__, __getitem__) to all data chunk iterators (SliceableDataChunkIterator, SpikeInterfaceRecordingDataChunkIterator, ImagingExtractorDataChunkIterator, VideoDataChunkIterator). PR #1673
add_segmentation_to_nwbfile now warns when user-provided RoiResponses metadata references traces the extractor does not have. PR #1693
Added column-first fast path for writing Units tables when the table is new (no append/merge). Uses id.extend() + add_column() instead of per-row add_unit() calls, reducing Python overhead for large sortings. PR #1669
Added documentation for the new ophys metadata structure: a how-to guide for annotating ophys metadata (docs/how_to/annotate_ophys_metadata.rst) and a developer guide describing the dict-based schema for Devices, ImagingPlanes, and MicroscopySeries (docs/developer_guide/ophys_metadata_structure.rst). PR #1653
Added documentation for the new discrete-events metadata structure: a how-to guide for annotating events metadata (docs/how_to/annotate_events_metadata.rst) and a developer guide describing the dict-based schema for the global EventTables and per-interface event_columns (docs/developer_guide/events_metadata_structure.rst), including the role of column_name when several event types are pooled into one EventsTable. PR #1759PR #1831
Dropped Plexon2 testing on macOS, where the wine-crossover Homebrew cask, the only tap that worked reliably on Apple Silicon, was removed upstream on 2026-04-16: the install-wine composite action is now a no-op there and TestPlexon2RecordingInterface is skipped on all macOS runners, while Linux keeps full coverage via apt-installed wine. The same PR bumped actions/checkout to v6, actions/setup-python to v6 and actions/cache to v5 for Node.js 24. PR #1720
CI and workflow maintenance. The test-data cache key is now the hash of each dataset's S3 listing rather than a gin query, a pull request from a fork restores the most recent cached copy through restore-keys instead of aborting at the data step for want of secrets, installation moved from pip to uv throughout testing.yml, the tested Python versions come from min_python_version.txt and max_python_version.txt, the live-service tests run on every operating system but only the newest Python per pull request with the full matrix still nightly, the YAML DANDI upload test polls the sandbox instead of sleeping a flat 60 seconds, the wine install dropped --no-quarantine, and the link checker no longer follows ffmpeg.org or nwb-users.slack.com. The formatwise gallery job no longer derives an installation extra from each page's filename but reads docs/conversion_examples_gallery/extras_by_gallery_entry.json, and MultiFileCSVFiberPhotometryInterface, a layout variant rather than a separate format, moved into the CSV fiber photometry gallery page instead of keeping one of its own. PR #1741PR #1699PR #1828PR #1836PR #1864PR #1912PR #1827PR #1887PR #1684PR #1861PR #1885
Test suite maintenance. Each interface now writes two NWB files in its test class instead of six and both are read back and checked, DataInterfaceTestMixin.test_metadata validates get_metadata() against the interface's own get_metadata_schema(), the SpikeGLX converters' dict-based metadata is pinned one test per probe count with every old-format test carrying an _old_list_format suffix, and tests were added for OnePhotonSeries, the processing/ophys container, non-iterative write, and segmentation image masks, ROI properties, timestamps, trace values, table regions and iterator options. Fixtures now read data that exists in gin rather than only on the CI S3 mirror, the Bruker expectations follow the 64x64 stubs that replaced the 512x512 ones, the compression test asserts on the numeric filter ID rather than the description hdf5plugin>=7.0.0 reworded, the 956 unactionable warnings of a green run are filtered, class-scoped fixtures are @classmethod as pytest 10 requires, and on-data test paths honor a NEUROCONV_TEST_DATA_PATH environment variable so every worktree inherits one setting. PR #1707PR #1766PR #1685PR #1811PR #1862PR #1866PR #1869PR #1930PR #1834PR #1693
Internal cleanup, with no change to the metadata shapes or the written files. Ecephys, ophys and the video interfaces write their devices through the top-level metadata["Devices"] registry instead of pre-building the entry themselves, and a nested sub-object written inline as metadata (an optical fiber's fiber_insertion, an imaging plane's optical_channel) is built from the parent's own constructor spec rather than by hand at each call site. The 33 text-mode open(), read_text() and write_text() sites under tests/ and docs/ that relied on the platform default now declare encoding="utf-8", which PR #1660 already made a hard error for the neuroconv module, and a duplicated definition of FiberPhotometryInterfaceTestMixin is gone. PR #1796PR #1829PR #1857PR #1671