[Bf-blender-cvs] [83de13f75aa] blender2.7: Cycles: add cycles.merge_images operator for combing EXR renders.

Brecht Van Lommel noreply at git.blender.org
Tue Mar 19 18:30:06 CET 2019


Commit: 83de13f75aafca7d4e1d58ddd36cca19121aa84f
Author: Brecht Van Lommel
Date:   Tue Mar 19 14:38:57 2019 +0100
Branches: blender2.7
https://developer.blender.org/rB83de13f75aafca7d4e1d58ddd36cca19121aa84f

Cycles: add cycles.merge_images operator for combing EXR renders.

This is only available through the API, mainly intended for render farms to
combine rendered multilayer EXR Files with different samples. The images are
currently expected to have the exact same render layers and passes, just with
different samples.

Variance passes are still simply a weighted average, ideally these should be
merged more intelligently.

Differential Revision: https://developer.blender.org/D4554

===================================================================

M	intern/cycles/blender/addon/operators.py
M	intern/cycles/blender/blender_python.cpp
M	intern/cycles/blender/blender_util.h
M	intern/cycles/render/CMakeLists.txt
A	intern/cycles/render/merge.cpp
A	intern/cycles/render/merge.h

===================================================================

diff --git a/intern/cycles/blender/addon/operators.py b/intern/cycles/blender/addon/operators.py
index 28657de24d4..f8511dc8de4 100644
--- a/intern/cycles/blender/addon/operators.py
+++ b/intern/cycles/blender/addon/operators.py
@@ -124,9 +124,48 @@ class CYCLES_OT_denoise_animation(Operator):
         return {'FINISHED'}
 
 
+class CYCLES_OT_merge_images(Operator):
+    "Combine OpenEXR multilayer images rendered with different sample" \
+    "ranges into one image with reduced noise."
+    bl_idname = "cycles.merge_images"
+    bl_label = "Merge Images"
+
+    input_filepath1: StringProperty(
+        name='Input Filepath',
+        description='File path for image to merge',
+        default='',
+        subtype='FILE_PATH')
+
+    input_filepath2: StringProperty(
+        name='Input Filepath',
+        description='File path for image to merge',
+        default='',
+        subtype='FILE_PATH')
+
+    output_filepath: StringProperty(
+        name='Output Filepath',
+        description='File path for merged image',
+        default='',
+        subtype='FILE_PATH')
+
+    def execute(self, context):
+        in_filepaths = [self.input_filepath1, self.input_filepath2]
+        out_filepath = self.output_filepath
+
+        import _cycles
+        try:
+            _cycles.merge(input=in_filepaths, output=out_filepath)
+        except Exception as e:
+            self.report({'ERROR'}, str(e))
+            return {'FINISHED'}
+
+        return {'FINISHED'}
+
+
 classes = (
     CYCLES_OT_use_shading_nodes,
-    CYCLES_OT_denoise_animation
+    CYCLES_OT_denoise_animation,
+    CYCLES_OT_merge_images
 )
 
 def register():
diff --git a/intern/cycles/blender/blender_python.cpp b/intern/cycles/blender/blender_python.cpp
index 647e7c6374b..063b0ede92b 100644
--- a/intern/cycles/blender/blender_python.cpp
+++ b/intern/cycles/blender/blender_python.cpp
@@ -23,6 +23,7 @@
 #include "blender/blender_session.h"
 
 #include "render/denoising.h"
+#include "render/merge.h"
 
 #include "util/util_debug.h"
 #include "util/util_foreach.h"
@@ -638,9 +639,8 @@ static PyObject *opencl_compile_func(PyObject * /*self*/, PyObject *args)
 }
 #endif
 
-static bool denoise_parse_filepaths(PyObject *pyfilepaths, vector<string>& filepaths)
+static bool image_parse_filepaths(PyObject *pyfilepaths, vector<string>& filepaths)
 {
-
 	if(PyUnicode_Check(pyfilepaths)) {
 		const char *filepath = PyUnicode_AsUTF8(pyfilepaths);
 		filepaths.push_back(filepath);
@@ -709,12 +709,12 @@ static PyObject *denoise_func(PyObject * /*self*/, PyObject *args, PyObject *key
 	/* Parse file paths list. */
 	vector<string> input, output;
 
-	if(!denoise_parse_filepaths(pyinput, input)) {
+	if(!image_parse_filepaths(pyinput, input)) {
 		return NULL;
 	}
 
 	if(pyoutput) {
-		if(!denoise_parse_filepaths(pyoutput, output)) {
+		if(!image_parse_filepaths(pyoutput, output)) {
 			return NULL;
 		}
 	}
@@ -753,6 +753,42 @@ static PyObject *denoise_func(PyObject * /*self*/, PyObject *args, PyObject *key
 	Py_RETURN_NONE;
 }
 
+static PyObject *merge_func(PyObject * /*self*/, PyObject *args, PyObject *keywords)
+{
+	static const char *keyword_list[] = {"input", "output", NULL};
+	PyObject *pyinput, *pyoutput = NULL;
+
+	if (!PyArg_ParseTupleAndKeywords(args, keywords, "OO", (char**)keyword_list, &pyinput, &pyoutput)) {
+		return NULL;
+	}
+
+	/* Parse input list. */
+	vector<string> input;
+	if(!image_parse_filepaths(pyinput, input)) {
+		return NULL;
+	}
+
+	/* Parse output string. */
+	if(!PyUnicode_Check(pyoutput)) {
+		PyErr_SetString(PyExc_ValueError, "Output must be a string.");
+		return NULL;
+	}
+	string output = PyUnicode_AsUTF8(pyoutput);
+
+	/* Merge. */
+	ImageMerger merger;
+	merger.input = input;
+	merger.output = output;
+
+	if(!merger.run()) {
+		PyErr_SetString(PyExc_ValueError, merger.error.c_str());
+		return NULL;
+	}
+
+	Py_RETURN_NONE;
+}
+
+
 static PyObject *debug_flags_update_func(PyObject * /*self*/, PyObject *args)
 {
 	PyObject *pyscene;
@@ -916,6 +952,7 @@ static PyMethodDef methods[] = {
 
 	/* Standalone denoising */
 	{"denoise", (PyCFunction)denoise_func, METH_VARARGS|METH_KEYWORDS, ""},
+	{"merge", (PyCFunction)merge_func, METH_VARARGS|METH_KEYWORDS, ""},
 
 	/* Debugging routines */
 	{"debug_flags_update", debug_flags_update_func, METH_VARARGS, ""},
diff --git a/intern/cycles/blender/blender_util.h b/intern/cycles/blender/blender_util.h
index eb7019f45bc..a2c1a68c454 100644
--- a/intern/cycles/blender/blender_util.h
+++ b/intern/cycles/blender/blender_util.h
@@ -32,7 +32,6 @@
  * todo: clean this up ... */
 
 extern "C" {
-size_t BLI_timecode_string_from_time_simple(char *str, size_t maxlen, double time_seconds);
 void BKE_image_user_frame_calc(void *iuser, int cfra, int fieldnr);
 void BKE_image_user_file_path(void *iuser, void *ima, char *path);
 unsigned char *BKE_image_get_pixels_for_frame(void *image, int frame);
diff --git a/intern/cycles/render/CMakeLists.txt b/intern/cycles/render/CMakeLists.txt
index e6afbc50463..b7c53f17c3d 100644
--- a/intern/cycles/render/CMakeLists.txt
+++ b/intern/cycles/render/CMakeLists.txt
@@ -22,6 +22,7 @@ set(SRC
 	image.cpp
 	integrator.cpp
 	light.cpp
+	merge.cpp
 	mesh.cpp
 	mesh_displace.cpp
 	mesh_subdivision.cpp
@@ -55,6 +56,7 @@ set(SRC_HEADERS
 	image.h
 	integrator.h
 	light.h
+	merge.h
 	mesh.h
 	nodes.h
 	object.h
diff --git a/intern/cycles/render/merge.cpp b/intern/cycles/render/merge.cpp
new file mode 100644
index 00000000000..af655417046
--- /dev/null
+++ b/intern/cycles/render/merge.cpp
@@ -0,0 +1,456 @@
+/*
+ * Copyright 2011-2019 Blender Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "render/merge.h"
+
+#include "util/util_array.h"
+#include "util/util_map.h"
+#include "util/util_system.h"
+#include "util/util_time.h"
+#include "util/util_unique_ptr.h"
+
+#include <OpenImageIO/imageio.h>
+#include <OpenImageIO/filesystem.h>
+
+OIIO_NAMESPACE_USING
+
+CCL_NAMESPACE_BEGIN
+
+/* Merge Image Layer */
+
+enum MergeChannelOp {
+	MERGE_CHANNEL_COPY,
+	MERGE_CHANNEL_SUM,
+	MERGE_CHANNEL_AVERAGE
+};
+
+struct MergeImageLayer {
+	/* Layer name. */
+	string name;
+
+	/* All channels belonging to this MergeImageLayer. */
+	vector<string> channel_names;
+	/* Offsets of layer channels in image. */
+	vector<int> channel_offsets;
+	/* Type of operation to perform when merging. */
+	vector<MergeChannelOp> channel_ops;
+
+	/* Sample amount that was used for rendering this layer. */
+	int samples;
+};
+
+/* Merge Image */
+
+class MergeImage {
+public:
+	/* OIIO file handle. */
+	unique_ptr<ImageInput> in;
+
+	/* Image file path. */
+	string filepath;
+
+	/* Render layers. */
+	vector<MergeImageLayer> layers;
+};
+
+/* Channel Parsing */
+
+static MergeChannelOp parse_channel_operation(const string& pass_name)
+{
+	if(pass_name == "Depth" ||
+	   pass_name == "IndexMA" ||
+	   pass_name == "IndexOB" ||
+	   string_startswith(pass_name, "Crypto"))
+	{
+		return MERGE_CHANNEL_COPY;
+	}
+	else if(string_startswith(pass_name, "Debug BVH") ||
+	        string_startswith(pass_name, "Debug Ray") ||
+	        string_startswith(pass_name, "Debug Render Time"))
+	{
+		return MERGE_CHANNEL_SUM;
+	}
+	else {
+		return MERGE_CHANNEL_AVERAGE;
+	}
+}
+
+/* Splits in at its last dot, setting suffix to the part after the dot and
+ * into the part before it. Returns whether a dot was found. */
+static bool split_last_dot(string &in, string &suffix)
+{
+	size_t pos = in.rfind(".");
+	if(pos == string::npos) {
+		return false;
+	}
+	suffix = in.substr(pos+1);
+	in = in.substr(0, pos);
+	return true;
+}
+
+/* Separate channel names as generated by Blender.
+ * Multiview format: RenderLayer.Pass.View.Channel
+ * Otherwise: RenderLayer.Pass.Channel */
+static bool parse_channel_name(string name,
+                               string &renderlayer,
+                               string &pass,
+                               string &channel,
+                               bool multiview_channels)
+{
+	if(!split_last_dot(name, channel)) {
+		return false;
+	}
+	string view;
+	if(multiview_channels && !split_last_dot(name, view)) {
+		return false;
+	}
+	if(!split_last_dot(name, pass)) {
+		return false;
+	}
+	renderlayer = name;
+
+	if(multiview_channels) {
+		renderlayer += "." + view;
+	}
+
+	return true;
+}
+
+static bool parse_channels(const ImageSpec &in_spec,
+                           vector<MergeImageLayer>& layers,
+                           string& error)
+{
+	const std::vector<string> &channels = in_spec.channelnames;
+	const ParamValue *multiview = in_spec.find_attribute("multiView");
+	const bool multiview_channels = (multiview &&
+	                                 multiview->type().basetype == TypeDesc::STRING &&
+	                                 multiview->type().arraylen >= 2);
+
+	layers.clear();
+
+	/* Loop over all the channels in the file, parse their name and sort them
+	 * by RenderLayer.
+	 * Channels that can't be parsed are directly passed through to the output. */
+	map<string, MergeImageLayer> file_layers;
+	for(int i = 0; i < channels.size(); i++) {
+		string layer, pass, channel;
+
+		if(parse_channel_name(channels[i], layer, pass, channel, multiview_channels)) {
+			file_layers[layer].channel_names.push_back(pass + "." + channel);
+			file_layers[layer].channel_offsets.push_back(i);
+			file_layers[layer].channel_ops.push_back(parse_channel_operation(pass));
+		}
+
+		/* Any unparsed channels are copied from the first image. */
+	}
+
+	/* Loop over all detected RenderLayers, check whether they contain a full set of input channels.
+	 * Any channels that won't be processed internally are also passed through. */
+	for(map<string, MergeImageLayer>::iterator i = file_layers.begin(); i

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-blender-cvs mailing list