Build Packages / Create release (push) Successful in 24s
Build Packages / build:viewer:macos-arm64:nocuda (push) Successful in 3m29s
Build Packages / build:rugnux:macos-arm64:nocuda (push) Successful in 2m43s
Build Packages / build:rugnux:linux-aarch64:cuda (push) Successful in 8m27s
Build Packages / build:rugnux:linux-x86_64:cuda (push) Successful in 9m53s
Build Packages / build:viewer:linux-x86_64:nocuda (push) Successful in 9m58s
Build Packages / build:viewer:linux-x86_64:cuda (push) Successful in 11m22s
Build Packages / build:jfjoch:rocky8:nocuda (push) Successful in 13m39s
Build Packages / build:viewer:windows-x86_64:nocuda (push) Successful in 18m37s
Build Packages / build:jfjoch:rocky9:nocuda (push) Successful in 16m32s
Build Packages / build:viewer:windows-x86_64:cuda (push) Successful in 24m11s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 25m30s
Build Packages / build:jfjoch:ubuntu2404:nocuda (push) Successful in 19m3s
Build Packages / build:jfjoch:ubuntu2204:nocuda (push) Successful in 20m23s
Build Packages / build:jfjoch:rocky8:cuda-sls9 (push) Successful in 19m41s
Build Packages / Generate python client (push) Successful in 50s
Build Packages / Build documentation (push) Successful in 1m16s
Build Packages / build:jfjoch:rocky9:cuda-sls9 (push) Successful in 21m0s
Build Packages / build:jfjoch:rocky8:cuda (push) Successful in 18m38s
Build Packages / build:rugnux:windows-x86_64:cuda (push) Successful in 14m33s
Build Packages / build:jfjoch:rocky9:cuda (push) Successful in 17m55s
Build Packages / build:jfjoch:ubuntu2204:cuda (push) Successful in 20m50s
Build Packages / build:jfjoch:ubuntu2404:cuda (push) Successful in 18m38s
Build Packages / Unit tests (push) Successful in 1h46m14s
* jfjoch_broker: Optional per-dataset authentication - statistics, images and plots can require a bearer token, which jfjoch_viewer supports. * jfjoch_viewer: Dark mode and a theme-matched colour scheme, a magnifier panel, and simpler contrast and background controls. * Rugnux: Multiple performance improvements on GPU and CPU (CPU-only processing up to 40% faster, faster image decoding on ARM), with unchanged results. * Rugnux: `--model` rigid-body refinement runs on the GPU, and the model-validation check is faster and more reliable. * Rugnux: Improved scaling and merging - error model, outlier rejection, absorption correction and French-Wilson amplitudes now agree more closely with XDS and ctruncate. * Rugnux: Improved integration - radial background on powder and ice rings, crowded rotation data keep their reflections, and CPU-only builds integrate large unit cells as GPU builds do. * Rugnux: More robust detector geometry - measured beam centre, X-ray bandwidth and goniometer rate, and geometry refinement accepted only on significant evidence. * Rugnux: Merged files are written in the standard setting, or in the setting of a reference MTZ, structure-factor mmCIF or model, with its free-R flags. * Rugnux: Richer report - ice and powder rings, further lattices, superstructure candidates and mosaicity, with warnings worded as prompts to check. * Rugnux: Clear error messages when a data set needs more GPU or host memory than is available. Reviewed-on: #83 Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
228 lines
9.3 KiB
C++
228 lines
9.3 KiB
C++
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#include "JFJochViewerToolbarDisplay.h"
|
|
#include "../ViewerTheme.h"
|
|
#include <QLabel>
|
|
#include "../../common/ColorScale.h"
|
|
#include "../RemoteDisplayMode.h"
|
|
#include "../widgets/ToolbarIcons.h"
|
|
|
|
constexpr double MAX_SLIDER_NO_IMAGE = 65000;
|
|
|
|
JFJochViewerToolbarDisplay::JFJochViewerToolbarDisplay(QWidget *parent)
|
|
: QToolBar(parent) {
|
|
setWindowTitle("Display"); // shown in the toolbar/dock context menu
|
|
|
|
foreground_tail_timer_ = new QTimer(this);
|
|
foreground_tail_timer_->setSingleShot(true);
|
|
connect(foreground_tail_timer_, &QTimer::timeout,
|
|
this, &JFJochViewerToolbarDisplay::ApplyPendingForeground);
|
|
|
|
background_tail_timer_ = new QTimer(this);
|
|
background_tail_timer_->setSingleShot(true);
|
|
connect(background_tail_timer_, &QTimer::timeout,
|
|
this, &JFJochViewerToolbarDisplay::ApplyPendingBackground);
|
|
|
|
// Two sliders share the strip, so each is capped rather than expanding across the window.
|
|
const int slider_cap = fontMetrics().averageCharWidth() * 36;
|
|
|
|
addWidget(new QLabel(" Foreground ", this));
|
|
|
|
foreground_slider = new SliderPlusBox(1, MAX_SLIDER_NO_IMAGE, 1.0, 1, this,
|
|
SliderPlusBox::ScaleType::Logarithmic);
|
|
foreground_slider->setValue(10);
|
|
foreground_slider->setToolTip("White point of the color map (image contrast); hold F and use"
|
|
" the mouse wheel, Ctrl+wheel for coarse steps");
|
|
SetThemedStyleSheet(foreground_slider, SliderStyle);
|
|
foreground_slider->setMaximumWidth(slider_cap);
|
|
addWidget(foreground_slider);
|
|
|
|
background_label_action = addWidget(new QLabel(" Background ", this));
|
|
|
|
background_slider = new SliderPlusBox(0, MAX_SLIDER_NO_IMAGE, 1.0, 1, this,
|
|
SliderPlusBox::ScaleType::Logarithmic);
|
|
background_slider->setValue(0);
|
|
background_slider->setToolTip("Black point of the color map (0 = off); hold B and use"
|
|
" the mouse wheel");
|
|
SetThemedStyleSheet(background_slider, SliderStyle);
|
|
background_slider->setMaximumWidth(slider_cap);
|
|
background_slider_action = addWidget(background_slider);
|
|
|
|
show_background_action = new QAction("Background slider", this);
|
|
show_background_action->setCheckable(true);
|
|
connect(show_background_action, &QAction::toggled, this, &JFJochViewerToolbarDisplay::showBackgroundSlider);
|
|
showBackgroundSlider(false);
|
|
|
|
auto makeToggle = [this](const QString &text, const QString &tip) {
|
|
auto *b = new QToolButton(this);
|
|
b->setText(text);
|
|
b->setToolButtonStyle(Qt::ToolButtonTextOnly);
|
|
b->setCheckable(true);
|
|
b->setToolTip(tip);
|
|
b->setCursor(Qt::PointingHandCursor);
|
|
SetThemedStyleSheet(b, ToolbarIcons::buttonStyle);
|
|
return b;
|
|
};
|
|
auto_foreground_button = makeToggle("Auto", "Auto-contrast to the current image");
|
|
addWidget(auto_foreground_button);
|
|
hdr_mode_button = makeToggle("HDR", "High dynamic range: show the full intensity range unclipped");
|
|
addWidget(hdr_mode_button);
|
|
|
|
addWidget(new QLabel(" Color map ", this));
|
|
|
|
// Initialize QComboBox with the options
|
|
color_map_select = new QComboBox(this);
|
|
color_map_select->addItem("Indigo", static_cast<int>(ColorScaleEnum::Indigo));
|
|
color_map_select->addItem("Viridis", static_cast<int>(ColorScaleEnum::Viridis));
|
|
color_map_select->addItem("Magma", static_cast<int>(ColorScaleEnum::Magma));
|
|
color_map_select->addItem("Inferno", static_cast<int>(ColorScaleEnum::Inferno));
|
|
color_map_select->addItem("Heat", static_cast<int>(ColorScaleEnum::Heat));
|
|
color_map_select->addItem("Black on white", static_cast<int>(ColorScaleEnum::BW));
|
|
color_map_select->addItem("White on black", static_cast<int>(ColorScaleEnum::WB));
|
|
color_map_select->addItem("Green", static_cast<int>(ColorScaleEnum::Green));
|
|
|
|
color_map_select->setCurrentIndex(static_cast<int>(ColorScaleEnum::Indigo));
|
|
addWidget(color_map_select);
|
|
|
|
connect(foreground_slider, &SliderPlusBox::valueChanged, this, &JFJochViewerToolbarDisplay::foregroundSet);
|
|
connect(background_slider, &SliderPlusBox::valueChanged, this, &JFJochViewerToolbarDisplay::backgroundSet);
|
|
connect(color_map_select, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
|
this, &JFJochViewerToolbarDisplay::colorComboBoxSet);
|
|
connect(auto_foreground_button, &QToolButton::clicked, this, &JFJochViewerToolbarDisplay::autoForegroundButtonPressed);
|
|
connect(hdr_mode_button, &QToolButton::clicked, this, &JFJochViewerToolbarDisplay::HDRModeButtonPressed);
|
|
auto *stretch = new QWidget(this);
|
|
stretch->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
|
addWidget(stretch);
|
|
|
|
auto makeIconButton = [this](const QIcon &icon, const QString &tip, bool checkable) {
|
|
auto *b = new QToolButton(this);
|
|
b->setIcon(icon);
|
|
b->setIconSize(QSize(20, 20));
|
|
b->setToolTip(tip);
|
|
b->setCheckable(checkable);
|
|
b->setCursor(Qt::PointingHandCursor);
|
|
SetThemedStyleSheet(b, ToolbarIcons::buttonStyle);
|
|
return b;
|
|
};
|
|
font_zoom_group = new QButtonGroup(this);
|
|
font_zoom_group->setExclusive(true);
|
|
for (int step = 0; step < 3; step++) {
|
|
const int percent = 100 + 25 * step;
|
|
auto *b = makeIconButton(ToolbarIcons::letterA(step), QString("Font size %1 %").arg(percent), true);
|
|
font_zoom_group->addButton(b, percent);
|
|
addWidget(b);
|
|
}
|
|
connect(font_zoom_group, &QButtonGroup::idClicked, this, &JFJochViewerToolbarDisplay::fontZoomSelected);
|
|
addSeparator();
|
|
|
|
theme_button = makeIconButton(ToolbarIcons::halfSun(), QString(), false);
|
|
addWidget(theme_button);
|
|
connect(theme_button, &QToolButton::clicked, this,
|
|
[] { SetThemeChoice(DarkTheme() ? ThemeChoice::Light : ThemeChoice::Dark); });
|
|
connect(ThemeNotifier::Instance(), &ThemeNotifier::changed, this, &JFJochViewerToolbarDisplay::UpdateThemeButton);
|
|
UpdateThemeButton();
|
|
}
|
|
|
|
void JFJochViewerToolbarDisplay::UpdateThemeButton() {
|
|
theme_button->setToolTip(DarkTheme() ? "Switch to the light theme" : "Switch to the dark theme");
|
|
}
|
|
|
|
void JFJochViewerToolbarDisplay::updateFontZoom(int percent) {
|
|
if (auto *b = font_zoom_group->button(percent))
|
|
b->setChecked(true);
|
|
}
|
|
|
|
void JFJochViewerToolbarDisplay::foregroundSet(double val) {
|
|
emit setForeground(static_cast<float>(val));
|
|
}
|
|
|
|
void JFJochViewerToolbarDisplay::backgroundSet(double val) {
|
|
emit setBackground(static_cast<float>(val));
|
|
}
|
|
|
|
void JFJochViewerToolbarDisplay::updateBackground(float val) {
|
|
if (val > 0.0f)
|
|
showBackgroundSlider(true);
|
|
pending_background_ = val;
|
|
if (RemoteDisplayMode() && background_rate_.isValid()
|
|
&& background_rate_.elapsed() < kRemoteRepaintIntervalMs) {
|
|
background_tail_timer_->start(kRemoteRepaintIntervalMs);
|
|
return;
|
|
}
|
|
background_tail_timer_->stop();
|
|
ApplyPendingBackground();
|
|
}
|
|
|
|
void JFJochViewerToolbarDisplay::showBackgroundSlider(bool val) {
|
|
show_background_action->setChecked(val);
|
|
background_label_action->setVisible(val);
|
|
background_slider_action->setVisible(val);
|
|
}
|
|
|
|
void JFJochViewerToolbarDisplay::ApplyPendingBackground() {
|
|
background_rate_.restart();
|
|
QSignalBlocker blocker(background_slider);
|
|
background_slider->setValue(pending_background_);
|
|
}
|
|
|
|
void JFJochViewerToolbarDisplay::updateForeground(float val) {
|
|
pending_foreground_ = val;
|
|
if (RemoteDisplayMode() && foreground_rate_.isValid()
|
|
&& foreground_rate_.elapsed() < kRemoteRepaintIntervalMs) {
|
|
foreground_tail_timer_->start(kRemoteRepaintIntervalMs);
|
|
return;
|
|
}
|
|
foreground_tail_timer_->stop();
|
|
ApplyPendingForeground();
|
|
}
|
|
|
|
void JFJochViewerToolbarDisplay::ApplyPendingForeground() {
|
|
foreground_rate_.restart();
|
|
QSignalBlocker blocker(foreground_slider);
|
|
foreground_slider->setValue(pending_foreground_);
|
|
}
|
|
|
|
void JFJochViewerToolbarDisplay::colorComboBoxSet(int val) {
|
|
emit colorMapChanged(val);
|
|
}
|
|
|
|
void JFJochViewerToolbarDisplay::autoForegroundButtonPressed() {
|
|
emit setAutoForeground(auto_foreground_button->isChecked());
|
|
}
|
|
|
|
void JFJochViewerToolbarDisplay::updateAutoForeground(bool val) {
|
|
QSignalBlocker blocker(auto_foreground_button);
|
|
auto_foreground_button->setChecked(val);
|
|
}
|
|
|
|
void JFJochViewerToolbarDisplay::updateColorMap(int val) {
|
|
QSignalBlocker blocker(color_map_select);
|
|
color_map_select->setCurrentIndex(val);
|
|
}
|
|
|
|
void JFJochViewerToolbarDisplay::updateHDRMode(bool val) {
|
|
QSignalBlocker blocker(hdr_mode_button);
|
|
hdr_mode_button->setChecked(val);
|
|
}
|
|
|
|
void JFJochViewerToolbarDisplay::HDRModeButtonPressed() {
|
|
emit setHDRMode(hdr_mode_button->isChecked());
|
|
}
|
|
|
|
void JFJochViewerToolbarDisplay::imageLoaded(std::shared_ptr<const JFJochReaderImage> image) {
|
|
if (image) {
|
|
auto valid = image->ValidMinMax();
|
|
if (valid.has_value() && valid->second > 1) {
|
|
foreground_slider->setMax(valid->second);
|
|
background_slider->setMax(valid->second);
|
|
} else {
|
|
foreground_slider->setMax(MAX_SLIDER_NO_IMAGE);
|
|
background_slider->setMax(MAX_SLIDER_NO_IMAGE);
|
|
}
|
|
} else {
|
|
foreground_slider->setMax(MAX_SLIDER_NO_IMAGE);
|
|
background_slider->setMax(MAX_SLIDER_NO_IMAGE);
|
|
}
|
|
}
|