Operator Programming

Atomic Operator

Operator differs from application in what it is atomic. The concept of atomicity does not refer to the grain-size of the operator but to the fact that the control inside the operator must be solved. An operator is not atomic when:

In such cases, the operator should must be split into several atomic operators.

The goal of such a principle is threefold:

Operator Template File

The following template gives the general structure of an operator file.
/* -*- mode: c++; c-basic-offset: 3 -*-
 *
 * Copyright (c) 2013, GREYC.
 * All rights reserved
 *
 * You may use this file under the terms of the BSD license as follows:
 *
 * "Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *   * Neither the name of the GREYC, nor the name of its
 *     contributors may be used to endorse or promote products
 *     derived from this software without specific prior written
 *     permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
 *
 * For more information, refer to:
 * https://clouard.users.greyc.fr/Pandore/
 */

#include <pandore.h>
using namespace pandore;

Errc Operator( const Img2duc &ims, Img2duc &imd, Short p ) {
   // Body
   return SUCCESS;
}

Errc Operator( const Img2dsl &ims, Img2dsl &imd, Short parameter ) {
   // Body
   return SUCCESS;
}

#ifdef MAIN

/*
 * Modify only the following constants, and the operator switches.
 */
#define USAGE   "usage: %s parameter [-m mask] [im_in|-] [im_out|-]"
#define PARC    1  // Number of parameters
#define FINC    1  // Number of input images
#define FOUTC   1  // Number of output images
#define MASK    0  // Level of masking

int main( int argc, char *argv[] ) {
   Errc result;                 // The result code of the execution.
   Pobject* mask;               // The mask.
   Pobject* objin[FINC + 1];    // The input objects.
   Pobject* objs[FINC + 1];     // The source objects masked by the mask.
   Pobject* objout[FOUTC + 1];  // The output objects.
   Pobject* objd[FOUTC + 1];    // The result objects of the execution.
   char* parv[PARC + 1];        // The input parameters.
  
   ReadArgs(argc, argv, PARC, FINC, FOUTC, &mask, objin, objs, objout, objd, parv, USAGE, MASK);
  
   switch(objs[0]->Type()){
   case Po_Img2duc: {
      Img2duc* const ims = (Img2duc*)objs[0];
      objd[0] = new Img2duc(ims->Props());
      Img2duc* const imd = (Img2duc*)objd[0];

      result = Operator(*ims, *imd, atoi(parv[0]));
      break;
   }
   case Po_Img2dsl: {
      Img2dsl* const ims = (Img2dsl*)objs[0];
      objd[0] = new Img2dsl(ims->Props());
      Img2dsl* const imd = (Img2dsl*)objd[0];
     
      result = Operator(*ims, *imd, atoi(parv[0]));
      break;
   }
   default:
      PrintErrorFormat(objin, FINC);
      result = FAILURE;
   }
  
   if (result) {
      WriteArgs(argc, argv, PARC, FINC, FOUTC, &mask, objin, objs, objout, objd, MASK);
   }
   
   Exit(result);
   return 0;
}
#endif

The operator() Function

Inputs and outputs are Pandore objects. To a first approximation, it is necessary to define one function per available object composition. For example, from the previous example, a function is defined for Img2duc x Img2duc and one for Img2dsl x Img2dsl.

However, it is possible to write only one function when the algorithm is the same for each type. There are two different ways to write generic functions:

  1. The first one uses the hierarchy of the objects -see below Writing Generic Operator Function Using Hierarchy.
  2. The second one uses the preprocessor -For more details see Preprocessing of operators.

Writing Generic Operator Function Using Hierarchy

For instance, all images and region maps inherit from the three classes Imx3d<Uchar>, Imx3d<Long>, Imx3d<Float>. So, the generic function can be defined as a template function. Such a function can be called with any image and region map types:
template <typename T1, typename T2>
Errc Operator ( const Imx3d<T1> &ims , Imx3d<T2> &imd, int p) {
   // Body
   return  SUCCESS;
}
The following example is the morphological erosion function for 2D images:
template <typename T>
Errc Erosion( const Img2d<T> &ims, Img2d<T> &imd, int connexity ) {
   Point2d p;
   T min,val;

   if (connexity != 4 && connexity != 8)
        return FAILURE;
   imd.Frame(0,1,1);
   if (connexity == 4) {
      for (p.y=1; p.y < ims.Width()-1; p.y++)
         for (p.x=1; p.x < ims.Height()-1; p.x++) {
            min=ims[p+v4[0]];
            for (int v=1; v < 4; v++)
               if ((val=ims[p+v4[v]] < min)
                  min = val;
            imd[p] = min;
         }
   } else { // connexity == 8.
      for (p.y=1; p.y <= ims.Height()-1; p.y++)
         for (p.x=1; p.x < ims.Width()-1; p.x++) {
            min=ims[p+v8[0]];
            for (int v=1; v < 8; v++)
               if ((val=ims[p+v8[v]] < min)
                  min = val;
            imd[p] = min;
         }
   }
   return SUCCESS;
}

Value Type

For a given type of Pandore object T the field T::ValueType gives access to its data type. For example:

Img2dsf::ValueType -> Float

template <typename T1, typename T2>
Errc Operator ( const Imx3d<T1> &ims , Imx3d<T2> &imd, int p) {
   for (int b=0; b<ims.Bands(); b++) {
      T1::ValueType *ps=ims.Vector(b);
      T2::ValueType *pd=imd.Vector(b);
      for ( ; p<ims.Vector()+ims.VectorSize(); ps++,pd++ ) {
        *pd = T2(*ps *2);
      }
   }
   return  SUCCESS;
}

Type Limits

The two traits Limits<T>::max() and Limits<T>::min() return respectively the maximum and the minimum values of the primitive type T (Uchar, Slong, Float....). For example:
Limits<Ushort>::max() -> 65535
Limits<Img2duc::ValueType>::max() -> 255

Type Deductions

Sometimes, it necessary to choose between two types. For example, an algorithm can take two types T1 and T2 as inputs and returns a result type that is the larger unsigned type between the two input types. This can be done using the trait Select:
  1. Select<T1,T2>::LargestUnsigned: returns the largest unsigned of the two types;
  2. Select<T1,T2>::LargestSigned: returns the largest signed of the two types;
  3. Select<T1,T2>::SmallestUnsigned: returns the smallest unsigned of the two types.
  4. Select<T1,T2>::SmallestSigned: returns the smallest signed of the two types;
  5. Select<T1,T2>::Largest: returns the largest of the two types (signed > unsigned);
  6. Select<T1,T2>::Smallest: returns the smallest of the two types (signed > unsigned).

For example:

Select<Uchar,Short>::LargestSigned -> returns Short
Select<Img3duc,Img3dsl>::LargestSigned -> returns Img2sdl
Select<Uchar,Short>::LargestUnsigned -> Ushort
Select<Uchar,Char>::Largest -> Char
Select<Uchar,Char>::Smallest -> Uchar

The main() Function

The main() function is used to generate a standalone program with the operator. When the operator is used as a function of another program then the main() must be discarded. That is why the main() is enclosed between the two C directives #ifdef MAIN and #end. If the value of macro MAIN is defined then the operator is compiled as a standalone program else simply as a separate module.

Reading Inputs

The function ReadArgs() makes the verification of the argument command line and reads the input files and the parameters.
 ReadArgs(argc,argv,PARC,FINC,FOUTC,&mask,objin,objs,objout,objd,parv,USAGE,MASK);

ReadArgs uses a set of constants that prototypes the operator command line:

Parameters are accessible by two ways:

Masking and Unmasking

The masking operation allows to apply a same operator to the whole image or to a determined part specified by a mask. The mask is a region map where the pixels with label 0 indicate the masked part.

First of all, the input image is built with the given input image masked by the given region map. All the pixel of the initial image that are masked by the region are set to 0, all the other are kept with their initial value.

Then, the operator is applied on the whole image even on the masked pixels.

Finally, the output image is built by the unmasking operation on the processed image. The output pixels are set with the new value if they are not masked or with their initial value if the are masked.

Remarks:
Sometimes, the masking operation cannot be applied as such since some pixel values are replaced by 0. For example:
The constant MASK is used to specify whether the masking and unmasking should be applied on the given operator.

The Switch

The switch control structure selects the convenient operator function from the input objects. The type of object is known from the Type() member function.
   switch(objs[0]->Type()){
   case Po_Img2duc :{
   }

Writing Outputs

The function WriteArgs() creates the output results.
   WriteArgs(argc,argv,PARC,FINC,FOUTC,&mask,objin,objs,objout,objd);

This function uses the same arguments than the ReadArgs function. This time, if MASK=1 or MASK=3 then output images are unmasked before return.

To set the output result value, use:

Exit(result);
This value can be get by the command pstatus.

The Pantheon project
Image Team GREYC Laboratory
UMR CNRS 6072 - ENSICAEN - University of Caen, France
This page was last modified on 19 June 2015