add: live gridded reciprocal-space volume (4/4) - #139
Conversation
69b76d2 to
6c8c969
Compare
188fdbb to
ec48bc7
Compare
6c8c969 to
5cf7012
Compare
3e547ea to
b2c60b2
Compare
|
The crash you hit here came from the same root cause as #137 — fixed in A bad profile no longer makes This branch was rebased (it had a duplicate cherry-pick of the fix), so please |
Requested by Osayi-ANL on #139: the HKL Setup editor and the HDF5 writing path both changed enough to warrant a minor bump ahead of merging the RSM stack.
6731035 to
9d77d11
Compare
5cf7012 to
d575f4c
Compare
|
Went through each item:
Current tip is |
There was a problem hiding this comment.
-
Change these '{"CENTER_CHANNEL_PIXEL": [300.0, 300.0], "DISTANCE": 400.644, "PIXEL_DIRECTION_1": "z-", "PIXEL_DIRECTION_2": "x-", "SIZE": [28.38, 28.38], "UNITS": "mm"}' to a single input in the GUI. You don't have to change the dict structure it was saved in just how we grap the values.
-
Sample Orientation input in the IOC_RSM_PARAMETER is not information that is used in 6IDB. We already have the Primary, Inplane, Sample directions which we don't necessarily use, but can be used in place of the input. So it is just redundant.
-
The HKL Setup does not populate based on the profile selected instead it selects the profile toggled in the setup config. Try opening the config and viewing an selected profile. Then open the HKL Setup. It opens that profile even though it is not selected.
…DETECTOR_SETUP into per-value fields Apply & Save and Reload both ran one check -- RAW_CONFIG != snapshot -- and reported every failure as "the IOC did not activate that snapshot", with a Retry button offered unconditionally. Three unrelated situations were being described with one wrong sentence, in vocabulary no beamline user can act on. Reproduced the report from #139: the editor is pinned to the profile it opened with (the IOC builds its record database from that profile at launch), but app_settings.reload() re-resolves the locator on every call, so selecting a different profile in another window makes the two diverge. The save had succeeded; nothing was corrupted; removing PVs was incidental. Retry could never have worked because the target had moved. The three cases are now distinguished and worded for someone who knows diffraction, not this codebase: - ActiveProfileChanged -- names both profiles ("HKL Setup is editing 'X', but 'Y' is now active"), states that the angle IOC is still serving X's PVs so live readers are unaffected, tells the user to reopen HKL Setup, and hides Retry because it cannot succeed. - ProfileContentMismatch -- the same profile came back with different values, i.e. a concurrent writer. Lists the differing settings in HKL terms ("Detector circle 1 - POSITION: saved ..., read back ...") under Show Details, capped at 15 with an overflow line. Retry still offered. - Anything restart_ioc raises -- the IOC genuinely failed to restart. Says the PVs still carry the previous values and nothing was rolled back. Profiles are named, never shown as database row ids. Detecting a switch needs the *resolved* profile, not app_settings.LOCATOR, which is None whenever the profile is auto-detected from the database selection -- comparing locators reports "unchanged" across exactly the switch this is meant to catch. Adds ConfigSource.resolved_identity() for that. Two adjacent fixes found on the way: - set_locator(None) wrote the literal string 'None' into the environment and the state file, so the next read returned it as a real, unresolvable locator. It now clears both, and _parse_locator treats a stale 'None' as unset so already-poisoned state files recover. - The out-of-sync text claimed "the profile is saved" even on the Reload path, which never saves. Also #139: DETECTOR_SETUP was a single line edit holding raw JSON. It is now one input per value, generated from DETECTOR_SETUP_FIELDS so the optional entries (PIXEL_SIZE, ROI, BINNING, DETROT/TILT, per-field units) each get a row with a placeholder showing the expected shape. Blank means absent. The stored table is untouched, including int-vs-float types, and unrecognised keys are carried through rather than dropped.
|
Thanks for the error text — that was the whole ballgame. Reproduced it exactly, and it's not what either of us assumed. The "IOC out of sync" errorIt was a profile switch, not a failed save, and nothing to do with removing PVs. The tell is in your own message: both the prefix and the record names differ — What happened: HKL Setup binds to whichever profile was active when it opened, because the IOC builds its EPICS record database from that profile at launch. But Your save went through fine. Nothing was corrupted and no rollback was needed. Do you remember having a second window open, or switching profiles in Workflow around then? That would confirm it end to end. What changed (
|
There was a problem hiding this comment.
Yes, I think an advanced section or tab to place the 'Sample Orientation' dropdown is necessary to not overstimulate the user. Let's do it that way.
- The protected_static_values change in 51cfa30 (rsm/02-naxis-issue132) reasserts profile values back onto the IOC on a timer, so caput can't be used from the terminal. We want all PVs assertable from the terminal.
I already approved #137, so I have a patch ready: it removes protected_static_values and the reassert loop. Static records still get set once at IOC startup. They track the ioc value and if the ioc is changed via caput or the gui a save dialog comes up and lists the changes. So caput changes can be detected by the ioc to be applied and saved to the profile.
Do you agree? I think that we should still allow caput's from the terminal to also be saved to profiles.
-
I don't think we need to save the AXIS_NUMBER as a dataset just a scalar is fine since it doesn'tchange
-
There are three ways we save a name associated to a PV(Label, Record Name, and Spec Motor Name). I don't think it needs to be that much maybe removing spec motor name.
|
@Osayi-ANL — status update after re-reviewing current stacked head Resolved on the current head
Remaining takeover blockers
Restack and gates
|
d575f4c to
3b9b10c
Compare
86e2605 to
d87cede
Compare
3b9b10c to
5b43d9c
Compare
d87cede to
d5aa57d
Compare
5b43d9c to
66fb8e7
Compare
d5aa57d to
18b0f4f
Compare
66fb8e7 to
8682fdf
Compare
18b0f4f to
c2befe5
Compare
…ing the two-line copyright notice
add: UChicago Argonne BSD open-source license header to all source files
setup GUI, and resolved-profile-identity handling (PR #139) The branch was restacked onto a new live-gridded-volume architecture and two reviewed pieces of prior work never made it back: an optional live-PV source for the UB matrix (backup/rsm-04-live-grid-pre-restack@2db947b) and a per-field detector-setup form plus a profile-identity fix for Apply & Save's out-of-sync messaging (@b500b8b). Ported as design reference only -- the surrounding change-review/CAS/persistence code has all changed since. IOC_RSM_PARAMETER gains two optional fields, additive to schema version 1: UB_MATRIX_SOURCE_PV and DETECTOR_SETUP.DISTANCE_SOURCE_PV. Each is validated as PV-name-only (a numeric string would pass the axis/energy SOURCE_PV convention's leniency but can never connect to anything, since UB_MATRIX and DETECTOR_SETUP.DISTANCE already have their own dedicated literal fields). UB_MATRIX/DISTANCE stay the validated static fallback either way. At IOC runtime, source_vector_or_fallback/source_scalar_or_fallback poll through the same cached EpicsPV(auto_monitor=True) mechanism as the existing per-axis source_value -- but fall back to the stored profile value instead of NaN on disconnect: a bad UB matrix or distance silently corrupts every downstream HKL index, unlike a stale axis angle. Logs only on failure/ recovery transitions. _adoptable_records excludes the UB/distance IOC records from live-caput adoption whenever a source PV is configured, keeping the existing unconditional adoption when it isn't. GUI: "UB Matrix (PV or value)" and "Distance (PV or value)" are dedicated widgets pulled out of the generic key.replace('_',' ').title() calibration loop (which produced "Ub Matrix"). Detector setup gets its own two-column grid generated from DETECTOR_SETUP_FIELDS, one row per declared field, placeholders showing the expected shape, direction-1/direction-2 wording throughout (never x/y). Both PV-or-value widgets track the last valid literal live via textChanged, not just on Apply -- otherwise typing a literal then switching to a PV in the same session would silently discard the just-typed value and fall back to whatever was last saved. DISTANCE_SOURCE_PV is excluded from the detector-setup "unknown key" carry-through, or switching back to a literal would leak the stale PV name back in on save. The close-time change-review table collapses each PV-or-value pair into one logical row instead of two independent flattened keys, so a keep/drop decision can't update only one half of the pair. Restores the three-way distinction Apply & Save lost: ActiveProfileChanged (the profile was switched in another window -- no Retry, it can't succeed), ProfileContentMismatch (same profile, different values -- a concurrent writer, Retry still offered), and a genuine restart failure. Detecting a switch needs ConfigSource.resolved_identity() (new), not app_settings.LOCATOR, which is None under DB auto-detect. The activation-mismatch diff also fixes a bug where an empty-dict section and an absent key flattened identically, letting a genuine mismatch report zero differences. set_locator(None) now clears the env var and state file instead of writing the literal string "None" into both; _get_effective_locator treats an already-poisoned "None" as unset so existing state files self-heal.
staleness warning (PR #139) Start Metadata Associator built its --metadata-channels string from whatever app_settings.HKL/METADATA_CA/METADATA_PVA already held in memory -- never reloading first, never checking the result actually covers what required_rsm_channels() needs to compute Q, and only deduping within the HKL-derived channels rather than across all sources. A profile missing a required channel would start a half-configured associator with no warning. run_associator_consumers now reloads app_settings first, then refuses to start and lists the missing channel names if required_rsm_channels(HKL) isn't fully covered by the built channel set. _build_metadata_channel_list returns the ordered URI list and the bare-name set together, rather than having the validation step re-split the joined ','-delimited string on ',' then '://' -- a free-text METADATA_CA/METADATA_PVA value containing a literal comma (a plausible Settings-tree typo) split into a fragment with no '://', raising an uncaught IndexError before the associator could ever start. _sync_associator_metadata no longer silently stops and restarts a running associator when its channel set or resolved profile identity drifts -- that could interrupt live acquisition mid-scan, and that decision belongs to the user. It now shows one non-modal "Metadata configuration changed -- restart required" notice instead. HKL Setup edits IOC_RSM_PARAMETER from a separate OS process and never calls back into Workflow, so a new ~10s QTimer polls for drift while an associator is running -- otherwise a config change made over there would never be noticed. The notice compares against the last *observed* state and always advances that baseline on detection, rather than gating on a run-start one-shot flag: a persisting drift still only notifies once (the next tick already matches the new baseline), but a later, different drift -- a second profile switch, or an earlier transient blip clearing -- is still caught. A one-shot-per-run flag would have permanently swallowed every notice after the first. Stopping the associator now also closes any lingering notice box instead of leaving it on screen telling the user to restart something they already stopped.


Stack 4 of 4. Base
rsm/03-detector-physics. This branch contains the whole stack — check this one out for beamline testing and record its SHA in the test notes.Draft until validated on real PVs. Three reviewable commits.
Why
Live 3D today is a point cloud with amnesia: a 100-frame, 1M-point ring buffer that overwrites its oldest frame. Scan a region twice and the second pass evicts the first rather than reinforcing it. PR #129 gave us the accumulator concept offline; this brings it live, so a voxel means the same thing during the scan as in the file you reopen later.
Commit 1 — Qt-free volume persistence
The accumulator runs in a pvaccess consumer, and this repo documents that mixing pvaccess with PyQt5 core-dumps (the two-process note in
consumers/ioc_rsm_parameter). So the write path had to lose Qt first.volume_io.pyimports no Qt and produces the same layout Workbench already reads.HDF5Writerbecomes a plain class with anon_finishedcallback;HDF5Handlerre-wraps it as the signal — the split its own docstring already described.hkl_3d_viewerandarea_det_viewerusedHDF5Writerdirectly as a threaded QObject and now useHDF5Handler.log_managerwas pulling PyQt5 into everything viaLogMixinfor one type annotation and one.instance()call; both deferred. A subprocess test asserts the write path stays Qt-free.Empty voxels become NaN with a coverage volume alongside —
Gridder3Dleaves un-hit bins at zero, indistinguishable from a measured zero.volume_result_to_metadatausedvolume.min()/max(), which would have returned[nan, nan]the moment empty voxels became NaN, breaking every colour scale that trusts it; now finite-only.Coverage does not make a flux-weighted mean recoverable: from
sum(I/m)andNyou cannot recoversum(I)andsum(m)separately unless the monitor was constant across a voxel's contributions.gridder_access.pyis the one place touchingGridder3D._gdata/_gnorm, avoiding.data(which copies the whole numerator and divides on every access — 134 MB per snapshot at 256³). Its guard pins the semantics, sum and count, not just that the attributes exist.Commit 2 — Fail-closed metadata binding
If a frame's angles belong to a different frame, every pixel lands in the wrong place and the result still looks like a diffraction volume. There is no visual tell. So a frame whose required metadata is missing, stale, non-finite or duplicated is rejected and counted, never gridded with a substituted value.
Channels are classified: STATIC geometry legitimately arrives once and is latched; REQUIRED_DYNAMIC (circle positions, monitor) must be fresh; OPTIONAL never blocks. Rejections are counted per reason, so "the preview is empty" resolves to a cause. Scope is frame-bound and trigger-latched metadata only — fly-scan interpolation and pulse-ID joins are deliberately not attempted, because guessing at them is the exact mis-binding this prevents.
Commit 3 — Accumulator, session, consumer, dock
Bounds are locked before the first frame:
Gridder3Dlatchesfixed_rangeon first use, and rebinning mid-scan would change what a voxel means. Changing bounds needs a new accumulator —Clear()keeps the latched range.Two grids over the same accepted samples: the full-resolution one that gets saved, and an independently gridded coarse one for the preview. One extra C call per frame, versus sweeping hundreds of MB of accumulator every snapshot in the process running the hot loop. Coarse shape is chosen per axis to fit 4 MiB while preserving aspect ratio.
Aggregation matches the offline builder exactly — unweighted mean, monitor-normalized per frame. Summing would brighten voxels wherever the scan path sampled more often, an artifact of the trajectory, not the scattering.
Nothing drops silently: out-of-range, non-finite and masked samples are counted separately and shown on screen. Masked pixels are excluded, not zeroed.
HpcRsmGridProcessoris an alternative toHpcRsmProcessor, not an addition — that one attaches three float64 Q arrays to every frame (~100 MB/frame at 2048²); this computes Q where it is consumed and publishes only the preview. It subclasses rather than forks the Q path. Single-instance by nature, so drops are counted and the preview marked incomplete: a drop-aware preview, not a lossless record. Full-resolution state never crosses PVA; saving happens in the consumer, confined toOUTPUT_PATH, refusing overwrite, and only fromstoppedso it cannot capture a torn accumulator.The HKL3D dock adds bounds/resolution, Estimate/Start/Stop/Clear/Save and live counters. Bounds lock while running. Estimate is labelled as observed bounds — later scan motion can still fall outside, which is why the out-of-grid count sits beside it. Uncovered voxels render transparent.
The assertion that matters
Live and offline agree for identical frames, bounds and resolution — coverage exactly equal, means to 1e-12.
build_volumegained an optionalfixed_boundsso the comparison is possible at all.444 tests pass (227 at #129),
ruff check src/dashpva/clean.Blocked on beam
metadata/ca/key is I0Known limits
Single consumer caps throughput; no mid-acquisition snapshot (save requires stop); no full-volume transfer over PVA; assumes shared storage visible to both the consumer and Workbench.