Sensor Types and Imagery
Choosing the right sensor type is the first analytical decision. Optical gives you color and detail but fails in clouds and darkness. SAR penetrates weather but produces alien-looking imagery with unfamiliar artifacts. Thermal reveals heat signatures invisible to the eye. LiDAR gives 3D structure. Each answers different questions.
Optical (Passive) Sensors
Optical sensors measure reflected sunlight. They require daylight and clear sky. This is the most intuitive imagery type — it looks like a photograph from above.
Panchromatic
Single wide band (typically 450-900nm) capturing all visible light plus near-infrared in one channel. The advantage: maximum spatial resolution, because all photons go into one measurement.
- Highest GSD available on a given satellite (e.g., MAXAR panchromatic = 30cm vs 1.2m multispectral)
- Grayscale only — no color information
- Used as the “sharpening” input for pan-sharpening
Multispectral
4-13 discrete bands at specific wavelengths. The workhorse of satellite imagery analysis.
Each band reveals different properties of the surface:
import numpy as np
import matplotlib.pyplot as plt
# Typical spectral signatures of common surfaces
wavelengths = [443, 490, 560, 665, 705, 740, 783, 842, 865, 1610, 2190]
band_names = ["B01", "B02", "B03", "B04", "B05", "B06", "B07", "B08", "B8A", "B11", "B12"]
# Approximate reflectance values (0-1)
vegetation = [0.03, 0.05, 0.09, 0.04, 0.15, 0.35, 0.45, 0.50, 0.48, 0.20, 0.10]
water = [0.08, 0.07, 0.05, 0.03, 0.02, 0.01, 0.01, 0.01, 0.01, 0.00, 0.00]
bare_soil = [0.08, 0.10, 0.14, 0.18, 0.20, 0.22, 0.23, 0.24, 0.25, 0.30, 0.28]
urban = [0.10, 0.12, 0.15, 0.18, 0.20, 0.21, 0.22, 0.23, 0.24, 0.25, 0.22]
snow = [0.95, 0.95, 0.90, 0.85, 0.70, 0.60, 0.50, 0.40, 0.35, 0.05, 0.02]
fig, ax = plt.subplots(figsize=(12, 6))
for name, spec, color in [
("Vegetation", vegetation, "green"),
("Water", water, "blue"),
("Bare soil", bare_soil, "brown"),
("Urban", urban, "gray"),
("Snow/Ice", snow, "cyan"),
]:
ax.plot(wavelengths, spec, "o-", label=name, color=color, linewidth=2)
# Mark key wavelength regions
ax.axvspan(380, 700, alpha=0.05, color="yellow", label="Visible")
ax.axvspan(700, 1000, alpha=0.05, color="red", label="NIR")
ax.axvspan(1000, 2500, alpha=0.05, color="purple", label="SWIR")
ax.set_xlabel("Wavelength (nm)")
ax.set_ylabel("Reflectance")
ax.set_title("Spectral Signatures — Sentinel-2 Bands")
ax.legend()
ax.grid(True, alpha=0.3)
ax.set_xlim(400, 2300)
plt.tight_layout()
plt.savefig("spectral_signatures.png", dpi=150)
plt.show()Key spectral features for intelligence:
- Red edge (700-780nm): vegetation health. Stressed vegetation shifts the red edge — this can detect camouflage (real vs fake vegetation) and crop damage
- SWIR (1600-2200nm): moisture content. Wet soil vs dry, active vs abandoned irrigation, recent vehicle tracks on dirt roads
- NIR (842nm): water boundary detection. Water absorbs NIR completely, making water/land boundary razor-sharp
Hyperspectral
100-200+ narrow bands (~10nm width each), covering the spectrum continuously. This enables material identification — distinguishing not just “green vegetation” but species of trees, or not just “metal roof” but type of metal.
Intelligence applications:
- Detect camouflage (artificial materials have different spectral curves than real vegetation)
- Identify chemical contamination (specific absorption features)
- Mineral mapping (geological intelligence)
- Limited availability: PRISMA (ASI), EnMAP (DLR), EMIT (NASA/ISS)
Pan-Sharpening
Combine the spatial resolution of panchromatic with the color information of multispectral. This is standard practice for high-res imagery.
import numpy as np
def brovey_pansharpen(pan, ms_bands, weights=None):
"""
Brovey pan-sharpening. Simple and fast.
pan: 2D array (high-res panchromatic)
ms_bands: list of 2D arrays (lower-res multispectral, upsampled to pan size)
weights: optional band weights
"""
if weights is None:
weights = np.ones(len(ms_bands))
# Weighted sum of MS bands
ms_sum = sum(w * b for w, b in zip(weights, ms_bands))
ms_sum[ms_sum == 0] = 1 # avoid division by zero
# Pan-sharpen each band
sharpened = []
for band in ms_bands:
sharp = (band / ms_sum) * pan
sharpened.append(sharp)
return sharpened
# Usage with rasterio:
# 1. Load pan band at full resolution
# 2. Load MS bands, resample to pan resolution
# 3. Apply Brovey transform
# 4. Result: MS color at pan spatial resolutionSAR (Synthetic Aperture Radar) — Active Sensor
SAR is the most important complementary sensor to optical. It works through clouds, at night, and reveals different physical properties. If you do GEOINT in Northern Europe, SAR is not optional — it’s essential.
Connection to EW-Recon: SAR uses the same radar principles you studied in your EW course. Transmit a pulse, measure the return. The “synthetic aperture” is the key difference — the satellite uses its motion to synthesize a much larger antenna, achieving spatial resolution that would require a physically impossible antenna size.
How SAR Works
- Satellite transmits a microwave pulse sideways (side-looking geometry)
- Pulse reflects off the ground and returns to the satellite
- Return signal encodes: range (time delay = distance), amplitude (how much energy returned), and phase (precise distance measurement)
- Synthetic aperture: combine returns from many positions along the orbit to achieve fine azimuth resolution
Connection to DSP: SAR image formation is a 2D matched filter problem. Range compression is pulse compression (your DSP course), azimuth compression is focusing the synthetic aperture. The raw SAR data is NOT an image — it must be “focused” through signal processing.
SAR Operating Modes
| Mode | Resolution | Swath | Use Case |
|---|---|---|---|
| Stripmap | 5m | 80km | Standard high-res |
| ScanSAR | 40m | 400km | Wide-area monitoring |
| IW (TOPS) | 5x20m | 250km | Sentinel-1 default |
| Spotlight | <1m | 10km | Detailed target imaging |
Polarization
SAR transmits and receives in different polarizations. The combination reveals surface properties.
| Polarization | Abbreviation | Best For |
|---|---|---|
| Vertical-Vertical | VV | Rough surfaces, ships (vertical structures) |
| Vertical-Horizontal | VH | Volume scattering (forests), vegetation biomass |
| Horizontal-Horizontal | HH | Water/ice discrimination |
| Horizontal-Vertical | HV | Vegetation structure |
| Full (quad-pol) | HH+HV+VH+VV | Maximum information, rare |
Sentinel-1 operates in dual-pol mode: VV + VH. This gives you:
- VV: dominated by surface/double-bounce scattering. Ships appear bright (metal corner reflectors). Water is dark (smooth surface, specular reflection away from sensor).
- VH: dominated by volume scattering. Forests appear brighter in VH. Open water is very dark.
SAR Geometric Artifacts
SAR’s side-looking geometry creates artifacts that don’t exist in optical:
Layover — Tall objects (mountains, buildings) lean TOWARD the sensor. The top of a mountain is closer to the sensor than the base, so it appears before the base in the range direction. Buildings in urban SAR imagery look like they’re falling toward you.
Foreshortening — Slopes facing the sensor are compressed. A mountain slope appears shorter than it is. The extreme case is layover.
Shadow — Areas behind tall objects that the radar pulse cannot reach. No return = black. Unlike optical shadows, SAR shadows contain NO information (not just reduced illumination).
import numpy as np
import matplotlib.pyplot as plt
# Illustrate SAR geometric effects
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
# Layover
ax = axes[0]
# Mountain profile
ground_x = np.array([0, 2, 3, 4, 6])
ground_z = np.array([0, 0, 3, 0, 0])
ax.fill_between(ground_x, 0, ground_z, alpha=0.3, color="brown", label="Terrain")
ax.annotate("SAR sensor\n(this side)", xy=(0, 4), fontsize=10,
ha="center", color="red")
ax.arrow(0.5, 3.5, 1.5, -1.5, head_width=0.15, color="red", alpha=0.5)
# In SAR image, top of mountain appears before base
ax.plot([2, 3], [0, 0], "b-", linewidth=8, alpha=0.5, label="SAR image (compressed)")
ax.set_title("Layover / Foreshortening")
ax.legend(fontsize=8)
ax.set_ylim(-0.5, 5)
# Shadow
ax = axes[1]
ground_x = np.array([0, 3, 4, 4.5, 7])
ground_z = np.array([0, 0, 3, 0, 0])
ax.fill_between(ground_x, 0, ground_z, alpha=0.3, color="brown")
ax.annotate("SAR sensor\n(this side)", xy=(0, 4), fontsize=10, ha="center", color="red")
ax.fill_between([4.5, 6], 0, 0.01, alpha=0.5, color="black", label="Shadow (no data)")
ax.arrow(0.5, 3.5, 2.5, -1.5, head_width=0.15, color="red", alpha=0.5)
ax.set_title("Radar Shadow")
ax.legend(fontsize=8)
ax.set_ylim(-0.5, 5)
# Corner reflector (double bounce)
ax = axes[2]
ax.fill_between([2, 4], 0, 0.2, alpha=0.3, color="gray") # ground
ax.fill_between([3.8, 4], 0, 2, alpha=0.5, color="gray", label="Building wall")
ax.annotate("SAR sensor", xy=(0, 3), fontsize=10, ha="center", color="red")
# Double bounce path
ax.annotate("", xy=(3.9, 0.1), xytext=(1, 2.5),
arrowprops=dict(arrowstyle="->", color="red"))
ax.annotate("", xy=(3.9, 1.5), xytext=(3.9, 0.1),
arrowprops=dict(arrowstyle="->", color="red"))
ax.annotate("", xy=(1, 2.5), xytext=(3.9, 1.5),
arrowprops=dict(arrowstyle="->", color="orange"))
ax.text(2, 1.3, "Double\nbounce", fontsize=9, color="red")
ax.set_title("Corner Reflector (very bright)")
ax.legend(fontsize=8)
ax.set_ylim(-0.5, 4)
for ax in axes:
ax.set_aspect("equal")
ax.grid(True, alpha=0.2)
plt.tight_layout()
plt.savefig("sar_geometry.png", dpi=150)
plt.show()Speckle Noise
SAR images are inherently noisy with a salt-and-pepper texture called speckle. This is NOT sensor noise — it’s a physical property of coherent imaging. Multiple scatterers within a pixel interfere constructively and destructively, creating random brightness variations.
Speckle must be reduced before analysis:
import numpy as np
from scipy.ndimage import uniform_filter
def lee_filter(img, size=7):
"""
Lee speckle filter. Adaptive filter that preserves edges.
Works by estimating local statistics and adjusting filtering strength.
"""
img = img.astype(np.float64)
mean = uniform_filter(img, size=size)
sqr_mean = uniform_filter(img**2, size=size)
variance = sqr_mean - mean**2
variance = np.maximum(variance, 0) # numerical safety
# Estimate noise variance (assuming multiplicative noise model)
overall_var = np.var(img)
noise_var = overall_var / (size * size)
# Weight: 0 = fully filtered, 1 = original pixel
weight = variance / (variance + noise_var + 1e-10)
filtered = mean + weight * (img - mean)
return filtered
# Usage:
# sar_image = rasterio.open("sentinel1_vv.tif").read(1)
# filtered = lee_filter(sar_image, size=7)SAR Data Products
| Product | Contains | Use For |
|---|---|---|
| SLC (Single Look Complex) | Amplitude + Phase | InSAR, coherence, polarimetry |
| GRD (Ground Range Detected) | Amplitude only (multi-looked) | Ship detection, backscatter analysis, change detection |
| OCN (Ocean) | Derived ocean products | Wind, waves |
For most GEOINT work, GRD is sufficient. SLC is needed for InSAR (interferometry).
Thermal Infrared
Thermal sensors detect emitted radiation (heat), not reflected sunlight. Everything above absolute zero emits thermal radiation. Objects at 20-40C emit peak radiation at 8-14 micrometers.
Intelligence applications:
- Industrial activity: factories, power plants, refineries emit heat — visible at night
- Vehicle detection: recently operated vehicles have warm engines
- Underground facility detection: ventilation and cooling systems create thermal signatures
- Military activity: field camps, generator operations, vehicle staging
- Fire detection: MODIS and VIIRS detect active fires globally in near real-time
Limitations:
- Poor spatial resolution (Landsat TIRS: 100m, MODIS: 1km thermal)
- Affected by emissivity (different materials emit differently at same temperature)
- Atmospheric absorption windows limit wavelength ranges
Key satellite systems with thermal:
- Landsat-9 TIRS: 100m thermal at 10.9 and 12.0 micrometers
- ECOSTRESS (ISS): 70m thermal, targets water stress and urban heat
- MODIS: 1km thermal, 4x daily, fire detection
- Sentinel-3 SLSTR: 1km thermal, daily
import numpy as np
import matplotlib.pyplot as plt
# Planck's law: spectral radiance as function of wavelength and temperature
def planck(wavelength_um, temp_K):
"""Spectral radiance in W/m^2/sr/um."""
h = 6.626e-34 # Planck constant
c = 2.998e8 # speed of light
k = 1.381e-23 # Boltzmann constant
wav = wavelength_um * 1e-6 # convert to meters
return (2 * h * c**2 / wav**5) / (np.exp(h * c / (wav * k * temp_K)) - 1) * 1e-6
wavelengths = np.linspace(3, 20, 500)
fig, ax = plt.subplots(figsize=(10, 6))
for temp, label in [(250, "Snow (-23C)"), (290, "Ground (17C)"),
(310, "Warm roof (37C)"), (400, "Industrial (127C)")]:
radiance = planck(wavelengths, temp)
ax.plot(wavelengths, radiance, label=label, linewidth=2)
# Mark atmospheric windows
ax.axvspan(3, 5, alpha=0.1, color="green", label="MWIR window")
ax.axvspan(8, 14, alpha=0.1, color="blue", label="LWIR window")
ax.set_xlabel("Wavelength (um)")
ax.set_ylabel("Spectral Radiance (W/m^2/sr/um)")
ax.set_title("Thermal Emission — Planck Curves at Different Temperatures")
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig("thermal_emission.png", dpi=150)
plt.show()LiDAR (Light Detection and Ranging)
Active sensor using laser pulses to measure distance. Returns a 3D point cloud. Primarily airborne, but some spaceborne systems exist.
Spaceborne LiDAR:
- ICESat-2 (NASA): photon-counting LiDAR, 17m footprint, measures ice sheet elevation, canopy height, water depth
- GEDI (ISS): full-waveform LiDAR, 25m footprint, forest biomass estimation
- Both are profiling (along-track lines), not imaging (wall-to-wall coverage)
Airborne LiDAR:
- High-density point clouds (10-100+ points/m^2)
- Penetrates vegetation canopy — sees ground under trees
- Produces DSM (Digital Surface Model — top of canopy/buildings) and DTM (Digital Terrain Model — bare earth)
- Available in many countries as open data (Estonia: Maa-amet LiDAR)
Intelligence applications:
- Tree canopy removal detection (even when optically the canopy looks intact)
- Building height measurement (3D city models)
- Concealed structure detection under forest canopy
- Terrain modeling for line-of-sight analysis
Sensor Comparison Table
| Property | Optical | SAR | Thermal IR | LiDAR |
|---|---|---|---|---|
| Energy source | Passive (sunlight) | Active (microwave) | Passive (heat) | Active (laser) |
| Weather dependence | Needs clear sky | All weather | Partially affected | Needs clear sky |
| Day/night | Day only | Day and night | Day and night | Day and night |
| Best spatial res. | 30cm (commercial) | 25cm (commercial) | 70m (ECOSTRESS) | ~25m (spaceborne) |
| What it measures | Reflected light (color) | Surface roughness, moisture, structure | Temperature | Distance (3D shape) |
| Best for | Visual interpretation, vegetation, water | Ship detection, change through clouds, subsidence | Heat sources, fire, industrial activity | 3D structure, under canopy |
| Key limitation | Clouds, darkness | Speckle, geometric artifacts | Poor resolution | Profiling only (spaceborne) |
Python: Display Different Sensor Types
import numpy as np
import matplotlib.pyplot as plt
# Simulate different sensor outputs for the same scene
np.random.seed(42)
size = 256
# --- Scene: port with water, buildings, vegetation, ships ---
# Optical RGB (simulated)
water_mask = np.zeros((size, size), dtype=bool)
water_mask[140:, :] = True # lower half is water
# Ships (bright pixels on water)
ship_positions = [(180, 50), (200, 120), (160, 200)]
# Buildings (urban area)
building_mask = np.zeros((size, size), dtype=bool)
building_mask[40:90, 30:100] = True
building_mask[60:100, 150:220] = True
# Vegetation
veg_mask = np.zeros((size, size), dtype=bool)
veg_mask[0:40, :] = True
veg_mask[100:140, 100:180] = True
# Optical: RGB composite
rgb = np.zeros((size, size, 3))
rgb[veg_mask] = [0.1, 0.4, 0.1] # vegetation = green
rgb[building_mask] = [0.5, 0.45, 0.4] # buildings = gray-brown
rgb[water_mask] = [0.05, 0.1, 0.3] # water = dark blue
rgb[~(water_mask | building_mask | veg_mask)] = [0.4, 0.35, 0.3] # bare ground
for sy, sx in ship_positions:
rgb[sy:sy+4, sx:sx+10] = [0.7, 0.7, 0.7] # ships
rgb += np.random.normal(0, 0.02, rgb.shape)
rgb = np.clip(rgb, 0, 1)
# SAR: amplitude (VV)
sar = np.random.exponential(0.3, (size, size)) # speckle
sar[water_mask] = np.random.exponential(0.05, np.sum(water_mask)) # water = dark
sar[building_mask] = np.random.exponential(1.5, np.sum(building_mask)) # buildings = bright (corner reflectors)
sar[veg_mask] = np.random.exponential(0.5, np.sum(veg_mask)) # vegetation = medium
for sy, sx in ship_positions:
sar[sy:sy+4, sx:sx+10] = np.random.exponential(3.0, (4, 10)) # ships VERY bright
# Thermal: temperature
thermal = np.full((size, size), 15.0) # ambient 15C
thermal[water_mask] = 12.0 # water cooler
thermal[building_mask] = 22.0 # buildings warm
thermal[veg_mask] = 14.0 # vegetation cool (evapotranspiration)
for sy, sx in ship_positions:
thermal[sy:sy+4, sx:sx+10] = 30.0 # ship engines warm
thermal += np.random.normal(0, 1.5, thermal.shape)
# NDVI
nir = np.zeros((size, size))
red = np.zeros((size, size))
nir[veg_mask] = 0.5
red[veg_mask] = 0.05
nir[building_mask] = 0.2
red[building_mask] = 0.2
nir[water_mask] = 0.01
red[water_mask] = 0.03
ndvi = (nir - red) / (nir + red + 1e-10)
# Plot all four
fig, axes = plt.subplots(2, 2, figsize=(12, 12))
axes[0, 0].imshow(rgb)
axes[0, 0].set_title("Optical (RGB)")
axes[0, 1].imshow(10 * np.log10(sar + 0.001), cmap="gray", vmin=-20, vmax=10)
axes[0, 1].set_title("SAR (VV, dB scale)")
axes[1, 0].imshow(thermal, cmap="inferno")
axes[1, 0].set_title("Thermal IR (Temperature, C)")
axes[1, 1].imshow(ndvi, cmap="RdYlGn", vmin=-0.2, vmax=0.8)
axes[1, 1].set_title("NDVI (NIR-Red / NIR+Red)")
for ax in axes.flat:
ax.axis("off")
plt.suptitle("Same Scene — Four Sensor Types", fontsize=14)
plt.tight_layout()
plt.savefig("sensor_comparison.png", dpi=150)
plt.show()Exercises
Exercise 1: SAR vs Optical Through Clouds
- Explain physically why microwave (SAR wavelength ~5cm) penetrates clouds but visible light (~0.5 micrometers) does not. Hint: relate wavelength to cloud droplet size (~10 micrometers).
- At what rain rate does C-band SAR (Sentinel-1) start to attenuate? Research and state the approximate dB/km attenuation for moderate rain at 5.4 GHz.
- Does SAR penetrate vegetation canopy? Under what conditions?
Exercise 2: Choose the Right Sensor
For each intelligence question, select the optimal sensor type and explain why:
- “Has a new building been constructed at this facility?” (cloudy region, winter)
- “What type of aircraft are on this airfield?”
- “Is this factory operating at night?”
- “Has the ground subsided over this mining area?”
- “Is this vegetation real or camouflage netting?”
Exercise 3: SAR Artifact Identification
You receive a SAR image of a coastal city. Describe what you would expect to see:
- At the waterfront (water-building boundary)
- On a bridge over water
- On a hillside facing toward the sensor
- On a hillside facing away from the sensor
- At a large metal ship in port
Self-Test Questions
- What is the fundamental difference between active and passive sensors?
- Why does panchromatic imagery have better spatial resolution than multispectral on the same satellite?
- What is speckle and why does it occur only in SAR (not optical)?
- Name three intelligence applications of thermal infrared imagery.
- A SAR image shows a bright line parallel to the coastline where buildings meet the water. What geometric effect causes this?
See also: Satellite Fundamentals | Multispectral Analysis | SAR Fundamentals and Analysis Next: Tutorial - Acquiring Free Satellite Imagery