Skip to content

Commit 9b58109

Browse files
mickpcarandraug
authored andcommitted
Make the Setting class private to enable refactoring of Settings (issue #125)
Managing different types of Settings may be achieved more flexibly and simply through the use of subclasses of Setting and the use of a factory pattern or helper method for their creation. So make the `Setting` class and the `Device.settings` attribute private so that the `Device` methods become the only sactioned method to get to them. * microscope/cameras/atmcd.py: use the `*_setting` methods instead of manipulating `self.settings` directly. * microscope/devices.py: mark `Setting` class and `Device.settings` attribute as private. * microscope/testsuite/test_setting.py: Setting class is now _Setting. * NEWS: make note of backwards incompatible change.
1 parent 94e8857 commit 9b58109

File tree

4 files changed

+81
-80
lines changed

4 files changed

+81
-80
lines changed

NEWS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ python-microscope releases.
44
Version 0.4.0 (upcoming)
55
------------------------
66

7+
* Selected most important, backwards incompatible, changes:
8+
9+
* The `Setting` class is now private. The only supported method to
10+
add settings to a `Device` is via its `add_setting` method.
11+
712
* New devices supported:
813

914
* Coherent Obis laser

microscope/cameras/atmcd.py

Lines changed: 56 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1132,7 +1132,7 @@ def __str__(self):
11321132

11331133
from threading import Lock
11341134
from microscope import devices
1135-
from microscope.devices import keep_acquiring, Setting, Binning, ROI
1135+
from microscope.devices import keep_acquiring, Binning, ROI
11361136
import time
11371137

11381138
# A lock on the DLL used to ensure DLL calls act on the correct device.
@@ -1253,21 +1253,21 @@ def initialize(self):
12531253
# Mode
12541254
name = 'readout mode'
12551255
if self._readout_modes:
1256-
self.settings[name] = Setting(name, 'enum',
1257-
None,
1258-
self._set_readout_mode,
1259-
lambda: [str(mode) for mode in self._readout_modes])
1260-
self.settings[name].set(0)
1256+
self.add_setting(name, 'enum',
1257+
None,
1258+
self._set_readout_mode,
1259+
lambda: [str(mode) for mode in self._readout_modes])
1260+
self.set_setting(name, 0)
12611261
# TriggerMode
12621262
name = 'TriggerMode'
1263-
self.settings[name] = Setting(name, 'enum',
1264-
None,
1265-
self._bind(SetTriggerMode),
1266-
TriggerMode)
1263+
self.add_setting(name, 'enum',
1264+
None,
1265+
self._bind(SetTriggerMode),
1266+
TriggerMode)
12671267
if self._caps.ulTriggerModes & AC_TRIGGERMODE_EXTERNAL:
1268-
self.settings[name].set(TriggerMode.EXTERNAL)
1268+
self.set_setting(name, TriggerMode.EXTERNAL)
12691269
elif self._caps.ulTriggerModes & AC_TRIGGERMODE_CONTINUOUS:
1270-
self.settings[name].set(TriggerMode.SOFTWARE)
1270+
self.set_setting(name, TriggerMode.SOFTWARE)
12711271
# Gain - device will use either EMGain or MCPGain
12721272
name = 'gain'
12731273
getter, setter, vrange = None, None, None
@@ -1282,9 +1282,7 @@ def initialize(self):
12821282
setter = self._bind(SetMCPGain)
12831283
vrange = self._bind(GetMCPGainRange)
12841284
if getter or setter:
1285-
self.settings[name] = Setting(name, 'int',
1286-
getter, setter, vrange,
1287-
setter is None)
1285+
self.add_setting(name, 'int', getter, setter, vrange, setter is None)
12881286
# Temperature
12891287
name = 'TemperatureSetPoint'
12901288
getter, setter, vrange = None, None, None
@@ -1293,77 +1291,73 @@ def initialize(self):
12931291
if self._caps.ulGetFunctions & AC_GETFUNCTION_TEMPERATURERANGE:
12941292
vrange = self._bind(GetTemperatureRange)
12951293
if setter:
1296-
self.settings[name] = Setting(name, 'int',
1297-
None, setter, vrange,
1298-
setter is None)
1294+
self.add_setting(name, 'int', None, setter, vrange, setter is None)
12991295
# Set a conservative default temperature set-point.
1300-
self.settings[name].set(-20)
1296+
self.set_setting(name, -20)
13011297
# Fan control
13021298
name = 'Temperature'
1303-
self.settings[name] = Setting(name, 'int',
1304-
self.get_sensor_temperature,
1305-
None, (None, None), True)
1299+
self.add_setting(name, 'int', self.get_sensor_temperature, None, (None, None), True)
13061300
name = 'Fan mode'
1307-
self.settings[name] = Setting(name, 'enum',
1308-
None, # Can't query fan mode
1309-
self._bind(SetFanMode),
1310-
{0:'full', 1:'low', 2:'off'}
1311-
)
1301+
self.add_setting(name, 'enum',
1302+
None, # Can't query fan mode
1303+
self._bind(SetFanMode),
1304+
{0:'full', 1:'low', 2:'off'}
1305+
)
13121306
# Cooler control
13131307
name = 'Cooler Enabled'
1314-
self.settings[name] = Setting(name, 'bool',
1315-
None,
1316-
self._set_cooler_state,
1317-
None)
1318-
self.settings[name].set(True)
1308+
self.add_setting(name, 'bool',
1309+
None,
1310+
self._set_cooler_state,
1311+
None)
1312+
self.set_setting(name, True)
13191313
# Binning
13201314
name = 'Binning'
1321-
self.settings[name] = Setting(name, 'tuple',
1322-
self.get_binning,
1323-
self.set_binning,
1324-
None)
1315+
self.add_setting(name, 'tuple',
1316+
self.get_binning,
1317+
self.set_binning,
1318+
None)
13251319
# Roi
13261320
name = 'Roi'
1327-
self.settings[name] = Setting(name, 'tuple',
1328-
self.get_roi,
1329-
lambda roi: self.set_roi(*roi),
1330-
None)
1321+
self.add_setting(name, 'tuple',
1322+
self.get_roi,
1323+
lambda roi: self.set_roi(*roi),
1324+
None)
13311325
# BaselineClamp
13321326
name = 'BaselineClamp'
13331327
if self._caps.ulSetFunctions & AC_SETFUNCTION_BASELINECLAMP:
1334-
self.settings[name] = Setting(name, 'bool',
1335-
None,
1336-
self._bind(SetBaselineClamp))
1337-
self.settings[name].set(False)
1328+
self.add_setting(name, 'bool',
1329+
None,
1330+
self._bind(SetBaselineClamp))
1331+
self.set_setting(name, False)
13381332
# BaselineOffset
13391333
name = 'BaselineOffset'
13401334
if self._caps.ulSetFunctions & AC_SETFUNCTION_BASELINEOFFSET:
1341-
self.settings[name] = Setting(name, 'int',
1342-
None,
1343-
self._bind(SetBaselineOffset),
1344-
(-1000, 1000))
1345-
self.settings[name].set(0)
1335+
self.add_setting(name, 'int',
1336+
None,
1337+
self._bind(SetBaselineOffset),
1338+
(-1000, 1000))
1339+
self.set_setting(name, 0)
13461340
# EMAdvanced
13471341
name = 'EMAdvanced'
13481342
if self._caps.ulSetFunctions & AC_SETFUNCTION_EMADVANCED:
1349-
self.settings[name] = Setting(name, 'bool',
1350-
None,
1351-
self._bind(SetEMAdvanced))
1352-
self.settings[name].set(False)
1343+
self.add_setting(name, 'bool',
1344+
None,
1345+
self._bind(SetEMAdvanced))
1346+
self.set_setting(name, False)
13531347
# GateMode
13541348
name = 'GateMode'
13551349
if self._caps.ulSetFunctions & AC_SETFUNCTION_GATEMODE:
13561350
vrange = range(0, [5,6][self._caps.ulCameraType & AC_CAMERATYPE_ISTAR])
1357-
self.setings[name] = Setting(name, 'int',
1358-
None,
1359-
self._bind(SetGateMode),
1360-
vrange)
1351+
self.add_setting(name, 'int',
1352+
None,
1353+
self._bind(SetGateMode),
1354+
vrange)
13611355
# HighCapacity
13621356
name = 'HighCapacity'
13631357
if self._caps.ulSetFunctions & AC_SETFUNCTION_HIGHCAPACITY:
1364-
self.settings[name] = Setting(name, 'bool',
1365-
None,
1366-
self._bind(SetHighCapacity))
1358+
self.add_setting(name, 'bool',
1359+
None,
1360+
self._bind(SetHighCapacity))
13671361

13681362
def _fetch_data(self):
13691363
"""Poll for data and return it, with minimal processing.
@@ -1429,7 +1423,7 @@ def _on_enable(self):
14291423
SetReadMode(ReadMode.IMAGE)
14301424
x, y = GetDetector()
14311425
self._set_image()
1432-
if not IsTriggerModeAvailable(self.settings['TriggerMode'].get()):
1426+
if not IsTriggerModeAvailable(self.get_setting('TriggerMode')):
14331427
raise AtmcdException("Trigger mode is not valid.")
14341428
StartAcquisition()
14351429
return True
@@ -1506,7 +1500,7 @@ def get_sensor_temperature(self):
15061500

15071501
def get_trigger_type(self):
15081502
"""Return the microscope.devices trigger type."""
1509-
trig = self.settings['TriggerMode'].get()
1503+
trig = self.get_setting('TriggerMode')
15101504
if trig == TriggerMode.BULB:
15111505
return devices.TRIGGER_DURATION
15121506
elif trig == TriggerMode.SOFTWARE:

microscope/devices.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,10 @@
7575
_call_if_callable = lambda f: f() if callable(f) else f
7676

7777

78-
class Setting():
78+
class _Setting():
79+
# TODO: refactor into subclasses to avoid if isinstance .. elif .. else.
80+
# Settings classes should be private: devices should use a factory method
81+
# rather than instantiate settings directly; most already use add_setting for this.
7982
def __init__(self, name, dtype, get_func, set_func=None, values=None, readonly=False):
8083
"""Create a setting.
8184
@@ -202,7 +205,7 @@ class Device(metaclass=abc.ABCMeta):
202205
def __init__(self, index=None):
203206
self.enabled = None
204207
# A list of settings. (Can't serialize OrderedDict, so use {}.)
205-
self.settings = OrderedDict()
208+
self._settings = OrderedDict()
206209
self._index = index
207210

208211
def __del__(self):
@@ -265,8 +268,6 @@ def make_safe(self):
265268
def add_setting(self, name, dtype, get_func, set_func, values, readonly=False):
266269
"""Add a setting definition.
267270
268-
Can also use self.settings[name] = Setting(name, dtype,...)
269-
270271
:param name: the setting's name
271272
:param dtype: a data type from ('int', 'float', 'bool', 'enum', 'str')
272273
:param get_func: a function to get the current value
@@ -290,45 +291,46 @@ class with getter, setter, etc., and adding Setting instances as
290291
raise Exception("Invalid values type for %s '%s': expected function or %s" %
291292
(dtype, name, DTYPES[dtype][1:]))
292293
else:
293-
self.settings[name] = Setting(name, dtype, get_func, set_func, values, readonly)
294+
self._settings[name] = _Setting(name, dtype, get_func, set_func,
295+
values, readonly)
294296

295297
def get_setting(self, name):
296298
"""Return the current value of a setting."""
297299
try:
298-
return self.settings[name].get()
300+
return self._settings[name].get()
299301
except Exception as err:
300302
_logger.error("in get_setting(%s):" % (name), exc_info=err)
301303
raise
302304

303305
def get_all_settings(self):
304306
"""Return ordered settings as a list of dicts."""
305307
try:
306-
return {k: v.get() for k, v in self.settings.items()}
308+
return {k: v.get() for k, v in self._settings.items()}
307309
except Exception as err:
308310
_logger.error("in get_all_settings:", exc_info=err)
309311
raise
310312

311313
def set_setting(self, name, value):
312314
"""Set a setting."""
313315
try:
314-
self.settings[name].set(value)
316+
self._settings[name].set(value)
315317
except Exception as err:
316318
_logger.error("in set_setting(%s):" % (name), exc_info=err)
317319
raise
318320

319321
def describe_setting(self, name):
320322
"""Return ordered setting descriptions as a list of dicts."""
321-
return self.settings[name].describe()
323+
return self._settings[name].describe()
322324

323325
def describe_settings(self):
324326
"""Return ordered setting descriptions as a list of dicts."""
325-
return [(k, v.describe()) for (k, v) in self.settings.items()]
327+
return [(k, v.describe()) for (k, v) in self._settings.items()]
326328

327329
def update_settings(self, incoming, init=False):
328330
"""Update settings based on dict of settings and values."""
329331
if init:
330332
# Assume nothing about state: set everything.
331-
my_keys = set(self.settings.keys())
333+
my_keys = set(self._settings.keys())
332334
their_keys = set(incoming.keys())
333335
update_keys = my_keys & their_keys
334336
if update_keys != my_keys:
@@ -338,24 +340,24 @@ def update_settings(self, incoming, init=False):
338340
raise Exception(msg)
339341
else:
340342
# Only update changed values.
341-
my_keys = set(self.settings.keys())
343+
my_keys = set(self._settings.keys())
342344
their_keys = set(incoming.keys())
343345
update_keys = set(key for key in my_keys & their_keys
344346
if self.get_setting(key) != incoming[key])
345347
results = {}
346348
# Update values.
347349
for key in update_keys:
348-
if key not in my_keys or not self.settings[key].set:
350+
if key not in my_keys or not self._settings[key].set:
349351
# Setting not recognised or no set function implemented
350352
results[key] = NotImplemented
351353
update_keys.remove(key)
352354
continue
353-
if _call_if_callable(self.settings[key].readonly):
355+
if _call_if_callable(self._settings[key].readonly):
354356
continue
355-
self.settings[key].set(incoming[key])
357+
self._settings[key].set(incoming[key])
356358
# Read back values in second loop.
357359
for key in update_keys:
358-
results[key] = self.settings[key].get()
360+
results[key] = self._settings[key].get()
359361
return results
360362

361363

microscope/testsuite/test_settings.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ def create_enum_setting(default, with_getter=True, with_setter=True):
4949
thing = ThingWithSomething(EnumSetting(default))
5050
getter = thing.get_val if with_getter else None
5151
setter = thing.set_val if with_setter else None
52-
setting = microscope.devices.Setting('foobar', 'enum', get_func=getter,
53-
set_func=setter, values=EnumSetting)
52+
setting = microscope.devices._Setting('foobar', 'enum', get_func=getter,
53+
set_func=setter, values=EnumSetting)
5454
return setting, thing
5555

5656

0 commit comments

Comments
 (0)