Files
Jungfraujoch/reader/JFJochReader.cpp
T
leonarski_fandClaude Opus 5 d74ff7fc29 Viewer: the settings panel says how the sample moved, and how polarized the beam was
Two experiment properties the panel could not state, both of which the analysis it
drives has an opinion about anyway.

The polarization factor is one number that corrects both communities' output - the
azimuthal profile through AzimuthalIntegrationMapping and the integrated intensities
through BraggIntegrationEngine - so it goes in the Geometry section, which the MX and
AzInt pages already share. Files carry no polarization factor at all, so before this
the interactive analysis integrated with none while "Analyze dataset" applied 0.99
from the rugnux defaults: the panel showed nothing and the two front ends disagreed.
The viewer's starting experiment now takes the same rugnux defaults, so the panel
shows what the analysis actually uses, and a processing job takes the panel's value
over the default - as it already did for the scaling fields.

The Goniometer section states which of the three things a dataset is - a still, a
rotation, or a grid scan - which is exactly the choice a file makes at
/entry/sample/transformations/omega vs /entry/sample/grid_scan vs neither. That
choice IS the rotation/stills switch, so "Process as stills" is gone from the
Indexing section rather than sitting beside it as a second control. It is not tied
to what the file says: a still file can be given an axis or a grid, and a rotation
file can be processed as stills. The inactive modes grey out but keep their values,
so switching away and back does not lose an axis; a file that names none offers
omega / -1 0 0 / 0.1 deg and a 10-point, 20 um raster.

Only the fast axis of a grid gets a count field, because that is all there is:
GridScanSettings derives the slow one from the image count, and the file stores
n_fast alone. The grid the settings make is spelled out under them instead. The
steps are signed - the sign is the direction the scan runs in - so only zero is
rejected, and a half-typed entry falls back to the default rather than throwing out
of a widget signal, which would abort the viewer.

JFJochReader::UpdateGeomMetadata carries a fixed whitelist of the fields a panel edit
may change, and it grew by three. Without them the new controls reset themselves on
the next dataset refresh, since a non-whitelisted field comes back as the file's.
The function has two call sites, both in the viewer's reading worker, and it is not
virtual: objdump on the rugnux binary shows the code linked in and never called, so
offline processing is untouched.

RugnuxCommandLine emits --force-still from the axis the experiment carries, which the
panel now clears when the mode is not Rotation - so the worker remembers the axis the
file was opened with, and the copied command line is built with that put back. rugnux
has no flag for the axis itself, so an axis edited or invented in the panel still
cannot be expressed on a command line; the in-process run gets the experiment object
and is unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 05:13:57 +02:00

134 lines
5.5 KiB
C++

// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include "JFJochReader.h"
#include <future>
JFJochReader &JFJochReader::Experiment(const DiffractionExperiment &experiment) {
std::unique_lock ul(m);
default_experiment = experiment;
return *this;
}
void JFJochReader::SummationThread(int64_t image0, int64_t n_image, int64_t image_jump, JFJochReaderImage &image) {
std::vector<uint8_t> buffer;
DataMessage msg;
for (int64_t i = image0; i < n_image; i += image_jump) {
bool ret = LoadImage_i(dataset, msg, buffer, i, false);
if (ret) {
auto image_sum = std::make_shared<JFJochReaderImage>(msg, dataset);
{
std::unique_lock ul(summation_mutex);
image.AddImage(*image_sum);
}
}
}
}
std::shared_ptr<JFJochReaderImage> JFJochReader::LoadImage(int64_t image_number, int64_t summation_factor) {
// It would be a mess to load two images at the same time
// so loading is protected via mutex
// yet copying share_ptr pointer is atomic and needs no mutex protection
std::unique_lock ul(m);
std::vector<uint8_t> buffer;
DataMessage msg;
if (LoadImage_i(dataset, msg, buffer, image_number, true)) {
auto image = std::make_shared<JFJochReaderImage>(msg, dataset);
if (summation_factor > 4) {
int64_t nthread = std::min<int64_t>(summation_factor - 1, 8);
std::vector<std::future<void>> futures;
for (int i = 0; i < nthread; i++)
futures.emplace_back(std::async(std::launch::async,
&JFJochReader::SummationThread, this,
image_number + 1 + i,
image_number + summation_factor,
nthread,
std::ref(*image)));
for (auto &f: futures)
f.get();
} else if (summation_factor > 1) {
SummationThread(image_number + 1, image_number + summation_factor, 1, *image);
}
return image;
}
return {};
}
void JFJochReader::SetStartMessage(const std::shared_ptr<JFJochReaderDataset> &val) {
std::unique_lock ul(m);
dataset = val;
}
std::shared_ptr<const JFJochReaderDataset> JFJochReader::GetDataset() const {
std::unique_lock ul(m);
return dataset;
}
void JFJochReader::UpdateGeomMetadata(const DiffractionExperiment &experiment) {
std::unique_lock ul(m);
if (!dataset)
return;
auto new_dataset = std::make_shared<JFJochReaderDataset>(*dataset);
// At the moment subset of options is limited to safe ones...need to change it in the future
new_dataset->experiment.BeamX_pxl(experiment.GetBeamX_pxl());
new_dataset->experiment.BeamY_pxl(experiment.GetBeamY_pxl());
new_dataset->experiment.DetectorDistance_mm(experiment.GetDetectorDistance_mm());
new_dataset->experiment.IncidentEnergy_keV(experiment.GetIncidentEnergy_keV());
new_dataset->experiment.PoniRot1_rad(experiment.GetDatasetSettings().GetPoniRot1_rad());
new_dataset->experiment.PoniRot2_rad(experiment.GetDatasetSettings().GetPoniRot2_rad());
new_dataset->experiment.PoniRot3_rad(experiment.GetDatasetSettings().GetPoniRot3_rad());
new_dataset->experiment.SetUnitCell(experiment.GetUnitCell());
new_dataset->experiment.SpaceGroupNumber(experiment.GetSpaceGroupNumber());
new_dataset->experiment.PolarizationFactor(experiment.GetPolarizationFactor());
new_dataset->experiment.Goniometer(experiment.GetGoniometer());
new_dataset->experiment.GridScan(experiment.GetGridScan());
new_dataset->experiment.ImportIndexingSettings(experiment.GetIndexingSettings());
new_dataset->experiment.ImportBraggIntegrationSettings(experiment.GetBraggIntegrationSettings());
new_dataset->experiment.DetectIceRings(experiment.IsDetectIceRings());
dataset = new_dataset;
}
void JFJochReader::UpdateUserMask(const std::vector<uint32_t> &mask) {
std::unique_lock ul(m);
if (!dataset)
return;
auto new_dataset = std::make_shared<JFJochReaderDataset>(*dataset);
// Copy-on-write: the mask is shared with the old snapshot, so edit a fresh copy, not in place.
auto new_mask = std::make_shared<PixelMask>(*dataset->pixel_mask);
new_mask->LoadUserMask(dataset->experiment, mask);
new_dataset->pixel_mask = new_mask;
dataset = new_dataset;
}
std::shared_ptr<JFJochReaderSpots> JFJochReader::ReadAllSpots(int64_t start_image, int64_t end_image,
int64_t stride) const {
if (start_image < 0)
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
"Start image must be non-negative");
if (start_image > end_image)
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
"Start image number is greater than end image number");
if (stride == 0)
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
"Stride cannot be zero");
size_t nelems = (end_image - start_image) / stride + 1;
auto ret = std::make_shared<JFJochReaderSpots>();
ret->start_image = static_cast<int64_t>(start_image);
ret->stride = static_cast<int64_t>(stride);
ret->spots.reserve(nelems);
for (int i = 0; i < nelems; i++)
ret->spots.emplace_back(ReadSpots(start_image + i * stride));
return ret;
}