Build Packages / Unit tests (push) Successful in 1h22m15s
Build Packages / build:windows:nocuda (push) Successful in 18m0s
Build Packages / build:windows:cuda (push) Successful in 20m30s
Build Packages / build:viewer-tgz:cpu (push) Successful in 10m32s
Build Packages / build:viewer-tgz:cuda (push) Successful in 11m39s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 8m55s
Build Packages / build:rugnux:windows (push) Successful in 11m25s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 20m6s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 16m27s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 20m19s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 15m34s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 20m25s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 19m36s
Build Packages / build:rpm (rocky8) (push) Successful in 17m43s
Build Packages / build:rpm (rocky9) (push) Successful in 13m34s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 21m28s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 18m19s
Build Packages / DIALS test (push) Successful in 12m36s
Build Packages / XDS test (durin plugin) (push) Successful in 6m56s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 6m48s
Build Packages / XDS test (neggia plugin) (push) Successful in 6m7s
Build Packages / Generate python client (push) Successful in 11s
Build Packages / Build documentation (push) Successful in 36s
Build Packages / Create release (push) Skipped
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 5m11s
* `rugnux --mode calibration` writes `<prefix>.json` beside the `.poni`, whose `dataset_settings` member is a `jfjoch_broker` `dataset_settings` body as it stands. * `rugnux` and `jfjoch_viewer` read PILATUS miniCBF sweeps natively, without conversion. * Masters written by other facilities open, including Eiger 1.x and third-party NXmx variants. * `rugnux` measures the beam centre on every run, and indexes with it when the file's value indexes nothing. * A detector swung out on a 2theta arm is placed where the file says it stands, and the calibration can hold the tilt fixed. * `rugnux` writes the unmerged MTZ by default, and a P1 merge beside it, so a wrong space group can be re-merged without reprocessing. * Significant improvements to symmetry handling in `rugnux`: the lattice, the point group, the setting and the systematic absences. * The `rugnux` report gives the resolution the CC1/2 fit reached, beside the range the reflections were written to. * The `rugnux` report gives the twinning statistics measured before the space group was decided, beside the ones measured after. * The `rugnux` report gives the strong-direction diffraction limit, and warns when CC1/2 is not monotone with resolution. * `rugnux` ranks screw axes on the evidence their absences carry, rather than on how many control reflections a candidate happens to have. * Twinning is no longer reported when the L-test contradicts it. * The `rugnux` report gives the detector tilt, the measured tilt and the direct beam beside the beam centre, and a post-refined beam centre is judged against the run's own measurement rather than the file's. * `--no-refine-tilt` holds the detector tilt at the value in the file, instead of zeroing it, when the calibration starts from the spots. * The `jfjoch_viewer` grid scan view draws the cells in the proportion of the scan steps, so the map has the shape of the scanned area. Reviewed-on: #76 Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
252 lines
10 KiB
C++
252 lines
10 KiB
C++
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#include "JFJochViewerToolbarImage.h"
|
|
#include <algorithm>
|
|
#include <QTimer>
|
|
#include "../widgets/ToolbarIcons.h"
|
|
|
|
JFJochViewerToolbarImage::JFJochViewerToolbarImage(QWidget *parent) : QToolBar(parent) {
|
|
setWindowTitle("Navigation"); // shown in the toolbar/dock context menu
|
|
image_count_in_dataset = 0;
|
|
|
|
auto makeButton = [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);
|
|
b->setStyleSheet(ToolbarIcons::buttonStyle());
|
|
return b;
|
|
};
|
|
|
|
// --- create widgets ---
|
|
open_button = makeButton(ToolbarIcons::openFile(), "Open dataset…", false);
|
|
open_button->setShortcut(QKeySequence::Open);
|
|
|
|
autoload_button = makeButton(ToolbarIcons::httpSync(ToolbarIcons::HttpState::Disconnected),
|
|
"Connect to a live source…", false);
|
|
autoload_button->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_S));
|
|
|
|
movie_button = makeButton(ToolbarIcons::movie(), "Play movie", true);
|
|
movie_button->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_M));
|
|
|
|
// The scrub slider is the prominent way to move across the dataset, so it expands to fill.
|
|
image_number_slider = new QSlider(Qt::Horizontal, this);
|
|
image_number_slider->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
|
image_number_slider->setMinimumWidth(140);
|
|
image_number_slider->setToolTip("Scrub through the dataset");
|
|
image_number_slider->setStyleSheet(
|
|
"QSlider::groove:horizontal { height:6px; background:#F3D9D4; border-radius:3px; }"
|
|
"QSlider::sub-page:horizontal { background:#FA7268; border-radius:3px; }"
|
|
"QSlider::handle:horizontal { background:#1F3A5F; width:14px; margin:-5px 0; border-radius:7px; }"
|
|
"QSlider::handle:horizontal:hover { background:#16314F; }");
|
|
image_number_slider_timer = new QTimer(this);
|
|
image_number_slider_timer->setSingleShot(true);
|
|
|
|
leftmost_button = makeButton(ToolbarIcons::first(), "First image", false);
|
|
left_button = makeButton(ToolbarIcons::prev(), "Previous (by jump)", false);
|
|
|
|
current_image_edit_validator = new QIntValidator(0, 1000, this);
|
|
current_image_edit = new QLineEdit("0", this);
|
|
current_image_edit->setFixedWidth(54);
|
|
current_image_edit->setAlignment(Qt::AlignmentFlag::AlignRight);
|
|
current_image_edit->setValidator(current_image_edit_validator);
|
|
current_image_edit->setToolTip("Current image — type a number to jump");
|
|
|
|
total_number_label = new QLabel("/0", this);
|
|
|
|
right_button = makeButton(ToolbarIcons::next(), "Next (by jump)", false);
|
|
rightmost_button = makeButton(ToolbarIcons::last(), "Last image", false);
|
|
|
|
jump = new NumericComboBox({1, 2, 5, 10, 20, 100, 500}, 1, 999, this);
|
|
connect(jump, &NumericComboBox::valueChanged, this, &JFJochViewerToolbarImage::setImageJump);
|
|
auto sum_box = new NumericComboBox({1, 2, 5, 10, 20, 50, 100}, 1, 10000, this);
|
|
connect(sum_box, &NumericComboBox::valueChanged, this, &JFJochViewerToolbarImage::setSummation);
|
|
|
|
// --- lay out: source actions, then the scrubber, then precise navigation ---
|
|
addWidget(open_button);
|
|
addWidget(autoload_button);
|
|
addWidget(movie_button);
|
|
addSeparator();
|
|
addWidget(image_number_slider);
|
|
addSeparator();
|
|
addWidget(leftmost_button);
|
|
addWidget(left_button);
|
|
addWidget(current_image_edit);
|
|
addWidget(total_number_label);
|
|
addWidget(right_button);
|
|
addWidget(rightmost_button);
|
|
addWidget(new QLabel(" Jump ", this));
|
|
addWidget(jump);
|
|
addWidget(new QLabel(" Sum ", this));
|
|
addWidget(sum_box);
|
|
|
|
connect(movie_button, &QToolButton::clicked, this, &JFJochViewerToolbarImage::movieButtonPressed);
|
|
connect(open_button, &QToolButton::clicked, this, [this] { emit requestOpenFile(); });
|
|
connect(autoload_button, &QToolButton::clicked, this, &JFJochViewerToolbarImage::autoloadButtonPressed);
|
|
|
|
setImageNumber(0, 0);
|
|
|
|
connect(left_button, &QToolButton::clicked, this, &JFJochViewerToolbarImage::leftButtonPressed);
|
|
connect(right_button, &QToolButton::clicked, this, &JFJochViewerToolbarImage::rightButtonPressed);
|
|
connect(leftmost_button, &QToolButton::clicked, this, &JFJochViewerToolbarImage::leftmostButtonPressed);
|
|
connect(rightmost_button, &QToolButton::clicked, this, &JFJochViewerToolbarImage::rightmostButtonPressed);
|
|
|
|
connect(current_image_edit, &QLineEdit::editingFinished, this, &JFJochViewerToolbarImage::editFinalized);
|
|
connect(image_number_slider, &QSlider::sliderPressed, this, &JFJochViewerToolbarImage::imageNumberSliderPressed);
|
|
connect(image_number_slider, &QSlider::sliderReleased, this, &JFJochViewerToolbarImage::imageNumberSliderReleased);
|
|
connect(image_number_slider, &QSlider::sliderMoved, this, &JFJochViewerToolbarImage::imageNumberSliderMoved);
|
|
}
|
|
|
|
void JFJochViewerToolbarImage::updateButtons() {
|
|
if (image_count_in_dataset > 0) {
|
|
current_image_edit->setDisabled(false);
|
|
current_image_edit->setText(QString::number(curr_image + 1));
|
|
current_image_edit_validator->setRange(1, image_count_in_dataset);
|
|
|
|
left_button->setDisabled(curr_image < jump_value);
|
|
right_button->setDisabled(curr_image >= image_count_in_dataset - jump_value);
|
|
leftmost_button->setDisabled(curr_image == 0);
|
|
rightmost_button->setDisabled(curr_image == image_count_in_dataset - 1);
|
|
|
|
image_number_slider->setRange(0, image_count_in_dataset - 1);
|
|
image_number_slider->setEnabled(true);
|
|
if (!image_number_slider_manual)
|
|
image_number_slider->setValue(curr_image);
|
|
} else {
|
|
left_button->setDisabled(true);
|
|
right_button->setDisabled(true);
|
|
leftmost_button->setDisabled(true);
|
|
rightmost_button->setDisabled(true);
|
|
|
|
current_image_edit->setText(QString::number(0));
|
|
image_number_slider->setValue(0);
|
|
image_number_slider->setDisabled(true);
|
|
}
|
|
if (image_count_in_dataset == 0)
|
|
total_number_label->setText("/ - ");
|
|
else
|
|
total_number_label->setText("/" + QString::number(image_count_in_dataset));
|
|
}
|
|
|
|
void JFJochViewerToolbarImage::updateHttpButton() {
|
|
ToolbarIcons::HttpState s;
|
|
QString tip;
|
|
if (!http_connected) {
|
|
s = ToolbarIcons::HttpState::Disconnected;
|
|
tip = "Connect to a live source…";
|
|
} else if (autoload_mode == JFJochImageReadingWorker::AutoloadMode::HTTPSync) {
|
|
s = ToolbarIcons::HttpState::Live;
|
|
tip = "Following live (image + data) — click to stop";
|
|
} else {
|
|
s = ToolbarIcons::HttpState::Frozen;
|
|
tip = "Connected, not following — click to follow live";
|
|
}
|
|
autoload_button->setIcon(ToolbarIcons::httpSync(s));
|
|
autoload_button->setToolTip(tip);
|
|
}
|
|
|
|
void JFJochViewerToolbarImage::setImageNumber(int64_t total_images, int64_t current_image) {
|
|
image_count_in_dataset = total_images;
|
|
curr_image = current_image;
|
|
updateButtons();
|
|
}
|
|
|
|
void JFJochViewerToolbarImage::setHttpConnection(bool connected, QString) {
|
|
http_connected = connected;
|
|
updateHttpButton();
|
|
}
|
|
|
|
void JFJochViewerToolbarImage::leftButtonPressed() {
|
|
if (curr_image >= jump_value)
|
|
emit loadImage(curr_image - jump_value, sum);
|
|
}
|
|
|
|
void JFJochViewerToolbarImage::rightButtonPressed() {
|
|
if (curr_image < image_count_in_dataset - jump_value)
|
|
emit loadImage(curr_image + jump_value, sum);
|
|
}
|
|
|
|
void JFJochViewerToolbarImage::rightmostButtonPressed() {
|
|
emit loadImage(image_count_in_dataset - 1, sum);
|
|
}
|
|
|
|
void JFJochViewerToolbarImage::leftmostButtonPressed() {
|
|
emit loadImage(0, sum);
|
|
}
|
|
|
|
void JFJochViewerToolbarImage::stepImage(int steps) {
|
|
if (image_count_in_dataset == 0)
|
|
return;
|
|
const auto target = std::clamp<int64_t>(curr_image + steps, 0,
|
|
static_cast<int64_t>(image_count_in_dataset) - 1);
|
|
if (target != curr_image)
|
|
emit loadImage(target, sum);
|
|
}
|
|
|
|
void JFJochViewerToolbarImage::imageNumberSliderPressed() {
|
|
image_number_slider_manual = true;
|
|
}
|
|
|
|
void JFJochViewerToolbarImage::imageNumberSliderReleased() {
|
|
image_number_slider_manual = false;
|
|
emit loadImage(image_number_slider->value(), sum);
|
|
}
|
|
|
|
void JFJochViewerToolbarImage::imageNumberSliderMoved(int val) {
|
|
if (!image_number_slider_timer->isActive()) {
|
|
emit loadImage(val, sum);
|
|
image_number_slider_timer->start(300);
|
|
}
|
|
}
|
|
|
|
void JFJochViewerToolbarImage::editFinalized() {
|
|
bool ok;
|
|
int editedValue = current_image_edit->text().toInt(&ok);
|
|
if (ok && editedValue >= 1 && editedValue <= image_count_in_dataset)
|
|
emit loadImage(editedValue - 1, sum);
|
|
}
|
|
|
|
void JFJochViewerToolbarImage::setImageJump(int val) {
|
|
jump_value = val;
|
|
updateButtons();
|
|
emit imageJumpChanged(jump_value);
|
|
}
|
|
|
|
void JFJochViewerToolbarImage::setSummation(int val) {
|
|
sum = val;
|
|
jump_value = val;
|
|
jump->setValue(val);
|
|
updateButtons();
|
|
emit loadImage(curr_image, sum);
|
|
}
|
|
|
|
void JFJochViewerToolbarImage::autoloadButtonPressed() {
|
|
// Not connected: ask the window to open the connection dialog. Connected: toggle live following.
|
|
if (!http_connected) {
|
|
emit requestHttpConnect();
|
|
return;
|
|
}
|
|
if (autoload_mode == JFJochImageReadingWorker::AutoloadMode::HTTPSync)
|
|
emit autoLoadButtonPressed(JFJochImageReadingWorker::AutoloadMode::None);
|
|
else
|
|
emit autoLoadButtonPressed(JFJochImageReadingWorker::AutoloadMode::HTTPSync);
|
|
}
|
|
|
|
void JFJochViewerToolbarImage::movieButtonPressed() {
|
|
if (movie_button->isChecked())
|
|
emit autoLoadButtonPressed(JFJochImageReadingWorker::AutoloadMode::Movie);
|
|
else
|
|
emit autoLoadButtonPressed(JFJochImageReadingWorker::AutoloadMode::None);
|
|
}
|
|
|
|
void JFJochViewerToolbarImage::setAutoloadMode(JFJochImageReadingWorker::AutoloadMode input) {
|
|
autoload_mode = input;
|
|
const bool playing = (input == JFJochImageReadingWorker::AutoloadMode::Movie);
|
|
movie_button->setChecked(playing); // checked (navy) = movie running; icon stays the camera
|
|
movie_button->setToolTip(playing ? "Stop movie" : "Play movie");
|
|
updateHttpButton();
|
|
}
|