Files
Jungfraujoch/reader/JFJochStartMessageReader.cpp
T
leonarski_fandClaude Opus 5.5 3a44779d79 broker: rugnux live - rugnux runs inside the broker while a DECTRIS rotation collection arrives (demo prototype)
A DECTRIS rotation collection, with the mode switched on (PUT /config/rugnux), goes to
JFJochReceiverRugnux instead of JFJochReceiverLite: the images are forwarded as before, without the
per-image analysis, and one rugnux thread reads them back from the image buffer
(JFJochImageBufferReader) as they arrive. END waits for rugnux and carries its lattice, refined
geometry and per-image results. A small second run on the first 60 degrees reports early whether
there is a lattice. The live state, steps and result are at GET /rugnux/status and on a new
"Rugnux live" frontend page.

rugnux: ProcessConfig::fast_live takes every decision where it is first made and does nothing again
once the sweep is in (near-tie re-checks and the run made again, rotation-scale walk, metric-symmetry
arms, geometry walk, the supercell and quality guards, the doubled cell), so the run ends a fixed
time after the last image. A cut (/cancel, or deadline_s after the last image) hands over what is
established. Off by default: the CLI and the battery are unchanged (--fast-live is hidden, with
--simulate-live).

With the mode off the broker is unchanged. Not built yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 12:20:38 +02:00

196 lines
7.9 KiB
C++

// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include "JFJochStartMessageReader.h"
#include <filesystem>
#include "spdlog/fmt/fmt.h"
#include "../common/JFJochException.h"
namespace {
// The data file name the writer gives file `file_number` (HDF5Metadata::DataFileName), without
// the directory: the data files sit next to the master file.
std::string DataFileName(const StartMessage &start, uint64_t file_number) {
const std::string prefix = std::filesystem::path(start.file_prefix).filename().string();
return fmt::format("{:s}_data_{:06d}.h5", prefix, file_number + 1);
}
}
std::shared_ptr<JFJochReaderDataset> DatasetFromStartMessage(const StartMessage &start,
const DiffractionExperiment &defaults) {
auto dataset = std::make_shared<JFJochReaderDataset>();
dataset->arm_date = start.arm_date;
dataset->jfjoch_release = start.jfjoch_release;
dataset->error_value = start.error_value;
dataset->file_detect_ice_rings = start.detect_ice_rings;
auto &x = dataset->experiment;
x = defaults;
// The reader hands every image out as signed 32-bit, as JFJochHDF5Reader does.
x.BitDepthImage(32);
x.PixelSigned(true);
x.FilePrefix(std::filesystem::path(start.file_prefix).filename().string());
x.BeamX_pxl(start.beam_center_x);
x.BeamY_pxl(start.beam_center_y);
x.DetectorDistance_mm(start.detector_distance * 1000.0f);
x.PoniRot1_rad(start.poni_rot1.value_or(0.0f));
x.PoniRot2_rad(start.poni_rot2.value_or(0.0f));
x.PoniRot3_rad(start.poni_rot3.value_or(0.0f));
x.IncidentEnergy_keV(WVL_1A_IN_KEV / start.incident_wavelength);
if (start.incident_wavelength_spread)
x.BandwidthFWHM(start.incident_wavelength_spread.value() / start.incident_wavelength);
x.DetectIceRings(start.detect_ice_rings.value_or(false));
DetectorSetup detector = DetDECTRIS(start.image_size_x, start.image_size_y, start.detector_description, {});
detector.PixelSize_um(start.pixel_size_x * 1e6f);
detector.MirrorY(start.mirror_y);
detector.ImageOrientation(DetectorOrientation(start.detector_orientation_mirror_y,
start.detector_orientation_quarter_turns));
detector.SensorThickness_um(start.sensor_thickness * 1e6f);
detector.SensorMaterial(start.sensor_material);
detector.SaturationLimit(SaturationLimitFromValue(start.saturation_value));
detector.BitDepthImage(32);
detector.MinFrameTime(std::chrono::microseconds(0));
detector.MinCountTime(std::chrono::microseconds(0));
detector.ReadOutTime(std::chrono::nanoseconds(0));
x.Detector(detector);
x.FrameTime(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::duration<float>(start.frame_time)),
std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::duration<float>(start.count_time)));
x.Goniometer(start.goniometer);
x.GridScan(start.grid_scan);
x.Smargon(start.smargon_position);
x.NumTriggers(1);
x.ImagesPerTrigger(static_cast<int64_t>(start.number_of_images));
InstrumentMetadata instrument;
instrument.SourceName(start.source_name);
instrument.SourceType(start.source_type);
instrument.InstrumentName(start.instrument_name);
x.ImportInstrumentMetadata(instrument);
x.SampleName(start.sample_name);
x.SampleTemperature_K(start.sample_temperature_K);
x.RingCurrent_mA(start.ring_current_mA);
x.TotalFlux(start.total_flux);
x.AttenuatorTransmission(start.attenuator_transmission);
if (start.beam_size_x && start.beam_size_y) {
x.BeamSizeX_um(start.beam_size_x.value() * 1e6f);
x.BeamSizeY_um(start.beam_size_y.value() * 1e6f);
}
if (!start.pixel_mask.empty())
dataset->pixel_mask = std::make_shared<const PixelMask>(start.pixel_mask.begin()->second);
else
dataset->pixel_mask = std::make_shared<const PixelMask>(
std::vector<uint32_t>(start.image_size_x * start.image_size_y));
return dataset;
}
void JFJochStartMessageReader::Open(const StartMessage &start, const std::string &master_path) {
if (start.images_per_file <= 0)
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
"The start message gives no images per file");
number_of_images_ = start.number_of_images;
images_per_file_ = static_cast<uint64_t>(start.images_per_file);
const uint64_t files = (number_of_images_ + images_per_file_ - 1) / images_per_file_;
const std::filesystem::path directory = std::filesystem::path(master_path).parent_path();
HDF5ImageLocator::Layout layout;
layout.format = FileWriterFormat::NXmxLegacy;
layout.images_per_file = images_per_file_;
for (uint64_t f = 0; f < files; f++)
layout.legacy_files.push_back({(directory / DataFileName(start, f)).string(), "/entry/data/data"});
{
std::unique_lock ul(hdf5_mutex);
image_source_.Configure(std::move(layout));
}
{
std::unique_lock ul(m_);
file_images_.assign(files, -1);
ended_ = false;
lost_.clear();
}
SetStartMessage(DatasetFromStartMessage(start, default_experiment));
}
void JFJochStartMessageReader::FileClosed(uint64_t file_number, uint64_t image_count) {
{
std::unique_lock ul(m_);
if (file_number < file_images_.size())
file_images_[file_number] = static_cast<int64_t>(image_count);
}
cv_.notify_all();
}
void JFJochStartMessageReader::Ended() {
{
std::unique_lock ul(m_);
ended_ = true;
}
cv_.notify_all();
}
void JFJochStartMessageReader::Lost(const std::string &why) {
{
std::unique_lock ul(m_);
if (!ended_)
lost_ = why;
}
cv_.notify_all();
}
uint64_t JFJochStartMessageReader::GetNumberOfImages() const {
return number_of_images_;
}
void JFJochStartMessageReader::Close() {
std::unique_lock ul(hdf5_mutex);
image_source_.Clear();
SetStartMessage({});
}
bool JFJochStartMessageReader::WaitForImage(int64_t image_number) {
if (image_number < 0 || static_cast<uint64_t>(image_number) >= number_of_images_)
throw JFJochException(JFJochExceptionCategory::HDF5, "Image out of bounds");
const uint64_t f = static_cast<uint64_t>(image_number) / images_per_file_;
std::unique_lock ul(m_);
cv_.wait(ul, [&] { return file_images_[f] >= 0 || ended_ || !lost_.empty(); });
if (file_images_[f] < 0) {
if (!lost_.empty())
throw LiveCollectionLost(lost_);
return false; // the collection ended without this file
}
return static_cast<int64_t>(static_cast<uint64_t>(image_number) % images_per_file_) < file_images_[f];
}
bool JFJochStartMessageReader::ReadRawImage(int64_t image_number, JFJochReaderRawImage &ret) {
if (!WaitForImage(image_number))
return false;
// As JFJochHDF5Reader: only the chunk lookup under the HDF5 lock, the read after it.
std::optional<HDF5ImageSource::DirectChunk> chunk;
{
std::unique_lock ul(hdf5_mutex);
const auto loc = image_source_.Resolve(image_number);
chunk = image_source_.PrepareDirectRead(loc);
if (!chunk) {
ret.image = image_source_.ReadImageAt(ret.image_buffer, loc);
return true;
}
}
ret.image = HDF5ImageSource::ReadDirect(ret.image_buffer, *chunk);
return true;
}
bool JFJochStartMessageReader::LoadImage_i(std::shared_ptr<JFJochReaderDataset> &dataset, DataMessage &message,
std::vector<uint8_t> &buffer, int64_t image_number, bool) {
if (!dataset || !WaitForImage(image_number))
return false;
std::unique_lock ul(hdf5_mutex);
message.image = image_source_.ReadImageAt(buffer, image_source_.Resolve(image_number));
message.number = image_number;
return true;
}