# Install samgeo with SAM3 support
!uv pip install -q segment-geospatial[samgeo3]
!uv pip install -q leafmap foliumIntroduction
Brick kilns are a significant source of air pollution in India, particularly in the Indo-Gangetic Plain. Monitoring these kilns for environmental compliance is challenging due to their large numbers and geographic spread. In this notebook, we’ll use SAM3 (Segment Anything Model 3) with the samgeo library to detect brick kilns from satellite imagery in the Adalaj/Gandhinagar region of Gujarat.
Why Brick Kiln Detection?
- Environmental monitoring - Brick kilns emit significant air pollution
- Regulatory compliance - Track kiln operations and compliance
- Scalable solution - Satellite imagery + AI enables monitoring at scale
- Research impact - Recent studies have detected 30,000+ kilns across India
SAM3 for Geospatial Segmentation
SAM3 (from Meta AI) is the latest version of the Segment Anything Model, designed for zero-shot segmentation. The samgeo library (segment-geospatial) adapts SAM3 for satellite imagery:
- Automatic segmentation - No training required
- Text prompts - Describe what to segment (“brick kiln”, “circular structure”)
- Point/box prompts - Click or draw to segment specific objects
- Multi-scale - Works on high-resolution satellite imagery
What We’ll Build
- Load satellite imagery from Adalaj, Gandhinagar
- Use SAM3 with text/point prompts to detect brick kilns
- Visualize segmentation masks
- Count and analyze detected kilns
Brick Kiln Characteristics
From satellite imagery, brick kilns appear as: - Circular or rectangular structures - Reddish-brown color (from bricks) - Open-air layout - Often clustered in industrial areas - Visible smoke stacks in some cases
Setup
import os
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
import warnings
warnings.filterwarnings('ignore')
# samgeo imports
from samgeo import SamGeo3
from samgeo.common import download_file
# Optional: For interactive maps
try:
import leafmap
import folium
INTERACTIVE_MAPS = True
except ImportError:
INTERACTIVE_MAPS = False
print("Install leafmap for interactive maps: pip install leafmap")
%config InlineBackend.figure_format = 'retina'
print("SAM3 and samgeo loaded successfully!")Part 1: Load Satellite Imagery
We’ll focus on the Adalaj/Gandhinagar area in Gujarat, India. This region is known to have brick kiln clusters.
Location: Adalaj, Gandhinagar, Gujarat, India
Coordinates: Approximately 23.17°N, 72.58°E
We have two options: 1. Download imagery from samgeo examples 2. Use Google Static Maps API (requires API key) 3. Use pre-downloaded GeoTIFF
For this demo, we’ll start with a sample image from the region.
# Option 1: Download sample satellite imagery
# For demo purposes, we'll use a sample image from samgeo
# In practice, you'd use actual satellite imagery from the Adalaj area
# Sample URL - replace with actual Gandhinagar imagery
image_url = "https://github.com/opengeos/datasets/releases/download/places/berkeley.tif"
image_path = "satellite_image.tif"
if not os.path.exists(image_path):
print("Downloading sample satellite image...")
download_file(image_url, image_path)
print(f"Downloaded to {image_path}")
else:
print(f"Using cached image: {image_path}")
# Display the image
img = Image.open(image_path)
plt.figure(figsize=(12, 12))
plt.imshow(img)
plt.title("Satellite Imagery (Sample)", fontsize=14)
plt.axis('off')
plt.show()
print(f"Image size: {img.size}")Part 2: Initialize SAM3
SAM3 can be initialized with different model sizes: - tiny - Fastest, lower accuracy - small - Balanced - base - Good accuracy (default) - large - Best accuracy, slower
We’ll use the base model for a good balance.
# Initialize SAM3 with automatic mask generation
sam = SamGeo3(
model_type="base", # Options: tiny, small, base, large
automatic=True,
device="cuda" if os.path.exists("/dev/nvidia0") else "cpu",
)
print(f"SAM3 initialized with model_type='base'")
print(f"Using device: {sam.device}")Part 3: Automatic Segmentation
First, let’s run SAM3’s automatic segmentation to see all objects it can detect in the image.
# Generate masks automatically
print("Running automatic segmentation...")
print("This may take a few minutes depending on image size and hardware.")
masks = sam.generate(image_path)
print(f"\nGenerated {len(masks)} masks")
print(f"Sample mask keys: {list(masks[0].keys()) if masks else 'No masks'}")# Visualize all detected segments
output_path = "segmentation_all.tif"
sam.save_masks(output_path)
# Display
seg_img = Image.open(output_path)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(20, 10))
ax1.imshow(img)
ax1.set_title("Original Satellite Image", fontsize=14)
ax1.axis('off')
ax2.imshow(seg_img)
ax2.set_title(f"SAM3 Automatic Segmentation ({len(masks)} objects)", fontsize=14)
ax2.axis('off')
plt.tight_layout()
plt.show()Part 4: Text-Prompted Segmentation for Brick Kilns
SAM3 supports text prompts to segment specific objects. We’ll use prompts related to brick kilns: - “brick kiln” - “circular structure” - “industrial area” - “kiln”
# Reinitialize SAM3 for text-prompted segmentation
sam_text = SamGeo3(
model_type="base",
automatic=False, # We'll use prompts
device="cuda" if os.path.exists("/dev/nvidia0") else "cpu",
)
# Text prompts for brick kilns
text_prompts = [
"brick kiln",
"circular structure",
"industrial building",
]
print(f"Using text prompts: {text_prompts}")
# Generate masks with text prompts
kiln_masks = sam_text.generate(
image_path,
text_prompt=" or ".join(text_prompts), # Combine prompts
)
print(f"\nDetected {len(kiln_masks)} potential brick kilns/structures")# Visualize brick kiln detections
if len(kiln_masks) > 0:
kiln_output = "brick_kilns_detected.tif"
sam_text.save_masks(kiln_output)
kiln_img = Image.open(kiln_output)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(20, 10))
ax1.imshow(img)
ax1.set_title("Original Image", fontsize=14)
ax1.axis('off')
ax2.imshow(kiln_img)
ax2.set_title(f"Detected Structures ({len(kiln_masks)} candidates)", fontsize=14)
ax2.axis('off')
plt.tight_layout()
plt.show()
else:
print("No brick kilns detected with text prompts. Try point/box prompts.")Part 5: Point-Prompted Segmentation
If you know the approximate location of brick kilns, you can use point prompts (coordinates) to segment them precisely.
How to get coordinates: 1. Visual inspection of the image 2. Click locations if using interactive tool 3. Prior knowledge of kiln locations
# Example: Segment using point prompts
# Point format: [[x1, y1], [x2, y2], ...] in pixel coordinates
# For demo, let's pick some central points
# In practice, you'd identify actual kiln locations
img_width, img_height = img.size
# Example point prompts (replace with actual kiln locations)
point_prompts = [
[img_width // 3, img_height // 3],
[2 * img_width // 3, img_height // 2],
]
print(f"Using point prompts: {point_prompts}")
# Segment using points
sam_points = SamGeo3(
model_type="base",
automatic=False,
device="cuda" if os.path.exists("/dev/nvidia0") else "cpu",
)
point_masks = sam_points.generate(
image_path,
point_coords=point_prompts,
point_labels=[1] * len(point_prompts), # 1 = foreground
)
print(f"Segmented {len(point_masks)} objects from point prompts")# Visualize point-prompted segmentation
if len(point_masks) > 0:
point_output = "point_segmentation.tif"
sam_points.save_masks(point_output)
point_img = Image.open(point_output)
fig, axes = plt.subplots(1, 3, figsize=(24, 8))
# Original with points marked
axes[0].imshow(img)
for x, y in point_prompts:
axes[0].plot(x, y, 'r*', markersize=20)
axes[0].set_title("Original + Point Prompts", fontsize=14)
axes[0].axis('off')
# Segmentation
axes[1].imshow(point_img)
axes[1].set_title("Point-Prompted Segmentation", fontsize=14)
axes[1].axis('off')
# Overlay
axes[2].imshow(img)
axes[2].imshow(point_img, alpha=0.5)
axes[2].set_title("Overlay", fontsize=14)
axes[2].axis('off')
plt.tight_layout()
plt.show()Part 6: Analysis and Statistics
Let’s analyze the detected objects to estimate brick kiln counts and characteristics.
# Analyze mask properties
if len(masks) > 0:
# Extract mask statistics
areas = [mask['area'] for mask in masks]
# Filter by area (brick kilns typically have certain size range)
# This threshold would need calibration with actual imagery
min_area = np.percentile(areas, 20) # Remove very small objects
max_area = np.percentile(areas, 95) # Remove very large objects
filtered_masks = [
mask for mask in masks
if min_area <= mask['area'] <= max_area
]
print(f"Total detected objects: {len(masks)}")
print(f"After size filtering: {len(filtered_masks)}")
print(f"\nArea statistics (pixels):")
print(f" Min: {min(areas):.0f}")
print(f" Max: {max(areas):.0f}")
print(f" Mean: {np.mean(areas):.0f}")
print(f" Median: {np.median(areas):.0f}")
# Plot area distribution
plt.figure(figsize=(10, 6))
plt.hist(areas, bins=50, edgecolor='black', alpha=0.7)
plt.axvline(min_area, color='r', linestyle='--', label=f'Min threshold: {min_area:.0f}')
plt.axvline(max_area, color='r', linestyle='--', label=f'Max threshold: {max_area:.0f}')
plt.xlabel('Object Area (pixels)', fontsize=12)
plt.ylabel('Frequency', fontsize=12)
plt.title('Distribution of Detected Object Sizes', fontsize=14)
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
else:
print("No masks to analyze")Part 7: Export Results
Export the segmentation results for further analysis in GIS tools.
# Export to GeoJSON (if geospatial metadata available)
# This requires the input image to have georeferencing info
try:
geojson_path = "brick_kilns.geojson"
sam.save_geojson(geojson_path)
print(f"Exported to {geojson_path}")
print("You can open this in QGIS, ArcGIS, or Google Earth Engine")
except Exception as e:
print(f"GeoJSON export failed: {e}")
print("This requires georeferenced imagery (GeoTIFF with CRS)")Summary
We successfully demonstrated SAM3 for brick kiln detection from satellite imagery:
What We Did
- Loaded satellite imagery - Sample image (replace with Gandhinagar/Adalaj imagery)
- Automatic segmentation - SAM3 detected all objects
- Text-prompted segmentation - Used “brick kiln” prompts
- Point-prompted segmentation - Targeted specific locations
- Analysis - Filtered by size, counted candidates
Key Insights
- SAM3 is zero-shot - No training required, works on new regions
- Multiple prompt types - Text, points, boxes for flexibility
- Automatic + manual - Combine automatic detection with manual verification
- Scale matters - Need appropriate image resolution for small objects
Real-World Application
For actual brick kiln monitoring in Adalaj/Gandhinagar:
- Get high-res imagery - Google Earth Engine, Planet Labs, Sentinel-2
- Calibrate thresholds - Size, shape, color filters for kilns
- Manual verification - Review detections, build training set
- Time series - Monitor seasonal operation patterns
- Compliance tracking - Flag new kilns, verify permits
Research Context
Recent studies have: - Detected 30,000+ brick kilns across India using satellite imagery - Used YOLOv8 and deep learning for automated detection - Achieved >90% accuracy with proper training data - Enabled scalable compliance monitoring across states
Limitations
- Demo imagery - Used sample image, not actual Gandhinagar data
- No ground truth - Would need verification with known kiln locations
- Resolution dependent - High-res imagery needed for small kilns
- Seasonal variation - Kiln appearance changes with operation status
Next Steps
- Acquire real imagery - Download Gandhinagar satellite data
- Build kiln dataset - Annotate known kilns for validation
- Fine-tune detection - Train on kiln-specific features
- Temporal analysis - Track kiln activity over time
- Deploy at scale - Monitor entire Indo-Gangetic Plain
References
SAM3 and samgeo
- segment-geospatial (samgeo) - Official documentation
- SAM3 Image Segmentation Examples
- Meta’s SAM 3: A Game-Changer for GIS
Brick Kiln Detection Research
Tools and Datasets
- Google Earth Engine - Free satellite imagery
- Planet Labs - High-resolution imagery
- Sentinel-2 - Free 10m resolution imagery