Skip to content

Device & Property objects#

pymmcore-plus offers two classes that provide a more object-oriented interface to common operations and queries performed on devices and their properties.

In the original CMMCore API, there are a lot of methods that accept a deviceLabel string as the first argument (and perhaps additional arguments) and query something about that device (e.g. getDeviceLibrary, getDeviceType, waitForDevice, etc...). In pymmcore-plus, the Device class acts as a "view" onto a specific device, and these methods are implemented as methods (that no longer require the deviceLabel argument), and the deviceLabel is passed to the constructor.

Similarly, there are many methods in the CMMCore API that require both a device label and a device property name, and modify that specific property (e.g. isPropertySequenceable, getProperty, isPropertyReadOnly, etc...). Here, the DeviceProperty class acts as a "view" onto a specific device property, with an object-oriented interface to these methods.

pymmcore_plus.DeviceAdapter #

Convenience view onto a device-adapter library.

This is the type of object that is returned by pymmcore_plus.CMMCorePlus.getAdapterObject

Parameters:

Name Type Description Default
library_name str

Device this property belongs to

required
mmcore CMMCorePlus

CMMCorePlus instance

required
Source code in pymmcore_plus/core/_adapter.py
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
class DeviceAdapter:
    """Convenience view onto a device-adapter library.

    This is the type of object that is returned by
    [`pymmcore_plus.CMMCorePlus.getAdapterObject`][]

    Parameters
    ----------
    library_name : str
        Device this property belongs to
    mmcore : CMMCorePlus
        CMMCorePlus instance
    """

    def __init__(self, library_name: str, mmcore: CMMCorePlus) -> None:
        self._name = library_name
        self._mmc = mmcore
        # self.propertyChanged = _DevicePropValueSignal(device_label, None, mmcore)

    @property
    def name(self) -> str:
        """Return the short name of this device adapter library."""
        return self._name

    @property
    def core(self) -> CMMCorePlus:
        """Return the `CMMCorePlus` instance to which this Device is bound."""
        return self._mmc

    @property
    def available_devices(self) -> tuple[Device, ...]:
        """Get available devices offered by this device adapter.

        Returns
        -------
        tuple[Device, ...]
            Tuple of `Device` objects, with the name, type, and description
            of each device.  These objects also have a `load` method that can be used
            to load the device under a given label.
        """
        try:
            devs = self._mmc.getAvailableDevices(self.name)
        except RuntimeError:
            return ()

        types = self._mmc.getAvailableDeviceTypes(self.name)
        descriptions = self._mmc.getAvailableDeviceDescriptions(self.name)
        return tuple(
            Device(
                mmcore=self._mmc,
                adapter_name=self.name,
                device_name=dev_name,
                type=DeviceType(dt),
                description=desc,
            )
            for dev_name, dt, desc in zip(devs, types, descriptions)
        )

    @property
    def loaded_devices(self) -> tuple[Device, ...]:
        """Get currently loaded devices controlled this adapter.

        Returns
        -------
        tuple[Device, ...]
            Tuple of loaded `Device` objects.
        """
        return tuple(self._mmc.iterDevices(device_adapter=self.name))

    def unload(self) -> None:
        """Forcefully unload this library."""
        self._mmc.unloadLibrary(self.name)

    def __repr__(self) -> str:
        """Return string representation of this adapter."""
        core = repr(self._mmc).strip("<>")
        try:
            ndevs = str(len(self._mmc.getAvailableDevices(self.name)))
        except Exception:
            ndevs = "ERR"
        return f"<Adapter {self.name!r} on {core}: {ndevs} devices>"

available_devices() -> tuple[Device, ...] property #

Get available devices offered by this device adapter.

Returns:

Type Description
tuple[Device, ...]

Tuple of Device objects, with the name, type, and description of each device. These objects also have a load method that can be used to load the device under a given label.

Source code in pymmcore_plus/core/_adapter.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@property
def available_devices(self) -> tuple[Device, ...]:
    """Get available devices offered by this device adapter.

    Returns
    -------
    tuple[Device, ...]
        Tuple of `Device` objects, with the name, type, and description
        of each device.  These objects also have a `load` method that can be used
        to load the device under a given label.
    """
    try:
        devs = self._mmc.getAvailableDevices(self.name)
    except RuntimeError:
        return ()

    types = self._mmc.getAvailableDeviceTypes(self.name)
    descriptions = self._mmc.getAvailableDeviceDescriptions(self.name)
    return tuple(
        Device(
            mmcore=self._mmc,
            adapter_name=self.name,
            device_name=dev_name,
            type=DeviceType(dt),
            description=desc,
        )
        for dev_name, dt, desc in zip(devs, types, descriptions)
    )

core() -> CMMCorePlus property #

Return the CMMCorePlus instance to which this Device is bound.

Source code in pymmcore_plus/core/_adapter.py
36
37
38
39
@property
def core(self) -> CMMCorePlus:
    """Return the `CMMCorePlus` instance to which this Device is bound."""
    return self._mmc

loaded_devices() -> tuple[Device, ...] property #

Get currently loaded devices controlled this adapter.

Returns:

Type Description
tuple[Device, ...]

Tuple of loaded Device objects.

Source code in pymmcore_plus/core/_adapter.py
70
71
72
73
74
75
76
77
78
79
@property
def loaded_devices(self) -> tuple[Device, ...]:
    """Get currently loaded devices controlled this adapter.

    Returns
    -------
    tuple[Device, ...]
        Tuple of loaded `Device` objects.
    """
    return tuple(self._mmc.iterDevices(device_adapter=self.name))

name() -> str property #

Return the short name of this device adapter library.

Source code in pymmcore_plus/core/_adapter.py
31
32
33
34
@property
def name(self) -> str:
    """Return the short name of this device adapter library."""
    return self._name

unload() -> None #

Forcefully unload this library.

Source code in pymmcore_plus/core/_adapter.py
81
82
83
def unload(self) -> None:
    """Forcefully unload this library."""
    self._mmc.unloadLibrary(self.name)

pymmcore_plus.Device #

Convenience view onto a device.

This is the type of object that is returned by pymmcore_plus.CMMCorePlus.getDeviceObject

Parameters:

Name Type Description Default
device_label str

Device this property belongs to

UNASIGNED
mmcore CMMCorePlus

CMMCorePlus instance

None

Examples:

>>> core = CMMCorePlus()
>>> device = Device("Camera", core)
>>> device.isLoaded()
>>> device.load("NotALib", "DCam")  # useful error
>>> device.load("DemoCamera", "DCam")
>>> device.initialize()
>>> device.load("DemoCamera", "DCam")  # no-op w/ useful warning
>>> device.properties  # tuple of DeviceProperty objects
>>> device.description()
>>> device.isBusy()
>>> device.wait()
>>> device.type()
>>> device.schema()  # JSON schema of device properties
Source code in pymmcore_plus/core/_device.py
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
class Device:
    """Convenience view onto a device.

    This is the type of object that is returned by
    [`pymmcore_plus.CMMCorePlus.getDeviceObject`][]

    Parameters
    ----------
    device_label : str
        Device this property belongs to
    mmcore : CMMCorePlus
        CMMCorePlus instance

    Examples
    --------
    >>> core = CMMCorePlus()
    >>> device = Device("Camera", core)
    >>> device.isLoaded()
    >>> device.load("NotALib", "DCam")  # useful error
    >>> device.load("DemoCamera", "DCam")
    >>> device.initialize()
    >>> device.load("DemoCamera", "DCam")  # no-op w/ useful warning
    >>> device.properties  # tuple of DeviceProperty objects
    >>> device.description()
    >>> device.isBusy()
    >>> device.wait()
    >>> device.type()
    >>> device.schema()  # JSON schema of device properties
    """

    UNASIGNED = "__UNASIGNED__"
    propertyChanged: PSignalInstance

    def __init__(
        self,
        device_label: str = UNASIGNED,
        mmcore: CMMCorePlus | None = None,
        adapter_name: str = "",
        device_name: str = "",
        type: DeviceType = DeviceType.UnknownType,
        description: str = "",
    ) -> None:
        if mmcore is None:
            from ._mmcore_plus import CMMCorePlus

            self._mmc = CMMCorePlus.instance()
        else:
            self._mmc = mmcore

        self._label = device_label
        self._adapter_name = adapter_name
        self._device_name = device_name
        self._type = type
        self._description = description
        self.propertyChanged = _DevicePropValueSignal(device_label, None, self._mmc)

    @property
    def label(self) -> str:
        """Return the assigned label of this device."""
        return self._label

    @label.setter
    def label(self, value: str) -> None:
        if self.isLoaded():
            raise RuntimeError("Cannot change label of loaded device")
        self._label = value

    @property
    def core(self) -> CMMCorePlus:
        """Return the `CMMCorePlus` instance to which this Device is bound."""
        return self._mmc

    def isBusy(self) -> bool:
        """Return busy status for this device."""
        return self._mmc.deviceBusy(self.label)

    def delayMs(self) -> float:
        """Return action delay in ms for this device."""
        return self._mmc.getDeviceDelayMs(self.label)

    def setDelayMs(self, delayMs: float) -> None:
        """Override the built-in value for the action delay."""
        self._mmc.setDeviceDelayMs(self.label, delayMs)

    def usesDelay(self) -> bool:
        """Return `True` if the device will use the delay setting or not."""
        return self._mmc.usesDeviceDelay(self.label)

    def description(self) -> str:
        """Return device description."""
        return self._description or self._mmc.getDeviceDescription(self.label)

    def library(self) -> str:
        """Return device library (aka module, device adapter) name."""
        return self._adapter_name or self._mmc.getDeviceLibrary(self.label)

    def name(self) -> str:
        """Return the device name (this is not the same as the assigned label)."""
        return self._device_name or self._mmc.getDeviceName(self.label)

    def propertyNames(self) -> tuple[str, ...]:
        """Return all property names supported by this device."""
        return self._mmc.getDevicePropertyNames(self.label)

    @property
    def properties(self) -> tuple[DeviceProperty, ...]:
        """Get all properties supported by device as DeviceProperty objects."""
        return tuple(
            DeviceProperty(self.label, name, self._mmc) for name in self.propertyNames()
        )

    def getPropertyObject(self, property_name: str) -> DeviceProperty:
        """Return a `DeviceProperty` object bound to this device on this core."""
        return DeviceProperty(self.label, property_name, self._mmc)

    def initialize(self) -> None:
        """Initialize device."""
        return self._mmc.initializeDevice(self.label)

    def load(
        self,
        adapter_name: str = "",
        device_name: str = "",
        device_label: str = "",
    ) -> None:
        """Load device from the plugin library.

        Parameters
        ----------
        adapter_name : str
            The name of the device adapter module (short name, not full file name).
            (This is what is returned by `Device.library()`). Must be specified if
            `adapter_name` was not provided to the `Device` constructor.
        device_name : str
            The name of the device. The name must correspond to one of the names
            recognized by the specific plugin library. (This is what is returned by
            `Device.name()`). Must be specified if `device_name` was not provided to
            the `Device` constructor.
        device_label : str
            The name to assign to the device. If not specified, the device will be
            assigned a default name: `adapter_name-device_name`, unless this Device
            instance was initialized with a label.
        """
        if not (adapter_name := adapter_name or self._adapter_name):
            raise TypeError("Must specify adapter_name")
        if not (device_name := device_name or self._device_name):
            raise TypeError("Must specify device_name")
        if device_label:
            self.label = device_label
        elif self.label == self.UNASIGNED:
            self.label = f"{adapter_name}-{device_name}"

        self._mmc.loadDevice(self.label, adapter_name, device_name)

    def unload(self) -> None:
        """Unload device from the core and adjust all configuration data."""
        return self._mmc.unloadDevice(self.label)

    def isLoaded(self) -> bool:
        """Return `True` if device is loaded."""
        return self.label in self._mmc.getLoadedDevices()

    def detect(self) -> DeviceDetectionStatus:
        """Tries to communicate to device through a given serial port.

        Used to automate discovery of correct serial port. Also configures the
        serial port correctly.
        """
        return self._mmc.detectDevice(self.label)

    def supportsDetection(self) -> bool:
        """Return whether or not the device supports automatic device detection.

        (i.e. whether or not detectDevice() may be safely called).
        """
        try:
            return self._mmc.supportsDeviceDetection(self.label)
        except RuntimeError:
            return False  # e.g. core devices

    def type(self) -> DeviceType:
        """Return device type."""
        return self._type or self._mmc.getDeviceType(self.label)

    def schema(self) -> DeviceSchema:
        """Return dict in JSON-schema format for properties of `device_label`."""
        return self._mmc.getDeviceSchema(self.label)

    def wait(self) -> None:
        """Block the calling thread until device becomes non-busy."""
        self._mmc.waitForDevice(self.label)

    def __repr__(self) -> str:
        if self.isLoaded():
            n = len(self.propertyNames())
            props = f'{n} {"properties" if n>1 else "property"}'
            lib = f"({self.library()}::{self.name()}) "
        else:
            props = "NOT LOADED"
            lib = ""
        core = repr(self._mmc).strip("<>")
        return f"<Device {self.label!r} {lib}on {core}: {props}>"

UNASIGNED = '__UNASIGNED__' class-attribute #

propertyChanged = _DevicePropValueSignal(device_label, None, self._mmc) instance-attribute #

core() -> CMMCorePlus property #

Return the CMMCorePlus instance to which this Device is bound.

Source code in pymmcore_plus/core/_device.py
83
84
85
86
@property
def core(self) -> CMMCorePlus:
    """Return the `CMMCorePlus` instance to which this Device is bound."""
    return self._mmc

delayMs() -> float #

Return action delay in ms for this device.

Source code in pymmcore_plus/core/_device.py
92
93
94
def delayMs(self) -> float:
    """Return action delay in ms for this device."""
    return self._mmc.getDeviceDelayMs(self.label)

description() -> str #

Return device description.

Source code in pymmcore_plus/core/_device.py
104
105
106
def description(self) -> str:
    """Return device description."""
    return self._description or self._mmc.getDeviceDescription(self.label)

detect() -> DeviceDetectionStatus #

Tries to communicate to device through a given serial port.

Used to automate discovery of correct serial port. Also configures the serial port correctly.

Source code in pymmcore_plus/core/_device.py
178
179
180
181
182
183
184
def detect(self) -> DeviceDetectionStatus:
    """Tries to communicate to device through a given serial port.

    Used to automate discovery of correct serial port. Also configures the
    serial port correctly.
    """
    return self._mmc.detectDevice(self.label)

getPropertyObject(property_name: str) -> DeviceProperty #

Return a DeviceProperty object bound to this device on this core.

Source code in pymmcore_plus/core/_device.py
127
128
129
def getPropertyObject(self, property_name: str) -> DeviceProperty:
    """Return a `DeviceProperty` object bound to this device on this core."""
    return DeviceProperty(self.label, property_name, self._mmc)

initialize() -> None #

Initialize device.

Source code in pymmcore_plus/core/_device.py
131
132
133
def initialize(self) -> None:
    """Initialize device."""
    return self._mmc.initializeDevice(self.label)

isBusy() -> bool #

Return busy status for this device.

Source code in pymmcore_plus/core/_device.py
88
89
90
def isBusy(self) -> bool:
    """Return busy status for this device."""
    return self._mmc.deviceBusy(self.label)

isLoaded() -> bool #

Return True if device is loaded.

Source code in pymmcore_plus/core/_device.py
174
175
176
def isLoaded(self) -> bool:
    """Return `True` if device is loaded."""
    return self.label in self._mmc.getLoadedDevices()

label() -> str property writable #

Return the assigned label of this device.

Source code in pymmcore_plus/core/_device.py
72
73
74
75
@property
def label(self) -> str:
    """Return the assigned label of this device."""
    return self._label

library() -> str #

Return device library (aka module, device adapter) name.

Source code in pymmcore_plus/core/_device.py
108
109
110
def library(self) -> str:
    """Return device library (aka module, device adapter) name."""
    return self._adapter_name or self._mmc.getDeviceLibrary(self.label)

load(adapter_name: str = '', device_name: str = '', device_label: str = '') -> None #

Load device from the plugin library.

Parameters:

Name Type Description Default
adapter_name str

The name of the device adapter module (short name, not full file name). (This is what is returned by Device.library()). Must be specified if adapter_name was not provided to the Device constructor.

''
device_name str

The name of the device. The name must correspond to one of the names recognized by the specific plugin library. (This is what is returned by Device.name()). Must be specified if device_name was not provided to the Device constructor.

''
device_label str

The name to assign to the device. If not specified, the device will be assigned a default name: adapter_name-device_name, unless this Device instance was initialized with a label.

''
Source code in pymmcore_plus/core/_device.py
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
def load(
    self,
    adapter_name: str = "",
    device_name: str = "",
    device_label: str = "",
) -> None:
    """Load device from the plugin library.

    Parameters
    ----------
    adapter_name : str
        The name of the device adapter module (short name, not full file name).
        (This is what is returned by `Device.library()`). Must be specified if
        `adapter_name` was not provided to the `Device` constructor.
    device_name : str
        The name of the device. The name must correspond to one of the names
        recognized by the specific plugin library. (This is what is returned by
        `Device.name()`). Must be specified if `device_name` was not provided to
        the `Device` constructor.
    device_label : str
        The name to assign to the device. If not specified, the device will be
        assigned a default name: `adapter_name-device_name`, unless this Device
        instance was initialized with a label.
    """
    if not (adapter_name := adapter_name or self._adapter_name):
        raise TypeError("Must specify adapter_name")
    if not (device_name := device_name or self._device_name):
        raise TypeError("Must specify device_name")
    if device_label:
        self.label = device_label
    elif self.label == self.UNASIGNED:
        self.label = f"{adapter_name}-{device_name}"

    self._mmc.loadDevice(self.label, adapter_name, device_name)

name() -> str #

Return the device name (this is not the same as the assigned label).

Source code in pymmcore_plus/core/_device.py
112
113
114
def name(self) -> str:
    """Return the device name (this is not the same as the assigned label)."""
    return self._device_name or self._mmc.getDeviceName(self.label)

properties() -> tuple[DeviceProperty, ...] property #

Get all properties supported by device as DeviceProperty objects.

Source code in pymmcore_plus/core/_device.py
120
121
122
123
124
125
@property
def properties(self) -> tuple[DeviceProperty, ...]:
    """Get all properties supported by device as DeviceProperty objects."""
    return tuple(
        DeviceProperty(self.label, name, self._mmc) for name in self.propertyNames()
    )

propertyNames() -> tuple[str, ...] #

Return all property names supported by this device.

Source code in pymmcore_plus/core/_device.py
116
117
118
def propertyNames(self) -> tuple[str, ...]:
    """Return all property names supported by this device."""
    return self._mmc.getDevicePropertyNames(self.label)

schema() -> DeviceSchema #

Return dict in JSON-schema format for properties of device_label.

Source code in pymmcore_plus/core/_device.py
200
201
202
def schema(self) -> DeviceSchema:
    """Return dict in JSON-schema format for properties of `device_label`."""
    return self._mmc.getDeviceSchema(self.label)

setDelayMs(delayMs: float) -> None #

Override the built-in value for the action delay.

Source code in pymmcore_plus/core/_device.py
96
97
98
def setDelayMs(self, delayMs: float) -> None:
    """Override the built-in value for the action delay."""
    self._mmc.setDeviceDelayMs(self.label, delayMs)

supportsDetection() -> bool #

Return whether or not the device supports automatic device detection.

(i.e. whether or not detectDevice() may be safely called).

Source code in pymmcore_plus/core/_device.py
186
187
188
189
190
191
192
193
194
def supportsDetection(self) -> bool:
    """Return whether or not the device supports automatic device detection.

    (i.e. whether or not detectDevice() may be safely called).
    """
    try:
        return self._mmc.supportsDeviceDetection(self.label)
    except RuntimeError:
        return False  # e.g. core devices

type() -> DeviceType #

Return device type.

Source code in pymmcore_plus/core/_device.py
196
197
198
def type(self) -> DeviceType:
    """Return device type."""
    return self._type or self._mmc.getDeviceType(self.label)

unload() -> None #

Unload device from the core and adjust all configuration data.

Source code in pymmcore_plus/core/_device.py
170
171
172
def unload(self) -> None:
    """Unload device from the core and adjust all configuration data."""
    return self._mmc.unloadDevice(self.label)

usesDelay() -> bool #

Return True if the device will use the delay setting or not.

Source code in pymmcore_plus/core/_device.py
100
101
102
def usesDelay(self) -> bool:
    """Return `True` if the device will use the delay setting or not."""
    return self._mmc.usesDeviceDelay(self.label)

wait() -> None #

Block the calling thread until device becomes non-busy.

Source code in pymmcore_plus/core/_device.py
204
205
206
def wait(self) -> None:
    """Block the calling thread until device becomes non-busy."""
    self._mmc.waitForDevice(self.label)

pymmcore_plus.DeviceProperty #

Convenience view onto a device property.

This is the type of object that is returned by pymmcore_plus.CMMCorePlus.getPropertyObject

Parameters:

Name Type Description Default
device_label str

Device this property belongs to

required
property_name str

Name of this property

required
mmcore CMMCorePlus

CMMCorePlus instance

required

Examples:

>>> core = CMMCorePlus()
>>> prop = DeviceProperty("Objective", "Label", core)
>>> prop.isValid()  # points to a loaded device property in core
>>> prop.value
>>> prop.value = "Objective-2"  # setter
>>> prop.isReadOnly()
>>> prop.hasLimits()
>>> prop.range()
>>> prop.dict()  # all the info in one dict.
Source code in pymmcore_plus/core/_property.py
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
class DeviceProperty:
    """Convenience view onto a device property.

    This is the type of object that is returned by
    [`pymmcore_plus.CMMCorePlus.getPropertyObject`][]

    Parameters
    ----------
    device_label : str
        Device this property belongs to
    property_name : str
        Name of this property
    mmcore : CMMCorePlus
        CMMCorePlus instance

    Examples
    --------
    >>> core = CMMCorePlus()
    >>> prop = DeviceProperty("Objective", "Label", core)
    >>> prop.isValid()  # points to a loaded device property in core
    >>> prop.value
    >>> prop.value = "Objective-2"  # setter
    >>> prop.isReadOnly()
    >>> prop.hasLimits()
    >>> prop.range()
    >>> prop.dict()  # all the info in one dict.
    """

    def __init__(
        self, device_label: str, property_name: str, mmcore: CMMCorePlus
    ) -> None:
        self.device = device_label
        self.name = property_name
        self._mmc = mmcore

    @cached_property
    def valueChanged(self) -> _DevicePropValueSignal:
        return _DevicePropValueSignal(self.device, self.name, self._mmc)

    def isValid(self) -> bool:
        """Return `True` if device is loaded and has a property by this name."""
        return self.isLoaded() and self._mmc.hasProperty(self.device, self.name)

    def isLoaded(self) -> bool:
        """Return true if the device name is loaded."""
        return self._mmc is not None and self.device in self._mmc.getLoadedDevices()

    @property
    def core(self) -> CMMCorePlus:
        """Return the `CMMCorePlus` instance to which this Property is bound."""
        return self._mmc

    @property
    def value(self) -> Any:
        """Return current property value, cast to appropriate type if applicable."""
        v = self._mmc.getProperty(self.device, self.name)
        if type_ := self.type().to_python():
            v = type_(v)
        return v

    @value.setter
    def value(self, val: Any) -> None:
        """Set current property value."""
        self.setValue(val)

    def fromCache(self) -> Any:
        """Return cached property value."""
        return self._mmc.getPropertyFromCache(self.device, self.name)

    def setValue(self, val: Any) -> None:
        """Functional alternate to property setter."""
        if self.isReadOnly():
            import warnings

            warnings.warn(
                f"'{self.device}::{self.name}' is a read-only property.", stacklevel=2
            )
        try:
            self._mmc.setProperty(self.device, self.name, val)
        except RuntimeError as e:
            msg = str(e)
            if allowed := self.allowedValues():
                msg += f". Allowed values: {allowed}"
            raise RuntimeError(msg) from None

    def isReadOnly(self) -> bool:
        """Return `True` if property is read only."""
        return self._mmc.isPropertyReadOnly(self.device, self.name)

    def isPreInit(self) -> bool:
        """Return `True` if property must be defined prior to initialization."""
        return self._mmc.isPropertyPreInit(self.device, self.name)

    def hasLimits(self) -> bool:
        """Return `True` if property has limits."""
        return self._mmc.hasPropertyLimits(self.device, self.name)

    def lowerLimit(self) -> float:
        """Return lower limit if property has limits, or 0 otherwise."""
        return self._mmc.getPropertyLowerLimit(self.device, self.name)

    def upperLimit(self) -> float:
        """Return upper limit if property has limits, or 0 otherwise."""
        return self._mmc.getPropertyUpperLimit(self.device, self.name)

    def range(self) -> tuple[float, float]:
        """Return (lowerLimit, upperLimit) range tuple."""
        return (self.lowerLimit(), self.upperLimit())

    def type(self) -> PropertyType:
        """Return `PropertyType` of this property."""
        return self._mmc.getPropertyType(self.device, self.name)

    def deviceType(self) -> DeviceType:
        """Return `DeviceType` of the device owning this property."""
        return self._mmc.getDeviceType(self.device)

    def allowedValues(self) -> tuple[str, ...]:
        """Return allowed values for this property, if contstrained."""
        # https://github.com/micro-manager/mmCoreAndDevices/issues/172
        allowed = self._mmc.getAllowedPropertyValues(self.device, self.name)
        if not allowed and self.deviceType() is DeviceType.StateDevice:
            if self.name == g_Keyword_State:
                n_states = self._mmc.getNumberOfStates(self.device)
                allowed = tuple(str(i) for i in range(n_states))
            elif self.name == g_Keyword_Label:
                allowed = self._mmc.getStateLabels(self.device)
        return allowed

    def isSequenceable(self) -> bool:
        """Return `True` if property can be used in a sequence."""
        return self._mmc.isPropertySequenceable(self.device, self.name)

    def sequenceMaxLength(self) -> int:
        """Return maximum number of property events that can be put in a sequence."""
        return self._mmc.getPropertySequenceMaxLength(self.device, self.name)

    def loadSequence(self, eventSequence: Sequence[str]) -> None:
        """Transfer a sequence of events/states/whatever to the device.

        Parameters
        ----------
        eventSequence : Sequence[str]
            The sequence of events/states that the device will execute in response
            to external triggers
        """
        self._mmc.loadPropertySequence(self.device, self.name, eventSequence)

    def startSequence(self) -> None:
        """Start an ongoing sequence of triggered events in a property."""
        self._mmc.startPropertySequence(self.device, self.name)

    def stopSequence(self) -> None:
        """Stop an ongoing sequence of triggered events in a property."""
        self._mmc.stopPropertySequence(self.device, self.name)

    def dict(self) -> InfoDict:
        """Return dict of info about this Property.

        Returns an [`InfoDict`][pymmcore_plus.core._property.InfoDict] with the
        following keys: `"valid", "value", "type", "device_type", "read_only",
        "pre_init", "range", "allowed"`.

        If the device is invalid or not loaded, the `"valid"` key will be `False`
        and the rest of the keys will be `None`.
        """
        if self.isValid():
            return {
                "valid": True,
                "value": self.value,
                "type": self.type().to_json(),
                "device_type": self.deviceType().name,
                "read_only": self.isReadOnly(),
                "sequenceable": self.isSequenceable(),
                "sequence_max_length": (
                    self.sequenceMaxLength() if self.isSequenceable() else None
                ),
                "pre_init": self.isPreInit(),
                "range": self.range() if self.hasLimits() else None,
                "allowed_values": self.allowedValues(),
            }
        else:
            return {
                "valid": False,
                "value": None,
                "type": None,
                "device_type": None,
                "read_only": None,
                "sequenceable": None,
                "sequence_max_length": None,
                "pre_init": None,
                "range": None,
                "allowed_values": None,
            }

    InfoDict = InfoDict

    def __repr__(self) -> str:
        v = f"value={self.value!r}" if self.isValid() else "INVALID"
        core = repr(self._mmc).strip("<>")
        return f"<Property '{self.device}::{self.name}' on {core}: {v}>"

InfoDict = InfoDict class-attribute #

device = device_label instance-attribute #

name = property_name instance-attribute #

allowedValues() -> tuple[str, ...] #

Return allowed values for this property, if contstrained.

Source code in pymmcore_plus/core/_property.py
145
146
147
148
149
150
151
152
153
154
155
def allowedValues(self) -> tuple[str, ...]:
    """Return allowed values for this property, if contstrained."""
    # https://github.com/micro-manager/mmCoreAndDevices/issues/172
    allowed = self._mmc.getAllowedPropertyValues(self.device, self.name)
    if not allowed and self.deviceType() is DeviceType.StateDevice:
        if self.name == g_Keyword_State:
            n_states = self._mmc.getNumberOfStates(self.device)
            allowed = tuple(str(i) for i in range(n_states))
        elif self.name == g_Keyword_Label:
            allowed = self._mmc.getStateLabels(self.device)
    return allowed

core() -> CMMCorePlus property #

Return the CMMCorePlus instance to which this Property is bound.

Source code in pymmcore_plus/core/_property.py
75
76
77
78
@property
def core(self) -> CMMCorePlus:
    """Return the `CMMCorePlus` instance to which this Property is bound."""
    return self._mmc

deviceType() -> DeviceType #

Return DeviceType of the device owning this property.

Source code in pymmcore_plus/core/_property.py
141
142
143
def deviceType(self) -> DeviceType:
    """Return `DeviceType` of the device owning this property."""
    return self._mmc.getDeviceType(self.device)

dict() -> InfoDict #

Return dict of info about this Property.

Returns an InfoDict with the following keys: "valid", "value", "type", "device_type", "read_only", "pre_init", "range", "allowed".

If the device is invalid or not loaded, the "valid" key will be False and the rest of the keys will be None.

Source code in pymmcore_plus/core/_property.py
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
def dict(self) -> InfoDict:
    """Return dict of info about this Property.

    Returns an [`InfoDict`][pymmcore_plus.core._property.InfoDict] with the
    following keys: `"valid", "value", "type", "device_type", "read_only",
    "pre_init", "range", "allowed"`.

    If the device is invalid or not loaded, the `"valid"` key will be `False`
    and the rest of the keys will be `None`.
    """
    if self.isValid():
        return {
            "valid": True,
            "value": self.value,
            "type": self.type().to_json(),
            "device_type": self.deviceType().name,
            "read_only": self.isReadOnly(),
            "sequenceable": self.isSequenceable(),
            "sequence_max_length": (
                self.sequenceMaxLength() if self.isSequenceable() else None
            ),
            "pre_init": self.isPreInit(),
            "range": self.range() if self.hasLimits() else None,
            "allowed_values": self.allowedValues(),
        }
    else:
        return {
            "valid": False,
            "value": None,
            "type": None,
            "device_type": None,
            "read_only": None,
            "sequenceable": None,
            "sequence_max_length": None,
            "pre_init": None,
            "range": None,
            "allowed_values": None,
        }

fromCache() -> Any #

Return cached property value.

Source code in pymmcore_plus/core/_property.py
93
94
95
def fromCache(self) -> Any:
    """Return cached property value."""
    return self._mmc.getPropertyFromCache(self.device, self.name)

hasLimits() -> bool #

Return True if property has limits.

Source code in pymmcore_plus/core/_property.py
121
122
123
def hasLimits(self) -> bool:
    """Return `True` if property has limits."""
    return self._mmc.hasPropertyLimits(self.device, self.name)

isLoaded() -> bool #

Return true if the device name is loaded.

Source code in pymmcore_plus/core/_property.py
71
72
73
def isLoaded(self) -> bool:
    """Return true if the device name is loaded."""
    return self._mmc is not None and self.device in self._mmc.getLoadedDevices()

isPreInit() -> bool #

Return True if property must be defined prior to initialization.

Source code in pymmcore_plus/core/_property.py
117
118
119
def isPreInit(self) -> bool:
    """Return `True` if property must be defined prior to initialization."""
    return self._mmc.isPropertyPreInit(self.device, self.name)

isReadOnly() -> bool #

Return True if property is read only.

Source code in pymmcore_plus/core/_property.py
113
114
115
def isReadOnly(self) -> bool:
    """Return `True` if property is read only."""
    return self._mmc.isPropertyReadOnly(self.device, self.name)

isSequenceable() -> bool #

Return True if property can be used in a sequence.

Source code in pymmcore_plus/core/_property.py
157
158
159
def isSequenceable(self) -> bool:
    """Return `True` if property can be used in a sequence."""
    return self._mmc.isPropertySequenceable(self.device, self.name)

isValid() -> bool #

Return True if device is loaded and has a property by this name.

Source code in pymmcore_plus/core/_property.py
67
68
69
def isValid(self) -> bool:
    """Return `True` if device is loaded and has a property by this name."""
    return self.isLoaded() and self._mmc.hasProperty(self.device, self.name)

loadSequence(eventSequence: Sequence[str]) -> None #

Transfer a sequence of events/states/whatever to the device.

Parameters:

Name Type Description Default
eventSequence Sequence[str]

The sequence of events/states that the device will execute in response to external triggers

required
Source code in pymmcore_plus/core/_property.py
165
166
167
168
169
170
171
172
173
174
def loadSequence(self, eventSequence: Sequence[str]) -> None:
    """Transfer a sequence of events/states/whatever to the device.

    Parameters
    ----------
    eventSequence : Sequence[str]
        The sequence of events/states that the device will execute in response
        to external triggers
    """
    self._mmc.loadPropertySequence(self.device, self.name, eventSequence)

lowerLimit() -> float #

Return lower limit if property has limits, or 0 otherwise.

Source code in pymmcore_plus/core/_property.py
125
126
127
def lowerLimit(self) -> float:
    """Return lower limit if property has limits, or 0 otherwise."""
    return self._mmc.getPropertyLowerLimit(self.device, self.name)

range() -> tuple[float, float] #

Return (lowerLimit, upperLimit) range tuple.

Source code in pymmcore_plus/core/_property.py
133
134
135
def range(self) -> tuple[float, float]:
    """Return (lowerLimit, upperLimit) range tuple."""
    return (self.lowerLimit(), self.upperLimit())

sequenceMaxLength() -> int #

Return maximum number of property events that can be put in a sequence.

Source code in pymmcore_plus/core/_property.py
161
162
163
def sequenceMaxLength(self) -> int:
    """Return maximum number of property events that can be put in a sequence."""
    return self._mmc.getPropertySequenceMaxLength(self.device, self.name)

setValue(val: Any) -> None #

Functional alternate to property setter.

Source code in pymmcore_plus/core/_property.py
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
def setValue(self, val: Any) -> None:
    """Functional alternate to property setter."""
    if self.isReadOnly():
        import warnings

        warnings.warn(
            f"'{self.device}::{self.name}' is a read-only property.", stacklevel=2
        )
    try:
        self._mmc.setProperty(self.device, self.name, val)
    except RuntimeError as e:
        msg = str(e)
        if allowed := self.allowedValues():
            msg += f". Allowed values: {allowed}"
        raise RuntimeError(msg) from None

startSequence() -> None #

Start an ongoing sequence of triggered events in a property.

Source code in pymmcore_plus/core/_property.py
176
177
178
def startSequence(self) -> None:
    """Start an ongoing sequence of triggered events in a property."""
    self._mmc.startPropertySequence(self.device, self.name)

stopSequence() -> None #

Stop an ongoing sequence of triggered events in a property.

Source code in pymmcore_plus/core/_property.py
180
181
182
def stopSequence(self) -> None:
    """Stop an ongoing sequence of triggered events in a property."""
    self._mmc.stopPropertySequence(self.device, self.name)

type() -> PropertyType #

Return PropertyType of this property.

Source code in pymmcore_plus/core/_property.py
137
138
139
def type(self) -> PropertyType:
    """Return `PropertyType` of this property."""
    return self._mmc.getPropertyType(self.device, self.name)

upperLimit() -> float #

Return upper limit if property has limits, or 0 otherwise.

Source code in pymmcore_plus/core/_property.py
129
130
131
def upperLimit(self) -> float:
    """Return upper limit if property has limits, or 0 otherwise."""
    return self._mmc.getPropertyUpperLimit(self.device, self.name)

value() -> Any property writable #

Return current property value, cast to appropriate type if applicable.

Source code in pymmcore_plus/core/_property.py
80
81
82
83
84
85
86
@property
def value(self) -> Any:
    """Return current property value, cast to appropriate type if applicable."""
    v = self._mmc.getProperty(self.device, self.name)
    if type_ := self.type().to_python():
        v = type_(v)
    return v

valueChanged() -> _DevicePropValueSignal cached property #

Source code in pymmcore_plus/core/_property.py
63
64
65
@cached_property
def valueChanged(self) -> _DevicePropValueSignal:
    return _DevicePropValueSignal(self.device, self.name, self._mmc)

pymmcore_plus.core._property.InfoDict #

allowed_values: tuple[str, ...] | None class-attribute #

device_type: str | None class-attribute #

pre_init: bool | None class-attribute #

range: tuple[float, float] | None class-attribute #

read_only: bool | None class-attribute #

sequence_max_length: int | None class-attribute #

sequenceable: bool | None class-attribute #

type: str | None class-attribute #

valid: bool class-attribute #

value: Any | None class-attribute #