From 549c0fade11f08ab70af5eef34dc833e97e32a26 Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Wed, 29 Oct 2025 14:49:14 +0100 Subject: [PATCH 01/33] jfjoch_viewer: Calibrant can be freely chosen from options (LaB6, Ag behenate, current sample) --- .../geom_refinement/AssignSpotsToRings.cpp | 39 +++++++++------ .../geom_refinement/AssignSpotsToRings.h | 7 +-- tests/DetGeomCalibTest.cpp | 20 ++++++-- viewer/JFJochImageReadingWorker.cpp | 13 +++-- viewer/JFJochImageReadingWorker.h | 3 +- viewer/JFJochViewerSidePanel.cpp | 49 ++++++++++++++++++- viewer/JFJochViewerSidePanel.h | 8 ++- 7 files changed, 109 insertions(+), 30 deletions(-) diff --git a/image_analysis/geom_refinement/AssignSpotsToRings.cpp b/image_analysis/geom_refinement/AssignSpotsToRings.cpp index 9babc079..0020f87b 100644 --- a/image_analysis/geom_refinement/AssignSpotsToRings.cpp +++ b/image_analysis/geom_refinement/AssignSpotsToRings.cpp @@ -9,6 +9,8 @@ #include #include +#include "../../common/CrystalLattice.h" + FindCircleCenterResult FindCircleCenter(const std::vector &v, int64_t width, int64_t height, int64_t max_spots) { if ((width <= 0) || (height <= 0) || (max_spots <= 0)) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Invalid image size"); @@ -148,16 +150,21 @@ std::vector AnalyzeClusters(const std::vector& r, const std return ret; } -// Build unique sorted Qu = sqrt(h^2 + k^2 + l^2) -// This handles rings spacing for (any) crystal with cubic symmetry -std::vector CalculateCubicXtalRings(float a, int hkl_max) { +std::vector CalculateXtalRings(const UnitCell &cell, int hkl_max) { + CrystalLattice latt(cell); + + Coord Astar = latt.Astar(); + Coord Bstar = latt.Bstar(); + Coord Cstar = latt.Cstar(); + std::vector u; for (int h = 0; h <= hkl_max; h++) { for (int k = 0; k <= hkl_max; k++) { for (int l = 0; l <= hkl_max; l++) { if (h == 0 && k == 0 && l == 0) continue; - float val = std::sqrt(float(h*h + k*k + l*l)); - u.push_back(val); + auto p = Astar * h + Bstar * k + Cstar * l; + float Q = 2.0f * M_PI * p.Length(); + u.push_back(Q); } } } @@ -165,15 +172,17 @@ std::vector CalculateCubicXtalRings(float a, int hkl_max) { // Deduplicate (since e.g. (100), (010), (001) all give sqrt(1)) u.erase(std::unique(u.begin(), u.end(), - [](float a, float b){ return std::fabs(a-b) < 1e-12; }), + [](float a, float b){ return std::fabs(a-b) < 1e-6; }), u.end()); - for (auto &iter: u) - iter = 2 * M_PI * iter / a; - return u; } + +std::vector CalculateCubicXtalRings(float a, int hkl_max) { + return CalculateXtalRings(UnitCell(a,a,a,90,90,90), hkl_max); +} + float GuessDetectorDistance(const DiffractionGeometry& geom, float ring_radius_pxl, float d_A) { float sin_theta = geom.GetWavelength_A() / (2 * d_A); if (sin_theta < 0 || sin_theta > 1) @@ -209,10 +218,12 @@ std::vector GuessInitialGeometry(DiffractionGeometry &geom, const return cluster_annot; } -void GuessGeometry(DiffractionGeometry &geom, const std::vector &v, float calibrant_a_A) { - auto cluster_annot = GuessInitialGeometry(geom, v, calibrant_a_A); +void GuessGeometry(DiffractionGeometry &geom, const std::vector &v, const UnitCell &calibrant_uc) { + std::vector ring_q = CalculateXtalRings(calibrant_uc); + if (ring_q.empty()) + throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Unit cell problem"); + auto cluster_annot = GuessInitialGeometry(geom, v, 2 * M_PI / ring_q[0]); - std::vector ring_q = CalculateCubicXtalRings(calibrant_a_A); std::vector optimizer_input; int ring_idx = 0; @@ -246,8 +257,8 @@ void GuessGeometry(DiffractionGeometry &geom, const std::vector &v, geom = optimizer.Run(optimizer_input); } -void OptimizeGeometry(DiffractionGeometry &geom, const std::vector &v, float calibrant_a_A) { - std::vector q_ring = CalculateCubicXtalRings(calibrant_a_A); +void OptimizeGeometry(DiffractionGeometry &geom, const std::vector &v, const UnitCell &calibrant) { + std::vector q_ring = CalculateXtalRings(calibrant); std::vector optimizer_input; diff --git a/image_analysis/geom_refinement/AssignSpotsToRings.h b/image_analysis/geom_refinement/AssignSpotsToRings.h index c833bf5c..835f9e59 100644 --- a/image_analysis/geom_refinement/AssignSpotsToRings.h +++ b/image_analysis/geom_refinement/AssignSpotsToRings.h @@ -5,7 +5,7 @@ #define JFJOCH_ASSIGNSPOTSTORINGS_H #include -#include +#include "../../common/UnitCell.h" #include "../../common/SpotToSave.h" #include "RingOptimizer.h" @@ -29,11 +29,12 @@ FindCircleCenterResult FindCircleCenter(const std::vector &v, std::vector> ClusterSpotsIntoRings(const std::vector& r, float eps = 1.5, int minPts = 5); std::vector AnalyzeClusters(const std::vector& r, const std::vector> &clusters); +std::vector CalculateXtalRings(const UnitCell &cell, int hkl_max = 6); std::vector CalculateCubicXtalRings( float a, int hkl_max = 4); float GuessDetectorDistance(const DiffractionGeometry& geom, float ring_radius_pxl, float d_A); std::vector GuessInitialGeometry(DiffractionGeometry &geom, const std::vector &v, float calibrant_a_A); -void GuessGeometry(DiffractionGeometry &geom, const std::vector &v, float calibrant_a_A); -void OptimizeGeometry(DiffractionGeometry &geom, const std::vector &v, float calibrant_a_A); +void GuessGeometry(DiffractionGeometry &geom, const std::vector &v, const UnitCell &calibrant); +void OptimizeGeometry(DiffractionGeometry &geom, const std::vector &v, const UnitCell &calibrant); #endif //JFJOCH_ASSIGNSPOTSTORINGS_H diff --git a/tests/DetGeomCalibTest.cpp b/tests/DetGeomCalibTest.cpp index 82c79c39..7328bf87 100644 --- a/tests/DetGeomCalibTest.cpp +++ b/tests/DetGeomCalibTest.cpp @@ -98,8 +98,8 @@ TEST_CASE("DetGeomCalib_AnalyzeClusters") { REQUIRE(ret[1].R_obs == Catch::Approx(100.0f)); } -TEST_CASE("DetGeomCalib_build_u") { - auto ret = CalculateCubicXtalRings(2.0); +TEST_CASE("DetGeomCalib_CalculateXtalRings_cubic") { + auto ret = CalculateXtalRings(UnitCell(2.0, 2.0, 2.0, 90, 90, 90)); CHECK(ret[0] == Catch::Approx(2.0 * M_PI * 1.0 / 2.0)); CHECK(ret[1] == Catch::Approx(2.0 * M_PI * sqrt( 2.0 )/ 2.0)); CHECK(ret[2] == Catch::Approx(2.0 * M_PI * sqrt( 3.0 )/ 2.0)); @@ -108,6 +108,18 @@ TEST_CASE("DetGeomCalib_build_u") { CHECK(ret[6] == Catch::Approx(2.0 * M_PI * sqrt( 8.0 )/ 2.0)); } +TEST_CASE("DetGeomCalib_CalculateXtalRings_one_long_axis") { + auto ret = CalculateXtalRings(UnitCell(50.0, 2.0, 2.0, 90, 90, 90)); + CHECK(ret[0] == Catch::Approx(2.0 * M_PI * 1.0 / 50.0)); + CHECK(ret[1] == Catch::Approx(2.0 * M_PI * 2.0 / 50.0)); + CHECK(ret[2] == Catch::Approx(2.0 * M_PI * 3.0 / 50.0)); + CHECK(ret[3] == Catch::Approx(2.0 * M_PI * 4.0 / 50.0)); + CHECK(ret[4] == Catch::Approx(2.0 * M_PI * 5.0 / 50.0)); + + + +} + TEST_CASE("DetGeomCalib_GuessDetectorDistance") { std::vector spots; @@ -178,7 +190,7 @@ TEST_CASE("DetGeomCalib_GuessGeometry") { DiffractionGeometry geom_out; geom_out.Wavelength_A(1.0).DetectorDistance_mm(200.0); - GuessGeometry(geom_out, spots, LAB6_CELL_A); + GuessGeometry(geom_out, spots, UnitCell(LAB6_CELL_A, LAB6_CELL_A, LAB6_CELL_A, 90,90,90)); CHECK(fabsf(geom_out.GetBeamX_pxl() - geom.GetBeamX_pxl()) < 0.001f); CHECK(fabsf(geom_out.GetBeamY_pxl() - geom.GetBeamY_pxl()) < 0.001f); @@ -246,7 +258,7 @@ TEST_CASE("DetGeomCalib_OptimizeGeometry") { geom_i.Wavelength_A(1.0).BeamX_pxl(995.0).BeamY_pxl(1277.0) .DetectorDistance_mm(98).PoniRot1_rad(0.0975).PoniRot2_rad(0.055); - OptimizeGeometry(geom_i, spots, lab6_a); + OptimizeGeometry(geom_i, spots, UnitCell(LAB6_CELL_A, LAB6_CELL_A, LAB6_CELL_A, 90,90,90)); CHECK(geom_i.GetBeamX_pxl() == Catch::Approx(geom.GetBeamX_pxl())); CHECK(geom_i.GetBeamY_pxl() == Catch::Approx(geom.GetBeamY_pxl())); diff --git a/viewer/JFJochImageReadingWorker.cpp b/viewer/JFJochImageReadingWorker.cpp index 58c55b89..83ba0d7c 100644 --- a/viewer/JFJochImageReadingWorker.cpp +++ b/viewer/JFJochImageReadingWorker.cpp @@ -234,7 +234,7 @@ void JFJochImageReadingWorker::Analyze() { emit imageLoaded(current_image_ptr); } -void JFJochImageReadingWorker::FindCenter() { +void JFJochImageReadingWorker::FindCenter(const UnitCell& calibrant) { QMutexLocker locker(&m); if (!current_image_ptr) return; @@ -242,7 +242,7 @@ void JFJochImageReadingWorker::FindCenter() { DiffractionGeometry geom = current_image_ptr->Dataset().experiment.GetDiffractionGeometry(); try { - GuessGeometry(geom, current_image_ptr->ImageData().spots, LAB6_CELL_A); + GuessGeometry(geom, current_image_ptr->ImageData().spots, calibrant); } catch (const JFJochException &e) { logger.ErrorException(e); return; @@ -258,10 +258,13 @@ void JFJochImageReadingWorker::FindCenter() { .PoniRot2_rad(geom.GetPoniRot2_rad()) .PoniRot3_rad(geom.GetPoniRot3_rad()); UpdateDataset_i(new_experiment); - QVector rings; - for (int i = 1; i < 7; i++) - rings.push_back(LAB6_CELL_A / sqrtf(i)); + std::vector ring_Q = CalculateXtalRings(calibrant); + + QVector rings; + for (int i = 0; i < 6 && i < ring_Q.size(); i++) { + rings.push_back(2 * M_PI / ring_Q[i]); + } emit setRings(rings); } diff --git a/viewer/JFJochImageReadingWorker.h b/viewer/JFJochImageReadingWorker.h index a3ea25de..19269499 100644 --- a/viewer/JFJochImageReadingWorker.h +++ b/viewer/JFJochImageReadingWorker.h @@ -22,6 +22,7 @@ Q_DECLARE_METATYPE(std::shared_ptr) Q_DECLARE_METATYPE(DiffractionExperiment) Q_DECLARE_METATYPE(SpotFindingSettings) Q_DECLARE_METATYPE(IndexingSettings) +Q_DECLARE_METATYPE(UnitCell) class JFJochImageReadingWorker : public QObject { Q_OBJECT @@ -77,7 +78,7 @@ public slots: void UpdateDataset(const DiffractionExperiment& experiment); - void FindCenter(); + void FindCenter(const UnitCell& calibrant); void Analyze(); void UpdateSpotFindingSettings(const SpotFindingSettings &settings, const IndexingSettings &indexing, int64_t max_spots, bool reanalyze); diff --git a/viewer/JFJochViewerSidePanel.cpp b/viewer/JFJochViewerSidePanel.cpp index 221f6f9d..c87bf27f 100644 --- a/viewer/JFJochViewerSidePanel.cpp +++ b/viewer/JFJochViewerSidePanel.cpp @@ -119,8 +119,15 @@ JFJochViewerSidePanel::JFJochViewerSidePanel(QWidget *parent) : QWidget(parent) connect(analyzeButton, &QPushButton::clicked,[this] {emit analyze();}); layout->addWidget(analyzeButton); - auto findBeamCenterButton = new QPushButton("Calibrate detector (LaB6)", this); - connect(findBeamCenterButton, &QPushButton::clicked,[this] {emit findBeamCenter();}); + // Calibrant selection combo box + layout->addWidget(new TitleLabel("Powder geometry calibration", this)); + + calibrantCombo = new QComboBox(this); + layout->addWidget(calibrantCombo); + updateCalibrantList(); + + auto findBeamCenterButton = new QPushButton("Calibrate detector", this); + connect(findBeamCenterButton, &QPushButton::clicked,this, &JFJochViewerSidePanel::findBeamCenterClicked); layout->addWidget(findBeamCenterButton); // Add preset ice rings button below LaB6 calibration @@ -136,6 +143,40 @@ JFJochViewerSidePanel::JFJochViewerSidePanel(QWidget *parent) : QWidget(parent) setLayout(layout); // Set the layout to the widget } +void JFJochViewerSidePanel::updateCalibrantList() { + calibrantCombo->clear(); + + calibrantCombo->addItem("LaB6"); + calibrantCombo->addItem("Silver Behenate"); + if (sample_cell) + calibrantCombo->addItem(QString("Current sample (%1 %2 %3 %4 %5 %6)") + .arg(QString::number(sample_cell->a, 'f', 1)) + .arg(QString::number(sample_cell->b, 'f', 1)) + .arg(QString::number(sample_cell->c, 'f', 1)) + .arg(QString::number(sample_cell->alpha, 'f', 1)) + .arg(QString::number(sample_cell->beta, 'f', 1)) + .arg(QString::number(sample_cell->gamma, 'f', 1))); + calibrantCombo->setCurrentIndex(0); +} + +void JFJochViewerSidePanel::findBeamCenterClicked() { + UnitCell uc(LAB6_CELL_A, LAB6_CELL_A, LAB6_CELL_A, 90, 90, 90); + switch (calibrantCombo->currentIndex()) { + case 1: + // T. C. Huang , H. Toraya, T. N. Blanton, Y. Wu, J. Appl. Cryst. 26 (1993), 180-184. + uc = UnitCell(5.1769, 4.7218, 58.380, 89.440, 89.634, 75.854); + break; + case 2: + if (sample_cell) + uc = sample_cell.value(); + break; + default: + break; + } + + emit findBeamCenter(uc); +} + void JFJochViewerSidePanel::setRings(const QVector &v) { QString txt; for (float i : v) { @@ -192,6 +233,10 @@ void JFJochViewerSidePanel::editingFinished() { } void JFJochViewerSidePanel::loadImage(std::shared_ptr image) { + if (image) + sample_cell = image->Dataset().experiment.GetUnitCell(); + updateCalibrantList(); + emit imageLoaded(image); } diff --git a/viewer/JFJochViewerSidePanel.h b/viewer/JFJochViewerSidePanel.h index 84bae130..2c7f8f51 100644 --- a/viewer/JFJochViewerSidePanel.h +++ b/viewer/JFJochViewerSidePanel.h @@ -16,10 +16,13 @@ class JFJochViewerSidePanel : public QWidget { Q_OBJECT + std::optional sample_cell; + QLineEdit *res_rings_edit = nullptr; QCheckBox *autoResRingsCheckBox; QCheckBox *resRingsCheckBox; + QComboBox* calibrantCombo{nullptr}; // stores current calibrant selection JFJochViewerSidePanelChart *chart = nullptr; signals: void showSpots(bool input); @@ -31,7 +34,7 @@ signals: void showSaturatedPixels(bool input); void showPredictions(bool input); void highlightIceRings(bool input); - void findBeamCenter(); + void findBeamCenter(const UnitCell &input); void analyze(); void imageLoaded(std::shared_ptr image); @@ -48,6 +51,9 @@ private slots: void highlightIceRingsToggled(bool input); void saturatedPixelsToggled(bool input); void setRings(const QVector &v); + + void findBeamCenterClicked(); + void updateCalibrantList(); }; -- 2.49.1 From 1950694ef20509e568869a6bdc8fedb8645f32e6 Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Wed, 29 Oct 2025 14:57:32 +0100 Subject: [PATCH 02/33] jfjoch_viewer: Add option to refine detector settings --- viewer/JFJochImageReadingWorker.cpp | 7 +++++-- viewer/JFJochImageReadingWorker.h | 3 ++- viewer/JFJochViewerSidePanel.cpp | 26 ++++++++++++++++++++++++-- viewer/JFJochViewerSidePanel.h | 3 ++- 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/viewer/JFJochImageReadingWorker.cpp b/viewer/JFJochImageReadingWorker.cpp index 83ba0d7c..250da652 100644 --- a/viewer/JFJochImageReadingWorker.cpp +++ b/viewer/JFJochImageReadingWorker.cpp @@ -234,7 +234,7 @@ void JFJochImageReadingWorker::Analyze() { emit imageLoaded(current_image_ptr); } -void JFJochImageReadingWorker::FindCenter(const UnitCell& calibrant) { +void JFJochImageReadingWorker::FindCenter(const UnitCell& calibrant, bool guess) { QMutexLocker locker(&m); if (!current_image_ptr) return; @@ -242,7 +242,10 @@ void JFJochImageReadingWorker::FindCenter(const UnitCell& calibrant) { DiffractionGeometry geom = current_image_ptr->Dataset().experiment.GetDiffractionGeometry(); try { - GuessGeometry(geom, current_image_ptr->ImageData().spots, calibrant); + if (guess) + GuessGeometry(geom, current_image_ptr->ImageData().spots, calibrant); + else + OptimizeGeometry(geom, current_image_ptr->ImageData().spots, calibrant); } catch (const JFJochException &e) { logger.ErrorException(e); return; diff --git a/viewer/JFJochImageReadingWorker.h b/viewer/JFJochImageReadingWorker.h index 19269499..5aa102ed 100644 --- a/viewer/JFJochImageReadingWorker.h +++ b/viewer/JFJochImageReadingWorker.h @@ -78,7 +78,8 @@ public slots: void UpdateDataset(const DiffractionExperiment& experiment); - void FindCenter(const UnitCell& calibrant); + void FindCenter(const UnitCell& calibrant, bool guess); + void Analyze(); void UpdateSpotFindingSettings(const SpotFindingSettings &settings, const IndexingSettings &indexing, int64_t max_spots, bool reanalyze); diff --git a/viewer/JFJochViewerSidePanel.cpp b/viewer/JFJochViewerSidePanel.cpp index c87bf27f..c373fb2e 100644 --- a/viewer/JFJochViewerSidePanel.cpp +++ b/viewer/JFJochViewerSidePanel.cpp @@ -126,10 +126,14 @@ JFJochViewerSidePanel::JFJochViewerSidePanel(QWidget *parent) : QWidget(parent) layout->addWidget(calibrantCombo); updateCalibrantList(); - auto findBeamCenterButton = new QPushButton("Calibrate detector", this); + auto findBeamCenterButton = new QPushButton("Guess detector calibration", this); connect(findBeamCenterButton, &QPushButton::clicked,this, &JFJochViewerSidePanel::findBeamCenterClicked); layout->addWidget(findBeamCenterButton); + auto optimizeBeamCenterButton = new QPushButton("Refine detector calibration", this); + connect(optimizeBeamCenterButton, &QPushButton::clicked,this, &JFJochViewerSidePanel::optimizeBeamCenterClicked); + layout->addWidget(optimizeBeamCenterButton); + // Add preset ice rings button below LaB6 calibration auto iceRingsButton = new QPushButton("Display ice rings", this); connect(iceRingsButton, &QPushButton::clicked, this, [this]() { @@ -159,6 +163,24 @@ void JFJochViewerSidePanel::updateCalibrantList() { calibrantCombo->setCurrentIndex(0); } +void JFJochViewerSidePanel::optimizeBeamCenterClicked() { + UnitCell uc(LAB6_CELL_A, LAB6_CELL_A, LAB6_CELL_A, 90, 90, 90); + switch (calibrantCombo->currentIndex()) { + case 1: + // T. C. Huang , H. Toraya, T. N. Blanton, Y. Wu, J. Appl. Cryst. 26 (1993), 180-184. + uc = UnitCell(5.1769, 4.7218, 58.380, 89.440, 89.634, 75.854); + break; + case 2: + if (sample_cell) + uc = sample_cell.value(); + break; + default: + break; + } + + emit findBeamCenter(uc, false); +} + void JFJochViewerSidePanel::findBeamCenterClicked() { UnitCell uc(LAB6_CELL_A, LAB6_CELL_A, LAB6_CELL_A, 90, 90, 90); switch (calibrantCombo->currentIndex()) { @@ -174,7 +196,7 @@ void JFJochViewerSidePanel::findBeamCenterClicked() { break; } - emit findBeamCenter(uc); + emit findBeamCenter(uc, true); } void JFJochViewerSidePanel::setRings(const QVector &v) { diff --git a/viewer/JFJochViewerSidePanel.h b/viewer/JFJochViewerSidePanel.h index 2c7f8f51..ae21092e 100644 --- a/viewer/JFJochViewerSidePanel.h +++ b/viewer/JFJochViewerSidePanel.h @@ -34,7 +34,7 @@ signals: void showSaturatedPixels(bool input); void showPredictions(bool input); void highlightIceRings(bool input); - void findBeamCenter(const UnitCell &input); + void findBeamCenter(const UnitCell &input, bool guess); void analyze(); void imageLoaded(std::shared_ptr image); @@ -53,6 +53,7 @@ private slots: void setRings(const QVector &v); void findBeamCenterClicked(); + void optimizeBeamCenterClicked(); void updateCalibrantList(); }; -- 2.49.1 From 2e4748fa3ef564f25a7bccc530fdb53d43acb75f Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Thu, 30 Oct 2025 21:06:09 +0100 Subject: [PATCH 03/33] jfjoch_viewer: Add wayland support --- docker/rocky8/Dockerfile | 2 ++ docker/rocky9/Dockerfile | 2 ++ docker/ubuntu2204/Dockerfile | 2 ++ docker/ubuntu2404/Dockerfile | 2 ++ viewer/CMakeLists.txt | 2 +- 5 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docker/rocky8/Dockerfile b/docker/rocky8/Dockerfile index 8b3bef04..e2721a34 100644 --- a/docker/rocky8/Dockerfile +++ b/docker/rocky8/Dockerfile @@ -37,6 +37,7 @@ RUN dnf -y update && \ xcb-util-devel \ libxkbcommon-devel \ libxkbcommon-x11-devel \ + libwayland-devel \ libX11-devel \ libXext-devel \ libXrender-devel \ @@ -120,6 +121,7 @@ RUN set -eux; \ -DQT_FEATURE_xcb=ON \ -DQT_FEATURE_xcb_xlib=OFF \ -DQT_FEATURE_xkbcommon_x11=ON \ + -DQT_FEATURE_wayland_client=ON \ -DQT_FEATURE_opengl=ON \ -DQT_FEATURE_opengl_desktop=ON \ -DQT_FEATURE_opengl_dynamic=OFF \ diff --git a/docker/rocky9/Dockerfile b/docker/rocky9/Dockerfile index 78fc11b9..cb1d2468 100644 --- a/docker/rocky9/Dockerfile +++ b/docker/rocky9/Dockerfile @@ -62,6 +62,7 @@ RUN dnf -y update && \ libdrm-devel \ libglvnd-core-devel \ libglvnd-devel \ + libwayland-devel \ freetype-devel && \ dnf clean all && rm -rf /var/cache/dnf @@ -107,6 +108,7 @@ RUN set -eux; \ -DQT_FEATURE_xcb=ON \ -DQT_FEATURE_xcb_xlib=OFF \ -DQT_FEATURE_xkbcommon_x11=ON \ + -DQT_FEATURE_wayland_client=ON \ -DQT_FEATURE_opengl=ON \ -DQT_FEATURE_opengl_desktop=ON \ -DQT_FEATURE_opengl_dynamic=OFF \ diff --git a/docker/ubuntu2204/Dockerfile b/docker/ubuntu2204/Dockerfile index 73bb6368..312b594d 100644 --- a/docker/ubuntu2204/Dockerfile +++ b/docker/ubuntu2204/Dockerfile @@ -74,6 +74,7 @@ RUN set -eux; \ mesa-utils \ libassimp-dev \ libglvnd-dev \ + libwayland-dev \ libfreetype6-dev; \ apt-get -y install gcc-12 g++-12; \ apt-get clean; \ @@ -124,6 +125,7 @@ RUN set -eux; \ -DQT_FEATURE_xcb=ON \ -DQT_FEATURE_xcb_xlib=ON \ -DQT_FEATURE_xkbcommon_x11=ON \ + -DQT_FEATURE_wayland_client=ON \ -DQT_FEATURE_opengl=ON \ -DQT_FEATURE_opengl_desktop=ON \ -DQT_FEATURE_opengl_dynamic=OFF \ diff --git a/docker/ubuntu2404/Dockerfile b/docker/ubuntu2404/Dockerfile index aa513f47..eb59f2b7 100644 --- a/docker/ubuntu2404/Dockerfile +++ b/docker/ubuntu2404/Dockerfile @@ -65,10 +65,12 @@ RUN set -eux; \ libfontconfig1-dev \ libopenblas-dev \ libfftw3-dev \ + libwayland-dev \ qt6-base-dev \ qt6-charts-dev \ qt6-tools-dev \ qt6-3d-dev \ + qt6-wayland-dev \ libeigen3-dev \ libfreetype6-dev; \ apt-get clean; \ diff --git a/viewer/CMakeLists.txt b/viewer/CMakeLists.txt index 8311dc12..045156fb 100644 --- a/viewer/CMakeLists.txt +++ b/viewer/CMakeLists.txt @@ -69,4 +69,4 @@ INSTALL( COMPONENT viewer ) -qt_import_plugins(jfjoch_viewer INCLUDE Qt::QLibInputPlugin Qt::QXcbIntegrationPlugin) +qt_import_plugins(jfjoch_viewer INCLUDE Qt::QLibInputPlugin Qt::QXcbIntegrationPlugin Qt::QWaylandIntegrationPlugin) -- 2.49.1 From e699152fa63e6c368e53b6897f01cf3b5a1aacd2 Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Thu, 30 Oct 2025 22:05:52 +0100 Subject: [PATCH 04/33] Docker: wayland fixes for rocky --- docker/rocky8/Dockerfile | 2 +- docker/rocky9/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/rocky8/Dockerfile b/docker/rocky8/Dockerfile index e2721a34..10851b1a 100644 --- a/docker/rocky8/Dockerfile +++ b/docker/rocky8/Dockerfile @@ -37,7 +37,7 @@ RUN dnf -y update && \ xcb-util-devel \ libxkbcommon-devel \ libxkbcommon-x11-devel \ - libwayland-devel \ + wayland-devel \ libX11-devel \ libXext-devel \ libXrender-devel \ diff --git a/docker/rocky9/Dockerfile b/docker/rocky9/Dockerfile index cb1d2468..5e10734b 100644 --- a/docker/rocky9/Dockerfile +++ b/docker/rocky9/Dockerfile @@ -62,7 +62,7 @@ RUN dnf -y update && \ libdrm-devel \ libglvnd-core-devel \ libglvnd-devel \ - libwayland-devel \ + wayland-devel \ freetype-devel && \ dnf clean all && rm -rf /var/cache/dnf -- 2.49.1 From 0217f3713eb58ecf902729d0f374a8d96739f954 Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Thu, 30 Oct 2025 22:07:43 +0100 Subject: [PATCH 05/33] Gitea: Fix release CI --- .gitea/workflows/build_and_test.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.gitea/workflows/build_and_test.yml b/.gitea/workflows/build_and_test.yml index 0aaaf900..debd5f9c 100644 --- a/.gitea/workflows/build_and_test.yml +++ b/.gitea/workflows/build_and_test.yml @@ -101,16 +101,16 @@ jobs: cd .. if [ "${{ matrix.distro }}" = "rocky8_nocuda" ]; then - for file in jfjoch-viewer*.rpm jfjoch-writer*rpm; do - python3 gitea_upload_file.py "build/$file" + for file in build/jfjoch-viewer*.rpm build/jfjoch-writer*rpm; do + python3 gitea_upload_file.py "$file" done elif [ "${{ matrix.distro }}" = "rocky9_nocuda" ]; then - for file in jfjoch-viewer*.rpm jfjoch-writer*rpm; do - python3 gitea_upload_file.py "build/$file" + for file in build/jfjoch-viewer*.rpm build/jfjoch-writer*rpm; do + python3 gitea_upload_file.py "$file" done elif [ "${{ matrix.distro }}" = "ubuntu2204_nocuda" ]; then - for file in jfjoch*viewer*.deb jfjoch*writer*.deb; do - python3 gitea_upload_file.py "build/$file" + for file in build/jfjoch*viewer*.deb build/jfjoch*writer*.deb; do + python3 gitea_upload_file.py "$file" done fi python-client: -- 2.49.1 From fce1a2dca9e8359f32674e0cee4994f00f9d82fa Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Fri, 31 Oct 2025 11:43:08 +0100 Subject: [PATCH 06/33] PreviewImage: Remove PNG support (to clean dependencies) --- CMakeLists.txt | 15 +-- broker/JFJochBrokerHttp.cpp | 29 ------ broker/JFJochBrokerHttp.h | 10 -- broker/gen/api/DefaultApi.cpp | 94 ------------------- broker/gen/api/DefaultApi.h | 17 ---- broker/jfjoch_api.yaml | 36 ------- broker/redoc-static.html | 38 +------- docker/rocky8/Dockerfile | 2 +- docker/rocky9/Dockerfile | 1 + docs/SOFTWARE.md | 1 + docs/python_client/README.md | 1 - docs/python_client/docs/DefaultApi.md | 85 ----------------- frontend/package-lock.json | 4 +- frontend/src/components/PreviewImage.tsx | 24 +---- .../src/openapi/services/DefaultService.ts | 47 ---------- preview/CMakeLists.txt | 2 - preview/JFJochPNG.cpp | 91 ------------------ preview/JFJochPNG.h | 11 --- preview/PreviewImage.cpp | 3 - preview/PreviewImage.h | 2 +- tests/CMakeLists.txt | 1 - tests/PNGTest.cpp | 32 ------- 22 files changed, 14 insertions(+), 532 deletions(-) delete mode 100644 preview/JFJochPNG.cpp delete mode 100644 preview/JFJochPNG.h delete mode 100644 tests/PNGTest.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 59b2578a..9eae92be 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,12 +47,6 @@ SET(lzma OFF) SET(jpeg OFF) SET(old-jpeg OFF) -# PNG -set(PNG_SHARED OFF) -set(PNG_STATIC ON) -set(PNG_EXECUTABLES OFF) -set(PNG_TESTS OFF) - INCLUDE(CheckLanguage) INCLUDE(CheckIncludeFile) CHECK_LANGUAGE(CUDA) @@ -99,13 +93,6 @@ FetchContent_Declare(tiff GIT_TAG v4.6.0 EXCLUDE_FROM_ALL) -FetchContent_Declare( - png - GIT_REPOSITORY https://github.com/pnggroup/libpng - GIT_TAG v1.6.49 - EXCLUDE_FROM_ALL -) - FetchContent_Declare(hdf5 GIT_REPOSITORY https://github.com/HDFGroup/hdf5/ GIT_TAG hdf5_1.14.5 @@ -149,7 +136,7 @@ FetchContent_Declare( EXCLUDE_FROM_ALL ) -FetchContent_MakeAvailable(pistache_http zstd sls_detector_package catch2 hdf5 tiff png) +FetchContent_MakeAvailable(pistache_http zstd sls_detector_package catch2 hdf5 tiff) ADD_SUBDIRECTORY(jungfrau) ADD_SUBDIRECTORY(compression) diff --git a/broker/JFJochBrokerHttp.cpp b/broker/JFJochBrokerHttp.cpp index e0d9b20f..3816efbe 100644 --- a/broker/JFJochBrokerHttp.cpp +++ b/broker/JFJochBrokerHttp.cpp @@ -497,35 +497,6 @@ void JFJochBrokerHttp::image_buffer_image_jpeg_get(const std::optional response.send(Pistache::Http::Code::Not_Found); } -void JFJochBrokerHttp::image_buffer_image_png_get(const std::optional &id, - const std::optional &showUserMask, - const std::optional &showRoi, - const std::optional &showSpots, - const std::optional &showBeamCenter, - const std::optional &saturation, - const std::optional &jpegQuality, - const std::optional &showResRing, - const std::optional &color, - Pistache::Http::ResponseWriter &response) { - int64_t image_id = id.value_or(ImageBuffer::MaxImage); - PreviewImageSettings settings{}; - - settings.show_user_mask = showUserMask.value_or(true); - settings.show_roi = showRoi.value_or(false); - settings.show_spots = showSpots.value_or(true); - settings.saturation_value = saturation.value_or(10); - settings.jpeg_quality = jpegQuality.value_or(100); - settings.resolution_ring = showResRing; - settings.scale = ConvertColorScale(color); - settings.show_beam_center = showBeamCenter.value_or(true); - settings.format = PreviewImageFormat::PNG; - std::string s = state_machine.GetPreviewJPEG(settings, image_id); - if (!s.empty()) - response.send(Pistache::Http::Code::Ok, s, Pistache::Http::Mime::MediaType::fromString("image/jpeg")); - else - response.send(Pistache::Http::Code::Not_Found); -} - void JFJochBrokerHttp::image_buffer_image_tiff_get(const std::optional &id, Pistache::Http::ResponseWriter &response) { int64_t image_id = ImageBuffer::MaxImage; diff --git a/broker/JFJochBrokerHttp.h b/broker/JFJochBrokerHttp.h index 43e21ae0..74e5b776 100644 --- a/broker/JFJochBrokerHttp.h +++ b/broker/JFJochBrokerHttp.h @@ -157,16 +157,6 @@ class JFJochBrokerHttp : public org::openapitools::server::api::DefaultApi { const std::optional &showResRing, const std::optional &color, Pistache::Http::ResponseWriter &response) override; - void image_buffer_image_png_get(const std::optional &id, - const std::optional &showUserMask, - const std::optional &showRoi, - const std::optional &showSpots, - const std::optional &showBeamCenter, - const std::optional &saturation, - const std::optional &jpegQuality, - const std::optional &showResRing, - const std::optional &color, - Pistache::Http::ResponseWriter &response) override; void image_buffer_image_tiff_get(const std::optional &id, Pistache::Http::ResponseWriter &response) override; diff --git a/broker/gen/api/DefaultApi.cpp b/broker/gen/api/DefaultApi.cpp index bc04315d..f98fad6c 100644 --- a/broker/gen/api/DefaultApi.cpp +++ b/broker/gen/api/DefaultApi.cpp @@ -72,7 +72,6 @@ void DefaultApi::setupRoutes() { Routes::Post(*router, base + "/image_buffer/clear", Routes::bind(&DefaultApi::image_buffer_clear_post_handler, this)); Routes::Get(*router, base + "/image_buffer/image.cbor", Routes::bind(&DefaultApi::image_buffer_image_cbor_get_handler, this)); Routes::Get(*router, base + "/image_buffer/image.jpeg", Routes::bind(&DefaultApi::image_buffer_image_jpeg_get_handler, this)); - Routes::Get(*router, base + "/image_buffer/image.png", Routes::bind(&DefaultApi::image_buffer_image_png_get_handler, this)); Routes::Get(*router, base + "/image_buffer/image.tiff", Routes::bind(&DefaultApi::image_buffer_image_tiff_get_handler, this)); Routes::Get(*router, base + "/image_buffer/start.cbor", Routes::bind(&DefaultApi::image_buffer_start_cbor_get_handler, this)); Routes::Get(*router, base + "/image_buffer/status", Routes::bind(&DefaultApi::image_buffer_status_get_handler, this)); @@ -1078,99 +1077,6 @@ void DefaultApi::image_buffer_image_jpeg_get_handler(const Pistache::Rest::Reque response.send(Pistache::Http::Code::Internal_Server_Error, e.what()); } -} -void DefaultApi::image_buffer_image_png_get_handler(const Pistache::Rest::Request &request, Pistache::Http::ResponseWriter response) { - try { - - - // Getting the query params - auto idQuery = request.query().get("id"); - std::optional id; - if(idQuery.has_value()){ - int64_t valueQuery_instance; - if(fromStringValue(idQuery.value(), valueQuery_instance)){ - id = valueQuery_instance; - } - } - auto showUserMaskQuery = request.query().get("show_user_mask"); - std::optional showUserMask; - if(showUserMaskQuery.has_value()){ - bool valueQuery_instance; - if(fromStringValue(showUserMaskQuery.value(), valueQuery_instance)){ - showUserMask = valueQuery_instance; - } - } - auto showRoiQuery = request.query().get("show_roi"); - std::optional showRoi; - if(showRoiQuery.has_value()){ - bool valueQuery_instance; - if(fromStringValue(showRoiQuery.value(), valueQuery_instance)){ - showRoi = valueQuery_instance; - } - } - auto showSpotsQuery = request.query().get("show_spots"); - std::optional showSpots; - if(showSpotsQuery.has_value()){ - bool valueQuery_instance; - if(fromStringValue(showSpotsQuery.value(), valueQuery_instance)){ - showSpots = valueQuery_instance; - } - } - auto showBeamCenterQuery = request.query().get("show_beam_center"); - std::optional showBeamCenter; - if(showBeamCenterQuery.has_value()){ - bool valueQuery_instance; - if(fromStringValue(showBeamCenterQuery.value(), valueQuery_instance)){ - showBeamCenter = valueQuery_instance; - } - } - auto saturationQuery = request.query().get("saturation"); - std::optional saturation; - if(saturationQuery.has_value()){ - float valueQuery_instance; - if(fromStringValue(saturationQuery.value(), valueQuery_instance)){ - saturation = valueQuery_instance; - } - } - auto jpegQualityQuery = request.query().get("jpeg_quality"); - std::optional jpegQuality; - if(jpegQualityQuery.has_value()){ - int64_t valueQuery_instance; - if(fromStringValue(jpegQualityQuery.value(), valueQuery_instance)){ - jpegQuality = valueQuery_instance; - } - } - auto showResRingQuery = request.query().get("show_res_ring"); - std::optional showResRing; - if(showResRingQuery.has_value()){ - float valueQuery_instance; - if(fromStringValue(showResRingQuery.value(), valueQuery_instance)){ - showResRing = valueQuery_instance; - } - } - auto colorQuery = request.query().get("color"); - std::optional color; - if(colorQuery.has_value()){ - std::string valueQuery_instance; - if(fromStringValue(colorQuery.value(), valueQuery_instance)){ - color = valueQuery_instance; - } - } - - try { - this->image_buffer_image_png_get(id, showUserMask, showRoi, showSpots, showBeamCenter, saturation, jpegQuality, showResRing, color, response); - } catch (Pistache::Http::HttpError &e) { - response.send(static_cast(e.code()), e.what()); - return; - } catch (std::exception &e) { - this->handleOperationException(e, response); - return; - } - - } catch (std::exception &e) { - response.send(Pistache::Http::Code::Internal_Server_Error, e.what()); - } - } void DefaultApi::image_buffer_image_tiff_get_handler(const Pistache::Rest::Request &request, Pistache::Http::ResponseWriter response) { try { diff --git a/broker/gen/api/DefaultApi.h b/broker/gen/api/DefaultApi.h index 076ee5ac..624ee666 100644 --- a/broker/gen/api/DefaultApi.h +++ b/broker/gen/api/DefaultApi.h @@ -107,7 +107,6 @@ private: void image_buffer_clear_post_handler(const Pistache::Rest::Request &request, Pistache::Http::ResponseWriter response); void image_buffer_image_cbor_get_handler(const Pistache::Rest::Request &request, Pistache::Http::ResponseWriter response); void image_buffer_image_jpeg_get_handler(const Pistache::Rest::Request &request, Pistache::Http::ResponseWriter response); - void image_buffer_image_png_get_handler(const Pistache::Rest::Request &request, Pistache::Http::ResponseWriter response); void image_buffer_image_tiff_get_handler(const Pistache::Rest::Request &request, Pistache::Http::ResponseWriter response); void image_buffer_start_cbor_get_handler(const Pistache::Rest::Request &request, Pistache::Http::ResponseWriter response); void image_buffer_status_get_handler(const Pistache::Rest::Request &request, Pistache::Http::ResponseWriter response); @@ -451,22 +450,6 @@ private: /// Color scale for preview image: 0 - indigo, 1 - viridis, 2 - B/W, 3 - heat (optional, default to "indigo") virtual void image_buffer_image_jpeg_get(const std::optional &id, const std::optional &showUserMask, const std::optional &showRoi, const std::optional &showSpots, const std::optional &showBeamCenter, const std::optional &saturation, const std::optional &jpegQuality, const std::optional &showResRing, const std::optional &color, Pistache::Http::ResponseWriter &response) = 0; /// - /// Get preview image in PNG format using custom settings - /// - /// - /// - /// - /// Image ID in the image buffer. Special values: -1 - last image in the buffer, -2: last indexed image in the buffer (optional, default to -1L) - /// Show user mask (optional, default to false) - /// Show ROI areas on the image (optional, default to false) - /// Show spot finding results on the image (optional, default to true) - /// Show beam center on the image (optional, default to true) - /// Saturation value to set contrast in the preview image (optional, default to 10.0f) - /// Quality of JPEG image (100 - highest; 0 - lowest) (optional, default to 100L) - /// Show resolution ring, provided in Angstrom (optional, default to 0.1f) - /// Color scale for preview image: 0 - indigo, 1 - viridis, 2 - B/W, 3 - heat (optional, default to "indigo") - virtual void image_buffer_image_png_get(const std::optional &id, const std::optional &showUserMask, const std::optional &showRoi, const std::optional &showSpots, const std::optional &showBeamCenter, const std::optional &saturation, const std::optional &jpegQuality, const std::optional &showResRing, const std::optional &color, Pistache::Http::ResponseWriter &response) = 0; - /// /// Get preview image in TIFF format /// /// diff --git a/broker/jfjoch_api.yaml b/broker/jfjoch_api.yaml index 9192704b..65d5d7ec 100644 --- a/broker/jfjoch_api.yaml +++ b/broker/jfjoch_api.yaml @@ -3103,42 +3103,6 @@ paths: application/json: schema: $ref: '#/components/schemas/error_message' - /image_buffer/image.png: - get: - summary: Get preview image in PNG format using custom settings - parameters: - - $ref: '#/components/parameters/image_id' - - $ref: '#/components/parameters/show_user_mask' - - $ref: '#/components/parameters/show_roi' - - $ref: '#/components/parameters/show_spots' - - $ref: '#/components/parameters/show_beam_center' - - $ref: '#/components/parameters/saturation' - - $ref: '#/components/parameters/jpeg_quality' - - $ref: '#/components/parameters/resolution_ring' - - $ref: '#/components/parameters/color_scale' - responses: - "200": - description: Preview image - content: - image/jpeg: - schema: - type: string - format: binary - "404": - description: Image not present in the buffer - either not yet measured or already replaced by a next image. - "400": - description: Input parsing or validation error - content: - text/plain: - schema: - type: string - description: Exception error - "500": - description: Error within Jungfraujoch code - see output message. - content: - application/json: - schema: - $ref: '#/components/schemas/error_message' /image_buffer/image.tiff: get: summary: Get preview image in TIFF format diff --git a/broker/redoc-static.html b/broker/redoc-static.html index 178d0ddf..32a68173 100644 --- a/broker/redoc-static.html +++ b/broker/redoc-static.html @@ -387,7 +387,7 @@ Get user mask of the detector (binary) Get user mask of the detector (TIFF) " aria-expanded="false" class="sc-cgHfjM ixknQI">
putUpload user mask of the detector

Error within Jungfraujoch code - see output message.

Response samples

Content type
application/json
{
  • "msg": "Detector in wrong state",
  • "reason": "WrongDAQState"
}

Get preview image in PNG format using custom settings

query Parameters
id
integer <int64> >= -2
Default: -1

Image ID in the image buffer. Special values: -1 - last image in the buffer, -2: last indexed image in the buffer

-
show_user_mask
boolean
Default: false

Show user mask

-
show_roi
boolean
Default: false

Show ROI areas on the image

-
show_spots
boolean
Default: true

Show spot finding results on the image

-
show_beam_center
boolean
Default: true

Show beam center on the image

-
saturation
number <float> [ -32767 .. 32767 ]
Default: 10

Saturation value to set contrast in the preview image

-
jpeg_quality
integer <int64> [ 0 .. 100 ]
Default: 100

Quality of JPEG image (100 - highest; 0 - lowest)

-
show_res_ring
number <float> [ 0.1 .. 100 ]
Default: 0.1

Show resolution ring, provided in Angstrom

-
color
string
Default: "indigo"
Enum: "indigo" "viridis" "bw" "heat"

Color scale for preview image: 0 - indigo, 1 - viridis, 2 - B/W, 3 - heat

-

Responses

Response samples

Content type
application/json
{
  • "msg": "Detector in wrong state",
  • "reason": "WrongDAQState"
}

Get preview image in TIFF format

query Parameters
id
integer <int64> >= -2
Default: -1
http://localhost:5232/image_buffer/image.jpeg

Response samples

Content type
application/json
{
  • "msg": "Detector in wrong state",
  • "reason": "WrongDAQState"
}

Get preview image in TIFF format

query Parameters
id
integer <int64> >= -2
Default: -1

Image ID in the image buffer. Special values: -1 - last image in the buffer, -2: last indexed image in the buffer

Responses

Response samples

Content type
application/json
{
  • "msg": "Detector in wrong state",
  • "reason": "WrongDAQState"
}

Get status of the image buffers

http://localhost:5232/image_buffer/clear

Response samples

Content type
application/json
{
  • "msg": "Detector in wrong state",
  • "reason": "WrongDAQState"
}

Get status of the image buffers

Can be run at any stage of Jungfraujoch operation, including during data collection. @@ -1391,13 +1363,13 @@ then image might be replaced in the buffer between calling /images and /image.cb " class="sc-eVqvcJ sc-fszimp sc-etsjJW kIppRw jnwENr ljKHqG">

Error within Jungfraujoch code - see output message.

Response samples

Content type
application/json
{
  • "min_image_number": 0,
  • "max_image_number": 0,
  • "image_numbers": [
    ],
  • "total_slots": 0,
  • "available_slots": 0
}

Get Jungfraujoch version of jfjoch_broker

Responses

Response samples

Content type
application/json
{
  • "min_image_number": 0,
  • "max_image_number": 0,
  • "image_numbers": [
    ],
  • "total_slots": 0,
  • "available_slots": 0
}

Get Jungfraujoch version of jfjoch_broker

Responses