GDALRaster Processing

gdal_calc

What is gdal_calc?

gdal_calc.py is GDAL's command-line raster calculator. It binds input rasters to single-letter variables (A, B, C, …), evaluates a Python/NumPy expression pixel-by-pixel, and writes the result to an output raster. It is how you compute indices like NDVI, apply thresholds, reclassify, or combine rasters arithmetically without writing a Python script.

Shell
gdal_calc.py -A <input1> [-B <input2>...] --outfile=<out.tif> --calc="<expression>"

Commonly used options:

  • -A, -B, … — input rasters (any OGR-opened letter)
  • --A_band <n> — source band for each letter (default 1)
  • --calc="<expression>" — NumPy expression (supports operators, logical_and, where, minimum, etc.)
  • --outfile=<filename> — output raster
  • --NoDataValue=<value> — output NoData
  • --type=<Byte|Int16|Float32...> — output data type
  • --format=<driver> / --co=<NAME>=<VALUE> — output driver and creation options
  • --allBands=A — evaluate the expression per band of A, producing a multi-band output
  • --overwrite — overwrite existing output
  • --quiet / --debug — verbosity

When would you use gdal_calc?

Use gdal_calc.py for any pixel-wise arithmetic that does not need a neighbourhood operation. Typical jobs: computing NDVI (gdal_calc.py -A nir.tif -B red.tif --calc="(A.astype(float)-B)/(A+B)" --outfile=ndvi.tif --type=Float32 --NoDataValue=-9999), thresholding an index to a binary mask (--calc="A>0.3"), reclassifying values (--calc="where(A==1, 10, where(A==2, 20, 0))"), or summing two rasters with matching grids.

For operations that need a moving window (slope, focal statistics), use gdaldem or a dedicated tool. For very large rasters, gdal_calc.py is convenient but not the fastest — it reads bands into memory block-wise. When performance matters, consider writing a short Python script using GDAL's ReadAsArray with explicit block iteration, or use numexpr-backed tools like rio calc.

FAQs

Why is my NDVI output all zeros or all ones?

Integer division. If A and B are UInt16, (A-B)/(A+B) is integer-divided and quantised to 0 or 1. Cast to float inside the expression: (A.astype(float)-B)/(A+B). Also set --type=Float32 on the output; otherwise the result is truncated to the default Float32-or-source type which may also surprise you. Finally, verify NoData handling: Sentinel-2 saturates to 0 outside the swath, which will divide by zero.

How do I reclassify with gdal_calc?

Use numpy.where nested calls: --calc="where(A<100, 1, where(A<200, 2, where(A<500, 3, 4)))". For many classes, this gets clumsy; gdal_translate with a VRT colour map, gdalwarp -r mode, or a dedicated reclass tool like whitebox_tools or rio are more legible. For table-driven reclass, pre-write a Python script.

Can I evaluate expressions across all bands in one input?

Yes — use --allBands=A. The expression is evaluated per band, producing an output with the same band count as A. Useful for applying the same scaling or mask to every band of a multispectral scene in one pass.

How do I handle NoData correctly?

Pass --NoDataValue to set the output NoData, and make sure the input NoData values are also declared on the source rasters (check with gdalinfo). gdal_calc.py masks NoData input pixels so they do not participate in arithmetic, but only if the source advertises a NoData value. If NoData is implied via a sentinel like -9999 but not set, use gdal_edit -a_nodata -9999 to declare it first.