Commit 9efbc6b11e5caff3e72ed01e98838f591ffabda2

Authored by jablonskiba
0 parents

Initialise the repository

Showing 44 changed files with 2354 additions and 0 deletions
.gitignore 0 → 100644
  1 +++ a/.gitignore
  1 +# Prerequisites
  2 +*.d
  3 +
  4 +# Compiled Object files
  5 +*.slo
  6 +*.lo
  7 +*.o
  8 +*.obj
  9 +
  10 +# Precompiled Headers
  11 +*.gch
  12 +*.pch
  13 +
  14 +# Compiled Dynamic libraries
  15 +*.so
  16 +*.dylib
  17 +*.dll
  18 +
  19 +# Fortran module files
  20 +*.mod
  21 +*.smod
  22 +
  23 +# Compiled Static libraries
  24 +*.lai
  25 +*.la
  26 +*.a
  27 +*.lib
  28 +
  29 +# Executables
  30 +*.exe
  31 +*.out
  32 +*.app
  33 +
  34 +.metadata
  35 +bin/
  36 +tmp/
  37 +*.tmp
  38 +*.bak
  39 +*.swp
  40 +*~.nib
  41 +local.properties
  42 +.settings/
  43 +.loadpath
  44 +.recommenders
  45 +
  46 +# External tool builders
  47 +.externalToolBuilders/
  48 +
  49 +# Locally stored "Eclipse launch configurations"
  50 +*.launch
  51 +
  52 +# PyDev specific (Python IDE for Eclipse)
  53 +*.pydevproject
  54 +
  55 +# CDT-specific (C/C++ Development Tooling)
  56 +.cproject
  57 +
  58 +# CDT- autotools
  59 +.autotools
  60 +
  61 +# Java annotation processor (APT)
  62 +.factorypath
  63 +
  64 +# PDT-specific (PHP Development Tools)
  65 +.buildpath
  66 +
  67 +# sbteclipse plugin
  68 +.target
  69 +
  70 +# Tern plugin
  71 +.tern-project
  72 +
  73 +# TeXlipse plugin
  74 +.texlipse
  75 +
  76 +# STS (Spring Tool Suite)
  77 +.springBeans
  78 +
  79 +# Code Recommenders
  80 +.recommenders/
  81 +
  82 +# Annotation Processing
  83 +.apt_generated/
  84 +.apt_generated_test/
  85 +
  86 +# Scala IDE specific (Scala & Java development for Eclipse)
  87 +.cache-main
  88 +.scala_dependencies
  89 +.worksheet
  90 +
  91 +# Uncomment this line if you wish to ignore the project description file.
  92 +# Typically, this file would be tracked if it contains build/dependency configurations:
  93 +#.project
  94 +
  95 +build/
  96 +bin/
  97 +.**/
  98 +.ptp*
... ...
CMakeLists.txt 0 → 100644
  1 +++ a/CMakeLists.txt
  1 +cmake_minimum_required(VERSION 3.18)
  2 +project(plasma-processing LANGUAGES CUDA CXX)
  3 +
  4 +# Link this 'library' to set the c++ standard / compile-time options requested
  5 +add_library(project_options INTERFACE)
  6 +target_compile_features(project_options INTERFACE cxx_std_17)
  7 +
  8 +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin)
  9 +
  10 +add_subdirectory(src)
0 11 \ No newline at end of file
... ...
README.md 0 → 100644
  1 +++ a/README.md
  1 +# Implementation of Thermal Event Image Processing Algorithms on Nvidia Tegra Embedded System on a Chip
  2 +
  3 +For details see Implementation of Thermal Event Image Processing Algorithms on Nvidia Tegra Embedded System on a Chip by Bartlomiej Jablonski, Dariusz Makowski, and Piotr Perek.
  4 +
  5 +## Dependencies
  6 +- `C++17`
  7 +- `CMake 3.18`
  8 +- `CUDA 10.2`
  9 +- `CUDAToolkit 10.2.89`
  10 +- `HDF5 1.10.0.1`
  11 +- `OpenCV 4.4.0` (requires compilation with `CUDA` support)
... ...
debug_build.sh 0 → 100755
  1 +++ a/debug_build.sh
  1 +#!/bin/sh
  2 +mkdir -p build/debug
  3 +cd build/debug
  4 +cmake -DCMAKE_BUILD_TYPE=Debug \
  5 + ../..
  6 +make
0 7 \ No newline at end of file
... ...
release_build.sh 0 → 100755
  1 +++ a/release_build.sh
  1 +#!/bin/sh
  2 +mkdir -p build/release
  3 +cd build/release
  4 +cmake -DCMAKE_BUILD_TYPE=Release \
  5 + ../..
  6 +make
0 7 \ No newline at end of file
... ...
src/CMakeLists.txt 0 → 100644
  1 +++ a/src/CMakeLists.txt
  1 +# OpenCV
  2 +find_package(OpenCV REQUIRED)
  3 +
  4 +# NPP
  5 +find_package(CUDAToolkit REQUIRED COMPONENTS nppif cudart)
  6 +
  7 +# HDF5
  8 +find_package(HDF5 REQUIRED COMPONENTS CXX)
  9 +
  10 +# Threads
  11 +find_package(Threads REQUIRED)
  12 +
  13 +# Project files
  14 +file(GLOB_RECURSE source_files CONFIGURE_DEPENDS *.cpp)
  15 +file(GLOB_RECURSE header_files CONFIGURE_DEPENDS *.hpp)
  16 +file(GLOB_RECURSE cuda_files CONFIGURE_DEPENDS *.cu)
  17 +
  18 +add_executable(executable ${source_files} ${header_files} ${cuda_files})
  19 +
  20 +# CUDA compute architectures
  21 +set_property(TARGET executable PROPERTY CUDA_ARCHITECTURES 62) # 62 - Jetson TX2
  22 +set_property(TARGET executable PROPERTY CUDA_STANDARD 14)
  23 +
  24 +# Allows to use absolute includes in regard to source directory
  25 +target_include_directories(executable PUBLIC ${PROJECT_SOURCE_DIR}/src ${HDF5_INCLUDE_DIRS})
  26 +
  27 +target_link_libraries(executable PUBLIC
  28 + project_options
  29 + CUDA::cudart
  30 + CUDA::nppif
  31 + ${OpenCV_LIBS}
  32 + PRIVATE
  33 + ${HDF5_CXX_LIBRARIES}
  34 + Threads::Threads)
0 35 \ No newline at end of file
... ...
src/algorithm/Algorithm.hpp 0 → 100644
  1 +++ a/src/algorithm/Algorithm.hpp
  1 +#pragma once
  2 +
  3 +#include <opencv2/core/mat.hpp>
  4 +
  5 +#include "data/hdf5/CVMatLoader.hpp"
  6 +
  7 +class Algorithm {
  8 + public:
  9 + Algorithm() = default;
  10 + Algorithm(const Algorithm &) = default;
  11 + Algorithm(Algorithm &&) noexcept = default;
  12 + Algorithm &operator=(Algorithm &&) noexcept = default;
  13 + Algorithm &operator=(const Algorithm &) = default;
  14 +
  15 + virtual void setup(const cv::Size &frameSize,
  16 + const CVMatLoader &loader) = 0;
  17 +
  18 + virtual void handleFrame(const cv::Mat &hostFrame,
  19 + unsigned long timestamp) = 0;
  20 +
  21 + virtual ~Algorithm() = default;
  22 +};
... ...
src/algorithm/common/PfcSegmenter.cpp 0 → 100644
  1 +++ a/src/algorithm/common/PfcSegmenter.cpp
  1 +#include "PfcSegmenter.hpp"
  2 +
  3 +const std::array<PfcSegmenter::Mapping, 5> PfcSegmenter::mappings{
  4 + Mapping(400, cv::Range(400'000'000, 600'000'000), "Baffles"),
  5 + Mapping(1800, cv::Range(1'000'000'000, 1'200'000'000), "Divertors"),
  6 + Mapping(200, cv::Range(1'300'000'000, 1'400'000'000),
  7 + "Wall Panels & Poloidal Closure"),
  8 + Mapping(200, cv::Range(1'500'000'000, 1'700'000'000), "Pumping Gap"),
  9 + Mapping(400, cv::Range(2'000'000'000, 2'147'483'647), "Wall Heat Shields")};
0 10 \ No newline at end of file
... ...
src/algorithm/common/PfcSegmenter.hpp 0 → 100644
  1 +++ a/src/algorithm/common/PfcSegmenter.hpp
  1 +#pragma once
  2 +
  3 +#include <array>
  4 +#include <functional>
  5 +
  6 +#include <opencv2/imgproc.hpp>
  7 +
  8 +#include "data/hdf5/CVMatLoader.hpp"
  9 +
  10 +class PfcSegmenter {
  11 + public:
  12 + PfcSegmenter(const CVMatLoader &loader, const cv::Size &size) : size(size) {
  13 + hostPfc = loader.asMat("/scene_model/PFC", CV_32F);
  14 + }
  15 +
  16 + cv::Mat segment() const {
  17 + return process(
  18 + CV_16UC1, [](const Mapping &mapping, cv::Mat &component) {
  19 + component.convertTo(component, CV_16UC1, (1L << 15) - 1);
  20 + return component & mapping.value;
  21 + });
  22 + }
  23 +
  24 + void contour(std::vector<std::vector<std::vector<cv::Point>>> &contours,
  25 + cv::Mat &components) const {
  26 + int i = 1;
  27 + components =
  28 + process(CV_8UC1, [&i, &contours](const Mapping & /*mapping*/,
  29 + cv::Mat &component) {
  30 + cv::threshold(component, component, 0, i++, cv::THRESH_BINARY);
  31 +
  32 + std::vector<std::vector<cv::Point>> contour;
  33 + cv::findContours(component, contour, cv::RETR_TREE,
  34 + cv::CHAIN_APPROX_SIMPLE);
  35 +
  36 + contours.emplace_back(std::move(contour));
  37 +
  38 + return component;
  39 + });
  40 + }
  41 +
  42 + cv::Mat mask(const std::string &label) const {
  43 + return process(CV_8UC1, [&label](const Mapping &mapping,
  44 + cv::Mat &component) {
  45 + if (label == mapping.label) {
  46 + return component;
  47 + }
  48 +
  49 + return cv::Mat{cv::Mat::zeros(component.size(), component.type())};
  50 + });
  51 + }
  52 +
  53 + private:
  54 + const cv::Size size;
  55 + cv::Mat hostPfc;
  56 +
  57 + class Mapping {
  58 + public:
  59 + Mapping(int value, const cv::Range &range,
  60 + const std::string &label = "")
  61 + : value(value), range(range), label(label) {}
  62 +
  63 + const int value;
  64 + const cv::Range range;
  65 + const std::string label;
  66 + };
  67 +
  68 + static const std::array<Mapping, 5> mappings;
  69 +
  70 + cv::Mat process(int outputType,
  71 + const std::function<cv::Mat(const Mapping &, cv::Mat &)>
  72 + &modifyMask) const {
  73 + cv::Mat output(size, outputType, cv::Scalar(0));
  74 +
  75 + const cv::Mat kernel =
  76 + cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 1));
  77 +
  78 + cv::Mat component;
  79 + for (const auto &mapping : mappings) {
  80 + /* Workaround as the image is unsigned 32-bit thus it allows values
  81 + * above the signed limit. It is assumed that values greater than
  82 + * the signed maximum belongs to component, which end boundary is
  83 + * at the limit
  84 + */
  85 + /* Threshold PFC identifier range */
  86 + const auto upperLimit = 2'147'483'647;
  87 + if (mapping.range.end >= upperLimit) {
  88 + component = hostPfc >= upperLimit;
  89 + } else {
  90 + cv::inRange(hostPfc, mapping.range.start, mapping.range.end,
  91 + component);
  92 + }
  93 + /* Apply close morphology */
  94 + cv::morphologyEx(component, component, cv::MORPH_CLOSE, kernel);
  95 + /* Apply median filter */
  96 + cv::medianBlur(component, component, 3);
  97 +
  98 + /* Set pixels to operational limit value */
  99 + output = output | modifyMask(mapping, component);
  100 + }
  101 +
  102 + /* Segmented PFC limits mask */
  103 + return output;
  104 + }
  105 +};
... ...
src/algorithm/common/SurfaceMap.hpp 0 → 100644
  1 +++ a/src/algorithm/common/SurfaceMap.hpp
  1 +#pragma once
  2 +
  3 +#include "data/ResourceLocator.hpp"
  4 +#include "data/hdf5/CVMatLoader.hpp"
  5 +
  6 +class SurfaceMap {
  7 + public:
  8 + explicit SurfaceMap(const cv::Size &size) : surface(buildSurface(size)) {}
  9 +
  10 + cv::Point3f at(int column, int row) const {
  11 + return surface.at<cv::Point3f>(row, column);
  12 + }
  13 +
  14 + cv::Point3f at(const cv::Point &point) const {
  15 + return at(point.x, point.y);
  16 + }
  17 +
  18 + std::vector<cv::Point3f> at(const std::vector<cv::Point> &points) const {
  19 + std::vector<cv::Point3f> surfacePoints(points.size());
  20 +
  21 + std::transform(points.begin(), points.end(), surfacePoints.begin(),
  22 + [&](const cv::Point &point) { return at(point); });
  23 +
  24 + return surfacePoints;
  25 + }
  26 +
  27 + private:
  28 + const cv::Mat surface;
  29 +
  30 + static cv::Mat buildSurface(const cv::Size &size) {
  31 + cv::Mat result(size, CV_32FC3);
  32 +
  33 + const CVMatLoader loader(
  34 + ResourceLocator::getPathProvider().path("20171114.053_AEF20.h5"));
  35 + const std::vector<cv::Mat> coordinates = {
  36 + loader.asMat("/scene_model/x", CV_32F),
  37 + loader.asMat("/scene_model/y", CV_32F),
  38 + loader.asMat("/scene_model/z", CV_32F),
  39 + };
  40 +
  41 + cv::merge(coordinates, result);
  42 + return result;
  43 + }
  44 +};
... ...
src/algorithm/cpu/common/ClusterCorrespondence.hpp 0 → 100644
  1 +++ a/src/algorithm/cpu/common/ClusterCorrespondence.hpp
  1 +/* Analysis of the outer divertor hot spot activity in the protection video
  2 +camera recordings at JET */
  3 +#pragma once
  4 +
  5 +#include <opencv2/imgproc.hpp>
  6 +
  7 +namespace Cpu {
  8 +
  9 +class ClusterCorrespondence {
  10 + public:
  11 + ClusterCorrespondence(float minOverlap, float maxOversize)
  12 + : minOverlap(minOverlap), maxOversize(maxOversize) {}
  13 +
  14 + bool corresponds(const cv::Mat &first, const cv::Mat &second) {
  15 + cv::bitwise_and(first, second, overlap);
  16 + const auto firstArea = cv::countNonZero(first),
  17 + secondArea = cv::countNonZero(second),
  18 + overlapArea = cv::countNonZero(overlap);
  19 +
  20 + return isOverlapping(firstArea, secondArea, overlapArea) &&
  21 + isSimilar(firstArea, secondArea);
  22 + }
  23 +
  24 + private:
  25 + float minOverlap;
  26 + float maxOversize;
  27 +
  28 + bool isOverlapping(int firstArea, int secondArea, int overlapArea) const {
  29 + return overlapArea >= minOverlap * std::min(firstArea, secondArea);
  30 + }
  31 +
  32 + bool isSimilar(int firstArea, int secondArea) const {
  33 + return (firstArea / static_cast<float>(secondArea) >=
  34 + 1.F / maxOversize) &&
  35 + (firstArea / static_cast<float>(secondArea) <= maxOversize);
  36 + }
  37 +
  38 + /* Buffors */
  39 + cv::Mat overlap;
  40 +};
  41 +
  42 +} // namespace Cpu
0 43 \ No newline at end of file
... ...
src/algorithm/cpu/hotspot/Hotspot.cpp 0 → 100644
  1 +++ a/src/algorithm/cpu/hotspot/Hotspot.cpp
  1 +#include "Hotspot.hpp"
  2 +
  3 +#include <opencv2/imgproc.hpp>
  4 +
  5 +Cpu::Hotspot::Hotspot(const cv::Mat &temperature, unsigned long timestamp,
  6 + int component, const Contour &contour,
  7 + const Surface &surface)
  8 + : Hotspot(temperature, timestamp, component, contour, surface,
  9 + createMask(temperature.size(), contour)) {}
  10 +
  11 +Cpu::Hotspot::Hotspot(const cv::Mat &temperature, unsigned long timestamp,
  12 + int component, const Contour &contour,
  13 + const Surface &surface, const cv::Mat &mask)
  14 + : timestamp(timestamp), component(component), contour(contour), mask(mask),
  15 + /* Compute hotspot attributes */
  16 + meanTemperature(calculateMean(temperature)),
  17 + maxTemperature(calculateMax(temperature)), size(calculateSize()),
  18 + surface(surface) {}
  19 +
  20 +double Cpu::Hotspot::perimeter() const {
  21 + double perimeter = 0;
  22 +
  23 + for (std::size_t i = 0, size = surface.size(); i < size; ++i) {
  24 + perimeter += cv::norm(surface[i] - surface[(i + 1) % size]);
  25 + }
  26 +
  27 + return perimeter;
  28 +}
  29 +
  30 +int Cpu::Hotspot::calculateSize() const { return cv::countNonZero(mask); }
  31 +
  32 +double Cpu::Hotspot::calculateMean(const cv::Mat &temperature) const {
  33 + return cv::mean(temperature, mask)[0];
  34 +}
  35 +
  36 +double Cpu::Hotspot::calculateMax(const cv::Mat &temperature) const {
  37 + double maxValue{-1};
  38 + cv::minMaxIdx(temperature, nullptr, &maxValue, nullptr, nullptr, mask);
  39 + return maxValue;
  40 +}
  41 +
  42 +cv::Mat Cpu::Hotspot::createMask(const cv::Size &size, const Contour &contour) {
  43 + cv::Mat result(cv::Mat::zeros(size, CV_8UC1));
  44 + cv::drawContours(result, std::vector<Contour>{contour}, -1, 255,
  45 + cv::FILLED);
  46 +
  47 + return result;
  48 +}
  49 +
  50 +Cpu::PersistentHotspot::PersistentHotspot(const Hotspot &initial)
  51 + : mask(initial.mask), component(initial.component) {
  52 + append(initial);
  53 +}
  54 +
  55 +void Cpu::PersistentHotspot::merge(const Hotspot &other) {
  56 + cv::bitwise_or(mask, other.mask, mask);
  57 + append(other);
  58 +}
  59 +
  60 +int Cpu::PersistentHotspot::area() const { return cv::countNonZero(mask); }
  61 +
  62 +void Cpu::PersistentHotspot::append(const Hotspot &other) {
  63 + timestamps.push_back(other.timestamp);
  64 + meanTemperatures.push_back(other.meanTemperature);
  65 + maxTemperatures.push_back(other.maxTemperature);
  66 +}
0 67 \ No newline at end of file
... ...
src/algorithm/cpu/hotspot/Hotspot.hpp 0 → 100644
  1 +++ a/src/algorithm/cpu/hotspot/Hotspot.hpp
  1 +#pragma once
  2 +
  3 +#include <vector>
  4 +
  5 +#include <opencv2/core/mat.hpp>
  6 +#include <opencv2/imgproc.hpp>
  7 +
  8 +namespace Cpu {
  9 +
  10 +class Hotspot {
  11 + public:
  12 + using Contour = std::vector<cv::Point>;
  13 + using Surface = std::vector<cv::Point3f>;
  14 +
  15 + const unsigned long timestamp;
  16 + const int component;
  17 + const Contour contour;
  18 +
  19 + const cv::Mat mask;
  20 +
  21 + const double meanTemperature, maxTemperature;
  22 + const int size;
  23 +
  24 + Hotspot(const cv::Mat &temperature, unsigned long timestamp, int component,
  25 + const Contour &contour, const Surface &surface);
  26 +
  27 + double perimeter() const;
  28 +
  29 + private:
  30 + const Surface surface;
  31 +
  32 + Hotspot(const cv::Mat &temperature, unsigned long timestamp, int component,
  33 + const Contour &contour, const Surface &surface,
  34 + const cv::Mat &mask);
  35 +
  36 + int calculateSize() const;
  37 + double calculateMean(const cv::Mat &temperature) const;
  38 + double calculateMax(const cv::Mat &temperature) const;
  39 +
  40 + static cv::Mat createMask(const cv::Size &size, const Contour &contour);
  41 +};
  42 +
  43 +class PersistentHotspot {
  44 + public:
  45 + explicit PersistentHotspot(const Hotspot &initial);
  46 +
  47 + void merge(const Hotspot &other);
  48 + int area() const;
  49 +
  50 + cv::Mat mask;
  51 + int component;
  52 + std::vector<unsigned long> timestamps;
  53 + std::vector<double> meanTemperatures;
  54 + std::vector<double> maxTemperatures;
  55 +
  56 + private:
  57 + void append(const Hotspot &other);
  58 +};
  59 +
  60 +} // namespace Cpu
0 61 \ No newline at end of file
... ...
src/algorithm/cpu/hotspot/OverloadHotspotDetectionAlgorithm.cpp 0 → 100644
  1 +++ a/src/algorithm/cpu/hotspot/OverloadHotspotDetectionAlgorithm.cpp
  1 +#include "OverloadHotspotDetectionAlgorithm.hpp"
  2 +
  3 +#include <algorithm>
  4 +#include <opencv2/imgproc.hpp>
  5 +
  6 +void Cpu::OverloadHotspotDetectionAlgorithm::setup(const cv::Size &frameSize,
  7 + const CVMatLoader &loader) {
  8 + currentFrameSize = frameSize;
  9 +
  10 + hostFov = loader.asMat("/scene_model/FOV", CV_8UC1);
  11 +
  12 + PfcSegmenter segmenter(loader, frameSize);
  13 + hostPfc = segmenter.segment();
  14 +
  15 + cv::Mat zerosMask;
  16 + cv::compare(hostPfc, 0, zerosMask, cv::CMP_EQ);
  17 + hostPfc.setTo(MAX_TEMPERATURE, zerosMask);
  18 +
  19 + cv::Mat model;
  20 + cv::cvtColor(loader.asMat("/scene_model/CAD", CV_8UC1), model,
  21 + cv::COLOR_GRAY2BGR);
  22 +
  23 + std::vector<Contours> pfcContours;
  24 + segmenter.contour(pfcContours, hostPfcMask);
  25 +
  26 + surfaceMap = std::make_unique<const SurfaceMap>(frameSize);
  27 +}
  28 +
  29 +void Cpu::OverloadHotspotDetectionAlgorithm::handleFrame(
  30 + const cv::Mat &sourceFrame, unsigned long timestamp) {
  31 + hostFrame = sourceFrame;
  32 +
  33 + /* Apply field-of-view mask */
  34 + cv::bitwise_and(hostFrame, hostFrame, hostInput, hostFov);
  35 + /* Convert Kelvin to Celsius */
  36 + cv::subtract(hostInput, KELVINS, hostTemperature);
  37 +
  38 + /* Apply median filter */
  39 + medianBlur(hostTemperature, hostInput, 3);
  40 +
  41 + /* Threshold overheating pixels */
  42 + cv::compare(hostInput, hostPfc, overheating, cv::CMP_GT);
  43 +
  44 + currentHotspots.clear();
  45 + identifyHotspots(timestamp);
  46 +}
  47 +
  48 +void Cpu::OverloadHotspotDetectionAlgorithm::identifyHotspots(
  49 + unsigned long timestamp) {
  50 + /* Analyse topologial structure */
  51 + Contours hotspotContours;
  52 + cv::findContours(overheating, hotspotContours, cv::RETR_TREE,
  53 + cv::CHAIN_APPROX_NONE);
  54 +
  55 + for (size_t i = 0, size = hotspotContours.size();
  56 + i < size && i < blobsLimit; ++i) {
  57 + const auto &contour = hotspotContours[i];
  58 + /* Discard blobs below minimum area */
  59 + if (contour.size() >= pixelsThreshold) {
  60 + const int component = hostPfcMask.at<uchar>(contour[0]);
  61 + assert(component > 0);
  62 +
  63 + Cpu::Hotspot hotspot(hostTemperature, timestamp, component, contour,
  64 + surfaceMap->at(contour));
  65 +
  66 + /* Find corresponding blobs */
  67 + matchHotspot(hotspot);
  68 + currentHotspots.emplace_back(std::move(hotspot));
  69 + }
  70 + }
  71 +}
  72 +
  73 +void Cpu::OverloadHotspotDetectionAlgorithm::matchHotspot(
  74 + const Cpu::Hotspot &hotspot) {
  75 + for (auto &persistentHotspot : uniqueHotspots) {
  76 + if (clusterCorrespondence.corresponds(persistentHotspot.mask,
  77 + hotspot.mask)) {
  78 + /* Merge matching blobs */
  79 + persistentHotspot.merge(hotspot);
  80 + return;
  81 + }
  82 + }
  83 + uniqueHotspots.emplace_back(hotspot);
  84 +}
... ...
src/algorithm/cpu/hotspot/OverloadHotspotDetectionAlgorithm.hpp 0 → 100644
  1 +++ a/src/algorithm/cpu/hotspot/OverloadHotspotDetectionAlgorithm.hpp
  1 +#pragma once
  2 +
  3 +#include <opencv2/core/mat.hpp>
  4 +
  5 +#include "algorithm/Algorithm.hpp"
  6 +
  7 +#include "algorithm/cpu/common/ClusterCorrespondence.hpp"
  8 +#include "algorithm/cpu/hotspot/Hotspot.hpp"
  9 +
  10 +#include "algorithm/common/PfcSegmenter.hpp"
  11 +#include "algorithm/common/SurfaceMap.hpp"
  12 +
  13 +namespace Cpu {
  14 +
  15 +class OverloadHotspotDetectionAlgorithm : public Algorithm {
  16 + public:
  17 + /* Configurable parameters */
  18 + std::size_t blobsLimit{5};
  19 + std::size_t pixelsThreshold{3};
  20 + /* Configurable parameters */
  21 +
  22 + OverloadHotspotDetectionAlgorithm() = default;
  23 +
  24 + void setup(const cv::Size &frameSize, const CVMatLoader &loader) override;
  25 + void handleFrame(const cv::Mat &sourceFrame,
  26 + unsigned long timestamp) override;
  27 +
  28 + std::vector<Cpu::PersistentHotspot> uniqueHotspots; /* Set of persistent hotspots */
  29 +
  30 + private:
  31 + using Contours = std::vector<std::vector<cv::Point>>;
  32 +
  33 + static constexpr int KELVINS{273};
  34 + static constexpr int MAX_TEMPERATURE{(1L << 15) - 1};
  35 +
  36 + cv::Size currentFrameSize;
  37 +
  38 + Cpu::ClusterCorrespondence clusterCorrespondence{.8, 2.};
  39 + std::unique_ptr<const SurfaceMap> surfaceMap;
  40 +
  41 + cv::Mat hostFov, hostModel, hostPfc;
  42 + cv::Mat hostPfcMask;
  43 +
  44 + std::vector<Cpu::Hotspot> currentHotspots;
  45 +
  46 + void identifyHotspots(unsigned long timestamp);
  47 + void matchHotspot(const Cpu::Hotspot &hotspot);
  48 +
  49 + /* Buffors */
  50 + cv::Mat overheating;
  51 + cv::Mat hostFrame, hostInput, hostTemperature;
  52 + /* Buffors */
  53 +};
  54 +
  55 +} // namespace Cpu
0 56 \ No newline at end of file
... ...
src/algorithm/cpu/surfacelayer/SurfaceLayerDetectionAlgorithm.cpp 0 → 100644
  1 +++ a/src/algorithm/cpu/surfacelayer/SurfaceLayerDetectionAlgorithm.cpp
  1 +#include "SurfaceLayerDetectionAlgorithm.hpp"
  2 +
  3 +#include <opencv2/imgproc.hpp>
  4 +
  5 +#include "algorithm/common/PfcSegmenter.hpp"
  6 +
  7 +cv::Mat
  8 +Cpu::SurfaceLayerDetectionAlgorithm::setMinimumTemperature(ushort temperature) {
  9 + hostMinTemperature.setTo(temperature);
  10 + return hostMinTemperature;
  11 +}
  12 +
  13 +void Cpu::SurfaceLayerDetectionAlgorithm::setup(const cv::Size &frameSize,
  14 + const CVMatLoader &loader) {
  15 +
  16 + hostRegionMask = loader.asMat("/scene_model/FOV", CV_8UC1) &
  17 + PfcSegmenter(loader, frameSize).mask("Divertors");
  18 +
  19 + hostMinTemperature.create(frameSize, CV_16UC1);
  20 + setMinimumTemperature(434);
  21 +
  22 + hostFloatInput.create(frameSize, CV_32F);
  23 + hostDifference.create(frameSize, CV_32F);
  24 + hostDifference.setTo(cv::Scalar::all(0));
  25 +
  26 + hostDifferenceAbs.create(frameSize, CV_32F);
  27 + hostDifferenceAbs.setTo(cv::Scalar::all(0));
  28 +
  29 + hostSurfaceLayers.create(frameSize, CV_8UC1);
  30 + hostSurfaceLayers.setTo(cv::Scalar::all(0));
  31 +}
  32 +
  33 +void Cpu::SurfaceLayerDetectionAlgorithm::handleFrame(
  34 + const cv::Mat &sourceFrame, unsigned long timestamp) {
  35 + currentTimestamp = timestamp;
  36 + hostFrame = sourceFrame;
  37 +
  38 + /* Apply median filter */
  39 + cv::medianBlur(hostFrame, hostFiltered, 3);
  40 + /* Apply detection PFC mask */
  41 + cv::bitwise_and(hostFiltered, hostFiltered, hostInput, hostRegionMask);
  42 +
  43 + /* Trim noise at minimum temperature */
  44 + cv::max(hostInput, hostMinTemperature, hostInput);
  45 +
  46 + /* Convert to float */
  47 + hostInput.convertTo(hostFloatInput, CV_32F);
  48 + /* Calculate normalized derivative of the temperature evolution */
  49 + normalizedDerivative(); /* Result in hostDifference & hostDifferenceAbs */
  50 +
  51 + /* Threshold rapidly evolving pixels */
  52 + cv::compare(hostDifferenceAbs, derivativeThreshold, hostResult, cv::CMP_GE);
  53 + /* Merge surface layers */
  54 + cv::bitwise_or(hostResult, hostSurfaceLayers, hostSurfaceLayers);
  55 +}
  56 +
  57 +void Cpu::SurfaceLayerDetectionAlgorithm::normalizedDerivative() {
  58 + if (hostPreviousPreviousFrame.empty()) {
  59 + previousPreviousTimestamp = currentTimestamp;
  60 + hostFloatInput.copyTo(hostPreviousPreviousFrame);
  61 + return;
  62 + }
  63 + if (hostPreviousFrame.empty()) {
  64 + previousTimestamp = currentTimestamp;
  65 + hostFloatInput.copyTo(hostPreviousFrame);
  66 + return;
  67 + }
  68 +
  69 + cv::subtract(hostFloatInput, hostPreviousPreviousFrame, hostDifference);
  70 + cv::multiply(hostDifference,
  71 + cv::Scalar::all(
  72 + (currentTimestamp - previousPreviousTimestamp) * NS_TO_S),
  73 + hostDifference);
  74 + cv::divide(hostDifference, hostPreviousFrame, hostDifference);
  75 + hostDifferenceAbs = cv::abs(hostDifference);
  76 +
  77 + previousPreviousTimestamp = previousTimestamp;
  78 + previousTimestamp = currentTimestamp;
  79 +
  80 + hostPreviousFrame.copyTo(hostPreviousPreviousFrame);
  81 + hostFloatInput.copyTo(hostPreviousFrame);
  82 +}
... ...
src/algorithm/cpu/surfacelayer/SurfaceLayerDetectionAlgorithm.hpp 0 → 100644
  1 +++ a/src/algorithm/cpu/surfacelayer/SurfaceLayerDetectionAlgorithm.hpp
  1 +#pragma once
  2 +
  3 +#include <opencv2/core/mat.hpp>
  4 +
  5 +#include "algorithm/Algorithm.hpp"
  6 +
  7 +namespace Cpu {
  8 +
  9 +class SurfaceLayerDetectionAlgorithm : public Algorithm {
  10 + public:
  11 + /* Configurable parameters */
  12 + cv::Scalar derivativeThreshold{7};
  13 + cv::Mat hostMinTemperature;
  14 +
  15 + cv::Mat setMinimumTemperature(ushort temperature);
  16 + /* Configurable parameters */
  17 +
  18 + SurfaceLayerDetectionAlgorithm() = default;
  19 +
  20 + void setup(const cv::Size &frameSize, const CVMatLoader &loader) override;
  21 + void handleFrame(const cv::Mat &sourceFrame,
  22 + unsigned long timestamp) override;
  23 +
  24 + cv::Mat hostSurfaceLayers; /* Surface layers mask */
  25 +
  26 + private:
  27 + /* ns to s and invert to multiply instead of divide */
  28 + static constexpr double NS_TO_S{1e-5};
  29 +
  30 + cv::Mat hostRegionMask;
  31 +
  32 + cv::Mat hostPreviousPreviousFrame, hostPreviousFrame;
  33 + unsigned long previousPreviousTimestamp{}, previousTimestamp{},
  34 + currentTimestamp{};
  35 +
  36 + void normalizedDerivative();
  37 +
  38 + /* Buffors */
  39 + cv::Mat hostFrame, hostFiltered, hostInput, hostFloatInput, hostDifference,
  40 + hostDifferenceAbs, hostResult;
  41 + /* Buffors */
  42 +};
  43 +
  44 +} // namespace Cpu
0 45 \ No newline at end of file
... ...
src/algorithm/gpu/common/ClusterCorrespondence.hpp 0 → 100644
  1 +++ a/src/algorithm/gpu/common/ClusterCorrespondence.hpp
  1 +/* Analysis of the outer divertor hot spot activity in the protection video
  2 +camera recordings at JET */
  3 +#pragma once
  4 +
  5 +#include <opencv2/cudaarithm.hpp>
  6 +
  7 +class ClusterCorrespondence {
  8 + public:
  9 + ClusterCorrespondence(float minOverlap, float maxOversize)
  10 + : minOverlap(minOverlap), maxOversize(maxOversize) {}
  11 +
  12 + bool corresponds(const cv::cuda::GpuMat &first,
  13 + const cv::cuda::GpuMat &second) {
  14 + cv::cuda::bitwise_and(first, second, overlap, cv::noArray(),
  15 + overlapStream);
  16 + cv::cuda::countNonZero(first, deviceFirstArea, firstStream);
  17 + cv::cuda::countNonZero(second, deviceSecondArea, secondStream),
  18 + cv::cuda::countNonZero(overlap, deviceOverlapArea, overlapStream);
  19 +
  20 + deviceFirstArea.download(hostFirstArea, firstStream);
  21 + deviceSecondArea.download(hostSecondArea, secondStream);
  22 + deviceOverlapArea.download(hostOverlapArea, overlapStream);
  23 +
  24 + firstStream.waitForCompletion();
  25 + secondStream.waitForCompletion();
  26 + overlapStream.waitForCompletion();
  27 + return meetsCriteria(hostFirstArea.at<int>(0),
  28 + hostSecondArea.at<int>(0),
  29 + hostOverlapArea.at<int>(0));
  30 + }
  31 +
  32 + private:
  33 + float minOverlap;
  34 + float maxOversize;
  35 +
  36 + bool meetsCriteria(int firstArea, int secondArea, int overlapArea) const {
  37 + return isOverlapping(firstArea, secondArea, overlapArea) &&
  38 + isSimilar(firstArea, secondArea);
  39 + }
  40 +
  41 + bool isOverlapping(int firstArea, int secondArea, int overlapArea) const {
  42 + return overlapArea >= minOverlap * std::min(firstArea, secondArea);
  43 + }
  44 +
  45 + bool isSimilar(int firstArea, int secondArea) const {
  46 + return (firstArea / static_cast<float>(secondArea) >=
  47 + 1.F / maxOversize) &&
  48 + (firstArea / static_cast<float>(secondArea) <= maxOversize);
  49 + }
  50 +
  51 + /* Buffors */
  52 + cv::cuda::Stream firstStream, secondStream, overlapStream;
  53 + cv::cuda::GpuMat overlap, deviceFirstArea, deviceSecondArea,
  54 + deviceOverlapArea;
  55 + cv::Mat hostFirstArea, hostSecondArea, hostOverlapArea;
  56 +};
0 57 \ No newline at end of file
... ...
src/algorithm/gpu/common/ConcurrentCopyExecute.hpp 0 → 100644
  1 +++ a/src/algorithm/gpu/common/ConcurrentCopyExecute.hpp
  1 +#pragma once
  2 +
  3 +#include <array>
  4 +#include <functional>
  5 +
  6 +#include <opencv2/core/cuda.hpp>
  7 +
  8 +template <std::size_t N> class ConcurrentCopyExecute {
  9 + public:
  10 + /* Width should be divisible by N without any reminder */
  11 + void run(const std::function<void(const cv::Range &, const cv::Range &,
  12 + cv::cuda::Stream &)> &callback,
  13 + const cv::Size &size) {
  14 + assert(size.height % N == 0);
  15 +
  16 + const cv::Range colRange(0, size.width);
  17 + const int stride = size.height / N;
  18 + for (auto i = 0, j = 0; i < size.height; i += stride, ++j) {
  19 + callback(cv::Range{i, i + stride}, colRange, streams[j]);
  20 + }
  21 + }
  22 +
  23 + void synchronize() {
  24 + for (auto &stream : streams) {
  25 + stream.waitForCompletion();
  26 + }
  27 + }
  28 +
  29 + private:
  30 + std::array<cv::cuda::Stream, N> streams;
  31 +};
0 32 \ No newline at end of file
... ...
src/algorithm/gpu/common/ContinuousGpuMat.hpp 0 → 100644
  1 +++ a/src/algorithm/gpu/common/ContinuousGpuMat.hpp
  1 +#pragma once
  2 +
  3 +#include <cuda_runtime.h>
  4 +
  5 +#include <opencv2/core/cuda.hpp>
  6 +
  7 +class ContinuousGpuMat {
  8 + public:
  9 + ContinuousGpuMat(const ContinuousGpuMat &) = delete;
  10 + ContinuousGpuMat &operator=(const ContinuousGpuMat &) = delete;
  11 + ContinuousGpuMat &operator=(ContinuousGpuMat &&) noexcept = delete;
  12 + ContinuousGpuMat(ContinuousGpuMat &&) noexcept = delete;
  13 +
  14 + ContinuousGpuMat(const int rows, const int cols, const int type) {
  15 + allocate(rows, cols, type);
  16 + }
  17 +
  18 + ContinuousGpuMat(const cv::Size &size, const int type)
  19 + : ContinuousGpuMat(size.height, size.width, type) {}
  20 +
  21 + explicit ContinuousGpuMat(cv::InputArray host)
  22 + : ContinuousGpuMat(host.rows(), host.cols(), host.type()) {
  23 + continuousMat.upload(host);
  24 + }
  25 +
  26 + void create(const cv::Size &size, const int type) {
  27 + allocate(size.height, size.width, type);
  28 + }
  29 +
  30 + cv::Size size() const { return continuousMat.size(); }
  31 +
  32 + int type() const { return continuousMat.type(); }
  33 +
  34 + cv::cuda::GpuMat &device() { return continuousMat; }
  35 +
  36 + ~ContinuousGpuMat() { cudaFree(deviceMemory); }
  37 +
  38 + private:
  39 + void *deviceMemory{nullptr};
  40 + cv::cuda::GpuMat continuousMat;
  41 +
  42 + void allocate(const int rows, const int cols, const int type) {
  43 + cudaMalloc(&deviceMemory, rows * cols * cv::getElemSize(type));
  44 + continuousMat = cv::cuda::GpuMat(rows, cols, type, deviceMemory);
  45 + }
  46 +};
0 47 \ No newline at end of file
... ...
src/algorithm/gpu/common/ManagedMat.hpp 0 → 100644
  1 +++ a/src/algorithm/gpu/common/ManagedMat.hpp
  1 +#pragma once
  2 +
  3 +#include <cuda_runtime.h>
  4 +
  5 +#include <opencv2/core/utility.hpp>
  6 +#include <opencv2/cudaarithm.hpp>
  7 +
  8 +class ManagedMat {
  9 + public:
  10 + ManagedMat(const ManagedMat &) = delete;
  11 + ManagedMat &operator=(const ManagedMat &) = delete;
  12 + ManagedMat &operator=(ManagedMat &&) noexcept = delete;
  13 + ManagedMat(ManagedMat &&) noexcept = delete;
  14 +
  15 + ManagedMat(int rows, int cols, int type)
  16 + : memorySize(rows * cols * cv::getElemSize(type)) {
  17 + cudaMallocManaged(&managedMemory, memorySize);
  18 +
  19 + hostMat = cv::Mat(rows, cols, type, managedMemory);
  20 + deviceMat = cv::cuda::GpuMat(rows, cols, type, managedMemory);
  21 + }
  22 + ManagedMat(const cv::Size &size, int type)
  23 + : ManagedMat(size.height, size.width, type) {}
  24 +
  25 + cv::Mat &host() {
  26 + prefetchToCpu();
  27 + return hostMat;
  28 + }
  29 +
  30 + cv::cuda::GpuMat &device() {
  31 + prefetchToGpu();
  32 + return deviceMat;
  33 + }
  34 +
  35 + ~ManagedMat() { cudaFree(managedMemory); }
  36 +
  37 + private:
  38 + const std::size_t memorySize;
  39 + void *managedMemory{nullptr};
  40 +
  41 + cv::Mat hostMat;
  42 + cv::cuda::GpuMat deviceMat;
  43 +
  44 + void prefetchToGpu() const {
  45 + cudaStreamAttachMemAsync(nullptr, managedMemory, memorySize,
  46 + cudaMemAttachGlobal);
  47 + }
  48 +
  49 + void prefetchToCpu() const {
  50 + cudaStreamAttachMemAsync(nullptr, managedMemory, memorySize,
  51 + cudaMemAttachHost);
  52 + cudaStreamSynchronize(nullptr);
  53 + }
  54 +};
0 55 \ No newline at end of file
... ...
src/algorithm/gpu/common/MedianFilter.cpp 0 → 100644
  1 +++ a/src/algorithm/gpu/common/MedianFilter.cpp
  1 +#include "MedianFilter.hpp"
  2 +
  3 +std::unique_ptr<cv::cuda::Filter>
  4 +CudaFilter::createMedianFilter16U(const cv::Size &size, int kernel) {
  5 + return std::make_unique<MedianFilter<Filter::U16Bit>>(size, kernel);
  6 +}
  7 +
  8 +std::unique_ptr<cv::cuda::Filter>
  9 +CudaFilter::createMedianFilter8U(const cv::Size &size, int kernel) {
  10 + return std::make_unique<MedianFilter<Filter::U8Bit>>(size, kernel);
  11 +}
0 12 \ No newline at end of file
... ...
src/algorithm/gpu/common/MedianFilter.hpp 0 → 100644
  1 +++ a/src/algorithm/gpu/common/MedianFilter.hpp
  1 +#pragma once
  2 +
  3 +#include <cuda_runtime.h>
  4 +
  5 +#include <npp.h>
  6 +
  7 +#include <opencv2/cudaarithm.hpp>
  8 +#include <opencv2/cudafilters.hpp>
  9 +
  10 +namespace Filter {
  11 +struct U16Bit {
  12 + template <typename... Args>
  13 + NppStatus calculateBufferSize(Args &&... args) const {
  14 + return nppiFilterMedianGetBufferSize_16u_C1R(
  15 + std::forward<Args>(args)...);
  16 + }
  17 +
  18 + template <typename... Args> NppStatus filter(Args &&... args) const {
  19 + return nppiFilterMedian_16u_C1R(std::forward<Args>(args)...);
  20 + }
  21 +
  22 + static constexpr int cvType{CV_16UC1};
  23 + using Type = Npp16u;
  24 +};
  25 +
  26 +struct U8Bit {
  27 + template <typename... Args>
  28 + NppStatus calculateBufferSize(Args &&... args) const {
  29 + return nppiFilterMedianGetBufferSize_8u_C1R(
  30 + std::forward<Args>(args)...);
  31 + }
  32 +
  33 + template <typename... Args> NppStatus filter(Args &&... args) const {
  34 + return nppiFilterMedian_8u_C1R(std::forward<Args>(args)...);
  35 + }
  36 +
  37 + static constexpr int cvType{CV_8UC1};
  38 + using Type = Npp8u;
  39 +};
  40 +} // namespace Filter
  41 +
  42 +/*
  43 + * Filter uses 0 value to handle borders
  44 + */
  45 +template <typename FilterType> class MedianFilter : public cv::cuda::Filter {
  46 + public:
  47 + MedianFilter(const MedianFilter &) = delete;
  48 + MedianFilter &operator=(const MedianFilter &) = delete;
  49 + MedianFilter &operator=(MedianFilter &&) noexcept = delete;
  50 + MedianFilter(MedianFilter &&) noexcept = delete;
  51 +
  52 + MedianFilter(const cv::Size &size, int kernel)
  53 + : size(size), kernelSize(NppiSize{kernel, kernel}),
  54 + anchor(NppiPoint{kernel / 2, kernel / 2}),
  55 + nppSize(NppiSize{size.width, size.height}),
  56 + nppScratchBuffer(allocateScratch()) {}
  57 +
  58 + void apply(cv::InputArray source, cv::OutputArray destination,
  59 + [[maybe_unused]] cv::cuda::Stream &stream =
  60 + cv::cuda::Stream::Null()) override {
  61 + /* Stream unsupported. If needed might be applied with NPPI Ctx function
  62 + * variants */
  63 + assert(cv::cuda::Stream::Null() == stream);
  64 + assert(filterDelegate.cvType == source.type());
  65 +
  66 + destination.create(size, filterDelegate.cvType);
  67 + cv::cuda::copyMakeBorder(source, paddedSource, anchor.y, anchor.y,
  68 + anchor.x, anchor.x, cv::BORDER_CONSTANT,
  69 + cv::Scalar(0));
  70 +
  71 + [[maybe_unused]] const auto nppStatus = filterDelegate.filter(
  72 + paddedSource.ptr<typename FilterType::Type>(anchor.y) + anchor.x,
  73 + paddedSource.step,
  74 + destination.getGpuMatRef().ptr<typename FilterType::Type>(),
  75 + destination.step(), nppSize, kernelSize, anchor, nppScratchBuffer);
  76 + assert(NPP_SUCCESS == nppStatus);
  77 + }
  78 +
  79 + ~MedianFilter() override { cudaFree(nppScratchBuffer); }
  80 +
  81 + private:
  82 + const FilterType filterDelegate{};
  83 +
  84 + const cv::Size size;
  85 + const NppiSize kernelSize;
  86 + const NppiPoint anchor;
  87 + const NppiSize nppSize;
  88 +
  89 + Npp8u *const nppScratchBuffer{nullptr};
  90 +
  91 + Npp8u *allocateScratch() const {
  92 + Npp32u bufferSize{0};
  93 + [[maybe_unused]] const auto nppStatus =
  94 + filterDelegate.calculateBufferSize(nppSize, kernelSize,
  95 + &bufferSize);
  96 + assert(NPP_SUCCESS == nppStatus);
  97 +
  98 + Npp8u *buffer{nullptr};
  99 + [[maybe_unused]] const auto cudaStatus =
  100 + cudaMalloc(&buffer, bufferSize);
  101 + assert(cudaSuccess == cudaStatus);
  102 +
  103 + return buffer;
  104 + }
  105 +
  106 + cv::cuda::GpuMat paddedSource;
  107 +};
  108 +
  109 +namespace CudaFilter {
  110 +
  111 +std::unique_ptr<cv::cuda::Filter> createMedianFilter16U(const cv::Size &size,
  112 + int kernel);
  113 +std::unique_ptr<cv::cuda::Filter> createMedianFilter8U(const cv::Size &size,
  114 + int kernel);
  115 +
  116 +} // namespace CudaFilter
0 117 \ No newline at end of file
... ...
src/algorithm/gpu/common/MedianFilter3x3.cu 0 → 100644
  1 +++ a/src/algorithm/gpu/common/MedianFilter3x3.cu
  1 +#include "MedianFilter3x3.hpp"
  2 +
  3 +/* Efficient exchange */
  4 +#define swap(a, b) \
  5 + { \
  6 + T tmp = a; \
  7 + a = min(a, b); \
  8 + b = max(tmp, b); \
  9 + }
  10 +#define mn3(a, b, c) \
  11 + swap(a, b); \
  12 + swap(a, c);
  13 +#define mx3(a, b, c) \
  14 + swap(b, c); \
  15 + swap(a, c);
  16 +
  17 +#define mnmx3(a, b, c) \
  18 + mx3(a, b, c); \
  19 + swap(a, b); /* 3 exchanges */
  20 +#define mnmx4(a, b, c, d) \
  21 + swap(a, b); \
  22 + swap(c, d); \
  23 + swap(a, c); \
  24 + swap(b, d); /* 4 exchanges */
  25 +#define mnmx5(a, b, c, d, e) \
  26 + swap(a, b); \
  27 + swap(c, d); \
  28 + mn3(a, c, e); \
  29 + mx3(b, d, e); /* 6 exchanges */
  30 +#define mnmx6(a, b, c, d, e, f) \
  31 + swap(a, d); \
  32 + swap(b, e); \
  33 + swap(c, f); \
  34 + mn3(a, b, c); \
  35 + mx3(d, e, f); /* 7 exchanges */
  36 +/* Efficient exchange */
  37 +
  38 +#define IDX(x, y) ((y)*width + (x))
  39 +#define SMEM(x, y) sharedMemory[(x) + 1][(y) + 1]
  40 +#define GMEM(x, y) source[IDX(x, y)]
  41 +
  42 +template <typename T, int TileWidth, int TileHeight>
  43 +__device__ T findMedian(const T sharedMemory[TileWidth + 2][TileHeight + 2],
  44 + const int tx, const int ty) {
  45 + /* Take upper six values from shared memory */
  46 + T v[6] = {SMEM(tx - 1, ty - 1), SMEM(tx, ty - 1), SMEM(tx + 1, ty - 1),
  47 + SMEM(tx - 1, ty), SMEM(tx, ty), SMEM(tx + 1, ty)};
  48 +
  49 + /* With each pass, remove min and max values and add new value */
  50 + mnmx6(v[0], v[1], v[2], v[3], v[4], v[5]);
  51 + v[5] = SMEM(tx - 1, ty + 1); /* Add new value */
  52 + mnmx5(v[1], v[2], v[3], v[4], v[5]);
  53 + v[5] = SMEM(tx, ty + 1); /* Add new value */
  54 + mnmx4(v[2], v[3], v[4], v[5]);
  55 + v[5] = SMEM(tx + 1, ty + 1); /* Add new value */
  56 + mnmx3(v[3], v[4], v[5]);
  57 +
  58 + /* Select the middle value */
  59 + return v[4];
  60 +}
  61 +
  62 +template <typename T, int TileWidth, int TileHeight>
  63 +__global__ void medianFilter3x3Kernel(const T *source, T *destination,
  64 + const int width, const int height) {
  65 + const int tx = threadIdx.x, ty = threadIdx.y;
  66 +
  67 + bool isLeftBorder = (tx == 0), isRightBorder = (tx == TileWidth - 1),
  68 + isTopBorder = (ty == 0), isBottomBorder = (ty == TileHeight - 1);
  69 +
  70 + __shared__ T sharedMemory[TileWidth + 2][TileHeight + 2];
  71 +
  72 + /* 0-initialise boundaries */
  73 + if (isLeftBorder) {
  74 + SMEM(tx - 1, ty) = 0;
  75 + } else if (isRightBorder) {
  76 + SMEM(tx + 1, ty) = 0;
  77 + }
  78 + if (isTopBorder) {
  79 + SMEM(tx, ty - 1) = 0;
  80 + if (isLeftBorder) {
  81 + SMEM(tx - 1, ty - 1) = 0;
  82 + } else if (isRightBorder) {
  83 + SMEM(tx + 1, ty - 1) = 0;
  84 + }
  85 + } else if (isBottomBorder) {
  86 + SMEM(tx, ty + 1) = 0;
  87 + if (isLeftBorder) {
  88 + SMEM(tx - 1, ty + 1) = 0;
  89 + } else if (isRightBorder) {
  90 + SMEM(tx + 1, ty + 1) = 0;
  91 + }
  92 + }
  93 +
  94 + const int x = blockIdx.x * blockDim.x + tx,
  95 + y = blockIdx.y * blockDim.y + ty;
  96 +
  97 + /* Check if padded values lies inside an image */
  98 + isLeftBorder &= (x > 0);
  99 + isRightBorder &= (x < width - 1);
  100 + isTopBorder &= (y > 0);
  101 + isBottomBorder &= (y < height - 1);
  102 +
  103 + /* Set pixel value */
  104 + SMEM(tx, ty) = GMEM(x, y);
  105 +
  106 + /* Set boundaries */
  107 + if (isLeftBorder) {
  108 + SMEM(tx - 1, ty) = GMEM(x - 1, y);
  109 + } else if (isRightBorder) {
  110 + SMEM(tx + 1, ty) = GMEM(x + 1, y);
  111 + }
  112 + if (isTopBorder) {
  113 + SMEM(tx, ty - 1) = GMEM(x, y - 1);
  114 + if (isLeftBorder) {
  115 + SMEM(tx - 1, ty - 1) = GMEM(x - 1, y - 1);
  116 + } else if (isRightBorder) {
  117 + SMEM(tx + 1, ty - 1) = GMEM(x + 1, y - 1);
  118 + }
  119 + } else if (isBottomBorder) {
  120 + SMEM(tx, ty + 1) = GMEM(x, y + 1);
  121 + if (isLeftBorder) {
  122 + SMEM(tx - 1, ty + 1) = GMEM(x - 1, y + 1);
  123 + } else if (isRightBorder) {
  124 + SMEM(tx + 1, ty + 1) = GMEM(x + 1, y + 1);
  125 + }
  126 + }
  127 + __syncthreads();
  128 +
  129 + destination[IDX(x, y)] = findMedian<T, TileWidth, TileHeight>(sharedMemory, tx, ty);
  130 +}
  131 +
  132 +template <>
  133 +void MedianFilter3x3<uchar, 16, 16>::callKernel(const uchar *source, uchar *destination,
  134 + cudaStream_t &stream) const {
  135 + medianFilter3x3Kernel<uchar, 16, 16><<<grid, block, 0, stream>>>(
  136 + source, destination, size.width, size.height);
  137 +}
  138 +
  139 +template <>
  140 +void MedianFilter3x3<ushort, 16, 16>::callKernel(const ushort *source,
  141 + ushort *destination,
  142 + cudaStream_t &stream) const {
  143 + medianFilter3x3Kernel<ushort, 16, 16><<<grid, block, 0, stream>>>(
  144 + source, destination, size.width, size.height);
  145 +}
0 146 \ No newline at end of file
... ...
src/algorithm/gpu/common/MedianFilter3x3.hpp 0 → 100644
  1 +++ a/src/algorithm/gpu/common/MedianFilter3x3.hpp
  1 +#pragma once
  2 +
  3 +#include <opencv2/core/cuda_stream_accessor.hpp>
  4 +#include <opencv2/cudafilters.hpp>
  5 +
  6 +/*
  7 + * Median Filter optimised for 3x3 kerner size and continous memory that use 0
  8 + * on borders. In order to obtain correct boundary values TileWidth and TileHeight
  9 + * should be a multiple of the size
  10 + */
  11 +template <typename T, std::size_t TileWidth=16, std::size_t TileHeight=TileWidth> class MedianFilter3x3 : public cv::cuda::Filter {
  12 + public:
  13 + static std::size_t gridSize(const std::size_t a, const std::size_t b) {
  14 + assert(a % b == 0);
  15 + return a / b + (a % b != 0);
  16 + }
  17 +
  18 + explicit MedianFilter3x3(const cv::Size &size)
  19 + : size(size),
  20 + grid(dim3(
  21 + gridSize(size.width, TileWidth),
  22 + gridSize(size.height, TileHeight))),
  23 + block(dim3(TileWidth, TileHeight)) {}
  24 +
  25 + void apply(cv::InputArray source, cv::OutputArray destination,
  26 + cv::cuda::Stream &stream = cv::cuda::Stream::Null()) override {
  27 + destination.create(size, source.type());
  28 +
  29 + /* Requires continuous memories */
  30 + assert(source.getGpuMat().isContinuous());
  31 + assert(destination.getGpuMatRef().isContinuous());
  32 +
  33 + auto cudaStream = cv::cuda::StreamAccessor::getStream(stream);
  34 + callKernel(source.getGpuMat().ptr<T>(),
  35 + destination.getGpuMatRef().ptr<T>(), cudaStream);
  36 +
  37 + stream.waitForCompletion();
  38 + }
  39 +
  40 + private:
  41 + const cv::Size size;
  42 + const dim3 grid, block;
  43 +
  44 + void callKernel(const T *source, T *destination,
  45 + cudaStream_t &stream) const;
  46 +};
0 47 \ No newline at end of file
... ...
src/algorithm/gpu/hotspot/Hotspot.cpp 0 → 100644
  1 +++ a/src/algorithm/gpu/hotspot/Hotspot.cpp
  1 +#include "Hotspot.hpp"
  2 +
  3 +#include <opencv2/imgproc.hpp>
  4 +
  5 +Hotspot::Hotspot(const cv::cuda::GpuMat &temperature, unsigned long timestamp,
  6 + int component, const Contour &contour, const Surface &surface)
  7 + : Hotspot(temperature, timestamp, component, contour, surface,
  8 + createMask(temperature.size(), contour)) {}
  9 +
  10 +Hotspot::Hotspot(const cv::cuda::GpuMat &temperature, unsigned long timestamp,
  11 + int component, const Contour &contour, const Surface &surface,
  12 + const cv::cuda::GpuMat &mask)
  13 + : timestamp(timestamp), component(component), contour(contour), mask(mask),
  14 + surface(surface) {
  15 + /* Compute hotspot attributes */
  16 + cv::cuda::countNonZero(mask, nonZeroDevice, nonZeroStream);
  17 + nonZeroDevice.download(nonZeroHost, nonZeroStream);
  18 +
  19 + cv::cuda::calcSum(temperature, sumDevice, mask, sumStream);
  20 + sumDevice.download(sumHost, sumStream);
  21 +
  22 + cv::cuda::findMinMax(temperature, minMaxDevice, mask, minMaxStream);
  23 + minMaxDevice.download(minMaxHost, minMaxStream);
  24 +
  25 + nonZeroStream.waitForCompletion();
  26 + size = nonZeroHost.at<int>(0);
  27 + sumStream.waitForCompletion();
  28 + meanTemperature = sumHost.at<double>(0) / size;
  29 + minMaxStream.waitForCompletion();
  30 + maxTemperature = minMaxHost.at<int>(1);
  31 +}
  32 +
  33 +double Hotspot::perimeter() const {
  34 + double perimeter = 0;
  35 +
  36 + for (std::size_t i = 0, size = surface.size(); i < size; ++i) {
  37 + perimeter += cv::norm(surface[i] - surface[(i + 1) % size]);
  38 + }
  39 +
  40 + return perimeter;
  41 +}
  42 +
  43 +cv::cuda::GpuMat Hotspot::createMask(const cv::Size &size,
  44 + const Contour &contour) {
  45 + cv::Mat result(cv::Mat::zeros(size, CV_8UC1));
  46 + cv::drawContours(result, std::vector<Contour>{contour}, -1, 255,
  47 + cv::FILLED);
  48 +
  49 + return cv::cuda::GpuMat{result};
  50 +}
  51 +
  52 +PersistentHotspot::PersistentHotspot(const Hotspot &initial)
  53 + : mask(initial.mask), component(initial.component) {
  54 + append(initial);
  55 +}
  56 +
  57 +void PersistentHotspot::merge(const Hotspot &other) {
  58 + cv::cuda::bitwise_or(mask, other.mask, mask, cv::noArray(), stream);
  59 + append(other);
  60 + stream.waitForCompletion();
  61 +}
  62 +
  63 +int PersistentHotspot::area() const { return cv::cuda::countNonZero(mask); }
  64 +
  65 +void PersistentHotspot::append(const Hotspot &other) {
  66 + timestamps.push_back(other.timestamp);
  67 + meanTemperatures.push_back(other.meanTemperature);
  68 + maxTemperatures.push_back(other.maxTemperature);
  69 +}
0 70 \ No newline at end of file
... ...
src/algorithm/gpu/hotspot/Hotspot.hpp 0 → 100644
  1 +++ a/src/algorithm/gpu/hotspot/Hotspot.hpp
  1 +#pragma once
  2 +
  3 +#include <vector>
  4 +
  5 +#include <opencv2/core/mat.hpp>
  6 +#include <opencv2/cudaarithm.hpp>
  7 +
  8 +class Hotspot {
  9 + public:
  10 + using Contour = std::vector<cv::Point>;
  11 + using Surface = std::vector<cv::Point3f>;
  12 +
  13 + const unsigned long timestamp;
  14 + const int component;
  15 + const Contour contour;
  16 +
  17 + const cv::cuda::GpuMat mask;
  18 +
  19 + int size;
  20 + double meanTemperature, maxTemperature;
  21 +
  22 + Hotspot(const cv::cuda::GpuMat &temperature, unsigned long timestamp,
  23 + int component, const Contour &contour, const Surface &surface);
  24 +
  25 + double perimeter() const;
  26 +
  27 + private:
  28 + const Surface surface;
  29 +
  30 + Hotspot(const cv::cuda::GpuMat &temperature, unsigned long timestamp,
  31 + int component, const Contour &contour, const Surface &surface,
  32 + const cv::cuda::GpuMat &mask);
  33 +
  34 + static cv::cuda::GpuMat createMask(const cv::Size &size,
  35 + const Contour &contour);
  36 +
  37 + /* Buffors */
  38 + cv::cuda::Stream nonZeroStream, sumStream, minMaxStream;
  39 + cv::cuda::GpuMat nonZeroDevice{1, 1, CV_32SC1}, sumDevice{1, 1, CV_64FC1},
  40 + minMaxDevice{1, 2, CV_32SC1};
  41 + cv::Mat nonZeroHost{1, 1, CV_32SC1}, sumHost{1, 1, CV_64FC1},
  42 + minMaxHost{1, 2, CV_32SC1};
  43 +};
  44 +
  45 +class PersistentHotspot {
  46 + public:
  47 + explicit PersistentHotspot(const Hotspot &initial);
  48 +
  49 + void merge(const Hotspot &other);
  50 + int area() const;
  51 +
  52 + cv::cuda::GpuMat mask;
  53 + int component;
  54 + std::vector<unsigned long> timestamps;
  55 + std::vector<double> meanTemperatures;
  56 + std::vector<double> maxTemperatures;
  57 +
  58 + private:
  59 + void append(const Hotspot &other);
  60 +
  61 + cv::cuda::Stream stream;
  62 +};
... ...
src/algorithm/gpu/hotspot/OverloadHotspotDetectionAlgorithm.cpp 0 → 100644
  1 +++ a/src/algorithm/gpu/hotspot/OverloadHotspotDetectionAlgorithm.cpp
  1 +#include "OverloadHotspotDetectionAlgorithm.hpp"
  2 +
  3 +#include "algorithm/gpu/common/MedianFilter.hpp"
  4 +#include "algorithm/gpu/common/MedianFilter3x3.hpp"
  5 +
  6 +void OverloadHotspotDetectionAlgorithm::setup(const cv::Size &frameSize,
  7 + const CVMatLoader &loader) {
  8 + currentFrameSize = frameSize;
  9 +
  10 + deviceFov.upload(loader.asMat("/scene_model/FOV", CV_8UC1));
  11 +
  12 + PfcSegmenter segmenter(loader, frameSize);
  13 + devicePfc.upload(segmenter.segment());
  14 +
  15 + cv::cuda::GpuMat zerosMask;
  16 + cv::cuda::compare(devicePfc, 0, zerosMask, cv::CMP_EQ);
  17 + devicePfc.setTo(MAX_TEMPERATURE, zerosMask);
  18 +
  19 + cv::Mat model;
  20 + cv::cvtColor(loader.asMat("/scene_model/CAD", CV_8UC1), model,
  21 + cv::COLOR_GRAY2BGR);
  22 +
  23 + std::vector<Contours> pfcContours;
  24 + segmenter.contour(pfcContours, hostPfcMask);
  25 +
  26 + surfaceMap = std::make_unique<const SurfaceMap>(frameSize);
  27 + medianFilter = createMedianFilter(3);
  28 +
  29 + overheating = std::make_unique<ManagedMat>(frameSize, CV_8UC1);
  30 +
  31 + deviceFrame.create(frameSize, CV_16UC1);
  32 +
  33 + medianFilterInput = std::make_unique<ContinuousGpuMat>(frameSize, CV_16UC1);
  34 + medianFilterOutput =
  35 + std::make_unique<ContinuousGpuMat>(frameSize, CV_16UC1);
  36 +
  37 + deviceTemperature = medianFilterInput->device();
  38 + deviceInput = medianFilterOutput->device();
  39 +}
  40 +
  41 +void OverloadHotspotDetectionAlgorithm::handleFrame(const cv::Mat &hostFrame,
  42 + unsigned long timestamp) {
  43 +
  44 + /* Overlap data transfer and computations */
  45 + concurrent.run(
  46 + [this, &hostFrame](const auto &rows, const auto &cols, auto &stream) {
  47 + deviceFrame(rows, cols).upload(hostFrame(rows, cols), stream);
  48 + },
  49 + currentFrameSize);
  50 +
  51 + concurrent.run(
  52 + [this](const auto &rows, const auto &cols, auto &stream) {
  53 + /* Apply field-of-view mask */
  54 + cv::cuda::bitwise_and(
  55 + deviceFrame(rows, cols), deviceFrame(rows, cols),
  56 + deviceInput(rows, cols), deviceFov(rows, cols), stream);
  57 + /* Convert Kelvin to Celsius */
  58 + cv::cuda::subtract(deviceInput(rows, cols), KELVINS,
  59 + deviceTemperature(rows, cols), cv::noArray(), -1,
  60 + stream);
  61 + },
  62 + currentFrameSize);
  63 +
  64 + concurrent.synchronize();
  65 +
  66 + /* Apply median filter */
  67 + medianFilter->apply(deviceTemperature, deviceInput);
  68 +
  69 + /* Threshold overheating pixels */
  70 + cv::cuda::compare(deviceInput, devicePfc, overheating->device(),
  71 + cv::CMP_GT);
  72 + currentHotspots.clear();
  73 + identifyHotspots(timestamp);
  74 +}
  75 +
  76 +void OverloadHotspotDetectionAlgorithm::identifyHotspots(
  77 + unsigned long timestamp) {
  78 + /* Analyse topologial structure */
  79 + Contours hotspotContours;
  80 + cv::findContours(overheating->host(), hotspotContours, cv::RETR_TREE,
  81 + cv::CHAIN_APPROX_SIMPLE);
  82 +
  83 + for (std::size_t i = 0, count = 0, size = hotspotContours.size();
  84 + i < size && count < blobsLimit; ++i) {
  85 + const auto &contour = hotspotContours[i];
  86 + /* Discard blobs below minimum area */
  87 + if (contour.size() >= pixelsThreshold) {
  88 + count += 1;
  89 +
  90 + const int component = hostPfcMask.at<uchar>(contour[0]);
  91 + assert(component > 0);
  92 +
  93 + Hotspot hotspot(deviceTemperature, timestamp, component, contour,
  94 + surfaceMap->at(contour));
  95 +
  96 + /* Find corresponding blobs */
  97 + matchHotspot(hotspot);
  98 + currentHotspots.emplace_back(std::move(hotspot));
  99 + }
  100 + }
  101 +}
  102 +
  103 +void OverloadHotspotDetectionAlgorithm::matchHotspot(const Hotspot &hotspot) {
  104 + for (auto &persistentHotspot : uniqueHotspots) {
  105 + if (clusterCorrespondence.corresponds(persistentHotspot.mask,
  106 + hotspot.mask)) {
  107 + /* Merge matching blobs */
  108 + persistentHotspot.merge(hotspot);
  109 + return;
  110 + }
  111 + }
  112 + uniqueHotspots.emplace_back(hotspot);
  113 +}
  114 +
  115 +std::unique_ptr<cv::cuda::Filter>
  116 +OverloadHotspotDetectionAlgorithm::createMedianFilter(int kernel) const {
  117 + if (kernel == 3) {
  118 + /* Implementation optimised for the small kernel size */
  119 + return std::make_unique<MedianFilter3x3<ushort>>(currentFrameSize);
  120 + }
  121 + return CudaFilter::createMedianFilter16U(currentFrameSize, kernel);
  122 +}
... ...
src/algorithm/gpu/hotspot/OverloadHotspotDetectionAlgorithm.hpp 0 → 100644
  1 +++ a/src/algorithm/gpu/hotspot/OverloadHotspotDetectionAlgorithm.hpp
  1 +#pragma once
  2 +
  3 +#include <opencv2/cudaarithm.hpp>
  4 +#include <opencv2/cudafilters.hpp>
  5 +
  6 +#include "algorithm/Algorithm.hpp"
  7 +
  8 +#include "algorithm/common/PfcSegmenter.hpp"
  9 +#include "algorithm/common/SurfaceMap.hpp"
  10 +
  11 +#include "algorithm/gpu/common/ClusterCorrespondence.hpp"
  12 +#include "algorithm/gpu/common/ConcurrentCopyExecute.hpp"
  13 +#include "algorithm/gpu/hotspot/Hotspot.hpp"
  14 +
  15 +#include "algorithm/gpu/common/ContinuousGpuMat.hpp"
  16 +#include "algorithm/gpu/common/ManagedMat.hpp"
  17 +
  18 +class OverloadHotspotDetectionAlgorithm : public Algorithm {
  19 + public:
  20 + /* Configurable parameters */
  21 + std::unique_ptr<cv::cuda::Filter> medianFilter;
  22 +
  23 + std::size_t blobsLimit{5};
  24 + std::size_t pixelsThreshold{3};
  25 +
  26 + std::unique_ptr<cv::cuda::Filter> createMedianFilter(int kernel) const;
  27 + /* Configurable parameters */
  28 +
  29 + OverloadHotspotDetectionAlgorithm() = default;
  30 +
  31 + void setup(const cv::Size &frameSize, const CVMatLoader &loader) override;
  32 + void handleFrame(const cv::Mat &hostFrame,
  33 + unsigned long timestamp) override;
  34 +
  35 + std::vector<PersistentHotspot> uniqueHotspots; /* Set of persistent hotspots */
  36 +
  37 + private:
  38 + using Contours = std::vector<std::vector<cv::Point>>;
  39 +
  40 + static constexpr int KELVINS{273};
  41 + static constexpr int MAX_TEMPERATURE{(1L << 15) - 1};
  42 +
  43 + ConcurrentCopyExecute<4> concurrent;
  44 + cv::Size currentFrameSize;
  45 +
  46 + ClusterCorrespondence clusterCorrespondence{.8, 2.};
  47 + std::unique_ptr<const SurfaceMap> surfaceMap;
  48 +
  49 + cv::cuda::GpuMat deviceFov, deviceModel, devicePfc;
  50 + cv::Mat hostPfcMask;
  51 +
  52 + std::vector<Hotspot> currentHotspots;
  53 +
  54 + void identifyHotspots(unsigned long timestamp);
  55 + void matchHotspot(const Hotspot &hotspot);
  56 +
  57 + /* Buffors */
  58 + std::unique_ptr<ContinuousGpuMat> medianFilterInput, medianFilterOutput;
  59 + std::unique_ptr<ManagedMat> overheating;
  60 + cv::cuda::GpuMat deviceFrame, deviceInput, deviceTemperature;
  61 + /* Buffors */
  62 +};
... ...
src/algorithm/gpu/surfacelayer/SurfaceLayerDetectionAlgorithm.cpp 0 → 100644
  1 +++ a/src/algorithm/gpu/surfacelayer/SurfaceLayerDetectionAlgorithm.cpp
  1 +#include "SurfaceLayerDetectionAlgorithm.hpp"
  2 +
  3 +#include "algorithm/common/PfcSegmenter.hpp"
  4 +
  5 +#include "algorithm/gpu/common/MedianFilter.hpp"
  6 +#include "algorithm/gpu/common/MedianFilter3x3.hpp"
  7 +
  8 +cv::cuda::GpuMat
  9 +SurfaceLayerDetectionAlgorithm::setMinimumTemperature(ushort temperature) {
  10 + deviceMinTemperature.setTo(temperature);
  11 + return deviceMinTemperature;
  12 +}
  13 +
  14 +void SurfaceLayerDetectionAlgorithm::setup(const cv::Size &frameSize,
  15 + const CVMatLoader &loader) {
  16 + currentFrameSize = frameSize;
  17 + medianFilter = createMedianFilter(3);
  18 +
  19 + deviceRegionMask.upload(loader.asMat("/scene_model/FOV", CV_8UC1) &
  20 + PfcSegmenter(loader, frameSize).mask("Divertors"));
  21 +
  22 + medianFilterInput = std::make_unique<ContinuousGpuMat>(frameSize, CV_16UC1);
  23 + medianFilterOutput =
  24 + std::make_unique<ContinuousGpuMat>(frameSize, CV_16UC1);
  25 +
  26 + deviceFrame = medianFilterInput->device();
  27 + deviceFiltered = medianFilterOutput->device();
  28 +
  29 + deviceMinTemperature.create(frameSize, CV_16UC1);
  30 + setMinimumTemperature(434);
  31 +
  32 + deviceFloatInput.create(frameSize, CV_32F);
  33 + deviceDifference.create(frameSize, CV_32F);
  34 + deviceDifference.setTo(cv::Scalar::all(0));
  35 +
  36 + deviceDifferenceAbs.create(frameSize, CV_32F);
  37 + deviceDifferenceAbs.setTo(cv::Scalar::all(0));
  38 +
  39 + deviceSurfaceLayers.create(frameSize, CV_8UC1);
  40 + deviceSurfaceLayers.setTo(cv::Scalar::all(0));
  41 +}
  42 +
  43 +void SurfaceLayerDetectionAlgorithm::handleFrame(const cv::Mat &hostFrame,
  44 + unsigned long timestamp) {
  45 + currentTimestamp = timestamp;
  46 + deviceFrame.upload(hostFrame);
  47 +
  48 + /* Apply median filter */
  49 + medianFilter->apply(deviceFrame, deviceFiltered);
  50 + /* Apply detection PFC mask */
  51 + cv::cuda::bitwise_and(deviceFiltered, deviceFiltered, deviceInput,
  52 + deviceRegionMask);
  53 +
  54 + /* Trim noise at minimum temperature */
  55 + cv::cuda::max(deviceInput, deviceMinTemperature, deviceInput);
  56 +
  57 + /* Convert to float */
  58 + deviceInput.convertTo(deviceFloatInput, CV_32F);
  59 + /* Calculate normalized derivative of the temperature evolution */
  60 + normalizedDerivative(); /* Result in deviceDifference & deviceDifferenceAbs */
  61 +
  62 + /* Threshold rapidly evolving pixels */
  63 + cv::cuda::compare(deviceDifferenceAbs, derivativeThreshold, deviceResult,
  64 + cv::CMP_GE);
  65 + /* Merge surface layers */
  66 + cv::cuda::bitwise_or(deviceResult, deviceSurfaceLayers,
  67 + deviceSurfaceLayers);
  68 +}
  69 +
  70 +void SurfaceLayerDetectionAlgorithm::normalizedDerivative() {
  71 + if (devicePreviousPreviousFrame.empty()) {
  72 + previousPreviousTimestamp = currentTimestamp;
  73 + deviceFloatInput.copyTo(devicePreviousPreviousFrame);
  74 + return;
  75 + }
  76 + if (devicePreviousFrame.empty()) {
  77 + previousTimestamp = currentTimestamp;
  78 + deviceFloatInput.copyTo(devicePreviousFrame);
  79 + return;
  80 + }
  81 +
  82 + cv::cuda::subtract(deviceFloatInput, devicePreviousPreviousFrame,
  83 + deviceDifference);
  84 + cv::cuda::multiply(
  85 + deviceDifference,
  86 + cv::Scalar::all((currentTimestamp - previousPreviousTimestamp) *
  87 + NS_TO_S),
  88 + deviceDifference);
  89 + cv::cuda::divide(deviceDifference, devicePreviousFrame, deviceDifference);
  90 + cv::cuda::abs(deviceDifference, deviceDifferenceAbs);
  91 +
  92 + previousPreviousTimestamp = previousTimestamp;
  93 + previousTimestamp = currentTimestamp;
  94 +
  95 + devicePreviousFrame.copyTo(devicePreviousPreviousFrame);
  96 + deviceFloatInput.copyTo(devicePreviousFrame);
  97 +}
  98 +
  99 +std::unique_ptr<cv::cuda::Filter>
  100 +SurfaceLayerDetectionAlgorithm::createMedianFilter(int kernel) const {
  101 + if (kernel == 3) {
  102 + /* Implementation optimised for the small kernel size */
  103 + return std::make_unique<MedianFilter3x3<ushort>>(currentFrameSize);
  104 + }
  105 + return CudaFilter::createMedianFilter16U(currentFrameSize, kernel);
  106 +}
0 107 \ No newline at end of file
... ...
src/algorithm/gpu/surfacelayer/SurfaceLayerDetectionAlgorithm.hpp 0 → 100644
  1 +++ a/src/algorithm/gpu/surfacelayer/SurfaceLayerDetectionAlgorithm.hpp
  1 +#pragma once
  2 +
  3 +#include <opencv2/cudaarithm.hpp>
  4 +#include <opencv2/cudafilters.hpp>
  5 +
  6 +#include "algorithm/Algorithm.hpp"
  7 +#include "algorithm/gpu/common/ContinuousGpuMat.hpp"
  8 +
  9 +class SurfaceLayerDetectionAlgorithm : public Algorithm {
  10 + public:
  11 + /* Configurable parameters */
  12 + std::unique_ptr<cv::cuda::Filter> medianFilter;
  13 + cv::Scalar derivativeThreshold{7};
  14 + cv::cuda::GpuMat deviceMinTemperature;
  15 +
  16 + std::unique_ptr<cv::cuda::Filter> createMedianFilter(int kernel) const;
  17 + cv::cuda::GpuMat setMinimumTemperature(ushort temperature);
  18 + /* Configurable parameters */
  19 +
  20 + SurfaceLayerDetectionAlgorithm() = default;
  21 +
  22 + void setup(const cv::Size &frameSize, const CVMatLoader &loader) override;
  23 + void handleFrame(const cv::Mat &hostFrame,
  24 + unsigned long timestamp) override;
  25 +
  26 + cv::cuda::GpuMat deviceSurfaceLayers; /* Surface layers mask */
  27 +
  28 + private:
  29 + /* ns to s and invert to multiply instead of divide */
  30 + static constexpr double NS_TO_S{1e-5};
  31 +
  32 + cv::Size currentFrameSize;
  33 +
  34 + cv::cuda::GpuMat deviceRegionMask;
  35 +
  36 + cv::cuda::GpuMat devicePreviousPreviousFrame, devicePreviousFrame;
  37 + unsigned long previousPreviousTimestamp{}, previousTimestamp{},
  38 + currentTimestamp{};
  39 +
  40 + void normalizedDerivative();
  41 +
  42 + /* Buffors */
  43 + std::unique_ptr<ContinuousGpuMat> medianFilterInput, medianFilterOutput;
  44 + cv::cuda::GpuMat deviceFrame, deviceFiltered, deviceInput, deviceFloatInput,
  45 + deviceDifference, deviceDifferenceAbs, deviceResult;
  46 + /* Buffors */
  47 +};
... ...
src/benchmark/benchmark.hpp 0 → 100644
  1 +++ a/src/benchmark/benchmark.hpp
  1 +#pragma once
  2 +
  3 +#include <iostream>
  4 +#include <memory>
  5 +
  6 +#include <opencv2/core.hpp>
  7 +
  8 +#include "data/hdf5/CVMatLoader.hpp"
  9 +#include "data/provider/H5FrameProvider.hpp"
  10 +
  11 +namespace Benchmark {
  12 +std::unique_ptr<H5FrameProvider>
  13 +createFrameProvider(const std::string &filePath = "20171114.053_AEF20.h5") {
  14 + const auto timestamps =
  15 + CVMatLoader(ResourceLocator::getPathProvider().path(filePath))
  16 + .asVec<unsigned long>("/timestamps", H5SingleDataReader::UINT64);
  17 + return std::make_unique<H5FrameProvider>(
  18 + ResourceLocator::getPathProvider().path(filePath), "/images",
  19 + timestamps);
  20 +}
  21 +
  22 +void displayStats(const std::vector<double> &timings) {
  23 + cv::Scalar mean, std;
  24 + cv::meanStdDev(timings, mean, std);
  25 +
  26 + double min{-1}, max{-1};
  27 + cv::minMaxIdx(timings, &min, &max, nullptr, nullptr);
  28 +
  29 + std::cout << "Mean: " << mean[0] << " Std Dev.: " << std[0]
  30 + << " Min: " << min << " Max: " << max << '\n';
  31 +}
  32 +} // namespace Benchmark
0 33 \ No newline at end of file
... ...
src/benchmark/median_filter_benchmark.hpp 0 → 100644
  1 +++ a/src/benchmark/median_filter_benchmark.hpp
  1 +#pragma once
  2 +
  3 +#include "benchmark.hpp"
  4 +
  5 +#include <opencv2/cudaarithm.hpp>
  6 +#include <opencv2/cudafilters.hpp>
  7 +#include <opencv2/imgcodecs.hpp>
  8 +
  9 +#include "algorithm/gpu/common/ContinuousGpuMat.hpp"
  10 +#include "algorithm/gpu/common/MedianFilter.hpp"
  11 +#include "algorithm/gpu/common/MedianFilter3x3.hpp"
  12 +
  13 +namespace MedianFilterBenchmark {
  14 +
  15 +template <std::size_t iters>
  16 +std::vector<double> benchmarkFilter(cv::cuda::Filter &filter,
  17 + const cv::cuda::GpuMat input,
  18 + const std::string &name) {
  19 + std::vector<double> timings;
  20 + timings.reserve(iters);
  21 +
  22 + cv::cuda::GpuMat output(input.size(), input.type());
  23 + /* Warm-up runs */
  24 + filter.apply(input, output);
  25 + filter.apply(input, output);
  26 + filter.apply(input, output);
  27 +
  28 + for (std::size_t i = 0; i < iters; ++i) {
  29 + const auto start = static_cast<double>(cv::getTickCount());
  30 + filter.apply(input, output);
  31 + const auto timeMs = 1e3 *
  32 + (static_cast<double>(cv::getTickCount()) - start) /
  33 + cv::getTickFrequency();
  34 + timings.push_back(timeMs);
  35 + }
  36 +
  37 + cv::imwrite(name + ".bmp", cv::Mat(output));
  38 + return timings;
  39 +}
  40 +
  41 +void run() {
  42 + constexpr auto iterations{100};
  43 +
  44 + cv::Mat frame(cv::Size(1024, 768), CV_16UC1);
  45 + Benchmark::createFrameProvider()->next(frame.data);
  46 +
  47 + ContinuousGpuMat deviceFrame8bit(cv::Size(1024, 768), CV_16UC1);
  48 + cv::cuda::normalize(cv::cuda::GpuMat(frame), deviceFrame8bit.device(), 0,
  49 + 255, cv::NORM_MINMAX, CV_8UC1);
  50 +
  51 + {
  52 + auto opencvFilter = cv::cuda::createMedianFilter(CV_8UC1, 3);
  53 + std::cout << "OpenCV 3x3 8-bit\n:";
  54 + Benchmark::displayStats(benchmarkFilter<iterations>(
  55 + *opencvFilter, deviceFrame8bit.device(), "opencv"));
  56 + }
  57 +
  58 + {
  59 + auto nppFilter = CudaFilter::createMedianFilter8U(frame.size(), 3);
  60 + std::cout << "NPP 3x3 8-bit\n:";
  61 + Benchmark::displayStats(benchmarkFilter<iterations>(
  62 + *nppFilter, deviceFrame8bit.device(), "npp"));
  63 + }
  64 +
  65 + {
  66 + auto cudaKernelFilter =
  67 + std::make_unique<MedianFilter3x3<uchar>>(frame.size());
  68 + std::cout << "CUDA kernel 3x3 8-bit\n:";
  69 + Benchmark::displayStats(benchmarkFilter<iterations>(
  70 + *cudaKernelFilter, deviceFrame8bit.device(), "cuda_kernel"));
  71 + }
  72 +}
  73 +} // namespace MedianFilterBenchmark
0 74 \ No newline at end of file
... ...
src/benchmark/thermal_event_detection_benchmark.hpp 0 → 100644
  1 +++ a/src/benchmark/thermal_event_detection_benchmark.hpp
  1 +#pragma once
  2 +
  3 +#include "benchmark.hpp"
  4 +
  5 +#include "data/ResourceLocator.hpp"
  6 +
  7 +#include "algorithm/gpu/hotspot/OverloadHotspotDetectionAlgorithm.hpp"
  8 +#include "algorithm/gpu/surfacelayer/SurfaceLayerDetectionAlgorithm.hpp"
  9 +/* CPU implementations are in Cpu:: namespace */
  10 +#include "algorithm/cpu/hotspot/OverloadHotspotDetectionAlgorithm.hpp"
  11 +#include "algorithm/cpu/surfacelayer/SurfaceLayerDetectionAlgorithm.hpp"
  12 +
  13 +namespace ThermalEventDetectionBenchmark {
  14 +
  15 +template <typename T, std::size_t firstFrame, std::size_t lastFrame>
  16 +std::vector<double> benchmarkAlgorithm(cv::Mat frame) {
  17 + std::vector<double> timings;
  18 + timings.reserve(lastFrame - firstFrame + 1);
  19 +
  20 + const CVMatLoader loader(
  21 + ResourceLocator::getPathProvider().path("20171114.053_AEF20.h5"));
  22 +
  23 + T algorithm{};
  24 + algorithm.setup(frame.size(), loader);
  25 +
  26 + auto provider = Benchmark::createFrameProvider();
  27 + for (std::size_t i = 0; provider->hasNext() && i <= lastFrame; ++i) {
  28 + const auto timestamp = provider->next(frame.data);
  29 + const auto start = static_cast<double>(cv::getTickCount());
  30 + algorithm.handleFrame(frame, timestamp);
  31 + const auto timeMs = 1e3 *
  32 + (static_cast<double>(cv::getTickCount()) - start) /
  33 + cv::getTickFrequency();
  34 + if (i >= firstFrame) {
  35 + timings.push_back(timeMs);
  36 + }
  37 + }
  38 +
  39 + return timings;
  40 +}
  41 +
  42 +template <typename T, std::size_t firstFrame, std::size_t lastFrame,
  43 + std::size_t iters>
  44 +std::vector<double> benchmarkAlgorithmN(cv::Mat frame) {
  45 + std::vector<double> timings;
  46 + timings.reserve(iters);
  47 +
  48 + for (std::size_t i = 0; i < iters; ++i) {
  49 + timings.push_back(
  50 + cv::mean(benchmarkAlgorithm<T, firstFrame, lastFrame>(frame))[0]);
  51 + }
  52 +
  53 + return timings;
  54 +}
  55 +
  56 +template <typename T, std::size_t firstFrame, std::size_t lastFrame,
  57 + std::size_t iters>
  58 +std::vector<double> benchmarkGpu() {
  59 + /* Page-locked memory */
  60 + void *data{nullptr};
  61 + cudaSetDeviceFlags(cudaDeviceMapHost);
  62 + cudaHostAlloc(&data, 1024 * 768 * cv::getElemSize(CV_16UC1),
  63 + cudaHostAllocMapped);
  64 + const cv::Mat frame(cv::Size(1024, 768), CV_16UC1, data);
  65 +
  66 + auto timings = benchmarkAlgorithmN<T, firstFrame, lastFrame, iters>(frame);
  67 + cudaFreeHost(data);
  68 +
  69 + return timings;
  70 +}
  71 +
  72 +template <typename T, std::size_t firstFrame, std::size_t lastFrame,
  73 + std::size_t iters>
  74 +std::vector<double> benchmarkCpu() {
  75 + const cv::Mat frame(cv::Size(1024, 768), CV_16UC1);
  76 + return benchmarkAlgorithmN<T, firstFrame, lastFrame, iters>(frame);
  77 +}
  78 +
  79 +void run() {
  80 + constexpr auto iterations{20};
  81 +
  82 + std::cout << "GPU Overload Hotspot Detection Algorithm\n";
  83 + Benchmark::displayStats(benchmarkGpu<OverloadHotspotDetectionAlgorithm, 239,
  84 + 258, iterations>());
  85 + std::cout << "GPU Surface Layer Detection Algorithm\n";
  86 + Benchmark::displayStats(
  87 + benchmarkGpu<SurfaceLayerDetectionAlgorithm, 16, 41, iterations>());
  88 + std::cout << "CPU Overload Hotspot Detection Algorithm\n";
  89 + Benchmark::displayStats(benchmarkCpu<Cpu::OverloadHotspotDetectionAlgorithm,
  90 + 239, 258, iterations>());
  91 + std::cout << "CPU Surface Layer Detection Algorithm\n";
  92 + Benchmark::displayStats(benchmarkCpu<Cpu::SurfaceLayerDetectionAlgorithm,
  93 + 16, 41, iterations>());
  94 +}
  95 +
  96 +} // namespace ThermalEventDetectionBenchmark
0 97 \ No newline at end of file
... ...
src/data/DataNotFoundException.hpp 0 → 100644
  1 +++ a/src/data/DataNotFoundException.hpp
  1 +#pragma once
  2 +
  3 +#include <exception>
  4 +#include <string>
  5 +
  6 +class DataNotFoundException : public std::runtime_error {
  7 + public:
  8 + explicit DataNotFoundException(const std::string &what)
  9 + : std::runtime_error(what) {}
  10 +};
0 11 \ No newline at end of file
... ...
src/data/ResourceLocator.cpp 0 → 100644
  1 +++ a/src/data/ResourceLocator.cpp
  1 +#include "ResourceLocator.hpp"
  2 +
  3 +std::unique_ptr<PathProvider> ResourceLocator::pathProvider{nullptr};
0 4 \ No newline at end of file
... ...
src/data/ResourceLocator.hpp 0 → 100644
  1 +++ a/src/data/ResourceLocator.hpp
  1 +#pragma once
  2 +
  3 +#include <exception>
  4 +#include <memory>
  5 +#include <string>
  6 +
  7 +class PathProvider {
  8 + public:
  9 + PathProvider() = default;
  10 + PathProvider(const PathProvider &) = default;
  11 + PathProvider(PathProvider &&) noexcept = default;
  12 + PathProvider &operator=(PathProvider &&) noexcept = default;
  13 + PathProvider &operator=(const PathProvider &) = default;
  14 + virtual ~PathProvider() = default;
  15 +
  16 + virtual std::string path(const std::string &file) const = 0;
  17 +};
  18 +
  19 +class AbsolutePathProvider : public PathProvider {
  20 + public:
  21 + explicit AbsolutePathProvider(const std::string &relativePath)
  22 + : relativePath(relativePath) {}
  23 +
  24 + std::string path(const std::string &file) const override {
  25 + return relativePath + file;
  26 + }
  27 +
  28 + private:
  29 + const std::string relativePath;
  30 +};
  31 +
  32 +class ResourceLocator {
  33 + public:
  34 + static const PathProvider &getPathProvider() {
  35 + if (!pathProvider) {
  36 + throw std::runtime_error("Path provider not provided");
  37 + }
  38 + return *pathProvider;
  39 + }
  40 +
  41 + static void setPathProvider(std::unique_ptr<PathProvider> provider) {
  42 + pathProvider = std::move(provider);
  43 + }
  44 +
  45 + private:
  46 + static std::unique_ptr<PathProvider> pathProvider;
  47 +};
0 48 \ No newline at end of file
... ...
src/data/hdf5/CVMatLoader.hpp 0 → 100644
  1 +++ a/src/data/hdf5/CVMatLoader.hpp
  1 +#pragma once
  2 +
  3 +#include <opencv2/core/mat.hpp>
  4 +
  5 +#include "data/DataNotFoundException.hpp"
  6 +#include "data/hdf5/H5SingleDataReader.hpp"
  7 +
  8 +class CVMatLoader {
  9 + public:
  10 + explicit CVMatLoader(const std::string &path) : path(path) {}
  11 +
  12 + cv::Mat asMat(const std::string &sourceDataSet, int destinationType) const {
  13 + const auto reader =
  14 + createReader(sourceDataSet, resolveType(destinationType), 2);
  15 +
  16 + cv::Mat output(cv::Size{static_cast<int>(reader.dimensions[1]),
  17 + static_cast<int>(reader.dimensions[0])},
  18 + destinationType);
  19 + assert(output.isContinuous());
  20 +
  21 + reader.read(output.data);
  22 + return output;
  23 + }
  24 +
  25 + template <typename T>
  26 + std::vector<T> asVec(const std::string &sourceDataSet,
  27 + const H5::PredType &sourceType) const {
  28 + const auto reader = createReader(sourceDataSet, sourceType, 1);
  29 +
  30 + std::vector<T> output(reader.size());
  31 + reader.read(output.data());
  32 +
  33 + return output;
  34 + }
  35 +
  36 + private:
  37 + const std::string path;
  38 +
  39 + H5SingleDataReader createReader(const std::string &sourceDataSet,
  40 + const H5::PredType &sourceType,
  41 + int dimensions) const {
  42 + try {
  43 + return {path, sourceDataSet, sourceType, dimensions};
  44 + } catch (const H5::FileIException &exception) {
  45 + throw DataNotFoundException(exception.getDetailMsg());
  46 + }
  47 + }
  48 +
  49 + static const H5::PredType &resolveType(int cvType) {
  50 + switch (CV_MAT_DEPTH(cvType)) {
  51 + case CV_8U:
  52 + return H5SingleDataReader::UINT8;
  53 + case CV_16U:
  54 + return H5SingleDataReader::UINT16;
  55 + case CV_8S:
  56 + return H5SingleDataReader::INT8;
  57 + case CV_16S:
  58 + return H5SingleDataReader::INT16;
  59 + case CV_32S:
  60 + return H5SingleDataReader::INT32;
  61 + case CV_32F:
  62 + return H5SingleDataReader::FLOAT;
  63 + case CV_64F:
  64 + return H5SingleDataReader::DOUBLE;
  65 + default:
  66 + throw std::runtime_error("Unknown OpenCV type " +
  67 + std::to_string(cvType));
  68 + }
  69 + }
  70 +};
... ...
src/data/hdf5/H5SingleDataReader.cpp 0 → 100644
  1 +++ a/src/data/hdf5/H5SingleDataReader.cpp
  1 +#include "H5SingleDataReader.hpp"
  2 +
  3 +const H5::PredType H5SingleDataReader::UINT8 = H5::PredType::NATIVE_UINT8;
  4 +const H5::PredType H5SingleDataReader::UINT16 = H5::PredType::NATIVE_UINT16;
  5 +const H5::PredType H5SingleDataReader::UINT32 = H5::PredType::NATIVE_UINT32;
  6 +const H5::PredType H5SingleDataReader::UINT64 = H5::PredType::NATIVE_UINT64;
  7 +const H5::PredType H5SingleDataReader::INT8 = H5::PredType::NATIVE_INT8;
  8 +const H5::PredType H5SingleDataReader::INT16 = H5::PredType::NATIVE_INT16;
  9 +const H5::PredType H5SingleDataReader::INT32 = H5::PredType::NATIVE_INT32;
  10 +const H5::PredType H5SingleDataReader::INT64 = H5::PredType::NATIVE_INT64;
  11 +const H5::PredType H5SingleDataReader::FLOAT = H5::PredType::NATIVE_FLOAT;
  12 +const H5::PredType H5SingleDataReader::DOUBLE = H5::PredType::NATIVE_DOUBLE;
  13 +
  14 +std::mutex H5SingleDataReader::lock{};
0 15 \ No newline at end of file
... ...
src/data/hdf5/H5SingleDataReader.hpp 0 → 100644
  1 +++ a/src/data/hdf5/H5SingleDataReader.hpp
  1 +#pragma once
  2 +
  3 +#include <algorithm>
  4 +#include <array>
  5 +#include <cassert>
  6 +#include <memory>
  7 +#include <mutex>
  8 +#include <numeric>
  9 +#include <string>
  10 +
  11 +#include <H5Cpp.h>
  12 +
  13 +/*
  14 + * HDF5 C++ API doesn't support multiple threads, even instantiation of types
  15 + * since it uses global structres.
  16 + *
  17 + * Thus, to avoid deafult construction of member H5 types, they are stored using
  18 + * not owning pointers.
  19 + */
  20 +class H5SingleDataReader {
  21 + public:
  22 + H5SingleDataReader(const H5SingleDataReader &) = delete;
  23 + H5SingleDataReader &operator=(const H5SingleDataReader &) = delete;
  24 +
  25 + H5SingleDataReader(H5SingleDataReader &&) noexcept = default;
  26 + H5SingleDataReader &operator=(H5SingleDataReader &&) noexcept = default;
  27 +
  28 + std::array<hsize_t, 3> dimensions{0, 0, 0};
  29 +
  30 + H5SingleDataReader(const std::string &path, const std::string &dataSetPath,
  31 + const H5::PredType &dataType, int outputDimension) {
  32 + std::lock_guard<std::mutex> guard(lock);
  33 +
  34 + type.reset(assign(dataType));
  35 +
  36 + const H5::H5File file(path, H5F_ACC_RDONLY);
  37 + dataSet.reset(assign(file.openDataSet(dataSetPath)));
  38 + fileSpace.reset(assign(dataSet->getSpace()));
  39 +
  40 + [[maybe_unused]] const auto rank =
  41 + fileSpace->getSimpleExtentDims(dimensions.data(), nullptr);
  42 + assert(outputDimension <= rank);
  43 +
  44 + const std::array<hsize_t, 1> memoryDimensions{std::accumulate(
  45 + dimensions.cbegin(), dimensions.cbegin() + outputDimension, 1LLU,
  46 + std::multiplies<>())};
  47 + const std::array<hsize_t, 1> memoryOffset{0};
  48 + memorySpace.reset(assign(H5::DataSpace(1, memoryDimensions.data())));
  49 + memorySpace->selectHyperslab(H5S_SELECT_SET, memoryDimensions.data(),
  50 + memoryOffset.data());
  51 +
  52 + totalSize = memorySpace->getSelectNpoints();
  53 + }
  54 +
  55 + hsize_t size() const { return totalSize; }
  56 +
  57 + void read(void *buffer) const {
  58 + std::lock_guard<std::mutex> guard(lock);
  59 + dataSet->read(buffer, *type, *memorySpace, *fileSpace);
  60 + }
  61 +
  62 + void select(const std::array<hsize_t, 3> selectionSize,
  63 + const std::array<hsize_t, 3> selectionOffset) const {
  64 + std::lock_guard<std::mutex> guard(lock);
  65 + fileSpace->selectHyperslab(H5S_SELECT_SET, selectionSize.data(),
  66 + selectionOffset.data());
  67 + }
  68 +
  69 + virtual ~H5SingleDataReader() {
  70 + std::lock_guard<std::mutex> guard(lock);
  71 + delete memorySpace.get();
  72 + delete fileSpace.get();
  73 + delete dataSet.get();
  74 + delete type.get();
  75 + }
  76 +
  77 + const static H5::PredType UINT8;
  78 + const static H5::PredType UINT16;
  79 + const static H5::PredType UINT32;
  80 + const static H5::PredType UINT64;
  81 + const static H5::PredType INT8;
  82 + const static H5::PredType INT16;
  83 + const static H5::PredType INT32;
  84 + const static H5::PredType INT64;
  85 + const static H5::PredType FLOAT;
  86 + const static H5::PredType DOUBLE;
  87 +
  88 + private:
  89 + static std::mutex lock;
  90 +
  91 + hsize_t totalSize;
  92 +
  93 + struct NoOp {
  94 + template <typename T> void operator()(T const &) const noexcept {}
  95 + };
  96 +
  97 + /**
  98 + * not_owning_ptr allows to use default move constructor
  99 + * and manually manage target memory
  100 + */
  101 + template <typename T> using not_owning_ptr = std::unique_ptr<T, NoOp>;
  102 +
  103 + not_owning_ptr<const H5::PredType> type;
  104 + not_owning_ptr<const H5::DataSet> dataSet;
  105 + not_owning_ptr<const H5::DataSpace> fileSpace;
  106 + not_owning_ptr<const H5::DataSpace> memorySpace;
  107 +
  108 + template <typename T> static const T *assign(const T &value) {
  109 + T *const result = new T(value);
  110 + *result = value;
  111 + return result;
  112 + }
  113 +};
... ...
src/data/provider/FrameProvider.hpp 0 → 100644
  1 +++ a/src/data/provider/FrameProvider.hpp
  1 +#pragma once
  2 +
  3 +class FrameProvider {
  4 + public:
  5 + FrameProvider() = default;
  6 + FrameProvider(const FrameProvider &) = default;
  7 + FrameProvider(FrameProvider &&) noexcept = default;
  8 + FrameProvider &operator=(FrameProvider &&) noexcept = default;
  9 + FrameProvider &operator=(const FrameProvider &) = default;
  10 +
  11 + virtual unsigned long next(void *buffer) = 0;
  12 +
  13 + virtual bool hasNext() const = 0;
  14 +
  15 + virtual size_t rows() const = 0;
  16 +
  17 + virtual size_t columns() const = 0;
  18 +
  19 + virtual ~FrameProvider() = default;
  20 +};
... ...
src/data/provider/H5FrameProvider.hpp 0 → 100644
  1 +++ a/src/data/provider/H5FrameProvider.hpp
  1 +
  2 +#pragma once
  3 +
  4 +#include <array>
  5 +
  6 +#include "data/hdf5/H5SingleDataReader.hpp"
  7 +#include "data/provider/FrameProvider.hpp"
  8 +#include "data/provider/ProviderOpenFailureException.hpp"
  9 +
  10 +class H5FrameProvider : public FrameProvider, public H5SingleDataReader {
  11 + public:
  12 + H5FrameProvider(const std::string &path, const std::string &dataSetPath,
  13 + const std::vector<unsigned long> &timestamps) try
  14 + : H5SingleDataReader(path, dataSetPath, H5SingleDataReader::UINT16, 2),
  15 + timestamps(timestamps) {
  16 + } catch (const H5::FileIException &exception) {
  17 + throw ProviderOpenFailureException(exception.getDetailMsg());
  18 + }
  19 +
  20 + H5FrameProvider(const std::string &path, const std::string &dataSetPath) try
  21 + : H5SingleDataReader(path, dataSetPath, H5SingleDataReader::UINT16, 2),
  22 + timestamps(std::vector<unsigned long>(dimensions[2], 0)) {
  23 + } catch (const H5::FileIException &exception) {
  24 + throw ProviderOpenFailureException(exception.getDetailMsg());
  25 + }
  26 +
  27 + bool hasNext() const override {
  28 + return current < dimensions[2] && current <= lastFrame;
  29 + }
  30 +
  31 + unsigned long next(void *buffer) override {
  32 + selectImage(current);
  33 + read(buffer);
  34 + return timestamps[current++];
  35 + }
  36 +
  37 + void first(void *buffer) const {
  38 + selectImage(0);
  39 + read(buffer);
  40 + }
  41 +
  42 + size_t rows() const override { return dimensions[0]; }
  43 +
  44 + size_t columns() const override { return dimensions[1]; }
  45 +
  46 + private:
  47 + hsize_t current = 0; // 239 - 5; /* Modify to start from further frame */
  48 +
  49 + static constexpr hsize_t lastFrame{258}; /* Peak */
  50 + // static constexpr hsize_t lastFrame{41}; /* Rise */
  51 + // static constexpr hsize_t lastFrame{9999}; /* Entire */
  52 +
  53 + const std::vector<unsigned long> timestamps;
  54 +
  55 + void selectImage(hsize_t image) const {
  56 + select({dimensions[0], dimensions[1], 1}, {0, 0, image});
  57 + }
  58 +};
0 59 \ No newline at end of file
... ...
src/data/provider/ProviderOpenFailureException.hpp 0 → 100644
  1 +++ a/src/data/provider/ProviderOpenFailureException.hpp
  1 +#pragma once
  2 +
  3 +#include "data/DataNotFoundException.hpp"
  4 +
  5 +class ProviderOpenFailureException : public DataNotFoundException {
  6 + public:
  7 + explicit ProviderOpenFailureException(const std::string &what)
  8 + : DataNotFoundException(what) {}
  9 +};
0 10 \ No newline at end of file
... ...
src/main.cpp 0 → 100644
  1 +++ a/src/main.cpp
  1 +#include "data/ResourceLocator.hpp"
  2 +
  3 +#include "benchmark/median_filter_benchmark.hpp"
  4 +#include "benchmark/thermal_event_detection_benchmark.hpp"
  5 +
  6 +int main(int, char **) {
  7 + ResourceLocator::setPathProvider(
  8 + std::make_unique<AbsolutePathProvider>("resource/"));
  9 +
  10 + ThermalEventDetectionBenchmark::run();
  11 + MedianFilterBenchmark::run();
  12 +
  13 + return 0;
  14 +}
0 15 \ No newline at end of file
... ...