Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move imports of extensions to the init of the interfaces #1144

Merged
merged 10 commits into from
Nov 22, 2024
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# Upcoming

## Deprecations
* Completely removed compression settings from most places[PR #1126](https://github.com/catalystneuro/neuroconv/pull/1126)
* Completely removed compression settings from most places [PR #1126](https://github.com/catalystneuro/neuroconv/pull/1126)

## Bug Fixes
* datetime objects now can be validated as conversion options [#1139](https://github.com/catalystneuro/neuroconv/pull/1126)
* Fix a bug where data in `DeepLabCutInterface` failed to write when `ndx-pose` was not imported. [#1144](https://github.com/catalystneuro/neuroconv/pull/1144)

## Features
* Propagate the `unit_electrode_indices` argument from the spikeinterface tools to `BaseSortingExtractorInterface`. This allows users to map units to the electrode table when adding sorting data [PR #1124](https://github.com/catalystneuro/neuroconv/pull/1124)
Expand Down
6 changes: 4 additions & 2 deletions src/neuroconv/datainterfaces/behavior/audio/audiointerface.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ def __init__(self, file_paths: list[FilePath], verbose: bool = False):

verbose : bool, default: False
"""
# This import is to assure that ndx_sound is in the global namespace when an pynwb.io object is created.
# For more detail, see https://github.com/rly/ndx-pose/issues/36
import ndx_sound # noqa: F401

suffixes = [suffix for file_path in file_paths for suffix in Path(file_path).suffixes]
format_is_not_supported = [
suffix for suffix in suffixes if suffix not in [".wav"]
Expand Down Expand Up @@ -166,7 +170,6 @@ def add_to_nwbfile(
stub_frames: int = 1000,
write_as: Literal["stimulus", "acquisition"] = "stimulus",
iterator_options: Optional[dict] = None,
compression_options: Optional[dict] = None, # TODO: remove completely after 10/1/2024
overwrite: bool = False,
verbose: bool = True,
):
Expand Down Expand Up @@ -224,7 +227,6 @@ def add_to_nwbfile(
write_as=write_as,
starting_time=starting_times[file_index],
iterator_options=iterator_options,
compression_options=compression_options, # TODO: remove completely after 10/1/2024; still passing for deprecation warning
)

return nwbfile
Original file line number Diff line number Diff line change
Expand Up @@ -279,13 +279,14 @@ def _write_pes_to_nwbfile(
else:
timestamps_cleaned = timestamps
pauladkisson marked this conversation as resolved.
Show resolved Hide resolved

timestamps = np.asarray(timestamps_cleaned).astype("float64", copy=False)
pes = PoseEstimationSeries(
name=f"{animal}_{keypoint}" if animal else keypoint,
description=f"Keypoint {keypoint} from individual {animal}.",
data=data[:, :2],
unit="pixels",
reference_frame="(0,0) corresponds to the bottom left corner of the video.",
timestamps=timestamps_cleaned,
timestamps=timestamps,
confidence=data[:, 2],
confidence_definition="Softmax output of the deep neural network.",
)
Expand All @@ -298,6 +299,7 @@ def _write_pes_to_nwbfile(

# TODO, taken from the original implementation, improve it if the video is passed
dimensions = [list(map(int, image_shape.split(",")))[1::2]]
dimensions = np.array(dimensions, dtype="uint32")
pose_estimation_default_kwargs = dict(
pose_estimation_series=pose_estimation_series,
description="2D keypoint coordinates estimated using DeepLabCut.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from pydantic import FilePath, validate_call
from pynwb.file import NWBFile

# import ndx_pose
from ....basetemporalalignmentinterface import BaseTemporalAlignmentInterface


Expand Down Expand Up @@ -48,6 +47,9 @@ def __init__(
verbose: bool, default: True
Controls verbosity.
"""
# This import is to assure that the ndx_pose is in the global namespace when an pynwb.io object is created
from ndx_pose import PoseEstimation, PoseEstimationSeries # noqa: F401

from ._dlc_utils import _read_config

file_path = Path(file_path)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ def __init__(
verbose : bool, default: True
controls verbosity. ``True`` by default.
"""
# This import is to assure that the ndx_pose is in the global namespace when an pynwb.io object is created
h-mayorquin marked this conversation as resolved.
Show resolved Hide resolved
# For more detail, see https://github.com/rly/ndx-pose/issues/36
import ndx_pose # noqa: F401

from neuroconv.datainterfaces.behavior.video.video_utils import (
VideoCaptureContext,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ def __init__(
verbose : bool, optional
Whether to print verbose output, by default True
"""
# This import is to assure that the ndx_events is in the global namespace when an pynwb.io object is created
h-mayorquin marked this conversation as resolved.
Show resolved Hide resolved
# For more detail, see https://github.com/rly/ndx-pose/issues/36
import ndx_events # noqa: F401

if aligned_timestamp_names is None:
aligned_timestamp_names = []
super().__init__(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def __init__(self, folder_path: DirectoryPath, verbose: bool = True):
folder_path=folder_path,
verbose=verbose,
)
# This module should be here so ndx_fiber_photometry is in the global namespace when an pynwb.io object is created
import ndx_fiber_photometry # noqa: F401

def get_metadata(self) -> DeepDict:
Expand Down
12 changes: 0 additions & 12 deletions src/neuroconv/tools/audio/audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ def add_acoustic_waveform_series(
starting_time: float = 0.0,
write_as: Literal["stimulus", "acquisition"] = "stimulus",
iterator_options: Optional[dict] = None,
compression_options: Optional[dict] = None, # TODO: remove completely after 10/1/2024
) -> NWBFile:
"""

Expand Down Expand Up @@ -53,17 +52,6 @@ def add_acoustic_waveform_series(
"acquisition",
], "Acoustic series can be written either as 'stimulus' or 'acquisition'."

# TODO: remove completely after 10/1/2024
if compression_options is not None:
warn(
message=(
"Specifying compression methods and their options at the level of tool functions has been deprecated. "
"Please use the `configure_backend` tool function for this purpose."
),
category=DeprecationWarning,
stacklevel=2,
)

iterator_options = iterator_options or dict()

container = nwbfile.acquisition if write_as == "acquisition" else nwbfile.stimulus
Expand Down
47 changes: 46 additions & 1 deletion tests/test_on_data/behavior/test_behavior_interfaces.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import sys
import unittest
from datetime import datetime, timezone
from pathlib import Path
Expand All @@ -10,7 +11,6 @@
from natsort import natsorted
from ndx_miniscope import Miniscope
from ndx_miniscope.utils import get_timestamps
from ndx_pose import PoseEstimation, PoseEstimationSeries
from numpy.testing import assert_array_equal
from parameterized import param, parameterized
from pynwb import NWBHDF5IO
Expand Down Expand Up @@ -105,6 +105,8 @@ def check_extracted_metadata(self, metadata: dict):
assert metadata["Behavior"][self.pose_estimation_name] == self.expected_metadata[self.pose_estimation_name]

def check_read_nwb(self, nwbfile_path: str):
from ndx_pose import PoseEstimation, PoseEstimationSeries

with NWBHDF5IO(path=nwbfile_path, mode="r", load_namespaces=True) as io:
nwbfile = io.read()

Expand Down Expand Up @@ -381,6 +383,49 @@ def check_read_nwb(self, nwbfile_path: str):
assert all(expected_pose_estimation_series_are_in_nwb_file)


@pytest.fixture
def clean_pose_extension_import():
modules_to_remove = [m for m in sys.modules if m.startswith("ndx_pose")]
for module in modules_to_remove:
del sys.modules[module]


@pytest.mark.skipif(
platform == "darwin" and python_version < version.parse("3.10"),
reason="interface not supported on macOS with Python < 3.10",
)
def test_deep_lab_cut_import_pose_extension_bug(clean_pose_extension_import, tmp_path):
"""
Test that the DeepLabCutInterface writes correctly without importing the ndx-pose extension.
See issues:
https://github.com/catalystneuro/neuroconv/issues/1114
https://github.com/rly/ndx-pose/issues/36

"""

interface_kwargs = dict(
file_path=str(
BEHAVIOR_DATA_PATH
/ "DLC"
/ "open_field_without_video"
/ "m3v1mp4DLC_resnet50_openfieldAug20shuffle1_30000.h5"
),
config_file_path=str(BEHAVIOR_DATA_PATH / "DLC" / "open_field_without_video" / "config.yaml"),
)

interface = DeepLabCutInterface(**interface_kwargs)
metadata = interface.get_metadata()
metadata["NWBFile"]["session_start_time"] = datetime(2023, 7, 24, 9, 30, 55, 440600, tzinfo=timezone.utc)

nwbfile_path = tmp_path / "test.nwb"
interface.run_conversion(nwbfile_path=nwbfile_path, metadata=metadata, overwrite=True)
with NWBHDF5IO(path=nwbfile_path, mode="r") as io:
read_nwbfile = io.read()
pose_estimation_container = read_nwbfile.processing["behavior"]["PoseEstimation"]

assert len(pose_estimation_container.fields) > 0


@pytest.mark.skipif(
platform == "darwin" and python_version < version.parse("3.10"),
reason="interface not supported on macOS with Python < 3.10",
Expand Down
3 changes: 2 additions & 1 deletion tests/test_on_data/behavior/test_lightningpose_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from warnings import warn

from hdmf.testing import TestCase
from ndx_pose import PoseEstimation
from pynwb import NWBHDF5IO
from pynwb.image import ImageSeries

Expand Down Expand Up @@ -134,6 +133,8 @@ def test_run_conversion_add_conversion_options(self):
self.assertNWBFileStructure(nwbfile_path=nwbfile_path, **self.conversion_options)

def assertNWBFileStructure(self, nwbfile_path: str, stub_test: bool = False):
from ndx_pose import PoseEstimation

with NWBHDF5IO(path=nwbfile_path) as io:
nwbfile = io.read()

Expand Down
Loading