algorithm chain for ram-vram
This commit is contained in:
@@ -34,6 +34,9 @@
|
||||
|
||||
namespace uLib {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Kernel shape interface (static check for operator()(float) and operator()(Vector3f))
|
||||
|
||||
namespace Interface {
|
||||
struct VoxImageFilterShape {
|
||||
template <class Self> void check_structural() {
|
||||
@@ -43,30 +46,47 @@ struct VoxImageFilterShape {
|
||||
};
|
||||
} // namespace Interface
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Forward declaration
|
||||
|
||||
template <typename VoxelT> class Kernel;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Abstract interface (type-erased, used by python bindings)
|
||||
|
||||
namespace Abstract {
|
||||
class VoxImageFilter {
|
||||
public:
|
||||
virtual void Run() = 0;
|
||||
|
||||
virtual void SetImage(Abstract::VoxImage *image) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~VoxImageFilter() {}
|
||||
};
|
||||
} // namespace Abstract
|
||||
|
||||
template <typename VoxelT, typename AlgorithmT>
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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:
|
||||
|
||||
virtual const char * GetClassName() const { return "VoxImageFilter"; }
|
||||
virtual const char* GetClassName() const { return "VoxImageFilter"; }
|
||||
|
||||
VoxImageFilter(const Vector3i &size);
|
||||
|
||||
// Algorithm interface ////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* @brief Process implements Algorithm::Process.
|
||||
* Applies the filter in-place on the input image and returns it.
|
||||
@@ -79,9 +99,9 @@ public:
|
||||
*/
|
||||
void Run();
|
||||
|
||||
/**
|
||||
* @brief Returns VRAM if image or kernel data is on GPU, RAM otherwise.
|
||||
*/
|
||||
// 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;
|
||||
@@ -90,38 +110,31 @@ public:
|
||||
return MemoryDevice::RAM;
|
||||
}
|
||||
|
||||
// Kernel setup ///////////////////////////////////////////////////////////////
|
||||
|
||||
void SetKernelNumericXZY(const std::vector<float> &numeric);
|
||||
|
||||
void SetKernelSpherical(float (*shape)(float));
|
||||
|
||||
template <class ShapeT> void SetKernelSpherical(ShapeT shape);
|
||||
|
||||
void SetKernelWeightFunction(float (*shape)(const Vector3f &));
|
||||
|
||||
template <class ShapeT> void SetKernelWeightFunction(ShapeT shape);
|
||||
|
||||
inline const Kernel<VoxelT> &GetKernelData() const {
|
||||
return this->m_KernelData;
|
||||
}
|
||||
inline Kernel<VoxelT> &GetKernelData() { return this->m_KernelData; }
|
||||
// Accessors //////////////////////////////////////////////////////////////////
|
||||
|
||||
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);
|
||||
|
||||
protected:
|
||||
float Convolve(const VoxImage<VoxelT> &buffer, int index); // remove //
|
||||
|
||||
void SetKernelOffset();
|
||||
|
||||
float Distance2(const Vector3i &v);
|
||||
|
||||
// protected members for algorithm access //
|
||||
Kernel<VoxelT> m_KernelData;
|
||||
VoxImage<VoxelT> *m_Image;
|
||||
|
||||
private:
|
||||
AlgorithmT *t_Algoritm;
|
||||
CrtpImplT *m_CrtpImpl;
|
||||
};
|
||||
|
||||
} // namespace uLib
|
||||
|
||||
@@ -33,7 +33,9 @@
|
||||
|
||||
namespace uLib {
|
||||
|
||||
// KERNEL //////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// KERNEL ////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename T> class Kernel : public StructuredData {
|
||||
typedef StructuredData BaseClass;
|
||||
@@ -41,13 +43,12 @@ template <typename T> class Kernel : public StructuredData {
|
||||
public:
|
||||
Kernel(const Vector3i &size);
|
||||
|
||||
inline T &operator[](const Vector3i &id) { return m_Data[Map(id)]; }
|
||||
inline T &operator[](const int &id) { return m_Data[id]; }
|
||||
inline int GetCenterData() const;
|
||||
T &operator[](const Vector3i &id) { return m_Data[Map(id)]; }
|
||||
T &operator[](const int &id) { return m_Data[id]; }
|
||||
int GetCenterData() const;
|
||||
|
||||
inline DataAllocator<T> &Data() { return this->m_Data; }
|
||||
|
||||
inline const DataAllocator<T> &ConstData() const { return this->m_Data; }
|
||||
DataAllocator<T> &Data() { return m_Data; }
|
||||
const DataAllocator<T> &ConstData() const { return m_Data; }
|
||||
|
||||
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>();
|
||||
}
|
||||
|
||||
template <typename T> inline int Kernel<T>::GetCenterData() const {
|
||||
template <typename T>
|
||||
int Kernel<T>::GetCenterData() const {
|
||||
static int center = Map(this->GetDims() / 2);
|
||||
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";
|
||||
Vector3i index;
|
||||
o << "\n Value: \n\n"
|
||||
@@ -96,33 +99,42 @@ template <typename T> void Kernel<T>::PrintSelf(std::ostream &o) const {
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// VOXIMAGEFILTER IMPLEMENTATION /////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#define _TPL_ template <typename VoxelT, typename AlgorithmT>
|
||||
#define _TPLT_ VoxelT, AlgorithmT
|
||||
template <typename VoxelT, typename CrtpImplT>
|
||||
VoxImageFilter<VoxelT, CrtpImplT>::VoxImageFilter(const Vector3i &size)
|
||||
: m_KernelData(size)
|
||||
, m_Image(nullptr)
|
||||
, m_CrtpImpl(static_cast<CrtpImplT *>(this))
|
||||
{}
|
||||
|
||||
_TPL_
|
||||
VoxImageFilter<_TPLT_>::VoxImageFilter(const Vector3i &size)
|
||||
: m_KernelData(size), t_Algoritm(static_cast<AlgorithmT *>(this)) {}
|
||||
|
||||
_TPL_
|
||||
VoxImage<VoxelT>* VoxImageFilter<_TPLT_>::Process(VoxImage<VoxelT>* const& image) {
|
||||
template <typename VoxelT, typename CrtpImplT>
|
||||
VoxImage<VoxelT>* VoxImageFilter<VoxelT, CrtpImplT>::Process(
|
||||
VoxImage<VoxelT>* const& image) {
|
||||
if (m_Image != image) SetImage(image);
|
||||
VoxImage<VoxelT> buffer = *m_Image;
|
||||
#pragma omp parallel for
|
||||
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
|
||||
return m_Image;
|
||||
}
|
||||
|
||||
_TPL_
|
||||
void VoxImageFilter<_TPLT_>::Run() {
|
||||
template <typename VoxelT, typename CrtpImplT>
|
||||
void VoxImageFilter<VoxelT, CrtpImplT>::Run() {
|
||||
Process(m_Image);
|
||||
}
|
||||
|
||||
_TPL_
|
||||
void VoxImageFilter<_TPLT_>::SetKernelOffset() {
|
||||
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);
|
||||
for (int z = 0; z < m_KernelData.GetDims()(2); ++z) {
|
||||
for (int x = 0; x < m_KernelData.GetDims()(0); ++x) {
|
||||
@@ -134,10 +146,10 @@ void VoxImageFilter<_TPLT_>::SetKernelOffset() {
|
||||
}
|
||||
}
|
||||
|
||||
_TPL_
|
||||
float VoxImageFilter<_TPLT_>::Distance2(const Vector3i &v) {
|
||||
template <typename VoxelT, typename CrtpImplT>
|
||||
float VoxImageFilter<VoxelT, CrtpImplT>::Distance2(const Vector3i &v) {
|
||||
Vector3i tmp = v;
|
||||
const Vector3i &dim = this->m_KernelData.GetDims();
|
||||
const Vector3i &dim = m_KernelData.GetDims();
|
||||
Vector3i center = dim / 2;
|
||||
tmp = tmp - center;
|
||||
center = center.cwiseProduct(center);
|
||||
@@ -147,12 +159,9 @@ float VoxImageFilter<_TPLT_>::Distance2(const Vector3i &v) {
|
||||
0.25 * (3 - (dim(0) % 2) - (dim(1) % 2) - (dim(2) % 2)));
|
||||
}
|
||||
|
||||
_TPL_
|
||||
void VoxImageFilter<_TPLT_>::SetKernelNumericXZY(
|
||||
template <typename VoxelT, typename CrtpImplT>
|
||||
void VoxImageFilter<VoxelT, CrtpImplT>::SetKernelNumericXZY(
|
||||
const std::vector<float> &numeric) {
|
||||
// set data order //
|
||||
StructuredData::Order order = m_KernelData.GetDataOrder();
|
||||
// m_KernelData.SetDataOrder(StructuredData::XZY);
|
||||
Vector3i id;
|
||||
int index = 0;
|
||||
for (int y = 0; y < m_KernelData.GetDims()(1); ++y) {
|
||||
@@ -163,38 +172,39 @@ void VoxImageFilter<_TPLT_>::SetKernelNumericXZY(
|
||||
}
|
||||
}
|
||||
}
|
||||
// m_KernelData.SetDataOrder(order);
|
||||
}
|
||||
|
||||
_TPL_
|
||||
void VoxImageFilter<_TPLT_>::SetKernelSpherical(float (*shape)(float)) {
|
||||
template <typename VoxelT, typename CrtpImplT>
|
||||
void VoxImageFilter<VoxelT, CrtpImplT>::SetKernelSpherical(
|
||||
float (*shape)(float)) {
|
||||
Vector3i id;
|
||||
for (int y = 0; y < m_KernelData.GetDims()(1); ++y) {
|
||||
for (int z = 0; z < m_KernelData.GetDims()(2); ++z) {
|
||||
for (int x = 0; x < m_KernelData.GetDims()(0); ++x) {
|
||||
id << x, y, z;
|
||||
m_KernelData[id].Value = shape(this->Distance2(id));
|
||||
m_KernelData[id].Value = shape(Distance2(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_TPL_ template <class ShapeT>
|
||||
void VoxImageFilter<_TPLT_>::SetKernelSpherical(ShapeT shape) {
|
||||
template <typename VoxelT, typename CrtpImplT>
|
||||
template <class ShapeT>
|
||||
void VoxImageFilter<VoxelT, CrtpImplT>::SetKernelSpherical(ShapeT shape) {
|
||||
Interface::IsA<ShapeT, Interface::VoxImageFilterShape>();
|
||||
Vector3i id;
|
||||
for (int y = 0; y < m_KernelData.GetDims()(1); ++y) {
|
||||
for (int z = 0; z < m_KernelData.GetDims()(2); ++z) {
|
||||
for (int x = 0; x < m_KernelData.GetDims()(0); ++x) {
|
||||
id << x, y, z;
|
||||
m_KernelData[id].Value = shape(this->Distance2(id));
|
||||
m_KernelData[id].Value = shape(Distance2(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_TPL_
|
||||
void VoxImageFilter<_TPLT_>::SetKernelWeightFunction(
|
||||
template <typename VoxelT, typename CrtpImplT>
|
||||
void VoxImageFilter<VoxelT, CrtpImplT>::SetKernelWeightFunction(
|
||||
float (*shape)(const Vector3f &)) {
|
||||
const Vector3i &dim = m_KernelData.GetDims();
|
||||
Vector3i id;
|
||||
@@ -202,20 +212,19 @@ void VoxImageFilter<_TPLT_>::SetKernelWeightFunction(
|
||||
for (int y = 0; y < dim(1); ++y) {
|
||||
for (int z = 0; z < dim(2); ++z) {
|
||||
for (int x = 0; x < dim(0); ++x) {
|
||||
// get voxels centroid coords from kernel center //
|
||||
id << x, y, z;
|
||||
pt << id(0) - dim(0) / 2 + 0.5 * !(dim(0) % 2),
|
||||
id(1) - dim(1) / 2 + 0.5 * !(dim(1) % 2),
|
||||
id(2) - dim(2) / 2 + 0.5 * !(dim(2) % 2);
|
||||
// compute function using given shape //
|
||||
m_KernelData[id].Value = shape(pt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_TPL_ template <class ShapeT>
|
||||
void VoxImageFilter<_TPLT_>::SetKernelWeightFunction(ShapeT shape) {
|
||||
template <typename VoxelT, typename CrtpImplT>
|
||||
template <class ShapeT>
|
||||
void VoxImageFilter<VoxelT, CrtpImplT>::SetKernelWeightFunction(ShapeT shape) {
|
||||
Interface::IsA<ShapeT, Interface::VoxImageFilterShape>();
|
||||
const Vector3i &dim = m_KernelData.GetDims();
|
||||
Vector3i id;
|
||||
@@ -223,45 +232,16 @@ void VoxImageFilter<_TPLT_>::SetKernelWeightFunction(ShapeT shape) {
|
||||
for (int y = 0; y < dim(1); ++y) {
|
||||
for (int z = 0; z < dim(2); ++z) {
|
||||
for (int x = 0; x < dim(0); ++x) {
|
||||
// get voxels centroid coords from kernel center //
|
||||
id << x, y, z;
|
||||
pt << id(0) - dim(0) / 2 + 0.5 * !(dim(0) % 2),
|
||||
id(1) - dim(1) / 2 + 0.5 * !(dim(1) % 2),
|
||||
id(2) - dim(2) / 2 + 0.5 * !(dim(2) % 2);
|
||||
// compute function using given shape //
|
||||
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
|
||||
|
||||
#endif // VOXIMAGEFILTER_HPP
|
||||
|
||||
@@ -30,8 +30,6 @@
|
||||
#include "VoxImageFilter.h"
|
||||
#include <Math/Dense.h>
|
||||
|
||||
#define likely(expr) __builtin_expect(!!(expr), 1)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
///// VOXIMAGE FILTER CUSTOM /////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -50,7 +48,7 @@ public:
|
||||
: BaseClass(size), m_CustomEvaluate(NULL) {}
|
||||
|
||||
float Evaluate(const VoxImage<VoxelT> &buffer, int index) {
|
||||
if (likely(m_CustomEvaluate)) {
|
||||
if (m_CustomEvaluate) {
|
||||
const DataAllocator<VoxelT> &vbuf = buffer.ConstData();
|
||||
const DataAllocator<VoxelT> &vker = this->m_KernelData.ConstData();
|
||||
int vox_size = vbuf.size();
|
||||
|
||||
@@ -23,8 +23,6 @@
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
|
||||
|
||||
#ifndef VOXIMAGEFILTERTHRESHOLD_HPP
|
||||
#define VOXIMAGEFILTERTHRESHOLD_HPP
|
||||
|
||||
@@ -39,40 +37,24 @@
|
||||
namespace uLib {
|
||||
|
||||
template <typename VoxelT>
|
||||
class VoxFilterAlgorithmThreshold :
|
||||
public VoxImageFilter<VoxelT, VoxFilterAlgorithmThreshold<VoxelT> > {
|
||||
class VoxFilterAlgorithmThreshold
|
||||
: public VoxImageFilter<VoxelT, VoxFilterAlgorithmThreshold<VoxelT>> {
|
||||
|
||||
typedef VoxImageFilter<VoxelT, VoxFilterAlgorithmThreshold<VoxelT> > BaseClass;
|
||||
// ULIB_OBJECT_PARAMETERS(BaseClass) {
|
||||
// float threshold;
|
||||
// };
|
||||
typedef VoxImageFilter<VoxelT, VoxFilterAlgorithmThreshold<VoxelT>> BaseClass;
|
||||
|
||||
float m_threshold;
|
||||
float m_threshold;
|
||||
|
||||
public:
|
||||
VoxFilterAlgorithmThreshold(const Vector3i &size) : BaseClass(size)
|
||||
{
|
||||
// init_parameters();
|
||||
m_threshold = 0;
|
||||
}
|
||||
VoxFilterAlgorithmThreshold(const Vector3i &size)
|
||||
: BaseClass(size), m_threshold(0) {}
|
||||
|
||||
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 );
|
||||
}
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
//template <typename VoxelT>
|
||||
//inline void VoxFilterAlgorithmThreshold<VoxelT>::init_parameters()
|
||||
//{
|
||||
// parameters().threshold = 0;
|
||||
//}
|
||||
|
||||
}
|
||||
} // namespace uLib
|
||||
|
||||
#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
|
||||
BitCodeTest
|
||||
UnitsTest
|
||||
AlgorithmCudaChainTest
|
||||
)
|
||||
|
||||
set(LIBRARIES
|
||||
@@ -28,6 +29,6 @@ set(LIBRARIES
|
||||
uLib_add_tests(Math)
|
||||
|
||||
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)
|
||||
endif()
|
||||
|
||||
Reference in New Issue
Block a user