Files
Jungfraujoch/viewer/widgets/SliderPlusBox.cpp
T
leonarski_f 6dfe065365
Build Packages / Create release (push) Successful in 16s
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 8m27s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 9m15s
Build Packages / build:viewer-tgz:cpu (push) Successful in 10m11s
Build Packages / build:viewer-tgz:cuda (push) Successful in 12m6s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 15m44s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 16m1s
Build Packages / build:windows:nocuda (push) Successful in 17m29s
Build Packages / build:windows:cuda (push) Successful in 19m58s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 24m7s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 19m8s
Build Packages / build:rugnux:windows (push) Successful in 10m58s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 20m46s
Build Packages / Generate python client (push) Successful in 53s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 20m13s
Build Packages / Build documentation (push) Successful in 1m36s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 19m57s
Build Packages / build:rpm (rocky8) (push) Successful in 18m7s
Build Packages / build:rpm (rocky9) (push) Successful in 18m54s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 19m32s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 17m30s
Build Packages / Unit tests (push) Successful in 1h39m2s
v1.0.0-rc.172 (#82)
* Fixed `jfjoch_broker` cancelling every data collection with a CUDA "out of memory" error after long operation: GPU memory no longer leaks with each collection.
* Rugnux scales a rotation sweep until the per-frame scales settle instead of for a fixed three rounds, and says so when they did not - merged intensities, and the space group, resolution cut and frame rejection read off them, change accordingly; `--scaling-iterations` is now the cap on that loop (default 100).
* Rugnux places every frame of a marCCD, SMV or miniCBF series at the spindle angle its own header states, so a series with missing frames, or with angles written modulo 360, is no longer read at the wrong geometry or refused.
* Every rotation run writes two diagnostic files beside its reflections: `<prefix>_detector.jpg`, the detector projection with the pixel mask and the detected beam-stop shadow drawn on it, and `<prefix>_plot.txt`, one row per image.

Reviewed-on: #82
Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
2026-09-22 06:48:37 +02:00

166 lines
5.9 KiB
C++

// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include <cmath>
#include "SliderPlusBox.h"
#include "../common/JFJochException.h"
SliderPlusBox::SliderPlusBox(double min, double max, double step, int decimals, QWidget *parent, ScaleType scaleType)
: QWidget(parent), v(min), m_step(step), m_min(min), m_max(max), m_decimals(decimals),
m_pendingSliderValue(0), m_sliderDragging(false), m_scaleType(scaleType) {
if (step <= 0)
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
"Scale factor for SliderPlusBox must be positive");
// A logarithmic scale accepts a minimum of exactly zero: slider position 0 means 0 ("off"),
// and the rest of the track spans [1, max] logarithmically.
if (m_scaleType == Logarithmic && m_min < 0)
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
"Minimum value must not be negative for logarithmic scale");
if (m_max <= m_min)
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
"Maximum value must be greater than minimum");
QHBoxLayout* layout = new QHBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
m_slider = new QSlider(Qt::Horizontal, this);
updateSliderRange();
m_doubleSpinBox = new QDoubleSpinBox(this);
m_doubleSpinBox->setRange(min, max);
m_doubleSpinBox->setDecimals(decimals);
m_doubleSpinBox->setSingleStep(step);
m_doubleSpinBox->setValue(v);
m_doubleSpinBox->setStyleSheet("background-color: rgb(255, 255, 255);");
m_updateTimer = new QTimer(this);
m_updateTimer->setInterval(500); // 500ms throttle
m_updateTimer->setSingleShot(false);
layout->addWidget(m_slider);
layout->addWidget(m_doubleSpinBox);
connect(m_slider, &QSlider::valueChanged, [this](int value) {
const QSignalBlocker blocker(m_doubleSpinBox);
m_pendingSliderValue = value;
double realValue = sliderToValue(value);
m_doubleSpinBox->setValue(realValue);
if (!m_updateTimer->isActive() && m_sliderDragging)
m_updateTimer->start();
}
);
connect(m_updateTimer, &QTimer::timeout, [this]() {
updateFromSlider(m_pendingSliderValue);
});
connect(m_slider, &QSlider::sliderPressed, [this]() {
m_sliderDragging = true;
});
connect(m_slider, &QSlider::sliderReleased, [this]() {
m_sliderDragging = false;
m_updateTimer->stop();
updateFromSlider(m_slider->value());
});
connect(m_doubleSpinBox, QOverload<double>::of(&QDoubleSpinBox::valueChanged),
[this](double value) {
const QSignalBlocker blocker(m_slider);
int sliderValue = valueToSlider(value);
m_slider->setValue(sliderValue);
v = value;
emit valueChanged(v);
});
setLayout(layout);
}
void SliderPlusBox::updateSliderRange() {
if (m_scaleType == Linear) {
m_slider->setRange(static_cast<int>(m_min / m_step), static_cast<int>(m_max / m_step));
m_slider->setValue(valueToSlider(v));
} else { // Logarithmic
// Use a higher resolution for logarithmic scale (1000 steps)
m_slider->setRange(0, 1000);
m_slider->setValue(valueToSlider(v));
}
}
double SliderPlusBox::sliderToValue(int sliderValue) const {
if (m_scaleType == Linear) {
return sliderValue * m_step;
} else { // Logarithmic
const double lo = (m_min > 0.0) ? m_min : 1.0;
if (m_min <= 0.0 && sliderValue <= 0)
return 0.0;
// Map from slider position (0-1000) to logarithmic value range
double logMin = std::log10(lo);
double logMax = std::log10(m_max);
double normalizedPos = static_cast<double>(sliderValue) / 1000.0;
double logValue = logMin + normalizedPos * (logMax - logMin);
return std::pow(10.0, logValue);
}
}
int SliderPlusBox::valueToSlider(double value) const {
if (m_scaleType == Linear) {
return static_cast<int>(value / m_step);
} else { // Logarithmic
const double lo = (m_min > 0.0) ? m_min : 1.0;
if (m_min <= 0.0 && value < lo)
return 0;
// Map from value to slider position (0-1000)
double logMin = std::log10(lo);
double logMax = std::log10(m_max);
double logValue = std::log10(std::max(value, lo)); // Ensure we don't go below min
double normalizedPos = (logValue - logMin) / (logMax - logMin);
return static_cast<int>(normalizedPos * 1000.0);
}
}
void SliderPlusBox::updateFromSlider(int sliderValue) {
// Update our internal value and emit the signal
v = sliderToValue(sliderValue);
emit valueChanged(v);
}
void SliderPlusBox::setValue(double value) {
m_doubleSpinBox->setValue(value);
}
void SliderPlusBox::setMax(double value) {
if (value <= m_min)
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
"Maximum value must be greater than minimum");
if (value >= v) {
// if current value is smaller than new max, don't do anything
m_max = value;
m_doubleSpinBox->setRange(m_min, m_max);
updateSliderRange();
}
}
void SliderPlusBox::setScaleType(ScaleType type) {
if (m_scaleType == type)
return;
if (type == Logarithmic && m_min < 0) {
qWarning() << "Cannot switch to logarithmic scale with min < 0";
return;
}
m_scaleType = type;
// Preserve the current value during scale type change
double currentValue = v;
// Update the slider range for the new scale type
updateSliderRange();
// Reset the value
setValue(currentValue);
}