I have been using ERDAS IMAGINE's ATCOR extension for ATmospheric CORrection of ASTER imagery.

What opensource options exist for atmospheric correction of ASTER Level 1B products?

I'm primarily interested in converting registered radiance at the sensor to true reflectance values.

  • 1
    Updated the answer below, added a link to a python script looping i.atcorr over Landsat imagery. Maybe a start to get a solution for ASTER as well? – Nikos Alexandris Nov 27 '14 at 9:41

One open source option for atmospherically correcting ASTER L1B products, in order to convert at-sensor Radiance values to Top of Canopy Reflectances, is GRASS GIS' i.atcorr module.

An implementation of the 6S algorithm in GRASS GIS

GRASS GIS features a dedicated module for the task in question called i.atcorr (in GRASS-GIS version 7 or in GRASS GIS vesion 6.4.x). The module is an implementation of NASA's 6S algorithm (Second Simulation of Satellite Signal in the Solar Spectrum). As stated in Land Surface Reflectance Science Computing Facility's web interface to the code:

The 6s code predicts the satellite signal from 0.25 to 4.0 microns assuming cloudless atmosphere. The main atmospheric effects (gaseous absorption by water vapor,carbon dioxyde,oxygen and ozone; scattering by molecules and aerosols) are taken into account. Non-uniform surfaces may be considered, as well as bidirectional reflectances as boundary conditions.

Using i.atcorr

In order to use GRASS' module, one needs to prepare a simple text file that contains the required parameters for the algorithm to run for each spectral band separately. Specifically, the text file should be constructed so as to describe in one line (for) each of the following parameters: Geometrical Conditions, Atmospherical Model, Aerosols Model, Visibility, Target & Sensor Altitude, Sensor's Band (spectral conditions). Among the various sensor's pre-described spectral conditions, the module contains the necessary relative spectral response data for ASTER acquisitions.

Such a parameters description file (here the example concerns Landsat data) might look like:

11 22 8.83574 23.83895740 34.59989709

or even with comments such as

8                          # indicates that it is an ETM+ image
05 24 15.67 -78.691 35.749 # image taken on the 24th of May, at 15:42 GMT in decimals; the center of the image lies at 78.691°W and 35.749°N 
2                          # the midlatitude summer atmospheric model
1                          # the continental aerosol model
50                         # the visibility for the aerosol model [km]
-0.110                     # the terrain lies 110 meters above sea level [km] * -1
-1000                      # image taken of a satellite sensor (1000)
61                         # spectral band, here 1: blue

All of the above are explained in the module's manual. Elaborating a bit more on how to increase the module's qualitative performance, a visibility estimation can be defined. In the absence of visibility maps, an estimation of the Aerosol Optical Depth at 550 nm can be derived from MODIS' MOD08 - Gridded Atmospheric Product. This products includes the layer Optical_Depth_Land_And_Ocean_Mean, described as Aerosol Optical Thickness at 0.55 microns for both Ocean (best) and Land (corrected): Mean. Some notes in the relevant GRASS-Wiki subsection Sources for AOD estimations.


Repeating the exercise of setting up a parameters file for each band separately is naturally a tedious task. Scripting is recommended.

in Bash

A custom (and messy, containing hard-coded values) bash-shell script might look like:

# set -u

# Nikos Alexandris, February 2013
# based on a script provided by Yann Chemin!
# i.atcorr scripting

# Warning: still contains hardcoded stuff!

echo "Usage: $0 [Meant Target Elevation] [AOD]"
echo "Note, the script has to be eXecuted from the directory that contains"
echo "the *MTL.txt metadata and within from the GRASS environment!"

# first, remove access to all mapsets but the current
g.mapsets mapset=`g.mapsets -p sep=newline | grep -v `g.mapset -p` | tr "\n" "," | sed 's:^\(.*\)\,$:\1:'` operation=remove
echo "Access to all mapsets removed..."
echo "Please, grant access to mapsets of interest and rerun the script!"

# # add all mapsets containing LT5 imagery?
# g.mapsets addmapset=`g.mapsets -l --v fs=, | tr -d "\n"` # grass64
# g.mapsets removemapset=`g.mapsets -l --v fs=, | tr -d "\n"` # grass70

# access only to specific mapsets! - currently, by hand!
# g.mapsets mapset=`g.mapsets -l --v sep=comma` operator=remove
Scenes_To_Correct=$(g.mapsets -p)
echo "Correcting scenes: $Scenes_To_Correct"
sleep 1 # wait a bit!

echo "-------------------------------------------------"
echo "6S Atmospheric Correction algorithm)"
echo "-------------------------------------------------"
# # echo "It will create for *.surf.* images from i.atcorr" 
# # echo "Atmospherically corrected image=surface reflectance"

echo "The following parameters are required"
echo "---------------------------------------------------"

# Location of parameter file(s)
echo "[The directory containing parameter files: $Parameters_Pool]"
sleep 1 # wait a bit!

# Define fixed parameters

Geometry=7 # Geometrical conditions (L5TM)
echo "Geometrical conditions index for Landsat TM 5: $Geometry"

Sensor_Height=-1000 # for satellite borne sensors set to -1000
echo "The sensor's height:          $Sensor_Height"

Atmospheric_Mode=3 # here: midlatitude winter
echo "Atmospheric mode:             $Atmospheric_Mode"

Aerosol_Model=1 # here: continental
echo "Aerosol model:                $Aerosol_Model"

# Define non-fixed parameters

# get first parameter $1 or read MTE!
        if [ ! -z $1 ]; # if $1 is not null, set MeanTargetElevation
          then MeanTargetElevation=$1
          else echo "Please provide a Mean Target Elevation (negatively signed value in Km, e.g. -0.5)"
          read MeanTargetElevation
          echo "Note, this value will be overwritten if a DEM raster has been defined as an input!"

echo "Mean target elevation parameter:       $MeanTargetElevation Km"

# aerosol depth, summer vs winter
  if [ ! -z $2 ];
    then AOD=$2
    echo "The provided aerosol optical depth (AOD) is: ${AOD} (unitless)"
    else echo "Please provide an AOD estimation"
    read AOD # else, read AOD

# AOD_Winter=0.111
# AOD_Summer=0.222
# AOD="${AOD_Winter}" # set to winter AOD
#   if [ ${Month} -gt 4 ] && [ ${Month} -lt 10 ]; # compare month of acquisition
#       then AOD="${AOD_Summer}"  # set to summer AOD if...
#   fi
echo "The provided aerosol optical depth: ${AOD} (unitless)"

# if AOD provided, set Visibility to Zero
  if [ ! -z ${AOD} ];
    then Visibility=0
    echo "Visibility was set to zero since AOD = ${AOD} is provided!"
    else echo "Please provide a visibility value:"
    read Visibility

        # ToDo: elaborate on Visibillity -- lines below from YannC's code
# # vis_list=(10 10 8 9.7 15 8 7 10 10 9.7 12 9.7 7 12 12 12 3 15 12 9.7 6 15)
# # vis_len=${#vis_list[*]}
# # echo $vis_len
# # i=0

# spectral conditions index for L5TM:
# 25,26,27,28,29,30 respectively for bands 1, 2, 3, 4, 5, 7
Satellite_Band_No=25 # 1st L5TM band to undergo atmospheric correction -- counter!

for SCENE in $Scenes_To_Correct

# Here we suppose you have Visibility (Visibility) maps ready

# #  r.mapcalc expression="visibility=${vis_list[$i]}" --overwrite

# #  # Dummy visibility value for atcorr param file
# #  vis=12 
# #  #Increment i
# #  i=$(echo "$i + 1" | bc)

echo "Acquisition metadata"

# scene's basename as in GRASS' db
Basename=$(g.mapset -p) #LT5_Basename=$(basename $BAND _MTL.txt)
echo "The scene's base name is ${Basename}"

# Date and time of the satellite's overpass (month, day, GMT in decimal hours)

# grep from the filename?
# MonthDay=$(echo $L5_Basename | sed 's/\(.*\)_.......\(.*\)/\2/')

# Safer to use metadata content!

# grep acquisition date from the metadata file
Date_Acquired=$(grep -a DATE_ACQUIRED "${Basename}"_MTL.txt | cut -d" " -f7)

Month=$(echo "${Date_Acquired}" | cut -d"-" -f2) # grep Month
#echo "Month of acquisition $Month"

Day=$(echo "${Date_Acquired}" | cut -d"-" -f3) # grep Day
#echo "Day of acquisition $Day"

# GMT in decimal hours
GMT=$(grep -a -e "TIME" "${Basename}"_MTL.txt | cut -d" " -f7 | sed 's:^\(.*\)Z$:\1:' | awk -F: '{print $1 + ( $2 / 60) + ($3 / 3600) }')
# echo "UTC time of the acquisition: $GMT"

# Merge in one variable
MDH="$Month $Day $GMT"
echo "Month, Day and UTC Decimal Hours of the acquisition: ${MDH}"

#Get the scene's central Lat/Long
Long_NonProj=`g.region -c | grep easting | sed 's/center\ easting:\ \(.*\)/\1/'`
Lat_NonProj=`g.region -c | grep northing | sed 's/center\ northing:\ \(.*\)/\1/'`
Long=`g.region -cgl | grep center_long | sed 's/center_long=\(.*\)/\1/'`
Lat=`g.region -cgl | grep center_lat | sed 's/center_lat=\(.*\)/\1/'`

echo "The scene's center geographic coordinates (in decimal degrees): ${Long_NonProj} $Lat_NonProj ($Long $Lat)"

# loop over Landsat bands in question
for Band_No in 1 2 3 4 5 7
  do # Generate the parameterization file (icnd_landsat5)
  echo "Processing band $Band_No (spectral conditions index: $Satellite_Band_No)"

  #   echo "$Geometry - geometrical conditions=Landsat 5 TM" > $Parameters_Pool/${Basename}_icnd_${Band_No}.txt
  echo $Geometry > $Parameters_Pool/${Basename}_icnd_${Band_No}.txt

  #   echo "$MDH $Long $Lat - month day hh.ddd longitude latitude" >>  $Parameters_Pool/${Basename}_icnd_${Band_No}.txt
  echo $MDH $Long $Lat >>  $Parameters_Pool/${Basename}_icnd_${Band_No}.txt

  #   echo "$Atmospheric_Mode - atmospheric mode=tropical" >> $Parameters_Pool/${Basename}_icnd_${Band_No}.txt
  echo $Atmospheric_Mode >> $Parameters_Pool/${Basename}_icnd_${Band_No}.txt

  #   echo "$Aerosol_Model - aerosols model=continental" >> $Parameters_Pool/${Basename}_icnd_${Band_No}.txt
  echo $Aerosol_Model >> $Parameters_Pool/${Basename}_icnd_${Band_No}.txt

  #   echo "$Visibility - visibility [km] (aerosol model concentration), not used as there is raster input" >> $Parameters_Pool/${Basename}_icnd_${Band_No}.txt
  echo $Visibility >> $Parameters_Pool/${Basename}_icnd_${Band_No}.txt

  #   echo "$AOD - aerosol optical depth at 550nm" >> $Parameters_Pool/${Basename}_icnd_${Band_No}.txt
  echo $AOD >> $Parameters_Pool/${Basename}_icnd_${Band_No}.txt

  # echo "$MeanTargetElevation - mean target elevation above sea level [km] (here 600m asl), not used as there is raster input" >> $Parameters_Pool/${Basename}_icnd_${Band_No}.txt
  echo $MeanTargetElevation >> $Parameters_Pool/${Basename}_icnd_${Band_No}.txt

  #   echo "$Sensor_Height - sensor height (here, sensor on board a satellite)" >> $Parameters_Pool/${Basename}_icnd_${Band_No}.txt
  echo $Sensor_Height >> $Parameters_Pool/${Basename}_icnd_${Band_No}.txt

  #   echo "$Satellite_Band_No - band ${Band_No} of TM Landsat 5" >> $Parameters_Pool/${Basename}_icnd_${Band_No}.txt
  echo $Satellite_Band_No >> $Parameters_Pool/${Basename}_icnd_${Band_No}.txt

  # Process band-wise atmospheric correction with 6s
  echo "Using the following parameters:"
  cat $Parameters_Pool/${Basename}_icnd_${Band_No}.txt | tr "\n" " "

# echo "Executing:"
# echo "i.atcorr -r \ "
# echo "input=Basename.ToAR.$Band_No \ "
# echo "elevation=$MeanTargetElevation visibility=$Visibility \ "
# echo "parameters=$Parameters_Pool/${Basename}_icnd_${Band_No}.txt \ "
# echo "output=B.Trimmed.ToCR.$Band_No \ "
# echo "range=0,1 rescale=0,1 \ "
# echo "--overwrite"

# Attention!
  ## input is radiance or reflectance?
  ## flag "-r" ?
  ## does the elevation cover the area of the images?
  ## input and output is converted in to float values anyway!

# i.atcorr -r --overwrite \
# input=B.Trimmed.ToAR.$Band_No \
# range=0,1 \
# elevation=aster_gdem2_ellas_smooothed_threshold_08_iterations_10 \
# parameters=$Parameters_Pool/${Basename}_icnd_${Band_No}.txt \
# output=Test_$Basename_$Band_No \
# rescale=0,1

# without elevation here!
i.atcorr -r --overwrite --v \
input=B.Trimmed.DOS1.ToCR.$Band_No \
range=0,1 \
parameters=${Parameters_Pool}/${Basename}_icnd_${Band_No}.txt \
output=B.Trimmed.ToCR.${Band_No} \

r.info -r B.Trimmed.ToCR.${Band_No} 
sleep 1

  if [ $Satellite_Band_No -eq 30 ]
   then break
   elif [ $Satellite_Band_No -gt 30 ]
   then Satellite_Band_No=25


in Python

A Python script "work-in-progress" & subject to changes, attempting to automate the use of i.atcorr for Landsat imagery is available here i.landsat.atcorr. It works well so far for Landsat OLI, ETM+ and TM sensors.


the Algorithm


in GRASS-Wiki:

  • 1
    A Python script would be more attractive as it would be (practically) cross-platform. – Nikos Alexandris Oct 12 '13 at 10:26
  • 1
    @Aaron Also, the process to add new sensors is not that hard though it takes some time and requires, of course, to be checked by more than one person so as to ensure quality. On the tracks are the high-resolution sensors QuickBird-2 and WorldView-2 -- hopefully they will be added officially soon (there is a ticket already for those). Hopefully the module will receive further improvements, e.g. in what concerns retrieving various metadata (at least for easily accessible/public domain data such as Landsat and MODIS imagery). – Nikos Alexandris Oct 16 '13 at 23:04
  • 1
    Added a link to a python script looping i.atcorr over Landsat imagery. – Nikos Alexandris Nov 27 '14 at 9:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.