#!/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[ ]: