Files
Jungfraujoch/reader/JFJochStartMessageReader.cpp
leonarski_f 3f8c72cd1e
Build Packages / build:portable:windows-x86_64:cuda (push) Successful in 28m9s
Build Packages / Generate python client (push) Successful in 31s
Build Packages / Validation Battery (push) Successful in 47m56s
Build Packages / build:jfjoch:ubuntu2404:nocuda (push) Successful in 26m51s
Build Packages / build:rugnux:linux-aarch64:cuda (push) Successful in 10m33s
Build Packages / build:jfjoch:ubuntu2204:nocuda (push) Successful in 26m49s
Build Packages / build:jfjoch:rocky8:cuda (push) Successful in 30m10s
Build Packages / build:jfjoch:rocky8:nocuda (push) Successful in 32m0s
Build Packages / build:jfjoch:rocky9:nocuda (push) Successful in 32m49s
Build Packages / Create release (push) Successful in 8s
Build Packages / build:portable:linux-x86_64:nocuda (push) Successful in 16m57s
Build Packages / Build documentation (push) Successful in 47s
Build Packages / build:portable:macos-arm64:nocuda (push) Successful in 5m7s
Build Packages / build:jfjoch:rocky8:cuda-sls9 (push) Successful in 29m33s
Build Packages / build:portable:linux-x86_64:cuda (push) Successful in 21m36s
Build Packages / build:portable:windows-x86_64:nocuda (push) Successful in 24m26s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 30m56s
Build Packages / build:jfjoch:rocky9:cuda-sls9 (push) Successful in 32m3s
Build Packages / build:jfjoch:rocky9:cuda (push) Successful in 30m50s
Build Packages / build:jfjoch:ubuntu2404:cuda (push) Successful in 28m52s
Build Packages / build:jfjoch:ubuntu2204:cuda (push) Successful in 25m22s
Build Packages / Unit tests (push) Successful in 1h44m43s
v1.0.0-rc.182 (#93)
* Rugnux: Fixed indexing of stills of a centred lattice with a known cell.
* jfjoch_broker (experimental): Live Rugnux processing of serial collections.
* Rugnux: Stills detector modules are placed from the data and written to `<prefix>_module_placement.json`; `--panel-refine` replaces `--refine-modules`.
* Rugnux: More reliable rotation processing - indexing, monoclinic setting, space-group choice and refinement of multi-domain twins.

Reviewed-on: #93
Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
2026-11-04 11:13:33 +01:00

202 lines
8.2 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);
// The modules one by one, where the start message states them (a JUNGFRAU's, as imaged).
if (!start.detector_modules.empty())
x.Modules(DetectorModules(start.detector_modules));
x.SetUnitCell(start.unit_cell);
if (start.space_group_number)
x.SpaceGroupNumber(static_cast<int64_t>(*start.space_group_number));
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;
}