Cycles: make cycles.merge_images work with incomplete layers and passes.
authorBrecht Van Lommel <brechtvanlommel@gmail.com>
Wed, 20 Mar 2019 15:02:38 +0000 (16:02 +0100)
committerBrecht Van Lommel <brechtvanlommel@gmail.com>
Wed, 20 Mar 2019 16:26:25 +0000 (17:26 +0100)
If layers and passes are not exactly the same in all files, we make a best
effort to merge them instead of failing.

intern/cycles/render/merge.cpp

index af65541..3a85ae1 100644 (file)
@@ -32,36 +32,41 @@ CCL_NAMESPACE_BEGIN
 /* Merge Image Layer */
 
 enum MergeChannelOp {
+       MERGE_CHANNEL_NOP,
        MERGE_CHANNEL_COPY,
        MERGE_CHANNEL_SUM,
        MERGE_CHANNEL_AVERAGE
 };
 
+struct MergeImagePass {
+       /* Full channel name. */
+    string channel_name;
+       /* Channel format in the file. */
+    TypeDesc format;
+       /* Type of operation to perform when merging. */
+       MergeChannelOp op;
+       /* Offset of layer channels in input image. */
+       int offset;
+       /* Offset of layer channels in merged image. */
+       int merge_offset;
+};
+
 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;
-
+       /* Passes. */
+       vector<MergeImagePass> passes;
        /* Sample amount that was used for rendering this layer. */
        int samples;
 };
 
 /* Merge Image */
 
-class MergeImage {
-public:
+struct MergeImage {
        /* OIIO file handle. */
        unique_ptr<ImageInput> in;
-
        /* Image file path. */
        string filepath;
-
        /* Render layers. */
        vector<MergeImageLayer> layers;
 };
@@ -133,7 +138,6 @@ 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 &&
@@ -145,29 +149,41 @@ static bool parse_channels(const ImageSpec &in_spec,
         * 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));
+       for(int i = 0; i < in_spec.nchannels; i++) {
+               MergeImagePass pass;
+               pass.channel_name = in_spec.channelnames[i];
+               pass.format = (in_spec.channelformats.size() > 0) ? in_spec.channelformats[i] : in_spec.format;
+               pass.offset = i;
+               pass.merge_offset = i;
+
+               string layername, passname, channelname;
+               if(parse_channel_name(pass.channel_name, layername, passname, channelname, multiview_channels)) {
+                       /* Channer part of a render layer. */
+                       pass.op = parse_channel_operation(passname);
+               }
+               else {
+                       /* Other channels are added in unnamed layer. */
+                       layername = "";
+                       pass.op = parse_channel_operation(pass.channel_name);
                }
 
-               /* Any unparsed channels are copied from the first image. */
+               file_layers[layername].passes.push_back(pass);
        }
 
        /* 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 != file_layers.end(); ++i) {
-               const string& name = i->first;
-               MergeImageLayer& layer = i->second;
+       for(auto& i: file_layers) {
+               const string& name = i.first;
+               MergeImageLayer& layer = i.second;
 
                layer.name = name;
                layer.samples = 0;
 
-               /* If the sample value isn't set yet, check if there is a layer-specific one in the input file. */
-               if(layer.samples < 1) {
+               /* Determine number of samples from metadata. */
+               if(layer.name == "") {
+                       layer.samples = 1;
+               }
+               else if(layer.samples < 1) {
                        string sample_string = in_spec.get_string_attribute("cycles." + name + ".samples", "");
                        if(sample_string != "") {
                                if(!sscanf(sample_string.c_str(), "%d", &layer.samples)) {
@@ -223,13 +239,10 @@ static bool open_images(const vector<string>& filepaths,
                        if(base_spec.width != spec.width ||
                           base_spec.height != spec.height ||
                           base_spec.depth != spec.depth ||
-                          base_spec.nchannels != spec.nchannels ||
                           base_spec.format != spec.format ||
-                          base_spec.channelformats != spec.channelformats ||
-                          base_spec.channelnames != spec.channelnames ||
                           base_spec.deep != spec.deep)
                        {
-                               error = "Images do not have exact matching data and channel layout.";
+                               error = "Images do not have matching size and data layout.";
                                return false;
                        }
                }
@@ -240,26 +253,6 @@ static bool open_images(const vector<string>& filepaths,
        return true;
 }
 
-static bool load_pixels(const MergeImage& image, array<float>& pixels, string& error)
-{
-       const ImageSpec& in_spec = image.in->spec();
-       const size_t width = in_spec.width;
-       const size_t height = in_spec.height;
-       const size_t num_channels = in_spec.nchannels;
-
-       const size_t num_pixels = (size_t)width * (size_t)height;
-       pixels.resize(num_pixels * num_channels);
-
-       /* Read all channels into buffer. Reading all channels at once is faster
-        * than individually due to interleaved EXR channel storage. */
-       if(!image.in->read_image(TypeDesc::FLOAT, pixels.data())) {
-               error = "Failed to read image: " + image.filepath;
-               return false;
-       }
-
-       return true;
-}
-
 static void merge_render_time(ImageSpec& spec,
                               const vector<MergeImage>& images,
                               const string& name,
@@ -281,24 +274,160 @@ static void merge_render_time(ImageSpec& spec,
 
 static void merge_layer_render_time(ImageSpec& spec,
                                     const vector<MergeImage>& images,
+                                    const string& layer_name,
                                     const string& time_name,
                                     const bool average)
 {
-       for(size_t i = 0; i < images[0].layers.size(); i++) {
-               string name = "cycles." + images[0].layers[i].name + "." + time_name;
-               double time = 0.0;
+       string name = "cycles." + layer_name + "." + time_name;
+       double time = 0.0;
+
+       for(const MergeImage& image: images) {
+               string time_str = image.in->spec().get_string_attribute(name, "");
+               time += time_human_readable_to_seconds(time_str);
+       }
+
+       if(average) {
+               time /= images.size();
+       }
+
+       spec.attribute(name, TypeDesc::STRING, time_human_readable_from_seconds(time));
+}
+
+static void merge_channels_metadata(vector<MergeImage>& images,
+                                    ImageSpec& out_spec,
+                                    vector<int>& channel_total_samples)
+{
+       /* Based on first image. */
+       out_spec  = images[0].in->spec();
+
+       /* Merge channels and compute offsets. */
+       out_spec.nchannels = 0;
+       out_spec.channelformats.clear();
+       out_spec.channelnames.clear();
+
+       for(MergeImage& image: images) {
+               for(MergeImageLayer& layer: image.layers) {
+                       for(MergeImagePass& pass: layer.passes) {
+                               /* Test if matching channel already exists in merged image. */
+                               bool found = false;
+
+                               for(size_t i = 0; i < out_spec.nchannels; i++) {
+                                       if(pass.channel_name == out_spec.channelnames[i]) {
+                                               pass.merge_offset = i;
+                                               channel_total_samples[i] += layer.samples;
+                                               /* First image wins for channels that can't be averaged or summed. */
+                                               if (pass.op == MERGE_CHANNEL_COPY) {
+                                                       pass.op = MERGE_CHANNEL_NOP;
+                                               }
+                                               found = true;
+                                               break;
+                                       }
+                               }
+
+                               if(!found) {
+                                       /* Add new channel. */
+                                       pass.merge_offset = out_spec.nchannels;
+                                       channel_total_samples.push_back(layer.samples);
 
-               for(const MergeImage& image: images) {
-                       string time_str = image.in->spec().get_string_attribute(name, "");
-                       time += time_human_readable_to_seconds(time_str);
+                                       out_spec.channelnames.push_back(pass.channel_name);
+                                       out_spec.channelformats.push_back(pass.format);
+                                       out_spec.nchannels++;
+                               }
+                       }
                }
+       }
+
+       /* Merge metadata. */
+       merge_render_time(out_spec, images, "RenderTime", false);
+
+       map<string, int> layer_num_samples;
+       for(MergeImage& image: images) {
+               for(MergeImageLayer& layer: image.layers) {
+                       if(layer.name != "") {
+                               layer_num_samples[layer.name] += layer.samples;
+                       }
+               }
+       }
+
+       for(const auto& i: layer_num_samples) {
+               string name = "cycles." + i.first + ".samples";
+               out_spec.attribute(name, TypeDesc::STRING, string_printf("%d", i.second));
 
-               if(average) {
-                       time /= images.size();
+               merge_layer_render_time(out_spec, images, i.first, "total_time", false);
+               merge_layer_render_time(out_spec, images, i.first, "render_time", false);
+               merge_layer_render_time(out_spec, images, i.first, "synchronization_time", true);
+       }
+}
+
+static void alloc_pixels(const ImageSpec& spec, array<float>& pixels)
+{
+       const size_t width = spec.width;
+       const size_t height = spec.height;
+       const size_t num_channels = spec.nchannels;
+
+       const size_t num_pixels = (size_t)width * (size_t)height;
+       pixels.resize(num_pixels * num_channels);
+}
+
+static bool merge_pixels(const vector<MergeImage>& images,
+                         const ImageSpec& out_spec,
+                         const vector<int>& channel_total_samples,
+                         array<float>& out_pixels,
+                         string& error)
+{
+       alloc_pixels(out_spec, out_pixels);
+       memset(out_pixels.data(), 0, out_pixels.size() * sizeof(float));
+
+       for(const MergeImage& image: images) {
+               /* Read all channels into buffer. Reading all channels at once is
+                * faster than individually due to interleaved EXR channel storage. */
+               array<float> pixels;
+               alloc_pixels(image.in->spec(), pixels);
+
+               if(!image.in->read_image(TypeDesc::FLOAT, pixels.data())) {
+                       error = "Failed to read image: " + image.filepath;
+                       return false;
                }
 
-               spec.attribute(name, TypeDesc::STRING, time_human_readable_from_seconds(time));
+               for(size_t li = 0; li < image.layers.size(); li++) {
+                       const MergeImageLayer& layer = image.layers[li];
+
+                       const size_t stride = out_spec.nchannels;
+                       const size_t num_pixels = pixels.size();
+
+                       for(const MergeImagePass& pass: layer.passes) {
+                               size_t offset = pass.offset;
+                               size_t merge_offset = pass.merge_offset;
+
+                               switch(pass.op) {
+                                       case MERGE_CHANNEL_NOP:
+                                               break;
+                                       case MERGE_CHANNEL_COPY:
+                                               for(size_t i = 0; i < num_pixels; i += stride) {
+                                                       out_pixels[i + merge_offset] = pixels[i + offset];
+                                               }
+                                               break;
+                                       case MERGE_CHANNEL_SUM:
+                                               for(size_t i = 0; i < num_pixels; i += stride) {
+                                                       out_pixels[i + merge_offset] += pixels[i + offset];
+                                               }
+                                               break;
+                                       case MERGE_CHANNEL_AVERAGE:
+                                               /* Weights based on sample metadata. Per channel since not
+                                                * all files are guaranteed to have the same channels. */
+                                               const int total_samples = channel_total_samples[offset];
+                                               const float t = (float)layer.samples / (float)total_samples;
+
+                                               for(size_t i = 0; i < num_pixels; i += stride) {
+                                                       out_pixels[i + merge_offset] += t * pixels[i + offset];
+                                               }
+                                               break;
+                               }
+                       }
+               }
        }
+
+       return true;
 }
 
 static bool save_output(const string& filepath,
@@ -374,83 +503,23 @@ bool ImageMerger::run()
                return false;
        }
 
-       /* Merge pixels. */
-       array<float> merge_pixels;
-       vector<int> merge_samples;
+       /* Merge metadata and setup channels and offsets. */
+       ImageSpec out_spec;
+       vector<int> channel_total_samples;
+       merge_channels_metadata(images, out_spec, channel_total_samples);
 
-       /* Load first image. */
-       if(!load_pixels(images[0], merge_pixels, error)) {
+       /* Merge pixels. */
+       array<float> out_pixels;
+       if(!merge_pixels(images, out_spec, channel_total_samples, out_pixels, error)) {
                return false;
        }
-       for(size_t layer = 0; layer < images[0].layers.size(); layer++) {
-               merge_samples.push_back(images[0].layers[layer].samples);
-       }
-
-       /* Merge other images. */
-       for(size_t i = 1; i < images.size(); i++) {
-               const MergeImage& image = images[i];
-
-               array<float> pixels;
-               if(!load_pixels(image, pixels, error)) {
-                       return false;
-               }
-
-               for(size_t li = 0; li < image.layers.size(); li++) {
-                       const MergeImageLayer& layer = image.layers[li];
-
-                       const int *offsets = layer.channel_offsets.data();
-                       const MergeChannelOp *ops = layer.channel_ops.data();
-
-                       const size_t stride = image.in->spec().nchannels;
-                       const size_t num_channels = layer.channel_offsets.size();
-                       const size_t num_pixels = pixels.size();
-
-                       /* Weights based on sample metadata. */
-                       const int sum_samples = merge_samples[li] + layer.samples;
-                       const float t = (float)layer.samples / (float)sum_samples;
-
-                       for(size_t pixel = 0; pixel < num_pixels; pixel += stride) {
-                               for(size_t channel = 0; channel < num_channels; channel++) {
-                                       size_t offset = pixel + offsets[channel];
-
-                                       switch(ops[channel]) {
-                                               case MERGE_CHANNEL_COPY:
-                                                       /* Already copied from first image. */
-                                                       break;
-                                               case MERGE_CHANNEL_SUM:
-                                                       merge_pixels[offset] += pixels[offset];
-                                                       break;
-                                               case MERGE_CHANNEL_AVERAGE:
-                                                       merge_pixels[offset] = (1.0f - t) * merge_pixels[offset] + t * pixels[offset];
-                                                       break;
-                                       }
-                               }
-                       }
-
-                       merge_samples[li] += layer.samples;
-               }
-       }
-
-       /* Save image with identical dimensions, channels and metadata. */
-       ImageSpec out_spec = images[0].in->spec();
-
-       /* Merge metadata. */
-       for(size_t i = 0; i < images[0].layers.size(); i++) {
-               string name = "cycles." + images[0].layers[i].name + ".samples";
-               out_spec.attribute(name, TypeDesc::STRING, string_printf("%d", merge_samples[i]));
-       }
-
-       merge_render_time(out_spec, images, "RenderTime", false);
-       merge_layer_render_time(out_spec, images, "total_time", false);
-       merge_layer_render_time(out_spec, images, "render_time", false);
-       merge_layer_render_time(out_spec, images, "synchronization_time", true);
 
        /* We don't need input anymore at this point, and will possibly
         * overwrite the same file. */
        images.clear();
 
        /* Save output file. */
-       return save_output(output, out_spec, merge_pixels, error);
+       return save_output(output, out_spec, out_pixels, error);
 }
 
 CCL_NAMESPACE_END