Beispiel #1
0
 def _state(self, info=False):
     t = "%.1f" % self.temperature.get()
     if self.temperature.get() > self.alarm.get():
         return(error_msg(t))
     if self.temperature.get() > self.warning.get():
         return(warning_msg(t))
     if info is True and self.temperature.get() > (0.5 * self.warning.get()):
         return(info_msg(t))
     return(t)
Beispiel #2
0
def report(text, level=None, slack=False):
    '''Print a string to:
      * the log file
      * the screen
      * the BMM beamtime slack channel

    Report level decorations  on screen:

      * 'error' (red)
      * 'warning' (yellow)
      * 'info' (brown)
      * 'url' (undecorated)
      * 'bold' (bright white)
      * 'verbosebold' (bright cyan)
      * 'list' (cyan)
      * 'disconnected' (purple)
      * 'whisper' (gray)

    not matching a report level will be undecorated
    '''
    BMMuser = user_ns['BMMuser']
    BMM_log_info(text)
    if color:  # test that color is sensible...
        if level == 'error':
            print(error_msg(text))
        elif level == 'warning':
            print(warning_msg(text))
        elif level == 'info':
            print(info_msg(text))
        elif level == 'url':
            print(url_msg(text))
        elif level == 'bold':
            print(bold_msg(text))
        elif level == 'verbosebold':
            print(verbosebold_msg(text))
        elif level == 'disconnected':
            print(disconnected_msg(text))
        elif level == 'list':
            print(list_msg(text))
        elif level == 'whisper':
            print(whisper(text))
        else:
            print(text)
    else:
        print(text)
    if BMMuser.use_slack and slack:
        post_to_slack(text)
Beispiel #3
0
def change_edge(el,
                focus=False,
                edge='K',
                energy=None,
                slits=True,
                target=300.,
                xrd=False,
                bender=True):
    '''Change edge energy by:
    1. Moving the DCM above the edge energy
    2. Moving the photon delivery system to the correct mode
    3. Running a rocking curve scan
    4. Running a slits_height scan

    Parameters
    ----------
    el : str
        one- or two-letter symbol
    focus : bool, optional
        T=focused or F=unfocused beam [False, unfocused]
    edge : str, optional
        edge symbol ['K']
    energy : float, optional
        e0 value [None, determined from el/edge]
    slits : bool, optional
        perform slit_height() scan [False]
    target : float, optional
        energy where rocking curve is measured [300]
    xrd : boolean, optional
        force photon delivery system to XRD [False]

    Examples
    --------
    Normal use, unfocused beam:
       
    >>> RE(change_edge('Fe'))

    Normal use, focused beam:
       
    >>> RE(change_edge('Fe', focus=True))

    L2 or L1 edge:
       
    >>> RE(change_edge('Re', edge='L2'))

    Measure rocking curve at edge energy:
      
    >>> RE(change_edge('Fe', target=0))

    XRD, new energy:
       
    >>> RE(change_edge('Fe', xrd=True, energy=8600))
        
    note that you must specify an element, but it doesn't matter which
    one the energy will be moved to the specified energy xrd=True
    implies focus=True and target=0

    '''
    BMMuser, RE, dcm, dm3_bct = user_ns['BMMuser'], user_ns['RE'], user_ns[
        'dcm'], user_ns['dm3_bct']
    dcm_pitch, dcm_bragg = user_ns['dcm_pitch'], user_ns['dcm_bragg']
    rkvs = user_ns['rkvs']
    try:
        xs = user_ns['xs']
    except:
        pass
    #BMMuser.prompt = True
    el = el.capitalize()

    ######################################################################
    # this is a tool for verifying a macro.  this replaces an xafsmod scan  #
    # with a sleep, allowing the user to easily map out motor motions in #
    # a macro                                                            #
    if BMMuser.macro_dryrun:
        print(
            info_msg(
                '\nBMMuser.macro_dryrun is True.  Sleeping for %.1f seconds rather than changing to the %s edge.\n'
                % (BMMuser.macro_sleep, el)))
        countdown(BMMuser.macro_sleep)
        return (yield from null())
    ######################################################################

    if pds_motors_ready() is False:
        print(
            error_msg(
                '\nOne or more motors are showing amplifier faults.\nToggle the correct kill switch, then re-enable the faulted motor.'
            ))
        return (yield from null())

    (ok, text) = BMM_clear_to_start()
    if ok is False:
        print(
            error_msg('\n' + text) +
            bold_msg('Quitting change_edge() macro....\n'))
        return (yield from null())

    if energy is None:
        energy = edge_energy(el, edge)

    if energy is None:
        print(
            error_msg('\nEither %s or %s is not a valid symbol\n' %
                      (el, edge)))
        return (yield from null())
    if energy > 23500:
        edge = 'L3'
        energy = edge_energy(el, 'L3')

    if energy < 4000:
        print(warning_msg('The %s edge energy is below 4950 eV' % el))
        print(warning_msg('You have to change energy by hand.'))
        return (yield from null())

    if energy > 23500:
        print(
            warning_msg(
                'The %s edge energy is outside the range of this beamline!' %
                el))
        return (yield from null())

    BMMuser.edge = edge
    BMMuser.element = el
    BMMuser.edge_energy = energy
    rkvs.set('BMM:pds:edge', edge)
    rkvs.set('BMM:pds:element', el)
    rkvs.set('BMM:pds:edge_energy', energy)

    if energy > 8000:
        mode = 'A' if focus else 'D'
    elif energy < 6000:
        #mode = 'B' if focus else 'F'   ## mode B currently is inaccessible :(
        mode = 'C' if focus else 'F'
    else:
        mode = 'C' if focus else 'E'
    if xrd:
        mode = 'XRD'
        focus = True
        target = 0.0
    current_mode = get_mode()
    if mode in ('D', 'E', 'F') and current_mode in ('D', 'E', 'F'):
        with_m2 = False
    elif mode in ('A', 'B',
                  'C') and current_mode in ('A', 'B',
                                            'C'):  # no need to move M2
        with_m2 = False
    else:
        with_m2 = True
    if all_connected(with_m2) is False:
        print(warning_msg('Ophyd connection failure' % el))
        return (yield from null())

    ################################
    # confirm configuration change #
    ################################
    print(bold_msg('\nEnergy change:'))
    print('   %s: %s %s' %
          (list_msg('edge'), el.capitalize(), edge.capitalize()))
    print('   %s: %.1f' % (list_msg('edge energy'), energy))
    print('   %s: %.1f' % (list_msg('target energy'), energy + target))
    print('   %s: %s' % (list_msg('focus'), str(focus)))
    print('   %s: %s' % (list_msg('photon delivery mode'), mode))
    print('   %s: %s' % (list_msg('optimizing slits height'), str(slits)))
    if BMMuser.prompt:
        action = input("\nBegin energy change? [Y/n then Enter] ")
        if action.lower() == 'q' or action.lower() == 'n':
            return (yield from null())
        if mode == 'C' and energy < 6000:
            print(
                warning_msg(
                    '\nMoving to mode C for focused beam and an edge energy below 6 keV.'
                ))
            action = input(
                "You will not get optimal harmonic rejection.  Continue anyway?  [Y/n then Enter] "
            )
            if action.lower() == 'q' or action.lower() == 'n':
                return (yield from null())

    start = time.time()
    if mode == 'XRD':
        report('Configuring beamline for XRD', level='bold', slack=True)
    else:
        report(
            f'Configuring beamline for {el.capitalize()} {edge.capitalize()} edge',
            level='bold',
            slack=True)
    yield from dcm.kill_plan()

    ################################################
    # change to the correct photon delivery mode   #
    #      + move mono to correct energy           #
    #      + move reference holder to correct slot #
    ################################################
    # if not calibrating and mode != current_mode:
    #     print('Moving to photon delivery mode %s...' % mode)
    yield from mv(dcm_bragg.acceleration, BMMuser.acc_slow)
    yield from change_mode(mode=mode,
                           prompt=False,
                           edge=energy + target,
                           reference=el,
                           bender=bender)
    yield from mv(dcm_bragg.acceleration, BMMuser.acc_fast)
    if arrived_in_mode(mode=mode) is False:
        print(error_msg(f'\nFailed to arrive in Mode {mode}'))
        print(
            'Fixing this is often as simple as re-running the change_mode() command.'
        )
        print('Or try dm3_bct.kill() the re-run the change_mode() command.')
        print('If that doesn\'t work, call for help')
        return (yield from null())

    yield from user_ns['kill_mirror_jacks']()
    yield from sleep(1)
    if BMMuser.motor_fault is not None:
        print(
            error_msg('\nSome motors are reporting amplifier faults: %s' %
                      BMMuser.motor_fault))
        print(
            'Clear the faults and try running the same change_edge() command again.'
        )
        print('Troubleshooting: ' + url_msg(
            'https://nsls-ii-bmm.github.io/BeamlineManual/trouble.html#amplifier-fault'
        ))
        BMMuser.motor_fault = None
        return (yield from null())
    BMMuser.motor_fault = None

    ############################
    # run a rocking curve scan #
    ############################
    print('Optimizing rocking curve...')
    yield from abs_set(dcm_pitch.kill_cmd, 1, wait=True)
    yield from mv(dcm_pitch, approximate_pitch(energy + target))
    yield from sleep(1)
    yield from abs_set(dcm_pitch.kill_cmd, 1, wait=True)
    yield from rocking_curve()
    close_last_plot()

    ##########################
    # run a slit height scan #
    ##########################
    if slits:
        print('Optimizing slits height...')
        yield from slit_height(move=True)
        close_last_plot()
        ## redo rocking curve?

    ##################################
    # set reference and roi channels #
    ##################################
    if not xrd:
        ## reference channel
        rois = user_ns['rois']
        print('Moving reference foil...')
        yield from rois.select_plan(el)
        ## Xspress3
        BMMuser.verify_roi(xs, el, edge)
        ## feedback
        show_edges()

    if mode == 'XRD':
        report('Finished configuring for XRD', level='bold', slack=True)
    else:
        report(
            f'Finished configuring for {el.capitalize()} {edge.capitalize()} edge',
            level='bold',
            slack=True)
    if slits is False:
        print(
            '  * You may need to verify the slit position:  RE(slit_height())')
    self.to_json(os.path.join(self.DATA, '.BMMuser'))
    yield from dcm.kill_plan()
    end = time.time()
    print('\n\nThat took %.1f min' % ((end - start) / 60))
    return ()
def linescan(detector,
             axis,
             start,
             stop,
             nsteps,
             pluck=True,
             force=False,
             inttime=0.1,
             md={}):  # integration time?
    '''
    Generic linescan plan.  This is a RELATIVE scan, relative to the
    current position of the selected motor.

    Examples
    --------

    >>> RE(linescan('it', 'x', -1, 1, 21))

    Parameters
    ----------
    detector : str
        detector to display -- if, it, ir, or i0
    axis : str or EpicsMotor
        motor or nickname
    start : float
        starting value for a relative scan
    stop : float
         ending value for a relative scan
    nsteps : int
        number of steps in scan
    pluck : bool, optional
        flag for whether to offer to pluck & move motor
    force : bool, optional
        flag for forcing a scan even if not clear to start
    inttime : float, optional
        integration time in seconds (default: 0.1)

    The motor is either the BlueSky name for a motor (e.g. xafs_linx)
    or a nickname for an XAFS sample motor (e.g. 'x' for xafs_linx).

    This does not write an ASCII data file, but it does make a log entry.

    Use the ls2dat() function to extract the linescan from the
    database and write it to a file.
    '''
    def main_plan(detector, axis, start, stop, nsteps, pluck, force):
        (ok, text) = BMM_clear_to_start()
        if force is False and ok is False:
            print(error_msg(text))
            yield from null()
            return

        detector, axis = ls_backwards_compatibility(detector, axis)
        # print('detector is: ' + str(detector))
        # print('axis is: ' + str(axis))
        # return(yield from null())

        RE.msg_hook = None
        ## sanitize input and set thismotor to an actual motor
        if type(axis) is str: axis = axis.lower()
        detector = detector.capitalize()

        ## sanity checks on axis
        if axis not in motor_nicknames.keys() and 'EpicsMotor' not in str(type(axis)) \
           and 'PseudoSingle' not in str(type(axis)) and 'WheelMotor' not in str(type(axis)):
            print(
                error_msg('\n*** %s is not a linescan motor (%s)\n' %
                          (axis, str.join(', ', motor_nicknames.keys()))))
            yield from null()
            return

        if 'EpicsMotor' in str(type(axis)):
            thismotor = axis
        elif 'PseudoSingle' in str(type(axis)):
            thismotor = axis
        elif 'WheelMotor' in str(type(axis)):
            thismotor = axis
        else:  # presume it's an xafs_XXXX motor
            thismotor = motor_nicknames[axis]

        current = thismotor.position
        if current + start < thismotor.limits[0]:
            print(
                error_msg(
                    f'These scan parameters will take {thismotor.name} outside it\'s lower limit of {thismotor.limits[0]}'
                ))
            print(whisper(f'(starting position = {thismotor.position})'))
            return (yield from null())
        if current + stop > thismotor.limits[1]:
            print(
                error_msg(
                    f'These scan parameters will take {thismotor.name} outside it\'s upper limit of {thismotor.limits[1]}'
                ))
            print(whisper(f'(starting position = {thismotor.position})'))
            return (yield from null())

        BMMuser.motor = thismotor

        ## sanity checks on detector
        if detector not in ('It', 'If', 'I0', 'Iy', 'Ir', 'Both', 'Bicron',
                            'Ia', 'Ib', 'Dualio', 'Xs', 'Xs1'):
            print(
                error_msg(
                    '\n*** %s is not a linescan measurement (%s)\n' %
                    (detector,
                     'it, if, i0, iy, ir, both, bicron, dualio, xs, xs1')))
            yield from null()
            return

        yield from abs_set(user_ns['_locked_dwell_time'], inttime, wait=True)
        if detector == 'Xs':
            yield from mv(xs.settings.acquire_time, inttime)
            yield from mv(xs.total_points, nsteps)
        dets = [
            user_ns['quadem1'],
        ]
        if user_ns['with_dualem']:
            dualio = user_ns['dualio']
        denominator = ''
        detname = ''

        ## func is an anonymous function, built on the fly, for feeding to DerivedPlot
        if detector == 'It':
            denominator = ' / I0'
            detname = 'transmission'
            func = lambda doc: (doc['data'][thismotor.name], doc['data']['It']
                                / doc['data']['I0'])
        elif detector == 'Ia' and dualio is not None:
            dets.append(dualio)
            detname = 'Ia'
            func = lambda doc: (doc['data'][thismotor.name], doc['data']['Ia'])
        elif detector == 'Ib' and dualio is not None:
            dets.append(dualio)
            detname = 'Ib'
            func = lambda doc: (doc['data'][thismotor.name], doc['data']['Ib'])
        elif detector == 'Ir':
            #denominator = ' / It'
            detname = 'reference'
            #func = lambda doc: (doc['data'][thismotor.name], doc['data']['Ir']/doc['data']['It'])
            func = lambda doc: (doc['data'][thismotor.name], doc['data']['Ir'])
        elif detector == 'I0':
            detname = 'I0'
            func = lambda doc: (doc['data'][thismotor.name], doc['data']['I0'])
        elif detector == 'Bicron':
            dets.append(user_ns['vor'])
            detname = 'Bicron'
            func = lambda doc: (doc['data'][thismotor.name], doc['data'][
                'Bicron'])
        elif detector == 'Iy':
            denominator = ' / I0'
            detname = 'electron yield'
            func = lambda doc: (doc['data'][thismotor.name], doc['data']['Iy']
                                / doc['data']['I0'])
        elif detector == 'If':
            dets.append(user_ns['vor'])
            denominator = ' / I0'
            detname = 'fluorescence'
            func = lambda doc: (doc['data'][thismotor.name], (doc['data'][
                BMMuser.dtc1] + doc['data'][BMMuser.dtc2] + doc['data'][
                    BMMuser.dtc3] + doc['data'][BMMuser.dtc4]) / doc['data'][
                        'I0'])
        elif detector == 'Xs':
            dets.append(user_ns['xs'])
            denominator = ' / I0'
            detname = 'fluorescence'
            func = lambda doc: (doc['data'][thismotor.name],
                                (doc['data'][BMMuser.xs1] + doc['data'][
                                    BMMuser.xs2] + doc['data'][BMMuser.xs3] +
                                 doc['data'][BMMuser.xs4]) / doc['data']['I0'])
            yield from mv(xs.total_points,
                          nsteps)  # Xspress3 demands that this be set up front

        elif detector == 'Xs1':
            dets.append(user_ns['xs'])
            denominator = ' / I0'
            detname = 'fluorescence'
            func = lambda doc: (doc['data'][thismotor.name], doc['data'][
                BMMuser.xs8] / doc['data']['I0'])
            yield from mv(xs1.total_points,
                          nsteps)  # Xspress3 demands that this be set up front

        elif detector == 'Dualio':
            dets.append(dualio)
            funcia = lambda doc: (doc['data'][thismotor.name], doc['data']['Ia'
                                                                           ])
            funcib = lambda doc: (doc['data'][thismotor.name], doc['data']['Ib'
                                                                           ])

        ## need a "Both" for trans + xs !!!!!!!!!!
        elif detector == 'Both':
            dets.append(user_ns['vor'])
            functr = lambda doc: (doc['data'][thismotor.name], doc['data'][
                'It'] / doc['data']['I0'])
            funcfl = lambda doc: (doc['data'][thismotor.name], (doc['data'][
                BMMuser.dtc1] + doc['data'][BMMuser.dtc2] + doc['data'][
                    BMMuser.dtc3] + doc['data'][BMMuser.dtc4]) / doc['data'][
                        'I0'])
        ## and this is the appropriate way to plot this linescan

        #abs_set(_locked_dwell_time, 0.5)
        if detector == 'Both':
            plot = [
                DerivedPlot(funcfl,
                            xlabel=thismotor.name,
                            ylabel='If/I0',
                            title='fluorescence vs. %s' % thismotor.name),
                DerivedPlot(functr,
                            xlabel=thismotor.name,
                            ylabel='It/I0',
                            title='transmission vs. %s' % thismotor.name)
            ]
        elif detector == 'Dualio':
            plot = [
                DerivedPlot(funcia,
                            xlabel=thismotor.name,
                            ylabel='Ia/I0',
                            title='Ia vs. %s' % thismotor.name),
                DerivedPlot(funcib,
                            xlabel=thismotor.name,
                            ylabel='Ib/I0',
                            title='Ib vs. %s' % thismotor.name)
            ]
        else:
            plot = DerivedPlot(func,
                               xlabel=thismotor.name,
                               ylabel=detector + denominator,
                               title='%s vs. %s' % (detname, thismotor.name))
        if 'PseudoSingle' in str(type(axis)):
            value = thismotor.readback.get()
        else:
            value = thismotor.user_readback.get()
        line1 = '%s, %s, %.3f, %.3f, %d -- starting at %.3f\n' % \
                (thismotor.name, detector, start, stop, nsteps, value)
        ##BMM_suspenders()            # engage suspenders

        thismd = dict()
        thismd['XDI'] = dict()
        thismd['XDI']['Facility'] = dict()
        thismd['XDI']['Facility']['GUP'] = BMMuser.gup
        thismd['XDI']['Facility']['SAF'] = BMMuser.saf

        rkvs.set('BMM:scan:type', 'line')
        rkvs.set('BMM:scan:starttime',
                 str(datetime.datetime.timestamp(datetime.datetime.now())))
        rkvs.set('BMM:scan:estimated', 0)

        @subs_decorator(plot)
        #@subs_decorator(src.callback)
        def scan_xafs_motor(dets, motor, start, stop, nsteps):
            uid = yield from rel_scan(dets,
                                      motor,
                                      start,
                                      stop,
                                      nsteps,
                                      md={
                                          **thismd,
                                          **md
                                      })
            return uid

        uid = yield from scan_xafs_motor(dets, thismotor, start, stop, nsteps)
        #global mytable
        #run = src.retrieve()
        #mytable = run.primary.read().to_dataframe()
        BMM_log_info('linescan: %s\tuid = %s, scan_id = %d' %
                     (line1, uid, user_ns['db'][-1].start['scan_id']))
        if pluck is True:
            action = input('\n' + bold_msg(
                'Pluck motor position from the plot? [Y/n then Enter] '))
            if action.lower() == 'n' or action.lower() == 'q':
                return (yield from null())
            yield from move_after_scan(thismotor)

    def cleanup_plan():
        ## BMM_clear_suspenders()
        ##RE.clear_suspenders()       # disable suspenders
        yield from resting_state_plan()

    RE, BMMuser, rkvs = user_ns['RE'], user_ns['BMMuser'], user_ns['rkvs']
    try:
        xs = user_ns['xs']
    except:
        pass
    ######################################################################
    # this is a tool for verifying a macro.  this replaces an xafs scan  #
    # with a sleep, allowing the user to easily map out motor motions in #
    # a macro                                                            #
    if BMMuser.macro_dryrun:
        print(
            info_msg(
                '\nBMMuser.macro_dryrun is True.  Sleeping for %.1f seconds rather than running a line scan.\n'
                % BMMuser.macro_sleep))
        countdown(BMMuser.macro_sleep)
        return (yield from null())
    ######################################################################
    RE.msg_hook = None
    yield from finalize_wrapper(
        main_plan(detector, axis, start, stop, nsteps, pluck, force),
        cleanup_plan())
    RE.msg_hook = BMM_msg_hook
def rocking_curve(start=-0.10,
                  stop=0.10,
                  nsteps=101,
                  detector='I0',
                  choice='peak'):
    '''Perform a relative scan of the DCM 2nd crystal pitch around the current
    position to find the peak of the crystal rocking curve.  Begin by opening
    the hutch slits to 3 mm. At the end, move to the position of maximum 
    intensity on I0, then return to the hutch slits to their original height.

    Parameters
    ----------
    start : (float)
        starting position relative to current [-0.1]
    end : (float)
        ending position relative to current [0.1]
    nsteps : (int)
        number of steps [101]
    detector : (string)
        'I0' or 'Bicron' ['I0']
    choice : (string)
        'peak', fit' or 'com' (center of mass) ['peak']

    If choice is fit, the fit is performed using the
    SkewedGaussianModel from lmfit, which works pretty well for this
    measurement at BMM.  The line shape is a bit skewed due to the
    convolution with the slightly misaligned entrance slits.

    '''
    def main_plan(start, stop, nsteps, detector):
        (ok, text) = BMM_clear_to_start()
        if ok is False:
            print(error_msg(text))
            yield from null()
            return

        RE.msg_hook = None
        BMMuser.motor = motor

        if detector.lower() == 'bicron':
            func = lambda doc: (doc['data'][motor.name], doc['data']['Bicron'])
            dets = [
                bicron,
            ]
            sgnl = 'Bicron'
            titl = 'Bicron signal vs. DCM 2nd crystal pitch'
        else:
            func = lambda doc: (doc['data'][motor.name], doc['data']['I0'])
            dets = [
                quadem1,
            ]
            sgnl = 'I0'
            titl = 'I0 signal vs. DCM 2nd crystal pitch'

        plot = DerivedPlot(func, xlabel=motor.name, ylabel=sgnl, title=titl)

        rkvs.set('BMM:scan:type', 'line')
        rkvs.set('BMM:scan:starttime',
                 str(datetime.datetime.timestamp(datetime.datetime.now())))
        rkvs.set('BMM:scan:estimated', 0)

        @subs_decorator(plot)
        #@subs_decorator(src.callback)
        def scan_dcmpitch(sgnl):
            line1 = '%s, %s, %.3f, %.3f, %d -- starting at %.3f\n' % \
                    (motor.name, sgnl, start, stop, nsteps, motor.user_readback.get())

            yield from abs_set(user_ns['_locked_dwell_time'], 0.1, wait=True)
            yield from dcm.kill_plan()

            yield from mv(slits3.vsize, 3)
            if sgnl == 'Bicron':
                yield from mv(slitsg.vsize, 5)

            uid = yield from rel_scan(dets, motor, start, stop, nsteps)
            #yield from rel_adaptive_scan(dets, 'I0', motor,
            #                             start=start,
            #                             stop=stop,
            #                             min_step=0.002,
            #                             max_step=0.03,
            #                             target_delta=.15,
            #                             backstep=True)
            t = db[-1].table()
            signal = t[sgnl]
            if choice.lower() == 'com':
                position = com(signal)
                top = t[motor.name][position]
            elif choice.lower() == 'fit':
                pitch = t['dcm_pitch']
                mod = SkewedGaussianModel()
                pars = mod.guess(signal, x=pitch)
                out = mod.fit(signal, pars, x=pitch)
                print(whisper(out.fit_report(min_correl=0)))
                out.plot()
                top = out.params['center'].value
            else:
                position = peak(signal)
                top = t[motor.name][position]

            yield from sleep(3.0)
            yield from abs_set(motor.kill_cmd, 1, wait=True)
            RE.msg_hook = BMM_msg_hook

            BMM_log_info('rocking curve scan: %s\tuid = %s, scan_id = %d' %
                         (line1, uid, user_ns['db'][-1].start['scan_id']))
            yield from mv(motor, top)
            if sgnl == 'Bicron':
                yield from mv(slitsg.vsize, gonio_slit_height)

        yield from scan_dcmpitch(sgnl)

    def cleanup_plan():
        yield from mv(user_ns['slits3'].vsize, slit_height)
        yield from abs_set(user_ns['_locked_dwell_time'], 0.5, wait=True)
        yield from sleep(1.0)
        yield from abs_set(motor.kill_cmd, 1, wait=True)
        yield from sleep(1.0)
        yield from user_ns['dcm'].kill_plan()
        yield from resting_state_plan()

    RE, BMMuser, db, rkvs = user_ns['RE'], user_ns['BMMuser'], user_ns[
        'db'], user_ns['rkvs']
    dcm, slits3, slitsg, quadem1 = user_ns['dcm'], user_ns['slits3'], user_ns[
        'slitsg'], user_ns['quadem1']
    ######################################################################
    # this is a tool for verifying a macro.  this replaces this rocking  #
    # curve scan with a sleep, allowing the user to easily map out motor #
    # motions in a macro                                                 #
    if BMMuser.macro_dryrun:
        print(
            info_msg(
                '\nBMMuser.macro_dryrun is True.  Sleeping for %.1f seconds rather than running a rocking curve scan.\n'
                % BMMuser.macro_sleep))
        countdown(BMMuser.macro_sleep)
        return (yield from null())
    ######################################################################
    motor = user_ns['dcm_pitch']
    slit_height = user_ns['slits3'].vsize.readback.get()
    try:
        gonio_slit_height = slitsg.vsize.readback.get()
    except:
        gonio_slit_height = 1
    RE.msg_hook = None
    yield from finalize_wrapper(main_plan(start, stop, nsteps, detector),
                                cleanup_plan())
    RE.msg_hook = BMM_msg_hook
def slit_height(start=-1.5,
                stop=1.5,
                nsteps=31,
                move=False,
                force=False,
                slp=1.0,
                choice='peak'):
    '''Perform a relative scan of the DM3 BCT motor around the current
    position to find the optimal position for slits3. Optionally, the
    motor will moved to the center of mass of the peak at the end of
    the scan.

    Parameters
    ----------
    start : float
        starting position relative to current [-3.0]
    end : float 
        ending position relative to current [3.0]
    nsteps : int
        number of steps [61]
    move : bool
        True=move to position of max signal, False=pluck and move [False]
    slp : float
        length of sleep before trying to move dm3_bct [3.0]
    choice : str 
        'peak' or 'com' (center of mass) ['peak']
    '''
    def main_plan(start, stop, nsteps, move, slp, force):
        (ok, text) = BMM_clear_to_start()
        if force is False and ok is False:
            print(error_msg(text))
            yield from null()
            return

        RE.msg_hook = None
        BMMuser.motor = user_ns['dm3_bct']
        func = lambda doc: (doc['data'][motor.name], doc['data']['I0'])
        plot = DerivedPlot(func,
                           xlabel=motor.name,
                           ylabel='I0',
                           title='I0 signal vs. slit height')
        line1 = '%s, %s, %.3f, %.3f, %d -- starting at %.3f\n' % \
                (motor.name, 'i0', start, stop, nsteps, motor.user_readback.get())
        rkvs.set('BMM:scan:type', 'line')
        rkvs.set('BMM:scan:starttime',
                 str(datetime.datetime.timestamp(datetime.datetime.now())))
        rkvs.set('BMM:scan:estimated', 0)

        @subs_decorator(plot)
        #@subs_decorator(src.callback)
        def scan_slit(slp):

            #if slit_height < 0.5:
            #    yield from mv(slits3.vsize, 0.5)

            yield from abs_set(quadem1.averaging_time, 0.1, wait=True)
            yield from abs_set(motor.velocity, 0.4, wait=True)
            yield from abs_set(motor.kill_cmd, 1, wait=True)

            uid = yield from rel_scan([quadem1], motor, start, stop, nsteps)

            RE.msg_hook = BMM_msg_hook
            BMM_log_info('slit height scan: %s\tuid = %s, scan_id = %d' %
                         (line1, uid, user_ns['db'][-1].start['scan_id']))
            if move:
                t = db[-1].table()
                signal = t['I0']
                #if get_mode() in ('A', 'B', 'C'):
                #    position = com(signal)
                #else:
                position = peak(signal)
                top = t[motor.name][position]

                yield from sleep(slp)
                yield from abs_set(motor.kill_cmd, 1, wait=True)
                yield from mv(motor, top)

            else:
                action = input('\n' + bold_msg(
                    'Pluck motor position from the plot? [Y/n then Enter] '))
                if action.lower() == 'n' or action.lower() == 'q':
                    return (yield from null())
                yield from sleep(slp)
                yield from abs_set(motor.kill_cmd, 1, wait=True)
                yield from move_after_scan(motor)
            yield from abs_set(quadem1.averaging_time, 0.5, wait=True)

        yield from scan_slit(slp)

    def cleanup_plan(slp):
        yield from mv(slits3.vsize, slit_height)
        yield from abs_set(user_ns['_locked_dwell_time'], 0.5, wait=True)
        yield from sleep(slp)
        yield from abs_set(motor.kill_cmd, 1, wait=True)
        yield from resting_state_plan()

    RE, BMMuser, db, slits3, quadem1 = user_ns['RE'], user_ns[
        'BMMuser'], user_ns['db'], user_ns['slits3'], user_ns['quadem1']
    rkvs = user_ns['rkvs']
    #######################################################################
    # this is a tool for verifying a macro.  this replaces this slit      #
    # height scan with a sleep, allowing the user to easily map out motor #
    # motions in a macro                                                  #
    if BMMuser.macro_dryrun:
        print(
            info_msg(
                '\nBMMuser.macro_dryrun is True.  Sleeping for %.1f seconds rather than running a slit height scan.\n'
                % BMMuser.macro_sleep))
        countdown(BMMuser.macro_sleep)
        return (yield from null())
    #######################################################################
    motor = user_ns['dm3_bct']
    slit_height = slits3.vsize.readback.get()
    RE.msg_hook = None
    yield from finalize_wrapper(
        main_plan(start, stop, nsteps, move, slp, force), cleanup_plan(slp))
    RE.msg_hook = BMM_msg_hook
Beispiel #7
0
    def auto_align(self, pitch=2, drop=None):
        '''Align a sample on a spinner automatically.  This performs 5 scans.
        The first four iterate twice between linear and pitch
        against the signal in It.  This find the flat position.

        Then the sample is pitched to the requested angle and a fifth
        scan is done to optimize the linear motor position against the
        fluorescence signal.

        The linear scans against It look like a step-down function.
        The center of this step is found as the centroid of a fitted
        error function.

        The xafs_pitch scan should be peaked.  Move to the max of the
        signal.

        The linear scan against fluorescence ideally looks like a
        flat-topped peak.  Move to the center of mass.

        At the end, a three-panel figure is drawn showing the last
        three scans.  This is posted to Slack.  It also finds its way
        into the dossier as a record of the quality of the alignment.

        Arguments
        =========
        pitch : int
          The angle at which to make the glancing angle measurements.
        drop : int or None
          If not None, then this many points will be dropped from the
          end of linear scan against transmission when fitting the error
          function. This is an attempt to deal gracefully with leakage 
          through the adhesive at very high energy.

        '''
        BMMuser = user_ns['BMMuser']
        if BMMuser.macro_dryrun:
            report(f'Auto-aligning glancing angle stage, spinner {self.current()}', level='bold', slack=False)
            print(info_msg(f'\nBMMuser.macro_dryrun is True.  Sleeping for %.1f seconds at spinner %d.\n' %
                           (BMMuser.macro_sleep, self.current())))
            countdown(BMMuser.macro_sleep)
            return(yield from null())

        report(f'Auto-aligning glancing angle stage, spinner {self.current()}', level='bold', slack=True)
            
        BMM_suspenders()

        ## first pass in transmission
        yield from self.align_linear(drop=drop)
        yield from self.align_pitch()

        ## for realsies X or Y in transmission
        yield from self.align_linear(drop=drop)
        self.y_uid = user_ns['db'].v2[-1].metadata['start']['uid'] 

        ## for realsies Y in pitch
        yield from self.align_pitch()
        self.pitch_uid = user_ns['db'].v2[-1].metadata['start']['uid'] 

        ## record the flat position
        if self.orientation == 'parallel':
            motor = user_ns['xafs_y']
        else:
            motor = user_ns['xafs_x']
        self.flat = [motor.position, user_ns['xafs_pitch'].position]

        ## move to measurement angle and align
        yield from mvr(user_ns['xafs_pitch'], pitch)
        yield from linescan(motor, 'xs', -2.3, 2.3, 51, pluck=False)
        self.f_uid = user_ns['db'].v2[-1].metadata['start']['uid'] 
        tf = user_ns['db'][-1].table()
        yy = tf[motor.name]
        signal = (tf[BMMuser.xs1] + tf[BMMuser.xs2] + tf[BMMuser.xs3] + tf[BMMuser.xs4]) / tf['I0']
        if BMMuser.element == 'Zr':
            centroid = yy[signal.idxmax()]
        else:
            com = int(center_of_mass(signal)[0])+1
            centroid = yy[com]
        yield from mv(motor, centroid)
        
        ## make a pretty picture, post it to slack
        self.alignment_plot(self.y_uid, self.pitch_uid, self.f_uid)
        self.alignment_filename = os.path.join(BMMuser.folder, 'snapshots', f'spinner{self.current()}-alignment-{now()}.png')
        plt.savefig(self.alignment_filename)
        try:
            img_to_slack(self.alignment_filename)
        except:
            post_to_slack('failed to post image: {self.alignment_filename}')
            pass
        BMM_clear_suspenders()
Beispiel #8
0
def areascan(detector,
             slow,
             startslow,
             stopslow,
             nslow,
             fast,
             startfast,
             stopfast,
             nfast,
             pluck=True,
             force=False,
             dwell=0.1,
             md={}):
    '''
    Generic areascan plan.  This is a RELATIVE scan, relative to the
    current positions of the selected motors.

    For example:
       RE(areascan('it', 'x', -1, 1, 21, 'y', -0.5, 0.5, 11))

       detector: detector to display -- if, it, ir, or i0
       slow:     slow axis motor or nickname
       sl1:      starting value for slow axis of a relative scan
       sl2:      ending value for slow axis of a relative scan
       nsl:      number of steps in slow axis
       fast:     fast axis motor or nickname
       fa1:      starting value for fast axis of a relative scan
       fa2:      ending value for fast axis of a relative scan
       nfa:      number of steps in fast axis
       pluck:    optional flag for whether to offer to pluck & move motor
       force:    optional flag for forcing a scan even if not clear to start
       dwell:    dwell time at each point (0.1 sec default)
       md:       composable dictionary of metadata

    slow and fast are either the BlueSky name for a motor (e.g. xafs_linx)
    or a nickname for an XAFS sample motor (e.g. 'x' for xafs_linx).

    This does not write an ASCII data file, but it does make a log entry.

    Use the as2dat() function to extract the areascan from the
    database and write it to a file.
    '''
    def main_plan(detector, slow, startslow, stopslow, nslow, fast, startfast,
                  stopfast, nfast, pluck, force, dwell, md):
        (ok, text) = BMM_clear_to_start()
        if force is False and ok is False:
            print(error_msg(text))
            BMMuser.final_log_entry = False
            yield from null()
            return

        RE.msg_hook = None

        ## sanity checks on slow axis
        if type(slow) is str: slow = slow.lower()
        if slow not in motor_nicknames.keys() and 'EpicsMotor' not in str(
                type(slow)) and 'PseudoSingle' not in str(type(slow)):
            print(
                error_msg('\n*** %s is not an areascan motor (%s)\n' %
                          (slow, str.join(', ', motor_nicknames.keys()))))
            BMMuser.final_log_entry = False
            yield from null()
            return
        if slow in motor_nicknames.keys():
            slow = motor_nicknames[slow]

        ## sanity checks on fast axis
        if type(fast) is str: fast = fast.lower()
        if fast not in motor_nicknames.keys() and 'EpicsMotor' not in str(
                type(fast)) and 'PseudoSingle' not in str(type(fast)):
            print(
                error_msg('\n*** %s is not an areascan motor (%s)\n' %
                          (fast, str.join(', ', motor_nicknames.keys()))))
            BMMuser.final_log_entry = False
            yield from null()
            return
        if fast in motor_nicknames.keys():
            fast = motor_nicknames[fast]

        detector = detector.capitalize()
        yield from abs_set(_locked_dwell_time, dwell, wait=True)
        dets = [
            quadem1,
        ]
        if detector == 'If':
            dets.append(vor)
            detector = 'ROI1'
        if detector.lower() == 'xs':
            dets.append(xs)
            detector = BMMuser.xs1

        if 'PseudoSingle' in str(type(slow)):
            valueslow = slow.readback.get()
        else:
            valueslow = slow.user_readback.get()
            line1 = 'slow motor: %s, %.3f, %.3f, %d -- starting at %.3f\n' % \
                    (slow.name, startslow, stopslow, nslow, valueslow)

        if 'PseudoSingle' in str(type(fast)):
            valuefast = fast.readback.get()
        else:
            valuefast = fast.user_readback.get()
        line2 = 'fast motor: %s, %.3f, %.3f, %d -- starting at %.3f\n' % \
                (fast.name, startfast, stopfast, nfast, valuefast)

        npoints = nfast * nslow
        estimate = int(npoints * (dwell + 0.7))

        # extent = (
        #     valuefast + startfast,
        #     valueslow + startslow,
        #     valuefast + stopfast,
        #     valueslow + stopslow,
        # )
        # extent = (
        #     0,
        #     nfast-1,
        #     0,
        #     nslow-1
        # )
        # print(extent)
        # return(yield from null())

        # areaplot = LiveScatter(fast.name, slow.name, detector,
        #                        xlim=(startfast, stopfast), ylim=(startslow, stopslow))

        areaplot = LiveGrid(
            (nslow, nfast),
            detector,  #aspect='equal', #aspect=float(nslow/nfast), extent=extent,
            xlabel='fast motor: %s' % fast.name,
            ylabel='slow motor: %s' % slow.name)
        #BMMuser.ax     = areaplot.ax
        #BMMuser.fig    = areaplot.ax.figure
        BMMuser.motor = fast
        BMMuser.motor2 = slow
        #BMMuser.fig.canvas.mpl_connect('close_event', handle_close)

        thismd = dict()
        thismd['XDI'] = dict()
        thismd['XDI']['Facility'] = dict()
        thismd['XDI']['Facility']['GUP'] = BMMuser.gup
        thismd['XDI']['Facility']['SAF'] = BMMuser.saf
        thismd['slow_motor'] = slow.name
        thismd['fast_motor'] = fast.name

        ## engage suspenders right before starting scan sequence
        if force is False: BMM_suspenders()

        @subs_decorator(areaplot)
        #@subs_decorator(src.callback)
        def make_areascan(dets,
                          slow,
                          startslow,
                          stopslow,
                          nslow,
                          fast,
                          startfast,
                          stopfast,
                          nfast,
                          snake=False):
            BMMuser.final_log_entry = False
            uid = yield from grid_scan(dets, slow, startslow, stopslow, nslow,
                                       fast, startfast, stopfast, nfast, snake)
            BMMuser.final_log_entry = True
            return uid

        rkvs.set('BMM:scan:type', 'area')
        rkvs.set('BMM:scan:starttime',
                 str(datetime.datetime.timestamp(datetime.datetime.now())))
        rkvs.set('BMM:scan:estimated', estimate)

        BMM_log_info('begin areascan observing: %s\n%s%s' %
                     (detector, line1, line2))
        uid = yield from make_areascan(dets, slow, valueslow + startslow,
                                       valueslow + stopslow, nslow, fast,
                                       valuefast + startfast,
                                       valuefast + stopfast, nfast, False)

        if pluck is True:
            action = input('\n' + bold_msg(
                'Pluck motor position from the plot? [Y/n then Enter] '))
            if action.lower() == 'n' or action.lower() == 'q':
                return (yield from null())
            print(
                'Single click the left mouse button on the plot to pluck a point...'
            )
            cid = BMMuser.fig.canvas.mpl_connect(
                'button_press_event',
                interpret_click)  # see 65-derivedplot.py and
            while BMMuser.x is None:  #  https://matplotlib.org/users/event_handling.html
                yield from sleep(0.5)

            print('Converting plot coordinates to real coordinates...')
            begin = valuefast + startfast
            stepsize = (stopfast - startfast) / (nfast - 1)
            pointfast = begin + stepsize * BMMuser.x
            #print(BMMuser.x, pointfast)

            begin = valueslow + startslow
            stepsize = (stopslow - startslow) / (nslow - 1)
            pointslow = begin + stepsize * BMMuser.y
            #print(BMMuser.y, pointslow)

            print('That translates to x=%.3f, y=%.3f' % (pointfast, pointslow))
            yield from mv(fast, pointfast, slow, pointslow)

    def cleanup_plan():
        print('Cleaning up after an area scan')
        RE.clear_suspenders()
        if BMMuser.final_log_entry is True:
            BMM_log_info('areascan finished\n\tuid = %s, scan_id = %d' %
                         (db[-1].start['uid'], db[-1].start['scan_id']))
        yield from resting_state_plan()
        RE.msg_hook = BMM_msg_hook

        print('Disabling plot for re-plucking.')
        try:
            cid = BMMuser.fig.canvas.mpl_disconnect(cid)
        except:
            pass
        BMMuser.x = None
        BMMuser.y = None
        BMMuser.motor = None
        BMMuser.motor2 = None
        BMMuser.fig = None
        BMMuser.ax = None

    RE, BMMuser, _locked_dwell_time, rkvs = user_ns['RE'], user_ns[
        'BMMuser'], user_ns['_locked_dwell_time'], user_ns['rkvs']
    db, quadem1, vor = user_ns['db'], user_ns['quadem1'], user_ns['vor']
    if user_ns['with_xspress3']:
        xs = user_ns['xs']
    ######################################################################
    # this is a tool for verifying a macro.  this replaces an xafs scan  #
    # with a sleep, allowing the user to easily map out motor motions in #
    # a macro                                                            #
    if BMMuser.macro_dryrun:
        print(
            info_msg(
                '\nBMMuser.macro_dryrun is True.  Sleeping for %.1f seconds rather than running an area scan.\n'
                % BMMuser.macro_sleep))
        countdown(BMMuser.macro_sleep)
        return (yield from null())
    ######################################################################
    BMMuser.final_log_entry = True
    RE.msg_hook = None
    ## encapsulation!
    yield from finalize_wrapper(
        main_plan(detector, slow, startslow, stopslow, nslow, fast, startfast,
                  stopfast, nfast, pluck, force, dwell, md), cleanup_plan())
    RE.msg_hook = BMM_msg_hook
def change_mode(mode=None,
                prompt=True,
                edge=None,
                reference=None,
                bender=True):
    '''Move the photon delivery system to a new mode. 
     A: focused at XAS end station, energy > 8000
     B: focused at XAS end station, energy < 6000
     C: focused at XAS end station, 6000 < energy < 8000
     D: unfocused, energy > 8000
     E: unfocused, 6000 < energy < 8000
     F: unfocused, energy < 8000
     XRD: focused at XRD end station, energy > 8000
     '''
    BMMuser, RE, dcm, dm3_bct, slits3 = user_ns['BMMuser'], user_ns[
        'RE'], user_ns['dcm'], user_ns['dm3_bct'], user_ns['slits3']
    xafs_table, m3, m2, m2_bender, xafs_ref = user_ns['xafs_table'], user_ns[
        'm3'], user_ns['m2'], user_ns['m2_bender'], user_ns['xafs_ref']
    if mode is None:
        print('No mode specified')
        return (yield from null())

    mode = mode.upper()
    if mode not in ('A', 'B', 'C', 'D', 'E', 'F', 'XRD'):
        print('%s is not a mode' % mode)
        return (yield from null())
    current_mode = get_mode()

    # crude hack around a problem I don't understand
    if dm3_bct.hlm.get() < 55 or dm3_bct.llm.get() > -55:
        dm3_bct.llm.put(-60)
        dm3_bct.hlm.put(60)

    if pds_motors_ready() is False:
        print(
            error_msg(
                '\nOne or more motors are showing amplifier faults.\nToggle the correct kill switch, then re-enable the faulted motor.'
            ))
        return (yield from null())

    ######################################################################
    # this is a tool for verifying a macro.  this replaces an xafs scan  #
    # with a sleep, allowing the user to easily map out motor motions in #
    # a macro                                                            #
    if BMMuser.macro_dryrun:
        print(
            info_msg(
                '\nBMMuser.macro_dryrun is True.  Sleeping for %.1f seconds rather than changing to mode %s.\n'
                % (BMMuser.macro_sleep, mode)))
        countdown(BMMuser.macro_sleep)
        return (yield from null())
    ######################################################################

    if mode == 'B':
        action = input(
            "You are entering Mode B -- focused beam below 6 keV is not properly configured at BMM. Continue? [y/N then Enter] "
        )
        if action.lower() != 'y':
            return (yield from null())

    if mode == 'A':
        description = 'focused, >8 keV'
    elif mode == 'B':
        description = 'focused, <6 keV'
    elif mode == 'C':
        description = 'focused, 6 to 8 keV'
    elif mode == 'D':
        description = 'unfocused, >8 keV'
    elif mode == 'E':
        description = 'unfocused, 6 to 8 keV'
    elif mode == 'F':
        description = 'unfocused, <6 keV'
    elif mode == 'XRD':
        description = 'focused at goniometer, >8 keV'
        print('Moving to mode %s (%s)' % (mode, description))
    if prompt:
        action = input("Begin moving motors? [Y/n then Enter] ")
        if action.lower() == 'q' or action.lower() == 'n':
            return (yield from null())

    RE.msg_hook = None
    BMM_log_info('Changing photon delivery system to mode %s' % mode)

    base = [
        dm3_bct,
        float(MODEDATA['dm3_bct'][mode]),
        xafs_table.yu,
        float(MODEDATA['xafs_yu'][mode]),
        xafs_table.ydo,
        float(MODEDATA['xafs_ydo'][mode]),
        xafs_table.ydi,
        float(MODEDATA['xafs_ydi'][mode]),
        m3.yu,
        float(MODEDATA['m3_yu'][mode]),
        m3.ydo,
        float(MODEDATA['m3_ydo'][mode]),
        m3.ydi,
        float(MODEDATA['m3_ydi'][mode]),
        m3.xu,
        float(MODEDATA['m3_xu'][mode]),
        m3.xd,
        float(MODEDATA['m3_xd'][mode]),
    ]
    if reference is not None:
        #base.extend([xafs_linxs, foils.position(reference.capitalize())])
        base.extend(
            [xafs_ref,
             xafs_ref.position_of_slot(reference.capitalize())])
    if edge is not None:
        #dcm_bragg.clear_encoder_loss()
        base.extend([dcm.energy, edge])
    # if mode in ('D', 'E', 'F'):
    #      base.extend([slits3.hcenter, 2])
    # else:
    #      base.extend([slits3.hcenter, 0])

    ###################################################################
    # check for amplifier faults on the motors, return without moving #
    # anything if any are found                                       #
    ###################################################################
    motors_ready = True
    problem_motors = list()
    for m in base[::2]:
        try:  # skip non-FMBO motors, which do not have the amfe or amfae attributes
            if m.amfe.get() == 1 or m.amfae.get() == 1:
                motors_ready = False
                problem_motors.append(m.name)
        except:
            continue
    if motors_ready is False:
        BMMuser.motor_fault = ', '.join(problem_motors)
        return (yield from null())

    ##########################
    # do the motor movements #
    ##########################
    yield from dcm.kill_plan()
    if dm3_bct.ampen.get() == 0:
        yield from mv(dm3_bct.enable_cmd, 1)
    #yield from mv(dm3_bct.kill_cmd, 1) # need to explicitly kill this before
    # starting a move, it is one of the
    # motors that reports MOVN=1 even when
    # still
    yield from sleep(0.2)
    yield from mv(dm3_bct.kill_cmd, 1)

    if mode in ('D', 'E', 'F') and current_mode in ('D', 'E', 'F'):
        yield from mv(*base)
    elif mode in ('A', 'B',
                  'C') and current_mode in ('A', 'B',
                                            'C'):  # no need to move M2
        yield from mv(*base)
    else:
        if bender is True:
            yield from mv(m2_bender.kill_cmd, 1)
            if mode == 'XRD':
                if abs(
                        m2_bender.user_readback.get() - BMMuser.bender_xrd
                ) > BMMuser.bender_margin:  # give some wiggle room for having
                    base.extend([m2_bender, BMMuser.bender_xrd
                                 ])  # recently adjusted the bend
            elif mode in ('A', 'B', 'C'):
                if abs(m2_bender.user_readback.get() -
                       BMMuser.bender_xas) > BMMuser.bender_margin:
                    base.extend([m2_bender, BMMuser.bender_xas])

        base.extend([m2.yu, float(MODEDATA['m2_yu'][mode])])
        base.extend([m2.ydo, float(MODEDATA['m2_ydo'][mode])])
        base.extend([m2.ydi, float(MODEDATA['m2_ydi'][mode])])
        yield from mv(*base)

    yield from sleep(2.0)
    yield from mv(m2_bender.kill_cmd, 1)
    yield from mv(dm3_bct.kill_cmd, 1)
    yield from m2.kill_jacks()
    yield from m3.kill_jacks()

    BMMuser.pds_mode = mode
    RE.msg_hook = BMM_msg_hook
    BMM_log_info(motor_status())
Beispiel #10
0
def change_xtals(xtal=None):
    '''Move between the Si(111) and Si(311) monochromators, also moving
     2nd crystal pitch and roll to approximate positions.  Then do a
     rocking curve scan.
     '''
    if xtal is None:
        print('No crystal set specified')
        return (yield from null())

    (ok, text) = BMM_clear_to_start()
    if ok == 0:
        print(error_msg(text))
        yield from null()
        return

    BMMuser, RE, dcm, dm3_bct = user_ns['BMMuser'], user_ns['RE'], user_ns[
        'dcm'], user_ns['dm3_bct']
    dcm_pitch, dcm_roll, dcm_x = user_ns['dcm_pitch'], user_ns[
        'dcm_roll'], user_ns['dcm_x']

    if '111' in xtal:
        xtal = 'Si(111)'
    if '311' in xtal:
        xtal = 'Si(311)'

    if xtal not in ('Si(111)', 'Si(311)'):
        print('%s is not a crytsal set' % xtal)
        return (yield from null())

    ######################################################################
    # this is a tool for verifying a macro.  this replaces an xafs scan  #
    # with a sleep, allowing the user to easily map out motor motions in #
    # a macro                                                            #
    if BMMuser.macro_dryrun:
        print(
            info_msg(
                '\nBMMuser.macro_dryrun is True.  Sleeping for %.1f seconds rather than changing to the %s crystal.\n'
                % (BMMuser.macro_sleep, xtal)))
        countdown(BMMuser.macro_sleep)
        return (yield from null())
    ######################################################################

    print('Moving to %s crystals' % xtal)
    action = input('Begin moving motors? [Y/n then Enter] ')
    if action.lower() == 'q' or action.lower() == 'n':
        yield from null()
        return

    current_energy = dcm.energy.readback.get()
    start = time.time()

    RE.msg_hook = None
    BMM_log_info('Moving to the %s crystals' % xtal)
    yield from mv(dcm_pitch.kill_cmd, 1)
    yield from mv(dcm_roll.kill_cmd, 1)
    if xtal is 'Si(111)':
        yield from mv(dcm_pitch, 4.1, dcm_roll, -6.26, dcm_x, 0.5)
        #dcm._crystal = '111'
        dcm.set_crystal('111')  # set d-spacing and bragg offset
    elif xtal is 'Si(311)':
        yield from mv(dcm_pitch, 2.28, dcm_roll, -23.86, dcm_x, 65.3)
        #dcm._crystal = '311'
        dcm.set_crystal('311')  # set d-spacing and bragg offset

    yield from sleep(2.0)
    yield from mv(dcm_roll.kill_cmd, 1)

    print('Returning to %.1f eV' % current_energy)
    yield from mv(dcm.energy, current_energy)

    print('Performing a rocking curve scan')
    yield from mv(dcm_pitch.kill_cmd, 1)
    yield from mv(dcm_pitch, approximate_pitch(current_energy))
    yield from sleep(1)
    yield from mv(dcm_pitch.kill_cmd, 1)
    yield from rocking_curve()
    yield from sleep(2.0)
    yield from mv(dcm_pitch.kill_cmd, 1)
    RE.msg_hook = BMM_msg_hook
    BMM_log_info(motor_status())
    close_last_plot()
    end = time.time()
    print('\n\nTime elapsed: %.1f min' % ((end - start) / 60))
def timescan(detector, readings, dwell, delay, force=False, md={}):
    '''
    Generic timescan plan.

    Parameters
    ----------
    detector : str
        detector to display -- if, it, ir, or i0
    readings : int
        number of measurements to make
    dwell : float
        dwell time in seconds for each measurement
    delay : float
        pause in seconds between measurements
    outfile :  str
        data file name (relative to DATA), False to not write
    force : bool
        flag for forcing a scan even if not clear to start

    This does not write an ASCII data file, but it does make a log entry.

    Use the ts2dat() function to extract the linescan from the
    database and write it to a file.

    Examples
       
    >>> RE(timescan('it', 100, 0.5))

    '''

    RE, BMMuser, quadem1, _locked_dwell_time = user_ns['RE'], user_ns[
        'BMMuser'], user_ns['quadem1'], user_ns['_locked_dwell_time']
    rkvs = user_ns['rkvs']
    ######################################################################
    # this is a tool for verifying a macro.  this replaces an xafs scan  #
    # with a sleep, allowing the user to easily map out motor motions in #
    # a macro                                                            #
    if BMMuser.macro_dryrun:
        print(
            info_msg(
                '\nBMMuser.macro_dryrun is True.  Sleeping for %.1f seconds rather than running a time scan.\n'
                % BMMuser.macro_sleep))
        countdown(BMMuser.macro_sleep)
        return (yield from null())
    ######################################################################

    (ok, text) = BMM_clear_to_start()
    if force is False and ok is False:
        print(error_msg(text))
        yield from null()
        return

    RE.msg_hook = None
    ## sanitize and sanity checks on detector
    detector = detector.capitalize()
    if detector not in ('It', 'If', 'I0', 'Iy',
                        'Ir') and 'Dtc' not in detector:
        print(
            error_msg('\n*** %s is not a timescan measurement (%s)\n' %
                      (detector, 'it, if, i0, iy, ir')))
        yield from null()
        return

    yield from mv(_locked_dwell_time, dwell)
    dets = [
        quadem1,
    ]
    denominator = ''

    epoch_offset = pandas.Timestamp.now(tz='UTC').value / 10**9
    ## func is an anonymous function, built on the fly, for feeding to DerivedPlot
    if detector == 'It':
        denominator = ' / I0'
        func = lambda doc: (doc['time'] - epoch_offset, doc['data']['It'] /
                            doc['data']['I0'])
    elif detector == 'Ir':
        denominator = ' / It'
        func = lambda doc: (doc['time'] - epoch_offset, doc['data']['Ir'] /
                            doc['data']['It'])
    elif detector == 'I0':
        func = lambda doc: (doc['time'] - epoch_offset, doc['data']['I0'])
    elif detector == 'Iy':
        denominator = ' / I0'
        func = lambda doc: (doc['time'] - epoch_offset, doc['data']['Iy'] /
                            doc['data']['I0'])
    elif detector == 'Dtc':
        dets.append(vor)
        denominator = ' / I0'
        func = lambda doc: (doc['time'] - epoch_offset, doc['data'][
            BMMuser.dtc2] / doc['data']['I0'])
        func3 = lambda doc: (doc['time'] - epoch_offset, doc['data'][
            BMMuser.dtc3] / doc['data']['I0'])
    elif detector == 'If':
        dets.append(vor)
        denominator = ' / I0'
        func = lambda doc: (doc['time'] - epoch_offset, (doc['data'][
            BMMuser.dtc1] + doc['data'][BMMuser.dtc2] + doc['data'][
                BMMuser.dtc3] + doc['data'][BMMuser.dtc4]) / doc['data']['I0'])

    ## and this is the appropriate way to plot this linescan
    if detector == 'Dtc':
        plot = [
            DerivedPlot(func,
                        xlabel='elapsed time (seconds)',
                        ylabel='dtc2',
                        title='time scan'),
            DerivedPlot(func3,
                        xlabel='elapsed time (seconds)',
                        ylabel='dtc3',
                        title='time scan')
        ]
    else:
        plot = DerivedPlot(func,
                           xlabel='elapsed time (seconds)',
                           ylabel=detector + denominator,
                           title='time scan')

    line1 = '%s, N=%s, dwell=%.3f, delay=%.3f\n' % (detector, readings, dwell,
                                                    delay)

    thismd = dict()
    thismd['XDI'] = dict()
    thismd['XDI']['Facility'] = dict()
    thismd['XDI']['Facility']['GUP'] = BMMuser.gup
    thismd['XDI']['Facility']['SAF'] = BMMuser.saf
    thismd['XDI']['Beamline'] = dict()
    thismd['XDI']['Beamline']['energy'] = dcm.energy.readback.get()
    thismd['XDI']['Scan'] = dict()
    thismd['XDI']['Scan']['dwell_time'] = dwell
    thismd['XDI']['Scan']['delay'] = delay

    @subs_decorator(plot)
    #@subs_decorator(src.callback)
    def count_scan(dets, readings, delay):
        #if 'purpose' not in md:
        #    md['purpose'] = 'measurement'
        uid = yield from count(dets,
                               num=readings,
                               delay=delay,
                               md={
                                   **thismd,
                                   **md, 'plan_name':
                                   f'count measurement {detector}'
                               })
        return uid

    rkvs.set('BMM:scan:type', 'time')
    rkvs.set('BMM:scan:starttime',
             str(datetime.datetime.timestamp(datetime.datetime.now())))
    rkvs.set('BMM:scan:estimated', 0)

    uid = yield from count_scan(dets, readings, delay, md)

    BMM_log_info('timescan: %s\tuid = %s, scan_id = %d' %
                 (line1, uid, db[-1].start['scan_id']))

    yield from mv(_locked_dwell_time, 0.5)
    RE.msg_hook = BMM_msg_hook
    return (uid)
def areascan(detector,
             slow,
             startslow,
             stopslow,
             nslow,
             fast,
             startfast,
             stopfast,
             nfast,
             pluck=True,
             force=False,
             dwell=0.1,
             md={}):
    '''
    Generic areascan plan.  This is a RELATIVE scan, relative to the
    current positions of the selected motors.

    For example:
       RE(areascan('it', 'x', -1, 1, 21, 'y', -0.5, 0.5, 11))

       detector: detector to display -- if, it, ir, or i0
       slow:     slow axis motor or nickname
       sl1:      starting value for slow axis of a relative scan
       sl2:      ending value for slow axis of a relative scan
       nsl:      number of steps in slow axis
       fast:     fast axis motor or nickname
       fa1:      starting value for fast axis of a relative scan
       fa2:      ending value for fast axis of a relative scan
       nfa:      number of steps in fast axis
       pluck:    optional flag for whether to offer to pluck & move motor
       force:    optional flag for forcing a scan even if not clear to start
       dwell:    dwell time at each point (0.1 sec default)
       md:       composable dictionary of metadata

    slow and fast are either the BlueSky name for a motor (e.g. xafs_linx)
    or a nickname for an XAFS sample motor (e.g. 'x' for xafs_linx).

    This does not write an ASCII data file, but it does make a log entry.

    Use the as2dat() function to extract the areascan from the
    database and write it to a file.
    '''
    def main_plan(detector, slow, startslow, stopslow, nslow, fast, startfast,
                  stopfast, nfast, pluck, force, dwell, md):
        (ok, text) = BMM_clear_to_start()
        if force is False and ok is False:
            print(error_msg(text))
            BMMuser.final_log_entry = False
            yield from null()
            return

        user_ns['RE'].msg_hook = None

        ## sanity checks on slow axis
        if type(slow) is str: slow = slow.lower()
        if slow not in motor_nicknames.keys() and 'EpicsMotor' not in str(
                type(slow)) and 'PseudoSingle' not in str(type(slow)):
            print(
                error_msg('\n*** %s is not an areascan motor (%s)\n' %
                          (slow, str.join(', ', motor_nicknames.keys()))))
            BMMuser.final_log_entry = False
            yield from null()
            return
        if slow in motor_nicknames.keys():
            slow = motor_nicknames[slow]

        ## sanity checks on fast axis
        if type(fast) is str: fast = fast.lower()
        if fast not in motor_nicknames.keys() and 'EpicsMotor' not in str(
                type(fast)) and 'PseudoSingle' not in str(type(fast)):
            print(
                error_msg('\n*** %s is not an areascan motor (%s)\n' %
                          (fast, str.join(', ', motor_nicknames.keys()))))
            BMMuser.final_log_entry = False
            yield from null()
            return
        if fast in motor_nicknames.keys():
            fast = motor_nicknames[fast]

        detector = detector.capitalize()
        yield from mv(_locked_dwell_time, dwell)
        dets = [
            quadem1,
        ]

        if with_xspress3 and detector == 'If':
            detector = 'Xs'

        if detector == 'If':
            dets.append(vor)
            detector = 'ROI1'
        if detector.lower() == 'xs':
            dets.append(xs)
            detector = BMMuser.xs1
            yield from mv(xs.total_points, nslow * nfast)

        if 'PseudoSingle' in str(type(slow)):
            valueslow = slow.readback.get()
        else:
            valueslow = slow.user_readback.get()
        line1 = 'slow motor: %s, %.3f, %.3f, %d -- starting at %.3f\n' % \
            (slow.name, startslow, stopslow, nslow, valueslow)

        if 'PseudoSingle' in str(type(fast)):
            valuefast = fast.readback.get()
        else:
            valuefast = fast.user_readback.get()
        line2 = 'fast motor: %s, %.3f, %.3f, %d -- starting at %.3f\n' % \
            (fast.name, startfast, stopfast, nfast, valuefast)

        npoints = nfast * nslow
        estimate = int(npoints * (dwell + 0.7))

        # extent = (
        #     valuefast + startfast,
        #     valueslow + startslow,
        #     valuefast + stopfast,
        #     valueslow + stopslow,
        # )
        # extent = (
        #     0,
        #     nfast-1,
        #     0,
        #     nslow-1
        # )
        # print(extent)
        # return(yield from null())

        # areaplot = LiveScatter(fast.name, slow.name, detector,
        #                        xlim=(startfast, stopfast), ylim=(startslow, stopslow))

        close_all_plots()

        areaplot = LiveGrid(
            (nslow, nfast),
            detector,  #aspect='equal', #aspect=float(nslow/nfast), extent=extent,
            xlabel='fast motor: %s' % fast.name,
            ylabel='slow motor: %s' % slow.name)
        #BMMuser.ax     = areaplot.ax
        #BMMuser.fig    = areaplot.ax.figure
        BMMuser.motor = fast
        BMMuser.motor2 = slow
        #BMMuser.fig.canvas.mpl_connect('close_event', handle_close)

        thismd = dict()
        thismd['XDI'] = dict()
        thismd['XDI']['Facility'] = dict()
        thismd['XDI']['Facility']['GUP'] = BMMuser.gup
        thismd['XDI']['Facility']['SAF'] = BMMuser.saf
        thismd['slow_motor'] = slow.name
        thismd['fast_motor'] = fast.name

        report(
            f'Starting areascan at x,y = {fast.position:.3f}, {slow.position:.3f}',
            level='bold',
            slack=True)

        ## engage suspenders right before starting scan sequence
        if force is False: BMM_suspenders()

        @subs_decorator(areaplot)
        #@subs_decorator(src.callback)
        def make_areascan(dets,
                          slow,
                          startslow,
                          stopslow,
                          nslow,
                          fast,
                          startfast,
                          stopfast,
                          nfast,
                          snake=False):
            BMMuser.final_log_entry = False
            uid = yield from grid_scan(
                dets,
                slow,
                startslow,
                stopslow,
                nslow,
                fast,
                startfast,
                stopfast,
                nfast,
                snake,
                md={
                    'plan_name':
                    f'grid_scan measurement {slow.name} {fast.name} {detector}'
                })
            BMMuser.final_log_entry = True
            return uid

        rkvs.set('BMM:scan:type', 'area')
        rkvs.set('BMM:scan:starttime',
                 str(datetime.datetime.timestamp(datetime.datetime.now())))
        rkvs.set('BMM:scan:estimated', estimate)

        BMM_log_info('begin areascan observing: %s\n%s%s' %
                     (detector, line1, line2))
        uid = yield from make_areascan(dets,
                                       slow,
                                       valueslow + startslow,
                                       valueslow + stopslow,
                                       nslow,
                                       fast,
                                       valuefast + startfast,
                                       valuefast + stopfast,
                                       nfast,
                                       snake=False)

        if pluck is True:

            close_all_plots()
            thismap = user_ns['db'].v2[uid]
            x = numpy.array(thismap.primary.read()[fast.name])
            y = numpy.array(thismap.primary.read()[slow.name])
            z=numpy.array(thismap.primary.read()[BMMuser.xs1]) +\
                numpy.array(thismap.primary.read()[BMMuser.xs2]) +\
                numpy.array(thismap.primary.read()[BMMuser.xs3]) +\
                numpy.array(thismap.primary.read()[BMMuser.xs4])
            z = z.reshape(nfast, nslow)

            # grabbing the first nfast elements of x and every
            # nslow-th element of y is more reliable than
            # numpy.unique due to float &/or motor precision issues

            #plt.title(f'Energy = {energies["below"]}')
            plt.xlabel(f'fast axis ({fast.name}) position (mm)')
            plt.ylabel(f'slow axis ({slow.name}) position (mm)')
            plt.gca().invert_yaxis()  # plot an xafs_x/xafs_y plot upright
            plt.contourf(x[:nfast], y[::nslow], z, cmap=plt.cm.viridis)
            plt.colorbar()
            plt.show()
            fname = os.path.join(BMMuser.folder, 'map-' + now() + '.png')
            plt.savefig(fname)
            try:
                img_to_slack(fname)
            except:
                post_to_slack('failed to post image: {fname}')
                pass

            BMMuser.x = None
            figs = list(map(plt.figure, plt.get_fignums()))
            canvas = figs[0].canvas
            action = input('\n' + bold_msg(
                'Pluck motor position from the plot? [Y/n then Enter] '))
            if action.lower() == 'n' or action.lower() == 'q':
                return (yield from null())
            print(
                'Single click the left mouse button on the plot to pluck a point...'
            )
            cid = canvas.mpl_connect(
                'button_press_event',
                interpret_click)  # see 65-derivedplot.py and
            while BMMuser.x is None:  #  https://matplotlib.org/users/event_handling.html
                yield from sleep(0.5)

            # print('Converting plot coordinates to real coordinates...')
            # begin = valuefast + startfast
            # stepsize = (stopfast - startfast) / (nfast - 1)
            # pointfast = begin + stepsize * BMMuser.x
            # #print(BMMuser.x, pointfast)

            # begin = valueslow + startslow
            # stepsize = (stopslow - startslow) / (nslow - 1)
            # pointslow = begin + stepsize * BMMuser.y
            # #print(BMMuser.y, pointslow)

            # print('That translates to x=%.3f, y=%.3f' % (pointfast, pointslow))
            yield from mv(fast, BMMuser.x, slow, BMMuser.y)
            report(
                f'Moved to position x,y = {fast.position:.3f}, {slow.position:.3f}',
                level='bold',
                slack=True)

    def cleanup_plan():
        print('Cleaning up after an area scan')
        db = user_ns['db']
        BMM_clear_suspenders()
        #user_ns['RE'].clear_suspenders()
        if BMMuser.final_log_entry is True:
            BMM_log_info('areascan finished\n\tuid = %s, scan_id = %d' %
                         (db[-1].start['uid'], db[-1].start['scan_id']))
        yield from resting_state_plan()
        user_ns['RE'].msg_hook = BMM_msg_hook

        print('Disabling plot for re-plucking.')
        try:
            cid = BMMuser.fig.canvas.mpl_disconnect(cid)
        except:
            pass
        BMMuser.x = None
        BMMuser.y = None
        BMMuser.motor = None
        BMMuser.motor2 = None
        BMMuser.fig = None
        BMMuser.ax = None

    ######################################################################
    # this is a tool for verifying a macro.  this replaces an xafs scan  #
    # with a sleep, allowing the user to easily map out motor motions in #
    # a macro                                                            #
    if BMMuser.macro_dryrun:
        print(
            info_msg(
                '\nBMMuser.macro_dryrun is True.  Sleeping for %.1f seconds rather than running an area scan.\n'
                % BMMuser.macro_sleep))
        countdown(BMMuser.macro_sleep)
        return (yield from null())
    ######################################################################
    BMMuser.final_log_entry = True
    user_ns['RE'].msg_hook = None
    ## encapsulation!
    yield from finalize_wrapper(
        main_plan(detector, slow, startslow, stopslow, nslow, fast, startfast,
                  stopfast, nfast, pluck, force, dwell, md), cleanup_plan())
    user_ns['RE'].msg_hook = BMM_msg_hook