feat(lamni,flomni): finish lamni webpage generator, share base via OMNY_shared
Extracts WebpageGeneratorBase, HttpUploader, LocalHttpServer, and make_webpage_generator() out of flomni_webpage_generator.py into OMNY_shared/webpage_generator_base.py, mirroring the tomo_queue_mixin precedent, then finishes LamNI_webpage_generator.py (fixes its broken import, corrects TOMO_TYPES to match lamni's real 3-mode tomo_scan(), enables HAS_TOMO_QUEUE now that the queue mixin backs it, adds lamni's own TQ_PARAM_DISPLAY, implements _collect_setup_data()) and wires it into LamNI.__init__. Also fixes a real bug surfaced by running two setups against the same shared status-page directory: each setup's logo is now copied to its own filename (e.g. flOMNI.png / LamNI.png) instead of a shared logo.png, since a fixed filename let a browser serve a stale, cached logo from the other instrument's prior session. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
"""
|
||||
LamNI/webpage_generator.py
|
||||
===========================
|
||||
LamNI/LamNI_webpage_generator.py
|
||||
==================================
|
||||
LamNI-specific webpage generator subclass.
|
||||
|
||||
Integration (inside the LamNI __init__ / startup):
|
||||
---------------------------------------------------
|
||||
from csaxs_bec.bec_ipython_client.plugins.LamNI.webpage_generator import (
|
||||
from csaxs_bec.bec_ipython_client.plugins.LamNI.LamNI_webpage_generator import (
|
||||
LamniWebpageGenerator,
|
||||
)
|
||||
self._webpage_gen = LamniWebpageGenerator(
|
||||
@@ -16,7 +16,7 @@ Integration (inside the LamNI __init__ / startup):
|
||||
|
||||
Or use the factory (auto-selects by session name "lamni"):
|
||||
----------------------------------------------------------
|
||||
from csaxs_bec.bec_ipython_client.plugins.flomni.webpage_generator import (
|
||||
from csaxs_bec.bec_ipython_client.plugins.OMNY_shared.webpage_generator_base import (
|
||||
make_webpage_generator,
|
||||
)
|
||||
self._webpage_gen = make_webpage_generator(bec, output_dir="~/data/raw/webpage/")
|
||||
@@ -32,11 +32,8 @@ Interactive helpers:
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from csaxs_bec.bec_ipython_client.plugins.flomni.webpage_generator import (
|
||||
from csaxs_bec.bec_ipython_client.plugins.OMNY_shared.webpage_generator_base import (
|
||||
WebpageGeneratorBase,
|
||||
_safe_get,
|
||||
_safe_float,
|
||||
_gvar,
|
||||
)
|
||||
|
||||
|
||||
@@ -49,57 +46,92 @@ class LamniWebpageGenerator(WebpageGeneratorBase):
|
||||
sample name, and measurement settings.
|
||||
"""
|
||||
|
||||
# LamNI has no persisted parameter-snapshot job queue (unlike flomni
|
||||
# and, later, omny) — the "Tomo queue" card and its global-var read
|
||||
# are skipped entirely for this setup.
|
||||
HAS_TOMO_QUEUE = False
|
||||
# LamNI's tomo-queue jobs (added via TomoQueueMixin, see
|
||||
# OMNY_shared/tomo_queue_mixin.py) are persisted under the same
|
||||
# "tomo_queue" global var key flomni uses, so the shared "Tomo queue"
|
||||
# UI card and its global-var read work unmodified for this setup too.
|
||||
HAS_TOMO_QUEUE = True
|
||||
|
||||
# LamNI's tomo scans come in two flavours: a 2-subtomo and an
|
||||
# 8-subtomo variant, both using the same equally-spaced-grid formula
|
||||
# as flomni's type 1 (just with a different sub-tomo count).
|
||||
# TODO: replace the placeholder keys (1, 2) with whatever values
|
||||
# progress["tomo_type"] actually takes for LamNI once the producer
|
||||
# side (LamNI equivalent of flomni.py) defines them.
|
||||
# LamNI's tomo_scan() (lamni.py) implements the same 3-mode scheme as
|
||||
# flomni: type 1 = equally-spaced grid, always 8 sub-tomograms
|
||||
# (`for ii in range(subtomo_start, 9)`), types 2 and 3 = golden-ratio
|
||||
# variants capped by golden_max_number_of_projections. Identical to
|
||||
# FlomniWebpageGenerator's TOMO_TYPES -- confirmed against lamni.py's
|
||||
# actual tomo_scan()/sub_tomo_scan() implementation, not a guess.
|
||||
TOMO_TYPES = {
|
||||
1: {"kind": "equally_spaced_grid", "n_subtomos": 2},
|
||||
2: {"kind": "equally_spaced_grid", "n_subtomos": 8},
|
||||
1: {"kind": "equally_spaced_grid", "n_subtomos": 8},
|
||||
2: {"kind": "golden_capped"},
|
||||
3: {"kind": "golden_capped"},
|
||||
}
|
||||
|
||||
# TODO: fill in LamNI-specific device paths
|
||||
# LamNI's own names for the tomo-queue job / "Current measurement" param
|
||||
# table, mirroring LamNI._TOMO_SCAN_PARAM_NAMES (lamni.py) rather than
|
||||
# flomni's fovx/fovy, stitch_x/y (lamni is laminography: a single
|
||||
# circular FOV, no 180 mode, no single-point acquisition).
|
||||
TQ_PARAM_DISPLAY = [
|
||||
("tomo_type", "Type"),
|
||||
("tomo_countingtime", "Exposure (s)"),
|
||||
("tomo_circfov", "Circular FOV (µm)"),
|
||||
("tomo_angle_stepsize", "Angle step (°)"),
|
||||
("tomo_shellstep", "Shell step (µm)"),
|
||||
("lamni_stitch_x", "Stitch x"),
|
||||
("lamni_stitch_y", "Stitch y"),
|
||||
("tomo_stitch_overlap", "Stitch overlap"),
|
||||
("corridor_size", "Corridor size"),
|
||||
("frames_per_trigger", "Frames/trigger"),
|
||||
("golden_ratio_bunch_size", "Golden bunch size"),
|
||||
("golden_max_number_of_projections", "Golden max projections"),
|
||||
("golden_projections_at_0_deg_for_damage_estimation", "0° damage-check shots"),
|
||||
("ptycho_reconstruct_foldername", "Recon folder"),
|
||||
]
|
||||
|
||||
# Global-var names of the tomo parameters to read live for the
|
||||
# 'Current measurement' section -- the TQ_PARAM_DISPLAY keys plus
|
||||
# at_each_angle_hook (rendered specially, only when set; see
|
||||
# buildParamRows() in the shared HTML/JS template). Any name that is
|
||||
# not actually a global var is skipped silently and just does not
|
||||
# appear as a row.
|
||||
_CURRENT_PARAM_KEYS = [key for key, _label in TQ_PARAM_DISPLAY] + ["at_each_angle_hook"]
|
||||
|
||||
# LamNI has no temperature/humidity monitoring device configured
|
||||
# (unlike flomni's flomni_temphum.*) -- leave empty until one exists.
|
||||
# label -> dotpath under device_manager.devices
|
||||
_TEMP_MAP = {
|
||||
# "Sample": "lamni_temphum.temperature_sample",
|
||||
# "OSA": "lamni_temphum.temperature_osa",
|
||||
}
|
||||
_TEMP_MAP = {}
|
||||
|
||||
def _logo_path(self):
|
||||
return Path(__file__).parent / "LamNI.png"
|
||||
|
||||
def _collect_setup_data(self) -> dict:
|
||||
# ── LamNI-specific data goes here ─────────────────────────────
|
||||
# Uncomment and adapt when device names are known:
|
||||
#
|
||||
# dm = self._bec.device_manager
|
||||
# sample_name = _safe_get(dm, "lamni_samples.sample_names.sample0") or "N/A"
|
||||
# temperatures = {
|
||||
# label: _safe_float(_safe_get(dm, path))
|
||||
# for label, path in self._TEMP_MAP.items()
|
||||
# }
|
||||
# settings = {
|
||||
# "Sample name": sample_name,
|
||||
# "FOV x / y": ...,
|
||||
# "Exposure time": _gvar(self._bec, "tomo_countingtime", ".3f", " s"),
|
||||
# "Angle step": _gvar(self._bec, "tomo_angle_stepsize", ".2f", "\u00b0"),
|
||||
# }
|
||||
# return {
|
||||
# "type": "lamni",
|
||||
# "sample_name": sample_name,
|
||||
# "temperatures": temperatures,
|
||||
# "settings": settings,
|
||||
# }
|
||||
g = self._bec
|
||||
|
||||
# ── Sample name ───────────────────────────────────────────────
|
||||
# LamNI has no automatic sample changer/device (unlike flomni's
|
||||
# gripper-backed flomni_samples); the active sample is tracked
|
||||
# manually via the sample_name global var (see LamNI.sample_name
|
||||
# property in lamni.py), same default as that property uses.
|
||||
sample_name = g.get_global_var("sample_name") or "bec_test_sample"
|
||||
|
||||
# ── Temperatures ──────────────────────────────────────────────
|
||||
# No lamni temp/humidity device exists yet -- always empty.
|
||||
temperatures = {}
|
||||
|
||||
# ── 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.
|
||||
current_params = {}
|
||||
for key in self._CURRENT_PARAM_KEYS:
|
||||
try:
|
||||
val = g.get_global_var(key)
|
||||
except Exception:
|
||||
val = None
|
||||
if val is not None:
|
||||
current_params[key] = val
|
||||
|
||||
# Placeholder — returns minimal info until implemented
|
||||
return {
|
||||
"type": "lamni",
|
||||
# LamNI-specific data here
|
||||
}
|
||||
"type": "lamni",
|
||||
"sample_name": sample_name,
|
||||
"temperatures": temperatures,
|
||||
"current_params": current_params,
|
||||
}
|
||||
|
||||
@@ -218,6 +218,22 @@ class LamNI(TomoQueueMixin, LamNIAlignmentMixin, LamNIOpticsMixin, LamniGuiTools
|
||||
self._progress_proxy = _ProgressProxy(self.client)
|
||||
self._init_tomo_queue()
|
||||
|
||||
from csaxs_bec.bec_ipython_client.plugins.LamNI.LamNI_webpage_generator import (
|
||||
LamniWebpageGenerator,
|
||||
)
|
||||
|
||||
self._webpage_gen = LamniWebpageGenerator(
|
||||
bec_client=client,
|
||||
output_dir="~/data/raw/webpage/",
|
||||
upload_url="https://v1p0zyg2w9n2k9c1.myfritz.net/upload.php",
|
||||
local_port=8080,
|
||||
)
|
||||
self._webpage_gen.start()
|
||||
|
||||
def set_web_password(self, password: str) -> None:
|
||||
"""Set the web password for the current BEC account."""
|
||||
self._webpage_gen.set_web_password(password)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Special angles
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,10 +5,10 @@ which flomni features (GUI, status webpage, tomo-parameter widget, angle
|
||||
distribution) are worth porting to lamni. This is a comparison only — no
|
||||
migration had been implemented yet as of when it was originally written.
|
||||
|
||||
**Status update (2026-07-16): items 2–4 of §5's recommended order are now
|
||||
done.** Only **item 1, the webpage generator (§4), remains open** — the rest
|
||||
of this document (originally a forward-looking comparison/plan) is now a
|
||||
historical snapshot of the *starting* gap, not the current state. See:
|
||||
**Status update (2026-07-16): all of §5's recommended order (items 1–4) is
|
||||
now done**, including the webpage generator (§4) — the rest of this document
|
||||
(originally a forward-looking comparison/plan) is now a historical snapshot
|
||||
of the *starting* gap, not the current state. See:
|
||||
|
||||
- `csaxs_bec/bec_ipython_client/plugins/LamNI/AI_docs/TOMO_QUEUE_PORT.md` —
|
||||
tomo-queue/job-list backend ported to lamni via a shared `TomoQueueMixin`
|
||||
@@ -19,11 +19,27 @@ historical snapshot of the *starting* gap, not the current state. See:
|
||||
button), plus several real `lamni.py` bugs found and fixed along the way
|
||||
(two `sub_tomo_scan()` angle bugs, three `tomo_scan()`/`write_pdf_report()`
|
||||
crashes, progress-GUI-auto-show + stale-busy-heartbeat).
|
||||
- **Remaining**: `LamNI_webpage_generator.py` is still the stub described in
|
||||
§4 below — `_collect_setup_data()` commented out, `_TEMP_MAP` empty,
|
||||
`HAS_TOMO_QUEUE = False` (now incorrect — a queue backend exists, see
|
||||
above), `TOMO_TYPES` placeholders, and never wired into
|
||||
`lamni.py.__init__`. §4's 5-step plan is otherwise still accurate.
|
||||
- **Webpage generator (§4) — done.** The shared base
|
||||
(`WebpageGeneratorBase`, `HttpUploader`, `LocalHttpServer`,
|
||||
`make_webpage_generator()`, the HTML/JS template) was extracted out of
|
||||
`flomni/flomni_webpage_generator.py` into
|
||||
`OMNY_shared/webpage_generator_base.py`, mirroring the tomo-queue-mixin
|
||||
precedent — `flomni_webpage_generator.py` now only holds
|
||||
`FlomniWebpageGenerator`. `LamNI_webpage_generator.py`'s broken import
|
||||
(`flomni.webpage_generator`, a module that never existed) is fixed to
|
||||
import from the shared module. `TOMO_TYPES` confirmed identical to
|
||||
flomni's 3-mode dict by reading `lamni.py`'s actual `tomo_scan()`.
|
||||
`HAS_TOMO_QUEUE` is now `True` (the tomo-queue mixin backs it). The HTML/JS
|
||||
template's tomo-queue param-display table (`TQ_PARAM_DISPLAY`), previously
|
||||
hardcoded to flomni's param names, is now a per-subclass override the same
|
||||
way `TOMO_TYPES` already was, so lamni's page shows its own param names
|
||||
(`tomo_circfov`, `lamni_stitch_x/y`, ...). Sample name comes from lamni's
|
||||
`sample_name` global var (no automatic sample changer exists on lamni, so
|
||||
no device-backed lookup); temperatures stay empty (no lamni temp/humidity
|
||||
device configured yet). Wired into `LamNI.__init__` the same way
|
||||
`FlomniWebpageGenerator` is wired into `Flomni.__init__`. omny was left
|
||||
untouched (`omny_webpage_generator.py` remains dead/unwired duplicate
|
||||
code — separate future task if omny ever needs a real one).
|
||||
- Also still explicitly deferred (confirmed with Mirko, separate future
|
||||
tasks): `zero_deg_reference_at_each_subtomo` for lamni (not implemented at
|
||||
all yet), and sharing angle-calculation code between flomni/lamni
|
||||
|
||||
@@ -32,14 +32,6 @@ from csaxs_bec.bec_ipython_client.plugins.omny.omny_general_tools import (
|
||||
from csaxs_bec.devices.omny.galil.galil_ophyd import GalilError
|
||||
from csaxs_bec.devices.omny.sample_desc_codec import pack_desc, unpack_desc
|
||||
|
||||
# from csaxs_bec.bec_ipython_client.plugins.flomni.webpage_generator import (
|
||||
# FlomniWebpageGenerator,
|
||||
# VERBOSITY_SILENT, # 0 — no output
|
||||
# VERBOSITY_NORMAL, # 1 — startup / stop messages only (default)
|
||||
# VERBOSITY_VERBOSE, # 2 — one-line summary per cycle
|
||||
# VERBOSITY_DEBUG, # 3 — full JSON payload per cycle
|
||||
# )
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
if builtins.__dict__.get("bec") is not None:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user