Skip to content

Quickstart

bacetiner edited this page Oct 3, 2026 · 1 revision

A complete first run: grade every building in a drone orthomosaic of Altadena, CA, captured after the 2025 Eaton Fire, with a local Gemma 4 model, and write the grades to GeoJSON.

The script runs offline once the weights are downloaded. It needs a GPU with a few gigabytes of memory for google/gemma-4-E2B-it; the section at the end swaps in a hosted model for machines without one. Install the package first (Installation).

The whole workflow

from pathlib import Path
import rapidtools as rt

rt.configure_logging()   # opt in to progress output

# 1. Sample data: an orthomosaic, building footprints and a prompt file.
raster, footprints, prompt = rt.download_dataset(
    ['eaton_patch2', 'altadena_sample_buildings', 'aerial_chs_prompts']
)

# 2. Load the footprints into a collection of assets.
buildings = rt.PhysicalAssetCollection.from_geojson(footprints)

# 3. Choose a model. Swap 'gemma4' for 'gemini', 'claude', 'openai', ...
model = rt.models.load('gemma4', model_id='google/gemma-4-E2B-it')

# 4. Build the pipeline: crop each footprint, then ask the model about it.
pipeline = rt.Pipeline([
    rt.AerialImageryExtractor(
        raster,
        save_directory=Path('output/crops'),
        buffer_m=5,
        overlay_asset_outline=True,
    ),
    rt.AssetAnalyzer(model, prompt=prompt, batch_size=8),
])
buildings = pipeline.run(buildings)

# 5. Export. Model answers are attributes prefixed with the provider key.
buildings.filter_empty().to_geojson(
    'output/buildings_graded.geojson', ignore_properties=['image_assets']
)

What happened

  1. AerialImageryExtractor read the raster once, cropped a square patch around each footprint with a 5 m margin, drew the footprint outline on the patch so the model knows which building to look at, and attached the JPEG to the asset as an ImageAsset.
  2. AssetAnalyzer sent each asset's crops and the prompt to the model, eight assets per batch, and stored the answer on the asset. The prompt file asks for a JSON object with a chs_level key, so the grade lands in the attribute gemma4_chs_level.
  3. PhysicalAssetCollection.to_geojson() wrote the footprints with their new attributes. ignore_properties=['image_assets'] keeps the image paths out of the file.

The first run also downloads the Gemma 4 weights from the Hugging Face Hub. Gemma is a gated repository: accept its licence on the model page and call rt.login() if you have not logged in before.

Reading the results

>>> asset = buildings['bldg_042']
>>> asset.attributes['gemma4_chs_level']
'3'
>>> asset.image_assets[0].path
PosixPath('/.../output/crops/aerial_3418715171_-11814335588.jpg')
>>> buildings.to_dataframe().groupby('gemma4_chs_level').size()

Open the GeoJSON in QGIS and colour it by gemma4_chs_level to see the damage map.

Using a hosted model instead

Only step 3 changes. Hosted models run several requests in parallel and back off automatically when the provider rate-limits them:

model = rt.models.load('gemini', api_key='AIza...', model_id='gemini-3.8-flash')
analyzer = rt.AssetAnalyzer(model, prompt=prompt, max_workers=4)

With a hosted model the answers land in gemini_chs_level instead. Credentials can also come from environment variables; see Supported Models for the names.

Asking for structured output

Prompts can request free text or JSON. When the answer parses as a JSON object, each key becomes an attribute. GenerationConfig pins down the generation options per call:

from rapidtools.models import GenerationConfig

analyzer = rt.AssetAnalyzer(
    model,
    prompt='Return JSON with a damage_level key (0-4) and a one-sentence evidence key.',
    generation=GenerationConfig(json_mode=True, temperature=0.0),
)

Next steps

Clone this wiki locally