diff --git a/docs/user-guide/dream/dream-simulation-si-wfm.ipynb b/docs/user-guide/dream/dream-simulation-si-wfm.ipynb
new file mode 100644
index 00000000..9c7240ca
--- /dev/null
+++ b/docs/user-guide/dream/dream-simulation-si-wfm.ipynb
@@ -0,0 +1,677 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "0",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "# DREAM: simulation with Si powder sample\n",
+ "\n",
+ "This notebook illustrates the reduction of Powder Diffraction data for DREAM.\n",
+ "\n",
+ "- It uses simulated data from McStas/GEANT4 in the High-Flux configuration\n",
+ "- The samples were Si powder and Vanadium (for normalisation)\n",
+ "\n",
+ "For Vanadium, we use the result of a McStas/GEANT4 simulation using the `Incoherent` component to model the Vanadium sample. \n",
+ "\n",
+ "In this notebook the input files are NeXus files.\n",
+ "They created using one of the NeXus files from the CODA pipeline and stored in Scicat and by adding simulated data from McStas/GEANT4."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "1",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "import scipp as sc\n",
+ "import scippneutron as scn\n",
+ "import plopp as pp\n",
+ "from ess.dream import data as dream_data\n",
+ "import scippnexus as snx"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "2",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def _nonfinite_to_zero(\n",
+ " data: sc.DataGroup | sc.DataArray | sc.Dataset,\n",
+ ") -> sc.DataGroup | sc.DataArray | sc.Dataset:\n",
+ " \"\"\"\n",
+ " Replace NaN, positive and negative infs by zero\n",
+ " \"\"\"\n",
+ " if data.variances is not None:\n",
+ " replacement = sc.scalar(0.0, variance=0.0, unit=data.unit)\n",
+ " else:\n",
+ " replacement = sc.scalar(0.0, unit=data.unit)\n",
+ " return sc.nan_to_num(data, nan=replacement, posinf=replacement, neginf=replacement)\n",
+ "\n",
+ "\n",
+ "def clean_all_dets(\n",
+ " data: sc.DataGroup | sc.DataArray | sc.Dataset,\n",
+ ") -> sc.DataGroup | sc.DataArray | sc.Dataset:\n",
+ " \"\"\"Clean the data, removing NaNs.\"\"\"\n",
+ " dg_out = sc.DataGroup()\n",
+ " for item in data.keys():\n",
+ " out = data[item].copy()\n",
+ " out.data = _nonfinite_to_zero(out.data)\n",
+ " dg_out[item] = out\n",
+ " return dg_out\n",
+ "\n",
+ "\n",
+ "def load_dream_nxs(filename: str) -> sc.DataGroup:\n",
+ " \"\"\" \"\"\"\n",
+ " dg_out = sc.DataGroup()\n",
+ " pattern = \"_detector\"\n",
+ " period = (1.0 / sc.scalar(14.0, unit=\"Hz\")).to(unit=\"ns\")\n",
+ "\n",
+ " # temporarily hard-coding values of sample, source and cave monitor positions\n",
+ " # until bug fix for DREAM NeXus file\n",
+ " source_position = sc.vector(value=np.array([-3.478, 0.0, -76550]), unit=\"mm\")\n",
+ " sample_position = sc.vector(value=np.array([0.0, 0.0, 0.0]), unit=\"mm\")\n",
+ "\n",
+ " with snx.File(filename) as f:\n",
+ " inst = f[\"entry\"][\"instrument\"]\n",
+ " for key in inst:\n",
+ " if key.endswith(pattern):\n",
+ " name = key.removesuffix(pattern)\n",
+ " dg = inst[key][()]\n",
+ " da = dg[f\"{name}_event_data\"]\n",
+ " # dg_out[name]['event'].variances = dg_out[name]['event'].values\n",
+ "\n",
+ " # add position\n",
+ " da.coords[\"position\"] = sc.spatial.as_vectors(\n",
+ " da.coords[\"x_pixel_offset\"],\n",
+ " da.coords[\"y_pixel_offset\"],\n",
+ " da.coords[\"z_pixel_offset\"],\n",
+ " )\n",
+ "\n",
+ " # add sample and source position\n",
+ " da.coords[\"source_position\"] = source_position\n",
+ " da.coords[\"sample_position\"] = sample_position\n",
+ "\n",
+ " # rename ToF\n",
+ " da.bins.coords[\"tof\"] = da.bins.coords.pop(\"event_time_offset\")\n",
+ "\n",
+ " # add variances\n",
+ " da.bins.constituents[\"data\"].variances = da.bins.constituents[\n",
+ " \"data\"\n",
+ " ].values\n",
+ "\n",
+ " # In the raw data, the tofs extend beyond 71ms.\n",
+ " # This is thus not an event_time_offset.\n",
+ " # In addition, there is only one event_time_zero for all events,\n",
+ " # when there should be at least 2 because some tofs are larger than 71ms\n",
+ "\n",
+ " # Bin the data into bins with a 71ms period\n",
+ " da = da.bin(tof=sc.arange(\"tof\", 3) * period)\n",
+ " # Add a event_time_zero coord for each bin, but not as bin edges, as all events in the same pulse have the same event_time_zero, hence the `[:2]`\n",
+ " da.coords[\"event_time_zero\"] = (\n",
+ " sc.scalar(1730450434078980000, unit=\"ns\") + da.coords[\"tof\"]\n",
+ " )[:2]\n",
+ " # Remove the meaningless tof coord at the top level\n",
+ " del da.coords[\"tof\"]\n",
+ " # Remove the original (wrong) event_time_zero event coord inside the bins and rename the dim\n",
+ " del da.bins.coords[\"event_time_zero\"]\n",
+ " da = da.rename_dims(tof=\"event_time_zero\")\n",
+ " # Compute a proper event_time_offset as tof % period\n",
+ " da.bins.coords[\"event_time_offset\"] = (\n",
+ " da.bins.coords.pop(\"tof\") % period\n",
+ " ).to(unit=\"us\")\n",
+ " dg_out[name] = da\n",
+ "\n",
+ " return dg_out\n",
+ "\n",
+ "\n",
+ "def load_monitor(file: str) -> sc.DataArray:\n",
+ " \"\"\"\n",
+ " Load histogrammed monitor data,\n",
+ " making sure that the time-of-flight wraps around the pulse period.\n",
+ " \"\"\"\n",
+ " tof, tof_count = np.loadtxt(file, usecols=(0, 1), skiprows=42, unpack=True)\n",
+ "\n",
+ " raw_data = sc.DataArray(\n",
+ " data=sc.array(dims=[\"tof\"], values=tof_count, unit=\"counts\"),\n",
+ " coords={\"tof\": sc.array(dims=[\"tof\"], values=tof * 1000, unit=\"ns\")},\n",
+ " )\n",
+ " raw_data = raw_data[\"tof\", 0.0 * sc.Unit(\"ns\") : 1e8 * sc.Unit(\"ns\")].copy()\n",
+ "\n",
+ " # Convert coord to bin edges\n",
+ " tof = raw_data.coords[\"tof\"]\n",
+ " center = sc.midpoints(tof, dim=\"tof\")\n",
+ " left = center[\"tof\", 0:1] - (tof[\"tof\", 1] - tof[\"tof\", 0])\n",
+ " right = center[\"tof\", -1] + (tof[\"tof\", -1] - tof[\"tof\", -2])\n",
+ " raw_data.coords[\"tof\"] = sc.concat([left, center, right], \"tof\")\n",
+ "\n",
+ " period = (1.0 / sc.scalar(14.0, unit=\"Hz\")).to(unit=raw_data.coords[\"tof\"].unit)\n",
+ " bins = sc.sort(sc.concat([raw_data.coords[\"tof\"], period], \"tof\"), \"tof\")\n",
+ " raw_data = raw_data.rebin(tof=bins)\n",
+ " part1 = raw_data[\"tof\", :period].copy()\n",
+ " part2 = raw_data[\"tof\", period:].copy()\n",
+ " part2.coords[\"tof\"] = part2.coords[\"tof\"] % period\n",
+ " bins = sc.linspace(\"tof\", 0, period.value, 513, unit=period.unit)\n",
+ "\n",
+ " out = part2.rebin(tof=bins) + part1.rebin(tof=bins)\n",
+ " out = out.rename(tof=\"time_of_flight\")\n",
+ "\n",
+ " out.coords[\"Ltotal\"] = sc.scalar(np.sqrt(3.478**2 + 72330**2), unit=\"mm\")\n",
+ " return out"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3",
+ "metadata": {},
+ "source": [
+ "## Beamline properties\n",
+ "\n",
+ "We set up the chopper parameters,\n",
+ "used to determine the frame boundaries and compute an accurate neutron time-of-flight."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "4",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import sciline as sl\n",
+ "from scippneutron.chopper import DiskChopper\n",
+ "from scippneutron.tof import unwrap\n",
+ "from scippneutron.tof import chopper_cascade\n",
+ "\n",
+ "psc1 = DiskChopper(\n",
+ " frequency=sc.scalar(14.0, unit=\"Hz\"),\n",
+ " beam_position=sc.scalar(0.0, unit=\"deg\"),\n",
+ " phase=sc.scalar(286 - 180, unit=\"deg\"),\n",
+ " axle_position=sc.vector(value=[0, 0, 6.145], unit=\"m\"),\n",
+ " slit_begin=sc.array(\n",
+ " dims=[\"cutout\"],\n",
+ " values=[-1.23, 70.49, 84.765, 113.565, 170.29, 271.635, 286.035, 301.17],\n",
+ " unit=\"deg\",\n",
+ " ),\n",
+ " slit_end=sc.array(\n",
+ " dims=[\"cutout\"],\n",
+ " values=[1.23, 73.51, 88.035, 116.835, 175.31, 275.565, 289.965, 303.63],\n",
+ " unit=\"deg\",\n",
+ " ),\n",
+ " slit_height=sc.scalar(10.0, unit=\"cm\"),\n",
+ " radius=sc.scalar(30.0, unit=\"cm\"),\n",
+ ")\n",
+ "\n",
+ "psc2 = DiskChopper(\n",
+ " frequency=sc.scalar(-14.0, unit=\"Hz\"),\n",
+ " beam_position=sc.scalar(0.0, unit=\"deg\"),\n",
+ " phase=sc.scalar(-236, unit=\"deg\"),\n",
+ " axle_position=sc.vector(value=[0, 0, 6.155], unit=\"m\"),\n",
+ " slit_begin=sc.array(\n",
+ " dims=[\"cutout\"],\n",
+ " values=[-1.23, 27.0, 55.8, 142.385, 156.765, 214.115, 257.23, 315.49],\n",
+ " unit=\"deg\",\n",
+ " ),\n",
+ " slit_end=sc.array(\n",
+ " dims=[\"cutout\"],\n",
+ " values=[1.23, 30.6, 59.4, 145.615, 160.035, 217.885, 261.17, 318.11],\n",
+ " unit=\"deg\",\n",
+ " ),\n",
+ " slit_height=sc.scalar(10.0, unit=\"cm\"),\n",
+ " radius=sc.scalar(30.0, unit=\"cm\"),\n",
+ ")\n",
+ "\n",
+ "oc = DiskChopper(\n",
+ " frequency=sc.scalar(14.0, unit=\"Hz\"),\n",
+ " beam_position=sc.scalar(0.0, unit=\"deg\"),\n",
+ " phase=sc.scalar(297 - 180 - 90, unit=\"deg\"),\n",
+ " axle_position=sc.vector(value=[0, 0, 6.174], unit=\"m\"),\n",
+ " slit_begin=sc.array(dims=[\"cutout\"], values=[-27.6 * 0.5], unit=\"deg\"),\n",
+ " slit_end=sc.array(dims=[\"cutout\"], values=[27.6 * 0.5], unit=\"deg\"),\n",
+ " slit_height=sc.scalar(10.0, unit=\"cm\"),\n",
+ " radius=sc.scalar(30.0, unit=\"cm\"),\n",
+ ")\n",
+ "\n",
+ "bcc = DiskChopper(\n",
+ " frequency=sc.scalar(112.0, unit=\"Hz\"),\n",
+ " beam_position=sc.scalar(0.0, unit=\"deg\"),\n",
+ " phase=sc.scalar(240 - 180, unit=\"deg\"),\n",
+ " axle_position=sc.vector(value=[0, 0, 9.78], unit=\"m\"),\n",
+ " slit_begin=sc.array(dims=[\"cutout\"], values=[-36.875, 143.125], unit=\"deg\"),\n",
+ " slit_end=sc.array(dims=[\"cutout\"], values=[36.875, 216.875], unit=\"deg\"),\n",
+ " slit_height=sc.scalar(10.0, unit=\"cm\"),\n",
+ " radius=sc.scalar(30.0, unit=\"cm\"),\n",
+ ")\n",
+ "\n",
+ "t0 = DiskChopper(\n",
+ " frequency=sc.scalar(28.0, unit=\"Hz\"),\n",
+ " beam_position=sc.scalar(0.0, unit=\"deg\"),\n",
+ " phase=sc.scalar(280 - 180, unit=\"deg\"),\n",
+ " axle_position=sc.vector(value=[0, 0, 13.05], unit=\"m\"),\n",
+ " slit_begin=sc.array(dims=[\"cutout\"], values=[-314.9 * 0.5], unit=\"deg\"),\n",
+ " slit_end=sc.array(dims=[\"cutout\"], values=[314.9 * 0.5], unit=\"deg\"),\n",
+ " slit_height=sc.scalar(10.0, unit=\"cm\"),\n",
+ " radius=sc.scalar(30.0, unit=\"cm\"),\n",
+ ")\n",
+ "\n",
+ "disk_choppers = {\"psc1\": psc1, \"psc2\": psc2, \"oc\": oc, \"bcc\": bcc, \"t0\": t0}"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "5",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "choppers = {\n",
+ " key: chopper_cascade.Chopper.from_disk_chopper(\n",
+ " chop,\n",
+ " pulse_frequency=sc.scalar(14.0, unit=\"Hz\"),\n",
+ " npulses=1,\n",
+ " )\n",
+ " for key, chop in disk_choppers.items()\n",
+ "}"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "pulse_tmin = sc.scalar(0.0, unit=\"ms\")\n",
+ "pulse_tmax = sc.scalar(5.0, unit=\"ms\")\n",
+ "pulse_wmin = sc.scalar(0.2, unit=\"angstrom\")\n",
+ "pulse_wmax = sc.scalar(20.0, unit=\"angstrom\")\n",
+ "\n",
+ "frames = chopper_cascade.FrameSequence.from_source_pulse(\n",
+ " time_min=pulse_tmin,\n",
+ " time_max=pulse_tmax,\n",
+ " wavelength_min=pulse_wmin,\n",
+ " wavelength_max=pulse_wmax,\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "7",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Chop the frames\n",
+ "chopped = frames.chop(choppers.values())\n",
+ "\n",
+ "# Propagate the neutrons to the detector\n",
+ "at_detector = chopped.propagate_to(sc.scalar(76.5, unit=\"m\"))\n",
+ "\n",
+ "# Visualize the results\n",
+ "cascade_fig, cascade_ax = at_detector.draw()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": []
+ },
+ "source": [
+ "## Load and process monitor data\n",
+ "\n",
+ "For normalisation, we consider the Cave monitor contained in McStas model of DREAM.\n",
+ "We load the monitors from the Si and Vanadium simulations.\n",
+ "\n",
+ "\n",
+ "
\n",
+ "\n",
+ "*NOTE:*\n",
+ "\n",
+ "We use the ASCII file from McStas. This is a temporary solution, which will be replaced once monitor data have been added to the NeXus file.\n",
+ "\n",
+ "
\n",
+ "\n",
+ "### Load raw data"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "9",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "tof_mon_cave_si = load_monitor(dream_data.stap_demo_2024_si_monitor())\n",
+ "tof_mon_cave_van = load_monitor(dream_data.stap_demo_2024_vanadium_monitor())\n",
+ "\n",
+ "tof_mon_cave_si.plot(grid=True, title=\"Si Cave tof monitor\") + tof_mon_cave_van.plot(\n",
+ " grid=True, title=\"Vana Cave tof monitor\"\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "10",
+ "metadata": {},
+ "source": [
+ "### Convert monitor data to wavelength"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "11",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Setup workflow\n",
+ "pl = sl.Pipeline(\n",
+ " (*unwrap.providers(), unwrap.re_histogram_tof_data), params=unwrap.params()\n",
+ ")\n",
+ "pl[unwrap.PulsePeriod] = 1.0 / sc.scalar(14.0, unit=\"Hz\")\n",
+ "\n",
+ "pl[unwrap.SourceTimeRange] = pulse_tmin, pulse_tmax\n",
+ "pl[unwrap.SourceWavelengthRange] = pulse_wmin, pulse_wmax\n",
+ "pl[unwrap.Choppers] = choppers\n",
+ "\n",
+ "# Si sample\n",
+ "pl[unwrap.RawData] = tof_mon_cave_si\n",
+ "pl[unwrap.Ltotal] = tof_mon_cave_si.coords[\"Ltotal\"]\n",
+ "tofs_si = pl.compute(unwrap.ReHistogrammedTofData)\n",
+ "\n",
+ "# Vanadium sample\n",
+ "pl[unwrap.RawData] = tof_mon_cave_van\n",
+ "pl[unwrap.Ltotal] = tof_mon_cave_van.coords[\"Ltotal\"]\n",
+ "tofs_van = pl.compute(unwrap.ReHistogrammedTofData)\n",
+ "\n",
+ "\n",
+ "# Convert to wavelength\n",
+ "wlgth_graph_monitor = {\n",
+ " **scn.conversion.graph.beamline.beamline(scatter=False),\n",
+ " **scn.conversion.graph.tof.elastic_wavelength(\"tof\"),\n",
+ "}\n",
+ "\n",
+ "mon_cave_si_wlgth = tofs_si.transform_coords(\"wavelength\", graph=wlgth_graph_monitor)\n",
+ "mon_cave_van_wlgth = tofs_van.transform_coords(\"wavelength\", graph=wlgth_graph_monitor)\n",
+ "\n",
+ "# Plot\n",
+ "mon_cave_si_wlgth.plot(\n",
+ " title=\"Monitor for Si sample in wavelength\", grid=True\n",
+ ") + mon_cave_van_wlgth.plot(\n",
+ " title=\"Monitor for Vanadium sample in wavelength\", grid=True\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "12",
+ "metadata": {},
+ "source": [
+ "## Load and process detector data\n",
+ "\n",
+ "### Load raw data"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "13",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from ess.dream import data as dream_data\n",
+ "\n",
+ "sample = load_dream_nxs(dream_data.stap_demo_2024_si_sample())\n",
+ "vana = load_dream_nxs(dream_data.stap_demo_2024_vanadium_sample())\n",
+ "\n",
+ "sample"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "14",
+ "metadata": {},
+ "source": [
+ "### Conversion to wavelength"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "15",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "detector_graph = {\n",
+ " **scn.conversion.graph.beamline.beamline(scatter=True),\n",
+ " **scn.conversion.graph.tof.elastic_wavelength(\"tof\"),\n",
+ "}\n",
+ "\n",
+ "dg_si_wlgth = sc.DataGroup()\n",
+ "dg_van_wlgth = sc.DataGroup()\n",
+ "\n",
+ "for run, out in ((sample, dg_si_wlgth), (vana, dg_van_wlgth)):\n",
+ " for key, val in run.items():\n",
+ " da = val.transform_coords([\"Ltotal\"], graph=detector_graph)\n",
+ " pl[unwrap.RawData] = da\n",
+ " pl[unwrap.Ltotal] = da.coords[\"Ltotal\"]\n",
+ "\n",
+ " result = pl.compute(unwrap.TofData)\n",
+ " out[key] = result.transform_coords(\"wavelength\", graph=detector_graph)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "16",
+ "metadata": {},
+ "source": [
+ "## Normalize by integrated monitor counts"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "17",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# TODO: we multiplied the sums by the bin width in the original notebook\n",
+ "\n",
+ "dg_si_wlgth_norm = dg_si_wlgth / mon_cave_si_wlgth.sum().data\n",
+ "dg_van_wlgth_norm = dg_van_wlgth / mon_cave_van_wlgth.sum().data"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "18",
+ "metadata": {},
+ "source": [
+ "## Convert to d-spacing"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "19",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "dspacing_graph = {\n",
+ " **scn.conversion.graph.beamline.beamline(scatter=True),\n",
+ " **scn.conversion.graph.tof.elastic_dspacing(\"tof\"),\n",
+ "}\n",
+ "\n",
+ "dg_si_dsp = dg_si_wlgth_norm.transform_coords(\n",
+ " [\"dspacing\", \"two_theta\"], graph=dspacing_graph\n",
+ ")\n",
+ "dg_van_dsp = dg_van_wlgth_norm.transform_coords(\n",
+ " [\"dspacing\", \"two_theta\"], graph=dspacing_graph\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "20",
+ "metadata": {},
+ "source": [
+ "## Binning in d-spacing and two-theta\n",
+ "\n",
+ "We consider only the high-resolution and endcap backward detector banks."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "21",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "ttheta_bin_edges_per_bank = {}\n",
+ "si_norm_bin = sc.DataGroup()\n",
+ "van_norm_bin = sc.DataGroup()\n",
+ "\n",
+ "dspacing_bins = sc.linspace(\"dspacing\", 0.55, 1.75, 601, unit=\"angstrom\")\n",
+ "\n",
+ "for bank in [\"endcap_backward\", \"high_resolution\"]:\n",
+ "\n",
+ " ttheta_bin_edges_per_bank[bank] = (\n",
+ " dg_si_dsp[bank].coords[\"two_theta\"].min().value,\n",
+ " dg_si_dsp[bank].coords[\"two_theta\"].max().value,\n",
+ " )\n",
+ "\n",
+ " twotheta_bins = sc.linspace(\n",
+ " \"two_theta\",\n",
+ " ttheta_bin_edges_per_bank[bank][0],\n",
+ " ttheta_bin_edges_per_bank[bank][1],\n",
+ " 181,\n",
+ " unit=\"rad\",\n",
+ " )\n",
+ "\n",
+ " si_norm_bin[bank] = dg_si_dsp[bank].bin(\n",
+ " two_theta=twotheta_bins, dspacing=dspacing_bins\n",
+ " )\n",
+ " van_norm_bin[bank] = dg_van_dsp[bank].bin(\n",
+ " two_theta=twotheta_bins, dspacing=dspacing_bins\n",
+ " )\n",
+ "\n",
+ "si_norm_bin"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "22",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "p_hr = pp.plot(\n",
+ " si_norm_bin[\"high_resolution\"].bins.concat(\"event_time_zero\").hist(),\n",
+ " norm=\"log\",\n",
+ " title=\"high_resolution\",\n",
+ ")\n",
+ "p_ebwd = pp.plot(\n",
+ " si_norm_bin[\"endcap_backward\"].bins.concat(\"event_time_zero\").hist(),\n",
+ " norm=\"log\",\n",
+ " title=\"endcap_backward\",\n",
+ ")\n",
+ "\n",
+ "p_hr / p_ebwd"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "23",
+ "metadata": {},
+ "source": [
+ "## Normalize Si sample data by Vanadium"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "24",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "si_final_norm = si_norm_bin.hist() / van_norm_bin.hist()\n",
+ "cleaned_final_si = clean_all_dets(si_final_norm).sum(\"event_time_zero\")\n",
+ "cleaned_final_si"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "25",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fig = cleaned_final_si.sum(\"two_theta\").plot()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "26",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "theory = np.array(\n",
+ " [\n",
+ " 3.1353, 1.6374, 1.5677, 1.3576, 1.2458, 1.0451, 0.9600, 0.9179, 0.8281,\n",
+ " 0.8187, 0.7838, 0.7604, 0.7257, 0.7070, 0.6788, 0.6634, 0.6271, 0.6229,\n",
+ " 0.6072, 0.5961, 0.5693, 0.5543, 0.5458, 0.5250, 0.5226,\n",
+ " ]\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "27",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "for line in theory:\n",
+ " fig.ax.axvline(line, color=\"gray\")\n",
+ "fig.canvas.draw()\n",
+ "fig.fig.set_size_inches((12.0, 4.0))\n",
+ "fig"
+ ]
+ }
+ ],
+ "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"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/requirements/base.txt b/requirements/base.txt
index 153660af..e34e07ed 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -5,7 +5,7 @@
#
# pip-compile-multi
#
-asttokens==2.4.1
+asttokens==3.0.0
# via stack-data
click==8.1.7
# via dask
@@ -19,7 +19,7 @@ cyclebane==24.10.0
# via sciline
cycler==0.12.1
# via matplotlib
-dask==2024.11.2
+dask==2024.12.0
# via -r base.in
decorator==5.1.1
# via ipython
@@ -29,7 +29,7 @@ exceptiongroup==1.2.2
# via ipython
executing==2.1.0
# via stack-data
-fonttools==4.55.0
+fonttools==4.55.3
# via matplotlib
fsspec==2024.10.0
# via dask
@@ -43,7 +43,7 @@ importlib-metadata==8.5.0
# via dask
ipydatawidgets==4.3.5
# via pythreejs
-ipython==8.29.0
+ipython==8.30.0
# via ipywidgets
ipywidgets==8.1.5
# via
@@ -57,7 +57,7 @@ kiwisolver==1.4.7
# via matplotlib
locket==1.0.0
# via partd
-matplotlib==3.9.2
+matplotlib==3.10.0
# via
# mpltoolbox
# plopp
@@ -67,7 +67,7 @@ mpltoolbox==24.5.1
# via scippneutron
networkx==3.4.2
# via cyclebane
-numpy==2.1.3
+numpy==2.2.0
# via
# -r base.in
# contourpy
@@ -123,7 +123,7 @@ scipp==24.11.2
# essreduce
# scippneutron
# scippnexus
-scippneutron==24.11.0
+scippneutron==24.12.0
# via
# -r base.in
# essreduce
@@ -136,10 +136,8 @@ scipy==1.14.1
# via
# scippneutron
# scippnexus
-six==1.16.0
- # via
- # asttokens
- # python-dateutil
+six==1.17.0
+ # via python-dateutil
stack-data==0.6.3
# via ipython
toolz==1.0.0
diff --git a/requirements/basetest.txt b/requirements/basetest.txt
index ad1f8e94..906d87e8 100644
--- a/requirements/basetest.txt
+++ b/requirements/basetest.txt
@@ -5,7 +5,7 @@
#
# pip-compile-multi
#
-certifi==2024.8.30
+certifi==2024.12.14
# via requests
charset-normalizer==3.4.0
# via requests
@@ -15,7 +15,7 @@ idna==3.10
# via requests
iniconfig==2.0.0
# via pytest
-numpy==2.1.3
+numpy==2.2.0
# via pandas
packaging==24.2
# via
@@ -29,7 +29,7 @@ pluggy==1.5.0
# via pytest
pooch==1.8.2
# via -r basetest.in
-pytest==8.3.3
+pytest==8.3.4
# via -r basetest.in
python-dateutil==2.9.0.post0
# via pandas
@@ -37,7 +37,7 @@ pytz==2024.2
# via pandas
requests==2.32.3
# via pooch
-six==1.16.0
+six==1.17.0
# via python-dateutil
tomli==2.2.1
# via pytest
diff --git a/requirements/ci.txt b/requirements/ci.txt
index 045acba6..c3ccdde1 100644
--- a/requirements/ci.txt
+++ b/requirements/ci.txt
@@ -7,7 +7,7 @@
#
cachetools==5.5.0
# via tox
-certifi==2024.8.30
+certifi==2024.12.14
# via requests
chardet==5.2.0
# via tox
diff --git a/requirements/dev.txt b/requirements/dev.txt
index d7d5de02..d77a0233 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -14,7 +14,7 @@
-r wheels.txt
annotated-types==0.7.0
# via pydantic
-anyio==4.6.2.post1
+anyio==4.7.0
# via
# httpx
# jupyter-server
@@ -40,7 +40,7 @@ h11==0.14.0
# via httpcore
httpcore==1.0.7
# via httpx
-httpx==0.27.2
+httpx==0.28.1
# via jupyterlab
isoduration==20.11.0
# via jsonschema
@@ -67,7 +67,7 @@ jupyter-server==2.14.2
# notebook-shim
jupyter-server-terminals==0.5.3
# via jupyter-server
-jupyterlab==4.3.1
+jupyterlab==4.3.3
# via -r dev.in
jupyterlab-server==2.27.3
# via jupyterlab
@@ -83,15 +83,15 @@ pip-tools==7.4.1
# via pip-compile-multi
plumbum==1.9.0
# via copier
-prometheus-client==0.21.0
+prometheus-client==0.21.1
# via jupyter-server
pycparser==2.22
# via cffi
-pydantic==2.10.2
+pydantic==2.10.3
# via copier
pydantic-core==2.27.1
# via pydantic
-python-json-logger==2.0.7
+python-json-logger==3.2.1
# via jupyter-events
questionary==1.10.0
# via copier
@@ -106,16 +106,14 @@ rfc3986-validator==0.1.1
send2trash==1.8.3
# via jupyter-server
sniffio==1.3.1
- # via
- # anyio
- # httpx
+ # via anyio
terminado==0.18.1
# via
# jupyter-server
# jupyter-server-terminals
toposort==1.10
# via pip-compile-multi
-types-python-dateutil==2.9.0.20241003
+types-python-dateutil==2.9.0.20241206
# via arrow
uri-template==1.3.0
# via jsonschema
diff --git a/requirements/docs.txt b/requirements/docs.txt
index 4dc07134..316cdbc4 100644
--- a/requirements/docs.txt
+++ b/requirements/docs.txt
@@ -10,7 +10,7 @@ accessible-pygments==0.0.5
# via pydata-sphinx-theme
alabaster==1.0.0
# via sphinx
-attrs==24.2.0
+attrs==24.3.0
# via
# jsonschema
# referencing
@@ -24,11 +24,11 @@ beautifulsoup4==4.12.3
# pydata-sphinx-theme
bleach==6.2.0
# via nbconvert
-certifi==2024.8.30
+certifi==2024.12.14
# via requests
charset-normalizer==3.4.0
# via requests
-debugpy==1.8.9
+debugpy==1.8.11
# via ipykernel
defusedxml==0.7.1
# via nbconvert
@@ -40,7 +40,7 @@ docutils==0.21.2
# pydata-sphinx-theme
# sphinx
# sphinxcontrib-bibtex
-fastjsonschema==2.21.0
+fastjsonschema==2.21.1
# via nbformat
idna==3.10
# via requests
@@ -93,7 +93,7 @@ mistune==3.0.2
# via nbconvert
myst-parser==4.0.0
# via -r docs.in
-nbclient==0.10.0
+nbclient==0.10.1
# via nbconvert
nbconvert==7.16.4
# via nbsphinx
@@ -142,7 +142,7 @@ requests==2.32.3
# via
# pooch
# sphinx
-rpds-py==0.21.0
+rpds-py==0.22.3
# via
# jsonschema
# referencing
diff --git a/requirements/nightly.txt b/requirements/nightly.txt
index 7ccb8b60..85bf1099 100644
--- a/requirements/nightly.txt
+++ b/requirements/nightly.txt
@@ -6,7 +6,7 @@
# pip-compile-multi
#
-r basetest.txt
-asttokens==2.4.1
+asttokens==3.0.0
# via stack-data
click==8.1.7
# via dask
@@ -20,7 +20,7 @@ cyclebane==24.10.0
# via sciline
cycler==0.12.1
# via matplotlib
-dask==2024.11.2
+dask==2024.12.0
# via -r nightly.in
decorator==5.1.1
# via ipython
@@ -28,7 +28,7 @@ essreduce @ git+https://github.com/scipp/essreduce@main
# via -r nightly.in
executing==2.1.0
# via stack-data
-fonttools==4.55.0
+fonttools==4.55.3
# via matplotlib
fsspec==2024.10.0
# via dask
@@ -42,7 +42,7 @@ importlib-metadata==8.5.0
# via dask
ipydatawidgets==4.3.5
# via pythreejs
-ipython==8.29.0
+ipython==8.30.0
# via ipywidgets
ipywidgets==8.1.5
# via
@@ -56,7 +56,7 @@ kiwisolver==1.4.7
# via matplotlib
locket==1.0.0
# via partd
-matplotlib==3.9.2
+matplotlib==3.10.0
# via
# mpltoolbox
# plopp
diff --git a/src/ess/dream/data.py b/src/ess/dream/data.py
index 48d00ec7..aefe8f5c 100644
--- a/src/ess/dream/data.py
+++ b/src/ess/dream/data.py
@@ -27,6 +27,10 @@ def _make_pooch():
"DREAM_simple_pwd_workflow/Cave_TOF_Monitor_diam_in_can.dat": "md5:ef24f4a4186c628574046e6629e31611", # noqa: E501
"DREAM_simple_pwd_workflow/Cave_TOF_Monitor_van_can.dat": "md5:e63456c347fb36a362a0b5ae2556b3cf", # noqa: E501
"DREAM_simple_pwd_workflow/Cave_TOF_Monitor_vana_inc_coh.dat": "md5:701d66792f20eb283a4ce76bae0c8f8f", # noqa: E501
+ "stap_demo_october_2024/268227_00024779_Si_BC_offset_240_deg.hdf": "md5:d0d89bcae5bee0619e0333adfda57d70", # noqa: E501
+ "stap_demo_october_2024/268227_00024779_Vana_inc_BC_offset_240_deg_wlgth.hdf": "md5:fbc8bde80c8889eea6ccea6f3f8eb9b8", # noqa: E501
+ "stap_demo_october_2024/Cave_TOF_Monitor_hf_si_bc240.dat": "md5:8cf6c0a1b42185662ddc579eda07d1e6", # noqa: E501
+ "stap_demo_october_2024/Cave_TOF_Monitor_hf_vanainc_bc240.dat": "md5:a9d5593d363a1c1fc51eb86757d6fa81", # noqa: E501
},
)
@@ -146,3 +150,37 @@ def simulated_monitor_empty_can() -> str:
This is the DREAM cave monitor for ``simulated_empty_can``.
"""
return get_path("DREAM_simple_pwd_workflow/Cave_TOF_Monitor_van_can.dat")
+
+
+def stap_demo_2024_si_sample() -> str:
+ """
+ Path to a file containing detector event data for a McStas/Geant4 simulation with a
+ silicon sample.
+ """
+ return get_path("stap_demo_october_2024/268227_00024779_Si_BC_offset_240_deg.hdf")
+
+
+def stap_demo_2024_vanadium_sample() -> str:
+ """
+ Path to a file containing detector event data for a McStas/Geant4 simulation with a
+ vanadium sample.
+ """
+ return get_path(
+ "stap_demo_october_2024/268227_00024779_Vana_inc_BC_offset_240_deg_wlgth.hdf"
+ )
+
+
+def stap_demo_2024_si_monitor() -> str:
+ """
+ Path to a file containing histogrammed monitor data for a McStas/Geant4 simulation
+ with a silicon sample.
+ """
+ return get_path("stap_demo_october_2024/Cave_TOF_Monitor_hf_si_bc240.dat")
+
+
+def stap_demo_2024_vanadium_monitor() -> str:
+ """
+ Path to a file containing histogrammed monitor data for a McStas/Geant4 simulation
+ with a vanadium sample.
+ """
+ return get_path("stap_demo_october_2024/Cave_TOF_Monitor_hf_vanainc_bc240.dat")