#!/usr/bin/env python # coding: utf-8 # # Good Night Bluesky: Running your overnight script # # In this notebook you will: # # * Perform final checks prior to executing the tested overnight plan # * Additional modifications (transient metadata) # * How to add ancillary signals that are important (baseline versus primary data streams) # * Experience common mistakes that trip up experienced bluesky users # # # **BONUS Material** # Located after our objectives. It is in the form of Q & A. # # Recommend Prerequisites: # * [Hello Python and Jupyter](./Hello%20Python%20and%20Jupyter.ipynb) # * [Good Morning Bluesky](./Good%20Morning%20Bluesky.ipynb) # * [Good Morning Afternoon](./Good%20Afternoon%20Bluesky.ipynb) # In[ ]: #pip install -U --pre databroker[all] # Hosted by Andi # ## For Bluesky Session Continuity # Run the next cells to make the ipython kernel match the previous session # # In[ ]: get_ipython().run_line_magic('run', '-i gm_user/user_profile.py') # In[ ]: get_ipython().run_line_magic('run', '-i gm_user/user_startup.py') get_ipython().run_line_magic('run', '-i gm_user/user_startup_night.py') # In[ ]: md_info() # In[ ]: temperature.readback.get() # ## Let's collect a good dataset for our current temperature # # First, open the file [gm_user/user_startup_night.py](./gm_user/user_startup_night.py) and view it side by side with this notebook. # # It's pretty clear that we should run `RE(one_temperature())` and this is a good first "real" test of our functions. # # # ```python # RE(one_temperature()) # ``` # # # But we can test how to put this together with our planned overnight script: # In[ ]: RE(one_temperature()) # **But** what if we want to try to automate some processing or take better advantage of data access tools like `databroker` or its replacement `tiled`? # # Let's look at **line 22** in [gm_user/user_startup_night.py](./gm_user/user_startup_night.py). # #
# Tip: Transient metadata is possible on a per scan basis for any built in bluesky plan. #
# #
line 22 # # Not the new agurment utilized in `count()`. # # ```python # md={'purpose':'analyze'} # ``` # # * The python dicitionary can be as complex as you like. # * Note that user defined `md` key names (`purpose`) are not enforced/checked # #
# Caution: If you reply heavily on the keys then creating custom plans are the best way to avoid typographical errors. #
# # [Read about more about bluesky metadata](https://blueskyproject.io/bluesky/metadata.html). # #
# # # In[ ]: db[-1].start # In[ ]: # In[ ]: # Let's test how to put this together with our planned overnight script: # In[ ]: check_limits( bpp.pchain(one_temperature(), my_experiment([41, 42])) ) # **NOW** edit the cell below to pass the pchained plans to the RE. # In[ ]: ( bpp.pchain(one_temperature(), my_experiment([41, 42])) ) # ## Recording ancillary signals # Is the tempeature being recorded? # # We know that it is not. The next tutorial deals with data access. However, two simple verifications utilize the start and stop documents. # In[ ]: db[-1].start # In[ ]: db[-1].stop # In[ ]: # ### Available Data Streams for Ancillary Signals # #
# Tip: Using the recommended data streams guarantees that the key names are always consistent. #
# # # It is recommended to record all data in data streams: # * primary # * [baseline](https://blueskyproject.io/bluesky/tutorial.html#baseline-readings-and-other-supplemental-data) # * monitors # * flyers # # # # ### Try adding `temperature` to the `primary` datastream # # #
Copy Paste Solution # # ```python # # RE(count([noisy_det, temperature], 5) ) # # ``` #
# # In[ ]: ### Add temperature to the primary datastream RE(count([noisy_det, ], 5) ) # ### Configuring and using the baseline stream # * check [gm_user/user_profile.py](./gm_user/user_profile.py) for initialization # * baseline is part of `SupplenmentalData` (`sd`) # * like the primary detectors, baseline is a python list # # In[ ]: sd # In[ ]: sd.baseline =[temperature] sd.baseline # In[ ]: RE(count([noisy_det], 5) ) #
# Tip: Baseline print to screen can be turned OFF - especially helpful when there are >10 devices. #
# # `BestEffortCallbacks` (`bec`) is used to control some of the bluesky interface features # In[ ]: # OPTIONAL, explore bec by uncommenting the line directly below # dir(bec) # In[ ]: bec.disable_baseline() # In[ ]: RE(count([noisy_det], 5) ) # In[ ]: db[-1].table('baseline') # Hosted by Josh # ## Common mistakes made by expert users # * switching shifts with team members # * a little tired # # Some mistakes are difficult to understand immediately. Let's have a look at a couple common ones and how troubleshoot. # ### **#1** - adding to `sd.baseline` # In[ ]: sd.baseline.append([motor1, motor2]) # In[ ]: RE(count([noisy_det], 2) ) # In[ ]: sd.baseline # #
Copy Paste Solution # # # ```python # sd.baseline =[temperature] # sd.baseline.extend([motor1, motor2]) # # ``` #
# In[ ]: # Try again after entering your **solution** above # In[ ]: RE(count([noisy_det], 2) ) # ## **#2** - using a long list of detectors # # ### **#2a** # # Code is easier to read when you make a long python list of detectors and just use that variable. # # ```python # dets = [det, noisy_det, temperature, motor1.readback, det2] # RE(scan(dets, motor, -5, 5, 3)) # ``` # # # In[ ]: RE(scan(dets, motor, -5, 5, 3)) #
Hint # # Let's look at **line 37** in [gm_user/user_startup_night.py](./gm_user/user_startup_night.py). # # # ```python # my_dets # ``` # #
# In[ ]: my_dets # In[ ]: dets = my_dets dets is my_dets # ### **#2b** #
Copy Paste Solution # # # ```python # RE(scan(dets, motor, -5, 5, 3)) # # ``` #
# In[ ]: RE(scan([dets], motor, -5, 5, 11)) # In[ ]: # In[ ]: dets # ### **#2c** # # And this is also a **trap** if you use **another common DAQ** # # ``` # ascan motor start stop steps time # # ``` # #
Copy Paste Solution # # # ```python # RE(scan([det], motor, -5, 5, 3)) # # ``` # # # In[ ]: RE(scan(det, motor, -5, 5, 3)) # In[ ]: # ## **#3** **the other DAQ trap** # # ### **#3a** # #
Copy Paste Solution # # # ```python # # RE(scan([det], motor, -5, 5, 3)) # # ``` #
# In[ ]: RE(scan( motor, -5, 5, 3)) # In[ ]: # ### **#3b** # #
Copy Paste Solution # # # ```python # # RE(scan(my_dets, motor, -5, 5, 3)) # # # ``` # # Counting time is handled per detector which makes asynchronous collection possible (more flexibility). #
# In[ ]: RE(scan( motor, -5, 5, 3, 1)) # In[ ]: # ### **#3c** # #
Copy Paste Solution # # # ```python # # RE(scan(my_dets, motor, -5, 5, 3)) # # # ``` # # Counting time is handled per detector which makes asynchronous collection possible (more flexibility). #
# In[ ]: RE(scan(my_dets, motor, -5, 5, 3, 1)) # In[ ]: # ## Questions for the above? # ## Questions in general about bluesky data collection? # In[ ]: # In[ ]: # Self-guided Tour # # ## BONUS / Q&A # # **Q1** What other pre-assembled plans are avaialbe with blueksy "out-of-the-box" installation? How do I make a 2D scan? # # **A1** Check the docs: # * [pre-assembeled plans](https://blueskyproject.io/bluesky/plans.html#pre-assembled-plans) # * [other essential stub plans](https://blueskyproject.io/bluesky/plans.html#stub-plans) # # In[ ]: # **Q2** Why is the scan so noisy when I know the detector has a low noise level? # # **A2** Realy motors are not perfect and electrical ground loops are difficult to remove. Try: # * plotting the detector signal as a function of `motor.setpoint` # * switch to absolute scans # # # In[ ]: # **Q3** Why is `summarize_plan(rel_scan([det], motor, -1, 1, 21))` incorrect # # **A3** `bluesky.simulators` use the current live position of all motors. Currently, relative scans generate the motor trajectory as the scan proceeds in the RE. For best results, you can fake relative scans: # # ```python # my_motor_pos = motor.setpoint.get() # yield from scan([det], motor, my_motor_pos-1, my_motor_pos+1, 21) # # ``` # In[ ]: # **Q4** What if I want to control `.get()` like the rest of bluesky's plan generators inside my custom plan? **NoteL** `.get()` emmits a query to the network inside `summarize_plan()`. # # **A4** Use `bps.rd()` # ```python # my_variable = yield from bps.rd(top_level_device.device_child_to_return_single_value) # # # ``` # In[ ]: # **Q5** How do perform a line scan on multiple motors (or through reciprocal space) on multiple "motors"? # # **A5** Using # ```python # scan? # ``` # # it is possible to see that you can scan N motors. Below N=2. # # ```python # # RE(scan([det], motor, -5, 5, motor1, -5, 5, 3)) # # ``` # # Not that only the first motor is plotted in the LivePlot. # In[ ]: # In[ ]: # **Q6** What about a theta-2theta scan? # # **A6** Alter the scan arguments # # # ```python # twotheta = motor # theta = motor1 # # # # RE(scan([det], twotheta, -5, 5, theta, -5/2, 5/2, 3)) # # ``` # # Not that only the first motor is plotted in the LivePlot. # In[ ]: # In[ ]: # **Q7** How do I control what detectors and motors are plotted and included in the table? # # **A7** Bluesky hints. Each signal has a "kind". The options are: # # - "normal" --> recorded but not displayed in the user interface during collection # - "omit" --> not recorded # - "config" --> record as metadata in descriptors (just once per scan) # - "hinted" --> plot in LivePlot and LiveTable # # # # # ```python # print(f'{temperature.setpoint.kind=}') # print(f'{temperature.readback.kind=}') # # temperature.setpoint.kind='hinted' # # print(f'\n{temperature.setpoint.kind=}') # ``` # # In[ ]: # **Q8** Bluesky doesn't have device I want to record or control. What is the fastest way to get going? # # **A8** ophyd EpicsSignal or EpicsSignalRO (**R**ead**O**nly) may be set up in 1 line. As long as you do not need bluesky to check many aspects of how the signal is used to ensure performance, then this should be a decent quick fix. # # ```python # from ophyd.signal import EpicsSignal # my_signal = EpicsSignal('sting_representing_EPICS_PV', name="my_signal") # ``` # # In terms of EPICS or pyepics, the usage would be: # ``` # pyepics.caget('sting_representing_EPICS_PV') # ``` # In[ ]: # **Q9** How do I make a 2D mesh-like scan for imaging? # # **A9** There are a few different pre-assembled plans. Check the [docs](https://blueskyproject.io/bluesky/plans.html) for the full list. # # ```python # RE(grid_scan([det], motor, -1, 1, 9, # motor1, -1, 1, 9, # True)) # ``` # # Last argument is for "snaking" (True or False). # ```python # grid_scan?? # # ``` # # # In[ ]: bec.disable_table() RE(grid_scan([det], motor, -1, 1, 9, motor1, -1, 1, 9, True)) # **Q10** Are there additional inspection/simulation plans for 2D imaging scans? # # **A10** Yes. # # ```python # # from ophyd.sim import motor, motor1, det # from bluesky.simulators import summarize_plan, check_limits, plot_raster_path # from bluesky.plans import * # # plan = grid_scan([det], motor, -1, 1, 9, # motor1, -1, 1, 9, # True) # plot_raster_path(plan, 'motor', 'motor1', probe_size=.1) # # ``` # In[ ]: # from ophyd.sim import motor, motor1, det # from bluesky.simulators import summarize_plan, check_limits, plot_raster_path # from bluesky.plans import * plan = grid_scan([det], motor, -1, 1, 9, motor1, -1, 1, 9, True) plot_raster_path(plan, 'motor', 'motor1', probe_size=.1) # Remember, plans are generators so the below will fail. # In[ ]: plan = grid_scan([det], motor, -1, 1, 9, motor1, -1, 1, 9, False) plot_raster_path(plan, 'motor', 'motor1', probe_size=.1) # In[ ]: # In[ ]: #
# Watch Out for a DAQ Trap: Relative scans producing image are labeled with relative positions. #
# In[ ]: def see_diff_with_relative(): yield from mv(motor, -1, motor1, -1) yield from rel_grid_scan([det], motor, -1, 1, 9, motor1, -1, 1, 9, True) bec.disable_table() RE(see_diff_with_relative()) # In[ ]: # In[ ]: