class FooDevice(BaseDevice): state = State(default='standby') no_write = Parameter() foo = Quantity(q.m, check=check(source='*', target='moved')) bar = Quantity(q.m) test = Quantity(q.m, fset=_test_setter, fget=_test_getter) def __init__(self, default): super(FooDevice, self).__init__() self._value = default self._param_value = 0 * q.mm self._test_value = 0 * q.mm async def _get_foo(self): return self._value @transition(target='moved') async def _set_foo(self, value): self._value = value async def _get_bar(self): return 5 * q.m param = Parameter(fget=_get_foo, fset=_set_foo) async def _get_param(self): return self._param_value async def _set_param(self, value): self._param_value = value param = Parameter(fget=_get_param, fset=_set_param)
def __init__(self, port_id, name, fget=None, fset=None, doc=None): if fget is None: new_fget = None else: new_fget = lambda: fget(port_id) if fset is None: new_fset = None else: new_fset = lambda value: fset(port_id, value) Parameter.__init__(self, name, fget=new_fget, fset=new_fset, doc=doc) self.port_id = port_id
def __init__(self, param, feedback): params = [ Parameter('minimum', doc="Left bound of the interval"), Parameter('maximum', doc="Right bound of the interval"), Parameter('intervals', doc="Number of intervals") ] super(Scanner, self).__init__(params) self.intervals = 64 self.param = param self.feedback = feedback
def __init__(self): def setter(value): pass def getter(): return 1 self.params = [Parameter('readonly', fget=getter), Parameter('writeonly', fset=setter)] super(MockDevice, self).__init__(self.params)
def __init__(self, calibration): params = [ Parameter("energy", self._get_energy, self._set_energy, q.eV, doc="Monochromatic energy"), Parameter("wavelength", self._get_wavelength, self._set_wavelength, q.nanometer, doc="Monochromatic wavelength") ] super(Monochromator, self).__init__(params) self._calibration = calibration
def __init__(self, parameters=None): super(Device, self).__init__(parameters) self.add_parameter(Parameter('state', self._get_state, owner_only=True)) self._lock = threading.Lock() self._states = set([self.NA]) self._state = self.NA
def __init__(self, directory): # Let users change the directory self.directory = directory params = [ Parameter('trigger-mode', lower=FileCamera.TRIGGER_AUTO, upper=FileCamera.TRIGGER_SOFTWARE) ] super(FileCamera, self).__init__(params) self._frame_rate = None self._recording = None self._index = -1 self._start_time = None self._stop_time = None self._trigger = Event() self._trigger_time = None self.roi_x = 0 self.roi_y = 0 self.roi_width = None self.roi_height = None self._files = [ os.path.join(directory, file_name) for file_name in sorted(os.listdir(directory)) ]
class SpiralMixin(Parameterizable): """ Mixin for spiral tomography. """ start_position_vertical = Quantity(q.mm) """Initial position of the vertical motor.""" vertical_shift_per_tomogram = Quantity(q.mm) """Vertical shift per tomogram.""" sample_height = Quantity(q.mm) """Height of the sample. *vertical_motor* will be scanned from *start_position_vertical* to *sample_height* + *vertical_shift_per_tomogram* to sample the whole specimen. """ num_tomograms = Parameter() """Number of tomograms, that are required to cover the whole specimen.""" def __init__(self, vertical_motor, start_position_vertical, sample_height, vertical_shift_per_tomogram): """ :param vertical_motor: LinearMotor to translate the sample along the tomographic axis. :type vertical_motor: concert.devices.motors.base.LinearMotor :param start_position_vertical: Start position of *vertical_motor*. :type start_position_vertical: q.mm :param sample_height: Height of the sample. :type sample_height: q.mm :param vertical_shift_per_tomogram: Distance *vertical_motor* is translated during one *angular_range*. :type vertical_shift_per_tomogram: q.mm """ self._start_position_vertical = start_position_vertical self._vertical_shift_per_tomogram = vertical_shift_per_tomogram self._sample_height = sample_height self._vertical_motor = vertical_motor Parameterizable.__init__(self) async def _get_start_position_vertical(self): return self._start_position_vertical async def _get_num_tomograms(self): shift = await self.get_vertical_shift_per_tomogram() height = await self.get_sample_height() return abs(height / shift).to_base_units().magnitude + 1 async def _get_vertical_shift_per_tomogram(self): return self._vertical_shift_per_tomogram async def _get_sample_height(self): return self._sample_height async def _set_start_position_vertical(self, position): self._start_position_vertical = position async def _set_sample_height(self, height): self._sample_height = height async def _set_vertical_shift_per_tomogram(self, shift): self._vertical_shift_per_tomogram = shift
class MockDevice(Device): readonly = Parameter() writeonly = Parameter() def __init__(self): super(MockDevice, self).__init__() self.aborted = False async def _get_readonly(self): return 1 async def _set_writeonly(self, value): pass async def _emergency_stop(self): self.aborted = True
def __init__(self): params = [ Parameter('current', fget=self._get_current, unit=q.mA, doc="Current of the ring"), Parameter('energy', fget=self._get_energy, unit=q.MeV, doc="Energy of the ring"), Parameter('lifetime', fget=self._get_lifetime, unit=q.h, doc="Lifetime of the ring") ] super(StorageRing, self).__init__(params)
def test_write_only_parameter(self): parameter = Parameter('foo', fset=empty_setter) self.assertTrue(parameter.is_writable()) self.assertFalse(parameter.is_readable()) parameter.set(1).result() with self.assertRaises(ReadAccessError) as ctx: parameter.get().result() self.assertEqual("parameter `foo' cannot be read", str(ctx.exception))
def __init__(self, parameters=None): # We have to create the lock early on because it will be accessed in # any add_parameter calls, especially those in the Parameterizable base # class self._lock = threading.Lock() super(Device, self).__init__(parameters) self.add_parameter(Parameter('state', self._get_state)) self._states = set([self.NA]) self._state = self.NA
def __init__(self, calibration): params = [ Parameter('flow_rate', fget=self._get_calibrated_flow_rate, fset=self._set_calibrated_flow_rate, unit=q.l / q.s, doc="Pump flow rate.") ] super(Pump, self).__init__(params) self._calibration = calibration self._states = self._states.union(set([self.STANDBY, self.PUMPING]))
def test_hard_limit(self): def setter(value): pass class Limited(object): in_limit = False def __call__(self): return self.in_limit in_limit = Limited() parameter = Parameter('foo', fset=setter, in_hard_limit=in_limit) parameter.set(0).result() parameter.set(0.5).result() parameter.set(1).result() with self.assertRaises(HardLimitError): in_limit.in_limit = True parameter.set(1.5).result()
class PyplotImageViewer(ImageViewerBase): """Dynamic image viewer using matplotlib. .. py:attribute:: imshow_kwargs matplotlib's imshow keyword arguments .. py:attribute:: fast Whether to use the fast version without colorbar """ colormap = Parameter(help='Colormap') def __init__(self, imshow_kwargs: dict = None, fast: bool = True, limits: str = 'stream', downsampling: int = 1, title: str = "", show_refresh_rate: bool = False, force: bool = False): super().__init__(limits=limits, downsampling=downsampling, title=title, show_refresh_rate=show_refresh_rate, force=force) self._has_colorbar = not fast self._imshow_kwargs = {} if imshow_kwargs is None else imshow_kwargs self._make_imshow_defaults() def _make_updater(self): if self._has_colorbar: return _PyplotImageUpdater( self._queue, self._imshow_kwargs, limits=self._limits, title=self._title, show_refresh_rate=self._show_refresh_rate ) return _SimplePyplotImageUpdater( self._queue, self._imshow_kwargs, limits=self._limits, title=self._title, show_refresh_rate=self._show_refresh_rate ) def reset(self): """Reset the viewer's state.""" self._queue.put(('reset', None)) def _make_imshow_defaults(self): """Override matplotlib's image showing defafults.""" if "cmap" not in self._imshow_kwargs: self._imshow_kwargs["cmap"] = 'gray' self._colormap = self._imshow_kwargs["cmap"] if "interpolation" not in self._imshow_kwargs: self._imshow_kwargs["interpolation"] = "nearest" async def _get_colormap(self): return self._colormap async def _set_colormap(self, colormap): """Set colormp of the shown image to *colormap*.""" self._queue.put(('colormap', colormap))
def __init__(self, pos=(0., 0.), imgsize=(640, 480)): params = [ Parameter('exposure-time', unit=q.s), Parameter('roi-width'), Parameter('roi-height'), Parameter('sensor-pixel-width', unit=q.micrometer), Parameter('sensor-pixel-height', unit=q.micrometer) ] super(DummyCamera, self).__init__(params) self.exposure_time = 1 * q.ms self.sensor_pixel_width = 5 * q.micrometer self.sensor_pixel_height = 5 * q.micrometer self._sigma = 10 self._rel_sec = 1. self._m_ax = [1, 0] self._imgsize = imgsize self._noise = imgnoise self._frm_mode = 'generate' self._coord = [pos[0] * q.meter, pos[1] * q.meter] self._coord_at_trigger = [pos[0] * q.meter, pos[1] * q.meter]
def __init__(self, camera, rotary_stage, prepare_dark_scan, prepare_flat_scan, prepare_proj_scan): params = [Parameter('angle', unit=q.deg)] self.camera = camera self.rotary_stage = rotary_stage self.prepare_dark_scan = prepare_dark_scan self.prepare_flat_scan = prepare_flat_scan self.prepare_proj_scan = prepare_proj_scan self.num_projections = 4 super(StepTomoScanner, self).__init__(params)
def _create_parameter(node, prop): from gi.repository import GObject getter, setter = None, None if prop.flags & GObject.ParamFlags.READABLE: getter = _create_getter(node, prop.name) if prop.flags & GObject.ParamFlags.WRITABLE: setter = _create_setter(node, prop.name) param = Parameter(prop.name, getter, setter, doc=prop.blurb) return param
def test_soft_limit(self): parameter = Parameter('foo', fset=empty_setter, unit=q.mm, lower=2 * q.mm, upper=4 * q.mm) parameter.set(2.3 * q.mm).wait() parameter.set(2 * q.mm).wait() parameter.set(4 * q.mm).wait() self.assertRaises(SoftLimitError, parameter.set(4.2 * q.mm).wait)
def __init__(self, background=None): params = [ Parameter('exposure-time', unit=q.s), Parameter('roi-width'), Parameter('roi-height'), Parameter('sensor-pixel-width', unit=q.micrometer), Parameter('sensor-pixel-height', unit=q.micrometer) ] super(Camera, self).__init__(params) self.exposure_time = 1 * q.ms self.sensor_pixel_width = 5 * q.micrometer self.sensor_pixel_height = 5 * q.micrometer if background: self.roi_width = background.shape[1] self.roi_height = background.shape[0] self._background = background else: shape = (640, 480) self.roi_width, self.roi_height = shape self._background = np.ones(shape)
def test_read_only_parameter(self): def getter(): return 0 parameter = Parameter('foo', fget=getter) self.assertTrue(parameter.is_readable()) self.assertFalse(parameter.is_writable()) self.assertEqual(parameter.get().result(), 0) with self.assertRaises(WriteAccessError) as ctx: parameter.set(None).result() self.assertEqual("parameter `foo' cannot be written", str(ctx.exception))
class DummyDevice(Device): """A dummy device.""" value = Parameter() def __init__(self): super(DummyDevice, self).__init__() self._value = None def _get_value(self): """Get the real value.""" return self._value def _set_value(self, value): """The real value setter.""" self._value = value @async def do_nothing(self): """Do nothing.""" pass
def __init__(self, name): from gi.repository import GObject, Uca self._manager = Uca.PluginManager() self._data = None self._array = None try: self.camera = self._manager.get_camerav(name, []) except: raise ValueError("`{0}' is not a valid camera".format(name)) units = { Uca.Unit.METER: q.m, Uca.Unit.SECOND: q.s, Uca.Unit.DEGREE_CELSIUS: q.Celsius, Uca.Unit.COUNT: q.count } parameters = [] for prop in self.camera.props: getter, setter, unit = None, None, None uca_unit = self.camera.get_unit(prop.name) if uca_unit in units: unit = units[uca_unit] if prop.flags & GObject.ParamFlags.READABLE: getter = _new_getter_wrapper(self.camera, prop.name, unit) if prop.flags & GObject.ParamFlags.WRITABLE: setter = _new_setter_wrapper(self.camera, prop.name, unit) parameter = Parameter(prop.name, getter, setter, unit) parameters.append(parameter) super(Camera, self).__init__(parameters)
class Experiment(Parameterizable): """ Experiment base class. An experiment can be run multiple times with the output data and log stored on disk. You can prepare every run by :meth:`.prepare` and finsh the run by :meth:`.finish`. These methods do nothing by default. They can be useful e.g. if you need to reinitialize some experiment parts or want to attach some logging output. .. py:attribute:: acquisitions :noindex: A list of acquisitions this experiment is composed of .. py:attribute:: walker A :class:`concert.storage.Walker` descends to a data set specific for every run if given .. py:attribute:: separate_scans If True, *walker* does not descend to data sets based on specific runs .. py:attribute:: name_fmt Since experiment can be run multiple times each iteration will have a separate entry on the disk. The entry consists of a name and a number of the current iteration, so the parameter is a formattable string. """ iteration = Parameter() separate_scans = Parameter() name_fmt = Parameter() state = State(default='standby') log_level = Selection(['critical', 'error', 'warning', 'info', 'debug']) def __init__(self, acquisitions, walker=None, separate_scans=True, name_fmt='scan_{:>04}'): self._acquisitions = [] for acquisition in acquisitions: self.add(acquisition) self.walker = walker self._separate_scans = separate_scans self._name_fmt = name_fmt self._iteration = 1 self.log = LOG Parameterizable.__init__(self) if separate_scans and walker: # The data is not supposed to be overwritten, so find an iteration which # hasn't been used yet while self.walker.exists(self._name_fmt.format(self._iteration)): self._iteration += 1 async def _get_iteration(self): return self._iteration async def _set_iteration(self, iteration): self._iteration = iteration async def _get_separate_scans(self): return self._separate_scans async def _set_separate_scans(self, separate_scans): self._separate_scans = separate_scans async def _get_name_fmt(self): return self._name_fmt async def _set_name_fmt(self, fmt): self._name_fmt = fmt async def _get_log_level(self): return logging.getLevelName(self.log.getEffectiveLevel()).lower() async def _set_log_level(self, level): self.log.setLevel(level.upper()) async def prepare(self): """Gets executed before every experiment run.""" pass async def finish(self): """Gets executed after every experiment run.""" pass @property def acquisitions(self): """Acquisitions is a read-only attribute which has to be manipulated by explicit methods provided by this class. """ return tuple(self._acquisitions) def add(self, acquisition): """ Add *acquisition* to the acquisition list and make it accessible as an attribute:: frames = Acquisition(...) experiment.add(frames) # This is possible experiment.frames """ self._acquisitions.append(acquisition) setattr(self, acquisition.name, acquisition) def remove(self, acquisition): """Remove *acquisition* from experiment.""" self._acquisitions.remove(acquisition) delattr(self, acquisition.name) def swap(self, first, second): """ Swap acquisition *first* with *second*. If there are more occurences of either of them then the ones which are found first in the acquisitions list are swapped. """ if first not in self._acquisitions or second not in self._acquisitions: raise ValueError( "Both acquisitions must be part of the experiment") first_index = self._acquisitions.index(first) second_index = self._acquisitions.index(second) self._acquisitions[first_index] = second self._acquisitions[second_index] = first def get_acquisition(self, name): """ Get acquisition by its *name*. In case there are more like it, the first one is returned. """ for acq in self._acquisitions: if acq.name == name: return acq raise ExperimentError( "Acquisition with name `{}' not found".format(name)) async def acquire(self): """ Acquire data by running the acquisitions. This is the method which implements the data acquisition and should be overriden if more functionality is required, unlike :meth:`~.Experiment.run`. """ for acq in wrap_iterable(self._acquisitions): if await self.get_state() != 'running': break await acq() @background @check(source=['standby', 'error'], target='standby') @transition(immediate='running', target='standby') async def run(self): start_time = time.time() handler = None iteration = await self.get_iteration() separate_scans = await self.get_separate_scans() try: if self.walker: if separate_scans: self.walker.descend( (await self.get_name_fmt()).format(iteration)) if os.path.exists(self.walker.current): # We might have a dummy walker which doesn't create the directory handler = logging.FileHandler( os.path.join(self.walker.current, 'experiment.log')) formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s ' '- %(message)s') handler.setFormatter(formatter) self.log.addHandler(handler) self.log.info(await self.info_table) LOG.debug('Experiment iteration %d start', iteration) await self.prepare() await self.acquire() except asyncio.CancelledError: # This is normal, no special state needed -> standby LOG.warn('Experiment cancelled') except Exception as e: # Something bad happened and we can't know what, so set the state to error LOG.warn(f"Error `{e}' while running experiment") raise StateError('error', msg=str(e)) except KeyboardInterrupt: LOG.warn('Experiment cancelled by keyboard interrupt') self._state_value = 'standby' raise finally: try: await self.finish() except Exception as e: LOG.warn(f"Error `{e}' while finalizing experiment") raise StateError('error', msg=str(e)) finally: if separate_scans and self.walker: self.walker.ascend() LOG.debug('Experiment iteration %d duration: %.2f s', iteration, time.time() - start_time) if handler: handler.close() self.log.removeHandler(handler) await self.set_iteration(iteration + 1)
class Camera(Device): """Base class for remotely controllable cameras. .. py:attribute:: frame-rate Frame rate of acquisition in q.count per time unit. """ trigger_sources = Bunch(['AUTO', 'SOFTWARE', 'EXTERNAL']) trigger_types = Bunch(['EDGE', 'LEVEL']) state = State(default='standby') frame_rate = Quantity(1 / q.second, help="Frame frequency") trigger_source = Parameter(help="Trigger source") def __init__(self): super(Camera, self).__init__() self.convert = identity @background @check(source='standby', target='recording') async def start_recording(self): """ start_recording() Start recording frames. """ await self._record_real() @background @check(source='recording', target='standby') async def stop_recording(self): """ stop_recording() Stop recording frames. """ await self._stop_real() @contextlib.asynccontextmanager async def recording(self): """ recording() A context manager for starting and stopping the camera. In general it is used with the ``async with`` keyword like this:: async with camera.recording(): frame = await camera.grab() """ await self.start_recording() try: yield finally: LOG.log(AIODEBUG, 'stop recording in recording()') await self.stop_recording() @background async def trigger(self): """Trigger a frame if possible.""" await self._trigger_real() @background async def grab(self): """Return a NumPy array with data of the current frame.""" return self.convert(await self._grab_real()) async def stream(self): """ stream() Grab frames continuously yield them. This is an async generator. """ await self.set_trigger_source(self.trigger_sources.AUTO) await self.start_recording() while await self.get_state() == 'recording': yield await self.grab() async def _get_trigger_source(self): raise AccessorNotImplementedError async def _set_trigger_source(self, source): raise AccessorNotImplementedError async def _record_real(self): raise AccessorNotImplementedError async def _stop_real(self): raise AccessorNotImplementedError async def _trigger_real(self): raise AccessorNotImplementedError async def _grab_real(self): raise AccessorNotImplementedError
def __init__(self, name, params=None): """ Create a new libuca camera. The *name* is passed to the uca plugin manager. :raises CameraError: In case camera *name* does not exist. """ super(Camera, self).__init__() import gi gi.require_version('Uca', '2.0') from gi.repository import GObject, GLib, Uca self._manager = Uca.PluginManager() params = params if params else {} try: self.uca = self._manager.get_camerah(name, params) except GLib.GError as ge: raise base.CameraError(str(ge)) except Exception: raise base.CameraError("`{0}' is not a valid camera".format(name)) units = { Uca.Unit.METER: q.m, Uca.Unit.SECOND: q.s, Uca.Unit.DEGREE_CELSIUS: q.celsius, Uca.Unit.COUNT: q.dimensionless, Uca.Unit.PIXEL: q.pixel, } parameters = {} for prop in self.uca.props: if prop.name in ('trigger-source', 'trigger-type', 'frames-per-second'): continue getter, setter, unit = None, None, None uca_unit = self.uca.get_unit(prop.name) if uca_unit in units: unit = units[uca_unit] if prop.flags & GObject.ParamFlags.READABLE: getter = _new_getter_wrapper(prop.name, unit) if prop.flags & GObject.ParamFlags.WRITABLE: setter = _new_setter_wrapper(prop.name, unit) name = prop.name.replace('-', '_') if uca_unit in units: parameters[name] = Quantity(unit, fget=getter, fset=setter, help=prop.blurb) else: parameters[name] = Parameter(fget=getter, fset=setter, help=prop.blurb) if parameters: self.install_parameters(parameters) class _Dummy(object): pass setattr(self.uca, 'enum_values', _Dummy()) def get_enum_bunch(enum): enum_map = {} for key, value in list(enum.__enum_values__.items()): name = value.value_nick.upper().replace('-', '_') enum_map[name] = key return Bunch(enum_map) for prop in self.uca.props: if hasattr(prop, 'enum_class'): setattr(self.uca.enum_values, prop.name.replace('-', '_'), get_enum_bunch(prop.default_value)) self._uca_get_frame_rate = _new_getter_wrapper('frames-per-second') self._uca_set_frame_rate = _new_setter_wrapper('frames-per-second') # Invert the uca trigger source dict in order to return concert values trigger_dict = self.uca.enum_values.trigger_source.__dict__ self._uca_to_concert_trigger = { v: k for k, v in list(trigger_dict.items()) } self._uca_get_trigger = _new_getter_wrapper('trigger-source') self._uca_set_trigger = _new_setter_wrapper('trigger-source') self._record_shape = None self._record_dtype = None
class ViewerBase(Parameterizable): """ A base class for data viewer which sends commands to a backend-specific updater which runs in a separate process. """ force = Parameter(help='Make sure every item is displayed') def __init__(self, force: bool = False): super().__init__() self._force = force self._queue = _MP_CTX.Queue() # This prevents hanging in the case we exit the session after something is put in the queue # and before it is consumed. self._queue.cancel_join_thread() self._paused = False # To be set up by an actual implementation which runs the drawing backed in a separate # process self._proc = None # __del__ is not going to help because it's never called from our concert session async def _set_force(self, value): self._force = value async def _get_force(self): return self._force @background async def __call__(self, producer, size=0, force=None): """ Display stream from *producer*. If *size* is specified, stop after displaying *size* items. If *force* is True make sure the item is displayed, if False it may be skipped if there is something in the queue waiting to be shown, if it is None, the viewer's *force* parameter is used. """ i = 0 async for item in producer: if not size or i < size: await self.show(item, force=self._force if force is None else force) i += 1 @background async def show(self, item, force=False): """Push *item* to the queue for display in a separate proces. If *force* is True make sure the item is displayed, otherwise it may be skipped if there is something in the queue waiting to be shown. """ # If the circumstances allow it, push the item to the queue for display # This must happen before instantiation of the updater below because _show may raise # exception, in which case we don't want the updater to have started yet. if not self._paused and (not self._queue.qsize() or force or not self._proc): await run_in_executor(self._show, item) # If there is no updater or it has been stopped, instantiate it and start it in a process. if not (self._proc and self._proc.is_alive()): updater = self._make_updater() self._proc = _MP_CTX.Process(target=updater.run, daemon=True) self._proc.start() # Make sure that all control commands, like changing colormap, have been processed while self._queue.qsize(): await asyncio.sleep(0.01) def pause(self): """Pause, no images are dispayed but image commands work.""" self._paused = True def resume(self): """Resume the viewer.""" self._paused = False def _show(self, item): """Implementation of pushing *item* to the display queue.""" raise NotImplementedError def _make_updater(self): """Updater factory method.""" raise NotImplementedError
class DummyDevice(Device): """A dummy device.""" position = Quantity(unit=q.mm) sleep_time = Quantity(unit=q.s) # Value with a check-decorated setter before it is bound to instance, so still a function value = Parameter() # value with check wrapping the bound method cvalue = Parameter(check=check(source='*', target='standby')) # Value set elsewhere evalue = Parameter(fset=set_evalue, fget=get_evalue, check=check(source='*', target='standby')) slow = Parameter() state = State(default='standby') def __init__(self, slow=None): super(DummyDevice, self).__init__() self._position = 1 * q.mm self._value = 0 self._slow = slow self._sleep_time = 0.5 * q.s async def _get_sleep_time(self): return self._sleep_time async def _set_sleep_time(self, value): self._sleep_time = value async def _get_position(self): return self._position async def _set_position(self, value): self._position = value async def _get_slow(self): try: LOG.log(AIODEBUG, 'get slow start %s', self._slow) await asyncio.sleep((await self.get_sleep_time()).magnitude) LOG.log(AIODEBUG, 'get slow finish %s', self._slow) return self._slow except asyncio.CancelledError: LOG.log(AIODEBUG, 'get slow cancelled %s', self._slow) raise except KeyboardInterrupt: # do not scream LOG.debug("KeyboardInterrupt caught while getting") async def _set_slow(self, value): try: LOG.log(AIODEBUG, 'set slow start %s', value) await asyncio.sleep((await self.get_sleep_time()).magnitude) LOG.log(AIODEBUG, 'set slow finish %s', value) self._slow = value except asyncio.CancelledError: LOG.log(AIODEBUG, 'set slow cancelled %s', value) raise async def _get_value(self): """Get the real value.""" return self._value async def _get_target_value(self): """Get the real value.""" return self._value + 1 @check(source='standby', target=['standby', 'hard-limit']) @transition(immediate='moving', target='standby') async def _set_value(self, value): """The real value setter.""" self._value = value async def _get_cvalue(self): """The real value setter.""" return self._value async def _set_cvalue(self, value): """The real value setter.""" self._value = value @background async def do_nothing(self, value=None): """Do nothing. For testing task canellation.""" await self._do_nothing(value=value) async def _do_nothing(self, value=None): LOG.log(AIODEBUG, f'Start doing nothing: {value}') try: await asyncio.sleep(1) LOG.log(AIODEBUG, f'Stop doing nothing: {value}') return value except asyncio.CancelledError: LOG.log(AIODEBUG, f'Doing nothing cancelled: {value}') raise async def _emergency_stop(self): LOG.debug('Emergency stop on a dummy device') await asyncio.sleep(0.5) self._state_value = 'aborted'
class ImageViewerBase(ViewerBase): """Backend-free base class for displaying images. .. py:attribute:: limits minimum and maximum gray value (black and white points). Can be a tuple (min, max), 'auto' or 'stream'. When 'auto', limits are adjusted for every shown image, when 'stream', limits are adjusted on every __call__. .. py:attribute:: downsampling Display every n-th pixel, which can speed up the viewer .. py:attribute:: title Image title .. py:attribute:: show_refresh_rate Whether or not to show refresh rate text directly embedded into the displayed image """ show_refresh_rate = Parameter(help='Display current refresh rate') limits = Parameter(help='Black and white point') downsampling = Parameter(help='Display only every n-th pixel') def __init__(self, limits: str = 'stream', downsampling: int = 1, title: str = "", show_refresh_rate: bool = False, force: bool = False): super().__init__(force=force) self._show_refresh_rate = show_refresh_rate self._title = title self._downsampling = downsampling self._limits = limits @background async def __call__(self, producer: Callable, size: int = None, force: bool = None): # In case limits are set to 'stream' we need to reset clim self._queue.put(('clim', self._limits)) return await super().__call__(producer, size=None, force=force) def _show(self, item): self._queue.put(('image', item[::self._downsampling, ::self._downsampling])) async def _get_downsampling(self): return self._downsampling async def _set_downsampling(self, value): if value not in range(1, 21): raise ValueError('Downsampling must be from interval [1, 20]') self._downsampling = value async def _get_limits(self): return self._limits async def _set_limits(self, limits): """ Minimum and maximum gray value (black and white points). Can be a tuple (min, max), 'auto' or 'stream'. When 'auto', limits are adjusted for every shown image, when 'stream', limits are adjusted on every __call__. """ if not (limits == 'auto' or limits == 'stream' or len(limits) == 2): raise ViewerError("limits can be a tuple (min, max), 'auto' or 'stream'") self._queue.put(('clim', limits)) self._limits = limits async def _get_show_refresh_rate(self): return self._show_refresh_rate async def _set_show_refresh_rate(self, value): if not isinstance(value, bool): raise ValueError('boolean value expected') self._queue.put(('show-fps', value)) self._show_refresh_rate = value
class PyplotViewer(ViewerBase): """ Dynamic plot viewer using matplotlib. .. py:attribute:: style One of matplotlib's linestyle format strings .. py:attribute:: plt_kwargs Keyword arguments accepted by matplotlib's plot() .. py:attribute:: autoscale If True, the axes limits will be expanded as needed by the new data, otherwise the user needs to rescale the axes .. py:attribute:: title Plot title """ style = Parameter(help='Line style') autoscale = Parameter(help='Autoscale view') def __init__(self, style: str = "o", plot_kwargs: dict = None, autoscale: bool = True, title: str = "", force: bool = False): super().__init__(force=force) self._autoscale = autoscale self._style = style self._plot_kwargs = plot_kwargs self._title = title def _show(self, item): """Unravel the *item* for x and y so that it is plotted correctly.""" try: if len(item) != 2: raise ValueError('Plotting accepts only (x, y) pairs') except TypeError as exc: raise ValueError('Plotting accepts only (x, y) pairs') from exc if isinstance(item, q.Quantity): item = item.magnitude first, second = item if isinstance(first, q.Quantity): first = first.magnitude if isinstance(second, q.Quantity): second = second.magnitude item = (first, second) self._queue.put(('plot', item)) def _make_updater(self): return _PyplotUpdater(self._queue, self._style, self._plot_kwargs, self._autoscale, title=self._title) def reset(self): """Clear the plotted data.""" self._queue.put(('clear', None)) async def _get_style(self): return self._style async def _set_style(self, style): """Set line style to *style*.""" self._queue.put(('style', style)) self._style = style async def _get_autoscale(self): return self._autoscale async def _set_autoscale(self, autoscale): """Set *autoscale* on the axes, can be True or False.""" self._queue.put(('autoscale', autoscale)) self._autoscale = autoscale