diff --git a/CHANGELOG.md b/CHANGELOG.md index c116304..08c9aa9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,11 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- +- Adds image viewer tab to open recorded .b2nd files and scroll over images. +- Adds missing camera models to JSON file. ### Changed -- +- All displaying operations run from a thread, and only calls the main thread when the process images are to be displayed. +- Restructures UI for a more clear use and structures controls inside a toolbox. +- Limits frequency at which images are displayed to avoid locking the UI. Images are still recorded at full speed. ### Removed diff --git a/resources/XiLensCameraProperties.json b/resources/XiLensCameraProperties.json index de445a5..ebe345c 100644 --- a/resources/XiLensCameraProperties.json +++ b/resources/XiLensCameraProperties.json @@ -389,6 +389,12 @@ "mosaicWidth": 0, "mosaicHeight": 0 }, + "MQ042CG-CM": { + "cameraType": "rgb", + "cameraFamily": "xiQ", + "mosaicWidth": 0, + "mosaicHeight": 0 + }, "MQ042CG-CM-S7": { "cameraType": "rgb", "cameraFamily": "xiQ", diff --git a/resources/dark_amber.css b/resources/dark_amber.css index 7a62fee..0fcd195 100644 --- a/resources/dark_amber.css +++ b/resources/dark_amber.css @@ -814,10 +814,11 @@ QMenuBar::item:pressed { QToolBox::tab { background-color: #232629; - color: #ffffff; + color: #ffd740; text-transform: uppercase; border-radius: 4px; padding-left: 15px; + font-weight: bold; } QToolBox::tab:selected, @@ -825,6 +826,12 @@ QToolBox::tab:hover { background-color: rgba(255, 215, 64, 0.2); } +QToolBox::tab:disabled { + color: rgba(79, 91, 98, 0.75); + background-color: rgba(35, 38, 41, 0.3); + border-color: #4f5b62; +} + /* ------------------------------------------------------------------------ */ /* QProgressBar */ diff --git a/src/camera.cpp b/src/camera.cpp index 654d932..c2e6db6 100644 --- a/src/camera.cpp +++ b/src/camera.cpp @@ -14,7 +14,7 @@ void CameraFamily::UpdateCameraTemperature() { } -QMap CameraFamily::getCameraTemperature() +QMap CameraFamily::GetCameraTemperature() { return this->m_cameraTemperature; } @@ -131,7 +131,7 @@ int RGBCamera::InitializeCamera() void XiSpecFamily::UpdateCameraTemperature() { - boost::lock_guard guard(this->mtx_); + boost::lock_guard guard(this->m_mutexCameraTemperature); float chipTemp, houseTemp, houseBackSideTemp, sensorBoardTemp; this->m_apiWrapper->xiGetParamFloat(*m_cameraHandle, XI_PRM_CHIP_TEMP, &chipTemp); @@ -146,7 +146,7 @@ void XiSpecFamily::UpdateCameraTemperature() void XiCFamily::UpdateCameraTemperature() { - boost::lock_guard guard(this->mtx_); + boost::lock_guard guard(this->m_mutexCameraTemperature); float sensorBoardTemp; this->m_apiWrapper->xiGetParamFloat(*m_cameraHandle, XI_PRM_SENSOR_BOARD_TEMP, &sensorBoardTemp); this->m_cameraTemperature[SENSOR_BOARD_TEMP] = sensorBoardTemp; @@ -154,7 +154,7 @@ void XiCFamily::UpdateCameraTemperature() void XiQFamily::UpdateCameraTemperature() { - boost::lock_guard guard(this->mtx_); + boost::lock_guard guard(this->m_mutexCameraTemperature); float chipTemp, houseTemp, houseBackSideTemp, sensorBoardTemp; if (*m_cameraHandle != INVALID_HANDLE_VALUE) { diff --git a/src/camera.h b/src/camera.h index 8d37820..470474f 100644 --- a/src/camera.h +++ b/src/camera.h @@ -26,7 +26,7 @@ class CameraFamily * Mutex used to lock access to variables like the camera temperature, this allows updating temperature from * multiple threads. */ - boost::mutex mtx_; + boost::mutex m_mutexCameraTemperature; public: explicit CameraFamily(HANDLE *handle) : m_cameraHandle(handle) @@ -90,7 +90,7 @@ class CameraFamily /* * Queries camera temperature */ - QMap getCameraTemperature(); + QMap GetCameraTemperature(); }; /** @@ -212,7 +212,7 @@ class Camera * @param family family of the camera to be constructed * @param handle camera handle used for all interactions with it */ - Camera(std::unique_ptr *family, HANDLE *handle) : family(family), m_cameraHandle(handle) + Camera(std::unique_ptr *family, HANDLE *handle) : m_cameraFamily(family), m_cameraHandle(handle) { } @@ -224,7 +224,7 @@ class Camera /** * Unique pointer to camera family */ - std::unique_ptr *family; + std::unique_ptr *m_cameraFamily; /** * initializes camera by setting parameters such as framerate, binning mode, diff --git a/src/cameraInterface.cpp b/src/cameraInterface.cpp index b348b11..1574d52 100644 --- a/src/cameraInterface.cpp +++ b/src/cameraInterface.cpp @@ -93,7 +93,7 @@ int CameraInterface::OpenDevice(DWORD cameraDeviceID) stat = this->m_apiWrapper->xiOpenDevice(cameraDeviceID, &m_cameraHandle); HandleResult(stat, "xiOepnDevice"); - this->setCamera(m_cameraType, m_cameraFamilyName); + this->SetCamera(m_cameraType, m_cameraFamilyName); stat = this->m_camera->InitializeCamera(); if (stat != XI_OK) @@ -169,7 +169,7 @@ CameraInterface::~CameraInterface() } } -void CameraInterface::setCamera(QString cameraType, QString cameraFamily) +void CameraInterface::SetCamera(QString cameraType, QString cameraFamily) { // instantiate camera type if (cameraType == CAMERA_TYPE_SPECTRAL) diff --git a/src/cameraInterface.h b/src/cameraInterface.h index e266b49..4c4329c 100644 --- a/src/cameraInterface.h +++ b/src/cameraInterface.h @@ -54,7 +54,7 @@ class CameraInterface : public QObject */ ~CameraInterface(); - void setCamera(QString cameraType, QString cameraFamily); + void SetCamera(QString cameraType, QString cameraFamily); /** * @brief Initializes a device with the specified camera ID. diff --git a/src/display.cpp b/src/display.cpp index 1356db8..75e642c 100644 --- a/src/display.cpp +++ b/src/display.cpp @@ -1,12 +1,10 @@ #include "display.h" -Displayer::Displayer() +Displayer::Displayer(QObject *parent) : QObject(parent) { } -Displayer::~Displayer() -{ -} +Displayer::~Displayer() = default; void Displayer::StopDisplayer() { @@ -16,4 +14,5 @@ void Displayer::StopDisplayer() void Displayer::StartDisplayer() { this->m_stop = false; + this->m_displayCondition.notify_one(); } diff --git a/src/display.h b/src/display.h index 21317a4..ee6022a 100644 --- a/src/display.h +++ b/src/display.h @@ -5,19 +5,20 @@ #ifndef DISPLAY_H #define DISPLAY_H -#include - #include #include +#include +#include +#include class Displayer : public QObject { Q_OBJECT public: - explicit Displayer(); + explicit Displayer(QObject *parent = nullptr); - ~Displayer(); + ~Displayer() override; QString m_cameraType; @@ -33,11 +34,39 @@ class Displayer : public QObject */ void StartDisplayer(); + signals: + /** + * Qt signal emitted when an RGB image is ready to be displayed in the UI. + */ + void ImageReadyToUpdateRGB(cv::Mat &); + + /** + * Qt signal emitted when a raw image is ready to be displayed in the UI. + */ + void ImageReadyToUpdateRaw(cv::Mat &); + + /** + * Qt signal emitted when a new image is ready to compute the saturation percentage for the display. + */ + void SaturationPercentageImageReady(cv::Mat &); + protected: + /** + * Indicate that process should stop displaying images. + */ bool m_stop = false; + /** + * condition variable used to wait until a new image is available to be processed. + */ + boost::condition_variable m_displayCondition; + public slots: + /** + * Qt slot in charge of displaying images. + * @param image + */ virtual void Display(XI_IMG &image) = 0; }; diff --git a/src/displayFunctional.cpp b/src/displayFunctional.cpp index 0abfd98..f0b5f3e 100644 --- a/src/displayFunctional.cpp +++ b/src/displayFunctional.cpp @@ -28,10 +28,34 @@ typedef cv::Point3_ Pixel; DisplayerFunctional::DisplayerFunctional(MainWindow *mainWindow) : Displayer(), m_mainWindow(mainWindow) { + auto result = QObject::connect(&m_displayTimer, &QTimer::timeout, this, &DisplayerFunctional::OnDisplayTimeout); + if (!result) + { + LOG_XILENS(error) << "Error while connecting displayer to timer"; + } + m_displayTimer.setInterval(m_displayIntervalMilliseconds); + m_displayTimer.start(); + m_displayThread = boost::thread(&DisplayerFunctional::ProcessImageOnThread, this); } DisplayerFunctional::~DisplayerFunctional() { + if (m_displayTimer.isActive()) + { + m_displayTimer.stop(); + } + { + boost::lock_guard guard(m_mutexImageDisplay); + m_stop = true; + } + m_displayCondition.notify_all(); + m_displayThread.interrupt(); + if (m_displayThread.joinable()) + { + m_displayThread.join(); + } + QObject::disconnect(); + m_clahe.release(); } void PrepareBGRImage(cv::Mat &bgr_image, int bgr_norm) @@ -50,14 +74,14 @@ void DisplayerFunctional::NormalizeBGRImage(cv::Mat &bgr_image) { cv::Mat lab_image; cvtColor(bgr_image, lab_image, cv::COLOR_BGR2Lab); - // ectract L channel + // extract L channel std::vector lab_planes(3); cv::split(lab_image, lab_planes); - // apply clahe to the L channel and save it in lab_planes + // apply m_clahe to the L channel and save it in lab_planes cv::Mat dst; - this->clahe->setClipLimit(m_mainWindow->GetBGRNorm()); - this->clahe->apply(lab_planes[0], dst); + this->m_clahe->setClipLimit(m_mainWindow->GetBGRNorm()); + this->m_clahe->apply(lab_planes[0], dst); dst.copyTo(lab_planes[0]); // merge color planes back to bgr_image @@ -74,14 +98,14 @@ void DisplayerFunctional::PrepareRawImage(cv::Mat &raw_image, bool equalize_hist cv::LUT(mask, m_lut, mask); if (equalize_hist) { - this->clahe->apply(raw_image, raw_image); + this->m_clahe->apply(raw_image, raw_image); } cvtColor(raw_image, raw_image, cv::COLOR_GRAY2RGB); if (m_mainWindow->IsSaturationButtonChecked()) { // Parallel execution on each pixel using C++11 lambda. - raw_image.forEach([mask, this](Pixel &p, const int position[]) -> void { + raw_image.forEach([mask](Pixel &p, const int position[]) -> void { if (mask.at(position[0], position[1]) == SATURATION_COLOR) { p.x = SATURATION_COLOR[0]; @@ -105,9 +129,9 @@ void DisplayerFunctional::GetBand(cv::Mat &image, cv::Mat &band_image, unsigned throw std::out_of_range("Band number is out of the expected range."); } // compute location of first value - int init_col = (band_nr - 1) % this->m_mosaicShape[0]; - int init_row = (band_nr - 1) / this->m_mosaicShape[1]; - // select data from specific band + int init_col = static_cast(band_nr - 1) % this->m_mosaicShape[0]; + int init_row = static_cast(band_nr - 1) / this->m_mosaicShape[1]; + // select data from the specific band int row = 0; for (int i = init_row; i < image.rows; i += this->m_mosaicShape[0]) { @@ -140,89 +164,117 @@ void DisplayerFunctional::Display(XI_IMG &image) { return; } - static int selected_display = 0; - selected_display++; - // give it some time to draw the first frame. For some reason neccessary. - // probably has to do with missing waitkeys after imshow (these crash the - // application) - if ((selected_display == 1) || (selected_display > 10)) + boost::lock_guard guard(m_mutexImageDisplay); + m_nextImage = image; + m_hasPendingImage = true; +} + +void DisplayerFunctional::OnDisplayTimeout() +{ + if (m_hasPendingImage) { - // additionally, give it some chance to recover from lots of ui interaction - // by skipping every 100th frame - if (selected_display % 100 > 0) - { - boost::lock_guard guard(mtx_); + m_displayCondition.notify_one(); + } +} - cv::Mat currentImage(image.height, image.width, CV_16UC1, image.bp); - cv::Mat rawImage; - static cv::Mat bgrImage; +[[noreturn]] void DisplayerFunctional::ProcessImageOnThread() +{ + while (true) + { + XI_IMG image; + { + boost::unique_lock lock(m_mutexImageDisplay); + boost::this_thread::interruption_point(); + m_displayCondition.wait(lock, [this] { return m_hasPendingImage; }); - if (m_cameraType == CAMERA_TYPE_SPECTRAL) + if (m_hasPendingImage) { - rawImage = InitializeBandImage(currentImage); - this->GetBand(currentImage, rawImage, m_mainWindow->GetBand()); - bgrImage = cv::Mat::zeros(currentImage.rows / this->m_mosaicShape[0], - currentImage.cols / this->m_mosaicShape[1], CV_8UC3); - this->GetBGRImage(currentImage, bgrImage); + image = m_nextImage; + m_hasPendingImage = false; } - else if (m_cameraType == CAMERA_TYPE_GRAY) - { - rawImage = currentImage.clone(); - rawImage /= m_scaling_factor; // 10 bit to 8 bit - rawImage.convertTo(rawImage, CV_8UC1); - cv::cvtColor(rawImage, bgrImage, cv::COLOR_GRAY2BGR); - } - else if (m_cameraType == CAMERA_TYPE_RGB) - { - rawImage = currentImage.clone(); - rawImage /= m_scaling_factor; // 10 bit to 8 bit - rawImage.convertTo(rawImage, CV_8UC3); - - bgrImage = currentImage.clone(); - if (image.color_filter_array == XI_CFA_BAYER_GBRG) - { - cv::cvtColor(bgrImage, bgrImage, cv::COLOR_BayerGB2BGR); - } - else - { - LOG_XILENS(error) << "Could not interpret filter array of type: " << image.color_filter_array; - } + } + ProcessImage(image); + } +} - bgrImage.convertTo(bgrImage, CV_8UC3, 1.0 / m_scaling_factor); - } - else - { - LOG_XILENS(error) << "Could not recognize camera type: " << m_cameraType.toStdString(); - throw std::runtime_error("Could not recognize camera type: " + m_cameraType.toStdString()); - } - cv::Mat raw_image_to_display = rawImage.clone(); - DownsampleImageIfNecessary(raw_image_to_display); - this->PrepareRawImage(raw_image_to_display, m_mainWindow->GetNormalize()); - // display BGR image - DownsampleImageIfNecessary(bgrImage); - if (m_mainWindow->GetNormalize()) - { - NormalizeBGRImage(bgrImage); - } - else - { - PrepareBGRImage(bgrImage, m_mainWindow->GetBGRNorm()); - } - // update saturation displays - m_mainWindow->UpdateSaturationPercentageLCDDisplays(rawImage); +void DisplayerFunctional::ProcessImage(XI_IMG &image) +{ + if (m_stop) + { + return; + } + cv::Mat currentImage; + int filterArrayType; + { + boost::lock_guard guard(m_mutexImageDisplay); + currentImage = + cv::Mat(static_cast(image.height), static_cast(image.width), CV_16UC1, image.bp).clone(); + filterArrayType = image.color_filter_array; + } + cv::Mat rawImage; + static cv::Mat bgrImage; - // display images for RGB and Raw - m_mainWindow->UpdateRGBImage(bgrImage); + if (m_cameraType == CAMERA_TYPE_SPECTRAL) + { + rawImage = InitializeBandImage(currentImage); + this->GetBand(currentImage, rawImage, m_mainWindow->GetBand()); + bgrImage = cv::Mat::zeros(currentImage.rows / this->m_mosaicShape[0], + currentImage.cols / this->m_mosaicShape[1], CV_8UC3); + this->GetBGRImage(currentImage, bgrImage); + } + else if (m_cameraType == CAMERA_TYPE_GRAY) + { + rawImage = currentImage.clone(); + rawImage /= m_scaling_factor; // 10 bit to 8 bit + rawImage.convertTo(rawImage, CV_8UC1); + cv::cvtColor(rawImage, bgrImage, cv::COLOR_GRAY2BGR); + } + else if (m_cameraType == CAMERA_TYPE_RGB) + { + rawImage = currentImage.clone(); + rawImage /= m_scaling_factor; // 10 bit to 8 bit + rawImage.convertTo(rawImage, CV_8UC3); - m_mainWindow->UpdateRawImage(raw_image_to_display); + bgrImage = currentImage.clone(); + if (filterArrayType == XI_CFA_BAYER_GBRG) + { + cv::cvtColor(bgrImage, bgrImage, cv::COLOR_BayerGB2BGR); } + else + { + LOG_XILENS(error) << "Could not interpret filter array of type: " << filterArrayType; + } + + bgrImage.convertTo(bgrImage, CV_8UC3, 1.0 / m_scaling_factor); + } + else + { + LOG_XILENS(error) << "Could not recognize camera type: " << m_cameraType.toStdString(); + throw std::runtime_error("Could not recognize camera type: " + m_cameraType.toStdString()); + } + cv::Mat rawImageToDisplay = rawImage.clone(); + DownsampleImageIfNecessary(rawImageToDisplay); + this->PrepareRawImage(rawImageToDisplay, m_mainWindow->GetNormalize()); + // display BGR image + DownsampleImageIfNecessary(bgrImage); + if (m_mainWindow->GetNormalize()) + { + NormalizeBGRImage(bgrImage); + } + else + { + PrepareBGRImage(bgrImage, static_cast(m_mainWindow->GetBGRNorm())); } + // Update saturation display and display images through the main thread + emit ImageReadyToUpdateRGB(bgrImage); + emit ImageReadyToUpdateRaw(rawImageToDisplay); + emit SaturationPercentageImageReady(rawImage); } void DisplayerFunctional::GetBGRImage(cv::Mat &image, cv::Mat &bgr_image) { std::vector channels; - for (int i : m_bgr_channels) + for (int i : m_BGRChannels) { cv::Mat band_image = InitializeBandImage(image); this->GetBand(image, band_image, i); diff --git a/src/displayFunctional.h b/src/displayFunctional.h index 643999e..7a266c6 100644 --- a/src/displayFunctional.h +++ b/src/displayFunctional.h @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -20,20 +21,6 @@ class MainWindow; -/** - * @brief Enumerates the types of display images. - * - * The DisplayImageType enumerator represents the different types of display - * images. It provides symbolic names for the supported image types. - */ -enum DisplayImageType -{ - RAW = 0, - RGB = 1, - VHB = 2, - OXY = 3 -}; - /** * @brief The DisplayerFunctional class is responsible for displaying images. * @@ -80,7 +67,7 @@ class DisplayerFunctional : public Displayer * * @param image image to be downsampled */ - void DownsampleImageIfNecessary(cv::Mat &image); + static void DownsampleImageIfNecessary(cv::Mat &image); /** * type of camera being used: spectral, gray, etc. @@ -103,24 +90,55 @@ class DisplayerFunctional : public Displayer * reference to main window, necessary to detect if normalization is turned on * / which band to display */ - MainWindow *m_mainWindow; + MainWindow *m_mainWindow{}; public slots: /** * Qt slot used to display images whenever a new image is queried from the - * camera + * camera. When a new image arrives, it is stored in the a member variable and it sets a flag to indicate that + * a new image is ready for processing. * * @param image image to be displayed */ void Display(XI_IMG &image) override; + /** + * Qt slot that triggers the process to display image once the timer has run out. + */ + void OnDisplayTimeout(); + private: + /** + * Thread where the image processing before displaying them should run. + */ + boost::thread m_displayThread; + + /** + * Indicates if an image has to be displayed. + */ + bool m_hasPendingImage = false; + + /** + * Timer to control when an image is displayed. + */ + QTimer m_displayTimer; + + /** + * Interval in milliseconds indicating how often a new image should be displayed. + */ + int m_displayIntervalMilliseconds = 40; + + /** + * Variable containing the data for the new image to be displayed. + */ + XI_IMG m_nextImage{}; + /** * Vector with channel numbers that can be used to construct an approximate * RGB image */ - std::vector m_bgr_channels = {11, 15, 3}; + std::vector m_BGRChannels = {11, 15, 3}; /** * Scaling factor used to convert image from 10bit to 8bit @@ -128,21 +146,27 @@ class DisplayerFunctional : public Displayer int m_scaling_factor = 4; /** - * Dummy method, not yet implemented - * - * @param image Image to be run through the network + * explicit mutex declaration */ - void RunNetwork(XI_IMG &image); + boost::mutex m_mutexImageDisplay; /** - * explicit mutex declaration + * Class to do histogram normalization with CLAHE */ - boost::mutex mtx_; + cv::Ptr m_clahe = cv::createCLAHE(); /** - * Class to do histogram normalization with CLAHE + * Processes a XIMEA image to display a Raw and RGB representation of the image in the main UI. + * + * @param image XIMEA image to be processed and displayed through the main UI. + */ + void ProcessImage(XI_IMG &image); + + /** + * Waits for an image to be available for processing and calls `ProcessImage` once an image is available. + * This method itself is triggered when the display timer runs out. */ - cv::Ptr clahe = cv::createCLAHE(); + [[noreturn]] void ProcessImageOnThread(); /** * @brief prepares raw image from XIMEA camera to be displayed, it does @@ -218,4 +242,4 @@ class DisplayerFunctional : public Displayer */ void PrepareBGRImage(cv::Mat &bgr_image, int bgr_norm); -#endif // DISPLAY_H +#endif // DISPLAYFUNCTIONAL_H diff --git a/src/imageContainer.cpp b/src/imageContainer.cpp index eaf1796..efc60ad 100644 --- a/src/imageContainer.cpp +++ b/src/imageContainer.cpp @@ -51,7 +51,7 @@ void ImageContainer::PollImage(HANDLE *cameraHandle, int pollingRate) while (m_PollImage) { { - boost::lock_guard guard(mtx_); + boost::lock_guard guard(m_mutexImageAccess); boost::this_thread::interruption_point(); if (cameraHandle != INVALID_HANDLE_VALUE) { @@ -74,7 +74,7 @@ void ImageContainer::PollImage(HANDLE *cameraHandle, int pollingRate) lastImageId = m_Image.acq_nframe; } } - wait(pollingRate); + WaitMilliseconds(pollingRate); } } @@ -90,6 +90,6 @@ void ImageContainer::StartPolling() XI_IMG ImageContainer::GetCurrentImage() { - boost::lock_guard guard(mtx_); + boost::lock_guard guard(m_mutexImageAccess); return m_Image; } diff --git a/src/imageContainer.h b/src/imageContainer.h index 14a4902..01907bc 100644 --- a/src/imageContainer.h +++ b/src/imageContainer.h @@ -114,7 +114,7 @@ class ImageContainer : public QObject /** * mutex declaration used to lock guard the current image in the container */ - boost::mutex mtx_; + boost::mutex m_mutexImageAccess; }; #endif // IMAGE_CONTAINER_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 82d7334..71f30ff 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -39,91 +40,99 @@ MainWindow::MainWindow(QWidget *parent, const std::shared_ptr &xiA : QMainWindow(parent), ui(new Ui::MainWindow), m_IOService(), m_temperatureIOService(), m_temperatureIOWork(new boost::asio::io_service::work(m_temperatureIOService)), m_cameraInterface(), m_recordedCount(0), m_testMode(g_commandLineArguments.test_mode), m_imageCounter(0), m_skippedCounter(0), - m_elapsedTimeTextStream(&m_elapsedTimeText), m_elapsedTime(0) + m_elapsedTimeTextStream(&m_elapsedTimeText), m_elapsedTime(0), m_viewerThreadRunning(true), + m_viewerThread(&MainWindow::ViewerWorkerThreadFunc, this) { this->m_xiAPIWrapper = xiAPIWrapper == nullptr ? this->m_xiAPIWrapper : xiAPIWrapper; m_cameraInterface.Initialize(this->m_xiAPIWrapper); m_imageContainer.Initialize(this->m_xiAPIWrapper); m_updateFPSDisplayTimer = new QTimer(this); ui->setupUi(this); - this->SetUpConnections(); this->SetUpCustomUiComponents(); + // Initialize BLOSC2 + blosc2_init(); + // Display needs to be instantiated before changing the camera list because // calling setCurrentIndex on the list. m_display = new DisplayerFunctional(this); // populate available cameras ui->cameraListComboBox->addItem("select camera to enable UI..."); - this->handleReloadCamerasPushButtonClicked(); + this->HandleReloadCamerasPushButtonClicked(); ui->cameraListComboBox->setCurrentIndex(0); - // set the base folder loc - m_baseFolderLoc = QDir::cleanPath(QDir::homePath()); - + // set the base folder path + m_baseFolderPath = QDir::cleanPath(QDir::homePath()); ui->baseFolderLineEdit->insert(this->GetBaseFolder()); - // synchronize slider and exposure checkbox - QSlider *slider = ui->exposureSlider; - QLineEdit *expEdit = ui->exposureLineEdit; + // synchronize expSlider and exposure checkbox + QSlider *expSlider = ui->exposureSlider; + QSpinBox *expSpinBox = ui->exposureSpinBox; // set default values and ranges - int slider_min = slider->minimum(); - int slider_max = slider->maximum(); - expEdit->setValidator(new QIntValidator(slider_min, slider_max, this)); - QString initialExpString = QString::number(slider->value()); - expEdit->setText(initialExpString); + expSpinBox->setValue(expSlider->value()); LOG_XILENS(info) << "test mode (recording everything to same file) is set to: " << m_testMode << "\n"; - + this->SetUpConnections(); EnableUi(false); } void MainWindow::SetUpConnections() { HANDLE_CONNECTION_RESULT( - QObject::connect(ui->snapshotButton, &QPushButton::clicked, this, &MainWindow::handleSnapshotButtonClicked)); - HANDLE_CONNECTION_RESULT(QObject::connect(ui->exposureSlider, &QSlider::valueChanged, this, - &MainWindow::handleExposureSliderValueChanged)); + QObject::connect(ui->snapshotButton, &QPushButton::clicked, this, &MainWindow::HandleSnapshotButtonClicked)); + HANDLE_CONNECTION_RESULT( + QObject::connect(ui->exposureSlider, &QSlider::valueChanged, this, &MainWindow::HandleExposureValueChanged)); HANDLE_CONNECTION_RESULT( - QObject::connect(ui->recordButton, &QPushButton::clicked, this, &MainWindow::handleRecordButtonClicked)); + QObject::connect(ui->exposureSpinBox, &QSpinBox::valueChanged, this, &MainWindow::HandleExposureValueChanged)); + HANDLE_CONNECTION_RESULT(QObject::connect(ui->viewerImageSlider, &QSlider::valueChanged, this, + &MainWindow::HandleViewerImageSliderValueChanged)); + HANDLE_CONNECTION_RESULT( + QObject::connect(ui->recordButton, &QPushButton::clicked, this, &MainWindow::HandleRecordButtonClicked)); HANDLE_CONNECTION_RESULT(QObject::connect(ui->baseFolderButton, &QPushButton::clicked, this, - &MainWindow::handleBaseFolderButtonClicked)); - HANDLE_CONNECTION_RESULT(QObject::connect(ui->exposureLineEdit, &QLineEdit::textEdited, this, - &MainWindow::handleExposureLineEditTextEdited)); - HANDLE_CONNECTION_RESULT(QObject::connect(ui->exposureLineEdit, &QLineEdit::returnPressed, this, - &MainWindow::handleExposureLineEditReturnPressed)); - HANDLE_CONNECTION_RESULT(QObject::connect(ui->filePrefixLineEdit, &QLineEdit::textEdited, this, - &MainWindow::handleFilePrefixLineEditTextEdited)); - HANDLE_CONNECTION_RESULT(QObject::connect(ui->filePrefixLineEdit, &QLineEdit::returnPressed, this, - &MainWindow::handleFilePrefixLineEditReturnPressed)); + &MainWindow::HandleBaseFolderButtonClicked)); + HANDLE_CONNECTION_RESULT( + QObject::connect(this, &MainWindow::ViewerImageProcessingComplete, this, &MainWindow::UpdateRawViewerImage)); + HANDLE_CONNECTION_RESULT(QObject::connect(ui->viewerFileButton, &QPushButton::clicked, this, + &MainWindow::HandleViewerFileButtonClicked)); + HANDLE_CONNECTION_RESULT(QObject::connect(ui->fileNameLineEdit, &QLineEdit::textEdited, this, + &MainWindow::HandleFileNameLineEditTextEdited)); + HANDLE_CONNECTION_RESULT(QObject::connect(ui->fileNameLineEdit, &QLineEdit::returnPressed, this, + &MainWindow::HandleFileNameLineEditReturnPressed)); HANDLE_CONNECTION_RESULT(QObject::connect(ui->autoexposureCheckbox, &QCheckBox::clicked, this, - &MainWindow::handleAutoexposureCheckboxClicked)); + &MainWindow::HandleAutoexposureCheckboxClicked)); HANDLE_CONNECTION_RESULT(QObject::connect(ui->whiteBalanceButton, &QPushButton::clicked, this, - &MainWindow::handleWhiteBalanceButtonClicked)); + &MainWindow::HandleWhiteBalanceButtonClicked)); HANDLE_CONNECTION_RESULT(QObject::connect(ui->darkCorrectionButton, &QPushButton::clicked, this, - &MainWindow::handleDarkCorrectionButtonClicked)); + &MainWindow::HandleDarkCorrectionButtonClicked)); HANDLE_CONNECTION_RESULT(QObject::connect(ui->logTextLineEdit, &QLineEdit::textEdited, this, - &MainWindow::handleLogTextLineEditTextEdited)); + &MainWindow::HandleLogTextLineEditTextEdited)); HANDLE_CONNECTION_RESULT(QObject::connect(ui->logTextLineEdit, &QLineEdit::returnPressed, this, - &MainWindow::handleLogTextLineEditReturnPressed)); + &MainWindow::HandleLogTextLineEditReturnPressed)); HANDLE_CONNECTION_RESULT(QObject::connect(ui->skipFramesSpinBox, &QSpinBox::valueChanged, this, - &MainWindow::handleSkipFramesSpinBoxValueChanged)); + &MainWindow::HandleSkipFramesSpinBoxValueChanged)); HANDLE_CONNECTION_RESULT(QObject::connect(ui->cameraListComboBox, &QComboBox::currentIndexChanged, this, - &MainWindow::handleCameraListComboBoxCurrentIndexChanged)); + &MainWindow::HandleCameraListComboBoxCurrentIndexChanged)); HANDLE_CONNECTION_RESULT(QObject::connect(ui->reloadCamerasPushButton, &QPushButton::clicked, this, - &MainWindow::handleReloadCamerasPushButtonClicked)); - HANDLE_CONNECTION_RESULT(QObject::connect(ui->filePrefixExtrasLineEdit, &QLineEdit::textEdited, this, - &MainWindow::handleFilePrefixExtrasLineEditTextEdited)); - HANDLE_CONNECTION_RESULT(QObject::connect(ui->filePrefixExtrasLineEdit, &QLineEdit::returnPressed, this, - &MainWindow::handleFilePrefixExtrasLineEditReturnPressed)); + &MainWindow::HandleReloadCamerasPushButtonClicked)); + HANDLE_CONNECTION_RESULT(QObject::connect(ui->fileNameSnapshotsLineEdit, &QLineEdit::textEdited, this, + &MainWindow::HandleFileNameSnapshotsLineEditTextEdited)); + HANDLE_CONNECTION_RESULT(QObject::connect(ui->fileNameSnapshotsLineEdit, &QLineEdit::returnPressed, this, + &MainWindow::HandleFileNameSnapshotsLineEditReturnPressed)); HANDLE_CONNECTION_RESULT(QObject::connect(ui->baseFolderLineEdit, &QLineEdit::returnPressed, this, - &MainWindow::handleBaseFolderLineEditReturnPressed)); - HANDLE_CONNECTION_RESULT(QObject::connect(ui->subFolderExtrasLineEdit, &QLineEdit::textEdited, this, - &MainWindow::handleSubFolderExtrasLineEditTextEdited)); + &MainWindow::HandleBaseFolderLineEditReturnPressed)); HANDLE_CONNECTION_RESULT(QObject::connect(ui->baseFolderLineEdit, &QLineEdit::textEdited, this, - &MainWindow::handleBaseFolderLineEditTextEdited)); - HANDLE_CONNECTION_RESULT(QObject::connect(ui->subFolderExtrasLineEdit, &QLineEdit::returnPressed, this, - &MainWindow::handleSubFolderExtrasLineEditReturnPressed)); + &MainWindow::HandleBaseFolderLineEditTextEdited)); + HANDLE_CONNECTION_RESULT(QObject::connect(ui->viewerFileLineEdit, &QLineEdit::textEdited, this, + &MainWindow::HandleViewerFileLineEditTextEdited)); + HANDLE_CONNECTION_RESULT(QObject::connect(ui->viewerFileLineEdit, &QLineEdit::returnPressed, this, + &MainWindow::HandleViewerFileLineEditReturnPressed)); + HANDLE_CONNECTION_RESULT( + QObject::connect(m_display, &Displayer::ImageReadyToUpdateRGB, this, &MainWindow::UpdateRGBImage)); + HANDLE_CONNECTION_RESULT( + QObject::connect(m_display, &Displayer::ImageReadyToUpdateRaw, this, &MainWindow::UpdateRawImage)); + HANDLE_CONNECTION_RESULT(QObject::connect(m_display, &Displayer::SaturationPercentageImageReady, this, + &MainWindow::UpdateSaturationPercentageLCDDisplays)); } void MainWindow::HandleConnectionResult(bool status, const char *file, int line, const char *func) @@ -194,8 +203,6 @@ void MainWindow::EnableUi(bool enable) SetGraphicsViewScene(); this->ui->exposureSlider->setEnabled(enable); this->ui->logTextLineEdit->setEnabled(enable); - QLayout *layoutExtras = ui->extrasVerticalLayout->layout(); - EnableWidgetsInLayout(layoutExtras, enable); } void MainWindow::SetUpCustomUiComponents() @@ -237,6 +244,16 @@ MainWindow::~MainWindow() m_temperatureIOService.stop(); m_threadGroup.join_all(); + { + boost::lock_guard lock(m_mutexImageViewer); + m_viewerThreadRunning = false; + } + m_viewerQueueCondition.notify_all(); + if (m_viewerThread.joinable()) + { + m_viewerThread.join(); + } + this->StopTemperatureThread(); this->StopSnapshotsThread(); this->StopReferenceRecordingThread(); @@ -244,6 +261,7 @@ MainWindow::~MainWindow() HANDLE_CONNECTION_RESULT( QObject::disconnect(&(this->m_imageContainer), &ImageContainer::NewImage, this, &MainWindow::Display)); + blosc2_destroy(); delete ui; } @@ -251,17 +269,15 @@ void MainWindow::RecordSnapshots() { int nr_images = ui->nSnapshotsSpinBox->value(); QMetaObject::invokeMethod(ui->nSnapshotsSpinBox, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, false)); - QMetaObject::invokeMethod(ui->filePrefixExtrasLineEdit, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, false)); - QMetaObject::invokeMethod(ui->subFolderExtrasLineEdit, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, false)); + QMetaObject::invokeMethod(ui->fileNameSnapshotsLineEdit, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, false)); - std::string filePrefix = ui->filePrefixExtrasLineEdit->text().toUtf8().constData(); - std::string subFolder = ui->subFolderExtrasLineEdit->text().toUtf8().constData(); + std::string fileName = ui->fileNameSnapshotsLineEdit->text().toUtf8().constData(); - if (filePrefix.empty()) + if (fileName.empty()) { - filePrefix = m_recPrefixLineEdit.toUtf8().constData(); + fileName = m_fileName.toUtf8().constData(); } - QString filePath = GetFullFilenameStandardFormat(std::move(filePrefix), ".b2nd", std::move(subFolder)); + QString filePath = GetFullFilenameStandardFormat(std::move(fileName), ".b2nd", ""); auto image = m_imageContainer.GetCurrentImage(); FileImage snapshotsFile(filePath.toStdString().c_str(), image.height, image.width); @@ -269,9 +285,9 @@ void MainWindow::RecordSnapshots() { int exp_time = m_cameraInterface.m_camera->GetExposureMs(); int waitTime = 2 * exp_time; - wait(waitTime); + WaitMilliseconds(waitTime); image = m_imageContainer.GetCurrentImage(); - snapshotsFile.write(image, GetCameraTemperature()); + snapshotsFile.WriteImageData(image, GetCameraTemperature()); int progress = static_cast((static_cast(i + 1) / static_cast(nr_images)) * 100); QMetaObject::invokeMethod(ui->progressBar, "setValue", Qt::QueuedConnection, Q_ARG(int, progress)); } @@ -279,25 +295,24 @@ void MainWindow::RecordSnapshots() LOG_XILENS(info) << "Closed snapshot recording file"; QMetaObject::invokeMethod(ui->progressBar, "setValue", Qt::QueuedConnection, Q_ARG(int, 0)); QMetaObject::invokeMethod(ui->nSnapshotsSpinBox, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, true)); - QMetaObject::invokeMethod(ui->filePrefixExtrasLineEdit, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, true)); - QMetaObject::invokeMethod(ui->subFolderExtrasLineEdit, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, true)); + QMetaObject::invokeMethod(ui->fileNameSnapshotsLineEdit, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, true)); } -void MainWindow::handleSnapshotButtonClicked() +void MainWindow::HandleSnapshotButtonClicked() { m_snapshotsThread = boost::thread(&MainWindow::RecordSnapshots, this); } QMap MainWindow::GetCameraTemperature() const { - m_cameraInterface.m_camera->family->get()->UpdateCameraTemperature(); - auto cameraTemperature = m_cameraInterface.m_camera->family->get()->m_cameraTemperature; + m_cameraInterface.m_camera->m_cameraFamily->get()->UpdateCameraTemperature(); + auto cameraTemperature = m_cameraInterface.m_camera->m_cameraFamily->get()->m_cameraTemperature; return cameraTemperature; } void MainWindow::DisplayCameraTemperature() { - double temp = m_cameraInterface.m_camera->family->get()->m_cameraTemperature.value(SENSOR_BOARD_TEMP); + double temp = m_cameraInterface.m_camera->m_cameraFamily->get()->m_cameraTemperature.value(SENSOR_BOARD_TEMP); QMetaObject::invokeMethod(ui->temperatureLCDNumber, "display", Qt::QueuedConnection, Q_ARG(double, temp)); } @@ -318,7 +333,7 @@ void MainWindow::HandleTemperatureTimer(const boost::system::error_code &error) return; } - m_cameraInterface.m_camera->family->get()->UpdateCameraTemperature(); + m_cameraInterface.m_camera->m_cameraFamily->get()->UpdateCameraTemperature(); this->DisplayCameraTemperature(); // Reset timer @@ -330,7 +345,7 @@ void MainWindow::HandleTemperatureTimer(const boost::system::error_code &error) void MainWindow::StartTemperatureThread() { // Initial temperature update to ensure that it is populated before recordings start. - m_cameraInterface.m_camera->family->get()->UpdateCameraTemperature(); + m_cameraInterface.m_camera->m_cameraFamily->get()->UpdateCameraTemperature(); if (m_temperatureThread.joinable()) { StopTemperatureThread(); @@ -375,28 +390,95 @@ void MainWindow::StopReferenceRecordingThread() } } -void MainWindow::handleExposureSliderValueChanged(int value) +void MainWindow::HandleExposureValueChanged(int value) { m_cameraInterface.m_camera->SetExposureMs(value); UpdateExposure(); } +void MainWindow::HandleViewerImageSliderValueChanged(int value) +{ + { + boost::lock_guard lock(m_mutexImageViewer); + m_viewerSliderQueue.push(value); + } + m_viewerQueueCondition.notify_one(); +} + +void MainWindow::ProcessViewerImageSliderValueChanged(int value) +{ + std::array slice_start = {0}; + std::array slice_stop = {0}; + std::array slice_shape = {0}; + for (int i = 0; i < this->m_viewerNDArray->ndim; i++) + { + slice_start[i] = i == 0 ? value : 0; + slice_stop[i] = i == 0 ? value + 1 : this->m_viewerNDArray->shape[i]; + slice_shape[i] = slice_stop[i] - slice_start[i]; + } + auto buffer_size = + static_cast(this->m_viewerNDArray->shape[1] * this->m_viewerNDArray->shape[2] * sizeof(uint16_t)); + std::vector buffer(this->m_viewerNDArray->shape[1] * this->m_viewerNDArray->shape[2]); + b2nd_get_slice_cbuffer(this->m_viewerNDArray, slice_start.data(), slice_stop.data(), buffer.data(), + slice_shape.data(), buffer_size); + + // Get image dimensions, dimensions are transposed compared to what OpenCv expects. + auto width = static_cast(slice_shape[1]); + auto height = static_cast(slice_shape[2]); + cv::Mat mat(width, height, CV_16UC1, buffer.data()); + mat /= 4; + mat.convertTo(mat, CV_8UC1); + + // Indicate that processing is finished. + emit ViewerImageProcessingComplete(mat); +} + +void MainWindow::ViewerWorkerThreadFunc() +{ + // This function is running in a separate thread + while (true) + { + int value = -1; // Default invalid value + + { + boost::unique_lock lock(m_mutexImageViewer); + m_viewerQueueCondition.wait(lock, + [this]() { return !m_viewerSliderQueue.empty() || !m_viewerThreadRunning; }); + + if (!m_viewerThreadRunning && m_viewerSliderQueue.empty()) + { + break; // Exit condition to shut down the thread + } + + if (!m_viewerSliderQueue.empty()) + { + value = m_viewerSliderQueue.front(); + m_viewerSliderQueue.pop(); + } + } + + if (value != -1) + { + ProcessViewerImageSliderValueChanged(value); + } + } +} + void MainWindow::UpdateExposure() { + // lock ui elements before updating them + const QSignalBlocker exposureSliderLock(ui->exposureSlider); + const QSignalBlocker exposureSpinBoxLock(ui->exposureSpinBox); int exp_ms = m_cameraInterface.m_camera->GetExposureMs(); + // update the estimated framerate int n_skip_frames = ui->skipFramesSpinBox->value(); - ui->exposureLineEdit->setText(QString::number((int)exp_ms)); ui->hzLabel->setText(QString::number((double)(1000.0 / (exp_ms * (n_skip_frames + 1))), 'g', 2)); - - // need to block the signals to make sure the event is not immediately - // thrown back to label_exp. - // could be done with a QSignalBlocker from Qt5.3 on for exception safe - // treatment. see: http://doc.qt.io/qt-5/qsignalblocker.html - const QSignalBlocker blocker_slider(ui->exposureSlider); + // set exposure values to bot spinbox and slider + ui->exposureSpinBox->setValue(exp_ms); ui->exposureSlider->setValue(exp_ms); } -void MainWindow::handleRecordButtonClicked(bool clicked) +void MainWindow::HandleRecordButtonClicked(bool clicked) { static QString original_colour; static QString original_button_text; @@ -430,7 +512,7 @@ void MainWindow::HandleElementsWhileRecording(bool recordingInProgress) if (recordingInProgress) { QMetaObject::invokeMethod(ui->baseFolderButton, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, false)); - QMetaObject::invokeMethod(ui->filePrefixLineEdit, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, false)); + QMetaObject::invokeMethod(ui->fileNameLineEdit, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, false)); QMetaObject::invokeMethod(ui->cameraListComboBox, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, false)); QMetaObject::invokeMethod(ui->whiteBalanceButton, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, false)); QMetaObject::invokeMethod(ui->darkCorrectionButton, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, false)); @@ -440,7 +522,7 @@ void MainWindow::HandleElementsWhileRecording(bool recordingInProgress) else { QMetaObject::invokeMethod(ui->baseFolderButton, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, true)); - QMetaObject::invokeMethod(ui->filePrefixLineEdit, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, true)); + QMetaObject::invokeMethod(ui->fileNameLineEdit, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, true)); QMetaObject::invokeMethod(ui->cameraListComboBox, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, true)); QMetaObject::invokeMethod(ui->whiteBalanceButton, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, true)); QMetaObject::invokeMethod(ui->darkCorrectionButton, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, true)); @@ -453,13 +535,13 @@ void MainWindow::closeEvent(QCloseEvent *event) { if (this->ui->recordButton->isChecked()) { - handleRecordButtonClicked(false); + HandleRecordButtonClicked(false); } this->StopPollingThread(); QMainWindow::closeEvent(event); } -void MainWindow::handleBaseFolderButtonClicked() +void MainWindow::HandleBaseFolderButtonClicked() { bool isValid = false; while (!isValid) @@ -472,7 +554,7 @@ void MainWindow::handleBaseFolderButtonClicked() isValid = true; if (!baseFolderPath.isEmpty()) { - this->SetBaseFolder(baseFolderPath); + m_baseFolderPath = baseFolderPath; ui->baseFolderLineEdit->clear(); ui->baseFolderLineEdit->insert(this->GetBaseFolder()); this->WriteLogHeader(); @@ -481,6 +563,32 @@ void MainWindow::handleBaseFolderButtonClicked() } } +void MainWindow::HandleViewerFileButtonClicked() +{ + QString filePath = QFileDialog::getOpenFileName(this, tr("Open File"), "", tr("NDArrays (*.b2nd)")); + if (QFile(filePath).exists()) + { + if (!filePath.isEmpty()) + { + m_viewerFilePath = filePath; + ui->viewerFileLineEdit->clear(); + ui->viewerFileLineEdit->insert(filePath); + this->HandleViewerFileLineEditReturnPressed(); + OpenFileInViewer(m_viewerFilePath); + } + } +} + +void MainWindow::OpenFileInViewer(const QString &filePath) +{ + char *path = strdup(filePath.toUtf8().constData()); + b2nd_open(path, &this->m_viewerNDArray); + this->ui->viewerImageSlider->setEnabled(true); + auto n_images = static_cast(this->m_viewerNDArray->shape[0] - 1); + this->ui->viewerImageSlider->setMaximum(n_images); + this->HandleViewerImageSliderValueChanged(this->ui->viewerImageSlider->value()); +} + void MainWindow::WriteLogHeader() { auto version = @@ -525,22 +633,9 @@ unsigned MainWindow::GetBGRNorm() const return this->ui->rgbNormSlider->value(); } -bool MainWindow::SetBaseFolder(const QString &baseFolderPath) -{ - if (QDir(baseFolderPath).exists()) - { - m_baseFolderLoc = baseFolderPath; - return true; - } - else - { - return false; - } -} - QString MainWindow::GetBaseFolder() const { - return m_baseFolderLoc; + return m_baseFolderPath; } void MainWindow::ThreadedRecordImage() @@ -548,13 +643,13 @@ void MainWindow::ThreadedRecordImage() this->m_IOService.post([this] { RecordImage(false); }); } -void MainWindow::InitializeImageFileRecorder(std::string subFolder, std::string filePrefix) +void MainWindow::InitializeImageFileRecorder(std::string subFolder, std::string fileName) { - if (filePrefix.empty()) + if (fileName.empty()) { - filePrefix = m_recPrefixLineEdit.toUtf8().constData(); + fileName = m_fileName.toUtf8().constData(); } - QString fullPath = GetFullFilenameStandardFormat(std::move(filePrefix), ".b2nd", std::move(subFolder)); + QString fullPath = GetFullFilenameStandardFormat(std::move(fileName), ".b2nd", std::move(subFolder)); this->m_imageContainer.InitializeFile(fullPath.toStdString().c_str()); } @@ -562,14 +657,14 @@ void MainWindow::RecordImage(bool ignoreSkipping) { boost::this_thread::interruption_point(); XI_IMG image = m_imageContainer.GetCurrentImage(); - boost::lock_guard guard(this->mtx_); + boost::lock_guard guard(this->m_mutexImageRecording); static long lastImageID = image.acq_nframe; int nSkipFrames = ui->skipFramesSpinBox->value(); if (MainWindow::ImageShouldBeRecorded(nSkipFrames, image.acq_nframe) || ignoreSkipping) { try { - this->m_imageContainer.m_imageFile->write(image, GetCameraTemperature()); + this->m_imageContainer.m_imageFile->WriteImageData(image, GetCameraTemperature()); m_recordedCount++; } catch (const std::runtime_error &e) @@ -608,7 +703,7 @@ void MainWindow::DisplayRecordCount() Q_ARG(int, static_cast(m_recordedCount.load()))); } -void MainWindow::updateTimer() +void MainWindow::UpdateTimer() { m_elapsedTime = static_cast(m_elapsedTimer.elapsed()) / 1000.0; int totalSeconds = static_cast(m_elapsedTime); @@ -631,7 +726,7 @@ void MainWindow::updateTimer() ui->timerLCDNumber->display(m_elapsedTimeText); } -void MainWindow::stopTimer() +void MainWindow::StopTimer() { ui->timerLCDNumber->display(0); } @@ -656,7 +751,7 @@ void MainWindow::StartRecording() HANDLE_CONNECTION_RESULT( QObject::connect(&(this->m_imageContainer), &ImageContainer::NewImage, this, &MainWindow::CountImages)); HANDLE_CONNECTION_RESULT( - QObject::connect(&(this->m_imageContainer), &ImageContainer::NewImage, this, &MainWindow::updateTimer)); + QObject::connect(&(this->m_imageContainer), &ImageContainer::NewImage, this, &MainWindow::UpdateTimer)); HANDLE_CONNECTION_RESULT( QObject::connect(m_updateFPSDisplayTimer, &QTimer::timeout, this, &MainWindow::UpdateFPSLCDDisplay)); m_updateFPSDisplayTimer->start(UPDATE_RATE_MS_FPS_TIMER); @@ -669,11 +764,11 @@ void MainWindow::StopRecording() HANDLE_CONNECTION_RESULT( QObject::disconnect(&(this->m_imageContainer), &ImageContainer::NewImage, this, &MainWindow::CountImages)); HANDLE_CONNECTION_RESULT( - QObject::disconnect(&(this->m_imageContainer), &ImageContainer::NewImage, this, &MainWindow::updateTimer)); + QObject::disconnect(&(this->m_imageContainer), &ImageContainer::NewImage, this, &MainWindow::UpdateTimer)); HANDLE_CONNECTION_RESULT( QObject::disconnect(m_updateFPSDisplayTimer, &QTimer::timeout, this, &MainWindow::UpdateFPSLCDDisplay)); QMetaObject::invokeMethod(this->ui->fpsLCDNumber, "display", Qt::QueuedConnection, Q_ARG(QString, "")); - this->stopTimer(); + this->StopTimer(); this->m_IOWork.reset(); this->m_IOWork = nullptr; this->m_IOService.stop(); @@ -705,7 +800,7 @@ void MainWindow::CreateFolderIfNecessary(const QString &folder) } } -QString MainWindow::GetFullFilenameStandardFormat(std::string &&filePrefix, const std::string &extension, +QString MainWindow::GetFullFilenameStandardFormat(std::string &&fileName, const std::string &extension, std::string &&subFolder) { QString writingFolder = GetWritingFolder() + QDir::separator() + QString::fromStdString(subFolder); @@ -715,18 +810,18 @@ QString MainWindow::GetFullFilenameStandardFormat(std::string &&filePrefix, cons } MainWindow::CreateFolderIfNecessary(writingFolder); - QString fileName; + QString fullFileName; if (!m_testMode) { - fileName = QString::fromStdString(filePrefix); + fullFileName = QString::fromStdString(fileName); } else { - fileName = QString("test"); + fullFileName = QString("test"); } - fileName += QString::fromStdString(extension); + fullFileName += QString::fromStdString(extension); - return QDir::cleanPath(writingFolder + fileName); + return QDir::cleanPath(writingFolder + fullFileName); } void MainWindow::StartPollingThread() @@ -743,15 +838,15 @@ void MainWindow::StopPollingThread() m_imageContainerThread.join(); } -void MainWindow::handleAutoexposureCheckboxClicked(bool setAutoexposure) +void MainWindow::HandleAutoexposureCheckboxClicked(bool setAutoexposure) { this->m_cameraInterface.m_camera->AutoExposure(setAutoexposure); ui->exposureSlider->setEnabled(!setAutoexposure); - ui->exposureLineEdit->setEnabled(!setAutoexposure); + ui->exposureSpinBox->setEnabled(!setAutoexposure); UpdateExposure(); } -void MainWindow::handleWhiteBalanceButtonClicked() +void MainWindow::HandleWhiteBalanceButtonClicked() { if (m_referenceRecordingThread.joinable()) { @@ -760,7 +855,7 @@ void MainWindow::handleWhiteBalanceButtonClicked() m_referenceRecordingThread = boost::thread(&MainWindow::RecordReferenceImages, this, "white"); } -void MainWindow::handleDarkCorrectionButtonClicked() +void MainWindow::HandleDarkCorrectionButtonClicked() { if (m_referenceRecordingThread.joinable()) { @@ -812,7 +907,7 @@ void MainWindow::RecordReferenceImages(const QString &referenceType) { int exp_time = m_cameraInterface.m_camera->GetExposureMs(); int waitTime = 2 * exp_time; - wait(waitTime); + WaitMilliseconds(waitTime); this->RecordImage(true); int progress = static_cast((static_cast(i + 1) / NR_REFERENCE_IMAGES_TO_RECORD) * 100); QMetaObject::invokeMethod(ui->progressBar, "setValue", Qt::QueuedConnection, Q_ARG(int, progress)); @@ -848,39 +943,50 @@ void MainWindow::RestoreLineEditStyle(QLineEdit *lineEdit) lineEdit->setStyleSheet(FIELD_ORIGINAL_STYLE); } -void MainWindow::handleExposureLineEditReturnPressed() -{ - m_labelExp = ui->exposureLineEdit->text(); - m_cameraInterface.m_camera->SetExposureMs(m_labelExp.toInt()); - UpdateExposure(); - RestoreLineEditStyle(ui->exposureLineEdit); -} - -void MainWindow::handleFilePrefixLineEditReturnPressed() +void MainWindow::HandleFileNameLineEditReturnPressed() { - m_recPrefixLineEdit = ui->filePrefixLineEdit->text(); - RestoreLineEditStyle(ui->filePrefixLineEdit); + m_fileName = ui->fileNameLineEdit->text(); + RestoreLineEditStyle(ui->fileNameLineEdit); } -void MainWindow::handleSubFolderExtrasLineEditReturnPressed() +void MainWindow::HandleViewerFileLineEditReturnPressed() { - m_extrasSubFolder = ui->subFolderExtrasLineEdit->text(); - RestoreLineEditStyle(ui->subFolderExtrasLineEdit); + auto file = QFile(ui->viewerFileLineEdit->text()); + if (file.exists()) + { + m_viewerFilePath = ui->viewerFileLineEdit->text(); + OpenFileInViewer(m_viewerFilePath); + RestoreLineEditStyle(ui->viewerFileLineEdit); + } + else + { + LOG_XILENS(error) << "Viewer file path does not exist."; + } } -void MainWindow::handleFilePrefixExtrasLineEditReturnPressed() +void MainWindow::HandleFileNameSnapshotsLineEditReturnPressed() { - m_extrasFilePrefix = ui->filePrefixExtrasLineEdit->text(); - RestoreLineEditStyle(ui->filePrefixExtrasLineEdit); + if (m_fileName == ui->fileNameSnapshotsLineEdit->text()) + { + QMessageBox msgBox; + msgBox.setIcon(QMessageBox::Critical); + msgBox.setWindowTitle("Error"); + msgBox.setText("Invalid file name."); + msgBox.setInformativeText("Snapshot file name cannot be the same as video recording file name."); + msgBox.exec(); + return; + } + m_snapshotsFileName = ui->fileNameSnapshotsLineEdit->text(); + RestoreLineEditStyle(ui->fileNameSnapshotsLineEdit); } -void MainWindow::handleBaseFolderLineEditReturnPressed() +void MainWindow::HandleBaseFolderLineEditReturnPressed() { - m_baseFolderLoc = ui->baseFolderLineEdit->text(); + m_baseFolderPath = ui->baseFolderLineEdit->text(); RestoreLineEditStyle(ui->baseFolderLineEdit); } -void MainWindow::handleLogTextLineEditReturnPressed() +void MainWindow::HandleLogTextLineEditReturnPressed() { QString timestamp; QString trigger_message = ui->logTextLineEdit->text(); @@ -901,34 +1007,29 @@ void MainWindow::handleLogTextLineEditReturnPressed() ui->logTextLineEdit->clear(); } -void MainWindow::handleFilePrefixLineEditTextEdited(const QString &newText) +void MainWindow::HandleFileNameLineEditTextEdited(const QString &newText) { - UpdateComponentEditedStyle(ui->filePrefixLineEdit, newText, m_recPrefixLineEdit); + UpdateComponentEditedStyle(ui->fileNameLineEdit, newText, m_fileName); } -void MainWindow::handleSubFolderExtrasLineEditTextEdited(const QString &newText) +void MainWindow::HandleFileNameSnapshotsLineEditTextEdited(const QString &newText) { - UpdateComponentEditedStyle(ui->subFolderExtrasLineEdit, newText, m_extrasSubFolder); + UpdateComponentEditedStyle(ui->fileNameSnapshotsLineEdit, newText, m_snapshotsFileName); } -void MainWindow::handleExposureLineEditTextEdited(const QString &newText) +void MainWindow::HandleLogTextLineEditTextEdited(const QString &newText) { - UpdateComponentEditedStyle(ui->exposureLineEdit, newText, m_labelExp); -} - -void MainWindow::handleFilePrefixExtrasLineEditTextEdited(const QString &newText) -{ - UpdateComponentEditedStyle(ui->filePrefixExtrasLineEdit, newText, m_extrasFilePrefix); + UpdateComponentEditedStyle(ui->logTextLineEdit, newText, m_triggerText); } -void MainWindow::handleLogTextLineEditTextEdited(const QString &newText) +void MainWindow::HandleBaseFolderLineEditTextEdited(const QString &newText) { - UpdateComponentEditedStyle(ui->logTextLineEdit, newText, m_triggerText); + UpdateComponentEditedStyle(ui->baseFolderLineEdit, newText, m_baseFolderPath); } -void MainWindow::handleBaseFolderLineEditTextEdited(const QString &newText) +void MainWindow::HandleViewerFileLineEditTextEdited(const QString &newText) { - UpdateComponentEditedStyle(ui->baseFolderLineEdit, newText, m_baseFolderLoc); + UpdateComponentEditedStyle(ui->viewerFileLineEdit, newText, m_viewerFilePath); } QString MainWindow::FormatTimeStamp(const QString ×tamp) @@ -942,7 +1043,7 @@ QString MainWindow::FormatTimeStamp(const QString ×tamp) * updates frames per second label in GUI when the number of skipped frames is * modified */ -void MainWindow::handleSkipFramesSpinBoxValueChanged() +void MainWindow::HandleSkipFramesSpinBoxValueChanged() { // spin boxes do not have a returnPressed slot in Qt, which is why the value // is always updated upon changes @@ -952,9 +1053,9 @@ void MainWindow::handleSkipFramesSpinBoxValueChanged() ui->hzLabel->setText(QString::number((double)(1000.0 / (exp_ms * (nSkipFrames + 1))), 'g', 2)); } -void MainWindow::handleCameraListComboBoxCurrentIndexChanged(int index) +void MainWindow::HandleCameraListComboBoxCurrentIndexChanged(int index) { - boost::lock_guard guard(mtx_); + boost::lock_guard guard(m_mutexImageRecording); // image acquisition should be stopped when index 0 (no camera) is selected // from the dropdown menu try @@ -1017,8 +1118,12 @@ void MainWindow::handleCameraListComboBoxCurrentIndexChanged(int index) } } -void MainWindow::handleReloadCamerasPushButtonClicked() +void MainWindow::HandleReloadCamerasPushButtonClicked() { + // set button style as pressed down, and process event loop before continuing + ui->reloadCamerasPushButton->setDown(true); + QCoreApplication::processEvents(); + QStringList cameraList = m_cameraInterface.GetAvailableCameraIdentifiers(); // Only add new camera models for (const QString &camera : cameraList) @@ -1042,6 +1147,9 @@ void MainWindow::handleReloadCamerasPushButtonClicked() ui->cameraListComboBox->removeItem(i); } } + + // restore button style + ui->reloadCamerasPushButton->setDown(false); } void MainWindow::UpdateSaturationPercentageLCDDisplays(cv::Mat &image) const @@ -1101,20 +1209,27 @@ void MainWindow::UpdateImage(cv::Mat &image, QImage::Format format, QGraphicsVie void MainWindow::UpdateRGBImage(cv::Mat &image) { - UpdateImage(image, QImage::Format_RGB888, this->ui->rgbImageGraphicsView, this->rgbPixMapItem, - this->rgbScene.get()); + UpdateImage(image, QImage::Format_RGB888, this->ui->rgbImageGraphicsView, this->m_rgbPixMapItem, + this->m_rgbScene.get()); } void MainWindow::UpdateRawImage(cv::Mat &image) { - UpdateImage(image, QImage::Format_BGR888, this->ui->rawImageGraphicsView, this->rawPixMapItem, - this->rawScene.get()); + UpdateImage(image, QImage::Format_BGR888, this->ui->rawImageGraphicsView, this->m_rawPixMapItem, + this->m_rawScene.get()); +} + +void MainWindow::UpdateRawViewerImage(cv::Mat &image) +{ + UpdateImage(image, QImage::Format_Grayscale8, this->ui->viewerGraphicsView, this->m_rawViewerPixMapItem, + this->m_rawViewerScene.get()); } void MainWindow::SetGraphicsViewScene() { - this->ui->rgbImageGraphicsView->setScene(this->rgbScene.get()); - this->ui->rawImageGraphicsView->setScene(this->rawScene.get()); + this->ui->rgbImageGraphicsView->setScene(this->m_rgbScene.get()); + this->ui->rawImageGraphicsView->setScene(this->m_rawScene.get()); + this->ui->viewerGraphicsView->setScene(this->m_rawViewerScene.get()); } bool MainWindow::IsSaturationButtonChecked() diff --git a/src/mainwindow.h b/src/mainwindow.h index 9dba88d..bc0260b 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -137,32 +137,23 @@ class MainWindow : public QMainWindow */ void StopSnapshotsThread(); - /** - * @brief Updates the saturation percentage on the LCD displays. - * - * @param image The input image of type CV_8UC1. It must be non-empty. - * @throws std::invalid_argument if the input matrix is empty or of the wrong type. - */ - void UpdateSaturationPercentageLCDDisplays(cv::Mat &image) const; - /** * Updates the frames per second that are stored to file on the UI. */ void UpdateFPSLCDDisplay(); /** - * Updates the RGB image displayed in the GUI. + * Updates the raw image displayed in the viewer tab. * - * @param image OpenCv matrix containing an 8bit (per channel) RGB image to be displayed. + * @param image OpenCV patrix to display. */ - void UpdateRGBImage(cv::Mat &image); + void UpdateRawViewerImage(cv::Mat &image); /** - * Updates the raw image displayed in the GUI. - * - * @param image OpenCV matrix containing an 8bit single channel image to be displayed. + * Waits for the viewer thread to be running and for new values to be available in the queue. It emits a + * signal indicating that a new value can be processed. */ - void UpdateRawImage(cv::Mat &image); + void ViewerWorkerThreadFunc(); /** * Takes an image, and scales it to the available width in the QtGraphicsView element before displaying it in the @@ -213,6 +204,11 @@ class MainWindow : public QMainWindow */ Ui::MainWindow *ui; + /** + * This is the NDArray structure that holds the connection to the data viewed in the Viewer tab of the application. + */ + b2nd_array_t *m_viewerNDArray = nullptr; + /** * @brief Event handler for the close event of the main window. * @@ -227,20 +223,58 @@ class MainWindow : public QMainWindow */ void closeEvent(QCloseEvent *event) override; + signals: + /** + * Qt signal that is emitted when reading an processing of the image to display in viewer tab is finished. + * + * @param mat OpenCV matrix containing the image to display. This should be a one channel image. + */ + void ViewerImageProcessingComplete(cv::Mat &mat); + + public slots: + /** + * Qt slot that updates the RGB image displayed in the GUI. + * + * @param image OpenCv matrix containing an 8bit (per channel) RGB image to be displayed. + */ + void UpdateRGBImage(cv::Mat &image); + + /** + * Qt slot that updates the raw image displayed in the GUI. + * + * @param image OpenCV matrix containing an 8bit single channel image to be displayed. + */ + void UpdateRawImage(cv::Mat &image); + + /** + * Qt slot that updates the saturation percentage on the LCD displays. + * + * @param image The input image of type CV_8UC1. It must be non-empty. + * @throws std::invalid_argument if the input matrix is empty or of the wrong type. + */ + void UpdateSaturationPercentageLCDDisplays(cv::Mat &image) const; + private slots: /** * Qt slot triggered when the snapshot button is pressed. Triggers the * recording of snapshot images or stops it when pressed a second time. */ - void handleSnapshotButtonClicked(); + void HandleSnapshotButtonClicked(); /** - * Qt slot triggered when the camera exposure slider is modified. + * Qt slot triggered when the camera exposure value is modified either from the slider or the spinbox. * * @param value exposure value. */ - void handleExposureSliderValueChanged(int value); + void HandleExposureValueChanged(int value); + + /** + * Qt slot triggered when the image index slider in the Viewer tab of the application changes value. + * + * @param value The new value of the slider. + */ + void HandleViewerImageSliderValueChanged(int value); /** * Qt slot triggered when the record button is pressed. Stars the continuous @@ -249,62 +283,53 @@ class MainWindow : public QMainWindow * * @param clicked indicates if the button is clicked. */ - void handleRecordButtonClicked(bool clicked); + void HandleRecordButtonClicked(bool clicked); /** * Qt slot triggered when the button to choose a base folder is clicked. Opens * a dialog where a folder can be selected. */ - void handleBaseFolderButtonClicked(); + void HandleBaseFolderButtonClicked(); /** - * Qt slot triggered when the exposure time labels is modified manually. This - * changes the appearance of the field but does not trigger the change in the - * camera. Return key needs to be pressed for the change to be applied. - * - * @param newText edited text. - */ - void handleExposureLineEditTextEdited(const QString &newText); - - /** - * Qt slot triggered when return key is pressed after modifying the exposure - * time. This is synchronized with the exposure time slider. + * Qt slot triggered when the file button in the viewer tab is clicked. Opens + * a dialog where a file can be selected. */ - void handleExposureLineEditReturnPressed(); + void HandleViewerFileButtonClicked(); /** * Qt slot triggered when the return key is pressed on the field that defines - * the file prefix in the UI. It updates the member variable that stores the + * the file name in the UI. It updates the member variable that stores the * value. */ - void handleFilePrefixLineEditReturnPressed(); + void HandleFileNameLineEditReturnPressed(); /** - * Qt slot triggered when the prefix file name is edited. It changes the + * Qt slot triggered when the file name is edited. It changes the * appearance of the field in the UI. It does not change the value of the - * member variable that stores the file prefix name. + * member variable that stores the file name. * * @param newText edited text. */ - void handleFilePrefixLineEditTextEdited(const QString &newText); + void HandleFileNameLineEditTextEdited(const QString &newText); /** * Qt slot triggered when auto exposure checkbox is pressed. Handles control * of the exposure time to camera. */ - void handleAutoexposureCheckboxClicked(bool setAutoexposure); + void HandleAutoexposureCheckboxClicked(bool setAutoexposure); /** * Qt slot triggered when white balance button is pressed. Records a new white * image and sets it in the network model. */ - void handleWhiteBalanceButtonClicked(); + void HandleWhiteBalanceButtonClicked(); /** * Qt slot triggered when the dark correction button is pressed. Records a new * dark image and sets it in the network model. */ - void handleDarkCorrectionButtonClicked(); + void HandleDarkCorrectionButtonClicked(); /** * Qt slot triggered when the trigger text is edited. It only changes the @@ -312,71 +337,75 @@ class MainWindow : public QMainWindow * * @param newText edited text. */ - void handleLogTextLineEditTextEdited(const QString &newText); + void HandleLogTextLineEditTextEdited(const QString &newText); /** * Qt slot triggered when the return key is pressed on the trigger text field. * It logs the message to the log file and displays it on the UI. */ - void handleLogTextLineEditReturnPressed(); + void HandleLogTextLineEditReturnPressed(); /** * Qt slot triggered when the spin box containing the number of images to skip * while recording is modified. It restyles the appearance of the field. */ - void handleSkipFramesSpinBoxValueChanged(); + void HandleSkipFramesSpinBoxValueChanged(); /** * Qt slot triggered when a new camera is selected from the drop-down menu. * * @param index index corresponding to the element selected from the combo box. */ - void handleCameraListComboBoxCurrentIndexChanged(int index); + void HandleCameraListComboBoxCurrentIndexChanged(int index); /** * Checks for connected XIMEA cameras and populates the dropdown list of available cameras. */ - void handleReloadCamerasPushButtonClicked(); + void HandleReloadCamerasPushButtonClicked(); /** - * Qt slot triggered when file name prefix for snapshots is edited on the UI. + * Qt slot triggered when file name for snapshots is edited on the UI. * * @param newText edited text. */ - void handleFilePrefixExtrasLineEditTextEdited(const QString &newText); + void HandleFileNameSnapshotsLineEditTextEdited(const QString &newText); /** - * Qt slot triggered when the return key is pressed on the file prefix field + * Qt slot triggered when the return key is pressed on the file name field * for snapshot images in the UI. + * This method will show a en error message box when the name of the snapshot file is the same as the file + * where the video is to be recorded. */ - void handleFilePrefixExtrasLineEditReturnPressed(); + void HandleFileNameSnapshotsLineEditReturnPressed(); /** * Qt slot triggered when the return key is pressed on the base folder field line edit in the UI. */ - void handleBaseFolderLineEditReturnPressed(); + void HandleBaseFolderLineEditReturnPressed(); /** - * Qt slot triggered when extras sub folder field is edited in the UI. + * Qt slot triggered when base folder field is edited in the UI. * * @param newText edited text. */ - void handleSubFolderExtrasLineEditTextEdited(const QString &newText); + void HandleBaseFolderLineEditTextEdited(const QString &newText); /** - * Qt slot triggered when base folder field is edited in the UI. + * Qt slot triggered when the file path in the viewer tab is edited through the UI. * - * @param newText edited text. + * @param newText edited text */ - void handleBaseFolderLineEditTextEdited(const QString &newText); + void HandleViewerFileLineEditTextEdited(const QString &newText); /** - * Qt slot triggered when the return key is pressed on the sub folder field in - * the extras tab in the UI. + * Qt slot triggered when the return key is pressed in the file path field of the viewer tab. */ - void handleSubFolderExtrasLineEditReturnPressed(); + void HandleViewerFileLineEditReturnPressed(); private: + /** + * Setups all UI Qt connections to handle all user interactions with the UI. + */ void SetUpConnections(); /** @@ -443,13 +472,6 @@ class MainWindow : public QMainWindow */ void StopPollingThread(); - /** - * Sets the base folder path where the data is to be stored. - * - * @param baseFolderPath path of base folder where data will be stored. - */ - bool SetBaseFolder(const QString &baseFolderPath); - /** * Creates a folder if it does not exist. * @@ -458,8 +480,7 @@ class MainWindow : public QMainWindow static void CreateFolderIfNecessary(const QString &folder); /** - * Records image to specified sub folder and using specified file prefix to - * name the file. + * Records image to specified sub folder and using specified file name. * * @param ignoreSkipping ignores the number of frames to skip and stores the * image anyways. @@ -476,9 +497,9 @@ class MainWindow : public QMainWindow * to store all images while recording to a single file. * * @param subFolder folder where data will be stored. - * @param filePrefix file prefix used for the file name. + * @param fileName file name. */ - void InitializeImageFileRecorder(std::string subFolder = "", std::string filePrefix = ""); + void InitializeImageFileRecorder(std::string subFolder = "", std::string fileName = ""); /** * Indicates if an image should be recorded to file or not depending on the @@ -498,12 +519,12 @@ class MainWindow : public QMainWindow /** * Updates timer displayed on the UI when recordings are started. */ - void updateTimer(); + void UpdateTimer(); /** * Stops the timer that is displayed in the UI when recordings are started. */ - void stopTimer(); + void StopTimer(); /** * @brief MEthod used to record singe snapshot images while recording. @@ -539,14 +560,14 @@ class MainWindow : public QMainWindow * It automatically add the current write path and puts the name in a standard * format including timestamp etc. * - * @param filePrefix the name of the file (snapshot, recording, liver_image, ...). + * @param fileName the name of the file (snapshot, recording, liver_image, ...). * @param frameNumber the acquisition frame number provided by ximea. * @param extension file extension (.b2nd). * @param subFolder sometimes we want to add an additional layer of subfolder. * specifically when saving white/dark balance images. * @return */ - QString GetFullFilenameStandardFormat(std::string &&filePrefix, const std::string &extension, + QString GetFullFilenameStandardFormat(std::string &&fileName, const std::string &extension, std::string &&subFolder); /** @@ -575,15 +596,37 @@ class MainWindow : public QMainWindow static QString FormatTimeStamp(const QString ×tamp); /** - * file prefix to be appended to each image file name. + * Opens the N-dimensional array and adjusts UI components based on the contents of the file: + * + * - Adjusts range of viewer image slider. + * - Triggers the display of the first image in the file. + * + * @param filePath path to the file to open. + */ + void OpenFileInViewer(const QString &filePath); + + /** + * Reads a single image slice from file and creates an OpenCv matrix containing the data of the image. + * It emits a signal indicating that the processing finished and provides the processed image through the signal. + * + * @param value image index to load from file + */ + void ProcessViewerImageSliderValueChanged(int value); + + /** + * Sets the scene for RGB and raw image viewers. It defines antialiasing and smooth pixmap transformations. + */ + void SetGraphicsViewScene(); + + /** + * Appends the current time to que of recorded time stamps that can be used to calculate the frames per second. */ - QString m_recPrefixLineEdit; + void RegisterTimeImageRecorded(); /** - * Folder path where extra images are to be stored (e.g. snapshots). This is a - * folder inside the base folder. + * The file name where videos are to be stored. */ - QString m_extrasSubFolder; + QString m_fileName; /** * Trigger text entered to the log function of the UI. @@ -593,7 +636,12 @@ class MainWindow : public QMainWindow /** * Folder path where all data is to be stored. */ - QString m_baseFolderLoc; + QString m_baseFolderPath; + + /** + * Path to a .b2nd file to be displayed in the viewer tab. + */ + QString m_viewerFilePath; /** * Value of exposure time for the camera. @@ -601,9 +649,9 @@ class MainWindow : public QMainWindow QString m_labelExp; /** - * File prefix used for snapshot images. + * File name used for snapshot images. */ - QString m_extrasFilePrefix; + QString m_snapshotsFileName; /** * Elapsed timer used for the timer displayed in the UI. @@ -676,6 +724,31 @@ class MainWindow : public QMainWindow */ boost::thread m_imageContainerThread; + /** + * Thread in charge of handling the image viewer processing before displaying it in the UI. + */ + boost::thread m_viewerThread; + + /** + * Queue used to store image indices that are then processed to load the corresponding images. + */ + std::queue m_viewerSliderQueue; + + /** + * Mutex used as a locking mechanism to avoid raises when processing images for the Viewer tab. + */ + boost::mutex m_mutexImageViewer; + + /** + * Primitive used to lock viewer thread execution until the que is not empty and when the thread has to stop. + */ + boost::condition_variable m_viewerQueueCondition; + + /** + * Indicates if the thread in charge of processing and displaying images in the viewer tab is running. + */ + bool m_viewerThreadRunning; + /** * IO service in charge of recording images to files. */ @@ -705,7 +778,7 @@ class MainWindow : public QMainWindow /** * Mutual exclusion mechanism in charge of synchronization. */ - boost::mutex mtx_; + boost::mutex m_mutexImageRecording; /** * Camera temperature recording thread. @@ -750,32 +823,32 @@ class MainWindow : public QMainWindow /** * Smart pointer to the RGB scene where the RGB images will be displayed. */ - std::unique_ptr rgbScene = std::make_unique(this); + std::unique_ptr m_rgbScene = std::make_unique(this); /** - * smart pointer to raw scene where the raw unprocessed images will be displayed. + * Smart pointer to raw scene where the raw unprocessed images will be displayed. */ - std::unique_ptr rawScene = std::make_unique(this); + std::unique_ptr m_rawScene = std::make_unique(this); /** - * smart pointer to pixmap used to display the RGB images. + * Smart pointer to a scene where the images for the Viewer tab are displayed. */ - std::unique_ptr rgbPixMapItem; + std::unique_ptr m_rawViewerScene = std::make_unique(this); /** - * Smart pointer to pixmap where raw unprocessed images will be displayed. + * Smart pointer to pixmap used to display the RGB images. */ - std::unique_ptr rawPixMapItem; + std::unique_ptr m_rgbPixMapItem; /** - * Sets the scene for RGB and raw image viewers. It defines antialiasing and smooth pixmap transformations. + * Smart pointer to pixmap where raw unprocessed images will be displayed. */ - void SetGraphicsViewScene(); + std::unique_ptr m_rawPixMapItem; /** - * Appends the current time to que of recorded time stamps that can be used to calculate the frames per second. + * Smart Pointer to a pixmap used to display the images in the RawViewer. */ - void RegisterTimeImageRecorded(); + std::unique_ptr m_rawViewerPixMapItem; /** * Timer that sets the rate of updates for the FPS LCD Display in the UI. diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 1e58180..7a107fe 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -11,7 +11,7 @@ - + 0 0 @@ -26,7 +26,7 @@ 0 - + @@ -42,14 +42,20 @@ 0 + + + 450 + 0 + + - 800 + 600 16777215 - Extra recording controls e.g. snapshots + QTabWidget::West @@ -80,20 +86,23 @@ - Recording + Video - + - + QLayout::SetDefaultConstraint + + 5 + - + 0 @@ -103,7 +112,7 @@ true - + 0 0 @@ -114,6 +123,12 @@ 20 + + + 1 + 1 + + Check if new cameras have been connected to the system @@ -144,402 +159,611 @@ - + - - - Qt::Horizontal - - - - - - - - - - 0 - 0 - - - - Select folder where all data is organized - - - Save folder - - - - - - - Folder where all data is stored - - - false - - - Base folder where all data is stored - - - - - - - - - - - - 0 - 0 - - - - File name - - - File Name - - - - - - - Prefix appended to the beginning each file stored - - - - - - file1, file2, ... - - - - - - - + - + 0 0 - - Press to start / stop recording - - - false - - - - - - Record video + + + 0 + 0 + - - - resources/play-button.pngresources/play-button.png + + QFrame::NoFrame - - true + + 0 + + + + 0 + 0 + 513 + 247 + + + + Video file management + + + + + + QLayout::SetDefaultConstraint + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + 0 + 0 + + + + Select folder where all data is organized + + + Save folder + + + + + + + Folder where all data is stored + + + + + + false + + + + + + + + + + + 0 + 0 + + + + File name + + + File Name + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + File name used to store the video data + + + + + + + + + + + + + Skip frames + + + + + + + + 0 + 0 + + + + Time to wait between consecutive recording frames + + + 5 + + + 1000 + + + + + + + + + + 0 + 0 + + + + Press to start / stop recording + + + false + + + + + + Record video + + + + resources/play-button.pngresources/play-button.png + + + true + + + + + + + true + + + Record dark reference images + + + Record dark reference + + + + + + + true + + + Record white reference images + + + Record white reference + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + 0 + 0 + 513 + 121 + + + + Exposure + + + + + + + + + 0 + 0 + + + + Exposure [ms]: + + + + + + + 25 + + + + + + + Hz + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 5 + + + 500 + + + 40 + + + + + + + + + Select to adjust exposure automatically + + + Autoexposure + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + 0 + 0 + 513 + 185 + + + + Display + + + + + + Select to normalize displayed images + + + Histogram normalization + + + false + + + + + + + + 0 + 0 + + + + Image channel + + + + + + + Select displayed spectral band + + + false + + + 1 + + + 16 + + + 1 + + + 10 + + + Qt::Horizontal + + + false + + + QSlider::TicksBelow + + + 1 + + + + + + + + 0 + 0 + + + + RGB norm factor + + + + + + + Select the normalizing factor for RGB noormalization + + + 1 + + + 30 + + + 1 + + + 5 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + 1 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + 0 + 0 + 513 + 127 + + + + Snapshots + + + + + + + + + + File name + + + + + + + File name used to store snapshot images + + + + + + + + + + + + + + + + + + 0 + 0 + + + + Record specified number of individual images on base folder + + + + + + Record snapshots + + + + resources/play-button.pngresources/play-button.png + + + false + + + + + + + Number of snapshots to record + + + 100 + + + 1 + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + - - - Qt::Horizontal - - + - - - false - - - Record dark reference images + + + Qt::Vertical - - Record dark reference + + + 20 + 40 + - + - + - false - - - Record white reference images - - - Record white reference - - - - - - - Qt::Horizontal + true - - - - - - - - - 0 - 0 - - - - Exposure [ms]: - - - - - - - Integration time in milliseconds used for each image - - - 40 - - - - - - - 25 - - - - - - - Hz - - - - - - - Qt::Vertical - - - - - - - Skip frames: - - - - - - - Time to wait between consecutive recording frames - - - 5 - - - 10000 - - - - - - - - - - - Select to normalize displayed images - - - Normalize - - - false - - - - - - - Select to adjust exposure automatically - - - Autoexposure - - - - - - - Qt::Vertical - - - - - - - - 0 - 0 - - - - Time recording: - - - - - - - - 0 - 0 - - - - - 0 - 60 - - - - Time elapsed while recording - - - QFrame::Raised - - - false - - - 8 - - - QLCDNumber::Dec - - - QLCDNumber::Flat - - - 0.000000000000000 - - - - - - - - + 0 0 - - Select RGB norm: - - - - - - - Select the normalizing factor for RGB noormalization - - - 1 - - - 30 - - - 1 + + + 200 + 200 + - - 5 + + + 16777215 + 16777215 + - - Qt::Horizontal + + Logged messages - - QSlider::TicksBelow + + QAbstractScrollArea::AdjustToContentsOnFirstShow - - 1 + + true - - - - 0 - 0 - - + - Select channel: + Message log - + - Select displayed spectral band - - - false - - - 1 - - - 16 - - - 1 - - - 10 + Write mesage to be logged and hit return key - - Qt::Horizontal - - - false + + - - QSlider::TicksBelow + + - - 1 + + - - - - Qt::Vertical - - - - 20 - 40 - - - - @@ -575,220 +799,62 @@ - - - - 6 - - - QLayout::SetDefaultConstraint - - - - - true - - - - 0 - 0 - - - - - 200 - 0 - - - - - 16777215 - 16777215 - - - - Logged messages - - - QAbstractScrollArea::AdjustToContentsOnFirstShow - - - true - - - - - - - Message log - - - - - - - - - - - - - - - - Write message to be logged - - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - Extra controls like snapshots recordings - + - Extras + Viewer - + - - - QLayout::SetDefaultConstraint - - - - - Extras like snapshots recording controls - - - + - + - - - - 0 - 0 - - - - Record specified number of individual images on base folder - - - - + - Record snapshots - - - - resources/play-button.pngresources/play-button.png - - - false + Select File - + - Number of snapshots to record + Path to .b2nd file - - 999 - - - 1 + + - + + + false + + + Image index + Qt::Horizontal - - - - - Sub folder - - - - - - - true - - - Sub folder where extra recordings (e.g. snapshots) will be stored - - - - - - Sub folder to store extra recordings like snapshot images - - - - - - - File prefix - - - - - - - File prefix used to prepend to anapshot file names - - - - - - File prefix to append to each stored file - - - - - - - - - Qt::Vertical - - + + - 20 - 40 + 512 + 272 - + @@ -934,6 +1000,16 @@ + + + + Temperature: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + @@ -957,7 +1033,7 @@ - overexp: + Overexp: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -987,7 +1063,7 @@ - underexp: + Underexp: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -1069,6 +1145,53 @@ + + + + Time: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + Time elapsed while recording + + + QFrame::Raised + + + false + + + 8 + + + QLCDNumber::Dec + + + QLCDNumber::Flat + + + 0.000000000000000 + + + @@ -1081,29 +1204,11 @@ QSliderLabeled QSlider -
widgets.h
+
widgets.h
- filePrefixLineEdit - baseFolderButton - baseFolderLineEdit - recordButton - darkCorrectionButton - whiteBalanceButton - exposureLineEdit - skipFramesSpinBox - normalizeCheckbox - autoexposureCheckbox - rgbNormSlider - bandSlider exposureSlider - logTextEdit - logTextLineEdit - subFolderExtrasLineEdit - filePrefixExtrasLineEdit - snapshotButton - nSnapshotsSpinBox diff --git a/src/util.cpp b/src/util.cpp index 1391cb7..48ca70f 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -21,8 +21,7 @@ FileImage::FileImage(const char *filePath, unsigned int imageHeight, unsigned int imageWidth) { - this->filePath = strdup(filePath); - blosc2_init(); + this->m_filePath = strdup(filePath); blosc2_cparams cparams = BLOSC2_CPARAMS_DEFAULTS; cparams.typesize = sizeof(uint16_t); cparams.compcode = BLOSC_ZSTD; @@ -34,7 +33,7 @@ FileImage::FileImage(const char *filePath, unsigned int imageHeight, unsigned in blosc2_storage storage = BLOSC2_STORAGE_DEFAULTS; storage.contiguous = true; storage.cparams = &cparams; - storage.urlpath = this->filePath; + storage.urlpath = this->m_filePath; // Shape of the ndarray int64_t shape[] = {0, imageHeight, imageWidth}; @@ -42,15 +41,15 @@ FileImage::FileImage(const char *filePath, unsigned int imageHeight, unsigned in int32_t chunk_shape[] = {1, static_cast(imageHeight), static_cast(imageWidth)}; int32_t block_shape[] = {1, static_cast(imageHeight), static_cast(imageWidth)}; - this->ctx = b2nd_create_ctx(&storage, 3, shape, chunk_shape, block_shape, "|u2", DTYPE_NUMPY_FORMAT, nullptr, 0); + this->m_ctx = b2nd_create_ctx(&storage, 3, shape, chunk_shape, block_shape, "|u2", DTYPE_NUMPY_FORMAT, nullptr, 0); int result; - if (access(this->filePath, F_OK) != -1) + if (access(this->m_filePath, F_OK) != -1) { - result = b2nd_open(this->filePath, &src); + result = b2nd_open(this->m_filePath, &m_src); } else { - result = b2nd_empty(this->ctx, &src); + result = b2nd_empty(this->m_ctx, &m_src); } HandleBLOSCResult(result, "b2nd_empty || b2nd_open"); } @@ -58,38 +57,37 @@ FileImage::FileImage(const char *filePath, unsigned int imageHeight, unsigned in FileImage::~FileImage() { // free BLOSC resources - b2nd_free(this->src); - b2nd_free_ctx(this->ctx); - blosc2_destroy(); + b2nd_free(this->m_src); + b2nd_free_ctx(this->m_ctx); } void FileImage::AppendMetadata() { // pack and append metadata - PackAndAppendMetadata(this->src, EXPOSURE_KEY, this->m_exposureMetadata); - PackAndAppendMetadata(this->src, FRAME_NUMBER_KEY, this->m_acqNframeMetadata); - PackAndAppendMetadata(this->src, COLOR_FILTER_ARRAY_FORMAT_KEY, this->m_colorFilterArray); - PackAndAppendMetadata(this->src, TIME_STAMP_KEY, this->m_timeStamp); + PackAndAppendMetadata(this->m_src, EXPOSURE_KEY, this->m_exposureMetadata); + PackAndAppendMetadata(this->m_src, FRAME_NUMBER_KEY, this->m_acqNframeMetadata); + PackAndAppendMetadata(this->m_src, COLOR_FILTER_ARRAY_FORMAT_KEY, this->m_colorFilterArray); + PackAndAppendMetadata(this->m_src, TIME_STAMP_KEY, this->m_timeStamp); for (const QString &key : m_additionalMetadata.keys()) { - PackAndAppendMetadata(this->src, key.toUtf8().constData(), this->m_additionalMetadata[key]); + PackAndAppendMetadata(this->m_src, key.toUtf8().constData(), this->m_additionalMetadata[key]); } LOG_XILENS(info) << "Metadata was written to file"; } -void FileImage::write(XI_IMG image, QMap additionalMetadata) +void FileImage::WriteImageData(XI_IMG image, QMap additionalMetadata) { const size_t buffer_size = static_cast(image.width) * static_cast(image.height) * sizeof(uint16_t); if (buffer_size > static_cast(INT64_MAX)) { throw std::overflow_error("Buffer size exceeds the maximum value of int64_t."); } - int result = b2nd_append(src, image.bp, static_cast(buffer_size), 0); + int result = b2nd_append(m_src, image.bp, static_cast(buffer_size), 0); HandleBLOSCResult(result, "b2nd_append"); // store metadata this->m_exposureMetadata.emplace_back(image.exposure_time_us); this->m_acqNframeMetadata.emplace_back(image.acq_nframe); - this->m_colorFilterArray.emplace_back(colorFilterToString(image.color_filter_array)); + this->m_colorFilterArray.emplace_back(ColorFilterToString(image.color_filter_array)); this->m_timeStamp.emplace_back(GetTimeStamp().toStdString()); for (const QString &key : additionalMetadata.keys()) { @@ -113,7 +111,7 @@ template void PackAndAppendMetadata(b2nd_array_t *src, const char * } } -std::string colorFilterToString(XI_COLOR_FILTER_ARRAY colorFilterArray) +std::string ColorFilterToString(XI_COLOR_FILTER_ARRAY colorFilterArray) { switch (colorFilterArray) { @@ -235,28 +233,11 @@ void AppendBLOSCVLMetadata(b2nd_array_t *src, const char *key, msgpack::sbuffer } } -void rescale(cv::Mat &mat, float high) -{ - double min, max; - cv::minMaxLoc(mat, &min, &max); - mat = (mat - ((float)min)) * high / ((float)max - min); -} - -void clamp(cv::Mat &mat, cv::Range bounds) -{ - cv::min(cv::max(mat, bounds.start), bounds.end, mat); -} - -void wait(int milliseconds) +void WaitMilliseconds(int milliseconds) { boost::this_thread::sleep_for(boost::chrono::milliseconds(milliseconds)); } -void initLogging(enum boost::log::trivial::severity_level severity) -{ - boost::log::core::get()->set_filter(boost::log::trivial::severity >= severity); -} - cv::Mat CreateLut(cv::Vec3b saturation_color, cv::Vec3b dark_color) { cv::Mat Lut(1, 256, CV_8UC3); diff --git a/src/util.h b/src/util.h index baaeb98..d62cbd1 100644 --- a/src/util.h +++ b/src/util.h @@ -88,17 +88,17 @@ class FileImage /** * path to file location */ - char *filePath; + char *m_filePath; /** * Storage context */ - b2nd_context_t *ctx; + b2nd_context_t *m_ctx; /** * Array storage created temporarily for BLOSC */ - b2nd_array_t *src; // New member to store array + b2nd_array_t *m_src; // New member to store array /** * Opens a file and throws runtime error when opening fails @@ -116,7 +116,7 @@ class FileImage * @param image Ximea image where data is stored * @param additionalMetadata Additional metadata to be stored in the array */ - void write(XI_IMG image, QMap additionalMetadata); + void WriteImageData(XI_IMG image, QMap additionalMetadata); /** * Appends metadata to BLOSC ND array. This method should be called before @@ -151,33 +151,13 @@ template void PackAndAppendMetadata(b2nd_array_t *src, const char * * @param colorFilterArray XIMEA color filter array representation * @return string representing the color filter array */ -std::string colorFilterToString(XI_COLOR_FILTER_ARRAY colorFilterArray); - -/** - * Initializes the logging by setting a severity - * @param severity level of logging to set - */ -void initLogging(enum boost::log::trivial::severity_level severity); +std::string ColorFilterToString(XI_COLOR_FILTER_ARRAY colorFilterArray); /** * waits a certain amount of milliseconds on a boost thread - * @param milliseconds amount of time to wait - */ -void wait(int milliseconds); - -/** - * Restricts the values in a matrix to the range defined by bounds - * @param mat matrix of values to restrict - * @param bounds range of values - */ -void clamp(cv::Mat &mat, cv::Range bounds); - -/** - * Rescales values to a range defined by high, lower bound is always 0 - * @param mat matrix values to rescale - * @param high maximum value that defines the range + * @param milliseconds amount of time to WaitMilliseconds */ -void rescale(cv::Mat &mat, float high); +void WaitMilliseconds(int milliseconds); /** * Created a look up table (LUT) that can be used to define the colors of pixels diff --git a/src/widgets.cpp b/src/widgets.cpp index 249065b..708b7aa 100644 --- a/src/widgets.cpp +++ b/src/widgets.cpp @@ -17,7 +17,7 @@ QSliderLabeled::QSliderLabeled(QWidget *parent) : QSlider(parent) { } -void QSliderLabeled::applyStyleSheet() +void QSliderLabeled::ApplyStyleSheet() { QFontMetrics fm(font()); QString maxLabel = QString::number(maximum()); @@ -106,23 +106,23 @@ void QSliderLabeled::mouseMoveEvent(QMouseEvent *event) QSlider::mouseMoveEvent(event); } -void QSliderLabeled::updatePainterPen() +void QSliderLabeled::UpdatePainterPen() { QColor penColor = isEnabled() ? QColor(255, 215, 64) : QColor(79, 91, 98); m_penColor = penColor; } -void QSliderLabeled::setGrooveMargin(int value) +void QSliderLabeled::SetGrooveMargin(int value) { m_grooveMargin = value; } -void QSliderLabeled::setMaxNumberOfLabels(int value) +void QSliderLabeled::SetMaxNumberOfLabels(int value) { m_maxNumberOfLabels = value; } -void QSliderLabeled::setSliderSpread(int value) +void QSliderLabeled::SetSliderSpread(int value) { m_sliderSpread = value; } diff --git a/src/widgets.h b/src/widgets.h index fa17ece..b074523 100644 --- a/src/widgets.h +++ b/src/widgets.h @@ -36,20 +36,20 @@ class QSliderLabeled : public QSlider * * @param value */ - void setGrooveMargin(int value); + void SetGrooveMargin(int value); /** * Sets Maximum number of labels to display in the slider. * * @param value maximum number of labels. */ - void setMaxNumberOfLabels(int value); + void SetMaxNumberOfLabels(int value); /** * Applies a custom style sheet that defines the width and height of the slider based on the orientation * of the slider. */ - void applyStyleSheet(); + void ApplyStyleSheet(); /** * Sets the maximum spread of the slider. This will represent the maximum height when slider is horizontal and @@ -57,7 +57,7 @@ class QSliderLabeled : public QSlider * * @param value the slider spread. */ - void setSliderSpread(int value); + void SetSliderSpread(int value); protected: /** @@ -77,14 +77,14 @@ class QSliderLabeled : public QSlider void showEvent(QShowEvent *event) override { QSlider::showEvent(event); - applyStyleSheet(); + ApplyStyleSheet(); } /** * @brief Overrides the event() method from the parent class. * * This method is triggered when an event is received by the widget. It specifically handles the - * `QEvent::EnabledChange` event and calls the `updatePainterPen()` method to update the painter pen. It then calls + * `QEvent::EnabledChange` event and calls the `UpdatePainterPen()` method to update the painter pen. It then calls * the event() method of the parent class to handle any other events. Finally, it returns a boolean value indicating * whether the event was handled. * @@ -95,7 +95,7 @@ class QSliderLabeled : public QSlider { if (e->type() == QEvent::EnabledChange) { - updatePainterPen(); + UpdatePainterPen(); } return QSlider::event(e); } @@ -135,7 +135,7 @@ class QSliderLabeled : public QSlider * Updates the painter's pen color based on the enabled state of the QSliderLabeled widget. * The pen color is set to a specific color if the widget is enabled, and to a different color if it is disabled. */ - void updatePainterPen(); + void UpdatePainterPen(); }; #endif // XILENS_WIDGETS_H diff --git a/tests/cameraInterfaceTest.cpp b/tests/cameraInterfaceTest.cpp index 692436b..f166a9c 100644 --- a/tests/cameraInterfaceTest.cpp +++ b/tests/cameraInterfaceTest.cpp @@ -36,7 +36,7 @@ TEST(CameraInterfaceTest, StartAcquisition_InvalidHandle) std::shared_ptr apiWrapper = std::make_shared(); CameraInterface cameraInterface; cameraInterface.m_apiWrapper = apiWrapper; - cameraInterface.setCamera(CAMERA_TYPE_SPECTRAL, CAMERA_FAMILY_XISPEC); + cameraInterface.SetCamera(CAMERA_TYPE_SPECTRAL, CAMERA_FAMILY_XISPEC); QString cameraIdentifier = "MockDeviceModel@MockSensorSN"; cameraInterface.m_availableCameras[cameraIdentifier] = 0; @@ -50,7 +50,7 @@ TEST(CameraInterfaceTest, StartAcquisition_StartSuccess) HANDLE cameraHandle; cameraInterface.m_cameraHandle = cameraHandle; cameraInterface.m_apiWrapper = apiWrapper; - cameraInterface.setCamera(CAMERA_TYPE_SPECTRAL, CAMERA_FAMILY_XISPEC); + cameraInterface.SetCamera(CAMERA_TYPE_SPECTRAL, CAMERA_FAMILY_XISPEC); QString cameraIdentifier = "MockDeviceModel@MockSensorSN"; cameraInterface.m_availableCameras[cameraIdentifier] = 0; diff --git a/tests/utilTest.cpp b/tests/utilTest.cpp index 4d0e1a8..37349b3 100644 --- a/tests/utilTest.cpp +++ b/tests/utilTest.cpp @@ -46,18 +46,6 @@ TEST(CreateLutTest, VerifyLutColorValues) } } -TEST(RescaleTest, CheckValuesAfterRescaling) -{ - cv::Mat mat = (cv::Mat_(3, 3) << 0.5, 1.2, 2.4, 3.2, 5.1, 6.3, 10.0, 20.0, 15.0); - rescale(mat, 100.0); - double min, max; - cv::minMaxLoc(mat, &min, &max); - EXPECT_GE(max, 0); - EXPECT_LE(max, 100); - EXPECT_GE(100, min); - EXPECT_LE(0, min); -} - TEST(XIIMGtoMatTest, MatDimensionsEqualToXIIMG) { XI_IMG xi_img; @@ -91,13 +79,15 @@ TEST_F(FileImageWriteTest, CheckContentsAfterWriting) xiImage.bp = malloc(static_cast(xiImage.width) * static_cast(xiImage.height) * sizeof(uint16_t)); std::fill_n((uint16_t *)xiImage.bp, xiImage.width * xiImage.height, 12345); const char *urlpath = strdup("test_image.b2nd"); + + blosc2_init(); blosc2_remove_urlpath(urlpath); FileImage fileImage(urlpath, xiImage.height, xiImage.width); QMap additionalMetadata = {{"extraMetadata", 1.0}}; for (int i = 0; i < nrImages; i++) { - fileImage.write(xiImage, additionalMetadata); + fileImage.WriteImageData(xiImage, additionalMetadata); } fileImage.AppendMetadata(); @@ -235,6 +225,7 @@ TEST_F(FileImageWriteTest, CheckContentsAfterWriting) } free(names); blosc2_remove_urlpath(urlpath); + blosc2_destroy(); } TEST_F(FileImageWriteTest, AppendMetadataTwice) @@ -247,20 +238,23 @@ TEST_F(FileImageWriteTest, AppendMetadataTwice) xiImage.bp = malloc(static_cast(xiImage.width) * static_cast(xiImage.height) * sizeof(uint16_t)); std::fill_n((uint16_t *)xiImage.bp, xiImage.width * xiImage.height, 12345); const char *urlpath = strdup("test_image.b2nd"); + + blosc2_init(); blosc2_remove_urlpath(urlpath); FileImage fileImage(urlpath, xiImage.height, xiImage.width); QMap additionalMetadata = {{"extraMetadata", 1.0}}; for (int i = 0; i < nrImages; i++) { - fileImage.write(xiImage, additionalMetadata); + fileImage.WriteImageData(xiImage, additionalMetadata); } fileImage.AppendMetadata(); // append more images to the file for (int i = 0; i < nrImages; i++) { - fileImage.write(xiImage, additionalMetadata); + fileImage.WriteImageData(xiImage, additionalMetadata); } fileImage.AppendMetadata(); + blosc2_destroy(); }