Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# v0.9.4 (Upcoming)

## Removals, Deprecations and Changes
* Removed the deprecated `stub_frames` parameter from ophys interfaces (`BaseImagingExtractorInterface`, `BaseSegmentationExtractorInterface`, `BrukerTiffMultiPlaneConverter`, `BrukerTiffSinglePlaneConverter`, `MiniscopeConverter`, `Suite2pSegmentationInterface`, `MinianSegmentationInterface`, `MiniscopeImagingDataInterface`). Use `stub_samples` instead. [PR #1676](https://github.com/catalystneuro/neuroconv/pull/1676)
* Removed deprecated wrapper functions from `roiextractors_pending_deprecation.py` (March 2026 deadline): `add_imaging_plane_to_nwbfile`, `add_image_segmentation_to_nwbfile`, `add_photon_series_to_nwbfile`, `add_plane_segmentation_to_nwbfile`, `add_background_plane_segmentation_to_nwbfile`, `add_background_fluorescence_traces_to_nwbfile`, `add_summary_images_to_nwbfile`. [PR #1680](https://github.com/catalystneuro/neuroconv/pull/1680)
* Added `*args` positional argument deprecation to `add_imaging_to_nwbfile` to enforce keyword-only arguments. Will be enforced on or after September 2026. [PR #1680](https://github.com/catalystneuro/neuroconv/pull/1680)
* Added `*args` positional argument deprecation to `add_segmentation_to_nwbfile` to enforce keyword-only arguments. Will be enforced on or after September 2026. [PR #1687](https://github.com/catalystneuro/neuroconv/pull/1687)
Expand All @@ -15,6 +16,7 @@
* Added dict-based metadata pipeline for imaging in `roiextractors.py`, supporting the new `MicroscopySeries`, `ImagingPlanes`, and `Devices` metadata format keyed by `metadata_key`. Old list-based functions are preserved (renamed with `_old_list_format` suffix) and dispatched automatically when `metadata_key` is not provided. [PR #1677](https://github.com/catalystneuro/neuroconv/pull/1677)

## Improvements
* Added array-like protocol methods (`shape`, `ndim`, `__len__`, `__getitem__`) to all data chunk iterators (`SliceableDataChunkIterator`, `SpikeInterfaceRecordingDataChunkIterator`, `ImagingExtractorDataChunkIterator`, `VideoDataChunkIterator`). [PR #1673](https://github.com/catalystneuro/neuroconv/pull/1673)
* Added tests for `OnePhotonSeries`, `processing/ophys` container, and non-iterative write to `TestAddImaging` (dict-based metadata pipeline). [PR #1685](https://github.com/catalystneuro/neuroconv/pull/1685)
* 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](https://github.com/catalystneuro/neuroconv/pull/1669)

Expand Down
46 changes: 43 additions & 3 deletions src/neuroconv/datainterfaces/behavior/video/video_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ def _get_scaled_buffer_shape(self, buffer_gb: float, chunk_shape: tuple) -> tupl
assert all(np.array(chunk_shape) > 0), f"Some dimensions of chunk_shape ({chunk_shape}) are less than zero!"

sample_shape = self._sample_shape
series_shape = self._get_maxshape()
series_shape = self.shape
dtype = self._get_dtype()

buffer_shape = get_image_series_buffer_shape(
Expand All @@ -320,11 +320,51 @@ def _get_scaled_buffer_shape(self, buffer_gb: float, chunk_shape: tuple) -> tupl

return buffer_shape

@property
def shape(self):
"""Return (num_frames, height, width, channels) for this video."""
return (self._num_samples, *self._sample_shape)

@property
def ndim(self):
"""Return the number of dimensions (always 4: frames x height x width x channels)."""
return len(self.shape)

def __len__(self):
"""Return the number of frames in this video."""
return self._num_samples

def __getitem__(self, selection):
"""Enable array-like slicing with random frame access for video data.

Unlike _get_data (which reads frames sequentially from the current cursor
position), this method seeks to the correct frame position first, reads the
requested range, and applies spatial slicing on the result.

Note that this mutates the video capture position, so concurrent access
from multiple threads is not safe.
"""
resolved = self._convert_index_to_slices(selection)

# Seek to the correct frame position before reading
start_frame = resolved[0].start
end_frame = resolved[0].stop
self.video_capture_ob.current_frame = start_frame

num_frames = end_frame - start_frame
frames = np.empty(shape=(num_frames, *self.shape[1:]), dtype=self._dtype)
for frame_index in range(num_frames):
frames[frame_index] = next(self.video_capture_ob)

# Apply spatial slicing (height, width, channels)
spatial_selection = (slice(0, num_frames),) + resolved[1:]
return frames[spatial_selection]

def _get_data(self, selection: tuple[slice]) -> np.ndarray:
start_frame = selection[0].start
end_frame = selection[0].stop

shape = (end_frame - start_frame, *self._maxshape[1:])
shape = (end_frame - start_frame, *self.shape[1:])
frames = np.empty(shape=shape, dtype=self._dtype)
for frame_number in range(end_frame - start_frame):
frames[frame_number] = next(self.video_capture_ob)
Expand All @@ -334,4 +374,4 @@ def _get_dtype(self) -> np.dtype:
return self._dtype

def _get_maxshape(self) -> tuple[int, int, int, int]:
return (self._num_samples, *self._sample_shape)
return self.shape
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,6 @@ def add_to_nwbfile(
photon_series_index: int = 0,
parent_container: Literal["acquisition", "processing/ophys"] = "acquisition",
stub_test: bool = False,
stub_frames: int | None = None,
always_write_timestamps: bool = False,
iterator_type: str | None = "v2",
iterator_options: dict | None = None,
Expand All @@ -198,9 +197,6 @@ def add_to_nwbfile(
under the "processing/ophys" module, by default "acquisition".
stub_test : bool, optional
If True, only writes a small subset of frames for testing purposes, by default False.
stub_frames : int, optional
.. deprecated:: February 2026
Use `stub_samples` instead.
always_write_timestamps : bool, optional
Whether to always write timestamps, by default False.
iterator_type : {"v2", None}, default: "v2"
Expand All @@ -217,7 +213,7 @@ def add_to_nwbfile(
via ``get_default_backend_configuration()`` and ``configure_backend()`` after calling
this method. See the backend configuration documentation for details.
stub_samples : int, default: 100
The number of samples (frames) to use for testing. When provided, takes precedence over `stub_frames`.
The number of samples (frames) to use for testing.
"""

from ...tools.roiextractors import add_imaging_to_nwbfile
Expand All @@ -229,7 +225,6 @@ def add_to_nwbfile(
"photon_series_index",
"parent_container",
"stub_test",
"stub_frames",
"always_write_timestamps",
"iterator_type",
"iterator_options",
Expand All @@ -256,30 +251,14 @@ def add_to_nwbfile(
photon_series_index = positional_values.get("photon_series_index", photon_series_index)
parent_container = positional_values.get("parent_container", parent_container)
stub_test = positional_values.get("stub_test", stub_test)
stub_frames = positional_values.get("stub_frames", stub_frames)
always_write_timestamps = positional_values.get("always_write_timestamps", always_write_timestamps)
iterator_type = positional_values.get("iterator_type", iterator_type)
iterator_options = positional_values.get("iterator_options", iterator_options)
stub_samples = positional_values.get("stub_samples", stub_samples)

# Handle deprecation of stub_frames in favor of stub_samples
if stub_frames is not None and stub_samples != 100:
raise ValueError("Cannot specify both 'stub_frames' and 'stub_samples'. Use 'stub_samples' only.")

if stub_frames is not None:
warnings.warn(
"The 'stub_frames' parameter is deprecated and will be removed on or after February 2026. "
"Use 'stub_samples' instead.",
FutureWarning,
stacklevel=2,
)
effective_stub_samples = stub_frames
else:
effective_stub_samples = stub_samples

if stub_test:
effective_stub_samples = min([effective_stub_samples, self.imaging_extractor.get_num_samples()])
imaging_extractor = self.imaging_extractor.slice_samples(start_sample=0, end_sample=effective_stub_samples)
stub_samples = min([stub_samples, self.imaging_extractor.get_num_samples()])
imaging_extractor = self.imaging_extractor.slice_samples(start_sample=0, end_sample=stub_samples)
else:
imaging_extractor = self.imaging_extractor

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,6 @@ def add_to_nwbfile(
metadata: dict | None = None,
*args, # TODO: change to * (keyword only) on or after August 2026
stub_test: bool = False,
stub_frames: int | None = None,
include_background_segmentation: bool = False,
include_roi_centroids: bool = True,
include_roi_acceptance: bool = True,
Expand All @@ -175,9 +174,6 @@ def add_to_nwbfile(
metadata : dict, optional
The metadata for the interface
stub_test : bool, default: False
stub_frames : int, optional
.. deprecated:: February 2026
Use `stub_samples` instead.
include_background_segmentation : bool, default: False
Whether to include the background plane segmentation and fluorescence traces in the NWB file. If False,
neuropil traces are included in the main plane segmentation rather than the background plane segmentation.
Expand Down Expand Up @@ -210,7 +206,7 @@ def add_to_nwbfile(
via ``get_default_backend_configuration()`` and ``configure_backend()`` after calling
this method. See the backend configuration documentation for details.
stub_samples : int, default: 100
The number of samples (frames) to use for testing. When provided, takes precedence over `stub_frames`.
The number of samples (frames) to use for testing.
roi_ids_to_add : list of str or int, optional
The ROI IDs to include in the NWB file. If ``None`` (default), all ROIs are included.
Use this to filter out rejected or unwanted ROIs and reduce file size.
Expand All @@ -228,7 +224,6 @@ def add_to_nwbfile(
if args:
parameter_names = [
"stub_test",
"stub_frames",
"include_background_segmentation",
"include_roi_centroids",
"include_roi_acceptance",
Expand Down Expand Up @@ -256,7 +251,6 @@ def add_to_nwbfile(
stacklevel=2,
)
stub_test = positional_values.get("stub_test", stub_test)
stub_frames = positional_values.get("stub_frames", stub_frames)
include_background_segmentation = positional_values.get(
"include_background_segmentation", include_background_segmentation
)
Expand All @@ -268,31 +262,14 @@ def add_to_nwbfile(
stub_samples = positional_values.get("stub_samples", stub_samples)
roi_ids_to_add = positional_values.get("roi_ids_to_add", roi_ids_to_add)

# Handle deprecation of stub_frames in favor of stub_samples
if stub_frames is not None and stub_samples != 100:
raise ValueError("Cannot specify both 'stub_frames' and 'stub_samples'. Use 'stub_samples' only.")

if stub_frames is not None:
warnings.warn(
"The 'stub_frames' parameter is deprecated and will be removed on or after February 2026. "
"Use 'stub_samples' instead.",
FutureWarning,
stacklevel=2,
)
effective_stub_samples = stub_frames
else:
effective_stub_samples = stub_samples

segmentation_extractor = self.segmentation_extractor

if roi_ids_to_add is not None:
segmentation_extractor = segmentation_extractor.select_rois(roi_ids=roi_ids_to_add)

if stub_test:
effective_stub_samples = min([effective_stub_samples, segmentation_extractor.get_num_samples()])
segmentation_extractor = segmentation_extractor.slice_samples(
start_sample=0, end_sample=effective_stub_samples
)
stub_samples = min([stub_samples, segmentation_extractor.get_num_samples()])
segmentation_extractor = segmentation_extractor.slice_samples(start_sample=0, end_sample=stub_samples)

metadata = metadata or self.get_metadata()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import warnings
from typing import Literal

from pydantic import DirectoryPath, validate_call
Expand Down Expand Up @@ -88,7 +87,6 @@ def add_to_nwbfile(
nwbfile: NWBFile,
metadata,
stub_test: bool = False,
stub_frames: int | None = None,
stub_samples: int = 100,
):
"""
Expand All @@ -102,34 +100,16 @@ def add_to_nwbfile(
Metadata dictionary containing information to describe the data being added to the NWB file.
stub_test : bool, optional
If True, only a subset of the data (up to `stub_samples`) will be added for testing purposes. Default is False.
stub_frames : int, optional
.. deprecated:: February 2026
Use `stub_samples` instead.
stub_samples : int, default: 100
The number of samples (frames) to use for testing. When provided, takes precedence over `stub_frames`.
The number of samples (frames) to use for testing.
"""
# Handle deprecation of stub_frames in favor of stub_samples
if stub_frames is not None and stub_samples != 100:
raise ValueError("Cannot specify both 'stub_frames' and 'stub_samples'. Use 'stub_samples' only.")

if stub_frames is not None:
warnings.warn(
"The 'stub_frames' parameter is deprecated and will be removed on or after February 2026. "
"Use 'stub_samples' instead.",
FutureWarning,
stacklevel=2,
)
effective_stub_samples = stub_frames
else:
effective_stub_samples = stub_samples

for photon_series_index, (interface_name, data_interface) in enumerate(self.data_interface_objects.items()):
data_interface.add_to_nwbfile(
nwbfile=nwbfile,
metadata=metadata,
photon_series_index=photon_series_index,
stub_test=stub_test,
stub_samples=effective_stub_samples,
stub_samples=stub_samples,
)


Expand Down Expand Up @@ -196,7 +176,6 @@ def add_to_nwbfile(
nwbfile: NWBFile,
metadata,
stub_test: bool = False,
stub_frames: int | None = None,
stub_samples: int = 100,
):
"""
Expand All @@ -211,32 +190,14 @@ def add_to_nwbfile(
stub_test : bool, optional
If True, only a subset of the data (defined by `stub_samples`) will be added for testing purposes,
by default False.
stub_frames : int, optional
.. deprecated:: February 2026
Use `stub_samples` instead.
stub_samples : int, default: 100
The number of samples (frames) to use for testing. When provided, takes precedence over `stub_frames`.
The number of samples (frames) to use for testing.
"""
# Handle deprecation of stub_frames in favor of stub_samples
if stub_frames is not None and stub_samples != 100:
raise ValueError("Cannot specify both 'stub_frames' and 'stub_samples'. Use 'stub_samples' only.")

if stub_frames is not None:
warnings.warn(
"The 'stub_frames' parameter is deprecated and will be removed on or after February 2026. "
"Use 'stub_samples' instead.",
FutureWarning,
stacklevel=2,
)
effective_stub_samples = stub_frames
else:
effective_stub_samples = stub_samples

for photon_series_index, (interface_name, data_interface) in enumerate(self.data_interface_objects.items()):
data_interface.add_to_nwbfile(
nwbfile=nwbfile,
metadata=metadata,
photon_series_index=photon_series_index,
stub_test=stub_test,
stub_samples=effective_stub_samples,
stub_samples=stub_samples,
)
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ def add_to_nwbfile(
metadata: Optional[dict] = None,
*args, # TODO: change to * (keyword only) on or after August 2026
stub_test: bool = False,
stub_frames: int = 100,
stub_samples: int = 100,
include_background_segmentation: bool = True,
include_roi_centroids: bool = True,
include_roi_acceptance: bool = False,
Expand All @@ -145,7 +145,7 @@ def add_to_nwbfile(
if args:
parameter_names = [
"stub_test",
"stub_frames",
"stub_samples",
"include_background_segmentation",
"include_roi_centroids",
"include_roi_acceptance",
Expand All @@ -172,7 +172,7 @@ def add_to_nwbfile(
stacklevel=2,
)
stub_test = positional_values.get("stub_test", stub_test)
stub_frames = positional_values.get("stub_frames", stub_frames)
stub_samples = positional_values.get("stub_samples", stub_samples)
include_background_segmentation = positional_values.get(
"include_background_segmentation", include_background_segmentation
)
Expand All @@ -186,7 +186,7 @@ def add_to_nwbfile(
nwbfile=nwbfile,
metadata=metadata,
stub_test=stub_test,
stub_frames=stub_frames,
stub_samples=stub_samples,
include_background_segmentation=include_background_segmentation,
include_roi_centroids=include_roi_centroids,
include_roi_acceptance=include_roi_acceptance,
Expand Down
Loading
Loading