Receivers: mechanical cleanups before the stream parent

LiveModel converts a result to a status model and is called from the stills analysis too, so it goes to
RugnuxLiveStatus. SetSpotFindingSettings has one body, in the base; the FPGA receiver keeps its override,
which adds the push to the cards. SecondsSince is written once, in time_utc.h.

JFJochReceiverPedestal loses what it copied from the FPGA receiver and does not use: the azimuthal bin
count check (a pedestal run integrates nothing, so such a configuration is no longer refused here), the
per-module ADU histograms that were created and never fed, and the spot finder parameters pushed to the
cards. Whether the card needs those before a pedestal run is inferred from the FPGA puller, which arms
without them; it is tested on the simulator only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HVeDQsU5a8AJUdJPgWevAK
This commit is contained in:
leonarski_fandClaude Opus 5.5 committed 2026-11-07 18:27:23 +01:00
1 parent 4c3a7852e9
commit 308b7dfb2a
15 files changed
+113 -138

No files matched your search

+4
View File
@@ -12,6 +12,10 @@
#include <ctime>
#include <cstdio>
inline double SecondsSince(std::chrono::steady_clock::time_point t0) {
return std::chrono::duration<double>(std::chrono::steady_clock::now() - t0).count();
}
inline std::string time_UTC(const std::chrono::time_point<std::chrono::system_clock> &input) {
auto time_ms = std::chrono::duration_cast<std::chrono::milliseconds>(input.time_since_epoch()).count();
+6
View File
@@ -61,6 +61,12 @@ SpotFindingSettings JFJochReceiver::GetSpotFindingSettings() {
return spot_finding_settings;
}
void JFJochReceiver::SetSpotFindingSettings(const SpotFindingSettings &in_spot_finding_settings) {
std::unique_lock ul(spot_finding_settings_mutex);
DiffractionExperiment::CheckDataProcessingSettings(in_spot_finding_settings);
spot_finding_settings = in_spot_finding_settings;
}
void JFJochReceiver::UpdateMaxImageSent(int64_t image_number) {
std::unique_lock ul(max_image_number_sent_mutex);
if (image_number + 1 > max_image_number_sent)
+1 -1
View File
@@ -125,7 +125,7 @@ public:
virtual void Cancel(bool silent);
virtual float GetProgress() const = 0;
virtual void SetSpotFindingSettings(const SpotFindingSettings &spot_finding_settings) = 0;
virtual void SetSpotFindingSettings(const SpotFindingSettings &spot_finding_settings);
virtual JFJochReceiverOutput GetFinalStatistics() const;
};
-6
View File
@@ -463,12 +463,6 @@ float JFJochReceiverLite::GetProgress() const {
return static_cast<float>(max_image_number_received) / static_cast<float>(ExpectedImages());
}
void JFJochReceiverLite::SetSpotFindingSettings(const SpotFindingSettings &in_spot_finding_settings) {
std::unique_lock ul(spot_finding_settings_mutex);
DiffractionExperiment::CheckDataProcessingSettings(in_spot_finding_settings);
spot_finding_settings = in_spot_finding_settings;
}
JFJochReceiverOutput JFJochReceiverLite::GetFinalStatistics() const {
JFJochReceiverOutput ret = JFJochReceiver::GetFinalStatistics();
if (experiment.GetDetectorMode() == DetectorMode::DarkMask)
-1
View File
@@ -92,7 +92,6 @@ public:
float GetEfficiency() const override;
float GetProgress() const override;
void SetSpotFindingSettings(const SpotFindingSettings &spot_finding_settings) override;
JFJochReceiverOutput GetFinalStatistics() const override;
};
-21
View File
@@ -30,19 +30,8 @@ JFJochReceiverPedestal::JFJochReceiverPedestal(const DiffractionExperiment &in_e
ndatastreams(experiment.GetDataStreamsNum()),
pedestal_nthreads((experiment.GetStorageCellNumber() > 2) ? 1 : 4),
data_acquisition_ready(ndatastreams) {
// The same limit as for an image run, so a configuration is refused the same way in both.
const auto &azint_settings = experiment.GetAzimuthalIntegrationSettings();
if (!azint_settings.IsForceCPUinFPGAWorkflow() && azint_settings.GetBinCount() > FPGA_INTEGRATION_BIN_COUNT)
throw JFJochException(JFJochExceptionCategory::InputParameterAboveMax,
fmt::format("Azimuthal integration bin count ({}) exceeds the FPGA limit ({}). "
"Enable ForceCPUinFPGAWorkflow to compute it on the CPU.",
azint_settings.GetBinCount(), FPGA_INTEGRATION_BIN_COUNT));
image_buffer.StartMeasurement(experiment);
for (int m = 0; m < experiment.GetModulesNum(); m++)
adu_histogram_module.emplace_back(std::make_unique<ADUHistogram>());
if (acquisition_device.size() < ndatastreams)
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
"Number of acquisition devices has to match data streams");
@@ -50,8 +39,6 @@ JFJochReceiverPedestal::JFJochReceiverPedestal(const DiffractionExperiment &in_e
expected_packets_per_image = 0;
for (int d = 0; d < ndatastreams; d++) {
acquisition_device[d].PrepareAction(experiment);
acquisition_device[d].SetSpotFinderParameters(spot_finding_settings);
expected_packets_per_image += acquisition_device[d].Counters().GetExpectedPacketsPerImage();
logger.Debug("Acquisition device {} prepared", d);
@@ -222,14 +209,6 @@ void JFJochReceiverPedestal::FinalizeMeasurement() {
current_status.SetStatus(GetStatus());
}
void JFJochReceiverPedestal::SetSpotFindingSettings(const SpotFindingSettings &in_spot_finding_settings) {
std::unique_lock ul(spot_finding_settings_mutex);
DiffractionExperiment::CheckDataProcessingSettings(in_spot_finding_settings);
spot_finding_settings = in_spot_finding_settings;
for (int i = 0; i < ndatastreams; i++)
acquisition_device[i].SetSpotFinderParameters(spot_finding_settings);
}
void JFJochReceiverPedestal::StopReceiver() {
if (measurement.valid()) {
measurement.get();
-2
View File
@@ -63,7 +63,5 @@ public:
void Cancel(bool silent) override;
float GetProgress() const override;
void SetSpotFindingSettings(const SpotFindingSettings &spot_finding_settings) override;
JFJochReceiverOutput GetFinalStatistics() const override;
};
+5 -94
View File
@@ -11,6 +11,7 @@
#include "JFJochReceiverLite.h"
#include "../common/HostMemory.h"
#include "../common/time_utc.h"
#include "../frame_serialize/CBORStream2Deserializer.h"
#include "../frame_serialize/CBORStream2Serializer.h"
#include "../image_analysis/indexing/IndexerThreadPool.h"
@@ -37,10 +38,6 @@ namespace {
return budget > taken ? budget - taken : 1;
}
double SecondsSince(std::chrono::steady_clock::time_point t) {
return std::chrono::duration<double>(std::chrono::steady_clock::now() - t).count();
}
// A run reachable for Cut while it exists: registered in `slot` (under `m`, cancelled at once where the
// cut came first) and taken off again before it is destroyed, an exception included.
class Reachable {
@@ -99,90 +96,6 @@ namespace {
}
}
namespace {
// A score the status carries only where it is a number: JSON has no NaN or infinity (nlohmann writes null)
void SetIfFinite(std::optional<double> &out, double v) {
if (std::isfinite(v))
out = v;
}
}
std::optional<RugnuxLiveModel> LiveModel(const ProcessResult &result) {
if (!result.model_validation && !result.model_prior)
return std::nullopt;
RugnuxLiveModel m;
m.placement = result.model_placement;
if (result.model_prior) {
const MolecularReplacementResult &mr = *result.model_prior;
m.model_name = mr.model_name;
m.predicted = mr.predicted_model;
m.mr_decision = mr.decision;
if (!mr.ok)
m.reason = mr.failure_reason;
if (mr.chosen >= 0) {
const MRCandidate &c = mr.candidates[mr.chosen];
m.mr_space_group = c.space_group.xhm();
m.copies = c.copies;
SetIfFinite(m.tf_z, c.tf_z);
SetIfFinite(m.rf_z, c.rf_z);
SetIfFinite(m.llg_z, c.llg_z);
SetIfFinite(m.fit_sigma, c.null_sigma);
SetIfFinite(m.mr_d_min, mr.d_min_search);
}
}
if (result.model_validation) {
const ModelValidationResult &v = *result.model_validation;
if (!v.model_name.empty())
m.model_name = v.model_name;
if (!v.ok) {
if (m.reason.empty())
m.reason = v.failure_reason;
} else {
SetIfFinite(m.r_work, v.r_work);
SetIfFinite(m.r_free, v.r_free);
SetIfFinite(m.cc_model, v.cc_model_overall);
if (v.fit_tested)
SetIfFinite(m.fit_sigma, v.r_work_sigma);
SetIfFinite(m.map_atom_sigma, v.mean_atom_density_sigma);
if (v.rigid_body_applied) {
SetIfFinite(m.rigid_body_angle_deg, v.rigid_body_angle_deg);
SetIfFinite(m.rigid_body_shift_A, v.rigid_body_shift_A);
}
if (!v.anomalous_sites.empty()) {
m.anomalous_site = v.anomalous_sites.front().label;
SetIfFinite(m.anomalous_site_sigma, v.anomalous_sites.front().sigma);
}
}
}
if (result.refinement) {
const RefinementResult &rf = *result.refinement;
RugnuxLiveRefinement r;
r.performed = rf.ok;
r.cancelled = rf.cancelled;
r.reason = rf.failure_reason;
r.on_gpu = rf.on_gpu;
r.seconds = rf.seconds;
if (rf.ok) {
SetIfFinite(r.r_work, rf.restrained ? rf.r_work : rf.r_work_placement);
SetIfFinite(r.r_free, rf.restrained ? rf.r_free : rf.r_free_placement);
if (rf.restrained) {
SetIfFinite(r.rmsd_bonds_A, rf.geometry.rmsd_bonds);
SetIfFinite(r.rmsd_angles_deg, rf.geometry.rmsd_angles);
}
}
r.blob_count = static_cast<int64_t>(rf.blobs.size());
for (size_t i = 0; i < std::min(rf.blobs.size(), RugnuxLiveRefinement::MAX_BLOBS); i++) {
const DensityBlob &b = rf.blobs[i];
RugnuxLiveBlob blob{DensityBlobKindName(b.kind), b.centroid.x, b.centroid.y, b.centroid.z, b.peak_sigma,
b.volume, b.nearest_atom, std::nullopt};
SetIfFinite(blob.nearest_atom_distance_A, b.nearest_atom_distance); // infinite: no atom near
r.blobs.push_back(blob);
}
m.refinement = std::move(r);
}
return m;
}
JFJochReceiverRugnux::JFJochReceiverRugnux(const DiffractionExperiment &in_experiment,
const PixelMask &in_pixel_mask,
ImagePuller &in_image_puller,
@@ -635,7 +548,8 @@ void JFJochReceiverRugnux::RugnuxThread() {
// The numbers the summary's Space group, Resolution and Signal rows give
std::optional<double> d_min, isa;
if (result.has_merge_statistics)
SetIfFinite(d_min, result.merge_statistics.overall.d_min);
if (std::isfinite(result.merge_statistics.overall.d_min))
d_min = result.merge_statistics.overall.d_min;
if (!result.merge_statistics.stills && result.error_model_isa_resolved && result.error_model_isa > 0.0)
isa = result.error_model_isa;
live_status.Headline(result.space_group ? result.space_group->xhm() : "", d_min, isa);
@@ -882,11 +796,8 @@ void JFJochReceiverRugnux::StopReceiver() {
void JFJochReceiverRugnux::SetSpotFindingSettings(const SpotFindingSettings &in_spot_finding_settings) {
// A rotation run takes rugnux's own spot finding; the frames of a serial run are analysed with these.
if (!stills_run)
return;
std::unique_lock ul(spot_finding_settings_mutex);
DiffractionExperiment::CheckDataProcessingSettings(in_spot_finding_settings);
spot_finding_settings = in_spot_finding_settings;
if (stills_run)
JFJochReceiver::SetSpotFindingSettings(in_spot_finding_settings);
}
float JFJochReceiverRugnux::GetEfficiency() const {
-3
View File
@@ -13,9 +13,6 @@
#include "RugnuxLiveStatus.h"
#include "StillsGPUAnalysis.h"
// What the run made of the model it was given, for the live status; none where no model was given.
std::optional<RugnuxLiveModel> LiveModel(const ProcessResult &result);
// A DECTRIS rotation collection forwarded to the writer as JFJochReceiverLite does, but analysed by
// rugnux while it arrives instead of image by image: the forward threads put each image into the
// image buffer and send it on, and one rugnux thread reads the collection back from that buffer. The
+89
View File
@@ -3,6 +3,11 @@
#include "RugnuxLiveStatus.h"
#include <algorithm>
#include <cmath>
#include "../rugnux/Rugnux.h"
double RugnuxLiveStatus::Since(std::chrono::steady_clock::time_point t) const {
return std::chrono::duration<double>(t - start).count();
}
@@ -157,3 +162,87 @@ RugnuxLiveSnapshot RugnuxLiveStatus::Get() const {
*ret.cut_s -= zero;
return ret;
}
namespace {
// A score the status carries only where it is a number: JSON has no NaN or infinity (nlohmann writes null)
void SetIfFinite(std::optional<double> &out, double v) {
if (std::isfinite(v))
out = v;
}
}
std::optional<RugnuxLiveModel> LiveModel(const ProcessResult &result) {
if (!result.model_validation && !result.model_prior)
return std::nullopt;
RugnuxLiveModel m;
m.placement = result.model_placement;
if (result.model_prior) {
const MolecularReplacementResult &mr = *result.model_prior;
m.model_name = mr.model_name;
m.predicted = mr.predicted_model;
m.mr_decision = mr.decision;
if (!mr.ok)
m.reason = mr.failure_reason;
if (mr.chosen >= 0) {
const MRCandidate &c = mr.candidates[mr.chosen];
m.mr_space_group = c.space_group.xhm();
m.copies = c.copies;
SetIfFinite(m.tf_z, c.tf_z);
SetIfFinite(m.rf_z, c.rf_z);
SetIfFinite(m.llg_z, c.llg_z);
SetIfFinite(m.fit_sigma, c.null_sigma);
SetIfFinite(m.mr_d_min, mr.d_min_search);
}
}
if (result.model_validation) {
const ModelValidationResult &v = *result.model_validation;
if (!v.model_name.empty())
m.model_name = v.model_name;
if (!v.ok) {
if (m.reason.empty())
m.reason = v.failure_reason;
} else {
SetIfFinite(m.r_work, v.r_work);
SetIfFinite(m.r_free, v.r_free);
SetIfFinite(m.cc_model, v.cc_model_overall);
if (v.fit_tested)
SetIfFinite(m.fit_sigma, v.r_work_sigma);
SetIfFinite(m.map_atom_sigma, v.mean_atom_density_sigma);
if (v.rigid_body_applied) {
SetIfFinite(m.rigid_body_angle_deg, v.rigid_body_angle_deg);
SetIfFinite(m.rigid_body_shift_A, v.rigid_body_shift_A);
}
if (!v.anomalous_sites.empty()) {
m.anomalous_site = v.anomalous_sites.front().label;
SetIfFinite(m.anomalous_site_sigma, v.anomalous_sites.front().sigma);
}
}
}
if (result.refinement) {
const RefinementResult &rf = *result.refinement;
RugnuxLiveRefinement r;
r.performed = rf.ok;
r.cancelled = rf.cancelled;
r.reason = rf.failure_reason;
r.on_gpu = rf.on_gpu;
r.seconds = rf.seconds;
if (rf.ok) {
SetIfFinite(r.r_work, rf.restrained ? rf.r_work : rf.r_work_placement);
SetIfFinite(r.r_free, rf.restrained ? rf.r_free : rf.r_free_placement);
if (rf.restrained) {
SetIfFinite(r.rmsd_bonds_A, rf.geometry.rmsd_bonds);
SetIfFinite(r.rmsd_angles_deg, rf.geometry.rmsd_angles);
}
}
r.blob_count = static_cast<int64_t>(rf.blobs.size());
for (size_t i = 0; i < std::min(rf.blobs.size(), RugnuxLiveRefinement::MAX_BLOBS); i++) {
const DensityBlob &b = rf.blobs[i];
RugnuxLiveBlob blob{DensityBlobKindName(b.kind), b.centroid.x, b.centroid.y, b.centroid.z, b.peak_sigma,
b.volume, b.nearest_atom, std::nullopt};
SetIfFinite(blob.nearest_atom_distance_A, b.nearest_atom_distance); // infinite: no atom near
r.blobs.push_back(blob);
}
m.refinement = std::move(r);
}
return m;
}
+4
View File
@@ -12,6 +12,7 @@
#include <vector>
class RugnuxStructure; // RugnuxStructure.h
struct ProcessResult; // rugnux/Rugnux.h
// Where the live rugnux run of the last collection is, for the broker's status route: the steps it has
// taken with what each found ("lattice found", "space group P 21 21 21"), the step it is on and how far
@@ -174,3 +175,6 @@ public:
void Clear();
RugnuxLiveSnapshot Get() const;
};
// What the run made of the model it was given, for the live status; none where no model was given.
std::optional<RugnuxLiveModel> LiveModel(const ProcessResult &result);
-1
View File
@@ -12,7 +12,6 @@
#include "../rugnux/RugnuxDefaults.h"
#include "../frame_serialize/CBORStream2Serializer.h"
#include "../writer/RugnuxResultBlocks.h"
#include "JFJochReceiverRugnux.h"
#include "RugnuxStructure.h"
StillsGPUAnalysis::StillsGPUAnalysis(const DiffractionExperiment &in_experiment, const PixelMask &in_mask,
+1 -4
View File
@@ -8,15 +8,12 @@
#include "spdlog/fmt/fmt.h"
#include "../common/time_utc.h"
#include "../image_analysis/scale_merge/CrystalSetting.h"
#include "../image_analysis/scale_merge/ReindexAmbiguity.h"
#include "../image_analysis/scale_merge/RfreeFlags.h"
namespace {
double SecondsSince(std::chrono::steady_clock::time_point t0) {
return std::chrono::duration<double>(std::chrono::steady_clock::now() - t0).count();
}
bool Cut(const ModelStepSettings &settings) {
return settings.cut != nullptr && settings.cut->load();
}
+1 -4
View File
@@ -7,6 +7,7 @@
#include <ctime>
#include "../common/ParallelFor.h"
#include "../common/time_utc.h"
#include "../image_analysis/scale_merge/Merge.h"
#include "../image_analysis/scale_merge/ReferenceFreeReindex.h"
#include "../image_analysis/scale_merge/ReindexAmbiguity.h"
@@ -14,10 +15,6 @@
#include "../image_analysis/scale_merge/SublatticeRelabel.h"
namespace {
double SecondsSince(std::chrono::steady_clock::time_point t0) {
return std::chrono::duration<double>(std::chrono::steady_clock::now() - t0).count();
}
size_t ImagesWithReflections(const std::vector<IntegrationOutcome> &outcomes) {
size_t n = 0;
for (const auto &o : outcomes)
+2 -1
View File
@@ -6,7 +6,8 @@
#include <cmath>
#include "../receiver/RugnuxLiveStatus.h"
#include "../receiver/JFJochReceiverRugnux.h"
#include "../receiver/RugnuxLiveStatus.h"
#include "../rugnux/Rugnux.h"
#include "../broker/OpenAPIConvert.h"
#include "../image_puller/TestImagePuller.h"