webpage_generator: instrument pane shows full live measurement parameters
CI for csaxs_bec / test (push) Successful in 1m34s

The instrument-details pane showed only a hand-built 6-row settings summary,
far less than the tomo queue shows per job, and nothing useful for a tomo
started outside the queue.

- Flomni _collect_setup_data now returns current_params: the tomo parameters
  read live from global vars, keyed by the same names the queue stores in
  job.params (_CURRENT_PARAM_KEYS, matching TQ_PARAM_DISPLAY). Values left raw;
  the page formats them. Replaces the old settings dict.
- Extract the queue's param-table builder into a shared buildParamRows() JS
  helper (carries the computed Projections row), used by both the queue and the
  new instrument 'Current measurement' section, so they can't drift.
- renderInstrument renders current_params via buildParamRows (Sample name row
  first); card hides only when there are neither params nor temperatures.
- Base _collect_setup_data contract updated: settings -> current_params.
This commit is contained in:
x12sa
2026-07-06 08:53:15 +02:00
parent a8fb13ca96
commit ac1ade2faf
@@ -1373,10 +1373,21 @@ class WebpageGeneratorBase:
by the HTML page as the 'Instrument details' section.
Expected keys (all optional):
type (str) -- setup identifier, e.g. "flomni"
sample_name (str) -- current sample name
temperatures (dict) -- label -> float (degrees C) or None
settings (dict) -- label -> formatted string
type (str) -- setup identifier, e.g. "flomni"
sample_name (str) -- current sample name
temperatures (dict) -- label -> float (degrees C) or None
current_params (dict) -- raw tomo parameters read live (keyed by
the same global-var names the tomo queue
stores in job.params, e.g. tomo_countingtime,
fovx, tomo_angle_stepsize, ...). Rendered by
the page through the same param-table path
as a queue job (buildParamRows), so the
'Current measurement' section always matches
the queue's level of detail -- and shows the
details even for a scan started outside the
queue. Values are left raw; the page formats
them (fmtTqParam) and computes the projection
count (calcProjections).
"""
return {}
@@ -1402,10 +1413,31 @@ class WebpageGeneratorBase:
class FlomniWebpageGenerator(WebpageGeneratorBase):
"""
flOMNI-specific webpage generator.
Adds: temperatures, sample name, measurement settings from global vars.
Adds: temperatures, sample name, and live current-measurement parameters
from global vars.
Logo: flOMNI.png from the same directory as this file.
"""
# Global-var names of the tomo parameters to read live for the
# 'Current measurement' section. Kept in the same order as, and matching,
# the tomo queue's TQ_PARAM_DISPLAY keys so the two render identically.
# (Any name that is not actually a global var is skipped silently and just
# does not appear as a row.)
_CURRENT_PARAM_KEYS = [
"tomo_type",
"tomo_countingtime",
"fovx",
"fovy",
"tomo_angle_stepsize",
"tomo_angle_range",
"tomo_shellstep",
"stitch_x",
"stitch_y",
"frames_per_trigger",
"single_point_instead_of_fermat_scan",
"ptycho_reconstruct_foldername",
]
# label -> dotpath under device_manager.devices
_TEMP_MAP = {
"Heater": "flomni_temphum.temperature_heater",
@@ -1429,40 +1461,28 @@ class FlomniWebpageGenerator(WebpageGeneratorBase):
for label, path in self._TEMP_MAP.items()
}
# ── Settings from global vars ─────────────────────────────────
# ── Current measurement parameters (read live) ────────────────
# Same keys the tomo queue stores in job.params, so the 'Current
# measurement' section on the page renders through the identical
# param-table path (buildParamRows -> fmtTqParam / calcProjections)
# and always matches the queue's level of detail. Reading these live
# means a tomo started OUTSIDE the queue still shows full parameters.
# Values are left raw; the page does the formatting.
g = self._bec
def _fmt2(v):
current_params = {}
for key in self._CURRENT_PARAM_KEYS:
try:
return f"{float(v):.2f}"
val = g.get_global_var(key)
except Exception:
return "N/A"
fovx = g.get_global_var("fovx")
fovy = g.get_global_var("fovy")
stx = g.get_global_var("stitch_x")
sty = g.get_global_var("stitch_y")
def _fmt_int(v):
try:
return str(int(v))
except (TypeError, ValueError):
return "N/A"
settings = {
"Sample name": sample_name,
"FOV x / y": f"{_fmt2(fovx)} / {_fmt2(fovy)} \u00b5m",
"Step size": _gvar(g, "tomo_shellstep", ".2f", " \u00b5m"),
"Exposure time": _gvar(g, "tomo_countingtime", ".3f", " s"),
"Angle step": _gvar(g, "tomo_angle_stepsize", ".2f", "\u00b0"),
"Stitch x / y": f"{_fmt_int(stx)} / {_fmt_int(sty)}",
}
val = None
if val is not None:
current_params[key] = val
return {
"type": "flomni",
"sample_name": sample_name,
"temperatures": temperatures,
"settings": settings,
"type": "flomni",
"sample_name": sample_name,
"temperatures": temperatures,
"current_params": current_params,
}
@@ -2656,12 +2676,18 @@ function esc(s){{return String(s==null?'N/A':s).replace(/&/g,'&amp;').replace(/<
function renderInstrument(setup){{
const card=document.getElementById('instrument-card'),grid=document.getElementById('instrument-grid');
if(!setup||(!setup.temperatures&&!setup.settings)){{card.style.display='none';return;}}
const cp=(setup&&setup.current_params)||null;
const hasParams=cp&&Object.keys(cp).length>0;
if(!setup||(!setup.temperatures&&!hasParams)){{card.style.display='none';return;}}
card.style.display='';
let html='';
if(setup.settings){{
html+='<div class="instrument-section"><h3>Measurement settings</h3><table class="kv-table">';
for(const[k,v] of Object.entries(setup.settings)) html+='<tr><td>'+esc(k)+'</td><td>'+esc(v)+'</td></tr>';
if(hasParams){{
// Same param-table path as a tomo queue job (buildParamRows), so the live
// 'Current measurement' matches the queue's level of detail — and shows
// full parameters even for a scan started outside the queue.
html+='<div class="instrument-section"><h3>Current measurement</h3><table class="kv-table tq-params">';
if(setup.sample_name) html+='<tr><td>Sample name</td><td>'+esc(setup.sample_name)+'</td></tr>';
html+=buildParamRows(cp);
html+='</table></div>';
}}
if(setup.temperatures){{
@@ -2761,6 +2787,24 @@ function calcProjections(params){{
return null;
}}
// Build the tomo-parameter table rows for a params object, in TQ_PARAM_DISPLAY
// order, injecting the computed Projections row after the angle step. Shared by
// the tomo queue (per job) and the instrument 'Current measurement' section, so
// both always show the same parameters, formatted the same way.
function buildParamRows(params){{
let rows='';
TQ_PARAM_DISPLAY.forEach(([key,dispLabel])=>{{
if(key in params){{
rows+='<tr><td>'+dispLabel+'</td><td>'+esc(fmtTqParam(key,params[key]))+'</td></tr>';
}}
if(key==='tomo_angle_stepsize'){{
const nproj=calcProjections(params);
if(nproj!=null) rows+='<tr><td>Projections</td><td>'+nproj+'</td></tr>';
}}
}});
return rows;
}}
function renderTomoQueue(jobs){{
const el=document.getElementById('tomo-queue-content');
if(!el) return; // setup has no tomo-queue card (HAS_TOMO_QUEUE=False)
@@ -2797,20 +2841,7 @@ function renderTomoQueue(jobs){{
shouldOpen=true;
autoOpened.add(key);
}}
let paramRows='';
TQ_PARAM_DISPLAY.forEach(([key,dispLabel])=>{{
if(key in params){{
paramRows+='<tr><td>'+dispLabel+'</td><td>'+esc(fmtTqParam(key,params[key]))+'</td></tr>';
}}
// After angle step, inject a computed projection count row, driven by
// this instrument's TOMO_TYPES config (must match the corresponding
// Python-side formula, e.g. _tomo_type1_actual_grid() for flomni's
// "equally_spaced_grid" type).
if(key==='tomo_angle_stepsize'){{
const nproj=calcProjections(params);
if(nproj!=null) paramRows+='<tr><td>Projections</td><td>'+nproj+'</td></tr>';
}}
}});
const paramRows=buildParamRows(params);
const addedAt=job.added_at
?'Added '+new Date(job.added_at).toLocaleString([],{{month:'short',day:'numeric',hour:'2-digit',minute:'2-digit'}})
:'';