From 90b9b2cb9213e7c1af23076bdec54b049c6a1887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Du=C5=A1an=20Poizl?= Date: Tue, 14 Oct 2025 18:14:01 +0200 Subject: [PATCH] Basic working GUI --- CMakeLists.txt | 58 +++--------- laplacian.cpp | 108 +++++++++++++++++++++ laplacian.h | 10 ++ main.cpp | 249 +++++++++++++------------------------------------ mainwindow.cpp | 138 ++++++++++++++++++++++++++- mainwindow.h | 21 ++++- mainwindow.ui | 118 +++++++++++++++++++---- serfile.cpp | 16 ++++ serfile.h | 3 + 9 files changed, 473 insertions(+), 248 deletions(-) create mode 100644 laplacian.cpp create mode 100644 laplacian.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1231b45..6522747 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.5) -project(eibin VERSION 0.1 LANGUAGES CXX) +project(kouryu VERSION 0.1 LANGUAGES CXX) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOMOC ON) @@ -9,53 +9,27 @@ set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets) +find_package(QT NAMES Qt6 REQUIRED COMPONENTS Widgets) find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets) find_package(OpenCV REQUIRED) set(PROJECT_SOURCES main.cpp - mainwindow.cpp - mainwindow.h - mainwindow.ui - serfile.cpp - serfile.h + mainwindow.cpp mainwindow.h mainwindow.ui + serfile.cpp serfile.h + laplacian.cpp laplacian.h ) -if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) - qt_add_executable(eibin - MANUAL_FINALIZATION - ${PROJECT_SOURCES} - ) -# Define target properties for Android with Qt 6 as: -# set_property(TARGET eibin APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR -# ${CMAKE_CURRENT_SOURCE_DIR}/android) -# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation -else() - if(ANDROID) - add_library(eibin SHARED - ${PROJECT_SOURCES} - ) -# Define properties for Android with Qt 5 after find_package() calls as: -# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android") - else() - add_executable(eibin - ${PROJECT_SOURCES} - ) - endif() -endif() +qt_add_executable(kouryu + MANUAL_FINALIZATION + ${PROJECT_SOURCES} +) -target_link_libraries(eibin PRIVATE Qt${QT_VERSION_MAJOR}::Widgets ${OpenCV_LIBS}) -target_include_directories(eibin PRIVATE ${OpenCV_INCLUDE_DIRS}) -target_compile_options(eibin PRIVATE -mavx2) +target_link_libraries(kouryu PRIVATE Qt${QT_VERSION_MAJOR}::Widgets ${OpenCV_LIBS}) +target_include_directories(kouryu PRIVATE ${OpenCV_INCLUDE_DIRS}) +target_compile_options(kouryu PRIVATE -mavx2) -# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1. -# If you are developing for iOS or macOS you should consider setting an -# explicit, fixed bundle identifier manually though. -if(${QT_VERSION} VERSION_LESS 6.1.0) - set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.eibin) -endif() -set_target_properties(eibin PROPERTIES +set_target_properties(kouryu PROPERTIES ${BUNDLE_ID_OPTION} MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} @@ -64,12 +38,10 @@ set_target_properties(eibin PROPERTIES ) include(GNUInstallDirs) -install(TARGETS eibin +install(TARGETS kouryu BUNDLE DESTINATION . LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ) -if(QT_VERSION_MAJOR EQUAL 6) - qt_finalize_executable(eibin) -endif() +qt_finalize_executable(kouryu) diff --git a/laplacian.cpp b/laplacian.cpp new file mode 100644 index 0000000..80d8665 --- /dev/null +++ b/laplacian.cpp @@ -0,0 +1,108 @@ +#include "laplacian.h" + +#include + +double laplacian(const uint16_t *img, int32_t *out, uint32_t width, uint32_t height) +{ + __m256 mean = _mm256_setzero_ps(); + __m256 M2 = _mm256_setzero_ps(); + uint32_t count = 0; + for(uint32_t y = 1; y < height - 1; y++) + { + uint32_t row = (y - 1) * width; + for(uint32_t x = 1; x < width - 8; x += 8) + { + __m128i p0 = _mm_loadu_si128(reinterpret_cast<__m128i const*>(img + row + x)); + __m128i p1 = _mm_loadu_si128(reinterpret_cast<__m128i const*>(img + (row + width) + x - 1)); + __m128i p2 = _mm_loadu_si128(reinterpret_cast<__m128i const*>(img + (row + width) + x)); + __m128i p3 = _mm_loadu_si128(reinterpret_cast<__m128i const*>(img + (row + width) + x + 1)); + __m128i p4 = _mm_loadu_si128(reinterpret_cast<__m128i const*>(img + (row + width * 2) + x)); + + __m256i sum = _mm256_setzero_si256(); + + __m256i a; + a = _mm256_cvtepu16_epi32(p0); + sum = _mm256_add_epi32(sum, _mm256_cvtepu16_epi32(p0)); + sum = _mm256_add_epi32(sum, _mm256_cvtepu16_epi32(p1)); + sum = _mm256_add_epi32(sum, _mm256_cvtepu16_epi32(p3)); + sum = _mm256_add_epi32(sum, _mm256_cvtepu16_epi32(p4)); + sum = _mm256_sub_epi32(sum, _mm256_sll_epi32(_mm256_cvtepu16_epi32(p2), _mm_set1_epi64x(2))); + + if(out) + { + _mm256_storeu_si256(reinterpret_cast<__m256i*>(out + row + x), sum); + } + + __m256 af = _mm256_cvtepi32_ps(sum); + + count++; + __m256 delta = _mm256_sub_ps(af, mean); + mean = _mm256_add_ps(mean, _mm256_div_ps(delta, _mm256_set1_ps(static_cast(count)))); + __m256 delta2 = _mm256_sub_ps(af, mean); + M2 = _mm256_add_ps(M2, _mm256_mul_ps(delta, delta2)); + } + } + float mean_2[8]; + float M2_2[8]; + _mm256_storeu_ps(mean_2, mean); + _mm256_storeu_ps(M2_2, M2); + + auto welford_merge = [](uint32_t n, float &mean_1, float mean_2, float &M2_1, float M2_2) + { + uint32_t count = 2 * n; + float delta = mean_2 - mean_1; + float mean = mean_1 + delta * ((float)n / count); + float M2 = M2_1 + M2_2 + delta * delta * n * n / count; + mean_1 = mean; + M2_1 = M2; + }; + + /*for(int i = 0; i < 8; i++) + qDebug() << M2_2[i] / count;*/ + + welford_merge(count, mean_2[0], mean_2[1], M2_2[0], M2_2[1]); + welford_merge(count, mean_2[2], mean_2[3], M2_2[2], M2_2[3]); + welford_merge(count, mean_2[4], mean_2[5], M2_2[4], M2_2[5]); + welford_merge(count, mean_2[6], mean_2[7], M2_2[6], M2_2[7]); + + welford_merge(count * 2, mean_2[0], mean_2[2], M2_2[0], M2_2[2]); + welford_merge(count * 2, mean_2[4], mean_2[6], M2_2[4], M2_2[6]); + + welford_merge(count * 4, mean_2[0], mean_2[4], M2_2[0], M2_2[4]); + + return (double)M2_2[0] / (count * 8); +} + +bool reflow(const cv::Mat &ref, const cv::Mat &img, cv::Mat &warped) +{ + try + { + cv::Mat flow(ref.size(), CV_32FC2); + cv::calcOpticalFlowFarneback(ref, img, flow, 0.5, 3, 40, 3, 5, 1.2, 0); + + std::vector flow_xy(2); + cv::split(flow, flow_xy); + cv::Mat flow_x = flow_xy[0]; + cv::Mat flow_y = flow_xy[1]; + + // --- Build map_x and map_y for remapping + cv::Mat map_x(ref.size(), CV_32FC1); + cv::Mat map_y(ref.size(), CV_32FC1); + + for (int y = 0; y < ref.rows; y++) { + for (int x = 0; x < ref.cols; x++) { + map_x.at(y, x) = x + flow_x.at(y, x); + map_y.at(y, x) = y + flow_y.at(y, x); + } + } + + // --- Warp img1 to align it with img2 using the optical flow + cv::remap(img, warped, map_x, map_y, cv::INTER_LANCZOS4); + } + catch (...) + { + return false; + } + return true; + +} diff --git a/laplacian.h b/laplacian.h new file mode 100644 index 0000000..1934763 --- /dev/null +++ b/laplacian.h @@ -0,0 +1,10 @@ +#ifndef LAPLACIAN_H +#define LAPLACIAN_H + +#include +#include + +double laplacian(const uint16_t *img, int32_t *out, uint32_t width, uint32_t height); +bool reflow(const cv::Mat &ref, const cv::Mat &img, cv::Mat &warped); + +#endif // LAPLACIAN_H diff --git a/main.cpp b/main.cpp index 9587097..88b5c75 100644 --- a/main.cpp +++ b/main.cpp @@ -2,8 +2,12 @@ #include "serfile.h" #include +#include +#include +#include +#include +#include #include -#include #include #include @@ -209,202 +213,81 @@ void fft(std::vector> &x, bool inv = false) } } -double laplacian(const uint16_t *img, int32_t *out, uint32_t width, uint32_t height) + + +//光流 +void opticalflow() { - __m256 mean = _mm256_setzero_ps(); - __m256 M2 = _mm256_setzero_ps(); - uint32_t count = 0; - for(uint32_t y = 1; y < height - 1; y++) + cv::Mat frame1, prvs; + SERFileReader ser; + ser.open("/media/data/indi_2025-09-29/indi_record_2025-09-29@17-29-08.ser"); + frame1 = cv::Mat(ser.height(), ser.width(), CV_16U); + ser.getFrame(0, (char*)frame1.data); + + cv::Mat frame2(ser.height(), ser.width(), CV_16U); + for(int i=0; i(img + row + x)); - __m256i p1 = _mm256_loadu_si256(reinterpret_cast<__m256i const*>(img + (row + width) + x - 1)); - __m256i p2 = _mm256_loadu_si256(reinterpret_cast<__m256i const*>(img + (row + width) + x)); - __m256i p3 = _mm256_loadu_si256(reinterpret_cast<__m256i const*>(img + (row + width) + x + 1)); - __m256i p4 = _mm256_loadu_si256(reinterpret_cast<__m256i const*>(img + (row + width * 2) + x)); + ser.getFrame(i, (char*)frame2.data); + if (frame2.empty()) + break; - __m256i sumA = _mm256_setzero_si256(); - __m256i sumB = _mm256_setzero_si256(); + cv::Mat flow(prvs.size(), CV_32FC2); + cv::calcOpticalFlowFarneback(frame1, frame2, flow, 0.5, 3, 40, 3, 5, 1.2, 0); - __m256i a,b; - a = _mm256_cvtepu16_epi32(_mm256_extracti128_si256(p0, 0)); - b = _mm256_cvtepu16_epi32(_mm256_extracti128_si256(p0, 1)); - sumA = _mm256_add_epi32(sumA, a); - sumB = _mm256_add_epi32(sumB, b); - a = _mm256_cvtepu16_epi32(_mm256_extracti128_si256(p1, 0)); - b = _mm256_cvtepu16_epi32(_mm256_extracti128_si256(p1, 1)); - sumA = _mm256_add_epi32(sumA, a); - sumB = _mm256_add_epi32(sumB, b); + std::vector flow_xy(2); + cv::split(flow, flow_xy); + cv::Mat flow_x = flow_xy[0]; + cv::Mat flow_y = flow_xy[1]; - a = _mm256_cvtepu16_epi32(_mm256_extracti128_si256(p2, 0)); - b = _mm256_cvtepu16_epi32(_mm256_extracti128_si256(p2, 1)); - a = _mm256_sll_epi32(a, _mm_set1_epi64x(2)); - b = _mm256_sll_epi32(b, _mm_set1_epi64x(2)); - sumA = _mm256_sub_epi32(sumA, a); - sumB = _mm256_sub_epi32(sumB, b); + // --- Build map_x and map_y for remapping + cv::Mat map_x(frame1.size(), CV_32FC1); + cv::Mat map_y(frame1.size(), CV_32FC1); - a = _mm256_cvtepu16_epi32(_mm256_extracti128_si256(p3, 0)); - b = _mm256_cvtepu16_epi32(_mm256_extracti128_si256(p3, 1)); - sumA = _mm256_add_epi32(sumA, a); - sumB = _mm256_add_epi32(sumB, b); - - a = _mm256_cvtepu16_epi32(_mm256_extracti128_si256(p4, 0)); - b = _mm256_cvtepu16_epi32(_mm256_extracti128_si256(p4, 1)); - sumA = _mm256_add_epi32(sumA, a); - sumB = _mm256_add_epi32(sumB, b); - - if(out) - { - _mm256_storeu_si256(reinterpret_cast<__m256i*>(out + row + x), sumA); - _mm256_storeu_si256(reinterpret_cast<__m256i*>(out + row + x + 8), sumB); + for (int y = 0; y < frame1.rows; y++) { + for (int x = 0; x < frame1.cols; x++) { + map_x.at(y, x) = x + flow_x.at(y, x); + map_y.at(y, x) = y + flow_y.at(y, x); } - - __m256 af = _mm256_cvtepi32_ps(sumA); - __m256 bf = _mm256_cvtepi32_ps(sumB); - - count++; - __m256 delta = _mm256_sub_ps(af, mean); - mean = _mm256_add_ps(mean, _mm256_div_ps(delta, _mm256_set1_ps(static_cast(count)))); - __m256 delta2 = _mm256_sub_ps(af, mean); - M2 = _mm256_add_ps(M2, _mm256_mul_ps(delta, delta2)); - - count++; - delta = _mm256_sub_ps(bf, mean); - mean = _mm256_add_ps(mean, _mm256_div_ps(delta, _mm256_set1_ps(static_cast(count)))); - delta2 = _mm256_sub_ps(bf, mean); - M2 = _mm256_add_ps(M2, _mm256_mul_ps(delta, delta2)); - - //count += 1 - //delta = new_value - mean - //mean += delta / count - //delta2 = new_value - mean - //M2 += delta * delta2 } + + // --- Warp img1 to align it with img2 using the optical flow + cv::Mat warped; + cv::remap(frame2, warped, map_x, map_y, cv::INTER_LANCZOS4); + cv::imshow("orig", frame2); + cv::imshow("warp", warped); + /*int key = cv::waitKey(3); + if (key == 'q' || key == 27) + break; + continue;*/ + + // visualization + cv::Mat flow_parts[2]; + split(flow, flow_parts); + cv::Mat magnitude, angle, magn_norm; + cv::cartToPolar(flow_parts[0], flow_parts[1], magnitude, angle, true); + cv::normalize(magnitude, magn_norm, 0.0f, 1.0f, cv::NORM_MINMAX); + cv::imshow("mag", magn_norm); + /*angle *= ((1.f / 360.f) * (180.f / 255.f)); + //build hsv image + cv::Mat _hsv[3], hsv, hsv8, bgr; + _hsv[0] = angle; + _hsv[1] = cv::Mat::ones(angle.size(), CV_32F); + _hsv[2] = magn_norm; + merge(_hsv, 3, hsv); + hsv.convertTo(hsv8, CV_8U, 255.0); + cvtColor(hsv8, bgr, cv::COLOR_HSV2BGR); + imshow("frame2", bgr);*/ + int keyboard = cv::waitKey(30); + if (keyboard == 'q' || keyboard == 27) + break; } - float mean_2[8]; - float M2_2[8]; - _mm256_storeu_ps(mean_2, mean); - _mm256_storeu_ps(M2_2, M2); - - auto welford_merge = [](uint32_t n, float &mean_1, float mean_2, float &M2_1, float M2_2) - { - uint32_t count = 2 * n; - float delta = mean_2 - mean_1; - float mean = mean_1 + delta * ((float)n / count); - float M2 = M2_1 + M2_2 + delta * delta * n * n / count; - mean_1 = mean; - M2_1 = M2; - }; - - for(int i = 0; i < 8; i++) - qDebug() << M2_2[i] / count; - - welford_merge(count, mean_2[0], mean_2[1], M2_2[0], M2_2[1]); - welford_merge(count, mean_2[2], mean_2[3], M2_2[2], M2_2[3]); - welford_merge(count, mean_2[4], mean_2[5], M2_2[4], M2_2[5]); - welford_merge(count, mean_2[6], mean_2[7], M2_2[6], M2_2[7]); - - welford_merge(count * 2, mean_2[0], mean_2[2], M2_2[0], M2_2[2]); - welford_merge(count * 2, mean_2[4], mean_2[6], M2_2[4], M2_2[6]); - - welford_merge(count * 4, mean_2[0], mean_2[4], M2_2[0], M2_2[4]); - - return (double)M2_2[0] / (count * 8); } int main(int argc, char *argv[]) { - SERFileReader ser; - ser.open("/home/nou/.wine/drive_c/indi_2025-10-03/indi_record_2025-10-03@18-24-37.ser"); - - cv::Rect rect(1024, 1024, 128, 128); - double maxQ = 0; - cv::Mat best; - cv::Mat lap; - cv::Mat first(ser.height(), ser.width(), CV_16U); - cv::Mat img(ser.height(), ser.width(), CV_16U); - cv::Mat out(ser.height(), ser.width(), CV_32S); - cv::Mat imgf32; - ser.getFrame(0, (char*)first.data); - first.convertTo(first, CV_32F); - for(uint32_t i = 0; i < ser.frameCount(); i++) - { - ser.getFrame(i, (char*)img.data); - double var = laplacian((uint16_t*)img.data, (int32_t*)out.data, img.cols, img.rows); - double minval, maxval; - cv::minMaxLoc(out, &minval, &maxval); - out.convertTo(out, CV_32F, 1.0 / (maxval - minval), -minval / (maxval - minval)); - qDebug() << "minmax" << minval << maxval; - cv::imshow("lap", out); - - img.convertTo(imgf32, CV_32F); - cv::Laplacian(imgf32, lap, CV_32F, 1); - cv::minMaxLoc(lap, &minval, &maxval); - qDebug() << "minmax" << minval << maxval; - cv::Mat stddev; - cv::Mat mean; - cv::meanStdDev(lap, mean, stddev); - lap -= minval; - lap /= (maxval - minval); - cv::imshow("lapcv", lap); - cv::waitKey(); - qDebug() << var << std::sqrt(var) << stddev.at(0); - //continue; - return 0; - - img.convertTo(imgf32, CV_32F); - cv::Laplacian(imgf32, lap, CV_32F, 1); - cv::Point2d off = cv::phaseCorrelate(first(rect), imgf32(rect)); - if(maxQ < stddev.at(0)) - { - maxQ = stddev.at(0); - img.copyTo(best); - //qDebug() << "new best" << i; - } - } - cv::imshow("lap", best); - cv::waitKeyEx(); - - return 0; - - - cv::Mat img1= cv::imread("/home/nou/Obrázky/astro/moon_2025-10-03/R.tif", cv::IMREAD_GRAYSCALE); - cv::Mat img2= cv::imread("/home/nou/Obrázky/astro/moon_2025-10-03/G.tif", cv::IMREAD_GRAYSCALE); - - img1.convertTo(img1, CV_32F); - img2.convertTo(img2, CV_32F); - - cv::Point2d point = cv::phaseCorrelate(img1, img2); - - - cv::Mat img2dst; - cv::Mat t(2, 3, CV_32F); - t.at(0, 0) = 1.0; - t.at(1, 1) = 1.0; - t.at(0, 2) = -point.x; - t.at(1, 2) = -point.y; - cv::warpAffine(img2, img2dst, t, img1.size()); - - //cv::imshow("img1", img1 / 64.0); - auto diff = img1 - img2dst; - double min, max; - cv::minMaxLoc(diff, &min, &max); - qDebug() << min << max; - cv::imshow("img2", (diff - min) / (32)); - cv::imwrite("diff.png", (diff - min) / (max - min) * 255); - cv::imwrite("avg.png", (img1 + img2dst) * 0.5); - - cv::minMaxLoc(img1, &min, &max); - qDebug() << min << max; - cv::waitKey(); - - - return 0; - /*QApplication a(argc, argv); + QApplication a(argc, argv); MainWindow w; w.show(); - return a.exec();*/ + return a.exec(); } diff --git a/mainwindow.cpp b/mainwindow.cpp index 796e9fa..90978b3 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -1,15 +1,147 @@ #include "mainwindow.h" #include "./ui_mainwindow.h" -#include +#include +#include +#include +#include +#include "laplacian.h" -MainWindow::MainWindow(QWidget *parent) - : QMainWindow(parent) +MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); + + connect(ui->actionOpen, &QAction::triggered, this, &MainWindow::openFile); + connect(ui->actionSave, &QAction::triggered, this, &MainWindow::saveImage); + connect(ui->analyzeButton, &QPushButton::clicked, this, &MainWindow::analyze); + connect(ui->stackButton, &QPushButton::clicked, this, &MainWindow::stack); + connect(ui->cancelButton, &QPushButton::clicked, this, [this](){ + if(_stack.isRunning())_stack.cancel(); + if(_analyze.isRunning())_analyze.cancel(); + }); + + ui->progressBar->hide(); + + connect(&_watcherAnalyze, &QFutureWatcher::progressValueChanged, [this](int val){ ui->progressBar->setValue(val); }); + connect(&_watcherAnalyze, &QFutureWatcher::finished, [this](){ ui->progressBar->hide(); ui->stackButton->setEnabled(true); }); + connect(&_watcherAnalyze, &QFutureWatcher::canceled, [this](){ ui->progressBar->hide(); }); + connect(&_watcherAnalyze, &QFutureWatcher::started, [this](){ ui->progressBar->show(); }); + connect(&_watcherStack, &QFutureWatcher::progressValueChanged, [this](int val){ ui->progressBar->setValue(val); }); + connect(&_watcherStack, &QFutureWatcher::finished, [this](){ ui->progressBar->hide(); stackFinish(); }); + connect(&_watcherStack, &QFutureWatcher::canceled, [this](){ ui->progressBar->hide(); }); + connect(&_watcherStack, &QFutureWatcher::started, [this](){ ui->progressBar->show(); }); + } MainWindow::~MainWindow() { delete ui; } + +void MainWindow::openFile() +{ + QString fileName = QFileDialog::getOpenFileName(this, tr("Open file"), QString(), tr("SER video (*.ser)")); + _variance.clear(); + ui->stackButton->setEnabled(false); + ui->analyzeButton->setEnabled(false); + if(!fileName.isEmpty()) + { + if(!_ser.open(fileName)) + { + QMessageBox::critical(this, tr("Failed to open file"), tr("Failed to open %1").arg(fileName)); + _ser.close(); + return; + } + + if(_ser.pixelDepth() < 9 || _ser.pixelDepth() > 16 || _ser.pixelFormat() != SERPixelFormat::MONO) + { + QMessageBox::critical(this, tr("Invalid format"), tr("Only 9-16 bit mono video file supported")); + ui->stackButton->setEnabled(false); + _ser.close(); + return; + } + + ui->analyzeButton->setEnabled(true); + } +} + +void MainWindow::saveImage() +{ + if(_stack.isValid()) + { + QString fileName = QFileDialog::getSaveFileName(this, tr("Save image"), QString(), tr("Image (*.png)")); + if(!fileName.isEmpty()) + { + cv::Mat stack = _stack.result(); + stack.convertTo(stack, CV_16U, 65355.0); + QImage img(stack.data, stack.cols, stack.rows, stack.cols * 2, QImage::Format_Grayscale16); + img.save(fileName, "PNG"); + } + } +} + +void MainWindow::analyze() +{ + if(_ser.frameCount() && !_analyze.isRunning() && !_stack.isRunning()) + { + _analyze = QtConcurrent::run([this](QPromise &promise) { + promise.start(); + cv::Mat img(_ser.height(), _ser.width(), CV_16U); + _variance.clear(); + promise.setProgressRange(0, 100); + for(uint32_t i = 0; i < _ser.frameCount() && !promise.isCanceled(); i++) + { + _ser.getFrame(i, (char*)img.data); + double var = laplacian((uint16_t*)img.data, nullptr, img.cols, img.rows); + _variance.push_back({i, var}); + promise.setProgressValue(i * 100.0 / _ser.frameCount()); + } + std::sort(_variance.begin(), _variance.end(), [](const std::pair &a, const std::pair &b){ + return a.second > b.second; + }); + + promise.finish(); + }); + _watcherAnalyze.setFuture(_analyze); + } +} + +void MainWindow::stack() +{ + if(!_variance.empty() && !_analyze.isRunning() && !_stack.isRunning()) + { + _stack = QtConcurrent::run([this](QPromise &promise, uint32_t frames) { + promise.start(); + cv::Mat img(_ser.height(), _ser.width(), CV_16U); + cv::Mat ref(_ser.height(), _ser.width(), CV_16U); + cv::Mat stack(_ser.height(), _ser.width(), CV_32F); + _ser.getFrame(_variance[0].first, (char*)ref.data); + stack += ref; + cv::Mat warp; + promise.setProgressRange(0, 100); + for(uint32_t i = 1; i < frames && !promise.isCanceled(); i++) + { + _ser.getFrame(_variance[i].first, (char*)img.data); + reflow(ref, img, warp); + stack += warp; + promise.setProgressValue(i * 100.0 / frames); + } + cv::Mat norm; + cv::normalize(stack, norm, 0.0, 1.0, cv::NORM_MINMAX); + promise.addResult(norm); + promise.finish(); + }, ui->frameTop->value() / 100.0 * _ser.frameCount()); + _watcherStack.setFuture(_stack); + } +} + +void MainWindow::stackFinish() +{ + cv::Mat stack = _stack.result(); + cv::Mat scaled; + QSize s = ui->image->size(); + cv::resize(stack, scaled, cv::Size(s.width(), s.height())); + scaled.convertTo(scaled, CV_8U, 255.0); + QImage img(scaled.data, s.width(), s.height(), scaled.cols, QImage::Format_Grayscale8); + ui->image->setPixmap(QPixmap::fromImage(img)); +} diff --git a/mainwindow.h b/mainwindow.h index f7a3da3..ce2dcfa 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -2,12 +2,15 @@ #define MAINWINDOW_H #include +#include +#include +#include +#include +#include "serfile.h" -QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } -QT_END_NAMESPACE class MainWindow : public QMainWindow { @@ -17,7 +20,21 @@ public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); +public slots: + void openFile(); + void saveImage(); + void analyze(); + void stack(); + + void stackFinish(); private: + SERFileReader _ser; Ui::MainWindow *ui; + QFuture _analyze; + QFuture _stack; + QFutureWatcher _watcherAnalyze; + QFutureWatcher _watcherStack; + std::vector> _variance; + int cvType = 0; }; #endif // MAINWINDOW_H diff --git a/mainwindow.ui b/mainwindow.ui index d927863..02a837a 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -6,39 +6,123 @@ 0 0 - 800 - 600 + 1213 + 744 - MainWindow + Kouryu - - - - 90 - 110 - 411 - 91 - - - - TextLabel - - + + + + + + + % + + + 1 + + + 5 + + + + + + + false + + + Analyze + + + + + + + false + + + Stack + + + + + + + 0 + + + + + + + Cancel + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + 1 + 0 + + + + TextLabel + + + + 0 0 - 800 + 1213 23 + + + File + + + + + + + + Open + + + + + Save + + diff --git a/serfile.cpp b/serfile.cpp index 1696c4e..b88be93 100644 --- a/serfile.cpp +++ b/serfile.cpp @@ -76,6 +76,12 @@ bool SERFileReader::open(const QString &path) return true; } +void SERFileReader::close() +{ + _fr.close(); + std::memset(&_header, 0, sizeof(SERHeader)); +} + uint32_t SERFileReader::width() const { return _header.imagewidth; @@ -96,6 +102,16 @@ uint64_t SERFileReader::frameSize() const return static_cast(_header.imagewidth) * _header.imageheight * _header.pixeldepth / 8; } +uint32_t SERFileReader::pixelDepth() const +{ + return _header.pixeldepth; +} + +SERPixelFormat SERFileReader::pixelFormat() const +{ + return _header.colorid; +} + void SERFileReader::getFrame(int32_t index, std::vector &data) { if(index >= static_cast(_header.framecount))return; diff --git a/serfile.h b/serfile.h index b947a02..11b0ebf 100644 --- a/serfile.h +++ b/serfile.h @@ -54,10 +54,13 @@ class SERFileReader { public: bool open(const QString &path); + void close(); uint32_t width() const; uint32_t height() const; uint32_t frameCount() const; uint64_t frameSize() const; + uint32_t pixelDepth() const; + SERPixelFormat pixelFormat() const; void getFrame(int32_t index, std::vector &data); void getFrame(int32_t index, char *data); private: