Files
Jungfraujoch/viewer/JFJochViewerMenu.cpp
T
leonarski_f 451310f43d
Build Packages / Unit tests (push) Successful in 1h32m35s
Build Packages / build:windows:cuda (push) Successful in 18m0s
Build Packages / build:viewer-tgz:cpu (push) Successful in 7m37s
Build Packages / build:viewer-tgz:cuda (push) Successful in 8m55s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 14m13s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 14m11s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 14m35s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 13m57s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 14m23s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 12m45s
Build Packages / build:rpm (rocky8) (push) Successful in 11m39s
Build Packages / build:rpm (rocky9) (push) Successful in 14m0s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 13m42s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 12m38s
Build Packages / DIALS test (push) Successful in 14m55s
Build Packages / XDS test (durin plugin) (push) Successful in 7m11s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 9m7s
Build Packages / XDS test (neggia plugin) (push) Successful in 8m34s
Build Packages / Generate python client (push) Successful in 28s
Build Packages / Build documentation (push) Successful in 1m3s
Build Packages / Create release (push) Skipped
Build Packages / build:windows:nocuda (push) Successful in 9m55s
v1.0.0-rc.158 (#68)
This is an UNSTABLE release. It includes many experimental features, as well as many AI generated fixes. We recommend using rc.152 for production use.

* Analysis: The azimuthal-integration solid-angle correction now follows the incidence angle to the detector normal (`cos^3` of that angle) instead of `cos^3(2*theta)`, so it is correct for a tilted detector and matches PyFAI `solidAngleArray` and MAX IV azint (unchanged for an untilted detector). Crystal geometry refinement (`XtalOptimizer`) no longer silently ignores an imported PONI `rot3` (rotation about the beam): it is applied as a fixed rotation in the residual so refinement stays consistent with the rest of the pipeline. Polarization and azimuthal binning already honoured `rot3` through the full PONI rotation.
* jfjoch_viewer: Open datasets on the WSL2/UNC filesystem (paths starting `\\`); write processing outputs next to the input file, with a Browse button and independent `_process.h5` / merged `.mtz`/`.cif` toggles; and show the determined space group in the merge-statistics window.
* rugnux: Accept an absolute `-o` output prefix in offline processing.
* Packaging: The self-contained Linux viewer `.tgz` now bundles cuFFT, so it runs without a system CUDA toolkit (`.deb`/`.rpm` are unchanged, distro-managed).
* Docs: Bring the analysis references up to date with the code. `docs/CPU_DATA_ANALYSIS.md` now reflects the unified profile-fit Bragg integration engine, multi-lattice indexing, azimuthal phi binning, the radial parallax/bandwidth profile with sub-pixel centring, the rot3d capture-fraction handling and the automatic CC1/2 resolution cutoff, and drops the descriptions of features that were never implemented (French-Wilson amplitudes, the still excitation-error partiality model); `docs/RUGNUX.md` documents the new `--resolution-cutoff`/`--resolution-cc-target`/`--resolution-shells`, `--min-captured-fraction`, `--mosaicity`, `--reference-column`, the azimuthal correction toggles and the geometry-override options, and corrects the `-N` default. The outdated in-source design notes (ICE_RING_DETECTION, BRAGG_INTEGRATION_ENGINE, NEXTGEN_INTEGRATOR) are removed.Reviewed-on: #68

Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
2026-07-12 19:42:29 +02:00

264 lines
9.7 KiB
C++

// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include "JFJochViewerMenu.h"
#include <QMessageBox>
#include <QApplication>
#include <QDir>
#include <QFileDialog>
#include <QString>
#include <QGridLayout>
#include <QLabel>
#include <QLineEdit>
#include <QSpinBox>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QDockWidget>
#include "JFJochViewerWindow.h"
#include "windows/JFJochLicenseWindow.h"
#include "../common/GitInfo.h"
#include "../common/CUDAWrapper.h"
JFJochViewerMenu::JFJochViewerMenu(QWidget *parent) : QMenuBar(parent) {
// Create "File" menu
QMenu *fileMenu = addMenu("File");
QAction *openAction = fileMenu->addAction("Open");
openAction->setShortcut(QKeySequence::Open);
connect(openAction, &QAction::triggered, this, &JFJochViewerMenu::openSelected);
QAction *openHttpAction = fileMenu->addAction("Open HTTP");
openHttpAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_H));
connect(openHttpAction, &QAction::triggered, this, &JFJochViewerMenu::openHttpSelected);
QAction *closeAction = fileMenu->addAction("Close");
closeAction->setShortcut(QKeySequence::Close);
connect(closeAction, &QAction::triggered, this, &JFJochViewerMenu::closeSelected);
// Add seperator
fileMenu->addSeparator();
// Add "Quit" action
QAction *quitAction = fileMenu->addAction("Quit");
quitAction->setShortcut(QKeySequence::Quit);
connect(quitAction, &QAction::triggered, this, &JFJochViewerMenu::quitSelected);
QMenu *maskMenu = addMenu("Mask");
const QAction *saveMaskTiff = maskMenu->addAction("Save User Mask as TIFF...");
connect(saveMaskTiff, &QAction::triggered, this, &JFJochViewerMenu::saveUserMaskAsTiffSelected);
const QAction *loadMaskReplace = maskMenu->addAction("Load User Mask from TIFF (replace)...");
connect(loadMaskReplace, &QAction::triggered, [this] { openLoadUserMaskTiff(true); });
const QAction *loadMaskAdd = maskMenu->addAction("Load User Mask from TIFF (add)...");
connect(loadMaskAdd, &QAction::triggered, [this] { openLoadUserMaskTiff(false); });
const QAction *uploadMask = maskMenu->addAction("&Upload User Mask");
connect(uploadMask, &QAction::triggered, this, &JFJochViewerMenu::uploadUserMaskAction);
const QAction *clearMask = maskMenu->addAction("Clear User Mask");
connect(clearMask, &QAction::triggered, [this] () {emit clearUserMaskSelected();});
windowMenu = addMenu("Window");
QMenu *dockMenu = addMenu("Charts");
const QAction *dockCalibration = dockMenu->addAction("New dataset info plot");
connect(dockCalibration, &QAction::triggered, [this] { emit openDatasetInfo();});
QMenu *viewMenu = addMenu("View");
const QAction *imageLayout = viewMenu->addAction("Image layout");
connect(imageLayout, &QAction::triggered, this, &JFJochViewerMenu::imageLayoutSelected);
const QAction *processingLayout = viewMenu->addAction("Processing layout");
connect(processingLayout, &QAction::triggered, this, &JFJochViewerMenu::processingLayoutSelected);
viewMenu->addSeparator();
const QAction *resetLayout = viewMenu->addAction("Reset layout");
connect(resetLayout, &QAction::triggered, this, &JFJochViewerMenu::resetLayoutSelected);
QMenu *helpMenu = addMenu("Help");
// Add "About" action
const QAction *aboutAction = helpMenu->addAction("About");
connect(aboutAction, &QAction::triggered, this, &JFJochViewerMenu::aboutSelected);
const QAction *licensesAction = helpMenu->addAction("Third-party Licenses");
connect(licensesAction, &QAction::triggered, this, &JFJochViewerMenu::licensesSelected);
}
void JFJochViewerMenu::AddWindowEntry(JFJochHelperWindow *window, const QString &name) {
auto action = windowMenu->addAction(name);
action->setCheckable(true);
action->setChecked(false);
connect(action, &QAction::toggled, window, &JFJochHelperWindow::toggle);
connect(window, &JFJochHelperWindow::closing, [action] {action->setChecked(false);});
}
void JFJochViewerMenu::AddDockEntry(QDockWidget *dock, const QString &name) {
auto *action = dock->toggleViewAction(); // checkable, stays in sync with the dock visibility
action->setText(name);
windowMenu->addAction(action);
}
void JFJochViewerMenu::aboutSelected() {
QPixmap pixmap(":/jfjoch.png");
QString version(QString::fromStdString(jfjoch_version()));
// Create QMessageBox
QMessageBox aboutBox(this);
aboutBox.setWindowTitle("About");
QString about_text = "<h3>Jungfraujoch Image Viewer</h3>" // Use HTML for improved style
"<p><b>Version:</b> " + version + "</p>"
"<p><b>Copyright:</b> 2019-2025 Paul Scherrer Institute</p>"
"<p><b>Author:</b> Filip Leonarski &lt;filip.leonarski@psi.ch&gt;</p>"
"<p><b>Links:</b> <a href='https://jungfraujoch.readthedocs.io'>Documentation</a>, "
"<a href='https://gitea.psi.ch/mx/jungfraujoch'>Repository</a></p>";
#ifdef JFJOCH_USE_CUDA
about_text += QString("<p>Compiled with CUDA support. %1 GPU(s) detected</p>")
.arg(QString::number(get_gpu_count()));
#else
about_text += "<p><b>Note:</b> CUDA is not available on this system.</p>";
#endif
// Set detailed text with proper spacing and structure
aboutBox.setText(about_text);
// Optional: Add a larger informative text, if needed
aboutBox.setInformativeText(
"This program comes with ABSOLUTELY NO WARRANTY. "
"This is free software, and you are welcome to redistribute it "
"under certain conditions (GPLv3).\n\n"
"Development supported by Innovation Project (101.535.1 IP-ENG) from Innosuisse."
);
// Set an icon
aboutBox.setIconPixmap(pixmap);
// Execute the dialog
aboutBox.exec();
}
void JFJochViewerMenu::licensesSelected() {
// Lazily create the modeless window and keep it; raise it if already open.
if (licenseWindow == nullptr)
licenseWindow = new JFJochLicenseWindow(window());
licenseWindow->show();
licenseWindow->raise();
licenseWindow->activateWindow();
}
void JFJochViewerMenu::openSelected() {
QString fileName = QFileDialog::getOpenFileName(
this,
"Open File", // Dialog title
"", // Default folder
"HDF5 Master Files (*_master.h5 *_process.h5);; HDF5 Files (*.h5);;All Files (*)" // Filter for .h5 files
);
if (!fileName.isEmpty())
// Qt returns '/'-separated paths even on Windows, but HDF5's H5Fopen needs native separators
// to recognise a UNC path (\\host\share, e.g. the WSL2 filesystem): '//host/share' is rejected,
// while 'C:/...' still works, which is exactly the reported symptom. No-op on Linux.
emit fileOpenSelected(QDir::toNativeSeparators(fileName), 0, 1, false);
}
void JFJochViewerMenu::closeSelected() {
emit fileCloseSelected();
}
void JFJochViewerMenu::quitSelected() {
QApplication::quit();
}
void JFJochViewerMenu::openHttpSelected() {
QDialog dialog(this);
dialog.setWindowTitle("Open HTTP Connection");
dialog.setMinimumWidth(400);
QGridLayout *layout = new QGridLayout(&dialog);
// Scheme selector: plain HTTP (broker directly) or HTTPS (e.g. broker behind a reverse proxy).
QComboBox *schemeCombo = new QComboBox(&dialog);
schemeCombo->addItem("http://");
schemeCombo->addItem("https://");
layout->addWidget(schemeCombo, 0, 0);
layout->addWidget(new QLabel(":"), 0, 2);
QLineEdit *hostEdit = new QLineEdit(&dialog);
char *host = std::getenv("JUNGFRAUJOCH_HTTP_HOST");
if (host != nullptr)
hostEdit->setText(host);
else
hostEdit->setText("localhost");
layout->addWidget(hostEdit, 0, 1);
QSpinBox *portSpinBox = new QSpinBox(&dialog);
portSpinBox->setRange(1, 65535);
char *port_std = std::getenv("JUNGFRAUJOCH_HTTP_PORT");
if (port_std != nullptr) {
const int tmp = std::stoi(std::string(port_std));
if (tmp < 65536 && tmp > 0)
portSpinBox->setValue(tmp);
} else
portSpinBox->setValue(8080);
layout->addWidget(portSpinBox, 0, 3);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
layout->addWidget(buttonBox, 1, 0, 1, 4);
dialog.setLayout(layout);
if (dialog.exec() == QDialog::Accepted) {
QString url = QString("%1%2:%3")
.arg(schemeCombo->currentText())
.arg(hostEdit->text())
.arg(portSpinBox->value());
emit fileOpenSelected(url, -1, 1, false);
}
}
void JFJochViewerMenu::saveUserMaskAsTiffSelected() {
QString filename = QFileDialog::getSaveFileName(
this,
"Save User Mask as TIFF",
"",
"TIFF Images (*.tif *.tiff);;All Files (*)"
);
if (!filename.isEmpty()) {
emit saveUserMaskTiffSelected(filename);
}
}
void JFJochViewerMenu::openLoadUserMaskTiff(bool replace) {
QString filename = QFileDialog::getOpenFileName(
this,
replace ? "Load User Mask from TIFF (replace)" : "Load User Mask from TIFF (add)",
"",
"TIFF Images (*.tif *.tiff);;All Files (*)"
);
if (!filename.isEmpty())
emit loadUserMaskTiffSelected(filename, replace);
}
void JFJochViewerMenu::uploadUserMaskAction() {
auto reply = QMessageBox::question(
this,
"Upload User Mask",
"Upload current user mask to the server?",
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No
);
if (reply == QMessageBox::Yes) {
emit uploadUserMaskSelected();
}
}