Compare commits
4 Commits
d9f1b35fd2
...
andrea-alg
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
876b8f4592 | ||
|
|
ec2027e980 | ||
|
|
69b47623f8 | ||
|
|
f5c1e317e8 |
7
.agents/rules/micromamba.md
Normal file
7
.agents/rules/micromamba.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
trigger: always_on
|
||||||
|
---
|
||||||
|
|
||||||
|
build in build directory using always micromamba "mutom" env.
|
||||||
|
build with make flag -j$(nproc).
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ endif()
|
|||||||
project(uLib)
|
project(uLib)
|
||||||
|
|
||||||
# CUDA Toolkit seems to be missing locally. Toggle ON if nvcc is made available.
|
# CUDA Toolkit seems to be missing locally. Toggle ON if nvcc is made available.
|
||||||
option(USE_CUDA "Enable CUDA support" ON)
|
option(USE_CUDA "Enable CUDA support" OFF)
|
||||||
if(USE_CUDA)
|
if(USE_CUDA)
|
||||||
set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -allow-unsupported-compiler")
|
set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -allow-unsupported-compiler")
|
||||||
set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr")
|
set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --expt-relaxed-constexpr")
|
||||||
@@ -115,7 +115,7 @@ set(Boost_USE_MULTITHREADED ON)
|
|||||||
set(Boost_USE_STATIC_RUNTIME OFF)
|
set(Boost_USE_STATIC_RUNTIME OFF)
|
||||||
message(STATUS "CMAKE_PREFIX_PATH is ${CMAKE_PREFIX_PATH}")
|
message(STATUS "CMAKE_PREFIX_PATH is ${CMAKE_PREFIX_PATH}")
|
||||||
|
|
||||||
find_package(HDF5 REQUIRED CONFIG)
|
find_package(HDF5 REQUIRED)
|
||||||
|
|
||||||
find_package(Boost 1.45.0 COMPONENTS program_options serialization unit_test_framework REQUIRED)
|
find_package(Boost 1.45.0 COMPONENTS program_options serialization unit_test_framework REQUIRED)
|
||||||
include_directories(${Boost_INCLUDE_DIRS})
|
include_directories(${Boost_INCLUDE_DIRS})
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "Vtk/uLibVtkInterface.h"
|
#include "Vtk/uLibVtkInterface.h"
|
||||||
#include "Math/Units.h"
|
#include "Math/Units.h"
|
||||||
#include "Math/Dense.h"
|
#include "Math/Dense.h"
|
||||||
|
#include "Settings.h"
|
||||||
|
|
||||||
namespace uLib {
|
namespace uLib {
|
||||||
namespace Qt {
|
namespace Qt {
|
||||||
@@ -15,8 +16,21 @@ PropertyWidgetBase::PropertyWidgetBase(PropertyBase* prop, QWidget* parent)
|
|||||||
: QWidget(parent), m_BaseProperty(prop) {
|
: QWidget(parent), m_BaseProperty(prop) {
|
||||||
m_Layout = new QHBoxLayout(this);
|
m_Layout = new QHBoxLayout(this);
|
||||||
m_Layout->setContentsMargins(4, 2, 4, 2);
|
m_Layout->setContentsMargins(4, 2, 4, 2);
|
||||||
m_Label = new QLabel(QString::fromStdString(prop->GetName()), this);
|
|
||||||
m_Label->setMinimumWidth(100);
|
std::string unit = prop->GetUnits();
|
||||||
|
QString labelText = QString::fromStdString(prop->GetName());
|
||||||
|
if (!unit.empty()) {
|
||||||
|
auto dim = Settings::Instance().IdentifyDimension(unit);
|
||||||
|
std::string pref = Settings::Instance().GetPreferredUnit(dim);
|
||||||
|
if (!pref.empty()) {
|
||||||
|
labelText += " [" + QString::fromStdString(pref) + "]";
|
||||||
|
} else {
|
||||||
|
labelText += " [" + QString::fromStdString(unit) + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Label = new QLabel(labelText, this);
|
||||||
|
m_Label->setMinimumWidth(120);
|
||||||
m_Layout->addWidget(m_Label);
|
m_Layout->addWidget(m_Label);
|
||||||
}
|
}
|
||||||
PropertyWidgetBase::~PropertyWidgetBase() {
|
PropertyWidgetBase::~PropertyWidgetBase() {
|
||||||
@@ -115,9 +129,6 @@ void UnitLineEdit::updateText() {
|
|||||||
s += ".0";
|
s += ".0";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!m_Suffix.isEmpty()) {
|
|
||||||
s += " " + m_Suffix;
|
|
||||||
}
|
|
||||||
setText(s);
|
setText(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,11 +140,12 @@ void UnitLineEdit::setIntegerOnly(bool integerOnly) {
|
|||||||
DoublePropertyWidget::DoublePropertyWidget(Property<double>* prop, QWidget* parent)
|
DoublePropertyWidget::DoublePropertyWidget(Property<double>* prop, QWidget* parent)
|
||||||
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
|
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
|
||||||
m_Edit = new UnitLineEdit(this);
|
m_Edit = new UnitLineEdit(this);
|
||||||
QString units = QString::fromStdString(prop->GetUnits());
|
std::string unit = prop->GetUnits();
|
||||||
if (!units.isEmpty()) {
|
if (!unit.empty()) {
|
||||||
double factor = 1.0;
|
auto dim = Settings::Instance().IdentifyDimension(unit);
|
||||||
parseWithUnits("1 " + units, &factor);
|
std::string pref = Settings::Instance().GetPreferredUnit(dim);
|
||||||
m_Edit->setUnits(units, factor);
|
double factor = Settings::Instance().GetUnitFactor(pref);
|
||||||
|
m_Edit->setUnits(QString::fromStdString(pref), factor);
|
||||||
}
|
}
|
||||||
m_Edit->setValue(prop->Get());
|
m_Edit->setValue(prop->Get());
|
||||||
m_Layout->addWidget(m_Edit, 1);
|
m_Layout->addWidget(m_Edit, 1);
|
||||||
@@ -146,11 +158,12 @@ DoublePropertyWidget::DoublePropertyWidget(Property<double>* prop, QWidget* pare
|
|||||||
FloatPropertyWidget::FloatPropertyWidget(Property<float>* prop, QWidget* parent)
|
FloatPropertyWidget::FloatPropertyWidget(Property<float>* prop, QWidget* parent)
|
||||||
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
|
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
|
||||||
m_Edit = new UnitLineEdit(this);
|
m_Edit = new UnitLineEdit(this);
|
||||||
QString units = QString::fromStdString(prop->GetUnits());
|
std::string unit = prop->GetUnits();
|
||||||
if (!units.isEmpty()) {
|
if (!unit.empty()) {
|
||||||
double factor = 1.0;
|
auto dim = Settings::Instance().IdentifyDimension(unit);
|
||||||
parseWithUnits("1 " + units, &factor);
|
std::string pref = Settings::Instance().GetPreferredUnit(dim);
|
||||||
m_Edit->setUnits(units, factor);
|
double factor = Settings::Instance().GetUnitFactor(pref);
|
||||||
|
m_Edit->setUnits(QString::fromStdString(pref), factor);
|
||||||
}
|
}
|
||||||
m_Edit->setValue(prop->Get());
|
m_Edit->setValue(prop->Get());
|
||||||
m_Layout->addWidget(m_Edit, 1);
|
m_Layout->addWidget(m_Edit, 1);
|
||||||
@@ -164,11 +177,12 @@ IntPropertyWidget::IntPropertyWidget(Property<int>* prop, QWidget* parent)
|
|||||||
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
|
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
|
||||||
m_Edit = new UnitLineEdit(this);
|
m_Edit = new UnitLineEdit(this);
|
||||||
m_Edit->setIntegerOnly(true);
|
m_Edit->setIntegerOnly(true);
|
||||||
QString units = QString::fromStdString(prop->GetUnits());
|
std::string unit = prop->GetUnits();
|
||||||
if (!units.isEmpty()) {
|
if (!unit.empty()) {
|
||||||
double factor = 1.0;
|
auto dim = Settings::Instance().IdentifyDimension(unit);
|
||||||
parseWithUnits("1 " + units, &factor);
|
std::string pref = Settings::Instance().GetPreferredUnit(dim);
|
||||||
m_Edit->setUnits(units, factor);
|
double factor = Settings::Instance().GetUnitFactor(pref);
|
||||||
|
m_Edit->setUnits(QString::fromStdString(pref), factor);
|
||||||
}
|
}
|
||||||
m_Edit->setValue(prop->Get());
|
m_Edit->setValue(prop->Get());
|
||||||
m_Layout->addWidget(m_Edit, 1);
|
m_Layout->addWidget(m_Edit, 1);
|
||||||
@@ -211,6 +225,26 @@ StringPropertyWidget::StringPropertyWidget(Property<std::string>* prop, QWidget*
|
|||||||
}
|
}
|
||||||
StringPropertyWidget::~StringPropertyWidget() {}
|
StringPropertyWidget::~StringPropertyWidget() {}
|
||||||
|
|
||||||
|
class GroupHeaderWidget : public QWidget {
|
||||||
|
public:
|
||||||
|
GroupHeaderWidget(const QString& name, QWidget* parent = nullptr) : QWidget(parent) {
|
||||||
|
auto* layout = new QVBoxLayout(this);
|
||||||
|
layout->setContentsMargins(0, 8, 0, 4);
|
||||||
|
auto* line = new QFrame(this);
|
||||||
|
line->setFrameShape(QFrame::HLine);
|
||||||
|
line->setFrameShadow(QFrame::Sunken);
|
||||||
|
line->setStyleSheet("color: #555;");
|
||||||
|
layout->addWidget(line);
|
||||||
|
auto* label = new QLabel(name, this);
|
||||||
|
QFont font = label->font();
|
||||||
|
font.setBold(true);
|
||||||
|
font.setPointSize(font.pointSize() + 1);
|
||||||
|
label->setFont(font);
|
||||||
|
label->setStyleSheet("color: #aaa; text-transform: uppercase;");
|
||||||
|
layout->addWidget(label);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class EnumPropertyWidget : public PropertyWidgetBase {
|
class EnumPropertyWidget : public PropertyWidgetBase {
|
||||||
PropertyBase* m_Prop;
|
PropertyBase* m_Prop;
|
||||||
QComboBox* m_Combo;
|
QComboBox* m_Combo;
|
||||||
@@ -305,26 +339,51 @@ void PropertyEditor::setObject(::uLib::Object* obj, bool displayOnly) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Group properties by their group string
|
||||||
|
std::map<std::string, std::vector<::uLib::PropertyBase*>> groupedProps;
|
||||||
|
std::vector<std::string> groupOrder;
|
||||||
|
|
||||||
for (auto* prop : *props) {
|
for (auto* prop : *props) {
|
||||||
// Priority 1: Check if it provides enum labels
|
std::string group = prop->GetGroup();
|
||||||
if (!prop->GetEnumLabels().empty()) {
|
if (groupedProps.find(group) == groupedProps.end()) {
|
||||||
m_ContainerLayout->addWidget(new EnumPropertyWidget(prop, m_Container));
|
groupOrder.push_back(group);
|
||||||
continue;
|
}
|
||||||
|
groupedProps[group].push_back(prop);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const auto& groupName : groupOrder) {
|
||||||
|
if (!groupName.empty()) {
|
||||||
|
m_ContainerLayout->addWidget(new GroupHeaderWidget(QString::fromStdString(groupName), m_Container));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto* prop : groupedProps[groupName]) {
|
||||||
|
QWidget* widget = nullptr;
|
||||||
|
|
||||||
|
// Priority 1: Check if it provides enum labels
|
||||||
|
if (!prop->GetEnumLabels().empty()) {
|
||||||
|
widget = new EnumPropertyWidget(prop, m_Container);
|
||||||
|
} else {
|
||||||
// Priority 2: Standard factory lookup
|
// Priority 2: Standard factory lookup
|
||||||
auto it = m_Factories.find(prop->GetTypeIndex());
|
auto it = m_Factories.find(prop->GetTypeIndex());
|
||||||
if (it != m_Factories.end()) {
|
if (it != m_Factories.end()) {
|
||||||
QWidget* widget = it->second(prop, m_Container);
|
widget = it->second(prop, m_Container);
|
||||||
m_ContainerLayout->addWidget(widget);
|
|
||||||
} else {
|
} else {
|
||||||
// Debug info for unknown types
|
// Debug info for unknown types
|
||||||
std::cout << "PropertyEditor: No factory for " << prop->GetName()
|
std::cout << "PropertyEditor: No factory for " << prop->GetQualifiedName()
|
||||||
<< " (Type: " << prop->GetTypeName() << ")" << std::endl;
|
<< " (Type: " << prop->GetTypeName() << ")" << std::endl;
|
||||||
|
|
||||||
QWidget* fallback = new PropertyWidgetBase(prop, m_Container);
|
widget = new PropertyWidgetBase(prop, m_Container);
|
||||||
fallback->layout()->addWidget(new QLabel("(Read-only: " + QString::fromStdString(prop->GetValueAsString()) + ")"));
|
widget->layout()->addWidget(new QLabel("(Read-only: " + QString::fromStdString(prop->GetValueAsString()) + ")"));
|
||||||
m_ContainerLayout->addWidget(fallback);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widget) {
|
||||||
|
if (!groupName.empty()) {
|
||||||
|
// Indent grouped properties
|
||||||
|
widget->setContentsMargins(16, 0, 0, 0);
|
||||||
|
}
|
||||||
|
m_ContainerLayout->addWidget(widget);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_ContainerLayout->addStretch(1);
|
m_ContainerLayout->addStretch(1);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#include "Core/Object.h"
|
#include "Core/Object.h"
|
||||||
#include "Core/Signal.h"
|
#include "Core/Signal.h"
|
||||||
#include "Math/Dense.h"
|
#include "Math/Dense.h"
|
||||||
|
#include "Settings.h"
|
||||||
|
|
||||||
namespace uLib {
|
namespace uLib {
|
||||||
namespace Qt {
|
namespace Qt {
|
||||||
@@ -93,18 +94,24 @@ class VectorPropertyWidget : public PropertyWidgetBase {
|
|||||||
public:
|
public:
|
||||||
VectorPropertyWidget(Property<VecT>* prop, QWidget* parent = nullptr)
|
VectorPropertyWidget(Property<VecT>* prop, QWidget* parent = nullptr)
|
||||||
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
|
: PropertyWidgetBase(prop, parent), m_Prop(prop) {
|
||||||
QString units = QString::fromStdString(prop->GetUnits());
|
|
||||||
|
std::string unit = prop->GetUnits();
|
||||||
double factor = 1.0;
|
double factor = 1.0;
|
||||||
if (!units.isEmpty()) {
|
QString prefSuffix;
|
||||||
parseWithUnits("1 " + units, &factor);
|
if (!unit.empty()) {
|
||||||
|
auto dim = Settings::Instance().IdentifyDimension(unit);
|
||||||
|
std::string pref = Settings::Instance().GetPreferredUnit(dim);
|
||||||
|
factor = Settings::Instance().GetUnitFactor(pref);
|
||||||
|
prefSuffix = QString::fromStdString(pref);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < Size; ++i) {
|
for (int i = 0; i < Size; ++i) {
|
||||||
m_Edits[i] = new UnitLineEdit(this);
|
m_Edits[i] = new UnitLineEdit(this);
|
||||||
if (std::is_integral<typename VecT::Scalar>::value) {
|
if (std::is_integral<typename VecT::Scalar>::value) {
|
||||||
m_Edits[i]->setIntegerOnly(true);
|
m_Edits[i]->setIntegerOnly(true);
|
||||||
}
|
}
|
||||||
if (!units.isEmpty()) {
|
if (!prefSuffix.isEmpty()) {
|
||||||
m_Edits[i]->setUnits(units, factor);
|
m_Edits[i]->setUnits(prefSuffix, factor);
|
||||||
}
|
}
|
||||||
m_Layout->addWidget(m_Edits[i], 1);
|
m_Layout->addWidget(m_Edits[i], 1);
|
||||||
|
|
||||||
|
|||||||
75
app/gcompose/src/Settings.h
Normal file
75
app/gcompose/src/Settings.h
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
#ifndef GCOMPOSE_SETTINGS_H
|
||||||
|
#define GCOMPOSE_SETTINGS_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
#include "Math/Units.h"
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
namespace Qt {
|
||||||
|
|
||||||
|
class Settings {
|
||||||
|
public:
|
||||||
|
static Settings& Instance() {
|
||||||
|
static Settings instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Dimension {
|
||||||
|
Length,
|
||||||
|
Angle,
|
||||||
|
Energy,
|
||||||
|
Time,
|
||||||
|
Dimensionless
|
||||||
|
};
|
||||||
|
|
||||||
|
void SetPreferredUnit(Dimension dim, const std::string& unit) {
|
||||||
|
m_PreferredUnits[dim] = unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetPreferredUnit(Dimension dim) const {
|
||||||
|
auto it = m_PreferredUnits.find(dim);
|
||||||
|
if (it != m_PreferredUnits.end()) return it->second;
|
||||||
|
|
||||||
|
switch(dim) {
|
||||||
|
case Length: return "mm";
|
||||||
|
case Angle: return "deg";
|
||||||
|
case Energy: return "MeV";
|
||||||
|
case Time: return "ns";
|
||||||
|
default: return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double GetUnitFactor(const std::string& unit) const {
|
||||||
|
if (unit == "m") return CLHEP::meter;
|
||||||
|
if (unit == "cm") return CLHEP::centimeter;
|
||||||
|
if (unit == "mm") return CLHEP::millimeter;
|
||||||
|
if (unit == "um") return CLHEP::micrometer;
|
||||||
|
if (unit == "deg") return CLHEP::degree;
|
||||||
|
if (unit == "rad") return CLHEP::radian;
|
||||||
|
if (unit == "ns") return CLHEP::nanosecond;
|
||||||
|
if (unit == "s") return CLHEP::second;
|
||||||
|
if (unit == "ms") return CLHEP::millisecond;
|
||||||
|
if (unit == "MeV") return CLHEP::megaelectronvolt;
|
||||||
|
if (unit == "GeV") return CLHEP::gigaelectronvolt;
|
||||||
|
if (unit == "eV") return CLHEP::electronvolt;
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Dimension IdentifyDimension(const std::string& unit) const {
|
||||||
|
if (unit == "m" || unit == "cm" || unit == "mm" || unit == "um" || unit == "nm") return Length;
|
||||||
|
if (unit == "deg" || unit == "rad") return Angle;
|
||||||
|
if (unit == "MeV" || unit == "GeV" || unit == "eV" || unit == "keV" || unit == "TeV") return Energy;
|
||||||
|
if (unit == "ns" || unit == "s" || unit == "ms" || unit == "us") return Time;
|
||||||
|
return Dimensionless;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Settings() {}
|
||||||
|
std::map<Dimension, std::string> m_PreferredUnits;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Qt
|
||||||
|
} // namespace uLib
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -46,23 +46,25 @@ ViewportPane::ViewportPane(QWidget* parent) : QWidget(parent), m_viewport(nullpt
|
|||||||
|
|
||||||
m_layout->addWidget(m_titleBar);
|
m_layout->addWidget(m_titleBar);
|
||||||
|
|
||||||
// Main horizontal container for viewport and display panel
|
// Main area with splitter for viewport and display panel
|
||||||
QWidget* mainArea = new QWidget(this);
|
m_areaSplitter = new QSplitter(Qt::Horizontal, this);
|
||||||
QHBoxLayout* hLayout = new QHBoxLayout(mainArea);
|
m_areaSplitter->setObjectName("ViewportAreaSplitter");
|
||||||
hLayout->setContentsMargins(0, 0, 0, 0);
|
m_layout->addWidget(m_areaSplitter, 1);
|
||||||
hLayout->setSpacing(0);
|
|
||||||
m_layout->addWidget(mainArea);
|
|
||||||
|
|
||||||
// Viewport will be added here via setViewport
|
// Viewport will be added here via setViewport
|
||||||
m_viewport = new uLib::Vtk::QViewport(mainArea);
|
m_viewport = new uLib::Vtk::QViewport(m_areaSplitter);
|
||||||
hLayout->addWidget(m_viewport);
|
m_areaSplitter->addWidget(m_viewport);
|
||||||
|
|
||||||
// Display Panel (Overlay/Slide-out)
|
// Display Panel (Overlay/Slide-out)
|
||||||
m_displayPanel = new QFrame(mainArea);
|
m_displayPanel = new QFrame(m_areaSplitter);
|
||||||
m_displayPanel->setObjectName("DisplayPropertiesPanel");
|
m_displayPanel->setObjectName("DisplayPropertiesPanel");
|
||||||
m_displayPanel->setFixedWidth(250);
|
m_displayPanel->setMinimumWidth(150);
|
||||||
m_displayPanel->hide();
|
m_displayPanel->hide();
|
||||||
|
|
||||||
|
m_areaSplitter->addWidget(m_displayPanel);
|
||||||
|
m_areaSplitter->setStretchFactor(0, 1);
|
||||||
|
m_areaSplitter->setStretchFactor(1, 0);
|
||||||
|
|
||||||
QVBoxLayout* panelLayout = new QVBoxLayout(m_displayPanel);
|
QVBoxLayout* panelLayout = new QVBoxLayout(m_displayPanel);
|
||||||
panelLayout->setContentsMargins(5, 5, 5, 5);
|
panelLayout->setContentsMargins(5, 5, 5, 5);
|
||||||
|
|
||||||
@@ -73,8 +75,6 @@ ViewportPane::ViewportPane(QWidget* parent) : QWidget(parent), m_viewport(nullpt
|
|||||||
m_displayEditor = new uLib::Qt::PropertyEditor(m_displayPanel);
|
m_displayEditor = new uLib::Qt::PropertyEditor(m_displayPanel);
|
||||||
panelLayout->addWidget(m_displayEditor);
|
panelLayout->addWidget(m_displayEditor);
|
||||||
|
|
||||||
hLayout->addWidget(m_displayPanel);
|
|
||||||
|
|
||||||
connect(m_toggleBtn, &QPushButton::toggled, this, &ViewportPane::toggleDisplayPanel);
|
connect(m_toggleBtn, &QPushButton::toggled, this, &ViewportPane::toggleDisplayPanel);
|
||||||
connect(m_titleBar, &QWidget::customContextMenuRequested, this, &ViewportPane::showContextMenu);
|
connect(m_titleBar, &QWidget::customContextMenuRequested, this, &ViewportPane::showContextMenu);
|
||||||
connect(closeBtn, &QToolButton::clicked, this, &ViewportPane::onCloseRequested);
|
connect(closeBtn, &QToolButton::clicked, this, &ViewportPane::onCloseRequested);
|
||||||
@@ -85,7 +85,15 @@ ViewportPane::ViewportPane(QWidget* parent) : QWidget(parent), m_viewport(nullpt
|
|||||||
ViewportPane::~ViewportPane() {}
|
ViewportPane::~ViewportPane() {}
|
||||||
|
|
||||||
void ViewportPane::toggleDisplayPanel() {
|
void ViewportPane::toggleDisplayPanel() {
|
||||||
m_displayPanel->setVisible(m_toggleBtn->isChecked());
|
bool visible = m_toggleBtn->isChecked();
|
||||||
|
m_displayPanel->setVisible(visible);
|
||||||
|
if (visible && m_areaSplitter->sizes().value(1, 0) == 0) {
|
||||||
|
QList<int> sizes = m_areaSplitter->sizes();
|
||||||
|
int total = sizes[0] + sizes[1];
|
||||||
|
sizes[1] = 250;
|
||||||
|
sizes[0] = total - 250;
|
||||||
|
m_areaSplitter->setSizes(sizes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ViewportPane::setObject(uLib::Object* obj) {
|
void ViewportPane::setObject(uLib::Object* obj) {
|
||||||
@@ -107,15 +115,14 @@ void ViewportPane::setObject(uLib::Object* obj) {
|
|||||||
|
|
||||||
void ViewportPane::setViewport(QWidget* viewport, const QString& title) {
|
void ViewportPane::setViewport(QWidget* viewport, const QString& title) {
|
||||||
if (m_viewport) {
|
if (m_viewport) {
|
||||||
m_viewport->parentWidget()->layout()->removeWidget(m_viewport);
|
|
||||||
delete m_viewport;
|
delete m_viewport;
|
||||||
}
|
}
|
||||||
m_viewport = viewport;
|
m_viewport = viewport;
|
||||||
m_titleLabel->setText(title);
|
m_titleLabel->setText(title);
|
||||||
|
|
||||||
m_viewport->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
m_viewport->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||||
auto* mainAreaLayout = static_cast<QHBoxLayout*>(m_displayPanel->parentWidget()->layout());
|
m_areaSplitter->insertWidget(0, m_viewport);
|
||||||
mainAreaLayout->insertWidget(0, m_viewport);
|
m_areaSplitter->setStretchFactor(0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ViewportPane::addVtkViewport() {
|
void ViewportPane::addVtkViewport() {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ namespace uLib {
|
|||||||
namespace Qt { class PropertyEditor; }
|
namespace Qt { class PropertyEditor; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class QSplitter;
|
||||||
class QVBoxLayout;
|
class QVBoxLayout;
|
||||||
class QLabel;
|
class QLabel;
|
||||||
|
|
||||||
@@ -39,6 +40,7 @@ private:
|
|||||||
QVBoxLayout* m_layout;
|
QVBoxLayout* m_layout;
|
||||||
QWidget* m_titleBar;
|
QWidget* m_titleBar;
|
||||||
QLabel* m_titleLabel;
|
QLabel* m_titleLabel;
|
||||||
|
QSplitter* m_areaSplitter;
|
||||||
QWidget* m_viewport;
|
QWidget* m_viewport;
|
||||||
|
|
||||||
// Display Properties Overlay
|
// Display Properties Overlay
|
||||||
|
|||||||
338
docs/algorithms/algoritm.md
Normal file
338
docs/algorithms/algoritm.md
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
# Algorithm Infrastructure
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
An algorithm in the uLib infrastructure is a class for containing a functional that can be dynamically loaded into memory as a plug-in.
|
||||||
|
It derives from the base `Object` class (`Core/Object.h`) and therefore can contain properties that define the serialization of operating parameters or the implementation of widgets for interactive parameter manipulation.
|
||||||
|
|
||||||
|
The algorithm class is designed to be inserted into an `AlgorithmTask`, a class for managing the execution of scheduled operations. A task contains `Run` and `Stop` methods to start and stop execution. A task can be configured to work in two modes:
|
||||||
|
|
||||||
|
- **Cyclic mode**: the algorithm is executed periodically with a configurable cycle time.
|
||||||
|
- **Asynchronous mode**: the task waits for a trigger before each execution. Triggers can come from the uLib signal-slot system (`Object::connect`) or from a condition variable as defined in the monitor pattern (`Core/Monitor.h`).
|
||||||
|
|
||||||
|
The algorithm is defined as a template class on two types `T_enc` and `T_dec`. The encoder is a type for data input or another algorithm that is chained with this one and outputs data in a compatible format. The decoder is the type of data output or a downstream algorithm compatible with it.
|
||||||
|
|
||||||
|
## Class Hierarchy
|
||||||
|
|
||||||
|
```
|
||||||
|
Object (Core/Object.h)
|
||||||
|
|
|
||||||
|
+-- Algorithm<T_enc, T_dec> (Core/Algorithm.h)
|
||||||
|
| |
|
||||||
|
| +-- VoxImageFilter<VoxelT, CrtpImplT> (Math/VoxImageFilter.h)
|
||||||
|
| |
|
||||||
|
| +-- VoxFilterAlgorithmLinear (Math/VoxImageFilterLinear.hpp)
|
||||||
|
| +-- VoxFilterAlgorithmMedian (Math/VoxImageFilterMedian.hpp)
|
||||||
|
| +-- VoxFilterAlgorithmAbtrim (Math/VoxImageFilterABTrim.hpp)
|
||||||
|
| +-- VoxFilterAlgorithmSPR (Math/VoxImageFilterABTrim.hpp)
|
||||||
|
| +-- VoxFilterAlgorithmThreshold (Math/VoxImageFilterThreshold.hpp)
|
||||||
|
| +-- VoxFilterAlgorithmBilateral (Math/VoxImageFilterBilateral.hpp)
|
||||||
|
| +-- VoxFilterAlgorithmBilateralTrim(Math/VoxImageFilterBilateral.hpp)
|
||||||
|
| +-- VoxFilterAlgorithm2ndStat (Math/VoxImageFilter2ndStat.hpp)
|
||||||
|
| +-- VoxFilterAlgorithmCustom (Math/VoxImageFilterCustom.hpp)
|
||||||
|
|
|
||||||
|
+-- Thread (Core/Threads.h)
|
||||||
|
|
|
||||||
|
+-- AlgorithmTask<T_enc, T_dec> (Core/Algorithm.h)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Algorithm (`Core/Algorithm.h`)
|
||||||
|
|
||||||
|
### Template Parameters
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template <typename T_enc, typename T_dec>
|
||||||
|
class Algorithm : public Object;
|
||||||
|
```
|
||||||
|
|
||||||
|
- **`T_enc`** (Encoder): the input data type. Can be a raw data type or a pointer to a data structure. When chaining algorithms, the upstream algorithm's `T_dec` must be compatible with this algorithm's `T_enc`.
|
||||||
|
- **`T_dec`** (Decoder): the output data type. Produced by `Process()` and consumed by the next algorithm in the chain.
|
||||||
|
|
||||||
|
### Core Interface
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `virtual T_dec Process(const T_enc& input) = 0` | Pure virtual. Implement the algorithm logic here. |
|
||||||
|
| `T_dec operator()(const T_enc& input)` | Calls `Process()`. Enables functional syntax: `result = alg(data)`. |
|
||||||
|
|
||||||
|
### Algorithm Chaining
|
||||||
|
|
||||||
|
Algorithms can be linked in processing pipelines via encoder/decoder pointers:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Algorithm* upstream; // SetEncoder() / GetEncoder()
|
||||||
|
Algorithm* downstream; // SetDecoder() / GetDecoder()
|
||||||
|
```
|
||||||
|
|
||||||
|
This allows building chains like:
|
||||||
|
|
||||||
|
```
|
||||||
|
[RawData] --> AlgorithmA --> AlgorithmB --> [Result]
|
||||||
|
encoder decoder
|
||||||
|
```
|
||||||
|
|
||||||
|
### Signals
|
||||||
|
|
||||||
|
| Signal | Emitted when |
|
||||||
|
|--------|-------------|
|
||||||
|
| `Started()` | The algorithm begins processing (caller responsibility). |
|
||||||
|
| `Finished()` | The algorithm completes processing (caller responsibility). |
|
||||||
|
|
||||||
|
### Device Preference (CUDA)
|
||||||
|
|
||||||
|
Algorithms report their preferred execution device via `GetPreferredDevice()`:
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `virtual MemoryDevice GetPreferredDevice() const` | Returns `RAM` or `VRAM`. Subclasses override. |
|
||||||
|
| `void SetPreferredDevice(MemoryDevice dev)` | Manually set the device preference. |
|
||||||
|
| `bool IsGPU() const` | Shorthand for `GetPreferredDevice() == VRAM`. |
|
||||||
|
|
||||||
|
GPU-based algorithms are responsible for calling `cudaDeviceSynchronize()` inside their `Process()` implementation before returning, so that results are available to the caller or downstream algorithm.
|
||||||
|
|
||||||
|
### Example: Defining a Custom Algorithm
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class MyFilter : public Algorithm<VoxImage<Voxel>*, VoxImage<Voxel>*> {
|
||||||
|
public:
|
||||||
|
const char* GetClassName() const override { return "MyFilter"; }
|
||||||
|
|
||||||
|
VoxImage<Voxel>* Process(VoxImage<Voxel>* const& image) override {
|
||||||
|
// ... filter the image in-place ...
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## AlgorithmTask (`Core/Algorithm.h`)
|
||||||
|
|
||||||
|
`AlgorithmTask` manages the execution of an `Algorithm` within a scheduled, threaded context. It inherits from `Thread` (`Core/Threads.h`) and uses `Mutex` (`Core/Monitor.h`) for synchronization.
|
||||||
|
|
||||||
|
### Template Parameters
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template <typename T_enc, typename T_dec>
|
||||||
|
class AlgorithmTask : public Thread;
|
||||||
|
```
|
||||||
|
|
||||||
|
Must match the `Algorithm<T_enc, T_dec>` it manages.
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `void SetAlgorithm(AlgorithmType* alg)` | Set the algorithm to execute. |
|
||||||
|
| `void SetMode(Mode mode)` | `Cyclic` or `Async`. |
|
||||||
|
| `void SetCycleTime(int ms)` | Period for cyclic mode (milliseconds). |
|
||||||
|
|
||||||
|
### Execution Modes
|
||||||
|
|
||||||
|
#### Cyclic Mode
|
||||||
|
|
||||||
|
The algorithm's `Process()` is called periodically. The cycle waits on a `condition_variable_any` with timeout, so `Stop()` can interrupt immediately without waiting for the full cycle.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
AlgorithmTask<int, int> task;
|
||||||
|
task.SetAlgorithm(&myAlgorithm);
|
||||||
|
task.SetMode(AlgorithmTask<int, int>::Cyclic);
|
||||||
|
task.SetCycleTime(100); // every 100ms
|
||||||
|
task.Run(inputData);
|
||||||
|
// ... later ...
|
||||||
|
task.Stop();
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Asynchronous Mode
|
||||||
|
|
||||||
|
The task thread blocks on a condition variable until `Notify()` is called. Each notification triggers exactly one `Process()` invocation.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
task.SetMode(AlgorithmTask<int, int>::Async);
|
||||||
|
task.Run(inputData);
|
||||||
|
|
||||||
|
// Trigger manually:
|
||||||
|
task.Notify();
|
||||||
|
|
||||||
|
// Or connect to a signal:
|
||||||
|
task.ConnectTrigger(sender, &SenderClass::DataReady);
|
||||||
|
// Now each emission of DataReady() triggers one Process() call.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lifecycle
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `void Run(const T_enc& input)` | Starts the background thread with the given input. |
|
||||||
|
| `void Stop()` | Requests stop and joins the thread. |
|
||||||
|
| `bool IsRunning()` | Inherited from `Thread`. |
|
||||||
|
|
||||||
|
### Signals
|
||||||
|
|
||||||
|
| Signal | Emitted when |
|
||||||
|
|--------|-------------|
|
||||||
|
| `Stopped()` | The task thread has completed (after last `Process()` and before thread exit). |
|
||||||
|
|
||||||
|
### Signal-Slot Triggering
|
||||||
|
|
||||||
|
`ConnectTrigger()` connects any uLib `Object` signal to the task's `Notify()` method:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
task.ConnectTrigger(detector, &Detector::EventReady);
|
||||||
|
```
|
||||||
|
|
||||||
|
This uses the uLib signal system (`Core/Signal.h`), not Qt signals. The connection is type-safe and works with the `Object::connect` infrastructure.
|
||||||
|
|
||||||
|
## VoxImageFilter (`Math/VoxImageFilter.h`)
|
||||||
|
|
||||||
|
`VoxImageFilter` specializes `Algorithm` for kernel-based volumetric image filtering. It uses CRTP (Curiously Recurring Template Pattern) so that concrete filters provide their `Evaluate()` method without virtual dispatch overhead in the inner loop.
|
||||||
|
|
||||||
|
### Template Parameters
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
template <typename VoxelT, typename CrtpImplT>
|
||||||
|
class VoxImageFilter : public Abstract::VoxImageFilter,
|
||||||
|
public Algorithm<VoxImage<VoxelT>*, VoxImage<VoxelT>*>;
|
||||||
|
```
|
||||||
|
|
||||||
|
- **`VoxelT`**: the voxel data type (must satisfy `Interface::Voxel` — requires `.Value` and `.Count` fields).
|
||||||
|
- **`CrtpImplT`**: the concrete filter subclass. Must implement:
|
||||||
|
```cpp
|
||||||
|
float Evaluate(const VoxImage<VoxelT>& buffer, int index);
|
||||||
|
```
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
|
||||||
|
1. `Process(image)` creates a read-only buffer copy of the input image.
|
||||||
|
2. For each voxel in parallel (OpenMP), it calls `CrtpImplT::Evaluate(buffer, index)`.
|
||||||
|
3. `Evaluate()` reads from the buffer using the kernel offsets and writes the result.
|
||||||
|
4. The filtered image is returned (in-place modification).
|
||||||
|
|
||||||
|
```
|
||||||
|
Process(image)
|
||||||
|
|
|
||||||
|
+-- buffer = copy of image (read-only snapshot)
|
||||||
|
|
|
||||||
|
+-- #pragma omp parallel for
|
||||||
|
| for each voxel i:
|
||||||
|
| image[i].Value = CrtpImplT::Evaluate(buffer, i)
|
||||||
|
|
|
||||||
|
+-- return image
|
||||||
|
```
|
||||||
|
|
||||||
|
### Kernel System
|
||||||
|
|
||||||
|
The `Kernel<VoxelT>` class stores convolution weights and precomputed index offsets:
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `SetKernelNumericXZY(values)` | Set kernel weights from a flat vector (XZY order). |
|
||||||
|
| `SetKernelSpherical(shape)` | Set weights via a radial function `f(distance^2)`. |
|
||||||
|
| `SetKernelWeightFunction(shape)` | Set weights via a 3D position function `f(Vector3f)`. |
|
||||||
|
|
||||||
|
### CUDA Support
|
||||||
|
|
||||||
|
Concrete filters can override `Process()` with a CUDA implementation:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#if defined(USE_CUDA) && defined(__CUDACC__)
|
||||||
|
VoxImage<VoxelT>* Process(VoxImage<VoxelT>* const& image) override {
|
||||||
|
if (this->GetPreferredDevice() == MemoryDevice::VRAM) {
|
||||||
|
// Launch CUDA kernel, synchronize, return
|
||||||
|
} else {
|
||||||
|
return BaseClass::Process(image); // CPU fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
|
||||||
|
The base class `GetPreferredDevice()` automatically returns `VRAM` when the image or kernel data resides on the GPU, enabling transparent device dispatch.
|
||||||
|
|
||||||
|
Filters with CUDA implementations: `VoxFilterAlgorithmLinear`, `VoxFilterAlgorithmAbtrim`, `VoxFilterAlgorithmSPR`.
|
||||||
|
|
||||||
|
### Concrete Filters
|
||||||
|
|
||||||
|
| Filter | File | Description |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| `VoxFilterAlgorithmLinear` | `VoxImageFilterLinear.hpp` | Weighted linear convolution (FIR filter). CUDA-enabled. |
|
||||||
|
| `VoxFilterAlgorithmMedian` | `VoxImageFilterMedian.hpp` | Median filter with kernel-weighted sorting. |
|
||||||
|
| `VoxFilterAlgorithmAbtrim` | `VoxImageFilterABTrim.hpp` | Alpha-beta trimmed mean filter. CUDA-enabled. |
|
||||||
|
| `VoxFilterAlgorithmSPR` | `VoxImageFilterABTrim.hpp` | Robespierre filter: trimmed mean applied only to outlier voxels. CUDA-enabled. |
|
||||||
|
| `VoxFilterAlgorithmThreshold` | `VoxImageFilterThreshold.hpp` | Binary threshold filter. |
|
||||||
|
| `VoxFilterAlgorithmBilateral` | `VoxImageFilterBilateral.hpp` | Edge-preserving bilateral filter (intensity-weighted Gaussian). |
|
||||||
|
| `VoxFilterAlgorithmBilateralTrim` | `VoxImageFilterBilateral.hpp` | Bilateral filter with alpha-beta trimming. |
|
||||||
|
| `VoxFilterAlgorithm2ndStat` | `VoxImageFilter2ndStat.hpp` | Local variance (second-order statistic). |
|
||||||
|
| `VoxFilterAlgorithmCustom` | `VoxImageFilterCustom.hpp` | User-supplied evaluation function via function pointer. |
|
||||||
|
|
||||||
|
### Example: Using a Filter with AlgorithmTask
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Create filter and configure kernel
|
||||||
|
VoxFilterAlgorithmLinear<Voxel> filter(Vector3i(3, 3, 3));
|
||||||
|
std::vector<float> weights(27, 1.0f); // uniform 3x3x3
|
||||||
|
filter.SetKernelNumericXZY(weights);
|
||||||
|
|
||||||
|
// Direct use
|
||||||
|
filter.SetImage(&image);
|
||||||
|
filter.Run();
|
||||||
|
|
||||||
|
// Or via Algorithm interface
|
||||||
|
VoxImage<Voxel>* result = filter.Process(&image);
|
||||||
|
|
||||||
|
// Or scheduled in a task
|
||||||
|
AlgorithmTask<VoxImage<Voxel>*, VoxImage<Voxel>*> task;
|
||||||
|
task.SetAlgorithm(&filter);
|
||||||
|
task.SetMode(AlgorithmTask<VoxImage<Voxel>*, VoxImage<Voxel>*>::Cyclic);
|
||||||
|
task.SetCycleTime(500);
|
||||||
|
task.Run(&image);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Structural Benefits
|
||||||
|
|
||||||
|
### 1. Uniform Processing Interface
|
||||||
|
|
||||||
|
Every algorithm — from a simple threshold to a GPU-accelerated convolution — exposes the same `Process(input) -> output` interface. Client code does not need to know the concrete type:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
Algorithm<VoxImage<Voxel>*, VoxImage<Voxel>*>* alg = &anyFilter;
|
||||||
|
alg->Process(&image);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Pipeline Composition
|
||||||
|
|
||||||
|
The encoder/decoder chaining allows building data processing pipelines where each stage transforms data and passes it to the next. Type safety is enforced at compile time through template parameters.
|
||||||
|
|
||||||
|
### 3. Scheduled and Event-Driven Execution
|
||||||
|
|
||||||
|
`AlgorithmTask` decouples the algorithm from its execution schedule. The same algorithm can be:
|
||||||
|
- Called directly (`Process()`)
|
||||||
|
- Run periodically (Cyclic mode for monitoring/acquisition)
|
||||||
|
- Triggered by events (Async mode for reactive processing)
|
||||||
|
|
||||||
|
### 4. Transparent CPU/GPU Dispatch
|
||||||
|
|
||||||
|
The `MemoryDevice` preference and `GetPreferredDevice()` virtual allow the same algorithm interface to dispatch to CPU or GPU implementations. The `DataAllocator` transparently manages RAM/VRAM transfers, and concrete filters override `Process()` with CUDA kernels when data is on the GPU.
|
||||||
|
|
||||||
|
### 5. Integration with the Object System
|
||||||
|
|
||||||
|
Since `Algorithm` inherits from `Object`, algorithms gain:
|
||||||
|
- **Properties**: serializable parameters via the `Property<T>` system, enabling persistent configuration and GUI widget generation.
|
||||||
|
- **Signals**: `Started`/`Finished` notifications for connecting to monitoring or logging.
|
||||||
|
- **Serialization**: save/load algorithm configuration via Boost archives.
|
||||||
|
- **Instance naming**: `SetInstanceName()` for runtime identification in contexts.
|
||||||
|
|
||||||
|
### 6. CRTP Performance for Inner Loops
|
||||||
|
|
||||||
|
`VoxImageFilter` uses CRTP to dispatch to `Evaluate()` without virtual function overhead. The per-voxel evaluation runs at full speed inside OpenMP parallel loops, while the outer `Process()` method remains virtual for polymorphic use through the Algorithm interface.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
```
|
||||||
|
Core/Object.h — base class, properties, signals, serialization
|
||||||
|
Core/Signal.h — signal-slot connection infrastructure
|
||||||
|
Core/Monitor.h — Mutex, condition variables, ULIB_MUTEX_LOCK
|
||||||
|
Core/Threads.h — Thread base class for AlgorithmTask
|
||||||
|
Core/DataAllocator.h — MemoryDevice enum, RAM/VRAM data management
|
||||||
|
Math/VoxImage.h — volumetric image container
|
||||||
|
Math/VoxImageFilter.h — kernel-based filter framework
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
263
src/Core/Algorithm.h
Normal file
263
src/Core/Algorithm.h
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
/*//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// CMT Cosmic Muon Tomography project //////////////////////////////////////////
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
Copyright (c) 2014, Universita' degli Studi di Padova, INFN sez. di Padova
|
||||||
|
All rights reserved
|
||||||
|
|
||||||
|
Authors: Andrea Rigoni Garola < andrea.rigoni@pd.infn.it >
|
||||||
|
|
||||||
|
------------------------------------------------------------------
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 3.0 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library.
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
#ifndef U_CORE_ALGORITHM_H
|
||||||
|
#define U_CORE_ALGORITHM_H
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <condition_variable>
|
||||||
|
|
||||||
|
#include "Core/Object.h"
|
||||||
|
#include "Core/Monitor.h"
|
||||||
|
#include "Core/Threads.h"
|
||||||
|
#include "Core/DataAllocator.h"
|
||||||
|
|
||||||
|
namespace uLib {
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//// ALGORITHM /////////////////////////////////////////////////////////////////
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Algorithm is a template class for containing a functional that can be
|
||||||
|
* dynamically loaded as a plug-in. It derives from Object and supports
|
||||||
|
* properties for serialization and interactive parameter widgets.
|
||||||
|
*
|
||||||
|
* Algorithms are responsible for their own GPU synchronization: if Process()
|
||||||
|
* launches CUDA kernels, it must call cudaDeviceSynchronize() before returning
|
||||||
|
* so that the result is available to the caller or downstream algorithm.
|
||||||
|
*
|
||||||
|
* @tparam T_enc Encoder type: the input data type, or a chained algorithm
|
||||||
|
* whose output is compatible with this algorithm's input.
|
||||||
|
* @tparam T_dec Decoder type: the output data type, or a chained algorithm
|
||||||
|
* whose input is compatible with this algorithm's output.
|
||||||
|
*/
|
||||||
|
template <typename T_enc, typename T_dec>
|
||||||
|
class Algorithm : public Object {
|
||||||
|
public:
|
||||||
|
using EncoderType = T_enc;
|
||||||
|
using DecoderType = T_dec;
|
||||||
|
|
||||||
|
Algorithm()
|
||||||
|
: Object()
|
||||||
|
, m_Encoder(nullptr)
|
||||||
|
, m_Decoder(nullptr)
|
||||||
|
, m_PreferredDevice(MemoryDevice::RAM)
|
||||||
|
{}
|
||||||
|
virtual ~Algorithm() = default;
|
||||||
|
|
||||||
|
virtual const char* GetClassName() const override { return "Algorithm"; }
|
||||||
|
|
||||||
|
// Processing ///////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Process input data and produce output.
|
||||||
|
* Override this in subclasses to implement the algorithm logic.
|
||||||
|
* GPU-based implementations must synchronize before returning.
|
||||||
|
*/
|
||||||
|
virtual T_dec Process(const T_enc& input) = 0;
|
||||||
|
|
||||||
|
/** @brief Operator form of Process for functional chaining. */
|
||||||
|
T_dec operator()(const T_enc& input) { return Process(input); }
|
||||||
|
|
||||||
|
// Chaining /////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
void SetEncoder(Algorithm* enc) { m_Encoder = enc; }
|
||||||
|
Algorithm* GetEncoder() const { return m_Encoder; }
|
||||||
|
|
||||||
|
void SetDecoder(Algorithm* dec) { m_Decoder = dec; }
|
||||||
|
Algorithm* GetDecoder() const { return m_Decoder; }
|
||||||
|
|
||||||
|
// Device preference ////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the preferred memory device for this algorithm.
|
||||||
|
* CUDA-capable algorithms should override to return VRAM when their
|
||||||
|
* data resides on the GPU.
|
||||||
|
*/
|
||||||
|
virtual MemoryDevice GetPreferredDevice() const { return m_PreferredDevice; }
|
||||||
|
void SetPreferredDevice(MemoryDevice dev) { m_PreferredDevice = dev; }
|
||||||
|
|
||||||
|
/** @brief Returns true if this algorithm prefers GPU execution. */
|
||||||
|
bool IsGPU() const { return GetPreferredDevice() == MemoryDevice::VRAM; }
|
||||||
|
|
||||||
|
// Signals //////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
signals:
|
||||||
|
virtual void Started() { ULIB_SIGNAL_EMIT(Algorithm::Started); }
|
||||||
|
virtual void Finished() { ULIB_SIGNAL_EMIT(Algorithm::Finished); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Algorithm* m_Encoder;
|
||||||
|
Algorithm* m_Decoder;
|
||||||
|
MemoryDevice m_PreferredDevice;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//// ALGORITHM TASK ////////////////////////////////////////////////////////////
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief AlgorithmTask manages the execution of an Algorithm within a
|
||||||
|
* scheduled context. Uses uLib::Thread for execution and uLib::Mutex for
|
||||||
|
* synchronization.
|
||||||
|
*
|
||||||
|
* Two execution modes:
|
||||||
|
* - Cyclic: executes Process() periodically with configurable cycle time.
|
||||||
|
* - Async: waits for Notify() or a connected signal before each execution.
|
||||||
|
*
|
||||||
|
* GPU synchronization is the algorithm's responsibility (see Algorithm::Process).
|
||||||
|
*/
|
||||||
|
template <typename T_enc, typename T_dec>
|
||||||
|
class AlgorithmTask : public Thread {
|
||||||
|
public:
|
||||||
|
using AlgorithmType = Algorithm<T_enc, T_dec>;
|
||||||
|
|
||||||
|
enum Mode { Cyclic, Async };
|
||||||
|
|
||||||
|
AlgorithmTask()
|
||||||
|
: Thread()
|
||||||
|
, m_Algorithm(nullptr)
|
||||||
|
, m_Mode(Cyclic)
|
||||||
|
, m_CycleTime_ms(1000)
|
||||||
|
, m_StopRequested(false)
|
||||||
|
, m_Triggered(false)
|
||||||
|
{}
|
||||||
|
|
||||||
|
virtual ~AlgorithmTask() { Stop(); }
|
||||||
|
|
||||||
|
virtual const char* GetClassName() const override { return "AlgorithmTask"; }
|
||||||
|
|
||||||
|
// Configuration ////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
void SetAlgorithm(AlgorithmType* alg) { m_Algorithm = alg; }
|
||||||
|
AlgorithmType* GetAlgorithm() const { return m_Algorithm; }
|
||||||
|
|
||||||
|
void SetMode(Mode mode) { m_Mode = mode; }
|
||||||
|
Mode GetMode() const { return m_Mode; }
|
||||||
|
|
||||||
|
void SetCycleTime(int milliseconds) { m_CycleTime_ms = milliseconds; }
|
||||||
|
int GetCycleTime() const { return m_CycleTime_ms; }
|
||||||
|
|
||||||
|
// Lifecycle ////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Start the task execution in a separate thread (via Thread::Start).
|
||||||
|
* In Cyclic mode, the algorithm is executed periodically.
|
||||||
|
* In Async mode, call Notify() or connect a signal to trigger execution.
|
||||||
|
*/
|
||||||
|
void Run(const T_enc& input) {
|
||||||
|
if (IsRunning()) return;
|
||||||
|
m_StopRequested.store(false);
|
||||||
|
m_Triggered.store(false);
|
||||||
|
m_Input = input;
|
||||||
|
Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Stop the task execution and join the thread. */
|
||||||
|
void Stop() {
|
||||||
|
m_StopRequested.store(true);
|
||||||
|
ULIB_MUTEX_LOCK(m_WaitMutex, -1) {
|
||||||
|
m_Condition.notify_all();
|
||||||
|
}
|
||||||
|
if (IsJoinable()) Join();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Async triggering /////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Notify the task to execute one iteration (Async mode).
|
||||||
|
* Can be called from a signal-slot connection or externally.
|
||||||
|
*/
|
||||||
|
void Notify() {
|
||||||
|
m_Triggered.store(true);
|
||||||
|
ULIB_MUTEX_LOCK(m_WaitMutex, -1) {
|
||||||
|
m_Condition.notify_one();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Connect an Object signal to trigger async execution.
|
||||||
|
* Usage: task.ConnectTrigger(sender, &SenderClass::SomeSignal);
|
||||||
|
*/
|
||||||
|
template <typename Func1>
|
||||||
|
Connection ConnectTrigger(typename FunctionPointer<Func1>::Object* sender, Func1 sigf) {
|
||||||
|
return Object::connect(sender, sigf, [this]() { Notify(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signals //////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
signals:
|
||||||
|
virtual void Stopped() { ULIB_SIGNAL_EMIT(AlgorithmTask::Stopped); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/** @brief Thread entry point — dispatches to cyclic or async loop. */
|
||||||
|
void Run() override {
|
||||||
|
if (m_Mode == Cyclic)
|
||||||
|
RunCyclic();
|
||||||
|
else
|
||||||
|
RunAsync();
|
||||||
|
Stopped();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void RunCyclic() {
|
||||||
|
while (!m_StopRequested.load()) {
|
||||||
|
if (m_Algorithm) m_Algorithm->Process(m_Input);
|
||||||
|
std::unique_lock<std::timed_mutex> lock(m_WaitMutex.GetNative());
|
||||||
|
m_Condition.wait_for(lock,
|
||||||
|
std::chrono::milliseconds(m_CycleTime_ms),
|
||||||
|
[this]() { return m_StopRequested.load(); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RunAsync() {
|
||||||
|
while (!m_StopRequested.load()) {
|
||||||
|
std::unique_lock<std::timed_mutex> lock(m_WaitMutex.GetNative());
|
||||||
|
m_Condition.wait(lock, [this]() {
|
||||||
|
return m_StopRequested.load() || m_Triggered.load();
|
||||||
|
});
|
||||||
|
if (m_StopRequested.load()) break;
|
||||||
|
m_Triggered.store(false);
|
||||||
|
if (m_Algorithm) m_Algorithm->Process(m_Input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AlgorithmType* m_Algorithm;
|
||||||
|
Mode m_Mode;
|
||||||
|
int m_CycleTime_ms;
|
||||||
|
T_enc m_Input;
|
||||||
|
|
||||||
|
std::atomic<bool> m_StopRequested;
|
||||||
|
std::atomic<bool> m_Triggered;
|
||||||
|
Mutex m_WaitMutex;
|
||||||
|
std::condition_variable_any m_Condition;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace uLib
|
||||||
|
|
||||||
|
#endif // U_CORE_ALGORITHM_H
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
|
|
||||||
set(HEADERS
|
set(HEADERS
|
||||||
|
Algorithm.h
|
||||||
Archives.h
|
Archives.h
|
||||||
Array.h
|
Array.h
|
||||||
Collection.h
|
Collection.h
|
||||||
|
|||||||
@@ -90,10 +90,10 @@ const std::vector<PropertyBase*>& Object::GetProperties() const {
|
|||||||
|
|
||||||
PropertyBase* Object::GetProperty(const std::string& name) const {
|
PropertyBase* Object::GetProperty(const std::string& name) const {
|
||||||
for (auto* p : d->m_Properties) {
|
for (auto* p : d->m_Properties) {
|
||||||
if (p->GetName() == name) return p;
|
if (p->GetName() == name || p->GetQualifiedName() == name) return p;
|
||||||
}
|
}
|
||||||
for (auto* p : d->m_DynamicProperties) {
|
for (auto* p : d->m_DynamicProperties) {
|
||||||
if (p->GetName() == name) return p;
|
if (p->GetName() == name || p->GetQualifiedName() == name) return p;
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,16 @@
|
|||||||
#define U_CORE_PROPERTY_H
|
#define U_CORE_PROPERTY_H
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <typeinfo>
|
#include <typeinfo>
|
||||||
#include <typeindex> // Added
|
#include <typeindex> // Added
|
||||||
#include <boost/serialization/nvp.hpp>
|
#include <boost/serialization/nvp.hpp>
|
||||||
#include <boost/lexical_cast.hpp>
|
#include <boost/lexical_cast.hpp>
|
||||||
|
#include <vector>
|
||||||
|
#include <boost/type_traits/is_class.hpp>
|
||||||
|
#include <boost/mpl/bool.hpp>
|
||||||
|
#include <boost/serialization/serialization.hpp>
|
||||||
#include "Core/Archives.h"
|
#include "Core/Archives.h"
|
||||||
#include "Core/Signal.h"
|
#include "Core/Signal.h"
|
||||||
#include "Core/Object.h"
|
#include "Core/Object.h"
|
||||||
@@ -29,6 +34,12 @@ public:
|
|||||||
static std::vector<std::string> empty;
|
static std::vector<std::string> empty;
|
||||||
return empty;
|
return empty;
|
||||||
}
|
}
|
||||||
|
virtual const std::string& GetGroup() const = 0;
|
||||||
|
virtual void SetGroup(const std::string& group) = 0;
|
||||||
|
std::string GetQualifiedName() const {
|
||||||
|
if (GetGroup().empty()) return GetName();
|
||||||
|
return GetGroup() + "." + GetName();
|
||||||
|
}
|
||||||
|
|
||||||
// Signal support
|
// Signal support
|
||||||
signals:
|
signals:
|
||||||
@@ -51,16 +62,16 @@ template <typename T>
|
|||||||
class Property : public PropertyBase {
|
class Property : public PropertyBase {
|
||||||
public:
|
public:
|
||||||
// PROXY: Use an existing variable as back-end storage
|
// PROXY: Use an existing variable as back-end storage
|
||||||
Property(Object* owner, const std::string& name, T* valuePtr, const std::string& units = "")
|
Property(Object* owner, const std::string& name, T* valuePtr, const std::string& units = "", const std::string& group = "")
|
||||||
: m_owner(owner), m_name(name), m_units(units), m_value(valuePtr), m_own(false) {
|
: m_owner(owner), m_name(name), m_units(units), m_group(group), m_value(valuePtr), m_own(false) {
|
||||||
if (m_owner) {
|
if (m_owner) {
|
||||||
m_owner->RegisterProperty(this);
|
m_owner->RegisterProperty(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MANAGED: Create and own internal storage
|
// MANAGED: Create and own internal storage
|
||||||
Property(Object* owner, const std::string& name, const T& defaultValue = T(), const std::string& units = "")
|
Property(Object* owner, const std::string& name, const T& defaultValue = T(), const std::string& units = "", const std::string& group = "")
|
||||||
: m_owner(owner), m_name(name), m_units(units), m_value(new T(defaultValue)), m_own(true) {
|
: m_owner(owner), m_name(name), m_units(units), m_group(group), m_value(new T(defaultValue)), m_own(true) {
|
||||||
if (m_owner) {
|
if (m_owner) {
|
||||||
m_owner->RegisterProperty(this);
|
m_owner->RegisterProperty(this);
|
||||||
}
|
}
|
||||||
@@ -76,6 +87,8 @@ public:
|
|||||||
virtual std::type_index GetTypeIndex() const override { return std::type_index(typeid(T)); }
|
virtual std::type_index GetTypeIndex() const override { return std::type_index(typeid(T)); }
|
||||||
virtual const std::string& GetUnits() const override { return m_units; }
|
virtual const std::string& GetUnits() const override { return m_units; }
|
||||||
virtual void SetUnits(const std::string& units) override { m_units = units; }
|
virtual void SetUnits(const std::string& units) override { m_units = units; }
|
||||||
|
virtual const std::string& GetGroup() const override { return m_group; }
|
||||||
|
virtual void SetGroup(const std::string& group) override { m_group = group; }
|
||||||
|
|
||||||
|
|
||||||
std::string GetValueAsString() const override {
|
std::string GetValueAsString() const override {
|
||||||
@@ -127,6 +140,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
std::string m_name;
|
std::string m_name;
|
||||||
std::string m_units;
|
std::string m_units;
|
||||||
|
std::string m_group;
|
||||||
T* m_value;
|
T* m_value;
|
||||||
bool m_own;
|
bool m_own;
|
||||||
Object* m_owner;
|
Object* m_owner;
|
||||||
@@ -149,8 +163,8 @@ typedef Property<Bool_t> BoolProperty;
|
|||||||
*/
|
*/
|
||||||
class EnumProperty : public Property<int> {
|
class EnumProperty : public Property<int> {
|
||||||
public:
|
public:
|
||||||
EnumProperty(Object* owner, const std::string& name, int* valuePtr, const std::vector<std::string>& labels, const std::string& units = "")
|
EnumProperty(Object* owner, const std::string& name, int* valuePtr, const std::vector<std::string>& labels, const std::string& units = "", const std::string& group = "")
|
||||||
: Property<int>(owner, name, valuePtr, units), m_Labels(labels) {}
|
: Property<int>(owner, name, valuePtr, units, group), m_Labels(labels) {}
|
||||||
|
|
||||||
const std::vector<std::string>& GetEnumLabels() const override { return m_Labels; }
|
const std::vector<std::string>& GetEnumLabels() const override { return m_Labels; }
|
||||||
const char* GetTypeName() const override { return "Enum"; }
|
const char* GetTypeName() const override { return "Enum"; }
|
||||||
@@ -209,11 +223,20 @@ public:
|
|||||||
boost::archive::detail::common_oarchive<property_register_archive>(boost::archive::no_header),
|
boost::archive::detail::common_oarchive<property_register_archive>(boost::archive::no_header),
|
||||||
m_Object(obj) {}
|
m_Object(obj) {}
|
||||||
|
|
||||||
|
std::string GetCurrentGroup() const {
|
||||||
|
std::string group;
|
||||||
|
for (const auto& g : m_GroupStack) {
|
||||||
|
if (!group.empty()) group += ".";
|
||||||
|
group += g;
|
||||||
|
}
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
// Core logic: encounter HRP -> Create Dynamic Property
|
// Core logic: encounter HRP -> Create Dynamic Property
|
||||||
template<class T>
|
template<class T>
|
||||||
void save_override(const boost::serialization::hrp<T> &t) {
|
void save_override(const boost::serialization::hrp<T> &t) {
|
||||||
if (m_Object) {
|
if (m_Object) {
|
||||||
Property<T>* p = new Property<T>(m_Object, t.name(), &const_cast<boost::serialization::hrp<T>&>(t).value(), t.units() ? t.units() : "");
|
Property<T>* p = new Property<T>(m_Object, t.name(), &const_cast<boost::serialization::hrp<T>&>(t).value(), t.units() ? t.units() : "", GetCurrentGroup());
|
||||||
m_Object->RegisterDynamicProperty(p);
|
m_Object->RegisterDynamicProperty(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,7 +244,7 @@ public:
|
|||||||
template<class T>
|
template<class T>
|
||||||
void save_override(const boost::serialization::hrp_enum<T> &t) {
|
void save_override(const boost::serialization::hrp_enum<T> &t) {
|
||||||
if (m_Object) {
|
if (m_Object) {
|
||||||
EnumProperty* p = new EnumProperty(m_Object, t.name(), (int*)&const_cast<boost::serialization::hrp_enum<T>&>(t).value(), t.labels(), t.units() ? t.units() : "");
|
EnumProperty* p = new EnumProperty(m_Object, t.name(), (int*)&const_cast<boost::serialization::hrp_enum<T>&>(t).value(), t.labels(), t.units() ? t.units() : "", GetCurrentGroup());
|
||||||
m_Object->RegisterDynamicProperty(p);
|
m_Object->RegisterDynamicProperty(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -229,11 +252,24 @@ public:
|
|||||||
// Handle standard NVPs by recursing (important for base classes)
|
// Handle standard NVPs by recursing (important for base classes)
|
||||||
template<class T>
|
template<class T>
|
||||||
void save_override(const boost::serialization::nvp<T> &t) {
|
void save_override(const boost::serialization::nvp<T> &t) {
|
||||||
boost::archive::detail::common_oarchive<property_register_archive>::save_override(t.const_value());
|
if (t.name()) m_GroupStack.push_back(t.name());
|
||||||
|
this->save_helper(t.const_value(), typename boost::is_class<T>::type());
|
||||||
|
if (t.name()) m_GroupStack.pop_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore everything else
|
// Recursion for nested classes, ignore primitives
|
||||||
template<class T> void save_override(const T &t) {}
|
template<class T>
|
||||||
|
void save_override(const T &t) {
|
||||||
|
this->save_helper(t, typename boost::is_class<T>::type());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
void save_helper(const T &t, boost::mpl::true_) {
|
||||||
|
boost::serialization::serialize_adl(*this, const_cast<T&>(t), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
void save_helper(const T &t, boost::mpl::false_) {}
|
||||||
|
|
||||||
// Required attribute overrides for common_oarchive
|
// Required attribute overrides for common_oarchive
|
||||||
void save_override(const boost::archive::object_id_type & t) {}
|
void save_override(const boost::archive::object_id_type & t) {}
|
||||||
@@ -244,6 +280,9 @@ public:
|
|||||||
void save_override(const boost::archive::class_id_reference_type & t) {}
|
void save_override(const boost::archive::class_id_reference_type & t) {}
|
||||||
void save_override(const boost::archive::class_name_type & t) {}
|
void save_override(const boost::archive::class_name_type & t) {}
|
||||||
void save_override(const boost::archive::tracking_type & t) {}
|
void save_override(const boost::archive::tracking_type & t) {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<std::string> m_GroupStack;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
206
src/Core/testing/AlgorithmTest.cpp
Normal file
206
src/Core/testing/AlgorithmTest.cpp
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
#include "Core/Algorithm.h"
|
||||||
|
#include <iostream>
|
||||||
|
#include <atomic>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
using namespace uLib;
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Test algorithms
|
||||||
|
|
||||||
|
class DoubleAlgorithm : public Algorithm<int, int> {
|
||||||
|
public:
|
||||||
|
const char* GetClassName() const override { return "DoubleAlgorithm"; }
|
||||||
|
int Process(const int& input) override {
|
||||||
|
m_CallCount++;
|
||||||
|
return input * 2;
|
||||||
|
}
|
||||||
|
std::atomic<int> m_CallCount{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
class StringifyAlgorithm : public Algorithm<int, std::string> {
|
||||||
|
public:
|
||||||
|
const char* GetClassName() const override { return "StringifyAlgorithm"; }
|
||||||
|
std::string Process(const int& input) override {
|
||||||
|
return std::to_string(input);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Signal source to test ConnectTrigger
|
||||||
|
class TriggerSource : public Object {
|
||||||
|
public:
|
||||||
|
const char* GetClassName() const override { return "TriggerSource"; }
|
||||||
|
signals:
|
||||||
|
virtual void DataReady() { ULIB_SIGNAL_EMIT(TriggerSource::DataReady); }
|
||||||
|
};
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Tests
|
||||||
|
|
||||||
|
void TestBasicProcess() {
|
||||||
|
std::cout << "Testing basic Algorithm::Process..." << std::endl;
|
||||||
|
DoubleAlgorithm alg;
|
||||||
|
assert(alg.Process(5) == 10);
|
||||||
|
assert(alg.Process(-3) == -6);
|
||||||
|
assert(alg.Process(0) == 0);
|
||||||
|
std::cout << " Passed." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestOperatorCall() {
|
||||||
|
std::cout << "Testing Algorithm::operator()..." << std::endl;
|
||||||
|
DoubleAlgorithm alg;
|
||||||
|
assert(alg(7) == 14);
|
||||||
|
assert(alg(0) == 0);
|
||||||
|
std::cout << " Passed." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestEncoderDecoderChain() {
|
||||||
|
std::cout << "Testing encoder/decoder chain pointers..." << std::endl;
|
||||||
|
DoubleAlgorithm a, b;
|
||||||
|
a.SetDecoder(&b);
|
||||||
|
b.SetEncoder(&a);
|
||||||
|
assert(a.GetDecoder() == &b);
|
||||||
|
assert(b.GetEncoder() == &a);
|
||||||
|
assert(a.GetEncoder() == nullptr);
|
||||||
|
assert(b.GetDecoder() == nullptr);
|
||||||
|
std::cout << " Passed." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestAlgorithmSignals() {
|
||||||
|
std::cout << "Testing Algorithm signals..." << std::endl;
|
||||||
|
DoubleAlgorithm alg;
|
||||||
|
bool started = false;
|
||||||
|
bool finished = false;
|
||||||
|
Object::connect(&alg, &DoubleAlgorithm::Started, [&]() { started = true; });
|
||||||
|
Object::connect(&alg, &DoubleAlgorithm::Finished, [&]() { finished = true; });
|
||||||
|
alg.Started();
|
||||||
|
alg.Finished();
|
||||||
|
assert(started);
|
||||||
|
assert(finished);
|
||||||
|
std::cout << " Passed." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestCyclicTask() {
|
||||||
|
std::cout << "Testing AlgorithmTask cyclic mode (Thread-based)..." << std::endl;
|
||||||
|
DoubleAlgorithm alg;
|
||||||
|
AlgorithmTask<int, int> task;
|
||||||
|
task.SetAlgorithm(&alg);
|
||||||
|
task.SetMode(AlgorithmTask<int, int>::Cyclic);
|
||||||
|
task.SetCycleTime(50);
|
||||||
|
|
||||||
|
assert(!task.IsRunning());
|
||||||
|
task.Run(5);
|
||||||
|
|
||||||
|
// Let it run for ~200ms -> expect ~4 cycles
|
||||||
|
Thread::Sleep(220);
|
||||||
|
task.Stop();
|
||||||
|
|
||||||
|
assert(!task.IsRunning());
|
||||||
|
int count = alg.m_CallCount.load();
|
||||||
|
std::cout << " Cyclic iterations: " << count << std::endl;
|
||||||
|
assert(count >= 3 && count <= 6);
|
||||||
|
std::cout << " Passed." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestAsyncTask() {
|
||||||
|
std::cout << "Testing AlgorithmTask async mode (Mutex + condition_variable)..." << std::endl;
|
||||||
|
DoubleAlgorithm alg;
|
||||||
|
AlgorithmTask<int, int> task;
|
||||||
|
task.SetAlgorithm(&alg);
|
||||||
|
task.SetMode(AlgorithmTask<int, int>::Async);
|
||||||
|
|
||||||
|
task.Run(42);
|
||||||
|
Thread::Sleep(50); // let the thread start and wait
|
||||||
|
|
||||||
|
// Trigger 3 notifications
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
task.Notify();
|
||||||
|
Thread::Sleep(30);
|
||||||
|
}
|
||||||
|
|
||||||
|
task.Stop();
|
||||||
|
int count = alg.m_CallCount.load();
|
||||||
|
std::cout << " Async invocations: " << count << std::endl;
|
||||||
|
assert(count == 3);
|
||||||
|
std::cout << " Passed." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestConnectTrigger() {
|
||||||
|
std::cout << "Testing AlgorithmTask::ConnectTrigger (signal-slot async)..." << std::endl;
|
||||||
|
DoubleAlgorithm alg;
|
||||||
|
AlgorithmTask<int, int> task;
|
||||||
|
task.SetAlgorithm(&alg);
|
||||||
|
task.SetMode(AlgorithmTask<int, int>::Async);
|
||||||
|
|
||||||
|
TriggerSource source;
|
||||||
|
task.ConnectTrigger(&source, &TriggerSource::DataReady);
|
||||||
|
|
||||||
|
task.Run(10);
|
||||||
|
Thread::Sleep(50);
|
||||||
|
|
||||||
|
// Emit signal 3 times
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
source.DataReady();
|
||||||
|
Thread::Sleep(30);
|
||||||
|
}
|
||||||
|
|
||||||
|
task.Stop();
|
||||||
|
int count = alg.m_CallCount.load();
|
||||||
|
std::cout << " Signal-triggered invocations: " << count << std::endl;
|
||||||
|
assert(count == 3);
|
||||||
|
std::cout << " Passed." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestTaskStoppedSignal() {
|
||||||
|
std::cout << "Testing AlgorithmTask Stopped signal..." << std::endl;
|
||||||
|
DoubleAlgorithm alg;
|
||||||
|
AlgorithmTask<int, int> task;
|
||||||
|
task.SetAlgorithm(&alg);
|
||||||
|
task.SetMode(AlgorithmTask<int, int>::Cyclic);
|
||||||
|
task.SetCycleTime(20);
|
||||||
|
|
||||||
|
std::atomic<bool> stopped{false};
|
||||||
|
Object::connect(&task, &AlgorithmTask<int, int>::Stopped,
|
||||||
|
[&]() { stopped.store(true); });
|
||||||
|
|
||||||
|
task.Run(1);
|
||||||
|
Thread::Sleep(50);
|
||||||
|
task.Stop();
|
||||||
|
Thread::Sleep(50);
|
||||||
|
|
||||||
|
assert(stopped.load());
|
||||||
|
std::cout << " Passed." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestClassName() {
|
||||||
|
std::cout << "Testing GetClassName..." << std::endl;
|
||||||
|
DoubleAlgorithm alg;
|
||||||
|
AlgorithmTask<int, int> task;
|
||||||
|
assert(std::string(alg.GetClassName()) == "DoubleAlgorithm");
|
||||||
|
assert(std::string(task.GetClassName()) == "AlgorithmTask");
|
||||||
|
std::cout << " Passed." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestDifferentTypes() {
|
||||||
|
std::cout << "Testing Algorithm with different enc/dec types..." << std::endl;
|
||||||
|
StringifyAlgorithm alg;
|
||||||
|
assert(alg.Process(42) == "42");
|
||||||
|
assert(alg.Process(-1) == "-1");
|
||||||
|
assert(alg(100) == "100");
|
||||||
|
std::cout << " Passed." << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
TestBasicProcess();
|
||||||
|
TestOperatorCall();
|
||||||
|
TestEncoderDecoderChain();
|
||||||
|
TestAlgorithmSignals();
|
||||||
|
TestDifferentTypes();
|
||||||
|
TestCyclicTask();
|
||||||
|
TestAsyncTask();
|
||||||
|
TestConnectTrigger();
|
||||||
|
TestTaskStoppedSignal();
|
||||||
|
TestClassName();
|
||||||
|
std::cout << "All Algorithm tests passed!" << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -23,11 +23,13 @@ set( TESTS
|
|||||||
VectorMetaAllocatorTest
|
VectorMetaAllocatorTest
|
||||||
PropertyTypesTest
|
PropertyTypesTest
|
||||||
HRPTest
|
HRPTest
|
||||||
|
PropertyGroupingTest
|
||||||
MutexTest
|
MutexTest
|
||||||
ThreadsTest
|
ThreadsTest
|
||||||
OpenMPTest
|
OpenMPTest
|
||||||
TeamTest
|
TeamTest
|
||||||
AffinityTest
|
AffinityTest
|
||||||
|
AlgorithmTest
|
||||||
)
|
)
|
||||||
|
|
||||||
set(LIBRARIES
|
set(LIBRARIES
|
||||||
|
|||||||
78
src/Core/testing/PropertyGroupingTest.cpp
Normal file
78
src/Core/testing/PropertyGroupingTest.cpp
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <cassert>
|
||||||
|
#include "Core/Object.h"
|
||||||
|
#include "Core/Property.h"
|
||||||
|
|
||||||
|
using namespace uLib;
|
||||||
|
|
||||||
|
struct Nested {
|
||||||
|
float x = 1.0f;
|
||||||
|
float y = 2.0f;
|
||||||
|
|
||||||
|
ULIB_SERIALIZE_ACCESS
|
||||||
|
template<class Archive>
|
||||||
|
void serialize(Archive & ar, const unsigned int version) {
|
||||||
|
ar & HRP(x);
|
||||||
|
ar & HRP(y);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class GroupObject : public Object {
|
||||||
|
uLibTypeMacro(GroupObject, Object)
|
||||||
|
public:
|
||||||
|
Nested position;
|
||||||
|
Nested orientation;
|
||||||
|
float weight = 50.0f;
|
||||||
|
|
||||||
|
ULIB_SERIALIZE_ACCESS
|
||||||
|
template<class Archive>
|
||||||
|
void serialize(Archive & ar, const unsigned int version) {
|
||||||
|
ar & boost::serialization::make_nvp("Position", position);
|
||||||
|
ar & boost::serialization::make_nvp("Orientation", orientation);
|
||||||
|
ar & HRP(weight);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
std::cout << "Testing Property Grouping..." << std::endl;
|
||||||
|
|
||||||
|
GroupObject obj;
|
||||||
|
ULIB_ACTIVATE_PROPERTIES(obj);
|
||||||
|
|
||||||
|
auto props = obj.GetProperties();
|
||||||
|
std::cout << "Registered " << props.size() << " properties." << std::endl;
|
||||||
|
|
||||||
|
for (auto* p : props) {
|
||||||
|
std::cout << "Prop: " << p->GetName()
|
||||||
|
<< " Group: " << p->GetGroup()
|
||||||
|
<< " Qualified: " << p->GetQualifiedName() << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if nested properties are registered
|
||||||
|
PropertyBase* p1 = obj.GetProperty("Position.x");
|
||||||
|
PropertyBase* p2 = obj.GetProperty("Position.y");
|
||||||
|
PropertyBase* p3 = obj.GetProperty("Orientation.x");
|
||||||
|
PropertyBase* p4 = obj.GetProperty("Orientation.y");
|
||||||
|
PropertyBase* p5 = obj.GetProperty("weight");
|
||||||
|
|
||||||
|
assert(p1 != nullptr && "Position.x not found");
|
||||||
|
assert(p2 != nullptr && "Position.y not found");
|
||||||
|
assert(p3 != nullptr && "Orientation.x not found");
|
||||||
|
assert(p4 != nullptr && "Orientation.y not found");
|
||||||
|
assert(p5 != nullptr && "weight not found");
|
||||||
|
|
||||||
|
assert(p1->GetGroup() == "Position");
|
||||||
|
assert(p2->GetGroup() == "Position");
|
||||||
|
assert(p3->GetGroup() == "Orientation");
|
||||||
|
assert(p4->GetGroup() == "Orientation");
|
||||||
|
assert(p5->GetGroup() == "");
|
||||||
|
|
||||||
|
assert(p1->GetQualifiedName() == "Position.x");
|
||||||
|
assert(p5->GetQualifiedName() == "weight");
|
||||||
|
|
||||||
|
std::cout << "Property Grouping Tests PASSED!" << std::endl;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -27,12 +27,16 @@
|
|||||||
#define VOXIMAGEFILTER_H
|
#define VOXIMAGEFILTER_H
|
||||||
|
|
||||||
#include "Core/StaticInterface.h"
|
#include "Core/StaticInterface.h"
|
||||||
|
#include "Core/Algorithm.h"
|
||||||
#include "Math/Dense.h"
|
#include "Math/Dense.h"
|
||||||
|
|
||||||
#include "Math/VoxImage.h"
|
#include "Math/VoxImage.h"
|
||||||
|
|
||||||
namespace uLib {
|
namespace uLib {
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Kernel shape interface (static check for operator()(float) and operator()(Vector3f))
|
||||||
|
|
||||||
namespace Interface {
|
namespace Interface {
|
||||||
struct VoxImageFilterShape {
|
struct VoxImageFilterShape {
|
||||||
template <class Self> void check_structural() {
|
template <class Self> void check_structural() {
|
||||||
@@ -42,63 +46,95 @@ struct VoxImageFilterShape {
|
|||||||
};
|
};
|
||||||
} // namespace Interface
|
} // namespace Interface
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Forward declaration
|
||||||
|
|
||||||
template <typename VoxelT> class Kernel;
|
template <typename VoxelT> class Kernel;
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Abstract interface (type-erased, used by python bindings)
|
||||||
|
|
||||||
namespace Abstract {
|
namespace Abstract {
|
||||||
class VoxImageFilter {
|
class VoxImageFilter {
|
||||||
public:
|
public:
|
||||||
virtual void Run() = 0;
|
virtual void Run() = 0;
|
||||||
|
|
||||||
virtual void SetImage(Abstract::VoxImage *image) = 0;
|
virtual void SetImage(Abstract::VoxImage *image) = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual ~VoxImageFilter() {}
|
virtual ~VoxImageFilter() {}
|
||||||
};
|
};
|
||||||
} // namespace Abstract
|
} // namespace Abstract
|
||||||
|
|
||||||
template <typename VoxelT, typename AlgorithmT>
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
class VoxImageFilter : public Abstract::VoxImageFilter, public Object {
|
// VoxImageFilter — kernel-based voxel filter using CRTP + Algorithm
|
||||||
|
//
|
||||||
|
// Template parameters:
|
||||||
|
// VoxelT — voxel data type (must satisfy Interface::Voxel)
|
||||||
|
// CrtpImplT — concrete filter subclass (CRTP), must provide:
|
||||||
|
// float Evaluate(const VoxImage<VoxelT>& buffer, int index)
|
||||||
|
//
|
||||||
|
// Inherits Algorithm<VoxImage<VoxelT>*, VoxImage<VoxelT>*> so that filters
|
||||||
|
// can be used with AlgorithmTask for scheduled/async execution, and chained
|
||||||
|
// via encoder/decoder.
|
||||||
|
|
||||||
|
template <typename VoxelT, typename CrtpImplT>
|
||||||
|
class VoxImageFilter : public Abstract::VoxImageFilter,
|
||||||
|
public Algorithm<VoxImage<VoxelT>*, VoxImage<VoxelT>*> {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
virtual const char* GetClassName() const { return "VoxImageFilter"; }
|
virtual const char* GetClassName() const { return "VoxImageFilter"; }
|
||||||
|
|
||||||
VoxImageFilter(const Vector3i &size);
|
VoxImageFilter(const Vector3i &size);
|
||||||
|
|
||||||
|
// Algorithm interface ////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Process implements Algorithm::Process.
|
||||||
|
* Applies the filter in-place on the input image and returns it.
|
||||||
|
*/
|
||||||
|
VoxImage<VoxelT>* Process(VoxImage<VoxelT>* const& image) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Run implements Abstract::VoxImageFilter::Run.
|
||||||
|
* Calls Process on the current image.
|
||||||
|
*/
|
||||||
void Run();
|
void Run();
|
||||||
|
|
||||||
|
// Device awareness ///////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/** @brief Returns VRAM if image or kernel data is on GPU, RAM otherwise. */
|
||||||
|
MemoryDevice GetPreferredDevice() const override {
|
||||||
|
if (m_Image && m_Image->Data().GetDevice() == MemoryDevice::VRAM)
|
||||||
|
return MemoryDevice::VRAM;
|
||||||
|
if (m_KernelData.ConstData().GetDevice() == MemoryDevice::VRAM)
|
||||||
|
return MemoryDevice::VRAM;
|
||||||
|
return MemoryDevice::RAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kernel setup ///////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
void SetKernelNumericXZY(const std::vector<float> &numeric);
|
void SetKernelNumericXZY(const std::vector<float> &numeric);
|
||||||
|
|
||||||
void SetKernelSpherical(float (*shape)(float));
|
void SetKernelSpherical(float (*shape)(float));
|
||||||
|
|
||||||
template <class ShapeT> void SetKernelSpherical(ShapeT shape);
|
template <class ShapeT> void SetKernelSpherical(ShapeT shape);
|
||||||
|
|
||||||
void SetKernelWeightFunction(float (*shape)(const Vector3f &));
|
void SetKernelWeightFunction(float (*shape)(const Vector3f &));
|
||||||
|
|
||||||
template <class ShapeT> void SetKernelWeightFunction(ShapeT shape);
|
template <class ShapeT> void SetKernelWeightFunction(ShapeT shape);
|
||||||
|
|
||||||
inline const Kernel<VoxelT> &GetKernelData() const {
|
// Accessors //////////////////////////////////////////////////////////////////
|
||||||
return this->m_KernelData;
|
|
||||||
}
|
|
||||||
inline Kernel<VoxelT> &GetKernelData() { return this->m_KernelData; }
|
|
||||||
|
|
||||||
inline VoxImage<VoxelT> *GetImage() const { return this->m_Image; }
|
const Kernel<VoxelT> &GetKernelData() const { return m_KernelData; }
|
||||||
|
Kernel<VoxelT> &GetKernelData() { return m_KernelData; }
|
||||||
|
|
||||||
|
VoxImage<VoxelT> *GetImage() const { return m_Image; }
|
||||||
void SetImage(Abstract::VoxImage *image);
|
void SetImage(Abstract::VoxImage *image);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
float Convolve(const VoxImage<VoxelT> &buffer, int index); // remove //
|
|
||||||
|
|
||||||
void SetKernelOffset();
|
void SetKernelOffset();
|
||||||
|
|
||||||
float Distance2(const Vector3i &v);
|
float Distance2(const Vector3i &v);
|
||||||
|
|
||||||
// protected members for algorithm access //
|
|
||||||
Kernel<VoxelT> m_KernelData;
|
Kernel<VoxelT> m_KernelData;
|
||||||
VoxImage<VoxelT> *m_Image;
|
VoxImage<VoxelT> *m_Image;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AlgorithmT *t_Algoritm;
|
CrtpImplT *m_CrtpImpl;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace uLib
|
} // namespace uLib
|
||||||
|
|||||||
@@ -33,7 +33,9 @@
|
|||||||
|
|
||||||
namespace uLib {
|
namespace uLib {
|
||||||
|
|
||||||
// KERNEL //////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//// KERNEL ////////////////////////////////////////////////////////////////////
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
template <typename T> class Kernel : public StructuredData {
|
template <typename T> class Kernel : public StructuredData {
|
||||||
typedef StructuredData BaseClass;
|
typedef StructuredData BaseClass;
|
||||||
@@ -41,13 +43,12 @@ template <typename T> class Kernel : public StructuredData {
|
|||||||
public:
|
public:
|
||||||
Kernel(const Vector3i &size);
|
Kernel(const Vector3i &size);
|
||||||
|
|
||||||
inline T &operator[](const Vector3i &id) { return m_Data[Map(id)]; }
|
T &operator[](const Vector3i &id) { return m_Data[Map(id)]; }
|
||||||
inline T &operator[](const int &id) { return m_Data[id]; }
|
T &operator[](const int &id) { return m_Data[id]; }
|
||||||
inline int GetCenterData() const;
|
int GetCenterData() const;
|
||||||
|
|
||||||
inline DataAllocator<T> &Data() { return this->m_Data; }
|
DataAllocator<T> &Data() { return m_Data; }
|
||||||
|
const DataAllocator<T> &ConstData() const { return m_Data; }
|
||||||
inline const DataAllocator<T> &ConstData() const { return this->m_Data; }
|
|
||||||
|
|
||||||
void PrintSelf(std::ostream &o) const;
|
void PrintSelf(std::ostream &o) const;
|
||||||
|
|
||||||
@@ -60,12 +61,14 @@ Kernel<T>::Kernel(const Vector3i &size) : BaseClass(size), m_Data(size.prod()) {
|
|||||||
Interface::IsA<T, Interface::Voxel>();
|
Interface::IsA<T, Interface::Voxel>();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T> inline int Kernel<T>::GetCenterData() const {
|
template <typename T>
|
||||||
|
int Kernel<T>::GetCenterData() const {
|
||||||
static int center = Map(this->GetDims() / 2);
|
static int center = Map(this->GetDims() / 2);
|
||||||
return center;
|
return center;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T> void Kernel<T>::PrintSelf(std::ostream &o) const {
|
template <typename T>
|
||||||
|
void Kernel<T>::PrintSelf(std::ostream &o) const {
|
||||||
o << " Filter Kernel Dump [XZ_Y]: \n";
|
o << " Filter Kernel Dump [XZ_Y]: \n";
|
||||||
Vector3i index;
|
Vector3i index;
|
||||||
o << "\n Value: \n\n"
|
o << "\n Value: \n\n"
|
||||||
@@ -96,26 +99,42 @@ template <typename T> void Kernel<T>::PrintSelf(std::ostream &o) const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//// VOXIMAGEFILTER IMPLEMENTATION /////////////////////////////////////////////
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
#define _TPL_ template <typename VoxelT, typename AlgorithmT>
|
template <typename VoxelT, typename CrtpImplT>
|
||||||
#define _TPLT_ VoxelT, AlgorithmT
|
VoxImageFilter<VoxelT, CrtpImplT>::VoxImageFilter(const Vector3i &size)
|
||||||
|
: m_KernelData(size)
|
||||||
|
, m_Image(nullptr)
|
||||||
|
, m_CrtpImpl(static_cast<CrtpImplT *>(this))
|
||||||
|
{}
|
||||||
|
|
||||||
_TPL_
|
template <typename VoxelT, typename CrtpImplT>
|
||||||
VoxImageFilter<_TPLT_>::VoxImageFilter(const Vector3i &size)
|
VoxImage<VoxelT>* VoxImageFilter<VoxelT, CrtpImplT>::Process(
|
||||||
: m_KernelData(size), t_Algoritm(static_cast<AlgorithmT *>(this)) {}
|
VoxImage<VoxelT>* const& image) {
|
||||||
|
if (m_Image != image) SetImage(image);
|
||||||
_TPL_
|
|
||||||
void VoxImageFilter<_TPLT_>::Run() {
|
|
||||||
VoxImage<VoxelT> buffer = *m_Image;
|
VoxImage<VoxelT> buffer = *m_Image;
|
||||||
#pragma omp parallel for
|
#pragma omp parallel for
|
||||||
for (int i = 0; i < m_Image->Data().size(); ++i)
|
for (int i = 0; i < m_Image->Data().size(); ++i)
|
||||||
m_Image->operator[](i).Value = this->t_Algoritm->Evaluate(buffer, i);
|
m_Image->operator[](i).Value = m_CrtpImpl->Evaluate(buffer, i);
|
||||||
#pragma omp barrier
|
#pragma omp barrier
|
||||||
|
return m_Image;
|
||||||
}
|
}
|
||||||
|
|
||||||
_TPL_
|
template <typename VoxelT, typename CrtpImplT>
|
||||||
void VoxImageFilter<_TPLT_>::SetKernelOffset() {
|
void VoxImageFilter<VoxelT, CrtpImplT>::Run() {
|
||||||
|
Process(m_Image);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename VoxelT, typename CrtpImplT>
|
||||||
|
void VoxImageFilter<VoxelT, CrtpImplT>::SetImage(Abstract::VoxImage *image) {
|
||||||
|
m_Image = reinterpret_cast<VoxImage<VoxelT> *>(image);
|
||||||
|
SetKernelOffset();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename VoxelT, typename CrtpImplT>
|
||||||
|
void VoxImageFilter<VoxelT, CrtpImplT>::SetKernelOffset() {
|
||||||
Vector3i id(0, 0, 0);
|
Vector3i id(0, 0, 0);
|
||||||
for (int z = 0; z < m_KernelData.GetDims()(2); ++z) {
|
for (int z = 0; z < m_KernelData.GetDims()(2); ++z) {
|
||||||
for (int x = 0; x < m_KernelData.GetDims()(0); ++x) {
|
for (int x = 0; x < m_KernelData.GetDims()(0); ++x) {
|
||||||
@@ -127,10 +146,10 @@ void VoxImageFilter<_TPLT_>::SetKernelOffset() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_TPL_
|
template <typename VoxelT, typename CrtpImplT>
|
||||||
float VoxImageFilter<_TPLT_>::Distance2(const Vector3i &v) {
|
float VoxImageFilter<VoxelT, CrtpImplT>::Distance2(const Vector3i &v) {
|
||||||
Vector3i tmp = v;
|
Vector3i tmp = v;
|
||||||
const Vector3i &dim = this->m_KernelData.GetDims();
|
const Vector3i &dim = m_KernelData.GetDims();
|
||||||
Vector3i center = dim / 2;
|
Vector3i center = dim / 2;
|
||||||
tmp = tmp - center;
|
tmp = tmp - center;
|
||||||
center = center.cwiseProduct(center);
|
center = center.cwiseProduct(center);
|
||||||
@@ -140,12 +159,9 @@ float VoxImageFilter<_TPLT_>::Distance2(const Vector3i &v) {
|
|||||||
0.25 * (3 - (dim(0) % 2) - (dim(1) % 2) - (dim(2) % 2)));
|
0.25 * (3 - (dim(0) % 2) - (dim(1) % 2) - (dim(2) % 2)));
|
||||||
}
|
}
|
||||||
|
|
||||||
_TPL_
|
template <typename VoxelT, typename CrtpImplT>
|
||||||
void VoxImageFilter<_TPLT_>::SetKernelNumericXZY(
|
void VoxImageFilter<VoxelT, CrtpImplT>::SetKernelNumericXZY(
|
||||||
const std::vector<float> &numeric) {
|
const std::vector<float> &numeric) {
|
||||||
// set data order //
|
|
||||||
StructuredData::Order order = m_KernelData.GetDataOrder();
|
|
||||||
// m_KernelData.SetDataOrder(StructuredData::XZY);
|
|
||||||
Vector3i id;
|
Vector3i id;
|
||||||
int index = 0;
|
int index = 0;
|
||||||
for (int y = 0; y < m_KernelData.GetDims()(1); ++y) {
|
for (int y = 0; y < m_KernelData.GetDims()(1); ++y) {
|
||||||
@@ -156,38 +172,39 @@ void VoxImageFilter<_TPLT_>::SetKernelNumericXZY(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// m_KernelData.SetDataOrder(order);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_TPL_
|
template <typename VoxelT, typename CrtpImplT>
|
||||||
void VoxImageFilter<_TPLT_>::SetKernelSpherical(float (*shape)(float)) {
|
void VoxImageFilter<VoxelT, CrtpImplT>::SetKernelSpherical(
|
||||||
|
float (*shape)(float)) {
|
||||||
Vector3i id;
|
Vector3i id;
|
||||||
for (int y = 0; y < m_KernelData.GetDims()(1); ++y) {
|
for (int y = 0; y < m_KernelData.GetDims()(1); ++y) {
|
||||||
for (int z = 0; z < m_KernelData.GetDims()(2); ++z) {
|
for (int z = 0; z < m_KernelData.GetDims()(2); ++z) {
|
||||||
for (int x = 0; x < m_KernelData.GetDims()(0); ++x) {
|
for (int x = 0; x < m_KernelData.GetDims()(0); ++x) {
|
||||||
id << x, y, z;
|
id << x, y, z;
|
||||||
m_KernelData[id].Value = shape(this->Distance2(id));
|
m_KernelData[id].Value = shape(Distance2(id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_TPL_ template <class ShapeT>
|
template <typename VoxelT, typename CrtpImplT>
|
||||||
void VoxImageFilter<_TPLT_>::SetKernelSpherical(ShapeT shape) {
|
template <class ShapeT>
|
||||||
|
void VoxImageFilter<VoxelT, CrtpImplT>::SetKernelSpherical(ShapeT shape) {
|
||||||
Interface::IsA<ShapeT, Interface::VoxImageFilterShape>();
|
Interface::IsA<ShapeT, Interface::VoxImageFilterShape>();
|
||||||
Vector3i id;
|
Vector3i id;
|
||||||
for (int y = 0; y < m_KernelData.GetDims()(1); ++y) {
|
for (int y = 0; y < m_KernelData.GetDims()(1); ++y) {
|
||||||
for (int z = 0; z < m_KernelData.GetDims()(2); ++z) {
|
for (int z = 0; z < m_KernelData.GetDims()(2); ++z) {
|
||||||
for (int x = 0; x < m_KernelData.GetDims()(0); ++x) {
|
for (int x = 0; x < m_KernelData.GetDims()(0); ++x) {
|
||||||
id << x, y, z;
|
id << x, y, z;
|
||||||
m_KernelData[id].Value = shape(this->Distance2(id));
|
m_KernelData[id].Value = shape(Distance2(id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_TPL_
|
template <typename VoxelT, typename CrtpImplT>
|
||||||
void VoxImageFilter<_TPLT_>::SetKernelWeightFunction(
|
void VoxImageFilter<VoxelT, CrtpImplT>::SetKernelWeightFunction(
|
||||||
float (*shape)(const Vector3f &)) {
|
float (*shape)(const Vector3f &)) {
|
||||||
const Vector3i &dim = m_KernelData.GetDims();
|
const Vector3i &dim = m_KernelData.GetDims();
|
||||||
Vector3i id;
|
Vector3i id;
|
||||||
@@ -195,20 +212,19 @@ void VoxImageFilter<_TPLT_>::SetKernelWeightFunction(
|
|||||||
for (int y = 0; y < dim(1); ++y) {
|
for (int y = 0; y < dim(1); ++y) {
|
||||||
for (int z = 0; z < dim(2); ++z) {
|
for (int z = 0; z < dim(2); ++z) {
|
||||||
for (int x = 0; x < dim(0); ++x) {
|
for (int x = 0; x < dim(0); ++x) {
|
||||||
// get voxels centroid coords from kernel center //
|
|
||||||
id << x, y, z;
|
id << x, y, z;
|
||||||
pt << id(0) - dim(0) / 2 + 0.5 * !(dim(0) % 2),
|
pt << id(0) - dim(0) / 2 + 0.5 * !(dim(0) % 2),
|
||||||
id(1) - dim(1) / 2 + 0.5 * !(dim(1) % 2),
|
id(1) - dim(1) / 2 + 0.5 * !(dim(1) % 2),
|
||||||
id(2) - dim(2) / 2 + 0.5 * !(dim(2) % 2);
|
id(2) - dim(2) / 2 + 0.5 * !(dim(2) % 2);
|
||||||
// compute function using given shape //
|
|
||||||
m_KernelData[id].Value = shape(pt);
|
m_KernelData[id].Value = shape(pt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_TPL_ template <class ShapeT>
|
template <typename VoxelT, typename CrtpImplT>
|
||||||
void VoxImageFilter<_TPLT_>::SetKernelWeightFunction(ShapeT shape) {
|
template <class ShapeT>
|
||||||
|
void VoxImageFilter<VoxelT, CrtpImplT>::SetKernelWeightFunction(ShapeT shape) {
|
||||||
Interface::IsA<ShapeT, Interface::VoxImageFilterShape>();
|
Interface::IsA<ShapeT, Interface::VoxImageFilterShape>();
|
||||||
const Vector3i &dim = m_KernelData.GetDims();
|
const Vector3i &dim = m_KernelData.GetDims();
|
||||||
Vector3i id;
|
Vector3i id;
|
||||||
@@ -216,45 +232,16 @@ void VoxImageFilter<_TPLT_>::SetKernelWeightFunction(ShapeT shape) {
|
|||||||
for (int y = 0; y < dim(1); ++y) {
|
for (int y = 0; y < dim(1); ++y) {
|
||||||
for (int z = 0; z < dim(2); ++z) {
|
for (int z = 0; z < dim(2); ++z) {
|
||||||
for (int x = 0; x < dim(0); ++x) {
|
for (int x = 0; x < dim(0); ++x) {
|
||||||
// get voxels centroid coords from kernel center //
|
|
||||||
id << x, y, z;
|
id << x, y, z;
|
||||||
pt << id(0) - dim(0) / 2 + 0.5 * !(dim(0) % 2),
|
pt << id(0) - dim(0) / 2 + 0.5 * !(dim(0) % 2),
|
||||||
id(1) - dim(1) / 2 + 0.5 * !(dim(1) % 2),
|
id(1) - dim(1) / 2 + 0.5 * !(dim(1) % 2),
|
||||||
id(2) - dim(2) / 2 + 0.5 * !(dim(2) % 2);
|
id(2) - dim(2) / 2 + 0.5 * !(dim(2) % 2);
|
||||||
// compute function using given shape //
|
|
||||||
m_KernelData[id].Value = shape(pt);
|
m_KernelData[id].Value = shape(pt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_TPL_
|
|
||||||
void VoxImageFilter<_TPLT_>::SetImage(Abstract::VoxImage *image) {
|
|
||||||
this->m_Image = reinterpret_cast<VoxImage<VoxelT> *>(image);
|
|
||||||
this->SetKernelOffset();
|
|
||||||
}
|
|
||||||
|
|
||||||
_TPL_
|
|
||||||
float VoxImageFilter<_TPLT_>::Convolve(const VoxImage<VoxelT> &buffer,
|
|
||||||
int index) {
|
|
||||||
const DataAllocator<VoxelT> &vbuf = buffer.ConstData();
|
|
||||||
const DataAllocator<VoxelT> &vker = m_KernelData.ConstData();
|
|
||||||
int vox_size = vbuf.size();
|
|
||||||
int ker_size = vker.size();
|
|
||||||
int pos;
|
|
||||||
float conv = 0, ksum = 0;
|
|
||||||
for (int ik = 0; ik < ker_size; ++ik) {
|
|
||||||
pos = index + vker[ik].Count - vker[m_KernelData.GetCenterData()].Count;
|
|
||||||
pos = (pos + vox_size) % vox_size;
|
|
||||||
conv += vbuf[pos].Value * vker[ik].Value;
|
|
||||||
ksum += vker[ik].Value;
|
|
||||||
}
|
|
||||||
return conv / ksum;
|
|
||||||
}
|
|
||||||
|
|
||||||
#undef _TPLT_
|
|
||||||
#undef _TPL_
|
|
||||||
|
|
||||||
} // namespace uLib
|
} // namespace uLib
|
||||||
|
|
||||||
#endif // VOXIMAGEFILTER_HPP
|
#endif // VOXIMAGEFILTER_HPP
|
||||||
|
|||||||
@@ -109,7 +109,8 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if defined(USE_CUDA) && defined(__CUDACC__)
|
#if defined(USE_CUDA) && defined(__CUDACC__)
|
||||||
void Run() {
|
VoxImage<VoxelT>* Process(VoxImage<VoxelT>* const& image) override {
|
||||||
|
if (this->m_Image != image) this->SetImage(image);
|
||||||
if (this->m_Image->Data().GetDevice() == MemoryDevice::VRAM ||
|
if (this->m_Image->Data().GetDevice() == MemoryDevice::VRAM ||
|
||||||
this->m_KernelData.Data().GetDevice() == MemoryDevice::VRAM) {
|
this->m_KernelData.Data().GetDevice() == MemoryDevice::VRAM) {
|
||||||
|
|
||||||
@@ -136,8 +137,9 @@ public:
|
|||||||
d_img_in, d_img_out, d_kernel, vox_size, ker_size, center_count,
|
d_img_in, d_img_out, d_kernel, vox_size, ker_size, center_count,
|
||||||
mAtrim, mBtrim);
|
mAtrim, mBtrim);
|
||||||
cudaDeviceSynchronize();
|
cudaDeviceSynchronize();
|
||||||
|
return this->m_Image;
|
||||||
} else {
|
} else {
|
||||||
BaseClass::Run();
|
return BaseClass::Process(image);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -207,7 +209,8 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if defined(USE_CUDA) && defined(__CUDACC__)
|
#if defined(USE_CUDA) && defined(__CUDACC__)
|
||||||
void Run() {
|
VoxImage<VoxelT>* Process(VoxImage<VoxelT>* const& image) override {
|
||||||
|
if (this->m_Image != image) this->SetImage(image);
|
||||||
if (this->m_Image->Data().GetDevice() == MemoryDevice::VRAM ||
|
if (this->m_Image->Data().GetDevice() == MemoryDevice::VRAM ||
|
||||||
this->m_KernelData.Data().GetDevice() == MemoryDevice::VRAM) {
|
this->m_KernelData.Data().GetDevice() == MemoryDevice::VRAM) {
|
||||||
|
|
||||||
@@ -234,8 +237,9 @@ public:
|
|||||||
d_img_in, d_img_out, d_kernel, vox_size, ker_size, center_count,
|
d_img_in, d_img_out, d_kernel, vox_size, ker_size, center_count,
|
||||||
mAtrim, mBtrim);
|
mAtrim, mBtrim);
|
||||||
cudaDeviceSynchronize();
|
cudaDeviceSynchronize();
|
||||||
|
return this->m_Image;
|
||||||
} else {
|
} else {
|
||||||
BaseClass::Run();
|
return BaseClass::Process(image);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -30,8 +30,6 @@
|
|||||||
#include "VoxImageFilter.h"
|
#include "VoxImageFilter.h"
|
||||||
#include <Math/Dense.h>
|
#include <Math/Dense.h>
|
||||||
|
|
||||||
#define likely(expr) __builtin_expect(!!(expr), 1)
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
///// VOXIMAGE FILTER CUSTOM /////////////////////////////////////////////////
|
///// VOXIMAGE FILTER CUSTOM /////////////////////////////////////////////////
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
@@ -50,7 +48,7 @@ public:
|
|||||||
: BaseClass(size), m_CustomEvaluate(NULL) {}
|
: BaseClass(size), m_CustomEvaluate(NULL) {}
|
||||||
|
|
||||||
float Evaluate(const VoxImage<VoxelT> &buffer, int index) {
|
float Evaluate(const VoxImage<VoxelT> &buffer, int index) {
|
||||||
if (likely(m_CustomEvaluate)) {
|
if (m_CustomEvaluate) {
|
||||||
const DataAllocator<VoxelT> &vbuf = buffer.ConstData();
|
const DataAllocator<VoxelT> &vbuf = buffer.ConstData();
|
||||||
const DataAllocator<VoxelT> &vker = this->m_KernelData.ConstData();
|
const DataAllocator<VoxelT> &vker = this->m_KernelData.ConstData();
|
||||||
int vox_size = vbuf.size();
|
int vox_size = vbuf.size();
|
||||||
|
|||||||
@@ -67,7 +67,8 @@ public:
|
|||||||
VoxFilterAlgorithmLinear(const Vector3i &size) : BaseClass(size) {}
|
VoxFilterAlgorithmLinear(const Vector3i &size) : BaseClass(size) {}
|
||||||
|
|
||||||
#if defined(USE_CUDA) && defined(__CUDACC__)
|
#if defined(USE_CUDA) && defined(__CUDACC__)
|
||||||
void Run() {
|
VoxImage<VoxelT>* Process(VoxImage<VoxelT>* const& image) override {
|
||||||
|
if (this->m_Image != image) this->SetImage(image);
|
||||||
if (this->m_Image->Data().GetDevice() == MemoryDevice::VRAM ||
|
if (this->m_Image->Data().GetDevice() == MemoryDevice::VRAM ||
|
||||||
this->m_KernelData.Data().GetDevice() == MemoryDevice::VRAM) {
|
this->m_KernelData.Data().GetDevice() == MemoryDevice::VRAM) {
|
||||||
|
|
||||||
@@ -92,8 +93,9 @@ public:
|
|||||||
LinearFilterKernel<<<blocksPerGrid, threadsPerBlock>>>(
|
LinearFilterKernel<<<blocksPerGrid, threadsPerBlock>>>(
|
||||||
d_img_in, d_img_out, d_kernel, vox_size, ker_size, center_count);
|
d_img_in, d_img_out, d_kernel, vox_size, ker_size, center_count);
|
||||||
cudaDeviceSynchronize();
|
cudaDeviceSynchronize();
|
||||||
|
return this->m_Image;
|
||||||
} else {
|
} else {
|
||||||
BaseClass::Run();
|
return BaseClass::Process(image);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -23,8 +23,6 @@
|
|||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////*/
|
//////////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef VOXIMAGEFILTERTHRESHOLD_HPP
|
#ifndef VOXIMAGEFILTERTHRESHOLD_HPP
|
||||||
#define VOXIMAGEFILTERTHRESHOLD_HPP
|
#define VOXIMAGEFILTERTHRESHOLD_HPP
|
||||||
|
|
||||||
@@ -39,40 +37,24 @@
|
|||||||
namespace uLib {
|
namespace uLib {
|
||||||
|
|
||||||
template <typename VoxelT>
|
template <typename VoxelT>
|
||||||
class VoxFilterAlgorithmThreshold :
|
class VoxFilterAlgorithmThreshold
|
||||||
public VoxImageFilter<VoxelT, VoxFilterAlgorithmThreshold<VoxelT> > {
|
: public VoxImageFilter<VoxelT, VoxFilterAlgorithmThreshold<VoxelT>> {
|
||||||
|
|
||||||
typedef VoxImageFilter<VoxelT, VoxFilterAlgorithmThreshold<VoxelT>> BaseClass;
|
typedef VoxImageFilter<VoxelT, VoxFilterAlgorithmThreshold<VoxelT>> BaseClass;
|
||||||
// ULIB_OBJECT_PARAMETERS(BaseClass) {
|
|
||||||
// float threshold;
|
|
||||||
// };
|
|
||||||
|
|
||||||
float m_threshold;
|
float m_threshold;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
VoxFilterAlgorithmThreshold(const Vector3i &size) : BaseClass(size)
|
VoxFilterAlgorithmThreshold(const Vector3i &size)
|
||||||
{
|
: BaseClass(size), m_threshold(0) {}
|
||||||
// init_parameters();
|
|
||||||
m_threshold = 0;
|
void SetThreshold(float th) { m_threshold = th; }
|
||||||
|
|
||||||
|
float Evaluate(const VoxImage<VoxelT> &buffer, int index) {
|
||||||
|
return static_cast<float>(buffer.ConstData().at(index).Value >= m_threshold);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void SetThreshold(float th) { m_threshold = th; }
|
|
||||||
|
|
||||||
float Evaluate(const VoxImage<VoxelT> &buffer, int index)
|
|
||||||
{
|
|
||||||
return static_cast<float>(buffer.ConstData().at(index).Value >=
|
|
||||||
// parameters().threshold);
|
|
||||||
m_threshold );
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//template <typename VoxelT>
|
} // namespace uLib
|
||||||
//inline void VoxFilterAlgorithmThreshold<VoxelT>::init_parameters()
|
|
||||||
//{
|
|
||||||
// parameters().threshold = 0;
|
|
||||||
//}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // VOXIMAGEFILTERTHRESHOLD_HPP
|
#endif // VOXIMAGEFILTERTHRESHOLD_HPP
|
||||||
|
|||||||
408
src/Math/testing/AlgorithmCudaChainTest.cpp
Normal file
408
src/Math/testing/AlgorithmCudaChainTest.cpp
Normal file
@@ -0,0 +1,408 @@
|
|||||||
|
/*//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// CMT Cosmic Muon Tomography project //////////////////////////////////////////
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
Copyright (c) 2014, Universita' degli Studi di Padova, INFN sez. di Padova
|
||||||
|
All rights reserved
|
||||||
|
|
||||||
|
Authors: Andrea Rigoni Garola < andrea.rigoni@pd.infn.it >
|
||||||
|
|
||||||
|
------------------------------------------------------------------
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 3.0 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library.
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////*/
|
||||||
|
|
||||||
|
#include "testing-prototype.h"
|
||||||
|
|
||||||
|
#include "Core/Algorithm.h"
|
||||||
|
#include "Math/VoxImage.h"
|
||||||
|
#include "Math/VoxImageFilter.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <thread>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
using namespace uLib;
|
||||||
|
|
||||||
|
struct TestVoxel {
|
||||||
|
Scalarf Value;
|
||||||
|
unsigned int Count;
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
BEGIN_TESTING(AlgorithmCudaChain);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// TEST 1: Single filter — GetPreferredDevice reflects data location
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
std::cout << "\n--- Test 1: GetPreferredDevice reflects data location ---\n";
|
||||||
|
|
||||||
|
VoxImage<TestVoxel> image(Vector3i(10, 10, 10));
|
||||||
|
image[Vector3i(5, 5, 5)].Value = 1;
|
||||||
|
|
||||||
|
VoxFilterAlgorithmLinear<TestVoxel> filter(Vector3i(3, 3, 3));
|
||||||
|
std::vector<float> weights(27, 1.0f);
|
||||||
|
filter.SetImage(&image);
|
||||||
|
filter.SetKernelNumericXZY(weights);
|
||||||
|
|
||||||
|
// Before VRAM move: should prefer RAM
|
||||||
|
TEST1(filter.GetPreferredDevice() == MemoryDevice::RAM);
|
||||||
|
TEST1(!filter.IsGPU());
|
||||||
|
std::cout << " RAM mode: PreferredDevice=RAM, IsGPU=false OK\n";
|
||||||
|
|
||||||
|
// Move image data to VRAM
|
||||||
|
image.Data().MoveToVRAM();
|
||||||
|
|
||||||
|
// After VRAM move: should prefer VRAM
|
||||||
|
TEST1(filter.GetPreferredDevice() == MemoryDevice::VRAM);
|
||||||
|
TEST1(filter.IsGPU());
|
||||||
|
std::cout << " VRAM mode: PreferredDevice=VRAM, IsGPU=true OK\n";
|
||||||
|
|
||||||
|
// Move back to RAM
|
||||||
|
image.Data().MoveToRAM();
|
||||||
|
TEST1(filter.GetPreferredDevice() == MemoryDevice::RAM);
|
||||||
|
std::cout << " Back to RAM: PreferredDevice=RAM OK\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// TEST 2: Kernel data on VRAM also triggers GPU preference
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
std::cout << "\n--- Test 2: Kernel on VRAM triggers GPU preference ---\n";
|
||||||
|
|
||||||
|
VoxImage<TestVoxel> image(Vector3i(8, 8, 8));
|
||||||
|
VoxFilterAlgorithmLinear<TestVoxel> filter(Vector3i(3, 3, 3));
|
||||||
|
std::vector<float> weights(27, 1.0f);
|
||||||
|
filter.SetImage(&image);
|
||||||
|
filter.SetKernelNumericXZY(weights);
|
||||||
|
|
||||||
|
TEST1(filter.GetPreferredDevice() == MemoryDevice::RAM);
|
||||||
|
|
||||||
|
// Only kernel on VRAM
|
||||||
|
filter.GetKernelData().Data().MoveToVRAM();
|
||||||
|
TEST1(filter.GetPreferredDevice() == MemoryDevice::VRAM);
|
||||||
|
std::cout << " Kernel on VRAM: PreferredDevice=VRAM OK\n";
|
||||||
|
|
||||||
|
filter.GetKernelData().Data().MoveToRAM();
|
||||||
|
TEST1(filter.GetPreferredDevice() == MemoryDevice::RAM);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// TEST 3: Algorithm interface — Process through base pointer
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
std::cout << "\n--- Test 3: Process through Algorithm base pointer ---\n";
|
||||||
|
|
||||||
|
VoxImage<TestVoxel> image(Vector3i(10, 10, 10));
|
||||||
|
image[Vector3i(5, 5, 5)].Value = 10;
|
||||||
|
|
||||||
|
VoxFilterAlgorithmLinear<TestVoxel> filter(Vector3i(3, 3, 3));
|
||||||
|
std::vector<float> weights(27, 1.0f);
|
||||||
|
filter.SetImage(&image);
|
||||||
|
filter.SetKernelNumericXZY(weights);
|
||||||
|
|
||||||
|
// Use through Algorithm base class pointer
|
||||||
|
Algorithm<VoxImage<TestVoxel>*, VoxImage<TestVoxel>*>* alg = &filter;
|
||||||
|
|
||||||
|
VoxImage<TestVoxel>* result = alg->Process(&image);
|
||||||
|
TEST1(result == &image);
|
||||||
|
std::cout << " Process through base pointer returned correct image OK\n";
|
||||||
|
|
||||||
|
// Verify filter actually ran (center voxel should be averaged)
|
||||||
|
// With uniform 3x3x3 kernel and single non-zero voxel at center,
|
||||||
|
// the center value should be 10/27 ≈ 0.37
|
||||||
|
TEST1(image[Vector3i(5, 5, 5)].Value < 10.0f);
|
||||||
|
std::cout << " Filter modified voxel values OK\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// TEST 4: Encoder/decoder chain — two filters linked
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
std::cout << "\n--- Test 4: Encoder/decoder chain ---\n";
|
||||||
|
|
||||||
|
VoxImage<TestVoxel> image(Vector3i(10, 10, 10));
|
||||||
|
image[Vector3i(5, 5, 5)].Value = 100;
|
||||||
|
|
||||||
|
// First filter: linear smoothing
|
||||||
|
VoxFilterAlgorithmLinear<TestVoxel> filter1(Vector3i(3, 3, 3));
|
||||||
|
std::vector<float> weights1(27, 1.0f);
|
||||||
|
filter1.SetImage(&image);
|
||||||
|
filter1.SetKernelNumericXZY(weights1);
|
||||||
|
|
||||||
|
// Second filter: threshold
|
||||||
|
VoxFilterAlgorithmThreshold<TestVoxel> filter2(Vector3i(1, 1, 1));
|
||||||
|
filter2.SetThreshold(0.5f);
|
||||||
|
filter2.SetImage(&image);
|
||||||
|
// 1x1x1 kernel with value 1
|
||||||
|
std::vector<float> weights2(1, 1.0f);
|
||||||
|
filter2.SetKernelNumericXZY(weights2);
|
||||||
|
|
||||||
|
// Chain: filter1 → filter2
|
||||||
|
filter1.SetDecoder(&filter2);
|
||||||
|
filter2.SetEncoder(&filter1);
|
||||||
|
|
||||||
|
TEST1(filter1.GetDecoder() == &filter2);
|
||||||
|
TEST1(filter2.GetEncoder() == &filter1);
|
||||||
|
std::cout << " Chain linked: filter1 -> filter2 OK\n";
|
||||||
|
|
||||||
|
// Execute chain manually (encoder first, then decoder)
|
||||||
|
filter1.Process(&image);
|
||||||
|
float smoothed_center = image[Vector3i(5, 5, 5)].Value;
|
||||||
|
std::cout << " After linear: center = " << smoothed_center << "\n";
|
||||||
|
|
||||||
|
filter2.Process(&image);
|
||||||
|
float thresholded_center = image[Vector3i(5, 5, 5)].Value;
|
||||||
|
std::cout << " After threshold: center = " << thresholded_center << "\n";
|
||||||
|
|
||||||
|
// After threshold, values should be 0 or 1
|
||||||
|
TEST1(thresholded_center == 0.0f || thresholded_center == 1.0f);
|
||||||
|
std::cout << " Chain execution produced valid results OK\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// TEST 5: CUDA chain — VRAM data through chained filters
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
std::cout << "\n--- Test 5: VRAM data through chained filters ---\n";
|
||||||
|
|
||||||
|
VoxImage<TestVoxel> image(Vector3i(10, 10, 10));
|
||||||
|
image[Vector3i(5, 5, 5)].Value = 50;
|
||||||
|
|
||||||
|
VoxFilterAlgorithmLinear<TestVoxel> filter1(Vector3i(3, 3, 3));
|
||||||
|
std::vector<float> weights1(27, 1.0f);
|
||||||
|
filter1.SetImage(&image);
|
||||||
|
filter1.SetKernelNumericXZY(weights1);
|
||||||
|
|
||||||
|
VoxFilterAlgorithmAbtrim<TestVoxel> filter2(Vector3i(3, 3, 3));
|
||||||
|
std::vector<float> weights2(27, 1.0f);
|
||||||
|
filter2.SetImage(&image);
|
||||||
|
filter2.SetKernelNumericXZY(weights2);
|
||||||
|
filter2.SetABTrim(1, 1);
|
||||||
|
|
||||||
|
// Chain
|
||||||
|
filter1.SetDecoder(&filter2);
|
||||||
|
filter2.SetEncoder(&filter1);
|
||||||
|
|
||||||
|
// Move data to VRAM
|
||||||
|
image.Data().MoveToVRAM();
|
||||||
|
filter1.GetKernelData().Data().MoveToVRAM();
|
||||||
|
filter2.GetKernelData().Data().MoveToVRAM();
|
||||||
|
|
||||||
|
// Both filters should report VRAM preference
|
||||||
|
TEST1(filter1.GetPreferredDevice() == MemoryDevice::VRAM);
|
||||||
|
TEST1(filter2.GetPreferredDevice() == MemoryDevice::VRAM);
|
||||||
|
TEST1(filter1.IsGPU());
|
||||||
|
TEST1(filter2.IsGPU());
|
||||||
|
std::cout << " Both filters detect VRAM preference OK\n";
|
||||||
|
|
||||||
|
// Verify the chain's device consistency
|
||||||
|
auto* encoder = filter2.GetEncoder();
|
||||||
|
TEST1(encoder != nullptr);
|
||||||
|
TEST1(encoder->IsGPU());
|
||||||
|
std::cout << " Encoder in chain also reports GPU OK\n";
|
||||||
|
|
||||||
|
#ifdef USE_CUDA
|
||||||
|
// With CUDA: filters execute on GPU via Process()
|
||||||
|
image.Data().MoveToRAM(); // reset for clean test
|
||||||
|
image[Vector3i(5, 5, 5)].Value = 50;
|
||||||
|
image.Data().MoveToVRAM();
|
||||||
|
|
||||||
|
filter1.Process(&image);
|
||||||
|
TEST1(image.Data().GetDevice() == MemoryDevice::VRAM);
|
||||||
|
std::cout << " CUDA: data stays in VRAM after filter1 OK\n";
|
||||||
|
|
||||||
|
filter2.Process(&image);
|
||||||
|
TEST1(image.Data().GetDevice() == MemoryDevice::VRAM);
|
||||||
|
std::cout << " CUDA: data stays in VRAM after filter2 OK\n";
|
||||||
|
#else
|
||||||
|
// Without CUDA: verify Process still works via CPU fallback
|
||||||
|
image.Data().MoveToRAM();
|
||||||
|
image[Vector3i(5, 5, 5)].Value = 50;
|
||||||
|
|
||||||
|
filter1.GetKernelData().Data().MoveToRAM();
|
||||||
|
filter2.GetKernelData().Data().MoveToRAM();
|
||||||
|
|
||||||
|
filter1.Process(&image);
|
||||||
|
filter2.Process(&image);
|
||||||
|
std::cout << " No CUDA: CPU fallback executed correctly OK\n";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// TEST 6: AlgorithmTask with VRAM-aware filter
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
std::cout << "\n--- Test 6: AlgorithmTask with VRAM-aware filter ---\n";
|
||||||
|
|
||||||
|
VoxImage<TestVoxel> image(Vector3i(8, 8, 8));
|
||||||
|
image[Vector3i(4, 4, 4)].Value = 20;
|
||||||
|
|
||||||
|
VoxFilterAlgorithmLinear<TestVoxel> filter(Vector3i(3, 3, 3));
|
||||||
|
std::vector<float> weights(27, 1.0f);
|
||||||
|
filter.SetImage(&image);
|
||||||
|
filter.SetKernelNumericXZY(weights);
|
||||||
|
|
||||||
|
// Set up task
|
||||||
|
AlgorithmTask<VoxImage<TestVoxel>*, VoxImage<TestVoxel>*> task;
|
||||||
|
task.SetAlgorithm(&filter);
|
||||||
|
task.SetMode(AlgorithmTask<VoxImage<TestVoxel>*, VoxImage<TestVoxel>*>::Cyclic);
|
||||||
|
task.SetCycleTime(50);
|
||||||
|
|
||||||
|
// Run task for a few cycles
|
||||||
|
task.Run(&image);
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
|
task.Stop();
|
||||||
|
|
||||||
|
// After cyclic execution, the filter should have smoothed values
|
||||||
|
TEST1(image[Vector3i(4, 4, 4)].Value < 20.0f);
|
||||||
|
std::cout << " Task cyclic execution modified image OK\n";
|
||||||
|
std::cout << " Center value after smoothing: "
|
||||||
|
<< image[Vector3i(4, 4, 4)].Value << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// TEST 7: AlgorithmTask async with chained filters
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
std::cout << "\n--- Test 7: AlgorithmTask async with filter ---\n";
|
||||||
|
|
||||||
|
VoxImage<TestVoxel> image(Vector3i(8, 8, 8));
|
||||||
|
image[Vector3i(4, 4, 4)].Value = 30;
|
||||||
|
|
||||||
|
VoxFilterAlgorithmLinear<TestVoxel> filter(Vector3i(3, 3, 3));
|
||||||
|
std::vector<float> weights(27, 1.0f);
|
||||||
|
filter.SetImage(&image);
|
||||||
|
filter.SetKernelNumericXZY(weights);
|
||||||
|
|
||||||
|
AlgorithmTask<VoxImage<TestVoxel>*, VoxImage<TestVoxel>*> task;
|
||||||
|
task.SetAlgorithm(&filter);
|
||||||
|
task.SetMode(AlgorithmTask<VoxImage<TestVoxel>*, VoxImage<TestVoxel>*>::Async);
|
||||||
|
|
||||||
|
float before = image[Vector3i(4, 4, 4)].Value;
|
||||||
|
|
||||||
|
task.Run(&image);
|
||||||
|
|
||||||
|
// Trigger one execution
|
||||||
|
task.Notify();
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||||
|
|
||||||
|
task.Stop();
|
||||||
|
|
||||||
|
float after = image[Vector3i(4, 4, 4)].Value;
|
||||||
|
TEST1(after < before);
|
||||||
|
std::cout << " Async trigger: value " << before << " -> " << after << " OK\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// TEST 8: Device preference propagation in chain
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
std::cout << "\n--- Test 8: Device preference propagation check ---\n";
|
||||||
|
|
||||||
|
VoxImage<TestVoxel> image(Vector3i(8, 8, 8));
|
||||||
|
image[Vector3i(4, 4, 4)].Value = 10;
|
||||||
|
|
||||||
|
VoxFilterAlgorithmLinear<TestVoxel> filterA(Vector3i(3, 3, 3));
|
||||||
|
VoxFilterAlgorithmAbtrim<TestVoxel> filterB(Vector3i(3, 3, 3));
|
||||||
|
VoxFilterAlgorithmThreshold<TestVoxel> filterC(Vector3i(1, 1, 1));
|
||||||
|
|
||||||
|
std::vector<float> w27(27, 1.0f);
|
||||||
|
std::vector<float> w1(1, 1.0f);
|
||||||
|
|
||||||
|
filterA.SetImage(&image);
|
||||||
|
filterA.SetKernelNumericXZY(w27);
|
||||||
|
filterB.SetImage(&image);
|
||||||
|
filterB.SetKernelNumericXZY(w27);
|
||||||
|
filterB.SetABTrim(1, 1);
|
||||||
|
filterC.SetImage(&image);
|
||||||
|
filterC.SetKernelNumericXZY(w1);
|
||||||
|
filterC.SetThreshold(0.1f);
|
||||||
|
|
||||||
|
// Chain: A → B → C
|
||||||
|
filterA.SetDecoder(&filterB);
|
||||||
|
filterB.SetEncoder(&filterA);
|
||||||
|
filterB.SetDecoder(&filterC);
|
||||||
|
filterC.SetEncoder(&filterB);
|
||||||
|
|
||||||
|
// All on RAM
|
||||||
|
TEST1(!filterA.IsGPU());
|
||||||
|
TEST1(!filterB.IsGPU());
|
||||||
|
TEST1(!filterC.IsGPU());
|
||||||
|
std::cout << " All filters on RAM OK\n";
|
||||||
|
|
||||||
|
// Move image to VRAM — filters A and B should detect it
|
||||||
|
image.Data().MoveToVRAM();
|
||||||
|
TEST1(filterA.IsGPU());
|
||||||
|
TEST1(filterB.IsGPU());
|
||||||
|
// filterC with 1x1x1 kernel doesn't have CUDA override, but still detects VRAM
|
||||||
|
TEST1(filterC.IsGPU());
|
||||||
|
std::cout << " Image on VRAM: all filters report GPU OK\n";
|
||||||
|
|
||||||
|
// Can walk the chain and check device consistency
|
||||||
|
auto* step = static_cast<Algorithm<VoxImage<TestVoxel>*, VoxImage<TestVoxel>*>*>(&filterA);
|
||||||
|
bool all_gpu = true;
|
||||||
|
while (step) {
|
||||||
|
if (!step->IsGPU()) all_gpu = false;
|
||||||
|
step = static_cast<Algorithm<VoxImage<TestVoxel>*, VoxImage<TestVoxel>*>*>(step->GetDecoder());
|
||||||
|
}
|
||||||
|
TEST1(all_gpu);
|
||||||
|
std::cout << " Chain walk: all steps report GPU OK\n";
|
||||||
|
|
||||||
|
image.Data().MoveToRAM();
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
// TEST 9: Process through chain with Algorithm interface
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
{
|
||||||
|
std::cout << "\n--- Test 9: Sequential chain processing via Algorithm interface ---\n";
|
||||||
|
|
||||||
|
VoxImage<TestVoxel> image(Vector3i(10, 10, 10));
|
||||||
|
// Set a pattern: single bright voxel
|
||||||
|
image[Vector3i(5, 5, 5)].Value = 100;
|
||||||
|
|
||||||
|
VoxFilterAlgorithmLinear<TestVoxel> filterA(Vector3i(3, 3, 3));
|
||||||
|
std::vector<float> w(27, 1.0f);
|
||||||
|
filterA.SetImage(&image);
|
||||||
|
filterA.SetKernelNumericXZY(w);
|
||||||
|
|
||||||
|
VoxFilterAlgorithmLinear<TestVoxel> filterB(Vector3i(3, 3, 3));
|
||||||
|
filterB.SetImage(&image);
|
||||||
|
filterB.SetKernelNumericXZY(w);
|
||||||
|
|
||||||
|
// Chain
|
||||||
|
filterA.SetDecoder(&filterB);
|
||||||
|
filterB.SetEncoder(&filterA);
|
||||||
|
|
||||||
|
// Process chain through base pointer
|
||||||
|
using AlgType = Algorithm<VoxImage<TestVoxel>*, VoxImage<TestVoxel>*>;
|
||||||
|
AlgType* chain = &filterA;
|
||||||
|
|
||||||
|
// Walk and process
|
||||||
|
AlgType* current = chain;
|
||||||
|
while (current) {
|
||||||
|
current->Process(&image);
|
||||||
|
current = static_cast<AlgType*>(current->GetDecoder());
|
||||||
|
}
|
||||||
|
|
||||||
|
// After two rounds of smoothing, the peak should be smaller than original
|
||||||
|
float final_val = image[Vector3i(5, 5, 5)].Value;
|
||||||
|
TEST1(final_val < 100.0f);
|
||||||
|
std::cout << " Two-stage smoothing: peak = " << final_val << " OK\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
END_TESTING;
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ set(TESTS
|
|||||||
QuadMeshTest
|
QuadMeshTest
|
||||||
BitCodeTest
|
BitCodeTest
|
||||||
UnitsTest
|
UnitsTest
|
||||||
|
AlgorithmCudaChainTest
|
||||||
)
|
)
|
||||||
|
|
||||||
set(LIBRARIES
|
set(LIBRARIES
|
||||||
@@ -28,6 +29,6 @@ set(LIBRARIES
|
|||||||
uLib_add_tests(Math)
|
uLib_add_tests(Math)
|
||||||
|
|
||||||
if(USE_CUDA)
|
if(USE_CUDA)
|
||||||
set_source_files_properties(VoxImageTest.cpp VoxImageCopyTest.cpp VoxImageFilterTest.cpp VoxRaytracerTest.cpp VoxRaytracerTestExtended.cpp PROPERTIES LANGUAGE CUDA)
|
set_source_files_properties(VoxImageTest.cpp VoxImageCopyTest.cpp VoxImageFilterTest.cpp VoxRaytracerTest.cpp VoxRaytracerTestExtended.cpp AlgorithmCudaChainTest.cpp PROPERTIES LANGUAGE CUDA)
|
||||||
set_source_files_properties(VoxRaytracerTest.cpp VoxRaytracerTestExtended.cpp PROPERTIES CXX_STANDARD 17 CUDA_STANDARD 17)
|
set_source_files_properties(VoxRaytracerTest.cpp VoxRaytracerTestExtended.cpp PROPERTIES CXX_STANDARD 17 CUDA_STANDARD 17)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -61,6 +61,7 @@
|
|||||||
|
|
||||||
#include "uLibVtkInterface.h"
|
#include "uLibVtkInterface.h"
|
||||||
#include "vtkHandlerWidget.h"
|
#include "vtkHandlerWidget.h"
|
||||||
|
#include "Math/Dense.h"
|
||||||
#include "Core/Property.h"
|
#include "Core/Property.h"
|
||||||
|
|
||||||
|
|
||||||
@@ -96,9 +97,9 @@ public:
|
|||||||
m_Dragable(true)
|
m_Dragable(true)
|
||||||
{
|
{
|
||||||
m_Color[0] = m_Color[1] = m_Color[2] = -1.0;
|
m_Color[0] = m_Color[1] = m_Color[2] = -1.0;
|
||||||
m_Position[0] = m_Position[1] = m_Position[2] = 0.0;
|
m_Position = Vector3d::Zero();
|
||||||
m_Orientation[0] = m_Orientation[1] = m_Orientation[2] = 0.0;
|
m_Orientation = Vector3d::Zero();
|
||||||
m_Scale[0] = m_Scale[1] = m_Scale[2] = 1.0;
|
m_Scale = Vector3d::Ones();
|
||||||
}
|
}
|
||||||
|
|
||||||
~PuppetData() {
|
~PuppetData() {
|
||||||
@@ -124,9 +125,9 @@ public:
|
|||||||
bool m_Selected;
|
bool m_Selected;
|
||||||
bool m_Visibility;
|
bool m_Visibility;
|
||||||
bool m_Dragable;
|
bool m_Dragable;
|
||||||
double m_Position[3];
|
Vector3d m_Position;
|
||||||
double m_Orientation[3];
|
Vector3d m_Orientation;
|
||||||
double m_Scale[3];
|
Vector3d m_Scale;
|
||||||
|
|
||||||
void ApplyAppearance(vtkProp *p) {
|
void ApplyAppearance(vtkProp *p) {
|
||||||
p->SetVisibility(m_Visibility);
|
p->SetVisibility(m_Visibility);
|
||||||
@@ -156,9 +157,9 @@ public:
|
|||||||
// Handle transformation if it's a Prop3D
|
// Handle transformation if it's a Prop3D
|
||||||
if (auto* p3d = vtkProp3D::SafeDownCast(p)) {
|
if (auto* p3d = vtkProp3D::SafeDownCast(p)) {
|
||||||
// NOTE: Usually managed by Puppet::Update from model, but here for direct prop manipulation
|
// NOTE: Usually managed by Puppet::Update from model, but here for direct prop manipulation
|
||||||
// p3d->SetPosition(m_Position);
|
// p3d->SetPosition(m_Position.data());
|
||||||
// p3d->SetOrientation(m_Orientation);
|
// p3d->SetOrientation(m_Orientation.data());
|
||||||
// p3d->SetScale(m_Scale);
|
// p3d->SetScale(m_Scale.data());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -499,9 +500,9 @@ void Puppet::Update()
|
|||||||
|
|
||||||
// Apply transformation if it's a Prop3D
|
// Apply transformation if it's a Prop3D
|
||||||
if (auto* p3d = vtkProp3D::SafeDownCast(root)) {
|
if (auto* p3d = vtkProp3D::SafeDownCast(root)) {
|
||||||
p3d->SetPosition(pd->m_Position);
|
p3d->SetPosition(pd->m_Position.data());
|
||||||
p3d->SetOrientation(pd->m_Orientation);
|
p3d->SetOrientation(pd->m_Orientation.data());
|
||||||
p3d->SetScale(pd->m_Scale);
|
p3d->SetScale(pd->m_Scale.data());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -549,9 +550,9 @@ void Puppet::SyncFromVtk()
|
|||||||
|
|
||||||
// Update properties
|
// Update properties
|
||||||
for (int i=0; i<3; ++i) {
|
for (int i=0; i<3; ++i) {
|
||||||
pd->m_Position[i] = pos[i];
|
pd->m_Position(i) = pos[i];
|
||||||
pd->m_Orientation[i] = ori[i];
|
pd->m_Orientation(i) = ori[i];
|
||||||
pd->m_Scale[i] = scale[i];
|
pd->m_Scale(i) = scale[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the properties from the object
|
// Get the properties from the object
|
||||||
@@ -567,7 +568,20 @@ void Puppet::ConnectInteractor(vtkRenderWindowInteractor *interactor)
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void Puppet::serialize_display(Archive::display_properties_archive & ar, const unsigned int version) {
|
struct TransformProxy {
|
||||||
|
PuppetData* pd;
|
||||||
|
template<class Archive>
|
||||||
|
void serialize(Archive & ar, const unsigned int version) {
|
||||||
|
ar & boost::serialization::make_hrp("Position", pd->m_Position, "mm");
|
||||||
|
ar & boost::serialization::make_hrp("Orientation", pd->m_Orientation, "deg");
|
||||||
|
ar & boost::serialization::make_hrp("Scale", pd->m_Scale, "");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AppearanceProxy {
|
||||||
|
PuppetData* pd;
|
||||||
|
template<class Archive>
|
||||||
|
void serialize(Archive & ar, const unsigned int version) {
|
||||||
ar & boost::serialization::make_hrp("ColorR", pd->m_Color[0]);
|
ar & boost::serialization::make_hrp("ColorR", pd->m_Color[0]);
|
||||||
ar & boost::serialization::make_hrp("ColorG", pd->m_Color[1]);
|
ar & boost::serialization::make_hrp("ColorG", pd->m_Color[1]);
|
||||||
ar & boost::serialization::make_hrp("ColorB", pd->m_Color[2]);
|
ar & boost::serialization::make_hrp("ColorB", pd->m_Color[2]);
|
||||||
@@ -576,17 +590,15 @@ void Puppet::serialize_display(Archive::display_properties_archive & ar, const u
|
|||||||
ar & boost::serialization::make_hrp("Visibility", pd->m_Visibility);
|
ar & boost::serialization::make_hrp("Visibility", pd->m_Visibility);
|
||||||
ar & boost::serialization::make_hrp("Pickable", pd->m_Selectable);
|
ar & boost::serialization::make_hrp("Pickable", pd->m_Selectable);
|
||||||
ar & boost::serialization::make_hrp("Dragable", pd->m_Dragable);
|
ar & boost::serialization::make_hrp("Dragable", pd->m_Dragable);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Geometry knobs (caution: these might be overridden by internal matrices)
|
void Puppet::serialize_display(Archive::display_properties_archive & ar, const unsigned int version) {
|
||||||
ar & boost::serialization::make_hrp("PosX", pd->m_Position[0], "mm");
|
AppearanceProxy appearance{pd};
|
||||||
ar & boost::serialization::make_hrp("PosY", pd->m_Position[1], "mm");
|
ar & boost::serialization::make_nvp("Appearance", appearance);
|
||||||
ar & boost::serialization::make_hrp("PosZ", pd->m_Position[2], "mm");
|
|
||||||
ar & boost::serialization::make_hrp("OriX", pd->m_Orientation[0], "deg");
|
TransformProxy transform{pd};
|
||||||
ar & boost::serialization::make_hrp("OriY", pd->m_Orientation[1], "deg");
|
ar & boost::serialization::make_nvp("Transform", transform);
|
||||||
ar & boost::serialization::make_hrp("OriZ", pd->m_Orientation[2], "deg");
|
|
||||||
ar & boost::serialization::make_hrp("ScaleX", pd->m_Scale[0]);
|
|
||||||
ar & boost::serialization::make_hrp("ScaleY", pd->m_Scale[1]);
|
|
||||||
ar & boost::serialization::make_hrp("ScaleZ", pd->m_Scale[2]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Puppet::serialize(Archive::xml_oarchive & ar, const unsigned int v) { }
|
void Puppet::serialize(Archive::xml_oarchive & ar, const unsigned int v) { }
|
||||||
|
|||||||
@@ -29,6 +29,9 @@
|
|||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <boost/type_traits/is_class.hpp>
|
||||||
|
#include <boost/mpl/bool.hpp>
|
||||||
|
#include <boost/serialization/serialization.hpp>
|
||||||
#include "Core/Object.h"
|
#include "Core/Object.h"
|
||||||
#include "Core/Property.h"
|
#include "Core/Property.h"
|
||||||
#include "Core/Monitor.h"
|
#include "Core/Monitor.h"
|
||||||
@@ -157,10 +160,19 @@ public:
|
|||||||
boost::archive::detail::common_oarchive<display_properties_archive>(boost::archive::no_header),
|
boost::archive::detail::common_oarchive<display_properties_archive>(boost::archive::no_header),
|
||||||
m_Puppet(puppet) {}
|
m_Puppet(puppet) {}
|
||||||
|
|
||||||
|
std::string GetCurrentGroup() const {
|
||||||
|
std::string group;
|
||||||
|
for (const auto& g : m_GroupStack) {
|
||||||
|
if (!group.empty()) group += ".";
|
||||||
|
group += g;
|
||||||
|
}
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
void save_override(const boost::serialization::hrp<T> &t) {
|
void save_override(const boost::serialization::hrp<T> &t) {
|
||||||
if (m_Puppet) {
|
if (m_Puppet) {
|
||||||
uLib::Property<T>* p = new uLib::Property<T>(m_Puppet, t.name(), &const_cast<boost::serialization::hrp<T>&>(t).value(), t.units() ? t.units() : "");
|
uLib::Property<T>* p = new uLib::Property<T>(m_Puppet, t.name(), &const_cast<boost::serialization::hrp<T>&>(t).value(), t.units() ? t.units() : "", GetCurrentGroup());
|
||||||
m_Puppet->RegisterDisplayProperty(p);
|
m_Puppet->RegisterDisplayProperty(p);
|
||||||
Vtk::Puppet* puppet = m_Puppet;
|
Vtk::Puppet* puppet = m_Puppet;
|
||||||
uLib::Object::connect(p, &uLib::PropertyBase::Updated, [puppet](){ puppet->Update(); });
|
uLib::Object::connect(p, &uLib::PropertyBase::Updated, [puppet](){ puppet->Update(); });
|
||||||
@@ -170,7 +182,7 @@ public:
|
|||||||
template<class T>
|
template<class T>
|
||||||
void save_override(const boost::serialization::hrp_enum<T> &t) {
|
void save_override(const boost::serialization::hrp_enum<T> &t) {
|
||||||
if (m_Puppet) {
|
if (m_Puppet) {
|
||||||
uLib::EnumProperty* p = new uLib::EnumProperty(m_Puppet, t.name(), (int*)&const_cast<boost::serialization::hrp_enum<T>&>(t).value(), t.labels(), t.units() ? t.units() : "");
|
uLib::EnumProperty* p = new uLib::EnumProperty(m_Puppet, t.name(), (int*)&const_cast<boost::serialization::hrp_enum<T>&>(t).value(), t.labels(), t.units() ? t.units() : "", GetCurrentGroup());
|
||||||
m_Puppet->RegisterDisplayProperty(p);
|
m_Puppet->RegisterDisplayProperty(p);
|
||||||
Vtk::Puppet* puppet = m_Puppet;
|
Vtk::Puppet* puppet = m_Puppet;
|
||||||
uLib::Object::connect(p, &uLib::PropertyBase::Updated, [puppet](){ puppet->Update(); });
|
uLib::Object::connect(p, &uLib::PropertyBase::Updated, [puppet](){ puppet->Update(); });
|
||||||
@@ -178,10 +190,23 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<class T> void save_override(const boost::serialization::nvp<T> &t) {
|
template<class T> void save_override(const boost::serialization::nvp<T> &t) {
|
||||||
boost::archive::detail::common_oarchive<display_properties_archive>::save_override(t.const_value());
|
if (t.name()) m_GroupStack.push_back(t.name());
|
||||||
|
this->save_helper(t.const_value(), typename boost::is_class<T>::type());
|
||||||
|
if (t.name()) m_GroupStack.pop_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T> void save_override(const T &t) {}
|
// Recursion for nested classes, ignore primitives
|
||||||
|
template<class T> void save_override(const T &t) {
|
||||||
|
this->save_helper(t, typename boost::is_class<T>::type());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
void save_helper(const T &t, boost::mpl::true_) {
|
||||||
|
boost::serialization::serialize_adl(*this, const_cast<T&>(t), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
void save_helper(const T &t, boost::mpl::false_) {}
|
||||||
|
|
||||||
void save_override(const boost::archive::object_id_type & t) {}
|
void save_override(const boost::archive::object_id_type & t) {}
|
||||||
void save_override(const boost::archive::object_reference_type & t) {}
|
void save_override(const boost::archive::object_reference_type & t) {}
|
||||||
@@ -194,6 +219,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Vtk::Puppet* m_Puppet;
|
Vtk::Puppet* m_Puppet;
|
||||||
|
std::vector<std::string> m_GroupStack;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Archive
|
} // namespace Archive
|
||||||
|
|||||||
Reference in New Issue
Block a user