Restore the ROI-to-mask action on the new list: "Add to mask" and
"Subtract from mask" buttons rasterise the selected ROI into the user
mask (set or clear), through the same UpdateUserMask_i path. The ROI is
evaluated with per-pixel resolution and phi from the geometry, so box,
circle and azimuthal (sector) ROIs all map correctly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
With the scratch ROI panel gone, delete the now-orphaned
JFJochViewerImageROIStatistics(_Box/_Circle) widgets and the worker's
unused scratch-ROI plumbing (SetROIBox/SetROICircle, the ROIElement
member, AddROIToUserMask/SubtractROIFromUserMask). ROI-to-mask, if wanted
later, should act on the selected list ROI.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Clarify in FPGA_DATA_ANALYSIS.md that the ROI and azimuthal definitions
are also evaluated on CPU via the shared image_analysis library, so they
apply to the DECTRIS SIMPLON (EIGER) path that has no FPGA, not only the
FPGA-accelerated JUNGFRAU/PSI path. Document the three NXmx writer formats
(Legacy/VDS/Integrated) and the two acquisition workflows in CLAUDE.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two Mask-menu entries read a 16-bit TIFF (via ReadTIFFFromString16) and
apply it as the user mask: "replace" overwrites the current mask, "add"
unions the TIFF into it. The TIFF must match the detector dimensions; any
non-zero pixel masks. Both go through UpdateUserMask_i like the other
mask edits.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add Download and "Upload to server" buttons to the ROI panel. The HTTP
reader gains GetROIDefinitions (GET /config/roi) and UploadROIDefinitions
(PUT /config/roi), converting between ROIDefinition and the generated
Roi_definitions model (including the optional azimuthal phi sector), the
same shapes OpenAPIConvert uses on the server. Download applies the
fetched ROIs through SetROIDefinition; upload pushes the current ones.
Both are no-ops unless the viewer is connected to a broker.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The interactive shift-drag (box) / shift+ctrl-drag (circle) now creates a
new persistent ROI in the list instead of feeding the old single-ROI
scratch panel. The base emits a roiScratchDrawn hook on release; the
diffraction image turns the drawn shape into a named ROI committed via
SetROIDefinition.
The old JFJochViewerImageROIStatistics scratch panel and all its wiring
(box/circle configuration, single-ROI result, add/subtract user mask) are
removed from the side panel and window; the ROI list is now the single
source.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The ROI panel now shows editable numeric fields for the selected ROI,
labelled by type: box min/max X/Y, circle centre/radius, or azimuthal
Q-range and phi sector. Editing a field rebuilds that ROI and commits it
through the same SetROIDefinition path, so all three types can be created
(via +) and dialled in by typing, not only by dragging.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Azimuthal ROIs now show discrete grab handles on the selected ROI: the
inner/outer arc (resize Q/d) and, for a sector, the two phi edges
(rotate). Editing is by clicking a handle, with a generous tolerance,
which fixes the previously near-impossible phi-edge grab. Clicking the
(large) interior of an azimuthal ROI now only selects it and lets the
view pan, instead of capturing the gesture.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Extend canvas ROI editing beyond move:
- Box: drag corners/edges to resize (handles shown on the selected ROI).
- Circle: drag the perimeter to resize, interior to move.
- Azimuthal: drag the inner/outer arc to change the Q/d range, or a radial
edge to rotate a phi bound; sampled through the geometry so it tracks
the conic.
- Delete removes the selected ROI (the view now takes keyboard focus).
All edits go through the same live, throttled SetROIDefinition path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Clicking a box/circle on the image now selects it (syncing the side-panel
combobox) and grabs it for moving, with a closed-hand cursor during the
drag. The move recomputes ROI statistics live rather than only on release,
throttled to at most one in-flight recompute so the worker is not flooded.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add base-class mouse hooks (roiEditPress/Move/Release) so the diffraction
image can edit the persistent ROI selected in the side-panel combobox.
The selected ROI is highlighted (dashed, thicker), and dragging its
interior moves a box or circle; on release the new geometry is committed
through SetROIDefinition, which recomputes only the ROIs. Resize,
azimuthal handles and delete-key follow.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Edited ROIs now override the file's for every subsequently loaded image
and survive settings changes, until a new file is opened (previously a
delete/add was lost on the next image). Tracked via an roi_override_ that
is re-applied to curr_experiment and to each loaded image's dataset.
- The ROI selector is a compact combobox instead of a list widget.
- "Show labels" and "Translucent fill" share one row to save space.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add JFJochViewerROIList to the side panel: a list of the dataset's ROIs
with selection, add (box/circle/azimuthal), rename and delete, plus a
statistics readout (sum/mean/std/max/valid/masked/centre-of-mass) for the
selected ROI, taken from the analysis output for the current image. Edits
emit a full ROIDefinition, routed to the worker's SetROIDefinition.
Per-ROI statistics now live in this panel rather than the canvas labels;
the diffraction image's labels show only the ROI name, and the ad-hoc
ROIIntegrationCPU computation there is removed in favour of the analysis
pipeline. The result widget now reports std dev instead of variance.
The single-ROI scratch panel remains for now and will be retired once the
interactive canvas editing replaces it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The reading worker's experiment is the source of truth for ROIs. Add a
SetROIDefinition slot that updates curr_experiment.ROI(), rebuilds the
analysis ROI engine, mutates the dataset (so the canvas reflects the new
ROIs) and recomputes only the ROIs for the current image via RunROIOnly.
On image load when full re-analysis is off, ROIs are still computed via
AnalyzeROIOnly so the statistics stay current.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add three entry points so ROI statistics can be (re)computed without a
full re-analysis, in support of interactive ROI editing in the viewer:
- RebuildROI(): recreate the ROI engine after the ROI set changes (the
CudaStream is now kept as a member so the GPU engine can be rebuilt).
- AnalyzeROIOnly(): decompress + preprocess + ROI, skipping azimuthal
integration, spot finding and indexing (a new image when re-analysis
is off).
- RunROIOnly(): rerun only the ROI integration on the already-preprocessed
image (an interactive ROI move). A full Analyze() still computes ROIs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When ROI labels are shown, each ROI's label now also reports its sum,
max and pixel count for the current image. Rather than reimplementing the
accumulation, this reuses the existing ROIIntegrationCPU engine (the
software counterpart of the FPGA roi_calc), built from the experiment and
cached per dataset. A small adapter folds the viewer's gap sentinel
(INT32_MIN+1) onto the engine's masked sentinel (INT32_MIN) so masked and
saturated pixels are handled correctly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The full-ring annulus built the inner ring as a separate QPainterPath and
merged it with addPath, which stitched a spurious segment from (0,0) to
the ring. Build both concentric rings as subpaths of one path, each begun
with an explicit moveTo, so there is no connecting segment.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Azimuthal ROIs now render on the diffraction image as annular sectors
(or full-ring annuli), sampled through DiffractionGeometry::ResPhiToPxl
so the outline follows the ROI footprint, including wrap-around sectors.
Add two side-panel toggles (both default off): a translucent fill for
every ROI (helpful when outline colours clash with the image colour map,
and with many ROIs) and ROI name labels (constant on-screen size). Wired
side panel -> window -> diffraction image like the existing feature
toggles.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When a file with ROI definitions is opened (read into experiment.ROI()
by the HDF5 reader), the diffraction image now overlays the configured
box and circle ROIs as distinct colour-coded outlines, alongside the
existing resolution rings and spots. This is the first step of the
ROI-map-based multi-ROI canvas: showing the ROIs loaded from a file.
Azimuthal ROIs (wedge rendering) and per-ROI statistics from the bitmap
follow in subsequent steps.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Footer "Paul Scherrer Institute" now links to https://www.psi.ch
- Reference drawer gains a "Source code" entry linking out to the Gitea
repository (new nav-item href that opens in a new tab)
- Reference drawer gains a "License" entry rendering the nicely formatted
docs/LICENSE.md inline (generalises the changelog renderer into MarkdownDoc)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The framed Sphinx changelog reused MUI with the same colours, so it read as
a picture-in-picture. Bundle docs/CHANGELOG.md at build time (?raw) and render
it with react-markdown, themed to match the app, for a quick frame-less view.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Documentation tab pointed at jungfraujoch.readthedocs.io, which lags
behind the installed version. Build the Sphinx docs into frontend/dist/docs
and serve them at /frontend/docs so they always match the package.
- make_doc.sh: accept an output dir (default public), absolutize it, run
from the script dir, and allow $PYTHON override
- package.json: add "docs" script building into dist/docs
- CMakeLists: run "npm run docs" in the frontend target so CPack packs the
docs via the existing dist/ install
- App.tsx: point the Documentation DocFrame at /frontend/docs/index.html
Also clean up the Sphinx build (911 -> 0 warnings):
- fix typo myst_heading_anchor -> myst_heading_anchors so cross-doc
#anchor links actually resolve
- suppress myst.header/myst.xref_missing (auto-generated OpenAPI client
docs only) and the harmless sphinx_material config.cache note
- drop a dangling "---" transition at the end of CPU_DATA_ANALYSIS.md
- add REPOSITORIES and the generated python_client model pages to toctrees
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The phi bounds were stored as plain floats internally but passed through
std::optional in the constructor, which obscured the intent. Take them
as float arguments defaulting to 0 instead. The convention is explicit:
phi_min == phi_max means the full ring (all angles), a sector wraps
across 0 when phi_min > phi_max, and otherwise phi_min <= phi_max. The
"both bounds or none" rule belongs at the API/file boundary
(OpenAPIConvert, reader), not in the core class, so it lives there now.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
In VDS mode the per-image ROI results (max/sum/sum_sq/npixel/x/y) are
written into the data files but were not exposed in the master, so a VDS
master surfaced no ROI statistics. Add virtual datasets under
/entry/roi/<name> in LinkToData_VDS, one group per ROI, mirroring how the
spot-finding and azimuthal-integration arrays are linked. Integrated and
legacy formats are unaffected (the results are already reachable there).
Extended the reader round-trip test to write real ROI results and check
they read back from the master for both VDS and integrated formats.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Relocate the ROI definitions and the roi_map bitmap from /entry/roi to a
separate /entry/roi_defs group in the master file. /entry/roi keeps its
original meaning (per-image ROI results only), so an older reader that
iterates /entry/roi is not disturbed by the bitmap/definition entries
(which would otherwise make it try to read /entry/roi/roi_map/max and
fail to open the file at all). Back-compatibility for already-deployed
viewers is the reason for the split.
JFJochHDF5Reader now reads the logical definitions into experiment.ROI()
and the roi_map bitmask (+ a name->bit index) into the dataset, for all
file formats (the master always carries them). Reading both lets a later
viewer either re-derive ROIs with the current geometry (logical) or show
the exact written footprint (bitmap).
Added a write/read round-trip test over VDS and integrated formats.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
AddROI now renders azimuthal ROIs in addition to box and circle ROIs:
inner/outer arcs at the Q (d) bounds and, for a sector, the two radial
edges at the phi bounds. The per-pixel phi comes from the same
DiffractionGeometry::Phi_rad used to assign ROIs, so the overlay matches
the ROI footprint exactly (including wrap-around sectors). Full-ring
azimuthal ROIs draw as two concentric arcs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Persist regions of interest so an acquisition's ROI layout can be
recovered from the master (_master.h5 / _process.h5):
- /entry/roi/roi_map: uint16 per-pixel bitmask (bit i == ROI i).
- /entry/roi/<name>: logical definition (type + geometry params, incl.
azimuthal phi sector) plus bit_index tying it to the bitmap plane.
The bitmap rides along in the StartMessage (CBOR, mirroring az_int_map)
and is filled both online (JFJochReceiver::SendStartMessage) and offline
(jfjoch_process). Definitions come from the already-transmitted rois.
Both are dataset-wide metadata, so they are written by the NXmx master
writer (new NXmx::ROI), not the per-image data-file plugin.
Documented the /entry/roi layout in docs/HDF5.md and the master-vs-data
writer convention in CLAUDE.md; added a CBOR roi_map round-trip test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Expose the optional phi_min_deg / phi_max_deg azimuthal-sector bounds
added to ROIAzimuthal through the wire formats. Both are optional, so
older clients and streams (Q-only ROIs) remain valid.
- jfjoch_api.yaml: optional phi_min_deg / phi_max_deg on roi_azimuthal;
regenerated the cpp-pistache server model and the TS frontend client.
- OpenAPIConvert: map phi both directions (set only when present).
- CBOR start message: emit phi_min/phi_max only for a sector; on decode,
absent phi means a full ring.
- Documented the new optional CBOR fields; extended the CBOR round-trip
test with a sector and a full-ring azimuthal ROI.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ROIAzimuthal can now be restricted to an azimuthal-angle sector in
addition to its Q/d range, enabling STXM-style directional ROIs. phi
bounds are optional (both-or-neither); absent means a full 360 ring, so
existing Q-only azimuthal ROIs are unchanged.
- ROIElement::CheckROI gains a phi_deg argument; box/circle ignore it.
- MarkROI gains an optional phi_map; ROIMap builds it from Phi_rad
alongside the resolution map only when azimuthal ROIs are present.
- phi bounds are normalized to [0,360) and wrap-around sectors are
supported (phi_min > phi_max).
- ROIConfigAzim carries phi_min/phi_max (kept trivially copyable for the
union; phi_min == phi_max means full ring).
No phi crosses the wire yet (CBOR/API wiring follows separately).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
DetectorSelection rendered the info-table rows and the detector-select
menu items without keys; ROI's grid action cells likewise. Add stable
keys to silence the warnings.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the stack of toggle-revealed sections with a left navigation
drawer (collapsible, expanded by default) clipped under a persistent
status bar. Dashboard (plots + measurement statistics + spot-finding)
is the default view; every other panel is its own drawer entry, so one
thing shows at a time.
- Group entries under Acquisition / Expert / Reference subheaders;
ROI folded into On-the-fly processing.
- Pale-indigo drawer with a lime accent on the selected entry.
- Gray out JUNGFRAU calibration / FPGA status when their data is empty.
- API reference and documentation open in-app via iframe.
- Fixed footer pinned to the page bottom.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Generate Zod schemas alongside the typed client (src/client/zod.gen.ts),
picking up the OpenAPI field defaults. Enables runtime validation and
default-filling for form payloads.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Match the unsaved-changes dot: the Upload button uses the warning color
while the panel has unedited changes, primary otherwise.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Generalize the expert-panel boilerplate (container + title + widgets +
upload button) and surface real upload errors via hey-api.
- SettingsPanel: consistent padded Paper + Typography title (one place to
size it) + optional Upload button + result snackbar; PanelTitle and
UploadSnackbarView exported for panels with bespoke layouts (DetectorSettings
grid, ROI)
- useUpload(mutation): wraps a generated hey-api mutation, shows the server's
error message on failure (errorMessage handles 400 string / 500 error_message)
- "unsaved changes" dot on panel titles, derived from the existing
last-downloaded snapshot (dirty = !isEqual(value, lastDownloaded))
- fix PixelMask: TIFF upload was swallowing errors; now shows them
- config panels (FileWriter, ImageFormat, Detector, AzInt, DarkMask, Indexing,
Instrument, ZeroMQ, ROI, DetectorSelection) upload through the typed SDK
instead of ButtonWithSnackbar's raw fetch; status/display panels share the
same shell for consistent spacing
- ButtonWithSnackbar now only powers bodyless action buttons (pedestal/cancel/
initialize, start/trigger, deactivate)
Build (tsc + vite) passes; dev server transforms all modules.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace all class components with function components and move server-state
polling to @tanstack/react-query.
- add @tanstack/react-query; generate query helpers via the hey-api
@tanstack/react-query plugin (src/client/@tanstack)
- QueryClientProvider + one-time client.setConfig({ baseUrl: '' }) in index.tsx
- App polls statistics with useQuery(getStatisticsOptions, refetchInterval),
DataProcessingPlot uses getPreviewPlotOptions; manual setInterval polling gone
- memo() on presentational children; with TanStack structural sharing this
re-renders a child only when its own statistics slice changes
- PreviewImage stays imperative (binary JPEG -> object URL) using useEffect +
a ref for the object-URL lifecycle
- fix AzIntSettings correction checkboxes that mutated state in place (relied on
the poll re-render); they now use setState
- drop dead code uncovered during the port (unused upload/deactivate handlers
and imports in ImageFormatSettings, DetectorSettings, ROI)
Build (tsc + vite) passes; dev server transforms all entry modules.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The raw stacked-module layout check compared the image width (cols) against
RAW_MODULE_SIZE (the per-module pixel count, 524288) instead of
RAW_MODULE_COLS (1024), so a raw-geometry TIFF could never be accepted.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PUT /config/user_mask.tiff only accepted 32-bit unsigned TIFF, so masks
exported by tools like PyFAI (8-bit) failed. Route the upload through the
universal ReadTIFF reader and let PixelMask take a CompressedImage directly:
it validates the 2D shape against the detector's converted/raw layouts,
binarizes any 8/16/32-bit integer image (non-zero == masked), and rejects
float/multi-channel images.
Also dedupe the TIFF readers: ReadTIFFFromString16 is now a thin wrapper over
ReadTIFF, and the now-unused ReadTIFFFromString32 is removed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Nothing in HDF5DataFile.cpp uses sys/stat.h (no stat/mkdir/S_* etc.); it relies
on <filesystem>. Removing the dead POSIX include also avoids a needless
non-portable header for the eventual MSVC build.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
AppendImage wrote the CBOR byte-string length (major type 2, 8-byte length,
which is defined big-endian) via __builtin_bswap64 under #ifdef LITTLE_ENDIAN.
That is broken two ways off GCC/Clang+glibc: __builtin_bswap64 is not available
on MSVC, and LITTLE_ENDIAN is not defined there, so the #else path skipped the
swap and emitted a little-endian length on a little-endian host -> corrupt
frames.
Write the 8 length bytes most-significant-first directly. This is big-endian by
construction on any host, needs no byte-swap intrinsic and no endianness macro,
and is byte-for-byte identical to the previous output on little-endian hosts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
M_PI is a POSIX <math.h> extension that MSVC does not define without
_USE_MATH_DEFINES. std::numbers::pi (introduced in the viewer guard commit) is
C++20, but CUDA here is compiled as C++17 (CMAKE_CUDA_STANDARD 17) and several
common/ headers are pulled into .cu device translation units, so std::numbers is
not available there.
Add common/JFJochMath.h with a dependency-free `constexpr double PI` that works
in host code (including MSVC), in CUDA device code, and under C++17/20, and use
it everywhere:
- common/ and image_analysis/ (incl. CUDA .cu): 78 M_PI occurrences, 22 files
- broker/OpenAPIConvert.cpp
- viewer/: the 5 files that used std::numbers::pi now use PI, for one consistent
convention across the codebase
Verified to build: JFJochImageAnalysis (incl. CUDA), jfjoch_viewer, JFJochBroker.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>