def test_live_grid(fresh_RE): RE = fresh_RE motor1._fake_sleep = 0 motor2._fake_sleep = 0 RE(outer_product_scan([det4], motor1, -3, 3, 6, motor2, -5, 5, 10, False), LiveGrid((6, 10), 'det4')) # Test the deprecated name. RE(outer_product_scan([det4], motor1, -3, 3, 6, motor2, -5, 5, 10, False), LiveRaster((6, 10), 'det4'))
def test_live_grid(RE, hw): hw.motor1.delay = 0 hw.motor2.delay = 0 RE(grid_scan([hw.det4], hw.motor1, -3, 3, 6, hw.motor2, -5, 5, 10, False), LiveGrid((6, 10), 'det4')) # Test the deprecated name. with pytest.warns(UserWarning): RE( grid_scan([hw.det4], hw.motor1, -3, 3, 6, hw.motor2, -5, 5, 10, False), LiveRaster((6, 10), 'det4'))
def setup_liveraster(*, motors, gs, shape, extent): """Setup a LiveTable by inspecting motors, shape, extent, and gs. """ if len(motors) != 2: return None ylab, xlab = [first_key_heuristic(m) for m in motors] raster = LiveGrid(shape, gs.MASTER_DET_FIELD, xlabel=xlab, ylabel=ylab, extent=extent) return raster
def nano_xrf(xstart, xstop, xstep, ystart, ystop, ystep, dwell, shutter=True, extra_dets=None, xmotor=nano_stage.sx, ymotor=nano_stage.sy, flag_snake=True): # calculate number of points xnum = np.int(np.abs(np.round((xstop - xstart) / xstep)) + 1) ynum = np.int(np.abs(np.round((ystop - ystart) / ystep)) + 1) # Setup detectors if extra_dets is None: extra_dets = [] dets = [sclr1, xs, xbpm2, xmotor, ymotor] + extra_dets # Record relevant metadata in the Start document, defined in 90-usersetup.py scan_md = {} get_stock_md(scan_md) # scan_md['scan_input'] = str([xstart, xstop, xstep, ystart, ystop, ystep, dwell]) # scan_md['scaninfo'] = {'type': 'XRF', # 'raster' : True} scan_md['scan']['type'] = 'XRF_STEP' scan_md['scan']['scan_input'] = [ xstart, xstop, xstep, ystart, ystop, ystep, dwell ] scan_md['scan']['detectors'] = [d.name for d in dets] scan_md['scan']['fast_axis'] = { 'motor_name': xmotor.name, 'units': xmotor.motor_egu.get() } scan_md['scan']['slow_axis'] = { 'motor_name': ymotor.name, 'units': ymotor.motor_egu.get() } scan_md['scan']['theta'] = { 'val': nano_stage.th.user_readback.get(), 'units': nano_stage.th.motor_egu.get() } scan_md['scan']['delta'] = {'val': 0, 'units': xmotor.motor_egu.get()} scan_md['scan']['snake'] = 1 if flag_snake else 0 scan_md['scan']['shape'] = (xnum, ynum) # Set counting time sclr1.preset_time.put(dwell) xs.external_trig.put(False) xs.settings.acquire_time.put(dwell) xs.total_points.put(xnum * ynum) if (merlin in dets): merlin.cam.acquire_time.put(dwell) merlin.cam.acquire_period.put(dwell + 0.005) merlin.hdf5.stage_sigs['num_capture'] = xnum * ynum scan_md['scan']['merlin'] = { 'merlin_exp_time': dwell, 'merlin_exp_period': dwell + 0.005 } # LiveGrid livecallbacks = [] livecallbacks.append(LiveTable([xmotor.name, ymotor.name])) roi_name = 'roi{:02}'.format(1) roi_key = getattr(xs.channel1.rois, roi_name).value.name livecallbacks.append( LiveGrid((ynum, xnum), roi_key, clim=None, cmap='viridis', xlabel='x [um]', ylabel='y [um]', extent=[xstart, xstop, ystart, ystop], x_positive='right', y_positive='down')) myplan = grid_scan(dets, ymotor, ystart, ystop, ynum, xmotor, xstart, xstop, xnum, flag_snake, md=scan_md) myplan = subs_wrapper(myplan, {'all': livecallbacks}) # Open shutter # if (shutter): # yield from mv(shut_b,'Open') yield from check_shutters(shutter, 'Open') # grid scan uid = yield from myplan # Close shutter # if (shutter): # yield from mv(shut_b,'Close') yield from check_shutters(shutter, 'Close') return uid
def hf2dxrf(*, xstart, xnumstep, xstepsize, ystart, ynumstep, ystepsize, acqtime, shutter=True, align=False, xmotor=hf_stage.x, ymotor=hf_stage.y, numrois=1, extra_dets=[], setenergy=None, u_detune=None, echange_waittime=10, samplename=None, snake=True): '''input: xstart, xnumstep, xstepsize : float ystart, ynumstep, ystepsize : float acqtime : float acqusition time to be set for both xspress3 and F460 numrois : integer number of ROIs set to display in the live raster scans. This is for display ONLY. The actualy number of ROIs saved depend on how many are enabled and set in the read_attr However noramlly one cares only the raw XRF spectra which are all saved and will be used for fitting. energy (float): set energy, use with caution, hdcm might become misaligned u_detune (float): amount of undulator to detune in the unit of keV ''' # Record relevant metadata in the Start document, defined in 90-usersetup.py scan_md = {} get_stock_md(scan_md) scan_md['sample'] = {'name': samplename} scan_md['scan_input'] = str( [xstart, xnumstep, xstepsize, ystart, ynumstep, ystepsize, acqtime]) scan_md['scaninfo'] = {'type': 'XRF', 'raster': True} # Setup detectors dets = [sclr1, xs] dets = dets + extra_dets dets_by_name = {d.name: d for d in dets} # Scaler if (acqtime < 0.001): acqtime = 0.001 sclr1.preset_time.put(acqtime) # XS3 xs.external_trig.put(False) xs.settings.acquire_time.put(acqtime) xs.total_points.put((xnumstep + 1) * (ynumstep + 1)) if ('merlin' in dets_by_name): dpc = dets_by_name['merlin'] # Setup Merlin dpc.cam.trigger_mode.put(0) dpc.cam.acquire_time.put(acqtime) dpc.cam.acquire_period.put(acqtime + 0.005) dpc.cam.num_images.put(1) dpc.hdf5.stage_sigs['num_capture'] = (xnumstep + 1) * (ynumstep + 1) dpc._mode = SRXMode.step dpc.total_points.put((xnumstep + 1) * (ynumstep + 1)) if ('xs2' in dets_by_name): xs2 = dets_by_name['xs2'] xs2.external_trig.put(False) xs2.settings.acquire_time.put(acqtime) xs2.total_points.put((xnumstep + 1) * (ynumstep + 1)) # Setup the live callbacks livecallbacks = [] # Setup scanbroker to update time remaining def time_per_point(name, doc, st=ttime.time()): if ('seq_num' in doc.keys()): scanrecord.scan0.tpp.put((doc['time'] - st) / doc['seq_num']) scanrecord.scan0.curpt.put(int(doc['seq_num'])) scanrecord.time_remaining.put( (doc['time'] - st) / doc['seq_num'] * ((xnumstep + 1) * (ynumstep + 1) - doc['seq_num']) / 3600) livecallbacks.append(time_per_point) # Setup LiveTable livetableitem = [xmotor.name, ymotor.name, i0.name] xstop = xstart + xnumstep * xstepsize ystop = ystart + ynumstep * ystepsize for roi_idx in range(numrois): roi_name = 'roi{:02}'.format(roi_idx + 1) roi_key = getattr(xs.channel1.rois, roi_name).value.name livetableitem.append(roi_key) roimap = LiveGrid((ynumstep + 1, xnumstep + 1), roi_key, clim=None, cmap='viridis', xlabel='x (mm)', ylabel='y (mm)', extent=[xstart, xstop, ystart, ystop], x_positive='right', y_positive='down') livecallbacks.append(roimap) if ('xs2' in dets_by_name): for roi_idx in range(numrois): roi_key = getattr(xs2.channel1.rois, roi_name).value.name livetableitem.append(roi_key) fig = plt.figure('xs2_ROI{:02}'.format(roi_idx + 1)) fig.clf() roimap = LiveGrid((ynumstep + 1, xnumstep + 1), roi_key, clim=None, cmap='viridis', xlabel='x (mm)', ylabel='y (mm)', extent=[xstart, xstop, ystart, ystop], x_positive='right', y_positive='down', ax=fig.gca()) livecallbacks.append(roimap) if ('merlin' in dets_by_name) and (hasattr(dpc, 'stats1')): fig = plt.figure('DPC') fig.clf() dpc_tmap = LiveGrid((ynumstep + 1, xnumstep + 1), dpc.stats1.total.name, clim=None, cmap='viridis', xlabel='x (mm)', ylabel='y (mm)', x_positive='right', y_positive='down', extent=[xstart, xstop, ystart, ystop], ax=fig.gca()) livecallbacks.append(dpc_tmap) # Change energy (if provided) if (setenergy is not None): if (u_detune is not None): energy.detune.put(u_detune) print('Changing energy to ', setenergy) yield from mv(energy, setenergy) print('Waiting time (s) ', echange_waittime) yield from bps.sleep(echange_waittime) def at_scan(name, doc): scanrecord.current_scan.put(doc['uid'][:6]) scanrecord.current_scan_id.put(str(doc['scan_id'])) scanrecord.current_type.put(scan_md['scaninfo']['type']) scanrecord.scanning.put(True) def finalize_scan(name, doc): scanrecord.scanning.put(False) # Setup the scan hf2dxrf_scanplan = outer_product_scan(dets, ymotor, ystart, ystop, ynumstep + 1, xmotor, xstart, xstop, xnumstep + 1, snake, md=scan_md) hf2dxrf_scanplan = subs_wrapper(hf2dxrf_scanplan, { 'all': livecallbacks, 'start': at_scan, 'stop': finalize_scan }) # Move to starting position yield from mv(xmotor, xstart, ymotor, ystart) # Peak up monochromator at this energy if (align): yield from peakup_fine(shutter=shutter) # Open shutter if (shutter): yield from mv(shut_b, 'Open') # Run the scan scaninfo = yield from hf2dxrf_scanplan #TO-DO: implement fast shutter control (close) if (shutter): yield from mv(shut_b, 'Close') # Write to scan log if ('merlin' in dets_by_name): logscan_event0info('2dxrf_withdpc') # Should this be here? merlin.hdf5.stage_sigs['num_capture'] = 0 else: logscan_detailed('2dxrf') return scaninfo
def nano_xrf(xstart, xstop, xstep, ystart, ystop, ystep, acqtime, shutter=True, extra_dets=None, xmotor=nano_stage.sx, ymotor=nano_stage.sy): # define motors # xmotor = nano_stage.x # ymotor = nano_stage.y # Record relevant metadata in the Start document, defined in 90-usersetup.py scan_md = {} get_stock_md(scan_md) scan_md['scan_input'] = str( [xstart, xstop, xstep, ystart, ystop, ystep, acqtime]) scan_md['scaninfo'] = {'type': 'XRF', 'raster': True} # calculate number of points xnum = np.int(np.abs(np.round((xstop - xstart) / xstep)) + 1) ynum = np.int(np.abs(np.round((ystop - ystart) / ystep)) + 1) # Setup detectors if extra_dets is None: extra_dets = [] dets = [sclr1, xs, xbpm2, xmotor, ymotor] + extra_dets # Set counting time sclr1.preset_time.put(acqtime) xs.external_trig.put(False) xs.settings.acquire_time.put(acqtime) xs.total_points.put(xnum * ynum) if (merlin in dets): merlin.cam.acquire_time.put(acqtime) merlin.cam.acquire_period.put(acqtime + 0.005) merlin.hdf5.stage_sigs['num_capture'] = xnum * ynum scan_md['merlin'] = { 'merlin_exp_time': acqtime, 'merlin_exp_period': acqtime + 0.005 } # LiveGrid livecallbacks = [] livecallbacks.append(LiveTable([xmotor.name, ymotor.name])) roi_name = 'roi{:02}'.format(1) roi_key = getattr(xs.channel1.rois, roi_name).value.name livecallbacks.append( LiveGrid((ynum, xnum), roi_key, clim=None, cmap='viridis', xlabel='x [um]', ylabel='y [um]', extent=[xstart, xstop, ystart, ystop], x_positive='right', y_positive='down')) myplan = grid_scan(dets, ymotor, ystart, ystop, ynum, xmotor, xstart, xstop, xnum, True, md=scan_md) myplan = subs_wrapper(myplan, {'all': livecallbacks}) # Open shutter if (shutter): yield from mv(shut_b, 'Open') # grid scan uid = yield from myplan # Open shutter if (shutter): yield from mv(shut_b, 'Close') return uid
def scan_and_fly_base(detectors, xstart, xstop, xnum, ystart, ystop, ynum, dwell, *, flying_zebra, xmotor, ymotor, delta=None, shutter=True, align=False, plot=True, md=None, snake=False, verbose=False): """Read IO from SIS3820. Zebra buffers x(t) points as a flyer. Xpress3 is our detector. The aerotech has the x and y positioners. delta should be chosen so that it takes about 0.5 sec to reach the gate?? ymotor slow axis xmotor fast axis Parameters ---------- Detectors : List[Device] These detectors must be known to the zebra xstart, xstop : float xnum : int ystart, ystop : float ynum : int dwell : float Dwell time in seconds flying_zebra : SRXFlyer1Axis xmotor, ymotor : EpicsMotor, kwarg only These should be known to the zebra # TODO sort out how to check this delta : float, optional, kwarg only offset on the ystage start position. If not given, derive from dwell + pixel size align : bool, optional, kwarg only If True, try to align the beamline shutter : bool, optional, kwarg only If True, try to open the shutter """ # t_setup = tic() # Check for negative number of points if (xnum < 1 or ynum < 1): print('Error: Number of points is negative.') return # Set metadata if md is None: md = {} md = get_stock_md(md) # Assign detectors to flying_zebra, this may fail flying_zebra.detectors = detectors # Setup detectors, combine the zebra, sclr, and the just set detector list detectors = (flying_zebra.encoder, flying_zebra.sclr) + flying_zebra.detectors dets_by_name = {d.name: d for d in detectors} # Set up the merlin if 'merlin' in dets_by_name: dpc = dets_by_name['merlin'] # TODO use stage sigs # Set trigger mode # dpc.cam.trigger_mode.put(2) # Make sure we respect whatever the exposure time is set to if (dwell < 0.0066392): print('The Merlin should not operate faster than 7 ms.') print('Changing the scan dwell time to 7 ms.') dwell = 0.007 # According to Ken's comments in hxntools, this is a de-bounce time # when in external trigger mode dpc.cam.stage_sigs['acquire_time'] = 0.50 * dwell - 0.0016392 dpc.cam.stage_sigs['acquire_period'] = 0.75 * dwell dpc.cam.stage_sigs['num_images'] = 1 dpc.stage_sigs['total_points'] = xnum dpc.hdf5.stage_sigs['num_capture'] = xnum del dpc # Setup dexela if ('dexela' in dets_by_name): xrd = dets_by_name['dexela'] xrd.cam.stage_sigs['acquire_time'] = dwell xrd.cam.stage_sigs['acquire_period'] = dwell del xrd # If delta is None, set delta based on time for acceleration if (delta is None): MIN_DELTA = 0.100 # old default value v = ((xstop - xstart) / (xnum - 1)) / dwell # compute "stage speed" t_acc = xmotor.acceleration.get() # acceleration time delta = 0.5 * t_acc * v # distance the stage will travel in t_acc delta = np.amax((delta, MIN_DELTA)) # delta = 0.500 #was 2.5 when npoint scanner drifted # Move to start scanning location # Calculate move to scan start pxsize = (xstop - xstart) / (xnum - 1) row_start = xstart - delta - (pxsize / 2) row_stop = xstop + delta + (pxsize / 2) # yield from mv(xmotor, row_start, # ymotor, ystart) # Run a peakup before the map? if (align): yield from peakup_fine(shutter=shutter) # Scan metadata md['scan']['type'] = 'XRF_FLY' md['scan']['scan_input'] = [ xstart, xstop, xnum, ystart, ystop, ynum, dwell ] md['scan']['sample_name'] = '' md['scan']['detectors'] = [d.name for d in detectors] md['scan']['dwell'] = dwell md['scan']['fast_axis'] = { 'motor_name': xmotor.name, 'units': xmotor.motor_egu.get() } md['scan']['slow_axis'] = { 'motor_name': ymotor.name, 'units': ymotor.motor_egu.get() } md['scan']['theta'] = { 'val': nano_stage.th.user_readback.get(), 'units': nano_stage.th.motor_egu.get() } md['scan']['delta'] = {'val': delta, 'units': xmotor.motor_egu.get()} md['scan']['snake'] = snake md['scan']['shape'] = (xnum, ynum) @stage_decorator(flying_zebra.detectors) def fly_each_step(motor, step, row_start, row_stop): def move_to_start_fly(): "See http://nsls-ii.github.io/bluesky/plans.html#the-per-step-hook" # row_str = short_uid('row') # yield from abs_set(xmotor, row_start, group=row_str) # yield from one_1d_step([temp_nanoKB], motor, step) # yield from bps.wait(group=row_str) row_str = short_uid('row') yield from bps.checkpoint() yield from bps.abs_set(xmotor, row_start, group=row_str) yield from bps.abs_set(motor, step, group=row_str) yield from bps.wait(group=row_str) yield from bps.trigger_and_read([temp_nanoKB, motor]) if verbose: t_mvstartfly = tic() yield from move_to_start_fly() # TODO Why are we re-trying the move? This should be fixed at # a lower level # yield from bps.sleep(1.0) # wait for the "x motor" to move x_set = row_start x_dial = xmotor.user_readback.get() # Get retry deadband value and check against that i = 0 DEADBAND = 0.050 # retry deadband of nPoint scanner while (np.abs(x_set - x_dial) > DEADBAND): if (i == 0): print('Waiting for motor to reach starting position...', end='', flush=True) i = i + 1 yield from mv(xmotor, row_start) yield from bps.sleep(0.1) x_dial = xmotor.user_readback.get() if (i != 0): print('done') if verbose: toc(t_mvstartfly, str='Move to start fly each') # Set the scan speed # Is abs_set(wait=True) or mv() faster? v = ((xstop - xstart) / (xnum - 1)) / dwell # compute "stage speed" # yield from abs_set(xmotor.velocity, v, wait=True) # set the "stage speed" if (v > xmotor.velocity.high_limit): raise ValueError( f'Desired motor velocity too high\nMax velocity: {xmotor.velocity.high_limit}' ) elif (v < xmotor.velocity.low_limit): raise ValueError( f'Desired motor velocity too low\nMin velocity: {xmotor.velocity.low_limit}' ) else: yield from mv(xmotor.velocity, v) # set up all of the detectors # TODO we should be able to move this out of the per-line call?! if ('xs' in dets_by_name): xs = dets_by_name['xs'] yield from abs_set(xs.hdf5.num_capture, xnum, group='set') yield from abs_set(xs.settings.num_images, xnum, group='set') yield from bps.wait(group='set') # yield from mv(xs.hdf5.num_capture, xnum, # xs.settings.num_images, xnum) # xs.hdf5.num_capture.put(xnum) # xs.settings.num_images.put(xnum) if ('xs2' in dets_by_name): xs2 = dets_by_name['xs2'] # yield from abs_set(xs2.hdf5.num_capture, xnum, wait=True) # yield from abs_set(xs2.settings.num_images, xnum, wait=True) yield from mv(xs2.hdf5.num_capture, xnum, xs2.settings.num_images, xnum) if ('merlin' in dets_by_name): merlin = dets_by_name['merlin'] yield from abs_set(merlin.hdf5.num_capture, xnum, wait=True) yield from abs_set(merlin.cam.num_images, xnum, wait=True) if ('dexela' in dets_by_name): dexela = dets_by_name['dexela'] yield from abs_set(dexela.hdf5.num_capture, xnum, wait=True) # yield from abs_set(dexela.hdf5.num_frames_chunks, xnum, wait=True) yield from abs_set(dexela.cam.num_images, xnum, wait=True) ion = flying_zebra.sclr yield from abs_set(ion.nuse_all, 2 * xnum) # arm the Zebra (start caching x positions) # @timer_wrapper def zebra_kickoff(): if row_start < row_stop: yield from kickoff(flying_zebra, xstart=xstart, xstop=xstop, xnum=xnum, dwell=dwell, wait=True) else: yield from kickoff(flying_zebra, xstart=xstop, xstop=xstart, xnum=xnum, dwell=dwell, wait=True) if verbose: t_zebkickoff = tic() yield from zebra_kickoff() if verbose: toc(t_zebkickoff, str='Zebra kickoff') if verbose: t_datacollect = tic() # arm SIS3820, note that there is a 1 sec delay in setting X # into motion so the first point *in each row* won't # normalize... yield from abs_set(ion.erase_start, 1) if verbose: toc(t_datacollect, str=' reset scaler') # trigger all of the detectors row_str = short_uid('row') if verbose: print('Data collection:') for d in flying_zebra.detectors: if verbose: print(f' triggering {d.name}') st = yield from bps.trigger(d, group=row_str) st.add_callback(lambda x: toc( t_datacollect, str= f" status object {datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d %H:%M:%S.%f')}" )) if (d.name == 'dexela'): yield from bps.sleep(1) if verbose: toc(t_datacollect, str=' trigger detectors') # yield from bps.sleep(1.5) if verbose: toc(t_datacollect, str=' sleep') # start the 'fly' def print_watch(*args, **kwargs): with open('/home/xf05id1/bluesky_output.txt', 'a') as f: f.write( datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d %H:%M:%S.%f\n')) # print(args) f.write(json.dumps(kwargs)) f.write('\n') st = yield from abs_set(xmotor, row_stop, group=row_str) # st.watch(print_watch) if verbose: toc(t_datacollect, str=' move start') if verbose and False: ttime.sleep(1) while (xmotor.motor_is_moving.get()): ttime.sleep(0.001) toc(t_datacollect, str=' move end') while (xs.settings.detector_state.get()): ttime.sleep(0.001) toc(t_datacollect, str=' xs done') while (sclr1.acquiring.get()): ttime.sleep(0.001) toc(t_datacollect, str=' sclr1 done') # wait for the motor and detectors to all agree they are done yield from bps.wait(group=row_str) st.wait() if verbose: toc(t_datacollect, str='Total time') # we still know about ion from above yield from abs_set(ion.stop_all, 1) # stop acquiring scaler # set speed back reset_scanner_velocity() # @timer_wrapper def zebra_complete(): yield from complete(flying_zebra) # tell the Zebra we are done if verbose: t_zebcomplete = tic() yield from zebra_complete() if verbose: toc(t_zebcomplete, str='Zebra complete') # @timer_wrapper def zebra_collect(): yield from collect(flying_zebra) # extract data from Zebra if verbose: t_zebcollect = tic() yield from zebra_collect() if verbose: toc(t_zebcollect, str='Zebra collect') def at_scan(name, doc): scanrecord.current_scan.put(doc['uid'][:6]) scanrecord.current_scan_id.put(str(doc['scan_id'])) scanrecord.current_type.put(md['scan']['type']) scanrecord.scanning.put(True) scanrecord.time_remaining.put((dwell * xnum + 3.8) / 3600) def finalize_scan(name, doc): logscan_detailed('XRF_FLY') scanrecord.scanning.put(False) scanrecord.time_remaining.put(0) # TODO remove this eventually? # xs = dets_by_name['xs'] # xs = dets_by_name['xs2'] # Not sure if this is always true xs = dets_by_name[flying_zebra.detectors[0].name] yield from mv(xs.erase, 0) # Setup LivePlot if plot: if (ynum == 1): livepopup = [ SRX1DFlyerPlot(xs.channel1.rois.roi01.value.name, xstart=xstart, xstep=(xstop - xstart) / (xnum - 1), xlabel=xmotor.name) ] else: livepopup = [ LiveGrid((ynum, xnum + 1), xs.channel1.rois.roi01.value.name, extent=(xstart, xstop, ystart, ystop), x_positive='right', y_positive='down') ] else: livepopup = [] @subs_decorator(livepopup) @subs_decorator({'start': at_scan}) @subs_decorator({'stop': finalize_scan}) # monitor values from xs @monitor_during_decorator([xs.channel1.rois.roi01.value]) # @monitor_during_decorator([xs.channel1.rois.roi01.value, xs.array_counter]) @stage_decorator([flying_zebra]) # Below, 'scan' stage ymotor. @run_decorator(md=md) def plan(): # TODO move this to stage sigs for d in flying_zebra.detectors: yield from bps.mov(d.total_points, xnum) # TODO move this to stage sigs yield from bps.mov(xs.external_trig, True) ystep = 0 for step in np.linspace(ystart, ystop, ynum): yield from abs_set(scanrecord.time_remaining, (ynum - ystep) * (dwell * xnum + 3.8) / 3600.) # 'arm' the all of the detectors for outputting fly data for d in flying_zebra.detectors: yield from bps.mov(d.fly_next, True) # print('h5 armed\t',time.time()) if (snake is False): direction = 0 start = row_start stop = row_stop else: if ystep % 2 == 0: direction = 0 start = row_start stop = row_stop else: direction = 1 start = row_stop stop = row_start # Do work if verbose: print(f'Direction = {direction}') print(f'Start = {start}') print(f'Stop = {stop}') flying_zebra._encoder.pc.dir.set(direction) yield from fly_each_step(ymotor, step, start, stop) # print('return from step\t',time.time()) ystep = ystep + 1 # TODO this should be taken care of by stage sigs ion = flying_zebra.sclr yield from bps.mov(xs.external_trig, False, ion.count_mode, 1) yield from mv(nano_stage.sx, 0, nano_stage.sy, 0, nano_stage.sz, 0) yield from bps.sleep(2) # toc(t_setup, str='Setup time') # Setup the final scan plan if shutter: final_plan = finalize_wrapper(plan(), check_shutters(shutter, 'Close')) else: final_plan = plan() # Open the shutter if verbose: t_open = tic() yield from check_shutters(shutter, 'Open') if verbose: toc(t_open, str='Open shutter') # Run the scan uid = yield from final_plan return uid
def xy_fly( scan_title, *, beamline_operator, dwell_time, xstart, xstop, xstep_size, ystart, ystop, ystep_size=None, xspress3=None, ): """Do a x-y fly scan. The x-motor is the 'fast' direction. Parameters ---------- scan_title : str A name for the scan. beamline_operator : str The individual responsible for this scan. Appears in output directory path. dwell_time : float Target time is s on each pixel xstart, xstop : float The start and stop values in the fast direction in mm xstep_size : xstep_size is step of x movement ystart, ystop : float The start and stop values in the slow direction in mm ystep_size : ystep_size use xstep_size if it isn't passed in scan_title : str Title of scan, required. """ xy_fly_stage = xy_stage _validate_motor_limits(xy_fly_stage.x, xstart, xstop, "x") _validate_motor_limits(xy_fly_stage.y, ystart, ystop, "y") ystep_size = ystep_size if ystep_size is not None else xstep_size assert dwell_time > 0, f"dwell_time ({dwell_time}) must be more than 0" assert xstep_size > 0, f"xstep_size ({xstep_size}) must be more than 0" assert ystep_size > 0, f"ystep_size ({ystep_size}) must be more than 0" ret = yield from bps.read(xy_fly_stage.x.mres) # (in mm) xmres = ret[ xy_fly_stage.x.mres.name]["value"] if ret is not None else 0.0003125 ret = yield from bps.read(xy_fly_stage.y.mres) # (in mm) ymres = ret[ xy_fly_stage.y.mres.name]["value"] if ret is not None else 0.0003125 prescale = int(np.floor((xstep_size / (5 * xmres)))) a_xstep_size = prescale * (5 * xmres) a_ystep_size = int(np.floor((ystep_size / (ymres)))) * ymres num_xpixels = int(np.floor((xstop - xstart) / a_xstep_size)) num_ypixels = int(np.floor((ystop - ystart) / a_ystep_size)) yield from bps.mv( x_centers, a_xstep_size / 2 + xstart + np.arange(num_xpixels) * a_xstep_size) # SRX original roi_key = getattr(xs.channel1.rois, roi_name).value.name roi_livegrid_key = xs.channel1.rois.roi01.value.name fig = plt.figure("xs") fig.clf() roi_livegrid = LiveGrid( (num_ypixels + 1, num_xpixels + 1), roi_livegrid_key, clim=None, cmap="inferno", xlabel="x (mm)", ylabel="y (mm)", extent=[xstart, xstop, ystart, ystop], x_positive="right", y_positive="down", ax=fig.gca(), ) flyspeed = a_xstep_size / dwell_time # this is in mm/s try: xy_fly_stage.x.velocity.check_value(flyspeed) except LimitError as e: raise LimitError(f"You requested a range of {xstop - xstart} with " f"{num_xpixels} pixels and a dwell time of " f"{dwell_time}. This requires a " f"motor velocity of {flyspeed} which " "is out of range.") from e # set up delta-tau trigger to fast motor for v in ["p1600=0", "p1607=1", "p1600=1"]: yield from bps.mv(dtt, v) yield from bps.sleep(0.1) # TODO make this a message? sclr.set_mode("flying") # poke the struck settings yield from bps.mv(sclr.mcas.prescale, prescale) yield from bps.mv(sclr.mcas.nuse, num_xpixels) if xspress3 is not None: yield from bps.mv(xs.external_trig, True) yield from bps.mv(xspress3.total_points, num_xpixels) yield from bps.mv(xspress3.hdf5.num_capture, num_xpixels) yield from bps.mv(xspress3.settings.num_images, num_xpixels) @bpp.reset_positions_decorator([xy_fly_stage.x, xy_fly_stage.y]) @bpp.subs_decorator({"all": [roi_livegrid]}) @bpp.monitor_during_decorator([xs.channel1.rois.roi01.value]) @bpp.stage_decorator([sclr]) @bpp.baseline_decorator([mono, xy_fly_stage]) # TODO put is other meta data @bpp.run_decorator( md={ "scan_title": scan_title, "operator": beamline_operator, "user_input": { "dwell_time": dwell_time, "xstart": xstart, "xstop": xstop, "xstep_size": xstep_size, "ystart": ystart, "ystep_size": ystep_size, }, "derived_input": { "actual_ystep_size": a_ystep_size, "actual_xstep_size": a_xstep_size, "fly_velocity": flyspeed, "xpixels": num_xpixels, "ypixels": num_ypixels, "prescale": prescale, }, }) def fly_body(): yield from bps.mv(xy_fly_stage.x, xstart, xy_fly_stage.y, ystart) @bpp.stage_decorator([x for x in [xspress3] if x is not None]) def fly_row(): # go to start of row target_y = ystart + y * a_ystep_size yield from bps.mv(xy_fly_stage.x, xstart, xy_fly_stage.y, target_y) yield from bps.mv(y_centers, np.ones(num_xpixels) * target_y) # set the fly speed ret = yield from bps.read(xy_fly_stage.z.user_readback) # (in mm) zpos = (ret[xy_fly_stage.z.user_readback.name]["value"] if ret is not None else 0) yield from bps.mov(z_centers, np.ones(num_xpixels) * zpos) yield from bps.mv(xy_fly_stage.x.velocity, flyspeed) yield from bps.trigger_and_read([xy_fly_stage], name="row_ends") for v in ["p1600=0", "p1600=1"]: yield from bps.mv(dtt, v) yield from bps.sleep(0.1) # arm the struck yield from bps.trigger(sclr, group=f"fly_row_{y}") # maybe start the xspress3 if xspress3 is not None: yield from bps.trigger(xspress3, group=f"fly_row_{y}") yield from bps.sleep(0.1) # fly the motor yield from bps.abs_set(xy_fly_stage.x, xstop + a_xstep_size, group=f"fly_row_{y}") yield from bps.wait(group=f"fly_row_{y}") yield from bps.trigger_and_read([xy_fly_stage], name="row_ends") yield from bps.mv(xy_fly_stage.x.velocity, 5.0) yield from bps.sleep(0.1) # read and save the struck yield from bps.create(name="primary") # yield from bps.read(sclr) yield from bps.read(mono) yield from bps.read(x_centers) yield from bps.read(y_centers) yield from bps.read(z_centers) yield from bps.read(xy_fly_stage.y) yield from bps.read(xy_fly_stage.z) # and maybe the xspress3 if xspress3 is not None: yield from bps.read(xspress3) yield from bps.save() for y in range(num_ypixels): if xspress3 is not None: yield from bps.mv(xspress3.fly_next, True) yield from fly_row() yield from fly_body() # save the start document to a file for the benefit of the user start = db[-1].start dt = datetime.datetime.fromtimestamp(start["time"]) filepath = os.path.expanduser( f"~/Users/Data/{start['operator']}/{dt.date().isoformat()}/xy_fly/" f"{start['scan_title']}-{start['scan_id']}-{start['operator']}-{dt.time().isoformat()}.log" ) os.makedirs(os.path.dirname(filepath), exist_ok=True) with open(filepath, "wt") as output_file: output_file.write(pprint.pformat(start))
def scan_and_fly_base(detectors, xstart, xstop, xnum, ystart, ystop, ynum, dwell, *, flying_zebra, xmotor, ymotor, delta=None, shutter=True, align=False, md=None): """Read IO from SIS3820. Zebra buffers x(t) points as a flyer. Xpress3 is our detector. The aerotech has the x and y positioners. delta should be chosen so that it takes about 0.5 sec to reach the gate?? ymotor slow axis xmotor fast axis Parameters ---------- Detectors : List[Device] These detectors must be known to the zebra xstart, xstop : float xnum : int ystart, ystop : float ynum : int dwell : float Dwell time in seconds flying_zebra : SRXFlyer1Axis xmotor, ymotor : EpicsMotor, kwarg only These should be known to the zebra # TODO sort out how to check this delta : float, optional, kwarg only offset on the ystage start position. If not given, derive from dwell + pixel size align : bool, optional, kwarg only If True, try to align the beamline shutter : bool, optional, kwarg only If True, try to open the shutter """ # t_setup = tic() # Check for negative number of points if (xnum < 1 or ynum < 1): print('Error: Number of points is negative.') return # Set metadata if md is None: md = {} # Change retry deadband for hf_stage.x and hf_stage.y if (hf_stage.x in (xmotor, ymotor)): OLD_RDBD_X = hf_stage.RETRY_DEADBAND_X.get() NEW_RDBD_X = 2e-4 hf_stage.RETRY_DEADBAND_X.put(NEW_RDBD_X) if (hf_stage.y in (xmotor, ymotor)): OLD_RDBD_Y = hf_stage.RETRY_DEADBAND_Y.get() NEW_RDBD_Y = 2e-4 hf_stage.RETRY_DEADBAND_Y.put(NEW_RDBD_Y) # assign detectors to flying_zebra, this may fail flying_zebra.detectors = detectors # Setup detectors, combine the zebra, sclr, and the just set detector list detectors = (flying_zebra.encoder, flying_zebra.sclr) + flying_zebra.detectors dets_by_name = {d.name : d for d in detectors} # set up the merlin if 'merlin' in dets_by_name: dpc = dets_by_name['merlin'] # TODO use stage sigs # Set trigger mode # dpc.cam.trigger_mode.put(2) # Make sure we respect whatever the exposure time is set to if (dwell < 0.0066392): print('The Merlin should not operate faster than 7 ms.') print('Changing the scan dwell time to 7 ms.') dwell = 0.007 # According to Ken's comments in hxntools, this is a de-bounce time # when in external trigger mode dpc.cam.stage_sigs['acquire_time'] = 0.50 * dwell - 0.0016392 dpc.cam.stage_sigs['acquire_period'] = 0.75 * dwell dpc.cam.stage_sigs['num_images'] = 1 dpc.stage_sigs['total_points'] = xnum dpc.hdf5.stage_sigs['num_capture'] = xnum del dpc # Setup dexela if ('dexela' in dets_by_name): xrd = dets_by_name['dexela'] xrd.cam.stage_sigs['acquire_time'] = 0.50 * dwell - 0.050 xrd.cam.stage_sigs['acquire_period'] = 0.50 * dwell - 0.020 del xrd # If delta is None, set delta based on time for acceleration if (delta is None): if (flying_zebra.encoder.pc.egu.get() == 'mm'): MIN_DELTA = 0.002 # old default value v = ((xstop - xstart) / (xnum-1)) / dwell # compute "stage speed" t_acc = 1.0 # acceleration time, default 1.0 s delta = t_acc * v # distance the stage will travel in t_acc delta = np.amax((delta, MIN_DELTA)) else: delta = 0.500 #was 2.5 when npoint scanner drifted # Move to start scanning location #if ('nano_stage' in xmotor.name): # yield from mv(xmotor.velocity, 30) # yield from mv(xmotor.velocity, 30) yield from mv(xmotor, xstart - delta, ymotor, ystart) # Run a peakup before the map? if (align): yield from peakup_fine(shutter=shutter) md = ChainMap(md, { 'plan_name': 'scan_and_fly', 'detectors': [d.name for d in detectors], 'dwell': dwell, 'shape': (xnum, ynum), 'scaninfo': {'type': 'XRF_fly', 'raster': False, 'fast_axis': flying_zebra.fast_axis.get()}, # 'slow_axis': flying_zebra.slow_axis.get(), # 'theta': hf_stage.th.position} 'scan_params': [xstart, xstop, xnum, ystart, ystop, ynum, dwell], 'scan_input': [xstart, xstop, xnum, ystart, ystop, ynum, dwell], 'delta': delta, 'beamline_status' : {'energy' : energy.position.energy} } ) if ('xs2' in dets_by_name): md['scaninfo']['type'] = 'XRF_E_tomo_fly' @stage_decorator(flying_zebra.detectors) def fly_each_step(motor, step): def move_to_start_fly(): "See http://nsls-ii.github.io/bluesky/plans.html#the-per-step-hook" yield from abs_set(xmotor, xstart-delta, group='row') yield from one_1d_step([temp_nanoKB], motor, step) yield from bps.wait(group='row') # t_mvstartfly = tic() yield from move_to_start_fly() # toc(t_mvstartfly, str='Move to start fly each') # TODO Why are we re-trying the move? This should be fixed at # a lower level # yield from bps.sleep(1.0) # wait for the "x motor" to move x_set = xstart - delta x_dial = xmotor.user_readback.get() # Get retry deadband value and check against that i = 0 if (xmotor.egu == 'mm'): DEADBAND = 0.0005 else: DEADBAND = 0.02 while (np.abs(x_set - x_dial) > DEADBAND): if (i == 0): print('Waiting for motor to reach starting position...', end='', flush=True) i = i + 1 yield from mv(xmotor, xstart - delta) yield from bps.sleep(0.1) x_dial = xmotor.user_readback.get() if (i != 0): print('done') # Set the scan speed # Is abs_set(wait=True) or mv() faster? v = ((xstop - xstart) / (xnum-1)) / dwell # compute "stage speed" # yield from abs_set(xmotor.velocity, v, wait=True) # set the "stage speed" yield from mv(xmotor.velocity, v) # Change backlash speed for hf_stage.x and hf_stage.y if (hf_stage.x is xmotor): yield from mv(hf_stage.BACKLASH_SPEED_X, v) if (hf_stage.y is xmotor): yield from mv(hf_stage.BACKLASH_SPEED_Y, v) # set up all of the detectors # TODO we should be able to move this out of the per-line call?! if ('xs' in dets_by_name): xs = dets_by_name['xs'] # yield from abs_set(xs.hdf5.num_capture, xnum, wait=True) # yield from abs_set(xs.settings.num_images, xnum, wait=True) yield from mv(xs.hdf5.num_capture, xnum, xs.settings.num_images, xnum) if ('xs2' in dets_by_name): xs2 = dets_by_name['xs2'] # yield from abs_set(xs2.hdf5.num_capture, xnum, wait=True) # yield from abs_set(xs2.settings.num_images, xnum, wait=True) yield from mv(xs2.hdf5.num_capture, xnum, xs2.settings.num_images, xnum) if ('merlin' in dets_by_name): merlin = dets_by_name['merlin'] yield from abs_set(merlin.hdf5.num_capture, xnum, wait=True) yield from abs_set(merlin.cam.num_images, xnum, wait=True) if ('dexela' in dets_by_name): dexela = dets_by_name['dexela'] yield from abs_set(dexela.hdf5.num_capture, xnum, wait=True) yield from abs_set(dexela.cam.num_images, xnum, wait=True) ion = flying_zebra.sclr yield from abs_set(ion.nuse_all,xnum) # arm the Zebra (start caching x positions) # @timer_wrapper def zebra_kickoff(): yield from kickoff(flying_zebra, xstart=xstart, xstop=xstop, xnum=xnum, dwell=dwell, wait=True) # t_zebkickoff = tic() yield from zebra_kickoff() # toc(t_zebkickoff, str='Zebra kickoff') # arm SIS3820, note that there is a 1 sec delay in setting X # into motion so the first point *in each row* won't # normalize... yield from abs_set(ion.erase_start, 1) # trigger all of the detectors for d in flying_zebra.detectors: yield from bps.trigger(d, group='row') if (d.name == 'dexela'): yield from bps.sleep(1) yield from bps.sleep(1.5) # start the 'fly' yield from abs_set(xmotor, xstop + 1*delta, group='row') # move in x # wait for the motor and detectors to all agree they are done yield from bps.wait(group='row') # we still know about ion from above yield from abs_set(ion.stop_all, 1) # stop acquiring scaler # @timer_wrapper def zebra_complete(): yield from complete(flying_zebra) # tell the Zebra we are done # t_zebcomplete = tic() yield from zebra_complete() # toc(t_zebcomplete, str='Zebra complete') # @timer_wrapper def zebra_collect(): yield from collect(flying_zebra) # extract data from Zebra # t_zebcollect = tic() yield from zebra_collect() # toc(t_zebcollect, str='Zebra collect') # TODO what? if ('e_tomo' in xmotor.name): v_return = min(4, xmotor.velocity.high_limit) yield from mv(xmotor.velocity, v_return) if ('nano_stage' in xmotor.name): yield from mv(xmotor.velocity, 30) else: # set the "stage speed" yield from mv(xmotor.velocity, 1.0) def at_scan(name, doc): scanrecord.current_scan.put(doc['uid'][:6]) scanrecord.current_scan_id.put(str(doc['scan_id'])) scanrecord.current_type.put(md['scaninfo']['type']) scanrecord.scanning.put(True) scanrecord.time_remaining.put((dwell*xnum + 3.8)/3600) def finalize_scan(name, doc): logscan_detailed('xrf_fly') scanrecord.scanning.put(False) scanrecord.time_remaining.put(0) if (hf_stage.x in (xmotor, ymotor)): hf_stage.RETRY_DEADBAND_X.put(OLD_RDBD_X) if (hf_stage.y in (xmotor, ymotor)): hf_stage.RETRY_DEADBAND_Y.put(OLD_RDBD_Y) # TODO remove this eventually? # xs = dets_by_name['xs'] # xs = dets_by_name['xs2'] # Not sure if this is always true xs = dets_by_name[flying_zebra.detectors[0].name] yield from mv(xs.erase, 0) # Setup LivePlot if (ynum == 1): # livepopup = LivePlot(xs.channel1.rois.roi01.value.name) livepopup = SRX1DFlyerPlot(xs.channel1.rois.roi01.value.name, xstart=xstart, xstep=(xstop-xstart)/(xnum-1), xlabel=xmotor.name) else: livepopup = LiveGrid((ynum, xnum+1), xs.channel1.rois.roi01.value.name, extent=(xstart, xstop, ystart, ystop), x_positive='right', y_positive='down') # livepopup = ArrayCounterLiveGrid( # (ynum, xnum + 1), # xs.channel1.rois.roi01.value.name, # array_counter_key=xs.array_counter.name, # extent=(xstart, xstop, ystart, ystop), # x_positive='right', y_positive='down' # ) @subs_decorator([livepopup]) @subs_decorator({'start': at_scan}) @subs_decorator({'stop': finalize_scan}) # monitor values from xs # @monitor_during_decorator([xs.channel1.rois.roi01.value]) @monitor_during_decorator([xs.channel1.rois.roi01.value, xs.array_counter]) @stage_decorator([flying_zebra]) # Below, 'scan' stage ymotor. @run_decorator(md=md) def plan(): # TODO move this to stage sigs for d in flying_zebra.detectors: yield from bps.mov(d.total_points, xnum) # TODO move this to stage sigs yield from bps.mov(xs.external_trig, True) ystep = 0 for step in np.linspace(ystart, ystop, ynum): yield from abs_set(scanrecord.time_remaining, (ynum - ystep) * ( dwell * xnum + 3.8 ) / 3600.) ystep = ystep + 1 # 'arm' the all of the detectors for outputting fly data for d in flying_zebra.detectors: yield from bps.mov(d.fly_next, True) # print('h5 armed\t',time.time()) yield from fly_each_step(ymotor, step) # print('return from step\t',time.time()) # TODO this should be taken care of by stage sigs ion = flying_zebra.sclr yield from bps.mov(xs.external_trig, False, ion.count_mode, 1) # toc(t_setup, str='Setup time') # Setup the final scan plan if shutter: final_plan = finalize_wrapper(plan(), bps.mov(shut_b, 'Close')) else: final_plan = plan() # Open the shutter if shutter: # t_open = tic() yield from mv(shut_b, 'Open') # toc(t_open, str='Open shutter') # Run the scan uid = yield from final_plan # Close the shutter # if shutter: # # t_close = tic() # yield from mv(shut_b, 'Close') # # toc(t_close, str='Close shutter') # Return sample stage defaults if ('hf_stage' in xmotor.name): hf_stage.reset_stage_defaults() return uid
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 descriptor(self, doc): self._descriptors[doc['uid']] = doc stream_name = doc.get('name', 'primary') # fall back for old docs if stream_name not in self._stream_names_seen: self._stream_names_seen.add(stream_name) if self._table_enabled: print("New stream: {!r}".format(stream_name)) columns = hinted_fields(doc) # ## This deals with old documents. ## # if stream_name == 'primary' and self._cleanup_motor_heuristic: # We stashed object names in self.dim_fields, which we now need to # look up the actual fields for. self._cleanup_motor_heuristic = False fixed_dim_fields = [] for obj_name in self.dim_fields: try: fields = doc.get('hints', {}).get(obj_name, {})['fields'] except KeyError: fields = doc['object_keys'][obj_name] fixed_dim_fields.extend(fields) self.dim_fields = fixed_dim_fields # ## TABLE ## # if stream_name == self.dim_stream: # Ensure that no independent variables ('dimensions') are # duplicated here. columns = [c for c in columns if c not in self.dim_fields] if self._table_enabled: self._table = LiveTable(list(self.dim_fields) + columns) self._table('start', self._start_doc) self._table('descriptor', doc) # ## DECIDE WHICH KIND OF PLOT CAN BE USED ## # if not self._plots_enabled: return if stream_name in self.noplot_streams: return if ((self._start_doc.get('num_points') == 1) and (stream_name == self.dim_stream) and self.omit_single_point_plot): return # This is a heuristic approach until we think of how to hint this in a # generalizable way. if stream_name == self.dim_stream: dim_fields = self.dim_fields else: dim_fields = ['time'] # 'time' once LivePlot can do that # Create a figure or reuse an existing one. fig_name = '{} vs {}'.format(' '.join(sorted(columns)), ' '.join(sorted(dim_fields))) if self.overplot and len(dim_fields) == 1: # If any open figure matches 'figname {number}', use it. If there # are multiple, the most recently touched one will be used. pat1 = re.compile('^' + fig_name + '$') pat2 = re.compile('^' + fig_name + ' \d+$') for label in plt.get_figlabels(): if pat1.match(label) or pat2.match(label): fig_name = label break else: if plt.fignum_exists(fig_name): # Generate a unique name by appending a number. for number in itertools.count(2): new_name = '{} {}'.format(fig_name, number) if not plt.fignum_exists(new_name): fig_name = new_name break ndims = len(dim_fields) if not 0 < ndims < 3: # we need 1 or 2 dims to do anything, do not make empty figures return fig = plt.figure(fig_name) if not fig.axes: # This is apparently a fresh figure. Make axes. # The complexity here is due to making a shared x axis. This can be # simplified when Figure supports the `subplots` method in a future # release of matplotlib. fig.set_size_inches(6.4, min(950, len(columns) * 400) / fig.dpi) for i in range(len(columns)): if i == 0: ax = fig.add_subplot(len(columns), 1, 1 + i) if ndims == 1: share_kwargs = {'sharex': ax} elif ndims == 2: share_kwargs = {'sharex': ax, 'sharey': ax} else: raise NotImplementedError("we now support 3D?!") else: ax = fig.add_subplot(len(columns), 1, 1 + i, **share_kwargs) axes = fig.axes # ## LIVE PLOT AND PEAK ANALYSIS ## # if ndims == 1: self._live_plots[doc['uid']] = {} self._peak_stats[doc['uid']] = {} x_key, = dim_fields for y_key, ax in zip(columns, axes): dtype = doc['data_keys'][y_key]['dtype'] if dtype not in ('number',): warn("Omitting {} from plot because dtype is {}" "".format(y_key, dtype)) continue # Create an instance of LivePlot and an instance of PeakStats. live_plot = LivePlotPlusPeaks(y=y_key, x=x_key, ax=ax, peak_results=self.peaks) live_plot('start', self._start_doc) live_plot('descriptor', doc) peak_stats = PeakStats(x=x_key, y=y_key) peak_stats('start', self._start_doc) peak_stats('descriptor', doc) # Stash them in state. self._live_plots[doc['uid']][y_key] = live_plot self._peak_stats[doc['uid']][y_key] = peak_stats for ax in axes[:-1]: ax.set_xlabel('') elif ndims == 2: # Decide whether to use LiveGrid or LiveScatter. LiveScatter is the # safer one to use, so it is the fallback.. gridding = self._start_doc.get('hints', {}).get('gridding') if gridding == 'rectilinear': self._live_grids[doc['uid']] = {} x_key, y_key = dim_fields try: extents = self._start_doc['extents'] shape = self._start_doc['shape'] except KeyError: warn("Need both 'shape' and 'extents' in plan metadata to " "create LiveGrid.") else: data_range = np.array([float(np.diff(e)) for e in extents]) y_step, x_step = data_range / [s - 1 for s in shape] adjusted_extent = [extents[1][0] - x_step / 2, extents[1][1] + x_step / 2, extents[0][0] - y_step / 2, extents[0][1] + y_step / 2] for I_key, ax in zip(columns, axes): # MAGIC NUMBERS based on what tacaswell thinks looks OK data_aspect_ratio = np.abs(data_range[1]/data_range[0]) MAR = 2 if (1/MAR < data_aspect_ratio < MAR): aspect = 'equal' ax.set_aspect(aspect, adjustable='box-forced') else: aspect = 'auto' ax.set_aspect(aspect, adjustable='datalim') live_grid = LiveGrid(shape, I_key, xlabel=x_key, ylabel=y_key, extent=adjusted_extent, aspect=aspect, ax=ax) live_grid('start', self._start_doc) live_grid('descriptor', doc) self._live_grids[doc['uid']][I_key] = live_grid else: self._live_scatters[doc['uid']] = {} x_key, y_key = dim_fields for I_key, ax in zip(columns, axes): try: extents = self._start_doc['extents'] except KeyError: xlim = ylim = None else: xlim, ylim = extents live_scatter = LiveScatter(x_key, y_key, I_key, xlim=xlim, ylim=ylim, # Let clim autoscale. ax=ax) live_scatter('start', self._start_doc) live_scatter('descriptor', doc) self._live_scatters[doc['uid']][I_key] = live_scatter else: raise NotImplementedError("we do not support 3D+ in BEC yet " "(and it should have bailed above)") fig.tight_layout()
def scan_and_fly(xstart, xstop, xnum, ystart, ystop, ynum, dwell, *, delta=None, shutter=True, xmotor=hf_stage.x, ymotor=hf_stage.y, xs=xs, ion=sclr1, align=False, flying_zebra=flying_zebra, md=None): """ Read IO from SIS3820. Zebra buffers x(t) points as a flyer. Xpress3 is our detector. The aerotech has the x and y positioners. delta should be chosen so that it takes about 0.5 sec to reach the gate?? ymotor slow axis xmotor fast axis """ c2pitch_kill = EpicsSignal("XF:05IDA-OP:1{Mono:HDCM-Ax:P2}Cmd:Kill-Cmd") if md is None: md = {} # If delta is None, set delta based on time for acceleration if delta is None: # delta = 0.002 # old default value v = (xstop - xstart) / (xnum-1) / dwell # compute "stage speed" t_acc = 1.0 # acceleration time, default 1.0 s delta = t_acc * v # distance the stage will travel in t_acc yield from abs_set(ymotor, ystart, wait=True) # ready to move yield from abs_set(xmotor, xstart - delta, wait=True) # ready to move if shutter is True: yield from mv(shut_b, 'Open') if align: fly_ps = PeakStats(dcm.c2_pitch.name, i0.name) align_scan = scan([sclr1], dcm.c2_pitch, -19.320, -19.360, 41) align_scan = bp.subs_wrapper(align_scan, fly_ps) yield from align_scan yield from abs_set(dcm.c2_pitch, fly_ps.max[0], wait=True) #ttime.sleep(10) #yield from abs_set(c2pitch_kill, 1) md = ChainMap(md, { 'plan_name': 'scan_and_fly', 'detectors': [zebra.name, xs.name, ion.name], 'dwell': dwell, 'shape': (xnum, ynum), 'scaninfo': {'type': 'XRF_fly', 'raster': False, 'fast_axis': flying_zebra._fast_axis}, # 'scaninfo': {'type': 'E_tomo', # 'raster': False, # 'fast_axis': flying_zebra._fast_axis}, 'scan_params': [xstart, xstop, xnum, ystart, ystop, ynum, dwell], 'scan_input': [xstart, xstop, xnum, ystart, ystop, ynum, dwell], 'delta': delta } ) if (xs.name == 'xs2'): md['scaninfo']['type'] = 'XRF_E_tomo_fly' @stage_decorator([xs]) def fly_each_step(detectors, motor, step, firststep): "See http://nsls-ii.github.io/bluesky/plans.html#the-per-step-hook" # First, let 'scan' handle the normal y step, including a checkpoint. yield from one_1d_step(detectors, motor, step) # Now do the x steps. v = (xstop - xstart) / (xnum-1) / dwell # compute "stage speed" yield from abs_set(xmotor, xstart - delta, wait=True) # ready to move yield from abs_set(xmotor.velocity, v, wait=True) # set the "stage speed" yield from abs_set(xs.hdf5.num_capture, xnum, wait=True) yield from abs_set(xs.settings.num_images, xnum, wait=True) yield from abs_set(ion.nuse_all,xnum) # arm the Zebra (start caching x positions) yield from kickoff(flying_zebra, xstart=xstart, xstop=xstop, xnum=xnum, dwell=dwell, wait=True) yield from abs_set(ion.erase_start, 1) # arm SIS3820, note that there is a 1 sec delay in setting X into motion # so the first point *in each row* won't normalize... yield from bps.trigger(xs, group='row') #if firststep == True: # ttime.sleep(0.) yield from bps.sleep(1.5) yield from abs_set(xmotor, xstop+1*delta, group='row') # move in x yield from bps.wait(group='row') # yield from abs_set(xs.settings.acquire, 0) # stop acquiring images yield from abs_set(ion.stop_all, 1) # stop acquiring scaler yield from complete(flying_zebra) # tell the Zebra we are done yield from collect(flying_zebra) # extract data from Zebra if ('e_tomo' in xmotor.name): v_return = 4 v_max = xmotor.velocity.high_limit if (v_return > v_max): xmotor.velocity.set(v_max) else: xmotor.velocity.set(v_return) else: yield from abs_set(xmotor.velocity, 1.0, wait=True) # set the "stage speed" def at_scan(name, doc): scanrecord.current_scan.put(doc['uid'][:6]) scanrecord.current_scan_id.put(str(doc['scan_id'])) scanrecord.current_type.put(md['scaninfo']['type']) scanrecord.scanning.put(True) scanrecord.time_remaining.put((dwell*xnum + 3.8)/3600) def finalize_scan(name, doc): logscan_detailed('xrf_fly') scanrecord.scanning.put(False) scanrecord.time_remaining.put(0) #@subs_decorator([LiveTable([ymotor]), RowBasedLiveGrid((ynum, xnum), ion.name, row_key=ymotor.name), LiveZebraPlot()]) #@subs_decorator([LiveTable([ymotor]), LiveGrid((ynum, xnum), sclr1.mca1.name)]) @subs_decorator([LiveGrid((ynum, xnum+1), xs.channel1.rois.roi01.value.name, extent=(xstart, xstop, ystop, ystart))]) @subs_decorator({'start': at_scan}) @subs_decorator({'stop': finalize_scan}) # monitor values from xs @monitor_during_decorator([xs.channel1.rois.roi01.value]) #@monitor_during_decorator([xs], run=False) # monitor values from xs @stage_decorator([flying_zebra]) # Below, 'scan' stage ymotor. @run_decorator(md=md) def plan(): yield from bps.mov(xs.total_points, xnum) # added to "prime" the detector #yield from abs_set(xs.settings.trigger_mode, 'TTL Veto Only') yield from bps.mov(xs.external_trig, True) ystep = 0 for step in np.linspace(ystart, ystop, ynum): yield from abs_set(scanrecord.time_remaining, (ynum - ystep) * ( dwell * xnum + 3.8 ) / 3600.) ystep = ystep + 1 # 'arm' the xs for outputting fly data yield from bps.mov(xs.fly_next, True) # print('h5 armed\t',time.time()) if step == ystart: firststep = True else: firststep = False yield from fly_each_step([], ymotor, step, firststep) # print('return from step\t',time.time()) yield from bps.mov(xs.external_trig, False, ion.count_mode, 1) if shutter is True: yield from mv(shut_b, 'Close') return (yield from plan())
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 scan_and_fly_base(detectors, xstart, xstop, xnum, ystart, ystop, ynum, dwell, *, flying_zebra, xmotor, ymotor, delta=None, shutter=True, align=False, md=None): """Read IO from SIS3820. Zebra buffers x(t) points as a flyer. Xpress3 is our detector. The aerotech has the x and y positioners. delta should be chosen so that it takes about 0.5 sec to reach the gate?? ymotor slow axis xmotor fast axis Parameters ---------- Detectors : List[Device] These detectors must be known to the zebra xstart, xstop : float xnum : int ystart, ystop : float ynum : int dwell : float Dwell time in seconds flying_zebra : SRXFlyer1Axis xmotor, ymotor : EpicsMotor, kwarg only These should be known to the zebra # TODO sort out how to check this delta : float, optional, kwarg only offset on the ystage start position. If not given, derive from dwell + pixel size align : bool, optional, kwarg only If True, try to align the beamline shutter : bool, optional, kwarg only If True, try to open the shutter """ c2pitch_kill = EpicsSignal("XF:05IDA-OP:1{Mono:HDCM-Ax:P2}Cmd:Kill-Cmd") if md is None: md = {} # assign detectors to flying_zebra, this may fail flying_zebra.detectors = detectors # Setup detectors, combine the zebra, sclr, and the just set detector list detectors = (flying_zebra.encoder, flying_zebra.sclr) + flying_zebra.detectors dets_by_name = {d.name: d for d in detectors} # set up the merlin if 'merlin' in dets_by_name: dpc = dets_by_name['merlin'] # TODO use stage sigs # Set trigger mode # dpc.cam.trigger_mode.put(2) # Make sure we respect whatever the exposure time is set to if (dwell < 0.0066392): print('The Merlin should not operate faster than 7 ms.') print('Changing the scan dwell time to 7 ms.') dwell = 0.007 # According to Ken's comments in hxntools, this is a de-bounce time # when in external trigger mode dpc.cam.stage_sigs['acquire_time'] = .25 * dwell - 0.0016392 dpc.cam.stage_sigs['acquire_period'] = .5 * dwell dpc.cam.stage_sigs['num_images'] = 1 dpc.stage_sigs['total_points'] = xnum dpc.hdf5.stage_sigs['num_capture'] = xnum del dpc # setup dexela if ('dexela' in dets_by_name): xrd = dets_by_name['dexela'] xrd.cam.stage_sigs['acquire_time'] = 1.00 * dwell - 0.050 xrd.cam.stage_sigs['acquire_period'] = 1.00 * dwell - 0.020 del xrd # If delta is None, set delta based on time for acceleration if delta is None: # delta = 0.002 # old default value v = ((xstop - xstart) / (xnum - 1)) / dwell # compute "stage speed" t_acc = 1.0 # acceleration time, default 1.0 s delta = t_acc * v # distance the stage will travel in t_acc # TODO can we do this move in parallel? yield from abs_set(ymotor, ystart, wait=True) # ready to move yield from abs_set(xmotor, xstart - delta, wait=True) # ready to move if shutter: yield from mv(shut_b, 'Open') if align: fly_ps = PeakStats(dcm.c2_pitch.name, i0.name) align_scan = bp.subs_wrapper( scan([flying_zebra.sclr], dcm.c2_pitch, -19.320, -19.360, 41), fly_ps) ret = yield from align_scan if ret is not None: yield from abs_set(dcm.c2_pitch, fly_ps.max[0], wait=True) #ttime.sleep(10) #yield from abs_set(c2pitch_kill, 1) md = ChainMap( md, { 'plan_name': 'scan_and_fly', 'detectors': [d.name for d in detectors], 'dwell': dwell, 'shape': (xnum, ynum), # 'scaninfo': {'type': 'XRF_fly', # 'raster': False, # 'fast_axis': flying_zebra._fast_axis}, # 'theta': hf_stage.th.position, 'scaninfo': { 'type': 'E_tomo', 'raster': False, 'fast_axis': flying_zebra._fast_axis }, 'scan_params': [xstart, xstop, xnum, ystart, ystop, ynum, dwell], 'scan_input': [xstart, xstop, xnum, ystart, ystop, ynum, dwell], 'delta': delta }) # if (xs == 'xs2'): # md['scaninfo']['type'] = 'XRF_E_tomo_fly' if ('xs2' in dets_by_name): md['scaninfo']['type'] = 'XRF_E_tomo_fly' @stage_decorator(flying_zebra.detectors) def fly_each_step(motor, step): "See http://nsls-ii.github.io/bluesky/plans.html#the-per-step-hook" # First, let 'scan' handle the normal y step, including a checkpoint. yield from one_1d_step([], motor, step) # yield from bps.sleep(1.0) # wait for the "x motor" to move # Now do the x steps. v = ((xstop - xstart) / (xnum - 1)) / dwell # compute "stage speed" yield from abs_set(xmotor, xstart - delta, wait=True) # ready to move # TODO Why are we re-trying the move? This should be fixed at # a lower level # yield from bps.sleep(1.0) # wait for the "x motor" to move x_set = xstart - delta x_dial = xmotor.user_readback.get() i = 0 while (np.abs(x_set - x_dial) > 0.0001): if (i == 0): print('Waiting for motor to reach starting position...', end='') i = i + 1 yield from abs_set(xmotor, xstart - delta, wait=True) yield from bps.sleep(1.0) x_dial = xmotor.user_readback.get() if (i != 0): print('done') yield from abs_set(xmotor.velocity, v, wait=True) # set the "stage speed" # set up all of the detectors # TODO we should be able to move this out of the per-line call?! if 'xs' in dets_by_name: xs = dets_by_name['xs'] yield from abs_set(xs.hdf5.num_capture, xnum, wait=True) yield from abs_set(xs.settings.num_images, xnum, wait=True) if 'xs2' in dets_by_name: xs2 = dets_by_name['xs2'] yield from abs_set(xs2.hdf5.num_capture, xnum, wait=True) yield from abs_set(xs2.settings.num_images, xnum, wait=True) if 'merlin' in dets_by_name: merlin = dets_by_name['merlin'] yield from abs_set(merlin.hdf5.num_capture, xnum, wait=True) yield from abs_set(merlin.cam.num_images, xnum, wait=True) if 'dexela' in dets_by_name: dexela = dets_by_name['dexela'] yield from abs_set(dexela.hdf5.num_capture, xnum, wait=True) yield from abs_set(dexela.cam.num_images, xnum, wait=True) ion = flying_zebra.sclr yield from abs_set(ion.nuse_all, xnum) # arm the Zebra (start caching x positions) yield from kickoff(flying_zebra, xstart=xstart, xstop=xstop, xnum=xnum, dwell=dwell, wait=True) # arm SIS3820, note that there is a 1 sec delay in setting X # into motion so the first point *in each row* won't # normalize... yield from abs_set(ion.erase_start, 1) # trigger all of the detectors for d in flying_zebra.detectors: yield from bps.trigger(d, group='row') yield from bps.sleep(1.5) # start the 'fly' yield from abs_set(xmotor, xstop + 1 * delta, group='row') # move in x # wait for the motor and detectors to all agree they are done yield from bps.wait(group='row') # yield from abs_set(xs.settings.acquire, 0) # stop acquiring images # we still know about ion from above yield from abs_set(ion.stop_all, 1) # stop acquiring scaler yield from complete(flying_zebra) # tell the Zebra we are done yield from collect(flying_zebra) # extract data from Zebra # TODO what? if ('e_tomo' in xmotor.name): v_return = min(4, xmotor.velocity.high_limit) yield from bps.mov(xmotor.velocity, v_return) else: # set the "stage speed" yield from bps.mov(xmotor.velocity, 1.0) # TODO wat # set the "stage speed" twice just in case yield from abs_set(xmotor.velocity, 1.0, wait=True) def at_scan(name, doc): scanrecord.current_scan.put(doc['uid'][:6]) scanrecord.current_scan_id.put(str(doc['scan_id'])) scanrecord.current_type.put(md['scaninfo']['type']) scanrecord.scanning.put(True) scanrecord.time_remaining.put((dwell * xnum + 3.8) / 3600) def finalize_scan(name, doc): logscan_detailed('xrf_fly') scanrecord.scanning.put(False) scanrecord.time_remaining.put(0) # TODO remove this eventually? xs = dets_by_name['xs'] # xs = dets_by_name['xs2'] # @subs_decorator([LiveTable([ymotor]), # RowBasedLiveGrid((ynum, xnum), ion.name, row_key=ymotor.name), # LiveZebraPlot()]) # @subs_decorator([LiveTable([ymotor]), LiveGrid((ynum, xnum), sclr1.mca1.name)]) if (ynum == 1): livepopup = LivePlot(xs.channel1.rois.roi01.value.name, xlim=(xstart, xstop)) else: livepopup = LiveGrid((ynum, xnum + 1), xs.channel1.rois.roi01.value.name, extent=(xstart, xstop, ystart, ystop), x_positive='right', y_positive='down') @subs_decorator([livepopup]) # @subs_decorator([LiveGrid((ynum, xnum+1), # xs.channel1.rois.roi01.value.name, # extent=(xstart, xstop, ystart, ystop), # x_positive='right', y_positive='down')]) @subs_decorator({'start': at_scan}) @subs_decorator({'stop': finalize_scan}) # monitor values from xs @monitor_during_decorator([xs.channel1.rois.roi01.value]) @stage_decorator([flying_zebra]) # Below, 'scan' stage ymotor. @run_decorator(md=md) def plan(): # TODO move this to stage sigs for d in flying_zebra.detectors: yield from bps.mov(d.total_points, xnum) # added to "prime" the detector #yield from abs_set(xs.settings.trigger_mode, 'TTL Veto Only') # TODO move this to stage sigs yield from bps.mov(xs.external_trig, True) ystep = 0 for step in np.linspace(ystart, ystop, ynum): yield from abs_set(scanrecord.time_remaining, (ynum - ystep) * (dwell * xnum + 3.8) / 3600.) ystep = ystep + 1 # 'arm' the all of the detectors for outputting fly data for d in flying_zebra.detectors: yield from bps.mov(d.fly_next, True) # print('h5 armed\t',time.time()) yield from fly_each_step(ymotor, step) # print('return from step\t',time.time()) # TODO this should be taken care of by stage sigs ion = flying_zebra.sclr yield from bps.mov(xs.external_trig, False, ion.count_mode, 1) if shutter: final_plan = finalize_wrapper(plan(), bps.mov(shut_b, 'Close')) else: final_plan = plan() return (yield from final_plan)