Skip to content

[BIFROST] Implement support for a moving detector#101

Merged
jl-wynen merged 21 commits into
mainfrom
jlw-workflow-patches
May 11, 2026
Merged

[BIFROST] Implement support for a moving detector#101
jl-wynen merged 21 commits into
mainfrom
jlw-workflow-patches

Conversation

@jl-wynen
Copy link
Copy Markdown
Member

@jl-wynen jl-wynen commented Apr 10, 2026

This implements support for a moving BIFROST detector vessel. Uses scipp/ess#246 and a custom time filter and detector assembly provider to broadcast the raw detector into a time dimension and then turn that into the (a3, a4) dimensions.

g5t and others added 13 commits April 7, 2026 11:41
The positions and orientation of components after the sample position
are, strictly speaking, time-dependent parameters. Previously their
transformation chains lacked this fidelity due to limitations in
creating the NeXus Structure JSON via `moreniius`. Those limitations
have been lifted and newly-generated BIFROST simulation files include
NXtransformation groups with NXlog constituents.

In order to calculate the correct analyzer and sample scattering angle
for each detector pixel, the BIFROST workflow performs some geometry
calculations in the coordinate frame which rotates with the detector
tank, around the sample position.
Since the analyzers and detectors are stationary in this frame, we can
(and must) remove any time-dependence from their positions and
orientations.

This PR introduces a quick-and-dirty hack to only use the first
time-point from any time-dependent transformation result for the
- sample
- analyzers
- detectors
The NeXus format specification now allows most base objects to specify
two attributes which help define the *expected* beam path through a
collection of instrument components: `@inputs` and `@outputs`.

Updates to `moreniius` mean that the BIFROST NeXus Structure JSON now
contains these attributes, including the one-to-many and many-to-one
transitions between, e.g., the sample position and the 45 analyzers.

Given a detector's HDF5 Group, it is possible to follow the @inputs
attributes analagously to a depends-on chain to identify which analyzer
the detector should have received its neutron events from.

This commit adds functionality which has only been tested outside of the
workflow, by defining the `DetectorAnalyzerMap` at repl scope and the
patching `ess.bifrost.io.nexus._get_analyzer_for_detector_name` to use
the static relational information.
I imagine it needs work to mold it into an appropriate `sciline`
workflow.
@jl-wynen jl-wynen marked this pull request as ready for review May 4, 2026 13:02
@SimonHeybrock SimonHeybrock self-assigned this May 7, 2026
Comment thread src/ess/bifrost/io/nexus.py Outdated
Comment on lines +117 to +118
# This function is separate from load_analyzer_for_detector so we get the default
# behavior for resolving NXtransformations.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get this comment. load_analyzer_for_detector does not appear to do anything related to NXtransformations. What are the uses of the two providers, and where do they differ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They need to be used together. load_analyzer_for_detector provides NeXusComponent[snx.NXcrystal, RunType] which get_calibrated_analyzer consumes. And https://github.com/scipp/ess/blob/c551f7eabddda0ffa7fdc8bd7bb82648e7ef80f7/packages/essreduce/src/ess/reduce/nexus/workflow.py#L286 extracts the transformation that is used for the other two arguments of get_calibrated_analyzer. So the 'default behaviour' refers to how ESSreduce extracts transformations and computes positions for components.

Does this make sense? If so, I will update the comment.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it does.

Comment on lines +178 to +188
def stepwise_transformation_time_filter(transform: sc.DataArray) -> sc.DataArray:
"""Collapse runs of equal values into a single value.

This can be used as a time filter for NeXus transformations when the component
mostly stays at a position and only rarely moves.
For example, a stepwise scan across detector rotations.
"""
collapsed = _collapse_runs(transform, 'time')
if collapsed.sizes['time'] == 1:
return collapsed.squeeze('time')
return collapsed
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why this and the np.isclose above is needed. EPICS PVs stream a setpoint, so there should be no need to filter out fluctuations (one might need to filter events during a motor move, but this code does not appear to do that).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do they stream a setpoint? To my understanding, that is different from a PV (which is the actual momentary value). Are you saying that NXlogs will have no noise?

Not sure why this [...] is needed.

By 'this', do you mean collapsing runs of equal values? This is about producing short extra dimensions instead of thousands of identical elements.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, setpoints are streamed. NXlogs will have nnoise or not, depending on whether it was written from a setpoint. If you have a real file, check the source attribute: If it ends in VAL it is a setpoint, if ends in RBV it is the (noisy) readback. There is also DMOV (done-moving, or something like that).

By 'this', do you mean collapsing runs of equal values? This is about producing short extra dimensions instead of thousands of identical elements.

Yes, I mean the collapse. What I mean is: We we would use the setpoint then there should not be any identical elements (provided we filter out intervals during move)? If using the readback, then the issue with many points during move does not disappear - we still need to remove those or may and with large extra dimensions.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do the files still contain repeated values for setpoints? Or only when the setpoint changes?

I'll have to check with Greg about what they want to use on BIFROST.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No repeated values, not according to info I got. Only on change.

@jl-wynen jl-wynen merged commit 4e26c0d into main May 11, 2026
4 checks passed
@jl-wynen jl-wynen deleted the jlw-workflow-patches branch May 11, 2026 10:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants