1 
2 //
3 // This source file is part of appleseed.
4 // Visit https://appleseedhq.net/ for additional information and resources.
5 //
6 // This software is released under the MIT license.
7 //
8 // Copyright (c) 2018 Esteban Tovagliari, The appleseedhq Organization
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining a copy
11 // of this software and associated documentation files (the "Software"), to deal
12 // in the Software without restriction, including without limitation the rights
13 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 // copies of the Software, and to permit persons to whom the Software is
15 // furnished to do so, subject to the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be included in
18 // all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26 // THE SOFTWARE.
27 //
28 
29 // Interface header.
30 #include "denoiser.h"
31 
32 // appleseed.renderer headers.
33 #include "renderer/global/globallogger.h"
34 
35 // appleseed.foundation headers.
36 #include "foundation/image/color.h"
37 #include "foundation/image/image.h"
38 #include "foundation/utility/job/iabortswitch.h"
39 
40 // BCD headers.
41 #include "bcd/DeepImage.h"
42 #include "bcd/Denoiser.h"
43 #include "bcd/IDenoiser.h"
44 #include "bcd/MultiscaleDenoiser.h"
45 #include "bcd/SpikeRemovalFilter.h"
46 #include "bcd/Utils.h"
47 
48 // Standard headers.
49 #include <cmath>
50 #include <memory>
51 #include <vector>
52 
53 using namespace bcd;
54 using namespace foundation;
55 using namespace std;
56 
57 namespace renderer
58 {
59 
60 namespace
61 {
62 
image_to_deepimage(const Image & src,Deepimf & dst)63     void image_to_deepimage(const Image& src, Deepimf& dst)
64     {
65         const CanvasProperties& src_props = src.properties();
66         assert(src_props.m_channel_count == 4);
67 
68         dst.resize(
69             static_cast<int>(src_props.m_canvas_width),
70             static_cast<int>(src_props.m_canvas_height),
71             3);
72 
73         for (size_t j = 0; j < src_props.m_canvas_height; ++j)
74         {
75             for (size_t i = 0; i < src_props.m_canvas_width; ++i)
76             {
77                 Color4f c;
78                 src.get_pixel(i, j, c);
79                 c.unpremultiply_in_place();
80 
81                 dst.set(static_cast<int>(j), static_cast<int>(i), 0, c[0]);
82                 dst.set(static_cast<int>(j), static_cast<int>(i), 1, c[1]);
83                 dst.set(static_cast<int>(j), static_cast<int>(i), 2, c[2]);
84             }
85         }
86     }
87 
deepimage_to_image(const Deepimf & src,Image & dst)88     void deepimage_to_image(const Deepimf& src, Image& dst)
89     {
90         const CanvasProperties& dst_props = dst.properties();
91 
92         assert(src.getWidth() == dst_props.m_canvas_width);
93         assert(src.getHeight() == dst_props.m_canvas_height);
94         assert(src.getDepth() == 3);
95         assert(dst_props.m_channel_count == 4);
96 
97         for (size_t j = 0; j < dst_props.m_canvas_height; ++j)
98         {
99             for (size_t i = 0; i < dst_props.m_canvas_width; ++i)
100             {
101                 Color4f c;
102                 dst.get_pixel(i, j, c);
103 
104                 c[0] = src.get(static_cast<int>(j), static_cast<int>(i), 0);
105                 c[1] = src.get(static_cast<int>(j), static_cast<int>(i), 1);
106                 c[2] = src.get(static_cast<int>(j), static_cast<int>(i), 2);
107 
108                 c.premultiply_in_place();
109                 dst.set_pixel(i, j, c);
110             }
111         }
112     }
113 
114     class DenoiserCallbacks
115       : public ICallbacks
116     {
117       public:
DenoiserCallbacks(IAbortSwitch * abort_switch)118         explicit DenoiserCallbacks(IAbortSwitch* abort_switch)
119           : m_abort_switch(abort_switch)
120         {
121         }
122 
progress(const float p) const123         void progress(const float p) const override
124         {
125         }
126 
isAborted() const127         bool isAborted() const override
128         {
129             if (m_abort_switch)
130                 return m_abort_switch->is_aborted();
131 
132             return false;
133         }
134 
135       private:
logInfo(const char * msg) const136         void logInfo(const char* msg) const override
137         {
138             RENDERER_LOG_INFO("%s", msg);
139         }
140 
logWarning(const char * msg) const141         void logWarning(const char* msg) const override
142         {
143             RENDERER_LOG_WARNING("%s", msg);
144         }
145 
logError(const char * msg) const146         void logError(const char* msg) const override
147         {
148             RENDERER_LOG_ERROR("%s", msg);
149         }
150 
logDebug(const char * msg) const151         void logDebug(const char* msg) const override
152         {
153             RENDERER_LOG_DEBUG("%s", msg);
154         }
155 
156         IAbortSwitch* m_abort_switch;
157     };
158 
do_denoise_image(Deepimf & src,const Deepimf & num_samples,const Deepimf & histograms,const Deepimf & covariances,const DenoiserOptions & options,IAbortSwitch * abort_switch,Deepimf & dst)159     bool do_denoise_image(
160         Deepimf&                src,
161         const Deepimf&          num_samples,
162         const Deepimf&          histograms,
163         const Deepimf&          covariances,
164         const DenoiserOptions&  options,
165         IAbortSwitch*           abort_switch,
166         Deepimf&                dst)
167     {
168         DenoiserInputs inputs;
169         inputs.m_pColors = &src;
170         inputs.m_pNbOfSamples = &num_samples;
171         inputs.m_pHistograms = &histograms;
172         inputs.m_pSampleCovariances = &covariances;
173 
174         DenoiserParameters parameters;
175         parameters.m_histogramDistanceThreshold = options.m_histogram_patch_distance_threshold;
176         parameters.m_patchRadius = static_cast<int>(options.m_patch_radius);
177         parameters.m_searchWindowRadius = static_cast<int>(options.m_search_window_radius);
178         parameters.m_minEigenValue = options.m_min_eigenvalue;
179         parameters.m_useRandomPixelOrder = options.m_use_random_pixel_order;
180         parameters.m_markedPixelsSkippingProbability = options.m_marked_pixels_skipping_probability;
181         parameters.m_nbOfCores = static_cast<int>(options.m_num_cores);
182         parameters.m_markInvalidPixels = options.m_mark_invalid_pixels;
183 
184         DenoiserOutputs outputs;
185         outputs.m_pDenoisedColors = &dst;
186 
187         unique_ptr<IDenoiser> denoiser;
188 
189         if (options.m_num_scales > 1)
190             denoiser.reset(new MultiscaleDenoiser(static_cast<int>(options.m_num_scales)));
191         else
192             denoiser.reset(new Denoiser());
193 
194         DenoiserCallbacks callbacks(abort_switch);
195         denoiser->setCallbacks(&callbacks);
196 
197         denoiser->setInputs(inputs);
198         denoiser->setOutputs(outputs);
199         denoiser->setParameters(parameters);
200 
201         if (!denoiser->inputsOutputsAreOk())
202             return false;
203 
204         return denoiser->denoise();
205     }
206 
207 }
208 
denoise_beauty_image(Image & img,Deepimf & num_samples,Deepimf & histograms,Deepimf & covariances,const DenoiserOptions & options,IAbortSwitch * abort_switch)209 bool denoise_beauty_image(
210     Image&                  img,
211     Deepimf&                num_samples,
212     Deepimf&                histograms,
213     Deepimf&                covariances,
214     const DenoiserOptions&  options,
215     IAbortSwitch*           abort_switch)
216 {
217     Deepimf src;
218     image_to_deepimage(img, src);
219 
220     if (options.m_prefilter_spikes)
221     {
222         SpikeRemovalFilter::filter(
223             src,
224             num_samples,
225             histograms,
226             covariances,
227             options.m_prefilter_threshold_stddev_factor);
228     }
229 
230     Deepimf dst(src);
231 
232     const bool success =
233         do_denoise_image(
234             src,
235             num_samples,
236             histograms,
237             covariances,
238             options,
239             abort_switch,
240             dst);
241 
242     if (success)
243         deepimage_to_image(dst, img);
244 
245     return success;
246 }
247 
denoise_aov_image(Image & img,const Deepimf & num_samples,const Deepimf & histograms,const Deepimf & covariances,const DenoiserOptions & options,IAbortSwitch * abort_switch)248 bool denoise_aov_image(
249     Image&                  img,
250     const Deepimf&          num_samples,
251     const Deepimf&          histograms,
252     const Deepimf&          covariances,
253     const DenoiserOptions&  options,
254     IAbortSwitch*           abort_switch)
255 {
256     Deepimf src;
257     image_to_deepimage(img, src);
258 
259     if (options.m_prefilter_spikes)
260     {
261         SpikeRemovalFilter::filter(
262             src,
263             options.m_prefilter_threshold_stddev_factor);
264     }
265 
266     Deepimf dst(src);
267 
268     const bool success =
269         do_denoise_image(
270             src,
271             num_samples,
272             histograms,
273             covariances,
274             options,
275             abort_switch,
276             dst);
277 
278     if (success)
279         deepimage_to_image(dst, img);
280 
281     return success;
282 }
283 
284 }   // namespace renderer
285