Skip to content

Commit

Permalink
finished futures/events/notifcations docs
Browse files Browse the repository at this point in the history
  • Loading branch information
henrypinkard committed Aug 21, 2024
1 parent 139be7f commit 4d3030e
Show file tree
Hide file tree
Showing 38 changed files with 733 additions and 316 deletions.
4 changes: 3 additions & 1 deletion docs/extending.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ Extending ExEngine
.. toctree::
:maxdepth: 2

extending/add_devices
extending/add_devices
extending/add_events
extending/add_notifications
4 changes: 2 additions & 2 deletions docs/extending/add_devices.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ All new devices should inherit from the ``Device`` base class or one or more of

.. code-block:: python
from exengine.kernel.device_types_base import Device
from exengine.base_classes import Device
class ANewDevice(Device):
def __init__(self, name):
Expand Down Expand Up @@ -64,7 +64,7 @@ Specialized device types implement functionality through abstract methods that m

.. code-block:: python
from exengine.kernel.device_types_base import Detector
from exengine.device_types import Detector
# TODO: may change this API in the future
class ANewCameraDevice(Detector):
Expand Down
89 changes: 89 additions & 0 deletions docs/extending/add_events.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
.. _add_events:

Creating Custom Events
=======================

Basic Event Creation
--------------------

To create a custom event:

1. Subclass ``ExecutorEvent``
2. Implement the ``execute()`` method

.. code-block:: python
from exengine.base_classes import ExecutorEvent
class MyCustomEvent(ExecutorEvent):
def execute(self):
# Main event logic goes here
result = self.perform_operation()
return result
def perform_operation(self):
# Implement your operation here
pass
Adding Notifications
--------------------

To add notifications:

1. Specify ``notification_types``
2. Use ``self.publish_notification()`` in ``execute()``

.. code-block:: python
from exengine.notifications import MyCustomNotification
class MyEventWithNotification(ExecutorEvent):
notification_types = [MyCustomNotification]
def execute(self):
# Event logic
self.publish_notification(MyCustomNotification(payload="Operation completed"))
Implementing Capabilities
-------------------------

Data Producing Capability
^^^^^^^^^^^^^^^^^^^^^^^^^

For events that produce data:

.. code-block:: python
from exengine.base_classes import ExecutorEvent, DataProducing
class MyDataProducingEvent(ExecutorEvent, DataProducing):
def execute(self):
data, metadata = self.generate_data()
self.put_data(data_coordinates, data, metadata)
def generate_data(self):
# Generate your data here
pass
Stoppable Capability
^^^^^^^^^^^^^^^^^^^^

For stoppable events:

.. code-block:: python
from exengine.base_classes import ExecutorEvent, Stoppable
class MyStoppableEvent(ExecutorEvent, Stoppable):
def execute(self):
while not self.is_stop_requested():
self.do_work()
self.cleanup()
def do_work(self):
# Implement your work here
pass
def cleanup(self):
# Cleanup logic here
pass
30 changes: 30 additions & 0 deletions docs/extending/add_notifications.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
.. _add_notifications:

Creating Custom Notifications
------------------------------

To create a custom notification:

1. Subclass ``exengine.base_classes.Notification``
2. Use Python's ``@dataclass`` decorator
3. Define ``category`` (from ``exengine.notifications.NotificationCategory`` enum) and ``description`` (string) as class variables
4. Optionally, specify a payload type using a type hint in the class inheritance. For example, ``class MyCustomNotification(Notification[str])`` indicates this notification's payload will be a string.

Keep payloads lightweight for efficient processing. Example:

.. code-block:: python
from dataclasses import dataclass
from exengine.base_classes import Notification
from exengine.notifications import NotificationCategory
@dataclass
class MyCustomNotification(Notification[str]):
category = NotificationCategory.Device
description = "A custom device status update"
# Usage
notification = MyCustomNotification(payload="Device XYZ is ready")
2 changes: 1 addition & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Key Features:

2. **Adaptable to Multiple Frontends**: Compatible with GUIs, scripts, networked automated labs, and AI-integrated microscopy

3. **Advanced Threading Capabilities**: Utilities for parallelization, asynchronous execution, and complex, multi-device workflows.
3. **Powerful Threading Capabilities**: Utilities for parallelization, asynchronous execution, and complex, multi-device workflows.

4. **Modality Agnostic**: Adaptable to diverse microscopy techniques thanks to general purpose design.

Expand Down
2 changes: 1 addition & 1 deletion docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ Usage

usage/installation
usage/key_concepts
usage/examples
usage/backends
10 changes: 10 additions & 0 deletions docs/usage/backends.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.. _backends:

========
Backends
========

.. toctree::
:maxdepth: 2

backends/micro-manager_backend
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.. _micro-manager_backend_example:
.. _micro-manager_backend:

##################################################################
Using ExEngine with Micro-Manager Backend
Micro-Manager Backend
##################################################################

Installation
Expand All @@ -25,11 +25,13 @@ Running the ExEngine

.. code-block:: python
# Micro-Manager backend-specific functions
from mmpycorex import create_core_instance, download_and_install_mm, terminate_core_instances
from exengine.kernel.executor import ExecutionEngine
from exengine import ExecutionEngine
from exengine.kernel.data_coords import DataCoordinates
from exengine.kernel.ex_event_base import DataHandler
from exengine.backends.micromanager.mm_device_implementations import MicroManagerCamera, MicroManagerSingleAxisStage
from exengine.backends.micromanager import MicroManagerCamera, MicroManagerSingleAxisStage
from exengine.storage_backends.NDTiffandRAM import NDRAMStorage
from exengine.events.detector_events import StartCapture, ReadoutData
Expand Down
9 changes: 9 additions & 0 deletions docs/usage/data.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.. _data:


=======
Data
=======


Data hand
80 changes: 57 additions & 23 deletions docs/usage/devices.rst
Original file line number Diff line number Diff line change
@@ -1,47 +1,81 @@
.. _devices:


#######
=======
Devices
#######
=======

Devices in ExEngine are software representations of hardware components in a microscope system. When possible, they provide a consistent way to interact with diverse equipment, abstracting away the complexities of individual hardware implementations. When not possible, devices can additionally expose specialized APIs specific to individual components.

ExEngine supports multiple **backends** - individual devices or libraries of devices (e.g., Micro-Manager). The method to create devices depends on the specific backend in use.
Devices in ExEngine represent hardware or software components. They provide:

- Standardized interfaces for common functionalities
- Thread-safe execution of commands
- Support for multiple backend implementations (i.e. physical hardware devices or libraries for the control of multiple devices)
- Flexibility to represent any grouping of related functionality

Here's a minimal example using the Micro-Manager backend:

While often used for microscopy hardware, ExEngine's device concept and its benefits are not limited to this domain. A device can represent physical hardware, virtual devices, or software services.



Using Devices
-------------
ExEngine exposes devices through specific backends.

For example, the Micro-Manager backend enables access to hardware devices controllable through Micro-Manager. (For installation and setup instructions, see :ref:`micro-manager_backend`).

.. code-block:: python
# (assuming MM backend already installed and initialized)
# load the micro-manager device for an objective lens switcher
objective = MicroManagerDevice("Objective")
# Set the objective in use
objective.Label = "Nikon 10X S Fluor"
Without further specialization, devices are free to have any method and property names. However, certain functionalities are standardized through device types:



Device Types
^^^^^^^^^^^^
Functionality can be grouped with certain device types:

For example, the Detector type, which has standardized methods like start, stop, arm, etc.

Here's a quick example of a Detector:

.. code-block:: python
from mmpycorex import create_core_instance
from exengine.kernel.executor import ExecutionEngine
from exengine.backends.micromanager.mm_device_implementations import MicroManagerSingleAxisStage
detector = MicroManagerCamera()
camera.arm(10) # this acquires 10 images
camera.start()
# Create the ExecutionEngine
executor = ExecutionEngine()
Events often take specific device types as parameters. This enables the re-use of events across multiple devices

# Initialize Micro-Manager core
create_core_instance(config_file='MMConfig_demo.cfg')
For example, the ReadoutData event takes a detector:

# Access Micro-Manager device
z_stage = MicroManagerSingleAxisStage()
.. code-block:: python
readout_event = ReadoutData(detector=camera, ...)
z_stage.set_position(1234.56)
Device Hierarchies
""""""""""""""""""
Devices in ExEngine exist in hierarchies. All devices must inherit from the exengine.Device base class. Further functionality can be standardized by inheriting from one or more specific device type classes. For example, ``MicroManagerSingleAxisStage`` is a subclass of ``exengine.SingleAxisPositioner``, which is itself a subclass of ``exengine.Device``.
Thread Safety
-------------
By default, all ExEngine devices are made thread-safe.

The advantage of this hierarchical structure is that it standardizes functionality, allowing code to be written for generic device types (e.g., a single axis positioner) that will work with many different device libraries. This approach enables a common API across different libraries of devices, similar to Micro-Manager's device abstraction layer but on a meta-level - spanning multiple device ecosystems rather than just devices within a single project. However, ExEngine's device system is designed to be adaptable. While adhering to the standardized interfaces offers the most benefits, users can still leverage many advantages of the system without implementing these specialized APIs.
This is done under the hood by intercepting and rerouting all device calls to common threads.

This can be turned off by setting the `no_executor` parameter to `True` when initializing a device:

.. code-block:: python
TODO: thread standardization features of devices (and how to turn off)
device = SomeDevice(no_executor=True)
TODO: calling functions on devices directly
TODO: link to guide to adding backends
TODO: transition to more complex with events
Adding New Device Types
-----------------------
For information on adding new device types, see :ref:`add_devices`.
Loading

0 comments on commit 4d3030e

Please sign in to comment.