Пример #1
0
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)
Пример #2
0
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())
Пример #3
0
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)
Пример #4
0
    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()
Пример #5
0
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
Пример #6
0
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)
Пример #7
0
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
Пример #8
0
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)
Пример #9
0
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)
Пример #10
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)
Пример #11
0
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)
Пример #12
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)
Пример #13
0
    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))
Пример #14
0
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
Пример #15
0
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)
Пример #16
0
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
Пример #17
0
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
Пример #18
0
    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()
Пример #19
0
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()
Пример #22
0
    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))
Пример #23
0
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()