plMapcalc ships a Python function plmapcalc() that mirrors the full command-line interface. You can call it directly from a Jupyter notebook or any Python script — no subprocess, no shell quoting, just keyword arguments.

Importing

Place plmapcalc.py in the same directory as your notebook (or anywhere on sys.path), then import the function:

notebook cell notebook
from plmapcalc import plmapcalc

Function signature

python python
plmapcalc(
    inputs,                     # list[str]  — required
    *,
    outputs       = None,       # list[str]
    execute       = None,       # str  — cell expression (-e)
    program       = None,       # str  — path to .mc file (-p)
    execute_begin = None,       # str  — code run before scan
    program_begin = None,       # str  — file with begin code
    execute_end   = None,       # str  — code run after scan
    program_end   = None,       # str  — file with end code
    memory        = 0,          # int  — MEM[] cells (-m)
    memory_read   = None,       # str  — load MEM from file (-r)
    memory_store  = None,       # str  — save MEM to file (-s)
    store_zeroes  = False,      # bool — include MEM[i]==0 in save
    threads       = 1,          # int  — compute threads (-t)
    force         = False,      # bool — overwrite outputs (-f)
    quiet         = 0,          # int  — 0=all  1=silent  2=progress only
    use_nan       = False,      # bool — treat NaN as value
    ignore_georef = False,      # bool — skip CRS/extent checks
    gpu           = False,      # bool — run pixel kernel on GPU (--gpu)
    no_jit        = False,      # bool — disable JIT/CUDA, plain Python (--no-jit)
    # Spatial alignment (WarpedVRT — optional)
    crs           = None,       # str  — target CRS, e.g. "EPSG:4326" (--crs)
    resolution    = None,       # str|float — "ref"|"highest"|"lowest"|N (--resolution)
    extent        = None,       # str  — "ref"|"intersection"|"union" (--extent)
    resample      = "nearest",  # str  — resampling method (--resample)
    on_progress   = None,       # callable(int) — progress callback 0-100
)

All arguments after inputs are keyword-only. Layer specifiers use the same colon-separated syntax as the CLI: "file.tif:band:shift_x:shift_y" for inputs and "file.tif:dtype:nodata:compress" for outputs.

no_jit=True disables numba JIT and CUDA. The cell expression runs as plain Python — full numpy support including dynamic slices (IN[i:i+3]), dict, print(), etc. Useful for complex expressions like BioClim or Köppen-Geiger that use variable slices. Slower than JIT but there are no syntax restrictions.

Output verbosity (quiet)

By default (quiet=0) plMapcalc prints layer info, JIT status, progress bar, and timing. In a notebook all output goes to stdout (white cell output area). JIT compiler diagnostics from numba are suppressed automatically so they do not cause the cell to render with red error styling.

quietCLI equivalentOutput
0(none)Layer info, JIT status, progress bar, timing
1-qSilent — no output at all
2-q -qProgress bar only, no messages

Shared global variables — G proxy

Variables set in execute (cell phase) are not accessible in execute_begin or execute_end — each phase runs in its own function scope. Use the built-in G object to share state across all three phases. Works identically with execute= and program=.

notebook cell notebook
plmapcalc(
    inputs         = ["ndvi.tif", "mask.tif"],
    execute_begin  = "G.total = 0.0; G.count = 0",
    execute        = "if IN[1] > 0: G.total += IN[0]; G.count += 1",
    execute_end    = "MEM[0] = G.total / G.count if G.count > 0 else 0.0",
    memory         = 1,
    memory_store   = "stats.txt",
    force          = True,
)

If you omit memory=N but use literal MEM[N] indices, plmapcalc auto-sizes the MEM array to max_index + 1 cells and prints [INFO] MEM[] auto-sized to N cells. For dynamic indices like MEM[COLS*ROW+COL] you must set memory=N explicitly.

Automatic spatial alignment (crs, resolution, extent)

By default all input layers must share identical CRS, pixel size and extent. Passing any of crs, resolution, or extent enables on-the-fly alignment via rasterio.vrt.WarpedVRT — no temp files:

notebook cell notebook
plmapcalc(
    inputs     = ["dem_10m.tif", "landcover_100m.tif"],
    outputs    = ["result.tif:float32:-9999"],
    execute    = "OUT[0] = IN[0] * IN[1]",
    resolution = "highest",   # align to 10m grid
    extent     = "intersection", # only common area
    resample   = "nearest",    # safe for discrete landcover
)

CRS reprojection warning: when crs causes any layer to be reprojected, plmapcalc prints a warning and processing is ~3–4× slower. For large datasets, pre-reproject with gdalwarp or QGIS Warp (Reproject layer), then run without crs=.

Progress callback (on_progress)

Pass a callable that receives an integer 0–100. Called after each tile, it replaces the printed 98% line. Useful for custom progress bars or notebook widgets:

notebook cell notebook
# Simple tqdm progress bar
from tqdm.auto import tqdm

with tqdm(total=100, desc="plMapcalc") as bar:
    plmapcalc(
        inputs      = ["input.tif"],
        outputs     = ["output.tif:float32:-9999"],
        execute     = "OUT[0] = IN[0] * 2",
        quiet       = 1,
        on_progress = lambda p: bar.update(p - bar.n),
    )

When on_progress is provided, the printed percentage line is suppressed. In the QGIS plugin, feedback.setProgress is used automatically — the native QGIS progress bar is updated without any extra code.

Examples

Add two layers

notebook cell notebook
from plmapcalc import plmapcalc

plmapcalc(
    inputs  = ["layer1.tif", "layer2.tif"],
    outputs = ["sum.tif:float32:-9999:DEFLATE"],
    execute = "OUT[0] = IN[0] + IN[1]",
    threads = 4,
    force   = True,
)

NDVI

notebook cell notebook
plmapcalc(
    inputs  = ["band4.tif", "band3.tif"],
    outputs = ["ndvi.tif:float32:-9999:DEFLATE2"],
    execute = "x=IN[0]+IN[1]; OUT[0]=-9999 if x==0 else (IN[0]-IN[1])/x",
    threads = 8,
    force   = True,
)

Statistics accumulated in MEM[], saved to file

notebook cell notebook
plmapcalc(
    inputs       = ["ndvi.tif", "mask.tif"],
    memory       = 3,
    execute      = "if IN[1]>0: MEM[0]+=1; MEM[1]+=IN[0]",
    execute_end  = "MEM[2] = MEM[1]/MEM[0] if MEM[0]>0 else 0",
    memory_store = "stats.txt",
)

Multi-pass patch area calculation

notebook cell notebook
plmapcalc(
    inputs      = ["patches.tif"],
    outputs     = ["areas.tif:float64:-9999:LZW"],
    memory      = 2000,
    execute     = "if ITERATION()==1: MEM[int(IN[0])]+=1\nelse: OUT[0]=MEM[int(IN[0])]",
    execute_end = "if ITERATION()==1: RESTART()",
    force       = True,
)

Silent batch processing

notebook cell notebook
import glob

for path in glob.glob("input/*.tif"):
    out = path.replace("input/", "output/")
    plmapcalc(
        inputs  = [path],
        outputs = [f"{out}:uint8:255"],
        execute = "OUT[0] = 1 if IN[0] > 0 else 0",
        force   = True,
        quiet   = 1,   # silent
    )

Built-in help

Call help(plmapcalc) in any notebook cell to display the full parameter reference in a readable plain-text format:

notebook cell notebook
help(plmapcalc)

Numba JIT compiler messages are automatically suppressed during compilation so they do not taint the cell output with red error styling. The first run will show a brief JIT compilation … done. message, then all subsequent calls reuse the compiled kernel with no overhead.