Hexagon Geospatial
MENU

Developers Knowledge Base

Read articles and post questions in this comprehensive developer community and support forum.
Showing results for 
Search instead for 
Do you mean 

Example 4: Determine whether pixel is NODATA

by Technical Evangelist ‎10-06-2015 11:36 AM - edited ‎06-22-2016 01:57 PM (480 Views)

This example is located in sm_RasterExample.  The sm_RasterExample project implements three new operators. In this example, we examine the IsNoData operator.  Examples later in this guide explain the other two operators in this project.  IsNoData creates a plug-in DLL that extends Spatial Modeler by adding an operator that takes raster data as input and returns whether each pixel of the raster is NODATA.  The example model that uses the Overlay operator to perform a lazy mosaic of two or more images is isnodata.gmdx.

 

IsNoData is the first of several examples that implement Raster Operators.  Raster operators differ from non-raster operators in the way that data is processed.  Non-raster operators process all of their data at once in the OnExecute() method; however, raster operators process the pixels in blocks in the ProcssBlock() method.  In the OnExecute() method they only set up information about what raster data they are able to produce and set it as RasterInfo data on the output port.  In ProcessBlock() they process the pixels for a block and put the output pixels on the output port as a TileData.

 

Dissecting Example 4

 

The IsNoData operator class

Let’s start by looking at the operator itself.  The code for the operator can be found in IsNoData.h and IsNoData.cpp.  The definition of the class, in IsNoData.h, is similar to that of the operator we created in the earlier example above. But since IsNoData operates on raster data, we add the include for the header file RasterOperator:

 

#include <sbrasterlib/RasterOperator.h>

Also, we derive from the class RasterOperator rather than Operator:

 

class IsNoData &colon; public Intergraph::SpatialModelerRaster::RasterOperator

We use the SBLIB_PLUGIN_OBJECT macro to set the namespace and name of the plugin, and the OPERATOR_DISPLAY_INFO macro to set the display information as in the previous example:

 

SBLIB_PLUGIN_OBJECT ( L"Example", L"Operator", L"IsNoData" );
OPERATOR_DISPLAY_INFO ( ETXT_TEXT_TR ( "Example" ), ETXT_TEXT_TR("IsNoData"), L"" );

Declare the default constructor and destructor, and the required Init and OnExecute methods as in the previous example:

 

IsNoData();
~IsNoData();
 
void Init();
void OnExecute();

Since this is a raster operator, there is another method we must implement; the ProcessBlock method. Non-raster data can be processed all at once in the OnExecute method. For rasters, Spatial Modeler splits up the data into a series of blocks (rectangular arrays of pixels). Blocks are also referred to as tiles. Each block of pixels is processed using the ProcessBlock method.

 

void ProcessBlock( const Intergraph::SpatialModelerRaster::RasterRequestPtr & rop );

 

IsNoData&colon;:Init() Implementation

 

Now let’s look at how IsNoData is implemented in IsNoData.cpp. As in the previous example, we create the ports for the operator in the Init() method.  IsNoData has two ports: an input raster and an output raster.  Both ports use RasterInfoData as the type supported by the port. We use SB_PORT_INPUT and SB_PORT_OUTPUT to identify the ports as input and output, respectively.

 

CreatePort<RasterInfoData>
   (
   L"InputRaster",
   SB_PORT_INPUT
   );
 
CreatePort<RasterInfoData>
   (
   L"OutputRaster",
   SB_PORT_OUTPUT
   );

 

IsNoData&colon;:OnExecute() Implementation

For a raster operation, the OnExecute method performs some initialization and setup. The “real work” of the operation is performed by the ProcessBlock method. 

 

For IsNodata&colon;:OnExecute, we first get a pointer to a RasterInfo. The RasterInfo is determined by settings in the model’s Processing Properties combined with the input rasters to the model.

 

RasterInfoPtr rinfo = GetRasterInfo();

IsNoData’s output is binary – each output is set to True if the input pixel is NODATA, or False is the input pixel isn’t NODATA. So we set the data type on the RasterInfo to binary (unsigned 1 bit):

 

rinfo->SetDataType(DATA_TYPE_U1);

Next we get the output port and set its data pointer to the RasterInfo:

 

GetPort(L"OutputRaster")->SetDataPtr<RasterInfoData>(rinfo);

 

IsNoData&colon;:ProcessBlock() Implementation

The ProcessBlock method is where we get input raster data, check each pixel to see if it is NODATA, and set the output pixels accordingly. The method is passed a pointer to a RasterRequest.  Note that, unlike OnExecute(), ProcessBlock() will be called from multiple threads for different tiles, so it should be thread safe.  If for some reason it is impossible to make the ProcessBlock() method on your Raster Operator thread-safe, you may call Operator::SetThreadSafe()in the Init() method of your operator, but performance may be affected.

 

void IsNoData&colon;:ProcessBlock( const RasterRequestPtr & rop )

From the input port, we get a pointer to a Tile which will hold a block of pixels from the input raster.

 

ReadonlyTilePtr input = GetPort(L"InputRaster")->GetDataPtr<TileData>(rop, Port::ThrowIfEmpty);

Next we create the Tile for the output. The output tile will have the same number of bands, the same width, and the same height as the input tile. Since our output is binary, we set the data type to unsigned 1 bit (DATA_TYPE_U1).

 

TilePtr result = CreateTile
   (
   input->GetNumBands(),
   input->GetWidth(),
   input->GetHeight(),
   DATA_TYPE_U1
   );

Start editing the output tile.

 

result->StartEdit();

Loop through the pixels of the input tile, and set the value of pixels in the output tile. We use a triple-nested loop, looping on bands, rows, and finally pixels in the row. We use GetMaskRow on the input tile to get each row of the mask, which designates which pixels are valid data as opposed to NODATA. The IS_MASK_VALUE_DATA macro checks that the given mask value is valid data. We use GetRow on the output tile to get each row of output pixels. We use a Data_U1 pointer for the output row, since we set the data type of the output tile to DATA_TYPE_U1.

 

for (long b = 0; b < input->GetNumBands(); ++b)
{
   for (long y = 0; y < input->GetHeight(); ++y)
   {
          const Byte * inputMaskRow = input->GetMaskRow(b, y);
          Data_U1 * outputDataRow = result->GetRow<Data_U1>(b, y);
          for (long x = 0; x < input->GetWidth(); ++x)
          {
                 outputDataRow[x] = IS_MASK_VALUE_DATA(inputMaskRow[x]) ? 0 : 1;
          }
   }
}

Finish the editing of the output tile.

 

result->EndEdit()

Now we set the output port to point to the data in the output tile.

 

GetPort(L"OutputRaster")->SetDataPtr< TileData>(rop, result);
 
Registering the plug-in

As in our previous example, we have to register the plug-in objects implemented in this DLL.  This is in the file SMRasterExamplePlugin.cpp.

 

if ( type == L"Operator" || type == L"" )
{
   pluginList.push_back ( SBLIB_PLUGIN_REGISTRATION ( L"IsNoData",
          sm_RasterExample::IsNoData ) );
}

 

Previous article:  Example 3: View Image Operator

Next article:  Example 5: Lazy-mosaic images

Overview
Contributors