jfjoch_viewer: Enable binning in the context menu for dataset info chart view
All checks were successful
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 9m45s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 10m2s
Build Packages / Generate python client (push) Successful in 32s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 10m53s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 11m6s
Build Packages / Create release (push) Has been skipped
Build Packages / Build documentation (push) Successful in 57s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 12m21s
Build Packages / build:rpm (rocky8) (push) Successful in 12m58s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 13m19s
Build Packages / build:rpm (rocky9) (push) Successful in 13m57s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 7m37s
Build Packages / Unit tests (push) Successful in 51m25s
All checks were successful
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 9m45s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 10m2s
Build Packages / Generate python client (push) Successful in 32s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 10m53s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 11m6s
Build Packages / Create release (push) Has been skipped
Build Packages / Build documentation (push) Successful in 57s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 12m21s
Build Packages / build:rpm (rocky8) (push) Successful in 12m58s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 13m19s
Build Packages / build:rpm (rocky9) (push) Successful in 13m57s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 7m37s
Build Packages / Unit tests (push) Successful in 51m25s
This commit is contained in:
@@ -20,14 +20,51 @@ JFJochDatasetInfoChartView::JFJochDatasetInfoChartView(QWidget *parent)
|
||||
connect(m_hoverLoadTimer, &QTimer::timeout, this, &JFJochDatasetInfoChartView::onHoverLoadTimeout);
|
||||
}
|
||||
|
||||
|
||||
void JFJochDatasetInfoChartView::setImage(int64_t val) {
|
||||
if (!currentSeries)
|
||||
return;
|
||||
|
||||
curr_image = val;
|
||||
currentSeries->clear();
|
||||
|
||||
if (val < values.size() && val >= 0) {
|
||||
currentSeries->clear();
|
||||
if (values.empty() || val < 0 ||
|
||||
val >= static_cast<int64_t>(values.size()))
|
||||
return;
|
||||
|
||||
// For binning > 1, show the binned mean at bin center
|
||||
if (binning > 1) {
|
||||
const int64_t nBins =
|
||||
static_cast<int64_t>(values.size()) / binning;
|
||||
if (nBins <= 0)
|
||||
return;
|
||||
|
||||
int64_t binIdx = val / binning;
|
||||
binIdx = std::clamp<int64_t>(binIdx, 0, nBins - 1);
|
||||
|
||||
double sum = 0.0;
|
||||
int64_t count = 0;
|
||||
for (int64_t b = 0; b < binning; ++b) {
|
||||
const int64_t idx = binIdx * binning + b;
|
||||
if (idx >= static_cast<int64_t>(values.size()))
|
||||
break;
|
||||
const double v = values[static_cast<size_t>(idx)];
|
||||
if (std::isfinite(v)) {
|
||||
sum += v;
|
||||
++count;
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
const double mean = sum / static_cast<double>(count);
|
||||
if (std::isfinite(mean)) {
|
||||
const double centerX =
|
||||
(static_cast<double>(binIdx) + 0.5) * static_cast<double>(binning);
|
||||
currentSeries->append(centerX, mean);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// binning == 1: original behavior, per-image value
|
||||
if (std::isfinite(values[curr_image])) {
|
||||
const double disp = values[curr_image];
|
||||
if (std::isfinite(disp))
|
||||
@@ -36,13 +73,53 @@ void JFJochDatasetInfoChartView::setImage(int64_t val) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void JFJochDatasetInfoChartView::mousePressEvent(QMouseEvent *event) {
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
QPointF clickedPoint = event->pos();
|
||||
QPointF chartCoord = chart()->mapToValue(clickedPoint);
|
||||
int64_t val = std::lround(chartCoord.x());
|
||||
if (val >= 0 && val < values.size())
|
||||
emit imageSelected(val);
|
||||
if (values.empty()) {
|
||||
QChartView::mousePressEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
const QPointF clickedPoint = event->pos();
|
||||
const QPointF chartCoord = chart()->mapToValue(clickedPoint, series);
|
||||
const double xVal = chartCoord.x();
|
||||
|
||||
if (!std::isfinite(xVal) || xVal < 0.0 ||
|
||||
xVal > static_cast<double>(values.size() - 1)) {
|
||||
QChartView::mousePressEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t selectedIdx = 0;
|
||||
|
||||
if (binning <= 1) {
|
||||
// Original behavior: pick nearest frame index
|
||||
selectedIdx = std::lround(xVal);
|
||||
} else {
|
||||
// Binned mode: pick bin index from x, then representative frame
|
||||
const int64_t nBins =
|
||||
static_cast<int64_t>(values.size()) / binning;
|
||||
if (nBins <= 0) {
|
||||
QChartView::mousePressEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t binIdx =
|
||||
static_cast<int64_t>(std::floor(xVal / static_cast<double>(binning)));
|
||||
binIdx = std::clamp<int64_t>(binIdx, 0, nBins - 1);
|
||||
|
||||
int64_t centerIdx = binIdx * binning + binning / 2;
|
||||
if (centerIdx >= static_cast<int64_t>(values.size()))
|
||||
centerIdx = static_cast<int64_t>(values.size()) - 1;
|
||||
|
||||
selectedIdx = centerIdx;
|
||||
}
|
||||
|
||||
if (selectedIdx >= 0 &&
|
||||
selectedIdx < static_cast<int64_t>(values.size())) {
|
||||
emit imageSelected(selectedIdx);
|
||||
}
|
||||
}
|
||||
QChartView::mousePressEvent(event); // Call the base implementation
|
||||
}
|
||||
@@ -70,7 +147,7 @@ void JFJochDatasetInfoChartView::updateChart() {
|
||||
}
|
||||
|
||||
void JFJochDatasetInfoChartView::buildTimeDomainChart() {
|
||||
if (values.size() >= binning) {
|
||||
if (values.size() >= static_cast<size_t>(binning)) {
|
||||
// At least one full point
|
||||
|
||||
series = new QLineSeries(this);
|
||||
@@ -80,20 +157,23 @@ void JFJochDatasetInfoChartView::buildTimeDomainChart() {
|
||||
double dispMax = -std::numeric_limits<double>::infinity();
|
||||
|
||||
if (binning == 1) {
|
||||
for (int i = 0; i < values.size(); i++) {
|
||||
if (!std::isfinite(values[i])) continue;
|
||||
const double disp = values[i];
|
||||
for (int i = 0; i < static_cast<int>(values.size()); i++) {
|
||||
if (!std::isfinite(values[static_cast<size_t>(i)])) continue;
|
||||
const double disp = values[static_cast<size_t>(i)];
|
||||
if (!std::isfinite(disp)) continue;
|
||||
series->append(i, disp);
|
||||
if (disp < dispMin) dispMin = disp;
|
||||
if (disp > dispMax) dispMax = disp;
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < static_cast<int>(values.size() / binning); i++) {
|
||||
for (int i = 0; i < static_cast<int>(values.size() / static_cast<size_t>(binning)); i++) {
|
||||
double tmp = 0.0;
|
||||
int64_t count = 0;
|
||||
for (int b = 0; b < binning; b++) {
|
||||
const double v = values[i * binning + b];
|
||||
const int64_t idx = static_cast<int64_t>(i) * binning + b;
|
||||
if (idx >= static_cast<int64_t>(values.size()))
|
||||
break;
|
||||
const double v = values[static_cast<size_t>(idx)];
|
||||
if (std::isfinite(v)) {
|
||||
tmp += v;
|
||||
count++;
|
||||
@@ -111,8 +191,44 @@ void JFJochDatasetInfoChartView::buildTimeDomainChart() {
|
||||
}
|
||||
}
|
||||
|
||||
if (curr_image < values.size() && curr_image >= 0 && std::isfinite(values[curr_image])) {
|
||||
currentSeries->append(curr_image, values[curr_image]);
|
||||
// ---- current point marker as binned value when binning > 1 ----
|
||||
if (curr_image >= 0 &&
|
||||
curr_image < static_cast<int64_t>(values.size())) {
|
||||
|
||||
if (binning > 1) {
|
||||
const int64_t nBins =
|
||||
static_cast<int64_t>(values.size()) / binning;
|
||||
if (nBins > 0) {
|
||||
int64_t binIdx = curr_image / binning;
|
||||
binIdx = std::clamp<int64_t>(binIdx, 0, nBins - 1);
|
||||
|
||||
double sum = 0.0;
|
||||
int64_t count = 0;
|
||||
for (int64_t b = 0; b < binning; ++b) {
|
||||
const int64_t idx = binIdx * binning + b;
|
||||
if (idx >= static_cast<int64_t>(values.size()))
|
||||
break;
|
||||
const double v = values[static_cast<size_t>(idx)];
|
||||
if (std::isfinite(v)) {
|
||||
sum += v;
|
||||
++count;
|
||||
}
|
||||
}
|
||||
if (count > 0) {
|
||||
const double mean =
|
||||
sum / static_cast<double>(count);
|
||||
if (std::isfinite(mean)) {
|
||||
const double centerX =
|
||||
(static_cast<double>(binIdx) + 0.5) *
|
||||
static_cast<double>(binning);
|
||||
currentSeries->append(centerX, mean);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (std::isfinite(values[static_cast<size_t>(curr_image)])) {
|
||||
currentSeries->append(curr_image,
|
||||
values[static_cast<size_t>(curr_image)]);
|
||||
}
|
||||
}
|
||||
|
||||
chart()->addSeries(series);
|
||||
@@ -248,7 +364,6 @@ void JFJochDatasetInfoChartView::buildTimeDomainChart() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void JFJochDatasetInfoChartView::setBinning(int64_t val) {
|
||||
if (val >= 1) {
|
||||
binning = val;
|
||||
@@ -273,6 +388,21 @@ void JFJochDatasetInfoChartView::contextMenuEvent(QContextMenuEvent *event) {
|
||||
actXGoniometer->setChecked(m_xUseGoniometerAxis);
|
||||
actXGoniometer->setEnabled(goniometer_axis.has_value());
|
||||
|
||||
// Binning sub‑menu (values are defined only once here)
|
||||
QMenu *binMenu = menu.addMenu("Binning");
|
||||
|
||||
const std::array<int, 8> binValues{1, 5, 10, 25, 50, 100, 250, 1000};
|
||||
QList<QAction *> binActions;
|
||||
binActions.reserve(static_cast<int>(binValues.size()));
|
||||
|
||||
for (int v : binValues) {
|
||||
QAction *act = binMenu->addAction(QString::number(v));
|
||||
act->setCheckable(true);
|
||||
act->setChecked(binning == v);
|
||||
act->setData(v); // remember which bin this action represents
|
||||
binActions.push_back(act);
|
||||
}
|
||||
|
||||
#ifdef JFJOCH_USE_FFTW
|
||||
QAction *actShowFFT = menu.addAction("Show FFT (amplitude vs Hz)");
|
||||
actShowFFT->setCheckable(true);
|
||||
@@ -299,6 +429,13 @@ void JFJochDatasetInfoChartView::contextMenuEvent(QContextMenuEvent *event) {
|
||||
} else if (chosen == actXGoniometer) {
|
||||
m_xUseGoniometerAxis = !m_xUseGoniometerAxis;
|
||||
updateChart();
|
||||
} else if (binActions.contains(chosen)) {
|
||||
// Any binning action selected: read the bin value from QAction::data
|
||||
bool ok = false;
|
||||
int v = chosen->data().toInt(&ok);
|
||||
if (ok && v >= 1) {
|
||||
setBinning(v);
|
||||
}
|
||||
#ifdef JFJOCH_USE_FFTW
|
||||
} else if (chosen == actShowFFT) {
|
||||
m_showFFT = !m_showFFT;
|
||||
@@ -371,16 +508,67 @@ void JFJochDatasetInfoChartView::mouseMoveEvent(QMouseEvent *event) {
|
||||
|
||||
// Map mouse position to chart coordinates
|
||||
const QPointF chartPos = chart()->mapToValue(event->pos(), series);
|
||||
double xVal = chartPos.x();
|
||||
const double xVal = chartPos.x();
|
||||
|
||||
if (!std::isfinite(xVal) || xVal < 0.0 ||
|
||||
xVal > static_cast<double>(values.size() - 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int64_t idx = 0;
|
||||
double yv = std::numeric_limits<double>::quiet_NaN();
|
||||
|
||||
if (binning <= 1) {
|
||||
// Original behavior: nearest frame index and per-image value
|
||||
idx = std::lround(xVal);
|
||||
if (idx < 0 || idx >= static_cast<int64_t>(values.size()))
|
||||
return;
|
||||
yv = values[static_cast<size_t>(idx)];
|
||||
} else {
|
||||
// Binned mode: map x to bin, then use bin mean as the "current point"
|
||||
const int64_t nBins =
|
||||
static_cast<int64_t>(values.size()) / binning;
|
||||
if (nBins <= 0)
|
||||
return;
|
||||
|
||||
int64_t binIdx =
|
||||
static_cast<int64_t>(std::floor(xVal / static_cast<double>(binning)));
|
||||
binIdx = std::clamp<int64_t>(binIdx, 0, nBins - 1);
|
||||
|
||||
// Representative frame index for status text & image loading
|
||||
int64_t centerIdx = binIdx * binning + binning / 2;
|
||||
if (centerIdx >= static_cast<int64_t>(values.size()))
|
||||
centerIdx = static_cast<int64_t>(values.size()) - 1;
|
||||
|
||||
idx = centerIdx;
|
||||
|
||||
// Compute bin mean for hover display / "current" value
|
||||
double sum = 0.0;
|
||||
int64_t count = 0;
|
||||
for (int64_t b = 0; b < binning; ++b) {
|
||||
const int64_t vIdx = binIdx * binning + b;
|
||||
if (vIdx >= static_cast<int64_t>(values.size()))
|
||||
break;
|
||||
const double v = values[static_cast<size_t>(vIdx)];
|
||||
if (std::isfinite(v)) {
|
||||
sum += v;
|
||||
++count;
|
||||
}
|
||||
}
|
||||
if (count > 0)
|
||||
yv = sum / static_cast<double>(count);
|
||||
else
|
||||
yv = std::numeric_limits<double>::quiet_NaN();
|
||||
}
|
||||
|
||||
// Convert x to closest image index
|
||||
int64_t idx = std::lround(xVal);
|
||||
if (idx < 0 || idx >= static_cast<int64_t>(values.size()))
|
||||
return;
|
||||
|
||||
// Map that x position to scene coords for the vertical line
|
||||
// Map that x position to scene coords for the vertical line.
|
||||
// In binned mode this is the bin center index, in unbinned mode the exact frame.
|
||||
const QRectF plotArea = chart()->plotArea();
|
||||
const QPointF ptOnChart = chart()->mapToPosition(QPointF(static_cast<double>(idx), 0.0), series);
|
||||
const QPointF ptOnChart =
|
||||
chart()->mapToPosition(QPointF(static_cast<double>(idx), 0.0), series);
|
||||
|
||||
if (!m_hoverLine) {
|
||||
m_hoverLine = new QGraphicsLineItem;
|
||||
@@ -391,10 +579,8 @@ void JFJochDatasetInfoChartView::mouseMoveEvent(QMouseEvent *event) {
|
||||
m_hoverLine->setLine(QLineF(ptOnChart.x(), plotArea.top(),
|
||||
ptOnChart.x(), plotArea.bottom()));
|
||||
|
||||
// Status bar text
|
||||
const double yv = values[static_cast<size_t>(idx)];
|
||||
// Status bar text based on yv (bin mean in binned mode)
|
||||
QString text;
|
||||
|
||||
if (m_yOneOverD) {
|
||||
if (std::isfinite(yv) && yv > 0.0) {
|
||||
const double d = 1.0 / std::sqrt(yv);
|
||||
@@ -405,9 +591,13 @@ void JFJochDatasetInfoChartView::mouseMoveEvent(QMouseEvent *event) {
|
||||
text = QString("image = %1, no resolution estimate").arg(idx);
|
||||
}
|
||||
} else {
|
||||
text = QString("image = %1 value = %2")
|
||||
.arg(idx)
|
||||
.arg(yv, 0, 'g', 6);
|
||||
if (std::isfinite(yv)) {
|
||||
text = QString("image = %1 value = %2")
|
||||
.arg(idx)
|
||||
.arg(yv, 0, 'g', 6);
|
||||
} else {
|
||||
text = QString("image = %1, no value").arg(idx);
|
||||
}
|
||||
}
|
||||
emit writeStatusBar(text, 6000);
|
||||
|
||||
@@ -418,14 +608,16 @@ void JFJochDatasetInfoChartView::mouseMoveEvent(QMouseEvent *event) {
|
||||
if (idx != curr_image)
|
||||
emit imageSelected(idx);
|
||||
m_hoverLoadTimer->start(500); // debounce
|
||||
} else
|
||||
} else {
|
||||
m_hoverPendingIdx = idx;
|
||||
}
|
||||
} else {
|
||||
m_hoverLoadTimer->stop();
|
||||
m_hoverPendingIdx = -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void JFJochDatasetInfoChartView::leaveEvent(QEvent *event) {
|
||||
QChartView::leaveEvent(event);
|
||||
if (m_hoverLine) {
|
||||
|
||||
@@ -54,15 +54,13 @@ class JFJochDatasetInfoChartView : public QChartView {
|
||||
std::vector<double> m_fftFrequenciesHz;
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
signals:
|
||||
void imageSelected(int64_t number);
|
||||
void writeStatusBar(QString string, int timeout_ms = 0);
|
||||
|
||||
private slots:
|
||||
void onHoverLoadTimeout();
|
||||
void setBinning(int64_t val);
|
||||
|
||||
private:
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
@@ -76,7 +74,6 @@ public:
|
||||
|
||||
public slots:
|
||||
void resetZoom();
|
||||
void setBinning(int64_t val);
|
||||
|
||||
public:
|
||||
template<class T>
|
||||
|
||||
Reference in New Issue
Block a user