diff --git a/src/numpydantic/interface/hdf5.py b/src/numpydantic/interface/hdf5.py index ec32b0e..656273d 100644 --- a/src/numpydantic/interface/hdf5.py +++ b/src/numpydantic/interface/hdf5.py @@ -121,6 +121,10 @@ class H5Proxy: else: obj[key, self.field] = value + def __len__(self) -> int: + """self.shape[0]""" + return self.shape[0] + def open(self, mode: str = "r") -> "h5py.Dataset": """ Return the opened :class:`h5py.Dataset` object diff --git a/src/numpydantic/interface/video.py b/src/numpydantic/interface/video.py index 4f2048d..0ac7c66 100644 --- a/src/numpydantic/interface/video.py +++ b/src/numpydantic/interface/video.py @@ -180,6 +180,10 @@ class VideoProxy: def __getattr__(self, item: str): return getattr(self.video, item) + def __len__(self) -> int: + """Number of frames in the video""" + return self.shape[0] + class VideoInterface(Interface): """ diff --git a/tests/fixtures.py b/tests/fixtures.py index 347152c..e74c9a6 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -8,6 +8,7 @@ import numpy as np import pytest from pydantic import BaseModel, Field import zarr +import cv2 from numpydantic.interface.hdf5 import H5ArrayPath from numpydantic.interface.zarr import ZarrArrayPath @@ -150,3 +151,34 @@ def zarr_array(tmp_output_dir_func) -> Path: array = zarr.open(str(file), mode="w", shape=(100, 100), chunks=(10, 10)) array[:] = 0 return file + + +@pytest.fixture(scope="function") +def avi_video(tmp_path) -> Callable[[Tuple[int, int], int, bool], Path]: + video_path = tmp_path / "test.avi" + + def _make_video(shape=(100, 50), frames=10, is_color=True) -> Path: + writer = cv2.VideoWriter( + str(video_path), + cv2.VideoWriter_fourcc(*"RGBA"), # raw video for testing purposes + 30, + (shape[1], shape[0]), + is_color, + ) + if is_color: + shape = (*shape, 3) + + for i in range(frames): + # make fresh array every time bc opencv eats them + array = np.zeros(shape, dtype=np.uint8) + if not is_color: + array[i, i] = i + else: + array[i, i, :] = i + writer.write(array) + writer.release() + return video_path + + yield _make_video + + video_path.unlink(missing_ok=True) diff --git a/tests/test_interface/conftest.py b/tests/test_interface/conftest.py index e3bf0f7..63bdc4a 100644 --- a/tests/test_interface/conftest.py +++ b/tests/test_interface/conftest.py @@ -1,10 +1,12 @@ import pytest +from typing import Tuple, Callable import numpy as np import dask.array as da import zarr +from pydantic import BaseModel -from numpydantic import interface +from numpydantic import interface, NDArray @pytest.fixture( @@ -17,6 +19,7 @@ from numpydantic import interface (zarr.ones((10, 10)), interface.ZarrInterface), ("zarr_nested_array", interface.ZarrInterface), ("zarr_array", interface.ZarrInterface), + ("avi_video", interface.VideoInterface), ], ids=[ "numpy_list", @@ -26,9 +29,10 @@ from numpydantic import interface "zarr_memory", "zarr_nested", "zarr_array", + "video", ], ) -def interface_type(request): +def interface_type(request) -> Tuple[NDArray, interface.Interface]: """ Test cases for each interface's ``check`` method - each input should match the provided interface and that interface only @@ -37,3 +41,20 @@ def interface_type(request): return (request.getfixturevalue(request.param[0]), request.param[1]) else: return request.param + + +@pytest.fixture() +def all_interfaces(interface_type) -> BaseModel: + """ + An instantiated version of each interface within a basemodel, + with the array in an `array` field + """ + array, interface = interface_type + if isinstance(array, Callable): + array = array() + + class MyModel(BaseModel): + array: NDArray + + instance = MyModel(array=array) + return instance diff --git a/tests/test_interface/test_dunder.py b/tests/test_interface/test_dunder.py new file mode 100644 index 0000000..60f42d8 --- /dev/null +++ b/tests/test_interface/test_dunder.py @@ -0,0 +1,10 @@ +""" +Tests for dunder methods on all interfaces +""" + + +def test_dunder_len(all_interfaces): + """ + Each interface or proxy type should support __len__ + """ + assert len(all_interfaces.array) == all_interfaces.array.shape[0] diff --git a/tests/test_interface/test_video.py b/tests/test_interface/test_video.py index bfd7f7d..d5ef1b3 100644 --- a/tests/test_interface/test_video.py +++ b/tests/test_interface/test_video.py @@ -2,8 +2,6 @@ Needs to be refactored to DRY, but works for now """ -import pdb - import numpy as np import pytest @@ -17,37 +15,6 @@ from numpydantic import dtype as dt from numpydantic.interface.video import VideoProxy -@pytest.fixture(scope="function") -def avi_video(tmp_path): - video_path = tmp_path / "test.avi" - - def _make_video(shape=(100, 50), frames=10, is_color=True) -> Path: - writer = cv2.VideoWriter( - str(video_path), - cv2.VideoWriter_fourcc(*"RGBA"), # raw video for testing purposes - 30, - (shape[1], shape[0]), - is_color, - ) - if is_color: - shape = (*shape, 3) - - for i in range(frames): - # make fresh array every time bc opencv eats them - array = np.zeros(shape, dtype=np.uint8) - if not is_color: - array[i, i] = i - else: - array[i, i, :] = i - writer.write(array) - writer.release() - return video_path - - yield _make_video - - video_path.unlink(missing_ok=True) - - @pytest.mark.parametrize("input_type", [str, Path]) def test_video_validation(avi_video, input_type): """Color videos should validate for normal uint8 shape specs"""