Zoom data reduction#
Introduction#
This notebook is an example of how ESSsans can be used to reduce data from Zoom at ISIS. The following description is kept relatively brief, for more context see the rest of the documentation. In particular the Sans2d notebook may be useful.
There are a few things that are not yet handled:
TOF or wavelength masks
Position corrections from user file (not automatically, have manual sample and detector bank offsets)
We begin with relevant imports:
[1]:
import scipp as sc
from ess import sans
from ess import isissans as isis
import ess.isissans.data # noqa: F401
from ess.sans.types import *
Create and configure the workflow#
We begin by creating the Zoom workflow object (this is a sciline.Pipeline which can be consulted for advanced usage). The Zoom workflow uses Mantid to load files. This tutorial comes with files that do not require Mantid, so we use a slightly modified workflow that does not require Mantid. The workflow is otherwise identical to the full Mantid-based workflow:
[2]:
workflow = isis.zoom.ZoomTutorialWorkflow()
# For real data use:
# workflow = isis.zoom.ZoomWorkflow()
We can insert steps for configuring the workflow. In this case, we would like to use the transmission monitor from the regular background and sample runs since there was no separate transmission run.
[3]:
workflow.insert(isis.io.transmission_from_background_run)
workflow.insert(isis.io.transmission_from_sample_run)
The workflow lacks some input parameters, as well as parameters where we do not want to use the defaults, which we can set now:
[4]:
workflow[NeXusMonitorName[Incident]] = 'monitor3'
workflow[NeXusMonitorName[Transmission]] = 'monitor5'
workflow[WavelengthBins] = sc.geomspace(
'wavelength', start=1.75, stop=16.5, num=141, unit='angstrom'
)
workflow[QBins] = sc.geomspace(
dim='Q', start=0.004, stop=0.8, num=141, unit='1/angstrom'
)
workflow[NonBackgroundWavelengthRange] = sc.array(
dims=['wavelength'], values=[0.7, 17.1], unit='angstrom'
)
workflow[CorrectForGravity] = True
workflow[UncertaintyBroadcastMode] = UncertaintyBroadcastMode.upper_bound
workflow[ReturnEvents] = False
Configuring data to load#
We have not configured which files we want to load. In this tutorial, we use helpers to fetch the tutorial data which return the filenames of the cached files. In a real use case, you would set these parameters manually:
[5]:
workflow[DirectBeamFilename] = isis.data.zoom_tutorial_direct_beam()
workflow[isis.CalibrationFilename] = isis.data.zoom_tutorial_calibration()
workflow[Filename[SampleRun]] = isis.data.zoom_tutorial_sample_run()
workflow[Filename[EmptyBeamRun]] = isis.data.zoom_tutorial_empty_beam_run()
workflow[isis.SampleOffset] = sc.vector([0.0, 0.0, 0.11], unit='m')
workflow[isis.DetectorBankOffset] = sc.vector([0.0, 0.0, 0.5], unit='m')
masks = isis.data.zoom_tutorial_mask_filenames()
workflow = sans.with_pixel_mask_filenames(workflow, masks)
Downloading file 'Direct_Zoom_4m_8mm_100522.txt.h5' from 'https://public.esss.dk/groups/scipp/ess/zoom/2/Direct_Zoom_4m_8mm_100522.txt.h5' to '/home/runner/.cache/ess/zoom/2'.
Downloading file '192tubeCalibration_11-02-2019_r5_10lines.nxs' from 'https://public.esss.dk/groups/scipp/ess/zoom/2/192tubeCalibration_11-02-2019_r5_10lines.nxs' to '/home/runner/.cache/ess/zoom/2'.
Downloading file 'ZOOM00034786.nxs.h5.zip' from 'https://public.esss.dk/groups/scipp/ess/zoom/2/ZOOM00034786.nxs.h5.zip' to '/home/runner/.cache/ess/zoom/2'.
Unzipping contents of '/home/runner/.cache/ess/zoom/2/ZOOM00034786.nxs.h5.zip' to '/home/runner/.cache/ess/zoom/2/ZOOM00034786.nxs.h5.zip.unzip'
Downloading file 'ZOOM00034787.nxs.h5' from 'https://public.esss.dk/groups/scipp/ess/zoom/2/ZOOM00034787.nxs.h5' to '/home/runner/.cache/ess/zoom/2'.
Downloading file 'andru_test.xml' from 'https://public.esss.dk/groups/scipp/ess/zoom/2/andru_test.xml' to '/home/runner/.cache/ess/zoom/2'.
Downloading file 'left_beg_18_2.xml' from 'https://public.esss.dk/groups/scipp/ess/zoom/2/left_beg_18_2.xml' to '/home/runner/.cache/ess/zoom/2'.
Downloading file 'right_beg_18_2.xml' from 'https://public.esss.dk/groups/scipp/ess/zoom/2/right_beg_18_2.xml' to '/home/runner/.cache/ess/zoom/2'.
Downloading file 'small_bs_232.xml' from 'https://public.esss.dk/groups/scipp/ess/zoom/2/small_bs_232.xml' to '/home/runner/.cache/ess/zoom/2'.
Downloading file 'small_BS_31032023.xml' from 'https://public.esss.dk/groups/scipp/ess/zoom/2/small_BS_31032023.xml' to '/home/runner/.cache/ess/zoom/2'.
Downloading file 'tube_1120_bottom.xml' from 'https://public.esss.dk/groups/scipp/ess/zoom/2/tube_1120_bottom.xml' to '/home/runner/.cache/ess/zoom/2'.
Downloading file 'tubes_beg_18_2.xml' from 'https://public.esss.dk/groups/scipp/ess/zoom/2/tubes_beg_18_2.xml' to '/home/runner/.cache/ess/zoom/2'.
The workflow can be visualized as a graph:
[6]:
# left-right layout works better for this graph
workflow.visualize(IofQ[SampleRun], graph_attr={'rankdir': 'LR'})
[6]:
Use the workflow#
Set or compute the beam center#
The beam center is not set by default. We can either set it to a known value, or compute it from the data:
[7]:
workflow[BeamCenter] = sans.beam_center_from_center_of_mass(workflow)
Compute final result#
We can now compute \(I(Q)\):
[8]:
da = workflow.compute(IofQ[SampleRun])
da.plot(norm='log', scale={'Q': 'log'})
[8]:
Compute intermediate results#
[9]:
monitors = (
WavelengthMonitor[SampleRun, Incident],
WavelengthMonitor[SampleRun, Transmission],
)
parts = (
WavelengthScaledQ[SampleRun, Numerator],
WavelengthScaledQ[SampleRun, Denominator],
)
iofqs = (IofQ[SampleRun],)
keys = (*monitors, MaskedData[SampleRun], *parts, *iofqs)
results = workflow.compute(keys)
display(sc.plot({str(key): results[key] for key in monitors}, norm='log'))
display(
isis.plot_flat_detector_xy(
results[MaskedData[SampleRun]], norm='log', figsize=(6, 10)
)
)
wavelength = workflow.compute(WavelengthBins)
display(
results[WavelengthScaledQ[SampleRun, Numerator]]
.hist(wavelength=wavelength)
.transpose()
.plot(norm='log')
)
display(results[WavelengthScaledQ[SampleRun, Denominator]].plot(norm='log'))
parts = {str(key): results[key] for key in parts}
parts = {
key: val.sum('wavelength') if val.bins is None else val.hist()
for key, val in parts.items()
}
display(sc.plot(parts, norm='log', scale={'Q': 'log'}))
iofqs = {str(key): results[key] for key in iofqs}
iofqs = {key: val if val.bins is None else val.hist() for key, val in iofqs.items()}
display(sc.plot(iofqs, norm='log', scale={'Q': 'log'}, aspect='equal'))
Computing Qx/Qy#
To compute \(I(Q_{x}, Q_{y})\) instead of the one-dimensional \(I(Q)\), we can compute IofQxy
instead of IofQ
. For this to work, we need to define QxBins
and QyBins
in our parameters:
[10]:
workflow[QxBins] = sc.linspace('Qx', start=-0.5, stop=0.5, num=101, unit='1/angstrom')
workflow[QyBins] = sc.linspace('Qy', start=-0.8, stop=0.8, num=101, unit='1/angstrom')
iqxqy = workflow.compute(IofQxy[SampleRun])
iqxqy.plot(norm='log', aspect='equal')
[10]: