Executable Ops

The vast majority of the time, the node tree is evaluated in response to either a Viewer or a Write node causing a render. The write node relies on what is known as ‘executable’ functionality to enable this to happen without the Viewer being connected. A node can use this executable mix in class to allow any arbitrary calculation to occur outside of the standard Viewer processing tree, in response to a user interaction event (generally implemented as a button knob). The most common example is where you might want to generate some data that relies on processing the entire image sequence. For example, if you’re writing a motion vector generator, you might want to know the maximum amount of inter-frame motion to be seen throughout the full range of frames. You might want to have an “Analyze” button that looks at every pair of consecutive frames and keeps a running guess of how much motion is observed throughout the sequence, and then updates some motion vector generator parameters accordingly. In addition, setting a node up as a render node includes the node in NUKE’s “Render” menu, allowing the user to call the node from the “Render All” or “Render Selected” options.

The NormaliseExecute.cpp example in the NDK creates an Op that extends the Normalise example to search for the highest value over a series of frames. That is, we’re going to have a button that first causes NUKE to scan through every frame of the sequence, find the overall max color value, and store it as a Float_knob. The value in that knob is then multiplied by every pixel to ‘normalize’ the image to 1.0. The first step is to instruct your plug-in to inherit from the mix in Executable class. This tells NUKE to treat it as a render node when required. When inheriting from this class, three key functions need to be overloaded: beginExecuting(), execute(), and endExecuting(). The beginExecuting() function is called once the node has been told to render and can be used to set up any data structures you’d like to store running data in. The execute() function is called on each frame. Grabbing the OutputContext::defaultContext() in this function gives you back all your frame and view information, and calling input(n) gives you back the input Op for the corresponding context. Once all the frames and views you specified when invoking the render have been processed, the endExecuting() function is called. This can be used to perform any analysis or clean-up of the data you collected over the render. For example, closing output files, performing cluster analysis, and so on. The Executable interesting parts of the code are as follows:

class NormaliseExecute : public Iop, Executable
{

  // snip....
  /// executable methods

  void beginExecuting();
  void endExecuting();
  void execute();
  virtual Executable* executable() { return this; }

void NormaliseExecute::beginExecuting()
{
  std::cerr << "Begin Executing." << std::endl;
  _maxValue = _calcMaxValue  = 0;
}

void NormaliseExecute::endExecuting()
{
  std::cerr <<"End Executing." << std::endl;
  knob("maxValue")->set_value( _calcMaxValue );
}


void NormaliseExecute::execute()
{
  // do anaylsis for current frame
  Format format = input0().format();

  // these useful format variables are used later
  const int fx = format.x();
  const int fy = format.y();
  const int fr = format.r();
  const int ft = format.t();

  ChannelSet readChannels = input0().info().channels();

  Interest interest( input0(), fx, fy, fr, ft, readChannels, true );
  interest.unlock();

  // fetch each row and find the highest number pixel
  _maxValue = 0;
  for ( int ry = fy; ry < ft; ry++) {
    progressFraction( ry, ft - fy );
    Row row( fx, fr );
    row.get( input0(), ry, fx, fr, readChannels );
    if ( aborted() )
      return;

    foreach( z, readChannels ) {
      const float *CUR = row[z] + fx;
      const float *END = row[z] + fr;
      while ( CUR < END ) {
        _calcMaxValue = std::max( (float)*CUR, (float)_calcMaxValue );
        CUR++;
      }
    }
  }
}

In the above snippet, you can see how our global (over the sequence) variables are initialized in the beginExecuting() function. The execute() function is then used to read in an image and update the running global counter. Finally, once the entire sequence batch is finished, the endExecuting() function is called to calculate the average color of all pixels in the sequence and update our _maxValue knob. Note there is one last function, executable(), that must be overridden for your plug-in to be treated as a render style node.

The following snippet shows the operation of the engine() function. Also shown is how to use a Python script knob, a PyScript_knob, to create a button for invoking the batch operation of the plug-in.

//...continued

void NormaliseExecute::engine ( int y, int x, int r,
                              ChannelMask channels, Row& row )
{
  Row in( x,r);
  in.get( input0(), y, x, r, channels );
  if ( aborted() )
    return;

  foreach( z, channels ) {
    float *CUR = row.writable(z) + x;
    const float* inptr = in[z] + x;
    const float *END = row[z] + r;
    while ( CUR < END ) {
        *CUR++ = *inptr++ * ( 1. / _maxValue );
    }
  }
}



void NormaliseExecute::knobs( Knob_Callback f ) {
   Float_knob(f,  &_maxValue, IRange(0,5), "maxValue", "Max");
   Divider(f);
   const char* renderScript = "currentNode = nuke.toNode(\"this\")\n"
   "nodeList = [currentNode]\n"
   "nukescripts.render_panel(nodeList, False)\n";
   PyScript_knob(f, renderScript, "Get Max Value");
}

In both these circumstances, the Python nukescripts.render_panel and TCL execute_panel provide a wrapper around the underlying Python nuke.execute and TCL execute respectively. These wrappers provide a user interface panel allowing selection of frame ranges, views, and so on. In some circumstances, you may not wish to allow this selection. For example, you may instead want the user to position the playhead on a particular frame and then analyze at that particular frame, without adding an extra user interface panel. To do this, you need to instead directly use the relevant execute method:

PyScript_knob(f, "nuke.execute(nuke.thisNode(), nuke.frame(), nuke.frame(), 1)", "calcMaxThisFrame", "Calculate Max Value This Frame")