示例#1
0
    def add_theta(self):
        """Add theta at the end of a scan.
        """
        log.info('add theta')

        full_file_name = self.epics_pvs['FPFullFileName'].get(as_string=True)
        if os.path.exists(full_file_name):
            try:
                with h5py.File(full_file_name, "a") as f:
                    if self.theta is not None:
                        unique_ids = f['/defaults/NDArrayUniqueId']
                        hdf_location = f['/defaults/HDF5FrameLocation']
                        total_dark_fields = self.num_dark_fields * (
                            (self.dark_field_mode in ('Start', 'Both')) +
                            (self.dark_field_mode in ('End', 'Both')))
                        total_flat_fields = self.num_flat_fields * (
                            (self.flat_field_mode in ('Start', 'Both')) +
                            (self.flat_field_mode in ('End', 'Both')))

                        proj_ids = unique_ids[hdf_location[:] ==
                                              b'/exchange/data']
                        flat_ids = unique_ids[hdf_location[:] ==
                                              b'/exchange/data_white']
                        dark_ids = unique_ids[hdf_location[:] ==
                                              b'/exchange/data_dark']

                        # create theta dataset in hdf5 file
                        if len(proj_ids) > 0:
                            theta_ds = f.create_dataset(
                                '/exchange/theta', (len(proj_ids), ))
                            theta_ds[:] = self.theta[proj_ids - proj_ids[0]]

                        # warnings that data is missing
                        if len(proj_ids) != len(self.theta):
                            log.warning(
                                f'There are {len(self.theta) - len(proj_ids)} missing data frames'
                            )
                            missed_ids = [
                                ele for ele in range(len(self.theta))
                                if ele not in proj_ids - proj_ids[0]
                            ]
                            missed_theta = self.theta[missed_ids]
                            # log.warning(f'Missed ids: {list(missed_ids)}')
                            log.warning(f'Missed theta: {list(missed_theta)}')
                        if len(flat_ids) != total_flat_fields:
                            log.warning(
                                f'There are {total_flat_fields - len(flat_ids)} missing flat field frames'
                            )
                        if (len(dark_ids) != total_dark_fields):
                            log.warning(
                                f'There are {total_dark_fields - len(dark_ids)} missing dark field frames'
                            )
            except:
                log.error('Add theta: Failed accessing: %s', full_file_name)
                traceback.print_exc(file=sys.stdout)

        else:
            log.error('Failed adding theta. %s file does not exist',
                      full_file_name)
示例#2
0
 def fly_scan(self):
     """Control of Sample X position
     """
     if(abs(self.epics_pvs['SampleInX'].value-self.epics_pvs['SampleX'].value)>1e-4):
         log.error('SampleInX is not the same as current SampleTopX')            
         self.epics_pvs['ScanStatus'].put('SampleX error')
         self.epics_pvs['StartScan'].put(0)        
         return
     super().fly_scan()
def run(fname):
    try:
        # Form auto-complete for scan
        out = subprocess.Popen(['tomo', 'scan', '-h'],
                               stdout=subprocess.PIPE,
                               stderr=subprocess.STDOUT)
        stdout, _ = out.communicate()
        stdout = str(stdout)

        cmdscan = []
        parscan = []
        st = stdout.find("optional")
        while (1):
            st = stdout.find('--', st)
            end = min(stdout.find(' ', st), stdout.find('\\n', st))
            if (st < 0):
                break
            cmdscan.append(stdout[st:end])
            st = stdout.find('(default:', end)
            st1 = stdout.find('--', end)
            if (st1 > st or st1 < 0):
                end = stdout.find(')', st)
                parscan.append(stdout[st + 9:end].replace(" ", "").replace(
                    "\\n", ""))
            else:
                parscan.append("")
            st = end
        # Create bash file
        fid = open(fname, 'w')
        fid.write(
            '#/usr/bin/env bash\n _tomo()\n{\n\tlocal cur prev opts\n\tCOMPREPLY=()\n\tcur="${COMP_WORDS[COMP_CWORD]}"\n\tprev="${COMP_WORDS[COMP_CWORD-1]}"\n'
        )

        # check all tomo scan
        fid.write('\tif [[ ${COMP_WORDS[1]} == "scan" ]] ; then\n')
        fid.write('\t\topts="')
        fid.write(' '.join(cmdscan))
        fid.write('"\n')
        fid.write(
            '\t\tCOMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )\n\tfi\n')

        for k in range(len(cmdscan)):
            fid.write('\tif [[ ${prev} == "')
            fid.write(cmdscan[k])
            fid.write('" ]] ; then\n')
            fid.write('\t\topts="')
            fid.write(parscan[k])
            fid.write('"\n')
            fid.write(
                '\t\tCOMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )\n\t\treturn 0\n\tfi\n'
            )
        fid.write('}\n')
        fid.write('complete -F _tomo tomo')
        fid.close()
    except:
        log.error('Autocomplete file was not created')
示例#4
0
def create_remote_directory(remote_server, remote_dir):
    cmd = 'mkdir -p ' + remote_dir
    try:
        log.info('      *** creating remote directory %s' % (remote_dir))
        subprocess.check_call(['ssh', '-t', remote_server, cmd])
        log.info('      *** creating remote directory %s: Done!' % (remote_dir))
        return 0

    except subprocess.CalledProcessError as e:
        log.error('  *** Error while creating remote directory. Error code: %d' % (e.returncode))
        return -1
示例#5
0
def check_remote_directory(remote_server, remote_dir):
    try:
        rcmd = 'ls ' + remote_dir
        # rcmd is the command used to check if the remote directory exists
        subprocess.check_call(['ssh', '-t', remote_server, rcmd], stderr=open(os.devnull, 'wb'), stdout=open(os.devnull, 'wb'))
        log.warning('      *** remote directory %s exists' % (remote_dir))
        return 0

    except subprocess.CalledProcessError as e: 
        log.warning('      *** remote directory %s does not exist' % (remote_dir))
        if e.returncode == 2:
            return e.returncode
        else:
            log.error('  *** Unknown error code returned: %d' % (e.returncode))
            return -1
示例#6
0
    def check_pvs_connected(self):
        """Checks whether all EPICS PVs are connected.

        Returns
        -------
        bool
            True if all PVs are connected, otherwise False.
        """

        all_connected = True
        for key in self.epics_pvs:
            if not self.epics_pvs[key].connected:
                log.error('PV %s is not connected', self.epics_pvs[key].pvname)
                all_connected = False
        return all_connected
示例#7
0
    def begin_scan(self):
        """Performs the operations needed at the very start of a scan.

        This does the following:

        - Calls the base class method.

        - Set the HDF plugin.
        """

        log.info('begin scan')
        # Call the base class method
        super().begin_scan()

        # Set angles for the interlaced scan
        if (self.epics_pvs['InterlacedScan'].get(as_string=True) == 'Yes'):
            interlacedfilename = self.epics_pvs['InterlacedFileName'].get(
                as_string=True)
            try:
                self.theta = np.load(interlacedfilename)
                # update angles and number of frames to be captured and collected
                self.total_images -= self.num_angles - len(self.theta)
                self.num_angles = len(self.theta)
                # print some information about angles
                stheta = np.sort(self.theta)
                log.info('file with interlaced angles %s', interlacedfilename)
                log.info('loaded %d interlaced angles', self.num_angles)
                log.info('min angle %f', stheta[0])
                log.info('max angle %f', stheta[-1])
                log.info('min distance between neigbouring sorted angles %f',
                         np.amin(np.abs(stheta[1::2] - stheta[::2])))
                log.info('max distance between neigbouring sorted angles %f',
                         np.amax(np.abs(stheta[1::2] - stheta[::2])))
            except:
                log.error('%s file with interlaced angles is not valid',
                          interlacedfilename)
                self.theta = []
                self.abort_scan()
                return
        else:
            self.theta = self.rotation_start + np.arange(
                self.num_angles) * self.rotation_step

        self.epics_pvs['FPNumCapture'].put(self.total_images, wait=True)
        self.epics_pvs['FPCapture'].put('Capture')
示例#8
0
    def set_trigger_mode(self, trigger_mode, num_images):
        """Sets the trigger mode SIS3820 and the camera.

        Parameters
        ----------
        trigger_mode : str
            Choices are: "FreeRun", "Internal", or "PSOExternal"

        num_images : int
            Number of images to collect.  Ignored if trigger_mode="FreeRun".
            This is used to set the ``NumImages`` PV of the camera.
        """
        camera_model = self.epics_pvs['CamModel'].get(as_string=True)
        if (camera_model == 'Grasshopper3 GS3-U3-51S5M'):
            self.set_trigger_mode_grasshopper(trigger_mode, num_images)
        else:
            log.error('Camera is not supported')
            exit(1)
示例#9
0
 def wait_pv(self, epics_pv, wait_val, timeout=np.inf, delta_t=0.01):
     """Wait on a pv to be a value until max_timeout (default forever)
        delay for pv to change
     """
     time.sleep(delta_t)
     start_time = time.time()
     while time.time() - start_time < timeout:
         pv_val = epics_pv.get()
         if isinstance(pv_val, float):
             if abs(pv_val - wait_val) < EPSILON:
                 return True
         if pv_val == wait_val:
             return True
         time.sleep(delta_t)
     else:
         log.error('  *** ERROR: PV TIMEOUT ***')
         log.error('  *** wait_pv(%s, %d, %5.2f reached max timeout. Return False',
                       epics_pv.pvname, wait_val, timeout)
         return False
示例#10
0
 def add_theta(self):
     """Add theta at the end of a scan.
     """
     log.info('add theta')
     self.theta = np.linspace(self.rotation_start, self.rotation_stop,
                              self.num_angles)
     full_file_name = self.epics_pvs['FPFullFileName'].get(as_string=True)
     file_name_path = Path(full_file_name)
     if os.path.exists(full_file_name):
         try:
             f = h5py.File(full_file_name, "a")
             with f:
                 try:
                     if self.theta is not None:
                         theta_ds = f.create_dataset('/exchange/theta',
                                                     data=self.theta)
                 except:
                     log.error('Add theta: Failed accessing: %s',
                               full_file_name)
                     traceback.print_exc(file=sys.stdout)
         except OSError:
             log.error('Add theta aborted')
     else:
         log.error('Failed adding theta. %s file does not exist',
                   full_file_name)
示例#11
0
    def add_theta(self):
        """Add theta at the end of a scan.
        """
        log.info('add theta')

        full_file_name = self.epics_pvs['FPFullFileName'].get(as_string=True)
        if os.path.exists(full_file_name):
            try:
                f = h5py.File(full_file_name, "a")
                with f:
                    try:
                        if self.theta is not None:
                            # theta_ds = f.create_dataset('/exchange/theta', (len(self.theta),))
                            # theta_ds[:] = self.theta[:]

                            unique_ids = f['/defaults/NDArrayUniqueId']
                            shift_start = int(self.num_dark_fields > 0 and (self.dark_field_mode in ('Start', 'Both')))+ \
                                          int(self.num_flat_fields > 0 and (self.flat_field_mode in ('Start', 'Both')))

                            # find beginnings of sorted subarrays
                            # for [1,2,1,3,1,2,3,4,1,2] returns 0,2,4,8
                            ids_list = [
                                0, *np.where(unique_ids[1:] -
                                             unique_ids[:-1] < 0)[0] + 1
                            ]
                            # extract projection ids
                            if (len(ids_list[shift_start:]) == 1):
                                proj_ids = unique_ids[ids_list[shift_start]:]
                            else:
                                proj_ids = unique_ids[ids_list[shift_start]:
                                                      ids_list[shift_start +
                                                               1]]
                            # subtract first id
                            proj_ids -= proj_ids[0]
                            # create theta dataset in hdf5 file
                            theta_ds = f.create_dataset(
                                '/exchange/theta', (len(proj_ids), ))
                            theta_ds[:] = self.theta[proj_ids]

                            if (len(proj_ids) != len(self.theta)):
                                log.warning('There are %d missing frames',
                                            len(self.theta) - len(proj_ids))
                                missed_ids = [
                                    ele for ele in range(len(self.theta))
                                    if ele not in proj_ids
                                ]
                                missed_theta = self.theta[missed_ids]
                                log.warning(f'Missed ids: {list(missed_ids)}')
                                log.warning(
                                    f'Missed theta: {list(missed_theta)}')

                    except:
                        log.error('Add theta: Failed accessing: %s',
                                  full_file_name)
                        traceback.print_exc(file=sys.stdout)
            except OSError:
                log.error('Add theta aborted: %s not closed', full_file_name)
        else:
            log.error('Failed adding theta. %s file does not exist',
                      full_file_name)
示例#12
0
    def end_scan(self):
        """Performs the operations needed at the very end of a scan.

        This does the following:

        - Calls ``save_configuration()``.

        - Put the camera back in "FreeRun" mode and acquiring so the user sees live images.

        - Sets the speed of the rotation stage back to the maximum value.

        - Calls ``move_sample_in()``.

        - Calls the base class method.
        """
        log.info('end scan')
        # Save the configuration
        # Strip the extension from the FullFileName and add .config
        full_file_name = self.epics_pvs['FPFullFileName'].get(as_string=True)
        log.info('data save location: %s', full_file_name)
        config_file_root = os.path.splitext(full_file_name)[0]
        try:
            self.save_configuration(config_file_root + '.config')
        except FileNotFoundError:
            log.error('config file write error')
            self.epics_pvs['ScanStatus'].put('Config File Write Error')

        # Put the camera back in FreeRun mode and acquiring
        self.set_trigger_mode('FreeRun', 1)

        # Set the rotation speed to maximum
        self.epics_pvs['RotationSpeed'].put(self.max_rotation_speed)
        self.cleanup_PSO()

        # Move the sample in.  Could be out if scan was aborted while taking flat fields
        self.move_sample_in()

        # Call the base class method
        super().end_scan()
示例#13
0
    def fly_scan(self):
        """Performs the operations for a tomography fly scan, i.e. with continuous rotation.

        This base class method does the following:

        - Moves the rotation motor to position defined by the ``RotationStart`` PV.

        - Calls ``begin_scan()``

        - If the ``DarkFieldMode`` PV is 'Start' or 'Both' calls ``collect_dark_fields()``

        - If the ``FlatFieldMode`` PV is 'Start' or 'Both' calls ``collect_flat_fields()``

        - Calls ``collect_projections()``

        - If the ``FlatFieldMode`` PV is 'End' or 'Both' calls ``collect_flat_fields()``

        - If the ``DarkFieldMode`` PV is 'End' or 'Both' calls ``collect_dark_fields()``

        - Calls ``end_scan``

        If there is either CameraTimeoutError exception or ScanAbortError exception during the scan,
        it jumps immediate to calling ``end_scan()`` and returns.

        It is not expected that most derived classes will need to override this method, but they are
        free to do so if required.
        """

        try:
            # Prepare for scan
            self.begin_scan()
            self.epics_pvs['ScanStatus'].put('Moving rotation axis to start')
            # Move the rotation to the start
            self.epics_pvs['Rotation'].put(self.rotation_start, wait=True, timeout=600)
            # Collect the pre-scan dark fields if required
            if (self.num_dark_fields > 0) and (self.dark_field_mode in ('Start', 'Both')):
                self.collect_dark_fields()
            # Collect the pre-scan flat fields if required
            if (self.num_flat_fields > 0) and (self.flat_field_mode in ('Start', 'Both')):
                self.collect_flat_fields()
            # Collect the projections
            self.collect_projections()
            # Collect the post-scan flat fields if required
            if (self.num_flat_fields > 0) and (self.flat_field_mode in ('End', 'Both')):
                self.collect_flat_fields()
            # Collect the post-scan dark fields if required
            if (self.num_dark_fields > 0) and (self.dark_field_mode in ('End', 'Both')):
                self.collect_dark_fields()
 
        except ScanAbortError:
            log.error('Scan aborted')
        except CameraTimeoutError:
            log.error('Camera timeout')
        except FileOverwriteError:
            log.error('File overwrite aborted')
        #Make sure we do cleanup tasks from the end of the scan
        finally:
            self.end_scan()
示例#14
0
 def program_helical_motion(self):
     """Computes the speed and magnitude from SampleY motion for helical
     scans.
     """
     y_pixels_per_rotation = self.epics_pvs['PixelsYPer360Deg'].get()
     pixel_size = self.epics_pvs['ImagePixelSize'].get() / 1000.
     angle_range = self.epics_pvs['RotationStep'].get() * (
         self.epics_pvs['NumAngles'].get() - 1)
     rotation_speed = self.epics_pvs['RotationSpeed'].get()
     speed_Y = np.abs(y_pixels_per_rotation * pixel_size / 360. *
                      rotation_speed)
     end_Y = (self.epics_pvs['SampleY'].get() +
              angle_range / 360. * y_pixels_per_rotation * pixel_size)
     #Arbitrarily add 5 s to the y motion to account for timing imperfections
     end_Y += np.sign(y_pixels_per_rotation) * speed_Y * 5.0
     #Check limits
     if end_Y > self.epics_pvs['SampleYHLM'].get(
     ) or end_Y < self.epics_pvs['SampleYLLM'].get():
         log.error(
             'helical: Y range would fall outside SampleY motor limits')
         self.epics_pvs['AbortScan'].put(1)
     self.epics_pvs['SampleYSpeed'].put(speed_Y)
     self.epics_pvs['SampleY'].put(end_Y)
示例#15
0
    def wait_pv(self, epics_pv, wait_val, timeout=-1):
        """Wait on a pv to be a value until max_timeout (default forever)
           delay for pv to change
        """

        time.sleep(.01)
        start_time = time.time()
        while True:
            pv_val = epics_pv.get()
            if isinstance(pv_val, float):
                if abs(pv_val - wait_val) < EPSILON:
                    return True
            if pv_val != wait_val:
                if timeout > -1:
                    current_time = time.time()
                    diff_time = current_time - start_time
                    if diff_time >= timeout:
                        log.error('  *** ERROR: DROPPED IMAGES ***')
                        log.error('  *** wait_pv(%s, %d, %5.2f reached max timeout. Return False',
                                      epics_pv.pvname, wait_val, timeout)
                        return False
                time.sleep(.01)
            else:
                return True
示例#16
0
    def begin_scan(self):
        """Performs the operations needed at the very start of a scan.

        This base class method does the following:

        - Sets the status string in the ``ScanStatus`` PV.

        - Stops the camera acquisition.

        - Calls ``set_exposure_time()``

        - Copies the ``FilePath`` and ``FileName`` PVs to the areaDetector file plugin.

        - Sets class variables with the important scan parameters

        - Checks whether the file that will be saved by the file plugin already exists.
          If it does, and if the OverwriteWarning PV is 'Yes' then it opens a dialog
          box asking the user if they want to overwrite the file.  If they answer 'No'
          then a FileOverwriteError exception is raised.

        It is expected that most derived classes will override this method.  In most cases they
        should first call this base class method, and then perform any beamline-specific operations.
        """

        self.scan_is_running = True
        self.epics_pvs['ScanStatus'].put('Beginning scan')
        # Stop the camera since it could be in free-run mode
        self.epics_pvs['CamAcquire'].put(0, wait=True)
        # Set the exposure time
        self.set_exposure_time()
        # Set the file path, file name and file number
        self.epics_pvs['FPFilePath'].put(self.epics_pvs['FilePath'].value)
        self.epics_pvs['FPFileName'].put(self.epics_pvs['FileName'].value)

        # Copy the current values of scan parameters into class variables
        self.exposure_time = self.epics_pvs['ExposureTime'].value
        self.rotation_start = self.epics_pvs['RotationStart'].value
        self.rotation_step = self.epics_pvs['RotationStep'].value
        self.num_angles = self.epics_pvs['NumAngles'].value
        self.rotation_stop = self.rotation_start + (self.num_angles *
                                                    self.rotation_step)
        self.rotation_resolution = self.epics_pvs['RotationResolution'].value
        self.max_rotation_speed = self.epics_pvs['RotationMaxSpeed'].value
        self.return_rotation = self.epics_pvs['ReturnRotation'].get(
            as_string=True)
        self.num_dark_fields = self.epics_pvs['NumDarkFields'].value
        self.dark_field_mode = self.epics_pvs['DarkFieldMode'].get(
            as_string=True)
        self.num_flat_fields = self.epics_pvs['NumFlatFields'].value
        self.flat_field_mode = self.epics_pvs['FlatFieldMode'].get(
            as_string=True)
        self.file_path_rbv = self.epics_pvs['FPFilePathRBV'].get(
            as_string=True)
        self.file_name_rbv = self.epics_pvs['FPFileNameRBV'].get(
            as_string=True)
        self.file_number = self.epics_pvs['FPFileNumber'].value
        self.file_template = self.epics_pvs['FPFileTemplate'].get(
            as_string=True)
        self.total_images = self.num_angles
        if self.dark_field_mode != 'None':
            self.total_images += self.num_dark_fields
        if self.dark_field_mode == 'Both':
            self.total_images += self.num_dark_fields
        if self.flat_field_mode != 'None':
            self.total_images += self.num_flat_fields
        if self.flat_field_mode == 'Both':
            self.total_images += self.num_flat_fields

        if self.epics_pvs['OverwriteWarning'].get(as_string=True) == 'Yes':
            # Make sure there is not already a file by this name
            try:
                file_name = self.file_template % (
                    self.file_path_rbv, self.file_name_rbv, self.file_number)
            except:
                try:
                    file_name = self.file_template % (self.file_path_rbv,
                                                      self.file_name_rbv)
                except:
                    try:
                        file_name = self.file_template % (self.file_path_rbv)
                    except:
                        log.error("File name template: %s not supported",
                                  self.file_template)
                        raise TypeError
            if os.path.exists(file_name):
                reply = pymsgbox.confirm(
                    'File ' + file_name + ' exists.  Overwrite?',
                    'Overwrite file', ['Yes', 'No'])
                if reply == 'No':
                    raise FileOverwriteError
示例#17
0
    def stream_sync(self):
        """Synchronize new angular step and exposure with rotation speed. Brodcast new array of angles for streaming reconstruction

        - cleanup PSO
        - save last unique ID of projection
        - program pso (base part)
        - change the JOG speed wrt exposure and angular step
        - initiate DATAACQ in aerotech for saving encoder values
        - ARM PSO
        - wait exposure time to acquire 1 projection
        - read encoder value for the projection unique_id + 1
        - convert encoder value to the angle, form array of angles
        - broadcast array of angles for streaming
        """
        log.info('stream sync')

        if self.epics_pvs['StreamMessage'].get(as_string=True)=='Done' and self.epics_pvs['StreamScanType'].get(as_string=True)!='backforth' and \
            (self.exposure_time!=self.epics_pvs['ExposureTime'].value or self.rotation_step!=self.epics_pvs['RotationStep'].value):

            self.exposure_time = self.epics_pvs['ExposureTime'].value
            self.rotation_step = self.epics_pvs['RotationStep'].value
            #compute new counts per step
            # Compute the actual delta to keep each interval an integer number of encoder counts
            encoder_multiply = float(
                self.epics_pvs['PSOCountsPerRotation'].get()) / 360.
            raw_delta_encoder_counts = self.rotation_step * encoder_multiply
            delta_encoder_counts = round(raw_delta_encoder_counts)
            if abs(raw_delta_encoder_counts - delta_encoder_counts) > 1e-4:
                log.warning(
                    '  *** *** *** Requested scan would have used a non-integer number of encoder counts.'
                )
                log.warning(
                    '  *** *** *** Calculated # of encoder counts per step = {0:9.4f}'
                    .format(raw_delta_encoder_counts))
                log.warning('  *** *** *** Instead, using {0:d}'.format(
                    delta_encoder_counts))
            self.epics_pvs['PSOEncoderCountsPerStep'].put(delta_encoder_counts)

            self.cleanup_PSO()
            # wait until last projection is acquired
            time.sleep(self.exposure_time + 0.1)

            # save last unique id of the projection
            projid = self.epics_pvs['CamNumImagesCounter'].get()

            pso_command = self.epics_pvs['PSOCommand.BOUT']
            pso_model = self.epics_pvs['PSOControllerModel'].get(
                as_string=True)
            pso_axis = self.epics_pvs['PSOAxisName'].get(as_string=True)
            pso_input = int(
                self.epics_pvs['PSOEncoderInput'].get(as_string=True))
            overall_sense, user_direction = self._compute_senses()

            # Make sure the PSO control is off
            pso_command.put('PSOCONTROL %s RESET' % pso_axis,
                            wait=True,
                            timeout=10.0)
            # Set the output to occur from the I/O terminal on the controller
            if (pso_model == 'Ensemble'):
                pso_command.put('PSOOUTPUT %s CONTROL 1' % pso_axis,
                                wait=True,
                                timeout=10.0)
            elif (pso_model == 'A3200'):
                pso_command.put('PSOOUTPUT %s CONTROL 0 1' % pso_axis,
                                wait=True,
                                timeout=10.0)
            # Set the pulse width.  The total width and active width are the same, since this is a single pulse.
            pulse_width = self.epics_pvs['PSOPulseWidth'].get()
            pso_command.put('PSOPULSE %s TIME %f,%f' %
                            (pso_axis, pulse_width, pulse_width),
                            wait=True,
                            timeout=10.0)
            # Set the pulses to only occur in a specific window
            pso_command.put('PSOOUTPUT %s PULSE WINDOW MASK' % pso_axis,
                            wait=True,
                            timeout=10.0)
            # Set which encoder we will use.  3 = the MXH (encoder multiplier) input, which is what we generally want
            pso_command.put('PSOTRACK %s INPUT %d' % (pso_axis, pso_input),
                            wait=True,
                            timeout=10.0)
            # Set the distance between pulses. Do this in encoder counts.
            pso_command.put(
                'PSODISTANCE %s FIXED %d' %
                (pso_axis,
                 int(np.abs(self.epics_pvs['PSOEncoderCountsPerStep'].get()))),
                wait=True,
                timeout=10.0)
            # Which encoder is being used to calculate whether we are in the window.  1 for single axis
            pso_command.put('PSOWINDOW %s 1 INPUT %d' % (pso_axis, pso_input),
                            wait=True,
                            timeout=10.0)

            # Calculate window function parameters.  Must be in encoder counts, and is
            # referenced from the stage location where we arm the PSO.  We are at that point now.
            # We want pulses to start at start - delta/2, end at end + delta/2.
            range_start = -round(
                np.abs(self.epics_pvs['PSOEncoderCountsPerStep'].get()) /
                2) * overall_sense
            range_length = np.abs(self.epics_pvs['PSOEncoderCountsPerStep'].
                                  get()) * self.num_angles
            # The start of the PSO window must be < end.  Handle this.
            if overall_sense > 0:
                window_start = range_start
                window_end = window_start + range_length
            else:
                window_end = range_start
                window_start = window_end - range_length
            pso_command.put('PSOWINDOW %s 1 RANGE %d,%d' %
                            (pso_axis, window_start - 5, window_end + 5),
                            wait=True,
                            timeout=10.0)

            self.epics_pvs['CamAcquireTime'].put(self.exposure_time,
                                                 wait=True,
                                                 timeout=10.0)
            time_per_angle = self.compute_frame_time()
            # compute acceleration time
            accelJog_time = np.abs(
                (self.motor_speed - self.rotation_step /
                 time_per_angle)) / self.control_pvs['RotationAccelJog'].value
            # recompute velocity
            self.motor_speed = self.rotation_step / time_per_angle
            self.control_pvs['RotationSpeedJog'].put(self.motor_speed,
                                                     wait=True)
            pso_command.put('PROGRAM RUN 1, "dataacqoff.bcx"',
                            wait=True,
                            timeout=10.0)
            pso_command.put('PROGRAM RUN 1, "dataacqon.bcx"',
                            wait=True,
                            timeout=10.0)
            # wait acceleration
            log.warning(f'wait {accelJog_time+1} for acceleration')
            time.sleep(accelJog_time + 1)
            # Arm the PSO
            log.warning(
                f'ARM PSO and wait until the first projection is acquired')
            pso_command.put('PSOCONTROL %s ARM' % pso_axis,
                            wait=True,
                            timeout=10.0)
            # wait while 1 projection is acquired
            time.sleep(self.exposure_time + 0.4)
            pso_command.put('PROGRAM RUN 1, "dataacqread.bcx"',
                            wait=True,
                            timeout=10.0)
            # need some delay here
            time.sleep(0.1)
            pso_command.put('IGLOBAL(0)', wait=True, timeout=10.0)
            time.sleep(0.1)
            reply = self.epics_pvs['PSOCommand.BINP'].get(as_string=True)
            time.sleep(0.1)
            if reply:
                encoder_angle = int(reply[1:])

                self.rotation_start = self.control_pvs[
                    'RotationOFF'].value + encoder_angle * self.epics_pvs[
                        'RotationEResolution'].value
                self.epics_pvs['FirstProjid'].put(projid + 1, wait=True)
                self.compute_positions_PSO()
                # Assign the fly scan angular position to theta[]
                self.theta = self.rotation_start + np.arange(
                    self.num_angles) * self.rotation_step
                self.pva_stream_theta['value'] = self.theta.astype('float32')
                self.pva_stream_theta['sizex'] = len(self.theta)
                log.info(
                    f'Angle {self.theta[0]} corresponds to unique ID {projid+1}'
                )
            else:
                log.error('PSO didnt return encoder value')
        else:
            log.info('stream sync ignore')
        self.epics_pvs['StreamSync'].put('Done', wait=True)
示例#18
0
    def compute_frame_time(self):
        """Computes the time to collect and readout an image from the camera.

        This method is used to compute the time between triggers to the camera.
        This can be used, for example, to configure a trigger generator or to compute
        the speed of the rotation stage.

        The calculation is camera specific.  The result can depend on the actual exposure time
        of the camera, and on a variety of camera configuration settings (pixel binning,
        pixel bit depth, video mode, etc.)

        The current version only supports the Point Grey Grasshopper3 GS3-U3-23S6M.
        The logic for additional cameras should be added to this function in the future
        if the camera is expected to be used at more than one beamline.
        If the camera is only to be used at a single beamline then the code should be added
        to this method in the derived class

        Returns
        -------
        float
            The frame time, which is the minimum time allowed between triggers for the value of the
            ``ExposureTime`` PV.
        """
        # The readout time of the camera depends on the model, and things like the
        # PixelFormat, VideoMode, etc.
        # The measured times in ms with 100 microsecond exposure time and 1000 frames
        # without dropping
        camera_model = self.epics_pvs['CamModel'].get(as_string=True)
        pixel_format = self.epics_pvs['CamPixelFormat'].get(as_string=True)
        readout = None
        if camera_model == 'Grasshopper3 GS3-U3-23S6M':
            video_mode = self.epics_pvs['CamVideoMode'].get(as_string=True)
            readout_times = {
                'Mono8': {
                    'Mode0': 6.2,
                    'Mode1': 6.2,
                    'Mode5': 6.2,
                    'Mode7': 7.9
                },
                'Mono12Packed': {
                    'Mode0': 9.2,
                    'Mode1': 6.2,
                    'Mode5': 6.2,
                    'Mode7': 11.5
                },
                'Mono16': {
                    'Mode0': 12.2,
                    'Mode1': 6.2,
                    'Mode5': 6.2,
                    'Mode7': 12.2
                }
            }
            readout = readout_times[pixel_format][video_mode] / 1000.
        if camera_model == 'Oryx ORX-10G-51S5M':
            # video_mode   = self.epics_pvs['CamVideoMode'].get(as_string=True)
            readout_times = {
                'Mono8': 6.18,
                'Mono12Packed': 8.20,
                'Mono16': 12.34
            }
            readout = readout_times[pixel_format] / 1000.
        if readout is None:
            log.error(
                'Unsupported combination of camera model, pixel format and video mode: %s %s %s',
                camera_model, pixel_format, video_mode)
            return 0

        # We need to use the actual exposure time that the camera is using, not the requested time
        exposure = self.epics_pvs['CamAcquireTimeRBV'].value
        # Add some extra time to exposure time for margin.  These values are empirical.
        if exposure > 2.3:
            frame_time = exposure + .01
        elif exposure > 1.0:
            frame_time = exposure + .002
        else:
            frame_time = exposure + .001

        # If the time is less than the readout time then use the readout time
        if frame_time < readout:
            frame_time = readout
        return frame_time
示例#19
0
    def __init__(self, pv_files, macros):
        self.scan_is_running = False
        self.config_pvs = {}
        self.control_pvs = {}
        self.pv_prefixes = {}

        # These variables are set in begin_scan().
        # They are used to prevent reading PVs repeatedly, and so that if the users changes
        # a PV during the scan it won't mess things up.
        self.exposure_time = None
        self.rotation_start = None
        self.rotation_step = None
        self.rotation_stop = None
        self.rotation_resolution = None
        self.max_rotation_speed = None
        self.return_rotation = None
        self.num_angles = None
        self.num_dark_fields = None
        self.dark_field_mode = None
        self.num_flat_fields = None
        self.flat_field_mode = None
        self.total_images = None
        self.file_path_rbv = None
        self.file_name_rbv = None
        self.file_number = None
        self.file_template = None

        if not isinstance(pv_files, list):
            pv_files = [pv_files]
        for pv_file in pv_files:
            self.read_pv_file(pv_file, macros)

        if 'Rotation' not in self.control_pvs:
            log.error('RotationPVName must be present in autoSettingsFile')
            sys.exit()
        if 'Camera' not in self.pv_prefixes:
            log.error('CameraPVPrefix must be present in autoSettingsFile')
            sys.exit()
        if 'FilePlugin' not in self.pv_prefixes:
            log.error('FilePluginPVPrefix must be present in autoSettingsFile')
            sys.exit()

        #Define PVs we will need from the rotation motor, which is on another IOC
        rotation_pv_name = self.control_pvs['Rotation'].pvname
        self.control_pvs['RotationSpeed'] = PV(rotation_pv_name + '.VELO')
        self.control_pvs['RotationMaxSpeed'] = PV(rotation_pv_name + '.VMAX')
        self.control_pvs['RotationResolution'] = PV(rotation_pv_name + '.MRES')
        self.control_pvs['RotationSet'] = PV(rotation_pv_name + '.SET')
        self.control_pvs['RotationStop'] = PV(rotation_pv_name + '.STOP')
        self.control_pvs['RotationDmov'] = PV(rotation_pv_name + '.DMOV')
        self.control_pvs['RotationDirection'] = PV(rotation_pv_name + '.DIR')

        #Define PVs from the camera IOC that we will need
        prefix = self.pv_prefixes['Camera']
        camera_prefix = prefix + 'cam1:'
        self.control_pvs['CamManufacturer'] = PV(camera_prefix +
                                                 'Manufacturer_RBV')
        self.control_pvs['CamModel'] = PV(camera_prefix + 'Model_RBV')
        self.control_pvs['CamAcquire'] = PV(camera_prefix + 'Acquire')
        self.control_pvs['CamAcquireBusy'] = PV(camera_prefix + 'AcquireBusy')
        self.control_pvs['CamImageMode'] = PV(camera_prefix + 'ImageMode')
        self.control_pvs['CamTriggerMode'] = PV(camera_prefix + 'TriggerMode')
        self.control_pvs['CamNumImages'] = PV(camera_prefix + 'NumImages')
        self.control_pvs['CamNumImagesCounter'] = PV(camera_prefix +
                                                     'NumImagesCounter_RBV')
        self.control_pvs['CamAcquireTime'] = PV(camera_prefix + 'AcquireTime')
        self.control_pvs['CamAcquireTimeRBV'] = PV(camera_prefix +
                                                   'AcquireTime_RBV')
        self.control_pvs['CamBinX'] = PV(camera_prefix + 'BinX')
        self.control_pvs['CamBinY'] = PV(camera_prefix + 'BinY')
        self.control_pvs['CamWaitForPlugins'] = PV(camera_prefix +
                                                   'WaitForPlugins')
        self.control_pvs['PortNameRBV'] = PV(camera_prefix + 'PortName_RBV')

        # If this is a Point Grey camera then assume we are running ADSpinnaker
        # and create some PVs specific to that driver
        manufacturer = self.control_pvs['CamManufacturer'].get(as_string=True)
        model = self.control_pvs['CamModel'].get(as_string=True)
        if (manufacturer.find('Point Grey') !=
                -1) or (manufacturer.find('FLIR') != -1):
            self.control_pvs['CamExposureMode'] = PV(camera_prefix +
                                                     'ExposureMode')
            self.control_pvs['CamTriggerOverlap'] = PV(camera_prefix +
                                                       'TriggerOverlap')
            self.control_pvs['CamPixelFormat'] = PV(camera_prefix +
                                                    'PixelFormat')
            self.control_pvs['CamArrayCallbacks'] = PV(camera_prefix +
                                                       'ArrayCallbacks')
            self.control_pvs['CamFrameRateEnable'] = PV(camera_prefix +
                                                        'FrameRateEnable')
            self.control_pvs['CamTriggerSource'] = PV(camera_prefix +
                                                      'TriggerSource')
            if model.find('Grasshopper3') != -1:
                self.control_pvs['CamVideoMode'] = PV(camera_prefix +
                                                      'GC_VideoMode_RBV')

        # Set some initial PV values
        self.control_pvs['CamWaitForPlugins'].put('Yes')
        self.control_pvs['StartScan'].put(0)

        prefix = self.pv_prefixes['FilePlugin']
        self.control_pvs['FPNDArrayPort'] = PV(prefix + 'NDArrayPort')
        self.control_pvs['FPFileWriteMode'] = PV(prefix + 'FileWriteMode')
        self.control_pvs['FPNumCapture'] = PV(prefix + 'NumCapture')
        self.control_pvs['FPNumCaptured'] = PV(prefix + 'NumCaptured_RBV')
        self.control_pvs['FPCapture'] = PV(prefix + 'Capture')
        self.control_pvs['FPCaptureRBV'] = PV(prefix + 'Capture_RBV')
        self.control_pvs['FPFilePath'] = PV(prefix + 'FilePath')
        self.control_pvs['FPFilePathRBV'] = PV(prefix + 'FilePath_RBV')
        self.control_pvs['FPFilePathExists'] = PV(prefix +
                                                  'FilePathExists_RBV')
        self.control_pvs['FPFileName'] = PV(prefix + 'FileName')
        self.control_pvs['FPFileNameRBV'] = PV(prefix + 'FileName_RBV')
        self.control_pvs['FPFileNumber'] = PV(prefix + 'FileNumber')
        self.control_pvs['FPAutoIncrement'] = PV(prefix + 'AutoIncrement')
        self.control_pvs['FPFileTemplate'] = PV(prefix + 'FileTemplate')
        self.control_pvs['FPFullFileName'] = PV(prefix + 'FullFileName_RBV')
        self.control_pvs['FPAutoSave'] = PV(prefix + 'AutoSave')
        self.control_pvs['FPEnableCallbacks'] = PV(prefix + 'EnableCallbacks')

        # Set some initial PV values
        file_path = self.config_pvs['FilePath'].get(as_string=True)
        self.control_pvs['FPFilePath'].put(file_path)
        file_name = self.config_pvs['FileName'].get(as_string=True)
        self.control_pvs['FPFileName'].put(file_name)
        self.control_pvs['FPAutoSave'].put('No')
        self.control_pvs['FPFileWriteMode'].put('Stream')
        self.control_pvs['FPEnableCallbacks'].put('Enable')

        #Define PVs from the MCS or PSO that live on another IOC
        if 'MCS' in self.pv_prefixes:
            prefix = self.pv_prefixes['MCS']
            self.control_pvs['MCSEraseStart'] = PV(prefix + 'EraseStart')
            self.control_pvs['MCSStopAll'] = PV(prefix + 'StopAll')
            self.control_pvs['MCSPrescale'] = PV(prefix + 'Prescale')
            self.control_pvs['MCSDwell'] = PV(prefix + 'Dwell')
            self.control_pvs['MCSLNEOutputWidth'] = PV(prefix +
                                                       'LNEOutputWidth')
            self.control_pvs['MCSChannelAdvance'] = PV(prefix +
                                                       'ChannelAdvance')
            self.control_pvs['MCSMaxChannels'] = PV(prefix + 'MaxChannels')
            self.control_pvs['MCSNuseAll'] = PV(prefix + 'NuseAll')

        if 'PSO' in self.pv_prefixes:
            prefix = self.pv_prefixes['PSO']
            self.control_pvs['PSOscanDelta'] = PV(prefix + 'scanDelta')
            self.control_pvs['PSOstartPos'] = PV(prefix + 'startPos')
            self.control_pvs['PSOendPos'] = PV(prefix + 'endPos')
            self.control_pvs['PSOslewSpeed'] = PV(prefix + 'slewSpeed')
            self.control_pvs['PSOtaxi'] = PV(prefix + 'taxi')
            self.control_pvs['PSOfly'] = PV(prefix + 'fly')
            self.control_pvs['PSOscanControl'] = PV(prefix + 'scanControl')
            self.control_pvs['PSOcalcProjections'] = PV(prefix + 'numTriggers')
            self.control_pvs['ThetaArray'] = PV(prefix + 'motorPos.AVAL')

        if 'PvaPlugin' in self.pv_prefixes:
            prefix = self.pv_prefixes['PvaPlugin']
            self.control_pvs['PVANDArrayPort'] = PV(prefix + 'NDArrayPort')
            self.control_pvs['PVAEnableCallbacks'] = PV(prefix +
                                                        'EnableCallbacks')

        if 'RoiPlugin' in self.pv_prefixes:
            prefix = self.pv_prefixes['RoiPlugin']
            self.control_pvs['ROINDArrayPort'] = PV(prefix + 'NDArrayPort')
            self.control_pvs['ROIScale'] = PV(prefix + 'Scale')
            self.control_pvs['ROIBinX'] = PV(prefix + 'BinX')
            self.control_pvs['ROIBinY'] = PV(prefix + 'BinY')
            self.control_pvs['ROIEnableCallbacks'] = PV(prefix +
                                                        'EnableCallbacks')

        if 'CbPlugin' in self.pv_prefixes:
            prefix = self.pv_prefixes['CbPlugin']
            self.control_pvs['CBPortNameRBV'] = PV(prefix + 'PortName_RBV')
            self.control_pvs['CBNDArrayPort'] = PV(prefix + 'NDArrayPort')
            self.control_pvs['CBPreCount'] = PV(prefix + 'PreCount')
            self.control_pvs['CBPostCount'] = PV(prefix + 'PostCount')
            self.control_pvs['CBCapture'] = PV(prefix + 'Capture')
            self.control_pvs['CBCaptureRBV'] = PV(prefix + 'Capture_RBV')
            self.control_pvs['CBTrigger'] = PV(prefix + 'Trigger')
            self.control_pvs['CBTriggerRBV'] = PV(prefix + 'Trigger_RBV')
            self.control_pvs['CBCurrentQtyRBV'] = PV(prefix + 'CurrentQty_RBV')
            self.control_pvs['CBEnableCallbacks'] = PV(prefix +
                                                       'EnableCallbacks')
            self.control_pvs['CBStatusMessage'] = PV(prefix + 'StatusMessage')

        self.epics_pvs = {**self.config_pvs, **self.control_pvs}
        # Wait 1 second for all PVs to connect
        time.sleep(1)
        self.check_pvs_connected()
        # Setting the pva servers to broadcast dark and flat fields
        if 'PvaStream' in self.pv_prefixes:
            prefix = self.pv_prefixes['PvaStream']
            self.pva_stream_dark = pvaccess.PvObject({
                'value': [pvaccess.pvaccess.ScalarType.FLOAT],
                'sizex':
                pvaccess.pvaccess.ScalarType.INT,
                'sizey':
                pvaccess.pvaccess.ScalarType.INT
            })
            self.pva_server_dark = pvaccess.PvaServer(prefix + 'dark',
                                                      self.pva_stream_dark)

            self.pva_stream_flat = pvaccess.PvObject({
                'value': [pvaccess.pvaccess.ScalarType.FLOAT],
                'sizex':
                pvaccess.pvaccess.ScalarType.INT,
                'sizey':
                pvaccess.pvaccess.ScalarType.INT
            })
            self.pva_server_flat = pvaccess.PvaServer(prefix + 'flat',
                                                      self.pva_stream_flat)

        # Configure callbacks on a few PVs
        for epics_pv in ('MoveSampleIn', 'MoveSampleOut', 'StartScan',
                         'AbortScan', 'ExposureTime', 'FilePath',
                         'FPFilePathExists'):
            self.epics_pvs[epics_pv].add_callback(self.pv_callback)

        # Synchronize the FilePathExists PV
        self.copy_file_path_exists()

        # Set ^C interrupt to abort the scan
        signal.signal(signal.SIGINT, self.signal_handler)

        # Start the watchdog timer thread
        thread = threading.Thread(target=self.reset_watchdog,
                                  args=(),
                                  daemon=True)
        thread.start()