def reset(self): '''Return glancing angle stage to spinner 1''' yield from self.alloff_plan() yield from mv(self.garot, self.home) report( 'Returned to spinner 1 at %d degrees and turned off all spinners' % self.home, level='bold')
def close_plan(self): user_ns['RE'].msg_hook = None count = 0 while self.state.get() != self.closeval: count += 1 print(u'\u231b', end=' ', flush=True) yield from mv(self.cls, 1) if count >= self.maxcount: print('tried %d times and failed to close %s %s' % (count, self.name, ':(')) return(yield from null()) time.sleep(1.5) report('Closed {}'.format(self.name)) user_ns['RE'].msg_hook = BMM_msg_hook
def open_plan(self): user_ns['RE'].msg_hook = None count = 0 while self.state.get() != self.openval: count += 1 print(u'\u231b', end=' ', flush=True) yield from mv(self.opn, 1) if count >= self.maxcount: print('tried %d times and failed to open %s %s' % (count, self.name, ':(')) # u'\u2639' unicode frown return(yield from null()) time.sleep(1.5) report('Opened {}'.format(self.name)) user_ns['RE'].msg_hook = BMM_msg_hook
def xrd_webcam(filename=None, **kwargs): XRDURL = 'http://xf06bm-cam6/axis-cgi/jpg/image.cgi' CAM_PROXIES = { "http": None, "https": None, } if filename is None: filename = os.environ['HOME'] + '/XRD_camera_' + now() + '.jpg' r = requests.get(XRDURL, proxies=CAM_PROXIES) Image.open(BytesIO(r.content)).save(filename, 'JPEG') if 'annotation' in kwargs: annotate_image(filename, kwargs['annotation']) report('XRD webcam image written to %s' % filename)
def set_slot(self, n): '''Move to a numbered slot on a sample wheel.''' if type(n) is not int: print( error_msg( 'Slots numbers must be integers between 1 and 24 (argument was %s)' % type(n))) return (yield from null()) if n < 1 or n > 24: print(error_msg('Slots are numbered from 1 to 24')) return (yield from null()) angle = self.angle_from_current(n) yield from mvr(self, angle) report('Arrived at sample wheel slot %d' % n, level='bold')
def close(self): user_ns['RE'].msg_hook = None if self.state.get() != self.closeval: count = 0 while self.state.get() != self.closeval: count += 1 print(u'\u231b', end=' ', flush=True) self.cls.put(1) if count >= self.maxcount: print('tried %d times and failed to close %s %s' % (count, self.name, ':(')) return time.sleep(1.5) report(' Closed {}'.format(self.name)) else: print('{} is closed'.format(self.name)) user_ns['RE'].msg_hook = BMM_msg_hook
def select_plan(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 (yield from null()) el = self.slots[el - 1] if el is None: self.myprint(error_msg('\nThat ROI is not configured\n')) return (yield from null()) if Z_number(el) is None: self.myprint(error_msg('\n%s is not an element\n' % el)) return (yield from null()) 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())) yield from null()
def open(self): RE.msg_hook = None if self.state.get() != self.openval: count = 0 while self.state.get() != self.openval: count += 1 print(u'\u231b', end=' ', flush=True) self.opn.put(1) if count >= self.maxcount: print('tried %d times and failed to open %s %s' % (count, self.name, ':(')) return time.sleep(1.5) report(' Opened {}'.format(self.name)) else: print('{} is open'.format(self.name)) RE.msg_hook = BMM_msg_hook
def end_experiment(self, force=False): ''' Copy data from the experiment that just finished to the NAS, then unset the logger and the DATA variable at the end of an experiment. ''' #global DATA if self.echem and not os.path.ismount('/mnt/nfs/ws3'): print( error_msg(''' ************************************************************************** This is an electrochemistry experiment and /mnt/nfs/ws3 is not mounted Electrochemistry data is not being backed up! ************************************************************************** ''')) if not force: if not self.user_is_defined: print(error_msg('There is not a current experiment!')) return (None) ###################################################################### #copy the electrochemistry data, if this was that sort of experiment # ###################################################################### if self.echem and os.path.ismount('/mnt/nfs/ws3'): try: self.fetch_echem() except: print( error_msg( 'Unable to copy electrochemistry data from ws3')) if not os.path.ismount('/mnt/nfs/ws3'): print( error_msg( '\t/mnt/nfs/ws3 seems not to be mounted...')) ####################################################################################### # create folder and sub-folders on NAS server for this user & experimental start date # ####################################################################################### destination = os.path.join(user_ns['nas_mount_point'], 'xf06bm', 'user', self.name, self.date) if not os.path.isdir(destination): os.makedirs(destination) for d in ('dossier', 'prj', 'snapshots'): if not os.path.isdir(os.path.join(destination, d)): os.makedirs(os.path.join(destination, d)) try: copy_tree(self.DATA, destination) report('NAS data store: "%s"' % destination, 'bold') except Exception as E: print(E) print(error_msg('Unable to write data to NAS server')) ##################################################################### # remove the json serialization of the start_experiment() arguments # ##################################################################### if os.path.isfile(os.path.join(os.environ['HOME'], 'Data', '.BMMuser')): os.remove(os.path.join(os.environ['HOME'], 'Data', '.BMMuser')) jsonfile = os.path.join(os.environ['HOME'], 'Data', '.user.json') if os.path.isfile(jsonfile): os.chmod(jsonfile, 0o644) os.remove(jsonfile) ############################################################### # unset self attributes, DATA, and experiment specific logger # ############################################################### BMM_unset_user_log() DATA = os.path.join(os.environ['HOME'], 'Data', 'bucket') + '/' self.DATA = os.path.join(os.environ['HOME'], 'Data', 'bucket') + '/' self.folder = self.DATA user_ns["wmb"].folder = self.folder self.date = '' self.gup = 0 self.saf = 0 self.name = None self.staff = False self.user_is_defined = False self.echem = False self.echem_remote = None return None
def fetch_echem(self): dest = os.path.join(self.folder, 'electrochemistry') copy_tree(self.echem_remote, dest) report( 'Copied electrochemistry data from: "%s" to "%s"' % (self.echem_remote, dest), 'bold')
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 main_plan(): dcm = user_ns['dcm'] report(f'Calibrating the {dcm._crystal} monochrmoator', 'bold') yield from calibrate_low_end(mono=dcm._crystal, focus=focus) yield from calibrate_high_end(mono=dcm._crystal, focus=focus) yield from resting_state_plan()
def analog_camera(filename=None, sample=None, folder=os.environ['HOME'], device='/dev/video0', camera=0, skip=30, frames=5, brightness=30, x=320, y=240, linecolor='white', nocrosshair=True, quiet=False, reset=False, usbid='534d:0021', title='NIST BMM (NSLS-II 06BM)', timestamp='%Y-%m-%d %H:%M:%S'): """A function for interacting with fswebcam in a way that meets the needs of 06BM. Parameters ---------- folder : str location to drop jpg image [$HOME] device : str char device of camera [/dev/video0] should be set to something in /dev/v4l/by-id/ camera : int camera number[0] skip : int number of frames to skip waiting for camera to wake up [30] frames : int number of frames to accumulate in image [5] brightness : int brightness setting of camera as a percentage [30] x : int middle of image, in pixels, X-location of cross hair [320] y : int middle of image, in pixels, Y-location of cross hair [240] linecolor : str color of cross hair lines [white] nocrosshair : bool flag to suppress cross hair [True] quiet : bool flag to suppress screen messages [False] usbid : str vendor and product ID of camera of AV to USB device at 06BM [534d:0021] title : str title string for fswebcam banner [NIST BMM (NSLS-II 06BM)] filename : str output file name """ USBDEVFS_RESET = 21780 if reset is True: if not quiet: print("resetting video device") try: lsusb_out = Popen("lsusb | grep -i %s" % usbid, shell=True, bufsize=64, stdin=PIPE, stdout=PIPE, close_fds=True).stdout.read().strip().split() bus = lsusb_out[1].decode('UTF-8') device = lsusb_out[3][:-1].decode('UTF-8') print("/dev/bus/usb/%s/%s" % (bus, device)) f = open("/dev/bus/usb/%s/%s" % (bus, device), 'w', os.O_WRONLY) fcntl.ioctl(f, USBDEVFS_RESET, 0) sleep(1) except Exception as msg: print("failed to reset device:", msg) quiet = '' if quiet: quiet = '-q ' if filename is None: filename = folder + '/analog_camera_' + now() + '.jpg' if sample is not None and sample != '': title = title + ' - ' + sample if user_ns['BMMuser'].host == 'xf06bm-ws1': command = [ 'fswebcam', quiet, '-i', f'{camera}', '-d', device, '-r', f'{x}x{y}', '--title', title, '--timestamp', timestamp, '-S', f'{skip}', '-F', f'{frames}', '--set', f'brightness={brightness}%', filename ] else: command = [ 'ssh', 'xf06bm@xf06bm-ws1', f"fswebcam {quiet}-i {camera} -d {device} -r {x}x{y} --title '{title}' --timestamp '{timestamp}' -S {skip} -F {frames} --set brightness={brightness}% '{filename}'" ] run(command) #command = f"fswebcam {quiet}-i {camera} -d {device} -r {x}x{y} --title '{title}' --timestamp '{timestamp}' -S {skip} -F {frames} --set brightness={brightness}% '{filename}'" #system(command) #command = f"fswebcam {quiet}-i {camera} -d {device} -r {x}x{y} --title '{title}' --timestamp '{timestamp}' -S {skip} -F {frames} --set brightness={brightness}% '{filename}'" #system(f'ssh xf06bm@xf06bm-ws1 "{command}"') report('Analog camera image written to %s' % filename)
def on(self): report('Turning {} on'.format(self.name)) self.state.put(1)
def off(self): report('Turning {} off'.format(self.name)) self.state.put(0)
def reset(self): '''Return a sample wheel to slot 1''' yield from mv(self, self.slotone) report('Returned to sample wheel slot 1 at %d degrees' % self.slotone, level='bold')
def analog_camera(filename=None, sample=None, folder=os.environ['HOME'], device='/dev/video0', camera=0, skip=30, frames=5, brightness=20, x=320, y=240, linecolor='white', nocrosshair=True, quiet=False, reset=False, usbid='534d:0021', title='NIST BMM (NSLS-II 06BM)', timestamp='%Y-%m-%d %H:%M:%S'): """A function for interacting with fswebcam in a way that meets the needs of 06BM. Parameters: folder: location to drop jpg image [$HOME] device: char device of camera [/dev/video0] camera: camera number [0] skip: number of frames to skip waiting for camera to wake up [30] frames: number of frames to accumulate in image [5] brightness: brightness setting of camera as a percentage [20] x: X-location of cross hair [320] (middle of image) y: Y-location of cross hair [240] (middle of image) linecolor: color of cross hair lines [white] nocrosshair: flag to suppress cross hair [True] quiet: flag to suppress screen messages [False] usbid: vendor and product ID of camera [534d:0021] (AV to USB device at 06BM) title: title string for fswebcam banner [NIST BMM (NSLS-II 06BM)] filename: output file name [ISO 8601 timestamp in folder] """ USBDEVFS_RESET = 21780 if reset is True: if not quiet: print("resetting video device") try: lsusb_out = Popen("lsusb | grep -i %s" % usbid, shell=True, bufsize=64, stdin=PIPE, stdout=PIPE, close_fds=True).stdout.read().strip().split() bus = lsusb_out[1].decode('UTF-8') device = lsusb_out[3][:-1].decode('UTF-8') print("/dev/bus/usb/%s/%s" % (bus, device)) f = open("/dev/bus/usb/%s/%s" % (bus, device), 'w', os.O_WRONLY) fcntl.ioctl(f, USBDEVFS_RESET, 0) sleep(1) except Exception as msg: print("failed to reset device:", msg) quiet = '' if quiet: quiet = '-q ' if filename is None: filename = folder + '/analog_camera_' + now() + '.jpg' if sample is not None: title = title + ' - ' + sample command = "fswebcam %s-i %s -d %s -r 640x480 --title \"%s\" --timestamp \"%s\" -S %d -F %d --set brightness=%s%% \"%s\"" %\ (quiet, camera, device, title, timestamp, skip, frames, brightness, filename) system(command) report('Analog camera image written to %s' % filename)
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 off_plan(self): report('Turning {} off'.format(self.name)) yield from abs_set(self.state, 0, wait=True)
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()