Skip to content
1 change: 1 addition & 0 deletions packages/essimaging/docs/odin/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ maxdepth: 1
---

odin-data-reduction
odin-orca-image-normalization
odin-make-wavelength-lookup-table
```
179 changes: 179 additions & 0 deletions packages/essimaging/docs/odin/odin-orca-image-normalization.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "0",
"metadata": {},
"source": [
"# Odin: Orca image normalization workflow\n",
"\n",
"This notebook shows how to use the workflow to compute normalized images recorded by the Orca detector on the ODIN instrument."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1",
"metadata": {},
"outputs": [],
"source": [
"import ess.odin.data # noqa: F401\n",
"from ess import odin\n",
"from ess.imaging.types import *\n",
"import scipp as sc\n",
"import plopp as pp\n",
"\n",
"%matplotlib widget"
]
},
{
"cell_type": "markdown",
"id": "2",
"metadata": {},
"source": [
"## Workflow setup"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3",
"metadata": {},
"outputs": [],
"source": [
"wf = odin.OdinOrcaWorkflow()\n",
"\n",
"wf[Filename[AllRuns]] = odin.data.odin_lego_images()\n",
"\n",
"wf[NeXusDetectorName] = 'histogram_mode_detectors/orca'\n",
"\n",
"wf[MaskingRules] = {} # No masks to begin with\n",
"wf[UncertaintyBroadcastMode] = UncertaintyBroadcastMode.upper_bound"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4",
"metadata": {},
"outputs": [],
"source": [
"wf.visualize(NormalizedImage, compact=True, graph_attr={\"rankdir\": \"LR\"})"
]
},
{
"cell_type": "markdown",
"id": "5",
"metadata": {},
"source": [
"## Run the workflow\n",
"\n",
"We compute the final normalized image:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6",
"metadata": {},
"outputs": [],
"source": [
"image = wf.compute(NormalizedImage)\n",
"image"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7",
"metadata": {},
"outputs": [],
"source": [
"pp.slicer(image, autoscale=False)"
]
},
{
"cell_type": "markdown",
"id": "8",
"metadata": {},
"source": [
"## Adding masks\n",
"\n",
"If we want to mask some part of the image, we update the masking rules.\n",
"For example, here we mask the upper part of the image:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9",
"metadata": {},
"outputs": [],
"source": [
"wf[MaskingRules] = {'y_pixel_offset': lambda x: x > sc.scalar(0.082, unit='m')}\n",
"\n",
"pp.slicer(wf.compute(NormalizedImage), autoscale=False)"
]
},
{
"cell_type": "markdown",
"id": "10",
"metadata": {},
"source": [
"## Intermediate results\n",
"\n",
"We can also inspect intermediate results, which is useful for debugging:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "11",
"metadata": {},
"outputs": [],
"source": [
"results = wf.compute(\n",
" [\n",
" FluxNormalizedDetector[SampleRun],\n",
" FluxNormalizedDetector[OpenBeamRun],\n",
" BackgroundSubtractedDetector[SampleRun],\n",
" ]\n",
")\n",
"\n",
"fig = pp.tiled(2, 2, hspace=0.3, wspace=0.3)\n",
"fig[0, 0] = results[FluxNormalizedDetector[SampleRun]]['time', 0].plot(\n",
" title='Sample (proton-charge normalized)'\n",
")\n",
"fig[0, 1] = results[FluxNormalizedDetector[OpenBeamRun]]['time', 0].plot(\n",
" title='Open beam (proton-charge normalized)'\n",
")\n",
"fig[1, 0] = results[BackgroundSubtractedDetector[SampleRun]]['time', 0].plot(\n",
" title='Background subtracted sample'\n",
")\n",
"fig[1, 1] = image['time', 0].plot(title='Final image')\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",
"version": "3.12.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
import scipp as sc

from ess.reduce.nexus import GenericNeXusWorkflow
from ess.reduce.nexus.types import NeXusDetectorName

from .. import imaging
from ..imaging.types import (
AllRuns,
BackgroundSubtractedDetector,
DarkBackgroundRun,
FluxNormalizedDetector,
Expand Down Expand Up @@ -57,20 +57,13 @@ def normalize_by_proton_charge_orca(
orca_providers = (normalize_by_proton_charge_orca,)


def default_parameters() -> dict:
"""Return the default NeXus names and detector name for the ORCA workflow."""
return {
NeXusDetectorName: 'orca_detector',
}


def OrcaNormalizedImagesWorkflow(**kwargs) -> sl.Pipeline:
"""
Workflow with default parameters for ORCA image normalization.
"""

wf = GenericNeXusWorkflow(
run_types=[SampleRun, OpenBeamRun, DarkBackgroundRun],
run_types=[SampleRun, OpenBeamRun, DarkBackgroundRun, AllRuns],
monitor_types=[],
**kwargs,
)
Expand All @@ -81,6 +74,4 @@ def OrcaNormalizedImagesWorkflow(**kwargs) -> sl.Pipeline:
*orca_providers,
):
wf.insert(provider)
for key, param in default_parameters().items():
wf[key] = param
return wf
12 changes: 11 additions & 1 deletion packages/essimaging/src/ess/imaging/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@

UncertaintyBroadcastMode = _UncertaintyBroadcastMode


SampleRun = NewType("SampleRun", int)
"""Sample run; a run with a sample in the beam."""

Expand All @@ -48,6 +47,9 @@
"""Open beam run; a run with no sample in the beam, and the shutter open, to measure the
beam profile."""

AllRuns = NewType("AllRuns", int)
"""A type to represent all runs: sample, open beam, and dark background runs."""

BeamMonitor1 = NewType("BeamMonitor1", int)
"""Beam monitor number 1"""

Expand Down Expand Up @@ -96,4 +98,12 @@ class BackgroundSubtractedDetector(sciline.Scope[RunType, sc.DataArray], sc.Data
beam run."""


ImageKey = NewType("ImageKey", sc.DataArray)
"""Key mapping image type to the time axis of the recording:
- 0: sample run
- 1: open beam run
- 2: dark background run
"""


del sc, sciline, NewType
3 changes: 2 additions & 1 deletion packages/essimaging/src/ess/odin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import importlib.metadata

from . import beamline
from .workflows import OdinBraggEdgeWorkflow, OdinWorkflow
from .workflows import OdinBraggEdgeWorkflow, OdinOrcaWorkflow, OdinWorkflow

try:
__version__ = importlib.metadata.version("esstbl")
Expand All @@ -15,6 +15,7 @@

__all__ = [
"OdinBraggEdgeWorkflow",
"OdinOrcaWorkflow",
"OdinWorkflow",
"beamline",
]
11 changes: 11 additions & 0 deletions packages/essimaging/src/ess/odin/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"iron_simulation_sample_large.nxs": "md5:c162b6abeccb51984880d8d5002bae95",
"iron_simulation_sample_small.nxs": "md5:dda6fb30aa88780c5a3d4cef6ea05278",
"ODIN-wavelength-lookup-table-5m-65m.h5": "md5:44eef2a2e826cec688aeb1b985eb9f9e", # noqa: E501
"ymir_lego_odin.hdf": "md5:8e8708891e2574046b6f372e5e3516a5",
},
)

Expand Down Expand Up @@ -69,3 +70,13 @@ def odin_wavelength_lookup_table() -> pathlib.Path:
with ``NumberOfSimulatedNeutrons = 5_000_000``.
"""
return _registry.get_path("ODIN-wavelength-lookup-table-5m-65m.h5")


def odin_lego_images() -> pathlib.Path:
"""
Return the path to the ODIN LEGO HDF5 file, created from the YMIR data.
This file was created using the tools/make-odin-images-from-ymir.ipynb notebook.
A ODIN file (coda_odin_999999_00011093.hdf) was used as a template for the NeXus
structure. The images were extracted from the YMIR LEGO run.
"""
return _registry.get_path("ymir_lego_odin.hdf")
Loading
Loading