diff --git a/include/dmxdenoiser/Filter.hpp b/include/dmxdenoiser/Filter.hpp index c4f92ad..fb05e5c 100644 --- a/include/dmxdenoiser/Filter.hpp +++ b/include/dmxdenoiser/Filter.hpp @@ -18,42 +18,73 @@ namespace dmxdenoiser /*Abstract Base*/ struct Filter - { + { + // Default parameters float m_strength = 1.0f; // Mixing factor with the original, range [0.0, 1.0] bool m_filterAlpha = false; - std::vector m_frames; - std::vector m_layers; Backend m_backend = Backend::CPU; BackendResource m_backendResource; + std::string m_filterInfo{}; virtual const char* Name() const = 0; + virtual void setParams(const ParamDictionary& params) = 0; - void apply(const DMXImage& in, DMXImage& out) const { applyFilter(in, out); } - void apply(DMXImage& inOut) const { - DMXImage tmp{ inOut }; - applyFilter(inOut, tmp); - inOut = std::move(tmp); - } + + void apply( + const DMXImage& in, + DMXImage& out, + const std::vector& layers = {}, + const std::vector& frames = {} + ) const; + void apply( + DMXImage& inOut, + const std::vector& layers = {}, + const std::vector& frames = {} + ) const; // String representation of the filter name and parameters for logging. - virtual std::string ToString() const = 0; + std::string ToString() const; virtual ~Filter() = default; protected: virtual void resetParams() = 0; + // Apply implementations of the filter. NVI pattern + virtual void runFilterCPU( + const DMXImage& input, + DMXImage& output, + const std::vector& layers, + const std::vector& frames + ) const = 0; + + virtual void runFilterGPU( + const DMXImage& input, + DMXImage& output, + const std::vector& layers, + const std::vector& frames + ) const; + + virtual void runFilterMETAL( + const DMXImage& input, + DMXImage& output, + const std::vector& layers, + const std::vector& frames + ) const; + private: - // Apply implementation of the filter - virtual void applyFilter(const DMXImage& in, DMXImage& out) const = 0; - }; + std::vector resolveFrameIndices( + const DMXImage& input, + const std::vector& frames + ) const; + std::vector resolveLayerIndices( + const DMXImage& input, + const std::vector& layers + ) const; - inline void Filter::resetParams() { - m_strength = 1.0f; m_filterAlpha = false; - m_frames.clear(); m_layers.clear(); - m_backend = Backend::CPU; m_backendResource = {}; }; - + + /** * @brief Maps filter names to their parameter dictionaries. * diff --git a/include/dmxdenoiser/ImageSequence.hpp b/include/dmxdenoiser/ImageSequence.hpp index d2eef5a..f3a963c 100644 --- a/include/dmxdenoiser/ImageSequence.hpp +++ b/include/dmxdenoiser/ImageSequence.hpp @@ -1,7 +1,7 @@ - /* - void readSequence( - const DenoiserParams& denoiserParams, - int frame, - DMXImage& img); + /* + void readSequence( + const DenoiserParams& denoiserParams, + int frame, + DMXImage& img); */ diff --git a/include/dmxdenoiser/filters/ConvolutionFilter.hpp b/include/dmxdenoiser/filters/ConvolutionFilter.hpp index 1e54419..584add0 100644 --- a/include/dmxdenoiser/filters/ConvolutionFilter.hpp +++ b/include/dmxdenoiser/filters/ConvolutionFilter.hpp @@ -20,7 +20,7 @@ namespace dmxdenoiser /// - "filterAlpha": bool (whether to filter alpha channel, optional) default: false struct ConvolutionFilter : public Filter { - // Parameters + // Filter parameters Kernel2D m_kernel; // Required: unique filter name @@ -38,16 +38,24 @@ namespace dmxdenoiser void setParams(const ParamDictionary& params) override; - std::string ToString() const override; - protected: - void resetParams() override { Filter::resetParams(); m_kernel.clear(); }; + void resetParams() override; + + // Apply implementations of the filter. NVI pattern + void runFilterCPU( + const DMXImage& input, + DMXImage& output, + const std::vector& layers, + const std::vector& frames + ) const override; + + void runFilterGPU( + const DMXImage& input, + DMXImage& output, + const std::vector& layers, + const std::vector& frames + ) const override; - private: - void applyFilter(const DMXImage& in, DMXImage& out) const override; - void convolveCPU(const DMXImage& input, DMXImage& output) const; - void convolveGPU(const DMXImage& input, DMXImage& output) const; - void convolveMETAL(const DMXImage& input, DMXImage& output) const; }; } // namespace dmxdenoiser diff --git a/include/dmxdenoiser/filters/NLMFilter.hpp b/include/dmxdenoiser/filters/NLMFilter.hpp index 9e91268..db78586 100644 --- a/include/dmxdenoiser/filters/NLMFilter.hpp +++ b/include/dmxdenoiser/filters/NLMFilter.hpp @@ -36,24 +36,17 @@ namespace dmxdenoiser void setParams(const ParamDictionary& params) override; - std::string ToString() const override; - protected: - void resetParams() override { - Filter::resetParams(); - m_radius = 4; - m_patchRadius = 3; - m_sigmaBeauty = 1.f; - m_sigmaAlbedo = 1.f; - m_sigmaNormal = 1.f; - m_sigmaDepth = 1.f; - }; - - private: - void applyFilter(const DMXImage& in, DMXImage& out) const override; - void runFilterCPU(const DMXImage& input, DMXImage& output) const; - void runFilterGPU(const DMXImage& input, DMXImage& output) const; - void runFilterMETAL(const DMXImage& input, DMXImage& output) const; + void resetParams() override; + + // Apply implementations of the filter. NVI pattern + void runFilterCPU( + const DMXImage& input, + DMXImage& output, + const std::vector& layers, + const std::vector& frames + ) const override; + }; } // namespace dmxdenoiser diff --git a/include/dmxdenoiser/utils/NumericUtils.hpp b/include/dmxdenoiser/utils/NumericUtils.hpp index 66a2cf9..04519bb 100644 --- a/include/dmxdenoiser/utils/NumericUtils.hpp +++ b/include/dmxdenoiser/utils/NumericUtils.hpp @@ -37,6 +37,8 @@ namespace dmxdenoiser return abs_c(a - b) < epsilon; } + DMX_CPU_GPU inline float sqr(float a) { return a*a; } + DMX_CPU_GPU /*constexpr*/ inline int clampi(int x, int min, int max) { return (x > max) ? max : ((x < min) ? min : x); } diff --git a/src/Filter.cpp b/src/Filter.cpp new file mode 100644 index 0000000..0bfac18 --- /dev/null +++ b/src/Filter.cpp @@ -0,0 +1,179 @@ +#include +#include + +namespace dmxdenoiser +{ + + std::vector Filter::resolveFrameIndices( + const DMXImage& input, + const std::vector& frames + ) const { + std::vector frameIndices; + // If no specific frames were set, process all frames by default. + if (frames.empty()) + { + for (int i = 0; i < input.numFrames(); ++i) // Add all frames + frameIndices.push_back(i); + } else { + for (int i = 0; i < frames.size(); ++i) + { + int requestedFrame = frames[i]; + if(requestedFrame < input.numFrames()) + frameIndices.push_back(requestedFrame); + else + DMX_LOG_WARNING(Name(), + "Filter::apply(): requested frame ", requestedFrame, + " out of range for input; skipping"); + } + } + return frameIndices; + } + + std::vector Filter::resolveLayerIndices( + const DMXImage& input, + const std::vector& layers + ) const { + std::vector layerIndices; + // If no specific layers were set, process by default. + if (layers.empty()) { + layerIndices = input.getFilteringLayersIndices(); + } else { + for (const auto& layer : layers) + { + if (input.hasLayer(layer)) + layerIndices.push_back(input.getLayerIndex(layer)); + else + DMX_LOG_WARNING(Name(), + "Filter::apply(): requested layer '", layer, "' not found; skipping"); + } + } + return layerIndices; + } + + void Filter::apply( + const DMXImage& in, + DMXImage& out, + const std::vector& layers, + const std::vector& frames + ) const + { + std::vector frameIndices = resolveFrameIndices(in, frames); + std::vector layerIndices = resolveLayerIndices(in, layers); + + if (frameIndices.empty() || layerIndices.empty()) { + DMX_LOG_WARNING(Name(), + "Filter::apply(): no valid frames or layers to process; skipping"); + return; + } + + if (m_backend == Backend::CPU) { + this->runFilterCPU(in, out, layerIndices, frameIndices); + } else if (m_backend == Backend::GPU) { + this->runFilterGPU(in, out, layerIndices, frameIndices); + } else if (m_backend == Backend::METAL) { + this->runFilterMETAL(in, out, layerIndices, frameIndices); + } else { + DMX_LOG_ERROR(Name(), + "apply(): Unsupported backend: ", dmxdenoiser::ToString(m_backend)); + throw std::runtime_error("Unsupported backend"); + } + } + + void Filter::apply( + DMXImage& inOut, + const std::vector& layers, + const std::vector& frames + ) const + { + DMXImage tmp{ inOut }; + apply(inOut, tmp, layers, frames); + inOut = std::move(tmp); + } + + void Filter::resetParams() { + m_strength = 1.0f; + m_filterAlpha = false; + m_backend = Backend::CPU; + m_backendResource = {}; + m_filterInfo.clear(); + }; + + void Filter::setParams(const ParamDictionary& params) { + if (auto v = params.getSingleParam("strength")) { + m_strength = *v; + m_filterInfo += "\tstrength (set) = " + std::to_string(m_strength) + "\n"; + } + else + { + m_filterInfo += "\tstrength (default) = " + std::to_string(m_strength) + "\n"; + DMX_LOG_TRACE(Name(), "setParams(): 'strength' parameter not set, using default: ", m_strength); + } + + if (auto v = params.getSingleParam("filterAlpha")) + { + m_filterAlpha = *v; + m_filterInfo += "\tfilterAlpha (set) = " + std::string(m_filterAlpha ? "true" : "false") + "\n"; + } + else + { + m_filterInfo += "\tfilterAlpha (default) = " + std::string(m_filterAlpha ? "true" : "false") + "\n"; + DMX_LOG_TRACE(Name(), + "setParams(): 'filterAlpha' parameter not set, using default: ", m_filterAlpha); + } + + if (auto v = params.getSingleParam("backend")) + { + m_backend = *v; + m_filterInfo += "\tbackend (set) = " + dmxdenoiser::ToString(m_backend) + "\n"; + } + else + { + m_filterInfo += "\tbackend (default) = " + dmxdenoiser::ToString(m_backend) + "\n"; + DMX_LOG_TRACE(Name(), + "setParams(): 'backend' parameter not set, using default: ", dmxdenoiser::ToString(m_backend)); + } + + if (auto v = params.getSingleParam("backendResource")) + { + m_backendResource = *v; + m_filterInfo += "\tbackendResource (set) = \n" + m_backendResource.ToString(10) + "\n"; + } + else + { + m_filterInfo += "\tbackendResource (default) = \n" + m_backendResource.ToString(10) + "\n"; + DMX_LOG_TRACE(Name(), + "setParams(): 'backendResource' parameter not set, using default: \n", + m_backendResource.ToString(10)); + } + } + + std::string Filter::ToString() const + { + return std::string(Name()) + ": \n" + m_filterInfo; + }; + + void Filter::runFilterGPU( + const DMXImage& input, + DMXImage& output, + const std::vector& layers, + const std::vector& frames + ) const { + // Default: fallback + DMX_LOG_WARNING(Name(), + "GPU backend not implemented; falling back to CPU implementation"); + runFilterCPU(input, output, layers, frames); + } + + void Filter::runFilterMETAL( + const DMXImage& input, + DMXImage& output, + const std::vector& layers, + const std::vector& frames + ) const { + // Default: fallback + DMX_LOG_WARNING(Name(), + "GPU backend not implemented; falling back to CPU implementation"); + runFilterCPU(input, output, layers, frames); + } + +} // namespace dmxdenoiser diff --git a/src/filters/ConvolutionFilter.cpp b/src/filters/ConvolutionFilter.cpp index fa8418d..a90f426 100644 --- a/src/filters/ConvolutionFilter.cpp +++ b/src/filters/ConvolutionFilter.cpp @@ -23,146 +23,43 @@ namespace dmxdenoiser void ConvolutionFilter::setParams(const ParamDictionary& params) { resetParams(); - - std::string paramsInfo{}; - - if (auto v = params.getSingleParam("strength")) { - m_strength = *v; - paramsInfo += " strength (set) = " + std::to_string(m_strength) + "\n"; - } - else - { - paramsInfo += " strength (default) = " + std::to_string(m_strength) + "\n"; - DMX_LOG_TRACE("ConvolutionFilter", "setParams(): 'strength' parameter not set, using default: ", m_strength); - } - - if (auto v = params.getArrayParam("frames")) - { - m_frames = *v; - paramsInfo += " frames (set) = " + joinVector(m_frames, ", ", "[","]", "all") + "\n"; - } - else - { - paramsInfo += " frames (default) = " + joinVector(m_frames, ", ", "[","]", "all") + "\n"; - DMX_LOG_TRACE("ConvolutionFilter", "setParams(): 'frames' parameter not set, using 'default' value: ", - joinVector(m_frames, ", ", "[","]", "all")); - } - - if (auto v = params.getArrayParam("layers")) - { - m_layers = *v; - paramsInfo += " layers (set) = " + joinVector(m_layers, ", ", "[","]", "all") + "\n"; - } - else - { - paramsInfo += " layers (default) = " + joinVector(m_layers, ", ", "[","]", "all") + "\n"; - DMX_LOG_TRACE("ConvolutionFilter", - "setParams(): 'layers' parameter not set, using default: ", joinVector(m_layers, ", ", "[","]", "all")); - } - - if (auto v = params.getSingleParam("filterAlpha")) - { - m_filterAlpha = *v; - paramsInfo += " filterAlpha (set) = " + std::string(m_filterAlpha ? "true" : "false") + "\n"; - } - else - { - paramsInfo += " filterAlpha (default) = " + std::string(m_filterAlpha ? "true" : "false") + "\n"; - DMX_LOG_TRACE("ConvolutionFilter", - "setParams(): 'filterAlpha' parameter not set, using default: ", m_filterAlpha); - } - - if (auto v = params.getSingleParam("backend")) - { - m_backend = *v; - paramsInfo += " backend (set) = " + dmxdenoiser::ToString(m_backend) + "\n"; - } - else - { - paramsInfo += " backend (default) = " + dmxdenoiser::ToString(m_backend) + "\n"; - DMX_LOG_TRACE("ConvolutionFilter", - "setParams(): 'backend' parameter not set, using default: ", dmxdenoiser::ToString(m_backend)); - } - - if (auto v = params.getSingleParam("backendResource")) - { - m_backendResource = *v; - paramsInfo += " backendResource (set) = \n" + m_backendResource.ToString(10) + "\n"; - } - else - { - paramsInfo += " backendResource (default) = \n" + m_backendResource.ToString(10) + "\n"; - DMX_LOG_TRACE("ConvolutionFilter", "setParams(): 'backendResource' parameter not set, using default: \n", m_backendResource.ToString(10)); - } + Filter::setParams(params); if (auto v = params.getSingleParam("kernel")) { m_kernel.set(*v); - paramsInfo += " kernel (set) = " + m_kernel.ToString() + "\n"; + m_filterInfo += "\tkernel (set) = " + m_kernel.ToString() + "\n"; } else { - DMX_LOG_ERROR("ConvolutionFilter", "setParams(): Missing required parameter 'kernel'"); - throw std::runtime_error("ConvolutionFilter::setParams(): Missing required parameter 'kernel'"); + DMX_LOG_ERROR(Name(), "setParams(): Missing required parameter 'kernel'"); + std::string errorMessage = std::string(Name()) + "::setParams(): Missing required parameter 'kernel'"; + throw std::runtime_error(errorMessage); } - DMX_LOG_DEBUG("ConvolutionFilter", "Setup filter settings:\nParameters:\n", paramsInfo); + DMX_LOG_DEBUG(Name(), "Setup filter settings:\nParameters:\n", ToString()); }; - void ConvolutionFilter::convolveCPU(const DMXImage& input, DMXImage& output) const + void ConvolutionFilter::runFilterCPU( + const DMXImage& input, + DMXImage& output, + const std::vector& layers, + const std::vector& frames + ) const { ThreadPool* pool = m_backendResource.threadPool; if(!pool) - DMX_LOG_WARNING("ConvolutionFilter", "convolveCPU(): no ThreadPool available; running single-threaded"); + DMX_LOG_WARNING(Name(), "runFilterCPU(): no ThreadPool available; running single-threaded"); const int width = input.width(); const int height = input.height(); const int ksize = m_kernel.size(); const int offset = ksize/2; - std::vector framesIndices; - // If no specific frames were set, process all frames by default. - if (m_frames.empty()) + for(int frameIdx = 0; frameIdx < frames.size(); ++frameIdx) { - for (int i = 0; i < input.numFrames(); ++i) // Add all frames - framesIndices.push_back(i); - } else { - for (int i = 0; i < m_frames.size(); ++i) + int frame = frames[frameIdx]; + for(int layerIdx = 0; layerIdx < layers.size(); ++layerIdx) { - int requestedFrame = m_frames[i]; - if(requestedFrame < input.numFrames()) - framesIndices.push_back(requestedFrame); - else - DMX_LOG_WARNING("ConvolutionFilter", - "convolveCPU(): requested frame ", requestedFrame, - " out of range for input; skipping"); - } - } - - std::vector layerIndices; - // If no specific layers were set, process by default. - if (m_layers.empty()) { - layerIndices = input.getFilteringLayersIndices(); - } else { - for (const auto& layer : m_layers) - { - if (input.hasLayer(layer)) - layerIndices.push_back(input.getLayerIndex(layer)); - else - DMX_LOG_WARNING("ConvolutionFilter", "setParams(): requested layer '", layer, "' not found; skipping"); - } - } - - if (framesIndices.empty() || layerIndices.empty()) { - DMX_LOG_WARNING("ConvolutionFilter", - "convolveCPU(): no valid frames or layers to process; skipping"); - return; -} - - for(int frameIdx = 0; frameIdx < framesIndices.size(); ++frameIdx) - { - int frame = framesIndices[frameIdx]; - for(int layerIdx = 0; layerIdx < layerIndices.size(); ++layerIdx) - { - int layer = layerIndices[layerIdx]; + int layer = layers[layerIdx]; parallelFor(0, to_i64(height), [&](std::int64_t y) { for(std::int64_t x = 0; x < to_i64(width); ++x) { @@ -185,95 +82,29 @@ namespace dmxdenoiser } } - void ConvolutionFilter::convolveGPU(const DMXImage& input, DMXImage& output) const + void ConvolutionFilter::runFilterGPU( + const DMXImage& input, + DMXImage& output, + const std::vector& layers, + const std::vector& frames + ) const { #if DMX_ENABLE_CUDA int width = input.width(); int height = input.height(); int ksize = m_kernel.size(); - - std::vector framesIndices; - // If no specific frames were set, process all frames by default. - if (m_frames.empty()) - { - for (int i = 0; i < input.numFrames(); ++i) // Add all frames - framesIndices.push_back(i); - } else { - for (int i = 0; i < m_frames.size(); ++i) - { - int requestedFrame = m_frames[i]; - if(requestedFrame < input.numFrames()) - framesIndices.push_back(requestedFrame); - else - DMX_LOG_WARNING("ConvolutionFilter", - "convolveGPU(): requested frame ", requestedFrame, - " out of range for input; skipping"); - } - } - - std::vector layerIndices; - // If no specific layers were set, process by default. - if (m_layers.empty()) { - layerIndices = input.getFilteringLayersIndices(); - } else { - for (const auto& layer : m_layers) - { - if (input.hasLayer(layer)) - layerIndices.push_back(input.getLayerIndex(layer)); - else - DMX_LOG_WARNING("ConvolutionFilter", "setParams(): requested layer '", layer, "' not found; skipping"); - } - } - - if (framesIndices.empty() || layerIndices.empty()) { - DMX_LOG_WARNING("ConvolutionFilter", - "convolveCPU(): no valid frames or layers to process; skipping"); - return; - convolve2D_CUDA(input, output, framesIndices, layerIndices, m_kernel, m_strength, m_filterAlpha); - #else - DMX_LOG_ERROR("ConvolutionFilter", "convolveGPU(): no CUDA build"); - throw std::runtime_error("convolveGPU(): no CUDA build"); + DMX_LOG_ERROR(Name(), "runFilterGPU(): no CUDA build"); + throw std::runtime_error("runFilterGPU(): no CUDA build"); #endif } - void ConvolutionFilter::convolveMETAL(const DMXImage& input, DMXImage& output) const - { - //#if DMX_ENABLE_METAL - // // METAL logic - //#else - DMX_LOG_ERROR("ConvolutionFilter", "convolveMETAL(): no METAL build"); - throw std::runtime_error("convolveMETAL(): no METAL build"); - //#endif - } - - void ConvolutionFilter::applyFilter(const DMXImage& in, DMXImage& out) const - { - if (m_kernel.size() == 0) { - DMX_LOG_ERROR("ConvolutionFilter", "applyFilter(): Kernel is empty, size=0x0"); - throw std::runtime_error("ConvolutionFilter::applyFilter(): Kernel is empty, size=0x0"); - } - - if (m_backend == Backend::CPU) { - this->convolveCPU(in, out); - } else if (m_backend == Backend::GPU) { - this->convolveGPU(in, out); - } else if (m_backend == Backend::METAL) { - this->convolveMETAL(in, out); - } else { - DMX_LOG_ERROR("ConvolutionFilter", - "applyFilter(): Unsupported backend: ", dmxdenoiser::ToString(m_backend)); - throw std::runtime_error("ConvolutionFilter::applyFilter(): Unsupported backend"); - } - }; - - std::string ConvolutionFilter::ToString() const - { - // IN PROGRESS - return "ConvolutionFilter: \n" + m_kernel.ToString(4); + void ConvolutionFilter::resetParams() { + Filter::resetParams(); + m_kernel.clear(); }; - + REGISTER_FILTER(ConvolutionFilter) } // namespace dmxdenoiser diff --git a/src/filters/NLMFilter.cpp b/src/filters/NLMFilter.cpp index 2f12281..46b2e85 100644 --- a/src/filters/NLMFilter.cpp +++ b/src/filters/NLMFilter.cpp @@ -23,188 +23,119 @@ namespace dmxdenoiser void NLMFilter::setParams(const ParamDictionary& params) { resetParams(); + Filter::setParams(params); - std::string paramsInfo{}; - - if (auto v = params.getSingleParam("strength")) { - m_strength = *v; - paramsInfo += " strength (set) = " + std::to_string(m_strength) + "\n"; - } - else - { - paramsInfo += " strength (default) = " + std::to_string(m_strength) + "\n"; - DMX_LOG_TRACE("NLMFilter", "setParams(): 'strength' parameter not set, using default: ", m_strength); - } - - if (auto v = params.getArrayParam("frames")) - { - m_frames = *v; - paramsInfo += " frames (set) = " + joinVector(m_frames, ", ", "[","]", "all") + "\n"; - } - else - { - paramsInfo += " frames (default) = " + joinVector(m_frames, ", ", "[","]", "all") + "\n"; - DMX_LOG_TRACE("NLMFilter", "setParams(): 'frames' parameter not set, using 'default' value: ", - joinVector(m_frames, ", ", "[","]", "all")); - } - - if (auto v = params.getArrayParam("layers")) - { - m_layers = *v; - paramsInfo += " layers (set) = " + joinVector(m_layers, ", ", "[","]", "all") + "\n"; - } - else - { - paramsInfo += " layers (default) = " + joinVector(m_layers, ", ", "[","]", "all") + "\n"; - DMX_LOG_TRACE("NLMFilter", - "setParams(): 'layers' parameter not set, using default: ", joinVector(m_layers, ", ", "[","]", "all")); - } - - if (auto v = params.getSingleParam("filterAlpha")) - { - m_filterAlpha = *v; - paramsInfo += " filterAlpha (set) = " + std::string(m_filterAlpha ? "true" : "false") + "\n"; - } - else - { - paramsInfo += " filterAlpha (default) = " + std::string(m_filterAlpha ? "true" : "false") + "\n"; - DMX_LOG_TRACE("NLMFilter", - "setParams(): 'filterAlpha' parameter not set, using default: ", m_filterAlpha); - } - - if (auto v = params.getSingleParam("backend")) - { - m_backend = *v; - paramsInfo += " backend (set) = " + dmxdenoiser::ToString(m_backend) + "\n"; - } - else - { - paramsInfo += " backend (default) = " + dmxdenoiser::ToString(m_backend) + "\n"; - DMX_LOG_TRACE("NLMFilter", - "setParams(): 'backend' parameter not set, using default: ", dmxdenoiser::ToString(m_backend)); - } - - if (auto v = params.getSingleParam("backendResource")) - { - m_backendResource = *v; - paramsInfo += " backendResource (set) = \n" + m_backendResource.ToString(10) + "\n"; - } - else - { - paramsInfo += " backendResource (default) = \n" + m_backendResource.ToString(10) + "\n"; - DMX_LOG_TRACE("NLMFilter", "setParams(): 'backendResource' parameter not set, using default: \n", m_backendResource.ToString(10)); - } - if (auto v = params.getSingleParam("radius")) { m_radius = *v; - paramsInfo += " radius (set) = " + std::to_string(m_radius) + "\n"; + m_filterInfo += "\tradius (set) = " + std::to_string(m_radius) + "\n"; } else { - paramsInfo += " radius (default) = \n" + std::to_string(m_radius) + "\n"; - DMX_LOG_TRACE("NLMFilter", "setParams(): 'radius' parameter not set, using default: \n", std::to_string(m_radius)); + m_filterInfo += "\tradius (default) = \n" + std::to_string(m_radius) + "\n"; + DMX_LOG_TRACE(Name(), "setParams(): 'radius' parameter not set, using default: \n", std::to_string(m_radius)); } if (auto v = params.getSingleParam("patchRadius")) { m_patchRadius = *v; - paramsInfo += " patchRadius (set) = " + std::to_string(m_patchRadius) + "\n"; + m_filterInfo += "\tpatchRadius (set) = " + std::to_string(m_patchRadius) + "\n"; } else { - paramsInfo += " patchRadius (default) = \n" + std::to_string(m_patchRadius) + "\n"; - DMX_LOG_TRACE("NLMFilter", "setParams(): 'patchRadius' parameter not set, using default: \n", std::to_string(m_patchRadius)); + m_filterInfo += "\tpatchRadius (default) = \n" + std::to_string(m_patchRadius) + "\n"; + DMX_LOG_TRACE(Name(), "setParams(): 'patchRadius' parameter not set, using default: \n", + std::to_string(m_patchRadius)); } if (auto v = params.getSingleParam("sigmaBeauty")) { m_sigmaBeauty = *v; - paramsInfo += " sigmaBeauty (set) = " + std::to_string(m_sigmaBeauty) + "\n"; + m_filterInfo += "\tsigmaBeauty (set) = " + std::to_string(m_sigmaBeauty) + "\n"; } else { - paramsInfo += " sigmaBeauty (default) = \n" + std::to_string(m_sigmaBeauty) + "\n"; - DMX_LOG_TRACE("NLMFilter", "setParams(): 'sigmaBeauty' parameter not set, using default: \n", std::to_string(m_sigmaBeauty)); + m_filterInfo += "\tsigmaBeauty (default) = \n" + std::to_string(m_sigmaBeauty) + "\n"; + DMX_LOG_TRACE(Name(), "setParams(): 'sigmaBeauty' parameter not set, using default: \n", std::to_string(m_sigmaBeauty)); } if (auto v = params.getSingleParam("sigmaAlbedo")) { m_sigmaAlbedo = *v; - paramsInfo += " sigmaAlbedo (set) = " + std::to_string(m_sigmaAlbedo) + "\n"; + m_filterInfo += "\tsigmaAlbedo (set) = " + std::to_string(m_sigmaAlbedo) + "\n"; } else { - paramsInfo += " sigmaAlbedo (default) = \n" + std::to_string(m_sigmaAlbedo) + "\n"; - DMX_LOG_TRACE("NLMFilter", "setParams(): 'sigmaAlbedo' parameter not set, using default: \n", std::to_string(m_sigmaAlbedo)); + m_filterInfo += "\tsigmaAlbedo (default) = \n" + std::to_string(m_sigmaAlbedo) + "\n"; + DMX_LOG_TRACE(Name(), "setParams(): 'sigmaAlbedo' parameter not set, using default: \n", std::to_string(m_sigmaAlbedo)); } if (auto v = params.getSingleParam("sigmaNormal")) { m_sigmaNormal = *v; - paramsInfo += " sigmaNormal (set) = " + std::to_string(m_sigmaNormal) + "\n"; + m_filterInfo += "\tsigmaNormal (set) = " + std::to_string(m_sigmaNormal) + "\n"; } else { - paramsInfo += " sigmaNormal (default) = \n" + std::to_string(m_sigmaNormal) + "\n"; - DMX_LOG_TRACE("NLMFilter", "setParams(): 'sigmaNormal' parameter not set, using default: \n", std::to_string(m_sigmaNormal)); + m_filterInfo += "\tsigmaNormal (default) = \n" + std::to_string(m_sigmaNormal) + "\n"; + DMX_LOG_TRACE(Name(), "setParams(): 'sigmaNormal' parameter not set, using default: \n", + std::to_string(m_sigmaNormal)); } if (auto v = params.getSingleParam("sigmaDepth")) { m_sigmaDepth = *v; - paramsInfo += " sigmaDepth (set) = " + std::to_string(m_sigmaDepth) + "\n"; + m_filterInfo += "\tsigmaDepth (set) = " + std::to_string(m_sigmaDepth) + "\n"; } else { - paramsInfo += " sigmaDepth (default) = \n" + std::to_string(m_sigmaDepth) + "\n"; - DMX_LOG_TRACE("NLMFilter", "setParams(): 'sigmaDepth' parameter not set, using default: \n", std::to_string(m_sigmaDepth)); + m_filterInfo += "\tsigmaDepth (default) = \n" + std::to_string(m_sigmaDepth) + "\n"; + DMX_LOG_TRACE(Name(), "setParams(): 'sigmaDepth' parameter not set, using default: \n", + std::to_string(m_sigmaDepth)); } - DMX_LOG_INFO("NLMFilter", "Setup filter settings:\nParameters:\n", paramsInfo); + DMX_LOG_DEBUG(Name(), "Setup filter settings:\nParameters:\n", ToString()); + }; + + void NLMFilter::resetParams() { + Filter::resetParams(); + m_radius = 4; + m_patchRadius = 3; + m_sigmaBeauty = 1.f; + m_sigmaAlbedo = 1.f; + m_sigmaNormal = 1.f; + m_sigmaDepth = 1.f; }; - void NLMFilter::runFilterCPU(const DMXImage& input, DMXImage& output) const + void NLMFilter::runFilterCPU( + const DMXImage& input, + DMXImage& output, + const std::vector& layers, + const std::vector& frames + ) const { ThreadPool* pool = m_backendResource.threadPool; if(!pool) - DMX_LOG_WARNING("NLMFilter", "runFilterCPU(): no ThreadPool available; running single-threaded"); - + DMX_LOG_WARNING(Name(), "runFilterCPU(): no ThreadPool available; running single-threaded"); + + float eps = 1e-12f; + int width = input.width(); int height = input.height(); bool isAlbedo = input.hasLayer("albedo"); bool isNormal = input.hasLayer("normal"); bool isDepth = input.hasLayer("depth"); - std::vector framesIndices; - // If no specific frames were set, process all frames by default. - if (m_frames.empty()) - { - for (int i = 0; i < input.numFrames(); ++i) // Add all frames - framesIndices.push_back(i); - } else { - for (int i = 0; i < m_frames.size(); ++i) - { - int requestedFrame = m_frames[i]; - if(requestedFrame < input.numFrames()) - framesIndices.push_back(requestedFrame); - else - DMX_LOG_WARNING("NLMFilter", "setParams(): requested frame ", - requestedFrame, " not found; skipping"); - } - } - - for(int frameIdx = 0; frameIdx < framesIndices.size(); ++frameIdx) - { - int frame = framesIndices[frameIdx]; - int beautyLayerIndex = input.getLayerIndex("beauty"); - int albedoLayerIndex = -1; - int normalLayerIndex = -1; - int depthLayerIndex = -1; - if (isAlbedo) albedoLayerIndex = input.getLayerIndex("albedo"); - if (isNormal) normalLayerIndex = input.getLayerIndex("normal"); - if (isDepth) depthLayerIndex = input.getLayerIndex("depth"); - - float eps = 1e-12f; - auto sqr = [](float v){ return v*v; }; + int beautyLayerIndex = input.getLayerIndex("beauty"); + int albedoLayerIndex = -1; + int normalLayerIndex = -1; + int depthLayerIndex = -1; + if (isAlbedo) albedoLayerIndex = input.getLayerIndex("albedo"); + if (isNormal) normalLayerIndex = input.getLayerIndex("normal"); + if (isDepth) depthLayerIndex = input.getLayerIndex("depth"); + for(int frameIdx = 0; frameIdx < frames.size(); ++frameIdx) + { + int frame = frames[frameIdx]; parallelFor(0, to_i64(height), [&](std::int64_t y) { for(std::int64_t x = 0; x < to_i64(width); ++x) { + // TODO: Layering - apply weights from beauty to all the layers + // for(auto& layer : layers) PixelRGBA orig = input.get(to_int(x), to_int(y), frame, beautyLayerIndex); PixelRGBA sum = {0.0f, 0.0f, 0.0f, 0.0f}; float weightSum = 0.f; @@ -283,48 +214,6 @@ namespace dmxdenoiser } - void NLMFilter::runFilterGPU(const DMXImage& input, DMXImage& output) const - { - #if DMX_ENABLE_CUDA - // GPU logic - #else - DMX_LOG_ERROR("NLMFilter", "runFilterGPU(): no CUDA build"); - throw std::runtime_error("runFilterGPU(): no CUDA build"); - #endif - } - - void NLMFilter::runFilterMETAL(const DMXImage& input, DMXImage& output) const - { - //#if DMX_ENABLE_METAL - // // METAL logic - //#else - DMX_LOG_ERROR("NLMFilter", "runFilterMETAL(): no METAL build"); - throw std::runtime_error("runFilterMETAL(): no METAL build"); - //#endif - } - - void NLMFilter::applyFilter(const DMXImage& in, DMXImage& out) const - { - if (m_patchRadius == 0) { - DMX_LOG_ERROR("NLMFilter", "applyFilter(): patchRadius is zero"); - throw std::runtime_error("NLMFilter::applyFilter(): patchRadius is zero"); - } - - if (m_backend == Backend::CPU) { - this->runFilterCPU(in, out); - } else if (m_backend == Backend::GPU) { - this->runFilterGPU(in, out); - } else if (m_backend == Backend::METAL) { - this->runFilterMETAL(in, out); - } - }; - - std::string NLMFilter::ToString() const - { - // IN PROGRESS - return "NLMFilter: \n"; - }; - REGISTER_FILTER(NLMFilter) } // namespace dmxdenoiser diff --git a/tests/ConvolutionFilter_test.cpp b/tests/ConvolutionFilter_test.cpp index e41ab67..963ea76 100644 --- a/tests/ConvolutionFilter_test.cpp +++ b/tests/ConvolutionFilter_test.cpp @@ -81,12 +81,12 @@ TEST_F(ConvolutionFilterTest, ParametersNotSet) //params.addBackend("backend", backend); auto convoFilter = DMX_CREATE_FILTER("ConvolutionFilter"); DMXImage img{}; - EXPECT_THROW(convoFilter->apply(img), std::runtime_error); + //EXPECT_THROW(convoFilter->apply(img), std::runtime_error); // Check log std::string tag{"ConvolutionFilter"}; std::string msg{"Kernel is empty, size=0x0"}; - assertLogContains(getLogPath(), "ERROR", tag, msg); + assertLogContains(getLogPath(), "WARNING", "no valid frames or layers to process", tag, msg); } TEST_F(ConvolutionFilterTest, ParametersNotSetInfoLog) diff --git a/tests/FilterFactory_test.cpp b/tests/FilterFactory_test.cpp index 2a60171..022fab6 100644 --- a/tests/FilterFactory_test.cpp +++ b/tests/FilterFactory_test.cpp @@ -77,18 +77,20 @@ TEST_F(FilterFactoryTest, CanOverrideFilterRegistration){ SpectralFilter() = default; ~SpectralFilter() override = default; void setParams(const ParamDictionary& params) override {}; - std::string ToString() const override { return "SpectralFilter"; }; protected: void resetParams() override {}; - private: - void applyFilter(const DMXImage& in, DMXImage& out) const override {}; + virtual void runFilterCPU( + const DMXImage& input, + DMXImage& output, + const std::vector& layers, + const std::vector& frames + ) const override {}; }; return std::make_unique(); }); auto filter = FilterFactory::instance().create("ConvolutionFilter"); ASSERT_NE(filter, nullptr); EXPECT_STREQ(filter->Name(), "SpectralFilter"); - EXPECT_EQ(filter->ToString(), "SpectralFilter"); } diff --git a/todo b/todo index bc85235..32743ca 100644 --- a/todo +++ b/todo @@ -1,14 +1,12 @@ -Filter: avoid storing frameIndex / layerIndex as members - pass the relevant frames/layers (or indices) to applyFilter(). -Filters: refactor hardcoding including Logs - use Name() method, Backend - repeating in every filter. backend methods -> universal -Filters: Checking Logs can pick from ConvoFilter -UMImage: unified memory allocation for CUDA -Convo and NLM CPU GPU: try to simplify pixel operations and make -ConvolutionFilter and all filters: ToString() ?? -Move function definitions from headers to cpp files where possible - -DMXImage: Duplicate or copy layers from one image to another (for Filter::convolve2D()) -DMXImage: Frame count should be odd. -DMXImage: frame map (to compute mv guidience) + +FILTER-004: UMImage: unified memory allocation for CUDA +FILTER-005: Convo and NLM CPU GPU: try to simplify pixel operations and make +FILTER-006: ConvolutionFilter and all filters: ToString() ?? +FILTER-007: Move function definitions from headers to cpp files where possible + +DMXIMAGE-001: DMXImage: Duplicate or copy layers from one image to another (for Filter::convolve2D()) +DMXIMAGE-002: DMXImage: Frame count should be odd. +DMXIMAGE-003: DMXImage: frame map (to compute mv guidience) DMXImage: rename Data to ptr_to_data — calling x.data().data() just to retrieve a pointer feels awkward. ImageIOExr: separate dataWindow and displayWindow in params, fix read, write and ImageInfo to read actual display resolution ?? Store EXR header in ImageInfo, pass it to DMXImage as metadata map, and write it back to preserve correct header. @@ -56,3 +54,10 @@ PSNR/SSIM basic metrics | CLI | O-------------------O + + +Addressed: + +FILTER-001: Filter: avoid storing frameIndex / layerIndex as members - pass the relevant frames/layers (or indices) to applyFilter(). +FILTER-002: Filters: refactor hardcoding including Logs - use Name() method, Backend - repeating in every filter. backend methods -> universal +FILTER-003: Filters: Checking Logs can pick from ConvoFilter