{ "cells": [ { "cell_type": "markdown", "id": "159a03cc", "metadata": {}, "source": [ "# Count the scaler\n", "\n", "From *APS Python Training for Bluesky Data Acquisition*.\n", "\n", "**Objective**\n", "\n", "In this notebook, we show how to count the scaler:\n", "\n", "* on the command line\n", "* using ophyd\n", "* using the bluesky RunEngine\n", "\n", "We also show how to:\n", "\n", "* set the counting time\n", "* choose certain channels for data acquisition\n", "\n", "## Start the `instrument` package\n", "\n", "Our instrument package is in the `bluesky` subdirectory here so we add that to the search path before importing it." ] }, { "cell_type": "code", "execution_count": 1, "id": "680c527f", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "/home/prjemian/bluesky/instrument/_iconfig.py\n", "Activating auto-logging. Current session state plus future input saved.\n", "Filename : /home/prjemian/Documents/projects/BCDA-APS/bluesky_training/docs/source/howto/.logs/ipython_console.log\n", "Mode : rotate\n", "Output logging : True\n", "Raw input log : False\n", "Timestamping : True\n", "State : active\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "I Thu-13:07:12 - ############################################################ startup\n", "I Thu-13:07:12 - logging started\n", "I Thu-13:07:12 - logging level = 10\n", "I Thu-13:07:12 - /home/prjemian/bluesky/instrument/session_logs.py\n", "I Thu-13:07:12 - /home/prjemian/bluesky/instrument/collection.py\n", "I Thu-13:07:12 - CONDA_PREFIX = /home/prjemian/.conda/envs/bluesky_2023_2\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Exception reporting mode: Minimal\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "I Thu-13:07:12 - xmode exception level: 'Minimal'\n", "I Thu-13:07:12 - /home/prjemian/bluesky/instrument/mpl/notebook.py\n", "I Thu-13:07:12 - #### Bluesky Framework ####\n", "I Thu-13:07:12 - /home/prjemian/bluesky/instrument/framework/check_python.py\n", "I Thu-13:07:12 - /home/prjemian/bluesky/instrument/framework/check_bluesky.py\n", "I Thu-13:07:12 - /home/prjemian/bluesky/instrument/framework/initialize.py\n", "I Thu-13:07:12 - RunEngine metadata saved in directory: /home/prjemian/Bluesky_RunEngine_md\n", "I Thu-13:07:12 - using databroker catalog 'training'\n", "I Thu-13:07:12 - using ophyd control layer: pyepics\n", "I Thu-13:07:12 - /home/prjemian/bluesky/instrument/framework/metadata.py\n", "I Thu-13:07:12 - /home/prjemian/bluesky/instrument/epics_signal_config.py\n", "I Thu-13:07:12 - Using RunEngine metadata for scan_id\n", "I Thu-13:07:12 - #### Devices ####\n", "I Thu-13:07:12 - /home/prjemian/bluesky/instrument/devices/area_detector.py\n", "I Thu-13:07:12 - /home/prjemian/bluesky/instrument/devices/calculation_records.py\n", "I Thu-13:07:14 - /home/prjemian/bluesky/instrument/devices/fourc_diffractometer.py\n", "I Thu-13:07:15 - /home/prjemian/bluesky/instrument/devices/ioc_stats.py\n", "I Thu-13:07:15 - /home/prjemian/bluesky/instrument/devices/kohzu_monochromator.py\n", "I Thu-13:07:15 - /home/prjemian/bluesky/instrument/devices/motors.py\n", "I Thu-13:07:15 - /home/prjemian/bluesky/instrument/devices/noisy_detector.py\n", "I Thu-13:07:15 - /home/prjemian/bluesky/instrument/devices/scaler.py\n", "I Thu-13:07:16 - /home/prjemian/bluesky/instrument/devices/shutter_simulator.py\n", "I Thu-13:07:16 - /home/prjemian/bluesky/instrument/devices/simulated_fourc.py\n", "I Thu-13:07:16 - /home/prjemian/bluesky/instrument/devices/simulated_kappa.py\n", "I Thu-13:07:16 - /home/prjemian/bluesky/instrument/devices/slits.py\n", "I Thu-13:07:16 - /home/prjemian/bluesky/instrument/devices/sixc_diffractometer.py\n", "I Thu-13:07:16 - /home/prjemian/bluesky/instrument/devices/temperature_signal.py\n", "I Thu-13:07:16 - #### Callbacks ####\n", "I Thu-13:07:16 - /home/prjemian/bluesky/instrument/callbacks/spec_data_file_writer.py\n", "I Thu-13:07:16 - #### Plans ####\n", "I Thu-13:07:16 - /home/prjemian/bluesky/instrument/plans/lup_plan.py\n", "I Thu-13:07:16 - /home/prjemian/bluesky/instrument/plans/peak_finder_example.py\n", "I Thu-13:07:16 - /home/prjemian/bluesky/instrument/utils/image_analysis.py\n", "I Thu-13:07:16 - #### Utilities ####\n", "I Thu-13:07:16 - writing to SPEC file: /home/prjemian/Documents/projects/BCDA-APS/bluesky_training/docs/source/howto/20230413-130716.dat\n", "I Thu-13:07:16 - >>>> Using default SPEC file name <<<<\n", "I Thu-13:07:16 - file will be created when bluesky ends its next scan\n", "I Thu-13:07:16 - to change SPEC file, use command: newSpecFile('title')\n", "I Thu-13:07:16 - #### Startup is complete. ####\n" ] } ], "source": [ "import pathlib, sys\n", "sys.path.append(str(pathlib.Path.home() / \"bluesky\"))\n", "from instrument.collection import *" ] }, { "cell_type": "markdown", "id": "a1cd6b53", "metadata": {}, "source": [ "## A closer look at the EPICS scaler\n", "\n", "A scaler is a device that counts digital pulses from a pulse detector such\n", "as a scintillation counter or from photodiodes or ionization chamber with\n", "pulse chain electronics. Scalers have\n", "many channels, some of which might have no associated detector. Our scaler\n", "(`scaler1`) is a simulated device that records a random number of pulses in \n", "each channel. We are only interested in the channels that have names provided\n", "by users in the GUI screens. In this control screen for our scaler, only a few of the\n", "channels are named:\n", "\n", "![`scaler` GUI](../_static/scaler.png \"`scaler1` GUI\")" ] }, { "cell_type": "markdown", "id": "11968d36", "metadata": {}, "source": [ "Let's configure `scaler1` to report only the `scint` and `I0` channels (plus the count time channel which will *always* be included). Keep in mind that the argument to this function is a Python list, so the channel names must be enclosed with `[]`. The function does not return a result. If something *is* printed, there is an error to be fixed." ] }, { "cell_type": "code", "execution_count": 2, "id": "2ba05627", "metadata": {}, "outputs": [], "source": [ "scaler1.select_channels([\"scint\", \"I0\"])" ] }, { "cell_type": "markdown", "id": "7cc70fa9", "metadata": {}, "source": [ "The _easiest_ way to count the `scaler` object is to use the [%ct](https://blueskyproject.io/bluesky/magics.html?highlight=label#taking-a-reading-using-ct-post-v1-3-0) bluesky magic command, which counts all objects with the `detectors` label.\n", "\n", "Note that the various magic commands are only available from the command line, not for use in a bluesky plan function." ] }, { "cell_type": "code", "execution_count": 3, "id": "44dc31e4", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[This data will not be saved. Use the RunEngine to collect data.]\n", "noisy 0.0\n", "I0 7.0\n", "scint 9.0\n", "scaler1_time 1.6\n" ] } ], "source": [ "%ct" ] }, { "cell_type": "markdown", "id": "4d4b4ac5", "metadata": {}, "source": [ "Compare with the reading when _all_ channels are selected.\n", "\n", "NOTE: To report _all_ named channels, call `.select_channels()` with empty parentheses." ] }, { "cell_type": "code", "execution_count": 4, "id": "6d08eb68", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[This data will not be saved. Use the RunEngine to collect data.]\n", "noisy 0.0\n", "timebase 16000000.0\n", "I0 8.0\n", "scint 7.0\n", "I000 8.0\n", "I00 7.0\n", "roi1 8.0\n", "scaler1_time 1.6\n" ] } ], "source": [ "scaler1.select_channels()\n", "%ct" ] }, { "cell_type": "markdown", "id": "f843f3a6", "metadata": {}, "source": [ "Now, select just the two channels again before continuing:" ] }, { "cell_type": "code", "execution_count": 5, "id": "f9b00d85", "metadata": {}, "outputs": [], "source": [ "scaler1.select_channels([\"scint\", \"I0\"])" ] }, { "cell_type": "markdown", "id": "7e873a8f", "metadata": {}, "source": [ "As noted before, the `%ct` command is only available from the command shell.\n", "\n", "### use ophyd to count the scaler\n", "\n", "We should learn how to use the underlying Python code to do the same steps.\n", "\n", "The first step is to use pure ophyd methods to count and report, then use a bluesky plan to do the same thing. The ophyd methods are `trigger`, `wait`, and `read`. The `trigger` and `wait` methods can be chained together:" ] }, { "cell_type": "code", "execution_count": 6, "id": "cfa9796a", "metadata": {}, "outputs": [], "source": [ "scaler1.trigger().wait()" ] }, { "cell_type": "markdown", "id": "1772e927", "metadata": {}, "source": [ "Technically, we should `stage` and `unstage` the object. **We'll use staging to control the count time of the scaler.**\n", "\n", "The ophyd [`.stage()` method](https://blueskyproject.io/ophyd/generated/ophyd.device.BlueskyInterface.stage.html?highlight=stage#ophyd.device.BlueskyInterface.stage) prepares the object for its `.trigger()` method, while the `.unstage()` method returns the object's settings to the previous state before the `.stage()` method was called." ] }, { "cell_type": "code", "execution_count": 7, "id": "06d4dbc5", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[Channels(prefix='gp:scaler1', name='scaler1_channels', parent='scaler1', read_attrs=['chan02', 'chan02.s', 'chan04', 'chan04.s'], configuration_attrs=['chan02', 'chan02.chname', 'chan02.preset', 'chan02.gate', 'chan04', 'chan04.chname', 'chan04.preset', 'chan04.gate']),\n", " ScalerCH(prefix='gp:scaler1', name='scaler1', read_attrs=['channels', 'channels.chan02', 'channels.chan02.s', 'channels.chan04', 'channels.chan04.s', 'time'], configuration_attrs=['channels', 'channels.chan02', 'channels.chan02.chname', 'channels.chan02.preset', 'channels.chan02.gate', 'channels.chan04', 'channels.chan04.chname', 'channels.chan04.preset', 'channels.chan04.gate', 'count_mode', 'delay', 'auto_count_delay', 'freq', 'preset_time', 'auto_count_time', 'egu'])]" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "scaler1.stage()\n", "scaler1.trigger().wait()\n", "scaler1.unstage()" ] }, { "cell_type": "markdown", "id": "0bee4409", "metadata": {}, "source": [ "Let's find out what happens when `scaler1` is staged. That's controlled by the contents of a Python dictionary `.stage_sigs`:" ] }, { "cell_type": "code", "execution_count": 8, "id": "7c0e3af2", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "OrderedDict()" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "scaler1.stage_sigs" ] }, { "cell_type": "markdown", "id": "fba12e4a", "metadata": {}, "source": [ "It's empty, so nothing has been preconfigured for us. Let's make sure that we get to pick the *counting time* (the time to accumulate pulses in the various channels), say 2.0 seconds, when we count here." ] }, { "cell_type": "code", "execution_count": 9, "id": "fff4d63c", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "OrderedDict([('preset_time', 2)])" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "scaler1.stage_sigs[\"preset_time\"] = 2\n", "scaler1.stage_sigs" ] }, { "cell_type": "markdown", "id": "0b670908", "metadata": {}, "source": [ "Show the counting time *before* we count, then `stage`, `trigger`, `wait`, `read`, `unstage`, then finally show the counting time *after* we count:" ] }, { "cell_type": "code", "execution_count": 10, "id": "51763cdb", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Scaler configured to count for 1.5s\n", "OrderedDict([('I0', {'value': 12.0, 'timestamp': 1681409246.542347}), ('scint', {'value': 10.0, 'timestamp': 1681409246.542347}), ('scaler1_time', {'value': 2.1, 'timestamp': 1681409244.179794})])\n", "Scaler configured to count for 1.5s\n" ] } ], "source": [ "print(f\"Scaler configured to count for {scaler1.preset_time.get()}s\")\n", "scaler1.stage()\n", "scaler1.trigger().wait()\n", "print(scaler1.read())\n", "scaler1.unstage()\n", "print(f\"Scaler configured to count for {scaler1.preset_time.get()}s\")" ] }, { "cell_type": "markdown", "id": "00df9a9c", "metadata": {}, "source": [ "The report from `.read()` includes both values and timestamps (in seconds since the Python [time](https://docs.python.org/3/library/time.html) epoch, UTC). The structure is a Python dictionary. This is the low-level method used to collect readings from any ophyd device. We had to `print()` this since the return result from a command within a sequence is not returned at the end of the sequence, just the return result of the *final* command in the sequence.\n", "\n", "See that the scaler counted for 2.1 seconds (a small bug in the scaler simulator it seems, always adds .1 to the count time!). But before staging, the scaler was configured for 1.0 seconds, and after unstaging, the scaler returned to that value.\n", "\n", "**That's how to control the counting time *for a scaler*.** (Area detectors use different terms. More on that later.)" ] }, { "cell_type": "markdown", "id": "53a090b3", "metadata": {}, "source": [ "
\n", " about scaler1_time\n", "\n", " Of note is the key `scaler1_time` which is the name of the ophyd symbol `scaler1.time` as returned by `scaler1.time.name`:\n", "\n", " In [21]: scaler1.time.name\n", " Out [21]: 'scaler1_time'\n", "\n", "
" ] }, { "cell_type": "markdown", "id": "4cfb1d7c", "metadata": {}, "source": [ "### use bluesky (the package) to count the scaler\n", "\n", "Now, use the bluesky [RunEngine](https://blueskyproject.io/bluesky/generated/bluesky.run_engine.RunEngine.html?highlight=runengine#bluesky.run_engine.RunEngine) (`RE`) to count `scaler1`. We'll use the bluesky plan ([bp](https://blueskyproject.io/bluesky/plans.html?highlight=count#pre-assembled-plans)) called [count()](https://blueskyproject.io/bluesky/generated/bluesky.plans.count.html#bluesky.plans.count). To be consistent with the result returned from `%ct`, we'll include the `noisy` detector." ] }, { "cell_type": "code", "execution_count": 11, "id": "bd32cb1d", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "\n", "Transient Scan ID: 932 Time: 2023-04-13 13:07:26\n", "Persistent Unique Scan ID: 'de519ff4-fb72-4f15-8ee3-d0b11db127da'\n", "New stream: 'label_start_motor'\n", "New stream: 'primary'\n", "+-----------+------------+------------+------------+------------+\n", "| seq_num | time | noisy | I0 | scint |\n", "+-----------+------------+------------+------------+------------+\n", "| 1 | 13:07:28.9 | 0.00000 | 12 | 8 |\n", "+-----------+------------+------------+------------+------------+\n", "generator count ['de519ff4'] (scan num: 932)\n" ] }, { "data": { "text/plain": [ "('de519ff4-fb72-4f15-8ee3-d0b11db127da',)" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "RE(bp.count([scaler1,noisy]))" ] }, { "cell_type": "markdown", "id": "c7c2d5a3", "metadata": {}, "source": [ "There are many ways to view data from bluesky runs. The most recent run can be picked from the database using the Python reference for *the last item in the list*: `[-1]`. We need the `v2` version of the database to access the `.primary` data stream.\n", "\n", "We'll pick one simple to view the run data, as an [xarray](http://xarray.pydata.org/) table since it is easy to display such structured content in a Jupyter notebook. (We could just as easily have displayed the data as a [dask](https://dask.org/) table by replacing `.read()` with `.to_dask()`.)" ] }, { "cell_type": "code", "execution_count": 12, "id": "74aaf4cb", "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "
<xarray.Dataset>\n",
       "Dimensions:       (time: 1)\n",
       "Coordinates:\n",
       "  * time          (time) float64 1.681e+09\n",
       "Data variables:\n",
       "    noisy         (time) float64 0.0\n",
       "    I0            (time) float64 12.0\n",
       "    scint         (time) float64 8.0\n",
       "    scaler1_time  (time) float64 2.1
" ], "text/plain": [ "\n", "Dimensions: (time: 1)\n", "Coordinates:\n", " * time (time) float64 1.681e+09\n", "Data variables:\n", " noisy (time) float64 0.0\n", " I0 (time) float64 12.0\n", " scint (time) float64 8.0\n", " scaler1_time (time) float64 2.1" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cat[-1].primary.read()" ] }, { "cell_type": "markdown", "id": "94e8851a", "metadata": {}, "source": [ "As a last action in this section, use the [listruns()](https://bcda-aps.github.io/apstools/latest/api/_utils.html#apstools.utils.list_runs.listruns) command from *apstools* to show the (default: 20) most recent runs in the database. The table shows a short version of the run's unique identifier (`short_uid`), and other more obvious columns of information, truncated to avoid lengthy output. The name of the [databroker *catalog*](https://blueskyproject.io/databroker/index.html) (`class_2021_03`) is shown before the table." ] }, { "cell_type": "code", "execution_count": 13, "id": "d8f0d076", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/home/prjemian/.conda/envs/bluesky_2023_2/lib/python3.10/site-packages/databroker/queries.py:89: PytzUsageWarning: The zone attribute is specific to pytz's interface; please migrate to a new time zone provider. For more details on how to do so, see https://pytz-deprecation-shim.readthedocs.io/en/latest/migration.html\n", " timezone = lz.zone\n" ] }, { "data": { "text/plain": [ "======= =================== ========= ==========================\n", "scan_id time plan_name detectors \n", "======= =================== ========= ==========================\n", "932 2023-04-13 13:07:26 count ['scaler1', 'noisy'] \n", "931 2023-04-13 13:06:44 count ['scaler1', 'noisy'] \n", "930 2023-04-11 18:48:34 scan ['noisy', 'th_tth_permit']\n", "929 2023-04-11 18:48:09 scan ['noisy', 'th_tth_permit']\n", "928 2023-04-11 18:47:57 scan ['noisy', 'th_tth_permit']\n", "927 2023-04-11 18:47:42 scan ['noisy', 'th_tth_permit']\n", "926 2023-04-11 18:47:33 scan ['noisy', 'th_tth_permit']\n", "925 2023-04-11 18:46:11 scan ['noisy', 'th_tth_permit']\n", "924 2023-04-11 18:45:11 scan ['noisy', 'th_tth_permit']\n", "923 2023-04-11 18:44:49 scan ['noisy', 'th_tth_permit']\n", "922 2023-04-11 18:42:15 scan ['noisy', 'th_tth_permit']\n", "921 2023-04-11 18:41:39 scan ['noisy', 'th_tth_permit']\n", "920 2023-04-11 18:41:23 scan ['noisy', 'th_tth_permit']\n", "919 2023-04-11 18:41:15 scan ['noisy', 'th_tth_permit']\n", "918 2023-04-11 18:40:39 scan ['noisy', 'th_tth_permit']\n", "917 2023-04-11 18:39:11 scan ['noisy', 'th_tth_permit']\n", "916 2023-04-11 18:38:51 scan ['noisy', 'th_tth_permit']\n", "915 2023-04-11 18:38:36 scan ['noisy', 'th_tth_permit']\n", "914 2023-04-11 18:38:27 scan ['noisy', 'th_tth_permit']\n", "913 2023-04-11 18:36:37 scan ['noisy', 'th_tth_permit']\n", "======= =================== ========= ==========================" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" }, { "name": "stderr", "output_type": "stream", "text": [ "CA.Client.Exception...............................................\n", " Warning: \"Virtual circuit disconnect\"\n", " Context: \"zap:45763\"\n", " Source File: ../cac.cpp line 1237\n", " Current Time: Thu Apr 13 2023 16:55:17.301061478\n", "..................................................................\n", "CA.Client.Exception...............................................\n", " Warning: \"Virtual circuit disconnect\"\n", " Context: \"zap:5064\"\n", " Source File: ../cac.cpp line 1237\n", " Current Time: Thu Apr 13 2023 16:55:30.733410696\n", "..................................................................\n" ] } ], "source": [ "listruns()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "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": 5 }