Repository navigation
Data Formats and Export
rAPIdtools keeps everything in WGS84 PhysicalAssetCollection objects in memory and reads and writes them as GeoJSON, ESRI Shapefiles and pandas DataFrames, with image metadata, masks and georeferencing carried along as plain JSON.
- All geometries in a
PhysicalAsset,BoundingBox,PolygonRegionorObservationare WGS84 (EPSG:4326), as(longitude, latitude). - Rasters may be in any CRS.
OrthomosaicReaderreprojects on read and crops carry the raster's native grid in thenative_georefimage property (see below), so masks are vectorized in that grid and then reprojected. - Distances in parameters are explicit:
'10 m','30 ft',buffer_m,min_width_ft.
to_geojson returns a FeatureCollection dictionary and writes it when file is given; from_geojson accepts a path or a dictionary.
from rapidtools import PhysicalAssetCollection
buildings = PhysicalAssetCollection.from_geojson('buildings.geojson', asset_type='building')
buildings.to_geojson('out.geojson', indent=2) # with image assets
buildings.to_geojson('out_light.geojson', ignore_properties=['image_assets']) # attributes only
data = buildings.to_geojson() # dict, no fileEach feature looks like this:
{
"type": "Feature",
"id": "bldg_01",
"geometry": {"type": "Polygon", "coordinates": [[[-118.14, 34.19], ...]]},
"properties": {
"asset_type": "building",
"gemma4_chs_level": "3",
"ai_model_used": "google/gemma-4-E2B-it",
"images_analyzed_count": 2,
"image_assets": [
{"id": "bldg_01_aerial", "path": "/abs/output/crops/....jpg",
"properties": {"wgs84_bounds": [...], "native_georef": {...}, "source_raster": "ortho.tif"},
"semantic_map": null, "instance_map": null, "allow_missing_file": false}
]
}
}- The asset
idis the featureid;attributesbecomeproperties. -
image_assetsis a list ofImageAsset.to_dict()records. On load,from_geojsonrebuilds them withImageAsset.from_dict(..., allow_missing_file=True), so a file whose images live on another machine still opens. -
asset_typeis read fromasset_type,typeorcategory; passasset_type=to fill blanks andoverwrite_asset_type=Trueto replace. - Values that are not JSON (dates, numpy scalars) are written through
str().
buildings.to_shapefile('damaged.shp', ignore_properties=['image_assets'])
again = PhysicalAssetCollection.from_shapefile('damaged.shp')Shapefiles impose a fixed table: column names are cut to 10 characters, strings to 254 characters, and lists or dictionaries are stored as JSON text. to_shapefile unifies the schema over all assets (missing values become null, mixed int/float columns become float, other conflicts become text) and writes .shp, .shx, .dbf and a .prj for EPSG:4326.
Data that does not fit the DBF goes to a sidecar next to the shapefile, <name>.rapidtools.json:
| Key | Content |
|---|---|
version |
1 |
fields |
truncated column name to original attribute key |
images |
asset ID to list of ImageAsset.to_dict() records |
The DBF itself only holds an n_images count per asset. from_shapefile reads the sidecar when present, restoring image_assets and full attribute names, and logs any record it skips for unreadable geometry. Keep the sidecar with the shapefile when you move it.
df = buildings.to_dataframe() # columns: id, asset_type, geometry_wkt, image_count, one per attribute
df.to_csv('buildings.csv', index=False)
images = buildings.collect_all_images() # ImageCollection across the collection
images.to_dataframe()
images.to_json('images.json') # list of {'id', 'path', 'properties'}An ImageAsset points at one file and carries id, properties, optional semantic_map and instance_map, and allow_missing_file. to_dict() writes exactly those fields (path as a string, caches excluded); from_dict() ignores unknown keys and turns mask-map keys that JSON made strings back into integers.
| Written by | Image ID | Properties |
|---|---|---|
AerialImageryExtractor |
<asset_id>_<image_prefix> |
wgs84_bounds, native_georef, source_raster
|
GoogleStreetViewImageExtractor |
<asset_id>_gsv_<n> |
pano_id, camera_latitude, camera_longitude, camera_heading, camera_distance_m, capture_date, view_angle_start, view_angle_end, zoom, optional panorama_path, depth_map_path
|
MapillaryImageExtractor |
<asset_id>_<axis> |
view_angle, original_pano_id
|
MapillaryObjectImageExtractor |
<asset_id>_<prefix>_<image_id> |
image_id, captured_at, label, confidence, range_m, bearing, crop_box, source_image_size, source
|
Use these in an image_filter (for example lambda img: img.properties.get('source') == 'google_streetview') or ImageCollection.filter_by_property(key, value).
-
wgs84_boundsis(min_lon, min_lat, max_lon, max_lat)of the crop: an envelope, exact only for north-up WGS84 rasters. -
native_georefisPatchGeoref.to_dict():transform(six affine coefficients),crs(WKT),width,height. Rebuild it withPatchGeoref.from_dictand vectorize masks withrapidtools.processing.vectorize.mask_to_wgs84_polygons(mask, georef=...).BuildingRegularizeruses it automatically; crops with onlywgs84_boundsstill work through the envelope.
Segmentation results sit either on the image or on the asset:
-
SAM3ImageSegmenterstoresattributes['sam3_masks']({image_id: (N, H, W) array}),attributes['sam3_bounding_boxes']andattributes['ai_model_used']. These arrays are not GeoJSON-friendly; vectorize them or drop them withignore_propertiesbefore export. -
ImageAsset.set_mask(data, mask_type, map_data)attaches a'semantic'or'instance'array in memory;save_mask(mask_type)writes a lossless PNG next to the image (photo_semantic.png,photo_instance.png, seeget_mask_path);load_mask(mask_type, custom_path=...)reads one back;save_interactive_html()writes an SVG overlay with hover labels. The class and instance maps travel into_dict().
Street-level discovery records every sighting of an object as an Observation: image_id, label, polygon in normalized image coordinates, camera_lon, camera_lat, compass_angle, is_pano, sequence_id, captured_at, confidence, source, and after localization bearing, elevation, range_m, lon, lat. They are stored as dictionaries under attributes['observations'] so they survive GeoJSON round trips (Observation.to_dict() / from_dict()), and MapillaryObjectImageExtractor reads them to decide what to crop. They are the heaviest attribute; add 'observations' to ignore_properties for a light file once the crops exist.
from rapidtools import BoundingBox
subset = buildings.filter_by_geometry(BoundingBox(-118.15, 34.18, -118.13, 34.20), predicate='within')
damaged = subset.filter_by_attribute('gemini_chs_level', ['3', '4'], operator='in')
with_images = damaged.filter_empty()
damaged.merge(other_collection, strategy='skip') # 'skip', 'overwrite' or 'raise' on duplicate IDs
damaged.summary()filter_by_attribute supports '==', '!=', '>', '>=', '<', '<=', 'in', 'contains' and 'exists'; filter_by_geometry accepts any Shapely binary predicate ('intersects' by default). set_attribute(key, value, overwrite=False) and set_asset_type stamp a whole collection, for example with a survey date before merging.
-
Huge GeoJSON files. Pass
ignore_properties=['image_assets'](and'observations') unless you need to regenerate crops from the file. -
Image paths break on another machine. Paths are stored absolute. Loading still works (
allow_missing_file=True), butis_downloadedisFalseand analyzers skip those images; copy the image folder alongside or re-run the extractor. -
Column names look mangled in GIS. That is the 10-character DBF limit; the original names are in the
.rapidtools.jsonsidecar and restored byfrom_shapefile. -
Model answers stored as strings. Attribute values from JSON answers keep their JSON type, but
Key: Valueparsing yields strings; compare as strings ('4') or cast. -
Numpy arrays in attributes.
sam3_maskscannot be serialized meaningfully; vectorize withmask_to_wgs84_polygonsor exclude them. -
Empty collection to shapefile.
to_shapefileraisesValueError; filter after checkinglen(). -
Stale sidecar.
to_shapefiledeletes a leftover sidecar when the new export has nothing to put in it, so never hand-edit the DBF without the sidecar.
See also Core Concepts, Aerial Imagery Workflows, Street-Level Imagery Workflows and the API reference.
rAPIdtools is developed by the NHERI RAPID Facility and released under the BSD-3-Clause license. Found a problem on this page? Open an issue or edit the page directly. Missing a capability? Request a feature. No GitHub account? Email us.
Getting started
Workflows
- Aerial Imagery Workflows
- Street-Level Imagery Workflows
- Vision-Language Models
- Data Formats and Export
Using the app
Help
Reference