From 843485a99fc985d7f115b5ab46006ba0b9594842 Mon Sep 17 00:00:00 2001 From: Jan-Lukas Wynen Date: Tue, 28 Oct 2025 13:22:08 +0100 Subject: [PATCH 1/4] Change names to standard scheme --- docs/user-guide/tof/dream.ipynb | 438 +++++++++----------- docs/user-guide/tof/frame-unwrapping.ipynb | 228 +++++----- docs/user-guide/tof/wfm.ipynb | 292 ++++++------- src/ess/reduce/live/raw.py | 10 +- src/ess/reduce/nexus/types.py | 14 +- src/ess/reduce/nexus/workflow.py | 28 +- src/ess/reduce/time_of_flight/__init__.py | 8 +- src/ess/reduce/time_of_flight/eto_to_tof.py | 24 +- src/ess/reduce/time_of_flight/types.py | 4 +- tests/nexus/workflow_test.py | 74 ++-- tests/time_of_flight/unwrap_test.py | 36 +- tests/time_of_flight/wfm_test.py | 10 +- tests/time_of_flight/workflow_test.py | 10 +- 13 files changed, 531 insertions(+), 645 deletions(-) diff --git a/docs/user-guide/tof/dream.ipynb b/docs/user-guide/tof/dream.ipynb index 62e125ad..fe914f87 100644 --- a/docs/user-guide/tof/dream.ipynb +++ b/docs/user-guide/tof/dream.ipynb @@ -1,9 +1,8 @@ { "cells": [ { - "cell_type": "markdown", - "id": "0", "metadata": {}, + "cell_type": "markdown", "source": [ "# The DREAM chopper cascade\n", "\n", @@ -12,26 +11,26 @@ "\n", "The case of DREAM is interesting because the pulse-shaping choppers can be used in a number of different modes,\n", "and the number of cutouts the choppers have typically does not equal the number of frames observed at the detectors." - ] + ], + "id": "eb68e1c217d3b35f" }, { - "cell_type": "code", - "execution_count": null, - "id": "1", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "import plopp as pp\n", "import scipp as sc\n", "from scippneutron.chopper import DiskChopper\n", - "from ess.reduce.nexus.types import AnyRun, DetectorData, SampleRun\n", + "from ess.reduce.nexus.types import AnyRun, RawDetector, SampleRun\n", "from ess.reduce.time_of_flight import *" - ] + ], + "id": "fcd99fcce9fd3502" }, { - "cell_type": "markdown", - "id": "2", "metadata": {}, + "cell_type": "markdown", "source": [ "## Setting up the beamline\n", "\n", @@ -46,14 +45,14 @@ "- 1 overlap chopper (OC)\n", "- 1 band-control chopper (BCC)\n", "- 1 T0 chopper" - ] + ], + "id": "621291535929c0d8" }, { - "cell_type": "code", - "execution_count": null, - "id": "3", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "psc1 = DiskChopper(\n", " frequency=sc.scalar(14.0, unit=\"Hz\"),\n", @@ -127,78 +126,66 @@ ")\n", "\n", "disk_choppers = {\"psc1\": psc1, \"psc2\": psc2, \"oc\": oc, \"bcc\": bcc, \"t0\": t0}" - ] + ], + "id": "fea61f1e4ae7a82e" }, { - "cell_type": "markdown", - "id": "4", "metadata": {}, - "source": [ - "It is possible to visualize the properties of the choppers by inspecting their `repr`:" - ] + "cell_type": "markdown", + "source": "It is possible to visualize the properties of the choppers by inspecting their `repr`:", + "id": "7057e21723b2bcbc" }, { - "cell_type": "code", - "execution_count": null, - "id": "5", "metadata": {}, + "cell_type": "code", "outputs": [], - "source": [ - "psc2" - ] + "execution_count": null, + "source": "psc2", + "id": "89c10a091396131e" }, { - "cell_type": "markdown", - "id": "6", "metadata": {}, - "source": [ - "Define the source position which is required to compute the distance that neutrons travelled." - ] + "cell_type": "markdown", + "source": "Define the source position which is required to compute the distance that neutrons travelled.", + "id": "6d6356854acaaaf2" }, { - "cell_type": "code", - "execution_count": null, - "id": "7", "metadata": {}, + "cell_type": "code", "outputs": [], - "source": [ - "source_position = sc.vector([0, 0, -76.55], unit=\"m\")" - ] + "execution_count": null, + "source": "source_position = sc.vector([0, 0, -76.55], unit=\"m\")", + "id": "d47cb9a756270240" }, { - "cell_type": "markdown", - "id": "8", "metadata": {}, - "source": [ - "### Adding a detector" - ] + "cell_type": "markdown", + "source": "### Adding a detector", + "id": "5333ea9873eecdca" }, { - "cell_type": "code", - "execution_count": null, - "id": "9", "metadata": {}, + "cell_type": "code", "outputs": [], - "source": [ - "Ltotal = sc.scalar(76.55 + 1.125, unit=\"m\")" - ] + "execution_count": null, + "source": "Ltotal = sc.scalar(76.55 + 1.125, unit=\"m\")", + "id": "803f33461f1b217b" }, { - "cell_type": "markdown", - "id": "10", "metadata": {}, + "cell_type": "markdown", "source": [ "## Creating some neutron events\n", "\n", "We create a semi-realistic set of neutron events based on the ESS pulse." - ] + ], + "id": "e045b7eda2f82d7" }, { - "cell_type": "code", - "execution_count": null, - "id": "11", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "from ess.reduce.time_of_flight.fakes import FakeBeamline\n", "\n", @@ -209,118 +196,108 @@ " run_length=sc.scalar(1 / 14, unit=\"s\") * 4,\n", " events_per_pulse=200_000,\n", ")" - ] + ], + "id": "c3315ad68571b786" }, { - "cell_type": "markdown", - "id": "12", "metadata": {}, - "source": [ - "The initial birth times and wavelengths of the generated neutrons can be visualized (for a single pulse):" - ] + "cell_type": "markdown", + "source": "The initial birth times and wavelengths of the generated neutrons can be visualized (for a single pulse):", + "id": "670fd7d344fd6faa" }, { - "cell_type": "code", - "execution_count": null, - "id": "13", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "one_pulse = ess_beamline.source.data[\"pulse\", 0]\n", "one_pulse.hist(birth_time=300).plot() + one_pulse.hist(wavelength=300).plot()" - ] + ], + "id": "d15c3d1352ffc2eb" }, { - "cell_type": "code", - "execution_count": null, - "id": "14", "metadata": {}, + "cell_type": "code", "outputs": [], - "source": [ - "ess_beamline.model_result.plot()" - ] + "execution_count": null, + "source": "ess_beamline.model_result.plot()", + "id": "9700ce289ebe604b" }, { - "cell_type": "markdown", - "id": "15", "metadata": {}, - "source": [ - "From this fake beamline, we extract the raw neutron signal at our detector:" - ] + "cell_type": "markdown", + "source": "From this fake beamline, we extract the raw neutron signal at our detector:", + "id": "917a527d7ab6c183" }, { - "cell_type": "code", - "execution_count": null, - "id": "16", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "raw_data = ess_beamline.get_monitor(\"detector\")[0]\n", "\n", "# Visualize\n", "raw_data.hist(event_time_offset=300).squeeze().plot()" - ] + ], + "id": "4a85559e8f60dcc5" }, { - "cell_type": "markdown", - "id": "17", "metadata": {}, - "source": [ - "The total number of neutrons in our sample data that make it through the to detector is:" - ] + "cell_type": "markdown", + "source": "The total number of neutrons in our sample data that make it through the to detector is:", + "id": "5a4338b781e1f184" }, { - "cell_type": "code", - "execution_count": null, - "id": "18", "metadata": {}, + "cell_type": "code", "outputs": [], - "source": [ - "raw_data.sum().value" - ] + "execution_count": null, + "source": "raw_data.sum().value", + "id": "fe6d6ac1ca120ba9" }, { - "cell_type": "markdown", - "id": "19", "metadata": {}, + "cell_type": "markdown", "source": [ "## Computing time-of-flight\n", "\n", "Next, we use a workflow that provides an estimate of the real time-of-flight as a function of neutron time-of-arrival.\n", "\n", "### Setting up the workflow" - ] + ], + "id": "8862d201b265f1f2" }, { - "cell_type": "code", - "execution_count": null, - "id": "20", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "wf = GenericTofWorkflow(run_types=[SampleRun], monitor_types=[])\n", "\n", - "wf[DetectorData[SampleRun]] = raw_data\n", + "wf[RawDetector[SampleRun]] = raw_data\n", "wf[DetectorLtotal[SampleRun]] = Ltotal\n", "\n", - "wf.visualize(DetectorTofData[SampleRun])" - ] + "wf.visualize(TofDetector[SampleRun])" + ], + "id": "3b0bcb24d17c3d9" }, { - "cell_type": "markdown", - "id": "21", "metadata": {}, + "cell_type": "markdown", "source": [ "By default, the workflow tries to load a `TimeOfFlightLookupTable` from a file.\n", "\n", "In this notebook, instead of using such a pre-made file,\n", "we will build our own lookup table from the chopper information and apply it to the workflow." - ] + ], + "id": "16ce814ebf1550ff" }, { - "cell_type": "markdown", - "id": "22", "metadata": {}, + "cell_type": "markdown", "source": [ "### Building the time-of-flight lookup table\n", "\n", @@ -330,14 +307,14 @@ "From this,\n", "we build a lookup table on which bilinear interpolation is used to compute a wavelength (and its corresponding time-of-flight)\n", "for every neutron event." - ] + ], + "id": "c331a1b58260bfca" }, { - "cell_type": "code", - "execution_count": null, - "id": "23", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "lut_wf = TofLookupTableWorkflow()\n", "lut_wf[DiskChoppers[AnyRun]] = disk_choppers\n", @@ -347,12 +324,12 @@ " sc.scalar(78.0, unit=\"m\"),\n", ")\n", "lut_wf.visualize(TimeOfFlightLookupTable)" - ] + ], + "id": "773b3c5f6abc6dec" }, { - "cell_type": "markdown", - "id": "24", "metadata": {}, + "cell_type": "markdown", "source": [ "### Inspecting the lookup table\n", "\n", @@ -363,14 +340,14 @@ "as a function of arrival time at the detector.\n", "\n", "This is the basis for creating our lookup table." - ] + ], + "id": "233b25c08d53e4ab" }, { - "cell_type": "code", - "execution_count": null, - "id": "25", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "sim = lut_wf.compute(SimulationResults)\n", "\n", @@ -392,66 +369,61 @@ "fig1 = events.hist(wavelength=300, event_time_offset=300).plot(norm=\"log\")\n", "fig2 = events.hist(tof=300, event_time_offset=300).plot(norm=\"log\")\n", "fig1 + fig2" - ] + ], + "id": "f6f5a75b784677f9" }, { - "cell_type": "markdown", - "id": "26", "metadata": {}, + "cell_type": "markdown", "source": [ "The lookup table is then obtained by computing the weighted mean of the time-of-flight inside each time-of-arrival bin.\n", "\n", "This is illustrated by the orange line in the figure below:" - ] + ], + "id": "19e4bd3a1b3448eb" }, { - "cell_type": "code", - "execution_count": null, - "id": "27", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "table = lut_wf.compute(TimeOfFlightLookupTable)\n", "\n", "# Overlay mean on the figure above\n", "table[\"distance\", 13].plot(ax=fig2.ax, color=\"C1\", ls=\"-\", marker=None)" - ] + ], + "id": "a02b4e433a80e39a" }, { - "cell_type": "markdown", - "id": "28", "metadata": {}, - "source": [ - "The full table covers a range of distances, and looks like" - ] + "cell_type": "markdown", + "source": "The full table covers a range of distances, and looks like", + "id": "104b3e255a92a1b8" }, { - "cell_type": "code", - "execution_count": null, - "id": "29", "metadata": {}, + "cell_type": "code", "outputs": [], - "source": [ - "table.plot()" - ] + "execution_count": null, + "source": "table.plot()", + "id": "616aff140261f422" }, { - "attachments": {}, - "cell_type": "markdown", - "id": "30", "metadata": {}, + "cell_type": "markdown", "source": [ "### Computing a time-of-flight coordinate\n", "\n", "We will now update our workflow, and use it to obtain our event data with a time-of-flight coordinate:" - ] + ], + "id": "a3b56cc85fff1488" }, { - "cell_type": "code", - "execution_count": null, - "id": "31", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "# Set the computed lookup table onto the original workflow\n", "wf[TimeOfFlightLookupTable] = table\n", @@ -459,42 +431,38 @@ "# Compute time-of-flight of neutron events\n", "tofs = wf.compute(DetectorTofData[SampleRun])\n", "tofs" - ] + ], + "id": "7d8aa7917fbc0f83" }, { - "cell_type": "markdown", - "id": "32", "metadata": {}, - "source": [ - "Histogramming the data for a plot should show a profile with 6 bumps that correspond to the frames:" - ] + "cell_type": "markdown", + "source": "Histogramming the data for a plot should show a profile with 6 bumps that correspond to the frames:", + "id": "81b1baf62777af47" }, { - "cell_type": "code", - "execution_count": null, - "id": "33", "metadata": {}, + "cell_type": "code", "outputs": [], - "source": [ - "tofs.bins.concat().hist(tof=300).plot()" - ] + "execution_count": null, + "source": "tofs.bins.concat().hist(tof=300).plot()", + "id": "97fd3ea1ec70b8d7" }, { - "cell_type": "markdown", - "id": "34", "metadata": {}, + "cell_type": "markdown", "source": [ "### Converting to wavelength\n", "\n", "We can now convert our new time-of-flight coordinate to a neutron wavelength, using `tranform_coords`:" - ] + ], + "id": "5f82a667b9ac561c" }, { - "cell_type": "code", - "execution_count": null, - "id": "35", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "from scippneutron.conversion.graph.beamline import beamline\n", "from scippneutron.conversion.graph.tof import elastic\n", @@ -508,25 +476,25 @@ "\n", "histogrammed = wav_wfm.hist(wavelength=wavs).squeeze()\n", "histogrammed.plot()" - ] + ], + "id": "79e6f40f08efd0cf" }, { - "cell_type": "markdown", - "id": "36", "metadata": {}, + "cell_type": "markdown", "source": [ "### Comparing to the ground truth\n", "\n", "As a consistency check, because we actually know the wavelengths of the neutrons we created,\n", "we can compare the true neutron wavelengths to those we computed above." - ] + ], + "id": "e3290da29db2d1dc" }, { - "cell_type": "code", - "execution_count": null, - "id": "37", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "ground_truth = ess_beamline.model_result[\"detector\"].data.flatten(to=\"event\")\n", "ground_truth = ground_truth[~ground_truth.masks[\"blocked_by_others\"]]\n", @@ -537,12 +505,12 @@ " \"ground_truth\": ground_truth.hist(wavelength=wavs),\n", " }\n", ")" - ] + ], + "id": "d50317290185a8d0" }, { - "cell_type": "markdown", - "id": "38", "metadata": {}, + "cell_type": "markdown", "source": [ "## Multiple detector pixels\n", "\n", @@ -552,14 +520,14 @@ "\n", "In our setup, we simply propagate the same neutrons to multiple detector pixels,\n", "as if they were not absorbed by the first pixel they meet." - ] + ], + "id": "76fb6bfa093ab993" }, { - "cell_type": "code", - "execution_count": null, - "id": "39", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "Ltotal = sc.array(dims=[\"detector_number\"], values=[77.675, 76.0], unit=\"m\")\n", "monitors = {f\"detector{i}\": ltot for i, ltot in enumerate(Ltotal)}\n", @@ -571,24 +539,24 @@ " run_length=sc.scalar(1 / 14, unit=\"s\") * 4,\n", " events_per_pulse=200_000,\n", ")" - ] + ], + "id": "b3c41e62376bb7aa" }, { - "cell_type": "markdown", - "id": "40", "metadata": {}, + "cell_type": "markdown", "source": [ "Our raw data has now a `detector_number` dimension of length 2.\n", "\n", "We can plot the neutron `event_time_offset` for the two detector pixels and see that the offsets are shifted to the left for the pixel that is closest to the source." - ] + ], + "id": "705c5b80b478e0f7" }, { - "cell_type": "code", - "execution_count": null, - "id": "41", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "raw_data = sc.concat(\n", " [ess_beamline.get_monitor(key)[0].squeeze() for key in monitors.keys()],\n", @@ -597,23 +565,23 @@ "\n", "# Visualize\n", "pp.plot(sc.collapse(raw_data.hist(event_time_offset=300), keep=\"event_time_offset\"))" - ] + ], + "id": "2a799bef41680ef5" }, { - "cell_type": "markdown", - "id": "42", "metadata": {}, + "cell_type": "markdown", "source": [ "Computing time-of-flight is done in the same way as above.\n", "We need to remember to update our workflow:" - ] + ], + "id": "27b3cae1341918f2" }, { - "cell_type": "code", - "execution_count": null, - "id": "43", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "# Update workflow\n", "wf[DetectorData[SampleRun]] = raw_data\n", @@ -641,12 +609,12 @@ "]\n", "\n", "figs[0] + figs[1]" - ] + ], + "id": "7bf393d248d1da63" }, { - "cell_type": "markdown", - "id": "44", "metadata": {}, + "cell_type": "markdown", "source": [ "## Handling time overlap between subframes\n", "\n", @@ -660,14 +628,14 @@ "ScippNeutron handles this by masking the overlapping regions and throwing away any neutrons that lie within it.\n", "\n", "To simulate this, we modify slightly the phase and the cutouts of the band-control chopper:" - ] + ], + "id": "5c55cb12891d252" }, { - "cell_type": "code", - "execution_count": null, - "id": "45", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "disk_choppers[\"bcc\"] = DiskChopper(\n", " frequency=sc.scalar(112.0, unit=\"Hz\"),\n", @@ -692,25 +660,25 @@ ")\n", "\n", "ess_beamline.model_result.plot()" - ] + ], + "id": "1df23f3473bdd12d" }, { - "cell_type": "markdown", - "id": "46", "metadata": {}, + "cell_type": "markdown", "source": [ "We can now see that there is no longer a gap between the two frames at the center of each pulse (green region).\n", "\n", "Another way of looking at this is looking at the wavelength vs time-of-arrival plot,\n", "which also shows overlap in time at the junction between the two frames:" - ] + ], + "id": "441bc00d3c00233c" }, { - "cell_type": "code", - "execution_count": null, - "id": "47", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "# Update workflow\n", "lut_wf[DiskChoppers[AnyRun]] = disk_choppers\n", @@ -719,12 +687,12 @@ "\n", "events = to_event_time_offset(sim)\n", "events.hist(wavelength=300, event_time_offset=300).plot(norm=\"log\")" - ] + ], + "id": "e1ee46302de31bc5" }, { - "cell_type": "markdown", - "id": "48", "metadata": {}, + "cell_type": "markdown", "source": [ "The data in the lookup table contains both the mean time-of-flight for each distance and time-of-arrival bin,\n", "but also the variance inside each bin.\n", @@ -735,23 +703,23 @@ "we are computing a mean between two regions which have similar 'brightness'.\n", "\n", "This leads to a large variance, and this is visible when plotting the relative standard deviations on a 2D figure." - ] + ], + "id": "81da86f190e5c099" }, { - "cell_type": "code", - "execution_count": null, - "id": "49", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "table = lut_wf.compute(TimeOfFlightLookupTable)\n", "table.plot() / (sc.stddevs(table) / sc.values(table)).plot(norm=\"log\")" - ] + ], + "id": "df218876a9a16cf1" }, { - "cell_type": "markdown", - "id": "50", "metadata": {}, + "cell_type": "markdown", "source": [ "The workflow has a parameter which is used to mask out regions where the standard deviation is above a certain threshold.\n", "\n", @@ -759,25 +727,25 @@ "as it can vary a lot depending on how much signal is received by the detectors,\n", "and how far the detectors are from the source.\n", "It is thus more robust to simply have a user tunable parameter on the workflow." - ] + ], + "id": "c73cf0a547cc8392" }, { - "cell_type": "code", - "execution_count": null, - "id": "51", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "lut_wf[LookupTableRelativeErrorThreshold] = 0.01\n", "\n", "table = lut_wf.compute(TimeOfFlightLookupTable)\n", "table.plot()" - ] + ], + "id": "d35accef1f0fe301" }, { - "cell_type": "markdown", - "id": "52", "metadata": {}, + "cell_type": "markdown", "source": [ "We can now see that the central region is masked out.\n", "\n", @@ -786,22 +754,22 @@ "\n", "This is visible when comparing to the true neutron wavelengths,\n", "where we see that some counts were lost between the two frames." - ] + ], + "id": "f19297304259ae8d" }, { - "cell_type": "code", - "execution_count": null, - "id": "53", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ - "wf[DetectorData[SampleRun]] = ess_beamline.get_monitor(\"detector\")[0]\n", + "wf[RawDetector[SampleRun]] = ess_beamline.get_monitor(\"detector\")[0]\n", "wf[DetectorLtotal[SampleRun]] = Ltotal\n", "\n", "wf[TimeOfFlightLookupTable] = table\n", "\n", "# Compute time-of-flight\n", - "tofs = wf.compute(DetectorTofData[SampleRun])\n", + "tofs = wf.compute(TofDetector[SampleRun])\n", "# Compute wavelength\n", "wav_wfm = tofs.transform_coords(\"wavelength\", graph=graph)\n", "\n", @@ -815,27 +783,11 @@ " \"ground_truth\": ground_truth.hist(wavelength=wavs),\n", " }\n", ")" - ] + ], + "id": "e74639ba34ae849d" } ], - "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" - } - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 5 } diff --git a/docs/user-guide/tof/frame-unwrapping.ipynb b/docs/user-guide/tof/frame-unwrapping.ipynb index c2369ad5..790dd75f 100644 --- a/docs/user-guide/tof/frame-unwrapping.ipynb +++ b/docs/user-guide/tof/frame-unwrapping.ipynb @@ -1,10 +1,8 @@ { "cells": [ { - "attachments": {}, - "cell_type": "markdown", - "id": "0", "metadata": {}, + "cell_type": "markdown", "source": [ "# Frame Unwrapping\n", "\n", @@ -20,39 +18,37 @@ "\n", "We refer to the process of \"unwrapping\" these time stamps into an actual time-of-flight as *frame unwrapping*, since `event_time_offset` \"wraps around\" with the period of the proton pulse and neutrons created by different proton pulses may be recorded with the *same* `event_time_zero`.\n", "The figures in the remainder of this document will clarify this." - ] + ], + "id": "e302add22f6aac6e" }, { - "cell_type": "code", - "execution_count": null, - "id": "1", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "import plopp as pp\n", "import scipp as sc\n", "from scippneutron.chopper import DiskChopper\n", - "from ess.reduce.nexus.types import AnyRun, DetectorData, SampleRun\n", + "from ess.reduce.nexus.types import AnyRun, RawDetector, SampleRun\n", "from ess.reduce.time_of_flight import *\n", "import tof\n", "\n", "Hz = sc.Unit(\"Hz\")\n", "deg = sc.Unit(\"deg\")\n", "meter = sc.Unit(\"m\")" - ] + ], + "id": "6f70852a226098ed" }, { - "cell_type": "markdown", - "id": "2", "metadata": {}, - "source": [ - "## Default mode" - ] + "cell_type": "markdown", + "source": "## Default mode", + "id": "3d7220fd0fdd5193" }, { - "cell_type": "markdown", - "id": "3", "metadata": {}, + "cell_type": "markdown", "source": [ "Often there is a 1:1 correspondence between source pulses and neutron pulses propagated to the sample and detectors.\n", "\n", @@ -61,14 +57,14 @@ "- We begin by creating a source of neutrons which mimics the ESS source.\n", "- We set up a single chopper with a single opening\n", "- We place 4 'monitors' along the path of the neutrons (none of which absorb any neutrons)" - ] + ], + "id": "6cf5d881afccc823" }, { - "cell_type": "code", - "execution_count": null, - "id": "4", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "source = tof.Source(facility=\"ess\", pulses=5)\n", "chopper = tof.Chopper(\n", @@ -94,12 +90,12 @@ " pl.ax.axvline(\n", " i * (1.0 / source.frequency).to(unit=\"us\").value, color=\"k\", ls=\"dotted\"\n", " )" - ] + ], + "id": "394694d48b668309" }, { - "cell_type": "markdown", - "id": "5", "metadata": {}, + "cell_type": "markdown", "source": [ "In the figure above, the dotted vertical lines represent the `event_time_zero` of each pulse,\n", "i.e. the start of a new origin for `event_time_offset` recorded at the various detectors.\n", @@ -114,14 +110,14 @@ "- **detector**: most of the neutrons arrive with an offset of two frames, but a small amount of neutrons (longest wavelengths) have a 3-frame offset\n", "\n", "We can further illustrate this by making histograms of the `event_time_offset` of the neutrons for each detector:" - ] + ], + "id": "2897b8e1b183fac7" }, { - "cell_type": "code", - "execution_count": null, - "id": "6", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "subplots = pp.tiled(2, 2, figsize=(9, 6))\n", "nxevent_data = results.to_nxevent_data()\n", @@ -133,12 +129,12 @@ " .plot(title=f\"{det.name}={det.distance:c}\", color=f\"C{i}\")\n", " )\n", "subplots" - ] + ], + "id": "f9270c5874469783" }, { - "cell_type": "markdown", - "id": "7", "metadata": {}, + "cell_type": "markdown", "source": [ "### Computing time-of-flight\n", "\n", @@ -150,27 +146,27 @@ "according to their `event_time_offset`.\n", "\n", "The workflow can be visualized as follows:" - ] + ], + "id": "d5d22f1cbbe2c41b" }, { - "cell_type": "code", - "execution_count": null, - "id": "8", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "wf = GenericTofWorkflow(run_types=[SampleRun], monitor_types=[])\n", "\n", - "wf[DetectorData[SampleRun]] = nxevent_data\n", + "wf[RawDetector[SampleRun]] = nxevent_data\n", "wf[DetectorLtotal[SampleRun]] = nxevent_data.coords[\"Ltotal\"]\n", "\n", - "wf.visualize(DetectorTofData[SampleRun])" - ] + "wf.visualize(TofDetector[SampleRun])" + ], + "id": "7f64b39fc153b3c" }, { - "cell_type": "markdown", - "id": "9", "metadata": {}, + "cell_type": "markdown", "source": [ "By default, the workflow tries to load a `TimeOfFlightLookupTable` from a file.\n", "\n", @@ -201,14 +197,14 @@ "- convert the wavelengths to a real time-of-flight to give our final lookup table\n", "\n", "This is done using a dedicated workflow:" - ] + ], + "id": "7b577d24c6fba85b" }, { - "cell_type": "code", - "execution_count": null, - "id": "10", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "lut_wf = TofLookupTableWorkflow()\n", "lut_wf[LtotalRange] = detectors[0].distance, detectors[-1].distance\n", @@ -229,58 +225,56 @@ "lut_wf[SourcePosition] = sc.vector([0, 0, 0], unit=\"m\")\n", "\n", "lut_wf.visualize(TimeOfFlightLookupTable)" - ] + ], + "id": "179557706f969c3" }, { - "cell_type": "markdown", - "id": "11", "metadata": {}, - "source": [ - "The table can be computed, and visualized as follows:" - ] + "cell_type": "markdown", + "source": "The table can be computed, and visualized as follows:", + "id": "15318405af5ea5cc" }, { - "cell_type": "code", - "execution_count": null, - "id": "12", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "table = lut_wf.compute(TimeOfFlightLookupTable)\n", "table.plot()" - ] + ], + "id": "b8334d8f8cbbf69" }, { - "cell_type": "markdown", - "id": "13", "metadata": {}, + "cell_type": "markdown", "source": [ "#### Computing time-of-flight from the lookup\n", "\n", "We now use the above table to perform a bilinear interpolation and compute the time-of-flight of every neutron." - ] + ], + "id": "d2b4e717d26de413" }, { - "cell_type": "code", - "execution_count": null, - "id": "14", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "# Set the computed lookup table on the original workflow\n", "wf[TimeOfFlightLookupTable] = table\n", "\n", "# Compute neutron tofs\n", - "tofs = wf.compute(DetectorTofData[SampleRun])\n", + "tofs = wf.compute(TofDetector[SampleRun])\n", "\n", "tof_hist = tofs.hist(tof=sc.scalar(500.0, unit=\"us\"))\n", "pp.plot({det.name: tof_hist[\"detector_number\", i] for i, det in enumerate(detectors)})" - ] + ], + "id": "905be49c87951c01" }, { - "cell_type": "markdown", - "id": "15", "metadata": {}, + "cell_type": "markdown", "source": [ "### Converting to wavelength\n", "\n", @@ -288,14 +282,14 @@ "\n", "Here, we compute the wavelengths from the time-of-flight using Scippneutron's `transform_coord` utility,\n", "and compare our computed wavelengths to the true wavelengths which are known for the simulated neutrons." - ] + ], + "id": "35143d0f273bd5d7" }, { - "cell_type": "code", - "execution_count": null, - "id": "16", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "from scippneutron.conversion.graph.beamline import beamline\n", "from scippneutron.conversion.graph.tof import elastic\n", @@ -317,12 +311,12 @@ "\n", "wavs[\"true\"] = ground_truth\n", "pp.plot(wavs)" - ] + ], + "id": "377f07810e0b1a0c" }, { - "cell_type": "markdown", - "id": "17", "metadata": {}, + "cell_type": "markdown", "source": [ "We see that all detectors agree on the wavelength spectrum,\n", "which is also in very good agreement with the true neutron wavelengths.\n", @@ -337,14 +331,14 @@ "This could also be every 3 or 4 pulses for very long instruments.\n", "\n", "The time-distance diagram may look something like:" - ] + ], + "id": "d364ee859683ef82" }, { - "cell_type": "code", - "execution_count": null, - "id": "18", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "source = tof.Source(facility=\"ess\", pulses=4)\n", "choppers = [\n", @@ -373,12 +367,12 @@ "model = tof.Model(source=source, choppers=choppers, detectors=detectors)\n", "results = model.run()\n", "results.plot(blocked_rays=5000)" - ] + ], + "id": "1e77c48a61e19e6c" }, { - "cell_type": "markdown", - "id": "19", "metadata": {}, + "cell_type": "markdown", "source": [ "### Computing time-of-flight\n", "\n", @@ -386,14 +380,14 @@ "we can use the same workflow as before.\n", "\n", "The only difference is that we set the `PulseStride` to 2 to skip every other pulse." - ] + ], + "id": "fc75e0b92ae9d6c5" }, { - "cell_type": "code", - "execution_count": null, - "id": "20", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "# Lookup table workflow\n", "lut_wf = TofLookupTableWorkflow()\n", @@ -416,72 +410,68 @@ "}\n", "lut_wf[SourcePosition] = sc.vector([0, 0, 0], unit=\"m\")\n", "lut_wf[DistanceResolution] = sc.scalar(0.5, unit=\"m\")" - ] + ], + "id": "a4c4820a53566272" }, { - "cell_type": "markdown", - "id": "21", "metadata": {}, - "source": [ - "The lookup table now spans 2 pulse periods, between 0 and ~142 ms:" - ] + "cell_type": "markdown", + "source": "The lookup table now spans 2 pulse periods, between 0 and ~142 ms:", + "id": "7792793fa62464c2" }, { - "cell_type": "code", - "execution_count": null, - "id": "22", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "table = lut_wf.compute(TimeOfFlightLookupTable)\n", "\n", "table.plot(figsize=(9, 4))" - ] + ], + "id": "4c7e587ca83062b5" }, { - "cell_type": "markdown", - "id": "23", "metadata": {}, - "source": [ - "The time-of-flight profiles are then:" - ] + "cell_type": "markdown", + "source": "The time-of-flight profiles are then:", + "id": "c8fc201975a2dd84" }, { - "cell_type": "code", - "execution_count": null, - "id": "24", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "# Reduction workflow\n", "wf = GenericTofWorkflow(run_types=[SampleRun], monitor_types=[])\n", "nxevent_data = results.to_nxevent_data()\n", - "wf[DetectorData[SampleRun]] = nxevent_data\n", + "wf[RawDetector[SampleRun]] = nxevent_data\n", "wf[DetectorLtotal[SampleRun]] = nxevent_data.coords[\"Ltotal\"]\n", "wf[TimeOfFlightLookupTable] = table\n", "\n", - "tofs = wf.compute(DetectorTofData[SampleRun])\n", + "tofs = wf.compute(TofDetector[SampleRun])\n", "\n", "tof_hist = tofs.hist(tof=sc.scalar(500.0, unit=\"us\"))\n", "pp.plot({det.name: tof_hist[\"detector_number\", i] for i, det in enumerate(detectors)})" - ] + ], + "id": "1b8524082e5ef8fc" }, { - "cell_type": "markdown", - "id": "25", "metadata": {}, + "cell_type": "markdown", "source": [ "### Conversion to wavelength\n", "\n", "We now use the `transform_coords` as above to convert to wavelength." - ] + ], + "id": "cc1566aaf0d76958" }, { - "cell_type": "code", - "execution_count": null, - "id": "26", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "# Define wavelength bin edges\n", "bins = sc.linspace(\"wavelength\", 1.0, 8.0, 401, unit=\"angstrom\")\n", @@ -497,27 +487,11 @@ "\n", "wavs[\"true\"] = ground_truth\n", "pp.plot(wavs)" - ] + ], + "id": "e5c30300862a7353" } ], - "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" - } - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 5 } diff --git a/docs/user-guide/tof/wfm.ipynb b/docs/user-guide/tof/wfm.ipynb index 99fd8909..cf7ecf88 100644 --- a/docs/user-guide/tof/wfm.ipynb +++ b/docs/user-guide/tof/wfm.ipynb @@ -1,9 +1,8 @@ { "cells": [ { - "cell_type": "markdown", - "id": "0", "metadata": {}, + "cell_type": "markdown", "source": [ "# Wavelength frame multiplication\n", "\n", @@ -12,27 +11,27 @@ "\n", "In this notebook, we show how to use `essreduce`'s `time_of_flight` module to compute an accurate a time-of-flight coordinate,\n", "from which a wavelength can be computed." - ] + ], + "id": "15cad2dca3d82303" }, { - "cell_type": "code", - "execution_count": null, - "id": "1", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "import numpy as np\n", "import plopp as pp\n", "import scipp as sc\n", "from scippneutron.chopper import DiskChopper\n", - "from ess.reduce.nexus.types import AnyRun, DetectorData, SampleRun\n", + "from ess.reduce.nexus.types import AnyRun, RawDetector, SampleRun\n", "from ess.reduce.time_of_flight import *" - ] + ], + "id": "fb4a8a673861a8a1" }, { - "cell_type": "markdown", - "id": "2", "metadata": {}, + "cell_type": "markdown", "source": [ "## Setting up the beamline\n", "\n", @@ -50,14 +49,14 @@ "\n", "The first 4 choppers have 6 openings (also known as cutouts),\n", "while the last one only has a single opening." - ] + ], + "id": "3f10166ff4890cf1" }, { - "cell_type": "code", - "execution_count": null, - "id": "3", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "wfm1 = DiskChopper(\n", " frequency=sc.scalar(-70.0, unit=\"Hz\"),\n", @@ -155,81 +154,73 @@ ")\n", "\n", "disk_choppers = {\"wfm1\": wfm1, \"wfm2\": wfm2, \"foc1\": foc1, \"foc2\": foc2, \"pol\": pol}" - ] + ], + "id": "6a37f19d68b47035" }, { - "cell_type": "markdown", - "id": "4", "metadata": {}, - "source": [ - "It is possible to visualize the properties of the choppers by inspecting their `repr`:" - ] + "cell_type": "markdown", + "source": "It is possible to visualize the properties of the choppers by inspecting their `repr`:", + "id": "df010fa5b0468833" }, { - "cell_type": "code", - "execution_count": null, - "id": "5", "metadata": {}, + "cell_type": "code", "outputs": [], - "source": [ - "wfm1" - ] + "execution_count": null, + "source": "wfm1", + "id": "d8460e03aba20a7f" }, { - "cell_type": "markdown", - "id": "6", "metadata": {}, + "cell_type": "markdown", "source": [ "Define the source position which is required to compute the distance that neutrons travelled.\n", "In this example, chopper positions are given relative to the source, so we set the source position to the origin." - ] + ], + "id": "f579bd64a6814438" }, { - "cell_type": "code", - "execution_count": null, - "id": "7", "metadata": {}, + "cell_type": "code", "outputs": [], - "source": [ - "source_position = sc.vector([0, 0, 0], unit=\"m\")" - ] + "execution_count": null, + "source": "source_position = sc.vector([0, 0, 0], unit=\"m\")", + "id": "1bc8fd09be092593" }, { - "cell_type": "markdown", - "id": "8", "metadata": {}, + "cell_type": "markdown", "source": [ "### Adding a detector\n", "\n", "We also have a detector, which we place 26 meters away from the source." - ] + ], + "id": "9d15bc81e3e36d8c" }, { - "cell_type": "code", - "execution_count": null, - "id": "9", "metadata": {}, + "cell_type": "code", "outputs": [], - "source": [ - "Ltotal = sc.scalar(26.0, unit=\"m\")" - ] + "execution_count": null, + "source": "Ltotal = sc.scalar(26.0, unit=\"m\")", + "id": "f66f1d5eab031032" }, { - "cell_type": "markdown", - "id": "10", "metadata": {}, + "cell_type": "markdown", "source": [ "## Creating some neutron events\n", "\n", "We create a semi-realistic set of neutron events based on the ESS pulse." - ] + ], + "id": "a637c51f345566bc" }, { - "cell_type": "code", - "execution_count": null, - "id": "11", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "from ess.reduce.time_of_flight.fakes import FakeBeamline\n", "\n", @@ -240,108 +231,100 @@ " run_length=sc.scalar(1 / 14, unit=\"s\") * 14,\n", " events_per_pulse=200_000,\n", ")" - ] + ], + "id": "c47001c9979199f0" }, { - "cell_type": "markdown", - "id": "12", "metadata": {}, - "source": [ - "The initial birth times and wavelengths of the generated neutrons can be visualized (for a single pulse):" - ] + "cell_type": "markdown", + "source": "The initial birth times and wavelengths of the generated neutrons can be visualized (for a single pulse):", + "id": "e83becdc10cf7c8b" }, { - "cell_type": "code", - "execution_count": null, - "id": "13", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "one_pulse = ess_beamline.source.data[\"pulse\", 0]\n", "one_pulse.hist(birth_time=300).plot() + one_pulse.hist(wavelength=300).plot()" - ] + ], + "id": "e7d1be89391bb61" }, { - "cell_type": "markdown", - "id": "14", "metadata": {}, - "source": [ - "From this fake beamline, we extract the raw neutron signal at our detector:" - ] + "cell_type": "markdown", + "source": "From this fake beamline, we extract the raw neutron signal at our detector:", + "id": "80a865b278d8b352" }, { - "cell_type": "code", - "execution_count": null, - "id": "15", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "raw_data = ess_beamline.get_monitor(\"detector\")[0]\n", "\n", "# Visualize\n", "raw_data.hist(event_time_offset=300).squeeze().plot()" - ] + ], + "id": "d37634b3ef1c5471" }, { - "cell_type": "markdown", - "id": "16", "metadata": {}, - "source": [ - "The total number of neutrons in our sample data that make it through the to detector is:" - ] + "cell_type": "markdown", + "source": "The total number of neutrons in our sample data that make it through the to detector is:", + "id": "a50df09b610c848" }, { - "cell_type": "code", - "execution_count": null, - "id": "17", "metadata": {}, + "cell_type": "code", "outputs": [], - "source": [ - "raw_data.sum().value" - ] + "execution_count": null, + "source": "raw_data.sum().value", + "id": "fc3c050b48c6d635" }, { - "cell_type": "markdown", - "id": "18", "metadata": {}, + "cell_type": "markdown", "source": [ "## Computing time-of-flight\n", "\n", "Next, we use a workflow that provides an estimate of the real time-of-flight as a function of neutron time-of-arrival.\n", "\n", "### Setting up the workflow" - ] + ], + "id": "8f51eb9f29b17352" }, { - "cell_type": "code", - "execution_count": null, - "id": "19", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "wf = GenericTofWorkflow(run_types=[SampleRun], monitor_types=[])\n", "\n", - "wf[DetectorData[SampleRun]] = raw_data\n", + "wf[RawDetector[SampleRun]] = raw_data\n", "wf[DetectorLtotal[SampleRun]] = Ltotal\n", "\n", - "wf.visualize(DetectorTofData[SampleRun])" - ] + "wf.visualize(TofDetector[SampleRun])" + ], + "id": "7033815b23e64b1b" }, { - "cell_type": "markdown", - "id": "20", "metadata": {}, + "cell_type": "markdown", "source": [ "By default, the workflow tries to load a `TimeOfFlightLookupTable` from a file.\n", "\n", "In this notebook, instead of using such a pre-made file,\n", "we will build our own lookup table from the chopper information and apply it to the workflow." - ] + ], + "id": "b093b608fb4d0eb8" }, { - "cell_type": "markdown", - "id": "21", "metadata": {}, + "cell_type": "markdown", "source": [ "### Building the time-of-flight lookup table\n", "\n", @@ -351,14 +334,14 @@ "From this,\n", "we build a lookup table on which bilinear interpolation is used to compute a wavelength (and its corresponding time-of-flight)\n", "for every neutron event." - ] + ], + "id": "9a924d0b22173beb" }, { - "cell_type": "code", - "execution_count": null, - "id": "22", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "lut_wf = TofLookupTableWorkflow()\n", "lut_wf[DiskChoppers[AnyRun]] = disk_choppers\n", @@ -366,12 +349,12 @@ "lut_wf[LtotalRange] = Ltotal, Ltotal\n", "lut_wf[LookupTableRelativeErrorThreshold] = 0.1\n", "lut_wf.visualize(TimeOfFlightLookupTable)" - ] + ], + "id": "bae376f1e93c45d" }, { - "cell_type": "markdown", - "id": "23", "metadata": {}, + "cell_type": "markdown", "source": [ "### Inspecting the lookup table\n", "\n", @@ -382,14 +365,14 @@ "as a function of arrival time at the detector.\n", "\n", "This is the basis for creating our lookup table." - ] + ], + "id": "e6018b37b25667b5" }, { - "cell_type": "code", - "execution_count": null, - "id": "24", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "sim = lut_wf.compute(SimulationResults)\n", "\n", @@ -411,24 +394,24 @@ "fig1 = events.hist(wavelength=300, event_time_offset=300).plot(norm=\"log\")\n", "fig2 = events.hist(tof=300, event_time_offset=300).plot(norm=\"log\")\n", "fig1 + fig2" - ] + ], + "id": "ee246bc7614d357f" }, { - "cell_type": "markdown", - "id": "25", "metadata": {}, + "cell_type": "markdown", "source": [ "The lookup table is then obtained by computing the weighted mean of the time-of-flight inside each time-of-arrival bin.\n", "\n", "This is illustrated by the orange line in the figure below:" - ] + ], + "id": "6562feda5f1bffab" }, { - "cell_type": "code", - "execution_count": null, - "id": "26", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "table = lut_wf.compute(TimeOfFlightLookupTable)\n", "\n", @@ -439,13 +422,12 @@ "fig2.canvas.xrange = 40000, 50000\n", "fig2.canvas.yrange = 35000, 50000\n", "fig2" - ] + ], + "id": "293fb2c2f0a39822" }, { - "attachments": {}, - "cell_type": "markdown", - "id": "27", "metadata": {}, + "cell_type": "markdown", "source": [ "We can see that the orange lines follow the center of the colored areas.\n", "\n", @@ -455,55 +437,51 @@ "### Computing a time-of-flight coordinate\n", "\n", "We will now update our original reduction workflow to compute our event data with a time-of-flight coordinate:" - ] + ], + "id": "b86048dd4a94c432" }, { - "cell_type": "code", - "execution_count": null, - "id": "28", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "wf[TimeOfFlightLookupTable] = table\n", "\n", - "tofs = wf.compute(DetectorTofData[SampleRun])\n", + "tofs = wf.compute(TofDetector[SampleRun])\n", "tofs" - ] + ], + "id": "418970f38372457b" }, { - "cell_type": "markdown", - "id": "29", "metadata": {}, - "source": [ - "Histogramming the data for a plot should show a profile with 6 bumps that correspond to the frames:" - ] + "cell_type": "markdown", + "source": "Histogramming the data for a plot should show a profile with 6 bumps that correspond to the frames:", + "id": "7c0ce32dbc88f2a6" }, { - "cell_type": "code", - "execution_count": null, - "id": "30", "metadata": {}, + "cell_type": "code", "outputs": [], - "source": [ - "tofs.bins.concat().hist(tof=300).plot()" - ] + "execution_count": null, + "source": "tofs.bins.concat().hist(tof=300).plot()", + "id": "cec58a544666f803" }, { - "cell_type": "markdown", - "id": "31", "metadata": {}, + "cell_type": "markdown", "source": [ "### Converting to wavelength\n", "\n", "We can now convert our new time-of-flight coordinate to a neutron wavelength, using `tranform_coords`:" - ] + ], + "id": "524232ce014bba27" }, { - "cell_type": "code", - "execution_count": null, - "id": "32", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "from scippneutron.conversion.graph.beamline import beamline\n", "from scippneutron.conversion.graph.tof import elastic\n", @@ -517,25 +495,25 @@ "\n", "histogrammed = wav_wfm.hist(wavelength=wavs).squeeze()\n", "histogrammed.plot()" - ] + ], + "id": "12ea22c4b126d302" }, { - "cell_type": "markdown", - "id": "33", "metadata": {}, + "cell_type": "markdown", "source": [ "### Comparing to the ground truth\n", "\n", "As a consistency check, because we actually know the wavelengths of the neutrons we created,\n", "we can compare the true neutron wavelengths to those we computed above." - ] + ], + "id": "8bea8b51b40e5a64" }, { - "cell_type": "code", - "execution_count": null, - "id": "34", "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "ground_truth = ess_beamline.model_result[\"detector\"].data.flatten(to=\"event\")\n", "ground_truth = ground_truth[~ground_truth.masks[\"blocked_by_others\"]]\n", @@ -546,27 +524,11 @@ " \"ground_truth\": ground_truth.hist(wavelength=wavs),\n", " }\n", ")" - ] + ], + "id": "777fe64c28e75278" } ], - "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" - } - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 5 } diff --git a/src/ess/reduce/live/raw.py b/src/ess/reduce/live/raw.py index 7c48ef31..ef3c0624 100644 --- a/src/ess/reduce/live/raw.py +++ b/src/ess/reduce/live/raw.py @@ -29,7 +29,7 @@ import scippnexus as snx from ess.reduce.nexus.types import ( - CalibratedDetector, + EmptyDetector, Filename, NeXusComponent, NeXusDetectorName, @@ -305,7 +305,7 @@ def transform_weights( @staticmethod def from_detector_and_histogrammer( - detector: CalibratedDetector[SampleRun], + detector: EmptyDetector[SampleRun], window: RollingDetectorViewWindow, projection: Histogrammer, ) -> RollingDetectorView: @@ -320,10 +320,10 @@ def from_detector_and_histogrammer( def from_detector_with_projection( projection: Callable[[sc.DataArray], sc.DataArray] | None, ) -> Callable[ - [CalibratedDetector[SampleRun], RollingDetectorViewWindow], RollingDetectorView + [EmptyDetector[SampleRun], RollingDetectorViewWindow], RollingDetectorView ]: def factory( - detector: CalibratedDetector[SampleRun], + detector: EmptyDetector[SampleRun], window: RollingDetectorViewWindow, ) -> RollingDetectorView: """Helper for constructing via a Sciline workflow.""" @@ -591,7 +591,7 @@ def gaussian_position_noise(sigma: PositionNoiseSigma) -> PositionNoise: def position_with_noisy_replicas( *, - detector: CalibratedDetector[SampleRun], + detector: EmptyDetector[SampleRun], position_noise: PositionNoise, replicas: PositionNoiseReplicaCount, ) -> CalibratedPositionWithNoisyReplicas: diff --git a/src/ess/reduce/nexus/types.py b/src/ess/reduce/nexus/types.py index 911d0718..1f2a7099 100644 --- a/src/ess/reduce/nexus/types.py +++ b/src/ess/reduce/nexus/types.py @@ -199,25 +199,23 @@ class MonitorPositionOffset( """Offset for the monitor position, added to base position.""" -class CalibratedDetector(sciline.Scope[RunType, sc.DataArray], sc.DataArray): - """Calibrated data from a detector.""" +class EmptyDetector(sciline.Scope[RunType, sc.DataArray], sc.DataArray): + """Detector without neutron data.""" class CalibratedBeamline(sciline.Scope[RunType, sc.DataArray], sc.DataArray): """Calibrated beamline with detector and other components.""" -class CalibratedMonitor( - sciline.Scope[RunType, MonitorType, sc.DataArray], sc.DataArray -): - """Calibrated data from a monitor.""" +class EmptyMonitor(sciline.Scope[RunType, MonitorType, sc.DataArray], sc.DataArray): + """Monitor without neutron data.""" -class DetectorData(sciline.Scope[RunType, sc.DataArray], sc.DataArray): +class RawDetector(sciline.Scope[RunType, sc.DataArray], sc.DataArray): """Calibrated detector merged with neutron event or histogram data.""" -class MonitorData(sciline.Scope[RunType, MonitorType, sc.DataArray], sc.DataArray): +class RawMonitor(sciline.Scope[RunType, MonitorType, sc.DataArray], sc.DataArray): """Calibrated monitor merged with neutron event or histogram data.""" diff --git a/src/ess/reduce/nexus/workflow.py b/src/ess/reduce/nexus/workflow.py index 86e4fdd1..b2499186 100644 --- a/src/ess/reduce/nexus/workflow.py +++ b/src/ess/reduce/nexus/workflow.py @@ -21,16 +21,14 @@ AllNeXusComponents, Beamline, CalibratedBeamline, - CalibratedDetector, - CalibratedMonitor, Component, DetectorBankSizes, - DetectorData, DetectorPositionOffset, + EmptyDetector, + EmptyMonitor, Filename, GravityVector, Measurement, - MonitorData, MonitorPositionOffset, MonitorType, NeXusAllComponentLocationSpec, @@ -46,6 +44,8 @@ Position, PreopenNeXusFile, RawChoppers, + RawDetector, + RawMonitor, RunType, TimeInterval, UniqueComponent, @@ -360,7 +360,7 @@ def get_calibrated_detector( # all. offset: DetectorPositionOffset[RunType], bank_sizes: DetectorBankSizes, -) -> CalibratedDetector[RunType]: +) -> EmptyDetector[RunType]: """ Extract the data array corresponding to a detector's signal field. @@ -395,13 +395,13 @@ def get_calibrated_detector( else: transform_value = transform.value position = transform_value * offsets - return CalibratedDetector[RunType]( + return EmptyDetector[RunType]( da.assign_coords(position=position + offset.to(unit=position.unit)) ) def assemble_beamline( - detector: CalibratedDetector[RunType], + detector: EmptyDetector[RunType], source_position: Position[snx.NXsource, RunType], sample_position: Position[snx.NXsample, RunType], gravity: GravityVector, @@ -438,7 +438,7 @@ def assemble_beamline( def assemble_detector_data( detector: CalibratedBeamline[RunType], event_data: NeXusData[snx.NXdetector, RunType], -) -> DetectorData[RunType]: +) -> RawDetector[RunType]: """ Assemble a detector data array with event data. @@ -454,7 +454,7 @@ def assemble_detector_data( grouped = nexus.group_event_data( event_data=event_data, detector_number=detector.coords['detector_number'] ) - return DetectorData[RunType]( + return RawDetector[RunType]( _add_variances(grouped) .assign_coords(detector.coords) .assign_masks(detector.masks) @@ -465,7 +465,7 @@ def get_calibrated_monitor( monitor: NeXusComponent[MonitorType, RunType], offset: MonitorPositionOffset[RunType, MonitorType], source_position: Position[snx.NXsource, RunType], -) -> CalibratedMonitor[RunType, MonitorType]: +) -> EmptyMonitor[RunType, MonitorType]: """ Extract the data array corresponding to a monitor's signal field. @@ -482,7 +482,7 @@ def get_calibrated_monitor( Position of the neutron source. """ monitor = nexus.compute_component_position(monitor) - return CalibratedMonitor[RunType, MonitorType]( + return EmptyMonitor[RunType, MonitorType]( nexus.extract_signal_data_array(monitor).assign_coords( position=monitor['position'] + offset.to(unit=monitor['position'].unit), source_position=source_position, @@ -491,9 +491,9 @@ def get_calibrated_monitor( def assemble_monitor_data( - monitor: CalibratedMonitor[RunType, MonitorType], + monitor: EmptyMonitor[RunType, MonitorType], data: NeXusData[MonitorType, RunType], -) -> MonitorData[RunType, MonitorType]: +) -> RawMonitor[RunType, MonitorType]: """ Assemble a monitor data array with event data. @@ -507,7 +507,7 @@ def assemble_monitor_data( Data array with neutron counts. """ da = data.assign_coords(monitor.coords).assign_masks(monitor.masks) - return MonitorData[RunType, MonitorType](_add_variances(da)) + return RawMonitor[RunType, MonitorType](_add_variances(da)) def parse_disk_choppers( diff --git a/src/ess/reduce/time_of_flight/__init__.py b/src/ess/reduce/time_of_flight/__init__.py index b64bba50..fa5374f9 100644 --- a/src/ess/reduce/time_of_flight/__init__.py +++ b/src/ess/reduce/time_of_flight/__init__.py @@ -24,25 +24,23 @@ ) from .types import ( DetectorLtotal, - DetectorTofData, MonitorLtotal, - MonitorTofData, PulseStrideOffset, TimeOfFlightLookupTable, TimeOfFlightLookupTableFilename, + TofDetector, + TofMonitor, ) from .workflow import GenericTofWorkflow __all__ = [ "DetectorLtotal", - "DetectorTofData", "DiskChoppers", "DistanceResolution", "GenericTofWorkflow", "LookupTableRelativeErrorThreshold", "LtotalRange", "MonitorLtotal", - "MonitorTofData", "NumberOfSimulatedNeutrons", "PulsePeriod", "PulseStride", @@ -53,7 +51,9 @@ "TimeOfFlightLookupTable", "TimeOfFlightLookupTableFilename", "TimeResolution", + "TofDetector", "TofLookupTableWorkflow", + "TofMonitor", "providers", "simulate_chopper_cascade_using_tof", ] diff --git a/src/ess/reduce/time_of_flight/eto_to_tof.py b/src/ess/reduce/time_of_flight/eto_to_tof.py index b0bacc06..64fb2e0c 100644 --- a/src/ess/reduce/time_of_flight/eto_to_tof.py +++ b/src/ess/reduce/time_of_flight/eto_to_tof.py @@ -21,20 +21,20 @@ from ..nexus.types import ( CalibratedBeamline, - CalibratedMonitor, - DetectorData, - MonitorData, + EmptyMonitor, MonitorType, + RawDetector, + RawMonitor, RunType, ) from .resample import rebin_strictly_increasing from .types import ( DetectorLtotal, - DetectorTofData, MonitorLtotal, - MonitorTofData, PulseStrideOffset, TimeOfFlightLookupTable, + TofDetector, + TofMonitor, ) @@ -294,7 +294,7 @@ def detector_ltotal_from_straight_line_approximation( def monitor_ltotal_from_straight_line_approximation( - monitor_beamline: CalibratedMonitor[RunType, MonitorType], + monitor_beamline: EmptyMonitor[RunType, MonitorType], ) -> MonitorLtotal[RunType, MonitorType]: """ Compute Ltotal for the monitor. @@ -334,11 +334,11 @@ def _compute_tof_data( def detector_time_of_flight_data( - detector_data: DetectorData[RunType], + detector_data: RawDetector[RunType], lookup: TimeOfFlightLookupTable, ltotal: DetectorLtotal[RunType], pulse_stride_offset: PulseStrideOffset, -) -> DetectorTofData[RunType]: +) -> TofDetector[RunType]: """ Convert the time-of-arrival data to time-of-flight data using a lookup table. The output data will have a time-of-flight coordinate. @@ -357,7 +357,7 @@ def detector_time_of_flight_data( When pulse-skipping, the offset of the first pulse in the stride. This is typically zero but can be a small integer < pulse_stride. """ - return DetectorTofData[RunType]( + return TofDetector[RunType]( _compute_tof_data( da=detector_data, lookup=lookup, @@ -368,11 +368,11 @@ def detector_time_of_flight_data( def monitor_time_of_flight_data( - monitor_data: MonitorData[RunType, MonitorType], + monitor_data: RawMonitor[RunType, MonitorType], lookup: TimeOfFlightLookupTable, ltotal: MonitorLtotal[RunType, MonitorType], pulse_stride_offset: PulseStrideOffset, -) -> MonitorTofData[RunType, MonitorType]: +) -> TofMonitor[RunType, MonitorType]: """ Convert the time-of-arrival data to time-of-flight data using a lookup table. The output data will have a time-of-flight coordinate. @@ -391,7 +391,7 @@ def monitor_time_of_flight_data( When pulse-skipping, the offset of the first pulse in the stride. This is typically zero but can be a small integer < pulse_stride. """ - return MonitorTofData[RunType, MonitorType]( + return TofMonitor[RunType, MonitorType]( _compute_tof_data( da=monitor_data, lookup=lookup, diff --git a/src/ess/reduce/time_of_flight/types.py b/src/ess/reduce/time_of_flight/types.py index 71bd0944..fc94f679 100644 --- a/src/ess/reduce/time_of_flight/types.py +++ b/src/ess/reduce/time_of_flight/types.py @@ -33,9 +33,9 @@ class MonitorLtotal(sl.Scope[RunType, MonitorType, sc.Variable], sc.Variable): """Total path length of neutrons from source to monitor.""" -class DetectorTofData(sl.Scope[RunType, sc.DataArray], sc.DataArray): +class TofDetector(sl.Scope[RunType, sc.DataArray], sc.DataArray): """Detector data with time-of-flight coordinate.""" -class MonitorTofData(sl.Scope[RunType, MonitorType, sc.DataArray], sc.DataArray): +class TofMonitor(sl.Scope[RunType, MonitorType, sc.DataArray], sc.DataArray): """Monitor data with time-of-flight coordinate.""" diff --git a/tests/nexus/workflow_test.py b/tests/nexus/workflow_test.py index 55ddd775..4192bdc6 100644 --- a/tests/nexus/workflow_test.py +++ b/tests/nexus/workflow_test.py @@ -12,20 +12,20 @@ from ess.reduce.nexus.types import ( BackgroundRun, Beamline, - DetectorData, EmptyBeamRun, Filename, FrameMonitor0, FrameMonitor1, FrameMonitor2, Measurement, - MonitorData, MonitorType, NeXusComponentLocationSpec, NeXusName, NeXusTransformation, PreopenNeXusFile, RawChoppers, + RawDetector, + RawMonitor, RunType, SampleRun, TimeInterval, @@ -333,9 +333,9 @@ def test_get_calibrated_detector_forwards_masks( @pytest.fixture -def calibrated_detector() -> workflow.CalibratedDetector[SampleRun]: +def calibrated_detector() -> workflow.EmptyDetector[SampleRun]: detector_number = sc.arange('detector_number', 6, unit=None) - return workflow.CalibratedDetector[SampleRun]( + return workflow.EmptyDetector[SampleRun]( sc.DataArray( sc.empty_like(detector_number), coords={ @@ -446,8 +446,8 @@ def test_get_calibrated_monitor_subtracts_offset_from_position( @pytest.fixture -def calibrated_monitor() -> workflow.CalibratedMonitor[SampleRun, FrameMonitor1]: - return workflow.CalibratedMonitor[SampleRun, FrameMonitor1]( +def calibrated_monitor() -> workflow.EmptyMonitor[SampleRun, FrameMonitor1]: + return workflow.EmptyMonitor[SampleRun, FrameMonitor1]( sc.DataArray( sc.scalar(0), coords={'position': sc.vector([1.0, 2.0, 3.0], unit='m')}, @@ -553,7 +553,7 @@ def test_load_event_monitor_workflow(loki_tutorial_sample_run_60250: Path) -> No wf = LoadMonitorWorkflow(run_types=[SampleRun], monitor_types=[FrameMonitor1]) wf[Filename[SampleRun]] = loki_tutorial_sample_run_60250 wf[NeXusName[FrameMonitor1]] = 'monitor_1' - da = wf.compute(MonitorData[SampleRun, FrameMonitor1]) + da = wf.compute(RawMonitor[SampleRun, FrameMonitor1]) assert 'position' in da.coords assert 'source_position' in da.coords assert da.bins is not None @@ -565,7 +565,7 @@ def test_load_histogram_monitor_workflow(dream_coda_test_file: Path) -> None: wf = LoadMonitorWorkflow(run_types=[SampleRun], monitor_types=[FrameMonitor1]) wf[Filename[SampleRun]] = dream_coda_test_file wf[NeXusName[FrameMonitor1]] = 'monitor_bunker' - da = wf.compute(MonitorData[SampleRun, FrameMonitor1]) + da = wf.compute(RawMonitor[SampleRun, FrameMonitor1]) assert 'position' in da.coords assert 'source_position' in da.coords assert da.bins is None @@ -579,7 +579,7 @@ def test_load_detector_workflow(loki_tutorial_sample_run_60250: Path) -> None: wf = LoadDetectorWorkflow(run_types=[SampleRun], monitor_types=[]) wf[Filename[SampleRun]] = loki_tutorial_sample_run_60250 wf[NeXusName[snx.NXdetector]] = 'larmor_detector' - da = wf.compute(DetectorData[SampleRun]) + da = wf.compute(RawDetector[SampleRun]) assert 'position' in da.coords assert 'sample_position' in da.coords assert 'source_position' in da.coords @@ -596,13 +596,13 @@ def test_generic_nexus_workflow( wf[NeXusName[FrameMonitor1]] = 'monitor_1' wf[NeXusName[snx.NXdetector]] = 'larmor_detector' wf[PreopenNeXusFile] = preopen - da = wf.compute(DetectorData[SampleRun]) + da = wf.compute(RawDetector[SampleRun]) assert 'position' in da.coords assert 'sample_position' in da.coords assert 'source_position' in da.coords assert da.bins is not None assert da.dims == ('detector_number',) - da = wf.compute(MonitorData[SampleRun, FrameMonitor1]) + da = wf.compute(RawMonitor[SampleRun, FrameMonitor1]) assert 'position' in da.coords assert 'source_position' in da.coords assert da.bins is not None @@ -663,14 +663,14 @@ def test_generic_nexus_workflow_includes_only_given_run_and_monitor_types() -> N graph = wf.underlying_graph # Check some examples to avoid relying entirely on complicated loops below. - assert DetectorData[SampleRun] in graph - assert DetectorData[BackgroundRun] not in graph - assert MonitorData[SampleRun, FrameMonitor1] in graph - assert MonitorData[SampleRun, FrameMonitor2] not in graph - assert MonitorData[SampleRun, FrameMonitor0] in graph - assert MonitorData[BackgroundRun, FrameMonitor0] not in graph - assert MonitorData[BackgroundRun, FrameMonitor1] not in graph - assert MonitorData[BackgroundRun, FrameMonitor2] not in graph + assert RawDetector[SampleRun] in graph + assert RawDetector[BackgroundRun] not in graph + assert RawMonitor[SampleRun, FrameMonitor1] in graph + assert RawMonitor[SampleRun, FrameMonitor2] not in graph + assert RawMonitor[SampleRun, FrameMonitor0] in graph + assert RawMonitor[BackgroundRun, FrameMonitor0] not in graph + assert RawMonitor[BackgroundRun, FrameMonitor1] not in graph + assert RawMonitor[BackgroundRun, FrameMonitor2] not in graph assert RawChoppers[SampleRun] in graph assert RawChoppers[BackgroundRun] not in graph @@ -705,14 +705,14 @@ def test_generic_nexus_workflow_includes_only_given_run_types() -> None: graph = wf.underlying_graph # Check some examples to avoid relying entirely on complicated loops below. - assert DetectorData[EmptyBeamRun] in graph - assert DetectorData[SampleRun] not in graph - assert MonitorData[EmptyBeamRun, FrameMonitor1] in graph - assert MonitorData[EmptyBeamRun, FrameMonitor2] in graph - assert MonitorData[EmptyBeamRun, FrameMonitor0] in graph - assert MonitorData[SampleRun, FrameMonitor1] not in graph - assert MonitorData[SampleRun, FrameMonitor2] not in graph - assert MonitorData[SampleRun, FrameMonitor0] not in graph + assert RawDetector[EmptyBeamRun] in graph + assert RawDetector[SampleRun] not in graph + assert RawMonitor[EmptyBeamRun, FrameMonitor1] in graph + assert RawMonitor[EmptyBeamRun, FrameMonitor2] in graph + assert RawMonitor[EmptyBeamRun, FrameMonitor0] in graph + assert RawMonitor[SampleRun, FrameMonitor1] not in graph + assert RawMonitor[SampleRun, FrameMonitor2] not in graph + assert RawMonitor[SampleRun, FrameMonitor0] not in graph assert RawChoppers[EmptyBeamRun] in graph assert RawChoppers[SampleRun] not in graph @@ -729,16 +729,16 @@ def test_generic_nexus_workflow_includes_only_given_monitor_types() -> None: graph = wf.underlying_graph # Check some examples to avoid relying entirely on complicated loops below. - assert DetectorData[SampleRun] in graph - assert DetectorData[BackgroundRun] in graph - assert MonitorData[SampleRun, TransmissionMonitor] in graph - assert MonitorData[SampleRun, FrameMonitor1] in graph - assert MonitorData[SampleRun, FrameMonitor2] not in graph - assert MonitorData[SampleRun, FrameMonitor0] not in graph - assert MonitorData[BackgroundRun, TransmissionMonitor] in graph - assert MonitorData[BackgroundRun, FrameMonitor1] in graph - assert MonitorData[BackgroundRun, FrameMonitor2] not in graph - assert MonitorData[BackgroundRun, FrameMonitor0] not in graph + assert RawDetector[SampleRun] in graph + assert RawDetector[BackgroundRun] in graph + assert RawMonitor[SampleRun, TransmissionMonitor] in graph + assert RawMonitor[SampleRun, FrameMonitor1] in graph + assert RawMonitor[SampleRun, FrameMonitor2] not in graph + assert RawMonitor[SampleRun, FrameMonitor0] not in graph + assert RawMonitor[BackgroundRun, TransmissionMonitor] in graph + assert RawMonitor[BackgroundRun, FrameMonitor1] in graph + assert RawMonitor[BackgroundRun, FrameMonitor2] not in graph + assert RawMonitor[BackgroundRun, FrameMonitor0] not in graph assert RawChoppers[SampleRun] in graph assert RawChoppers[BackgroundRun] in graph diff --git a/tests/time_of_flight/unwrap_test.py b/tests/time_of_flight/unwrap_test.py index 4e9cc3c1..94d75e01 100644 --- a/tests/time_of_flight/unwrap_test.py +++ b/tests/time_of_flight/unwrap_test.py @@ -8,7 +8,7 @@ from scippneutron.conversion.graph.tof import elastic as elastic_graph from ess.reduce import time_of_flight -from ess.reduce.nexus.types import AnyRun, DetectorData, SampleRun +from ess.reduce.nexus.types import AnyRun, RawDetector, SampleRun from ess.reduce.time_of_flight import GenericTofWorkflow, TofLookupTableWorkflow, fakes sl = pytest.importorskip("sciline") @@ -62,7 +62,7 @@ def _make_workflow_event_mode( mon, ref = beamline.get_monitor("detector") pl = GenericTofWorkflow(run_types=[SampleRun], monitor_types=[]) - pl[DetectorData[SampleRun]] = mon + pl[RawDetector[SampleRun]] = mon pl[time_of_flight.DetectorLtotal[SampleRun]] = distance pl[time_of_flight.PulseStrideOffset] = pulse_stride_offset @@ -93,7 +93,7 @@ def _make_workflow_histogram_mode(dim, distance, choppers, lut_workflow, seed): ).rename(event_time_offset=dim) pl = GenericTofWorkflow(run_types=[SampleRun], monitor_types=[]) - pl[DetectorData[SampleRun]] = mon + pl[RawDetector[SampleRun]] = mon pl[time_of_flight.DetectorLtotal[SampleRun]] = distance lut_wf = lut_workflow.copy() @@ -161,7 +161,7 @@ def test_unwrap_with_no_choppers() -> None: error_threshold=1.0, ) - tofs = pl.compute(time_of_flight.DetectorTofData[SampleRun]) + tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) _validate_result_events( tofs=tofs, ref=ref, percentile=96, diff_threshold=1.0, rtol=0.02 @@ -184,7 +184,7 @@ def test_standard_unwrap(dist, lut_workflow_psc_choppers) -> None: error_threshold=0.1, ) - tofs = pl.compute(time_of_flight.DetectorTofData[SampleRun]) + tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) _validate_result_events( tofs=tofs, ref=ref, percentile=100, diff_threshold=0.02, rtol=0.05 @@ -206,7 +206,7 @@ def test_standard_unwrap_histogram_mode(dist, dim, lut_workflow_psc_choppers) -> seed=37, ) - tofs = pl.compute(time_of_flight.DetectorTofData[SampleRun]) + tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) _validate_result_histogram_mode( tofs=tofs, ref=ref, percentile=96, diff_threshold=0.4, rtol=0.05 @@ -225,7 +225,7 @@ def test_pulse_skipping_unwrap(dist, lut_workflow_pulse_skipping) -> None: error_threshold=0.1, ) - tofs = pl.compute(time_of_flight.DetectorTofData[SampleRun]) + tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) _validate_result_events( tofs=tofs, ref=ref, percentile=100, diff_threshold=0.1, rtol=0.05 @@ -249,7 +249,7 @@ def test_pulse_skipping_unwrap_180_phase_shift() -> None: error_threshold=0.1, ) - tofs = pl.compute(time_of_flight.DetectorTofData[SampleRun]) + tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) _validate_result_events( tofs=tofs, ref=ref, percentile=100, diff_threshold=0.1, rtol=0.05 @@ -269,7 +269,7 @@ def test_pulse_skipping_stride_offset_guess_gives_expected_result( error_threshold=0.1, ) - tofs = pl.compute(time_of_flight.DetectorTofData[SampleRun]) + tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) _validate_result_events( tofs=tofs, ref=ref, percentile=100, diff_threshold=0.1, rtol=0.05 @@ -302,7 +302,7 @@ def test_pulse_skipping_unwrap_when_all_neutrons_arrive_after_second_pulse() -> error_threshold=0.1, ) - tofs = pl.compute(time_of_flight.DetectorTofData[SampleRun]) + tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) _validate_result_events( tofs=tofs, ref=ref, percentile=100, diff_threshold=0.1, rtol=0.05 @@ -332,7 +332,7 @@ def test_pulse_skipping_unwrap_when_first_half_of_first_pulse_is_missing() -> No # Skip first pulse = half of the first frame a = mon.group('event_time_zero')['event_time_zero', 1:] a.bins.coords['event_time_zero'] = sc.bins_like(a, a.coords['event_time_zero']) - pl[DetectorData[SampleRun]] = a.bins.concat('event_time_zero') + pl[RawDetector[SampleRun]] = a.bins.concat('event_time_zero') pl[time_of_flight.DetectorLtotal[SampleRun]] = distance pl[time_of_flight.TimeOfFlightLookupTable] = lut_wf.compute( @@ -340,7 +340,7 @@ def test_pulse_skipping_unwrap_when_first_half_of_first_pulse_is_missing() -> No ) pl[time_of_flight.PulseStrideOffset] = 1 # Start the stride at the second pulse - tofs = pl.compute(time_of_flight.DetectorTofData[SampleRun]) + tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) # Convert to wavelength graph = {**beamline_graph(scatter=False), **elastic_graph("tof")} @@ -373,7 +373,7 @@ def test_pulse_skipping_unwrap_when_first_half_of_first_pulse_is_missing() -> No # Make sure that we have not lost too many events (we lose some because they may be # given a NaN tof from the lookup). assert sc.isclose( - pl.compute(DetectorData[SampleRun]).data.nansum(), + pl.compute(RawDetector[SampleRun]).data.nansum(), tofs.data.nansum(), rtol=sc.scalar(1.0e-3), ) @@ -396,7 +396,7 @@ def test_pulse_skipping_stride_3() -> None: error_threshold=0.1, ) - tofs = pl.compute(time_of_flight.DetectorTofData[SampleRun]) + tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) _validate_result_events( tofs=tofs, ref=ref, percentile=100, diff_threshold=0.1, rtol=0.05 @@ -412,7 +412,7 @@ def test_pulse_skipping_unwrap_histogram_mode(lut_workflow_pulse_skipping) -> No seed=9, ) - tofs = pl.compute(time_of_flight.DetectorTofData[SampleRun]) + tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) _validate_result_histogram_mode( tofs=tofs, ref=ref, percentile=96, diff_threshold=0.4, rtol=0.05 @@ -430,13 +430,13 @@ def test_unwrap_int(dtype, lut_workflow_psc_choppers) -> None: error_threshold=0.1, ) - mon = pl.compute(DetectorData[SampleRun]).copy() + mon = pl.compute(RawDetector[SampleRun]).copy() mon.bins.coords["event_time_offset"] = mon.bins.coords["event_time_offset"].to( dtype=dtype, unit="ns" ) - pl[DetectorData[SampleRun]] = mon + pl[RawDetector[SampleRun]] = mon - tofs = pl.compute(time_of_flight.DetectorTofData[SampleRun]) + tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) _validate_result_events( tofs=tofs, ref=ref, percentile=100, diff_threshold=0.02, rtol=0.05 diff --git a/tests/time_of_flight/wfm_test.py b/tests/time_of_flight/wfm_test.py index 04e196b6..c7c347c4 100644 --- a/tests/time_of_flight/wfm_test.py +++ b/tests/time_of_flight/wfm_test.py @@ -9,7 +9,7 @@ from scippneutron.conversion.graph.tof import elastic as elastic_graph from ess.reduce import time_of_flight -from ess.reduce.nexus.types import AnyRun, DetectorData, SampleRun +from ess.reduce.nexus.types import AnyRun, RawDetector, SampleRun from ess.reduce.time_of_flight import GenericTofWorkflow, TofLookupTableWorkflow, fakes sl = pytest.importorskip("sciline") @@ -130,7 +130,7 @@ def setup_workflow( error_threshold: float = 0.1, ) -> sl.Pipeline: pl = GenericTofWorkflow(run_types=[SampleRun], monitor_types=[]) - pl[DetectorData[SampleRun]] = raw_data + pl[RawDetector[SampleRun]] = raw_data pl[time_of_flight.DetectorLtotal[SampleRun]] = ltotal lut_wf = lut_workflow.copy() @@ -194,7 +194,7 @@ def test_dream_wfm( raw_data=raw, ltotal=ltotal, lut_workflow=lut_workflow_dream_choppers ) - tofs = pl.compute(time_of_flight.DetectorTofData[SampleRun]) + tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) # Convert to wavelength graph = {**beamline_graph(scatter=False), **elastic_graph("tof")} @@ -281,7 +281,7 @@ def test_dream_wfm_with_subframe_time_overlap( error_threshold=0.01, ) - tofs = pl.compute(time_of_flight.DetectorTofData[SampleRun]) + tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) # Convert to wavelength graph = {**beamline_graph(scatter=False), **elastic_graph("tof")} @@ -464,7 +464,7 @@ def test_v20_compute_wavelengths_from_wfm( raw_data=raw, ltotal=ltotal, lut_workflow=lut_workflow_v20_choppers ) - tofs = pl.compute(time_of_flight.DetectorTofData[SampleRun]) + tofs = pl.compute(time_of_flight.TofDetector[SampleRun]) # Convert to wavelength graph = {**beamline_graph(scatter=False), **elastic_graph("tof")} diff --git a/tests/time_of_flight/workflow_test.py b/tests/time_of_flight/workflow_test.py index 4187db8a..a7a4337c 100644 --- a/tests/time_of_flight/workflow_test.py +++ b/tests/time_of_flight/workflow_test.py @@ -11,9 +11,9 @@ from ess.reduce.nexus.types import ( AnyRun, CalibratedBeamline, - DetectorData, DiskChoppers, NeXusData, + RawDetector, SampleRun, ) from ess.reduce.time_of_flight import ( @@ -82,12 +82,12 @@ def test_GenericTofWorkflow_with_tof_lut_from_tof_simulation( # Should be able to compute DetectorData without chopper and simulation params # This contains event_time_offset (time-of-arrival). - _ = wf.compute(DetectorData[SampleRun]) + _ = wf.compute(RawDetector[SampleRun]) # By default, the workflow tries to load the LUT from file with pytest.raises(sciline.UnsatisfiedRequirement): _ = wf.compute(time_of_flight.TimeOfFlightLookupTable) with pytest.raises(sciline.UnsatisfiedRequirement): - _ = wf.compute(time_of_flight.DetectorTofData[SampleRun]) + _ = wf.compute(time_of_flight.TofDetector[SampleRun]) lut_wf = TofLookupTableWorkflow() lut_wf[DiskChoppers[AnyRun]] = fakes.psc_choppers() @@ -101,7 +101,7 @@ def test_GenericTofWorkflow_with_tof_lut_from_tof_simulation( wf[time_of_flight.TimeOfFlightLookupTable] = table # Should now be able to compute DetectorData with chopper and simulation params - detector = wf.compute(time_of_flight.DetectorTofData[SampleRun]) + detector = wf.compute(time_of_flight.TofDetector[SampleRun]) assert 'tof' in detector.bins.coords @@ -131,5 +131,5 @@ def test_GenericTofWorkflow_with_tof_lut_from_file( loaded_lut = wf.compute(time_of_flight.TimeOfFlightLookupTable) assert_identical(lut, loaded_lut) - detector = wf.compute(time_of_flight.DetectorTofData[SampleRun]) + detector = wf.compute(time_of_flight.TofDetector[SampleRun]) assert 'tof' in detector.bins.coords From b20654ec1e97f5d4071f5abc9ed8a9671f541401 Mon Sep 17 00:00:00 2001 From: Jan-Lukas Wynen Date: Tue, 28 Oct 2025 17:05:11 +0100 Subject: [PATCH 2/4] Remove CalibratedBeamline --- src/ess/reduce/nexus/types.py | 4 -- src/ess/reduce/nexus/workflow.py | 43 +-------------------- src/ess/reduce/time_of_flight/eto_to_tof.py | 35 ++++++++++++----- tests/nexus/workflow_test.py | 4 -- tests/time_of_flight/workflow_test.py | 13 +++++-- 5 files changed, 37 insertions(+), 62 deletions(-) diff --git a/src/ess/reduce/nexus/types.py b/src/ess/reduce/nexus/types.py index 1f2a7099..05dcbb62 100644 --- a/src/ess/reduce/nexus/types.py +++ b/src/ess/reduce/nexus/types.py @@ -203,10 +203,6 @@ class EmptyDetector(sciline.Scope[RunType, sc.DataArray], sc.DataArray): """Detector without neutron data.""" -class CalibratedBeamline(sciline.Scope[RunType, sc.DataArray], sc.DataArray): - """Calibrated beamline with detector and other components.""" - - class EmptyMonitor(sciline.Scope[RunType, MonitorType, sc.DataArray], sc.DataArray): """Monitor without neutron data.""" diff --git a/src/ess/reduce/nexus/workflow.py b/src/ess/reduce/nexus/workflow.py index b2499186..80057f5a 100644 --- a/src/ess/reduce/nexus/workflow.py +++ b/src/ess/reduce/nexus/workflow.py @@ -20,7 +20,6 @@ COMPONENT_CONSTRAINTS, AllNeXusComponents, Beamline, - CalibratedBeamline, Component, DetectorBankSizes, DetectorPositionOffset, @@ -364,9 +363,7 @@ def get_calibrated_detector( """ Extract the data array corresponding to a detector's signal field. - The returned data array includes coords and masks pertaining directly to the - signal values array, but not additional information about the detector. The - data array is reshaped to the logical detector shape, which by folding the data + The data array is reshaped to the logical detector shape, by folding the data array along the detector_number dimension. Parameters @@ -400,43 +397,8 @@ def get_calibrated_detector( ) -def assemble_beamline( - detector: EmptyDetector[RunType], - source_position: Position[snx.NXsource, RunType], - sample_position: Position[snx.NXsample, RunType], - gravity: GravityVector, -) -> CalibratedBeamline[RunType]: - """ - Add beamline information (gravity vector, source- and sample-position) to detector. - - This is performed separately and after :py:func:`get_calibrated_detector` to avoid - as false dependency of, e.g., the reshaped detector numbers on the sample position. - The latter can change during a run, e.g., for a rotating sample. The detector - numbers might be used, e.g., to mask certain detector pixels, and should not depend - on the sample position. - - Parameters - ---------- - detector: - NeXus detector group. - source_position: - Position of the neutron source. - sample_position: - Position of the sample. - gravity: - Gravity vector. - """ - return CalibratedBeamline[RunType]( - detector.assign_coords( - source_position=source_position, - sample_position=sample_position, - gravity=gravity, - ) - ) - - def assemble_detector_data( - detector: CalibratedBeamline[RunType], + detector: EmptyDetector[RunType], event_data: NeXusData[snx.NXdetector, RunType], ) -> RawDetector[RunType]: """ @@ -655,7 +617,6 @@ def load_measurement_metadata_from_nexus( no_detector_position_offset, load_nexus_sample, get_calibrated_detector, - assemble_beamline, assemble_detector_data, ) diff --git a/src/ess/reduce/time_of_flight/eto_to_tof.py b/src/ess/reduce/time_of_flight/eto_to_tof.py index 64fb2e0c..1d81da9b 100644 --- a/src/ess/reduce/time_of_flight/eto_to_tof.py +++ b/src/ess/reduce/time_of_flight/eto_to_tof.py @@ -12,6 +12,7 @@ import numpy as np import scipp as sc import scippneutron as scn +import scippnexus as snx from scippneutron._utils import elem_unit try: @@ -20,9 +21,11 @@ from .interpolator_scipy import Interpolator as InterpolatorImpl from ..nexus.types import ( - CalibratedBeamline, + EmptyDetector, EmptyMonitor, + GravityVector, MonitorType, + Position, RawDetector, RawMonitor, RunType, @@ -271,23 +274,35 @@ def _time_of_flight_data_events( def detector_ltotal_from_straight_line_approximation( - detector_beamline: CalibratedBeamline[RunType], + detector: EmptyDetector[RunType], + source_position: Position[snx.NXsource, RunType], + sample_position: Position[snx.NXsample, RunType], + gravity: GravityVector, ) -> DetectorLtotal[RunType]: - """ - Compute Ltotal for the detector pixels. + """Compute Ltotal for the detector pixels. + This is a naive straight-line approximation to Ltotal based on basic component positions. Parameters ---------- - detector_beamline: - Beamline data for the detector that contains the positions necessary to compute - the straight-line approximation to Ltotal (source, sample, and detector - positions). + detector: + Data array with detector positions. + source_position: + Position of the neutron source. + sample_position: + Position of the sample. + gravity: + Gravity vector. """ - graph = scn.conversion.graph.beamline.beamline(scatter=True) + graph = { + **scn.conversion.graph.beamline.beamline(scatter=True), + 'source_position': lambda: source_position, + 'sample_position': lambda: sample_position, + 'gravity': lambda: gravity, + } return DetectorLtotal[RunType]( - detector_beamline.transform_coords( + detector.transform_coords( "Ltotal", graph=graph, keep_intermediate=False ).coords["Ltotal"] ) diff --git a/tests/nexus/workflow_test.py b/tests/nexus/workflow_test.py index 4192bdc6..826eb38f 100644 --- a/tests/nexus/workflow_test.py +++ b/tests/nexus/workflow_test.py @@ -581,8 +581,6 @@ def test_load_detector_workflow(loki_tutorial_sample_run_60250: Path) -> None: wf[NeXusName[snx.NXdetector]] = 'larmor_detector' da = wf.compute(RawDetector[SampleRun]) assert 'position' in da.coords - assert 'sample_position' in da.coords - assert 'source_position' in da.coords assert da.bins is not None assert da.dims == ('detector_number',) @@ -598,8 +596,6 @@ def test_generic_nexus_workflow( wf[PreopenNeXusFile] = preopen da = wf.compute(RawDetector[SampleRun]) assert 'position' in da.coords - assert 'sample_position' in da.coords - assert 'source_position' in da.coords assert da.bins is not None assert da.dims == ('detector_number',) da = wf.compute(RawMonitor[SampleRun, FrameMonitor1]) diff --git a/tests/time_of_flight/workflow_test.py b/tests/time_of_flight/workflow_test.py index a7a4337c..2f6ad0aa 100644 --- a/tests/time_of_flight/workflow_test.py +++ b/tests/time_of_flight/workflow_test.py @@ -10,9 +10,10 @@ from ess.reduce import time_of_flight from ess.reduce.nexus.types import ( AnyRun, - CalibratedBeamline, DiskChoppers, + EmptyDetector, NeXusData, + Position, RawDetector, SampleRun, ) @@ -77,8 +78,11 @@ def test_GenericTofWorkflow_with_tof_lut_from_tof_simulation( calibrated_beamline: sc.DataArray, nexus_data: sc.DataArray ): wf = GenericTofWorkflow(run_types=[SampleRun], monitor_types=[]) - wf[CalibratedBeamline[SampleRun]] = calibrated_beamline + wf[EmptyDetector[SampleRun]] = calibrated_beamline wf[NeXusData[snx.NXdetector, SampleRun]] = nexus_data + # Unused because calibrated_beamline contains Ltotal but needed by wf structure + wf[Position[snx.NXsample, SampleRun]] = sc.vector([1e10, 1e10, 1e10], unit='m') + wf[Position[snx.NXsource, SampleRun]] = sc.vector([1e10, 1e10, 1e10], unit='m') # Should be able to compute DetectorData without chopper and simulation params # This contains event_time_offset (time-of-arrival). @@ -122,11 +126,14 @@ def test_GenericTofWorkflow_with_tof_lut_from_file( lut.save_hdf5(filename=tmp_path / "lut.h5") wf = GenericTofWorkflow(run_types=[SampleRun], monitor_types=[]) - wf[CalibratedBeamline[SampleRun]] = calibrated_beamline + wf[EmptyDetector[SampleRun]] = calibrated_beamline wf[NeXusData[snx.NXdetector, SampleRun]] = nexus_data wf[time_of_flight.TimeOfFlightLookupTableFilename] = ( tmp_path / "lut.h5" ).as_posix() + # Unused because calibrated_beamline contains Ltotal but needed by wf structure + wf[Position[snx.NXsample, SampleRun]] = sc.vector([1e10, 1e10, 1e10], unit='m') + wf[Position[snx.NXsource, SampleRun]] = sc.vector([1e10, 1e10, 1e10], unit='m') loaded_lut = wf.compute(time_of_flight.TimeOfFlightLookupTable) assert_identical(lut, loaded_lut) From 442fa5ee6207e80f8178359d7d61b769ed5420e5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 10:01:57 +0000 Subject: [PATCH 3/4] Apply automatic formatting --- docs/user-guide/tof/dream.ipynb | 408 +++++++++++---------- docs/user-guide/tof/frame-unwrapping.ipynb | 196 +++++----- docs/user-guide/tof/wfm.ipynb | 264 +++++++------ 3 files changed, 463 insertions(+), 405 deletions(-) diff --git a/docs/user-guide/tof/dream.ipynb b/docs/user-guide/tof/dream.ipynb index fe914f87..b798be12 100644 --- a/docs/user-guide/tof/dream.ipynb +++ b/docs/user-guide/tof/dream.ipynb @@ -1,8 +1,9 @@ { "cells": [ { - "metadata": {}, "cell_type": "markdown", + "id": "0", + "metadata": {}, "source": [ "# The DREAM chopper cascade\n", "\n", @@ -11,26 +12,26 @@ "\n", "The case of DREAM is interesting because the pulse-shaping choppers can be used in a number of different modes,\n", "and the number of cutouts the choppers have typically does not equal the number of frames observed at the detectors." - ], - "id": "eb68e1c217d3b35f" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "1", + "metadata": {}, + "outputs": [], "source": [ "import plopp as pp\n", "import scipp as sc\n", "from scippneutron.chopper import DiskChopper\n", "from ess.reduce.nexus.types import AnyRun, RawDetector, SampleRun\n", "from ess.reduce.time_of_flight import *" - ], - "id": "fcd99fcce9fd3502" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "2", + "metadata": {}, "source": [ "## Setting up the beamline\n", "\n", @@ -45,14 +46,14 @@ "- 1 overlap chopper (OC)\n", "- 1 band-control chopper (BCC)\n", "- 1 T0 chopper" - ], - "id": "621291535929c0d8" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "3", + "metadata": {}, + "outputs": [], "source": [ "psc1 = DiskChopper(\n", " frequency=sc.scalar(14.0, unit=\"Hz\"),\n", @@ -126,66 +127,78 @@ ")\n", "\n", "disk_choppers = {\"psc1\": psc1, \"psc2\": psc2, \"oc\": oc, \"bcc\": bcc, \"t0\": t0}" - ], - "id": "fea61f1e4ae7a82e" + ] }, { - "metadata": {}, "cell_type": "markdown", - "source": "It is possible to visualize the properties of the choppers by inspecting their `repr`:", - "id": "7057e21723b2bcbc" + "id": "4", + "metadata": {}, + "source": [ + "It is possible to visualize the properties of the choppers by inspecting their `repr`:" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, - "source": "psc2", - "id": "89c10a091396131e" + "id": "5", + "metadata": {}, + "outputs": [], + "source": [ + "psc2" + ] }, { - "metadata": {}, "cell_type": "markdown", - "source": "Define the source position which is required to compute the distance that neutrons travelled.", - "id": "6d6356854acaaaf2" + "id": "6", + "metadata": {}, + "source": [ + "Define the source position which is required to compute the distance that neutrons travelled." + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, - "source": "source_position = sc.vector([0, 0, -76.55], unit=\"m\")", - "id": "d47cb9a756270240" + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "source_position = sc.vector([0, 0, -76.55], unit=\"m\")" + ] }, { - "metadata": {}, "cell_type": "markdown", - "source": "### Adding a detector", - "id": "5333ea9873eecdca" + "id": "8", + "metadata": {}, + "source": [ + "### Adding a detector" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, - "source": "Ltotal = sc.scalar(76.55 + 1.125, unit=\"m\")", - "id": "803f33461f1b217b" + "id": "9", + "metadata": {}, + "outputs": [], + "source": [ + "Ltotal = sc.scalar(76.55 + 1.125, unit=\"m\")" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "10", + "metadata": {}, "source": [ "## Creating some neutron events\n", "\n", "We create a semi-realistic set of neutron events based on the ESS pulse." - ], - "id": "e045b7eda2f82d7" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "11", + "metadata": {}, + "outputs": [], "source": [ "from ess.reduce.time_of_flight.fakes import FakeBeamline\n", "\n", @@ -196,84 +209,94 @@ " run_length=sc.scalar(1 / 14, unit=\"s\") * 4,\n", " events_per_pulse=200_000,\n", ")" - ], - "id": "c3315ad68571b786" + ] }, { - "metadata": {}, "cell_type": "markdown", - "source": "The initial birth times and wavelengths of the generated neutrons can be visualized (for a single pulse):", - "id": "670fd7d344fd6faa" + "id": "12", + "metadata": {}, + "source": [ + "The initial birth times and wavelengths of the generated neutrons can be visualized (for a single pulse):" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "13", + "metadata": {}, + "outputs": [], "source": [ "one_pulse = ess_beamline.source.data[\"pulse\", 0]\n", "one_pulse.hist(birth_time=300).plot() + one_pulse.hist(wavelength=300).plot()" - ], - "id": "d15c3d1352ffc2eb" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, - "source": "ess_beamline.model_result.plot()", - "id": "9700ce289ebe604b" + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "ess_beamline.model_result.plot()" + ] }, { - "metadata": {}, "cell_type": "markdown", - "source": "From this fake beamline, we extract the raw neutron signal at our detector:", - "id": "917a527d7ab6c183" + "id": "15", + "metadata": {}, + "source": [ + "From this fake beamline, we extract the raw neutron signal at our detector:" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], "source": [ "raw_data = ess_beamline.get_monitor(\"detector\")[0]\n", "\n", "# Visualize\n", "raw_data.hist(event_time_offset=300).squeeze().plot()" - ], - "id": "4a85559e8f60dcc5" + ] }, { - "metadata": {}, "cell_type": "markdown", - "source": "The total number of neutrons in our sample data that make it through the to detector is:", - "id": "5a4338b781e1f184" + "id": "17", + "metadata": {}, + "source": [ + "The total number of neutrons in our sample data that make it through the to detector is:" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, - "source": "raw_data.sum().value", - "id": "fe6d6ac1ca120ba9" + "id": "18", + "metadata": {}, + "outputs": [], + "source": [ + "raw_data.sum().value" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "19", + "metadata": {}, "source": [ "## Computing time-of-flight\n", "\n", "Next, we use a workflow that provides an estimate of the real time-of-flight as a function of neutron time-of-arrival.\n", "\n", "### Setting up the workflow" - ], - "id": "8862d201b265f1f2" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "20", + "metadata": {}, + "outputs": [], "source": [ "wf = GenericTofWorkflow(run_types=[SampleRun], monitor_types=[])\n", "\n", @@ -281,23 +304,23 @@ "wf[DetectorLtotal[SampleRun]] = Ltotal\n", "\n", "wf.visualize(TofDetector[SampleRun])" - ], - "id": "3b0bcb24d17c3d9" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "21", + "metadata": {}, "source": [ "By default, the workflow tries to load a `TimeOfFlightLookupTable` from a file.\n", "\n", "In this notebook, instead of using such a pre-made file,\n", "we will build our own lookup table from the chopper information and apply it to the workflow." - ], - "id": "16ce814ebf1550ff" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "22", + "metadata": {}, "source": [ "### Building the time-of-flight lookup table\n", "\n", @@ -307,14 +330,14 @@ "From this,\n", "we build a lookup table on which bilinear interpolation is used to compute a wavelength (and its corresponding time-of-flight)\n", "for every neutron event." - ], - "id": "c331a1b58260bfca" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "23", + "metadata": {}, + "outputs": [], "source": [ "lut_wf = TofLookupTableWorkflow()\n", "lut_wf[DiskChoppers[AnyRun]] = disk_choppers\n", @@ -324,12 +347,12 @@ " sc.scalar(78.0, unit=\"m\"),\n", ")\n", "lut_wf.visualize(TimeOfFlightLookupTable)" - ], - "id": "773b3c5f6abc6dec" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "24", + "metadata": {}, "source": [ "### Inspecting the lookup table\n", "\n", @@ -340,14 +363,14 @@ "as a function of arrival time at the detector.\n", "\n", "This is the basis for creating our lookup table." - ], - "id": "233b25c08d53e4ab" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "25", + "metadata": {}, + "outputs": [], "source": [ "sim = lut_wf.compute(SimulationResults)\n", "\n", @@ -369,61 +392,65 @@ "fig1 = events.hist(wavelength=300, event_time_offset=300).plot(norm=\"log\")\n", "fig2 = events.hist(tof=300, event_time_offset=300).plot(norm=\"log\")\n", "fig1 + fig2" - ], - "id": "f6f5a75b784677f9" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "26", + "metadata": {}, "source": [ "The lookup table is then obtained by computing the weighted mean of the time-of-flight inside each time-of-arrival bin.\n", "\n", "This is illustrated by the orange line in the figure below:" - ], - "id": "19e4bd3a1b3448eb" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "27", + "metadata": {}, + "outputs": [], "source": [ "table = lut_wf.compute(TimeOfFlightLookupTable)\n", "\n", "# Overlay mean on the figure above\n", "table[\"distance\", 13].plot(ax=fig2.ax, color=\"C1\", ls=\"-\", marker=None)" - ], - "id": "a02b4e433a80e39a" + ] }, { - "metadata": {}, "cell_type": "markdown", - "source": "The full table covers a range of distances, and looks like", - "id": "104b3e255a92a1b8" + "id": "28", + "metadata": {}, + "source": [ + "The full table covers a range of distances, and looks like" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, - "source": "table.plot()", - "id": "616aff140261f422" + "id": "29", + "metadata": {}, + "outputs": [], + "source": [ + "table.plot()" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "30", + "metadata": {}, "source": [ "### Computing a time-of-flight coordinate\n", "\n", "We will now update our workflow, and use it to obtain our event data with a time-of-flight coordinate:" - ], - "id": "a3b56cc85fff1488" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "31", + "metadata": {}, + "outputs": [], "source": [ "# Set the computed lookup table onto the original workflow\n", "wf[TimeOfFlightLookupTable] = table\n", @@ -431,38 +458,42 @@ "# Compute time-of-flight of neutron events\n", "tofs = wf.compute(DetectorTofData[SampleRun])\n", "tofs" - ], - "id": "7d8aa7917fbc0f83" + ] }, { - "metadata": {}, "cell_type": "markdown", - "source": "Histogramming the data for a plot should show a profile with 6 bumps that correspond to the frames:", - "id": "81b1baf62777af47" + "id": "32", + "metadata": {}, + "source": [ + "Histogramming the data for a plot should show a profile with 6 bumps that correspond to the frames:" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, - "source": "tofs.bins.concat().hist(tof=300).plot()", - "id": "97fd3ea1ec70b8d7" + "id": "33", + "metadata": {}, + "outputs": [], + "source": [ + "tofs.bins.concat().hist(tof=300).plot()" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "34", + "metadata": {}, "source": [ "### Converting to wavelength\n", "\n", "We can now convert our new time-of-flight coordinate to a neutron wavelength, using `tranform_coords`:" - ], - "id": "5f82a667b9ac561c" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "35", + "metadata": {}, + "outputs": [], "source": [ "from scippneutron.conversion.graph.beamline import beamline\n", "from scippneutron.conversion.graph.tof import elastic\n", @@ -476,25 +507,25 @@ "\n", "histogrammed = wav_wfm.hist(wavelength=wavs).squeeze()\n", "histogrammed.plot()" - ], - "id": "79e6f40f08efd0cf" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "36", + "metadata": {}, "source": [ "### Comparing to the ground truth\n", "\n", "As a consistency check, because we actually know the wavelengths of the neutrons we created,\n", "we can compare the true neutron wavelengths to those we computed above." - ], - "id": "e3290da29db2d1dc" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "37", + "metadata": {}, + "outputs": [], "source": [ "ground_truth = ess_beamline.model_result[\"detector\"].data.flatten(to=\"event\")\n", "ground_truth = ground_truth[~ground_truth.masks[\"blocked_by_others\"]]\n", @@ -505,12 +536,12 @@ " \"ground_truth\": ground_truth.hist(wavelength=wavs),\n", " }\n", ")" - ], - "id": "d50317290185a8d0" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "38", + "metadata": {}, "source": [ "## Multiple detector pixels\n", "\n", @@ -520,14 +551,14 @@ "\n", "In our setup, we simply propagate the same neutrons to multiple detector pixels,\n", "as if they were not absorbed by the first pixel they meet." - ], - "id": "76fb6bfa093ab993" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "39", + "metadata": {}, + "outputs": [], "source": [ "Ltotal = sc.array(dims=[\"detector_number\"], values=[77.675, 76.0], unit=\"m\")\n", "monitors = {f\"detector{i}\": ltot for i, ltot in enumerate(Ltotal)}\n", @@ -539,24 +570,24 @@ " run_length=sc.scalar(1 / 14, unit=\"s\") * 4,\n", " events_per_pulse=200_000,\n", ")" - ], - "id": "b3c41e62376bb7aa" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "40", + "metadata": {}, "source": [ "Our raw data has now a `detector_number` dimension of length 2.\n", "\n", "We can plot the neutron `event_time_offset` for the two detector pixels and see that the offsets are shifted to the left for the pixel that is closest to the source." - ], - "id": "705c5b80b478e0f7" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "41", + "metadata": {}, + "outputs": [], "source": [ "raw_data = sc.concat(\n", " [ess_beamline.get_monitor(key)[0].squeeze() for key in monitors.keys()],\n", @@ -565,23 +596,23 @@ "\n", "# Visualize\n", "pp.plot(sc.collapse(raw_data.hist(event_time_offset=300), keep=\"event_time_offset\"))" - ], - "id": "2a799bef41680ef5" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "42", + "metadata": {}, "source": [ "Computing time-of-flight is done in the same way as above.\n", "We need to remember to update our workflow:" - ], - "id": "27b3cae1341918f2" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "43", + "metadata": {}, + "outputs": [], "source": [ "# Update workflow\n", "wf[DetectorData[SampleRun]] = raw_data\n", @@ -609,12 +640,12 @@ "]\n", "\n", "figs[0] + figs[1]" - ], - "id": "7bf393d248d1da63" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "44", + "metadata": {}, "source": [ "## Handling time overlap between subframes\n", "\n", @@ -628,14 +659,14 @@ "ScippNeutron handles this by masking the overlapping regions and throwing away any neutrons that lie within it.\n", "\n", "To simulate this, we modify slightly the phase and the cutouts of the band-control chopper:" - ], - "id": "5c55cb12891d252" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "45", + "metadata": {}, + "outputs": [], "source": [ "disk_choppers[\"bcc\"] = DiskChopper(\n", " frequency=sc.scalar(112.0, unit=\"Hz\"),\n", @@ -660,25 +691,25 @@ ")\n", "\n", "ess_beamline.model_result.plot()" - ], - "id": "1df23f3473bdd12d" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "46", + "metadata": {}, "source": [ "We can now see that there is no longer a gap between the two frames at the center of each pulse (green region).\n", "\n", "Another way of looking at this is looking at the wavelength vs time-of-arrival plot,\n", "which also shows overlap in time at the junction between the two frames:" - ], - "id": "441bc00d3c00233c" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "47", + "metadata": {}, + "outputs": [], "source": [ "# Update workflow\n", "lut_wf[DiskChoppers[AnyRun]] = disk_choppers\n", @@ -687,12 +718,12 @@ "\n", "events = to_event_time_offset(sim)\n", "events.hist(wavelength=300, event_time_offset=300).plot(norm=\"log\")" - ], - "id": "e1ee46302de31bc5" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "48", + "metadata": {}, "source": [ "The data in the lookup table contains both the mean time-of-flight for each distance and time-of-arrival bin,\n", "but also the variance inside each bin.\n", @@ -703,23 +734,23 @@ "we are computing a mean between two regions which have similar 'brightness'.\n", "\n", "This leads to a large variance, and this is visible when plotting the relative standard deviations on a 2D figure." - ], - "id": "81da86f190e5c099" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "49", + "metadata": {}, + "outputs": [], "source": [ "table = lut_wf.compute(TimeOfFlightLookupTable)\n", "table.plot() / (sc.stddevs(table) / sc.values(table)).plot(norm=\"log\")" - ], - "id": "df218876a9a16cf1" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "50", + "metadata": {}, "source": [ "The workflow has a parameter which is used to mask out regions where the standard deviation is above a certain threshold.\n", "\n", @@ -727,25 +758,25 @@ "as it can vary a lot depending on how much signal is received by the detectors,\n", "and how far the detectors are from the source.\n", "It is thus more robust to simply have a user tunable parameter on the workflow." - ], - "id": "c73cf0a547cc8392" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "51", + "metadata": {}, + "outputs": [], "source": [ "lut_wf[LookupTableRelativeErrorThreshold] = 0.01\n", "\n", "table = lut_wf.compute(TimeOfFlightLookupTable)\n", "table.plot()" - ], - "id": "d35accef1f0fe301" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "52", + "metadata": {}, "source": [ "We can now see that the central region is masked out.\n", "\n", @@ -754,14 +785,14 @@ "\n", "This is visible when comparing to the true neutron wavelengths,\n", "where we see that some counts were lost between the two frames." - ], - "id": "f19297304259ae8d" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "53", + "metadata": {}, + "outputs": [], "source": [ "wf[RawDetector[SampleRun]] = ess_beamline.get_monitor(\"detector\")[0]\n", "wf[DetectorLtotal[SampleRun]] = Ltotal\n", @@ -783,8 +814,7 @@ " \"ground_truth\": ground_truth.hist(wavelength=wavs),\n", " }\n", ")" - ], - "id": "e74639ba34ae849d" + ] } ], "metadata": {}, diff --git a/docs/user-guide/tof/frame-unwrapping.ipynb b/docs/user-guide/tof/frame-unwrapping.ipynb index 790dd75f..43f58116 100644 --- a/docs/user-guide/tof/frame-unwrapping.ipynb +++ b/docs/user-guide/tof/frame-unwrapping.ipynb @@ -1,8 +1,9 @@ { "cells": [ { - "metadata": {}, "cell_type": "markdown", + "id": "0", + "metadata": {}, "source": [ "# Frame Unwrapping\n", "\n", @@ -18,14 +19,14 @@ "\n", "We refer to the process of \"unwrapping\" these time stamps into an actual time-of-flight as *frame unwrapping*, since `event_time_offset` \"wraps around\" with the period of the proton pulse and neutrons created by different proton pulses may be recorded with the *same* `event_time_zero`.\n", "The figures in the remainder of this document will clarify this." - ], - "id": "e302add22f6aac6e" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "1", + "metadata": {}, + "outputs": [], "source": [ "import plopp as pp\n", "import scipp as sc\n", @@ -37,18 +38,20 @@ "Hz = sc.Unit(\"Hz\")\n", "deg = sc.Unit(\"deg\")\n", "meter = sc.Unit(\"m\")" - ], - "id": "6f70852a226098ed" + ] }, { - "metadata": {}, "cell_type": "markdown", - "source": "## Default mode", - "id": "3d7220fd0fdd5193" + "id": "2", + "metadata": {}, + "source": [ + "## Default mode" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "3", + "metadata": {}, "source": [ "Often there is a 1:1 correspondence between source pulses and neutron pulses propagated to the sample and detectors.\n", "\n", @@ -57,14 +60,14 @@ "- We begin by creating a source of neutrons which mimics the ESS source.\n", "- We set up a single chopper with a single opening\n", "- We place 4 'monitors' along the path of the neutrons (none of which absorb any neutrons)" - ], - "id": "6cf5d881afccc823" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "4", + "metadata": {}, + "outputs": [], "source": [ "source = tof.Source(facility=\"ess\", pulses=5)\n", "chopper = tof.Chopper(\n", @@ -90,12 +93,12 @@ " pl.ax.axvline(\n", " i * (1.0 / source.frequency).to(unit=\"us\").value, color=\"k\", ls=\"dotted\"\n", " )" - ], - "id": "394694d48b668309" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "5", + "metadata": {}, "source": [ "In the figure above, the dotted vertical lines represent the `event_time_zero` of each pulse,\n", "i.e. the start of a new origin for `event_time_offset` recorded at the various detectors.\n", @@ -110,14 +113,14 @@ "- **detector**: most of the neutrons arrive with an offset of two frames, but a small amount of neutrons (longest wavelengths) have a 3-frame offset\n", "\n", "We can further illustrate this by making histograms of the `event_time_offset` of the neutrons for each detector:" - ], - "id": "2897b8e1b183fac7" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "6", + "metadata": {}, + "outputs": [], "source": [ "subplots = pp.tiled(2, 2, figsize=(9, 6))\n", "nxevent_data = results.to_nxevent_data()\n", @@ -129,12 +132,12 @@ " .plot(title=f\"{det.name}={det.distance:c}\", color=f\"C{i}\")\n", " )\n", "subplots" - ], - "id": "f9270c5874469783" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "7", + "metadata": {}, "source": [ "### Computing time-of-flight\n", "\n", @@ -146,14 +149,14 @@ "according to their `event_time_offset`.\n", "\n", "The workflow can be visualized as follows:" - ], - "id": "d5d22f1cbbe2c41b" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "8", + "metadata": {}, + "outputs": [], "source": [ "wf = GenericTofWorkflow(run_types=[SampleRun], monitor_types=[])\n", "\n", @@ -161,12 +164,12 @@ "wf[DetectorLtotal[SampleRun]] = nxevent_data.coords[\"Ltotal\"]\n", "\n", "wf.visualize(TofDetector[SampleRun])" - ], - "id": "7f64b39fc153b3c" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "9", + "metadata": {}, "source": [ "By default, the workflow tries to load a `TimeOfFlightLookupTable` from a file.\n", "\n", @@ -197,14 +200,14 @@ "- convert the wavelengths to a real time-of-flight to give our final lookup table\n", "\n", "This is done using a dedicated workflow:" - ], - "id": "7b577d24c6fba85b" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], "source": [ "lut_wf = TofLookupTableWorkflow()\n", "lut_wf[LtotalRange] = detectors[0].distance, detectors[-1].distance\n", @@ -225,41 +228,43 @@ "lut_wf[SourcePosition] = sc.vector([0, 0, 0], unit=\"m\")\n", "\n", "lut_wf.visualize(TimeOfFlightLookupTable)" - ], - "id": "179557706f969c3" + ] }, { - "metadata": {}, "cell_type": "markdown", - "source": "The table can be computed, and visualized as follows:", - "id": "15318405af5ea5cc" + "id": "11", + "metadata": {}, + "source": [ + "The table can be computed, and visualized as follows:" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], "source": [ "table = lut_wf.compute(TimeOfFlightLookupTable)\n", "table.plot()" - ], - "id": "b8334d8f8cbbf69" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "13", + "metadata": {}, "source": [ "#### Computing time-of-flight from the lookup\n", "\n", "We now use the above table to perform a bilinear interpolation and compute the time-of-flight of every neutron." - ], - "id": "d2b4e717d26de413" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], "source": [ "# Set the computed lookup table on the original workflow\n", "wf[TimeOfFlightLookupTable] = table\n", @@ -269,12 +274,12 @@ "\n", "tof_hist = tofs.hist(tof=sc.scalar(500.0, unit=\"us\"))\n", "pp.plot({det.name: tof_hist[\"detector_number\", i] for i, det in enumerate(detectors)})" - ], - "id": "905be49c87951c01" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "15", + "metadata": {}, "source": [ "### Converting to wavelength\n", "\n", @@ -282,14 +287,14 @@ "\n", "Here, we compute the wavelengths from the time-of-flight using Scippneutron's `transform_coord` utility,\n", "and compare our computed wavelengths to the true wavelengths which are known for the simulated neutrons." - ], - "id": "35143d0f273bd5d7" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], "source": [ "from scippneutron.conversion.graph.beamline import beamline\n", "from scippneutron.conversion.graph.tof import elastic\n", @@ -311,12 +316,12 @@ "\n", "wavs[\"true\"] = ground_truth\n", "pp.plot(wavs)" - ], - "id": "377f07810e0b1a0c" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "17", + "metadata": {}, "source": [ "We see that all detectors agree on the wavelength spectrum,\n", "which is also in very good agreement with the true neutron wavelengths.\n", @@ -331,14 +336,14 @@ "This could also be every 3 or 4 pulses for very long instruments.\n", "\n", "The time-distance diagram may look something like:" - ], - "id": "d364ee859683ef82" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "18", + "metadata": {}, + "outputs": [], "source": [ "source = tof.Source(facility=\"ess\", pulses=4)\n", "choppers = [\n", @@ -367,12 +372,12 @@ "model = tof.Model(source=source, choppers=choppers, detectors=detectors)\n", "results = model.run()\n", "results.plot(blocked_rays=5000)" - ], - "id": "1e77c48a61e19e6c" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "19", + "metadata": {}, "source": [ "### Computing time-of-flight\n", "\n", @@ -380,14 +385,14 @@ "we can use the same workflow as before.\n", "\n", "The only difference is that we set the `PulseStride` to 2 to skip every other pulse." - ], - "id": "fc75e0b92ae9d6c5" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "20", + "metadata": {}, + "outputs": [], "source": [ "# Lookup table workflow\n", "lut_wf = TofLookupTableWorkflow()\n", @@ -410,38 +415,42 @@ "}\n", "lut_wf[SourcePosition] = sc.vector([0, 0, 0], unit=\"m\")\n", "lut_wf[DistanceResolution] = sc.scalar(0.5, unit=\"m\")" - ], - "id": "a4c4820a53566272" + ] }, { - "metadata": {}, "cell_type": "markdown", - "source": "The lookup table now spans 2 pulse periods, between 0 and ~142 ms:", - "id": "7792793fa62464c2" + "id": "21", + "metadata": {}, + "source": [ + "The lookup table now spans 2 pulse periods, between 0 and ~142 ms:" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "22", + "metadata": {}, + "outputs": [], "source": [ "table = lut_wf.compute(TimeOfFlightLookupTable)\n", "\n", "table.plot(figsize=(9, 4))" - ], - "id": "4c7e587ca83062b5" + ] }, { - "metadata": {}, "cell_type": "markdown", - "source": "The time-of-flight profiles are then:", - "id": "c8fc201975a2dd84" + "id": "23", + "metadata": {}, + "source": [ + "The time-of-flight profiles are then:" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "24", + "metadata": {}, + "outputs": [], "source": [ "# Reduction workflow\n", "wf = GenericTofWorkflow(run_types=[SampleRun], monitor_types=[])\n", @@ -454,24 +463,24 @@ "\n", "tof_hist = tofs.hist(tof=sc.scalar(500.0, unit=\"us\"))\n", "pp.plot({det.name: tof_hist[\"detector_number\", i] for i, det in enumerate(detectors)})" - ], - "id": "1b8524082e5ef8fc" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "25", + "metadata": {}, "source": [ "### Conversion to wavelength\n", "\n", "We now use the `transform_coords` as above to convert to wavelength." - ], - "id": "cc1566aaf0d76958" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "26", + "metadata": {}, + "outputs": [], "source": [ "# Define wavelength bin edges\n", "bins = sc.linspace(\"wavelength\", 1.0, 8.0, 401, unit=\"angstrom\")\n", @@ -487,8 +496,7 @@ "\n", "wavs[\"true\"] = ground_truth\n", "pp.plot(wavs)" - ], - "id": "e5c30300862a7353" + ] } ], "metadata": {}, diff --git a/docs/user-guide/tof/wfm.ipynb b/docs/user-guide/tof/wfm.ipynb index cf7ecf88..e6eb7000 100644 --- a/docs/user-guide/tof/wfm.ipynb +++ b/docs/user-guide/tof/wfm.ipynb @@ -1,8 +1,9 @@ { "cells": [ { - "metadata": {}, "cell_type": "markdown", + "id": "0", + "metadata": {}, "source": [ "# Wavelength frame multiplication\n", "\n", @@ -11,14 +12,14 @@ "\n", "In this notebook, we show how to use `essreduce`'s `time_of_flight` module to compute an accurate a time-of-flight coordinate,\n", "from which a wavelength can be computed." - ], - "id": "15cad2dca3d82303" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "1", + "metadata": {}, + "outputs": [], "source": [ "import numpy as np\n", "import plopp as pp\n", @@ -26,12 +27,12 @@ "from scippneutron.chopper import DiskChopper\n", "from ess.reduce.nexus.types import AnyRun, RawDetector, SampleRun\n", "from ess.reduce.time_of_flight import *" - ], - "id": "fb4a8a673861a8a1" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "2", + "metadata": {}, "source": [ "## Setting up the beamline\n", "\n", @@ -49,14 +50,14 @@ "\n", "The first 4 choppers have 6 openings (also known as cutouts),\n", "while the last one only has a single opening." - ], - "id": "3f10166ff4890cf1" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "3", + "metadata": {}, + "outputs": [], "source": [ "wfm1 = DiskChopper(\n", " frequency=sc.scalar(-70.0, unit=\"Hz\"),\n", @@ -154,73 +155,81 @@ ")\n", "\n", "disk_choppers = {\"wfm1\": wfm1, \"wfm2\": wfm2, \"foc1\": foc1, \"foc2\": foc2, \"pol\": pol}" - ], - "id": "6a37f19d68b47035" + ] }, { - "metadata": {}, "cell_type": "markdown", - "source": "It is possible to visualize the properties of the choppers by inspecting their `repr`:", - "id": "df010fa5b0468833" + "id": "4", + "metadata": {}, + "source": [ + "It is possible to visualize the properties of the choppers by inspecting their `repr`:" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, - "source": "wfm1", - "id": "d8460e03aba20a7f" + "id": "5", + "metadata": {}, + "outputs": [], + "source": [ + "wfm1" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "6", + "metadata": {}, "source": [ "Define the source position which is required to compute the distance that neutrons travelled.\n", "In this example, chopper positions are given relative to the source, so we set the source position to the origin." - ], - "id": "f579bd64a6814438" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, - "source": "source_position = sc.vector([0, 0, 0], unit=\"m\")", - "id": "1bc8fd09be092593" + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "source_position = sc.vector([0, 0, 0], unit=\"m\")" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "8", + "metadata": {}, "source": [ "### Adding a detector\n", "\n", "We also have a detector, which we place 26 meters away from the source." - ], - "id": "9d15bc81e3e36d8c" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, - "source": "Ltotal = sc.scalar(26.0, unit=\"m\")", - "id": "f66f1d5eab031032" + "id": "9", + "metadata": {}, + "outputs": [], + "source": [ + "Ltotal = sc.scalar(26.0, unit=\"m\")" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "10", + "metadata": {}, "source": [ "## Creating some neutron events\n", "\n", "We create a semi-realistic set of neutron events based on the ESS pulse." - ], - "id": "a637c51f345566bc" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "11", + "metadata": {}, + "outputs": [], "source": [ "from ess.reduce.time_of_flight.fakes import FakeBeamline\n", "\n", @@ -231,76 +240,84 @@ " run_length=sc.scalar(1 / 14, unit=\"s\") * 14,\n", " events_per_pulse=200_000,\n", ")" - ], - "id": "c47001c9979199f0" + ] }, { - "metadata": {}, "cell_type": "markdown", - "source": "The initial birth times and wavelengths of the generated neutrons can be visualized (for a single pulse):", - "id": "e83becdc10cf7c8b" + "id": "12", + "metadata": {}, + "source": [ + "The initial birth times and wavelengths of the generated neutrons can be visualized (for a single pulse):" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "13", + "metadata": {}, + "outputs": [], "source": [ "one_pulse = ess_beamline.source.data[\"pulse\", 0]\n", "one_pulse.hist(birth_time=300).plot() + one_pulse.hist(wavelength=300).plot()" - ], - "id": "e7d1be89391bb61" + ] }, { - "metadata": {}, "cell_type": "markdown", - "source": "From this fake beamline, we extract the raw neutron signal at our detector:", - "id": "80a865b278d8b352" + "id": "14", + "metadata": {}, + "source": [ + "From this fake beamline, we extract the raw neutron signal at our detector:" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "15", + "metadata": {}, + "outputs": [], "source": [ "raw_data = ess_beamline.get_monitor(\"detector\")[0]\n", "\n", "# Visualize\n", "raw_data.hist(event_time_offset=300).squeeze().plot()" - ], - "id": "d37634b3ef1c5471" + ] }, { - "metadata": {}, "cell_type": "markdown", - "source": "The total number of neutrons in our sample data that make it through the to detector is:", - "id": "a50df09b610c848" + "id": "16", + "metadata": {}, + "source": [ + "The total number of neutrons in our sample data that make it through the to detector is:" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, - "source": "raw_data.sum().value", - "id": "fc3c050b48c6d635" + "id": "17", + "metadata": {}, + "outputs": [], + "source": [ + "raw_data.sum().value" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "18", + "metadata": {}, "source": [ "## Computing time-of-flight\n", "\n", "Next, we use a workflow that provides an estimate of the real time-of-flight as a function of neutron time-of-arrival.\n", "\n", "### Setting up the workflow" - ], - "id": "8f51eb9f29b17352" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "19", + "metadata": {}, + "outputs": [], "source": [ "wf = GenericTofWorkflow(run_types=[SampleRun], monitor_types=[])\n", "\n", @@ -308,23 +325,23 @@ "wf[DetectorLtotal[SampleRun]] = Ltotal\n", "\n", "wf.visualize(TofDetector[SampleRun])" - ], - "id": "7033815b23e64b1b" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "20", + "metadata": {}, "source": [ "By default, the workflow tries to load a `TimeOfFlightLookupTable` from a file.\n", "\n", "In this notebook, instead of using such a pre-made file,\n", "we will build our own lookup table from the chopper information and apply it to the workflow." - ], - "id": "b093b608fb4d0eb8" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "21", + "metadata": {}, "source": [ "### Building the time-of-flight lookup table\n", "\n", @@ -334,14 +351,14 @@ "From this,\n", "we build a lookup table on which bilinear interpolation is used to compute a wavelength (and its corresponding time-of-flight)\n", "for every neutron event." - ], - "id": "9a924d0b22173beb" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "22", + "metadata": {}, + "outputs": [], "source": [ "lut_wf = TofLookupTableWorkflow()\n", "lut_wf[DiskChoppers[AnyRun]] = disk_choppers\n", @@ -349,12 +366,12 @@ "lut_wf[LtotalRange] = Ltotal, Ltotal\n", "lut_wf[LookupTableRelativeErrorThreshold] = 0.1\n", "lut_wf.visualize(TimeOfFlightLookupTable)" - ], - "id": "bae376f1e93c45d" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "23", + "metadata": {}, "source": [ "### Inspecting the lookup table\n", "\n", @@ -365,14 +382,14 @@ "as a function of arrival time at the detector.\n", "\n", "This is the basis for creating our lookup table." - ], - "id": "e6018b37b25667b5" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "24", + "metadata": {}, + "outputs": [], "source": [ "sim = lut_wf.compute(SimulationResults)\n", "\n", @@ -394,24 +411,24 @@ "fig1 = events.hist(wavelength=300, event_time_offset=300).plot(norm=\"log\")\n", "fig2 = events.hist(tof=300, event_time_offset=300).plot(norm=\"log\")\n", "fig1 + fig2" - ], - "id": "ee246bc7614d357f" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "25", + "metadata": {}, "source": [ "The lookup table is then obtained by computing the weighted mean of the time-of-flight inside each time-of-arrival bin.\n", "\n", "This is illustrated by the orange line in the figure below:" - ], - "id": "6562feda5f1bffab" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "26", + "metadata": {}, + "outputs": [], "source": [ "table = lut_wf.compute(TimeOfFlightLookupTable)\n", "\n", @@ -422,12 +439,12 @@ "fig2.canvas.xrange = 40000, 50000\n", "fig2.canvas.yrange = 35000, 50000\n", "fig2" - ], - "id": "293fb2c2f0a39822" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "27", + "metadata": {}, "source": [ "We can see that the orange lines follow the center of the colored areas.\n", "\n", @@ -437,51 +454,55 @@ "### Computing a time-of-flight coordinate\n", "\n", "We will now update our original reduction workflow to compute our event data with a time-of-flight coordinate:" - ], - "id": "b86048dd4a94c432" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "28", + "metadata": {}, + "outputs": [], "source": [ "wf[TimeOfFlightLookupTable] = table\n", "\n", "tofs = wf.compute(TofDetector[SampleRun])\n", "tofs" - ], - "id": "418970f38372457b" + ] }, { - "metadata": {}, "cell_type": "markdown", - "source": "Histogramming the data for a plot should show a profile with 6 bumps that correspond to the frames:", - "id": "7c0ce32dbc88f2a6" + "id": "29", + "metadata": {}, + "source": [ + "Histogramming the data for a plot should show a profile with 6 bumps that correspond to the frames:" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, - "source": "tofs.bins.concat().hist(tof=300).plot()", - "id": "cec58a544666f803" + "id": "30", + "metadata": {}, + "outputs": [], + "source": [ + "tofs.bins.concat().hist(tof=300).plot()" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "31", + "metadata": {}, "source": [ "### Converting to wavelength\n", "\n", "We can now convert our new time-of-flight coordinate to a neutron wavelength, using `tranform_coords`:" - ], - "id": "524232ce014bba27" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "32", + "metadata": {}, + "outputs": [], "source": [ "from scippneutron.conversion.graph.beamline import beamline\n", "from scippneutron.conversion.graph.tof import elastic\n", @@ -495,25 +516,25 @@ "\n", "histogrammed = wav_wfm.hist(wavelength=wavs).squeeze()\n", "histogrammed.plot()" - ], - "id": "12ea22c4b126d302" + ] }, { - "metadata": {}, "cell_type": "markdown", + "id": "33", + "metadata": {}, "source": [ "### Comparing to the ground truth\n", "\n", "As a consistency check, because we actually know the wavelengths of the neutrons we created,\n", "we can compare the true neutron wavelengths to those we computed above." - ], - "id": "8bea8b51b40e5a64" + ] }, { - "metadata": {}, "cell_type": "code", - "outputs": [], "execution_count": null, + "id": "34", + "metadata": {}, + "outputs": [], "source": [ "ground_truth = ess_beamline.model_result[\"detector\"].data.flatten(to=\"event\")\n", "ground_truth = ground_truth[~ground_truth.masks[\"blocked_by_others\"]]\n", @@ -524,8 +545,7 @@ " \"ground_truth\": ground_truth.hist(wavelength=wavs),\n", " }\n", ")" - ], - "id": "777fe64c28e75278" + ] } ], "metadata": {}, From 8ac1b533764a052071b5d74c54ed3abf2d031162 Mon Sep 17 00:00:00 2001 From: Jan-Lukas Wynen Date: Mon, 3 Nov 2025 11:10:24 +0100 Subject: [PATCH 4/4] Rename domain types in docs --- docs/user-guide/tof/dream.ipynb | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/user-guide/tof/dream.ipynb b/docs/user-guide/tof/dream.ipynb index b798be12..3ea29af8 100644 --- a/docs/user-guide/tof/dream.ipynb +++ b/docs/user-guide/tof/dream.ipynb @@ -456,7 +456,7 @@ "wf[TimeOfFlightLookupTable] = table\n", "\n", "# Compute time-of-flight of neutron events\n", - "tofs = wf.compute(DetectorTofData[SampleRun])\n", + "tofs = wf.compute(TofDetector[SampleRun])\n", "tofs" ] }, @@ -615,11 +615,11 @@ "outputs": [], "source": [ "# Update workflow\n", - "wf[DetectorData[SampleRun]] = raw_data\n", + "wf[RawDetector[SampleRun]] = raw_data\n", "wf[DetectorLtotal[SampleRun]] = Ltotal\n", "\n", "# Compute tofs and wavelengths\n", - "tofs = wf.compute(DetectorTofData[SampleRun])\n", + "tofs = wf.compute(TofDetector[SampleRun])\n", "wav_wfm = tofs.transform_coords(\"wavelength\", graph=graph)\n", "\n", "# Compare in plot\n", @@ -817,7 +817,13 @@ ] } ], - "metadata": {}, + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + } + }, "nbformat": 4, "nbformat_minor": 5 }