Every recolour ended with QPixmap::fromImage(), which allocates a second full-size buffer and converts the whole image into the screen format. That conversion was the largest single cost left in the colouring path. Replace QGraphicsPixmapItem with a small item that paints qimg_buffer_ with QPainter::drawImage. The buffer is already what the raster engine wants, so nothing is converted or copied. The item declares its opaque area, as the pixmap item did, so the view still skips the background fill underneath it, and it turns SmoothPixmapTransform off before drawing to keep the nearest-neighbour sampling QGraphicsPixmapItem gave us by default -- zoomed-in detector pixels stay sharp squares. GeneratePixmap is renamed RenderImage: it no longer makes a pixmap. 18.1 Mpx recolour: 22 -> 5.6 ms (28.0 ms before this series). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
178 lines
5.3 KiB
C++
178 lines
5.3 KiB
C++
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#include "JFJochGridScanImage.h"
|
|
|
|
#include <QGraphicsItem>
|
|
|
|
JFJochGridScanImage::JFJochGridScanImage(QWidget *parent) : JFJochImage(parent) {}
|
|
|
|
void JFJochGridScanImage::clear() {
|
|
W = 0;
|
|
H = 0;
|
|
this->settings = std::nullopt;
|
|
if (scene())
|
|
scene()->clear();
|
|
resetScenePointers();
|
|
CalcROI();
|
|
}
|
|
|
|
void JFJochGridScanImage::loadData(const std::vector<float> &data, const GridScanSettings &settings, bool in_one_over_d2) {
|
|
if (data.empty()) {
|
|
clear();
|
|
return;
|
|
}
|
|
this->settings = settings;
|
|
|
|
W = settings.GetGridSizeX_step();
|
|
H = settings.GetGridSizeY_step();
|
|
image_fp = settings.Rearrange(data, NAN);
|
|
|
|
std::vector<int64_t> indices(image_fp.size());
|
|
std::iota(indices.begin(), indices.end(), 0);
|
|
|
|
image_index = settings.Rearrange(indices, -1);
|
|
|
|
one_over_d2 = in_one_over_d2;
|
|
if (one_over_d2) {
|
|
for (int i = 0; i < image_fp.size(); i++)
|
|
if (!std::isnan(image_fp[i]))
|
|
image_fp[i] = 1.0f / (image_fp[i] * image_fp[i]);
|
|
}
|
|
|
|
float minv = std::numeric_limits<float>::infinity();
|
|
float maxv = -std::numeric_limits<float>::infinity();
|
|
for (float v : image_fp) {
|
|
if (std::isfinite(v)) {
|
|
if (v < minv) minv = v;
|
|
if (v > maxv) maxv = v;
|
|
}
|
|
}
|
|
if (!std::isfinite(minv) || !std::isfinite(maxv)) {
|
|
minv = 0.0f;
|
|
maxv = 1.0f;
|
|
}
|
|
if (!(maxv > minv)) {
|
|
maxv = minv + 1.0f;
|
|
}
|
|
background = minv;
|
|
foreground = maxv;
|
|
|
|
RenderImage();
|
|
Redraw();
|
|
CalcROI();
|
|
}
|
|
|
|
void JFJochGridScanImage::mouseHover(QMouseEvent *event) {
|
|
// Map mouse position to image pixel if inside bounds
|
|
if (W == 0 || H == 0 || image_index.empty())
|
|
return;
|
|
|
|
const QPointF pt = mapToScene(event->pos());
|
|
|
|
// Convert view coordinates to image pixel by truncation
|
|
int x = static_cast<int>(pt.x());
|
|
int y = static_cast<int>(pt.y());
|
|
|
|
if (x < 0 || x >= W || y < 0 || y >= H)
|
|
return;
|
|
|
|
int idx = y * W + x;
|
|
if (idx < 0 || idx >= static_cast<int>(image_index.size()))
|
|
return;
|
|
int64_t image_id = image_index[idx];
|
|
if (image_id >= 0) {
|
|
if (event->modifiers() & Qt::ShiftModifier)
|
|
emit imageSelected(image_id);
|
|
|
|
if (one_over_d2) {
|
|
if (std::isnan(image_fp[idx]))
|
|
emit writeStatusBar(QString("Image %1 x %2 y %3 no resolution estimation")
|
|
.arg(image_id)
|
|
.arg(x)
|
|
.arg(y),
|
|
6000);
|
|
else
|
|
emit writeStatusBar(QString("Image %1 x %2 y %3 d = %4 Å")
|
|
.arg(image_id)
|
|
.arg(x)
|
|
.arg(y)
|
|
.arg(QString::number(1 / std::sqrt(image_fp[idx]), 'f', 2)),
|
|
6000);
|
|
} else {
|
|
emit writeStatusBar(QString("Image %1 x %2 y %3 value %4")
|
|
.arg(image_id)
|
|
.arg(x)
|
|
.arg(y)
|
|
.arg(QString::number(image_fp[idx], 'f', 3)),
|
|
6000);
|
|
}
|
|
}
|
|
}
|
|
|
|
void JFJochGridScanImage::loadImage(QMouseEvent *event) {
|
|
// Map mouse position to image pixel if inside bounds
|
|
if (W == 0 || H == 0 || image_index.empty())
|
|
return;
|
|
|
|
const QPointF pt = mapToScene(event->pos());
|
|
|
|
// Convert view coordinates to image pixel by truncation
|
|
int x = static_cast<int>(pt.x());
|
|
int y = static_cast<int>(pt.y());
|
|
|
|
if (x < 0 || x >= W || y < 0 || y >= H)
|
|
return;
|
|
|
|
int idx = y * W + x;
|
|
if (idx < 0 || idx >= static_cast<int>(image_index.size()))
|
|
return;
|
|
int64_t image_id = image_index[idx];
|
|
if (image_id >= 0)
|
|
emit imageSelected(image_id);
|
|
}
|
|
|
|
void JFJochGridScanImage::mouseDoubleClickEvent(QMouseEvent *event) {
|
|
// Map mouse position to image pixel if inside bounds
|
|
if (W == 0 || H == 0 || image_index.empty())
|
|
return;
|
|
|
|
const QPointF pt = mapToScene(event->pos());
|
|
|
|
// Convert view coordinates to image pixel by truncation
|
|
int x = static_cast<int>(pt.x());
|
|
int y = static_cast<int>(pt.y());
|
|
|
|
if (x < 0 || x >= W || y < 0 || y >= H)
|
|
return;
|
|
|
|
int idx = y * W + x;
|
|
if (idx < 0 || idx >= static_cast<int>(image_index.size()))
|
|
return;
|
|
int64_t image_id = image_index[idx];
|
|
if (image_id >= 0)
|
|
emit imageSelected(image_id);
|
|
JFJochImage::mouseDoubleClickEvent(event);
|
|
}
|
|
|
|
void JFJochGridScanImage::setImage(int64_t val) {
|
|
if (settings) {
|
|
current_image_W = settings->GetElementPosX_step(val);
|
|
current_image_H = settings->GetElementPosY_step(val);
|
|
} else {
|
|
current_image_W = -1;
|
|
current_image_H = -1;
|
|
}
|
|
Redraw();
|
|
}
|
|
|
|
void JFJochGridScanImage::addCustomOverlay() {
|
|
if (current_image_W < 0 || current_image_H < 0 || current_image_W >= W || current_image_H >= H)
|
|
return;
|
|
|
|
QPen pen(feature_color, 3);
|
|
pen.setCosmetic(true);
|
|
|
|
auto *rect = scene()->addRect(current_image_W, current_image_H, 1, 1, pen);
|
|
addOverlayItem(rect);
|
|
} |