# How to find the minimum extent of a given pixel value of a raster?

I am wondering if there is a way to find the minimum bounding extent for a raster with a particular value. I clipped a raster from a global image and the extent is set as the global extent with a lot of NoData area. I would like to remove the NoData area from this raster and retain only the area containing the pixels of the particular value. How can I do this?

--Edited---

Here is my example: I would like to extract value = 1 (Blue area) and use the blue area's extent rather than the whole world for further processing.

-
Could you post a sample? –  Aaron Jan 4 '13 at 0:36
"I would like to delete the null rows and columns for this raster." What exactly does this mean? I don't understand what the desired end result is. –  blah238 Jan 4 '13 at 0:54
By "minimum bounding extent" are you looking for a rectangular extent or a polygonal footprint representing the area of the image with data? –  blah238 Jan 4 '13 at 0:55
@Tomek, the OP is looking to find the extent, not have to create it manually. –  blah238 Jan 4 '13 at 9:40
If literally anything is fair game, then some software has built-in commands to do this; see reference.wolfram.com/mathematica/ref/ImageCrop.html for instance. –  whuber Mar 21 '13 at 21:00

IF I have understood the question correctly it sounds like you want know the minimum bounding box of the values that are not null.Maybe you could convert the raster to polygons, select the polygons you are interested in and then convert them back to a raster. You can then look at the properties values which should give you the minium bounding box.

-
All told this is probably the best approach given the confines of the ArcGIS raster processing workflow. –  blah238 Jan 4 '13 at 9:35
I did this exactly. Is there any automatic way? I think the raster to polygon algorithm has a step to extract the minimum bounding box of the raster. –  Seen Jan 4 '13 at 15:25
are you after a python solution? –  dango Jan 7 '13 at 11:52

For all its analytical power, ArcGIS lacks basic raster manipulations that you can find with traditional desktop image editors like GIMP. It expects that you want to use the same analysis extent for your output raster as your input raster, unless you manually override the Output Extent environment setting. Since this is exactly what you are looking to find, not set, the ArcGIS way of doing things is getting in the way.

Despite my best efforts, and without resorting to programming, I could find no way to get the extent of your desired subset of the image (without raster-to-vector conversion which is computationally wasteful).

Instead, I used GIMP to select the blue area using the select by color tool and then inverted the selection, hit Delete to remove the rest of the pixels, inverted the selection again, cropped the image to selection, and finally exported it back out to PNG. GIMP saved it as a 1-bit depth image. The result is below:

Of course, because your sample image lacked a spatial reference component, and GIMP is not spatially aware, the output image is about as useful as your sample input. You will need to georeference it for it to be of use in a spatial analysis.

-
Actually, this operation used to be easy in previous versions of Spatial Analyst: the zonal max and min of the two coordinate grids (X and Y), using the feature as the zone, give the extent exactly. (Well, you might want to expand it by half a cellsize in all four directions.) In ArcGIS 10, you need to be creative (or to use Python) to make a coordinate grid. Regardless, the whole thing can be done entirely within SA using only grid operations and no manual intervention. –  whuber Jan 4 '13 at 14:45
@whuber I saw your solution somewhere else, but still not sure how I can implement your method. Could you show me some more detail of this? –  Seen Jan 4 '13 at 15:24
@Seen A quick search of this site finds an account of the method at gis.stackexchange.com/a/13467. –  whuber Jan 4 '13 at 16:00

Here is one possibility using SAGA GIS: http://permalink.gmane.org/gmane.comp.gis.gdal.devel/33021

In SAGA GIS there is a "Crop to Data" module (in Grid Tools module library), which performs the task.

But this would require you to import your Geotif with the GDAL import module, process it in SAGA, and finally export it as Geotif again with the GDAL export module.

Another possibility using only ArcGIS GP tools would be to build a TIN from your raster using Raster to TIN, compute its boundary using TIN Domain, and Clip your raster by the boundary (or its envelope using Feature Envelope to Polygon).

-

The trick is to compute the limits of the data that have values. Perhaps the fastest, most natural, and most general way to obtain these is with zonal summaries: by using all non-NoData cells for the zone, the zonal min and max of grids containing the X and Y coordinates will provide the full extent.

ESRI keeps changing the ways in which these calculations can be done; for instance, built-in expressions for the coordinate grids were dropped with ArcGIS 8 and appear not to have returned. Just for fun, here's a set of fast, simple calculations that will do the job no matter what.

1. Convert the grid into a single zone by equating it with itself, as in

"My grid" == "My grid"

2. Create a column index grid by flow-accumulating a constant grid with value 1. (The indexes will start with 0.) If desired, multiply this by the cellsize and add the x-coordinate of the origin to obtain an x-coordinate grid "X" (shown below).

3. Similarly, create a row index grid (and then a y-coordinate grid "Y") by flow-accumulating a constant grid with value 64.

4. Use the zone grid from step (1) to compute the zonal min and max of "X" and "Y": you now have the desired extent.

(The extent, as shown in the two tables of zonal statistics, is depicted by a rectangular outline in this figure. Grid "I" is the zone grid obtained in step (1).)

To go further, you will need to extract these four numbers from their output tables and use them to limit the analysis extent. Copying the original grid, with the restricted analysis extent in place, completes the task.

-

Here's a version of @whubers method for ArcGIS 10.1+ as a python toolbox (.pyt).

``````import arcpy

class Toolbox(object):
def __init__(self):
"""Define the toolbox (the name of the toolbox is the name of the
.pyt file)."""
self.label = "Raster Toolbox"
self.alias = ""

# List of tool classes associated with this toolbox
self.tools = [ClipNoData]

class ClipNoData(object):
def __init__(self):
"""Clip raster extent to the data that have values"""
self.label = "Clip NoData"
self.description = "Clip raster extent to the data that have values. "
self.description += "Method by Bill Huber - http://gis.stackexchange.com/a/55150/2856"

self.canRunInBackground = False

def getParameterInfo(self):
"""Define parameter definitions"""
params = []

# First parameter
params+=[arcpy.Parameter(
displayName="Input Raster",
name="in_raster",
datatype='GPRasterLayer',
parameterType="Required",
direction="Input")
]

# Second parameter
params+=[arcpy.Parameter(
displayName="Output Raster",
name="out_raster",
datatype="DERasterDataset",
parameterType="Required",
direction="Output")
]

return params

"""Set whether tool is licensed to execute."""
return arcpy.CheckExtension('spatial')==u'Available'

def execute(self, parameters, messages):
"""See http://gis.stackexchange.com/a/55150/2856
"""
try:
#Setup
arcpy.CheckOutExtension('spatial')
from arcpy.sa import *
in_raster = parameters[0].valueAsText
out_raster = parameters[1].valueAsText

dsc=arcpy.Describe(in_raster)
xmin=dsc.extent.XMin
ymin=dsc.extent.YMin
mx=dsc.meanCellWidth
my=dsc.meanCellHeight
arcpy.env.extent=in_raster
arcpy.env.cellSize=in_raster

## 1. Convert the grid into a single zone by equating it with itself
arcpy.AddMessage(r'1. Convert the grid into a single zone by equating it with itself...')
zones = Raster(in_raster) == Raster(in_raster)

## 2. Create a column index grid by flow-accumulating a constant grid
##    with value 1. (The indexes will start with 0.) Multiply this by
##    the cellsize and add the x-coordinate of the origin to obtain
##    an x-coordinate grid.
const = CreateConstantRaster(1)

xmap = (FlowAccumulation(const)) * mx + xmin

## 3. Similarly, create a y-coordinate grid by flow-accumulating a
##    constant grid with value 64.
ymap = (FlowAccumulation(const * 64)) * my + ymin

## 4. Use the zone grid from step (1) to compute the zonal min and
##    max of "X" and "Y"
arcpy.AddMessage(r'4. Use the zone grid from step (1) to compute the zonal min and max of "X" and "Y"...')

xminmax=ZonalStatisticsAsTable(zones, "value", xmap,r"IN_MEMORY\xrange", "NODATA", "MIN_MAX")
xmin,xmax=arcpy.da.SearchCursor(r"IN_MEMORY\xrange", ["MIN","MAX"]).next()

yminmax=ZonalStatisticsAsTable(zones, "value", ymap,r"IN_MEMORY\yrange", "NODATA", "MIN_MAX")
ymin,ymax=arcpy.da.SearchCursor(r"IN_MEMORY\yrange", ["MIN","MAX"]).next()

arcpy.Delete_management(r"IN_MEMORY\xrange")
arcpy.Delete_management(r"IN_MEMORY\yrange")

# Write out the reduced raster
arcpy.env.extent = arcpy.Extent(xmin,ymin,xmax+mx,ymax+my)
output = Raster(in_raster) * 1
output.save(out_raster)

except:raise
finally:arcpy.CheckInExtension('spatial')
``````
-
Very nice Luke. Self contained, runnable, uses in_memory, and well commented to boot. I had to turn off background processing (Geoprocessing > options >...) to get it working. –  matt wilkie Mar 22 '13 at 15:52
I've updated the script and set canRunInBackground=False. I will note that it's worth changing the workspace/scratchworkspace environments to a local folder (not FGDB) as I found when I left them as default (i.e <network user profile>\Default.gdb) the script took 9min!!! to run on a 250x250 cell grid. Changing to a local FGDB it took 9sec and to a local folder 1-2sec... –  Luke Mar 22 '13 at 20:31
good point about local folders, and thanks for the quick background fix (much better than writing instructions for everyone I pass it to). Might be worth throwing this up on bitbucket/github/gcode/etc. –  matt wilkie Mar 22 '13 at 20:42
Well I'm not going to put it up on ArcScripts (or whatever it's called this month) as I can't figure out how find scripts, let alone upload them... ;) –  Luke Mar 22 '13 at 21:44
+1 Thanks for this contribution, Luke! I appreciate you filling in the (rather large) gap left in my answer. –  whuber Mar 23 '13 at 3:34