{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# The synApps `sscan` and `SscanRecord`\n", "\n", "The synApps `sscan` record is used to measure scans of detector(s) *v*. positioner(s). \n", "\n", "**Goals**: Demonstrate use of the `sscan` record with Bluesky.\n", "\n", "1. Press SCAN button of a preconfigured scan.\n", "2. Same example as section 1, but uses\n", " [SscanRecord](https://bcda-aps.github.io/apstools/latest/api/synApps/_sscan.html)\n", " instead of\n", " [EpicsSignal](https://blueskyproject.io/ophyd/user/tutorials/single-PV.html).\n", "3. Setup the same scan from bluesky.\n", "4. Add the scan data as a bluesky run.\n", "\n", "This notebook is intended for those who are familiar with EPICS and its motor, scaler, and sscan records but are new to Bluesky.\n", "\n", "## sscan record configuration\n", "\n", "Consider this `sscan` record (`gp:scan1`) which is configured for a step scan of\n", "`scaler` (`gp:scaler1`) *vs.* `motor` (`gp:m1`).\n", "\n", "Figure (1a) shows `gp:scan1` configured to step scan motor `m1` from -1.2 to\n", "1.2 in 21 points, collecting counts from scaler `gp:scaler1` channels 2 & 4\n", "(`I0` & `diode`, respectively). Figure (1b) shows `gp:scaler1` configured\n", "with a counting time of 0.2 seconds per point and several detector channels.\n", "\n", "Figure (1a) scan | Figure (1b) scaler\n", "--- | ---\n", "![scan1 setup](./sscan-scaler-v-motor.png) | ![scaler1 setup](./scaler16.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The *SCAN* button is connected to EPICS PV `gp:scan1.EXSC`. The scan starts\n", "when SCAN is pressed. (Try it.) When the *SCAN* button is pressed, the GUI\n", "sends `1` to the EPICS PV and the scan starts. When the scan finishes (or\n", "aborts), the value of the PV changes to `0` which is then sent back to the GUI." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Python setup\n", "\n", "All these examples need this minimum setup. The first example will not need\n", "any databroker catalog. The EPICS IOC has a prefix of `gp:`." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import bluesky\n", "import bluesky.plan_stubs as bps\n", "\n", "RE = bluesky.RunEngine()\n", "IOC = \"gp:\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1. Press SCAN button of a preconfigured scan\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Above, we said the scan can be run by pressing the *SCAN* button in the GUI. Let's do the same thing with bluesky. That is, we'll have bluesky press the *SCAN* button and then wait for the scan to end.\n", "\n", "First, connect with the EPICS PV of the *SCAN* button (`gp:scan1.EXSC`) using\n", "`ophyd.EpicsSignal`. Once the object is connected with the EPICS PV, show the\n", "current value of the PV." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n" ] } ], "source": [ "from ophyd import EpicsSignal\n", "\n", "scan_button = EpicsSignal(f\"{IOC}scan1.EXSC\", name=\"scan_button\")\n", "scan_button.wait_for_connection()\n", "print(f\"{scan_button.get()}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Write a bluesky plan that starts the scan (by pushing the button) and watches\n", "the button's value until it reports the scan ended. As written above, we know\n", "exactly when the scan has ended when the button value changes from `1` to `0`.\n", "\n", "We use `bps.sleep()` here to allow the `RE` to attend to its other responsibilities while waiting, rather than `time.sleep()` which would suspend all python activities (i.e. block the `RE`)." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "def run_sscan():\n", " yield from bps.mv(scan_button, 1)\n", "\n", " # Wait for the scan to end with a polling loop.\n", " while scan_button.get() != 0:\n", " yield from bps.sleep(0.1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "NOTE: The polling loop, used by this simple example, is not recommended for bluesky plans.\n", "\n", "
\n", "Use a Status object instead of a polling loop.\n", "\n", "Polling loops are discouraged because:\n", "\n", "- they are not efficient (involving waiting periods of empirical duration)\n", "- they do not handle timeouts, settling times, Python exceptions\n", "- the `RE` already has a main event loop\n", "- we often want to watch *multiple* \n", " [signals](https://blueskyproject.io/ophyd/user/tutorials/single-PV.html#set)\n", " with different update rates or complex logic\n", "\n", "Instead of polling the value of an EpicsSignal, it is more efficient to start an\n", "EPICS CA monitor on the EpicsSignal. When new values of the signal are reported\n", "by EPICS as CA monitor events, a designated *callback* function is called to respond.\n", "\n", "Ophyd\n", "[Status](https://blueskyproject.io/ophyd/ser/generated/ophyd.status.Status.html#ophyd.status.Status)\n", "objects track actions, like moving and triggering, that could take some time to\n", "complete. These ophyd status objects have additional features such as timeout\n", "and settling time.\n", "\n", "See the ophyd\n", "[tutorial](https://blueskyproject.io/tutorials/Ophyd/02%20-%20Complex%20Behaviors%20%28Set%20and%20Multiple%20PVs%29.html#adding-a-set-method-to-device)\n", "for use of a status object with a `.set()` method (which is the method called by\n", "`bps.mv()`). It is not intuitive to use `bps.mv(scan_button, 1)`\n", "here. That would only trigger the scan to *start* but would not wait for the\n", "scan button value to return to `0`. We also want to wait until the scan is\n", "complete.\n", "\n", "Instead, `bps.trigger(ophyd_object)` tells the ophyd object (such as a scaler or\n", "area detector) to acquire its data. This triggers the ophyd object (by calling\n", "the object's `.trigger()` method which returns an ophyd Status object) and\n", "(optionally) waits for that status object to report it is done.\n", "\n", "We can add such a `.trigger()` method if we create a subclass of `EpicsSignal`.\n", "The `.trigger()` method is called from a bluesky plan using\n", "`bps.trigger(scan_button)`.\n", "\n", "In our `.trigger()` method, our status object is built from the\n", "[SubscriptionStatus](https://blueskyproject.io/ophyd/user/generated/ophyd.status.SubscriptionStatus.html#ophyd-status-subscriptionstatus)\n", "class, which manages the subscription to CA monitor events. The designated\n", "function receives `old_value` and `value` from a CA monitor event and returns a\n", "boolean value. Once the scan ends, the status object is set to `done=True` and\n", "`success=True` and the CA monitor subscription is removed.\n", "\n", "Here is the code for the scan button, written with a status object:\n", "\n", "```py\n", "from bluesky import plan_stubs as bps\n", "from ophyd import EpicsSignal\n", "from ophyd.status import SubscriptionStatus\n", "\n", "class MySscanScanButton(EpicsSignal):\n", " timeout = 60\n", "\n", " def trigger(self):\n", " \"\"\"\n", " Start the scan and return status to monitor completion.\n", "\n", " This method is called from 'bps.trigger(scan_button, wait=True)'.\n", " \"\"\"\n", "\n", " def just_ended(old_value, value, **kwargs):\n", " \"\"\"Returns True when scan ends (signal changes from 1 to 0).\"\"\"\n", " return old_value == 1 and value == 0\n", "\n", " # Starts an EPICS CA monitor on this signal and calls 'just_ended()' with updates.\n", " # Once the status object is set to done, the CA subscription will be ended.\n", " status = SubscriptionStatus(self, just_ended, timeout=self.timeout)\n", "\n", " # Push the scan button...\n", " self.put(1)\n", "\n", " # And return the status object.\n", " # The caller can use it to tell when the scan is complete.\n", " return status\n", "\n", "scan_button = MySscanScanButton(\"gp:scan1.EXSC\", name=\"scan_button\")\n", "\n", "def run_sscan():\n", " yield from bps.trigger(scan_button, wait=True)\n", "```\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Execute the `run_sscan()` plan using the bluesky RunEngine `RE`." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "()" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "RE(run_sscan())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We'll get the data arrays from the `sscan` record in a later section of this notebook." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2. Run preconfigured `sscan` record\n", "\n", "The\n", "[SscanRecord](https://bcda-aps.github.io/apstools/latest/api/synApps/_sscan.html#apstools.synApps.sscan.SscanRecord)\n", "class from `apstools.synApps` provides access to most fields of the `sscan`\n", "record. Use `SscanRecord` to connect with `gp:scan1`. Repeat the above example.\n", "In the `SscanRecord` class, the scan button is called `execute_scan`.\n", "\n", "*Again, it is recommended to use an ophyd Status object instead of a polling loop.*" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "from apstools.synApps import SscanRecord\n", "\n", "scan1 = SscanRecord(f\"{IOC}scan1\", name=\"scan1\")\n", "scan1.wait_for_connection()\n", "\n", "def run_sscan():\n", " yield from bps.mv(scan1.execute_scan, 1)\n", "\n", " # Again, it is advised to use a Status object instead of a polling loop.\n", " # Wait for the scan to end with a polling loop.\n", " while scan1.execute_scan.get() != 0:\n", " yield from bps.sleep(0.1)" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "()" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "RE(run_sscan())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Get the data arrays from the `sscan` record\n", "\n", "Retrieve the data collected by `scan1` as a dictionary of numpy arrays. Include\n", "the motor and both detectors. The `sscan` record has buffers capable of\n", "collecting as many as 1,000 points per array (in its default configuration).\n", "First get the number of points collected, then limit each array length to that\n", "number.\n", "\n", "Write this as a function so it can be called again later. It does not have any\n", "statements that would block the RunEngine and it executes quickly. It does not\n", "need to be written as a bluesky plan." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'m1': array([-1.2 , -1.08, -0.96, -0.84, -0.72, -0.6 , -0.48, -0.36, -0.24,\n", " -0.12, 0. , 0.12, 0.24, 0.36, 0.48, 0.6 , 0.72, 0.84,\n", " 0.96, 1.08, 1.2 ]),\n", " 'I0': array([1., 1., 1., 1., 1., 1., 1., 1., 2., 3., 3., 4., 4., 2., 1., 1., 1.,\n", " 1., 1., 1., 2.], dtype=float32),\n", " 'diode': array([2., 1., 0., 1., 2., 0., 1., 1., 2., 2., 4., 3., 3., 3., 1., 1., 1.,\n", " 2., 2., 2., 1.], dtype=float32)}" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def get_sscan_data():\n", " npts = scan1.current_point.get()\n", " data = {\n", " \"m1\": scan1.positioners.p1.array.get()[:npts],\n", " \"I0\": scan1.detectors.d01.array.get()[:npts],\n", " \"diode\": scan1.detectors.d02.array.get()[:npts],\n", " }\n", " return data\n", "\n", "get_sscan_data()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. Setup and run same scan from bluesky\n", "\n", "Repeat the scan from the previous examples, but use bluesky to configure\n", "`scan1`. It will be useful to connect the motor, the scaler, and two of the\n", "scaler channels." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "from ophyd import EpicsMotor\n", "from ophyd.scaler import ScalerCH\n", "\n", "m1 = EpicsMotor(f\"{IOC}m1\", name=\"m1\")\n", "scaler1 = ScalerCH(f\"{IOC}scaler1\", name=\"scaler1\")\n", "m1.wait_for_connection()\n", "scaler1.wait_for_connection()\n", "\n", "# for convenience\n", "I0 = scaler1.channels.chan02.s\n", "diode = scaler1.channels.chan04.s" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can supply the count time per point and scan range parameters as arguments to\n", "the setup. After setting the counting time for the scaler, the next step in the\n", "setup is to clear any existing configuration of `scan1` using its `reset()`\n", "method. Because `scan1.reset()` was written as an ophyd function, we'll call it\n", "with `yield from run_blocking_function(scan1.reset)`.\n", "\n", "Finally, we'll setup `scan1` with the EPICS PV names to be used." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "from apstools.plans import run_blocking_function\n", "\n", "def setup_scan1(start, finish, npts, ct=1):\n", " yield from bps.mv(scaler1.preset_time, ct) # counting time/point\n", " yield from run_blocking_function(scan1.reset)\n", " yield from bps.sleep(0.2) # arbitrary wait for EPICS to finish the reset.\n", "\n", " # positioners\n", " yield from bps.mv(\n", " scan1.number_points, npts,\n", " scan1.positioners.p1.start, start,\n", " scan1.positioners.p1.end, finish,\n", " scan1.positioners.p1.readback_pv, m1.user_readback.pvname,\n", " scan1.positioners.p1.setpoint_pv, m1.user_setpoint.pvname,\n", " )\n", " # triggers (push scaler count button at each point)\n", " yield from bps.mv(\n", " scan1.triggers.t1.trigger_pv, scaler1.count.pvname,\n", " )\n", " # detectors\n", " yield from bps.mv(\n", " scan1.detectors.d01.input_pv, I0.pvname,\n", " scan1.detectors.d02.input_pv, diode.pvname,\n", " )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Setup the scan." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "()" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "RE(setup_scan1(-1.2, 1.2, 21, 0.2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Run the scan. We should not have to reprogram this plan." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "()" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "RE(run_sscan())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Get the scan data. Same function as before. It's not a bluesky plan, so `RE()`\n", "is not needed." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'m1': array([-1.2 , -1.08, -0.96, -0.84, -0.72, -0.6 , -0.48, -0.36, -0.24,\n", " -0.12, 0. , 0.12, 0.24, 0.36, 0.48, 0.6 , 0.72, 0.84,\n", " 0.96, 1.08, 1.2 ]),\n", " 'I0': array([1., 1., 1., 1., 1., 1., 1., 1., 2., 2., 4., 3., 3., 2., 1., 2., 2.,\n", " 1., 1., 1., 1.], dtype=float32),\n", " 'diode': array([1., 1., 2., 1., 2., 1., 0., 1., 2., 2., 4., 3., 3., 1., 1., 2., 0.,\n", " 1., 1., 1., 1.], dtype=float32)}" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "get_sscan_data()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4. Post data to bluesky as a run\n", "\n", "So far, we have just run `scan1` and shown the data collected. We'll need to do\n", "a bit more work to get this data into bluesky as a run. \n", "\n", "Bluesky obtains data for a run from an ophyd Device or Signal. We'll need to convert the data arrays into a structure that the `RE` will accept.\n", "\n", "While we might just use `bps.read(scan1)` to get the data, the data would be\n", "named according to the `scan1` structure. We want more meaningful names so we\n", "re-assign the names of the original objects (motor and scaler channel names).\n", "\n", "To post *bluesky data*, it must come from an ophyd `Device` (subclass). (See\n", "[here](https://nsls-ii.github.io/ophyd/device-overview.html#device-and-component)\n", "for help with `Device` and `Component`.)\n", "\n", "The `ThisScanData()` class is such an ophyd `Device` subclass, customized for\n", "the arrays of our `scan1`. We'll read the arrays from `scan1`, then write them\n", "(using `bps.mv()`) to our custom device. Since we do not have timestamps for\n", "each of the data points, we'll post the entire array as a single RunEngine\n", "[event](https://blueskyproject.io/bluesky/generated/bluesky.plan_stubs.create.html#bluesky.plan_stubs.create).\n", "The event will have the timestamp from the `bps.mv()` plan stub.\n", "\n", "As we make an object of this class, we define `name=\"scan1\"` so the names of the\n", "data in the databroker will appear as `scan1_m1`, `scan1_I0`, & `scan1_diode`.\n", "\n", "The steps are all descriptive. The run is opened with the metadata dictionary supplied (`md`), the `primary` stream is created and written with the `scan_data`, then all is buttoned up and bluesky\n", "finishes the run." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "from ophyd import Component, Device, Signal\n", "\n", "class ThisSscanData(Device):\n", " m1 = Component(Signal)\n", " I0 = Component(Signal)\n", " diode = Component(Signal)\n", "\n", "scan_data = ThisSscanData(\"\", name=\"scan1\")\n", "\n", "def post_sscan_data(md={}):\n", " yield from bps.open_run(md)\n", " yield from bps.create(name=\"primary\")\n", " data = get_sscan_data()\n", " yield from bps.mv(\n", " scan_data.m1, data[\"m1\"],\n", " scan_data.I0, data[\"I0\"],\n", " scan_data.diode, data[\"diode\"],\n", " )\n", " yield from bps.read(scan_data)\n", " yield from bps.save()\n", " yield from bps.close_run()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we need a databroker catalog. Make a temporary one for this notebook." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "0" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import databroker\n", "\n", "cat = databroker.temp().v2\n", "RE.subscribe(cat.v1.insert)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Post the data from the recent scan as a new bluesky run.\n", "\n", "Note: A bluesky plan can generate zero or more runs, each indexed by a `uid`.\n", "Print the tuple of run uids returned from `RE()`." ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "('b40e4926-d365-4c72-9936-76d28425c7fc',)" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "uids = RE(post_sscan_data())\n", "uids" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Get the run entry (by uid) from the catalog." ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "BlueskyRun\n", " uid='b40e4926-d365-4c72-9936-76d28425c7fc'\n", " exit_status='success'\n", " 2024-03-18 16:06:34.111 -- 2024-03-18 16:06:34.117\n", " Streams:\n", " * primary\n" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "run = cat[uids[0]]\n", "run" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Show the data arrays from the run's primary stream. The data are returned as a\n", "single xarray [Dataset](https://docs.xarray.dev/en/stable/generated/xarray.Dataset.html)." ] }, { "cell_type": "code", "execution_count": 17, "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, dim_0: 21, dim_1: 21, dim_2: 21)\n",
       "Coordinates:\n",
       "  * time         (time) float64 1.711e+09\n",
       "Dimensions without coordinates: dim_0, dim_1, dim_2\n",
       "Data variables:\n",
       "    scan1_m1     (time, dim_0) float64 -1.2 -1.08 -0.96 -0.84 ... 0.96 1.08 1.2\n",
       "    scan1_I0     (time, dim_1) float32 1.0 1.0 1.0 1.0 1.0 ... 1.0 1.0 1.0 1.0\n",
       "    scan1_diode  (time, dim_2) float32 1.0 1.0 2.0 1.0 2.0 ... 1.0 1.0 1.0 1.0
" ], "text/plain": [ "\n", "Dimensions: (time: 1, dim_0: 21, dim_1: 21, dim_2: 21)\n", "Coordinates:\n", " * time (time) float64 1.711e+09\n", "Dimensions without coordinates: dim_0, dim_1, dim_2\n", "Data variables:\n", " scan1_m1 (time, dim_0) float64 -1.2 -1.08 -0.96 -0.84 ... 0.96 1.08 1.2\n", " scan1_I0 (time, dim_1) float32 1.0 1.0 1.0 1.0 1.0 ... 1.0 1.0 1.0 1.0\n", " scan1_diode (time, dim_2) float32 1.0 1.0 2.0 1.0 2.0 ... 1.0 1.0 1.0 1.0" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "run.primary.read()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Combined setup, scan, post data\n", "\n", "Instead of calling the `RE()` separately with each of these plans, combine them\n", "into an outer plan." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "def combined(start, finish, npts, ct=1, md={}):\n", " yield from setup_scan1(start, finish, npts, ct=ct)\n", " yield from run_sscan()\n", " yield from post_sscan_data(md)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Run the combined plan and retrieve the data from the databroker catalog." ] }, { "cell_type": "code", "execution_count": 19, "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, dim_0: 21, dim_1: 21, dim_2: 21)\n",
       "Coordinates:\n",
       "  * time         (time) float64 1.711e+09\n",
       "Dimensions without coordinates: dim_0, dim_1, dim_2\n",
       "Data variables:\n",
       "    scan1_m1     (time, dim_0) float64 -1.2 -1.08 -0.96 -0.84 ... 0.96 1.08 1.2\n",
       "    scan1_I0     (time, dim_1) float32 2.0 0.0 0.0 0.0 0.0 ... 1.0 1.0 1.0 1.0\n",
       "    scan1_diode  (time, dim_2) float32 2.0 0.0 0.0 1.0 1.0 ... 1.0 1.0 1.0 1.0
" ], "text/plain": [ "\n", "Dimensions: (time: 1, dim_0: 21, dim_1: 21, dim_2: 21)\n", "Coordinates:\n", " * time (time) float64 1.711e+09\n", "Dimensions without coordinates: dim_0, dim_1, dim_2\n", "Data variables:\n", " scan1_m1 (time, dim_0) float64 -1.2 -1.08 -0.96 -0.84 ... 0.96 1.08 1.2\n", " scan1_I0 (time, dim_1) float32 2.0 0.0 0.0 0.0 0.0 ... 1.0 1.0 1.0 1.0\n", " scan1_diode (time, dim_2) float32 2.0 0.0 0.0 1.0 1.0 ... 1.0 1.0 1.0 1.0" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "uids = RE(combined(-1.2, 1.2, 21, ct=.2))\n", "cat[uids[0]].primary.read()" ] } ], "metadata": { "kernelspec": { "display_name": "bluesky_2024_1", "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.11.6" } }, "nbformat": 4, "nbformat_minor": 2 }