From eaf7f84ab5541633b1e8b59f942a4b2c6f4ea3f3 Mon Sep 17 00:00:00 2001 From: CAOShurong <170531907+CAOShurong@users.noreply.github.com> Date: Thu, 17 Sep 2026 07:45:23 +0800 Subject: [PATCH] Load auxiliary signals with distinct dimensions Keep NXdata valid when an auxiliary signal uses dimensions unrelated to the primary signal, and fall back from Dataset to DataGroup while preserving assembled signal semantics. Add a focused regression for the ESS frame-total layout. Developed with OpenAI Codex assistance. --- src/scippnexus/nxdata.py | 12 +++++++++++- tests/nxdata_test.py | 20 ++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/scippnexus/nxdata.py b/src/scippnexus/nxdata.py index 38d58550..feaa22a3 100644 --- a/src/scippnexus/nxdata.py +++ b/src/scippnexus/nxdata.py @@ -110,6 +110,10 @@ def _init_field_dims(self, name: str, field: Field | Group) -> None: # len(dims). shape = _squeeze_trailing(dims, field.dataset.shape) field.sizes = dict(zip(dims, shape, strict=False)) + elif name in self._aux_signals: + # Auxiliary signals are allowed to have dimensions unrelated to the + # primary signal. Keep the field's fallback dimensions in that case. + return elif self._valid: s1 = self._signal.sizes s2 = field.sizes @@ -376,7 +380,13 @@ def _assemble_as_data( signals = {self._signal_name: da} signals.update(aux) if all(isinstance(v, sc.Variable | sc.DataArray) for v in signals.values()): - return sc.Dataset(signals) + try: + return sc.Dataset(signals) + except sc.DimensionError: + # A Dataset requires matching item dimensionality. NeXus permits + # auxiliary signals on unrelated dimensions, so retain those as + # separate DataGroup entries instead. + pass return sc.DataGroup(signals) return da diff --git a/tests/nxdata_test.py b/tests/nxdata_test.py index 2567f699..5621fb69 100644 --- a/tests/nxdata_test.py +++ b/tests/nxdata_test.py @@ -276,6 +276,26 @@ def test_auxiliary_signal_causes_load_as_dataset(h5root) -> None: assert_identical(data[...], sc.Dataset({'signal': signal, 'xx': aux})) +def test_auxiliary_signal_with_different_dims_loads_as_data_group(h5root) -> None: + signal = sc.array(dims=['tof'], unit='counts', values=[1, 2, 3]) + frame_total = sc.array(dims=['frame'], unit='counts', values=[4, 5]) + data = snx.create_class(h5root, 'data1', NXdata) + data.attrs['axes'] = signal.dims + data.attrs['signal'] = 'signal' + data.attrs['auxiliary_signals'] = ['frame_total'] + snx.create_field(data, 'signal', signal) + snx.create_field(data, 'frame_total', frame_total) + + loaded = snx.Group(data, definitions=snx.base_definitions())[...] + + assert isinstance(loaded, sc.DataGroup) + assert_identical(loaded['signal'], sc.DataArray(signal)) + assert_identical( + loaded['frame_total'], + sc.array(dims=['dim_0'], unit='counts', values=[4, 5]), + ) + + def test_NXlog_data_is_loaded_as_time_dependent_data_array(nxroot) -> None: da = sc.DataArray( data=sc.array(dims=['time'], unit='K', values=[1, 2, 3]),