From 003e2f0a8e6d9e4415f44e5b965b008eaa39f47a Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Thu, 6 Nov 2025 16:01:37 +0100 Subject: [PATCH] jfjoch_viewer: Add calibration viewer --- viewer/CMakeLists.txt | 5 + viewer/JFJochImageReadingWorker.cpp | 14 +- viewer/JFJochImageReadingWorker.h | 5 + viewer/JFJochViewerMenu.cpp | 17 ++ viewer/JFJochViewerMenu.h | 9 + viewer/JFJochViewerWindow.cpp | 14 + viewer/SimpleImage.h | 10 + viewer/widgets/JFJochSimpleImageViewer.cpp | 331 +++++++++++++++++++++ viewer/widgets/JFJochSimpleImageViewer.h | 71 +++++ viewer/windows/JFJochCalibrationWindow.cpp | 108 +++++++ viewer/windows/JFJochCalibrationWindow.h | 46 +++ 11 files changed, 629 insertions(+), 1 deletion(-) create mode 100644 viewer/SimpleImage.h create mode 100644 viewer/widgets/JFJochSimpleImageViewer.cpp create mode 100644 viewer/widgets/JFJochSimpleImageViewer.h create mode 100644 viewer/windows/JFJochCalibrationWindow.cpp create mode 100644 viewer/windows/JFJochCalibrationWindow.h diff --git a/viewer/CMakeLists.txt b/viewer/CMakeLists.txt index acf8a6c7..acef49e3 100644 --- a/viewer/CMakeLists.txt +++ b/viewer/CMakeLists.txt @@ -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 diff --git a/viewer/JFJochImageReadingWorker.cpp b/viewer/JFJochImageReadingWorker.cpp index fd8dc25b..4dcae12b 100644 --- a/viewer/JFJochImageReadingWorker.cpp +++ b/viewer/JFJochImageReadingWorker.cpp @@ -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(); - + try { + tmp->image = file_reader.ReadCalibration(tmp->buffer, dataset.toStdString()); + std::shared_ptr 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"); +} diff --git a/viewer/JFJochImageReadingWorker.h b/viewer/JFJochImageReadingWorker.h index 841bec3b..745ba070 100644 --- a/viewer/JFJochImageReadingWorker.h +++ b/viewer/JFJochImageReadingWorker.h @@ -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) Q_DECLARE_METATYPE(std::shared_ptr) @@ -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) 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 &v); + void simpleImageLoaded(std::shared_ptr &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); }; diff --git a/viewer/JFJochViewerMenu.cpp b/viewer/JFJochViewerMenu.cpp index 3321d508..583f2cc7 100644 --- a/viewer/JFJochViewerMenu.cpp +++ b/viewer/JFJochViewerMenu.cpp @@ -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(); diff --git a/viewer/JFJochViewerMenu.h b/viewer/JFJochViewerMenu.h index 0539e14d..f6702c7a 100644 --- a/viewer/JFJochViewerMenu.h +++ b/viewer/JFJochViewerMenu.h @@ -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(); diff --git a/viewer/JFJochViewerWindow.cpp b/viewer/JFJochViewerWindow.cpp index 8fc66247..5f809706 100644 --- a/viewer/JFJochViewerWindow.cpp +++ b/viewer/JFJochViewerWindow.cpp @@ -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); } diff --git a/viewer/SimpleImage.h b/viewer/SimpleImage.h new file mode 100644 index 00000000..12c65880 --- /dev/null +++ b/viewer/SimpleImage.h @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#pragma once +#include "../common/CompressedImage.h" + +struct SimpleImage { + CompressedImage image; + std::vector buffer; +}; diff --git a/viewer/widgets/JFJochSimpleImageViewer.cpp b/viewer/widgets/JFJochSimpleImageViewer.cpp new file mode 100644 index 00000000..d9b0adc7 --- /dev/null +++ b/viewer/widgets/JFJochSimpleImageViewer.cpp @@ -0,0 +1,331 @@ +// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#include "JFJochSimpleImageViewer.h" +#include +#include +#include +#include +#include +#include +#include + +#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 &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(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 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(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(qimg, src); + break; + case CompressedImageMode::Int8: + renderImage(qimg, src); + break; + case CompressedImageMode::Uint16: + renderImage(qimg, src); + break; + case CompressedImageMode::Int16: + renderImage(qimg, src); + break; + case CompressedImageMode::Uint32: + renderImage(qimg, src); + break; + case CompressedImageMode::Int32: + renderImage(qimg, src); + break; + case CompressedImageMode::Float32: + renderImage(qimg, src); + break; + case CompressedImageMode::Float64: + renderImage(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(std::floor(visibleRect.left()))); + const int endX = std::min(W, static_cast(std::ceil(visibleRect.right()))); + const int startY = std::max(0, static_cast(std::floor(visibleRect.top()))); + const int endY = std::min(H, static_cast(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 +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(input); + int64_t val; + for (int i = 0; i < W * H; i++) { + if (std::is_floating_point_v) + val = std::lround(ptr[i]); + else + val = static_cast(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 + } + } +} diff --git a/viewer/widgets/JFJochSimpleImageViewer.h b/viewer/widgets/JFJochSimpleImageViewer.h new file mode 100644 index 00000000..368a4302 --- /dev/null +++ b/viewer/widgets/JFJochSimpleImageViewer.h @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#pragma once + +#include +#include +#include +#include +#include +#include + +#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 &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 image_; + bool has_image_ = false; + QPixmap pixmap_; + std::vector image_rgb_; // intermediate RGB buffer to feed QImage + std::vector 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 + void renderImage(QImage &qimg, const uint8_t *input); +}; diff --git a/viewer/windows/JFJochCalibrationWindow.cpp b/viewer/windows/JFJochCalibrationWindow.cpp new file mode 100644 index 00000000..214ebc12 --- /dev/null +++ b/viewer/windows/JFJochCalibrationWindow.cpp @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#include "JFJochCalibrationWindow.h" +#include +#include +#include +#include + +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(ColorScaleEnum::Viridis)); + color_map_select->addItem("Heat", static_cast(ColorScaleEnum::Heat)); + color_map_select->addItem("Indigo", static_cast(ColorScaleEnum::Indigo)); + color_map_select->addItem("B/W", static_cast(ColorScaleEnum::BW)); + color_map_select->setCurrentIndex(static_cast(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 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 &image) { + viewer->setImage(image); +} + +void JFJochCalibrationWindow::LoadMask() { + if (dataset) { + auto mask = std::make_shared(); + 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)); +} diff --git a/viewer/windows/JFJochCalibrationWindow.h b/viewer/windows/JFJochCalibrationWindow.h new file mode 100644 index 00000000..2018f3a8 --- /dev/null +++ b/viewer/windows/JFJochCalibrationWindow.h @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute +// SPDX-License-Identifier: GPL-3.0-only + +#pragma once + +#include +#include +#include +#include + +#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 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 in_dataset); + void calibrationLoaded(std::shared_ptr &image); +};