Parent Files and the Scan Registry¶
This page documents how NXRefine relates a parent file to the individual scan files it groups together, the on-disk data model that records the relationship, and the classes and dialogs that read and write it. It is aimed at developers extending the reduction workflow.
Overview¶
A single experiment is organised around a parent file, named
{sample}_scans.nxs, which holds the shared geometry, reduction settings,
and a registry of the scans that belong to it. Each scan file is an
individual rotation dataset (for example one temperature point) that records
which parent it belongs to.
The relationship is bidirectional by convention:
the parent lists each scan in
/entry/nxscans/scans(with a parallelselectedflag), andeach scan names its parent in its own
/entry/nxscans/parentfield.
NXParent.is_parent verifies that the two agree. Keeping both sides
consistent is the responsibility of the code that creates scans
(NXParent.add_scan) and of the reconciliation paths described in
Reconciling a copied parent.
The nxscans data model¶
nxscans is an NXprocess group that lives in the parent file at
/entry/nxscans (or /entry/{subentry}/nxscans when a subentry is
active). It is the central registry of the scan collection and contains:
scansA resizable 1-D string field (
string_dtype,maxshape=(None,)). Each element is a scan file stem (the filename without.nxs), e.g.sample1_300K.selectedA resizable 1-D
boolfield, index-aligned withscans. Only the selected scans feed downstream consolidation and reduction.settingsAn
NXparametersgroup of reduction parameters shared by all scans (threshold, frame limits, monitor/normalisation, Q limits, mask parameters,scan_path/scan_units, …).transformAn optional
NXdatagroup holding the output Q-grid axes (Qh/Qk/Ql).descriptionAn optional
NXnotedescribing a subentry.
Note that the on-disk field is named scans; scan_files is a Python
property on NXParent that resolves those stems to absolute Path
objects — the two are not the same thing.
In a scan file, the same nxscans group instead holds a single
parent field: the parent’s file name (e.g. sample_scans.nxs),
stored as a plain string, not a NeXus link. It is resolved relative to the
scan’s own directory, which is what makes the reference survive a copy to
another disk.
The NXParent class¶
NXParent (src/nxrefine/nxparent.py) wraps a parent _scans.nxs
file and is the single source of truth for the data model above. All of the
refine/experiment dialogs and NXReduce build on it.
Construction and entry selection¶
NXParent(filename, subentry=None) accepts a path, an NXroot, or
None. The filename must end with _scans. A subentry selects a
group within /entry; when set, all accessors operate on
/entry/{subentry} instead of /entry (see entry_path,
scan_entry, scan_info).
Key accessors¶
scan_infoThe active
nxscansNXprocessgroup;settingsandtransformreturn its like-named children.scans/selectedSorted (
natural_sort) lists of scan stems and their booleans;index(scan)gives the raw, unsorted position needed for in-place writes.scan_file(scan)/scan_files/selected_scansResolve a stem to an absolute
.nxspath; the paths of all registered scans; and the paths of only the selected scans.other_scan_files.nxsfiles in the directory that are neither registered scans nor*_scansparents.is_parent(scan)/has_parent(scan)Whether a scan’s
parentback-pointer names this parent, and whether it records any parent.
Key mutators¶
initialize()Builds the
entry -> nxscans (scans, selected, settings)skeleton plus anNXsample. Thescansandselectedfields are created empty.add_scan(scan, selected=True)The central mutator. Resizes and appends to
scans/selected(guarded against duplicates), then callsadd_parentto stamp the scan’s back-pointer, and migrates any legacy structure.add_parent(scan)Writes
{entry_path}/nxscans/parent = <parent filename>into the scan file.add_scans(selected=True)Globs
{sample}_*.nxsand adds each one (does not checkis_parent).sync_scans(selected=True)Reconciles the registry from the scans’ back-pointers — see Reconciling a copied parent.
update_scan_data()/create_scan_data()Consolidate per-scan
NXdatagroups into the parent withnxconsolidatealongscan_path.
Dialogs¶
The New Parent dialog¶
src/nxrefine/plugins/experiment/new_parent.py (Experiment -> New
Parent). A wizard that chooses the experiment directory, sample/label, and a
configuration file to copy geometry from, then edits reduction parameters.
create_parent() calls NXParent.initialize() and copy_file(...) and
writes the parameters into nxscans/settings. The parent is created with
an empty scan list — scans are added later.
The New Scan dialog¶
src/nxrefine/plugins/experiment/new_scan.py (Experiment -> New Scan).
create_scan() clones the parent entries into a new per-value scan file,
repoints the raw-data links, writes the scan variable, then calls
NXParent.add_scan(...). That single call is what appends the new scan to
the parent registry and stamps the back-pointer into the scan file.
The Initialize Scans and Select Files dialogs¶
src/nxrefine/plugins/refine/initialize_scans.py is a hub for a chosen
parent: pick/create a subentry, then open Select Files, Edit Settings,
Define Lattice, Setup Transforms, or Copy NeXus File.
Select Files (select_files.py, FilesDialog) shows two checkbox
lists: the already-registered Scan Files (pre-checked per selected)
and the unregistered Other Files (other_scan_files). On accept it
toggles selected in place for existing scans (by index) and calls
add_scan for newly chosen files, then consolidates data on a background
thread (ScanDataWorker -> update_scan_data).
NXReduce and the parent¶
NXReduce (src/nxrefine/nxreduce.py) is the reduction workflow
controller for a single scan entry (NXMultiReduce handles the cross-entry
combine/PDF steps).
Parent resolution is lazy through three properties:
parent_fileReads
entry/nxscans/parentfrom the scan’s own wrapper file and resolves it againstbase_directory; falls back to{sample}_scans.nxswhen the field is absent.parentWraps
parent_fileas anNXParent(with the current subentry) when the file exists.parent_entryThe parent’s
NXentrymatching the current entry name.
Reduction parameters are read from the parent’s nxscans/settings
(get_parameter) and written back there (write_parameters ->
NXParent.write_settings); when no parent exists they fall back to the
wrapper’s /entry/nxreduce for backward compatibility.
The workflow operations — nxload, nxlink, nxmax, nxfind,
nxrefine, nxprepare, nxtransform, nxsum (and
NXMultiReduce.nxcombine / nxpdf) — are dispatched by nxreduce()
according to boolean flags. They all pass through record_start /
record / record_end for status tracking, which makes record_start
a convenient single choke point for per-run hooks.
Reconciling a copied parent¶
A parent is often created on one machine and copied to a second disk before
any scans are added, so the copy starts with an empty registry even though the
scan files (each carrying a parent back-pointer) are also copied over. Two
paths rebuild the registry from those back-pointers:
NXParent.sync_scans()Iterates
other_scan_filesand registers each file whose back-pointer matches this parent (is_parent) and that is not already listed. It is called fromFilesDialog.__init__so Select Files shows the recovered scans pre-checked rather than under Other Files.NXReduce.register_with_parent()Called once per instance from
record_start. When the scan reports a parent that does not yet list it, the scan adds itself viaadd_scan(..., selected=True). It is a no-op for scans with no recorded parent and for the parent file itself.
Because both paths funnel through add_scan (which is duplicate-guarded and
resizes under the file lock configured by nxsetconfig), they are
idempotent. Concurrency between reduction jobs each registering themselves is
serialised by that same NeXus file locking.