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)
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)
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
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()
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())
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