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. - Closes shutter. """ if self.epics_pvs['ReturnRotation'].get(as_string=True) == 'Yes': while True: ang1 = self.epics_pvs['RotationRBV'].value time.sleep(1) ang2 = self.epics_pvs['RotationRBV'].value print(ang1, ang2) if np.abs(ang1 - ang2) < 1e-4: break if np.abs(self.epics_pvs['RotationRBV'].value) > 720: log.warning('home stage') self.epics_pvs['RotationHomF'].put(1, wait=True) self.epics_pvs['LensSelect'].clear_callbacks() time.sleep(2) # Call the base class method super().end_scan() # Close shutter self.close_shutter()
def open_frontend_shutter(self): """Opens the shutters to collect flat fields or projections. This does the following: - Checks if we are in testing mode. If we are, do nothing else opens the 2-BM-A front-end shutter. """ if self.epics_pvs['Testing'].get(): log.warning('In testing mode, so not opening shutters.') else: # Open 2-BM-A front-end shutter if not self.epics_pvs['OpenShutter'] is None: pv = self.epics_pvs['OpenShutter'] value = self.epics_pvs['OpenShutterValue'].get(as_string=True) status = self.epics_pvs['ShutterStatus'].get(as_string=True) log.info('shutter status: %s', status) log.info('open shutter: %s, value: %s', pv, value) self.epics_pvs['OpenShutter'].put(value, wait=True) self.wait_frontend_shutter_open() # self.wait_pv(self.epics_pvs['ShutterStatus'], 1) status = self.epics_pvs['ShutterStatus'].get(as_string=True) log.info('shutter status: %s', status) #VN: fix later, fast shutter status check is needed time.sleep(2)
def close_shutter(self): """Closes the shutter to collect dark fields and at the end of a scan This does the following: - Checks if we are in testing mode. If we are, do nothing - Closes the 7-BM-B fast shutter. - Closes the beamline shutter. """ if self.epics_pvs['Testing'].get(): log.warning('In testing mode, so not closing shutters.') return # Close 7-BM-B fast shutter; don't wait for it if not self.epics_pvs['CloseFastShutter'] is None: pv = self.epics_pvs['CloseFastShutter'] value = self.epics_pvs['CloseFastShutterValue'].get(as_string=True) log.info('close fast shutter: %s, value: %s', pv, value) self.epics_pvs['CloseFastShutter'].put(value, wait=False) # Call the base class method if not self.epics_pvs['CloseShutter'] is None: pv = self.epics_pvs['CloseShutter'] value = self.epics_pvs['CloseShutterValue'].get(as_string=True) status = self.epics_pvs['ShutterStatus'].get(as_string=True) log.info('shutter status: %s', status) log.info('close shutter: %s, value: %s', pv, value) self.epics_pvs['CloseShutter'].put(value, wait=True) self.wait_pv(self.epics_pvs['ShutterStatus'], 1) status = self.epics_pvs['ShutterStatus'].get(as_string=True) log.info('shutter status: %s', status)
def open_shutter(self): """Opens the shutter to collect flat fields or projections. This does the following: - Checks if we are in testing mode. If we are, do nothing. - Opens the front end shutter, waiting for it to indicate it is open. This is copied from the 2-BM implementation 9/2020 - Opens the 7-BM-B fast shutter. """ if self.epics_pvs['Testing'].get(): log.warning('In testing mode, so not opening shutters.') return # Open the front end shutter if not self.epics_pvs['OpenShutter'] is None: pv = self.epics_pvs['OpenShutter'] value = self.epics_pvs['OpenShutterValue'].get(as_string=True) status = self.epics_pvs['ShutterStatus'].get(as_string=True) log.info('shutter status: %s', status) log.info('open shutter: %s, value: %s', pv, value) self.epics_pvs['OpenShutter'].put(value, wait=True) self.wait_pv(self.epics_pvs['ShutterStatus'], 0) status = self.epics_pvs['ShutterStatus'].get(as_string=True) log.info('shutter status: %s', status) # Open 7-BM-B fast shutter if not self.epics_pvs['OpenFastShutter'] is None: pv = self.epics_pvs['OpenFastShutter'] value = self.epics_pvs['OpenFastShutterValue'].get(as_string=True) log.info('open fast shutter: %s, value: %s', pv, value) self.epics_pvs['OpenFastShutter'].put(value, wait=True)
def add_theta(self): """Add theta at the end of a scan. Taken from tomoscan_2BM.py function. This gives the correct theta for scans with missing frames """ log.info('add theta') if self.theta is None: log.warning('no theta to add') return 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: 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 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)
def begin_scan(self): """Performs the operations needed at the very start of a scan. This does the following: - Calls the base class method. - Opens the front-end shutter. - Sets the PSO controller. - Creates theta array using list from PSO. """ log.info('begin scan') # Call the base class method super().begin_scan() # Opens the front-end shutter self.open_frontend_shutter() # Confirm angle step is an integer number of encoder pulses # Pass the user selected values to the PSO self.epics_pvs['PSOstartPos'].put(self.rotation_start, wait=True) self.wait_pv(self.epics_pvs['PSOstartPos'], self.rotation_start) self.epics_pvs['PSOendPos'].put(self.rotation_stop, wait=True) self.wait_pv(self.epics_pvs['PSOendPos'], self.rotation_stop) # Compute and set the motor speed time_per_angle = self.compute_frame_time()+7.2/1000 motor_speed = self.rotation_step / time_per_angle self.epics_pvs['PSOslewSpeed'].put(motor_speed) self.wait_pv(self.epics_pvs['PSOslewSpeed'], motor_speed) self.epics_pvs['PSOscanDelta'].put(self.rotation_step, wait=True) self.wait_pv(self.epics_pvs['PSOscanDelta'], self.rotation_step) # Get the number of projections and angle steps calculated by the PSO calc_rotation_step = self.epics_pvs['PSOscanDelta'].value calc_num_proj = int(self.epics_pvs['PSOcalcProjections'].value) # If this is different from the user selected values adjust them if calc_rotation_step != self.rotation_step: # This should happen most of the time since rotation_step is rounded down to the closest integer # number of encoder pulses log.warning('PSO changed rotation step from %s to %s', self.rotation_step, calc_rotation_step) self.rotation_step = calc_rotation_step if calc_num_proj != self.num_angles: # This happens rarely an it is a +/-1 change in the number of projections to make sure that # after the rotation_step round down we don't pass the user set rotation_stop log.warning('PSO changed number of projections from %s to %s', self.num_angles, calc_num_proj) self.num_angles = calc_num_proj self.epics_pvs['PSOscanControl'].put('Standard') self.wait_pv(self.epics_pvs['PSOscanControl'], 0) time.sleep(1) # Create theta array self.theta = [] self.theta = self.epics_pvs['ThetaArray'].get(count=int(self.num_angles)) self.begin_stream()
def yes_or_no(question): answer = str(input(question + " (Y/N): ")).lower().strip() while not(answer == "y" or answer == "yes" or answer == "n" or answer == "no"): log.warning("Input yes or no") answer = str(input(question + "(Y/N): ")).lower().strip() if answer[0] == "y": return True else: return False
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. - Closes shutter. - Add theta to the raw data file. - Copy raw data to data analysis computer. """ if self.return_rotation == 'Yes': # Reset rotation position by mod 360 , the actual return # to start position is handled by super().end_scan() log.info('wait until the stage is stopped') time.sleep(self.epics_pvs['RotationAccelTime'].get() * 1.2) ang = self.epics_pvs['RotationRBV'].get() current_angle = np.sign(ang) * (np.abs(ang) % 360) self.epics_pvs['RotationSet'].put('Set', wait=True) self.epics_pvs['Rotation'].put(current_angle, wait=True) self.epics_pvs['RotationSet'].put('Use', wait=True) # Call the base class method super().end_scan() # Close shutter self.close_shutter() # Stop the file plugin self.epics_pvs['FPCapture'].put('Done') self.wait_pv(self.epics_pvs['FPCaptureRBV'], 0) # Add theta in the hdf file self.add_theta() # Copy raw data to data analysis computer if self.epics_pvs['CopyToAnalysisDir'].get(): log.info( 'Automatic data trasfer to data analysis computer is enabled.') full_file_name = self.epics_pvs['FPFullFileName'].get( as_string=True) remote_analysis_dir = self.epics_pvs['RemoteAnalysisDir'].get( as_string=True) dm.scp(full_file_name, remote_analysis_dir) else: log.warning( 'Automatic data trasfer to data analysis computer is disabled.' )
def auto_copy_data(self): '''Copies data from detector computer to analysis computer. ''' # Copy raw data to data analysis computer if self.epics_pvs['CopyToAnalysisDir'].get(): log.info('Automatic data trasfer to data analysis computer is enabled.') full_file_name = self.epics_pvs['FPFullFileName'].get(as_string=True) remote_analysis_dir = self.epics_pvs['RemoteAnalysisDir'].get(as_string=True) dm.scp(full_file_name, remote_analysis_dir) else: log.warning('Automatic data trasfer to data analysis computer is disabled.')
def set_scan_exposure_time(self, exposure_time=None): """Sets the camera exposure time during the scan. The exposure_time is written to the camera's ``AcquireTime`` PV. Parameters ---------- exposure_time : float, optional The exposure time to use. If None then the value of the ``ExposureTime`` PV is used. """ if exposure_time is None: exposure_time = self.epics_pvs['ExposureTime'].value log.warning('Setting exposure time: %f s', exposure_time) self.epics_pvs['CamAcquireTime'].put(exposure_time, wait=True, timeout = 10.)
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
def close_shutter(self): """Closes the shutters to collect dark fields. This does the following: - Closes the 2-BM-A fast shutter. """ # Close 2-BM-A fast shutter if not self.epics_pvs['CloseFastShutter'] is None: pv = self.epics_pvs['CloseFastShutter'] value = self.epics_pvs['CloseFastShutterValue'].get(as_string=True) log.info('close fast shutter: %s, value: %s', pv, value) self.epics_pvs['CloseFastShutter'].put(value, wait=True) log.warning('close fast shutter sleep 2 sec') time.sleep(2)
def end_scan(self): """Performs the operations needed at the very end of a scan. This does the following: - Reset rotation position by mod 360. - Calls the base class method. - Stop the file plugin. - Closes shutter. - Add theta to the raw data file. - Copy raw data to data analysis computer. """ if self.return_rotation == 'Yes': # Reset rotation position by mod 360 , the actual return # to start position is handled by super().end_scan() current_angle = self.epics_pvs['Rotation'].get() % 360 self.epics_pvs['RotationSet'].put('Set', wait=True) self.epics_pvs['Rotation'].put(current_angle, wait=True) self.epics_pvs['RotationSet'].put('Use', wait=True) # Call the base class method super().end_scan() # Close shutter self.close_shutter() # Stop the file plugin self.epics_pvs['FPCapture'].put('Done') self.wait_pv(self.epics_pvs['FPCaptureRBV'], 0) # Add theta in the hdf file self.add_theta() # Copy raw data to data analysis computer if self.epics_pvs['CopyToAnalysisDir'].get(): log.info( 'Automatic data trasfer to data analysis computer is enabled.') full_file_name = self.epics_pvs['FPFullFileName'].get( as_string=True) remote_analysis_dir = self.epics_pvs['RemoteAnalysisDir'].get( as_string=True) dm.scp(full_file_name, remote_analysis_dir) else: log.warning( 'Automatic data trasfer to data analysis computer is disabled.' )
def copy_flat_dark_to_hdf(self): """Copies the flat and dark field data to the HDF5 file with the projection data. This allows the file to be a fully valid DXchange file. Once the file is created it will be copied to the remote data analysis computer (if CopyToAnalysisDir is set to yes) """ log.info('save dark and flat to projection hdf file') fname = self.epics_pvs['FPFullFileName'].get(as_string=True) basename = os.path.basename(fname) dirname = os.path.dirname(fname) darkfield_name = os.path.join(dirname, 'dark_fields_' + basename) flatfield_name = os.path.join(dirname, 'flat_fields_' + basename) log.info('save dark fields') cmd = 'cp ' + os.path.join(dirname, 'dark_fields.h5') + ' ' + darkfield_name os.system(cmd) log.info('save flat fields') cmd = 'cp ' + os.path.join(dirname, 'flat_fields.h5') + ' ' + flatfield_name os.system(cmd) with h5py.File(fname, 'r+') as proj_hdf: if 'data_white' in proj_hdf['/exchange'].keys(): del (proj_hdf['/exchange/data_white']) with h5py.File(flatfield_name, 'r') as flat_hdf: proj_hdf['/exchange'].create_dataset( 'data_white', data=flat_hdf['/exchange/data_white'][...]) if 'data_dark' in proj_hdf['/exchange'].keys(): del (proj_hdf['/exchange/data_dark']) with h5py.File(darkfield_name, 'r') as dark_hdf: proj_hdf['/exchange'].create_dataset( 'data_dark', data=dark_hdf['/exchange/data_dark'][...]) log.info('done saving dark and flat to projection hdf file') # Copy raw data to data analysis computer if self.epics_pvs['CopyToAnalysisDir'].get(): log.info( 'Automatic data trasfer to data analysis computer is enabled.') full_file_name = self.epics_pvs['FPFullFileName'].get( as_string=True) remote_analysis_dir = self.epics_pvs['RemoteAnalysisDir'].get( as_string=True) dm.scp(full_file_name, remote_analysis_dir) else: log.warning( 'Automatic data trasfer to data analysis computer is disabled.' )
def show_config(args): """Log all values set in the args namespace. Arguments are grouped according to their section and logged alphabetically using the DEBUG log level thus --verbose is required. """ args = args.__dict__ log.warning('tomoscan status start') for section, name in zip(SECTIONS, NICE_NAMES): entries = sorted((k for k in args.keys() if k.replace('_', '-') in SECTIONS[section])) if entries: for entry in entries: value = args[entry] if args[entry] != None else "-" log.info(" {:<16} {}".format(entry, value)) log.warning('tomoscan status end')
def close_shutter(self): """Closes the shutters to collect dark fields. This does the following: - Closes the 2-BM fast shutter. """ # Close 2-BM fast shutter if not self.epics_pvs['CloseFastShutter'] is None: pv = self.epics_pvs['CloseFastShutter'] value = self.epics_pvs['CloseFastShutterValue'].get(as_string=True) log.info('close fast shutter: %s, value: %s', pv, value) self.epics_pvs['CloseFastShutter'].put(value, wait=True) log.warning( "Wait 2s - Temporarily while there is no fast shutter at 2bmb " ) time.sleep(2)
def set_flat_exposure_time(self, exposure_time=None): """Sets the camera exposure time for flat fields. The exposure_time is written to the camera's ``AcquireTime`` PV. Parameters ---------- exposure_time : float, optional The exposure time to use. If None then the value of the ``FlatExposureTime`` PV is used. """ if self.epics_pvs['DifferentFlatExposure'].get(as_string=True) == 'Same': self.set_exposure_time(exposure_time) return if exposure_time is None: exposure_time = self.epics_pvs['FlatExposureTime'].value log.warning('Setting flat field exposure time: %f s', exposure_time) self.epics_pvs['CamAcquireTime'].put(exposure_time, wait=True, timeout = 10.)
def open_shutter(self): """Opens the shutters to collect flat fields or projections. This does the following: - Opens the 2-BM-A fast shutter. """ # Open 2-BM-A fast shutter if not self.epics_pvs['OpenFastShutter'] is None: pv = self.epics_pvs['OpenFastShutter'] value = self.epics_pvs['OpenFastShutterValue'].get(as_string=True) log.info('open fast shutter: %s, value: %s', pv, value) self.epics_pvs['OpenFastShutter'].put(value, wait=True) log.warning( "Wait 2s - Temporarily while there is no fast shutter at 2bmb " ) time.sleep(2)
def close_frontend_shutter(self): """Closes the shutters to collect dark fields. This does the following: - Closes the 2-BM-A front-end shutter. """ if self.epics_pvs['Testing'].get(): log.warning('In testing mode, so not opening shutters.') else: # Close 2-BM-A front-end shutter if not self.epics_pvs['CloseShutter'] is None: pv = self.epics_pvs['CloseShutter'] value = self.epics_pvs['CloseShutterValue'].get(as_string=True) status = self.epics_pvs['ShutterStatus'].get(as_string=True) log.info('shutter status: %s', status) log.info('close shutter: %s, value: %s', pv, value) self.epics_pvs['CloseShutter'].put(value, wait=True) self.wait_pv(self.epics_pvs['ShutterStatus'], 0) status = self.epics_pvs['ShutterStatus'].get(as_string=True) log.info('shutter status: %s', status)
def compute_positions_PSO(self): '''Computes several parameters describing the fly scan motion. Computes the spacing between points, ensuring it is an integer number of encoder counts. Uses this spacing to recalculate the end of the scan, if necessary. Computes the taxi distance at the beginning and end of scan to allow the stage to accelerate to speed. Assign the fly scan angular position to theta[] ''' overall_sense, user_direction = self._compute_senses() # 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) # Change the rotation step Python variable and PV self.rotation_step = delta_encoder_counts / encoder_multiply self.epics_pvs['RotationStep'].put(self.rotation_step) # Compute the time for each frame time_per_angle = self.compute_frame_time() self.motor_speed = np.abs(self.rotation_step) / time_per_angle # Get the distance needed for acceleration = 1/2 a t^2 = 1/2 * v * t motor_accl_time = float(self.epics_pvs['RotationAccelTime'].get() ) # Acceleration time in s accel_dist = motor_accl_time / 2.0 * float(self.motor_speed) # Make taxi distance an integer number of measurement deltas >= accel distance # Add 1/2 of a delta to ensure that we are really up to speed. if self.rotation_step > 0: taxi_dist = math.ceil(accel_dist / self.rotation_step + 0.5) * self.rotation_step else: taxi_dist = math.floor(accel_dist / self.rotation_step - 0.5) * self.rotation_step self.epics_pvs['PSOStartTaxi'].put(self.rotation_start - taxi_dist * user_direction) #Where will the last point actually be? self.rotation_stop = ( self.rotation_start + (self.num_angles - 1) * self.rotation_step * user_direction) self.epics_pvs['PSOEndTaxi'].put(self.rotation_stop + taxi_dist * user_direction) # Assign the fly scan angular position to theta[] self.theta = self.rotation_start + np.arange( self.num_angles) * self.rotation_step * user_direction self.pva_stream_theta['value'] = self.theta self.pva_stream_theta['sizex'] = self.num_angles
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)
def open_shutter(self): """Opens the shutter to collect flat fields or projections. This does the following: - Checks if we are in testing mode. If we are, do nothing. - Calls the base class method. - Opens the 2-BM-A fast shutter. """ if self.epics_pvs['Testing'].get(): log.warning('In testing mode, so not opening shutters.') return # Call the base class method super().open_shutter() # Open 7-BM-B fast shutter if not self.epics_pvs['OpenFastShutter'] is None: pv = self.epics_pvs['OpenFastShutter'] value = self.epics_pvs['OpenFastShutterValue'].get(as_string=True) log.info('open fast shutter: %s, value: %s', pv, value) self.epics_pvs['OpenFastShutter'].put(value, wait=True)
def begin_scan(self): """Performs the operations needed at the very start of a scan. This does the following: - Set data directory. - Set the TomoScan xml files - Calls the base class method. - Opens the front-end shutter. - Sets the PSO controller. - Creates theta array using list from PSO. """ log.info('begin scan') # Set data directory file_path = self.epics_pvs['DetectorTopDir'].get( as_string=True) + self.epics_pvs['ExperimentYearMonth'].get( as_string=True ) + os.path.sep + self.epics_pvs['UserLastName'].get( as_string=True) + os.path.sep self.epics_pvs['FilePath'].put(file_path, wait=True) if self.epics_pvs['ReturnRotation'].get(as_string=True) == 'Yes': if np.abs(self.epics_pvs['RotationRBV'].get()) > 720: log.warning('home stage') self.epics_pvs['RotationHomF'].put(1, wait=True) self.lens_cur = self.epics_pvs['LensSelect'].get() # Call the base class method super().begin_scan() # Opens the front-end shutter self.open_frontend_shutter() self.epics_pvs['LensSelect'].add_callback(self.pv_callback_stream_2bm)
def close_shutter(self): """Closes the shutter to collect dark fields. This does the following: - Checks if we are in testing mode. If we are, do nothing - Calls the base class method. - Closes the 7-BM-B fast shutter. """ if self.epics_pvs['Testing'].get(): log.warning('In testing mode, so not closing shutters.') return # Call the base class method super().close_shutter() # Close 7-BM-B fast shutter if not self.epics_pvs['CloseFastShutter'] is None: pv = self.epics_pvs['CloseFastShutter'] value = self.epics_pvs['CloseFastShutterValue'].get(as_string=True) log.info('close fast shutter: %s, value: %s', pv, value) self.epics_pvs['CloseFastShutter'].put(value, wait=True)
def wait_frontend_shutter_open(self, timeout=-1): """Waits for the front end shutter to open, or for ``abort_scan()`` to be called. While waiting this method periodically tries to open the shutter.. Parameters ---------- timeout : float The maximum number of seconds to wait before raising a ShutterTimeoutError exception. Raises ------ ScanAbortError If ``abort_scan()`` is called ShutterTimeoutError If the open shutter has not completed within timeout value. """ start_time = time.time() pv = self.epics_pvs['OpenShutter'] value = self.epics_pvs['OpenShutterValue'].get(as_string=True) log.info('open shutter: %s, value: %s', pv, value) elapsed_time = 0 while True: if self.epics_pvs['ShutterStatus'].get() == int(value): log.warning("Shutter is open in %f s", elapsed_time) return if not self.scan_is_running: exit() value = self.epics_pvs['OpenShutterValue'].get() time.sleep(1.0) current_time = time.time() elapsed_time = current_time - start_time log.warning("Waiting on shutter to open: %f s", elapsed_time) self.epics_pvs['OpenShutter'].put(value, wait=True) if timeout > 0: if elapsed_time >= timeout: exit()
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. - Closes shutter. - Add theta to the raw data file. - Copy raw data to data analysis computer. """ if self.return_rotation == 'Yes': # Reset rotation position by mod 360 , the actual return # to start position is handled by super().end_scan() # allow stage to stop log.info('wait until the stage is stopped') time.sleep(self.epics_pvs['RotationAccelTime'].get() * 1.2) ang = self.epics_pvs['RotationRBV'].get() current_angle = np.sign(ang) * (np.abs(ang) % 360) self.epics_pvs['RotationSet'].put('Set', wait=True) self.epics_pvs['Rotation'].put(current_angle, wait=True) self.epics_pvs['RotationSet'].put('Use', wait=True) # Close shutter self.close_shutter() # Stop the file plugin self.epics_pvs['FPCapture'].put('Done') self.wait_pv(self.epics_pvs['FPCaptureRBV'], 0) # Add theta in the hdf file self.add_theta() log.info('Adding a frame from the IP camera') ret, frame = cv2.VideoCapture( 'http://*****:*****@164.54.113.162/cgi-bin/mjpeg?stream=1' ).read() # we should hide the password #station A # NetBooter = NetBooter_Control(mode='telnet',id=self.access_dic['pdu_username'],password=self.access_dic['pdu_password'],ip=self.access_dic['pdu_ip_address']) # NetBooter.power_on(1) # log.info('wait 10 sec while the web camera has focused') # time.sleep(10) # ret, frame = cv2.VideoCapture('http://*****:*****@164.54.113.137/cgi-bin/mjpeg?stream=1').read()# we should hide the password #ret, frame = cv2.VideoCapture('http://' + self.access_dic['webcam_username'] +':' + self.access_dic['webcam_password'] + '@' + self.access_dic['webcam_ip_address'] + '/cgi-bin/mjpeg?stream=1').read() # NetBooter.power_off(1) if ret == True: full_file_name = self.epics_pvs['FPFullFileName'].get( as_string=True) with h5py.File(full_file_name, 'r+') as fid: fid.create_dataset('exchange/web_camera_frame', data=frame) log.info('The frame was added') else: log.warning('The frame was not added') # Copy raw data to data analysis computer if self.epics_pvs['CopyToAnalysisDir'].get(): log.info( 'Automatic data trasfer to data analysis computer is enabled.') full_file_name = self.epics_pvs['FPFullFileName'].get( as_string=True) remote_analysis_dir = self.epics_pvs['RemoteAnalysisDir'].get( as_string=True) dm.scp(full_file_name, remote_analysis_dir) else: log.warning( 'Automatic data trasfer to data analysis computer is disabled.' ) # Call the base class method super().end_scan()
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 data directory. - Opens the front-end shutter. - Sets the PSO controller. - Creates theta array using list from PSO. - Turns on data capture. """ log.info('begin scan') # Set data directory file_path = self.epics_pvs['DetectorTopDir'].get(as_string=True) + self.epics_pvs['ExperimentYearMonth'].get(as_string=True) + os.path.sep + self.epics_pvs['UserLastName'].get(as_string=True) + os.path.sep self.epics_pvs['FilePath'].put(file_path, wait=True) # Call the base class method super().begin_scan() # Opens the front-end shutter self.open_frontend_shutter() # Confirm angle step is an integer number of encoder pulses # Pass the user selected values to the PSO self.epics_pvs['PSOstartPos'].put(self.rotation_start, wait=True) self.wait_pv(self.epics_pvs['PSOstartPos'], self.rotation_start) self.epics_pvs['PSOendPos'].put(self.rotation_stop+self.rotation_step, wait=True) self.wait_pv(self.epics_pvs['PSOendPos'], self.rotation_stop+self.rotation_step) # Compute and set the motor speed time_per_angle = self.compute_frame_time()#+7.2/1000 ##no overlap mode -> time_per_angle=exposure+readout motor_speed = self.rotation_step / time_per_angle self.epics_pvs['PSOslewSpeed'].put(motor_speed) self.wait_pv(self.epics_pvs['PSOslewSpeed'], motor_speed) self.epics_pvs['PSOscanDelta'].put(self.rotation_step, wait=True) self.wait_pv(self.epics_pvs['PSOscanDelta'], self.rotation_step) # Get the number of projections and angle steps calculated by the PSO calc_rotation_step = self.epics_pvs['PSOscanDelta'].value calc_num_proj = int(self.epics_pvs['PSOcalcProjections'].value) # If this is different from the user selected values adjust them if calc_rotation_step != self.rotation_step: # This should happen most of the time since rotation_step is rounded down to the closest integer # number of encoder pulses log.warning('PSO changed rotation step from %s to %s', self.rotation_step, calc_rotation_step) self.rotation_step = calc_rotation_step if calc_num_proj != self.num_angles: # This happens rarely an it is a +/-1 change in the number of projections to make sure that # after the rotation_step round down we don't pass the user set rotation_stop log.warning('PSO changed number of projections from %s to %s', self.num_angles, calc_num_proj) self.num_angles = calc_num_proj self.epics_pvs['PSOscanControl'].put('Standard') self.wait_pv(self.epics_pvs['PSOscanControl'], 0) time.sleep(1) # # Create theta array self.theta = [] self.theta = self.epics_pvs['ThetaArray'].get(count=int(self.num_angles)) print(self.theta,self.num_angles) # Compute total number of frames to capture 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 # Set the total number of frames to capture and start capture on file plugin self.epics_pvs['FPNumCapture'].put(self.total_images, wait=True) self.epics_pvs['FPCapture'].put('Capture')
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)