diff --git a/docs/source/examples/de_sscan.ipynb b/docs/source/examples/de_sscan.ipynb index 14f3af681..fbb312c64 100644 --- a/docs/source/examples/de_sscan.ipynb +++ b/docs/source/examples/de_sscan.ipynb @@ -11,10 +11,6 @@ "**Goals**: Demonstrate use of the `sscan` record with Bluesky.\n", "\n", "1. Press SCAN button of a preconfigured scan.\n", - "\n", - " 1. Use a polling loop to wait for scan to end.\n", - " 2. Use a Status object to wait for scan to end.\n", - "\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", @@ -84,7 +80,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "First, connect with the EPICS PV of the button using `ophyd.EpicsSignal`. Once connected, show the current value." + "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." ] }, { @@ -112,104 +112,46 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### 1.A. wait with a polling loop\n", - "\n", - "Write a bluesky plan that pushes the button, waits a brief moment for the scan\n", - "to start, then waits for the scan to end. This simple example waits by\n", - "periodically polling the value of the button." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "def run_sscan():\n", - " yield from bps.mv(scan_button, 1) # i.e. caput(\"gp:scan1.EXSC\", 1)\n", - " print(\"Started scan ...\")\n", - " yield from bps.sleep(0.1) # wait for scan to start\n", - " while scan_button.get() != 0:\n", - " yield from bps.sleep(0.1)\n", - " print(\"Scan done.\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Execute the `run_sscan()` plan using the bluesky RunEngine `RE`." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Started scan ...\n", - "Scan done.\n" - ] - }, - { - "data": { - "text/plain": [ - "()" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "RE(run_sscan())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To get the data from the `sscan` record, we use `ophyd.EpicsSignalRO` to connect with the arrays. We'll do that in a later section of this notebook.\n", + "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", - "### 1.B. wait with a Status object\n", - "\n", - "Let's eliminate that polling loop. This will make the plan respond more quickly\n", - "when the button changes value. The `ophyd.status.Status` class (runs in the\n", - "background) reports when the scan has finished or times out. We define an inner\n", - "`watch_the_button()` function to detect when the button goes from `1` to `0`\n", - "(the scan stops). We setup a subscription (*after* we start the move) to\n", - "monitor the button value and respond immediately. \n", - "\n", - "We wait for the status object to finish with `st.wait()` where `wait(timeout=None)` blocks until the action completes; when the action has finished successfully, returns `None`; if the action has failed, raises an exception.\n", + "
\n", "\n", - "In a bluesky plan,\n", - "we'll need to use `apstools.plans.run_blocking_function` with `st.wait()` since\n", - "it is a blocking function. The statement `yield from\n", - "run_blocking_function(st.wait)` runs `st.wait()` in a background thread so it\n", - "does not block the `RE`.\n", + "1. The `ophyd.status.Status` class (runs in the background) is used here to\n", + " report when the scan has finished, aborted, or has not finished in the\n", + " expected time. Here, `st = Status(timeout=20)` creates a status object with a\n", + " default timeout of 20s.\n", "\n", - "
\n", + "1. The inner `watch_the_button()` function detects when the button goes from `1`\n", + " to `0` (the scan has finished). Once the scan has finished,\n", + " `st.set_finished()` sets `st.done=True` and `st.success=True`.\n", "\n", - "The `if` statement compares both `old_value` and `value`. We catch the exact\n", - "event when the scan finishes. This is cautious programming to avoid the odd\n", - "case when `old_value=0` and `value=0` (occurs when IOC just booted and scan not\n", - "started yet).\n", + " The `if` statement compares both `old_value` and `value`. We catch the exact\n", + " event when the scan finishes. This is cautious programming to avoid the odd\n", + " case when `old_value=0` and `value=0` (occurs when IOC just booted and scan\n", + " not started yet).\n", "\n", - "The `ophyd.EpicsSignal` class handles the job of supplying arguments to\n", - "subscription functions such as `watch_the_button`.\n", + "1. A subscription is started (*after* we start the move) to monitor the button\n", + " value and respond promptly to any updates of the PV from EPICS. A\n", + " subscription starts a CA monitor on the signal and calls `watch_the_button()`\n", + " whenever a new value is received. The ophyd `EpicsSignal` class is\n", + " responsible for supplying the keyword arguments each time it calls a\n", + " subscription function (such as `watch_the_button()`) in response to an EPICS\n", + " CA monitor event.\n", "\n", - "
\n", + "1. The plan waits for the scan to finish by waiting for `st.done=True`. We must\n", + " use `apstools.plans.run_blocking_function` with `st.wait()` since it is a\n", + " blocking function. The statement `run_blocking_function(st.wait)` runs\n", + " `st.wait()` in a background thread so it does not block the `RE`.\n", "\n", - "Finally, we remove the subscription of the button." + "Finally, we remove the subscription of the button.\n", + "
" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -217,8 +159,9 @@ "from ophyd.status import Status\n", "\n", "def run_sscan():\n", - " # Create an instance of the Status object.\n", - " # If it is not marked as finished within 20s, it will raise a timeout exception.\n", + " # Create an instance of the Status class.\n", + " # Set a default timeout to 20s.\n", + " # If not finished by this time, it raises a timeout exception.\n", " st = Status(timeout=20)\n", "\n", " def watch_the_button(old_value, value, **kwargs):\n", @@ -232,9 +175,16 @@ " yield from run_blocking_function(st.wait)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Execute the `run_sscan()` plan using the bluesky RunEngine `RE`." + ] + }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -243,7 +193,7 @@ "()" ] }, - "execution_count": 6, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -252,20 +202,29 @@ "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 `apstools.synApps.SscanRecord` class provides access to most of the fields\n", - "provided by a `sscan` record. Use `SscanRecord` to connect and repeat the above\n", - "example. With the `SscanRecord` class, the button is called `execute_scan`." + "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`." ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -294,7 +253,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 6, "metadata": {}, "outputs": [ { @@ -303,7 +262,7 @@ "()" ] }, - "execution_count": 8, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -316,17 +275,21 @@ "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", - "both detectors and the motor. The `sscan` record has buffers capable of\n", + "the motor and both detectors. The `sscan` record has buffers capable of\n", "collecting as many as 1,000 points per array. First get the number of points\n", "collected, then limit each array length to that number.\n", "\n", - "If we write this as a function, we can call it again later. Since it executes quickly, it does not need to be written as a bluesky plan." + "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": 9, + "execution_count": 7, "metadata": {}, "outputs": [ { @@ -335,13 +298,13 @@ "{'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., 2., 1., 2., 1., 2., 3., 5., 3., 3., 2., 1., 1., 2.,\n", - " 2., 0., 1., 1.], dtype=float32),\n", - " 'diode': array([1., 1., 1., 1., 1., 0., 1., 1., 3., 3., 4., 4., 3., 2., 2., 1., 1.,\n", - " 1., 1., 1., 0.], dtype=float32)}" + " '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": 9, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -372,7 +335,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -404,7 +367,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -441,7 +404,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -450,13 +413,13 @@ "()" ] }, - "execution_count": 12, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "RE(setup_scan1(-1.1, 1.1, 11, 0.2))" + "RE(setup_scan1(-1.2, 1.2, 21, 0.2))" ] }, { @@ -468,7 +431,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 11, "metadata": {}, "outputs": [ { @@ -477,7 +440,7 @@ "()" ] }, - "execution_count": 13, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -496,19 +459,22 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "{'m1': array([-1.1 , -0.88, -0.66, -0.44, -0.22, 0. , 0.22, 0.44, 0.66,\n", - " 0.88, 1.1 ]),\n", - " 'I0': array([0., 1., 2., 1., 1., 3., 3., 2., 1., 1., 0.], dtype=float32),\n", - " 'diode': array([1., 2., 2., 0., 1., 3., 4., 2., 2., 1., 1.], dtype=float32)}" + "{'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": 14, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -546,7 +512,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ @@ -582,7 +548,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 14, "metadata": {}, "outputs": [ { @@ -591,7 +557,7 @@ "0" ] }, - "execution_count": 16, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -615,16 +581,16 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "('eaa4598f-2e97-4e19-9e7c-4818ff62e042',)" + "('b40e4926-d365-4c72-9936-76d28425c7fc',)" ] }, - "execution_count": 17, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } @@ -643,21 +609,21 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "BlueskyRun\n", - " uid='eaa4598f-2e97-4e19-9e7c-4818ff62e042'\n", + " uid='b40e4926-d365-4c72-9936-76d28425c7fc'\n", " exit_status='success'\n", - " 2024-03-18 14:34:24.557 -- 2024-03-18 14:34:24.563\n", + " 2024-03-18 16:06:34.111 -- 2024-03-18 16:06:34.117\n", " Streams:\n", " * primary\n" ] }, - "execution_count": 18, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } @@ -677,7 +643,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 17, "metadata": {}, "outputs": [ { @@ -1047,29 +1013,32 @@ " fill: currentColor;\n", "}\n", "
<xarray.Dataset>\n",
-       "Dimensions:      (time: 1, dim_0: 11, dim_1: 11, dim_2: 11)\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.1 -0.88 -0.66 -0.44 ... 0.66 0.88 1.1\n",
-       "    scan1_I0     (time, dim_1) float32 0.0 1.0 2.0 1.0 1.0 ... 2.0 1.0 1.0 0.0\n",
-       "    scan1_diode  (time, dim_2) float32 1.0 2.0 2.0 0.0 1.0 ... 2.0 2.0 1.0 1.0
" + " 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: 11, dim_1: 11, dim_2: 11)\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.1 -0.88 -0.66 -0.44 ... 0.66 0.88 1.1\n", - " scan1_I0 (time, dim_1) float32 0.0 1.0 2.0 1.0 1.0 ... 2.0 1.0 1.0 0.0\n", - " scan1_diode (time, dim_2) float32 1.0 2.0 2.0 0.0 1.0 ... 2.0 2.0 1.0 1.0" + " 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": 19, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } @@ -1090,7 +1059,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 18, "metadata": {}, "outputs": [], "source": [ @@ -1109,7 +1078,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 19, "metadata": {}, "outputs": [ { @@ -1485,12 +1454,12 @@ "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 1.0 1.0 0.0 ... 2.0 2.0 1.0 1.0\n", - " scan1_diode (time, dim_2) float32 1.0 1.0 1.0 2.0 1.0 ... 0.0 1.0 2.0 0.0
    • time
      PandasIndex
      PandasIndex(Index([1710796011.975966], dtype='float64', name='time'))
  • " ], "text/plain": [ "\n", @@ -1500,11 +1469,11 @@ "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 1.0 1.0 0.0 ... 2.0 2.0 1.0 1.0\n", - " scan1_diode (time, dim_2) float32 1.0 1.0 1.0 2.0 1.0 ... 0.0 1.0 2.0 0.0" + " 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": 21, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" }