Files
Jungfraujoch/viewer/widgets/JFJochSimpleImage.cpp
Filip Leonarski f48eb44f2a
All checks were successful
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 11m5s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 12m2s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 12m13s
Build Packages / Generate python client (push) Successful in 32s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 12m57s
Build Packages / Create release (push) Has been skipped
Build Packages / Build documentation (push) Successful in 47s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 13m8s
Build Packages / build:rpm (rocky8) (push) Successful in 13m20s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 13m22s
Build Packages / build:rpm (rocky9) (push) Successful in 14m1s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 7m10s
Build Packages / Unit tests (push) Successful in 53m43s
jfjoch_viewer: JFJochImage work in progress to improve performance (at least a bit)
2025-11-07 19:41:16 +01:00

170 lines
5.7 KiB
C++

// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include "JFJochSimpleImage.h"
#include <QGraphicsScene>
#include <QGraphicsPixmapItem>
#include <QMouseEvent>
#include <QtMath>
#include <cstring>
#include <algorithm>
#include "../../common/JFJochException.h"
JFJochSimpleImage::JFJochSimpleImage(QWidget *parent)
: JFJochImage(parent) {
auto *scn = new QGraphicsScene(this);
setScene(scn);
// Keep overlays in pixel units independent of zoom (for labels font sizing)
setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
}
void JFJochSimpleImage::setImage(std::shared_ptr<const SimpleImage> img) {
if (img) {
image_ = std::move(img);
loadImageInternal();
GeneratePixmap();
Redraw();
} else {
image_.reset();
W = 0; H = 0;
if (scene())
scene()->clear();
}
}
void JFJochSimpleImage::mouseHover(QMouseEvent *event) {
const QPointF scenePos = mapToScene(event->pos());
// Hover feedback / status bar display
if ((scenePos.x() >= 0)
&& (scenePos.x() < image_->image.GetWidth())
&& (scenePos.y() >= 0)
&& (scenePos.y() < image_->image.GetHeight())) {
const auto ix = int(scenePos.x());
const auto iy = int(scenePos.y());
const auto idx = iy * int(image_->image.GetWidth()) + ix;
if (idx >= 0 && idx < int(image_fp.size()))
emit writeStatusBar(QString("x=%1 y=%2 I=%3")
.arg(scenePos.x(), 0, 'f', 1)
.arg(scenePos.y(), 0, 'f', 1)
.arg(image_fp[size_t(idx)]), 3000);
} else {
emit writeStatusBar("", 1000);
}
}
template<class T>
void JFJochSimpleImage::loadImageInternal(const uint8_t *input) {
const size_t W = image_->image.GetWidth();
const size_t H = image_->image.GetHeight();
auto ptr = reinterpret_cast<const T *>(input);
for (int i = 0; i < W * H; i++)
image_fp[i] = static_cast<float>(ptr[i]);
}
void JFJochSimpleImage::loadImageInternal() {
W = image_->image.GetWidth();
H = image_->image.GetHeight();
if (W == 0 || H == 0) return;
image_fp.resize(W * H);
std::vector<uint8_t> image_buffer;
// Access uncompressed data
const uint8_t *src = image_->image.GetUncompressedPtr(image_buffer);
const auto mode = image_->image.GetMode();
switch (mode) {
case CompressedImageMode::Uint8:
loadImageInternal<uint8_t>(src);
break;
case CompressedImageMode::Int8:
loadImageInternal<int8_t>(src);
break;
case CompressedImageMode::Uint16:
loadImageInternal<uint16_t>(src);
break;
case CompressedImageMode::Int16:
loadImageInternal<int16_t>(src);
break;
case CompressedImageMode::Uint32:
loadImageInternal<uint32_t>(src);
break;
case CompressedImageMode::Int32:
loadImageInternal<int32_t>(src);
break;
case CompressedImageMode::Float32:
loadImageInternal<float>(src);
break;
case CompressedImageMode::Float64:
loadImageInternal<double>(src);
break;
default:
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Image format not supported");
}
}
void JFJochSimpleImage::updateOverlay() {
if (!scene()) return;
scene()->clear();
if (W*H <= 0) return;
// Add base image
scene()->addItem(new QGraphicsPixmapItem(pixmap));
QFont font("Arial", 1); // Font for pixel value text
font.setPixelSize(1); // This will render very small text (1-pixel high).
// Get the visible area in the scene coordinates
QRectF visibleRect = mapToScene(viewport()->geometry()).boundingRect();
// Calculate the range of pixels to process
const int W = int(image_->image.GetWidth());
const int H = int(image_->image.GetHeight());
const int startX = std::max(0, static_cast<int>(std::floor(visibleRect.left())));
const int endX = std::min(W, static_cast<int>(std::ceil(visibleRect.right())));
const int startY = std::max(0, static_cast<int>(std::floor(visibleRect.top())));
const int endY = std::min(H, static_cast<int>(std::ceil(visibleRect.bottom())));
// Only when zoomed in and region small (performance)
if (scale_factor > 20.0 && (endX - startX + 1) * (endY - startY + 1) < 500) {
const qreal f = std::clamp(scale_factor, 0.5, 50.0);
QFont font("Arial", 1);
font.setPixelSize(1); // 1 px font; we use scaling below as needed
for (int y = startY; y < endY; ++y) {
const size_t base = size_t(y) * size_t(W);
for (int x = startX; x < endX; ++x) {
const size_t idx = base + size_t(x);
const float raw = image_fp[idx];
const rgb c = image_rgb[idx];
// If no scalar was stored (e.g., RGB source), you can skip or compute some alt text
if (std::isnan(raw)) continue;
// Text content from original value
const QString pixelText = QString::number(raw);
// Contrast against colored pixel
QGraphicsTextItem *textItem = scene()->addText(pixelText, font);
if (luminance(c) > 128.0)
textItem->setDefaultTextColor(Qt::black);
else
textItem->setDefaultTextColor(Qt::white);
textItem->setPos(x - 0.7, y - 0.8);
textItem->setScale(0.2);
textItem->setZValue(10.0);
}
}
}
DrawROI();
}