def test_peak_statistics_with_derivatives(RE): """peak statistics calculation on simple gaussian function with derivatives """ x = "motor" y = "det" num_points = 100 ps = PeakStats(x, y, calc_derivative_and_stats=True) RE.subscribe(ps) RE(scan([det], motor, -5, 5, num_points)) assert hasattr(ps, "derivative_stats") der_fields = [ "x", "y", "min", "max", "com", "cen", "crossings", "fwhm", "lin_bkg" ] for field in der_fields: assert hasattr(ps.derivative_stats, field), f"{field} is not an attribute of ps.der" assert type(ps.derivative_stats.x) is np.ndarray assert type(ps.derivative_stats.y) is np.ndarray assert type(ps.derivative_stats.min) is tuple assert type(ps.derivative_stats.max) is tuple assert type(ps.derivative_stats.com) is np.float64 assert type(ps.derivative_stats.cen) is np.float64 assert type(ps.derivative_stats.crossings) is np.ndarray if len(ps.derivative_stats.crossings) >= 2: assert type(ps.derivative_stats.fwhm) is float else: assert ps.derivative_stats.fwhm is None assert len(ps.derivative_stats.x) == num_points - 1 assert len(ps.derivative_stats.y) == num_points - 1 assert np.allclose(np.diff(ps.y_data), ps.derivative_stats.y, atol=1e-10)
def tune(signal, motor, width=None, num=None, peak_choice=None, md=None): """tune an axis, single pass. Scans axis centered at current position from ``-width/2`` to ``width/2`` with ``num`` observations """ # set up parameters for tune name = motor.name try: # if both exist, take provided parameter # if argument defined, short circuting ignores KeyError width = width or tune_params[name]['width'] num = num or tune_params[name]['num'] peak_choice = peak_choice or tune_params[name]['peak_choice'] except KeyError as ke: msg = f'Tuning parameters not defined for provided motor: {name}' raise KeyError(msg) initial_position = motor.position # TO-DO Check to make sure params are in dictionary, abort if not # gather metadata _md = {'plan_name': motor.name + '.tune', 'motors': motor.name, 'detectors': signal.name, 'hints': dict( dimensions = [ ([motor.name], 'primary')] ) } _md.update(md or {}) peak_stats = PeakStats(x=motor.name, y=signal.name) # start plans @subs_decorator(peak_stats) @inject_md_decorator(_md) def _scan(md=None): yield from bp.rel_scan([signal], motor, -width/2, width/2, num) # Grab final position from subscribed peak_stats valid_peak = peak_stats.max[-1] >= (4 * peak_stats.min[-1]) if peak_stats.max is None: valid_peak = False final_position = initial_position if valid_peak: # Condition for finding a peak if peak_choice == 'cen': final_position = peak_stats.cen elif peak_choice == 'com': final_position = peak_stats.com yield from bps.mv(motor, final_position) return (yield from _scan())
def test_peak_statistics(RE): """peak statistics calculation on simple gaussian function """ x = 'motor' y = 'det' ps = PeakStats(x, y) RE.subscribe(ps) RE(scan([det], motor, -5, 5, 100)) np.allclose(ps.cen, 0, atol=1e-6) np.allclose(ps.com, 0, atol=1e-6) fwhm_gauss = 2 * np.sqrt(2 * np.log(2)) # theoretical value with std=1 np.allclose(ps.fwhm, fwhm_gauss, atol=1e-2)
def inner(): y_signal = peaky.calculated_value x_signal = mot ps = PeakStats(x_signal.name, y_signal.name) RE.subscribe(ps) # collects data during scan # FIXME: trouble here yield from bp.scan([y_signal, calc.calculated_value], x_signal, -2, 0, 41, md=md) tbl = pyRestTable.Table() tbl.labels = "PeakStats value".split() tbl.rows = [[k, ps.__getattribute__(k)] for k in sorted("min max cen com fwhm".split())] h = db[-1] logger.info(f"scan_id={RE.md['scan_id']}") logger.info(f"uid={h.start['uid']}") logger.info(tbl) calc.reset()
def check_tth(): '''Align the spectrometer rotation angle''' yield from bps.mv(geo.det_mode, 1) yield from bps.mv(abs2, 6) yield from mabt(0, 0, 0) tmp1 = geo.tth.position print('resetting tth') yield from bps.mv(sh, -1) yield from bps.mv(shutter, 1) # open shutter local_peaks = PeakStats(tth.user_readback.name, quadem.current3.mean_value.name) #yield from bp.rel_scan([quadem],tth,-0.1,0.1,21) yield from bpp.subs_wrapper(bp.rel_scan([quadem], tth, -0.1, 0.1, 21), local_peaks) tmp2 = local_peaks.cen #get the height for roi2 of quadem with a max intens yield from bps.mv(tth, tmp2) yield from set_tth(tmp1) yield from bps.mv(shutter, 0) # close shutter
def check_astth(detector=lambda_det): '''Align the detector arm rotation angle''' yield from bps.mv(geo.det_mode, 1) yield from bps.mv(abs2, 6) yield from mabt(0.0, 0.0, 0) tmp1 = geo.astth.position yield from bps.mvr(sh, -1) print('setting astth') yield from bps.mv(shutter, 1) # open shutter # yield from bp.rel_scan([detector],astth,-0.1,0.1,21) # tmp2=peaks.cen['%s_stats2_total'%detector.name] local_peaks = PeakStats(astth.user_readback.name, '%s_stats2_total' % detector.name) yield from bpp.subs_wrapper(bp.rel_scan([detector], astth, -0.1, 0.1, 21), local_peaks) tmp2 = local_peaks.cen #get the height for roi2 of detector.name with max intens yield from bps.mv(astth, tmp2) yield from bps.mv(shutter, 0) # close shutter yield from set_astth(tmp1)
def check_ih(): '''Align the Align the spectrometer stage height ''' yield from bps.mv(geo.det_mode, 1) #move lamda detector in ? yield from bps.mv(abs2, 6) #move the second absorber in yield from mabt(0, 0, 0) # don't understand???, yield from bps.mv(sh, -1) # move the Sample vertical translation to -1 yield from bps.mv(shutter, 1) # open shutter print('resetting ih') #yield from bp.rel_scan([quadem],ih,-0.15,0.15,16) #scan the quadem detector against XtalDfl-height #tmp=peaks.cen['quadem_current3_mean_value'] #get the height for roi2 of quadem with a max intensity local_peaks = PeakStats(ih.user_readback.name, quadem.current3.mean_value.name) yield from bpp.subs_wrapper(bp.rel_scan([quadem], ih, -0.15, 0.15, 16), local_peaks) tmp = local_peaks.cen #get the height for roi2 of quadem with a max intens yield from bps.mv(ih, tmp) #move the XtalDfl to this height yield from set_ih(0) #set this height as 0 yield from bps.mv(shutter, 0) # close shutter
def test_peak_statistics(RE): """peak statistics calculation on simple gaussian function """ x = 'motor' y = 'det' ps = PeakStats(x, y) RE.subscribe(ps) RE(scan([det], motor, -5, 5, 100)) fields = [ "x", "y", "min", "max", "com", "cen", "crossings", "fwhm", "lin_bkg" ] for field in fields: assert hasattr(ps, field), f"{field} is not an attribute of ps" np.allclose(ps.cen, 0, atol=1e-6) np.allclose(ps.com, 0, atol=1e-6) fwhm_gauss = 2 * np.sqrt(2 * np.log(2)) # theoretical value with std=1 assert np.allclose(ps.fwhm, fwhm_gauss, atol=1e-2)
def check_sh_coarse(value=0, detector=lambda_det): ''' Aligh the sample height ''' yield from bps.mv(geo.det_mode, 1) yield from bps.mv(abs2, 6) yield from mabt(value, value, 0) tmp1 = geo.sh.position #Msg('reset_settle_time', sh.settle_time, 2) print('Start the height scan before GID') # yield from bp.rel_scan([detector],sh,-1,1,21,per_step=shutter_flash_scan) # tmp2=peaks.cen['%s_stats2_total'%detector.name] local_peaks = PeakStats(sh.user_readback.name, '%s_stats2_total' % detector.name) yield from bpp.subs_wrapper( bp.rel_scan([detector], sh, -1, 1, 21, per_step=shutter_flash_scan), local_peaks) tmp2 = local_peaks.cen #get the height for roi2 of detector.name with max intens ) yield from bps.mv(sh, tmp2) yield from set_sh(tmp1) Msg('reset_settle_time', sh.settle_time, 0)
def test_peak_statistics_compare_chx(RE): """This test focuses on gaussian function with noise. """ s = np.random.RandomState(1) noisy_det_fix = SynGauss('noisy_det_fix', motor, 'motor', center=0, Imax=1, noise='uniform', sigma=1, noise_multiplier=0.1, random_state=s) x = 'motor' y = 'noisy_det_fix' ps = PeakStats(x, y) RE.subscribe(ps) RE(scan([noisy_det_fix], motor, -5, 5, 100)) ps_chx = get_ps(ps.x_data, ps.y_data) assert np.allclose(ps.cen, ps_chx['cen'], atol=1e-6) assert np.allclose(ps.com, ps_chx['com'], atol=1e-6) assert np.allclose(ps.fwhm, ps_chx['fwhm'], atol=1e-6)
def check_sh_fine(value=0.05, detector=lambda_det): yield from bps.mv(geo.det_mode, 1) yield from bps.mv(abs2, 5) yield from mabt(value, value, 0) tmp1 = geo.sh.position print('Start the height scan before GID') # Msg('reset_settle_time', sh.settle_time, value) # yield from bp.rel_scan([detector],sh,-0.1,0.1,21,per_step=shutter_flash_scan) # tmp2=peaks.cen['%s_stats2_total'%detector.name] local_peaks = PeakStats(sh.user_readback.name, '%s_stats2_total' % detector.name) yield from bpp.subs_wrapper( bp.rel_scan([detector], sh, -0.15, 0.15, 16, per_step=shutter_flash_scan), local_peaks) print("at #1") tmp2 = local_peaks.cen #get the height for roi2 of detector.name with max intens print("at #2") yield from bps.mv(sh, tmp2) yield from set_sh(tmp1) Msg('reset_settle_time', sh.settle_time, 0)
if not SimAlign: motor2.move(det4offset+10.0) motor.move(alignRange[0]) if plotter is None: figTh, thAx = plt.subplots() else: thAx = plotter cur_color = fit_colors[iteration % len(fit_colors)] coarsePlot = LivePlot(detector_name,x=motor_name, linestyle = 'none', marker = '^', markerfacecolor = 'none', markeredgecolor = cur_color, ax = thAx, label = '{} - coarse'.format(iteration)) coarsePeak = PeakStats(motor_name,detector_name) lt = LiveTable([detector, motor]) if verbose: coarse_cbs = [coarsePlot,coarsePeak, lt] else: coarse_cbs = [coarsePlot,coarsePeak] @subs_decorator(coarse_cbs) def coarseScan(detector, motor, cRange, pts = 10): #Coarse scan to find region of peak yield from scan([detector], motor, cRange[0], cRange[1], num = pts) yield from coarseScan(detector, motor, alignRange, pts = coarsePTS)
def tune(self, width=None, num=None, md=None): """ BlueSky plan to execute one pass through the current scan range Scan self.axis centered about current position from ``-width/2`` to ``+width/2`` with ``num`` observations. If a peak was detected (default check is that max >= 4*min), then set ``self.tune_ok = True``. PARAMETERS width : float width of the tuning scan in the units of ``self.axis`` Default value in ``self.width`` (initially 1) num : int number of steps Default value in ``self.num`` (initially 10) md : dict, optional metadata """ width = width or self.width num = num or self.num if self.peak_choice not in self._peak_choices_: msg = "peak_choice must be one of {}, geave {}" msg = msg.format(self._peak_choices_, self.peak_choice) raise ValueError(msg) initial_position = self.axis.position final_position = initial_position # unless tuned start = initial_position - width/2 finish = initial_position + width/2 self.tune_ok = False tune_md = dict( width = width, initial_position = self.axis.position, time_iso8601 = str(datetime.datetime.now()), ) _md = {'tune_md': tune_md, 'plan_name': self.__class__.__name__ + '.tune', 'tune_parameters': dict( num = num, width = width, initial_position = self.axis.position, peak_choice = self.peak_choice, x_axis = self.axis.name, y_axis = self.signal_name, ), 'hints': dict( dimensions = [ ( [self.axis.name], 'primary')] ) } _md.update(md or {}) if "pass_max" not in _md: self.stats = [] self.peaks = PeakStats(x=self.axis.name, y=self.signal_name) class Results(Device): """because bps.read() needs a Device or a Signal)""" tune_ok = Component(Signal) initial_position = Component(Signal) final_position = Component(Signal) center = Component(Signal) # - - - - - x = Component(Signal) y = Component(Signal) cen = Component(Signal) com = Component(Signal) fwhm = Component(Signal) min = Component(Signal) max = Component(Signal) crossings = Component(Signal) peakstats_attrs = "x y cen com fwhm min max crossings".split() def report(self): keys = self.peakstats_attrs + "tune_ok center initial_position final_position".split() for key in keys: print("{} : {}".format(key, getattr(self, key).value)) @bpp.subs_decorator(self.peaks) def _scan(md=None): yield from bps.open_run(md) position_list = np.linspace(start, finish, num) signal_list = list(self.signals) signal_list += [self.axis,] for pos in position_list: yield from bps.mv(self.axis, pos) yield from bps.trigger_and_read(signal_list) final_position = initial_position if self.peak_detected(): self.tune_ok = True if self.peak_choice == "cen": final_position = self.peaks.cen elif self.peak_choice == "com": final_position = self.peaks.com else: final_position = None self.center = final_position # add stream with results # yield from add_results_stream() stream_name = "PeakStats" results = Results(name=stream_name) for key in "tune_ok center".split(): getattr(results, key).put(getattr(self, key)) results.final_position.put(final_position) results.initial_position.put(initial_position) for key in results.peakstats_attrs: v = getattr(self.peaks, key) if key in ("crossings", "min", "max"): v = np.array(v) getattr(results, key).put(v) if results.tune_ok.value: yield from bps.create(name=stream_name) yield from bps.read(results) yield from bps.save() yield from bps.mv(self.axis, final_position) self.stats.append(self.peaks) yield from bps.close_run() results.report() return (yield from _scan(md=_md))
class TuneAxis(object): """ tune an axis with a signal This class provides a tuning object so that a Device or other entity may gain its own tuning process, keeping track of the particulars needed to tune this device again. For example, one could add a tuner to a motor stage:: motor = EpicsMotor("xxx:motor", "motor") motor.tuner = TuneAxis([det], motor) Then the ``motor`` could be tuned individually:: RE(motor.tuner.tune(md={"activity": "tuning"})) or the :meth:`tune()` could be part of a plan with other steps. Example:: tuner = TuneAxis([det], axis) live_table = LiveTable(["axis", "det"]) RE(tuner.multi_pass_tune(width=2, num=9), live_table) RE(tuner.tune(width=0.05, num=9), live_table) Also see the jupyter notebook referenced here: :ref:`example_tuneaxis`. .. autosummary:: ~tune ~multi_pass_tune ~peak_detected """ _peak_choices_ = "cen com".split() def __init__(self, signals, axis, signal_name=None): self.signals = signals self.signal_name = signal_name or signals[0].name self.axis = axis self.stats = {} self.tune_ok = False self.peaks = None self.peak_choice = self._peak_choices_[0] self.center = None self.stats = [] # defaults self.width = 1 self.num = 10 self.step_factor = 4 self.pass_max = 6 self.snake = True def tune(self, width=None, num=None, md=None): """ BlueSky plan to execute one pass through the current scan range Scan self.axis centered about current position from ``-width/2`` to ``+width/2`` with ``num`` observations. If a peak was detected (default check is that max >= 4*min), then set ``self.tune_ok = True``. PARAMETERS width : float width of the tuning scan in the units of ``self.axis`` Default value in ``self.width`` (initially 1) num : int number of steps Default value in ``self.num`` (initially 10) md : dict, optional metadata """ width = width or self.width num = num or self.num if self.peak_choice not in self._peak_choices_: msg = "peak_choice must be one of {}, geave {}" msg = msg.format(self._peak_choices_, self.peak_choice) raise ValueError(msg) initial_position = self.axis.position final_position = initial_position # unless tuned start = initial_position - width/2 finish = initial_position + width/2 self.tune_ok = False tune_md = dict( width = width, initial_position = self.axis.position, time_iso8601 = str(datetime.datetime.now()), ) _md = {'tune_md': tune_md, 'plan_name': self.__class__.__name__ + '.tune', 'tune_parameters': dict( num = num, width = width, initial_position = self.axis.position, peak_choice = self.peak_choice, x_axis = self.axis.name, y_axis = self.signal_name, ), 'hints': dict( dimensions = [ ( [self.axis.name], 'primary')] ) } _md.update(md or {}) if "pass_max" not in _md: self.stats = [] self.peaks = PeakStats(x=self.axis.name, y=self.signal_name) class Results(Device): """because bps.read() needs a Device or a Signal)""" tune_ok = Component(Signal) initial_position = Component(Signal) final_position = Component(Signal) center = Component(Signal) # - - - - - x = Component(Signal) y = Component(Signal) cen = Component(Signal) com = Component(Signal) fwhm = Component(Signal) min = Component(Signal) max = Component(Signal) crossings = Component(Signal) peakstats_attrs = "x y cen com fwhm min max crossings".split() def report(self): keys = self.peakstats_attrs + "tune_ok center initial_position final_position".split() for key in keys: print("{} : {}".format(key, getattr(self, key).value)) @bpp.subs_decorator(self.peaks) def _scan(md=None): yield from bps.open_run(md) position_list = np.linspace(start, finish, num) signal_list = list(self.signals) signal_list += [self.axis,] for pos in position_list: yield from bps.mv(self.axis, pos) yield from bps.trigger_and_read(signal_list) final_position = initial_position if self.peak_detected(): self.tune_ok = True if self.peak_choice == "cen": final_position = self.peaks.cen elif self.peak_choice == "com": final_position = self.peaks.com else: final_position = None self.center = final_position # add stream with results # yield from add_results_stream() stream_name = "PeakStats" results = Results(name=stream_name) for key in "tune_ok center".split(): getattr(results, key).put(getattr(self, key)) results.final_position.put(final_position) results.initial_position.put(initial_position) for key in results.peakstats_attrs: v = getattr(self.peaks, key) if key in ("crossings", "min", "max"): v = np.array(v) getattr(results, key).put(v) if results.tune_ok.value: yield from bps.create(name=stream_name) yield from bps.read(results) yield from bps.save() yield from bps.mv(self.axis, final_position) self.stats.append(self.peaks) yield from bps.close_run() results.report() return (yield from _scan(md=_md)) def multi_pass_tune(self, width=None, step_factor=None, num=None, pass_max=None, snake=None, md=None): """ BlueSky plan for tuning this axis with this signal Execute multiple passes to refine the centroid determination. Each subsequent pass will reduce the width of scan by ``step_factor``. If ``snake=True`` then the scan direction will reverse with each subsequent pass. PARAMETERS width : float width of the tuning scan in the units of ``self.axis`` Default value in ``self.width`` (initially 1) num : int number of steps Default value in ``self.num`` (initially 10) step_factor : float This reduces the width of the next tuning scan by the given factor. Default value in ``self.step_factor`` (initially 4) pass_max : int Maximum number of passes to be executed (avoids runaway scans when a centroid is not found). Default value in ``self.pass_max`` (initially 10) snake : bool If ``True``, reverse scan direction on next pass. Default value in ``self.snake`` (initially True) md : dict, optional metadata """ width = width or self.width num = num or self.num step_factor = step_factor or self.step_factor snake = snake or self.snake pass_max = pass_max or self.pass_max self.stats = [] def _scan(width=1, step_factor=10, num=10, snake=True): for _pass_number in range(pass_max): _md = {'pass': _pass_number+1, 'pass_max': pass_max, 'plan_name': self.__class__.__name__ + '.multi_pass_tune', } _md.update(md or {}) yield from self.tune(width=width, num=num, md=_md) if not self.tune_ok: return width /= step_factor if snake: width *= -1 return ( yield from _scan( width=width, step_factor=step_factor, num=num, snake=snake)) def peak_detected(self): """ returns True if a peak was detected, otherwise False The default algorithm identifies a peak when the maximum value is four times the minimum value. Change this routine by subclassing :class:`TuneAxis` and override :meth:`peak_detected`. """ if self.peaks is None: return False self.peaks.compute() if self.peaks.max is None: return False ymax = self.peaks.max[-1] ymin = self.peaks.min[-1] return ymax > 4*ymin # this works for USAXS@APS
from bluesky import RunEngine from bluesky.callbacks.fitting import PeakStats from bluesky.callbacks.mpl_plotting import plot_peak_stats from ophyd.sim import motor, det from bluesky.plans import scan RE = RunEngine({}) ps = PeakStats('motor', 'det') RE(scan([det], motor, -5, 5, 10), ps) plot_peak_stats(ps)
class TuneAxis(object): """ tune an axis with a signal This class provides a tuning object so that a Device or other entity may gain its own tuning process, keeping track of the particulars needed to tune this device again. For example, one could add a tuner to a motor stage:: motor = EpicsMotor("xxx:motor", "motor") motor.tuner = TuneAxis([det], motor) Then the ``motor`` could be tuned individually:: RE(motor.tuner.tune(md={"activity": "tuning"})) or the :meth:`tune()` could be part of a plan with other steps. Example:: tuner = TuneAxis([det], axis) live_table = LiveTable(["axis", "det"]) RE(tuner.multi_pass_tune(width=2, num=9), live_table) RE(tuner.tune(width=0.05, num=9), live_table) Also see the jupyter notebook referenced here: :ref:`example_tuneaxis`. .. autosummary:: ~tune ~multi_pass_tune ~peak_detected SEE ALSO .. autosummary:: ~tune_axes """ _peak_choices_ = "cen com".split() def __init__(self, signals, axis, signal_name=None): self.signals = signals self.signal_name = signal_name or signals[0].name self.axis = axis self.tune_ok = False self.peaks = None self.peak_choice = self._peak_choices_[0] self.center = None self.stats = [] # defaults self.width = 1 self.num = 10 self.step_factor = 4 self.peak_factor = 4 self.pass_max = 4 self.snake = True def tune(self, width=None, num=None, peak_factor=None, md=None): """ Bluesky plan to execute one pass through the current scan range Scan self.axis centered about current position from ``-width/2`` to ``+width/2`` with ``num`` observations. If a peak was detected (default check is that max >= 4*min), then set ``self.tune_ok = True``. PARAMETERS width : float width of the tuning scan in the units of ``self.axis`` Default value in ``self.width`` (initially 1) num : int number of steps Default value in ``self.num`` (initially 10) md : dict, optional metadata """ width = width or self.width num = num or self.num peak_factor = peak_factor or self.peak_factor if self.peak_choice not in self._peak_choices_: msg = "peak_choice must be one of {}, geave {}" msg = msg.format(self._peak_choices_, self.peak_choice) raise ValueError(msg) initial_position = self.axis.position # final_position = initial_position # unless tuned start = initial_position - width/2 finish = initial_position + width/2 self.tune_ok = False tune_md = dict( width = width, initial_position = self.axis.position, time_iso8601 = str(datetime.datetime.now()), ) _md = {'tune_md': tune_md, 'plan_name': self.__class__.__name__ + '.tune', 'tune_parameters': dict( num = num, width = width, initial_position = self.axis.position, peak_choice = self.peak_choice, x_axis = self.axis.name, y_axis = self.signal_name, ), 'motors': (self.axis.name,), 'detectors': (self.signal_name,), 'hints': dict( dimensions = [ ( [self.axis.name], 'primary')] ) } _md.update(md or {}) if "pass_max" not in _md: self.stats = [] self.peaks = PeakStats(x=self.axis.name, y=self.signal_name) @bpp.subs_decorator(self.peaks) def _scan(md=None): yield from bps.open_run(md) position_list = np.linspace(start, finish, num) signal_list = list(self.signals) signal_list += [self.axis,] for pos in position_list: yield from bps.mv(self.axis, pos) yield from bps.trigger_and_read(signal_list) final_position = initial_position if self.peak_detected(peak_factor=peak_factor): self.tune_ok = True if self.peak_choice == "cen": final_position = self.peaks.cen elif self.peak_choice == "com": final_position = self.peaks.com else: final_position = None self.center = final_position # add stream with results # yield from add_results_stream() stream_name = "PeakStats" results = TuneResults(name=stream_name) results.tune_ok.put(self.tune_ok) results.center.put(self.center) results.final_position.put(final_position) results.initial_position.put(initial_position) results.set_stats(self.peaks) self.stats.append(results) if results.tune_ok.get(): yield from bps.create(name=stream_name) try: yield from bps.read(results) except ValueError as ex: separator = " "*8 + "-"*12 print(separator) print(f"Error saving stream {stream_name}:\n{ex}") print(separator) yield from bps.save() yield from bps.mv(self.axis, final_position) yield from bps.close_run() results.report(stream_name) return (yield from _scan(md=_md)) def multi_pass_tune(self, width=None, step_factor=None, num=None, pass_max=None, peak_factor=None, snake=None, md=None): """ Bluesky plan for tuning this axis with this signal Execute multiple passes to refine the centroid determination. Each subsequent pass will reduce the width of scan by ``step_factor``. If ``snake=True`` then the scan direction will reverse with each subsequent pass. PARAMETERS width : float width of the tuning scan in the units of ``self.axis`` Default value in ``self.width`` (initially 1) num : int number of steps Default value in ``self.num`` (initially 10) step_factor : float This reduces the width of the next tuning scan by the given factor. Default value in ``self.step_factor`` (initially 4) pass_max : int Maximum number of passes to be executed (avoids runaway scans when a centroid is not found). Default value in ``self.pass_max`` (initially 4) peak_factor : float (default: 4) peak maximum must be greater than ``peak_factor*minimum`` snake : bool If ``True``, reverse scan direction on next pass. Default value in ``self.snake`` (initially True) md : dict, optional metadata """ width = width or self.width num = num or self.num step_factor = step_factor or self.step_factor snake = snake or self.snake pass_max = pass_max or self.pass_max peak_factor = peak_factor or self.peak_factor self.stats = [] def _scan(width=1, step_factor=10, num=10, snake=True): for _pass_number in range(pass_max): logger.info("Multipass tune %d of %d", _pass_number+1, pass_max) _md = {'pass': _pass_number+1, 'pass_max': pass_max, 'plan_name': self.__class__.__name__ + '.multi_pass_tune', } _md.update(md or {}) yield from self.tune(width=width, num=num, peak_factor=peak_factor, md=_md) if not self.tune_ok: return if width > 0: sign = 1 else: sign = -1 width = sign * 2 * self.stats[-1].fwhm.get() if snake: width *= -1 return ( yield from _scan( width=width, step_factor=step_factor, num=num, snake=snake)) def multi_pass_tune_summary(self): t = pyRestTable.Table() t.labels = "pass Ok? center width max.X max.Y".split() for i, stat in enumerate(self.stats): row = [i+1,] row.append(stat.tune_ok.get()) row.append(stat.cen.get()) row.append(stat.fwhm.get()) x, y = stat.max.get() row += [x, y] t.addRow(row) return t def peak_detected(self, peak_factor=None): """ returns True if a peak was detected, otherwise False The default algorithm identifies a peak when the maximum value is four times the minimum value. Change this routine by subclassing :class:`TuneAxis` and override :meth:`peak_detected`. """ peak_factor = peak_factor or self.peak_factor if self.peaks is None: return False self.peaks.compute() if self.peaks.max is None: return False ymax = self.peaks.max[-1] ymin = self.peaks.min[-1] ok = ymax >= peak_factor*ymin # this works for USAXS@APS if not ok: logger.info("ymax < ymin * %f: is it a peak?", peak_factor) return ok
def peakup_dcm(correct_roll=True, plot=False, shutter=True, use_calib=False): """ Scan the HDCM fine pitch and, optionally, roll against the ion chamber in the D Hutch correct_roll <Bool> If True, align the beam in the vertical (roll) plot <Bool> If True, plot the intensity as a function of pitch/roll shutter <Bool> If True, the shutter will be automatically opened/closed use_calib <Bool> If True, use a previous calibration as an initial guess """ e_value = energy.energy.get()[1] pitch_old = dcm.c2_pitch.position roll_old = dcm.c1_roll.position det = [sclr1] ps = PeakStats(dcm.c2_pitch.name, im.name) ps1 = PeakStats(dcm.c1_roll.name, im.name) if (shutter == True): RE(mv(shut_b, 'Open')) c2pitch_kill = EpicsSignal("XF:05IDA-OP:1{Mono:HDCM-Ax:P2}Cmd:Kill-Cmd") # Turn off the ePID loop for the pitch motor # 'XF:05IDD-CT{FbPid:02}PID:on' c2_pid = EpicsSignal("XF:05IDD-CT{FbPid:02}PID:on") c2_pid.put(0) # Turn off the ePID loop c2_V = EpicsSignal("XF:05ID-BI{EM:BPM1}DAC1") c2_V.put(3.0) # Reset the piezo voltage to 3 V # pitch_lim = (-19.320, -19.370) # pitch_num = 51 # roll_lim = (-4.9, -5.14) # roll_num = 45 # pitch_lim = (-19.375, -19.425) # pitch_num = 51 # roll_lim = (-4.9, -5.6) # roll_num = 51 pitch_lim = (pitch_old - 0.025, pitch_old + 0.025) roll_lim = (roll_old - 0.2, roll_old + 0.2) pitch_num = 51 roll_num = 51 if (use_calib): # Factor to convert eV to keV K = 1 if (e_value > 1000): K = 1 / 1000 # Pitch calibration pitch_guess = -0.00055357 * K * e_value - 19.39382381 dcm.c2_pitch.move(pitch_guess, wait=True) # Roll calibration roll_guess = -0.01124286 * K * e_value - 4.93568571 dcm.c1_roll.move(roll_guess, wait=True) # Output guess print('\nMoving to guess:') print('\tC2 Pitch: %f' % (pitch_guess)) print('\tC1 Roll: %f\n' % (roll_guess)) #if e_value < 10.: # sclr1.preset_time.put(0.1) # RE(scan([sclr1], dcm.c2_pitch, -19.335, -19.305, 31), [ps]) #else: # sclr1.preset_time.put(1.) # RE(scan([sclr1], dcm.c2_pitch, -19.355, -19.310, 46), [ps]) if e_value < 14.: # sclr1.preset_time.put(0.1) sclr1.preset_time.put(1.0) else: sclr1.preset_time.put(1.0) if (plot == True): sclr1.preset_time.put( 1.0) # Let's collect a longer scan since we're plotting it RE(scan(det, dcm.c2_pitch, pitch_lim[0], pitch_lim[1], pitch_num), [ps]) print('Pitch: Centroid at %f\n\n' % (ps.cen)) plt.figure() plt.plot(ps.x_data, ps.y_data, label='Data') plt.plot((ps.cen, ps.cen), (np.amin(ps.y_data), np.amax(ps.y_data)), '--k', label='Centroid') plt.xlabel('HDCM C2 PITCH') plt.ylabel('Counts') plt.legend() else: RE(scan(det, dcm.c2_pitch, pitch_lim[0], pitch_lim[1], pitch_num), [ps]) time.sleep(0.5) dcm.c2_pitch.move(ps.cen, wait=True) time.sleep(0.5) if (np.abs(dcm.c2_pitch.position - ps.cen) > 0.001): print('The pitch motor did not move on the first try. Trying again...', end='') dcm.c2_pitch.move(ps.cen, wait=True) if (np.abs(dcm.c2_pitch.position - ps.cen) > 0.001): print('FAIL! Check motor location.\n') else: print('OK\n') logscan('peakup_pitch') c2pitch_kill.put(1) if correct_roll == True: if (plot == True): sclr1.preset_time.put( 1.0) # If we are debugging, let's collect a longer scan RE(scan(det, dcm.c1_roll, roll_lim[0], roll_lim[1], roll_num), [ps1]) # print('Roll: Maximum flux at %f' % (ps1.max[0])) print('Roll: Centroid at %f\n\n' % (ps1.cen)) plt.figure() plt.plot(ps1.x_data, ps1.y_data, label='Data') plt.plot((ps1.cen, ps1.cen), (np.amin(ps1.y_data), np.amax(ps1.y_data)), '--k', label='Centroid') plt.xlabel('HDCM ROLL') plt.ylabel('Counts') plt.legend() else: RE(scan(det, dcm.c1_roll, roll_lim[0], roll_lim[1], roll_num), [ps1]) time.sleep(0.5) dcm.c1_roll.move(ps1.cen, wait=True) time.sleep(0.5) if (np.abs(dcm.c1_roll.position - ps1.cen) > 0.001): print( 'The roll motor did not move on the first try. Trying again...', end='') dcm.c1_roll.move(ps1.cen, wait=True) if (np.abs(dcm.c1_roll.position - ps1.cen) > 0.001): print('FAIL! Check motor location.\n') else: print('OK\n') logscan('peakup_roll') # Output old/new values print('Old pitch value:\t%f' % pitch_old) print('New pitch value:\t%f' % ps.cen) print('Current pitch value:\t%f' % dcm.c2_pitch.position) print('Old roll value: \t%f' % roll_old) print('New roll value: \t%f' % ps1.cen) print('Current roll value: \t%f\n' % dcm.c1_roll.position) if (shutter == True): RE(mv(shut_b, 'Close')) #for some reason we now need to kill the pitch motion to keep it from overheating. 6/8/17 #this need has disappeared mysteriously after the shutdown - gjw 2018/01/19 # This has now reappeared - amk 2018/06/06 time.sleep(1) c2pitch_kill.put(1) c2_pid.put(1) # Turn on the ePID loop
def tune(self, width=None, num=None, md=None): """ Bluesky plan to execute one pass through the current scan range Scan self.axis centered about current position from ``-width/2`` to ``+width/2`` with ``num`` observations. If a peak was detected (default check is that max >= 4*min), then set ``self.tune_ok = True``. PARAMETERS width : float width of the tuning scan in the units of ``self.axis`` Default value in ``self.width`` (initially 1) num : int number of steps Default value in ``self.num`` (initially 10) md : dict, optional metadata """ width = width or self.width num = num or self.num if self.peak_choice not in self._peak_choices_: raise ValueError( f"peak_choice must be one of {self._peak_choices_}," f" gave {self.peak_choice}") initial_position = self.axis.position start = initial_position - width / 2 finish = initial_position + width / 2 self.tune_ok = False signal_list = list(self.signals) signal_list += [ self.axis, ] tune_md = dict( width=width, initial_position=self.axis.position, time_iso8601=str(datetime.datetime.now()), ) _md = { 'tune_md': tune_md, 'plan_name': self.__class__.__name__ + '.tune', 'tune_parameters': dict( num=num, width=width, initial_position=self.axis.position, peak_choice=self.peak_choice, x_axis=self.axis.name, y_axis=self.signal_name, ), 'motors': (self.axis.name, ), 'detectors': (self.signal_name, ), 'hints': dict(dimensions=[([self.axis.name], 'primary')]) } _md.update(md or {}) if "pass_max" not in _md: self.stats = [] self.peaks = PeakStats(x=self.axis.name, y=self.signal_name) @bpp.subs_decorator(self.peaks) def _scan(md=None): yield from bp.scan(signal_list, self.axis, start, finish, num, md=_md) yield from self.peak_analysis(initial_position) yield from _scan()
class UsaxsTuneAxis(TuneAxis): """use bp.rel_scan() for the tune()""" width_signal = None _width_default = 1 # fallback default when width_signal is None def __init__(self, signals, axis, signal_name=None, width_signal=None): """ Adds to the ``apstools.devices.TuneAxis()`` signature signal_width (obj): Instance of `ophyd.EpicsSignal` connected to PV with default tune width for this axis. If undefined (set to ``None``), then a private attribute (``_width_default``) will be used for the value. """ super().__init__(signals, axis, signal_name=signal_name) self.width_signal = width_signal @property def width(self): """Get default tune width for this axis.""" if self.width_signal is None: return self._width_default else: return self.width_signal.get() @width.setter def width(self, value): """ Set the width PV value - blocking call, not a plan. To set the width PV in a plan, use ``yield from bps.mv(self.width_signal, value)``. CAUTION: Do NOT call this setter from a bluesky plan! """ if self.width_signal is None: self._width_default = value else: self.width_signal.put(value) def peak_analysis(self, initial_position): if self.peak_detected(): self.tune_ok = True if self.peak_choice == "cen": final_position = self.peaks.cen elif self.peak_choice == "com": final_position = self.peaks.com else: final_position = None self.center = final_position else: self.tune_ok = False final_position = initial_position yield from bps.mv(self.axis, final_position) stream_name = "PeakStats" results = TuningResults(name=stream_name) results.tune_ok.put(self.tune_ok) results.center.put(self.center) results.final_position.put(final_position) results.initial_position.put(initial_position) if self.peaks is None: logger.info("PeakStats object is None.") results.put_results({}) else: results.put_results(self.peaks) self.stats.append(results) t = results.report(print_enable=False) logger.info("%s\n%s", stream_name, str(t)) def tune(self, width=None, num=None, md=None): """ Bluesky plan to execute one pass through the current scan range Scan self.axis centered about current position from ``-width/2`` to ``+width/2`` with ``num`` observations. If a peak was detected (default check is that max >= 4*min), then set ``self.tune_ok = True``. PARAMETERS width : float width of the tuning scan in the units of ``self.axis`` Default value in ``self.width`` (initially 1) num : int number of steps Default value in ``self.num`` (initially 10) md : dict, optional metadata """ width = width or self.width num = num or self.num if self.peak_choice not in self._peak_choices_: raise ValueError( f"peak_choice must be one of {self._peak_choices_}," f" gave {self.peak_choice}") initial_position = self.axis.position start = initial_position - width / 2 finish = initial_position + width / 2 self.tune_ok = False signal_list = list(self.signals) signal_list += [ self.axis, ] tune_md = dict( width=width, initial_position=self.axis.position, time_iso8601=str(datetime.datetime.now()), ) _md = { 'tune_md': tune_md, 'plan_name': self.__class__.__name__ + '.tune', 'tune_parameters': dict( num=num, width=width, initial_position=self.axis.position, peak_choice=self.peak_choice, x_axis=self.axis.name, y_axis=self.signal_name, ), 'motors': (self.axis.name, ), 'detectors': (self.signal_name, ), 'hints': dict(dimensions=[([self.axis.name], 'primary')]) } _md.update(md or {}) if "pass_max" not in _md: self.stats = [] self.peaks = PeakStats(x=self.axis.name, y=self.signal_name) @bpp.subs_decorator(self.peaks) def _scan(md=None): yield from bp.scan(signal_list, self.axis, start, finish, num, md=_md) yield from self.peak_analysis(initial_position) yield from _scan() def multi_pass_tune(self, width=None, step_factor=None, num=None, pass_max=None, snake=None, md=None): """ BlueSky plan for tuning this axis with this signal Execute multiple passes to refine the centroid determination. Each subsequent pass will reduce the width of scan by ``step_factor``. If ``snake=True`` then the scan direction will reverse with each subsequent pass. PARAMETERS width : float width of the tuning scan in the units of ``self.axis`` Default value in ``self.width`` (initially 1) num : int number of steps Default value in ``self.num`` (initially 10) step_factor : float This reduces the width of the next tuning scan by the given factor. Default value in ``self.step_factor`` (initially 4) pass_max : int Maximum number of passes to be executed (avoids runaway scans when a centroid is not found). Default value in ``self.pass_max`` (initially 10) snake : bool If ``True``, reverse scan direction on next pass. Default value in ``self.snake`` (initially True) md : dict, optional metadata """ width = width or self.width num = num or self.num step_factor = step_factor or self.step_factor snake = snake or self.snake pass_max = pass_max or self.pass_max self.stats = [] def _scan(width=1, step_factor=10, num=10, snake=True): for _pass_number in range(pass_max): _md = { 'pass': _pass_number + 1, 'pass_max': pass_max, 'plan_name': self.__class__.__name__ + '.multi_pass_tune', } _md.update(md or {}) yield from self.tune(width=width, num=num, md=_md) if not self.tune_ok: break width /= step_factor if snake: width *= -1 t = pyRestTable.Table() t.labels = "pass Ok? center width max.X max.Y".split() for i, stat in enumerate(self.stats): row = [ i + 1, ] row.append(stat.tune_ok.get()) row.append(stat.cen.get()) row.append(stat.fwhm.get()) x, y = stat.max.get() row += [x, y] t.addRow(row) logger.info("Results\n%s", str(t)) logger.info("Final tune position: %s = %f", self.axis.name, self.axis.position) return (yield from _scan(width=width, step_factor=step_factor, num=num, snake=snake)) def peak_detected(self): """ returns True if a peak was detected, otherwise False The default algorithm identifies a peak when the maximum value is four times the minimum value. Change this routine by subclassing :class:`TuneAxis` and override :meth:`peak_detected`. """ if self.peaks is None: logger.info("PeakStats = None") return False self.peaks.compute() if self.peaks.max is None: logger.info("PeakStats : no max reported") return False ymax = self.peaks.max[-1] ymin = self.peaks.min[-1] ok = ymax > 4 * ymin # this works for USAXS@APS if not ok: logger.info("ymax/yman not big enough: is it a peak?") return ok
def undulator_calibration(outfile=None, UCalibDir='/home/xf05id1/current_user_data/', u_gap_start=6500, u_gap_end=12000, u_gap_step=500): ''' outfile string filename for a txt file for the lookup table desirable to name it with the date of the calibration e.g. SRXUgapCalibration20161225.txt undulator gap set point range are defined in: u_gap_start float u_gap_end float u_gap_step float ''' # Format a default filename if (outfile is None): outfile = f"{datetime.datetime.now().strftime('%Y%m%d')}_SRXUgapCalibration.txt" # Check if the file exists if (not os.path.exists(UCalibDir + outfile)): with open(UCalibDir + outfile, 'w') as f: f.write('Undulator_gap\tFundemental_energy\n') # Bragg scan setup default energy_res = 0.002 # keV bragg_scanwidth = 0.2 # keV +/- this value bragg_scanpoint = (np.floor( (2 * bragg_scanwidth) / (energy_res)) + 1).astype('int') harmonic = 3 energy.harmonic.put(harmonic) # Generate lookup table by scanning Bragg at each undulator gap set point for u_gap_setpoint in np.arange(u_gap_start, u_gap_end + u_gap_step, u_gap_step): # Look up the energy from the previous lookup table # Right now, the lookup table is in mm, not um! # A new lookup table should be created with the correct units energy_setpoint = (float(energy.utoelookup(u_gap_setpoint / 1000)) * harmonic) print('Move u_gap to:\t', u_gap_setpoint) print('Move Bragg energy to:\t', energy_setpoint) # energy.move_c2_x.put(False) energy.move_u_gap.put(True) yield from mv(energy, energy_setpoint) energy.move_u_gap.put(False) # Setup LiveCallbacks # liveplotfig1 = plt.figure() liveplotx = energy.energy.name liveploty = xbpm1.sumT.name livetableitem = [ energy.energy.name, ring_current.name, xbpm1.sumT.name ] ps = PeakStats(energy.energy.name, xbpm1.sumT.name) livecallbacks = [ LiveTable(livetableitem), LivePlot(liveploty, x=liveplotx), ps ] # Setup the scan @subs_decorator(livecallbacks) def braggscan(): yield from scan([xbpm1, ring_current], energy, energy_setpoint - bragg_scanwidth, energy_setpoint + bragg_scanwidth, bragg_scanpoint) # Run the scan yield from braggscan() # Find the maximum and output result to file maxenergy = ps.max[0] maxintensity = ps.max[1] fwhm = ps.fwhm print('Max energy is:\t', maxenergy) print('Fundemental energy:\t', maxenergy / harmonic) with open(UCalibDir + outfile, 'a') as f: f.write( f"{energy.u_gap.position:.3f}\t{(maxenergy / harmonic):.8f}\n") # Return moving u_gap energy.move_u_gap.put(True)
def undulator_calibration( outfile=None, UCalibDir='/nsls2/xf05id1/shared/config/undulator_calibration/', u_gap_start=6500, u_gap_end=12000, u_gap_step=500): ''' outfile string filename for a txt file for the lookup table desirable to name it with the date of the calibration e.g. SRXUgapCalibration20161225.txt undulator gap set point range are defined in: u_gap_start float u_gap_end float u_gap_step float ''' bpmAD.cam.read_attrs = ['acquire_time'] bpmAD.configuration_attrs = ['cam'] # Format a default filename if (outfile is None): outfile = '%s_SRXUgapCalibration.txt' % ( datetime.datetime.now().strftime('%Y%m%d')) # Check if the file exists if (not os.path.exists(UCalibDir + outfile)): f = open(UCalibDir + outfile, 'w') f.write('Undulator_gap\tFundemental_energy\n') f.close() # Bragg scan setup default energy_res = 0.002 # keV bragg_scanwidth = 0.1 # keV bragg_scanpoint = int(bragg_scanwidth * 2 / energy_res + 1) harmonic = 3 energy.harmonic.put(harmonic) # Generate lookup table by scanning Bragg at each undulator gap set point for u_gap_setpoint in np.arange(u_gap_start, u_gap_end + u_gap_step, u_gap_step): # Look up the energy from the previous lookup table # Right now, the lookup table is in mm, not um! # A new lookup table should be created with the correct units energy_setpoint = (float(energy.utoelookup(u_gap_setpoint / 1000)) * harmonic) print('Move u_gap to:\t', u_gap_setpoint) print('Move Bragg energy to:\t', energy_setpoint) energy.move_c2_x.put(False) energy.move_u_gap.put(True) yield from bps.sleep(0.2) yield from mv(energy, energy_setpoint) yield from bpmAD_exposuretime_adjust() energy.move_u_gap.put(False) # Setup LiveCallbacks liveplotfig1 = plt.figure() liveploty = bpmAD.stats1.total.name livetableitem = [energy.energy, bpmAD.stats1.total, ring_current] liveplotx = energy.energy.name ps = PeakStats(energy.energy.name, bpmAD.stats1.total.name) livecallbacks = [ LiveTable(livetableitem), LivePlot(liveploty, x=liveplotx, fig=liveplotfig1), ps ] # Setup the scan @subs_decorator(livecallbacks) def braggscan(): yield from scan([bpmAD, pu, ring_current], energy, energy_setpoint - bragg_scanwidth, energy_setpoint + bragg_scanwidth, bragg_scanpoint) # Run the scan yield from braggscan() # Find the maximum and output result to file maxenergy = ps.max[0] maxintensity = ps.max[1] fwhm = ps.fwhm print('Max energy is:\t', maxenergy) print('Fundemental energy:\t', maxenergy / harmonic) f = open(UCalibDir + outfile, 'a') f.write(f"{str(energy.u_gap.position)}\t{str(maxenergy / harmonic)}\n") f.close()
def tune(self, width=None, num=None, peak_factor=None, md=None): """ Bluesky plan to execute one pass through the current scan range Scan self.axis centered about current position from ``-width/2`` to ``+width/2`` with ``num`` observations. If a peak was detected (default check is that max >= 4*min), then set ``self.tune_ok = True``. PARAMETERS width : float width of the tuning scan in the units of ``self.axis`` Default value in ``self.width`` (initially 1) num : int number of steps Default value in ``self.num`` (initially 10) md : dict, optional metadata """ width = width or self.width num = num or self.num peak_factor = peak_factor or self.peak_factor if self.peak_choice not in self._peak_choices_: msg = "peak_choice must be one of {}, geave {}" msg = msg.format(self._peak_choices_, self.peak_choice) raise ValueError(msg) initial_position = self.axis.position # final_position = initial_position # unless tuned start = initial_position - width/2 finish = initial_position + width/2 self.tune_ok = False tune_md = dict( width = width, initial_position = self.axis.position, time_iso8601 = str(datetime.datetime.now()), ) _md = {'tune_md': tune_md, 'plan_name': self.__class__.__name__ + '.tune', 'tune_parameters': dict( num = num, width = width, initial_position = self.axis.position, peak_choice = self.peak_choice, x_axis = self.axis.name, y_axis = self.signal_name, ), 'motors': (self.axis.name,), 'detectors': (self.signal_name,), 'hints': dict( dimensions = [ ( [self.axis.name], 'primary')] ) } _md.update(md or {}) if "pass_max" not in _md: self.stats = [] self.peaks = PeakStats(x=self.axis.name, y=self.signal_name) @bpp.subs_decorator(self.peaks) def _scan(md=None): yield from bps.open_run(md) position_list = np.linspace(start, finish, num) signal_list = list(self.signals) signal_list += [self.axis,] for pos in position_list: yield from bps.mv(self.axis, pos) yield from bps.trigger_and_read(signal_list) final_position = initial_position if self.peak_detected(peak_factor=peak_factor): self.tune_ok = True if self.peak_choice == "cen": final_position = self.peaks.cen elif self.peak_choice == "com": final_position = self.peaks.com else: final_position = None self.center = final_position # add stream with results # yield from add_results_stream() stream_name = "PeakStats" results = TuneResults(name=stream_name) results.tune_ok.put(self.tune_ok) results.center.put(self.center) results.final_position.put(final_position) results.initial_position.put(initial_position) results.set_stats(self.peaks) self.stats.append(results) if results.tune_ok.get(): yield from bps.create(name=stream_name) try: yield from bps.read(results) except ValueError as ex: separator = " "*8 + "-"*12 print(separator) print(f"Error saving stream {stream_name}:\n{ex}") print(separator) yield from bps.save() yield from bps.mv(self.axis, final_position) yield from bps.close_run() results.report(stream_name) return (yield from _scan(md=_md))
def undulator_calibration(outfile='SRXUgapCalibration.txt', u_gap_start=9.53, u_gap_end=18.03, u_gap_step=0.5): ''' outfile (string) is a .txt file for the lookup table, desirable to name it with the date of the calibration, e.g. SRXUgapCalibration20161225.txt undulator gap set point range are defined in: u_gap_start (float) u_gap_end (float) u_gap_step (float) ''' bpmAD.cam.read_attrs = ['acquire_time'] bpmAD.configuration_attrs = ['cam'] #default output directory for the lookup table UCalibDir = '/nfs/xf05id1/UndulatorCalibration/' newfile = False if newfile is True: f = open(UCalibDir + outfile, 'w') f.write('undulator_gap fundemental_energy\n') f.close() #bragg scan setup default energy_res = 0.002 #keV bragg_scanwidth = 0.1 #keV bragg_scanpoint = int(bragg_scanwidth * 2 / energy_res + 1) harmonic = 3 energy.harmonic.set(harmonic) #generate lookup table by scanning Bragg at each undulator gap set point for u_gap_setpoint in numpy.arange(u_gap_start, u_gap_end + u_gap_step, u_gap_step): energy_setpoint = float( energy.u_gap.utoelookup(u_gap_setpoint)) * harmonic print('move u_gap to:', u_gap_setpoint) print('move bragg energy to:', energy_setpoint) energy.move_c2_x.put(False) energy.move_u_gap.set(True) time.sleep(0.2) energy.move(energy_setpoint) ps = PeakStats(energy.energy.name, bpmAD.stats1.total.name) bpmAD_exposuretime_adjust() energy.move_u_gap.set(False) braggscan = bp.scan([bpmAD, pu, ring_current], energy, energy_setpoint - bragg_scanwidth, energy_setpoint + bragg_scanwidth, bragg_scanpoint) liveploty = bpmAD.stats1.total.name livetableitem = [energy.energy, bpmAD.stats1.total, ring_current] liveplotx = energy.energy.name liveplotfig1 = plt.figure() plt.show() RE(braggscan, [ LiveTable(livetableitem), LivePlot(liveploty, x=liveplotx, fig=liveplotfig1), ps ]) maxenergy = ps.max[0] maxintensity = ps.max[1] fwhm = ps.fwhm print('max energy is:', maxenergy) print('fundemental energy:', maxenergy / harmonic) f = open(UCalibDir + outfile, 'a') f.write( str(energy.u_gap.position) + ' ' + str(maxenergy / harmonic) + '\n') f.close()