Пример #1
0
    def set(self, val):
        if self._set_st is not None:
            raise RuntimeError(f'trying to set {self.name}'
                               ' while a set is in progress')

        cmd_map = {self.open_str: self.open_cmd,
                   self.close_str: self.close_cmd}
        target_map = {self.open_str: self.open_val,
                      self.close_str: self.close_val}

        cmd_sig = cmd_map[val]
        target_val = target_map[val]

        st = DeviceStatus(self)
        if self.status.get() == target_val:
            st._finished()
            return st

        self._set_st = st
        print(self.name, val, id(st))
        enums = self.status.enum_strs

        def shutter_cb(value, timestamp, **kwargs):
            value = enums[int(value)]
            if value == target_val:
                self._set_st = None
                self.status.clear_sub(shutter_cb)
                st._finished()

        cmd_enums = cmd_sig.enum_strs
        count = 0

        def cmd_retry_cb(value, timestamp, **kwargs):
            nonlocal count
            value = cmd_enums[int(value)]
            count += 1
            if count > self.MAX_ATTEMPTS:
                cmd_sig.clear_sub(cmd_retry_cb)
                self._set_st = None
                self.status.clear_sub(shutter_cb)
                st._finished(success=False)
            if value == 'None':
                if not st.done:
                    time.sleep(self.RETRY_PERIOD)
                    cmd_sig.set(1)

                    ts = datetime.datetime.fromtimestamp(timestamp) \
                        .strftime(_time_fmtstr)
                    if count > 2:
                        msg = '** ({}) Had to reactuate shutter while {}ing'
                        print(msg.format(ts, val if val != 'Close'
                                         else val[:-1]))
                else:
                    cmd_sig.clear_sub(cmd_retry_cb)

        cmd_sig.subscribe(cmd_retry_cb, run=False)
        self.status.subscribe(shutter_cb)
        cmd_sig.set(1)

        return st
Пример #2
0
class SaturnSoftTrigger(BlueskyInterface):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._status = None
        self._acquisition_signal = self.mca.erase_start

        self.stage_sigs[self.mca.stop_signal] = 1
        self.stage_sigs[self.dxp.preset_mode] = 'Real time'
        #self.stage_sigs[self.dxp.preset_mode] = 'Live time'

        self._count_signal = self.mca.preset_real_time
        self._count_time = None

    def stage(self):
        if self._count_time is not None:
            self.stage_sigs[self._count_signal] = self._count_time

        super().stage()

    def unstage(self):
        try:
            super().unstage()
        finally:
            if self._count_signal in self.stage_sigs:
                del self.stage_sigs[self._count_signal]
                self._count_time = None

    def trigger(self):
        "Trigger one acquisition."
        if self._staged != Staged.yes:
            raise RuntimeError("This detector is not ready to trigger."
                               "Call the stage() method before triggering.")

        self._status = DeviceStatus(self)
        self._acquisition_signal.put(1, callback=self._acquisition_done)
        return self._status

    def _acquisition_done(self, **kwargs):
        '''pyepics callback for when put completion finishes'''
        if self._status is not None:
            self._status._finished()
            self._status = None

    @property
    def count_time(self):
        '''Exposure time, as set by bluesky'''
        return self._count_time

    @count_time.setter
    def count_time(self, count_time):
        self._count_time = count_time
Пример #3
0
    def trigger(self):
        status = DeviceStatus(self)

        if self.status:
            status._finished()
        else:
            self._statuses.append(status)

        if not status.done:
            logger.warning('---')
            logger.warning('Waiting for beam status to change...')
            logger.warning('---')

        return status
Пример #4
0
class SaturnSoftTrigger(BlueskyInterface):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._status = None
        self._acquisition_signal = self.mca.erase_start

        self.stage_sigs[self.mca.stop_signal] = 1
        self.stage_sigs[self.dxp.preset_mode] = 'Real time'

        self._count_signal = self.mca.preset_real_time
        self._count_time = None

    def stage(self):
        if self._count_time is not None:
            self.stage_sigs[self._count_signal] = self._count_time

        super().stage()

    def unstage(self):
        try:
            super().unstage()
        finally:
            if self._count_signal in self.stage_sigs:
                del self.stage_sigs[self._count_signal]
                self._count_time = None

    def trigger(self):
        "Trigger one acquisition."
        if self._staged != Staged.yes:
            raise RuntimeError("This detector is not ready to trigger."
                               "Call the stage() method before triggering.")

        self._status = DeviceStatus(self)
        self._acquisition_signal.put(1, callback=self._acquisition_done)
        return self._status

    def _acquisition_done(self, **kwargs):
        '''pyepics callback for when put completion finishes'''
        if self._status is not None:
            self._status._finished()
            self._status = None

    @property
    def count_time(self):
        '''Exposure time, as set by bluesky'''
        return self._count_time

    @count_time.setter
    def count_time(self, count_time):
        self._count_time = count_time
Пример #5
0
    def wait_until_settled(self, timeout=None, timeout_fail=False):
        """
        plan: wait for controller signal to reach target within tolerance
        """
        # see: https://stackoverflow.com/questions/2829329/catch-a-threads-exception-in-the-caller-thread-in-python
        t0 = time.time()
        _st = DeviceStatus(self.signal)

        if self.settled:
            # just in case signal already at target
            _st._finished(success=True)
        else:
            started = False

            def changing_cb(*args, **kwargs):
                if started and self.settled:
                    _st._finished(success=True)

            token = self.signal.subscribe(changing_cb)
            started = True
            report = 0
            while not _st.done and not self.settled:
                elapsed = time.time() - t0
                if timeout is not None and elapsed > timeout:
                    _st._finished(success=self.settled)
                    msg = f"{self.controller_name} Timeout after {elapsed:.2f}s"
                    msg += f", target {self.target.get():.2f}{self.units.get()}"
                    msg += f", now {self.signal.get():.2f}{self.units.get()}"
                    print(msg)
                    if timeout_fail:
                        raise TimeoutError(msg)
                    continue
                if elapsed >= report:
                    report += self.report_interval_s
                    msg = f"Waiting {elapsed:.1f}s"
                    msg += f" to reach {self.target.get():.2f}{self.units.get()}"
                    msg += f", now {self.signal.get():.2f}{self.units.get()}"
                    print(msg)
                yield from bps.sleep(self.poll_s)

            self.signal.unsubscribe(token)

        self.record_signal()
        elapsed = time.time() - t0
        print(f"Total time: {elapsed:.3f}s, settled:{_st.success}")
    def kickoff(self):
        self._running_status = running_status = DeviceStatus(self)
        holding_status = DeviceStatus(self)

        if self.hold.get():
            def state_cb(value, old_value, **kwargs):
                if old_value != 2 and value == 2:
                    holding_status._finished()
                    self.state.clear_sub(state_cb)

            self.state.subscribe(state_cb, run=False)
        else:
            holding_status._finished()

        self.go.put(1, wait=True)

        def running_cb(value, old_value, obj, **kwargs):
            if old_value == 1 and value == 0:
                obj.clear_sub(running_cb)
                running_status._finished()

        self.running.subscribe(running_cb, run=False)
        return holding_status
    def kickoff(self):
        self._running_status = running_status = DeviceStatus(self)
        holding_status = DeviceStatus(self)

        if self.hold.get():

            def state_cb(value, old_value, **kwargs):
                if old_value != 2 and value == 2:
                    holding_status._finished()
                    self.state.clear_sub(state_cb)

            self.state.subscribe(state_cb, run=False)
        else:
            holding_status._finished()

        self.go.put(1, wait=True)

        def running_cb(value, old_value, obj, **kwargs):
            if old_value == 1 and value == 0:
                obj.clear_sub(running_cb)
                running_status._finished()

        self.running.subscribe(running_cb, run=False)
        return holding_status
Пример #8
0
 def trigger(self):
     # Re-implement this to trigger as desired in bluesky
     status = DeviceStatus(self)
     status._finished()
     return status
Пример #9
0
def sscan_1D(
        sscan,
        poll_delay_s=0.001,
        phase_timeout_s = 60.0,
        running_stream="primary",
        final_array_stream=None,
        device_settings_stream="settings",
        md=None):
    """
    simple 1-D scan using EPICS synApps sscan record

    assumes the sscan record has already been setup properly for a scan

    PARAMETERS

    sscan : Device
        one EPICS sscan record (instance of `apstools.synApps.sscanRecord`)
    running_stream : str or `None`
        (default: ``"primary"``)
        Name of document stream to write positioners and detectors data
        made available while the sscan is running.  This is typically
        the scan data, row by row.
        If set to `None`, this stream will not be written.
    final_array_stream : str or `None`
        (default: ``None``)
        Name of document stream to write positioners and detectors data
        posted *after* the sscan has ended.
        If set to `None`, this stream will not be written.
    device_settings_stream : str or `None`
        (default: ``"settings"``)
        Name of document stream to write *settings* of the sscan device.
        This is all the information returned by ``sscan.read()``.
        If set to `None`, this stream will not be written.
    poll_delay_s : float
        (default: 0.001 seconds)
        How long to sleep during each polling loop while collecting
        interim data values and waiting for sscan to complete.
        Must be a number between zero and 0.1 seconds.
    phase_timeout_s : float
        (default: 60 seconds)
        How long to wait after last update of the ``sscan.FAZE``.
        When scanning, we expect the scan phase to update regularly
        as positioners move and detectors are triggered.  If the scan
        hangs for some reason, this is a way to end the plan early.
        To cancel this feature, set it to ``None``.

    NOTE about the document stream names

    Make certain the names for the document streams are different from
    each other.  If you make them all the same (such as ``primary``),
    you will have difficulty when reading your data later on.

    *Don't cross the streams!*

    EXAMPLE

    Assume that the chosen sscan record has already been setup.

        from apstools.devices import sscanDevice
        scans = sscanDevice(P, name="scans")

        from apstools.plans import sscan_1D
        RE(sscan_1D(scans.scan1), md=dict(purpose="demo"))

    """
    global new_data, inactive_deadline

    if not (0 <= poll_delay_s <= 0.1):
        raise ValueError(
            "poll_delay_s must be a number between 0 and 0.1,"
            f" received {poll_delay_s}")

    t0 = time.time()
    sscan_status = DeviceStatus(sscan.execute_scan)
    started = False
    new_data = False
    inactive_deadline = time.time()
    if phase_timeout_s is not None:
        inactive_deadline += phase_timeout_s

    def execute_cb(value, timestamp, **kwargs):
        """watch for sscan to complete"""
        if started and value in (0, "IDLE"):
            sscan_status._finished()
            sscan.execute_scan.unsubscribe_all()
            sscan.scan_phase.unsubscribe_all()

    def phase_cb(value, timestamp, **kwargs):
        """watch for new data"""
        global new_data, inactive_deadline
        if phase_timeout_s is not None:
            inactive_deadline = time.time() + phase_timeout_s
        if value in (15, "RECORD SCALAR DATA"):
            new_data = True            # set flag for main plan

    # acquire only the channels with non-empty configuration in EPICS
    sscan.select_channels()
    # pre-identify the configured channels
    sscan_data_objects = _get_sscan_data_objects(sscan)

    # watch for sscan to complete
    sscan.execute_scan.subscribe(execute_cb)
    # watch for new data to be read out
    sscan.scan_phase.subscribe(phase_cb)

    _md = dict(plan_name="sscan_1D")
    _md.update(md or {})

    yield from bps.open_run(_md)               # start data collection
    yield from bps.mv(sscan.execute_scan, 1)   # start sscan
    started = True

    # collect and emit data, wait for sscan to end
    while not sscan_status.done or new_data:
        if new_data and running_stream is not None:
            yield from bps.create(running_stream)
            for _k, obj in sscan_data_objects.items():
                yield from bps.read(obj)
            yield from bps.save()
        new_data = False
        if phase_timeout_s is not None and time.time() > inactive_deadline:
            print(f"No change in sscan record for {phase_timeout_s} seconds.")
            print("ending plan early as unsuccessful")
            sscan_status._finished(success=False)
        yield from bps.sleep(poll_delay_s)

    # dump the complete data arrays
    if final_array_stream is not None:
        yield from bps.create(final_array_stream)
        # we have to search for the arrays since they have ``kind="omitted"``
        # (which means they do not get reported by the ``.read()`` method)
        for part in (sscan.positioners, sscan.detectors):
            for nm in part.read_attrs:
                if "." not in nm:
                    # TODO: write just the acquired data, not the FULL arrays!
                    yield from bps.read(getattr(part, nm).array)
        yield from bps.save()

    # dump the entire sscan record into another stream
    if device_settings_stream is not None:
        yield from bps.create(device_settings_stream)
        yield from bps.read(sscan)
        yield from bps.save()

    yield from bps.close_run()

    elapsed = time.time() - t0
    print(f"total time for sscan_1D: {elapsed} s")

    return sscan_status
Пример #10
0
class HPLC(Device):
    ready = Cpt(EpicsSignal, 'io1')
    injected = Cpt(EpicsSignalRO, 'io2')
    done = Cpt(EpicsSignalRO, 'io3')
    bypass = Cpt(EpicsSignal, '_bypass')

    def __init__(self, *args, read_filepath, write_filepath, **kwargs):
        self.hplc_status = HPLCStatus.idle
        self._injected_status = None
        self._done_status = None
        self._bypass_status = None
        self._resource = None
        self._read_filepath = read_filepath
        self._write_filepath = write_filepath
        super().__init__(*args, **kwargs)

    def stage(self):
        self.injected.subscribe(self._injected_changed)
        self.done.subscribe(self._done_changed)
        self.bypass.subscribe(self._bypass_changed)

    def unstage(self):
        self.injected.clear_sub(self._injected_changed)
        self.done.clear_sub(self._done_changed)
        self.bypass.clear_sub(self._bypass_changed)
        self._injected_status = None
        self._done_status = None
        self._bypass_status = None
        # self._resource = None

    def kickoff(self):
        """
        Set 'ready' to True and return a status object tied to 'injected'.
        """
        self.ready.set(1)
        self.hplc_status = HPLCStatus.waiting_injected
        self._injected_status = DeviceStatus(self.injected)
        self._done_status = DeviceStatus(self.done)
        return self._injected_status

    def complete(self):
        """
        Return a status object tied to 'done'.
        """
        if self._done_status is None:
            raise RuntimeError("must call kickoff() before complete()")
        return self._done_status

    def collect(self):
        """
        Yield events that reference the data files generated by HPLC.
        the HPLC run using a batch file should always export data into /GPFS/xf16id/Windows/hplc_export.txt
        """

        # in principle there are lots of things that can be saved
        # for now just keep the chromatograms
        sections = readShimadzuDatafile('/GPFS/xf16id/Windows/hplc_export.txt',
                                        return_all_sections=True)

        import numpy as np
        yield {
            'time': time.time(),
            'seq_num': 1,
            'data': {
                'foo': np.random.rand(2048, 1)
            },
            'timestamps': {
                'foo': time.time()
            }
        }

        # TODO Decide whether you want to 'chunk' the dataset into 'events'.
        # Insert a datum per event and yield a partial event document.
        #for i in range(1):
        #    yield {'time': time.time(),
        #           'seq_num': i+1,
        #           'data': {'foo': np.random.rand(2048, 1)},  #datum_id},
        #           'timestamps': {'foo': time.time()}}

    def describe_collect(self):
        return {
            self.name: {
                'foo': {
                    'dtype': 'array',
                    'shape': (2048, ),
                    'source': 'TO DO'
                }
            }
        }

    def _injected_changed(self, value, old_value, **kwargs):
        """Mark the status object returned by 'kickoff' as finished when
        injected goes from 0 to 1."""
        if self._injected_status is None:
            return
        if (old_value == 0) and (value == 1):
            self.ready.set(0)
            self.hplc_status = HPLCStatus.waiting_done
            self._injected_status._finished()

    def _done_changed(self, value, old_value, **kwargs):
        """Mark the status object returned by 'complete' as finished when
        done goes from 0 to 1."""
        if self._done_status is None:
            return
        if (old_value == 0) and (value == 1):
            self.hplc_status = HPLCStatus.idle
            self._done_status._finished()

    def _bypass_changed(self, value, old_value, **kwargs):
        """Mark the status object returned by 'complete' as finished when
        done goes from 0 to 1."""
        if value == 0:
            return
        print('Bypass used: {}, hplc state: {}'.format(value,
                                                       self.hplc_status))
        if (value == 1) and self.hplc_status == HPLCStatus.waiting_injected:
            self._injected_changed(1, 0)
        elif (value == 2) and self.hplc_status == HPLCStatus.waiting_done:
            self._done_changed(1, 0)
        self.bypass.set(0)
Пример #11
0
class XPStraj(Device):
    def __init__(self, ip_addr, group, name, devices=None):
        """ ip_addr: IP of the XPS controller
            group: PVT positioner grouped defined in the controller
            name: 
            devices: the corresponding Ophyd device for the posiitoner group, useful in a scan
        """

        if devices is None:
            raise Exception(
                "devices is None: the corelation between XPS and bs motor names must be defined."
            )

        super().__init__(name=name)
        self.xps = XPS()
        self.ip_addr = ip_addr
        self.sID = self.xps.TCP_ConnectToServer(ip_addr, 5001, 0.050)
        # 20 ms timeout is suggested for single-socket communication, per programming manual

        objs = self.xps.ObjectsListGet(self.sID)[1].split(';;')[0].split(';')
        if group not in objs:
            print("group %s does not exist." % group)
            print("valid objects are", objs)
            raise Exception
        self.group = group
        self.motors = [mot for mot in objs if (group + '.') in mot]
        self.Nmot = len(self.motors)

        self.devices = devices
        for m in devices.keys():
            if m not in self.motors:
                raise Exception('invalid motor: ', m)
            print(m, devices[m].name)
        self.device_names = [devices[k].name for k in devices.keys()]

        self.verified = False
        uname = getpass.getuser()
        self.traj_files = [
            "TrajScan_FW.trj-%s" % uname,
            "TrajScan_BK.trj-%s" % uname
        ]
        self.traj_par = {
            'run_forward_traj': True,
            'no_of_segments': 0,
            'no_of_rampup_points': 0,
            'segment_displacement': 0,
            'segment_duration': 0,
            'motor': None,
            'rampup_distance': 0,
            'motor2': None
        }
        self.time_modified = time.time()
        self.start_time = 0
        self._traj_status = None
        self.detectors = None
        self.datum = None

    def stage(self):
        self.datum = {}

    def unstage(self):
        """ abort whatever is still going on??
        """
        self.abort_traj()
        self._traj_status = None

    def pulse(self, duration=0.00001):
        # GPIO3.DO, pin 4
        mask = '1'  # it seems that only DO1 works
        self.xps.GPIODigitalSet(self.sID, "GPIO3.DO", mask, 1)
        #time.sleep(duration)
        self.xps.GPIODigitalSet(self.sID, "GPIO3.DO", mask, 0)

    def read_configuration(self):
        ret = [(k, {
            'value': self.traj_par[k],
            'timestamp': self.time_modified
        }) for k in self.traj_par.keys()]
        return OrderedDict(ret)

    def describe_configuration(self):
        pass

    def select_forward_traj(self, op=True):
        if op:
            self.traj_par['run_forward_traj'] = True
        else:
            self.traj_par['run_forward_traj'] = False

    def moving(self):
        err, msg = self.xps.GroupMotionStatusGet(self.sID, self.group,
                                                 self.Nmot)
        if (np.asarray(msg.split(',')) == '0').all():
            return False
        return True

    def abort_traj(self):
        if self.moving():
            err, msg = self.xps.GroupMoveAbort(self.sID, self.group)
            return err, msg
        return

    def kickoff(self):
        """
        run the trajectory
        """
        if self.verified == False:
            raise Exception("trajectory not defined/verified.")

        #self._traj_status = DeviceStatus(self.ExecuteState)
        self._traj_status = DeviceStatus(self)

        ##self.exec_traj(self.traj_par['run_forward_traj'])  # should not block
        th = threading.Thread(target=self.exec_traj,
                              args=(self.traj_par['run_forward_traj'], ))
        th.start()
        # always done, the scan should never even try to wait for this
        #status = DeviceStatus(self)
        #status._finished()
        return self._traj_status

    def complete(self):
        """
            according to run_engine.py: Tell a flyer, 'stop collecting, whenever you are ready'.
            Return a status object tied to 'done'.
        """
        if self._traj_status is None:
            raise RuntimeError("must call kickoff() before complete()")
        while not self._traj_status.done:
            print(
                f"{time.asctime()}: waiting for the trajectory to finish ...   ",
                end='')
            time.sleep(1)
        print("Done.")

        return self._traj_status

    def collect_asset_docs(self):
        """ when the run eigine process the "collect" message, 3 functions are called (see bluesky.bundlers)
                collect_asset_docs(): returns resource and datum document (name, doc)
                                      RE emit(DocumentNames(name), doc)
                                      called once per scan? name is always "resource"?
                describe_collect(): returns a dictionary of {stream_name: data_keys, ...}
                                    RE emit(DocumentNames.descriptor, doc) 
                collect(): returns a list of events [ev, ...], 
                           RE emit(DocumentNames.event, ev) or add to bulk data for later emit() call
            DocumentNames is defined in event_model, enum

            followed HXN example
        """
        asset_docs_cache = []

        for det in self.detectors:
            k = f'{det.name}_image'
            #det.dispatch(k, ttime.time())
            (name, resource), = det.file.collect_asset_docs()
            assert name == 'resource'
            asset_docs_cache.append(('resource', resource))
            resource_uid = resource['uid']
            datum_id = '{}/{}'.format(resource_uid, 0)
            self.datum[k] = [datum_id, ttime.time()]
            datum = {
                'resource': resource_uid,
                'datum_id': datum_id,
                'datum_kwargs': {
                    'point_number': 0
                }
            }
            asset_docs_cache.append(('datum', datum))

        return tuple(asset_docs_cache)

    def collect(self):
        """
        save position data, called at the end of a scan (not at the end of a trajectory)
        this is now recorded in self.readback, as accumulated by self.update_readback()
        """
        now = time.time()
        data = {}
        ts = {}

        data[self.traj_par['fast_axis']] = self.read_back['fast_axis']
        ts[self.traj_par['fast_axis']] = self.read_back['timestamp']
        if self.traj_par['motor2'] is not None:
            data[self.traj_par['slow_axis']] = self.read_back['slow_axis']
            ts[self.traj_par['slow_axis']] = self.read_back['timestamp2']

        for det in self.detectors:
            # first make sure that all data file are saved, otherwise next time when the detector is
            # staged, the files that are not yet saved will be lost
            Ni = det.cam.num_images.get()
            #Nc1 = 0
            t0 = time.time()

            while det.cam.detector_state.get(as_string=True) is "Acquire":
                time.sleep(0.1)
            """
            while True:
                Nc = det.cam.array_counter.get()
                #if Ni==Nc or Nc==Nc1:
                if Ni==Nc or time.time()-t0>5:  
                    # either the final file count is reached, or waited for too long (5s) and no new files 
                    # show up, which can happen if one or more trajectories were cut short during the raster
                    break
                #Nc1 = Nc
                print('data files are still being written for %s, %d -> %d' % (det.name, Nc, Ni))
                #time.sleep(5)
                time.sleep(0.5)
            """

            k = f'{det.name}_image'
            (data[k], ts[k]) = self.datum[k]
            for k, desc in det.read().items():
                data[k] = desc['value']
                ts[k] = desc['timestamp']

        ret = {
            'time': time.time(),
            'data': data,
            'timestamps': ts,
        }

        yield ret

    def describe_collect(self):
        '''Describe details for the flyer collect() method'''
        ret = {}
        ret[self.traj_par['fast_axis']] = {
            'dtype': 'number',
            'shape': (1, ),
            'source': 'PVT trajectory readback position'
        }
        if self.traj_par['motor2'] is not None:
            ret[self.traj_par['slow_axis']] = {
                'dtype': 'number',
                'shape': (1, ),
                'source': 'motor position readback'
            }
        for det in self.detectors:
            ret[f'{det.name}_image'] = det.make_data_key()
            for k, desc in det.describe().items():
                ret[k] = desc

        return {'primary': ret}

    def define_traj(self, motor, N, dx, dt, motor2=None, dy=0, Nr=2):
        """ the idea is to use FW/BK trjectories in a scan
            each trajactory involves a single motor only
            relative motion, N segements of length dx from the current position
            duration of each segment is dt
            
            Additional segments (Nr, at least 2) are required to ramp up and down, e.g.:
            
            # dt,  x,  v_out
            1.0,  0.16667, 0.5
            1.0,  1.0,     1.0
            ... ...
            1.0,  1.0,     1.0
            1.0,  0.83333, 0.5
            1.0,  0,0,     0.0
            detector triggering should start from the 5th segment
            
        """
        self.verified = False

        if motor not in self.motors:
            print("motor %s not in the list of motors: " % motor, self.motors)
            raise Exception

        err, ret = self.xps.PositionerMaximumVelocityAndAccelerationGet(
            self.sID, motor)
        mvel, macc = np.asarray(ret.split(','), dtype=np.float)
        midx = self.motors.index(motor)

        jj = np.zeros(Nr + N + Nr)
        jj[0] = 1
        jj[Nr - 1] = -1
        jj[-1] = 1
        jj[-Nr] = -1
        # these include the starting state of acc=vel=disp=0
        disp = np.zeros(Nr + N + Nr + 1)
        vel = np.zeros(Nr + N + Nr + 1)
        acc = np.zeros(Nr + N + Nr + 1)

        for i in range(N + 2 * Nr):
            acc[i + 1] = acc[i] + jj[i] * dt
            vel[i + 1] = vel[i] + acc[i] * dt + jj[i] * dt * dt / 2
            disp[i + 1] = vel[i] * dt + acc[i] * dt * dt / 2 + jj[
                i] * dt * dt * dt / 6
        vel = vel / vel.max() * dx / dt
        disp = disp / disp.max() * dx
        self.ramp_dist = disp[1:Nr + 1].sum()

        # rows in a PVT trajectory file correspond ot the segments
        # for each row/segment, the elements are
        #     time, axis 1 displancement, axis 1 velocity out, axsi 2 ...
        ot1 = np.zeros((Nr + N + Nr, 1 + 2 * self.Nmot))
        ot1[:, 0] = dt
        ot1[:, 2 * midx + 1] = disp[1:]
        ot1[:, 2 * midx + 2] = vel[1:]
        ot2 = np.zeros((Nr + N + Nr, 1 + 2 * self.Nmot))
        ot2[:, 0] = dt
        ot2[:, 2 * midx + 1] = -disp[1:]
        ot2[:, 2 * midx + 2] = -vel[1:]

        np.savetxt("/tmp/" + self.traj_files[0], ot1, fmt='%f', delimiter=', ')
        np.savetxt("/tmp/" + self.traj_files[1], ot2, fmt='%f', delimiter=', ')
        ftp = FTP(self.ip_addr)
        ftp.connect()
        ftp.login("Administrator", "Administrator")
        ftp.cwd("Public/Trajectories")
        for fn in self.traj_files:
            file = open("/tmp/" + fn, "rb")
            ftp.storbinary('STOR %s' % fn, file)
            file.close()
        ftp.quit()

        for fn in self.traj_files:
            err, ret = self.xps.MultipleAxesPVTVerification(
                self.sID, self.group, fn)
            if err != '0':
                print(ret)
                raise Exception("trajectory verification failed.")
            err, ret = self.xps.MultipleAxesPVTVerificationResultGet(
                self.sID, motor)
        self.verified = True
        self.traj_par = {
            'run_forward_traj': True,
            'no_of_segments': N,
            'no_of_rampup_points': Nr,
            'segment_displacement': dx,
            'segment_duration': dt,
            'motor': motor,
            'rampup_distance': self.ramp_dist,
            'motor2': motor2,
            'motor2_disp': dy
        }
        self.traj_par['fast_axis'] = xps_trj.devices[motor].name
        if motor2 is not None:
            self.traj_par['slow_axis'] = motor2.name
        self.time_modified = time.time()

    def exec_traj(self, forward=True, clean_event_queue=False, n_retry=5):
        """
           execuate either the foward or backward trajectory
        """
        if self.verified == False:
            raise Exception("trajectory not defined/verified.")

        N = self.traj_par['no_of_segments']
        Nr = self.traj_par['no_of_rampup_points']
        motor = self.traj_par['motor']
        dt = self.traj_par['segment_duration']

        if forward:
            traj_fn = self.traj_files[0]
        else:
            traj_fn = self.traj_files[1]

        # otherwise starting the trajectory might generate an error
        while self.moving():
            time.sleep(0.2)

        # first set up gathering
        self.xps.GatheringReset(self.sID)
        # pulse is generated when the positioner enters the segment
        print(
            "starting a trajectory with triggering parameters: %d, %d, %.3f ..."
            % (Nr + 1, N + Nr + 1, dt))
        self.xps.MultipleAxesPVTPulseOutputSet(self.sID, self.group, Nr + 1,
                                               N + Nr + 1, dt)
        self.xps.MultipleAxesPVTVerification(self.sID, self.group, traj_fn)
        self.xps.GatheringConfigurationSet(self.sID,
                                           [motor + ".CurrentPosition"])
        self.xps.sendAndReceive(
            self.sID, 'EventExtendedConfigurationTriggerSet(' +
            'Always,0,0,0,0,%s.PVT.TrajectoryPulse,0,0,0,0)' % self.group)
        self.xps.sendAndReceive(
            self.sID, 'EventExtendedConfigurationActionSet(' +
            'GatheringOneData,0,0,0,0)')
        # all trigger event for gathering should be removed
        if clean_event_queue:
            err, ret = self.xps.EventExtendedAllGet(self.sID)
            if err == '0':
                for ev in ret.split(';'):
                    self.xps.EventExtendedRemove(self.sID, ev)
        eID = self.xps.EventExtendedStart(self.sID)[1]
        self.start_time = time.time()

        for i in range(n_retry):
            # can this generate some triggers and then fail? if so this failure should kill the run
            # as there is no book keeping to know which images from the detectors are garbage
            # and the postions and image sequences will be off
            [err,
             ret] = self.xps.MultipleAxesPVTExecution(self.sID, self.group,
                                                      traj_fn, 1)
            if err == '0':
                break
            elif i == n_retry - 1:
                self.safe_stop()
            print(
                f'An error (code {err}) as occured when starting trajectory execution, retry #{i+1} ...'
            )
            #if err=='-22': # Group state must be READY
            [err, ret] = self.xps.GroupMotionEnable(self.sID, self.group)
            print(f"attempted to re-enable motion group: ", end='')
            time.sleep(1)

        self.xps.GatheringStopAndSave(self.sID)
        self.xps.EventExtendedRemove(self.sID, eID)
        self.update_readback()
        print('end of trajectory execution, ', end='')

        if self._traj_status != None:
            self._traj_status._finished()

    def safe_stop(self):
        fast_shutter.close()

        # generate enough triggers to complete exposure
        det = self.detectors[0]
        Ni = det.cam.num_images.get()
        Nc = det.cam.array_counter.get()
        """for i in range(Ni-Nc):
            det.trigger()
            print('%d more data points to complete exposure ...   ' % (Ni-Nc-i), end='\r')
            time.sleep(self.traj_par['segment_duration'])     
        """
        raise Exception('a hardware error has occured, aborting ... ')

    def readback_traj(self):
        print('reading back trajectory ...')
        err, ret = self.xps.GatheringCurrentNumberGet(self.sID)
        ndata = int(ret.split(',')[0])
        err, ret = self.xps.GatheringDataMultipleLinesGet(self.sID, 0, ndata)
        return [float(p) for p in ret.split('\n') if p != '']

    def clear_readback(self):
        self.read_back = {}
        self.read_back['fast_axis'] = []
        self.read_back['timestamp'] = []
        if self.traj_par['motor2'] is not None:
            self.read_back['slow_axis'] = []
            self.read_back['timestamp2'] = []

    def update_readback(self):
        self.read_back['fast_axis'] = self.readback_traj()
        # start_time is the beginning of the execution
        # pulse is generated when the positioner enters the segment ??
        # timestamp correspond to the middle of the segment
        N = self.traj_par['no_of_segments']
        Nr = self.traj_par['no_of_rampup_points']
        dt = self.traj_par['segment_duration']
        self.read_back['timestamp'] = list(self.start_time +
                                           (0.5 + Nr + np.arange(N + 1)) * dt)
        if self.traj_par['motor2'] is not None:
            self.read_back['slow_axis'] = self.traj_par['motor2'].position
            self.read_back['timestamp2'] = time.time()
Пример #12
0
 def trigger(self):
     # Re-implement this to trigger as desired in bluesky
     status = DeviceStatus(self)
     status._finished()
     return status
Пример #13
0
class XPStraj(Device):
    def __init__(self,
                 ip_addr,
                 group,
                 name,
                 devices={
                     'scan.rY': ss2.ry,
                     'scan.Y': ss2.y,
                     'scan.X': ss2.x
                 }):
        """ ip_addr: IP of the XPS controller
            group: PVT positioner grouped defined in the controller
            name: 
            devices: the corresponding Ophyd device for the posiitoner group, useful in a scan
        """
        super().__init__(name=name)
        self.xps = XPS()
        self.ip_addr = ip_addr
        self.sID = self.xps.TCP_ConnectToServer(ip_addr, 5001, 0.050)
        # 20 ms timeout is suggested for single-socket communication, per programming manual

        objs = self.xps.ObjectsListGet(self.sID)[1].split(';;')[0].split(';')
        if group not in objs:
            print("group %s does not exist." % group)
            print("valid objects are", objs)
            raise Exception
        self.group = group
        self.motors = [mot for mot in objs if (group + '.') in mot]
        self.Nmot = len(self.motors)

        self.devices = devices
        for m in devices.keys():
            if m not in self.motors:
                raise Exception('invalid motor: ', m)
            print(m, devices[m].name)
        self.device_names = [devices[k].name for k in devices.keys()]

        self.verified = False
        uname = getpass.getuser()
        self.traj_files = [
            "TrajScan_FW.trj-%s" % uname,
            "TrajScan_BK.trj-%s" % uname
        ]
        self.traj_par = {
            'run_forward_traj': True,
            'no_of_segments': 0,
            'no_of_rampup_points': 0,
            'segment_displacement': 0,
            'segment_duration': 0,
            'motor': None,
            'rampup_distance': 0,
            'motor2': None
        }
        self.time_modified = time.time()
        self.start_time = 0
        self._traj_status = None
        self.detectors = None
        self.datum = None

    def stage(self):
        self.datum = {}

    def unstage(self):
        """ abort whatever is still going on??
        """
        self.abort_traj()
        self._traj_status = None

    def pulse(self, duration=0.00001):
        # GPIO3.DO, pin 4
        mask = '1'  # it seems that only DO1 works
        self.xps.GPIODigitalSet(self.sID, "GPIO3.DO", mask, 1)
        #time.sleep(duration)
        self.xps.GPIODigitalSet(self.sID, "GPIO3.DO", mask, 0)

    def read_configuration(self):
        ret = [(k, {
            'value': self.traj_par[k],
            'timestamp': self.time_modified
        }) for k in self.traj_par.keys()]
        return OrderedDict(ret)

    def describe_configuration(self):
        pass

    def select_forward_traj(self, op=True):
        if op:
            self.traj_par['run_forward_traj'] = True
        else:
            self.traj_par['run_forward_traj'] = False

    def moving(self):
        err, msg = self.xps.GroupMotionStatusGet(self.sID, self.group,
                                                 self.Nmot)
        if (np.asarray(msg.split(',')) == '0').all():
            return False
        return True

    def abort_traj(self):
        if self.moving():
            err, msg = self.xps.GroupMoveAbort(self.sID, self.group)
            return err, msg
        return

    def kickoff(self):
        """
        run the trajectory
        """
        if self.verified == False:
            raise Exception("trajectory not defined/verified.")

        #self._traj_status = DeviceStatus(self.ExecuteState)
        self._traj_status = DeviceStatus(self)

        ##self.exec_traj(self.traj_par['run_forward_traj'])  # should not block
        th = threading.Thread(target=self.exec_traj,
                              args=(self.traj_par['run_forward_traj'], ))
        th.start()
        # always done, the scan should never even try to wait for this
        #status = DeviceStatus(self)
        #status._finished()
        return self._traj_status

    def complete(self):
        """
        Return a status object tied to 'done'.
        """
        #if self._traj_status is None:
        #    raise RuntimeError("must call kickoff() before complete()")
        self._traj_status.done = not self.moving()

        return self._traj_status

    def collect_asset_docs(self):
        """ adapted from HXN fly scan example
        """
        asset_docs_cache = []

        for det in self.detectors:
            k = f'{det.name}_image'
            #det.dispatch(k, ttime.time())
            (name, resource), = det.file.collect_asset_docs()
            assert name == 'resource'
            asset_docs_cache.append(('resource', resource))
            resource_uid = resource['uid']
            datum_id = '{}/{}'.format(resource_uid, 0)
            self.datum[k] = [datum_id, ttime.time()]
            datum = {
                'resource': resource_uid,
                'datum_id': datum_id,
                'datum_kwargs': {
                    'point_number': 0
                }
            }
            asset_docs_cache.append(('datum', datum))

        return tuple(asset_docs_cache)

    def collect(self):
        """
        save position data, called at the end of a scan (not at the end of a trajectory)
        this is now recorded in self.readback, as accumulated by self.update_readback()
        """
        now = time.time()
        data = {}
        ts = {}

        data[self.traj_par['fast_axis']] = self.read_back['fast_axis']
        ts[self.traj_par['fast_axis']] = now
        if self.traj_par['motor2'] is not None:
            data[self.traj_par['slow_axis']] = self.read_back['slow_axis']
            ts[self.traj_par['slow_axis']] = now

        for det in self.detectors:
            k = f'{det.name}_image'
            (data[k], ts[k]) = self.datum[k]
            for k, desc in det.read().items():
                data[k] = desc['value']
                ts[k] = desc['timestamp']

        ret = {
            'time': time.time(),
            'data': data,
            'timestamps': ts,
        }

        yield ret

    def describe_collect(self):
        '''Describe details for the flyer collect() method'''
        ret = {}
        ret[self.traj_par['fast_axis']] = {
            'dtype': 'number',
            'shape': (1, ),
            'source': 'PVT trajectory readback position'
        }
        if self.traj_par['motor2'] is not None:
            ret[self.traj_par['slow_axis']] = {
                'dtype': 'number',
                'shape': (1, ),
                'source': 'motor position readback'
            }
        for det in self.detectors:
            ret[f'{det.name}_image'] = det.make_data_key()
            for k, desc in det.describe().items():
                ret[k] = desc

        return {'primary': ret}

    def define_traj(self, motor, N, dx, dt, motor2=None, dy=0, Nr=2):
        """ the idea is to use FW/BK trjectories in a scan
            each trajactory involves a single motor only
            relative motion, N segements of length dx from the current position
            duration of each segment is dt
            
            Additional segments (Nr, at least 2) are required to ramp up and down, e.g.:
            
            # dt,  x,  v_out
            1.0,  0.16667, 0.5
            1.0,  1.0,     1.0
            ... ...
            1.0,  1.0,     1.0
            1.0,  0.83333, 0.5
            1.0,  0,0,     0.0
            detector triggering should start from the 5th segment
            
        """
        self.verified = False

        if motor not in self.motors:
            print("motor %s not in the list of motors: " % motor, self.motors)
            raise Exception

        err, ret = self.xps.PositionerMaximumVelocityAndAccelerationGet(
            self.sID, motor)
        mvel, macc = np.asarray(ret.split(','), dtype=np.float)
        midx = self.motors.index(motor)

        jj = np.zeros(Nr + N + Nr)
        jj[0] = 1
        jj[Nr - 1] = -1
        jj[-1] = 1
        jj[-Nr] = -1
        # these include the starting state of acc=vel=disp=0
        disp = np.zeros(Nr + N + Nr + 1)
        vel = np.zeros(Nr + N + Nr + 1)
        acc = np.zeros(Nr + N + Nr + 1)

        for i in range(N + 2 * Nr):
            acc[i + 1] = acc[i] + jj[i] * dt
            vel[i + 1] = vel[i] + acc[i] * dt + jj[i] * dt * dt / 2
            disp[i + 1] = vel[i] * dt + acc[i] * dt * dt / 2 + jj[
                i] * dt * dt * dt / 6
        vel = vel / vel.max() * dx / dt
        disp = disp / disp.max() * dx
        self.ramp_dist = disp[1:Nr + 1].sum()

        # rows in a PVT trajectory file correspond ot the segments
        # for each row/segment, the elements are
        #     time, axis 1 displancement, axis 1 velocity out, axsi 2 ...
        ot1 = np.zeros((Nr + N + Nr, 1 + 2 * self.Nmot))
        ot1[:, 0] = dt
        ot1[:, 2 * midx + 1] = disp[1:]
        ot1[:, 2 * midx + 2] = vel[1:]
        ot2 = np.zeros((Nr + N + Nr, 1 + 2 * self.Nmot))
        ot2[:, 0] = dt
        ot2[:, 2 * midx + 1] = -disp[1:]
        ot2[:, 2 * midx + 2] = -vel[1:]

        np.savetxt("/tmp/" + self.traj_files[0], ot1, fmt='%f', delimiter=', ')
        np.savetxt("/tmp/" + self.traj_files[1], ot2, fmt='%f', delimiter=', ')
        ftp = FTP(self.ip_addr)
        ftp.connect()
        ftp.login("Administrator", "Administrator")
        ftp.cwd("Public/Trajectories")
        for fn in self.traj_files:
            file = open("/tmp/" + fn, "rb")
            ftp.storbinary('STOR %s' % fn, file)
            file.close()
        ftp.quit()

        for fn in self.traj_files:
            err, ret = self.xps.MultipleAxesPVTVerification(
                self.sID, self.group, fn)
            if err != '0':
                print(ret)
                raise Exception("trajectory verification failed.")
            err, ret = self.xps.MultipleAxesPVTVerificationResultGet(
                self.sID, motor)
        self.verified = True
        self.traj_par = {
            'run_forward_traj': True,
            'no_of_segments': N,
            'no_of_rampup_points': Nr,
            'segment_displacement': dx,
            'segment_duration': dt,
            'motor': motor,
            'rampup_distance': self.ramp_dist,
            'motor2': motor2,
            'motor2_disp': dy
        }
        self.traj_par['fast_axis'] = xps_trj.devices[motor].name
        if motor2 is not None:
            self.traj_par['slow_axis'] = motor2.name
        self.time_modified = time.time()

    def exec_traj(self, forward=True, clean_event_queue=False):
        """
           execuate either the foward or backward trajectory
        """
        if self.verified == False:
            raise Exception("trajectory not defined/verified.")

        N = self.traj_par['no_of_segments']
        Nr = self.traj_par['no_of_rampup_points']
        motor = self.traj_par['motor']
        dt = self.traj_par['segment_duration']

        if forward:
            traj_fn = self.traj_files[0]
        else:
            traj_fn = self.traj_files[1]

        # otherwise starting the trajectory might generate an error
        while self.moving():
            time.sleep(0.2)

        # first set up gathering
        self.xps.GatheringReset(self.sID)
        # pulse is generated when the positioner enters the segment
        print("setting triggering parameters: %d, %d, %.3f" %
              (Nr + 1, N + Nr + 1, dt))
        self.xps.MultipleAxesPVTPulseOutputSet(self.sID, self.group, Nr + 1,
                                               N + Nr + 1, dt)
        self.xps.MultipleAxesPVTVerification(self.sID, self.group, traj_fn)
        self.xps.GatheringConfigurationSet(self.sID,
                                           [motor + ".CurrentPosition"])
        self.xps.sendAndReceive(
            self.sID, 'EventExtendedConfigurationTriggerSet(' +
            'Always,0,0,0,0,%s.PVT.TrajectoryPulse,0,0,0,0)' % self.group)
        self.xps.sendAndReceive(
            self.sID, 'EventExtendedConfigurationActionSet(' +
            'GatheringOneData,0,0,0,0)')
        # all trigger event for gathering should be removed
        if clean_event_queue:
            err, ret = self.xps.EventExtendedAllGet(self.sID)
            if err == '0':
                for ev in ret.split(';'):
                    self.xps.EventExtendedRemove(self.sID, ev)
        eID = self.xps.EventExtendedStart(self.sID)[1]
        self.start_time = time.time()
        self.xps.MultipleAxesPVTExecution(self.sID, self.group, traj_fn, 1)
        self.xps.GatheringStopAndSave(self.sID)
        self.xps.EventExtendedRemove(self.sID, eID)
        self.update_readback()

        if self._traj_status != None:
            self._traj_status._finished()

    def readback_traj(self):
        err, ret = self.xps.GatheringCurrentNumberGet(self.sID)
        ndata = int(ret.split(',')[0])
        err, ret = self.xps.GatheringDataMultipleLinesGet(self.sID, 0, ndata)
        return [float(p) for p in ret.split('\n') if p != '']

    def clear_readback(self):
        self.read_back = {}
        self.read_back['fast_axis'] = []
        if self.traj_par['motor2'] is not None:
            self.read_back['slow_axis'] = []

    def update_readback(self):
        self.read_back['fast_axis'] += self.readback_traj()
        if self.traj_par['motor2'] is not None:
            self.read_back['slow_axis'] += [self.traj_par['motor2'].position]
Пример #14
0
 def trigger(self):
     #TODO: modify settle_time to be from a acquiretime pv
     acq_time = self.acquire_time.get()
     _status = DeviceStatus(self, settle_time=acq_time)
     _status._finished(success=True)
     return _status
Пример #15
0
class ContinuousAcquisitionTrigger(BlueskyInterface):
    """
    This trigger mixin class records images when it is triggered.

    It expects the detector to *already* be acquiring, continously.
    """
    def __init__(self, *args, plugin_name=None, image_name=None, **kwargs):
        if plugin_name is None:
            raise ValueError("plugin name is a required keyword argument")
        super().__init__(*args, **kwargs)
        self._plugin = getattr(self, plugin_name)
        if image_name is None:
            image_name = '_'.join([self.name, 'image'])
        self._plugin.stage_sigs[self._plugin.auto_save] = 'No'
        self.cam.stage_sigs[self.cam.image_mode] = 'Continuous'
        self._plugin.stage_sigs[self._plugin.file_write_mode] = 'Capture'
        self._image_name = image_name
        self._status = None
        self._num_captured_signal = self._plugin.num_captured
        self._num_captured_signal.subscribe(self._num_captured_changed)
        self._save_started = False

    def stage(self):
        if self.cam.acquire.get() != 1:
            ##foolishly added by DO
            try:
                self.cam.acquire.put(1)
                print("The detector wasn't properly acquiring :(")
                time.sleep(1)
                print("Don't worry, I probably fixed it for you!")
            except:
                raise RuntimeError(
                    "The ContinuousAcuqisitionTrigger expects "
                    "the detector to already be acquiring."
                    "I was unable to fix it, please restart the ui."
                )  #new line, DO
            #old style, simple runtime error (no rescue attempt)
            #raise RuntimeError("The ContinuousAcuqisitionTrigger expects "
            #                   "the detector to already be acquiring.")

        #if we get this far, we can now stage the detector.
        super().stage()

        # put logic to look up proper dark frame
        # die if none is found

    def trigger(self):
        "Trigger one acquisition."
        if not self._staged:
            raise RuntimeError("This detector is not ready to trigger."
                               "Call the stage() method before triggering.")
        self._save_started = False
        self._status = DeviceStatus(self)
        self._desired_number_of_sets = self.number_of_sets.get()
        self._plugin.num_capture.put(self._desired_number_of_sets)
        self.dispatch(self._image_name, ttime.time())
        # reset the proc buffer, this needs to be generalized
        self.proc.reset_filter.put(1)
        self._plugin.capture.put(1)  # Now the TIFF plugin is capturing.
        return self._status

    def _num_captured_changed(self, value=None, old_value=None, **kwargs):
        "This is called when the 'acquire' signal changes."
        if self._status is None:
            return
        if value == self._desired_number_of_sets:
            # This is run on a thread, so exceptions might pass silently.
            # Print and reraise so they are at least noticed.
            try:
                self.tiff.write_file.put(1)
            except Exception as e:
                print(e)
                raise
            self._save_started = True
        if value == 0 and self._save_started:
            self._status._finished()
            self._status = None
            self._save_started = False