microscope.devices module

Classes for control of microscope components.

This module provides base classes for experiment control and data acquisition devices that can be served over Pyro. This means that each device may be served from a separate process, or even from a different PC.

class microscope.devices.AxisLimits(lower, upper)

Bases: tuple

lower

Alias for field number 0

upper

Alias for field number 1

class microscope.devices.Binning(h, v)

Bases: tuple

h

Alias for field number 0

v

Alias for field number 1

class microscope.devices.CameraDevice(**kwargs)[source]

Bases: microscope.devices.DataDevice

Adds functionality to DataDevice to support cameras.

Defines the interface for cameras. Applies a transform to acquired data in the processing step.

ALLOWED_TRANSFORMS = [(False, False, False), (False, False, True), (False, True, False), (False, True, True), (True, False, False), (True, False, True), (True, True, False), (True, True, True)]
_get_binning()[source]

Return a tuple of (horizontal, vertical)

_get_roi()[source]

Return the ROI as it is on hardware.

_get_sensor_shape()[source]

Return a tuple of (width, height) indicating shape in pixels.

_process_data(data)[source]

Apply self._transform to data.

_set_binning(binning)[source]

Set binning along both axes. Return True if successful.

_set_readout_transform(new_transform)[source]

Update readout transform and update resultant transform.

_set_roi(roi)[source]

Set the ROI on the hardware, return True if successful.

get_binning()[source]

Return a tuple of (horizontal, vertical) corrected for transform.

get_cycle_time()[source]

Return the cycle time, in seconds.

get_exposure_time()[source]

Return the current exposure time, in seconds.

get_meta_data()[source]

Return metadata.

get_roi()[source]

Return ROI as a rectangle (left, top, width, height).

Chosen this rectangle format as it completely defines the ROI without reference to the sensor geometry.

get_sensor_shape()[source]

Return a tuple of (width, height), corrected for transform.

get_sensor_temperature()[source]

Return the sensor temperature.

get_transform()[source]

Return the current transform without readout transform.

get_trigger_type()[source]

Return the current trigger mode.

One of
TRIGGER_AFTER, TRIGGER_BEFORE or TRIGGER_DURATION (bulb exposure.)
set_binning(binning)[source]

Set binning along both axes. Return True if successful.

set_exposure_time(value)[source]

Set the exposure time on the device.

Parameters:value – exposure time in seconds
set_readout_mode(description)[source]

Set the readout mode and _readout_transform.

set_roi(roi)[source]

Set the ROI according to the provided rectangle. ROI is a tuple (left, right, width, height) Return True if ROI set correctly, False otherwise.

set_transform(transform)[source]

Combine provided transform with readout transform.

soft_trigger()[source]

Optional software trigger - implement if available.

class microscope.devices.ControllerDevice(index=None)[source]

Bases: microscope.devices.Device

Device that controls multiple devices.

Controller devices usually control multiple stage devices, typically a XY and Z stage, a filterwheel, and a light source. Controller devices also include multi light source engines.

Each of the controlled devices requires a name. The choice of name and its documentation is left to the concrete class.

Initialising and shutting down a controller device must initialise and shutdown the controlled devices. Concrete classes should be careful to prevent that the shutdown of a controlled device does not shutdown the controller and the other controlled devices. This might require that controlled devices do nothing as part of their shutdown and initialisation.

devices

Map of names to the controlled devices.

initialize() → None[source]

Initialize the device.

class microscope.devices.DataDevice(buffer_length=0, **kwargs)[source]

Bases: microscope.devices.Device

A data capture device.

This class handles a thread to fetch data from a device and dispatch it to a client. The client is set using set_client(uri) or (legacy) receiveClient(uri).

Derived classed should implement::
  • abort(self) — required
  • _fetch_data(self) — required
  • _process_data(self, data) — optional

Derived classes may override __init__, enable and disable, but must ensure to call this class’s implementations as indicated in the docstrings.

_dispatch_loop()[source]

Process data and send results to any client.

_fetch_data()[source]

Poll for data and return it, with minimal processing.

If the device uses buffering in software, this function should copy the data from the buffer, release or recycle the buffer, then return a reference to the copy. Otherwise, if the SDK returns a data object that will not be written to again, this function can just return a reference to the object. If no data is available, return None.

_fetch_loop()[source]

Poll source for data and put it into dispatch buffer.

_process_data(data)[source]

Do any data processing and return data.

_put(data, timestamp)[source]

Put data and timestamp into dispatch buffer with target dispatch client.

_send_data(client, data, timestamp)[source]

Dispatch data to the client.

abort()[source]

Stop acquisition as soon as possible.

disable()[source]

Disable the data capture device.

Implement device-specific code in _on_disable .

enable()[source]

Enable the data capture device.

Ensures that a data handling threads are running. Implement device-specific code in _on_enable .

grab_next_data(soft_trigger=True)[source]

Returns results from next trigger via a direct call.

Parameters:soft_trigger – calls soft_trigger if True, waits for hardware trigger if False.
receiveClient(client_uri)[source]

A passthrough for compatibility.

receiveData(data, timestamp)[source]

Unblocks grab_next_frame so it can return.

set_client(new_client)[source]

Set up a connection to our client.

Clients now sit in a stack so that a single device may send different data to multiple clients in a single experiment. The usage is currently:

device.set_client(client) # Add client to top of stack
# do stuff, send triggers, receive data
device.set_client(None)   # Pop top client off stack.

There is a risk that some other client calls None before the current client is finished. Avoiding this will require rework here to identify the caller and remove only that caller from the client stack.

set_setting(*args, **kwargs)
update_settings(*args, **kwargs)[source]
class microscope.devices.DeformableMirror(**kwargs)[source]

Bases: microscope.devices.Device

Base class for Deformable Mirrors.

There is no method to reset or clear a deformable mirror. While different vendors provide functions to do that, it is unclear exactly what it does the actuators. Does it set all actuators back to something based on a calibration file? Does it apply a voltage of zero to each? Does it set the values to zero and what does that mean since different deformable mirrors expect values in a different range? For the sake of uniformity, it is better for python-microscope users to pass the pattern they want, probably a pattern that flattens the mirror.

It is also unclear what the use case for a reset. If it just to set the mirror to an initial state and not a specific shape, then destroying and re-constructing the DeformableMirror object provides the most obvious solution.

_validate_patterns(patterns: numpy.ndarray) → None[source]

Validate the shape of a series of patterns.

Only validates the shape of the patterns, not if the values are actually in the [0 1] range. If some hardware is unable to handle values outside their defined range (most will simply clip them), then it’s the responsability of the subclass to do the clipping before sending the values.

apply_pattern(pattern: numpy.ndarray) → None[source]

Apply this pattern.

initialize() → None[source]

Initialize the device.

n_actuators
next_pattern() → None[source]

Apply the next pattern in the queue.

A convenience fallback is provided.

queue_patterns(patterns: numpy.ndarray) → None[source]

Send values to the mirror.

Parameters:
  • patterns (numpy.array) – An KxN elements array of values in the range [0 1], where N equals the number of actuators, and K is the number of patterns.
  • convenience fallback is provided for software triggering is (A) –
  • provided.
class microscope.devices.Device(index=None)[source]

Bases: object

A base device class. All devices should subclass this class.

Parameters:index (int) – the index of the device on a shared library. This argument is added by the deviceserver.
_on_disable()[source]

Do any device-specific work on disable.

Subclasses should override this method, rather than modify disable(self).

_on_enable()[source]

Do any device-specific work on enable.

Subclasses should override this method, rather than modify enable(self).

_on_shutdown()[source]

Subclasses over-ride this with tasks to do on shutdown.

add_setting(name, dtype, get_func, set_func, values, readonly=False)[source]

Add a setting definition.

Parameters:
  • name – the setting’s name
  • dtype – a data type from (‘int’, ‘float’, ‘bool’, ‘enum’, ‘str’)
  • get_func – a function to get the current value
  • set_func – a function to set the value
  • values – a description of allowed values dependent on dtype, or function that returns a description
  • readonly – an optional flag to indicate a read-only setting.

A client needs some way of knowing a setting name and data type, retrieving the current value and, if settable, a way to retrieve allowable values, and set the value. We store this info in an OrderedDict. I considered having a Setting class with getter, setter, etc., and adding Setting instances as device attributes, but Pyro does not support dot notation to access the functions we need (e.g. Device.some_setting.set ), so I’d have to write access functions, anyway.

describe_setting(name)[source]

Return ordered setting descriptions as a list of dicts.

describe_settings()[source]

Return ordered setting descriptions as a list of dicts.

disable()[source]

Disable the device for a short period for inactivity.

enable()[source]

Enable the device.

get_all_settings()[source]

Return ordered settings as a list of dicts.

get_is_enabled()[source]
get_setting(name)[source]

Return the current value of a setting.

initialize()[source]

Initialize the device.

make_safe()[source]

Put the device into a safe state.

set_setting(name, value)[source]

Set a setting.

shutdown()[source]

Shutdown the device for a prolonged period of inactivity.

update_settings(incoming, init=False)[source]

Update settings based on dict of settings and values.

class microscope.devices.FilterWheelBase(filters: Union[Mapping[int, str], Iterable[T_co]] = [], positions: int = 0, **kwargs)[source]

Bases: microscope.devices.Device

get_filters() → List[Tuple[int, str]][source]
get_num_positions() → int[source]

Returns the number of wheel positions.

get_position() → int[source]

Return the wheel’s current position

set_position(position: int) → None[source]

Set the wheel position.

class microscope.devices.FloatingDeviceMixin[source]

Bases: object

A mixin for devices that ‘float’.

Some SDKs handling multiple devices do not allow for explicit selection of a specific device: instead, a device must be initialized and then queried to determine its ID. This class is a mixin which identifies a subclass as floating, and enforces the implementation of a ‘get_id’ method.

get_id()[source]

Return a unique hardware identifier, such as a serial number.

class microscope.devices.LaserDevice(**kwargs)[source]

Bases: microscope.devices.Device

_set_power_mw(mw)[source]

Set the power on the device in mW.

get_is_on()[source]

Return True if the laser is currently able to produce light.

get_max_power_mw()[source]

Return the max. power in mW.

get_min_power_mw()[source]

Return the min power in mW.

get_power_mw()[source]

“” Return the current power in mW.

get_set_power_mw()[source]

Return the power set point.

get_status()[source]

Query and return the laser status.

set_power_mw(mw)[source]

Set the power from an argument in mW and save the set point.

Parameters:mw (float) – Power in mW. Value will be clipped to the valid range for the laser. See the methods get_max_power_mw() and get_min_power_mw() to retrieve the valid range.
Returns:void
class microscope.devices.ROI(left, top, width, height)

Bases: tuple

height

Alias for field number 3

left

Alias for field number 0

top

Alias for field number 1

width

Alias for field number 2

class microscope.devices.SerialDeviceMixIn(**kwargs)[source]

Bases: object

MixIn for devices that are controlled via serial.

Currently handles the flushing and locking of the comms channel until a command has finished, and the passthrough to the serial channel.

TODO: add more logic to handle the code duplication of serial devices.

_readline()[source]

Read a line from connection without leading and trailing whitespace.

_write(command)[source]

Send a command to the device.

This is not a simple passthrough to serial.Serial.write, it will append b'\r\n' to command. Override this method if a device requires a specific format.

is_alive()[source]

Query if device is alive and we can send messages.

static lock_comms(func)[source]

Decorator to flush input buffer and lock communications.

There have been problems with the DeepStar lasers returning junk characters after the expected response, so it is advisable to flush the input buffer prior to running a command and subsequent readline. It also locks the comms channel so that a function must finish all its communications before another can run.

class microscope.devices.StageAxis[source]

Bases: object

A single dimension axis for a StageDevice.

A StageAxis represents a single axis of a stage and is not a Device instance on itself. Even stages with a single axis, such as Z-axis piezos, are implemented as a StageDevice composed of a single StageAxis instance.

The interface for StageAxis maps to that of StageDevice so refer to its documentation.

limits

Upper and lower limits values for position.

move_by(delta: float) → None[source]

Move axis by given amount.

move_to(pos: float) → None[source]

Move axis to specified position.

position

Current axis position.

class microscope.devices.StageDevice(index=None)[source]

Bases: microscope.devices.Device

A stage device, composed of StageAxis instances.

A stage device can have any number of axes and dimensions. For a single StageDevice instance each axis has a name that uniquely identifies it. The names of the individual axes are hardware dependent and will be part of the concrete class documentation. They are typically strings such as “x” or “y”.

stage = SomeStageDevice()
stage.initialize()
stage.enable() # may trigger a stage move

# move operations
stage.move_to({'x': 42.0, 'y': -5.1})
stage.move_by({'x': -5.3, 'y': 14.6})

# Individual StageAxis can be controlled directly.
x_axis = stage.axes['x']
y_axis = stage.axes['y']
x_axis.move_to(42.0)
y_axis.move_by(-5.3)

Not all stage devices support simultaneous move of multiple axes. Because of this, there is no guarantee that move operations with multiple axes are done simultaneously. Refer to the concrete class documentation for hardware specific details.

If a move operation involves multiple axes and there is no support for simultaneous move, the order of the moves is undefined. If a specific order is required, one can either call the move functions multiple times in the expected order, or do so via the individual axes, like so:

# Move the x axis first, then mvoe the y axis:
stage.move_by({'x': 10})
stage.move_by({'y': 4})

# The same thing but via the individual axes:
stage.axes['x'].move_by(10)
stage.axes['y'].move_by(4)

Move operations will not attempt to move a stage beyond its limits. If a call to the move functions would require the stage to move beyond its limits the move operation is clipped to the axes limits. No exception is raised.

# Moves x axis to the its upper limit:
x_axis.move_to(x_axis.limits.upper)

# The same as above since the move operations are clipped to
# the axes limits automatically.
import math
x_axis.move_to(math.inf)
x_axis.move_by(math.inf)

Some stages need to find a reference position, home, before being able to be moved. If required, this happens automatically during enable().

axes

Map of axis names to the corresponding StageAxis.

for name, axis in stage.axes.items():
    print(f'moving axis named {name}')
    axis.move_by(1)

If an axis is not available then it is not included, i.e., given a stage with optional axes the missing axes will not appear on the returned dict with a value of None or some other special StageAxis instance.

limits

Map of axis name to its upper and lower limits.

for name, limits in stage.limits.items():
    print(f'{name} axis lower limit is {limits.lower}')
    print(f'{name} axis upper limit is {limits.upper}')

These are the limits currently imposed by the device or underlying software and may change over the time of the StageDevice object.

The units of the limits is the same as the ones being currently used for the move operations.

move_by(delta: Mapping[str, float]) → None[source]

Move axes by the corresponding amounts.

Parameters:delta – map of axis name to the amount to be moved.
# Move 'x' axis by 10.2 units and the y axis by -5 units:
stage.move_by({'x': 10.2, 'y': -5})

# The above is equivalent, but possibly faster than:
stage.axes['x'].move_by(10.2)
stage.axes['y'].move_by(-5)

The axes will not move beyond limits(). If delta would move an axis beyond it limit, no exception is raised. Instead, the stage will move until the axis limit.

move_to(position: Mapping[str, float]) → None[source]

Move axes to the corresponding positions.

Parameters:position – map of axis name to the positions to move to.
# Move 'x' axis to position 8 and the y axis to position -5.3
stage.move_to({'x': 8, 'y': -5.3})

# The above is equivalent to
stage.axes['x'].move_to(8)
stage.axes['y'].move_to(-5.3)

The axes will not move beyond limits(). If positions is beyond the limits, no exception is raised. Instead, the stage will move until the axes limit.

position

Map of axis name to their current position.

for name, position in stage.position.items():
    print(f'{name} axis is at position {position}')

The units of the position is the same as the ones being currently used for the absolute move (move_to()) operations.

class microscope.devices.TriggerMode[source]

Bases: enum.Enum

An enumeration.

BULB = 2
ONCE = 1
START = 4
STROBE = 3
class microscope.devices.TriggerTargetMixIn[source]

Bases: object

MixIn for Device that may be the target of a hardware trigger.

TODO: need some way to retrieve the supported trigger types and
modes. We could require subclasses to define _trigger_types and _trigger_modes listing what is supported but would still not be enough since often not all trigger type and mode are supported.
set_trigger(ttype: microscope.devices.TriggerType, tmode: microscope.devices.TriggerMode) → None[source]

Set device for a specific trigger.

trigger_mode
trigger_type
class microscope.devices.TriggerType[source]

Bases: enum.Enum

An enumeration.

FALLING_EDGE = 2
PULSE = 3
RISING_EDGE = 1
SOFTWARE = 0
microscope.devices._call_if_callable(f)[source]

Call callables, or return value of non-callables.

microscope.devices.device(cls, host, port, conf={}, uid=None)[source]

Define a device and where to serve it.

A device definition for use in deviceserver config files.

Parameters:
  • cls (type) – type/class of device to serve.
  • host (str) – hostname or ip address serving the device.
  • port (int) – port number used to serve the device.
  • conf (dict) – keyword arguments to construct the device. The device is effectively constructed with cls(**conf).
  • uid (str) – used to identify “floating” devices (see documentation for FloatingDeviceMixin)
microscope.devices.keep_acquiring(func)[source]

Wrapper to preserve acquiring state of data capture devices.