From 3421e6bded5c26e154dfb2cf22801b4bf746f65c Mon Sep 17 00:00:00 2001 From: Brooke Ferber Date: Mon, 24 Jul 2017 11:54:18 -0400 Subject: [PATCH 01/13] DEV: added robot file --- xpdsim/robot.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 xpdsim/robot.py diff --git a/xpdsim/robot.py b/xpdsim/robot.py new file mode 100644 index 0000000..7e437bc --- /dev/null +++ b/xpdsim/robot.py @@ -0,0 +1,13 @@ +import bluesky.examples as be + + +class Robot(be.Mover): + # Include class variables (and the mpas) that appear in reference code? + def __init__(self, name, fields, initial_set, theta, sample_map, **kwargs): + theta = be.Mover('theta', {'rad': lambda x: x}, {'x': 0}) + self.theta = theta + self._current_sample_gemorety = None + super().__init__(name, fields, initial_set, **kwargs) + + # What functions should be included? + From 5ec26372302c2a853eee7e4c9ba807ff1c03b667 Mon Sep 17 00:00:00 2001 From: Brooke Ferber Date: Mon, 24 Jul 2017 14:28:13 -0400 Subject: [PATCH 02/13] DEV: Added load_sample function .ropeproject .gitignore --- xpdsim/robot.py | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/xpdsim/robot.py b/xpdsim/robot.py index 7e437bc..af01149 100644 --- a/xpdsim/robot.py +++ b/xpdsim/robot.py @@ -2,12 +2,48 @@ class Robot(be.Mover): - # Include class variables (and the mpas) that appear in reference code? - def __init__(self, name, fields, initial_set, theta, sample_map, **kwargs): + def __init__(self, name, fields, initial_set, theta, sample_map=None): theta = be.Mover('theta', {'rad': lambda x: x}, {'x': 0}) self.theta = theta - self._current_sample_gemorety = None + if sample_map is None: + # sample_map maps positions with image cycles @ build_image_cycle + # sample_map = {path (str): Cycler (iterable like object to cycle + # through images} + self._current_sample_geometry = None super().__init__(name, fields, initial_set, **kwargs) + def load_sample(self, saple_number, sample_geometry=None): + # If no sample is loaded, current_sample_number = 0 + # is reported by the robot + if self.current_sample_number.get() != 0: + raise RuntimeError("Sample %d is already loaded." + % self.current_sample_number.get()) + + #Rotate theta into loading position if necessary + load_pos = self.TH_POS[sample_geometry]['load'] + if load_post is not None: + print('Moving theta to load position') + self.theta.move(load_pos, wait=True) + + # Loading the sample is a three-step procedure: + # Set sample_number; issue load_cmd; issue execute_cmd. + set_and_wait(self.sample_number, sample_number) + set_and_wait(self.load_cmd, 1) + self.execute_cmd.put(1) + print('Loading...') + self._poll_until_idle() + + # Rotate theta into measurement position if necessary + measure_pos = self.TH_POS[sample_geometry]['measure'] + if measure_pos is not None: + print('Moving theta to measure position') + self.theta.move(measure_pos, wait=True) + + # Stash the current sample geomtery for reference when we unload + self._current_sample_geometry = sample_geometry + + + + # What functions should be included? From 3824d9e6407af498110334b5f3070408b4146ffb Mon Sep 17 00:00:00 2001 From: Brooke Ferber Date: Tue, 25 Jul 2017 16:45:33 -0400 Subject: [PATCH 03/13] DEV: Planned next stages for robot in cmmnts --- xpdsim/dets.py | 2 ++ xpdsim/robot.py | 16 +++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/xpdsim/dets.py b/xpdsim/dets.py index b243426..2bcd3d2 100644 --- a/xpdsim/dets.py +++ b/xpdsim/dets.py @@ -111,6 +111,8 @@ def trigger(self): def build_image_cycle(path): + # Goal: replace this with a mediator to the robot, to be handled in + # detector factory fxn """Build image cycles, essentially generators with endless images Parameters diff --git a/xpdsim/robot.py b/xpdsim/robot.py index af01149..9fdf558 100644 --- a/xpdsim/robot.py +++ b/xpdsim/robot.py @@ -1,7 +1,13 @@ +# Detector will get the sample information from the robot to determine which +# image to display. import bluesky.examples as be class Robot(be.Mover): + # create dummy components? Will need to find EpicsSignal syntax + sample_number = Cpt(EpicsSignal, 'ID:Tgt-SP') + current_sample_number = Cpt(EpicsSignalRO, 'Addr:CurrSmpl-I') + def __init__(self, name, fields, initial_set, theta, sample_map=None): theta = be.Mover('theta', {'rad': lambda x: x}, {'x': 0}) self.theta = theta @@ -9,6 +15,8 @@ def __init__(self, name, fields, initial_set, theta, sample_map=None): # sample_map maps positions with image cycles @ build_image_cycle # sample_map = {path (str): Cycler (iterable like object to cycle # through images} + # Will need sample info + # Is this TH_POS in API? self._current_sample_geometry = None super().__init__(name, fields, initial_set, **kwargs) @@ -19,7 +27,7 @@ def load_sample(self, saple_number, sample_geometry=None): raise RuntimeError("Sample %d is already loaded." % self.current_sample_number.get()) - #Rotate theta into loading position if necessary + # Rotate theta into loading position if necessary load_pos = self.TH_POS[sample_geometry]['load'] if load_post is not None: print('Moving theta to load position') @@ -41,9 +49,3 @@ def load_sample(self, saple_number, sample_geometry=None): # Stash the current sample geomtery for reference when we unload self._current_sample_geometry = sample_geometry - - - - - # What functions should be included? - From af1daa50949c59817cc99fec90b3549945eb3400 Mon Sep 17 00:00:00 2001 From: Brooke Ferber Date: Wed, 26 Jul 2017 13:53:23 -0400 Subject: [PATCH 04/13] DEV: Wrote robot class with ophyd --- xpdsim/dets.py | 6 ++-- xpdsim/robot.py | 87 +++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 73 insertions(+), 20 deletions(-) diff --git a/xpdsim/dets.py b/xpdsim/dets.py index 2bcd3d2..a7382bc 100644 --- a/xpdsim/dets.py +++ b/xpdsim/dets.py @@ -111,8 +111,6 @@ def trigger(self): def build_image_cycle(path): - # Goal: replace this with a mediator to the robot, to be handled in - # detector factory fxn """Build image cycles, essentially generators with endless images Parameters @@ -136,6 +134,7 @@ def build_image_cycle(path): def det_factory(name, fs, path, shutter=None, **kwargs): + # Revise HOW image cycle is handed to the detector """Build a detector using real images Parameters @@ -176,3 +175,6 @@ def dark_nexter(): return SimulatedPE1C(name, {'pe1_image': lambda: nexter()}, fs=fs, **kwargs) + + # Robot factory - will need to pass in a sample map + # From robot factory, extract info about what images detector should display diff --git a/xpdsim/robot.py b/xpdsim/robot.py index 9fdf558..fdc9cbf 100644 --- a/xpdsim/robot.py +++ b/xpdsim/robot.py @@ -1,26 +1,53 @@ -# Detector will get the sample information from the robot to determine which -# image to display. -import bluesky.examples as be +# Detector will get the sample information from the robot via a mediator to +# determine which image to display. +import time as ttime +from ophyd.utils import set_and_wait +from ophyd import EpicsSignal +from ophyd import Component as Cpt +from ophyd import Device -class Robot(be.Mover): - # create dummy components? Will need to find EpicsSignal syntax +class Robot(Device): + # sample_number = be.Mover('ID:Tgt-SP', + # {'ID:Tgt-SP': lambda x: x}, {'x': 0}) + # load_cmd = be.Mover('Cmd:Load-Cmd.PROC', + # {'Cmd:Load-Cmd.PROC': lambda x: x}, {'x': 0}) + # unload_cmd = be.Mover('Cmd:Unload-Cmd.PROC', + # {'Cmd:Unload-Cmd.PROC': lambda x: x}, {'x': 0}) + # execute_cmd = be.Mover('Cmd:Exec-Cmd', + # {'Cmd:Exec-Cmd': lambda x: x}, {'x': 0}) + # status = be.Mover('Sts-Sts', {'Sts-Sts': lambda x: x}, {'x': 0}) + # current_sample_number = be.Mover('Addr:CurrSmpl-I', + # {'Addr:CurrSmpl-I': lambda x: x}, {'x': 0}) + sample_number = Cpt(EpicsSignal, 'ID:Tgt-SP') - current_sample_number = Cpt(EpicsSignalRO, 'Addr:CurrSmpl-I') - - def __init__(self, name, fields, initial_set, theta, sample_map=None): - theta = be.Mover('theta', {'rad': lambda x: x}, {'x': 0}) - self.theta = theta - if sample_map is None: - # sample_map maps positions with image cycles @ build_image_cycle - # sample_map = {path (str): Cycler (iterable like object to cycle - # through images} - # Will need sample info - # Is this TH_POS in API? + load_cmd = Cpt(EpicsSignal, 'Cmd:Load-Cmd.PROC') + unload_cmd = Cpt(EpicsSignal, 'Cmd:Unload-Cmd.PROC') + execute_cmd = Cpt(EpicsSignal, 'Cmd:Exec-Cmd') + status = Cpt(EpicsSignal, 'Sts-Sts') + current_sample_number = Cpt(EpicsSignal, 'Addr:CurrSmpl-I') + + # Map sample types to load position and measurement position + TH_POS = {'capillary': {'load': None, 'measure': None}, + 'plate': {'load': 0, 'measure': 90}, + None: {'load': None, 'measure': None}} + + def __init__(self, name, fields, initial_set, theta, sample_map, **kwargs): + self.theta = theta # theta is a motor + self.sample_map = sample_map # sample_map is a dict self._current_sample_geometry = None super().__init__(name, fields, initial_set, **kwargs) - def load_sample(self, saple_number, sample_geometry=None): + def _poll_until_idle(self): + ttime.sleep(3) # gives robot plenty of time to start + while self.status.get() != 'Idle': + ttime.sleep(.1) + + def _poll_until_sample_cleared(self): + while self.current_sample_number.get() != 0: + ttime.sleep(.1) + + def load_sample(self, sample_number, sample_geometry=None): # If no sample is loaded, current_sample_number = 0 # is reported by the robot if self.current_sample_number.get() != 0: @@ -29,7 +56,7 @@ def load_sample(self, saple_number, sample_geometry=None): # Rotate theta into loading position if necessary load_pos = self.TH_POS[sample_geometry]['load'] - if load_post is not None: + if load_pos is not None: print('Moving theta to load position') self.theta.move(load_pos, wait=True) @@ -37,6 +64,7 @@ def load_sample(self, saple_number, sample_geometry=None): # Set sample_number; issue load_cmd; issue execute_cmd. set_and_wait(self.sample_number, sample_number) set_and_wait(self.load_cmd, 1) + # set_and_wait is an ophyd.utils import self.execute_cmd.put(1) print('Loading...') self._poll_until_idle() @@ -49,3 +77,26 @@ def load_sample(self, saple_number, sample_geometry=None): # Stash the current sample geomtery for reference when we unload self._current_sample_geometry = sample_geometry + + def unload_sample(self): + if self.current_sample_number.get() == 0: + # there is nothing to do + return + + # Rotate theta into loading position if necessary (e.g. flat plate + # model) + load_pos = self.TH_POS[self._current_sample_geomgery]['load'] + if load_pos is not None: + print('Moving theta to unload position') + self.theta.move(load_pos, wait=True) + + set_and_wait(self.unload_cmd, 1) + self.execute_cmd.put(1) + print('Unloading...') + self._poll_until_idle() + self._poll_until_sample_cleared() + self._current_sample_geometry = None + + def stop(self): + self.theta.stop() + super().stop() From 7d862ad7cfd7b3283bb5e03b3962d99c33bf6329 Mon Sep 17 00:00:00 2001 From: Brooke Ferber Date: Wed, 26 Jul 2017 15:54:37 -0400 Subject: [PATCH 05/13] ENH: Added robot factory to dets --- xpdsim/dets.py | 9 +++++++-- xpdsim/robot.py | 12 ------------ 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/xpdsim/dets.py b/xpdsim/dets.py index a7382bc..91b134d 100644 --- a/xpdsim/dets.py +++ b/xpdsim/dets.py @@ -176,5 +176,10 @@ def dark_nexter(): {'pe1_image': lambda: nexter()}, fs=fs, **kwargs) - # Robot factory - will need to pass in a sample map - # From robot factory, extract info about what images detector should display + +def robot_current_sample_number_getter(name, fs, path, Robot=None, **kwargs): + if Robot: + sample_map = {1: 'cycle1', 2: 'cycle2'} + return sample_map.get(Robot.current_sample_number) + # If I do it this way, do I need sample_map as a parameter in Robot? + # sample_map would correlate sample_number with cycle diff --git a/xpdsim/robot.py b/xpdsim/robot.py index fdc9cbf..3f69d44 100644 --- a/xpdsim/robot.py +++ b/xpdsim/robot.py @@ -8,18 +8,6 @@ class Robot(Device): - # sample_number = be.Mover('ID:Tgt-SP', - # {'ID:Tgt-SP': lambda x: x}, {'x': 0}) - # load_cmd = be.Mover('Cmd:Load-Cmd.PROC', - # {'Cmd:Load-Cmd.PROC': lambda x: x}, {'x': 0}) - # unload_cmd = be.Mover('Cmd:Unload-Cmd.PROC', - # {'Cmd:Unload-Cmd.PROC': lambda x: x}, {'x': 0}) - # execute_cmd = be.Mover('Cmd:Exec-Cmd', - # {'Cmd:Exec-Cmd': lambda x: x}, {'x': 0}) - # status = be.Mover('Sts-Sts', {'Sts-Sts': lambda x: x}, {'x': 0}) - # current_sample_number = be.Mover('Addr:CurrSmpl-I', - # {'Addr:CurrSmpl-I': lambda x: x}, {'x': 0}) - sample_number = Cpt(EpicsSignal, 'ID:Tgt-SP') load_cmd = Cpt(EpicsSignal, 'Cmd:Load-Cmd.PROC') unload_cmd = Cpt(EpicsSignal, 'Cmd:Unload-Cmd.PROC') From 8ca41619481ae4e1ff00e1b830fca97291f8b5f2 Mon Sep 17 00:00:00 2001 From: Brooke Ferber Date: Thu, 27 Jul 2017 15:00:00 -0400 Subject: [PATCH 06/13] ENH: Revised robot method --- xpdsim/dets.py | 5 ++++- xpdsim/robot.py | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/xpdsim/dets.py b/xpdsim/dets.py index 91b134d..5f95427 100644 --- a/xpdsim/dets.py +++ b/xpdsim/dets.py @@ -151,6 +151,7 @@ def det_factory(name, fs, path, shutter=None, **kwargs): detector: SimulatedPE1C instance The detector """ + cycle = build_image_cycle( path) gen = cycle() @@ -177,9 +178,11 @@ def dark_nexter(): **kwargs) -def robot_current_sample_number_getter(name, fs, path, Robot=None, **kwargs): +def robot_current_sample_number_getter(Robot=None, **kwargs): if Robot: sample_map = {1: 'cycle1', 2: 'cycle2'} return sample_map.get(Robot.current_sample_number) # If I do it this way, do I need sample_map as a parameter in Robot? # sample_map would correlate sample_number with cycle + # I want to call this function in det_factory, so where should I + # construct the robot? Pass the robot to det_factory as a parameter? diff --git a/xpdsim/robot.py b/xpdsim/robot.py index 3f69d44..262a580 100644 --- a/xpdsim/robot.py +++ b/xpdsim/robot.py @@ -20,6 +20,8 @@ class Robot(Device): 'plate': {'load': 0, 'measure': 90}, None: {'load': None, 'measure': None}} + # init is unlike robot api - that produces error message + # how to resolve ? def __init__(self, name, fields, initial_set, theta, sample_map, **kwargs): self.theta = theta # theta is a motor self.sample_map = sample_map # sample_map is a dict From a522f44edec193dfa3d55cf3919e88168f28c846 Mon Sep 17 00:00:00 2001 From: Brooke Ferber Date: Thu, 27 Jul 2017 15:01:29 -0400 Subject: [PATCH 07/13] DEV: robot test --- xpdsim/tests/test_robot.py | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 xpdsim/tests/test_robot.py diff --git a/xpdsim/tests/test_robot.py b/xpdsim/tests/test_robot.py new file mode 100644 index 0000000..670ffc5 --- /dev/null +++ b/xpdsim/tests/test_robot.py @@ -0,0 +1,11 @@ +from ..robot import Robot +import bluesky.examples as be + + +def test_robot(): + th = be.Mover('theta', {'theta': lambda x: x}, {'x': 0}) + sm = {1: 'cycle1', 2: 'cycle2'} + r = Robot('XF:28IDC-ES:1{SM}', {lambda x: x}, {'x': 0}, + theta=th, sample_map=sm) + # Crashes : 2 extra parameters + assert r.sample_map.get(1) == 'cycle1' From 0ea77c743f2842d48da307ccb308b77903514f57 Mon Sep 17 00:00:00 2001 From: Brooke Ferber Date: Mon, 31 Jul 2017 11:15:13 -0400 Subject: [PATCH 08/13] DEV: Revising Robot init method --- xpdsim/dets.py | 6 +++--- xpdsim/robot.py | 20 +++++++++++--------- xpdsim/tests/test_robot.py | 9 ++++----- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/xpdsim/dets.py b/xpdsim/dets.py index 5f95427..1da4d64 100644 --- a/xpdsim/dets.py +++ b/xpdsim/dets.py @@ -151,7 +151,7 @@ def det_factory(name, fs, path, shutter=None, **kwargs): detector: SimulatedPE1C instance The detector """ - + robot_current_sample_number_getter() cycle = build_image_cycle( path) gen = cycle() @@ -182,7 +182,7 @@ def robot_current_sample_number_getter(Robot=None, **kwargs): if Robot: sample_map = {1: 'cycle1', 2: 'cycle2'} return sample_map.get(Robot.current_sample_number) - # If I do it this way, do I need sample_map as a parameter in Robot? - # sample_map would correlate sample_number with cycle + # I think this would eliminate the need for sample_map to be a parameter + # of Robot # I want to call this function in det_factory, so where should I # construct the robot? Pass the robot to det_factory as a parameter? diff --git a/xpdsim/robot.py b/xpdsim/robot.py index 262a580..9a26dc8 100644 --- a/xpdsim/robot.py +++ b/xpdsim/robot.py @@ -1,10 +1,13 @@ # Detector will get the sample information from the robot via a mediator to # determine which image to display. -import time as ttime -from ophyd.utils import set_and_wait -from ophyd import EpicsSignal +import asyncio +from ophyd import Device, EpicsSignal, EpicsSignalRO from ophyd import Component as Cpt -from ophyd import Device +from ophyd.utils import set_and_wait +from bluesky import Msg +from bluesky.plans import subs_wrapper, count, list_scan, single_gen +from bluesky.callbacks import LiveTable +import time as ttime class Robot(Device): @@ -20,13 +23,12 @@ class Robot(Device): 'plate': {'load': 0, 'measure': 90}, None: {'load': None, 'measure': None}} - # init is unlike robot api - that produces error message - # how to resolve ? - def __init__(self, name, fields, initial_set, theta, sample_map, **kwargs): + # I've taken init from the Robot API - why is the syntax invalid? + def __init__(self, *args, theta, diff=None, **kwargs): self.theta = theta # theta is a motor - self.sample_map = sample_map # sample_map is a dict + # self.sample_map = sample_map # sample_map is a dict self._current_sample_geometry = None - super().__init__(name, fields, initial_set, **kwargs) + super().__init__(*args, **kwargs) def _poll_until_idle(self): ttime.sleep(3) # gives robot plenty of time to start diff --git a/xpdsim/tests/test_robot.py b/xpdsim/tests/test_robot.py index 670ffc5..89db9c3 100644 --- a/xpdsim/tests/test_robot.py +++ b/xpdsim/tests/test_robot.py @@ -4,8 +4,7 @@ def test_robot(): th = be.Mover('theta', {'theta': lambda x: x}, {'x': 0}) - sm = {1: 'cycle1', 2: 'cycle2'} - r = Robot('XF:28IDC-ES:1{SM}', {lambda x: x}, {'x': 0}, - theta=th, sample_map=sm) - # Crashes : 2 extra parameters - assert r.sample_map.get(1) == 'cycle1' + robot = Robot('XF:28IDC-ES:1{SM}', theta=th) + # Crashes : Cannot find Epics CA DLL, but taken from Robot API reference + # file? +# What features of the robot should be tested ? From 8ee1004848a3c615a22210f60b7b3ef762714e99 Mon Sep 17 00:00:00 2001 From: Brooke Ferber Date: Wed, 2 Aug 2017 14:37:54 -0400 Subject: [PATCH 09/13] ENH: Added robot to det and det_factory --- xpdsim/dets.py | 96 ++++++++++++++++---------------------- xpdsim/robot.py | 5 +- xpdsim/tests/test_dets.py | 3 +- xpdsim/tests/test_robot.py | 7 ++- 4 files changed, 46 insertions(+), 65 deletions(-) diff --git a/xpdsim/dets.py b/xpdsim/dets.py index 1da4d64..1120176 100644 --- a/xpdsim/dets.py +++ b/xpdsim/dets.py @@ -23,7 +23,7 @@ from cycler import cycler from pims import ImageSequence from pkg_resources import resource_filename as rs_fn -from bluesky.utils import new_uid +from .robot import Robot DATA_DIR = rs_fn('xpdsim', 'data/') @@ -61,11 +61,12 @@ class SimulatedPE1C(be.ReaderWithFileStore): """ def __init__(self, name, read_fields, fs, shutter=None, - dark_fields=None, **kwargs): + dark_fields=None, Robot=None, **kwargs): self.images_per_set = PutGet() self.number_of_sets = PutGet() self.cam = SimulatedCam() self.shutter = shutter + self.robot = Robot self._staged = False super().__init__(name, read_fields, fs=fs, **kwargs) self.ready = True # work around a hack in Reader @@ -75,39 +76,19 @@ def __init__(self, name, read_fields, fs, shutter=None, else: self._dark_fields = None - def trigger(self): + def trigger_read(self): if self.shutter and self._dark_fields and \ - self.shutter.read()['rad']['value'] == 0: - read_v = {field: {'value': func(), 'timestamp': ttime.time()} - for field, func in self._dark_fields.items() - if field in self.read_attrs} - self._result.clear() - for idx, (name, reading) in enumerate(read_v.items()): - # Save the actual reading['value'] to disk and create a record - # in FileStore. - np.save('{}_{}.npy'.format(self._path_stem, idx), - reading['value']) - datum_id = new_uid() - self.fs.insert_datum(self._resource_id, datum_id, - dict(index=idx)) - # And now change the reading in place, replacing the value with - # a reference to FileStore. - reading['value'] = datum_id - self._result[name] = reading - - delay_time = self.exposure_time - if delay_time: - if self.loop.is_running(): - st = be.SimpleStatus() - self.loop.call_later(delay_time, st._finished) - return st - else: - ttime.sleep(delay_time) - - return be.NullStatus() - + self.shutter.read()['rad']['value'] == 0: + rv = {field: {'value': func(), 'timestamp': ttime.time()} + for field, func in self._dark_fields.items() + if field in self.read_attrs} + print('======Triggered shutter======') + print(rv) else: - return super().trigger() + rv = super().trigger_read() + read_v = dict(rv) + read_v['pe1_image']['value'] = read_v['pe1_image']['value'].copy() + return read_v def build_image_cycle(path): @@ -133,8 +114,11 @@ def build_image_cycle(path): chess_path = os.path.join(DATA_DIR, 'chess/') -def det_factory(name, fs, path, shutter=None, **kwargs): - # Revise HOW image cycle is handed to the detector +def robot_factory(self, theta, sample_map=None, **kwargs): + return Robot(theta, sample_map) + + +def det_factory(name, fs, path, shutter=None, Robot=None, **kwargs): """Build a detector using real images Parameters @@ -151,9 +135,7 @@ def det_factory(name, fs, path, shutter=None, **kwargs): detector: SimulatedPE1C instance The detector """ - robot_current_sample_number_getter() - cycle = build_image_cycle( - path) + cycle = build_image_cycle(path) gen = cycle() def nexter(): @@ -162,27 +144,27 @@ def nexter(): if shutter: stream_piece = next(gen) sample_img = stream_piece['pe1_image'] - gen = chain((i for i in [stream_piece]), gen) # put the piece on top + gen = chain((i for i in [stream_piece]), gen) + # put the piece on top def dark_nexter(): return np.zeros(sample_img.shape) - return SimulatedPE1C(name, - {'pe1_image': lambda: nexter()}, fs=fs, - shutter=shutter, - dark_fields={'pe1_image': lambda: dark_nexter()}, + if Robot is None: + return SimulatedPE1C(name, {'pe1_image': lambda: nexter()}, + fs=fs, shutter=shutter, dark_fields= + {'pe1_image': lambda: dark_nexter()}, + **kwargs) + else: + return SimulatedPE1C(name, {'pe1_image': lambda: nexter()}, + fs=fs, shutter=shutter, + dark_fields={'pe1_image': lambda: + dark_nexter()}, + Robot=Robot, **kwargs) + + if Robot is None: + return SimulatedPE1C(name, {'pe1_image': lambda: nexter()}, fs=fs, **kwargs) - - return SimulatedPE1C(name, - {'pe1_image': lambda: nexter()}, fs=fs, - **kwargs) - - -def robot_current_sample_number_getter(Robot=None, **kwargs): - if Robot: - sample_map = {1: 'cycle1', 2: 'cycle2'} - return sample_map.get(Robot.current_sample_number) - # I think this would eliminate the need for sample_map to be a parameter - # of Robot - # I want to call this function in det_factory, so where should I - # construct the robot? Pass the robot to det_factory as a parameter? + else: + return SimulatedPE1C(name, {'pe1_image': lambda: nexter()}, fs=fs, + Robot=Robot, **kwargs) diff --git a/xpdsim/robot.py b/xpdsim/robot.py index 9a26dc8..dcebd49 100644 --- a/xpdsim/robot.py +++ b/xpdsim/robot.py @@ -23,10 +23,9 @@ class Robot(Device): 'plate': {'load': 0, 'measure': 90}, None: {'load': None, 'measure': None}} - # I've taken init from the Robot API - why is the syntax invalid? - def __init__(self, *args, theta, diff=None, **kwargs): + def __init__(self, theta, *args, sample_map=None, **kwargs): self.theta = theta # theta is a motor - # self.sample_map = sample_map # sample_map is a dict + self.sample_map = sample_map # sample_map is a dict self._current_sample_geometry = None super().__init__(*args, **kwargs) diff --git a/xpdsim/tests/test_dets.py b/xpdsim/tests/test_dets.py index d7c4f27..72221cd 100644 --- a/xpdsim/tests/test_dets.py +++ b/xpdsim/tests/test_dets.py @@ -41,12 +41,13 @@ def test_dets_shutter(db, tmp_dir, name, fp): for n, d in db.restream(db[-1], fill=True): if n == 'event': assert_array_equal(d['data']['pe1_image'], - np.zeros(next(cg)['pe1_image'].shape)) + np.zeros(d['data']['pe1_image'].shape)) assert uid is not None # With the shutter up RE(abs_set(shctl1, 1, wait=True)) uid = RE(scan) + next(cg) for n, d in db.restream(db[-1], fill=True): if n == 'event': assert_array_equal(d['data']['pe1_image'], next(cg)['pe1_image']) diff --git a/xpdsim/tests/test_robot.py b/xpdsim/tests/test_robot.py index 89db9c3..44f7c4b 100644 --- a/xpdsim/tests/test_robot.py +++ b/xpdsim/tests/test_robot.py @@ -4,7 +4,6 @@ def test_robot(): th = be.Mover('theta', {'theta': lambda x: x}, {'x': 0}) - robot = Robot('XF:28IDC-ES:1{SM}', theta=th) - # Crashes : Cannot find Epics CA DLL, but taken from Robot API reference - # file? -# What features of the robot should be tested ? + s_m = {'sample1': 'img1', 'sample2': 'img2'} + r = Robot(th, sample_map=s_m) + assert r.sample_map.get('sample1') is 'img1' From c53114c876a1d88c04a878237d4248d4f4fcaede Mon Sep 17 00:00:00 2001 From: Brooke Ferber Date: Thu, 3 Aug 2017 13:40:57 -0400 Subject: [PATCH 10/13] ENH: Added robot functionaltiy to trigger_read --- xpdsim/dets.py | 17 +++++++++++------ xpdsim/robot.py | 7 +++++-- xpdsim/tests/test_robot.py | 4 +++- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/xpdsim/dets.py b/xpdsim/dets.py index 1120176..bb41a6e 100644 --- a/xpdsim/dets.py +++ b/xpdsim/dets.py @@ -65,8 +65,9 @@ def __init__(self, name, read_fields, fs, shutter=None, self.images_per_set = PutGet() self.number_of_sets = PutGet() self.cam = SimulatedCam() + self.current_sample_num = None self.shutter = shutter - self.robot = Robot + self.Robot = Robot self._staged = False super().__init__(name, read_fields, fs=fs, **kwargs) self.ready = True # work around a hack in Reader @@ -77,13 +78,17 @@ def __init__(self, name, read_fields, fs, shutter=None, self._dark_fields = None def trigger_read(self): + if self.Robot: + if self.Robot.get_current_sample_number() != \ + self.current_sample_num: + self.current_sample_num = self.Robot.get_current_sample_number() + self.read_fiels = self.Robot.read() + if self.shutter and self._dark_fields and \ self.shutter.read()['rad']['value'] == 0: rv = {field: {'value': func(), 'timestamp': ttime.time()} for field, func in self._dark_fields.items() if field in self.read_attrs} - print('======Triggered shutter======') - print(rv) else: rv = super().trigger_read() read_v = dict(rv) @@ -152,9 +157,9 @@ def dark_nexter(): if Robot is None: return SimulatedPE1C(name, {'pe1_image': lambda: nexter()}, - fs=fs, shutter=shutter, dark_fields= - {'pe1_image': lambda: dark_nexter()}, - **kwargs) + fs=fs, shutter=shutter, + dark_fields={'pe1_image': lambda: + dark_nexter()}, **kwargs) else: return SimulatedPE1C(name, {'pe1_image': lambda: nexter()}, fs=fs, shutter=shutter, diff --git a/xpdsim/robot.py b/xpdsim/robot.py index dcebd49..cc9a7fd 100644 --- a/xpdsim/robot.py +++ b/xpdsim/robot.py @@ -10,7 +10,7 @@ import time as ttime -class Robot(Device): +class Robot: sample_number = Cpt(EpicsSignal, 'ID:Tgt-SP') load_cmd = Cpt(EpicsSignal, 'Cmd:Load-Cmd.PROC') unload_cmd = Cpt(EpicsSignal, 'Cmd:Unload-Cmd.PROC') @@ -27,7 +27,10 @@ def __init__(self, theta, *args, sample_map=None, **kwargs): self.theta = theta # theta is a motor self.sample_map = sample_map # sample_map is a dict self._current_sample_geometry = None - super().__init__(*args, **kwargs) + # super().__init__(*args, **kwargs) + + def get_current_sample_number(self): + return self.current_sample_number def _poll_until_idle(self): ttime.sleep(3) # gives robot plenty of time to start diff --git a/xpdsim/tests/test_robot.py b/xpdsim/tests/test_robot.py index 44f7c4b..64b1521 100644 --- a/xpdsim/tests/test_robot.py +++ b/xpdsim/tests/test_robot.py @@ -5,5 +5,7 @@ def test_robot(): th = be.Mover('theta', {'theta': lambda x: x}, {'x': 0}) s_m = {'sample1': 'img1', 'sample2': 'img2'} - r = Robot(th, sample_map=s_m) + r = Robot('PV_PREFIX:', th, sample_map=s_m) assert r.sample_map.get('sample1') is 'img1' + + From f6656f5a0391462c372cf6e03942680479fe2062 Mon Sep 17 00:00:00 2001 From: Brooke Ferber Date: Thu, 3 Aug 2017 16:29:24 -0400 Subject: [PATCH 11/13] ENH: Added sample num to robot test --- xpdsim/tests/test_robot.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/xpdsim/tests/test_robot.py b/xpdsim/tests/test_robot.py index 64b1521..6902ab7 100644 --- a/xpdsim/tests/test_robot.py +++ b/xpdsim/tests/test_robot.py @@ -1,11 +1,15 @@ -from ..robot import Robot import bluesky.examples as be +import pytest +from ..dets import robot_factory, nsls_ii_path, chess_path, det_factory +test_params = [('nslsii', nsls_ii_path), ('chess', chess_path)] + +@pytest.mark.parametrize(('name', 'fp'), test_params) def test_robot(): th = be.Mover('theta', {'theta': lambda x: x}, {'x': 0}) - s_m = {'sample1': 'img1', 'sample2': 'img2'} - r = Robot('PV_PREFIX:', th, sample_map=s_m) - assert r.sample_map.get('sample1') is 'img1' - - + s_m = {'nslsii': nsls_ii_path, 'chess': chess_path} + r = robot_factory(th, sample_map=s_m) + print(r.current_sample_number) + assert r.sample_map.get('nslsii') is nsls_ii_path + det_factory(name, db.fs, fp, save_path=tmp_dir, Robot=r) From 699303a83eee04c49adfa14c82ebc04a42b32bda Mon Sep 17 00:00:00 2001 From: Brooke Ferber Date: Fri, 4 Aug 2017 16:52:39 -0400 Subject: [PATCH 12/13] ENH: Added sample map to robot test --- xpdsim/dets.py | 17 ++++++++++------- xpdsim/robot.py | 17 ++++++++++++----- xpdsim/tests/test_robot.py | 38 +++++++++++++++++++++++++++++--------- 3 files changed, 51 insertions(+), 21 deletions(-) diff --git a/xpdsim/dets.py b/xpdsim/dets.py index bb41a6e..5844454 100644 --- a/xpdsim/dets.py +++ b/xpdsim/dets.py @@ -65,7 +65,8 @@ def __init__(self, name, read_fields, fs, shutter=None, self.images_per_set = PutGet() self.number_of_sets = PutGet() self.cam = SimulatedCam() - self.current_sample_num = None + self.read_fields = read_fields + self.sample_num = None self.shutter = shutter self.Robot = Robot self._staged = False @@ -79,10 +80,12 @@ def __init__(self, name, read_fields, fs, shutter=None, def trigger_read(self): if self.Robot: - if self.Robot.get_current_sample_number() != \ - self.current_sample_num: - self.current_sample_num = self.Robot.get_current_sample_number() - self.read_fiels = self.Robot.read() + if self.Robot.get_sample_number() != \ + self.sample_num: + self.sample_num = self.Robot.get_sample_number() + self.read_fields.update({k: v for k, v in + self.Robot.get_fields_dict().items if k + in self.read_fields.keys()}) if self.shutter and self._dark_fields and \ self.shutter.read()['rad']['value'] == 0: @@ -119,8 +122,8 @@ def build_image_cycle(path): chess_path = os.path.join(DATA_DIR, 'chess/') -def robot_factory(self, theta, sample_map=None, **kwargs): - return Robot(theta, sample_map) +def robot_factory(theta, sample_map=None, **kwargs): + return Robot(theta, sample_map=sample_map) def det_factory(name, fs, path, shutter=None, Robot=None, **kwargs): diff --git a/xpdsim/robot.py b/xpdsim/robot.py index cc9a7fd..0e3fd36 100644 --- a/xpdsim/robot.py +++ b/xpdsim/robot.py @@ -3,6 +3,7 @@ import asyncio from ophyd import Device, EpicsSignal, EpicsSignalRO from ophyd import Component as Cpt +from ophyd import PseudoPositioner, PseudoSingle from ophyd.utils import set_and_wait from bluesky import Msg from bluesky.plans import subs_wrapper, count, list_scan, single_gen @@ -10,8 +11,9 @@ import time as ttime -class Robot: - sample_number = Cpt(EpicsSignal, 'ID:Tgt-SP') +class Robot(): + # pseudo positioners + sample_number = Cpt(PseudoSingle) load_cmd = Cpt(EpicsSignal, 'Cmd:Load-Cmd.PROC') unload_cmd = Cpt(EpicsSignal, 'Cmd:Unload-Cmd.PROC') execute_cmd = Cpt(EpicsSignal, 'Cmd:Exec-Cmd') @@ -25,12 +27,17 @@ class Robot: def __init__(self, theta, *args, sample_map=None, **kwargs): self.theta = theta # theta is a motor - self.sample_map = sample_map # sample_map is a dict + self.sample_map = sample_map + # sample_map = {sample #: {'pe1_image: nexter} self._current_sample_geometry = None # super().__init__(*args, **kwargs) - def get_current_sample_number(self): - return self.current_sample_number + def get_fields_dict(self): + n = self.get_sample_number() + return self.sample_map[n] + + def get_sample_number(self): + return self.sample_number def _poll_until_idle(self): ttime.sleep(3) # gives robot plenty of time to start diff --git a/xpdsim/tests/test_robot.py b/xpdsim/tests/test_robot.py index 6902ab7..326b92b 100644 --- a/xpdsim/tests/test_robot.py +++ b/xpdsim/tests/test_robot.py @@ -1,15 +1,35 @@ import bluesky.examples as be -import pytest -from ..dets import robot_factory, nsls_ii_path, chess_path, det_factory +from bluesky.plans import Count +from bluesky.tests.utils import setup_test_run_engine +from numpy.testing import assert_array_equal +from ..dets import robot_factory, nsls_ii_path, build_image_cycle, \ + chess_path, det_factory + test_params = [('nslsii', nsls_ii_path), ('chess', chess_path)] -@pytest.mark.parametrize(('name', 'fp'), test_params) -def test_robot(): +# @pytest.mark.parametrize(('name', 'fp'), test_params) +def test_robot(db, tmp_dir): th = be.Mover('theta', {'theta': lambda x: x}, {'x': 0}) - s_m = {'nslsii': nsls_ii_path, 'chess': chess_path} - r = robot_factory(th, sample_map=s_m) - print(r.current_sample_number) - assert r.sample_map.get('nslsii') is nsls_ii_path - det_factory(name, db.fs, fp, save_path=tmp_dir, Robot=r) + sm = {} + for i, (name, path) in enumerate(test_params): + cycle = build_image_cycle(path) + gen = cycle() + + def nexter(): + return next(gen)['pe1_image'] + sm[i] = {'pe1_image': lambda: nexter()} + r = robot_factory(th, sample_map=sm) + det = det_factory(name, db.fs, path, save_path=tmp_dir, Robot=r) + RE = setup_test_run_engine() + RE.subscribe('all', db.mds.insert) + scan = Count([det], ) + uid = RE(scan) + db.fs.register_handler('RWFS_NPY', be.ReaderWithFSHandler) + cycle2 = build_image_cycle() + cg = cycle2() + for n, d in db.restream(db[-1], fill=True): + if n == 'event': + assert_array_equal(d['data']['pe1_image'], next(cg)['pe1_image']) + assert uid is not None From db47d440c395d4bca1fbf24e84193a6705edf724 Mon Sep 17 00:00:00 2001 From: Brooke Ferber Date: Wed, 9 Aug 2017 15:38:59 -0400 Subject: [PATCH 13/13] DEV: Changed to Signals to fake device --- .travis.yml | 2 +- xpdsim/robot.py | 19 +++++++++---------- xpdsim/tests/test_robot.py | 2 +- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5dbf2ec..0a8364d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -53,7 +53,7 @@ before_install: install: - export GIT_FULL_HASH=`git rev-parse HEAD` - - conda create -n testenv nose python=$TRAVIS_PYTHON_VERSION pytest coverage pip databroker bluesky flake8 pyFAI mongoquery codecov attrs metadatastore filestore -c conda-forge -c lightsource2-tag -c soft-matter + - conda create -n testenv nose python=$TRAVIS_PYTHON_VERSION pytest coverage pip databroker bluesky flake8 pyFAI mongoquery codecov attrs metadatastore filestore -c conda-forge -c lightsource2-tag -c soft-matter ophyd - source activate testenv - 'pip install https://github.com/NSLS-II/portable-mds/zipball/master#egg=portable_mds' - 'pip install https://github.com/NSLS-II/portable-fs/zipball/master#egg=portable_fs' diff --git a/xpdsim/robot.py b/xpdsim/robot.py index 0e3fd36..1334f8e 100644 --- a/xpdsim/robot.py +++ b/xpdsim/robot.py @@ -3,7 +3,7 @@ import asyncio from ophyd import Device, EpicsSignal, EpicsSignalRO from ophyd import Component as Cpt -from ophyd import PseudoPositioner, PseudoSingle +from ophyd import Signal from ophyd.utils import set_and_wait from bluesky import Msg from bluesky.plans import subs_wrapper, count, list_scan, single_gen @@ -11,14 +11,13 @@ import time as ttime -class Robot(): - # pseudo positioners - sample_number = Cpt(PseudoSingle) - load_cmd = Cpt(EpicsSignal, 'Cmd:Load-Cmd.PROC') - unload_cmd = Cpt(EpicsSignal, 'Cmd:Unload-Cmd.PROC') - execute_cmd = Cpt(EpicsSignal, 'Cmd:Exec-Cmd') - status = Cpt(EpicsSignal, 'Sts-Sts') - current_sample_number = Cpt(EpicsSignal, 'Addr:CurrSmpl-I') +class Robot(Device): + sample_number = Cpt(Signal, value=0) + load_cmd = Cpt(Signal, value=0) + unload_cmd = Cpt(Signal, value=0) + execute_cmd = Cpt(Signal, value=0) + status = Cpt(Signal, value=0) + current_sample_number = Cpt(Signal, value=0) # Map sample types to load position and measurement position TH_POS = {'capillary': {'load': None, 'measure': None}, @@ -30,7 +29,7 @@ def __init__(self, theta, *args, sample_map=None, **kwargs): self.sample_map = sample_map # sample_map = {sample #: {'pe1_image: nexter} self._current_sample_geometry = None - # super().__init__(*args, **kwargs) + super().__init__("", *args, **kwargs) def get_fields_dict(self): n = self.get_sample_number() diff --git a/xpdsim/tests/test_robot.py b/xpdsim/tests/test_robot.py index 326b92b..a788e09 100644 --- a/xpdsim/tests/test_robot.py +++ b/xpdsim/tests/test_robot.py @@ -20,7 +20,7 @@ def test_robot(db, tmp_dir): def nexter(): return next(gen)['pe1_image'] sm[i] = {'pe1_image': lambda: nexter()} - r = robot_factory(th, sample_map=sm) + r = robot_factory(th, sample_map=sm) det = det_factory(name, db.fs, path, save_path=tmp_dir, Robot=r) RE = setup_test_run_engine() RE.subscribe('all', db.mds.insert)