Brick Kiln Detection using SAM3 and Satellite Imagery

SAM3
geospatial
object-detection
satellite-imagery
segmentation
environmental-monitoring
Author

Nipun Batra

Published

December 29, 2025

Introduction

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

  1. Load satellite imagery from Adalaj, Gandhinagar
  2. Use SAM3 with text/point prompts to detect brick kilns
  3. Visualize segmentation masks
  4. 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

# Install samgeo with SAM3 support
!uv pip install -q segment-geospatial[samgeo3]
!uv pip install -q leafmap folium
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

  1. Loaded satellite imagery - Sample image (replace with Gandhinagar/Adalaj imagery)
  2. Automatic segmentation - SAM3 detected all objects
  3. Text-prompted segmentation - Used “brick kiln” prompts
  4. Point-prompted segmentation - Targeted specific locations
  5. 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:

  1. Get high-res imagery - Google Earth Engine, Planet Labs, Sentinel-2
  2. Calibrate thresholds - Size, shape, color filters for kilns
  3. Manual verification - Review detections, build training set
  4. Time series - Monitor seasonal operation patterns
  5. 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

  1. Acquire real imagery - Download Gandhinagar satellite data
  2. Build kiln dataset - Annotate known kilns for validation
  3. Fine-tune detection - Train on kiln-specific features
  4. Temporal analysis - Track kiln activity over time
  5. Deploy at scale - Monitor entire Indo-Gangetic Plain

References

SAM3 and samgeo

Brick Kiln Detection Research

Tools and Datasets