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 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 write_dossier(self): BMMuser, dcm, ga = user_ns['BMMuser'], user_ns['dcm'], user_ns['ga'] if self.filename is None or self.start is None: print(error_msg('Filename and/or start number not given.')) return None firstfile = f'{self.filename}.{self.start:03d}' if not os.path.isfile(os.path.join(BMMuser.DATA, firstfile)): print( error_msg( f'Could not find {os.path.join(BMMuser.DATA, firstfile)}')) return None # figure out various filenames basename = self.filename htmlfilename = os.path.join(BMMuser.DATA, 'dossier/', self.filename + '-01.html') seqnumber = 1 if os.path.isfile(htmlfilename): seqnumber = 2 while os.path.isfile( os.path.join(BMMuser.DATA, 'dossier', "%s-%2.2d.html" % (self.filename, seqnumber))): seqnumber += 1 basename = "%s-%2.2d" % (self.filename, seqnumber) htmlfilename = os.path.join( BMMuser.DATA, 'dossier', "%s-%2.2d.html" % (self.filename, seqnumber)) ## generate triplot as a png image (or fail gracefully) prjfilename, pngfilename = None, None try: if self.uidlist is not None: pngfilename = os.path.join(BMMuser.DATA, 'snapshots', f"{basename}.png") #prjfilename = os.path.join(BMMuser.DATA, 'prj', f"{basename}.prj") self.make_merged_triplot(self.uidlist, pngfilename, self.mode) except Exception as e: print(error_msg('failure to make triplot')) print(e) # slurp in the INI file contents if self.initext is None: with open(os.path.join(BMMuser.DATA, self.inifile)) as f: self.initext = ''.join(f.readlines()) # gather some information about the photon delivery system pdstext = f'{get_mode()} ({describe_mode()})' mono = 'Si(%s)' % dcm._crystal if self.ththth: mono = 'Si(333)' try: # dossier header with open(os.path.join(startup_dir, 'tmpl', 'dossier_top.tmpl')) as f: content = f.readlines() thiscontent = ''.join(content).format( filename=self.filename, date=BMMuser.date, seqnumber=seqnumber, ) # left sidebar, entry for XRF file in the case of fluorescence data thismode = plotting_mode(self.mode) if thismode == 'xs' or thismode == 'xs1': with open( os.path.join(startup_dir, 'tmpl', 'dossier_xrf_file.tmpl')) as f: content = f.readlines() thiscontent += ''.join(content).format( basename=basename, xrffile=quote('../XRF/' + str(self.xrffile)), xrfuid=self.xrfuid, ) # middle part of dossier with open(os.path.join(startup_dir, 'tmpl', 'dossier_middle.tmpl')) as f: content = f.readlines() thiscontent += ''.join(content).format( basename=basename, scanlist=self.scanlist, motors=self.motors, sample=self.sample, prep=self.prep, comment=self.comment, temperature=self.temperature, websnap=quote('../snapshots/' + self.websnap), webuid=self.webuid, anasnap=quote('../snapshots/' + self.anasnap), anauid=self.anauid, usb1snap=quote('../snapshots/' + self.usb1snap), usb1uid=self.usb1uid, usb2snap=quote('../snapshots/' + self.usb2snap), usb2uid=self.usb2uid, ) # middle part, XRF and glancing angle alignment images if thismode == 'xs' or thismode == 'xs1': with open( os.path.join(startup_dir, 'tmpl', 'dossier_xrf_image.tmpl')) as f: content = f.readlines() thiscontent += ''.join(content).format( xrfsnap=quote('../XRF/' + str(self.xrfsnap)), pccenergy='%.1f' % self.pccenergy, ocrs=self.ocrs, rois=self.rois, symbol=self.element, ) if 'glancing' in BMMuser.instrument: with open( os.path.join(startup_dir, 'tmpl', 'dossier_ga.tmpl')) as f: content = f.readlines() thiscontent += ''.join(content).format( ga_align=ga.alignment_filename, ga_yuid=ga.y_uid, ga_puid=ga.pitch_uid, ga_fuid=ga.f_uid, ) # end of dossier with open(os.path.join(startup_dir, 'tmpl', 'dossier_bottom.tmpl')) as f: content = f.readlines() thiscontent += ''.join(content).format( e0='%.1f' % self.e0, edge=self.edge, element=self.element_text(), mode=self.mode, bounds=self.bounds, steps=self.steps, times=self.times, seqstart=self.seqstart, seqend=self.seqend, mono=mono, pdsmode=pdstext, pccenergy='%.1f' % self.pccenergy, experimenters=self.experimenters, gup=BMMuser.gup, saf=BMMuser.saf, url=self.url, doi=self.doi, cif=self.cif, initext=highlight(self.initext, IniLexer(), HtmlFormatter()), clargs=highlight(self.clargs, PythonLexer(), HtmlFormatter()), filename=self.filename, ) with open(htmlfilename, 'a') as o: o.write(thiscontent) print(f'wrote {htmlfilename}') except Exception as E: print(E) manifest = open(self.manifest_file, 'a') manifest.write(htmlfilename + '\n') manifest.close() self.write_manifest() if pngfilename is not None and os.path.isfile(pngfilename): try: img_to_slack(pngfilename) except: post_to_slack('failed to post image: {pngfilename}') pass return htmlfilename
def tell_slack_shb_opened(): print('triggering opened message') post_to_slack('B shutter opened') return (yield from null())