jfjoch_viewer: Add 3D image (WIP)
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 11m41s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 11m48s
Build Packages / Generate python client (push) Successful in 36s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 12m21s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 12m59s
Build Packages / Create release (push) Has been skipped
Build Packages / Build documentation (push) Successful in 38s
Build Packages / build:rpm (rocky8) (push) Successful in 12m56s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 13m2s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 13m0s
Build Packages / build:rpm (rocky9) (push) Successful in 13m45s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 6m52s
Build Packages / Unit tests (push) Successful in 52m53s

This commit is contained in:
2025-11-01 18:41:01 +01:00
parent 963c7097bc
commit 563878dde5
6 changed files with 309 additions and 0 deletions
+2
View File
@@ -56,6 +56,8 @@ ADD_EXECUTABLE(jfjoch_viewer jfjoch_viewer.cpp JFJochViewerWindow.cpp JFJochView
windows/JFJochViewerReflectionListWindow.h
widgets/JFJochOneOverResSqChartView.cpp
widgets/JFJochOneOverResSqChartView.h
windows/JFJochViewer3DDiffractionWindow.cpp
windows/JFJochViewer3DDiffractionWindow.h
)
TARGET_LINK_LIBRARIES(jfjoch_viewer Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Charts Qt6::DBus
+17
View File
@@ -66,6 +66,12 @@ JFJochViewerMenu::JFJochViewerMenu(QWidget *parent) : QMenuBar(parent) {
connect(toggleProcessingWindowAction, &QAction::toggled,
this, &JFJochViewerMenu::processingWindowToggled);
toggleDiffractionWindowAction = windowMenu->addAction("3D diffraction geometry");
toggleDiffractionWindowAction->setCheckable(true);
toggleDiffractionWindowAction->setChecked(false);
connect(toggleDiffractionWindowAction, &QAction::toggled,
this, &JFJochViewerMenu::diffractionWindowToggled);
QMenu *helpMenu = addMenu("Help");
// Add "About" action
const QAction *aboutAction = helpMenu->addAction("About");
@@ -175,6 +181,17 @@ void JFJochViewerMenu::processingWindowClosing() {
toggleProcessingWindowAction->setChecked(false);
}
void JFJochViewerMenu::diffractionWindowToggled(bool checked) {
if (checked)
emit openDiffractionWindow();
else
emit closeDiffractionWindow();
}
void JFJochViewerMenu::diffractionWindowClosing() {
toggleDiffractionWindowAction->setChecked(false);
}
void JFJochViewerMenu::openHttpSelected() {
QDialog dialog(this);
dialog.setWindowTitle("Open HTTP Connection");
+5
View File
@@ -13,6 +13,7 @@ class JFJochViewerMenu : public QMenuBar {
QAction *toggleSpotWindowAction = nullptr;
QAction *toggleReflectionWindowAction = nullptr;
QAction *toggleProcessingWindowAction = nullptr;
QAction *toggleDiffractionWindowAction = nullptr;
public:
explicit JFJochViewerMenu(QWidget *parent = nullptr);
~JFJochViewerMenu() override = default;
@@ -30,12 +31,15 @@ signals:
void closeReflectionListWindow();
void openProcessingWindow();
void closeProcessingWindow();
void openDiffractionWindow();
void closeDiffractionWindow();
public slots:
void imageListWindowClosing();
void metadataWindowClosing();
void spotListWindowClosing();
void reflectionListWindowClosing();
void processingWindowClosing();
void diffractionWindowClosing();
private slots:
void aboutSelected();
void quitSelected();
@@ -47,6 +51,7 @@ private slots:
void spotListWindowToggled(bool checked);
void reflectionListWindowToggled(bool checked);
void processingWindowToggled(bool checked);
void diffractionWindowToggled(bool checked);
};
+12
View File
@@ -14,6 +14,7 @@
#include "windows/JFJochViewerImageListWindow.h"
#include "windows/JFJochViewerMetadataWindow.h"
#include "dbus/JFJochViewerAdaptor.h"
#include "windows/JFJochViewer3DDiffractionWindow.h"
#include "windows/JFJochViewerProcessingWindow.h"
#include "windows/JFJochViewerSpotListWindow.h"
#include "windows/JFJochViewerReflectionListWindow.h"
@@ -84,6 +85,7 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString
auto spotWindow = new JFJochViewerSpotListWindow(this);
auto reflectionWindow = new JFJochViewerReflectionListWindow(this);
auto processingWindow = new JFJochViewerProcessingWindow(spot_finding_settings, indexing_settings, this);
auto diffractionWindow = new JFJochViewer3DDiffractionWindow(this);
new JFJochViewerAdaptor(this);
@@ -176,6 +178,9 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString
connect(reading_worker, &JFJochImageReadingWorker::imageLoaded,
dataset_info, &JFJochViewerDatasetInfo::imageLoaded);
connect(reading_worker, &JFJochImageReadingWorker::imageLoaded,
dataset_info, &JFJochViewerDatasetInfo::imageLoaded);
connect(dataset_info, &JFJochViewerDatasetInfo::imageSelected,
reading_worker, &JFJochImageReadingWorker::LoadImage);
@@ -229,6 +234,13 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString
connect(menuBar, &JFJochViewerMenu::closeProcessingWindow,
processingWindow, &JFJochViewerProcessingWindow::close);
connect(menuBar, &JFJochViewerMenu::openDiffractionWindow,
diffractionWindow, &JFJochViewer3DDiffractionWindow::open);
connect(menuBar, &JFJochViewerMenu::closeDiffractionWindow,
diffractionWindow, &JFJochViewer3DDiffractionWindow::close);
connect(diffractionWindow, &JFJochViewer3DDiffractionWindow::closing,
menuBar, &JFJochViewerMenu::diffractionWindowClosing);
connect(processingWindow, &JFJochViewerProcessingWindow::closing,
menuBar, &JFJochViewerMenu::processingWindowClosing);
@@ -0,0 +1,219 @@
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include "JFJochViewer3DDiffractionWindow.h"
#include <Qt3DRender/QPointLight>
#include <Qt3DRender/QCamera>
#include <Qt3DExtras/QPhongAlphaMaterial>
#include <QVBoxLayout>
#include <QCloseEvent>
using V3 = QVector3D;
static QMatrix4x4 fromBasisPose(const V3& t, const V3& z, const V3& x) {
// Build a pose matrix from origin t, forward z, right x; y = z x x
V3 zz = z.normalized();
V3 xx = x.normalized();
V3 yy = QVector3D::crossProduct(zz, xx).normalized();
QMatrix4x4 M;
M.setColumn(0, QVector4D(xx, 0));
M.setColumn(1, QVector4D(yy, 0));
M.setColumn(2, QVector4D(zz, 0));
M.setColumn(3, QVector4D(t, 1));
return M;
}
JFJochViewer3DDiffractionWindow::JFJochViewer3DDiffractionWindow(QWidget* parent) : QMainWindow(parent) {
setWindowTitle("Diffraction 3D");
auto centralWidget = new QWidget(this);
setCentralWidget(centralWidget);
view = new Qt3DExtras::Qt3DWindow();
container = QWidget::createWindowContainer(view, centralWidget);
auto layout = new QVBoxLayout(centralWidget);
layout->setContentsMargins(0,0,0,0);
layout->addWidget(container);
root = new Qt3DCore::QEntity();
view->setRootEntity(root);
// Camera + controller
auto cam = view->camera();
cam->lens()->setPerspectiveProjection(45.f, 1.6f, 0.01f, 1000.f);
cam->setPosition(V3(3, 2, 3));
cam->setViewCenter(V3(0, 0, 0));
auto camCtrl = new Qt3DExtras::QOrbitCameraController(root);
camCtrl->setCamera(cam);
// Light
auto light = new Qt3DRender::QPointLight(root);
auto lightEntity = new Qt3DCore::QEntity(root);
lightEntity->addComponent(light);
auto lt = new Qt3DCore::QTransform();
lt->setTranslation(V3(5, 5, 5));
lightEntity->addComponent(lt);
rebuildScene();
}
void JFJochViewer3DDiffractionWindow::imageLoaded(std::shared_ptr<const JFJochReaderImage> image) {
if (image) {
geom_ = image->Dataset().experiment.GetDiffractionGeometry();
lattice_ = image->ImageData().indexing_lattice;
axis_ = image->Dataset().experiment.GetGoniometer();
} else {
lattice_ = std::nullopt;
axis_ = std::nullopt;
}
rebuildScene();
}
Qt3DCore::QEntity* JFJochViewer3DDiffractionWindow::makeArrow(Qt3DCore::QEntity* parent, const V3& from, const V3& to,
const QColor& color, float radius) {
auto group = new Qt3DCore::QEntity(parent);
V3 dir = to - from;
float len = dir.length();
if (len <= 1e-6f) return group;
V3 z = dir / len;
V3 x = qAbs(QVector3D::dotProduct(z, V3(0,1,0))) > 0.9f ? V3(1,0,0) : V3(0,1,0);
QMatrix4x4 basis = fromBasisPose(from, z, x);
// Shaft
auto shaft = new Qt3DCore::QEntity(group);
auto shaftMesh = new Qt3DExtras::QCylinderMesh();
shaftMesh->setLength(len * 0.85f);
shaftMesh->setRadius(radius);
auto shaftTr = new Qt3DCore::QTransform();
QMatrix4x4 M = basis;
M.translate(0, 0, len * 0.425f);
shaftTr->setMatrix(M);
auto mat = new Qt3DExtras::QPhongMaterial(group);
mat->setDiffuse(color);
shaft->addComponent(shaftMesh);
shaft->addComponent(shaftTr);
shaft->addComponent(mat);
// Head
auto head = new Qt3DCore::QEntity(group);
auto headMesh = new Qt3DExtras::QConeMesh();
headMesh->setLength(len * 0.15f);
headMesh->setTopRadius(0.f);
headMesh->setBottomRadius(radius * 2.2f);
auto headTr = new Qt3DCore::QTransform();
QMatrix4x4 H = basis;
H.translate(0, 0, len * 0.925f);
headTr->setMatrix(H);
head->addComponent(headMesh);
head->addComponent(headTr);
head->addComponent(mat);
return group;
}
Qt3DCore::QEntity* JFJochViewer3DDiffractionWindow::makePlane(Qt3DCore::QEntity* parent, const QSizeF& size,
const QMatrix4x4& pose, const QColor& color) {
auto e = new Qt3DCore::QEntity(parent);
auto mesh = new Qt3DExtras::QPlaneMesh();
mesh->setWidth(size.width());
mesh->setHeight(size.height());
mesh->setMeshResolution(QSize(2,2));
auto tr = new Qt3DCore::QTransform();
tr->setMatrix(pose);
auto mat = new Qt3DExtras::QPhongAlphaMaterial(e);
mat->setDiffuse(color);
mat->setAlpha(0.3f);
e->addComponent(mesh);
e->addComponent(tr);
e->addComponent(mat);
return e;
}
void JFJochViewer3DDiffractionWindow::rebuildScene() {
// Clear previous children
for (auto* child : root->children()) child->setParent(nullptr);
// Sample: sphere at origin
{
sampleEntity = new Qt3DCore::QEntity(root);
auto mesh = new Qt3DExtras::QSphereMesh();
mesh->setRadius(0.2f);
auto tr = new Qt3DCore::QTransform();
tr->setTranslation(V3(0,0,0));
auto mat = new Qt3DExtras::QPhongAlphaMaterial(sampleEntity);
mat->setDiffuse(QColor("#6fa8dc"));
mat->setAlpha(0.6f);
sampleEntity->addComponent(mesh);
sampleEntity->addComponent(tr);
sampleEntity->addComponent(mat);
}
// Lattice vectors as arrows (scale to reasonable scene units)
if (lattice_.has_value()) {
latticeEntity = new Qt3DCore::QEntity(root);
auto a = lattice_->Vec0(); auto b = lattice_->Vec1(); auto c = lattice_->Vec2();
float s = 0.01f; // scale Angstroms to scene units (1 unit = 100 Å)
auto A = makeArrow(latticeEntity, V3(0,0,0), V3(a.x*s, a.y*s, a.z*s), QColor("#e6194B"));
auto B = makeArrow(latticeEntity, V3(0,0,0), V3(b.x*s, b.y*s, b.z*s), QColor("#3cb44b"));
auto C = makeArrow(latticeEntity, V3(0,0,0), V3(c.x*s, c.y*s, c.z*s), QColor("#4363d8"));
Q_UNUSED(A); Q_UNUSED(B); Q_UNUSED(C);
}
// Detector plane using PONI rotation and beam/distance
{
// Build detector basis in lab coords.
// Assume beam travels +Z from sample at origin.
// Detector center is at z = distance (meters), with X/Y axes rotated by PONI.
const float px_mm = geom_.GetPixelSize_mm();
// Example: 4k x 4k display area; replace with your actual detector size if available.
const int nx = 4096, ny = 4096;
const float w = nx * px_mm / 1000.f; // meters
const float h = ny * px_mm / 1000.f; // meters
const float zc = geom_.GetDetectorDistance_mm() / 1000.f;
// PONI rotation matrix (assumed aligns detector normal)
const auto& R = geom_.GetPoniRotMatrix();
const auto x = R * Coord(1,0,0);
const auto y = R * Coord(0,1,0);
const auto z = R * Coord(0,0,1);
// Convert RotMatrix to QMatrix4x4
QMatrix4x4 rot;
rot.setRow(0, QVector4D(x[0], x[1], x[2], 0));
rot.setRow(1, QVector4D(y[0], y[1], y[2], 0));
rot.setRow(2, QVector4D(z[0], z[1], z[2], 0));
rot.setRow(3, QVector4D(0,0,0,1));
QMatrix4x4 pose = rot;
pose.translate(0, 0, zc);
detectorEntity = makePlane(root, QSizeF(w, h), pose, QColor("#ff9900"));
// Draw beam direction
makeArrow(root, V3(0,0,0), V3(0,0,zc*1.1f), Qt::yellow, 0.01f);
}
// Rotation axis (if present)
if (axis_.has_value()) {
axisEntity = new Qt3DCore::QEntity(root);
auto u = axis_->GetAxis();
V3 p0(0,0,0);
V3 p1(u.x, u.y, u.z);
p1.normalize();
p1 *= 0.6f;
makeArrow(axisEntity, p0, p1, QColor("#00ffff"), 0.015f);
}
}
void JFJochViewer3DDiffractionWindow::closeEvent(QCloseEvent *event) {
event->accept();
emit closing();
}
void JFJochViewer3DDiffractionWindow::open() {
show();
raise();
activateWindow();
}
@@ -0,0 +1,54 @@
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
// A QWidget embedding a Qt3D scene that visualizes diffraction setup
#pragma once
#include <QMainWindow>
#include <QWidget>
#include <Qt3DCore/QEntity>
#include <Qt3DExtras/Qt3DWindow>
#include <Qt3DExtras/QOrbitCameraController>
#include <Qt3DExtras/QPhongMaterial>
#include <Qt3DExtras/QSphereMesh>
#include <Qt3DExtras/QConeMesh>
#include <Qt3DExtras/QCylinderMesh>
#include <Qt3DExtras/QPlaneMesh>
#include <Qt3DCore/QTransform>
#include "../../common/CrystalLattice.h"
#include "../../common/DiffractionGeometry.h"
#include "../../common/GoniometerAxis.h"
#include "../../reader/JFJochReaderImage.h"
class JFJochViewer3DDiffractionWindow : public QMainWindow {
Q_OBJECT
public:
explicit JFJochViewer3DDiffractionWindow(QWidget* parent = nullptr);
private:
Qt3DExtras::Qt3DWindow* view = nullptr;
QWidget* container = nullptr;
Qt3DCore::QEntity* root = nullptr;
Qt3DCore::QEntity* sampleEntity = nullptr;
Qt3DCore::QEntity* latticeEntity = nullptr;
Qt3DCore::QEntity* detectorEntity = nullptr;
Qt3DCore::QEntity* axisEntity = nullptr;
DiffractionGeometry geom_;
std::optional<CrystalLattice> lattice_;
std::optional<GoniometerAxis> axis_;
void rebuildScene();
Qt3DCore::QEntity* makeArrow(Qt3DCore::QEntity* parent, const QVector3D& from, const QVector3D& to,
const QColor& color, float radius = 0.02f);
Qt3DCore::QEntity* makePlane(Qt3DCore::QEntity* parent, const QSizeF& size, const QMatrix4x4& pose,
const QColor& color);
void closeEvent(QCloseEvent *event) override;
signals:
void closing();
public slots:
void open();
void imageLoaded(std::shared_ptr<const JFJochReaderImage> image);
};