{
"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": [
"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",
"
<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