jfjoch_viewer: Better display (to be tested) of pixel refine
Build Packages / Unit tests (push) Failing after 1s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 25m52s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 29m5s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 29m54s
Build Packages / build:rpm (rocky8) (push) Successful in 31m55s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 32m12s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 32m48s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 35m27s
Build Packages / Generate python client (push) Successful in 25s
Build Packages / build:rpm (rocky9) (push) Successful in 31m59s
Build Packages / Create release (push) Skipped
Build Packages / Build documentation (push) Successful in 1m36s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 24m8s
Build Packages / XDS test (neggia plugin) (push) Successful in 17m46s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 21m36s
Build Packages / XDS test (durin plugin) (push) Successful in 19m40s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 19m38s
Build Packages / DIALS test (push) Successful in 26m30s

This commit is contained in:
2026-06-09 16:28:17 +02:00
parent 6c85aaba2b
commit efe882f4b6
10 changed files with 282 additions and 4 deletions
+76 -4
View File
@@ -8,6 +8,7 @@
#include <cstring>
#include "JFJochImageReadingWorker.h"
#include "../reader/JFJochReaderImage.h" // JFJochReaderImage + GAP/ERROR/SATURATED sentinels
#include "../image_analysis/LoadFCalcFromMtz.h"
#include "../image_analysis/bragg_prediction/BraggPredictionFactory.h"
#include "../image_analysis/geom_refinement/AssignSpotsToRings.h"
@@ -69,6 +70,7 @@ JFJochImageReadingWorker::JFJochImageReadingWorker(const SpotFindingSettings &se
indexing_settings(experiment.GetIndexingSettings()),
azint_settings(experiment.GetAzimuthalIntegrationSettings()) {
qRegisterMetaType<PixelRefineParams>("PixelRefineParams");
qRegisterMetaType<QVector<QRect>>("QVector<QRect>");
spot_finding_settings = settings;;
indexing = std::make_unique<IndexerThreadPool>(indexing_settings);
@@ -798,6 +800,74 @@ std::shared_ptr<SimpleImage> JFJochImageReadingWorker::WrapFloatImage_i(const st
return si;
}
void JFJochImageReadingWorker::SquaredResidualWithImage_i(std::vector<float> &pred) const {
// PredictImage() returns raw detector units (same as the measured counts), so
// pred - measured is the per-pixel residual the model fails to explain. We plot
// |pred - measured|^2: sign-free, so it needs no diverging colour scale and just
// highlights where the model disagrees most. Masked / saturated pixels carry
// sentinels rather than counts, so no comparison is possible -> NaN (gap).
if (!current_image_ptr)
return;
const auto &img = current_image_ptr->Image();
const size_t n = std::min(pred.size(), img.size());
for (size_t i = 0; i < n; ++i) {
const int32_t v = img[i];
if (v == GAP_PXL_VALUE || v == ERROR_PXL_VALUE || v == SATURATED_PXL_VALUE) {
pred[i] = NAN;
} else {
const float diff = pred[i] - static_cast<float>(v);
pred[i] = diff * diff;
}
}
}
void JFJochImageReadingWorker::MaskMeasuredSentinels_i(std::vector<float> &img) const {
// The chi^2 image is 0 outside shoeboxes; show masked/saturated pixels as a gap
// (NaN) instead, so they read as "not comparable" rather than "zero cost".
if (!current_image_ptr)
return;
const auto &measured = current_image_ptr->Image();
const size_t n = std::min(img.size(), measured.size());
for (size_t i = 0; i < n; ++i) {
const int32_t v = measured[i];
if (v == GAP_PXL_VALUE || v == ERROR_PXL_VALUE || v == SATURATED_PXL_VALUE)
img[i] = NAN;
}
}
QVector<QRect> JFJochImageReadingWorker::BuildShoeboxes_i(const PixelRefineData &data) const {
// One rectangle per fitted reflection: the shoebox the optimizer summed over,
// centred on the predicted position with half-size data.shoebox_radius.
QVector<QRect> boxes;
boxes.reserve(static_cast<int>(data.reflections.size()));
const int r = data.shoebox_radius;
const int side = 2 * r + 1;
for (const auto &refl : data.reflections) {
if (!std::isfinite(refl.predicted_x) || !std::isfinite(refl.predicted_y))
continue;
const int cx = static_cast<int>(std::lround(refl.predicted_x));
const int cy = static_cast<int>(std::lround(refl.predicted_y));
boxes.push_back(QRect(cx - r, cy - r, side, side));
}
return boxes;
}
std::vector<float> JFJochImageReadingWorker::BuildDisplayImage_i(const PixelRefineData &data,
int display_mode) const {
if (display_mode == PixelRefineParams::ChiSquared) {
// The cost density the optimizer actually minimizes (weighted residual^2).
const auto &img32 = current_image_ptr->Image();
auto chi2 = pixel_refine_->ChiSquaredImage<int32_t>(img32.data(), *last_profile_, *pixel_pred_, data);
MaskMeasuredSentinels_i(chi2);
return chi2;
}
auto pred = pixel_refine_->PredictImage(*last_profile_, *pixel_pred_, data, true);
if (display_mode == PixelRefineParams::SquaredDifference)
SquaredResidualWithImage_i(pred);
return pred;
}
void JFJochImageReadingWorker::LoadReference(QString path) {
QMutexLocker ul(&m);
try {
@@ -840,8 +910,9 @@ void JFJochImageReadingWorker::PixelRefinePreview(PixelRefineParams params) {
pixel_refine_->Run<int32_t>(img32.data(), *last_profile_, *pixel_pred_, d);
emit pixelRefineResidual(d.final_cost, d.cc, static_cast<int64_t>(d.reflections.size()));
auto pred = pixel_refine_->PredictImage(*last_profile_, *pixel_pred_, d, true);
emit predictedImageReady(WrapFloatImage_i(pred));
auto display = BuildDisplayImage_i(d, params.display_mode);
emit predictedImageReady(WrapFloatImage_i(display));
emit predictedShoeboxes(BuildShoeboxes_i(d));
} catch (const std::exception &e) {
emit pixelRefineStatus(QString("PixelRefine preview failed: %1").arg(e.what()));
}
@@ -884,8 +955,9 @@ void JFJochImageReadingWorker::PixelRefineRun(PixelRefineParams params) {
emit pixelRefineParamsRefined(out);
emit pixelRefineResidual(d.final_cost, d.cc, static_cast<int64_t>(d.reflections.size()));
auto pred = pixel_refine_->PredictImage(*last_profile_, *pixel_pred_, d, true);
emit predictedImageReady(WrapFloatImage_i(pred));
auto display = BuildDisplayImage_i(d, params.display_mode);
emit predictedImageReady(WrapFloatImage_i(display));
emit predictedShoeboxes(BuildShoeboxes_i(d));
// Show the refined predictions on the main image too.
auto new_image = std::make_shared<JFJochReaderImage>(*current_image_ptr);