jfjoch_viewer: Add calibration viewer
This commit is contained in:
@@ -64,6 +64,11 @@ ADD_EXECUTABLE(jfjoch_viewer jfjoch_viewer.cpp JFJochViewerWindow.cpp JFJochView
|
||||
JFJochViewerToolbarImage.h
|
||||
JFJochViewerToolbarDisplay.cpp
|
||||
JFJochViewerToolbarDisplay.h
|
||||
SimpleImage.h
|
||||
widgets/JFJochSimpleImageViewer.cpp
|
||||
widgets/JFJochSimpleImageViewer.h
|
||||
windows/JFJochCalibrationWindow.cpp
|
||||
windows/JFJochCalibrationWindow.h
|
||||
)
|
||||
|
||||
TARGET_LINK_LIBRARIES(jfjoch_viewer Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Charts Qt6::DBus
|
||||
|
||||
@@ -388,5 +388,17 @@ void JFJochImageReadingWorker::UploadUserMask() {
|
||||
http_reader.UploadUserMask(user_mask);
|
||||
}
|
||||
|
||||
void JFJochImageReadingWorker::LoadCalibration(QString dataset) {
|
||||
if (!http_mode) {
|
||||
auto tmp = std::make_shared<SimpleImage>();
|
||||
|
||||
|
||||
try {
|
||||
tmp->image = file_reader.ReadCalibration(tmp->buffer, dataset.toStdString());
|
||||
std::shared_ptr<const SimpleImage> ctmp = tmp;
|
||||
emit simpleImageLoaded(ctmp);
|
||||
} catch (const std::exception &e) {
|
||||
logger.Info("Error loading calibration: {}", e.what());
|
||||
}
|
||||
}
|
||||
logger.Info("HTTP mode doesn't allow to read calibration (at the moment");
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "../common/Logger.h"
|
||||
#include "../reader/JFJochHttpReader.h"
|
||||
#include "../image_analysis/MXAnalysisWithoutFPGA.h"
|
||||
#include "SimpleImage.h"
|
||||
|
||||
Q_DECLARE_METATYPE(std::shared_ptr<const JFJochReaderDataset>)
|
||||
Q_DECLARE_METATYPE(std::shared_ptr<const JFJochReaderImage>)
|
||||
@@ -23,6 +24,7 @@ Q_DECLARE_METATYPE(DiffractionExperiment)
|
||||
Q_DECLARE_METATYPE(SpotFindingSettings)
|
||||
Q_DECLARE_METATYPE(IndexingSettings)
|
||||
Q_DECLARE_METATYPE(UnitCell)
|
||||
Q_DECLARE_METATYPE(std::shared_ptr<const SimpleImage>)
|
||||
|
||||
class JFJochImageReadingWorker : public QObject {
|
||||
Q_OBJECT
|
||||
@@ -67,6 +69,7 @@ signals:
|
||||
void imageNumberChanged(int64_t total_images, int64_t current_image);
|
||||
void setToolbarMode(JFJochViewerToolbarImage::ToolbarMode input);
|
||||
void setRings(const QVector<float> &v);
|
||||
void simpleImageLoaded(std::shared_ptr<const SimpleImage> &image);
|
||||
public:
|
||||
JFJochImageReadingWorker(const SpotFindingSettings &settings, const DiffractionExperiment& experiment, QObject *parent = nullptr);
|
||||
~JFJochImageReadingWorker() override = default;
|
||||
@@ -91,6 +94,8 @@ public slots:
|
||||
void SaveUserMaskTIFF(QString filename);
|
||||
void UploadUserMask();
|
||||
void ClearUserMask();
|
||||
|
||||
void LoadCalibration(QString dataset);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -76,6 +76,12 @@ JFJochViewerMenu::JFJochViewerMenu(QWidget *parent) : QMenuBar(parent) {
|
||||
connect(toggleProcessingWindowAction, &QAction::toggled,
|
||||
this, &JFJochViewerMenu::processingWindowToggled);
|
||||
|
||||
toggleCalibrationWindowAction = windowMenu->addAction("Calibration");
|
||||
toggleCalibrationWindowAction->setCheckable(true);
|
||||
toggleCalibrationWindowAction->setChecked(false);
|
||||
connect(toggleCalibrationWindowAction, &QAction::toggled,
|
||||
this, &JFJochViewerMenu::calibrationWindowToggled);
|
||||
|
||||
QMenu *helpMenu = addMenu("Help");
|
||||
// Add "About" action
|
||||
const QAction *aboutAction = helpMenu->addAction("About");
|
||||
@@ -153,6 +159,17 @@ void JFJochViewerMenu::metadataWindowClosing() {
|
||||
toggleMetadataWindowAction->setChecked(false);
|
||||
}
|
||||
|
||||
void JFJochViewerMenu::calibrationWindowClosing() {
|
||||
toggleCalibrationWindowAction->setChecked(false);
|
||||
}
|
||||
|
||||
void JFJochViewerMenu::calibrationWindowToggled(bool checked) {
|
||||
if (checked)
|
||||
emit openCalibrationWindow();
|
||||
else
|
||||
emit closeCalibrationWindow();
|
||||
}
|
||||
|
||||
void JFJochViewerMenu::spotListWindowToggled(bool checked) {
|
||||
if (checked)
|
||||
emit openSpotListWindow();
|
||||
|
||||
@@ -13,6 +13,8 @@ class JFJochViewerMenu : public QMenuBar {
|
||||
QAction *toggleSpotWindowAction = nullptr;
|
||||
QAction *toggleReflectionWindowAction = nullptr;
|
||||
QAction *toggleProcessingWindowAction = nullptr;
|
||||
QAction *toggleCalibrationWindowAction = nullptr;
|
||||
|
||||
public:
|
||||
explicit JFJochViewerMenu(QWidget *parent = nullptr);
|
||||
~JFJochViewerMenu() override = default;
|
||||
@@ -31,6 +33,10 @@ signals:
|
||||
void openProcessingWindow();
|
||||
void closeProcessingWindow();
|
||||
|
||||
void openCalibrationWindow();
|
||||
void closeCalibrationWindow();
|
||||
|
||||
|
||||
void saveUserMaskTiffSelected(const QString &filename);
|
||||
void uploadUserMaskSelected();
|
||||
void clearUserMaskSelected();
|
||||
@@ -41,6 +47,8 @@ public slots:
|
||||
void spotListWindowClosing();
|
||||
void reflectionListWindowClosing();
|
||||
void processingWindowClosing();
|
||||
void calibrationWindowClosing();
|
||||
|
||||
private slots:
|
||||
void aboutSelected();
|
||||
void quitSelected();
|
||||
@@ -52,6 +60,7 @@ private slots:
|
||||
void spotListWindowToggled(bool checked);
|
||||
void reflectionListWindowToggled(bool checked);
|
||||
void processingWindowToggled(bool checked);
|
||||
void calibrationWindowToggled(bool checked);
|
||||
|
||||
void saveUserMaskAsTiffSelected();
|
||||
void uploadUserMaskAction();
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "windows/JFJochViewerProcessingWindow.h"
|
||||
#include "windows/JFJochViewerSpotListWindow.h"
|
||||
#include "windows/JFJochViewerReflectionListWindow.h"
|
||||
#include "windows/JFJochCalibrationWindow.h"
|
||||
|
||||
JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString &file) : QMainWindow(parent) {
|
||||
menuBar = new JFJochViewerMenu(this);
|
||||
@@ -90,6 +91,7 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString
|
||||
auto spotWindow = new JFJochViewerSpotListWindow(this);
|
||||
auto reflectionWindow = new JFJochViewerReflectionListWindow(this);
|
||||
auto processingWindow = new JFJochViewerProcessingWindow(spot_finding_settings, indexing_settings, this);
|
||||
auto calibrationWindow = new JFJochCalibrationWindow(this);
|
||||
|
||||
new JFJochViewerAdaptor(this);
|
||||
|
||||
@@ -309,6 +311,18 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString
|
||||
connect(side_panel, &JFJochViewerSidePanel::highlightIceRings,
|
||||
viewer, &JFJochViewerImage::highlightIceRings);
|
||||
|
||||
connect(menuBar, &JFJochViewerMenu::openCalibrationWindow,
|
||||
calibrationWindow, &JFJochCalibrationWindow::open);
|
||||
connect(menuBar, &JFJochViewerMenu::closeCalibrationWindow,
|
||||
calibrationWindow, &JFJochCalibrationWindow::close);
|
||||
connect(calibrationWindow, &JFJochCalibrationWindow::closing,
|
||||
menuBar, &JFJochViewerMenu::calibrationWindowClosing);
|
||||
|
||||
connect(reading_worker, &JFJochImageReadingWorker::datasetLoaded,
|
||||
calibrationWindow, &JFJochCalibrationWindow::datasetLoaded);
|
||||
connect(reading_worker, &JFJochImageReadingWorker::simpleImageLoaded,
|
||||
calibrationWindow, &JFJochCalibrationWindow::calibrationLoaded);
|
||||
|
||||
if (!file.isEmpty())
|
||||
LoadFile(file, 0, 1);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#pragma once
|
||||
#include "../common/CompressedImage.h"
|
||||
|
||||
struct SimpleImage {
|
||||
CompressedImage image;
|
||||
std::vector<uint8_t> buffer;
|
||||
};
|
||||
@@ -0,0 +1,331 @@
|
||||
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#include "JFJochSimpleImageViewer.h"
|
||||
#include <QGraphicsScene>
|
||||
#include <QGraphicsPixmapItem>
|
||||
#include <QWheelEvent>
|
||||
#include <QScrollBar>
|
||||
#include <QtMath>
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
|
||||
#include "../../common/JFJochException.h"
|
||||
|
||||
static inline double normalize_to_unit(double raw, double bg, double fg) {
|
||||
const double denom = std::max(1e-12, double(fg) - double(bg));
|
||||
double v = (raw - bg) / denom;
|
||||
if (v < 0.0) v = 0.0;
|
||||
if (v > 1.0) v = 1.0;
|
||||
return v;
|
||||
}
|
||||
|
||||
JFJochSimpleImageViewer::JFJochSimpleImageViewer(QWidget *parent)
|
||||
: QGraphicsView(parent) {
|
||||
setDragMode(QGraphicsView::NoDrag);
|
||||
setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
|
||||
setRenderHint(QPainter::Antialiasing);
|
||||
setRenderHint(QPainter::SmoothPixmapTransform);
|
||||
setFocusPolicy(Qt::ClickFocus);
|
||||
|
||||
auto *scn = new QGraphicsScene(this);
|
||||
setScene(scn);
|
||||
|
||||
// Optional: a sensible default colormap
|
||||
color_scale_.Select(ColorScaleEnum::Indigo);
|
||||
|
||||
// Keep overlays in pixel units independent of zoom (for labels font sizing)
|
||||
setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
|
||||
}
|
||||
|
||||
void JFJochSimpleImageViewer::clear() {
|
||||
has_image_ = false;
|
||||
image_.reset();
|
||||
|
||||
if (scene())
|
||||
scene()->clear();
|
||||
}
|
||||
|
||||
void JFJochSimpleImageViewer::setImage(const std::shared_ptr<const SimpleImage> &img) {
|
||||
image_ = img;
|
||||
has_image_ = true;
|
||||
renderImage();
|
||||
updateScene();
|
||||
}
|
||||
|
||||
void JFJochSimpleImageViewer::setBackground(float v) {
|
||||
background_ = v;
|
||||
emit backgroundChanged(background_);
|
||||
if (has_image_) {
|
||||
renderImage();
|
||||
// Preserve current transform while updating
|
||||
updateScene();
|
||||
}
|
||||
}
|
||||
|
||||
void JFJochSimpleImageViewer::setForeground(float v) {
|
||||
foreground_ = std::max(1e-6f, v);
|
||||
emit foregroundChanged(foreground_);
|
||||
if (has_image_) {
|
||||
renderImage();
|
||||
updateScene();
|
||||
}
|
||||
}
|
||||
|
||||
void JFJochSimpleImageViewer::setColorMap(int colorMapEnumValue) {
|
||||
try {
|
||||
color_scale_.Select(static_cast<ColorScaleEnum>(colorMapEnumValue));
|
||||
if (has_image_) {
|
||||
renderImage();
|
||||
updateScene();
|
||||
}
|
||||
} catch (...) {
|
||||
// ignore invalid value
|
||||
}
|
||||
}
|
||||
|
||||
void JFJochSimpleImageViewer::wheelEvent(QWheelEvent *event) {
|
||||
if (!scene() || !has_image_) return;
|
||||
|
||||
const double zoomFactor = 1.15;
|
||||
const QPointF targetScenePos = mapToScene(event->position().toPoint());
|
||||
|
||||
if (event->modifiers() == Qt::ShiftModifier) {
|
||||
// Shift + wheel adjusts foreground (like your main viewer)
|
||||
float new_fg = foreground_ + event->angleDelta().y() / 120.0f;
|
||||
if (new_fg < 1.0f) new_fg = 1.0f;
|
||||
setForeground(new_fg);
|
||||
} else {
|
||||
if (event->angleDelta().y() > 0) {
|
||||
if (scale_factor_ * zoomFactor < 500.0) {
|
||||
scale_factor_ *= zoomFactor;
|
||||
scale(zoomFactor, zoomFactor);
|
||||
}
|
||||
} else {
|
||||
if (scale_factor_ > 0.2) {
|
||||
scale_factor_ *= 1.0 / zoomFactor;
|
||||
scale(1.0 / zoomFactor, 1.0 / zoomFactor);
|
||||
}
|
||||
}
|
||||
// Keep focus under mouse
|
||||
const QPointF updatedCenter = mapToScene(viewport()->rect().center());
|
||||
const QPointF delta = targetScenePos - updatedCenter;
|
||||
translate(delta.x(), delta.y());
|
||||
// Only labels depend on redraw; pixmap stays. Rebuild scene to re-place labels
|
||||
updateScene();
|
||||
}
|
||||
}
|
||||
|
||||
void JFJochSimpleImageViewer::mousePressEvent(QMouseEvent *event) {
|
||||
if (!scene() || !has_image_) return;
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
panning_ = true;
|
||||
setCursor(Qt::ClosedHandCursor);
|
||||
last_mouse_pos_ = event->pos();
|
||||
}
|
||||
QGraphicsView::mousePressEvent(event);
|
||||
}
|
||||
|
||||
void JFJochSimpleImageViewer::mouseMoveEvent(QMouseEvent *event) {
|
||||
if (!scene() || !has_image_) return;
|
||||
if (panning_) {
|
||||
const QPointF delta = mapToScene(event->pos()) - mapToScene(last_mouse_pos_);
|
||||
last_mouse_pos_ = event->pos();
|
||||
translate(delta.x(), delta.y());
|
||||
// labels positions are in scene coords, scene items re-used; no special handling
|
||||
} else {
|
||||
auto coord = mapToScene(event->pos());
|
||||
if ((coord.x() >= 0)
|
||||
&& (coord.x() < image_->image.GetWidth())
|
||||
&& (coord.y() >= 0)
|
||||
&& (coord.y() < image_->image.GetHeight())) {
|
||||
emit writeStatusBar(QString("x=%1 y=%2 %3 ")
|
||||
.arg(coord.x(), 0, 'f', 1)
|
||||
.arg(coord.y(), 0, 'f', 1)
|
||||
.arg(image_values_[int(coord.x()) + int(coord.y()) * image_->image.GetWidth()]
|
||||
), 6000);
|
||||
} else
|
||||
emit writeStatusBar("", 6000);
|
||||
}
|
||||
QGraphicsView::mouseMoveEvent(event);
|
||||
}
|
||||
|
||||
void JFJochSimpleImageViewer::mouseReleaseEvent(QMouseEvent *event) {
|
||||
if (!scene() || !has_image_) return;
|
||||
if (event->button() == Qt::LeftButton && panning_) {
|
||||
panning_ = false;
|
||||
setCursor(Qt::ArrowCursor);
|
||||
}
|
||||
QGraphicsView::mouseReleaseEvent(event);
|
||||
}
|
||||
|
||||
void JFJochSimpleImageViewer::resizeEvent(QResizeEvent *event) {
|
||||
QGraphicsView::resizeEvent(event);
|
||||
// Nothing special; keep current transform and scene
|
||||
}
|
||||
|
||||
void JFJochSimpleImageViewer::renderImage() {
|
||||
const size_t W = image_->image.GetWidth();
|
||||
const size_t H = image_->image.GetHeight();
|
||||
if (W == 0 || H == 0) return;
|
||||
|
||||
// Prepare destination QImage (RGB888)
|
||||
QImage qimg(int(W), int(H), QImage::Format_RGB888);
|
||||
image_rgb_.resize(W * H);
|
||||
image_values_.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();
|
||||
|
||||
auto write_rgb_row = [&](int y) -> uchar * {
|
||||
return qimg.scanLine(y);
|
||||
};
|
||||
|
||||
auto write_pixel = [&](size_t idx, uchar *dstRow, const rgb &c) {
|
||||
dstRow[idx * 3 + 0] = c.r;
|
||||
dstRow[idx * 3 + 1] = c.g;
|
||||
dstRow[idx * 3 + 2] = c.b;
|
||||
image_rgb_[idx] = c;
|
||||
};
|
||||
|
||||
// Fast path: packed RGB (assumed 3 bytes per pixel)
|
||||
if (mode == CompressedImageMode::RGB) {
|
||||
for (int y = 0; y < int(H); ++y) {
|
||||
const rgb *row = reinterpret_cast<const rgb *>(src + y * W * sizeof(rgb));
|
||||
uchar *dst = write_rgb_row(y);
|
||||
for (size_t x = 0; x < W; ++x) {
|
||||
const rgb c = row[x];
|
||||
write_pixel(x, dst, c);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch (mode) {
|
||||
case CompressedImageMode::Uint8:
|
||||
renderImage<uint8_t>(qimg, src);
|
||||
break;
|
||||
case CompressedImageMode::Int8:
|
||||
renderImage<int8_t>(qimg, src);
|
||||
break;
|
||||
case CompressedImageMode::Uint16:
|
||||
renderImage<uint16_t>(qimg, src);
|
||||
break;
|
||||
case CompressedImageMode::Int16:
|
||||
renderImage<int16_t>(qimg, src);
|
||||
break;
|
||||
case CompressedImageMode::Uint32:
|
||||
renderImage<uint32_t>(qimg, src);
|
||||
break;
|
||||
case CompressedImageMode::Int32:
|
||||
renderImage<int32_t>(qimg, src);
|
||||
break;
|
||||
case CompressedImageMode::Float32:
|
||||
renderImage<float>(qimg, src);
|
||||
break;
|
||||
case CompressedImageMode::Float64:
|
||||
renderImage<double>(qimg, src);
|
||||
break;
|
||||
case CompressedImageMode::Float16:
|
||||
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Float16 not supported");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Build pixmap
|
||||
pixmap_ = QPixmap::fromImage(qimg);
|
||||
}
|
||||
|
||||
void JFJochSimpleImageViewer::updateScene() {
|
||||
if (!scene()) return;
|
||||
scene()->clear();
|
||||
if (!has_image_) 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 double raw = image_values_[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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void JFJochSimpleImageViewer::renderImage(QImage &qimg, 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);
|
||||
int64_t val;
|
||||
for (int i = 0; i < W * H; i++) {
|
||||
if (std::is_floating_point_v<T>)
|
||||
val = std::lround(ptr[i]);
|
||||
else
|
||||
val = static_cast<int64_t>(ptr[i]);
|
||||
image_values_[i] = val;
|
||||
}
|
||||
|
||||
if (auto_fgbg) {
|
||||
auto vec1(image_values_);
|
||||
std::sort(vec1.begin(), vec1.end());
|
||||
background_ = vec1[0];
|
||||
foreground_ = vec1[vec1.size() - 1];
|
||||
emit backgroundChanged(background_);
|
||||
emit foregroundChanged(foreground_);
|
||||
}
|
||||
|
||||
for (int y = 0; y < H; ++y) {
|
||||
uchar *scanLine = qimg.scanLine(y); // Get writable pointer to the row
|
||||
for (int x = 0; x < W; ++x) {
|
||||
image_rgb_[y * W + x] = color_scale_.Apply(image_values_[y * W + x], background_, foreground_);
|
||||
scanLine[x * 3 + 0] = image_rgb_[y * W + x].r; // Red
|
||||
scanLine[x * 3 + 1] = image_rgb_[y * W + x].g; // Green
|
||||
scanLine[x * 3 + 2] = image_rgb_[y * W + x].b; // Blue
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QGraphicsView>
|
||||
#include <QVector>
|
||||
#include <QColor>
|
||||
#include <QPointF>
|
||||
#include <QGraphicsTextItem>
|
||||
#include <vector>
|
||||
|
||||
#include "../SimpleImage.h"
|
||||
#include "../../common/ColorScale.h"
|
||||
|
||||
class JFJochSimpleImageViewer : public QGraphicsView {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit JFJochSimpleImageViewer(QWidget* parent = nullptr);
|
||||
void setImage(const std::shared_ptr<const SimpleImage> &img);
|
||||
void clear();
|
||||
|
||||
public slots:
|
||||
void setBackground(float v);
|
||||
void setForeground(float v);
|
||||
void setColorMap(int colorMapEnumValue);
|
||||
|
||||
signals:
|
||||
void foregroundChanged(float v);
|
||||
void backgroundChanged(float v);
|
||||
void writeStatusBar(QString string, int timeout);
|
||||
|
||||
protected:
|
||||
void wheelEvent(QWheelEvent* event) override;
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
void mouseMoveEvent(QMouseEvent* event) override;
|
||||
void mouseReleaseEvent(QMouseEvent* event) override;
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
|
||||
private:
|
||||
struct Label {
|
||||
QPointF pos;
|
||||
QString text;
|
||||
QColor color;
|
||||
};
|
||||
|
||||
void renderImage(); // builds pixmap_ from current image_ + settings
|
||||
void updateScene(); // clears scene, adds pixmap + labels
|
||||
|
||||
// Image and rendering state
|
||||
std::shared_ptr<const SimpleImage> image_;
|
||||
bool has_image_ = false;
|
||||
QPixmap pixmap_;
|
||||
std::vector<rgb> image_rgb_; // intermediate RGB buffer to feed QImage
|
||||
std::vector<int64_t> image_values_; // original scalar per pixel (for label text)
|
||||
|
||||
// View/interaction
|
||||
double scale_factor_ = 1.0;
|
||||
QPoint last_mouse_pos_;
|
||||
bool panning_ = false;
|
||||
|
||||
// Settings
|
||||
float background_ = 0.0f;
|
||||
float foreground_ = 10.0f;
|
||||
ColorScale color_scale_;
|
||||
|
||||
bool auto_fgbg = true;
|
||||
|
||||
template <class T>
|
||||
void renderImage(QImage &qimg, const uint8_t *input);
|
||||
};
|
||||
@@ -0,0 +1,108 @@
|
||||
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#include "JFJochCalibrationWindow.h"
|
||||
#include <QCloseEvent>
|
||||
#include <QGridLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
|
||||
JFJochCalibrationWindow::JFJochCalibrationWindow(QWidget *parent) : QMainWindow(parent) {
|
||||
QWidget *centralWidget = new QWidget(this);
|
||||
setCentralWidget(centralWidget);
|
||||
|
||||
auto grid_layout = new QGridLayout();
|
||||
|
||||
viewer = new JFJochSimpleImageViewer(this);
|
||||
|
||||
calibration_option = new QComboBox(this);
|
||||
color_map_select = new QComboBox(this);
|
||||
|
||||
color_map_select->addItem("Viridis", static_cast<int>(ColorScaleEnum::Viridis));
|
||||
color_map_select->addItem("Heat", static_cast<int>(ColorScaleEnum::Heat));
|
||||
color_map_select->addItem("Indigo", static_cast<int>(ColorScaleEnum::Indigo));
|
||||
color_map_select->addItem("B/W", static_cast<int>(ColorScaleEnum::BW));
|
||||
color_map_select->setCurrentIndex(static_cast<int>(ColorScaleEnum::Indigo));
|
||||
|
||||
background_slider = new SliderPlusBox(1, 32768, 1.0, 0, this, SliderPlusBox::ScaleType::Linear);
|
||||
foreground_slider = new SliderPlusBox(1, 32768, 1.0, 0, this, SliderPlusBox::ScaleType::Linear);
|
||||
|
||||
auto background_row = new QHBoxLayout();
|
||||
auto foreground_row = new QHBoxLayout();
|
||||
background_row->addWidget(new QLabel("Background:"));
|
||||
background_row->addWidget(background_slider);
|
||||
foreground_row->addWidget(new QLabel("Foreground:"));
|
||||
foreground_row->addWidget(foreground_slider);
|
||||
|
||||
grid_layout->addWidget(calibration_option, 0, 0);
|
||||
grid_layout->addWidget(color_map_select, 0, 1);
|
||||
grid_layout->addLayout(background_row, 1, 0, 1, 2);
|
||||
grid_layout->addLayout(foreground_row, 2, 0, 1, 2);
|
||||
grid_layout->addWidget(viewer, 3, 0, 1, 2);
|
||||
|
||||
connect(viewer, &JFJochSimpleImageViewer::backgroundChanged,
|
||||
[this] (float val) {background_slider->setValue(val);});
|
||||
|
||||
connect(viewer, &JFJochSimpleImageViewer::foregroundChanged,
|
||||
[this] (float val) {foreground_slider->setValue(val);});
|
||||
|
||||
connect(color_map_select, &QComboBox::currentIndexChanged, this, [this](int index) {
|
||||
viewer->setColorMap(index);
|
||||
});
|
||||
|
||||
connect(calibration_option, &QComboBox::currentIndexChanged, this, &JFJochCalibrationWindow::CalibrationSelected);
|
||||
centralWidget->setLayout(grid_layout);
|
||||
|
||||
statusBar = new QStatusBar(this);
|
||||
setStatusBar(statusBar);
|
||||
|
||||
connect(viewer, &JFJochSimpleImageViewer::writeStatusBar,
|
||||
statusBar, &QStatusBar::showMessage);
|
||||
}
|
||||
|
||||
void JFJochCalibrationWindow::closeEvent(QCloseEvent *event) {
|
||||
event->accept();
|
||||
emit closing();
|
||||
}
|
||||
|
||||
void JFJochCalibrationWindow::open() {
|
||||
show();
|
||||
raise();
|
||||
activateWindow();
|
||||
}
|
||||
|
||||
void JFJochCalibrationWindow::datasetLoaded(std::shared_ptr<const JFJochReaderDataset> in_dataset) {
|
||||
dataset = std::move(in_dataset);
|
||||
|
||||
QSignalBlocker b(calibration_option);
|
||||
calibration_option->clear();
|
||||
if (dataset) {
|
||||
calibration_option->addItem("Pixel mask");
|
||||
for (auto &calibration : dataset->calibration_data)
|
||||
calibration_option->addItem(calibration.c_str());
|
||||
}
|
||||
LoadMask();
|
||||
}
|
||||
|
||||
void JFJochCalibrationWindow::calibrationLoaded(std::shared_ptr<const SimpleImage> &image) {
|
||||
viewer->setImage(image);
|
||||
}
|
||||
|
||||
void JFJochCalibrationWindow::LoadMask() {
|
||||
if (dataset) {
|
||||
auto mask = std::make_shared<SimpleImage>();
|
||||
auto tmp = dataset->pixel_mask.GetMask();
|
||||
mask->buffer.resize(tmp.size() * sizeof(uint32_t));
|
||||
memcpy(mask->buffer.data(), tmp.data(), tmp.size() * sizeof(uint32_t));
|
||||
mask->image = CompressedImage(mask->buffer.data(), mask->buffer.size(),
|
||||
dataset->experiment.GetXPixelsNum(), dataset->experiment.GetYPixelsNum(), CompressedImageMode::Uint32);
|
||||
viewer->setImage(mask);
|
||||
}
|
||||
}
|
||||
|
||||
void JFJochCalibrationWindow::CalibrationSelected(int val) {
|
||||
if (val == 0)
|
||||
LoadMask();
|
||||
else
|
||||
emit loadCalibration(calibration_option->itemText(val));
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QComboBox>
|
||||
#include <QString>
|
||||
#include <QStatusBar>
|
||||
|
||||
#include "../widgets/JFJochSimpleImageViewer.h"
|
||||
#include "../../reader/JFJochReaderDataset.h"
|
||||
#include "../SimpleImage.h"
|
||||
#include "../widgets/SliderPlusBox.h"
|
||||
|
||||
class JFJochCalibrationWindow : public QMainWindow {
|
||||
Q_OBJECT
|
||||
|
||||
QComboBox *calibration_option;
|
||||
QComboBox *color_map_select;
|
||||
SliderPlusBox *foreground_slider;
|
||||
SliderPlusBox *background_slider;
|
||||
|
||||
QStatusBar *statusBar;
|
||||
|
||||
std::shared_ptr<const JFJochReaderDataset> dataset;
|
||||
|
||||
JFJochSimpleImageViewer *viewer = nullptr;
|
||||
|
||||
void closeEvent(QCloseEvent *event) override;
|
||||
|
||||
public:
|
||||
JFJochCalibrationWindow(QWidget *parent = nullptr);
|
||||
|
||||
signals:
|
||||
void closing();
|
||||
void loadCalibration(QString name);
|
||||
|
||||
private slots:
|
||||
void LoadMask();
|
||||
void CalibrationSelected(int val);
|
||||
public slots:
|
||||
void open();
|
||||
void datasetLoaded(std::shared_ptr<const JFJochReaderDataset> in_dataset);
|
||||
void calibrationLoaded(std::shared_ptr<const SimpleImage> &image);
|
||||
};
|
||||
Reference in New Issue
Block a user