Run GRASS and SAGA Algorithms from PyQGIS
QGIS's native algorithms cover a lot, but some of the most powerful spatial tools live in GRASS GIS and SAGA: hydrological modelling, viewsheds with curvature, cost-distance surfaces, terrain indices, image segmentation, network cleaning. Processing wraps both as providers, so their tools can be called from Python with processing.run like any native algorithm — with a few differences in naming, parameters and behaviour that trip up first-time users.
This recipe belongs to Chaining Processing Algorithms. It checks that the providers are available, finds algorithm ids robustly, passes GRASS parameters and flags, controls the computational region and resolution, handles outputs and errors, and compares GRASS, SAGA and native tools.
Prerequisites
- QGIS 3.34 LTR or newer, or the QGIS 4 series. GRASS is bundled with the Windows (OSGeo4W) and macOS installers; on Linux it is a separate package. SAGA support comes from the separate "Processing Saga NextGen Provider" plugin on current releases.
- Inputs in a projected CRS for anything involving distances or areas.
Check the providers are available
Scripts that depend on GRASS or SAGA should verify the providers before doing any work, because a missing provider fails with an unhelpful "algorithm not found".
from qgis.core import QgsApplication
registry = QgsApplication.processingRegistry()
for p in registry.providers():
if p.id().startswith(("grass", "saga", "sagang")):
print(f"{p.id():<8} {p.name():<28} active={p.isActive()} algorithms={len(p.algorithms())}")
def require_provider(prefix):
for p in registry.providers():
if p.id().startswith(prefix) and p.isActive() and p.algorithms():
return p
raise RuntimeError(f"Processing provider '{prefix}*' is not installed or not active")
grass = require_provider("grass")
Breakdown: Listing providers shows their ids, whether they are active and how many algorithms they expose — a provider that is active but has zero algorithms usually means the external program was not found. The GRASS provider's id has been grass7 on older releases and grass on newer ones; SAGA appears as sagang from the NextGen plugin. Failing early with a clear message is much friendlier than a missing-algorithm error halfway through a batch.
Find algorithm ids robustly
Because provider ids differ between versions, hard-coding grass7:r.slope.aspect breaks on a newer QGIS. Looking algorithms up by the tool name keeps scripts portable.
def find_alg(tool, prefixes=("grass", "sagang", "saga")):
for alg in registry.algorithms():
prov, _, name = alg.id().partition(":")
if name == tool and prov.startswith(prefixes):
return alg.id()
raise RuntimeError(f"{tool} not found in providers {prefixes}")
R_SLOPE = find_alg("r.slope.aspect")
print(R_SLOPE)
import processing
print(processing.algorithmHelp(R_SLOPE)[:800])
Breakdown: Matching the part after the colon finds grass:r.slope.aspect or grass7:r.slope.aspect alike; the same works for SAGA tool names. algorithmHelp prints every parameter with its name, type and default — essential, because GRASS and SAGA parameter names come from the tools themselves and differ from native conventions. Keep the lookup function in a small shared module so every script uses it.
Pass GRASS parameters and flags
GRASS parameters use the tool's own option names in lower case, and flags are booleans with a leading dash. Output parameters take file paths.
dem = "/data/terrain/dem_10m.tif"
res = processing.run(R_SLOPE, {
"elevation": dem,
"format": 0, # 0 = degrees, 1 = percent
"precision": 0, # FCELL
"-a": True, # do not align region to input
"-e": True, # compute values at edges
"zscale": 1.0,
"min_slope": 0.0,
"slope": "/data/terrain/slope_grass.tif",
"aspect": "/data/terrain/aspect_grass.tif",
"GRASS_REGION_PARAMETER": None,
"GRASS_REGION_CELLSIZE_PARAMETER": 0,
"GRASS_RASTER_FORMAT_OPT": "COMPRESS=DEFLATE",
})
print(res["slope"], res["aspect"])
Breakdown: Option names (elevation, format, zscale) are exactly those in the GRASS manual for r.slope.aspect. Flags are keys with a dash; omitting a flag means false. Each output is a separate parameter with a file path, and unrequested outputs can be left out. GRASS_REGION_PARAMETER set to None uses the input's extent; GRASS_REGION_CELLSIZE_PARAMETER 0 uses the input's resolution. GRASS_RASTER_FORMAT_OPT passes creation options to the exported GeoTIFF. The same output from QGIS's native algorithms is covered in generating slope, aspect and hillshade, useful for comparison.
Control region and resolution
GRASS computes on a region: an extent and a cell size. When several inputs have different extents or resolutions, setting the region explicitly decides the output grid and avoids surprises.
from qgis.core import QgsRasterLayer
ref = QgsRasterLayer(dem, "dem")
e = ref.extent()
region = f"{e.xMinimum()},{e.xMaximum()},{e.yMinimum()},{e.yMaximum()} [{ref.crs().authid()}]"
R_COST = find_alg("r.cost")
cost = processing.run(R_COST, {
"input": "/data/model/friction_10m.tif",
"start_points": "/data/model/origins.gpkg",
"max_cost": 0, "null_cost": None, "memory": 1024,
"-k": True, # knight's move for smoother surfaces
"output": "/data/model/cumulative_cost.tif",
"GRASS_REGION_PARAMETER": region,
"GRASS_REGION_CELLSIZE_PARAMETER": ref.rasterUnitsPerPixelX(),
})["output"]
Breakdown: Passing the reference raster's extent and pixel size as region and cell size makes the cost surface align with the DEM exactly, so later raster arithmetic needs no resampling — the alignment discipline from resampling and aligning rasters. Vector inputs such as start points are converted to GRASS vectors automatically. The memory option, in megabytes, lets GRASS use more RAM for large rasters, which speeds up many modules considerably.
Run SAGA tools
SAGA tools follow the same pattern with their own parameter names, which are upper-case in Processing. The NextGen provider exposes them under sagang:.
TWI = find_alg("topographicwetnessindex", prefixes=("sagang", "saga"))
twi = processing.run(TWI, {
"SLOPE": "/data/terrain/slope_rad.tif",
"AREA": "/data/hydro/catchment_area.tif",
"TRANS": None,
"CONV": 0,
"METHOD": 0,
"TWI": "/data/terrain/twi.sdat",
})["TWI"]
print(twi)
Breakdown: SAGA tool names in Processing are lower-case versions of the SAGA tool titles without spaces; find them with the lookup helper and confirm parameters with algorithmHelp. SAGA writes its native .sdat format by default; QGIS reads it directly, and gdal:translate converts it to GeoTIFF when needed. The topographic wetness index here combines slope and upslope area, typically computed with SAGA's own catchment area tool. Exact parameter names vary between SAGA versions, which is another reason to read the help on the target machine.
Handle outputs and errors
External tools fail differently from native algorithms: missing dependencies, unsupported data types, a region that excludes all inputs. Catching exceptions and reading the log gives the real reason.
from qgis.core import QgsProcessingFeedback
class CollectingFeedback(QgsProcessingFeedback):
def __init__(self):
super().__init__()
self.lines = []
def pushInfo(self, info):
self.lines.append(info)
def reportError(self, error, fatalError=False):
self.lines.append("ERROR: " + error)
def pushConsoleInfo(self, info):
self.lines.append(info)
fb = CollectingFeedback()
try:
processing.run(R_SLOPE, {"elevation": "/data/terrain/missing.tif",
"slope": "/tmp/s.tif"}, feedback=fb)
except Exception as err:
print("failed:", err)
print("\n".join(fb.lines[-15:]))
Breakdown: A feedback subclass that records pushInfo, pushConsoleInfo and errors captures the external tool's console output, which is where GRASS and SAGA explain what went wrong. Printing the last lines after a failure turns "algorithm failed" into "Raster map not found" or "region contains no data". In unattended scripts, log these lines rather than printing them, following handling errors and logging in unattended scripts.
Native, GRASS or SAGA?
Prefer native algorithms when one exists: they start instantly, accept memory layers, need no external installation and behave identically on every machine. Use GRASS for its depth in hydrology, cost surfaces, viewsheds and topology cleaning, and when its algorithms are the established reference in a field. Use SAGA for terrain analysis indices, some classification tools and its large set of specialised modules. Whichever you choose, record the provider and version with the results — the same tool name can produce slightly different numbers across versions — and keep a fallback in mind for machines where the provider is missing.
QGIS version compatibility
GRASS algorithms are available on QGIS 3.34 LTR, 3.40 LTR and QGIS 4 when GRASS 8 is installed; the provider id changed from grass7 to grass during the 3.x series, hence the lookup. SAGA support comes from the Saga NextGen provider plugin on current releases, which supports SAGA 7.3 and newer. Parameter names follow the external tools and can change with their versions.
Troubleshooting
- "Algorithm not found". The provider is missing or the id prefix differs; use the lookup helper.
- The provider shows zero algorithms. The external program is not installed or not on the path.
- Outputs are offset or resampled. The region was not set; pass region and cell size explicitly.
- Very slow on large rasters. Increase GRASS's
memoryoption and avoid unnecessary format conversions.
Conclusion
Check providers before running, find algorithm ids by tool name rather than hard-coded prefixes, pass GRASS options by their own names with dashed boolean flags and file outputs, set region and cell size to control the output grid, use upper-case parameters for SAGA tools, capture feedback to see the real error, and prefer native algorithms where they exist.
Frequently Asked Questions
Can GRASS algorithms use memory layers as input? Yes — the provider exports them to temporary files first. Outputs are files.
Do I need a GRASS location or mapset? No. The provider creates a temporary one for each run.
Can I chain GRASS steps without re-exporting data? Not through Processing; each call exports and imports. For long GRASS-only chains, a GRASS session script is faster.
Are WhiteboxTools available the same way? Yes, through the WhiteboxTools for QGIS plugin, with the same lookup approach.