Python script to calculate NDVI anomalies.

4577
33
Jump to solution
06-07-2018 12:11 PM
LuizVianna
Occasional Contributor

Why the last line of this code is not working? It is the line of "Minus_3d" calculation?

This is a python script to calculate NDVI anomalies.

Thanks.

# Import arcpy module
import arcpy
from arcpy import env
from arcpy.sa import *
arcpy.env.overwriteOutput = True

# Check out any necessary licenses
arcpy.CheckOutExtension("spatial")

#Define workspaces
arcpy.env.workspace="D:\\Modis250\\NDVI_10dias_norm" #Workspace with decendial (ten days period) NDVI
#Filenames standard is "norm_ndvi_yyyy_dec_d.tif" where "yyyy" is the year and "d" is the ten days period: d=1 = january 1-10; d=2 = january 11-20; ...

for n in range (1,37):
d=str(1)
y = str(2004)
NDVINormAll=arcpy.ListRasters("norm_ndvi_*_"+d+".tif", "tif")#List of decendial NDVI for all years to calculate the dcendial mean
NDVINormY=arcpy.ListRasters("norm_ndvi_"+y+"_dec_"+d+".tif", "tif")#List of decendial NDVI rasters for the last year (y)
NDVIMeanOut = "D:\\Modis250\\NDVI_10dias_mean\\"+"mean_dec_"+d+".tif" #Define the decendial NDVI mean output (could be temporary)
NDVIMean = arcpy.gp.CellStatistics_sa(NDVINormAll, NDVIMeanOut,"MEAN", "NODATA")#Calculate the decendial NDVI mean (could be temporary)
NDVIAnomOut = "D:\\Modis250\\NDVI_10dias_anom\\"+"anom_"+y+"_dec_"+d+".tif" #Define the last year decendial NDVI anomaly output
NDVIAnom=arcpy.gp.Minus_3d(NDVINormY, NDVIMean, NDVIAnomOut) #Calculate the last year decendial NDVI anomaly and record the output tifs
print ("Decade "+d+" of "+y+" ok")

0 Kudos
1 Solution

Accepted Solutions
XanderBakker
Esri Esteemed Contributor

I tried it too and had some different results in Pro (including ArcGIS Pro closing very efficiently). I also obtained an error with the Cell statistics in some cases and decided to remove the minus and include the paths in the list of rasters that are used in the Cell Statistics. This seems to be working (you will need to change the paths back again).

def main():
    import arcpy
    import os
    arcpy.env.overwriteOutput = True

    # Check out SA license
    arcpy.CheckOutExtension("spatial")

    # settings
##    ws_norm = r'D:\Modis250\NDVI_10dias_norm'
##    ws_mean = r'D:\Modis250\NDVI_10dias_mean'
##    ws_anom = r'D:\Modis250\NDVI_10dias_anom'
    ws_norm = r'C:\GeoNet\Modis250\NDVI_10dias_norm'
    ws_mean = r'C:\GeoNet\Modis250\NDVI_10dias_mean'
    ws_anom = r'C:\GeoNet\Modis250\NDVI_10dias_anom'


    # create a list of all raster in norm folder
    arcpy.env.workspace = ws_norm
    lst_ndvi_ras = arcpy.ListRasters()
    print ("lst_ndvi_ras: {}".format(lst_ndvi_ras))

    # get list of years
    decades = GetListOfDecades(lst_ndvi_ras)
    print ("decades: {}".format(decades))

    # loop through each year
    for decade in decades:
        # Get rasters for given year
        lst_ndvi_decade = GetListOfRasterForGivenDecade(lst_ndvi_ras, decade)
        print("lst_ndvi_decade: {}".format(lst_ndvi_decade))

        # calculate mean raster for decade
        lst_ndvi_decade_paths = [os.path.join(ws_norm, r) for r in lst_ndvi_decade]
        print("lst_ndvi_decade_paths: {}".format(lst_ndvi_decade_paths))
        mean_ras = arcpy.sa.CellStatistics(lst_ndvi_decade_paths, "MEAN", "DATA")

        # store mean ras for decade
        # mean_dec_d_.tif
        out_name_mean = "mean_dec_{}.tif".format(decade)  ###
        out_name_path_mean =  os.path.join(ws_mean, out_name_mean)
        print ("out_name_path_mean: {}".format(out_name_path_mean))
        mean_ras.save(out_name_path_mean)

        # loop through each raster in decade
        for ndvi_ras in lst_ndvi_decade:
            # calculate anom for each raster in decade
            # anom_2002_dec_1.tif = norm_ndvi_2002_dec_1.tif - mean_dec_1_.tif;
            print ("ndvi_ras: {}".format(ndvi_ras))
            # minus_ras = arcpy.sa.Minus(ndvi_ras, out_name_path_mean)
            out_name_path_norm = os.path.join(ws_norm, ndvi_ras)
            minus_ras = arcpy.Raster(out_name_path_mean) - arcpy.Raster(out_name_path_norm)

            # store anom raster
            out_name_anom = ndvi_ras.replace("norm_ndvi", "anom")
            out_name_path_anom = os.path.join(ws_anom, out_name_anom)
            minus_ras.save(out_name_path_anom)
            print ("out_name_path_anom: {}".format(out_name_path_anom))


    # return SA license
    arcpy.CheckInExtension("spatial")

def GetListOfDecades(lst):
    # norm_ndvi_yyyy_dec_d.tif
    lst1 = list(set([r.split("_")[4] for r in lst]))
    return [r.split('.')[0] for r in lst1]

def GetListOfRasterForGivenDecade(lst, decade):
    lst1 = []
    search = "_{}.tif".format(decade)
    for r in lst:
        if search in r.lower():
            lst1.append(r)
    return lst1


if __name__ == '__main__':
    main()

View solution in original post

0 Kudos
33 Replies
DanPatterson_Retired
MVP Emeritus

Luiz  ... could you format your code Code Formatting the basics please

And the error message? 

The last line?

#print ("Decade "+d+" of "+y+" ok")

# ---- maybe try this instead, it handles type conversion to string

print("Decade {} of {} ok".format(d, y))
0 Kudos
LuizVianna
Occasional Contributor

Thank you Dan!

The problem is in line "NDVIAnom=arcpy.gp.Minus_3d(NDVINormY, NDVIMean, NDVIAnomOut) #Calculate the last year decendial NDVI anomaly and record the output tifs"

I´ve also tried "NDVIAnom = NDVINormY - NDVIMean
NDVIAnom.save(NDVIAnomOut)"

The errors are:

For the arcpy.gp.Minus_3d approach:

Traceback (most recent call last):
File "<string>", line 22, in <module>
File "c:\program files\arcgis\pro\Resources\arcpy\arcpy\geoprocessing\_base.py", line 506, in <lambda>
return lambda *args: val(*gp_fixargs(args, True))
RuntimeError: Object: Error in executing tool

For the "NDVIAnom = NDVINormY - NDVIMean

NDVIAnom.save(NDVIAnomOut)" approach

Traceback (most recent call last):
File "<string>", line 23, in <module>
TypeError: unsupported operand type(s) for -: 'list' and 'geoprocessing server result object'

0 Kudos
JoeBorgione
MVP Emeritus

Luiz- I've reformatted your script as Dan suggested to make it a little easier to read; in this case you say your problem is on line 33.  The error indicates your arguments are in error.  Looking upstream, my guess is Line 30, and your definition of NDVIAnomOut.  Try picking out the individual components that make of the string and set them to variables rather than stringing the whole thing inside quotes: that's what I  do and then using Dan's approach of '{}'.format(variable_name) works quite nicely...

# Import arcpy module
import arcpy
from arcpy import env
from arcpy.sa import * 
arcpy.env.overwriteOutput = True

# Check out any necessary licenses
arcpy.CheckOutExtension("spatial")

#Define workspaces
arcpy.env.workspace="D:\\Modis250\\NDVI_10dias_norm" #Workspace with decendial (ten days period) NDVI

#Filenames standard is "norm_ndvi_yyyy_dec_d.tif" where "yyyy" is the year and "d" is the ten days period: d=1 = january 1-10; d=2 = january 11-20; ...

for n in range (1,37):
    d=str(1)
    y = str(2004)
    NDVINormAll=arcpy.ListRasters("norm_ndvi_*_"+d+".tif", "tif")
    #List of decendial NDVI for all years to calculate the dcendial mean

    NDVINormY=arcpy.ListRasters("norm_ndvi_"+y+"_dec_"+d+".tif", "tif")
    #List of decendial NDVI rasters for the last year (y)

    NDVIMeanOut = "D:\\Modis250\\NDVI_10dias_mean\\"+"mean_dec_"+d+".tif" 
    #Define the decendial NDVI mean output (could be temporary)

    NDVIMean = arcpy.gp.CellStatistics_sa(NDVINormAll, NDVIMeanOut,"MEAN", "NODATA")
    #Calculate the decendial NDVI mean (could be temporary)

    NDVIAnomOut = "D:\\Modis250\\NDVI_10dias_anom\\"+"anom_"+y+"_dec_"+d+".tif" 
    Define the last year decendial NDVI anomaly output

    NDVIAnom=arcpy.gp.Minus_3d(NDVINormY, NDVIMean, NDVIAnomOut) 
    #Calculate the last year decendial NDVI anomaly and record the output tifs

    print ("Decade "+d+" of "+y+" ok")‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
That should just about do it....
0 Kudos
LuizVianna
Occasional Contributor

Thank you Joe!

I´m not expert in Python and I´m just copying some codes from internet and trying to make them work.

I changed lines 24 and line 30 to:

24   NDVIMeanOut = ("D:\\Modis250\\NDVI_10dias_anom\\mean_dec_{}.tif".format(d))

30   NDVIAnomOut = ("D:\\Modis250\\NDVI_10dias_anom\\anom_{}_dec_{}.tif".format(y, d))

and when I run the code, after line 33

33   NDVIAnom=arcpy.gp.Minus_3d(NDVINormY, NDVIMean, NDVIAnomOut)

the error is:

Traceback (most recent call last):
File "<string>", line 24, in <module>
File "c:\program files\arcgis\pro\Resources\arcpy\arcpy\geoprocessing\_base.py", line 506, in <lambda>
return lambda *args: val(*gp_fixargs(args, True))
RuntimeError: Object: Error in executing tool

If I try:

NDVIAnom = NDVINormY - NDVIMean
NDVIAnom.save(NDVIAnomOut)

the error is

Traceback (most recent call last):
File "<string>", line 25, in <module>
TypeError: unsupported operand type(s) for -: 'list' and 'geoprocessing server result object'

New code is: (sorry, I dont know how to format it)

# Import arcpy module
import arcpy
from arcpy import env
from arcpy.sa import *
arcpy.env.overwriteOutput = True

# Check out any necessary licenses
arcpy.CheckOutExtension("spatial")

#Define workspaces
arcpy.env.workspace="D:\\Modis250\\NDVI_10dias_norm" #Workspace with decendial (ten days period) NDVI
#Filenames standard is "norm_ndvi_yyyy_dec_d.tif" where "yyyy" is the year and "d" is the ten days period: d=1 = january 1-10; d=2 = january 11-20; ...

for n in range (1,37):
d=str(n)
y = str(2004)
NDVINormAll=arcpy.ListRasters("norm_ndvi_*_"+d+".tif", "tif")#List of decendial NDVI for all years to calculate the dcendial mean
NDVINormY=arcpy.ListRasters("norm_ndvi_"+y+"_dec_"+d+".tif", "tif")#List of decendial NDVI rasters for the last year (y)
#NDVIMeanOut = "D:\\Modis250\\NDVI_10dias_mean\\"+"mean_dec_"+d+".tif" #Define the decendial NDVI mean output (could be temporary)
NDVIMeanOut = ("D:\\Modis250\\NDVI_10dias_anom\\mean_dec_{}.tif".format(d))
NDVIMean = arcpy.gp.CellStatistics_sa(NDVINormAll, NDVIMeanOut,"MEAN", "NODATA")#Calculate the decendial NDVI mean (could be temporary)
#NDVIAnomOut = "D:\\Modis250\\NDVI_10dias_anom\\"+"anom_"+y+"_dec_"+d+".tif" #Define the last year decendial NDVI anomaly output
NDVIAnomOut = ("D:\\Modis250\\NDVI_10dias_anom\\anom_{}_dec_{}.tif".format(y, d))
#NDVIAnom=arcpy.gp.Minus_3d(NDVINormY, NDVIMean, NDVIAnomOut) #Calculate the last year decendial NDVI anomaly and record the output tifs
NDVIAnom = NDVINormY - NDVIMean
NDVIAnom.save(NDVIAnomOut)
#print ("Decade "+d+" of "+y+" ok")
print("Decade {} of {} ok".format(d, y))

0 Kudos
XanderBakker
Esri Esteemed Contributor

Hi Joe Borgione ,

What does not  make sense is:

  • There is for loop on line 15 that would require indentation afterwards.
  • During the loop a variable "n" is set that is not used, causing a loop of 37 iteration doing the same thing
  • the Minus_3d has the output set as parameter "Minus_3d (in_raster_or_constant1, in_raster_or_constant2, out_raster)" and this should not be used in combination as "another_output = Minus_3d (in_raster_or_constant1, in_raster_or_constant2, out_raster)"
  • The first parameter of the Minus_3d is a  list of rasters and should be a single raster
JoeBorgione
MVP Emeritus

Good catch Xander!  I've indented the entire loop. 

Luiz- as Xander mentions, your loop has a malfunction in it as you don't mention the the incrementer (n) to move through the loop.

Xander Bakker

Luiz Vianna

for n in range(1,37):
  print '{}'.format(n)
That should just about do it....
0 Kudos
XanderBakker
Esri Esteemed Contributor

I can imagine that the idea is to calculate for each raster in the list with "decendial NDVI rasters for the last year (y)" the difference needs to be calculated with the mean. The mean should be calculated outside the look and the loop should go through each item in the list of rasters.

Look at the following structure:

year_range = range(2004, 2011) # to get the values from 2004 until 2010
for year in year_range:
    print year

    # for each year:
    # - create list of all NDVI for current year: lst_ndvi_ras
    # - calculate the mean of all NDVI for current year: mean_ndvi_year

    for ndvi_ras in lst_ndvi_ras:
        # calculate anomaly and save result with unique name

I assume you will have a range of years. Each year will have a number of NDVI rasters (is that true?). How are the NDVI rasters for each year stored? Each year has its own folder or the year is part of the name? 

LuizVianna
Occasional Contributor

Hi Xander!

Let me try to explain what I need: There are 423 NDVI tifs in folder 'D: \ Modis250 \ NDVI_10dias_norm'. The tifs are named by year (from 2002 to 2018) and "decade" (36 ten days periods) as: norm_ndvi_yyyy_dec_d.tif, where "yyyy" is the year and the "d" is the "decade".

The loop calculates the mean for each decade, considering all years:

Mean (norm_ndvi_2002_dec_1.tif, norm_ndvi_2003_dec_1.tif, ..., norm_ndvi_2018_dec_1.tif ).

The 36 means are storaged in 'D:\Modis250\NDVI_10dias_mean' like 'mean_dec_d_.tif'.

The loop also calculates the anomaly for a choosing year ("y" in the code), subtracting each decade NDVI from those year by the NDVI mean for each decade:

anom_2002_dec_1.tif = norm_ndvi_2002_dec_1.tif - mean_dec_1_.tif;

anom_2002_dec_2.tif = norm_ndvi_2002_dec_2.tif - mean_dec_2_.tif;

anom_yyyy_dec_d.tif = norm_ndvi_yyyy_dec_d.tif - mean_dec_d_.tif;

The anomaly results shoud be storages in 'D:\Modis250\NDVI_10dias_anom' like anom_yyyy_dec_d.tif

The code seems to be ok until the mean calculation, however the 'Minus' operation is not working.

How can I set the input rasters for the "Minus" operation?

Thank you!

0 Kudos
LuizVianna
Occasional Contributor

The 36 "decades" NDVI means could be temporary... i dont need to store them, just use to calculate the anomaly...

0 Kudos