Repository navigation
[essreduce] feat: Merge NeXus files of compatible runs - #837
SimonHeybrock wants to merge 6 commits into
Conversation
plan_merge checks the runs and returns a MergePlan holding only metadata and the issues found; MergePlan.write copies the first file and appends the other runs to every dataset with an unlimited first dimension, so attributes, links, chunking, and compression are kept. Index fields (event_index, cue_index) are offset, NXlog summaries recomputed, and the per-run NXentry fields of all runs kept in <entry>/merged_runs. MergeRules, loadable from TOML, set warn/error tolerances on the spread of NXlog values over all runs, and paths of fixed-size fields that may differ. All other fixed-size fields must be identical. Refs #836 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A unit-less tolerance took the unit of the log, so a file producer changing a unit, e.g., from mm to m, silently changed the tolerance. Tolerances now need a unit; a bare number is dimensionless, and only zero, the same in any unit, needs none. A unit change of a log between runs is reported once as such, not also as a spread in mixed units. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Drop leading NXlog rows that repeat the end of the previous run. The file writer starts each series with values from before the run, so plain concatenation made static transformations time-dependent and broke GenericNeXusWorkflow on merged files. - Refuse NXlog rows with time 0, which the file writer appears to write as padding, NaN in checked logs, unknown NXlog fields, index fields of unknown groups, and merged files as input. - Ignored paths and per-run fields cover the whole subtree. - Write to a temporary file and rename it when complete. - Set the root file_name and file_time to the merged file. - Validate rules files; resolve input paths; compute spreads of integer logs without float conversion. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Messages from Kafka arrive out of order, so the pulses of a run are not sorted. Compare the earliest pulse of a run with the latest pulse of the runs before, to make sure no events are counted twice. Dropping repeated NXlog rows assumes sorted times, so an unsorted series is an error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Note from discussion with Judith Houston: In general, samples should be re-run under indentical conditions, so we do not need to worry about merging over non-equivalent configurations. However, circumstances are possible that we need to be robust to, such as: A user removes their sample from position AT, and then decides the statistics are insufficient from an initial reduction, so before cleaning it they decide to run it again, and insert it into position DT in the changer. In this case, maybe a warning at most, since in this case the sample changer position (and hence different motor positions) should not modify the sample scattering. |
Programmers always worry about checking such things, because it is way easier to check early, instead of backtracing once you have have analyzed or published.
What we have in this PR allows for configuring this using a config file, with distinct warning and error thresholds, as well as which dataset to compare or not. |
| _INDEX_FIELDS = { | ||
| ('NXevent_data', 'event_index'): 'event_time_offset', | ||
| ('NXevent_data', 'cue_index'): 'event_time_offset', | ||
| ('NXlog', 'cue_index'): 'time', |
There was a problem hiding this comment.
How do you handle histogram monitor data (NXdata)? I guess we need to concatenate along the time dimension? The monitor data is important for normalization.
Is NeXus happy having a non-regular time coordinate?
There was a problem hiding this comment.
This is not what this is about: _INDEX_FIELDS lists fields that index into datasets that get concatenated, and thus need to be rewritten (see the cumsum code lower down).
There was a problem hiding this comment.
Ok, maybe I attached the comment in the wrong place, but my questions still stands.
How do you handle monitor data? I could not find any instances of either NXmonitor or NXdata in the merging code.
|
|
||
| def test_rule_refuses_tolerance_without_unit() -> None: | ||
| with pytest.raises(ValueError, match='without unit'): | ||
| Rule(paths=('/a',), error=sc.scalar(0.0, unit=None)) |
There was a problem hiding this comment.
Did you test with real e.g. loki files?
There was a problem hiding this comment.
You mean in general or this particular test case?
There was a problem hiding this comment.
I meant in general. I wrote here instead of a general comment because it is then easier to have a conversation without having to copy/paste/quote in general comments
You may already be aware of this requirement, but because the language here specifically pertains to reduction– I wanted to be 100% clear here that this does not solely apply to sample files. It is also possible that users will want to merge multiple background measurements (so empty cells, direct/blocked beam and flood field data) which are themselves used for reduction of single- or multiple-run sample files. |
Closes #836.
Adds
ess.reduce.nexus.merge, a generic utility to merge the raw NeXus files of consecutive, compatible runs into one file that looks like a single long run. Scientists reduce and check each run first, then merge by hand. The regular single-run workflows then process the merged file.The rules file sets
warnand/orerrortolerances on the spread (max - min) of NXlog values over all runs, and lists fixed-size paths that may differ between runs (ignore):Choices
WorkflowSpeccan output it and a local UI can callwrite. Writing on the backend waits until essapps supports writing files.GenericNeXusWorkflowrejects time-dependent transformations. The merge drops leading rows that are identical to the end of the previous run; any other row that is not later than the previous run's last row is an error.event_indexandcue_indexget offsets. NXlogminimum_value/maximum_value/average_valueare recomputed;average_valueis a plain mean, which matches the file writer's values in the coda files.end_timetakes the last run's value, and the root attributesfile_nameandfile_timedescribe the merged file. Each run's per-run NXentry fields (start_time,end_time,entry_identifier,entry_identifier_uuid,scripts) and file name are kept in<entry>/merged_runs/run_<i>. The file is written under a temporary name and renamed when complete.ignore. Runs are sorted bystart_time, and runs that overlap in time are an error. So are pulses of a run that are not later than all pulses of the runs before (only the earliest and latest pulse are compared, since messages from Kafka arrive out of order and pulses within a run are not sorted), unsorted NXlog series, NaN in a checked log, NXlog fields or index fields (event_index,cue_index) the merge does not know how to append, and merged files as input. NXlogs that no rule matches are not checked; if their values change, the plan reports them as info.error = 0works for any log. A log whose unit differs between runs is an error.Not included
Test plan
coda_freia_999999_00026865.hdfwith a copy made to look like the next run: all times shifted past the end of the original, and the rows from before its start replaced by the last rows of the original. The plan refuses the original files for their time-0 rows. With those rows trimmed, it reports no errors; 136 single-row logs stay single-row, andGenericNeXusWorkflowcomputes the same detector position from the merged file as from the original. Writing takes about 3 s.🤖 Generated with Claude Code