Skip to content

Commit

Permalink
Merge pull request #5 from henrypinkard/main
Browse files Browse the repository at this point in the history
Many changes, including GH actions testing
  • Loading branch information
henrypinkard authored Jul 25, 2024
2 parents 3816150 + ab3e5b8 commit e10d226
Show file tree
Hide file tree
Showing 36 changed files with 1,193 additions and 709 deletions.
34 changes: 34 additions & 0 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Run Tests

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
test:
runs-on: windows-latest

strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12"]

steps:
- name: Checkout repo
uses: actions/checkout@v3

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
architecture: 'x64'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e .[test]
- name: Test with pytest
run: |
pytest -v -s
42 changes: 34 additions & 8 deletions adding_devices.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,19 @@ There are specialized device types that standardize functionalities through meth
Specilzed device types implement functionality through abstract methods that must be implemented by subclasses. For example:

```python
from exengine.kernel.device_types_base import Camera
from exengine.kernel.device_types_base import Detector


# TODO: may change this API in the future
class ANewCameraDevice(Camera):
class ANewCameraDevice(Detector):
def set_exposure(self, exposure: float) -> None:
# Implementation here

# Implementation here

def get_exposure(self) -> float:
# Implementation here
# Implementation here

# Implement other camera-specific methods...
# Implement other camera-specific methods...
```


Expand All @@ -83,12 +85,36 @@ Inheriting from the `Device` class or its subclasses provides two main benefits:

#### Bypassing the Executor

In some cases, you may have an attribute that doesn't interact with hardware and doesn't need to go through the executor. You can bypass the executor by appending `_noexec` to the attribute name. This will cause the attribute to be executed directly on the calling thread.
In some cases, you may have attributes or methods that don't interact with hardware and don't need to go through the executor. You can bypass the executor for specific attributes or for the entire device:

1. Specify attributes to bypass in the Device constructor:

```python
class MyNewDevice(Device):
def __init__(self, name):
super().__init__(name)
super().__init__(name, no_executor_attrs=('_some_internal_variable', 'some_method'))
# This will be executed on the calling thread like a normal attribute
self._some_internal_variable_noexec = 0
self._some_internal_variable = 0

def some_method(self):
# This method will be executed directly on the calling thread
pass
```

2. Bypass the executor for all attributes and methods:

```python
class MyNewDevice(Device):
def __init__(self, name):
super().__init__(name, no_executor=True)
# All attributes and methods in this class will bypass the executor
self._some_internal_variable = 0

def some_method(self):
# This method will be executed directly on the calling thread
pass
```

Using the first approach allows you to selectively bypass the executor for specific attributes or methods, while the second approach bypasses the executor for the entire device.
Note that when using no_executor_attrs, you need to specify the names of the attributes or methods as strings in a sequence (e.g., tuple or list) passed to the no_executor_attrs parameter in the super().__init__() call.
These approaches provide flexibility in controlling which parts of your device interact with the executor, allowing for optimization where direct access is safe and beneficial.
5 changes: 2 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@ classifiers = [
"Operating System :: OS Independent"
]
dependencies = [
"mmpycorex"
]
dynamic = ["version"]
description = "Microscopy execution engine with support for multiple hardware backends"
readme = "README.md"

[project.optional-dependencies]
micromanager = ["mmpycorex"]
minimal = [] # A minimal installation without any specific backends

test = ["pytest"] # New test dependencies group


[project.urls]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
Implementation of Micro-Manager device_implementations.py in terms of the AcqEng bottom API
"""

from exengine.kernel.device_types_base import (Camera, TriggerableSingleAxisPositioner, TriggerableDoubleAxisPositioner)
from kernel.device_types_base import Device
from exengine.kernel.device_types_base import (Detector, TriggerableSingleAxisPositioner, TriggerableDoubleAxisPositioner)
from exengine.kernel.device_types_base import Device
from mmpycorex import Core
import numpy as np
import pymmcore
Expand Down Expand Up @@ -200,7 +200,7 @@ def stop_position_sequence(self) -> None:
return self._core_noexec.stop_xy_stage_sequence(self._device_name_noexec)


class MicroManagerCamera(MicroManagerDevice, Camera):
class MicroManagerCamera(MicroManagerDevice, Detector):

def __init__(self, name=None):
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@
import time
import os
import itertools
from mmpycorex import create_core_instance, terminate_core_instances
from mmpycorex import create_core_instance, terminate_core_instances, get_default_install_location
from exengine.kernel.executor import ExecutionEngine
from exengine.kernel.data_handler import DataHandler
from exengine.kernel.data_coords import DataCoordinates
from backends.micromanager.mm_device_implementations import MicroManagerCamera
from exengine.storage_backends.NDTiffandRAM import NDRAMStorage
from exengine.events.camera_events import (StartCapture, ReadoutImages,
StartContinuousCapture, StopCapture)
from exengine.events.detector_events import (StartCapture, ReadoutData,
StartContinuousCapture, StopCapture)

@pytest.fixture(scope="module")
def setup_micromanager():
mm_install_dir = '/Users/henrypinkard/Micro-Manager'
mm_install_dir = get_default_install_location()
config_file = os.path.join(mm_install_dir, 'MMConfig_demo.cfg')
create_core_instance(mm_install_dir, config_file,
buffer_size_mb=1024, max_memory_mb=1024, # set these low for github actions
Expand All @@ -37,9 +37,9 @@ def capture_images(num_images, executor, camera):
data_handler = DataHandler(storage=storage)

start_capture_event = StartCapture(num_images=num_images, camera=camera)
readout_images_event = ReadoutImages(num_images=num_images, camera=camera,
image_coordinate_iterator=[DataCoordinates(time=t) for t in range(num_images)],
data_handler=data_handler)
readout_images_event = ReadoutData(num_images=num_images, camera=camera,
image_coordinate_iterator=[DataCoordinates(time=t) for t in range(num_images)],
data_handler=data_handler)

executor.submit([start_capture_event, readout_images_event])

Expand Down Expand Up @@ -71,9 +71,9 @@ def test_continuous_capture(executor, camera):
data_handler = DataHandler(storage=storage)

start_capture_event = StartContinuousCapture(camera=camera)
readout_images_event = ReadoutImages(camera=camera,
image_coordinate_iterator=(DataCoordinates(time=t) for t in itertools.count()),
data_handler=data_handler)
readout_images_event = ReadoutData(camera=camera,
image_coordinate_iterator=(DataCoordinates(time=t) for t in itertools.count()),
data_handler=data_handler)
stop_capture_event = StopCapture(camera=camera)

_, readout_future, _ = executor.submit([start_capture_event, readout_images_event, stop_capture_event])
Expand Down
10 changes: 5 additions & 5 deletions src/exengine/backends/micromanager/test/test_mm_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@
import pytest
from mmpycorex import Core
import os
from mmpycorex import create_core_instance
from mmpycorex import create_core_instance, terminate_core_instances, get_default_install_location
from exengine.kernel.executor import ExecutionEngine
from backends.micromanager.mm_device_implementations import MicroManagerDevice
from exengine.backends.micromanager.mm_device_implementations import MicroManagerDevice


@pytest.fixture(scope="module")
def setup_micromanager():
mm_install_dir = '/Users/henrypinkard/Micro-Manager'
mm_install_dir = get_default_install_location()
config_file = os.path.join(mm_install_dir, 'MMConfig_demo.cfg')
create_core_instance(mm_install_dir, config_file,
buffer_size_mb=1024, max_memory_mb=1024, # set these low for github actions
python_backend=True,
debug=False)
yield
# No specific teardown needed for start_headless
terminate_core_instances()

@pytest.fixture(scope="module")
def executor():
Expand All @@ -38,7 +38,7 @@ def device(core, executor):

def test_init(device):
assert isinstance(device, MicroManagerDevice)
assert device.name == "Camera"
assert device._name == "Camera"


def test_getattr_existing_property(device):
Expand Down
102 changes: 0 additions & 102 deletions src/exengine/events/camera_events.py

This file was deleted.

Loading

0 comments on commit e10d226

Please sign in to comment.