Bragg-edge imaging with ODIN#
This notebook illustrates how to convert recorded events on the ODIN detector to a single wavelength spectrum, revealing a Bragg edge in the data. WFM mode was used in the chopper cascade.
Loading dataset#
Loader is not part of
essimaging
since McStas dataset format is not stabilized yet.
[1]:
import scipp as sc
import scippnexus as snx
import scipp.constants as scc
from typing import cast, NewType
from ess.reduce.nexus.types import FilePath
_DataPath = NewType('_DataPath', str)
_DefaultDataPath = _DataPath(
"entry1/data/transmission_event_signal_dat_list_p_t_x_y_z_vx_vy_vz/events"
)
_FileLock = NewType('_FileLock', bool)
"""Lock the file to prevent concurrent access."""
_DefaultFileLock = _FileLock(True)
OdinSimulationRawData = NewType('OdinSimulationRawData', sc.DataArray)
ProbabilityToCountsScaleFactor = NewType('ProbabilityToCountsScaleFactor', sc.Variable)
"""Translate the probability to counts."""
DefaultProbabilityToCountsScaleFactor = ProbabilityToCountsScaleFactor(
sc.scalar(1_000, unit='dimensionless')
)
DetectorStartX = NewType('DetectorStartX', sc.Variable)
"""Start of the detector in x direction."""
DefaultDetectorStartX = DetectorStartX(sc.scalar(-0.03, unit='m'))
DetectorStartY = NewType('DetectorStartY', sc.Variable)
"""Start of the detector in y direction."""
DefaultDetectorStartY = DetectorStartY(sc.scalar(-0.03, unit='m'))
DetectorEndX = NewType('DetectorEndX', sc.Variable)
"""End of the detector in x direction."""
DefaultDetectorEndX = DetectorEndX(sc.scalar(0.03, unit='m'))
DetectorEndY = NewType('DetectorEndY', sc.Variable)
"""End of the detector in y direction."""
DefaultDetectorEndY = DetectorEndY(sc.scalar(0.03, unit='m'))
McStasManualResolution = NewType('McStasManualResolution', tuple)
"""Manual resolution for McStas data (how many pixels per axis x, y)"""
DefaultMcStasManualResolution = McStasManualResolution((1024, 1024))
example_resolution = McStasManualResolution((128, 128))
# Small resolution for faster testing and documentation build.
def _nth_col_or_row_lookup(
start: sc.Variable, stop: sc.Variable, resolution: int, dim: str
) -> sc.Lookup:
"""Lookup the nth column or row."""
position = sc.linspace(
dim, start=start, stop=stop, num=resolution + 1, unit=start.unit
)
nth_col_or_row = sc.arange(dim=dim, start=0, stop=resolution, unit='dimensionless')
hist = sc.DataArray(data=nth_col_or_row, coords={dim: position})
return sc.lookup(hist, dim)
def _position_to_pixel_id(
*,
x_pos: sc.Variable,
y_pos: sc.Variable,
detector_start_x: DetectorStartX = DefaultDetectorStartX,
detector_start_y: DetectorStartY = DefaultDetectorStartY,
detector_end_x: DetectorEndX = DefaultDetectorEndX,
detector_end_y: DetectorEndY = DefaultDetectorEndY,
resolution: McStasManualResolution = DefaultMcStasManualResolution,
) -> sc.Variable:
"""Hardcode pixel ids from positions."""
x_position_lookup = _nth_col_or_row_lookup(
detector_start_x, detector_end_x, resolution[0], 'x'
)
y_position_lookup = _nth_col_or_row_lookup(
detector_start_y, detector_end_y, resolution[1], 'y'
)
n_cols = x_position_lookup[x_pos]
n_rows = y_position_lookup[y_pos]
return n_rows * resolution[0] + n_cols
McStasVelocities = NewType('McStasVelocities', sc.DataGroup)
def load_velocities(
file_path: FilePath,
_data_path: _DataPath = _DefaultDataPath,
_file_lock: _FileLock = _DefaultFileLock,
) -> McStasVelocities:
with snx.File(file_path, "r", locking=_file_lock) as f:
data = f[_data_path][()].rename_dims({'dim_0': 'event'})
velocities = data['dim_1', 5:8]
vx = cast(sc.Variable, velocities['dim_1', 0].copy())
vy = cast(sc.Variable, velocities['dim_1', 1].copy())
vz = cast(sc.Variable, velocities['dim_1', 2].copy())
for v_component in (vx, vy, vz):
v_component.unit = 'm/s'
# Add special tags if you want to use them as coordinates
# for example, da.coords['vx_MC'] = vx
# to distinguish them from the measurement
return McStasVelocities(sc.DataGroup(vx=vx, vy=vy, vz=vz))
LoadTrueVelocities = NewType('LoadTrueVelocities', bool)
DefaultLoadTrueVelocities = LoadTrueVelocities(True)
def load_odin_simulation_data(
file_path: FilePath,
_data_path: _DataPath = _DefaultDataPath,
_file_lock: _FileLock = _DefaultFileLock,
detector_start_x: DetectorStartX = DefaultDetectorStartX,
detector_start_y: DetectorStartY = DefaultDetectorStartY,
detector_end_x: DetectorEndX = DefaultDetectorEndX,
detector_end_y: DetectorEndY = DefaultDetectorEndY,
resolution: McStasManualResolution = DefaultMcStasManualResolution,
probability_scale_factor: ProbabilityToCountsScaleFactor = DefaultProbabilityToCountsScaleFactor,
load_true_velocities: LoadTrueVelocities = DefaultLoadTrueVelocities,
) -> OdinSimulationRawData:
with snx.File(file_path, "r", locking=_file_lock) as f:
# The name p_t_x_y_z_vx_vy_vz represents
# probability, time of arrival, position(x, y, z) and velocity(vx, vy, vz).
# The name also represents the order of each field in the table.
# For example, probability is the first field, so data['dim_1', 0] is the probability.
data = f[_data_path][()].rename_dims({'dim_0': 'event'})
probabilities = cast(sc.Variable, data['dim_1', 0].copy())
probabilities.unit = 'dimensionless'
time_of_arrival = cast(sc.Variable, data['dim_1', 1].copy())
time_of_arrival.unit = 's' # Hardcoded unit from the data.
positions = data['dim_1', 2:5]
counts = (probabilities / probabilities.max()) * probability_scale_factor
counts.unit = 'counts'
# Units are hardcoded from the data.
x_pos = cast(sc.Variable, positions['dim_1', 0].copy())
x_pos.unit = 'm'
y_pos = cast(sc.Variable, positions['dim_1', 1].copy())
y_pos.unit = 'm'
pixel_id = _position_to_pixel_id(
x_pos=x_pos,
y_pos=y_pos,
detector_start_x=detector_start_x,
detector_start_y=detector_start_y,
detector_end_x=detector_end_x,
detector_end_y=detector_end_y,
resolution=resolution,
)
da = sc.DataArray(
data=counts.copy().astype(sc.DType.int32),
coords={
'time_of_arrival': time_of_arrival.to(unit='us'),
'sample_position': sc.vector([0.0, 0.0, 60.5], unit='m'),
# Hardcoded from the data.
'source_position': sc.vector([0.0, 0.0, 0.0], unit="m"),
# Hardcoded from the data.
'pixel_id': pixel_id,
},
)
if load_true_velocities:
velocities = load_velocities(file_path, _data_path, _file_lock)
speeds = sc.norm(
sc.vectors(
dims=['event'],
values=sc.transpose(
sc.concat(list(velocities.values()), 'speed')
).values,
unit='m/s',
)
)
da.coords['sim_wavelength'] = (scc.h / scc.neutron_mass / speeds).to(
unit='angstrom'
)
return OdinSimulationRawData(da.to(dtype=float))
[2]:
from ess.imaging.data import get_mcstas_ob_images_path, get_mcstas_sample_images_path
ob_file_path = FilePath(get_mcstas_ob_images_path())
sample_file_path = FilePath(get_mcstas_sample_images_path())
ob_da = load_odin_simulation_data(ob_file_path, resolution=example_resolution)
sample_da = load_odin_simulation_data(sample_file_path, resolution=example_resolution)
sample_da
Downloading file 'small_mcstas_ob_images.h5' from 'https://public.esss.dk/groups/scipp/ess/imaging/1/small_mcstas_ob_images.h5' to '/home/runner/.cache/essimaging/1'.
Downloading file 'small_mcstas_sample_images.h5' from 'https://public.esss.dk/groups/scipp/ess/imaging/1/small_mcstas_sample_images.h5' to '/home/runner/.cache/essimaging/1'.
[2]:
- event: 388566
- pixel_id(event)int64𝟙8122, 5408, ..., 15774, 969
Values:
array([ 8122, 5408, 4970, ..., 8714, 15774, 969], shape=(388566,)) - sample_position()vector3m[ 0. 0. 60.5]
Values:
array([ 0. , 0. , 60.5]) - sim_wavelength(event)float64Å5.727, 5.673, ..., 8.380, 6.448
Values:
array([5.72678489, 5.67272213, 3.1759751 , ..., 7.56736601, 8.38023051, 6.44810453], shape=(388566,)) - source_position()vector3m[0. 0. 0.]
Values:
array([0., 0., 0.]) - time_of_arrival(event)float64µs8.994e+04, 8.894e+04, ..., 1.304e+05, 9.970e+04
Values:
array([ 89942.25084908, 88942.61380286, 50284.40890975, ..., 117180.5024966 , 130417.13999026, 99700.22439706], shape=(388566,))
- (event)float64counts22.0, 0.0, ..., 3.0, 0.0
Values:
array([ 22., 0., 198., ..., 0., 3., 0.], shape=(388566,))
[3]:
def _pixel_ids_to_x(
*,
pixel_id: sc.Variable,
resolution: McStasManualResolution = DefaultMcStasManualResolution,
detector_start_x: DetectorStartX = DefaultDetectorStartX,
detector_end_x: DetectorEndX = DefaultDetectorEndX,
) -> sc.Variable:
n_col = pixel_id % resolution[0]
x_interval = (detector_end_x - detector_start_x) / resolution[0]
return (
detector_start_x + n_col * x_interval
) + x_interval / 2 # Center of the pixel|
def _pixel_ids_to_y(
*,
pixel_id: sc.Variable,
resolution: McStasManualResolution = DefaultMcStasManualResolution,
detector_start_y: DetectorStartY = DefaultDetectorStartY,
detector_end_y: DetectorEndY = DefaultDetectorEndY,
) -> sc.Variable:
n_row = pixel_id // resolution[0]
y_interval = (detector_end_y - detector_start_y) / resolution[1]
return (
detector_start_y + n_row * y_interval
) + y_interval / 2 # Center of the pixel
def _pixel_ids_to_position(
*, x: sc.Variable, y: sc.Variable, z_pos: sc.Variable
) -> sc.Variable:
z = sc.zeros_like(x) + z_pos
var = (
sc.concat([x, y, z], 'event')
.fold('event', dims=['pos', 'event'], shape=[3, len(x)])
.transpose(dims=['event', 'pos'])
.values
)
return sc.vectors(dims=['event'], values=var, unit='m')
[4]:
import scipp as sc
from scippneutron.conversion import graph
plane_graph = {**graph.beamline.beamline(False), **graph.tof.kinematic("tof")}
# TODO: Replace this with actual WFM stitching method
plane_graph['tof'] = lambda time_of_arrival: time_of_arrival
plane_graph['x'] = lambda pixel_id: _pixel_ids_to_x(
pixel_id=pixel_id, resolution=example_resolution
)
plane_graph['y'] = lambda pixel_id: _pixel_ids_to_y(
pixel_id=pixel_id, resolution=example_resolution
)
plane_graph['position'] = lambda x, y: _pixel_ids_to_position(
x=x,
y=y,
z_pos=sc.scalar(60.5, unit='m'), # Hardcoded from the data.
)
sc.show_graph(plane_graph, simplified=True)
[4]:
[5]:
coords = ["tof", "position", "x", "y", "sim_wavelength", "Ltotal"]
sample_da = sample_da.transform_coords(
coords,
graph=plane_graph,
keep_intermediate=False,
)
ob_da = ob_da.transform_coords(
coords,
graph=plane_graph,
keep_intermediate=False,
)
sample_da
[5]:
- event: 388566
- Ltotal(event)float64m60.500, 60.500, ..., 60.500, 60.500
Values:
array([60.50000006, 60.50000264, 60.50000446, ..., 60.50000523, 60.50000847, 60.50000596], shape=(388566,)) - pixel_id(event)int64𝟙8122, 5408, ..., 15774, 969
Values:
array([ 8122, 5408, 4970, ..., 8714, 15774, 969], shape=(388566,)) - position(event)vector3m[-2.578125e-03 -2.343750e-04 6.050000e+01], [-1.4765625e-02 -1.0078125e-02 6.0500000e+01], ..., [-1.5703125e-02 2.7890625e-02 6.0500000e+01], [ 4.4531250e-03 -2.6484375e-02 6.0500000e+01]
Values:
array([[-2.5781250e-03, -2.3437500e-04, 6.0500000e+01], [-1.4765625e-02, -1.0078125e-02, 6.0500000e+01], [ 1.9921875e-02, -1.1953125e-02, 6.0500000e+01], ..., [-2.5078125e-02, 2.1093750e-03, 6.0500000e+01], [-1.5703125e-02, 2.7890625e-02, 6.0500000e+01], [ 4.4531250e-03, -2.6484375e-02, 6.0500000e+01]], shape=(388566, 3)) - sample_position()vector3m[ 0. 0. 60.5]
Values:
array([ 0. , 0. , 60.5]) - sim_wavelength(event)float64Å5.727, 5.673, ..., 8.380, 6.448
Values:
array([5.72678489, 5.67272213, 3.1759751 , ..., 7.56736601, 8.38023051, 6.44810453], shape=(388566,)) - source_position()vector3m[0. 0. 0.]
Values:
array([0., 0., 0.]) - time_of_arrival(event)float64µs8.994e+04, 8.894e+04, ..., 1.304e+05, 9.970e+04
Values:
array([ 89942.25084908, 88942.61380286, 50284.40890975, ..., 117180.5024966 , 130417.13999026, 99700.22439706], shape=(388566,)) - tof(event)float64µs8.994e+04, 8.894e+04, ..., 1.304e+05, 9.970e+04
Values:
array([ 89942.25084908, 88942.61380286, 50284.40890975, ..., 117180.5024966 , 130417.13999026, 99700.22439706], shape=(388566,)) - x(event)float64m-0.003, -0.015, ..., -0.016, 0.004
Values:
array([-0.00257812, -0.01476562, 0.01992187, ..., -0.02507812, -0.01570313, 0.00445313], shape=(388566,)) - y(event)float64m-0.000, -0.010, ..., 0.028, -0.026
Values:
array([-0.00023438, -0.01007812, -0.01195313, ..., 0.00210938, 0.02789062, -0.02648438], shape=(388566,))
- (event)float64counts22.0, 0.0, ..., 3.0, 0.0
Values:
array([ 22., 0., 198., ..., 0., 3., 0.], shape=(388566,))
[6]:
sample_da.hist(tof=300).plot()
[6]:
Convert McStas raw data to NeXus#
The raw McStas data looks different from what data in a NeXus file would look like. The time-of-flight recorded by the McStas monitor is a unwrapped time of arrival (see https://scipp.github.io/scippneutron/user-guide/chopper/frame-unwrapping.html); the tof
coordinate has values beyond 71ms, as can be seen in the plot above.
The workflow that computes wavelengths from the WFM chopper cascade expects data in the NeXus format, so we transform the data here.
[7]:
def to_nexus(da):
unit = da.coords['tof'].unit
period = (1.0 / sc.scalar(14., unit='Hz')).to(unit=unit)
# Bin the data into bins with a 71ms period
n = int(sample_da.coords['tof'].max() / period)
da = da.bin(tof=sc.arange('tof', n + 2) * period)
# 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]`
da.coords['event_time_zero'] = (sc.scalar(1730450434078980000, unit='ns').to(unit=unit) + da.coords['tof'])[:-1]
# Remove the meaningless tof coord at the top level
del da.coords['tof']
# Remove the original (wrong) event_time_zero event coord inside the bins and rename the dim
del da.bins.coords['time_of_arrival']
del da.bins.coords['Ltotal']
da = da.rename_dims(tof='event_time_zero')
# Compute a proper event_time_offset as tof % period
da.bins.coords['event_time_offset'] = (da.bins.coords.pop('tof') % period)#.to(unit=)
return da
def add_positions(da):
temp = da.bins.concat('event_time_zero').copy()
out = da.copy()
out.coords['position'] = temp.bins.coords['position'].bins.mean()
del out.bins.coords['position']
return out.transform_coords(
"Ltotal",
graph=plane_graph,
keep_intermediate=True,
)
sample_nexus = add_positions(to_nexus(sample_da).group('pixel_id'))
ob_nexus = add_positions(to_nexus(ob_da).group('pixel_id'))
sample_nexus
[7]:
- event_time_zero: 3
- pixel_id: 16384
- Ltotal(pixel_id)float64m60.500, 60.500, ..., 60.500, 60.500
Values:
array([60.50001464, 60.50001442, 60.50001419, ..., 60.50001419, 60.50001442, 60.50001464], shape=(16384,)) - event_time_zero(event_time_zero)float64µs1.730e+15, 1.730e+15, 1.730e+15
Values:
array([1.73045043e+15, 1.73045043e+15, 1.73045043e+15]) - pixel_id(pixel_id)int64𝟙0, 1, ..., 16382, 16383
Values:
array([ 0, 1, 2, ..., 16381, 16382, 16383], shape=(16384,)) - position(pixel_id)vector3m[-2.9765625e-02 -2.9765625e-02 6.0500000e+01], [-2.9296875e-02 -2.9765625e-02 6.0500000e+01], ..., [2.9296875e-02 2.9765625e-02 6.0500000e+01], [2.9765625e-02 2.9765625e-02 6.0500000e+01]
Values:
array([[-2.9765625e-02, -2.9765625e-02, 6.0500000e+01], [-2.9296875e-02, -2.9765625e-02, 6.0500000e+01], [-2.8828125e-02, -2.9765625e-02, 6.0500000e+01], ..., [ 2.8828125e-02, 2.9765625e-02, 6.0500000e+01], [ 2.9296875e-02, 2.9765625e-02, 6.0500000e+01], [ 2.9765625e-02, 2.9765625e-02, 6.0500000e+01]], shape=(16384, 3)) - sample_position()vector3m[ 0. 0. 60.5]
Values:
array([ 0. , 0. , 60.5]) - source_position()vector3m[0. 0. 0.]
Values:
array([0., 0., 0.])
- (event_time_zero, pixel_id)DataArrayViewbinned data [len=5, len=2, ..., len=0, len=2]
dim='event', content=DataArray( dims=(event: 388566), data=float64[counts], coords={'sim_wavelength':float64[Å], 'y':float64[m], 'x':float64[m], 'event_time_offset':float64[µs]})
[8]:
# Visualize
fig_nexus = sample_nexus.bins.concat().hist(event_time_offset=300).plot(title='McStas simulation: sample')
fig_nexus + ob_nexus.bins.concat().hist(event_time_offset=300).plot(title='McStas simulation: open beam')
[8]:
[9]:
import plopp as pp
pp.scatter3d(sample_nexus.sum('event_time_zero'), pos='position', cbar=True, pixel_size=0.0005)
[9]:
Choppers#
To accurately compute the wavelengths of the neutrons from their time-of-arrival, we need the parameters of the choppers in the beamline.
[10]:
import sciline as sl
from scippneutron.chopper import DiskChopper
from scippneutron.tof import unwrap
from scippneutron.tof import chopper_cascade
Hz = sc.Unit("Hz")
deg = sc.Unit("deg")
meter = sc.Unit("m")
parameters = {
"WFMC_1": {
"frequency": 56.0,
"phase": 93.244,
"distance": 6.85,
"open": [-1.9419, 49.5756, 98.9315, 146.2165, 191.5176, 234.9179],
"close": [1.9419, 55.7157, 107.2332, 156.5891, 203.8741, 249.1752]
},
"WFMC_2": {
"frequency": 56.0,
"phase": 97.128,
"distance": 7.15,
"open": [-1.9419, 51.8318, 103.3493, 152.7052, 199.9903, 245.2914],
"close": [1.9419, 57.9719, 111.6510, 163.0778, 212.3468, 259.5486]
},
"FOC_1": {
"frequency": 42.0,
"phase": 81.303297,
"distance": 8.4,
"open": [-5.1362, 42.5536, 88.2425, 132.0144, 173.9497, 216.7867],
"close": [5.1362, 54.2095, 101.2237, 146.2653, 189.417, 230.7582]
},
"BP_1": {
"frequency": 7.0,
"phase": 31.080,
"distance": 8.45,
"open": [-23.6029],
"close": [23.6029]
},
"FOC_2": {
"frequency": 42.0,
"phase": 107.013442,
"distance": 12.2,
"open": [-16.3227, 53.7401, 120.8633, 185.1701, 246.7787, 307.0165],
"close": [16.3227, 86.8303, 154.3794, 218.7551, 280.7508, 340.3188]
},
"BP_2": {
"frequency": 7.0,
"phase": 44.224,
"distance": 12.25,
"open": [-34.4663],
"close": [34.4663]
},
"T0_alpha": {
"frequency": 14.0,
"phase": 179.672,
"distance": 13.5,
"open": [-167.8986],
"close": [167.8986]
},
"T0_beta": {
"frequency": 14.0,
"phase": 179.672,
"distance": 13.7,
"open": [-167.8986],
"close": [167.8986]
},
"FOC_3": {
"frequency": 28.0,
"phase": 92.993,
"distance": 17.0,
"open": [-20.302, 45.247, 108.0457, 168.2095, 225.8489, 282.2199],
"close": [20.302, 85.357, 147.6824, 207.3927, 264.5977, 319.4024]
},
"FOC_4": {
"frequency": 14.0,
"phase": 61.584,
"distance": 23.69,
"open": [-16.7157, 29.1882, 73.1661, 115.2988, 155.6636, 195.5254],
"close": [16.7157, 61.8217, 105.0352, 146.4355, 186.0987, 224.0978]
},
"FOC_5": {
"frequency": 14.0,
"phase": 82.581,
"distance": 33.0,
"open": [-25.8514, 38.3239, 99.8064, 160.1254, 217.4321, 272.5426],
"close": [25.8514, 88.4621, 147.4729, 204.0245, 257.7603, 313.7139]
},
}
disk_choppers = {key: DiskChopper(
frequency=-ch["frequency"] * Hz,
beam_position=sc.scalar(0.0, unit="deg"),
phase=-ch["phase"] * deg,
axle_position=sc.vector(value=[0, 0, ch["distance"]], unit="m"),
slit_begin=sc.array(dims=["cutout"], values=ch["open"], unit="deg"),
slit_end=sc.array(dims=["cutout"], values=ch["close"], unit="deg")
) for key, ch in parameters.items() }
[11]:
disk_choppers["WFMC_1"]
[11]:
- axle_positionscippVariable()vector3m[0. 0. 6.85]
- frequencyscippVariable()float64Hz-56.0
- beam_positionscippVariable()float64deg0.0
- phasescippVariable()float64deg-93.244
- slit_beginscippVariable(cutout: 6)float64deg-1.942, 49.576, ..., 191.518, 234.918
- slit_endscippVariable(cutout: 6)float64deg1.942, 55.716, ..., 203.874, 249.175
- slit_heightNoneType()None
- radiusNoneType()None
[12]:
choppers = {
key: chopper_cascade.Chopper.from_disk_chopper(
chop,
pulse_frequency=sc.scalar(14.0, unit="Hz"),
npulses=1,
)
for key, chop in disk_choppers.items()
}
Check that the chopper settings make sense with a quick tof
run#
As useful sanity check is to run a basic simulation, propagating neutrons through the chopper cascade, using the Tof package.
[13]:
from scippneutron.tof.fakes import FakeBeamlineEss
Ltotal = sample_da.coords['Ltotal'].mean()
ess_beamline = FakeBeamlineEss(
choppers=choppers,
monitors={"detector": Ltotal},
run_length=sc.scalar(1 / 14, unit="s") * 8,
events_per_pulse=100_000,
)
ess_beamline.model_result.plot()
[13]:
Plot(ax=<Axes: xlabel='Time-of-flight (us)', ylabel='Distance (m)'>, fig=<Figure size 1200x480 with 2 Axes>)
We observe that the WFM choppers make 6 distinct frames at the detector, and that the other choppers skip every other pulse to maximize wavelength coverage.
We can now compare the counts on the detector to our raw data, to make sure they broadly resemble each other.
[14]:
raw_data = ess_beamline.get_monitor("detector")[0]
# Visualize
fig_nexus + raw_data.hist(event_time_offset=300).sum("pulse").plot(title='Tof simulation')
[14]:
Use WFM workflow#
We now set up the workflow which will convert the raw neutron arrival times to a real time-of-flight, and thus a wavelength.
Chopper cascade#
[15]:
one_pulse = ess_beamline.source.data["pulse", 0]
pulse_tmin = one_pulse.coords["time"].min()
pulse_tmax = one_pulse.coords["time"].max()
pulse_wmin = one_pulse.coords["wavelength"].min()
pulse_wmax = one_pulse.coords["wavelength"].max()
frames = chopper_cascade.FrameSequence.from_source_pulse(
time_min=pulse_tmin,
time_max=pulse_tmax,
wavelength_min=pulse_wmin,
wavelength_max=pulse_wmax,
)
# Chop the frames
chopped = frames.chop(choppers.values())
# Propagate the neutrons to the detector
at_sample = chopped.propagate_to(Ltotal)
# Visualize the results
cascade_fig, cascade_ax = at_sample.draw()
Pipeline#
[16]:
workflow = sl.Pipeline(unwrap.providers(), params=unwrap.params())
workflow[unwrap.PulsePeriod] = sc.reciprocal(ess_beamline.source.frequency)
workflow[unwrap.PulseStride] = 2 # Need for pulse-skipping
workflow[unwrap.SourceTimeRange] = pulse_tmin, pulse_tmax
workflow[unwrap.SourceWavelengthRange] = pulse_wmin, pulse_wmax
workflow[unwrap.Choppers] = choppers
workflow[unwrap.Ltotal] = sample_nexus.coords['Ltotal']
workflow[unwrap.RawData] = sample_nexus
workflow.visualize(unwrap.TofData)
[16]:
[17]:
sample_tofs = workflow.compute(unwrap.TofData)
sample_tofs
[17]:
- event_time_zero: 3
- pixel_id: 16384
- Ltotal(pixel_id)float64m60.500, 60.500, ..., 60.500, 60.500
Values:
array([60.50001464, 60.50001442, 60.50001419, ..., 60.50001419, 60.50001442, 60.50001464], shape=(16384,)) - event_time_zero(event_time_zero)float64µs1.730e+15, 1.730e+15, 1.730e+15
Values:
array([1.73045043e+15, 1.73045043e+15, 1.73045043e+15]) - pixel_id(pixel_id)int64𝟙0, 1, ..., 16382, 16383
Values:
array([ 0, 1, 2, ..., 16381, 16382, 16383], shape=(16384,)) - position(pixel_id)vector3m[-2.9765625e-02 -2.9765625e-02 6.0500000e+01], [-2.9296875e-02 -2.9765625e-02 6.0500000e+01], ..., [2.9296875e-02 2.9765625e-02 6.0500000e+01], [2.9765625e-02 2.9765625e-02 6.0500000e+01]
Values:
array([[-2.9765625e-02, -2.9765625e-02, 6.0500000e+01], [-2.9296875e-02, -2.9765625e-02, 6.0500000e+01], [-2.8828125e-02, -2.9765625e-02, 6.0500000e+01], ..., [ 2.8828125e-02, 2.9765625e-02, 6.0500000e+01], [ 2.9296875e-02, 2.9765625e-02, 6.0500000e+01], [ 2.9765625e-02, 2.9765625e-02, 6.0500000e+01]], shape=(16384, 3)) - sample_position()vector3m[ 0. 0. 60.5]
Values:
array([ 0. , 0. , 60.5]) - source_position()vector3m[0. 0. 0.]
Values:
array([0., 0., 0.])
- (event_time_zero, pixel_id)DataArrayViewbinned data [len=5, len=2, ..., len=0, len=2]
dim='event', content=DataArray( dims=(event: 388566), data=float64[counts], coords={'sim_wavelength':float64[Å], 'y':float64[m], 'x':float64[m], 'event_time_offset':float64[µs], 'tof':float64[µs]})
[18]:
sample_wavs = sample_tofs.transform_coords('wavelength', graph=plane_graph)
sample_wavs
[18]:
- event_time_zero: 3
- pixel_id: 16384
- Ltotal(pixel_id)float64m60.500, 60.500, ..., 60.500, 60.500
Values:
array([60.50001464, 60.50001442, 60.50001419, ..., 60.50001419, 60.50001442, 60.50001464], shape=(16384,)) - event_time_zero(event_time_zero)float64µs1.730e+15, 1.730e+15, 1.730e+15
Values:
array([1.73045043e+15, 1.73045043e+15, 1.73045043e+15]) - pixel_id(pixel_id)int64𝟙0, 1, ..., 16382, 16383
Values:
array([ 0, 1, 2, ..., 16381, 16382, 16383], shape=(16384,)) - position(pixel_id)vector3m[-2.9765625e-02 -2.9765625e-02 6.0500000e+01], [-2.9296875e-02 -2.9765625e-02 6.0500000e+01], ..., [2.9296875e-02 2.9765625e-02 6.0500000e+01], [2.9765625e-02 2.9765625e-02 6.0500000e+01]
Values:
array([[-2.9765625e-02, -2.9765625e-02, 6.0500000e+01], [-2.9296875e-02, -2.9765625e-02, 6.0500000e+01], [-2.8828125e-02, -2.9765625e-02, 6.0500000e+01], ..., [ 2.8828125e-02, 2.9765625e-02, 6.0500000e+01], [ 2.9296875e-02, 2.9765625e-02, 6.0500000e+01], [ 2.9765625e-02, 2.9765625e-02, 6.0500000e+01]], shape=(16384, 3)) - sample_position()vector3m[ 0. 0. 60.5]
Values:
array([ 0. , 0. , 60.5]) - source_position()vector3m[0. 0. 0.]
Values:
array([0., 0., 0.])
- (event_time_zero, pixel_id)DataArrayViewbinned data [len=5, len=2, ..., len=0, len=2]
dim='event', content=DataArray( dims=(event: 388566), data=float64[counts], coords={'sim_wavelength':float64[Å], 'y':float64[m], 'x':float64[m], 'event_time_offset':float64[µs], 'tof':float64[µs], 'wavelength':float64[Å]})
We can now compare our computed wavelengths to the true wavelengths of the neutrons in the McStas simulation:
[19]:
true_wavs = sample_da.hist(sim_wavelength=300).rename(sim_wavelength='wavelength')
pp.plot({
'true': true_wavs,
'wfm': sample_wavs.bins.concat().hist(wavelength=true_wavs.coords['wavelength'])
}, title="ODIN McStas simulation")
[19]:
Region of interest#
Looking at the counts on the 2d detector panel, we see that there is a central rectangular darker region, surrounded by brighter edges.
[20]:
sample_folded = sample_wavs.bins.concat('event_time_zero').fold(dim='pixel_id', sizes={'y': 128, 'x': 128})
sample_folded.hist().plot(aspect='equal')
[20]:
The dark region is where the beam was absorbed by the sample, and this is the region of interest. The brighter edges need to be discarded.
We crop the data using simple array slicing:
[21]:
sel = slice(11, 116, 1)
sample_cropped = sample_folded['y', sel]['x', sel]
sample_cropped.hist().plot(aspect='equal')
[21]:
Repeat for the open-beam#
We repeat the conversion to wavelength and crop the edges of the open-beam measurement.
[22]:
# Give the same pixel positions to both sample and open beam.
# Note: this is only because of the way we computed the positions.
# In practice, the geometry should come from the nexus file and this won't be needed.
ob_nexus.coords.update({key: sample_nexus.coords[key] for key in ('position', 'Ltotal')})
workflow[unwrap.Ltotal] = ob_nexus.coords['Ltotal']
workflow[unwrap.RawData] = ob_nexus
ob_tofs = workflow.compute(unwrap.TofData)
ob_wavs = ob_tofs.transform_coords('wavelength', graph=plane_graph)
ob_folded = ob_wavs.bins.concat('event_time_zero').fold(dim='pixel_id', sizes={'y': 128, 'x': 128})
ob_cropped = ob_folded['y', sel]['x', sel]
Normalize the signal#
Finally, we are able to normalize our sample measurement to the open-beam data.
Here, we sum over all pixels before normalizing. There is no spatial structure in the signal, and we are only interested in the wavelength spectrum (where the Bragg edge is). So this is effectively like degrading the detector resolution to a single pixel.
[23]:
# Common set of bins
bins = sc.linspace('wavelength', 1.1, 9.5, 301, unit='angstrom')
num = sample_cropped.bins.concat().hist(wavelength=bins)
den = ob_cropped.bins.concat().hist(wavelength=bins)
# Add variances
num.variances = num.values
den.variances = den.values
normalized = num / den
normalized
[23]:
- wavelength: 300
- sample_position()vector3m[ 0. 0. 60.5]
Values:
array([ 0. , 0. , 60.5]) - source_position()vector3m[0. 0. 0.]
Values:
array([0., 0., 0.]) - wavelength(wavelength [bin-edge])float64Å1.1, 1.128, ..., 9.472, 9.5
Values:
array([1.1 , 1.128, 1.156, 1.184, 1.212, 1.24 , 1.268, 1.296, 1.324, 1.352, 1.38 , 1.408, 1.436, 1.464, 1.492, 1.52 , 1.548, 1.576, 1.604, 1.632, 1.66 , 1.688, 1.716, 1.744, 1.772, 1.8 , 1.828, 1.856, 1.884, 1.912, 1.94 , 1.968, 1.996, 2.024, 2.052, 2.08 , 2.108, 2.136, 2.164, 2.192, 2.22 , 2.248, 2.276, 2.304, 2.332, 2.36 , 2.388, 2.416, 2.444, 2.472, 2.5 , 2.528, 2.556, 2.584, 2.612, 2.64 , 2.668, 2.696, 2.724, 2.752, 2.78 , 2.808, 2.836, 2.864, 2.892, 2.92 , 2.948, 2.976, 3.004, 3.032, 3.06 , 3.088, 3.116, 3.144, 3.172, 3.2 , 3.228, 3.256, 3.284, 3.312, 3.34 , 3.368, 3.396, 3.424, 3.452, 3.48 , 3.508, 3.536, 3.564, 3.592, 3.62 , 3.648, 3.676, 3.704, 3.732, 3.76 , 3.788, 3.816, 3.844, 3.872, 3.9 , 3.928, 3.956, 3.984, 4.012, 4.04 , 4.068, 4.096, 4.124, 4.152, 4.18 , 4.208, 4.236, 4.264, 4.292, 4.32 , 4.348, 4.376, 4.404, 4.432, 4.46 , 4.488, 4.516, 4.544, 4.572, 4.6 , 4.628, 4.656, 4.684, 4.712, 4.74 , 4.768, 4.796, 4.824, 4.852, 4.88 , 4.908, 4.936, 4.964, 4.992, 5.02 , 5.048, 5.076, 5.104, 5.132, 5.16 , 5.188, 5.216, 5.244, 5.272, 5.3 , 5.328, 5.356, 5.384, 5.412, 5.44 , 5.468, 5.496, 5.524, 5.552, 5.58 , 5.608, 5.636, 5.664, 5.692, 5.72 , 5.748, 5.776, 5.804, 5.832, 5.86 , 5.888, 5.916, 5.944, 5.972, 6. , 6.028, 6.056, 6.084, 6.112, 6.14 , 6.168, 6.196, 6.224, 6.252, 6.28 , 6.308, 6.336, 6.364, 6.392, 6.42 , 6.448, 6.476, 6.504, 6.532, 6.56 , 6.588, 6.616, 6.644, 6.672, 6.7 , 6.728, 6.756, 6.784, 6.812, 6.84 , 6.868, 6.896, 6.924, 6.952, 6.98 , 7.008, 7.036, 7.064, 7.092, 7.12 , 7.148, 7.176, 7.204, 7.232, 7.26 , 7.288, 7.316, 7.344, 7.372, 7.4 , 7.428, 7.456, 7.484, 7.512, 7.54 , 7.568, 7.596, 7.624, 7.652, 7.68 , 7.708, 7.736, 7.764, 7.792, 7.82 , 7.848, 7.876, 7.904, 7.932, 7.96 , 7.988, 8.016, 8.044, 8.072, 8.1 , 8.128, 8.156, 8.184, 8.212, 8.24 , 8.268, 8.296, 8.324, 8.352, 8.38 , 8.408, 8.436, 8.464, 8.492, 8.52 , 8.548, 8.576, 8.604, 8.632, 8.66 , 8.688, 8.716, 8.744, 8.772, 8.8 , 8.828, 8.856, 8.884, 8.912, 8.94 , 8.968, 8.996, 9.024, 9.052, 9.08 , 9.108, 9.136, 9.164, 9.192, 9.22 , 9.248, 9.276, 9.304, 9.332, 9.36 , 9.388, 9.416, 9.444, 9.472, 9.5 ])
- (wavelength)float64𝟙0.203, 0.299, ..., nan, nanσ = 0.002, 0.002, ..., nan, nan
Values:
array([0.20305885, 0.298517 , 0.26657272, 0.23468619, 0.27309398, 0.25859862, 0.20911155, 0.19287048, 0.22262896, 0.21579718, 0.22066977, 0.20901057, 0.25778419, 0.23492068, 0.26978935, 0.30174112, 0.25789055, 0.19465909, 0.22742941, 0.19387524, 0.23701684, 0.24139434, 0.17726019, 0.23851864, 0.21214185, 0.20058296, 0.23701192, 0.21973267, 0.2246839 , 0.22673686, 0.23648984, 0.20615584, 0.19810347, 0.19144866, 0.23542659, 0.19635902, 0.19977864, 0.22267813, 0.23223037, 0.22675592, 0.13598439, 0.15963286, 0.1612627 , 0.17513632, 0.18322103, 0.2877577 , 0.27020968, 0.18514289, 0.17197507, 0.19590591, 0.22271499, 0.23195811, 0.21803925, 0.1929823 , 0.20716739, 0.24946071, 0.24546904, 0.19065116, 0.17914902, 0.1868659 , 0.17505631, 0.1830747 , 0.18057887, 0.19849887, 0.18383637, 0.23887873, 0.23452593, 0.1918844 , 0.17905533, 0.1910841 , 0.19864238, 0.19010148, 0.17789515, 0.18603461, 0.18273807, 0.20076333, 0.17672305, 0.19126784, 0.16163583, 0.16926268, 0.21341869, 0.18230094, 0.16424782, 0.16681085, 0.15389352, 0.15473417, 0.14923241, 0.15741474, 0.16312388, 0.16086082, 0.12666257, 0.16915116, 0.15223396, 0.14300318, 0.12823844, 0.13796595, 0.14674089, 0.11887511, 0.14403659, 0.10191998, 0.12120476, 0.10131311, 0.13067276, 0.1333081 , 0.1186684 , 0.13361134, 0.24023971, 0.32965852, 0.30455242, 0.31352362, 0.32912617, 0.25780771, 0.29260109, 0.28582712, 0.31070556, 0.29087978, 0.28538297, 0.32503014, 0.27752369, 0.28088129, 0.31081308, 0.33021583, 0.30645791, 0.29337918, 0.29597272, 0.28752214, 0.2888452 , 0.2668881 , 0.31362102, 0.27338785, 0.2952788 , 0.28913268, 0.30910181, 0.29839627, 0.29444584, 0.26028107, 0.26012592, 0.27539727, 0.26093925, 0.28724735, 0.30257028, 0.28892286, 0.25253944, 0.2550615 , 0.27857276, 0.27005181, 0.27486623, 0.31311787, 0.25343973, 0.24858144, 0.2529135 , 0.30244067, 0.27809181, 0.27362763, 0.2405646 , 0.24111017, 0.28706148, 0.26004099, 0.30621664, 0.25846482, 0.2524315 , 0.25019702, 0.25244483, 0.26831422, 0.22910217, 0.28519051, 0.25028803, 0.24192345, 0.23548351, 0.23333206, 0.24378836, 0.2486244 , 0.2438818 , 0.25757255, 0.24609881, 0.25059398, 0.25083546, 0.26783868, 0.25518748, 0.24165695, 0.25689221, 0.22934907, 0.20707947, 0.23493095, 0.23799512, 0.24201602, 0.24119263, 0.22098713, 0.22910896, 0.22992514, 0.22194994, 0.23683219, 0.22335878, 0.24688016, 0.2326519 , 0.23146235, 0.22675936, 0.21173573, 0.23648312, 0.21927844, 0.21474325, 0.2131792 , 0.17967469, 0.20694922, 0.20539804, 0.21448856, 0.2127286 , 0.20335412, 0.21594698, 0.22229512, 0.20864877, 0.21768901, 0.20946439, 0.19372763, 0.20676283, 0.21544889, 0.21878148, 0.23102135, 0.21072899, 0.20565189, 0.21556784, 0.2012182 , 0.21137754, 0.22243013, 0.20368567, 0.20037298, 0.1982842 , 0.21000778, 0.19987097, 0.20541848, 0.18450061, 0.18039971, 0.20891713, 0.19252837, 0.19521733, 0.17340786, 0.19945363, 0.1976459 , 0.19508912, 0.18514961, 0.17993773, 0.18287986, 0.18815831, 0.19075495, 0.19692821, 0.18647962, 0.17933189, 0.15515995, 0.19903651, nan, 0.19188055, 0.19590059, 0.16248811, 0.18427167, 0.1843403 , 0.17652679, 0.18536852, 0.19037717, 0.17309643, 0.1877581 , 0.19000313, 0.17676971, 0.1732694 , 0.17835733, 0.18041845, 0.15968751, 0.17132655, 0.16133463, 0.15584889, 0.16554913, 0.17727815, 0.17068043, 0.15456427, 0.17129402, 0.17579703, 0.1560759 , 0.16721622, 0.17051381, 0.15455044, 0.1604986 , 0.16573348, 0.16172193, 0.17159208, 0.17024748, 0.15159817, 0.16531245, 0.15625617, 0.16697528, 0.15485388, 0.15718571, 0.15059453, 0.16038043, 0.14699499, 0.15503587, 0.13781138, 0.14903435, 0.13130395, nan, nan, nan])
Variances (σ²):
array([3.57878940e-06, 6.03840533e-06, 5.00828802e-06, 4.36535900e-06, 4.50951138e-06, 4.68190314e-06, 2.95884459e-06, 2.60362703e-06, 3.25620410e-06, 3.07237660e-06, 3.15737245e-06, 3.09282281e-06, 4.36853278e-06, 3.91029104e-06, 4.48221435e-06, 5.60566315e-06, 4.09712523e-06, 2.77736139e-06, 3.41221789e-06, 2.91698606e-06, 3.86259080e-06, 3.95828031e-06, 2.64013266e-06, 4.09132159e-06, 3.57479906e-06, 3.32670000e-06, 4.17650643e-06, 3.56811141e-06, 3.81497821e-06, 3.85324456e-06, 4.40301282e-06, 3.65842849e-06, 3.26660777e-06, 2.94826350e-06, 4.19882015e-06, 2.95142709e-06, 2.94767439e-06, 3.34476270e-06, 4.24987852e-06, 3.95347157e-06, 1.76316459e-06, 2.08219555e-06, 2.35803872e-06, 2.67830572e-06, 3.00572575e-06, 5.60896965e-06, 5.12954452e-06, 2.98312501e-06, 3.45132524e-06, 4.61145640e-06, 4.32867519e-06, 3.01679273e-06, 2.59021924e-06, 2.09443481e-06, 2.05463213e-06, 2.40132014e-06, 2.07446373e-06, 1.49744394e-06, 1.42942569e-06, 1.42053169e-06, 1.36279997e-06, 1.39694701e-06, 1.52960813e-06, 1.43191172e-06, 1.27546259e-06, 1.94690851e-06, 1.85706993e-06, 1.46170375e-06, 1.24584636e-06, 1.28676880e-06, 1.47738434e-06, 1.45683120e-06, 1.27952762e-06, 1.37659557e-06, 1.32224349e-06, 1.60014105e-06, 1.34012626e-06, 1.52555794e-06, 1.04471818e-06, 1.23654375e-06, 1.77694224e-06, 1.39072510e-06, 1.18499599e-06, 1.27563714e-06, 1.13894757e-06, 1.15298438e-06, 1.09384408e-06, 1.22266458e-06, 1.40771536e-06, 1.37062745e-06, 9.96884254e-07, 1.52275533e-06, 1.24874804e-06, 1.28536229e-06, 1.09282548e-06, 1.32465323e-06, 1.43678840e-06, 1.13106455e-06, 1.52861460e-06, 1.03418813e-06, 1.43564571e-06, 1.41285569e-06, 2.37854587e-06, 1.42959074e-06, 1.21375294e-06, 1.71168211e-06, 2.91760749e-06, 3.79904021e-06, 3.27425459e-06, 3.18300745e-06, 3.44004751e-06, 2.48705761e-06, 2.90379577e-06, 2.96211375e-06, 3.35987321e-06, 2.98202659e-06, 2.78932114e-06, 3.43843844e-06, 2.71611846e-06, 2.85851526e-06, 3.25437010e-06, 3.76206166e-06, 3.55162209e-06, 3.03414777e-06, 3.29512710e-06, 2.96691689e-06, 3.18212454e-06, 2.82891321e-06, 3.77782317e-06, 3.20203788e-06, 3.50827266e-06, 3.32178089e-06, 3.84969921e-06, 3.76799558e-06, 3.83155761e-06, 3.26710657e-06, 3.16037958e-06, 3.44323466e-06, 3.19584804e-06, 3.78990594e-06, 4.12625294e-06, 4.09603578e-06, 3.24223413e-06, 3.39147432e-06, 4.41275521e-06, 3.94910530e-06, 4.59576209e-06, 5.82331053e-06, 4.21080333e-06, 4.74701628e-06, 4.93703628e-06, 7.38407808e-06, 8.40630239e-06, 1.05229699e-05, 6.97426874e-06, 6.21186717e-06, 7.06732797e-06, 5.28734895e-06, 5.35090192e-06, 3.94179310e-06, 3.90216193e-06, 3.73505093e-06, 3.83142746e-06, 4.35531308e-06, 3.44583231e-06, 4.92249605e-06, 3.83542249e-06, 3.93131059e-06, 3.61667260e-06, 3.67346922e-06, 3.88550760e-06, 3.99103268e-06, 4.01084331e-06, 4.56985892e-06, 4.31896001e-06, 4.45861112e-06, 4.40554203e-06, 4.94598122e-06, 4.51519782e-06, 4.21621016e-06, 4.98688463e-06, 4.25623559e-06, 3.55482929e-06, 4.46680572e-06, 4.52167396e-06, 4.82631589e-06, 4.89625004e-06, 4.21841449e-06, 4.75579075e-06, 4.83353338e-06, 4.74727313e-06, 5.51807846e-06, 4.99119444e-06, 6.12682318e-06, 5.81029665e-06, 6.07172582e-06, 6.10055408e-06, 5.79709321e-06, 6.93713996e-06, 7.36330124e-06, 9.33468983e-06, 1.21039254e-05, 1.33626081e-05, 1.58952015e-05, 8.65595875e-06, 7.11304409e-06, 5.88274859e-06, 5.10433704e-06, 5.27375124e-06, 5.57091513e-06, 4.90790896e-06, 5.38589339e-06, 5.28595001e-06, 4.57293756e-06, 5.11959490e-06, 5.33714706e-06, 5.71430919e-06, 6.05257218e-06, 5.67914749e-06, 5.23675394e-06, 5.61276032e-06, 5.22067816e-06, 5.75980761e-06, 6.44020108e-06, 5.96848736e-06, 5.53765047e-06, 5.74192405e-06, 6.18154748e-06, 5.73017729e-06, 5.98972504e-06, 5.22563030e-06, 5.12746850e-06, 6.44574179e-06, 5.80328961e-06, 5.84867725e-06, 5.26558881e-06, 6.73769620e-06, 6.82888984e-06, 6.82340364e-06, 6.38694807e-06, 6.54506353e-06, 6.77306458e-06, 7.13571229e-06, 7.64171695e-06, 9.35464238e-06, 1.08150486e-05, 1.34828391e-05, 1.61968696e-05, 6.05101531e-05, nan, 2.82204713e-05, 1.67317258e-05, 9.45255979e-06, 9.75101519e-06, 8.75282206e-06, 8.26851271e-06, 8.56413462e-06, 9.24039272e-06, 7.62776759e-06, 8.60681553e-06, 8.84325399e-06, 7.60992278e-06, 7.73648757e-06, 8.02507393e-06, 7.84474963e-06, 6.85651497e-06, 7.62343609e-06, 6.77307230e-06, 6.56861758e-06, 7.43567046e-06, 8.30504168e-06, 7.63808244e-06, 6.97060232e-06, 8.21603862e-06, 8.10562811e-06, 7.02986637e-06, 8.04457539e-06, 8.53162201e-06, 7.52863929e-06, 8.00594905e-06, 8.32117624e-06, 8.40720967e-06, 8.98805914e-06, 9.03053623e-06, 7.97169770e-06, 9.48641657e-06, 8.91415814e-06, 1.06119169e-05, 1.00119586e-05, 1.02956394e-05, 1.03016197e-05, 1.21231395e-05, 1.26190039e-05, 1.66842441e-05, 1.94328111e-05, 2.92677477e-05, 6.74896323e-05, nan, nan, nan])
[24]:
normalized.plot()
[24]:
Save the final result#
[25]:
from scippneutron.io import save_xye
to_disk = normalized.copy(deep=False)
to_disk.coords['wavelength'] = sc.midpoints(to_disk.coords['wavelength'])
save_xye('fe_bragg_edge.xye', to_disk)