def warmup(self): """ A convenience method for 'priming' the plugin. The plugin has to 'see' one acquisition before it is ready to capture. This sets the array size, etc. NOTE : this comes from: https://github.com/NSLS-II/ophyd/blob/master/ophyd/areadetector/plugins.py We had to replace "cam" with "settings" here. Also modified the stage sigs. """ print(whisper(" warming up the hdf5 plugin...")) set_and_wait(self.enable, 1) sigs = OrderedDict([(self.parent.settings.array_callbacks, 1), (self.parent.settings.trigger_mode, 'Internal'), # just in case the acquisition time is set very long... (self.parent.settings.acquire_time, 1), # (self.capture, 1), (self.parent.settings.acquire, 1)]) original_vals = {sig: sig.get() for sig in sigs} # Remove the hdf5.capture item here to avoid an error as it should reset back to 0 itself # del original_vals[self.capture] for sig, val in sigs.items(): ttime.sleep(0.1) # abundance of caution set_and_wait(sig, val) ttime.sleep(2) # wait for acquisition for sig, val in reversed(list(original_vals.items())): ttime.sleep(0.1) set_and_wait(sig, val) print(whisper(" done"))
def cycle(self, mc=None): '''Cycle power to the amplifiers on a motor controller, then reenable the motors on that controller. Identify the motor controller by these strings: 'dcm', 'slits2', 'm2', 'm3', 'dm3' ''' if self.check(mc) is False: return print(bold_msg(f'Cycling amplifiers on {mc} motor controller')) print(whisper('killing amplifiers')) self.kill(mc) countdown(5) print(whisper('reactivating amplifiers')) self.enable(mc) print(whisper('enabling motors')) if mc == 'm2': user_ns['m2'].ena() elif mc == 'm3': user_ns['m3'].ena() elif mc == 'slits2': user_ns['slits2'].enable() elif mc == 'dm3': user_ns['slits3'].enable() for axis in ('dm3_bct', 'dm3_bpm', 'dm3_foils', 'dm3_fs'): user_ns[axis].enable() time.sleep(0.5) user_ns[axis].kill() elif mc == 'dcm': user_ns['dcm'].ena() time.sleep(0.5) user_ns['dcm'].kill()
def make_merged_triplot(self, uidlist, filename, mode): '''Make a pretty, three panel plot of the data from the scan sequence just finished. ''' BMMuser = user_ns['BMMuser'] cnt = 0 try: base = Pandrosus() projname = os.path.join(BMMuser.folder, 'prj', os.path.basename(filename)).replace( '.png', '.prj') proj = create_athena(projname) base.fetch(uidlist[0], mode=mode) ee = base.group.energy mm = base.group.mu save = base.group.args['label'] proj.add_group(base.group) base.group.args['label'] = save cnt = 1 if len(uidlist) > 1: for uid in uidlist[1:]: this = Pandrosus() try: this.fetch(uid, mode=mode) mu = numpy.interp(ee, this.group.energy, this.group.mu) mm = mm + mu save = this.group.args['label'] proj.add_group(this.group) this.group.args['label'] = save cnt += 1 except: pass # presumably this is noisy data for which a valid background was not found except: pass # presumably this is noisy data for which a valid background was not found if cnt == 0: print(whisper(f'Unable to make triplot')) try: self.simple_plot(uidlist, filename, mode) except: print(whisper(f'Also unable to make simple plot')) return mm = mm / cnt merge = Pandrosus() merge.put(ee, mm, 'merge') thisagg = matplotlib.get_backend() matplotlib.use('Agg') # produce a plot without screen display merge.triplot() plt.savefig(filename) print(whisper(f'Wrote triplot to {filename}')) matplotlib.use(thisagg) # return to screen display proj.save() print(whisper(f'Wrote Athena project to {projname}'))
def do_ChangeXtals(self): if user_ns['dcm']._crystal == '111': print( go_msg('You would like to change from the ') + whisper('Si(111)') + go_msg(' to the ') + bold_msg('Si(311)') + go_msg(' crystals...\n')) print(disconnected_msg('yield from change_xtals("311")')) else: print( go_msg('You would like to change from the ') + whisper('Si(311)') + go_msg(' to the ') + bold_msg('Si(111)') + go_msg(' crystals...\n')) print(disconnected_msg('yield from change_xtals("111")')) yield from null()
def align_linear(self, force=False, drop=None): '''Fit an error function to the linear scan against It. Plot the result. Move to the centroid of the error function.''' if self.orientation == 'parallel': motor = user_ns['xafs_liny'] else: motor = user_ns['xafs_linx'] yield from linescan(motor, 'it', -2.3, 2.3, 51, pluck=False) close_last_plot() table = user_ns['db'][-1].table() yy = table[motor.name] signal = table['It']/table['I0'] if drop is not None: yy = yy[:-drop] signal = signal[:-drop] if float(signal[2]) > list(signal)[-2] : ss = -(signal - signal[2]) self.inverted = 'inverted ' else: ss = signal - signal[2] self.inverted = '' mod = StepModel(form='erf') pars = mod.guess(ss, x=numpy.array(yy)) out = mod.fit(ss, pars, x=numpy.array(yy)) print(whisper(out.fit_report(min_correl=0))) target = out.params['center'].value yield from mv(motor, target) self.y_plot(yy, out)
def all_connected(with_m2=False): motors = [ dm3_bct, xafs_yu, xafs_ydo, xafs_ydi, m3_yu, m3_ydo, m3_ydi, m3_xu, m3_xd, ] if with_m2 is True: motors.extend([m2_yu, m2_ydo, m2_ydi]) ok = True for m in motors: if m.connected is False: print(disconnected_msg(f'{m.name} is not connected')) for walk in m.walk_signals(include_lazy=False): if walk.item.connected is False: print( disconnected_msg( f' {walk.item.name} is a disconnected PV')) print( whisper( f'try: {m.name} = {m.__class__}("{m.prefix}", name={m.name})' )) ok = False return ok
def select(self, el): '''Choose the ROI configured for element el''' if type(el) is int: if el < 1 or el > 3: self.myprint(error_msg('\n%d is not a valid ROI channel\n' % el)) return el = self.slots[el-1] if el is None: self.myprint(error_msg('\nThat ROI is not configured\n')) return if Z_number(el) is None: self.myprint(error_msg('\n%s is not an element\n' % el)) return selected = False vor = user_ns['vor'] BMMuser = user_ns['BMMuser'] for i in range(3): if element_symbol(el) == self.slots[i]: BMMuser.roi_channel = i+1 if i == 0: # help out the best effort callback (BMMuser.roi1, BMMuser.roi2, BMMuser.roi3, BMMuser.roi4) = ('ROI1', 'ROI2', 'ROI3', 'ROI4') (BMMuser.dtc1, BMMuser.dtc2, BMMuser.dtc3, BMMuser.dtc4) = ('DTC1', 'DTC2', 'DTC3', 'DTC4') vor.set_hints(1) elif i == 1: (BMMuser.roi1, BMMuser.roi2, BMMuser.roi3, BMMuser.roi4) = ('ROI2_1', 'ROI2_2', 'ROI2_3', 'ROI2_4') (BMMuser.dtc1, BMMuser.dtc2, BMMuser.dtc3, BMMuser.dtc4) = ('DTC2_1', 'DTC2_2', 'DTC2_3', 'DTC2_4') vor.set_hints(2) elif i == 2: (BMMuser.roi1, BMMuser.roi2, BMMuser.roi3, BMMuser.roi4) = ('ROI3_1', 'ROI3_2', 'ROI3_3', 'ROI3_4') (BMMuser.dtc1, BMMuser.dtc2, BMMuser.dtc3, BMMuser.dtc4) = ('DTC3_1', 'DTC3_2', 'DTC3_3', 'DTC3_4') vor.set_hints(3) report('Set ROI channel to %s at channel %d' % (el.capitalize(), i+1)) selected = True if not selected: self.myprint(whisper('%s is not in a configured channel, not changing BMMuser.roi_channel' % el.capitalize()))
def doscan(): line1 = '%s, %s, %.3f, %.3f, %d -- starting at %.3f\n' % \ (motor.name, sgnl, start, stop, nsteps, motor.user_readback.get()) uid = yield from rel_scan(dets, motor, start, stop, nsteps, md={'plan_name' : f'rel_scan linescan {motor.name} I0'}) t = user_ns['db'][-1].table() if detector.lower() == 'if': signal = numpy.array((t[BMMuser.xs1]+t[BMMuser.xs2]+t[BMMuser.xs3]+t[BMMuser.xs4])/t['I0']) elif detector.lower() == 'it': signal = numpy.array(t['It']/t['I0']) elif detector.lower() == 'ir': signal = numpy.array(t['Ir']/t['It']) signal = signal - signal[0] if negate is True: signal = -1 * signal pos = numpy.array(t[motor.name]) mod = RectangleModel(form='erf') pars = mod.guess(signal, x=pos) out = mod.fit(signal, pars, x=pos) print(whisper(out.fit_report(min_correl=0))) out.plot() if filename is not None: plt.savefig(filename) #middle = out.params['center1'].value + (out.params['center2'].value - out.params['center1'].value)/2 yield from mv(motor, out.params['midpoint'].value) print(bold_msg(f'Found center at {motor.name} = {motor.position}')) rkvs.set('BMM:lmfit:center1', out.params['center1'].value) rkvs.set('BMM:lmfit:center2', out.params['center2'].value) rkvs.set('BMM:lmfit:sigma1', out.params['sigma1'].value) rkvs.set('BMM:lmfit:sigma2', out.params['sigma2'].value) rkvs.set('BMM:lmfit:amplitude', out.params['amplitude'].value) rkvs.set('BMM:lmfit:midpoint', out.params['midpoint'].value)
def align_y(self, force=False, drop=None): '''Fit an error function to the xafs_y scan against It. Plot the result. Move to the centroid of the error function.''' xafs_y = user_ns['xafs_y'] db = user_ns['db'] yield from linescan(xafs_y, 'it', -1, 1, 31, pluck=False) close_last_plot() table = db[-1].table() yy = table['xafs_y'] signal = table['It'] / table['I0'] if drop is not None: yy = yy[:-drop] signal = signal[:-drop] if float(signal[2]) > list(signal)[-2]: ss = -(signal - signal[2]) self.inverted = 'inverted ' else: ss = signal - signal[2] self.inverted = '' mod = StepModel(form='erf') pars = mod.guess(ss, x=numpy.array(yy)) out = mod.fit(ss, pars, x=numpy.array(yy)) print(whisper(out.fit_report(min_correl=0))) self.y_plot(yy, out) target = out.params['center'].value yield from mv(xafs_y, target)
def do_ChangeEdge(self): print(go_msg('You would like to change to a different edge...\n')) el = input(" What element? ") el = el.capitalize() if el == '': print(whisper('doing nothing')) return (yield from null()) if el not in ELEMENTS: print(error_msg(f'{el} is not an element')) return (yield from null()) if Z_number(el) < 46: default_ed = 'K' prompt = go_msg('K') + '/L3/L2/L1' else: default_ed = 'L3' prompt = 'K/' + go_msg('L3') + '/L2/L1' ed = input(f' What edge? [{prompt}] ') ed = ed.capitalize() if ed not in ('K', 'L3', 'L2', 'L1'): ed = default_ed focus = input(' Focused beam? [y/N] ') if focus.lower() == 'y': focus = True else: focus = False print( disconnected_msg( f'yield from change_edge("{el}", focus={focus}, edge="{ed}")')) yield from null()
def wafer_edge(motor='x'): '''Fit an error function to the linear scan against It. Plot the result. Move to the centroid of the error function.''' if motor == 'x': motor = user_ns['xafs_linx'] else: motor = user_ns['xafs_liny'] yield from linescan(motor, 'it', -2, 2, 41, pluck=False) close_last_plot() table = user_ns['db'][-1].table() yy = table[motor.name] signal = table['It'] / table['I0'] if float(signal[2]) > list(signal)[-2]: ss = -(signal - signal[2]) else: ss = signal - signal[2] mod = StepModel(form='erf') pars = mod.guess(ss, x=numpy.array(yy)) out = mod.fit(ss, pars, x=numpy.array(yy)) print(whisper(out.fit_report(min_correl=0))) out.plot() target = out.params['center'].value yield from mv(motor, target) yield from resting_state_plan() print( f'Edge found at X={user_ns["xafs_x"].position} and Y={user_ns["xafs_y"].position}' )
def calibrate_pitch(mono='111'): BMMuser = user_ns['BMMuser'] # read content from INI file datafile = os.path.join(BMMuser.DATA, 'edges%s.ini' % mono) print(f'reading {datafile}') config.read_file(open(datafile)) edges = dict() for i in config.items('edges'): el = i[0] vals = [float(j) for j in i[1].split(',') ] # convert CSV string -> list of strings -> list of floats edges[el] = vals # organize the data from the INI file ordered = [y[1] for y in sorted([(edges[x][1], x) for x in edges.keys()])] ee = list() tt = list() for el in ordered: ee.append(edges[el][1]) tt.append(edges[el][3]) mod = LinearModel() pars = mod.guess(tt, x=ee) out = mod.fit(tt, pars, x=ee) print(whisper(out.fit_report(min_correl=0))) out.plot()
def show_shutters(): ena_text = ' Beamline: ' try: if bl_enabled.get() == 1: ena_text += 'enabled ' else: ena_text += error_msg('disabled ') except: ena_text += whisper('unavailable ') bmps_text = ' BMPS: ' try: bmps_state = bool(bmps.state.get()) if bmps_state is True: bmps_text += 'open' else: bmps_text += error_msg('closed') except: bmps_text += whisper('unavailable ') idps_text = ' IDPS: ' try: idps_state = bool(idps.state.get()) if idps_state is True: idps_text += 'open' else: idps_text += error_msg('closed') except: idps_text += whisper('unavailable ') # sha_state = bool(sha.enabled.get()) and bool(sha.state.get()) # sha_text = ' FOE Shutter: ' # if sha_state is True: # sha_text += 'open' # else: # sha_text += error_msg('closed') shb_state = bool(shb.state.get()) shb_text = ' Photon Shutter: ' if shb_state is False: shb_text += 'open' else: shb_text += error_msg('closed') return (ena_text + bmps_text + idps_text + shb_text)
def write_ini_and_plan(self): ################################# # write out the master INI file # ################################# config = configparser.ConfigParser() default = self.measurements[0].copy() # things in the spreadsheet but not in the INI file for k in ('default', 'slot', 'measure', 'spin', 'focus', 'method', 'samplep', 'samplex', 'sampley', 'slitwidth', 'detectorx', 'settle', 'power', 'temperature', 'motor1', 'position1', 'motor2', 'position2'): default.pop(k, None) default['url'] = '...' default['doi'] = '...' default['cif'] = '...' default['experimenters'] = self.ws['E1'].value # top line of xlsx file default = self.ini_sanity(default) if default is None: print(error_msg(f'Could not interpret {self.source} as a wheel macro.')) return # print(default) config.read_dict({'scan': default}) with open(self.ini, 'w') as configfile: config.write(configfile) print(whisper('Wrote default INI file: %s' % self.ini)) ######################################################## # write the full macro to a file and %run -i that file # ######################################################## with open(self.tmpl) as f: text = f.readlines() fullmacro = ''.join(text).format(folder = self.folder, base = self.basename, content = self.content, description = self.description, instrument = self.instrument, cleanup = self.cleanup, initialize = self.initialize) o = open(self.macro, 'w') o.write(fullmacro) o.close() ## I think this will never be called by queueserver from IPython import get_ipython ipython = get_ipython() ipython.magic('run -i \'%s\'' % self.macro) print(whisper('Wrote and read macro file: %s' % self.macro))
def reset_rois(self, el=None, tab=''): BMMuser = user_ns['BMMuser'] if el is None: el = BMMuser.element if el in self.slots: print(whisper(f'{tab}Resetting rois with {el} as the active ROI')) BMMuser.element = el self.set_rois() self.measure_roi() else: print( error_msg( f'{tab}Cannot reset rois, {el} is not in {self.name}.slots' ))
def ocd(text, signal): '''Indicated open/close/disconnected state with suitable text coloring. ''' outtext = text try: if signal.connected is False: outtext += disconnected_msg('disconnected ') elif signal.get() == 1: outtext += 'enabled ' else: outtext += error_msg('disabled ') except: outtext += whisper('unavailable ') return outtext
def status(self): text = '\n %s is %s\n\n' % (self.name, self.prefix) for signal in list(self.configuration_attrs): if signal.upper() not in status_list.keys(): continue suffix = getattr(self, signal).pvname.replace(self.prefix, '') string = getattr(self, signal).enum_strs[getattr(self, signal).get()] if signal != 'asscs': if getattr(self, signal).get() != status_list[signal.upper()]: string = error_msg('%-19s' % string) text += ' %-26s : %-19s %s %s \n' % ( getattr(self, signal + '_desc').get(), string, bold_msg(getattr(self, signal).get()), whisper(suffix)) boxedtext('%s status signals' % self.name, text, 'green')
def _pressure(self): #print(self.pressure.get()) #print(type(self.pressure.get())) if self.connected is False: return(disconnected_msg('?????')) if self.pressure.get() == 'OFF': return(disconnected_msg(-1.1E-15)) if type(self.pressure.get()) is str and self.pressure.get() == 'LO<E-11': return whisper('1.00e-11') if float(self.pressure.get()) > 1e-6: return error_msg(self.pressure.get()) if float(self.pressure.get()) > 1e-8: return warning_msg(self.pressure.get()) return(self.pressure.get())
def cycle(self, mc=None): '''Cycle power to the amplifiers on a motor controller, then reenable the motors on that controller. Identify the motor controller by these strings: 'dcm', 'slits2', 'm2', 'm3', 'dm3' ''' if self.check(mc) is False: return print(bold_msg(f'Cycling amplifiers on {mc} motor controller')) print(whisper('killing amplifiers')) self.kill(mc) countdown(5) print(whisper('reactivating amplifiers')) self.enable(mc) print(whisper('enabling motors')) if mc == 'm2': m2.ena() elif mc == 'm3': m3.ena() elif mc == 'slits2': slits2.enable() elif mc == 'dm3': slits3.enable() for axis in (dm3_bct, dm3_bpm, dm3_foils, dm3_fs): try: axis.enable() time.sleep(0.5) axis.kill() except: pass elif mc == dcm: dcm.ena() time.sleep(0.5) dcm.kill()
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 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)
def status(self): text = '\n %s is %s\n\n' % (self.name, self.prefix) for signal in status_list.keys(): sig = signal.lower() try: suffix = getattr(self, sig).pvname.replace(self.prefix, '') string = getattr(self, sig).enum_strs[getattr(self, sig).get()] if signal != 'asscs': if getattr(self, sig).get() != status_list[signal]: string = verbosebold_msg('%-19s' % string) #text += ' %-26s : %-19s %s %s \n' % (getattr(self, sig+'_desc').get(), # string, # bold_msg(getattr(self, sig).get()), # whisper(suffix)) text += ' %-19s %s %s \n' % ( string, bold_msg(getattr(self, sig).get()), whisper(suffix)) except: pass boxedtext('%s status signals' % self.name, text, 'green')
def simple_plot(self, uidlist, filename, mode): '''If the triplot cannot be made for some reason, make a fallback, much simpler plot so that something is available for Slack and the dossier. ''' BMMuser = user_ns['BMMuser'] this = db.v2[uidlist[0]].primary.read() if mode == 'test': title, ylab = 'XAS test scan', 'I0 (nA)' signal = this['I0'] elif mode == 'transmission': title, ylab = '', 'transmission' signal = numpy.log(numpy.abs(this['I0'] / this['It'])) elif mode == 'reference': title, ylab = '', 'reference' signal = numpy.log(numpy.abs(this['It'] / this['Ir'])) elif mode == 'yield': title, ylab = '', 'yield' signal = this['Iy'] / this['I0'] elif mode == 'xs1': title, ylab = '', 'fluorescence' signal = (this[BMMuser.xs8] + 0.001) / (this['I0'] + 1) else: title, ylab = '', 'fluorescence' signal = (this[BMMuser.xs1] + this[BMMuser.xs2] + this[BMMuser.xs3] + this[BMMuser.xs4] + 0.001) / (this['I0'] + 1) thisagg = matplotlib.get_backend() matplotlib.use('Agg') # produce a plot without screen display plt.cla() plt.title(title) plt.xlabel('energy (eV)') plt.ylabel(ylab) plt.plot(this['dcm_energy'], signal) plt.savefig(filename) matplotlib.use(thisagg) # return to screen display print(whisper(f'Wrote simple plot to {filename}'))
def check_for_synaxis(): '''A disconnected motor (due to IOC or controller not running) will be defined as a SynAxis. This does a test for that situation and reports about it at startup. It also sets BMMuser.syns to True so things like motor_status() behave non-disastrously. ''' BMMuser.syns = False syns = [] for m in mcs8_motors: if 'SynAxis' in f'{m}': syns.append(m.name) if len(syns) > 0: BMMuser.syns = True text = 'The following are disconnected & defined as simulated motors:\n\n' text += '\n'.join( disconnected_msg(x) for x in textwrap.wrap(', '.join(syns))) + '\n\n' text += 'This allows bsui to operate normally, but do not expect anything\n' text += 'involving those motors to work correctly.\n' text += whisper( '(This likely means that an IOC or a motor controller (or both) are off.)' ) boxedtext('Disconnected motors', text, 'red', width=74)
def new_experiment(self, folder, gup=0, saf=0, name='Betty Cooper', use_pilatus=False, echem=False): ''' Do the work of prepping for a new experiment. This will: * Create a data folder and it's subfolders, if needed, and set the DATA variable * Set up the experimental log, creating an experiment.log file, if needed * Write templates for scan.ini and macro.py + xlsx templates, if needed * Make folders for XRF, HDF5, Pilatus, and electrochemistry * Set the GUP and SAF numbers as metadata Parameters ---------- folder : str data destination gup : str GUP number saf : str SAF number name : str name of PI use_pilatus : bool true if this experiment uses the Pilatus echem : bool true if this experiment uses the BioLogic potentiostat ''' step = 1 ## --*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*-- ## Main folders, point BMMuser and wmb objects at data folder data_folder = os.path.join(folder, self.date) user_ns['DATA'] = self.DATA = self.folder = data_folder + '/' try: hdf5folder = os.path.join('/nsls2', 'data', 'bmm', 'assets', 'xspress3', *self.date.split('-')) user_ns['xs'].hdf5.read_path_template = hdf5folder user_ns['xs'].hdf5.write_path_template = hdf5folder user_ns['xs'].hdf5.file_path.put(hdf5folder) except: # if we are starting up bsui, the xs folder will get set # later. the try block is needed when running # start_experiment from a running bsui instance. pass try: user_ns['wmb'].folder = data_folder + '/' except: pass imagefolder = os.path.join(data_folder, 'snapshots') prjfolder = os.path.join(data_folder, 'prj') htmlfolder = os.path.join(data_folder, 'dossier') self.establish_folder(step, 'Lustre folder', folder) self.establish_folder(0, 'data folder', data_folder) self.establish_folder(0, 'snapshot folder', imagefolder) self.establish_folder(0, 'Athena prj folder', prjfolder) if self.establish_folder(0, 'dossier folder', htmlfolder) == 'Created': # 'sample.tmpl', 'sample_xs.tmpl', 'sample_ga.tmpl' for f in ('manifest.tmpl', 'logo.png', 'style.css', 'trac.css'): shutil.copyfile(os.path.join(startup_dir, 'dossier', f), os.path.join(htmlfolder, f)) manifest = open(os.path.join(self.DATA, 'dossier', 'MANIFEST'), 'a') manifest.close() print(' copied html generation files, touched MANIFEST') step += 1 ## --*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*-- ## setup logger BMM_user_log(os.path.join(data_folder, 'experiment.log')) print('%2d. Set up experimental log file: %-75s' % (step, os.path.join(data_folder, 'experiment.log'))) step += 1 ## --*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*-- ## scan.ini template, macro template & wheel/ga spreadsheets initmpl = os.path.join(startup_dir, 'tmpl', 'scan.tmpl') scanini = os.path.join(data_folder, 'scan.ini') if not os.path.isfile(scanini): with open(initmpl) as f: content = f.readlines() o = open(scanini, 'w') o.write(''.join(content).format(folder=data_folder, name=name)) o.close() verb, pad = 'Copied', ' ' else: verb, pad = 'Found', ' ' self.print_verb_message(step, verb, 'INI file', pad, scanini) macrotmpl = os.path.join(startup_dir, 'tmpl', 'macro.tmpl') macropy = os.path.join(data_folder, 'sample_macro.py') commands = ''' ## sample 1 yield from slot(1) yield from xafs('scan.ini', filename='samp1', sample='first sample') close_last_plot() ## sample 2 yield from slot(2) ## yield from mvr(xafs_x, 0.5) yield from xafs('scan.ini', filename='samp2', sample='another sample', comment='my comment') close_last_plot() ## sample 3 yield from slot(3) yield from xafs('scan.ini', filename='samp3', sample='a different sample', prep='this sample prep', nscans=4) close_last_plot()''' if not os.path.isfile(macropy): with open(macrotmpl) as f: content = f.readlines() o = open(macropy, 'w') o.write(''.join(content).format(folder=data_folder, base='sample', content=commands, description='(example...)', instrument='', cleanup='', initialize='')) o.close() verb, pad = 'Copied', ' ' else: verb, pad = 'Found', ' ' self.print_verb_message(0, verb, 'macro template', pad, macropy) self.find_or_copy_file(0, 'wheel macro spreadsheet', 'wheel.xlsx') self.find_or_copy_file(0, 'glancing angle spreadsheet', 'glancing_angle.xlsx') self.find_or_copy_file(0, 'double wheel spreadsheet', 'doublewheel.xlsx') self.find_or_copy_file(0, 'Linkam stage spreadsheet', 'linkam.xlsx') self.find_or_copy_file(0, 'motor grid spreadsheet', 'grid.xlsx') step += 1 ## --*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*-- ## XRF & HDF5folders xrffolder = os.path.join(data_folder, 'XRF') self.establish_folder(step, 'XRF spectra folder', xrffolder) #hdf5folder = os.path.join(data_folder, 'raw', 'HDF5') #hdf5folder = os.path.join('/nsls2', 'data', 'bmm', 'assets', 'xspress3', *self.date.split('-')) #self.establish_folder(0, 'Xspress3 HDF5 folder', hdf5folder) #xs.hdf5.file_path.put(hdf5folder) ## --*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*-- ## Pilatus folder if use_pilatus: pilfolder = os.path.join(data_folder, 'raw', 'Pilatus') self.establish_folder(0, 'Pilatus folder', pilfolder) ## --*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*-- ## echem folder if echem: self.echem = True ecfolder = os.path.join(data_folder, 'raw', 'electrochemistry') self.establish_folder(0, 'electrochemistry folder', ecfolder) #self.echem_remote = os.path.join('/mnt/nfs/ws3', name, self.date) #if not os.path.isdir(self.echem_remote): # os.makedirs(self.echem_remote) # print(' Created remote echem folder: %-75s' % (self.echem_remote)) #else: # print(' Found remote echem folder: %-75s' % (self.echem_remote)) step += 1 ## --*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*-- ## GDrive folder user_folder = make_gdrive_folder(prefix=' ', update=False) for f in ('logo.png', 'style.css', 'trac.css'): shutil.copyfile(os.path.join(startup_dir, 'dossier', f), os.path.join(user_folder, 'dossier', f)) print('%2d. Established Google Drive folder: %-75s' % (step, user_folder)) print( whisper( f' Don\'t foget to share Google Drive folder and Slack channel with {name}' )) step += 1 ## --*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*-- ## SAF and GUP self.gup = gup self.saf = saf print( f'{step:2d}. Set GUP and SAF numbers as metadata GUP: {gup} SAF: {saf}' ) shipping_folder = os.path.join(os.getenv('HOME'), 'SDS', self.cycle, f'{saf}') self.establish_folder(0, 'shipping docs folder', shipping_folder) step += 1 self.user_is_defined = True return None
def _write_macro(self): '''Write a macro paragraph for each sample described in the spreadsheet. A paragraph consists of lines to change the temperature, lines to move sample and detector to the correct positions (if needed), a line to change the edge energy (if needed), a line to measure the XAFS using the correct set of control parameters, and a line to close plot windows after the scan. ''' element, edge, focus = (None, None, None) settle_time, ramp_time = 0,0 previous = 25 self.content += self.tab + 'yield from mv(linkam.setpoint, linkam.readback.get())\n\n' self.content += self.tab + 'yield from linkam.on_plan()\n' self.content += self.tab + 'yield from sleep(15)\n' for m in self.measurements: if m['default'] is True: element = m['element'] edge = m['edge'] temperature = m['temperature'] continue if m['temperature'] is None: continue if self.skip_row(m) is True: continue ####################################### # default element/edge(/focus) values # ####################################### for k in ('element', 'edge', 'focus'): if m[k] is None: m[k] = self.measurements[0][k] if m['settle'] == 0: m['settle'] = self.measurements[0]['settle'] ################################ # change temperature is needed # ################################ if m['temperature'] != temperature: self.content += self.tab + f"report('== Moving to temperature {m['temperature']:.1f}C', slack=True)\n" self.content += self.tab + f'linkam.settle_time = {m["settle"]:.1f}\n' self.content += self.tab + f'yield from mv(linkam, {m["temperature"]:.1f})\n' temperature = m['temperature'] settle_time += m["settle"] ramp_time += (temperature - previous) / user_ns['linkam'].RR.get() previous = temperature ############################ # sample and slit movement # ############################ if m['samplex'] is not None: self.content += self.tab + f'yield from mv(xafs_x, {m["samplex"]:.3f})\n' if m['sampley'] is not None: self.content += self.tab + f'yield from mv(xafs_y, {m["sampley"]:.3f})\n' if m['detectorx'] is not None: self.content += self.tab + f'yield from mv(xafs_det, {m["detectorx"]:.2f})\n' ########################## # change edge, if needed # ########################## focus = False if m['focus'] == 'focused': focus = True if self.do_first_change is True: self.content += self.tab + 'yield from change_edge(\'%s\', edge=\'%s\', focus=%r)\n' % (m['element'], m['edge'], focus) self.do_first_change = False self.totaltime += 4 elif m['element'] != element or m['edge'] != edge: # focus... element = m['element'] edge = m['edge'] self.content += self.tab + 'yield from change_edge(\'%s\', edge=\'%s\', focus=%r)\n' % (m['element'], m['edge'], focus) self.totaltime += 4 else: if self.verbose: self.content += self.tab + '## staying at %s %s\n' % (m['element'], m['edge']) pass ###################################### # measure XAFS, then close all plots # ###################################### command = self.tab + 'yield from xafs(\'%s.ini\'' % self.basename for k in m.keys(): ## skip cells with macro-building parameters that are not INI parameters if self.skip_keyword(k): continue ## skip element & edge if they are same as default elif k in ('element', 'edge'): if m[k] == self.measurements[0][k]: continue ## skip cells with only whitespace if type(m[k]) is str and len(m[k].strip()) == 0: m[k] = None ## if a cell has data, put it in the argument list for xafs() if m[k] is not None: if k == 'filename': fname = self.make_filename(m) command += f', filename=\'{fname}\'' elif type(m[k]) is int: command += ', %s=%d' % (k, m[k]) elif type(m[k]) is float: command += ', %s=%.3f' % (k, m[k]) else: command += ', %s=\'%s\'' % (k, m[k]) command += ')\n' self.content += command self.content += self.tab + 'close_last_plot()\n\n' #self.content += self.tab + 'yield from linkam.off_plan()\n\n' ######################################## # approximate time cost of this sample # ######################################## self.estimate_time(m, element, edge) print(whisper(f'XAS time: about {self.totaltime/60:.1f} hours')) print(whisper(f'ramp time: about {ramp_time:.1f} minutes')) print(whisper(f'settling time: about {settle_time/60:.1f} minutes')) self.totaltime = self.totaltime + (settle_time / 60) + ramp_time if self.close_shutters: self.content += self.tab + 'if not dryrun:\n' self.content += self.tab + ' BMMuser.running_macro = False\n' self.content += self.tab + ' BMM_clear_suspenders()\n' self.content += self.tab + ' yield from shb.close_plan()\n'
def bailout(): print(whisper('doing nothing')) yield from null()
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 recenter(self): here = self.user_readback.get() center = round(here / 15) * 15 yield from mv(self, center) print(whisper('recentered %s to %.1f' % (self.name, center)))
def do_nothing(self): print(whisper('doing nothing')) yield from null()