From fef9c2f4c346d5f73255081bc6b4bfd83d7d5b6c Mon Sep 17 00:00:00 2001 From: leonarski_f Date: Fri, 19 Jun 2026 14:52:21 +0200 Subject: [PATCH] viewer: load user mask from TIFF (replace or add) Two Mask-menu entries read a 16-bit TIFF (via ReadTIFFFromString16) and apply it as the user mask: "replace" overwrites the current mask, "add" unions the TIFF into it. The TIFF must match the detector dimensions; any non-zero pixel masks. Both go through UpdateUserMask_i like the other mask edits. Co-Authored-By: Claude Opus 4.8 --- viewer/JFJochImageReadingWorker.cpp | 36 +++++++++++++++++++++++++++++ viewer/JFJochImageReadingWorker.h | 1 + viewer/JFJochViewerMenu.cpp | 17 ++++++++++++++ viewer/JFJochViewerMenu.h | 4 ++++ viewer/JFJochViewerWindow.cpp | 3 +++ 5 files changed, 61 insertions(+) diff --git a/viewer/JFJochImageReadingWorker.cpp b/viewer/JFJochImageReadingWorker.cpp index 015f47b3..a3a3bdd4 100644 --- a/viewer/JFJochImageReadingWorker.cpp +++ b/viewer/JFJochImageReadingWorker.cpp @@ -3,6 +3,8 @@ #include #include +#include +#include #include "../common/JFJochMath.h" #ifndef _WIN32 #include @@ -747,6 +749,40 @@ void JFJochImageReadingWorker::SaveUserMaskTIFF(QString filename) { WriteTIFFToFile(filename.toStdString(), mask_image); } +void JFJochImageReadingWorker::LoadUserMaskTIFF(QString filename, bool replace) { + QMutexLocker locker(&m); + if (!current_image_ptr) + return; + + std::ifstream f(filename.toStdString(), std::ios::binary); + const std::string content((std::istreambuf_iterator(f)), std::istreambuf_iterator()); + + uint32_t cols = 0, lines = 0; + std::vector tiff; + try { + tiff = ReadTIFFFromString16(content, cols, lines); + } catch (const std::exception &e) { + logger.Error("Could not read mask TIFF {}: {}", filename.toStdString(), e.what()); + return; + } + + const int64_t width = current_image_ptr->Dataset().experiment.GetXPixelsNum(); + const int64_t height = current_image_ptr->Dataset().experiment.GetYPixelsNum(); + if (static_cast(cols) != width || static_cast(lines) != height) { + logger.Error("Mask TIFF is {}x{} but the detector is {}x{}", cols, lines, width, height); + return; + } + + // replace: start from an empty mask; add: keep the current one. A non-zero TIFF pixel masks. + auto user_mask = replace ? std::vector(width * height, 0) + : current_image_ptr->Dataset().pixel_mask.GetUserMask(); + for (size_t i = 0; i < tiff.size(); i++) + if (tiff[i] != 0) + user_mask[i] = 1; + + UpdateUserMask_i(user_mask); +} + void JFJochImageReadingWorker::UploadUserMask() { QMutexLocker locker(&m); if (!current_image_ptr) diff --git a/viewer/JFJochImageReadingWorker.h b/viewer/JFJochImageReadingWorker.h index e4857d49..87b26843 100644 --- a/viewer/JFJochImageReadingWorker.h +++ b/viewer/JFJochImageReadingWorker.h @@ -175,6 +175,7 @@ public slots: void UploadROIsToServer(); void SaveUserMaskTIFF(QString filename); + void LoadUserMaskTIFF(QString filename, bool replace); void UploadUserMask(); void ClearUserMask(); diff --git a/viewer/JFJochViewerMenu.cpp b/viewer/JFJochViewerMenu.cpp index ff630a4d..563aa54a 100644 --- a/viewer/JFJochViewerMenu.cpp +++ b/viewer/JFJochViewerMenu.cpp @@ -44,6 +44,12 @@ JFJochViewerMenu::JFJochViewerMenu(QWidget *parent) : QMenuBar(parent) { const QAction *saveMaskTiff = maskMenu->addAction("Save User Mask as TIFF..."); connect(saveMaskTiff, &QAction::triggered, this, &JFJochViewerMenu::saveUserMaskAsTiffSelected); + const QAction *loadMaskReplace = maskMenu->addAction("Load User Mask from TIFF (replace)..."); + connect(loadMaskReplace, &QAction::triggered, [this] { openLoadUserMaskTiff(true); }); + + const QAction *loadMaskAdd = maskMenu->addAction("Load User Mask from TIFF (add)..."); + connect(loadMaskAdd, &QAction::triggered, [this] { openLoadUserMaskTiff(false); }); + const QAction *uploadMask = maskMenu->addAction("&Upload User Mask"); connect(uploadMask, &QAction::triggered, this, &JFJochViewerMenu::uploadUserMaskAction); @@ -185,6 +191,17 @@ void JFJochViewerMenu::saveUserMaskAsTiffSelected() { } } +void JFJochViewerMenu::openLoadUserMaskTiff(bool replace) { + QString filename = QFileDialog::getOpenFileName( + this, + replace ? "Load User Mask from TIFF (replace)" : "Load User Mask from TIFF (add)", + "", + "TIFF Images (*.tif *.tiff);;All Files (*)" + ); + if (!filename.isEmpty()) + emit loadUserMaskTiffSelected(filename, replace); +} + void JFJochViewerMenu::uploadUserMaskAction() { auto reply = QMessageBox::question( this, diff --git a/viewer/JFJochViewerMenu.h b/viewer/JFJochViewerMenu.h index 1bed4966..6d64a674 100644 --- a/viewer/JFJochViewerMenu.h +++ b/viewer/JFJochViewerMenu.h @@ -28,6 +28,7 @@ signals: void fileCloseSelected(); void saveUserMaskTiffSelected(const QString &filename); + void loadUserMaskTiffSelected(const QString &filename, bool replace); void uploadUserMaskSelected(); void clearUserMaskSelected(); void openDatasetInfo(); @@ -41,4 +42,7 @@ private slots: void saveUserMaskAsTiffSelected(); void uploadUserMaskAction(); + +private: + void openLoadUserMaskTiff(bool replace); }; diff --git a/viewer/JFJochViewerWindow.cpp b/viewer/JFJochViewerWindow.cpp index 0f2f04f5..90f04c95 100644 --- a/viewer/JFJochViewerWindow.cpp +++ b/viewer/JFJochViewerWindow.cpp @@ -208,6 +208,9 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString connect(menuBar, &JFJochViewerMenu::saveUserMaskTiffSelected, reading_worker, &JFJochImageReadingWorker::SaveUserMaskTIFF); + connect(menuBar, &JFJochViewerMenu::loadUserMaskTiffSelected, + reading_worker, &JFJochImageReadingWorker::LoadUserMaskTIFF); + connect(menuBar, &JFJochViewerMenu::uploadUserMaskSelected, reading_worker, &JFJochImageReadingWorker::UploadUserMask);