andrea-dev #1
@@ -90,7 +90,8 @@ struct Voxel {
|
||||
} // namespace Interface
|
||||
|
||||
struct Voxel {
|
||||
Scalarf Value;
|
||||
Scalarf Value = 0.0f;
|
||||
Scalari Count = 0;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
namespace uLib {
|
||||
|
||||
#ifdef USE_CUDA
|
||||
#if defined(USE_CUDA) && defined(__CUDACC__)
|
||||
template <typename VoxelT>
|
||||
__global__ void ABTrimFilterKernel(const VoxelT *in, VoxelT *out,
|
||||
const VoxelT *kernel, int vox_size,
|
||||
@@ -108,7 +108,7 @@ public:
|
||||
mBtrim = 0;
|
||||
}
|
||||
|
||||
#ifdef USE_CUDA
|
||||
#if defined(USE_CUDA) && defined(__CUDACC__)
|
||||
void Run() {
|
||||
if (this->m_Image->Data().GetDevice() == MemoryDevice::VRAM ||
|
||||
this->m_KernelData.Data().GetDevice() == MemoryDevice::VRAM) {
|
||||
@@ -206,7 +206,7 @@ public:
|
||||
mBtrim = 0;
|
||||
}
|
||||
|
||||
#ifdef USE_CUDA
|
||||
#if defined(USE_CUDA) && defined(__CUDACC__)
|
||||
void Run() {
|
||||
if (this->m_Image->Data().GetDevice() == MemoryDevice::VRAM ||
|
||||
this->m_KernelData.Data().GetDevice() == MemoryDevice::VRAM) {
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
namespace uLib {
|
||||
|
||||
#ifdef USE_CUDA
|
||||
#if defined(USE_CUDA) && defined(__CUDACC__)
|
||||
template <typename VoxelT>
|
||||
__global__ void LinearFilterKernel(const VoxelT *in, VoxelT *out,
|
||||
const VoxelT *kernel, int vox_size,
|
||||
@@ -66,7 +66,7 @@ public:
|
||||
typedef VoxImageFilter<VoxelT, VoxFilterAlgorithmLinear<VoxelT>> BaseClass;
|
||||
VoxFilterAlgorithmLinear(const Vector3i &size) : BaseClass(size) {}
|
||||
|
||||
#ifdef USE_CUDA
|
||||
#if defined(USE_CUDA) && defined(__CUDACC__)
|
||||
void Run() {
|
||||
if (this->m_Image->Data().GetDevice() == MemoryDevice::VRAM ||
|
||||
this->m_KernelData.Data().GetDevice() == MemoryDevice::VRAM) {
|
||||
|
||||
@@ -4,10 +4,11 @@ set(SOURCES
|
||||
module.cpp
|
||||
core_bindings.cpp
|
||||
math_bindings.cpp
|
||||
math_filters_bindings.cpp
|
||||
)
|
||||
|
||||
# Use pybind11 to add the python module
|
||||
pybind11_add_module(uLib_python module.cpp core_bindings.cpp math_bindings.cpp)
|
||||
pybind11_add_module(uLib_python module.cpp core_bindings.cpp math_bindings.cpp math_filters_bindings.cpp)
|
||||
|
||||
# Link against our C++ libraries
|
||||
target_link_libraries(uLib_python PRIVATE
|
||||
@@ -49,4 +50,9 @@ if(BUILD_TESTING)
|
||||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/testing/math_pybind_test.py)
|
||||
set_tests_properties(pybind_math PROPERTIES
|
||||
ENVIRONMENT "PYTHONPATH=$<TARGET_FILE_DIR:uLib_python>:${PROJECT_SOURCE_DIR}/src/Python")
|
||||
|
||||
add_test(NAME pybind_math_filters
|
||||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/testing/math_filters_test.py)
|
||||
set_tests_properties(pybind_math_filters PROPERTIES
|
||||
ENVIRONMENT "PYTHONPATH=$<TARGET_FILE_DIR:uLib_python>:${PROJECT_SOURCE_DIR}/src/Python")
|
||||
endif()
|
||||
|
||||
@@ -327,7 +327,8 @@ void init_math(py::module_ &m) {
|
||||
// 6. High-level Structures
|
||||
py::class_<Voxel>(m, "Voxel")
|
||||
.def(py::init<>())
|
||||
.def_readwrite("Value", &Voxel::Value);
|
||||
.def_readwrite("Value", &Voxel::Value)
|
||||
.def_readwrite("Count", &Voxel::Count);
|
||||
|
||||
py::class_<Abstract::VoxImage, StructuredGrid>(m, "AbstractVoxImage")
|
||||
.def("GetValue", py::overload_cast<const Vector3i &>(&Abstract::VoxImage::GetValue, py::const_))
|
||||
|
||||
100
src/Python/math_filters_bindings.cpp
Normal file
100
src/Python/math_filters_bindings.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/eigen.h>
|
||||
#include <pybind11/stl.h>
|
||||
|
||||
#include "Math/VoxImage.h"
|
||||
#include "Math/VoxImageFilter.h"
|
||||
#include "Math/VoxImageFilterLinear.hpp"
|
||||
#include "Math/VoxImageFilterABTrim.hpp"
|
||||
#include "Math/VoxImageFilterBilateral.hpp"
|
||||
#include "Math/VoxImageFilterThreshold.hpp"
|
||||
#include "Math/VoxImageFilterMedian.hpp"
|
||||
#include "Math/VoxImageFilter2ndStat.hpp"
|
||||
#include "Math/VoxImageFilterCustom.hpp"
|
||||
|
||||
namespace py = pybind11;
|
||||
using namespace uLib;
|
||||
|
||||
template <typename Algorithm>
|
||||
void bind_common_filter(py::class_<Algorithm, Abstract::VoxImageFilter> &cls) {
|
||||
cls.def(py::init<const Vector3i &>())
|
||||
.def("Run", &Algorithm::Run)
|
||||
.def("SetKernelNumericXZY", &Algorithm::SetKernelNumericXZY)
|
||||
.def("GetImage", &Algorithm::GetImage, py::return_value_policy::reference_internal)
|
||||
.def("SetImage", &Algorithm::SetImage);
|
||||
}
|
||||
|
||||
void init_math_filters(py::module_ &m) {
|
||||
|
||||
// Abstract::VoxImageFilter
|
||||
py::class_<Abstract::VoxImageFilter, std::unique_ptr<Abstract::VoxImageFilter, py::nodelete>>(m, "AbstractVoxImageFilter")
|
||||
.def("Run", &Abstract::VoxImageFilter::Run)
|
||||
.def("SetImage", &Abstract::VoxImageFilter::SetImage);
|
||||
|
||||
// Helper macro to define standard bindings for a filter
|
||||
#define BIND_FILTER(ClassName) \
|
||||
{ \
|
||||
auto cls = py::class_<ClassName<Voxel>, Abstract::VoxImageFilter>(m, #ClassName); \
|
||||
bind_common_filter(cls); \
|
||||
}
|
||||
|
||||
// VoxFilterAlgorithmLinear
|
||||
{
|
||||
auto cls = py::class_<VoxFilterAlgorithmLinear<Voxel>, Abstract::VoxImageFilter>(m, "VoxFilterAlgorithmLinear");
|
||||
bind_common_filter(cls);
|
||||
}
|
||||
|
||||
// VoxFilterAlgorithmAbtrim
|
||||
{
|
||||
auto cls = py::class_<VoxFilterAlgorithmAbtrim<Voxel>, Abstract::VoxImageFilter>(m, "VoxFilterAlgorithmAbtrim");
|
||||
bind_common_filter(cls);
|
||||
cls.def("SetABTrim", &VoxFilterAlgorithmAbtrim<Voxel>::SetABTrim);
|
||||
}
|
||||
|
||||
// VoxFilterAlgorithmSPR
|
||||
{
|
||||
auto cls = py::class_<VoxFilterAlgorithmSPR<Voxel>, Abstract::VoxImageFilter>(m, "VoxFilterAlgorithmSPR");
|
||||
bind_common_filter(cls);
|
||||
cls.def("SetABTrim", &VoxFilterAlgorithmSPR<Voxel>::SetABTrim);
|
||||
}
|
||||
|
||||
// VoxFilterAlgorithmBilateral
|
||||
{
|
||||
auto cls = py::class_<VoxFilterAlgorithmBilateral<Voxel>, Abstract::VoxImageFilter>(m, "VoxFilterAlgorithmBilateral");
|
||||
bind_common_filter(cls);
|
||||
cls.def("SetIntensitySigma", &VoxFilterAlgorithmBilateral<Voxel>::SetIntensitySigma);
|
||||
}
|
||||
|
||||
// VoxFilterAlgorithmBilateralTrim
|
||||
{
|
||||
auto cls = py::class_<VoxFilterAlgorithmBilateralTrim<Voxel>, Abstract::VoxImageFilter>(m, "VoxFilterAlgorithmBilateralTrim");
|
||||
bind_common_filter(cls);
|
||||
cls.def("SetIntensitySigma", &VoxFilterAlgorithmBilateralTrim<Voxel>::SetIntensitySigma);
|
||||
cls.def("SetABTrim", &VoxFilterAlgorithmBilateralTrim<Voxel>::SetABTrim);
|
||||
}
|
||||
|
||||
// VoxFilterAlgorithmThreshold
|
||||
{
|
||||
auto cls = py::class_<VoxFilterAlgorithmThreshold<Voxel>, Abstract::VoxImageFilter>(m, "VoxFilterAlgorithmThreshold");
|
||||
bind_common_filter(cls);
|
||||
cls.def("SetThreshold", &VoxFilterAlgorithmThreshold<Voxel>::SetThreshold);
|
||||
}
|
||||
|
||||
// VoxFilterAlgorithmMedian
|
||||
{
|
||||
auto cls = py::class_<VoxFilterAlgorithmMedian<Voxel>, Abstract::VoxImageFilter>(m, "VoxFilterAlgorithmMedian");
|
||||
bind_common_filter(cls);
|
||||
}
|
||||
|
||||
// VoxFilterAlgorithm2ndStat
|
||||
{
|
||||
auto cls = py::class_<VoxFilterAlgorithm2ndStat<Voxel>, Abstract::VoxImageFilter>(m, "VoxFilterAlgorithm2ndStat");
|
||||
bind_common_filter(cls);
|
||||
}
|
||||
|
||||
// VoxFilterAlgorithmCustom (Omit CustomEvaluate since it uses static function ptrs)
|
||||
{
|
||||
auto cls = py::class_<VoxFilterAlgorithmCustom<Voxel>, Abstract::VoxImageFilter>(m, "VoxFilterAlgorithmCustom");
|
||||
bind_common_filter(cls);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace py = pybind11;
|
||||
|
||||
void init_core(py::module_ &m);
|
||||
void init_math(py::module_ &m);
|
||||
void init_math_filters(py::module_ &m);
|
||||
|
||||
PYBIND11_MODULE(uLib_python, m) {
|
||||
m.doc() = "Python bindings for uLib Core and Math libraries";
|
||||
@@ -15,4 +16,5 @@ PYBIND11_MODULE(uLib_python, m) {
|
||||
// Math submodule
|
||||
py::module_ math = m.def_submodule("Math", "Math library bindings");
|
||||
init_math(math);
|
||||
init_math_filters(math);
|
||||
}
|
||||
|
||||
151
src/Python/testing/math_filters_test.py
Normal file
151
src/Python/testing/math_filters_test.py
Normal file
@@ -0,0 +1,151 @@
|
||||
import unittest
|
||||
import numpy as np
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Ensure PYTHONPATH is correct if run from root
|
||||
sys.path.append(os.path.join(os.getcwd(), 'src', 'Python'))
|
||||
|
||||
import uLib
|
||||
|
||||
class TestMathFilters(unittest.TestCase):
|
||||
def test_filter_creation(self):
|
||||
# 1. Linear Filter
|
||||
dims = [10, 10, 10]
|
||||
v_dims = uLib.Math.Vector3i(dims)
|
||||
|
||||
linear_filter = uLib.Math.VoxFilterAlgorithmLinear(v_dims)
|
||||
self.assertIsNotNone(linear_filter)
|
||||
|
||||
# 2. ABTrim Filter
|
||||
abtrim_filter = uLib.Math.VoxFilterAlgorithmAbtrim(v_dims)
|
||||
self.assertIsNotNone(abtrim_filter)
|
||||
abtrim_filter.SetABTrim(1, 1)
|
||||
|
||||
# 3. Bilateral Filter
|
||||
bilat_filter = uLib.Math.VoxFilterAlgorithmBilateral(v_dims)
|
||||
self.assertIsNotNone(bilat_filter)
|
||||
bilat_filter.SetIntensitySigma(0.5)
|
||||
|
||||
# 4. Threshold Filter
|
||||
threshold_filter = uLib.Math.VoxFilterAlgorithmThreshold(v_dims)
|
||||
self.assertIsNotNone(threshold_filter)
|
||||
threshold_filter.SetThreshold(0.5)
|
||||
|
||||
# 5. Median Filter
|
||||
median_filter = uLib.Math.VoxFilterAlgorithmMedian(v_dims)
|
||||
self.assertIsNotNone(median_filter)
|
||||
|
||||
def test_filter_run(self):
|
||||
# Create image
|
||||
dims = [10, 10, 10]
|
||||
vox_img = uLib.Math.VoxImage(dims)
|
||||
for i in range(10*10*10):
|
||||
vox_img.SetValue(i, 1.0)
|
||||
|
||||
# Linear filter
|
||||
linear_filter = uLib.Math.VoxFilterAlgorithmLinear([3, 3, 3])
|
||||
linear_filter.SetImage(vox_img)
|
||||
|
||||
# Set kernel (simple 3x3x3 all ones)
|
||||
# Weights are usually normalized in linear filter logic?
|
||||
# Let's just test it runs.
|
||||
linear_filter.SetKernelNumericXZY([1.0] * 27)
|
||||
|
||||
# Run filter
|
||||
linear_filter.Run()
|
||||
|
||||
# Value should be 1.0 (mean of all 1.0 is 1.0)
|
||||
self.assertAlmostEqual(vox_img.GetValue(0), 1.0)
|
||||
|
||||
def test_filter_run_abtrim(self):
|
||||
# Create image
|
||||
dims = [10, 10, 10]
|
||||
vox_img = uLib.Math.VoxImage(dims)
|
||||
for i in range(10*10*10):
|
||||
vox_img.SetValue(i, 1.0)
|
||||
|
||||
# ABTrim filter
|
||||
abtrim_filter = uLib.Math.VoxFilterAlgorithmAbtrim([3, 3, 3])
|
||||
abtrim_filter.SetImage(vox_img)
|
||||
|
||||
# Set kernel (simple 3x3x3 all ones)
|
||||
# Weights are usually normalized in linear filter logic?
|
||||
# Let's just test it runs.
|
||||
abtrim_filter.SetKernelNumericXZY([1.0] * 27)
|
||||
|
||||
# Run filter
|
||||
abtrim_filter.Run()
|
||||
|
||||
# Value should be 1.0 (mean of all 1.0 is 1.0)
|
||||
self.assertAlmostEqual(vox_img.GetValue(0), 1.0)
|
||||
|
||||
def test_filter_run_bilateral(self):
|
||||
# Create image
|
||||
dims = [10, 10, 10]
|
||||
vox_img = uLib.Math.VoxImage(dims)
|
||||
for i in range(10*10*10):
|
||||
vox_img.SetValue(i, 1.0)
|
||||
|
||||
# Bilateral filter
|
||||
bilat_filter = uLib.Math.VoxFilterAlgorithmBilateral([3, 3, 3])
|
||||
bilat_filter.SetImage(vox_img)
|
||||
|
||||
# Set kernel (simple 3x3x3 all ones)
|
||||
# Weights are usually normalized in linear filter logic?
|
||||
# Let's just test it runs.
|
||||
bilat_filter.SetKernelNumericXZY([1.0] * 27)
|
||||
|
||||
# Run filter
|
||||
bilat_filter.Run()
|
||||
|
||||
# Value should be 1.0 (mean of all 1.0 is 1.0)
|
||||
self.assertAlmostEqual(vox_img.GetValue(0), 1.0)
|
||||
|
||||
def test_filter_run_threshold(self):
|
||||
# Create image
|
||||
dims = [10, 10, 10]
|
||||
vox_img = uLib.Math.VoxImage(dims)
|
||||
for i in range(10*10*10):
|
||||
vox_img.SetValue(i, 1.0)
|
||||
|
||||
# Threshold filter
|
||||
threshold_filter = uLib.Math.VoxFilterAlgorithmThreshold([3, 3, 3])
|
||||
threshold_filter.SetImage(vox_img)
|
||||
|
||||
# Set kernel (simple 3x3x3 all ones)
|
||||
# Weights are usually normalized in linear filter logic?
|
||||
# Let's just test it runs.
|
||||
threshold_filter.SetKernelNumericXZY([1.0] * 27)
|
||||
|
||||
# Run filter
|
||||
threshold_filter.Run()
|
||||
|
||||
# Value should be 1.0 (mean of all 1.0 is 1.0)
|
||||
self.assertAlmostEqual(vox_img.GetValue(0), 1.0)
|
||||
|
||||
def test_filter_run_median(self):
|
||||
# Create image
|
||||
dims = [10, 10, 10]
|
||||
vox_img = uLib.Math.VoxImage(dims)
|
||||
for i in range(10*10*10):
|
||||
vox_img.SetValue(i, 1.0)
|
||||
|
||||
# Median filter
|
||||
median_filter = uLib.Math.VoxFilterAlgorithmMedian([3, 3, 3])
|
||||
median_filter.SetImage(vox_img)
|
||||
|
||||
# Set kernel (simple 3x3x3 all ones)
|
||||
# Weights are usually normalized in linear filter logic?
|
||||
# Let's just test it runs.
|
||||
median_filter.SetKernelNumericXZY([1.0] * 27)
|
||||
|
||||
# Run filter
|
||||
median_filter.Run()
|
||||
|
||||
# Value should be 1.0 (mean of all 1.0 is 1.0)
|
||||
self.assertAlmostEqual(vox_img.GetValue(0), 1.0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user