import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
import matplotlib.patches as patches
from matplotlib.patches import Rectangle
import seaborn as sns
# Set style for better plots
'default')
plt.style.use("husl") sns.set_palette(
Introduction
Effective visualization is crucial for understanding satellite imagery and geospatial data. This cheatsheet covers essential plotting techniques.
Creating Sample Satellite Data
def create_sample_satellite_scene(size=256):
"""Create a realistic-looking satellite scene"""
# Create coordinate grids
= np.linspace(0, 10, size)
x = np.linspace(0, 10, size)
y = np.meshgrid(x, y)
X, Y
# Simulate different land cover types
# Water bodies (low values)
= np.exp(-((X - 2)**2 + (Y - 7)**2) / 2) * 0.3
water += np.exp(-((X - 8)**2 + (Y - 3)**2) / 4) * 0.25
water
# Forest/vegetation (medium-high values)
= np.exp(-((X - 6)**2 + (Y - 8)**2) / 8) * 0.7
forest += np.exp(-((X - 3)**2 + (Y - 2)**2) / 6) * 0.6
forest
# Urban areas (varied values)
= np.exp(-((X - 7)**2 + (Y - 5)**2) / 3) * 0.8
urban
# Combine and add noise
= water + forest + urban
scene = np.random.normal(0, 0.05, scene.shape)
noise = np.clip(scene + noise, 0, 1)
scene
# Scale to typical satellite data range
return (scene * 255).astype(np.uint8)
# Create sample scenes for different bands
= create_sample_satellite_scene()
red_band = create_sample_satellite_scene() * 1.1
green_band = create_sample_satellite_scene() * 0.8
blue_band = create_sample_satellite_scene() * 1.3
nir_band
print(f"Band shapes: {red_band.shape}")
print(f"Data ranges - Red: {red_band.min()}-{red_band.max()}")
Band shapes: (256, 256)
Data ranges - Red: 0-255
Single Band Visualization
= plt.subplots(2, 2, figsize=(12, 10))
fig, axes
# Different colormaps for single bands
= ['gray', 'viridis', 'plasma', 'RdYlBu_r']
cmaps = ['Grayscale', 'Viridis', 'Plasma', 'Red-Yellow-Blue']
band_names
for i, (cmap, name) in enumerate(zip(cmaps, band_names)):
= axes[i//2, i%2]
ax = ax.imshow(red_band, cmap=cmap, interpolation='bilinear')
im f'{name} Colormap')
ax.set_title('off')
ax.axis(=ax, shrink=0.8)
plt.colorbar(im, ax
'Single Band Visualization with Different Colormaps', fontsize=14)
plt.suptitle(
plt.tight_layout() plt.show()
RGB Composite Images
def create_rgb_composite(r, g, b, enhance=True):
"""Create RGB composite with optional enhancement"""
# Stack bands
= np.stack([r, g, b], axis=-1)
rgb
if enhance:
# Simple linear stretch enhancement
for i in range(3):
= rgb[:,:,i].astype(float)
band # Stretch to 2-98 percentile
= np.percentile(band, (2, 98))
p2, p98 = np.clip((band - p2) / (p98 - p2), 0, 1)
band = (band * 255).astype(np.uint8)
rgb[:,:,i]
return rgb
# Create different composites
= create_rgb_composite(red_band, green_band, blue_band)
true_color = create_rgb_composite(nir_band, red_band, green_band)
false_color
= plt.subplots(1, 2, figsize=(15, 6))
fig, axes
0].imshow(true_color)
axes[0].set_title('True Color Composite (RGB)')
axes[0].axis('off')
axes[
1].imshow(false_color)
axes[1].set_title('False Color Composite (NIR-Red-Green)')
axes[1].axis('off')
axes[
plt.tight_layout() plt.show()
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers). Got range [0.0..255.0].
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers). Got range [0.0..255.0].
Spectral Indices Visualization
# Calculate NDVI
def calculate_ndvi(nir, red):
"""Calculate Normalized Difference Vegetation Index"""
= nir.astype(float)
nir_f = red.astype(float)
red_f return (nir_f - red_f) / (nir_f + red_f + 1e-8)
# Calculate other indices
= calculate_ndvi(nir_band, red_band)
ndvi
# Water index (using blue and NIR)
= calculate_ndvi(green_band, nir_band)
ndwi
= plt.subplots(1, 3, figsize=(18, 5))
fig, axes
# NDVI
= axes[0].imshow(ndvi, cmap='RdYlGn', vmin=-1, vmax=1, interpolation='bilinear')
im1 0].set_title('NDVI (Vegetation Index)')
axes[0].axis('off')
axes[=axes[0], shrink=0.8)
plt.colorbar(im1, ax
# NDWI
= axes[1].imshow(ndwi, cmap='RdYlBu', vmin=-1, vmax=1, interpolation='bilinear')
im2 1].set_title('NDWI (Water Index)')
axes[1].axis('off')
axes[=axes[1], shrink=0.8)
plt.colorbar(im2, ax
# Histogram of NDVI values
2].hist(ndvi.flatten(), bins=50, alpha=0.7, color='green', edgecolor='black')
axes[2].set_xlabel('NDVI Value')
axes[2].set_ylabel('Frequency')
axes[2].set_title('NDVI Distribution')
axes[2].grid(True, alpha=0.3)
axes[
plt.tight_layout()
plt.show()
print(f"NDVI range: {ndvi.min():.3f} to {ndvi.max():.3f}")
print(f"NDVI mean: {ndvi.mean():.3f}")
NDVI range: -1.000 to 1.000
NDVI mean: 0.121
Custom Colormaps and Classification
# Create land cover classification
def classify_land_cover(ndvi, ndwi):
"""Simple land cover classification"""
= np.zeros_like(ndvi, dtype=int)
classification
# Water (high NDWI, low NDVI)
= (ndwi > 0.3) & (ndvi < 0.1)
water_mask = 1
classification[water_mask]
# Vegetation (high NDVI)
= ndvi > 0.4
veg_mask = 2
classification[veg_mask]
# Bare soil/urban (low NDVI, low NDWI)
= (ndvi < 0.2) & (ndwi < 0.1)
bare_mask = 3
classification[bare_mask]
return classification
# Classify the scene
= classify_land_cover(ndvi, ndwi)
land_cover
# Create custom colormap
= ['black', 'blue', 'green', 'brown', 'gray']
colors = len(np.unique(land_cover))
n_classes = ListedColormap(colors[:n_classes])
custom_cmap
= plt.subplots(1, 2, figsize=(14, 6))
fig, axes
# Show classification
= axes[0].imshow(land_cover, cmap=custom_cmap, interpolation='nearest')
im1 0].set_title('Land Cover Classification')
axes[0].axis('off')
axes[
# Custom legend
from matplotlib.patches import Patch
= [
legend_elements ='black', label='Unclassified'),
Patch(facecolor='blue', label='Water'),
Patch(facecolor='green', label='Vegetation'),
Patch(facecolor='brown', label='Bare Soil/Urban')
Patch(facecolor
]0].legend(handles=legend_elements, loc='upper right')
axes[
# Classification statistics
= np.unique(land_cover, return_counts=True)
unique_classes, counts = counts / counts.sum() * 100
percentages
1].bar(['Unclassified', 'Water', 'Vegetation', 'Bare/Urban'][:len(unique_classes)],
axes[=['black', 'blue', 'green', 'brown'][:len(unique_classes)])
percentages, color1].set_ylabel('Percentage of Pixels')
axes[1].set_title('Land Cover Distribution')
axes[1].grid(True, alpha=0.3)
axes[
for i, (cls, pct) in enumerate(zip(unique_classes, percentages)):
1].text(i, pct + 1, f'{pct:.1f}%', ha='center', va='bottom')
axes[
plt.tight_layout() plt.show()
Advanced Visualization Techniques
# Multi-scale visualization
= plt.subplots(2, 3, figsize=(18, 12))
fig, axes
# Full scene
0, 0].imshow(true_color)
axes[0, 0].set_title('Full Scene')
axes[0, 0].axis('off')
axes[
# Add rectangle showing zoom area
= Rectangle((100, 100), 80, 80, linewidth=2,
zoom_area ='red', facecolor='none')
edgecolor0, 0].add_patch(zoom_area)
axes[
# Zoomed areas with different processing
= (slice(100, 180), slice(100, 180))
zoom_slice
# Zoomed true color
0, 1].imshow(true_color[zoom_slice])
axes[0, 1].set_title('Zoomed True Color')
axes[0, 1].axis('off')
axes[
# Zoomed false color
0, 2].imshow(false_color[zoom_slice])
axes[0, 2].set_title('Zoomed False Color')
axes[0, 2].axis('off')
axes[
# Zoomed NDVI
= axes[1, 0].imshow(ndvi[zoom_slice], cmap='RdYlGn', vmin=-1, vmax=1)
im1 1, 0].set_title('Zoomed NDVI')
axes[1, 0].axis('off')
axes[=axes[1, 0], shrink=0.8)
plt.colorbar(im1, ax
# Zoomed classification
1, 1].imshow(land_cover[zoom_slice], cmap=custom_cmap, interpolation='nearest')
axes[1, 1].set_title('Zoomed Classification')
axes[1, 1].axis('off')
axes[
# Profile plot
= land_cover.shape[0] // 2
center_row = ndvi[center_row, :]
profile_ndvi = np.arange(len(profile_ndvi))
profile_x
1, 2].plot(profile_x, profile_ndvi, 'g-', linewidth=2)
axes[1, 2].axhline(y=0, color='k', linestyle='--', alpha=0.5)
axes[1, 2].axhline(y=0.4, color='r', linestyle='--', alpha=0.5, label='Vegetation threshold')
axes[1, 2].set_xlabel('Pixel Position')
axes[1, 2].set_ylabel('NDVI Value')
axes[1, 2].set_title('NDVI Profile (Center Row)')
axes[1, 2].grid(True, alpha=0.3)
axes[1, 2].legend()
axes[
plt.tight_layout() plt.show()
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers). Got range [0.0..255.0].
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers). Got range [36.0..255.0].
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers). Got range [36.0..255.0].
Time Series Visualization
# Simulate time series of NDVI
def create_ndvi_time_series(n_dates=12):
"""Create sample NDVI time series"""
= []
dates = []
ndvi_values
# Simulate seasonal pattern
= 0.4
base_ndvi = 0.3
seasonal_amplitude
for i in range(n_dates):
# Simulate monthly data
= (i / 12) * 2 * np.pi
month_angle = seasonal_amplitude * np.sin(month_angle + np.pi/2) # Peak in summer
seasonal_component = np.random.normal(0, 0.05)
noise
= base_ndvi + seasonal_component + noise
ndvi_val
ndvi_values.append(ndvi_val)f'Month {i+1}')
dates.append(
return dates, ndvi_values
# Create sample time series for different land cover types
= create_ndvi_time_series()
dates, forest_ndvi = create_ndvi_time_series()
_, cropland_ndvi = create_ndvi_time_series()
_, urban_ndvi
# Adjust for different land cover characteristics
= [v * 0.8 + 0.1 for v in cropland_ndvi] # Lower peak, higher base
cropland_ndvi = [v * 0.3 + 0.15 for v in urban_ndvi] # Much lower overall
urban_ndvi
=(12, 6))
plt.figure(figsize
'g-o', label='Forest', linewidth=2, markersize=6)
plt.plot(dates, forest_ndvi, 'orange', marker='s', label='Cropland', linewidth=2, markersize=6)
plt.plot(dates, cropland_ndvi, 'gray', marker='^', label='Urban', linewidth=2, markersize=6)
plt.plot(dates, urban_ndvi,
'Time Period')
plt.xlabel('NDVI Value')
plt.ylabel('NDVI Time Series by Land Cover Type')
plt.title(
plt.legend()True, alpha=0.3)
plt.grid(=45)
plt.xticks(rotation-0.1, 0.8)
plt.ylim(
# Add shaded regions for seasons
= [4, 5, 6, 7] # Months 5-8
summer_months 0]-0.5, summer_months[-1]+0.5, alpha=0.2, color='yellow', label='Summer')
plt.axvspan(summer_months[
plt.tight_layout()
plt.show()
# Print some statistics
print("NDVI Statistics by Land Cover:")
print(f"Forest - Mean: {np.mean(forest_ndvi):.3f}, Std: {np.std(forest_ndvi):.3f}")
print(f"Cropland - Mean: {np.mean(cropland_ndvi):.3f}, Std: {np.std(cropland_ndvi):.3f}")
print(f"Urban - Mean: {np.mean(urban_ndvi):.3f}, Std: {np.std(urban_ndvi):.3f}")
NDVI Statistics by Land Cover:
Forest - Mean: 0.401, Std: 0.223
Cropland - Mean: 0.418, Std: 0.172
Urban - Mean: 0.268, Std: 0.065
Summary
Key visualization techniques covered:
- Single band visualization with different colormaps
- RGB composite creation and enhancement
- Spectral indices (NDVI, NDWI) visualization
- Custom colormaps and classification maps
- Multi-scale visualization with zoom and profiles
- Time series plotting for temporal analysis
These techniques form the foundation for effective satellite imagery analysis and presentation.