Skip to content

Commit

Permalink
restore tests. lol maybe it is not a great idea to have all the runti…
Browse files Browse the repository at this point in the history
…me classes be pydantic models
  • Loading branch information
sneakers-the-rat committed Dec 13, 2024
1 parent 57f1832 commit 5b86156
Show file tree
Hide file tree
Showing 12 changed files with 102 additions and 29 deletions.
4 changes: 2 additions & 2 deletions mio/devices/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,20 +68,20 @@ def fps(self, value: Union[int, float]) -> None:
"""

@property
@abstractmethod
def exposure(self) -> int:
"""
Returns:
int:
"""
raise NotImplementedError("exposure getter is not implemented")

@exposure.setter
@abstractmethod
def exposure(self, value: int) -> None:
"""
Args:
value (int): Value to set
"""
raise NotImplementedError("exposure setter is not implemented")

@property
@abstractmethod
Expand Down
12 changes: 7 additions & 5 deletions mio/devices/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
"""

from abc import abstractmethod
from typing import TYPE_CHECKING, Any, Union
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Optional, Union

from mio.models import MiniscopeConfig, MiniscopeIOModel, Pipeline, PipelineConfig
from mio.models import MiniscopeConfig, Pipeline, PipelineConfig

if TYPE_CHECKING:
from mio.models.pipeline import Sink, Source, Transform
Expand All @@ -21,15 +22,16 @@ class DeviceConfig(MiniscopeConfig):
pipeline: PipelineConfig = PipelineConfig()


class Device(MiniscopeIOModel):
@dataclass(kw_only=True)
class Device:
"""
Abstract base class for devices.
Currently a placeholder to allow room for expansion/renaming in the future
"""

pipeline: Pipeline
config: DeviceConfig
pipeline: Optional[Pipeline] = None
# config: Optional[DeviceConfig] = None

@abstractmethod
def init(self) -> None:
Expand Down
91 changes: 74 additions & 17 deletions mio/devices/wirefree.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
"""

import contextlib
from dataclasses import dataclass, field
from pathlib import Path
from typing import BinaryIO, Literal, Optional, Union, overload
from typing import Any, BinaryIO, Literal, Optional, Union, overload

import cv2
import numpy as np
Expand All @@ -15,14 +16,15 @@
from mio.exceptions import EndOfRecordingException, ReadHeaderException
from mio.models.data import Frame
from mio.models.sdcard import SDBufferHeader, SDConfig, SDLayout
from mio.types import ConfigSource, Resolution


class WireFreeConfig(DeviceConfig):
"""Configuration for wire free miniscope"""

pass


@dataclass(kw_only=True)
class WireFreeMiniscope(Miniscope, RecordingCameraMixin):
"""
I/O for data on an SD Card recorded with a WireFree Miniscope
Expand All @@ -39,13 +41,22 @@ class WireFreeMiniscope(Miniscope, RecordingCameraMixin):

drive: Path
"""The path to the SD card drive"""
config: WireFreeConfig
"""Configuration """
# layout:
# config: WireFreeConfig
# """Configuration """
layout: Union[SDLayout, ConfigSource] = "wirefree-sd-layout"
positions: dict[int, int] = field(default_factory=dict)
"""
A mapping between frame number and byte position in the video that makes for
faster seeking :)
As we read, we store the locations of each frame before reading it. Later,
we can assign to `frame` to seek back to those positions. Assigning to `frame`
works without caching position, but has to manually iterate through each frame.
"""

def __post_init__(self, drive: Union[str, Path], layout: SDLayout):
self.drive = drive
self.layout = layout
def __post_init__(self) -> None:
"""post-init create private vars"""
self.layout = SDLayout.from_any(self.layout)
self.logger = init_logger("WireFreeMiniscope")

# Private attributes used when the file reading context is entered
Expand All @@ -57,15 +68,7 @@ def __post_init__(self, drive: Union[str, Path], layout: SDLayout):
"""
n_pix x 1 array used to store pixels while reading buffers
"""
self.positions = {}
"""
A mapping between frame number and byte position in the video that makes for
faster seeking :)
As we read, we store the locations of each frame before reading it. Later,
we can assign to `frame` to seek back to those positions. Assigning to `frame`
works without caching position, but has to manually iterate through each frame.
"""


# --------------------------------------------------
# Properties
Expand All @@ -91,6 +94,13 @@ def config(self) -> SDConfig:

return self._config

@classmethod
def configure(cls, drive: Union[str, Path], config: WireFreeConfig) -> None:
"""
Configure a WireFreeMiniscope SD card for recording
"""
raise NotImplementedError()

@property
def position(self) -> Optional[int]:
"""
Expand Down Expand Up @@ -606,3 +616,50 @@ def check_valid(self) -> bool:
valid = True

return valid

# --------------------------------------------------
# ABC methods
# --------------------------------------------------

def init(self) -> None:
"""Do nothing"""
pass

def deinit(self) -> None:
"""Do nothing"""
pass

def start(self) -> None:
"""start pipeline"""
raise NotImplementedError()

def stop(self) -> None:
"""stop pipeline"""
raise NotImplementedError()

def join(self) -> None:
"""join pipeline"""
raise NotImplementedError()

@property
def excitation(self) -> float:
"""LED Excitation"""
raise NotImplementedError()

@property
def fps(self) -> int:
"""FPS"""
return self.config.fs

@property
def resolution(self) -> Resolution:
"""Resolution of recorded video"""
return Resolution(self.config.width, self.config.height)

def get(self, key:str) -> Any:
"""get a configuration value by its name"""
return getattr(self.config, key)

def set(self, key:str, value:Any) -> None:
"""set a configuration value"""
raise NotImplementedError()
17 changes: 16 additions & 1 deletion pdm.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 3 additions & 4 deletions tests/test_devices/test_wirefree.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

from mio.devices import WireFreeMiniscope
from mio.exceptions import EndOfRecordingException
from mio.formats import WireFreeSDLayout, WireFreeSDLayout_Battery
from mio.models.data import Frame
from mio.models.sdcard import SDBufferHeader
from mio.utils import hash_video, hash_file
Expand Down Expand Up @@ -98,7 +97,7 @@ def test_relative_path():
rel_path = abs_child.relative_to(abs_cwd)

assert not rel_path.is_absolute()
sdcard = WireFreeMiniscope(drive=rel_path, layout=WireFreeSDLayout)
sdcard = WireFreeMiniscope(drive=rel_path, layout="wirefree-sd-layout")

# check we can do something basic like read config
assert sdcard.config is not None
Expand All @@ -109,7 +108,7 @@ def test_relative_path():
# now try with an absolute path
abs_path = rel_path.resolve()
assert abs_path.is_absolute()
sdcard_abs = WireFreeMiniscope(drive=abs_path, layout=WireFreeSDLayout)
sdcard_abs = WireFreeMiniscope(drive=abs_path, layout="wirefree-sd-layout")
assert sdcard_abs.config is not None
assert sdcard_abs.drive.is_absolute()

Expand Down Expand Up @@ -141,7 +140,7 @@ def test_to_img(wirefree_battery, n_frames, hash, tmp_path):

assert out_hash == hash

sd = WireFreeMiniscope(out_file, WireFreeSDLayout_Battery)
sd = WireFreeMiniscope(drive=out_file, layout="wirefree-sd-layout")

# we should be able to read all the frames!
frames = []
Expand Down

0 comments on commit 5b86156

Please sign in to comment.