{ "cells": [ { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "# Lesson 3, Part A: Show the data as it is acquired\n", "\n", "In this lesson, we'll show how to use the tools provided with Bluesky to show the data as it is acquired using both a table representation and a graphical view, as well. These capabilities are provided by using callbacks. In lessons 1 and 2, we wrote our own simple callback to view the documents that come from the RunEngine during execution of a plan. Quickly, the data became too complex for simple viewing.\n", "\n", "The *LiveTable* and *LivePlot* callbacks provide a table and graphical view of the data from the plan. We'll get to those first. Later, we'll show the *BestEffortCallback*, which combines both those callbacks plus a little more. For routine work, we'll want to use *BestEffortCallback* all the time. We'll show how to make that happen so we *set it and forget about it*.\n", "\n", "-------\n", "\n", "**note**: This tutorial expects to find an EPICS IOC on the local network configured as a synApps [xxx](https://github.com/epics-modules/xxx) IOC with prefix `sky:`. A docker container is available to provide this IOC. See this URL for instructions: https://github.com/prjemian/epics-docker/blob/master/n3_synApps/README.md\n", "\n", "Starting with the configuration from lessons 1 and 2, we first group the `import`s together as is common Python practice:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from ophyd import EpicsMotor\n", "from ophyd.scaler import ScalerCH\n", "from bluesky import RunEngine\n", "import bluesky.plans as bp\n", "from apstools.devices import use_EPICS_scaler_channels" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, make a RunEngine (for scanning) and connect our motor and scaler" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "RE = RunEngine({})\n", "\n", "P = \"sky:\"\n", "m1 = EpicsMotor(f\"{P}m1\", name=\"m1\")\n", "scaler = ScalerCH(f\"{P}scaler1\", name=\"scaler\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Reconfigure the scaler for channel names, set the counting time to 0.5 s, and read the scaler values." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "scaler.channels.chan01.chname.put(\"clock\")\n", "scaler.channels.chan02.chname.put(\"I0\")\n", "scaler.channels.chan03.chname.put(\"scint\")\n", "\n", "scaler.preset_time.put(0.4)\n", "\n", "scaler.select_channels(None)\n", "scaler.read()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Showing the data\n", "\n", "In lessons 1 and 2, we wrote a *callback* routine that printed information as the scan prpogressed (that is, we *printed select content from the stream of documents emitted by the RunEngine while executing a plan*). But our callback was simple and we found there is a lot of content in the documents from the RunEngine.\n", "\n", "The [simplest example of a Bluesky callback](https://blueskyproject.io/bluesky/callbacks.html#simplest-working-example) is the `print` function. We want a callback function that understands our data and uses reasonable assumptions to show that data as it is being acquired.\n", "\n", "One method to display our data is in a [table](https://blueskyproject.io/bluesky/callbacks.html#livetable) that updates as the scan progresses. We'll import the `LiveTable` callback from the Bluesky library:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from bluesky.callbacks import LiveTable" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`LiveTable()` shows acquired data as a plan is executed. The argument is the list of detectors to show in the table. First, we'll count the scaler 5 times." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "RE(bp.count([scaler], 5), LiveTable([scaler]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You see columns for the data collection sequence number, the time of collection, and each of the *named* scaler channels. At the end, the short form for the scan's `uid` is shown as well as `scan num` which is a more convenient reference to the scan. The user has control to set or reset `scan num` so do not rely on that number to be unique.\n", "\n", "Next, we'll scan with motor and scaler, as we did in lesson 2, displaying the acquired data in a `LiveTable`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "RE(bp.scan([scaler], m1, 1, 5, 5), LiveTable([m1, scaler]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In addition to the data columns from `count` above, the motor position (both where the motor reported as its position and where the motor was told to go, respectively) are shown." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "---------\n", "There is a callback routine that will plot the data as it is acquired. When starting graphics, it is necessary to first initialize the graphics manager of the display. The setup is specific to the graphics manager. For command line or python program use, see https://blueskyproject.io/bluesky/callbacks.html#aside-making-plots-update-live.\n", "\n", "For jupyter notebooks:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%matplotlib notebook\n", "from bluesky.utils import install_nb_kicker\n", "install_nb_kicker()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We'll import the `LivePlot` callback from the Bluesky library:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from bluesky.callbacks import LivePlot" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Count the scaler 5 times. We'll just plot the `scint` signal." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "RE(bp.count([scaler],num=5), LivePlot(\"scint\"))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To scan, we need to tell `LivePlot` to plot `scint' *vs.* the motor:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "RE(bp.scan([scaler], m1, 1, 5, 5), LivePlot(\"scint\", \"m1\"))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "------------------\n", "Both the table and the plot are very useful diagnostics for routine use. They have been combined in the [*Best-Efforts Callback*](https://blueskyproject.io/bluesky/callbacks.html#best-effort-callback) which provides best-effort plots and visualization for any plan. It uses [user-configurable information](https://blueskyproject.io/bluesky/callbacks.html#hints) that is part of every ophyd device to make reasonable assumptions about what information is appropriate to display in the context of the current plan.\n", "\n", "We'll import the `BestEffortCallback` callback from the Bluesky library:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from bluesky.callbacks.best_effort import BestEffortCallback" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Count the scaler 5 times:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "RE(bp.count([scaler], num=5), BestEffortCallback())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You see both the `LiveTable` and the `LivePlot` output tangled up here in the jupyter notebook. Each is created on demand and then updated as the plan progresses. When executing in a command line environment, the `LivePlot` is shown in a separate window.\n", "\n", "Repeat the same scan, noting that we do not need to inform the callback what to display:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "RE(bp.scan([scaler], m1, 1, 5, 5), BestEffortCallback())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Because this is such a useful tool, we want to make this callback happen all the time. The RunEngine manages a list of such callbacks. We *subscribe* the `BestEffortCallback`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "RE.subscribe(BestEffortCallback())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Repeat the count of the scaler (without adding the callback in the command):" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In jupyter notebook, we can see the `LiveTable` after our scan command. To see the `LivePlot`, we have to look up a few cells, where the plots of scaler channels *vs.* `m1` are shown, our latest data identified by `scan num` in the legend.RE(bp.count([scaler], num=5))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "RE(bp.count([scaler], num=5))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In jupyter notebook, we can see the `LiveTable` after our count command. To see the `LivePlot`, we have to look up a few cells, where the plots of scaler channels *vs.* *time* are shown, our latest data identified by `scan num` in the legend.\n", "\n", "Then, repeat the scan (again, without adding the callback in the command):" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "RE(bp.scan([scaler], m1, 1, 5, 5))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In jupyter notebook, we can see the `LiveTable` after our scan command. Notice that the number of columns displayed is less than when we called `LiveTable` ourselves. To see the `LivePlot`, we have to look up a few cells, where the plots of scaler channels *vs.* `m1` are shown, our latest data identified by `scan num` in the legend." ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## Summary\n", "\n", "We'll show this code as a python program:\n", "\n", "```\n", "#!/usr/bin/env python\n", "\n", "\"lesson 3: Show the data as it is acquired\"\n", "\n", "from ophyd import EpicsMotor\n", "from ophyd.scaler import ScalerCH\n", "from bluesky import RunEngine\n", "import bluesky.plans as bp\n", "from bluesky.callbacks import LiveTable\n", "from bluesky.callbacks import LivePlot\n", "from bluesky.callbacks.best_effort import BestEffortCallback\n", "from apstools.devices import use_EPICS_scaler_channels\n", "\n", "\n", "%matplotlib notebook\n", "from bluesky.utils import install_qt_kicker\n", "install_qt_kicker()\n", "\n", "\n", "RE = RunEngine({})\n", "\n", "P = \"sky:\"\n", "m1 = EpicsMotor(f\"{P}m1\", name=\"m1\")\n", "scaler = ScalerCH(f\"{P}scaler1\", name=\"scaler\")\n", "m1.wait_for_connection()\n", "scaler.wait_for_connection()\n", "scaler.preset_time.put(0.4)\n", "scaler.select_channels(None)\n", "print(scaler.read())\n", "\n", "RE(bp.count([scaler], num=5), LiveTable([scaler]))\n", "RE(bp.scan([scaler], m1, 1, 5, 5), LiveTable([m1, scaler]))\n", "\n", "RE(bp.count([scaler], num=5), LivePlot(\"scint\"))\n", "RE(bp.scan([scaler], m1, 1, 5, 5), LivePlot(\"scint\", \"m1\"))\n", "\n", "RE(bp.count([scaler], num=5), BestEffortCallback())\n", "RE(bp.scan([scaler], m1, 1, 5, 5), BestEffortCallback())\n", "\n", "RE.subscribe(BestEffortCallback())\n", "\n", "RE(bp.count([scaler], num=5))\n", "RE(bp.scan([scaler], m1, 1, 5, 5))\n", "```" ] } ], "metadata": { "kernelspec": { "display_name": "base", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.10" } }, "nbformat": 4, "nbformat_minor": 2 }