Deep to 2D Ops

It is possible to write a regular 2D Iop that takes deep inputs (or a mix of deep an Iop inputs). Here’s a simple example:

#include "DDImage/Iop.h"
#include "DDImage/Row.h"
#include "DDImage/DeepOp.h"

const char* CLASS = "DeepSampleCount";

using namespace DD::Image;

class DeepSampleCount : public Iop
{
public:
  DeepSampleCount(Node* node) : Iop(node)
  {
    inputs(1);
  }

  virtual bool test_input(int idx, Op*op) const
  {
    return dynamic_cast<DeepOp*>(op);
  }

  virtual Op* default_input(int idx) const
  {
    return NULL;
  }

  DeepOp* input0()
  {
    return dynamic_cast<DeepOp*>(Op::input(0));
  }

  const char* Class() const
  {
    return CLASS;
  }

  const char* node_help() const
  {
    return "counts the number of deep samples at each position, and places this in the red channel";
  }

  void _validate(bool real)
  {
    if (input0()) {
      input0()->validate(real);
      info_ = input0()->deepInfo();
      info_.channels(Mask_Red);
      set_out_channels(Mask_Red);
    } else {
      info_.set(Box());
      info_.channels(Mask_None);
    }
  }

  void _request(int x, int y, int r, int t, ChannelMask channels, int count)
  {
    if (input0()) {
      input0()->deepRequest(Box(x, y, r, t), Mask_Red, count);
    }
  }

  void engine(int y, int x, int r, const ChannelSet& cs, Row& row)
  {
    DeepOp* deepIn = input0();

    if (!deepIn) {
      foreach(z, cs) {
        row.erase(z);
      }
      return;
    }

    DeepPlane deepRow;
    if (!deepIn->deepEngine(y, x, r, Mask_Red, deepRow)) {
      Iop::abort();
      foreach(z, cs) {
        row.erase(z);
      }
      return;
    }

    while (x < r) {
      DeepPixel deepPixel = deepRow.getPixel(y, x);
      foreach(z, cs) {
        row.writable(z)[x] = deepPixel.getSampleCount();
      }
      x++;
    }
  }

  virtual int getViewableModes() const
  {
    return eViewableMode2D;
  }
  };

static Op* build(Node* node) { return new DeepSampleCount(node); }

static const Op::Description desc(::CLASS, "Image/DeepSampleCount", build);

Some of this is simple Iop boilerplate, covered elsewhere. The important functions here are as follows:

virtual bool DeepSampleCount::test_input(int idx, Op *op) const

This function is called by NUKE to determine whether a given Op can be used as the input connection for another. The default implementation in Iop returns true only for Iops. If your node needs to accept something other than an Iop as an input, you need to override this function. Our sample implementation only has one deep input, so a simple dynamic_cast to DeepOp is sufficient.

virtual Op *DeepSampleCount::default_input(int idx) const

If test_input returns false or if no input is connected, then NUKE calls default_input to obtain a fallback to use as an input. For Iops the default implementation of this returns a Black Op set to the Root’s format.

For our Op, this is inappropriate as it requires a deep input, not an Iop. Therefore, in the example, we instead return NULL. This means that functions that use the input later have to check for NULL and handle these cases.

virtual void DeepSampleCount::_validate(bool for_real)

Validate sets the info_ of the Iop. It does this here by validating the input, copying the deepInfo of the input, and then altering any parts that need changing (in this case, it uses only the Red channel). It also handles the case where Iop is NULL.

virtual void DeepSampleCount::_request(int x, int y, int r, int t, ChannelMask channels, int count)

This function makes a deepRequest() call on the input DeepOp, if connected.

virtual void engine(int y, int x, int r, const ChannelSet &cs, Row &row)

This function does the actual work of converting from a deep image to a 2D image. Firstly, it handles the case where there is no deep input by erasing all the requested channels from the row, then returning.

Next, it prepares and fetches a DeepPlane corresponding to the row that has been requested. The deepEngine() function has a special three-int variant which takes “y, x, r” - the same order that the parameters to engine are in - just for fetching a single row. The return value for this is then tested - if it was false there is an abort and it calls Iop::abort() to abort further processing, and blanks the output lines.

Otherwise, it iterates over the horizontal span of the row being fetched, and places the sample count into each pixel.