Reclassify raster data simply

3029
0
08-14-2016 12:27 PM
Labels (1)
DanPatterson_Retired
MVP Emeritus
0 0 3,029

Reclassifying raster data can be a bit of a challenge, particularly if there are nodata values in the raster.  This is a simple example of how to perform classifications using a sample array. (background 6,000+ rasters the follow-up  )

An array will be used since it is simple to bring in raster data to numpy using arcpy's:

  •   RasterToNumPyArray
    •   RasterToNumPyArray (in_raster, {lower_left_corner}, {ncols}, {nrows}, {nodata_to_value})

and sending the result back out using:

  • NumPyArrayToRaster
    • NumPyArrayToRaster (in_array, {lower_left_corner}, {x_cell_size}, {y_cell_size}, {value_to_nodata})

On with the demo...

Raster with full data

old                                new

[ 0  1  2  3  4  5  6  7  8  9]    [1 1 1 1 1 2 2 2 2 2]

[10 11 12 13 14 15 16 17 18 19]    [3 3 3 3 3 4 4 4 4 4]

[20 21 22 23 24 25 26 27 28 29]    [5 5 5 5 5 6 6 6 6 6]

[30 31 32 33 34 35 36 37 38 39]    [7 7 7 7 7 7 7 7 7 7]

[40 41 42 43 44 45 46 47 48 49]    [7 7 7 7 7 7 7 7 7 7]

[50 51 52 53 54 55 56 57 58 59]    [7 7 7 7 7 7 7 7 7 7]

# basic structure
a = np.arange(60).reshape((6, 10))
a_rc = np.zeros_like(a)
bins = [0, 5, 10, 15, 20, 25, 30, 60, 100]
new_bins = [1, 2, 3, 4, 5, 6, 7, 8]
new_classes = zip(bins[:-1], bins[1:], new_bins)
for rc in new_classes:
    q1 = (a >= rc[0])
    q2 = (a < rc[1])
    z = np.where(q1 & q2, rc[2], 0)
    a_rc = a_rc + z
return a_rc
# result returned
‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Lines 2, 3, 4 and 5 describe the array/raster, the classes that are to be used in reclassifying the raster and the new classes to assign to each class.  Line 5 simply zips the bins and new_bins into a new_classes arrangement which will subsequently be used to query the array, locate the appropriate values and perform the assignment (lines 6-10 )

Line 3 is simply the array that the results will be placed.  The np.zeros_like function essentially creates an array with the same structure and data type as the input array.  There are other options that could be used to create containment or result arrays, but reclassification is going to be a simple addition process...

  • locate the old classes
  • reclass those cells to a new value
  • add the results to the containment raster/array

Simple but effective... just ensure that your new classes are inclusive by adding one class value outside the possible range of the data.

Line 10 contains the np.where statement which cleverly allows you to put in a query and assign an output value where the condition is met and where it is not met.  You could be foolish and try to build the big wonking query that handles everything in one line... but you would soon forget when you revisit the resultant the next day.  So to alleviate this possibility, the little tiny for loop does the reclassification one grouping at a time and adds the resultant to the destination array.  When the process is complete, the final array is returned.

Now on to the arrays/rasters that have nodata values.  The assignment of nodata values is handled by RasterToNumPyArray so you should be aware of what is assigned to it.

Raster with nodata values

old                                  new

[--  1  2  3  4  5  6 --  8  9]      [--  1  1  1  1  2  2 --  2  2]

[10 11 12 13 -- 15 16 17 18 19]      [ 3  3  3  3 --  4  4  4  4  4]

[20 -- 22 23 24 25 26 27 -- 29]      [ 5 --  5  5  5  6  6  6 --  6]

[30 31 32 33 34 -- 36 37 38 39]      [ 7  7  7  7  7 --  7  7  7  7]

[40 41 -- 43 44 45 46 47 48 --]      [ 7  7 --  7  7  7  7  7  7 --]

[50 51 52 53 54 55 -- 57 58 59]]     [ 7  7  7  7  7  7 --  7  7  7]

Make a mask (aka ... nodata values) where the numbers are divisible by 7 and the remainder is 0.

Perform the reclassification using the previous conditions.

# mask the values
a_mask = np.ma.masked_where(a%7==0, a)
a_mask.set_fill_value(-1)
# set the nodata value
‍‍‍‍‍‍‍‍‍

The attached sample script prints out the test with the following information:

Input array ... type ndarray

...snip...

Reclassification using

:  from [0, 5, 10, 15, 20, 25, 30, 60, 100]

:  to   [1, 2, 3, 4, 5, 6, 7, 8]

:  mask is False value is None

Reclassed array

...snip...

Input array ... type MaskedArray

...snip

Reclassification using

:  from [0, 5, 10, 15, 20, 25, 30, 60, 100]

:  to   [1, 2, 3, 4, 5, 6, 7, 8]

:  mask is True value is -1

Reclassed array

...snip....

-------------------------------------

That's about all for now.  Check the documentation on masked arrays and their functions.  Most functions and properties that apply to ndarrays also apply to masked arrays... it's like learning a new language just by altering the pronounciation of what you already know.

About the Author
Retired Geomatics Instructor at Carleton University. I am a forum MVP and Moderator. Current interests focus on python-based integration in GIS. See... Py... blog, my GeoNet blog...
Labels