Skip to content

Data Formats and Export

bacetiner edited this page Oct 3, 2026 · 1 revision

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.

Coordinate systems

  • All geometries in a PhysicalAsset, BoundingBox, PolygonRegion or Observation are WGS84 (EPSG:4326), as (longitude, latitude).
  • Rasters may be in any CRS. OrthomosaicReader reprojects on read and crops carry the raster's native grid in the native_georef image 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.

GeoJSON

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 file

Each 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 id is the feature id; attributes become properties.
  • image_assets is a list of ImageAsset.to_dict() records. On load, from_geojson rebuilds them with ImageAsset.from_dict(..., allow_missing_file=True), so a file whose images live on another machine still opens.
  • asset_type is read from asset_type, type or category; pass asset_type= to fill blanks and overwrite_asset_type=True to replace.
  • Values that are not JSON (dates, numpy scalars) are written through str().

Shapefiles

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.

DataFrames

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'}

Image assets and their 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).

Georeferencing records

  • wgs84_bounds is (min_lon, min_lat, max_lon, max_lat) of the crop: an envelope, exact only for north-up WGS84 rasters.
  • native_georef is PatchGeoref.to_dict(): transform (six affine coefficients), crs (WKT), width, height. Rebuild it with PatchGeoref.from_dict and vectorize masks with rapidtools.processing.vectorize.mask_to_wgs84_polygons(mask, georef=...). BuildingRegularizer uses it automatically; crops with only wgs84_bounds still work through the envelope.

Masks

Segmentation results sit either on the image or on the asset:

  • SAM3ImageSegmenter stores attributes['sam3_masks'] ({image_id: (N, H, W) array}), attributes['sam3_bounding_boxes'] and attributes['ai_model_used']. These arrays are not GeoJSON-friendly; vectorize them or drop them with ignore_properties before 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, see get_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 in to_dict().

Observations

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.

Shaping a collection before export

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.

Common pitfalls

  • 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), but is_downloaded is False and 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.json sidecar and restored by from_shapefile.
  • Model answers stored as strings. Attribute values from JSON answers keep their JSON type, but Key: Value parsing yields strings; compare as strings ('4') or cast.
  • Numpy arrays in attributes. sam3_masks cannot be serialized meaningfully; vectorize with mask_to_wgs84_polygons or exclude them.
  • Empty collection to shapefile. to_shapefile raises ValueError; filter after checking len().
  • Stale sidecar. to_shapefile deletes 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.

Clone this wiki locally