diff --git a/pxiii_bec/scans/mx_measurements.py b/pxiii_bec/scans/mx_measurements.py index dd61fb9..41573c4 100644 --- a/pxiii_bec/scans/mx_measurements.py +++ b/pxiii_bec/scans/mx_measurements.py @@ -48,6 +48,9 @@ class AerotechFlyscanBase(AsyncFlyScanBase): Low-level base class for standard scans at the PX beamlines at SLS. Theese scans use the A3200 rotation stage and the actual experiment is performed using an AeroBasic script controlled via global variables. + + IMPORTANT: The AeroBasic scripts take care of the PSO configuration. + """ scan_report_hint = "table" arg_input = {} @@ -126,17 +129,17 @@ class AerotechFlyscanBase(AsyncFlyScanBase): class MeasureStandardWedge(AerotechFlyscanBase): - """ Standard wedge scan + """ Standard wedge scan using the OMEGA motor - Measure a standard continous line scan from `start` to `start` + `range` - during `scan_time` on the Omega axis. + Measure an absolute continous line scan from `start` to `start` + `range` + during `move_time` on the Omega axis with PSO output. The scan itself is executed by the scan service running on the Aerotech controller. Ophyd just configures, launches it and waits for completion. Example ------- - >>> scans.standard_wedge(start=42, range=10, scan_time=20) + >>> scans.standard_wedge(start=42, range=10, move_time=20) Parameters ---------- @@ -144,26 +147,26 @@ class MeasureStandardWedge(AerotechFlyscanBase): Scan start position of the axis. range : (float, float) Scan range of the axis. - scan_time : (float) - Total fly time for the movement [s]. + move_time : (float) + Total travel time for the movement [s]. ready_rate : float, optional No clue what is this... (default=500) """ scan_name = "standard_wedge" - required_kwargs = ["start", "range", "scan_time"] + required_kwargs = ["start", "range", "move_time"] def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = ['omega'] + self.axis = ['abr.omega'] # Set parameters from meaningful inputs self.scan_start = parameter['kwargs'].get("start") self.scan_range = parameter['kwargs'].get("range") - self.scan_scan_time = parameter['kwargs'].get("scan_time") + self.scan_move_time = parameter['kwargs'].get("move_time") self.scan_ready_rate = parameter['kwargs'].get("ready_rate", 500) self.abr_command = AbrCmd.MEASURE_STANDARD self.abr_globals = { "var_1" : self.scan_start, "var_2" : self.scan_range, - "var_3" : self.scan_scan_time, + "var_3" : self.scan_move_time, "var_4" : self.scan_ready_rate, } # Call base class @@ -171,40 +174,88 @@ class MeasureStandardWedge(AerotechFlyscanBase): -class MeasureScanSlits(AerotechFlyscanBase): - """ Slit scan - Measure a standard slit scan. +class MeasureVerticalLine(AerotechFlyscanBase): + """ Vertical line scan using the GMY motor + + Simple relative continous line scan that records a single vertical line + with PSO output. There's no actual stepping, it's only used for velocity + calculation. The scan itself is executed by the scan service running on the Aerotech controller. Ophyd just configures, launches it and waits for completion. Example ------- - >>> scans.slit_scan(start=42, range=10, scan_time=20) + >>> scans.measure_vline(range_y=12, steps_y=40, exp_time=0.1) Parameters ---------- - delta_x : (float, float) - Scan range with slit in x. - delta_y : (float, float) - Scan range with slit in y. - velocity : (float) - Scan velocity [mm/s]. + range_y : float + Step size [mm]. + steps_y : int + Scan range of the axis. + exp_time : float + Eeffective exposure time per step [s] """ - scan_name = "slit_scan" - required_kwargs = ["delta_x", "delta_y", "velocity"] + scan_name = "measure_vline" + required_kwargs = ["exp_time", "range_y", "steps_y"] def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = [] + self.axis = ['abr.gmy'] + # Set parameters from meaningful inputs + self.scan_exp_time = parameter['kwargs'].get("exp_time") + self.scan_range_y = parameter['kwargs'].get("range_y") + self.scan_stepnum_y = parameter['kwargs'].get("steps_y") + self.scan_stepsize_y = self.scan_range_y / self.scan_stepnum_y + + self.abr_command = AbrCmd.VERTICAL_LINE_SCAN + self.abr_globals = { + 'var_1': self.scan_exp_time, + 'var_2': self.scan_stepsize_y, + 'var_3': self.scan_stepnum_y, + } + # Call base class + super().__init__(parameter=parameter, **kwargs) + + + +class MeasureScanSlits(AerotechFlyscanBase): + """ Coordinated scan + + Measure a hardware coordinated relative line scan with the X- and Y axes. + This is used to accurately track solid supports with long linear grooves, + as theese might be slightly rotated. + + The scan itself is executed by the scan service running on the Aerotech + controller. Ophyd just configures, launches it and waits for completion. + + Example + ------- + >>> scans.slit_scan(range=[10, 0.3], scan_time=20) + + Parameters + ---------- + range_x : float + Move distance in X [mm]. + range_y : float + Move distance in Y [mm]. + velocity : float + Effective movement velocity [mm/s]. + """ + scan_name = "slit_scan" + required_kwargs = ["range", "velocity"] + + def __init__(self, *args, parameter: dict = None, **kwargs): + self.axis = ["abr.gmx", "abr.gmy"] # Set parameters from meaningful inputs self.abr_command = AbrCmd.SLIT_SCAN - self.scan_delta_x = parameter['kwargs'].get("delta_x") - self.scan_delta_y = parameter['kwargs'].get("delta_y") + self.scan_range_x = parameter['kwargs'].get("range_x") + self.scan_range_y = parameter['kwargs'].get("range_y") self.scan_velocity = parameter['kwargs'].get("velocity") self.abr_globals = { - "var_1" : self.scan_delta_x, - "var_2" : self.scan_delta_y, + "var_1" : self.scan_range_x, + "var_2" : self.scan_range_y, "var_3" : self.scan_velocity, } # Call base class @@ -215,40 +266,45 @@ class MeasureScanSlits(AerotechFlyscanBase): class MeasureRasterSimple(AerotechFlyscanBase): """ Simple raster scan - Measure a simplified raster scan, assuming the goniometer is currently - at the CENTER of TOP-LEFT CELL. + Measure a simplified relative zigzag raster scan in the X-Y plane. + The scan is relative assumes the goniometer is currently at the CENTER of + the first cell (i.e. TOP-LEFT). Each line is executed as a single continous + movement, i.e. there's no actual stepping in the X direction. The scan itself is executed by the scan service running on the Aerotech controller. Ophyd just configures, launches it and waits for completion. Example ------- - >>> scans.raster_simple(exp_time=0.5, cell_width=0.2, cell_height=0.2, ncols=10, nrows=10) + >>> scans.raster_simple(exp_time=0.1, range_x=4, range_y=4, steps_x=80, steps_y=80) Parameters ---------- - exp_time : (float) - Exposure time per point [s]. - cell_width : (float) + exp_time : float + Effective exposure time for each cell along the X axis [s]. + range_x : float Scan step size [mm]. - cell_height : (float) + range_y : float Scan step size [mm]. - ncols : (int) - Number of scan steps. - nrows : (int) - Number of scan steps. + steps_x : int + Number of scan steps in X (fast). Only used for velocity calculation. + steps_y : int + Number of scan steps in Y (slow). """ scan_name = "raster_simple" - required_kwargs = ["exp_time", "cell_width", "cell_height", "ncols", "nrows"] + required_kwargs = ["exp_time", "range_x", "range_y", "steps_x", "steps_y"] def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = [] + self.axis = ['abr.gmx', 'abr.gmy'] # Set parameters from meaningful inputs self.scan_exp_time = parameter['kwargs'].get("exp_time") - self.scan_stepsize_x = parameter['kwargs'].get("cell_width") - self.scan_stepsize_y = parameter['kwargs'].get("cell_height") - self.scan_stepnum_x = parameter['kwargs'].get("ncols") - self.scan_stepnum_y = parameter['kwargs'].get("nrows") + self.scan_range_x = parameter['kwargs'].get("range_x") + self.scan_range_y = parameter['kwargs'].get("range_y") + self.scan_stepnum_x = parameter['kwargs'].get("steps_x") + self.scan_stepnum_y = parameter['kwargs'].get("steps_y") + self.scan_stepsize_x = self.scan_range_x / self.scan_stepnum_x + self.scan_stepsize_y = self.scan_range_y / self.scan_stepnum_y + self.abr_command = AbrCmd.RASTER_SCAN_SIMPLE self.abr_raster_reset = True self.abr_globals = { @@ -269,29 +325,33 @@ class MeasureRasterSimple(AerotechFlyscanBase): class MeasureSASTT(AerotechFlyscanBase): """ Small angle scanning tensor tomography scan - Measure a Small Angle Scanning Tensor Tomography scan on the specified - grid assuming the goniometer is currently at the CENTER of the required - grid. + Measure a single projection for Small Angle Scanning Tensor Tomography. + It's essentially a separate grid scan, that can be modified independently. + The scan is relative and assumes the goniometer is currently at the CENTER + of the first cell (i.e. TOP-LEFT). Each line is executed as a single + continous movement, there's no actual stepping in the X direction. + + NOTE: Not all beamlines have all SASTT modes implemented. The scan itself is executed by the scan service running on the Aerotech controller. Ophyd just configures, launches it and waits for completion. Example ------- - >>> scans.measure_sastt(exp_time=1.0, cell_width=0.01, cell_height=0.01, ncols=100, nrows=100) + >>> scans.measure_sastt(exp_time=0.01, range_x=1, range_y=1, steps_x=200, steps_y=200) Parameters ---------- - exp_time : (float) - Exposure time per point [s]. - cell_width : (float) + exp_time : float + Effective exposure time for each cell along the X axis [s]. + range_x : float Scan step size [mm]. - cell_height : (float) + range_y : float Scan step size [mm]. - ncols : (int) - Number of scan steps. - nrows : (int) - Number of scan steps. + steps_x : int + Number of scan steps in X (fast). Only used for velocity calculation. + steps_y : int + Number of scan steps in Y (slow). odd_tweak : (float) Distance to be added before the open shutter command [mm]. Used only in scan version=3. Should be small (default: 0)! @@ -305,10 +365,10 @@ class MeasureSASTT(AerotechFlyscanBase): 3 = snake scan alternating PSO window for even/odd rows """ scan_name = "measure_sastt" - required_kwargs = ["exp_time", "cell_width", "cell_height", "ncols", "nrows"] + required_kwargs = ["exp_time", "range_x", "range_y", "steps_x", "steps_y"] def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = [] + self.axis = ['abr.gmx', 'abr.gmy'] # Set parameters from meaningful inputs self.scan_version = int(parameter['kwargs'].get("version", 1)) if self.scan_version==1: @@ -319,14 +379,18 @@ class MeasureSASTT(AerotechFlyscanBase): self.abr_command = AbrCmd.SCAN_SASTT_V3 else: raise RuntimeError(f"Non existing SAS-TT scan mode: {self.scan_version}") - + + # Set parameters from meaningful inputs self.scan_exp_time = parameter['kwargs'].get("exp_time") - self.scan_stepsize_x = parameter['kwargs'].get("cell_width") - self.scan_stepsize_y = parameter['kwargs'].get("cell_height") - self.scan_stepnum_x = parameter['kwargs'].get("ncols") - self.scan_stepnum_y = parameter['kwargs'].get("nrows") + self.scan_range_x = parameter['kwargs'].get("range_x") + self.scan_range_y = parameter['kwargs'].get("range_y") + self.scan_stepnum_x = parameter['kwargs'].get("steps_x") + self.scan_stepnum_y = parameter['kwargs'].get("steps_y") self.scan_even_tweak = parameter['kwargs'].get("even_tweak", 0) self.scan_odd_tweak = parameter['kwargs'].get("odd_tweak", 0) + self.scan_stepsize_x = self.scan_range_x / self.scan_stepnum_x + self.scan_stepsize_y = self.scan_range_y / self.scan_stepnum_y + self.abr_globals = { 'var_1': self.scan_exp_time, 'var_2': self.scan_stepsize_x, @@ -342,90 +406,57 @@ class MeasureSASTT(AerotechFlyscanBase): -class MeasureVerticalLine(AerotechFlyscanBase): - """ Vertical line scan using the GMY motor - - Simple fly scan that records a single vertical line. - The line is nummCells * cellHeight long and the exposureTime is per cell. - - The scan itself is executed by the scan service running on the Aerotech - controller. Ophyd just configures, launches it and waits for completion. - - Example - ------- - >>> scans.measure_vline(cell_height=12, cell_num=10, cell_time=20) - - Parameters - ---------- - cell_height : (float) - Step size [mm]. - cell_num : (int) - Scan range of the axis. - cell_time : (float) - Exposure time per cell [s] - """ - scan_name = "measure_vline" - required_kwargs = ["cell_time", "cell_height", "cell_num"] - - def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = ['gmy'] - # Set parameters from meaningful inputs - self.scan_cell_time = parameter['kwargs'].get("cell_time") - self.scan_stepsize_y = parameter['kwargs'].get("cell_height") - self.scan_stepnum_y = parameter['kwargs'].get("cell_num") - self.abr_command = AbrCmd.VERTICAL_LINE_SCAN - self.abr_globals = { - 'var_1': self.scan_cell_time, - 'var_2': self.scan_stepsize_y, - 'var_3': self.scan_stepnum_y, - } - # Call base class - super().__init__(parameter=parameter, **kwargs) - class MeasureScreening(AerotechFlyscanBase): """ Sample screening scan + Sample screening scan that scans intervals on the rotation axis taking + 1 image/interval. This makes sure that we hit diffraction peaks if there + are any crystals. + The scan itself is executed by the scan service running on the Aerotech controller. Ophyd just configures, launches it and waits for completion. Example ------- - >>> scans.measure_screening(start=42, range=10, scan_time=20, degrees=180, frames=1800) + >>> scans.measure_screening(start=42, range=180, steps=18, exp_time=0.1, oscrange=2.0) Parameters ---------- start : float - Scan start position of the axis. + Absolute scan start position of the omega axis [deg]. range : float - Scan range of the axis. - scan_time : float - Total scan time for the movement [s]. - degrees : (???) - frames : (???) - delta : (???) - (default: 0.5). + Total screened range of the omega axis relative to 'start' [deg]. + steps : int + Number of blurred intervals. + exp_time : float + Exposure time per blurred interval [s]. + oscrange : float + Motion blurring of each interval [deg] + delta : float + Safety margin for sub-range movements (default: 0.5) [deg]. """ scan_name = "measure_screening" - required_kwargs = ["start", "range", "scan_time", "degrees", "frames"] + required_kwargs = ["start", "range", "steps", "exp_time", "oscrange"] def __init__(self, *args, parameter: dict = None, **kwargs): self.axis = ['gmy'] # Set parameters from meaningful inputs self.scan_start = parameter['kwargs'].get("start") - self.scan_range = parameter['kwargs'].get("range") - self.scan_scan_time = parameter['kwargs'].get("scan_time") - self.scan_degrees = parameter['kwargs'].get("degrees") - self.scan_frames = parameter['kwargs'].get("frames") + self.scan_range_o = parameter['kwargs'].get("range") + self.scan_stepnum_o = parameter['kwargs'].get("steps") + self.scan_exp_time = parameter['kwargs'].get("exp_time") + self.scan_oscrange = parameter['kwargs'].get("oscrange") self.scan_delta = parameter['kwargs'].get("delta", 0.5) + self.scan_stepsize_o = self.scan_range_o / self.scan_stepnum_o self.abr_command = AbrCmd.SCREENING self.abr_globals = { "var_1" : self.scan_start, - "var_2" : self.scan_range, - "var_3" : self.scan_scan_time, - "var_4" : self.scan_degrees, - "var_5" : self.scan_frames, + "var_2" : self.scan_oscrange, + "var_3" : self.scan_exp_time, + "var_4" : self.scan_stepsize_o, + "var_5" : self.scan_stepnum_o, "var_6" : self.scan_delta, } # Call base class @@ -436,28 +467,32 @@ class MeasureScreening(AerotechFlyscanBase): class MeasureFastOmega(AerotechFlyscanBase): """ Fast omega scan - MEasures a fast rotational scan with the rotation stage (omega). + Performs a fast rotational scan with the rotation stage (omega). It ramps + up to constant speed and sets off PSO for the expected travel time. I.e. + not really a PSO output (as it ignores acceleration distance) but it's + expected to trigger at-speed for 'range' distance. The scan itself is executed by the scan service running on the Aerotech controller. Ophyd just configures, launches it and waits for completion. Example ------- - >>> scans.measure_fast_omega(start=42, range=180, scan_time=20) + >>> scans.measure_fast_omega(start=42, range=180, move_time=1.0) Parameters ---------- start : float - Scan start position of the axis. + Scan start position of the axis [deg]. range : float - Scan range of the axis. - scan_time : float - Total scan time for the movement [s]. + At-speed range of the axis relative to 'start' + acceleration + distance [deg]. + move_time : float + Total time for the at-speed movement and trigger [s]. rate : (???) (default: 200). """ scan_name = "measure_fast_omega" - required_kwargs = ["start", "range", "scan_time"] + required_kwargs = ["start", "range", "move_time"] def __init__(self, *args, parameter: dict = None, **kwargs): self.axis = ['omega'] @@ -465,14 +500,14 @@ class MeasureFastOmega(AerotechFlyscanBase): logger.info(parameter) self.scan_start = parameter['kwargs'].get("start") self.scan_range = parameter['kwargs'].get("range") - self.scan_scan_time = parameter['kwargs'].get("scan_time") - self.scan_rate = parameter['kwargs'].get("rate", 200) + self.scan_move_time = parameter['kwargs'].get("move_time") + self.scan_acceleration = parameter['kwargs'].get("acceleration", 200) self.abr_command = AbrCmd.SUPER_FAST_OMEGA self.abr_globals = { "var_1" : self.scan_start, "var_2" : self.scan_range, - "var_3" : self.scan_scan_time, - "var_4" : self.scan_rate, + "var_3" : self.scan_move_time, + "var_4" : self.scan_acceleration, } # Call base class super().__init__(parameter=parameter, **kwargs) @@ -482,42 +517,47 @@ class MeasureFastOmega(AerotechFlyscanBase): class MeasureStillWedge(AerotechFlyscanBase): """ Still wedge scan + Measure a simple step scan with the omega stage with PSO triggering. + The scan is similar to the continous wedge scan. + The scan itself is executed by the scan service running on the Aerotech controller. Ophyd just configures, launches it and waits for completion. Example ------- - >>> scans.measure_still_wedge(start=42, range=90, scan_time=20, oscrange=15) + >>> scans.measure_still_wedge(start=42, range=180, exp_time=0.1, steps=60) Parameters ---------- start : (float) - Scan start position of the axis. + Scan start position of the axis [mm]. range : (float) - Scan range of the axis. - scan_time : (float) - Total scan time for the movement [s]. - oscrange : (float) + Scan range of the omega axis [mm]. + exp_time : (float) + Exposure time per point [s]. + steps : int + Number of actual steps. sleep_after_shutter_close : (float) [s] (default: 0.01). """ scan_name = "measure_still_wedge" - required_kwargs = ["start", "range", "exp_time", "oscrange"] + required_kwargs = ["start", "range", "exp_time", "steps"] def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = ['omega'] + self.axis = ['abr.omega'] # Set parameters from meaningful inputs self.scan_start = parameter['kwargs'].get("start") self.scan_range = parameter['kwargs'].get("range") - self.scan_scan_time = parameter['kwargs'].get("scan_time") - self.scan_oscrange = parameter['kwargs'].get("oscrange") + self.scan_stepnum_o = parameter['kwargs'].get("steps") + self.scan_exp_time = parameter['kwargs'].get("exp_time") self.scan_shutter_sleep = parameter['kwargs'].get("sleep_after_shutter_close", 0.01) + self.scan_stepsize_o = self.scan_stepnum_o / self.scan_stepnum_o self.abr_command = AbrCmd.STILL_WEDGE self.abr_globals = { "var_1" : self.scan_start, "var_2" : self.scan_range, - "var_3" : self.scan_scan_time, - "var_4" : self.scan_oscrange, + "var_3" : self.scan_exp_time, + "var_4" : self.scan_stepsize_o, "var_5" : self.scan_shutter_sleep, } # Call base class @@ -528,37 +568,37 @@ class MeasureStillWedge(AerotechFlyscanBase): class MeasureStills(AerotechFlyscanBase): """ Still image sequence scan - Measures a series of still images without any motor movement. - IMPORTANT: use idle_time=0.0 to prevent shutter open/close + Measures a series of PSO triggered images without any motor movement. + NOTE: Use idle_time=0.0 to prevent shutter open/close. The scan itself is executed by the scan service running on the Aerotech controller. Ophyd just configures, launches it and waits for completion. Example ------- - >>> scans.measure_stills(nframes=100, exp_time=0.1, idle_time=30) + >>> scans.measure_stills(steps=100, exp_time=0.1, idle_time=3) Parameters ---------- - nframes : int + steps : int Total number of frames to be recorded. - exposure_time : float + exp_time : float Exposure time of each frame [s]. idle_time : float Sleep time between the frames [s]. """ scan_name = "measure_stills" - required_kwargs = ["exp_time", "nframes", "idle_time"] + required_kwargs = ["exp_time", "steps", "idle_time"] def __init__(self, *args, parameter: dict = None, **kwargs): self.axis = [] # Set parameters from meaningful inputs self.scan_exp_time = parameter['kwargs'].get("exp_time") - self.scan_num_frames = parameter['kwargs'].get("nframes") + self.scan_numsteps = parameter['kwargs'].get("steps") self.scan_idle_time = parameter['kwargs'].get("idle_time") self.abr_command = AbrCmd.STILLS self.abr_globals = { - "var_1" : self.scan_num_frames, + "var_1" : self.scan_numsteps, "var_2" : self.scan_exp_time, "var_3" : self.scan_idle_time, } @@ -570,42 +610,44 @@ class MeasureStills(AerotechFlyscanBase): class MeasureSingleOscillation(AerotechFlyscanBase): """ Single oscillation scan + Short line scan with the omega axis with PSO trigger. + The scan itself is executed by the scan service running on the Aerotech controller. Ophyd just configures, launches it and waits for completion. Example ------- - >>> scans.measure_single_osc(start=42, range=180, scan_time=20) + >>> scans.measure_single_osc(start=42, range=180, move_time=20) Parameters ---------- start : float - Scan start position of the axis. + Scan start position of the omegaaxis [mm]. range : float - Oscillation range of the axis. - scan_time : float + Oscillation range of the axis [mm]. + move_time : float Total scan time for the movement [s]. delta : (???) - (default: 0). + Safety margin for movement (default: 0). settle : (???) (default: 0.5). """ scan_name = "measure_single_osc" - required_kwargs = ["start", "range", "scan_time"] + required_kwargs = ["start", "range", "move_time"] def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = ['omega'] + self.axis = ['abr.omega'] # Set parameters from meaningful inputs self.scan_start = parameter['kwargs'].get("start") self.scan_range = parameter['kwargs'].get("range") - self.scan_scan_time = parameter['kwargs'].get("scan_time") + self.scan_move_time = parameter['kwargs'].get("move_time") self.scan_delta = parameter['kwargs'].get("delta", 0) self.scan_settle = parameter['kwargs'].get("settle", 0.5) self.abr_command = AbrCmd.SINGLE_OSCILLATION self.abr_globals = { "var_1" : self.scan_start, "var_2" : self.scan_range, - "var_3" : self.scan_scan_time, + "var_3" : self.scan_move_time, "var_4" : self.scan_delta, "var_5" : self.scan_settle, } @@ -617,41 +659,44 @@ class MeasureSingleOscillation(AerotechFlyscanBase): class MeasureRepeatedOscillation(AerotechFlyscanBase): """ Repeated oscillation scan + Repeated relative line scans with the omega axis with PSO enable trigger. + The lines are performed in zigzag mode with PSO triggering. + The scan itself is executed by the scan service running on the Aerotech controller. Ophyd just configures, launches it and waits for completion. Example ------- - >>> scans.measure_multi_osc(start=42, range=180, scan_time=20) + >>> scans.measure_multi_osc(steps=50, range=30, move_time=3) Parameters ---------- - nframes : int - ??? - scan_time : float + steps : int + Number of cycles to repeat the scan + move_time : float Total scan time for the movement [s]. range : float - Oscillation range of the axis. + Relative oscillation range of the omega axis [deg]. settle : (???) - (default: 0). + Movement settle time after each line (default: 0) [s]. delta : (???) - (default: 0.5). + Safety margin for movement (default: 0.5) [deg]. """ scan_name = "measure_multi_osc" - required_kwargs = ["nframes", "range", "scan_time"] + required_kwargs = ["steps", "range", "scan_time"] def __init__(self, *args, parameter: dict = None, **kwargs): self.axis = ['omega'] # Set parameters from meaningful inputs - self.scan_num_frames = parameter['kwargs'].get("nframes") + self.scan_stepnum = parameter['kwargs'].get("steps") self.scan_range = parameter['kwargs'].get("range") - self.scan_scan_time = parameter['kwargs'].get("scan_time") + self.scan_move_time = parameter['kwargs'].get("move_time") self.scan_sleep = parameter['kwargs'].get("sleep", 0) self.scan_delta = parameter['kwargs'].get("delta", 0.5) self.abr_command = AbrCmd.REPEAT_SINGLE_OSCILLATION self.abr_globals = { - "var_1" : self.scan_num_frames, - "var_2" : self.scan_scan_time, + "var_1" : self.scan_stepnum, + "var_2" : self.scan_move_time, "var_3" : self.scan_range, "var_4" : self.scan_sleep, "var_5" : self.scan_delta, @@ -664,12 +709,15 @@ class MeasureRepeatedOscillation(AerotechFlyscanBase): class MeasureMSOX(AerotechFlyscanBase): """ Standard MSOX scan + MSOX experiment for Florian, i.e. a glorified oscillation scan this time + with absolute positions. The lines are unidirectional with PSO triggering. + The scan itself is executed by the scan service running on the Aerotech controller. Ophyd just configures, launches it and waits for completion. Example ------- - >>> scans.measure_msox(start=42, range=10, exp_time=0.1) + >>> scans.measure_msox(start=42, range=30, move_time=3.0) Parameters ---------- @@ -677,32 +725,32 @@ class MeasureMSOX(AerotechFlyscanBase): Scan start position of the axis. range : float Scan range of the axis. - exp_time : float + move_time : float Exposure time for each point [s]. - num_datasets : int - ??? - rest_time : float - ??? + steps : int + Number of repeats. + settle_time : float + Settle time before line start (default=0) [s]. """ scan_name = "measure_msox" - required_kwargs = ["start", "range", "exp_time"] + required_kwargs = ["start", "range", "move_time"] def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = ['omega'] + self.axis = ['abr.omega'] # Set parameters from meaningful inputs self.scan_start = parameter['kwargs'].get("start") self.scan_range = parameter['kwargs'].get("range") - self.scan_exp_time = parameter['kwargs'].get("exp_time") - self.scan_num_datasets = parameter['kwargs'].get("num_datasets", 1) - self.scan_rest_time = parameter['kwargs'].get("rest_time", 0) + self.scan_move_time = parameter['kwargs'].get("move_time") + self.scan_stepnum = parameter['kwargs'].get("steps", 1) + self.scan_settle_time = parameter['kwargs'].get("settle_time", 0) self.abr_command = AbrCmd.MSOX self.abr_reset = True self.abr_globals = { "var_1" : self.scan_start, "var_2" : self.scan_range, - "var_3" : self.scan_exp_time, - "var_4" : self.scan_num_datasets, - "var_5" : self.scan_rest_time, + "var_3" : self.scan_move_time, + "var_4" : self.scan_stepnum, + "var_5" : self.scan_settle_time, "var_6" : 0, "var_7" : 0, "var_8" : 0, @@ -717,6 +765,11 @@ class MeasureMSOX(AerotechFlyscanBase): class MeasureGrid(AerotechFlyscanBase): """ General grid scan + Performs an X-Y-Omega coordinated grid scan: + X axis is absolute (fast axis) + Y axis is relative to start + Omega starts at the current value and is synchronized to X + Note: This was probably never used in its current form, because the code had an undefined variable 'self.position' @@ -731,12 +784,14 @@ class MeasureGrid(AerotechFlyscanBase): ---------- positions : 2D-array Scan position of each axis, i.e. (N, 3) shaped array. - ncols : int - Nmber of columns. + steps_x : int + Number of points along the X axis (fast). + steps_y : int + Number of points in the Y axis (slow). angle: - ??? + Triggered angular range on the synchronized omega [deg] exp_time : (float) - Exposure time for each point [s]. + Exposure time for each point, only used for number of points and velocity [s]. """ scan_name = "measure_raster" required_kwargs = ["positions", "ncols", "angle", "exp_time"] @@ -750,16 +805,19 @@ class MeasureGrid(AerotechFlyscanBase): scan_gmy_step = 0.010 def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = ['omega'] + self.axis = ['abr.omega', 'abr.gmx', 'abr.gmy'] # Set parameters from meaningful inputs self.abr_command = AbrCmd.RASTER_SCAN self.abr_reset = True self.scan_positions = parameter['kwargs'].get("positions") - self.scan_stepnum_x = parameter['kwargs'].get("ncols") - self.scan_stepnum_y = self.scan_positions / self.scan_stepnum_x - self.scan_angle = parameter['kwargs'].get("angle") + self.scan_stepnum_x = parameter['kwargs'].get("steps_x") + self.scan_stepnum_y = parameter['kwargs'].get("steps_y") + self.scan_anglerange = parameter['kwargs'].get("angle") self.scan_exp_time = parameter['kwargs'].get("exp_time") + if len(self.scan_positions) != self.scan_stepnum_x * self.scan_stepnum_y: + raise RuntimeError("Number of points and positions do not match") + if len(self.scan_positions)==1: raise RuntimeWarning("Raster scan with one cell makes no sense") @@ -784,26 +842,17 @@ class MeasureGrid(AerotechFlyscanBase): self.scan_gmy_step = np.linalg.norm([self.scan_ystep, self.scan_zstep]) # ToDo: Check with Zac what are theese parameters - self.scan_omega_position = yield from self.stubs.send_rpc_and_wait("abs", "omega.readback.get") + self.scan_start_o = yield from self.stubs.send_rpc_and_wait("abs", "omega.readback.get") self.scan_gmx_offset = yield from self.stubs.send_rpc_and_wait("abs", "gmx.readback.get") - # ToDo : This was left out - # self._raster_starting_x = start_x - # self._raster_starting_y = y1 - # self._raster_starting_z = z1 - # self._raster_step_y = ystep - # self._raster_step_z = zstep - # self._raster_current_row = 0 - # self._raster_num_rows = rows - # Configure the global variables self.abr_globals = { - "var_1" : self.scan_omega_position, + "var_1" : self.scan_start_o, "var_2" : self.scan_start_x, "var_3" : self.scan_end_x, "var_4" : self.scan_stepnum_x, "var_5" : self.scan_stepnum_y, - "var_6" : self.scan_angle, + "var_6" : self.scan_anglerange, "var_7" : self.scan_exp_time, "var_8" : HALF_PERIOD, "var_9" : self.scan_gmx_offset, @@ -815,6 +864,11 @@ class MeasureGrid(AerotechFlyscanBase): class MeasureGridStill(AerotechFlyscanBase): """ Still grid scan + Performs an X-Y-Omega coordinated grid scan in stepping mode: + X axis is absolute + Y axis is relative to start + Omega starts at the current value and is synchronized to X + Note: This was probably never used in its current form, because the code had an undefined variable 'self.position' @@ -823,7 +877,7 @@ class MeasureGridStill(AerotechFlyscanBase): Example ------- - >>> scans.measure_raster(positions=[[11, 2, 3.4], [11, 2, 3.5], ...], columns=20, exp_time=0.1, delay=0.05) + >>> scans.measure_raster(positions=[[11, 2, 3.4], [11, 2, 3.5], ...], steps_x=20, steps_y=20, exp_time=0.1, delay=0.05) Parameters ---------- @@ -839,7 +893,7 @@ class MeasureGridStill(AerotechFlyscanBase): ??? """ scan_name = "measure_raster_still" - required_kwargs = ["positions", "columns", "exp_time", "delay"] + required_kwargs = ["positions", "steps_x", "steps_y", "exp_time", "delay"] # Default values scan_start_x = None @@ -850,16 +904,19 @@ class MeasureGridStill(AerotechFlyscanBase): scan_gmy_step = 0.010 def __init__(self, *args, parameter: dict = None, **kwargs): - self.axis = ['omega'] + self.axis = ['abr.gmx', 'abr.gmy', 'abr.omega'] # Set parameters from meaningful inputs self.abr_command = AbrCmd.RASTER_SCAN_STILL self.abr_reset = True self.scan_positions = parameter['kwargs'].get("positions") - self.scan_stepnum_x = parameter['kwargs'].get("columns") - self.scan_stepnum_y = self.scan_positions / self.scan_stepnum_x + self.scan_stepnum_x = parameter['kwargs'].get("steps_x") + self.scan_stepnum_y = parameter['kwargs'].get("steps_y") self.scan_exp_time = parameter['kwargs'].get("exp_time") self.scan_delay = parameter['kwargs'].get("delay") + if len(self.scan_positions) != self.scan_stepnum_x * self.scan_stepnum_y: + raise RuntimeError("Number of points and positions do not match") + if len(self.scan_positions)==1: raise RuntimeWarning("Raster scan with one cell makes no sense") # Call base class @@ -886,15 +943,6 @@ class MeasureGridStill(AerotechFlyscanBase): self.scan_omega_position = yield from self.stubs.send_rpc_and_wait("abs", "omega.readback.get") self.scan_gmx_offset = yield from self.stubs.send_rpc_and_wait("abs", "gmx.readback.get") - # ToDo : This was left out - # self._raster_starting_x = start_x - # self._raster_starting_y = y1 - # self._raster_starting_z = z1 - # self._raster_step_y = ystep - # self._raster_step_z = zstep - # self._raster_current_row = 0 - # self._raster_num_rows = rows - self.abr_globals = { "var_1" : self.scan_omega_position, "var_2" : self.scan_start_x, @@ -922,13 +970,13 @@ class MeasureJungfrau(AerotechFlyscanBase): Parameters ---------- - columns : int + steps_x : int ??? - rows : int + steps_y : int ??? - width : int + range_x : int ??? - height : int + range_y : int ??? exp_time : float Exposure time per point [s]. @@ -936,15 +984,17 @@ class MeasureJungfrau(AerotechFlyscanBase): (default: 0.1). """ scan_name = "measure_jungfrau" - required_kwargs = ["columns", "rows", "width", "height", "exp_time"] + required_kwargs = ["steps_x", "steps_y", "range_x", "range_y", "exp_time"] def __init__(self, *args, parameter: dict = None, **kwargs): self.axis = ['omega'] # Set parameters from meaningful inputs - self.scan_num_cols = parameter['kwargs'].get("columns") - self.scan_num_rows = parameter['kwargs'].get("rows") - self.scan_width = parameter['kwargs'].get("width") - self.scan_height = parameter['kwargs'].get("height") + self.scan_stepnum_x = parameter['kwargs'].get("steps_x") + self.scan_stepnum_y = parameter['kwargs'].get("steps_y") + self.scan_range_x = parameter['kwargs'].get("range_x") + self.scan_range_y = parameter['kwargs'].get("range_y") + self.scan_stepsize_x = self.scan_range_x / self.scan_stepnum_x + self.scan_stepsize_y = self.scan_range_y / self.scan_stepnum_y self.scan_exp_time = parameter['kwargs'].get("exp_time") self.scan_sleep = parameter['kwargs'].get("sleep", 0.1) self.abr_timeout = None if self.scan_sleep <= 0 else 5.0 + (self.scan_num_rows * self.scan_sleep) + (self.scan_num_cols * self.scan_num_rows * self.scan_exp_time) @@ -952,10 +1002,10 @@ class MeasureJungfrau(AerotechFlyscanBase): self.abr_reset = True self.abr_complete = True self.abr_globals = { - "var_1" : self.scan_num_cols, - "var_2" : self.scan_num_rows, - "var_3" : self.scan_width, - "var_4" : self.scan_height, + "var_1" : self.scan_stepnum_x, + "var_2" : self.scan_stepnum_y, + "var_3" : self.scan_stepsize_x, + "var_4" : self.scan_stepsize_y, "var_5" : self.scan_exp_time, "var_6" : self.scan_sleep, "var_7" : 0,