MARS Map Generation#
This notebook demonstrates automated map generation from satellite imagery using the MARS model to extract infrastructure features at scale.
What This Does:#
MARS automatically analyzes high-resolution aerial imagery to create structured geospatial datasets of buildings, roads, and railways.
Key Capabilities:#
ποΈ Building Extraction: Detects building footprints as polygon geometries
π£οΈ Road Network Mapping: Identifies road networks as line geometries
π Railway Detection: Maps railway infrastructure as line geometries
π Multi-Location Processing: Process multiple areas of interest concurrently
π€ GeoCatalog Publishing: Results published as STAC items with full metadata
Workflow Overview:#
Define your areas of interest (AOI) - neighborhoods, cities, or regions
SDK queries STAC catalogs for available high-resolution imagery
MARS model analyzes imagery to detect and vectorize infrastructure
Results published to your GeoCatalog as searchable, reusable geospatial data
Prerequisites:#
Before running this notebook:
Deploy Model in Azure AI Foundry: Follow the Azure AI Foundry deployment guide to deploy a MARS model and obtain your endpoint URL and API key
Create Azure Storage Account: Set up a storage account with a blob container for storing results (e.g.,
sdk-results)Prepare GeoCatalog: Set up your GeoCatalog instance for publishing results to a collection
Azure RBAC Roles: Ensure you have the required roles assigned:
Storage:
Storage Blob Data Contributor+Storage Blob DelegatorModel endpoint:
AzureML Data ScientistorAzure AI DeveloperGeoCatalog: Write access to target collection
Install SDK:
# From wheel (download from GitHub Release) pip install "geoai_sdk-0.1.0-py3-none-any.whl[examples]" # Or from cloned repo pip install "tools/geoai-sdk[examples]" # Register Jupyter kernel python -m ipykernel install --user --name=geoai-sdk
Azure Authentication: Configure Azure CLI
az loginSelect Kernel: Choose βPython 3.9+ (geoai-sdk)β as the notebook kernel
Configure Settings: Update cells with your model endpoint and storage details
Optional: Copy .env.example to .env (in the same directory as this notebook) to set environment variables
1. Setup and Imports#
The SDK automatically configures clean, production-ready logging (INFO level by default).
Logging Modes:
INFO (default): High-level progress only - clean logs for production
DEBUG: Detailed per-chip processing logs - useful for troubleshooting
To enable debug mode:
os.environ['GEOAI_LOG_LEVEL'] = 'DEBUG' # Before importing geoai
import asyncio
import os
from pathlib import Path
from azure.identity import DefaultAzureCredential
# Load environment variables from .env file if it exists
try:
from dotenv import load_dotenv
env_path = Path(".env")
if env_path.exists():
load_dotenv(env_path)
print("β
Loaded environment variables from .env")
except ImportError:
pass # python-dotenv not installed
import geoai
print("β
SDK loaded successfully!")
print(f" Log level: {os.getenv('GEOAI_LOG_LEVEL', 'INFO')}")
2. Azure Authentication#
When you need Azure authentication:
π€ Publishing results to GeoCatalog (includes blob storage for assets)
π Using private GeoCatalog as input
# Initialize Azure credential
credential = DefaultAzureCredential()
# Optional: Load storage account key from environment
storage_account_key = os.getenv("STORAGE_ACCOUNT_KEY")
if storage_account_key:
print("π Using storage account key for SAS generation")
else:
print("π Using Azure AD authentication (recommended)")
print(" Ensure you have 'Storage Blob Delegator' role")
3. Discover Available Models (Optional)#
Check which models are available and what collections they support.
About Collections:
Planetary Computer: Pre-defined datasets like NAIP, Landsat, Sentinel-2
EOOS/MARS support: NAIP only (high-resolution imagery)
Private GeoCatalog: Your own ingested high-resolution imagery
Use any collection name you want
# List all available models
print("π Available Models:\n")
for model_info in geoai.models.list():
print(f" π€ {model_info['model_name']} ({model_info['name'].upper()})")
print(f" Collections: {', '.join(model_info['supported_collections'])}")
print(f" Bands: {', '.join(model_info['required_bands'])}")
print(f" Resolution: {model_info['preferred_resolution_meters']}m")
print()
# Get detailed information about MARS
print("π MARS Model Details:")
mars_info = geoai.models.get("mars")
print(f" Supported collections: {mars_info['data_requirements']['supported_collections']}")
print(f" Required bands: {mars_info['data_requirements']['required_bands']}")
print(f" Detects: Building, Road, Railway")
4. Define Input Source#
Configure where to get satellite imagery.
Option 1: Public Planetary Computer (default - easiest!)
Just specify collection:
naipNo authentication needed
NAIP: High-resolution aerial imagery (0.6m, USA)
Option 2: Private GeoCatalog (for your own data)
Ingest your high-resolution imagery as STAC items
Specify your GeoCatalog URI and collection name
Requires Azure authentication
# Option 1: Public Planetary Computer (default)
input_source = geoai.Input(collection="naip") # Public collection from Microsoft Planetary Computer
# Option 2: Private GeoCatalog (uncomment and configure)
# input_source = geoai.Input(
# collection="your-high-res-imagery", # Your custom collection name
# geocatalog_uri="https://your-company.geocatalog.com/stac",
# credential=credential # Azure credential required
# )
print(f"π₯ Input configured: {input_source.collection}")
print(f" Source: {input_source.geocatalog_uri}")
5. Define Areas of Interest (Multi-AOI)#
This example processes multiple locations simultaneously to demonstrate multi-AOI capabilities.
Example File: test_miami_mars.geoparquet
Contains 2 AOIs in Miami Beach area for demonstration:
miami_beach_1 - Miami Beach Coastal area
miami_beach_2 - Miami Beach Inland area
Each AOI covers urban areas with buildings, roads, and infrastructure
Shows programmatic GeoDataFrame creation (alternative to loading from file)
Multi-AOI Benefits:
π Process many locations in one run
π Efficient STAC search (SDK handles optimization)
π€ All results published to single collection
β‘ Concurrent chip processing across all AOIs
NAIP Update Cycle:
NAIP imagery is collected approximately every 3 years
For best data availability, use datetime ranges 3+ years apart (e.g., β2017-01-01/2020-12-31β or β2020-01-01/2023-12-31β)
Narrow date ranges may result in no imagery coverage for some AOIs
Tips:
Use https://geojson.io to draw custom AOIs
Can use GeoDataFrame, GeoParquet file, or GeoJSON
Each AOI processed independently (failures donβt affect others)
Best for: Multiple buildings, neighborhoods, or districts
# Create multi-AOI constraint using GeoDataFrame
# This demonstrates direct GeoDataFrame usage (alternative: load test_miami_mars.geoparquet)
import geopandas as gpd
from shapely.geometry import box
# Define multiple AOIs in Miami Beach area (matches test_miami_mars.geoparquet)
aois_data = {
'id': ['miami_beach_1', 'miami_beach_2'],
'name': ['Miami Beach - Coastal', 'Miami Beach - Inland'],
'geometry': [
box(-80.13773083243422, 25.78740094399644, -80.1352643098228, 25.790009223060604),
box(-80.13187961358523, 25.807251402127395, -80.12776159322529, 25.811404605351793)
]
}
# Create GeoDataFrame with WGS84 CRS
gdf = gpd.GeoDataFrame(aois_data, crs="EPSG:4326")
# Create constraint with GeoDataFrame
constraint = geoai.Constraint(
aois=gdf, # Pass GeoDataFrame directly
datetime="2020-01-01/2024-12-31" # NAIP updates ~every 3 years - use 3+ year range for best coverage
)
print(f"βοΈ Constraint configured:")
print(f" Mode: {constraint.mode}") # Should show "multi"
print(f" AOIs: {len(list(constraint.iter_aois()))}")
print(f" Datetime: {constraint.datetime}")
# Preview AOIs
print(f"\nπ Processing {len(gdf)} AOIs:")
for idx, row in gdf.iterrows():
area_km2 = row.geometry.area * 111 * 111 # Rough conversion
print(f" {row['id'].upper()}: {row['name']} (~{area_km2:.3f} kmΒ²)")
# Alternative: Load from GeoParquet file (same AOIs as above)
# constraint = geoai.Constraint(
# aois="test_miami_mars.geoparquet",
# datetime="2020-01-01/2024-12-31" # NAIP: Use 3+ year range
# )
# Alternative: Single AOI from bbox (uncomment to use instead)
# constraint = geoai.Constraint(
# bbox=[-80.1353305066925, 25.782802504890395, -80.13279975500333, 25.785420545651412],
# datetime="2020-01-01/2024-12-31" # NAIP: Use 3+ year range
# )
# Optional: Filter by imagery resolution (GSD - Ground Sample Distance)
# NAIP example - find high-resolution imagery only:
# constraint = geoai.Constraint(
# bbox=[-80.1353305066925, 25.782802504890395, -80.13279975500333, 25.785420545651412],
# datetime="2020-01-01/2023-12-31",
# filter={"gsd": {"lte": 0.5}} # Only imagery 0.5m/pixel or better
# )
# Note: Cloud cover filters only work with satellite imagery (Sentinel, Landsat)
# NAIP is aerial imagery without cloud metadata, so cloud filters are not applicable
# For satellite imagery, combine GSD and cloud cover:
# filter={"gsd": {"lte": 10}, "eo:cloud_cover": {"lt": 5}} # Sentinel/Landsat only
5a. Advanced: Custom STAC Search (Optional)#
Skip this section if youβre using the simple constraint above.
For power users who need full control over imagery selection (sorting, limiting, complex filters), you can build a custom STAC search and pass it directly to the SDK.
When to use:
π― Sort by date (latest first) or cloud cover (lowest first)
π Limit to N best items (testing, cost control)
π Multi-AOI efficiency (one search for many locations)
βοΈ Complex CQL2 filter expressions
How it works:
You build the search with
pystac_clientSDK uses your search results for all AOIs
AOIs without imagery in search results are skipped with warning
# OPTIONAL: Build custom STAC search for advanced control
# Uncomment to use this instead of the simple constraint above
# import pystac_client
#
# # Build custom search with advanced options
# client = pystac_client.Client.open("https://planetarycomputer.microsoft.com/api/stac/v1")
#
# custom_search = client.search(
# collections=["naip"],
# bbox=[-80.1353305066925, 25.782802504890395, -80.13279975500333, 25.785420545651412],
# datetime="2020-01-01/2024-12-31",
#
# # Advanced options:
# query={"eo:cloud_cover": {"lt": 5}}, # Very low cloud cover (satellite only)
# sortby=[{"field": "properties.datetime", "direction": "desc"}], # Latest first
# limit=10 # Only top 10 items
# )
#
# # Replace simple constraint with advanced version
# constraint = geoai.Constraint(
# bbox=[-80.1353305066925, 25.782802504890395, -80.13279975500333, 25.785420545651412],
# stac_search=custom_search # SDK will use your custom search
# )
#
# print(f"π Advanced constraint configured")
# print(f" Using custom STAC search with sorting/limiting")
# For multi-AOI: Search large area once, process many AOIs efficiently!
# search = client.search(bbox=[-80.35, 25.75, -80.10, 25.85], limit=50) # All of Miami Beach
# constraint = geoai.Constraint(aois=gdf, stac_search=search)
6. Configure Output Destination#
All results are published to GeoCatalog with assets stored in Azure Blob Storage.
Required Setup:
GeoCatalog: Your GeoCatalog endpoint URL and collection name
Azure Blob Storage: Storage account and container for chip imagery and STAC item assets
Create a storage account
Create a container (e.g., βsdk-resultsβ)
Authentication (choose one):
Recommended: Azure RBAC roles (no key needed)
Storage Blob Data Contributor
Storage Blob Delegator
Alternative: Set
STORAGE_ACCOUNT_KEYenvironment variableexport STORAGE_ACCOUNT_KEY="your_key_here"
Optional: Enable save_local=True to save GeoJSON files locally for debugging.
Update the values below with your settings:
# Configure output destination (GeoCatalog + Blob Storage)
output = geoai.Output(
# GeoCatalog endpoint (required)
geocatalog_uri="https://your-geocatalog.example.com/",
collection_name="my-mars-detections",
credential=credential,
# Blob storage for chip imagery and STAC assets (required)
storage_url="https://yourstorageaccount.blob.core.windows.net",
blob_container="sdk-results",
storage_account_key=os.getenv("STORAGE_ACCOUNT_KEY"), # Optional: use Azure AD if not provided
# Run identifier (optional - auto-generates UUID if not provided)
run_id=None, # Auto-generate unique ID
# Local file saving (optional - for debugging only)
save_local=True, # Set to True to save GeoJSON files locally
output_dir="./output" # Only used if save_local=True
)
print(f"π€ Output configured:")
print(f" GeoCatalog: {output.geocatalog_uri}")
print(f" Collection: {output.collection_name}")
print(f" Blob container: {output.blob_container}")
print(f" Run ID: {output.run_id or 'auto-generate'}")
if output.save_local:
print(f" Local output: {output.output_dir}/")
7. Initialize MARS Model#
Configure the model endpoint. Supports both Azure ML and Azure AI Foundry deployments.
Authentication Options:
Method |
Use Case |
Setup |
|---|---|---|
API Key/Token |
Simple, works everywhere |
Get from Azure ML Studio or AI Foundry portal |
Azure AD β |
Production, no secrets in code |
Requires role assignment (see below) |
Required Azure Roles for Azure AD:
Azure ML:
AzureML Data Scientistrole on workspaceAI Foundry:
Azure AI DeveloperorCognitive Services Userrole on project
Assign roles:
# Azure ML
az role assignment create --assignee user@contoso.com --role "AzureML Data Scientist" \
--scope /subscriptions/{sub-id}/resourceGroups/{rg}/providers/Microsoft.MachineLearningServices/workspaces/{workspace}
# AI Foundry
az role assignment create --assignee user@contoso.com --role "Azure AI Developer" \
--scope /subscriptions/{sub-id}/resourceGroups/{rg}/providers/Microsoft.CognitiveServices/accounts/{ai-service}
Note: SDK automatically detects endpoint type and uses the correct OAuth scope.
# Initialize MARS model with your endpoint
# Authentication: Uses MARS_API_KEY from .env if set, otherwise uses Azure AD (credential)
model = geoai.models.MARS(
endpoint="https://your-mars-endpoint.eastus.inference.ml.azure.com/score",
credential=os.getenv("MARS_API_KEY") or credential, # API key from .env or Azure AD
# num_instances=1, # Configure based on your endpoint capacity
# concurrent_per_instance=1 # Configure based on your endpoint capacity
)
print("π€ MARS model initialized")
print(f" Endpoint: {model.endpoint}")
if isinstance(model.credential, str):
print(f" Auth: API Key (from MARS_API_KEY env var)")
else:
print(f" Auth: Azure AD (DefaultAzureCredential)")
8. Configure Detection Parameters#
Set model-specific parameters for MARS processing.
Parameters:
chip_size: Size of image chips in pixels (default: 1024, max: 2048) - SDK-internal parameterstride: Overlap between chips in pixels (default: 800) - SDK-internal parametercategories: Optional filter for specific features (default: all 3 categories) - sent to model endpoint
Note: MARS endpoint doesnβt support threshold parameter. Detection confidence is managed by the model internally.
π‘ Recommended: Default parameters are pre-optimized for accuracy and speed.
# Detection parameters for MARS
params = {
"chip_size": 1024, # pixels (common: 2048, 1024)
"stride": 800, # pixels (no overlap when stride = chip_size)
# "categories": ["Building"] # Optional: filter specific categories
}
print(f"βοΈ Detection parameters:")
print(f" Chip size: {params['chip_size']}px")
print(f" Stride: {params['stride']}px")
categories_str = 'All (Building, Road, Railway)' if 'categories' not in params else ', '.join(params['categories'])
print(f" Categories: {categories_str}")
8a. Validate Input (Optional but Recommended)#
Comprehensive Pre-flight Check
Before running estimation or the full workflow, you can explicitly validate your configuration to catch issues early:
Input Validation:
β Imagery availability - Queries STAC to verify data exists for AOI
β Required bands available - Checks collection has RGB bands
β Resolution compatibility - Verifies resolution matches model requirements
β Collection compatibility with model
β Filter compatibility with collection
β Parameter validity (ranges, types)
β AOI format is readable
Model Endpoint Validation:
β Endpoint reachable - Tests model endpoint connectivity
β Authentication works - Validates your API key/credential (prevents 401 errors!)
Output Validation (if output provided):
β GeoCatalog accessible - Tests write permissions
β Blob storage accessible - Verifies storage account access
Note: Validation happens automatically in both estimate() and run(), so this step is optional.
What gets checked:
Queries actual STAC catalog for your AOI
Tests model endpoint authentication
Verifies output destination access
Reports number of imagery items found
Returns detected resolution and available bands
# Optional: Validate configuration before estimation
print("π Validating input configuration...")
print(" This queries STAC to verify imagery availability")
print(" and tests model endpoint + output destination access\n")
validation = await model.validate_input(
input=input_source,
constraint=constraint,
params=params,
output=output # Also validates model endpoint and output destination!
)
# Check validation result
if validation.is_valid:
print("β
Validation passed!")
print(f" π Found {validation.stac_items_count} imagery items")
print(f" π Detected resolution: {validation.detected_resolution}m/pixel")
print(f" π¨ Bands available: {validation.bands_found}")
if validation.warnings:
print("\nβ οΈ Warnings:")
for warning in validation.warnings:
print(f" β’ {warning}")
else:
print(" β No issues found")
else:
print("β Validation failed!")
print("\nErrors:")
for error in validation.errors:
print(f" β’ {error}")
print("\nβ οΈ Fix these errors before proceeding") # raise Exception("Validation failed")
# Uncomment to stop execution on validation failure
9. Estimate Job Scope (Optional but Recommended)#
Before running the full workflow, estimate the job scope to:
β Verify imagery is available for your AOI
β Preview chip count and estimated duration
β Check for potential issues (missing data, oversized jobs)
What estimation provides:
Queries STAC to verify data exists
Calculates chip count based on AOI geometry
Estimates processing time (includes inference + GeoCatalog publishing)
Per-AOI breakdown for multi-AOI workflows
Note: MARS processing is slower than EOOS (~30s per chip vs ~3s), so duration estimates help plan accordingly.
Includes automatic validation - no need to run validate_input() separately if you run estimate().- Want to know how long processing will take
Unfamiliar geographic areas
When to use estimation:- Large AOIs or many chips
# Run estimation before full processing
print("π Running estimation...")
estimate = await model.estimate(
input=input_source,
constraint=constraint,
params=params
)
# Print summary
print(f"\nβ
Estimation Complete:")
print(f" Total chips: {estimate.estimated_chips}")
print(f" STAC items: {estimate.stac_items_found}")
print(f" Inference requests: {estimate.total_requests}")
# Display estimated duration
if hasattr(estimate, 'estimated_duration_minutes') and estimate.estimated_duration_minutes:
print(f" β±οΈ Estimated duration: ~{estimate.estimated_duration_minutes} minutes")
# Per-AOI breakdown (if multi-AOI)
if hasattr(estimate, 'aoi_estimates') and estimate.aoi_estimates:
print(f"\nπ Per-AOI Breakdown:")
for aoi_data in estimate.aoi_estimates:
aoi_id = aoi_data.get('aoi_id', 'unknown')
chips = aoi_data.get('estimated_chips', 0)
# Try different possible field names for STAC items
items = (aoi_data.get('stac_items_found', 0) or
aoi_data.get('stac_items', 0) or
aoi_data.get('items_found', 0))
print(f" {aoi_id}: {chips} chips, {items} items")
# Simple validation
if estimate.stac_items_found == 0:
print("\nβ No imagery found - adjust datetime/AOI and re-run")
elif estimate.estimated_chips > 1000:
print(f"\nβ οΈ Large job ({estimate.estimated_chips} chips) - consider reducing AOIs or increasing stride")
else:
print("\nβ
Ready to proceed!")
10. Run MARS Map Generation#
Execute the detection workflow. This may take several minutes depending on AOI size.
Workflow Steps:
π Validation
π² Chip creation
π STAC search
π₯ Image fetching
π€ Model inference (Building, Road, Railway detection)
π Result merging
π€ GeoCatalog publishing
# Run MARS map generation with publishing
# SDK handles all logging internally
result = await model.run(
input=input_source,
constraint=constraint,
params=params,
output=output
)
11. View Results#
Review detection statistics, category breakdown, and output locations.
Results include:
Category breakdown: Buildings, Roads, Railways
Chip processing statistics
Local GeoJSON file with all detections
GeoCatalog publication link (if enabled)
Blob storage location for chip imagery
# Print results summary
print("\n" + "="*70)
print("π MARS Detection Results")
print("="*70)
# GeoCatalog Published section first
if result.published:
print(f"\nβοΈ GeoCatalog Published:")
if result.total_aois:
# Multi-AOI: show collection-level info
collection_url = f"{output.geocatalog_uri.rstrip('/')}/collections/{output.collection_name}"
print(f" Collection: {collection_url}")
print(f" Items published: {result.successful_aois} AOIs")
# Extract run_id from first AOI result (all AOIs share same run_id)
workflow_run_id = output.run_id # User-provided run_id (if any)
if not workflow_run_id and hasattr(result, '_raw_result') and 'results' in result._raw_result:
# SDK stores run_id in each AOI result
for aoi_result in result._raw_result['results']:
if 'result' in aoi_result:
workflow_run_id = aoi_result['result'].get('run_id')
if workflow_run_id:
break
if workflow_run_id:
print(f" Run ID: {workflow_run_id}")
print(f" Blob storage: {output.storage_url}/{output.blob_container}/mars/{workflow_run_id}/")
print(f" βββ Results in: mars/{workflow_run_id}/results/<aoi_id>/")
else:
print(f" Run ID: (not available)")
print(f" Blob storage: {output.storage_url}/{output.blob_container}/")
print(f"\n π‘ View individual AOI results in the GeoCatalog collection")
else:
# Single AOI results
collection_url = f"{output.geocatalog_uri.rstrip('/')}/collections/{output.collection_name}"
print(f" Collection: {collection_url}")
# Get run_id from output (user-provided) or from result
workflow_run_id = output.run_id
if not workflow_run_id and hasattr(result, '_raw_result'):
workflow_run_id = result._raw_result.get('run_id')
if workflow_run_id:
print(f" Run ID: {workflow_run_id}")
print(f" Blob storage: {output.storage_url}/{output.blob_container}/mars/{workflow_run_id}/")
else:
print(f" Run ID: (not available)")
print(f" Blob storage: {output.storage_url}/{output.blob_container}/")
else:
print(f"\nβ οΈ GeoCatalog publishing: Not enabled or failed")
# Local output section (only shown if save_local=True)
if output.save_local:
print(f"\nπΎ Local Output:")
if result.total_aois:
# Multi-AOI: each AOI has its own subdirectory
print(f" Directory: {output.output_dir}/")
print(f" Structure:")
print(f" {output.output_dir}/")
print(f" βββ <aoi_id>/")
print(f" β βββ detections.geojson (filtered to AOI bounds)")
print(f" β βββ final_overlay.jpg (visual overlay)")
else:
# Single AOI: files in output_dir
print(f" Directory: {output.output_dir}/")
print(f" Files: detections.geojson, final_overlay.jpg")
# Check if multi-AOI or single AOI
if result.total_aois:
# Multi-AOI results
print(f"\nπ Multi-AOI Processing:")
print(f" Total AOIs: {result.total_aois}")
print(f" Successful: {result.successful_aois}")
print(f" Failed: {result.total_aois - result.successful_aois}")
print(f"\nπ Aggregate Statistics:")
print(f" Total chips: {result.total_chips}")
print(f" Successful: {result.successful_chips}")
print(f" Total detections: {result.detection_count}")
# Show detection categories if available (for multi-class models)
print(f"\nπ Detections by Category:")
aoi_results = result._raw_result.get('results', [])
if aoi_results:
category_totals = {}
for aoi_result in aoi_results:
aoi_data = aoi_result.get('result', {})
if 'detection_counts' in aoi_data:
for category, count in aoi_data['detection_counts'].items():
category_totals[category] = category_totals.get(category, 0) + count
if category_totals:
for category, count in category_totals.items():
print(f" β’ {category}: {count}")
else:
print(f" Total: {result.detection_count}")
else:
print(f" Total: {result.detection_count}")
print(f"\nπ Per-AOI Breakdown:")
aoi_results = result._raw_result.get('results', [])
if aoi_results:
for aoi_result in aoi_results:
aoi_id = aoi_result.get('aoi_id', 'unknown')
aoi_data = aoi_result.get('result', {})
print(f" {aoi_id.upper()}:")
print(f" Chips: {aoi_data.get('total_chips', 0)}")
print(f" Detections: {aoi_data.get('detection_count', 0)}")
else:
# Single AOI results
print(f"\nπ Processing Summary:")
print(f" Total chips: {result.total_chips}")
print(f" Successful: {result.successful_chips}")
print(f" Failed: {result.total_chips - result.successful_chips}")
print(f"\nπ― Detections:")
print(f" Objects detected: {result.detection_count}")
print(f" Avg per chip: {result.detection_count / result.successful_chips:.1f}" if result.successful_chips > 0 else " Avg per chip: 0")
# Show detection categories if available (for multi-class models)
aoi_data = result._raw_result.get('results', [{}])[0].get('result', {}) if hasattr(result, '_raw_result') else {}
if 'detection_counts' in aoi_data and len(aoi_data['detection_counts']) > 1:
print(f"\n By Category:")
for category, count in aoi_data['detection_counts'].items():
print(f" β’ {category}: {count}")
print("\n" + "="*70)
12. Next Steps#
View Results:
Open GeoCatalog URL above to visualize detections
Buildings: Red polygons
Roads: Cyan lines
Railways: Yellow lines
Check
./output/directory for local files and visualizationsLoad GeoJSON in QGIS, ArcGIS, or other GIS tools
Process More Locations:
Add more AOIs to the GeoDataFrame in section 5
Use GeoParquet file like EOOS example:
constraint = geoai.Constraint(aois="areas.geoparquet", ...)Create custom AOIs with different Miami neighborhoods
Adjust Parameters:
Threshold (section 8): Increase to reduce false positives (try 0.7-0.8)
Categories (section 8): Filter specific features
["Building"]or["Road", "Railway"]Chip size (section 8): Larger chips (1024px) for coverage, smaller (512px) for detail
AOIs (section 5): Add/remove locations, change datetime range
Before Large Jobs:
Always run estimation (section 9) to verify data availability
Check chip count and adjust stride if needed
Test on small AOI subset first to validate parameters
Multi-AOI Tips:
SDK processes each AOI independently
Failed AOIs donβt stop the entire job
All results published to single GeoCatalog collection
Check per-AOI breakdown in results
Troubleshooting:
No detections: Lower threshold, verify AOI has visible structures
Too many false positives: Increase threshold, filter by category
Missing buildings: Check AOI has clear building footprints
Authentication errors: Run
az loginand verify RBAC rolesGeoCatalog errors: Check collection exists and credentials have write access
Some AOIs skipped: Check STAC item coverage, expand datetime range
π Additional Resources#
Documentation:
SDK Documentation - Complete SDK reference
Examples README - All example notebooks
EOOS Notebook - Object detection workflow
External Resources:
MARS Model Paper - Research paper
Planetary Computer - Public STAC catalog
NAIP Imagery - Dataset documentation
STAC Specification - STAC API reference
SDK Features:
π Discovery:
models.list(),models.get(),Model.get_supported_collections()π Validation: Automatic input/collection validation before processing
π Estimation:
model.estimate()- Preview chip count and verify dataπ Execution:
model.run()- Process with automatic validation
Model Details:
MARS detects: Building (Polygon), Road (LineString), Railway (LineString)
Optimized for high-resolution aerial imagery (< 1m/pixel)
Returns native GeoJSON geometries (not just bounding boxes)