Extract distinct pixel values from raster in C#

979
3
Jump to solution
07-31-2022 06:27 AM
LewisTrotter
New Contributor III

Hi,

I have a folder of single band GeoTiffs and I'm trying to use C# and the SDK for Pro to iterate each raster and get an array of distinct values for each raster.

Here is an simplified example of my code:

// conn is a connection path defineded up here...
// item.Value is the name of a tif file...
foreach (var item in downloadedMasks)
{
  await QueuedTask.Run(() =>
  {
    FileSystemDatastore dstore = new FileSystemDatastore(conn);
    RasterDataset rasterDataset = dstore.OpenDataset<RasterDataset>(item.Value);
    Raster raster = rasterDataset.CreateFullRaster();

    int width = raster.GetWidth();
    int height = raster.GetHeight();

    var block = raster.CreatePixelBlock(width, height);
    raster.Read(0, 0, block);
    Array array = block.GetPixelData(0, true);

    int validCount = 0;
    for (int i = 0; i < width; i++)
    {
      for (int j = 0; j < height; j++)
      {
        int pixelValue = Convert.ToInt16(block.GetValue(0, i, j));
        // check unique value and store in a list...
      }
    }
  });
};

This process is really slow - I get much quicker results using GDAL.Open() and Numpy.Unique in Python. 

Does anyone have any advice on how I could speed this up? Any advice on how to effieciently extract pixel values from a raster in C# would be appreciated.

0 Kudos
3 Solutions

Accepted Solutions
Wolf
by Esri Regular Contributor
Esri Regular Contributor

You are getting copy of the array of pixels from the pixel block corresponding to the plane (== 0) with a copy of the pixel data (second argument is set to true).

Array array = block.GetPixelData(0, true);

But you are not using the array in your analysis, maybe try:

Array array = block.GetPixelData(0, true);
for (int y = 0; y < height; y++)
{
  for (int x = 0; x < width; x++)
  {
    int pixelValue = array[x, y];
  }
}

I don't have a raster handy, so i am not sure which array dimension corresponds to x (width) and y (height).  I assume that the dimension arguments correspond to sourcePixels.GetValue (x, y), but you should double check this in your code to make sure that's the case.

View solution in original post

JamesTenbrink
New Contributor III

Hi Lewis:

Do the rasters have value attribute tables? If so, you can query the rows from the VAT to get the unique cell values. Another option might be to create a unique value renderer for each of the rasters and query the classes from the renderer.

Apropos the first suggestion, you can use the desktop.geoprocessing assembly to invoke the build raster attribute table gp tool for each tiff, if the tables don't exist yet (i'm not sure if the uv renderer will do that automatically).

regards,

Jim TenBrink

spatial analyst team

View solution in original post

LewisTrotter
New Contributor III

Thanks Wolf and Jim,

I ended up speeding this up using an approahc similar to Wolf's example, except I converted the 2D arrays into 1D arrays and using LINQ to quickly get a count of all unique values within the Raster:

Raster raster = rasterDataset.CreateFullRaster();

// Get a pixel block for quicker reading and read from pixel top left pixel
PixelBlock block = raster.CreatePixelBlock(raster.GetWidth(), raster.GetHeight());
raster.Read(0, 0, block);

// Read 2-dimensional pixel values into 1-dimensional byte array
Array pixels2D = block.GetPixelData(0, false);
byte[] pixels1D = new byte[pixels2D.Length];
Buffer.BlockCopy(pixels2D, 0, pixels1D, 0, pixels2D.Length);

// Count percentage of valid pixels and keep if > minimum allowed
double totalPixels = pixels2D.Length;
double validPixels = pixels1D.Where(e => validClasses.Contains(e)).ToArray().Length;

 

View solution in original post

0 Kudos
3 Replies
Wolf
by Esri Regular Contributor
Esri Regular Contributor

You are getting copy of the array of pixels from the pixel block corresponding to the plane (== 0) with a copy of the pixel data (second argument is set to true).

Array array = block.GetPixelData(0, true);

But you are not using the array in your analysis, maybe try:

Array array = block.GetPixelData(0, true);
for (int y = 0; y < height; y++)
{
  for (int x = 0; x < width; x++)
  {
    int pixelValue = array[x, y];
  }
}

I don't have a raster handy, so i am not sure which array dimension corresponds to x (width) and y (height).  I assume that the dimension arguments correspond to sourcePixels.GetValue (x, y), but you should double check this in your code to make sure that's the case.

JamesTenbrink
New Contributor III

Hi Lewis:

Do the rasters have value attribute tables? If so, you can query the rows from the VAT to get the unique cell values. Another option might be to create a unique value renderer for each of the rasters and query the classes from the renderer.

Apropos the first suggestion, you can use the desktop.geoprocessing assembly to invoke the build raster attribute table gp tool for each tiff, if the tables don't exist yet (i'm not sure if the uv renderer will do that automatically).

regards,

Jim TenBrink

spatial analyst team

LewisTrotter
New Contributor III

Thanks Wolf and Jim,

I ended up speeding this up using an approahc similar to Wolf's example, except I converted the 2D arrays into 1D arrays and using LINQ to quickly get a count of all unique values within the Raster:

Raster raster = rasterDataset.CreateFullRaster();

// Get a pixel block for quicker reading and read from pixel top left pixel
PixelBlock block = raster.CreatePixelBlock(raster.GetWidth(), raster.GetHeight());
raster.Read(0, 0, block);

// Read 2-dimensional pixel values into 1-dimensional byte array
Array pixels2D = block.GetPixelData(0, false);
byte[] pixels1D = new byte[pixels2D.Length];
Buffer.BlockCopy(pixels2D, 0, pixels1D, 0, pixels2D.Length);

// Count percentage of valid pixels and keep if > minimum allowed
double totalPixels = pixels2D.Length;
double validPixels = pixels1D.Where(e => validClasses.Contains(e)).ToArray().Length;

 

0 Kudos