Flip-n-Slide Data Augmentation for Geospatial Tiling¶
This notebook demonstrates the Flip-n-Slide tiling strategy for geospatial imagery, based on the paper:
Abrahams, E., Snow, T., Siegfried, M. R., & Perez, F. (2024). A Concise Tiling Strategy for Preserving Spatial Context in Earth Observation Imagery. ML4RS Workshop @ ICLR 2024 (Best Short Paper). arXiv:2404.10927
Overview¶
Traditional grid tiling discards spatial context at tile boundaries. Flip-n-Slide generates overlapping tiles with diverse augmentations that:
- Preserve spatial context across tile boundaries
- Eliminate redundant pixel representations through rotations and flips
- Produce more diverse training data without external augmentation libraries
The algorithm creates two sets of tiles:
- Standard overlapping tiles (half-stride) with rotational augmentations (identity, 90°, 180°, 270°)
- Inner offset tiles (25%/75% positions) with flip + rotation augmentations (h-flip, v-flip, 90°+h-flip, 90°+v-flip)
GitHub Issue: #88
# %pip install -U geoai
Import libraries¶
import os
import geoai
import numpy as np
import rasterio
import matplotlib.pyplot as plt
from pathlib import Path
Download sample data¶
We'll use NAIP aerial imagery with building footprints for this demonstration.
raster_url = (
"https://huggingface.co/datasets/giswqs/geospatial/resolve/main/naip_rgb_train.tif"
)
vector_url = "https://huggingface.co/datasets/giswqs/geospatial/resolve/main/naip_train_buildings.geojson"
sample_image = geoai.download_file(raster_url)
sample_vector = geoai.download_file(vector_url)
print(f"Downloaded image: {sample_image}")
print(f"Downloaded labels: {sample_vector}")
Visualize sample data¶
geoai.get_raster_info(sample_image)
geoai.view_vector_interactive(sample_vector, tiles=sample_image)
Flip-n-Slide with numpy arrays¶
The flipnslide_augmentation function works directly on numpy arrays of shape (channels, height, width). It returns the tiles and a list of augmentation indices.
# Read the raster as a numpy array
with rasterio.open(sample_image) as src:
image_data = src.read()
print(f"Image shape: {image_data.shape}")
print(f"Image dtype: {image_data.dtype}")
# Apply Flip-n-Slide augmentation
tiles, aug_indices = geoai.flipnslide_augmentation(image_data, tile_size=256)
print(f"Number of tiles: {tiles.shape[0]}")
print(f"Tile shape: {tiles.shape[1:]}")
print(f"Augmentation types used: {sorted(set(aug_indices))}\n")
# Count each augmentation type
aug_names = {
0: "Identity",
1: "180° rotation",
2: "90° rotation",
3: "270° rotation",
4: "Horizontal flip",
5: "Vertical flip",
6: "90° + H-flip",
7: "90° + V-flip",
}
from collections import Counter
counts = Counter(aug_indices)
for idx in sorted(counts):
print(f" {aug_names.get(idx, f'Type {idx}')}: {counts[idx]} tiles")
Visualize augmented tiles¶
Let's display a grid of tiles grouped by augmentation type to see the diversity of transformations.
fig, axes = plt.subplots(2, 4, figsize=(16, 8))
fig.suptitle("Flip-n-Slide Augmentation Types", fontsize=16, fontweight="bold")
for aug_type in range(8):
row, col = divmod(aug_type, 4)
ax = axes[row, col]
# Find first tile with this augmentation type
matching = [i for i, a in enumerate(aug_indices) if a == aug_type]
if matching:
tile = tiles[matching[0]]
# Convert CHW -> HWC for display
tile_rgb = np.transpose(tile[:3], (1, 2, 0))
# Normalize to 0-1 for display
tile_rgb = tile_rgb.astype(float)
if tile_rgb.max() > 1:
tile_rgb = tile_rgb / 255.0
tile_rgb = np.clip(tile_rgb, 0, 1)
ax.imshow(tile_rgb)
ax.set_title(aug_names.get(aug_type, f"Type {aug_type}"), fontsize=11)
ax.axis("off")
plt.tight_layout()
plt.show()
PyTorch tensor output¶
The function also supports outputting tiles as PyTorch tensors, useful for direct integration with training pipelines.
tiles_tensor, aug_indices_tensor = geoai.flipnslide_augmentation(
image_data, tile_size=256, output_format="torch"
)
print(f"Type: {type(tiles_tensor)}")
print(f"Shape: {tiles_tensor.shape}")
print(f"Dtype: {tiles_tensor.dtype}")
Export Flip-n-Slide tiles as GeoTIFFs¶
The export_flipnslide_tiles function is a geospatial-aware wrapper that preserves CRS and geotransform information for each tile.
output_flipnslide = "flipnslide_demo/flipnslide_tiles"
stats = geoai.export_flipnslide_tiles(
sample_image,
output_flipnslide,
tile_size=256,
)
print(f"\nTotal tiles: {stats['total_tiles']}")
print(f"Tile size: {stats['tile_size']}px")
print(f"Output folder: {stats['output_folder']}")
Export with label/mask data¶
When a label raster is provided, matching mask tiles are generated with identical augmentations.
output_with_labels = "flipnslide_demo/flipnslide_tiles_with_labels"
# First create a raster mask from the vector labels
from geoai.utils import export_geotiff_tiles
# Use export_geotiff_tiles with flipnslide strategy and vector class data
stats_labels = geoai.export_geotiff_tiles(
sample_image,
output_with_labels,
in_class_data=sample_vector,
tile_size=256,
tiling_strategy="flipnslide",
)
Compare grid tiling vs Flip-n-Slide¶
Let's compare the number of tiles generated by standard grid tiling versus the Flip-n-Slide strategy.
# Standard grid tiling (no overlap)
output_grid = "flipnslide_demo/grid_tiles"
geoai.export_geotiff_tiles(
sample_image,
output_grid,
tile_size=256,
stride=256,
tiling_strategy="grid",
)
grid_tiles = list(Path(output_grid, "images").glob("*.tif"))
# Standard grid tiling with half-stride overlap
output_overlap = "flipnslide_demo/overlap_tiles"
geoai.export_geotiff_tiles(
sample_image,
output_overlap,
tile_size=256,
stride=128,
tiling_strategy="grid",
)
overlap_tiles = list(Path(output_overlap, "images").glob("*.tif"))
# Flip-n-Slide
fns_tiles = list(Path(output_flipnslide, "images").glob("*.tif"))
print("Tiling Strategy Comparison:")
print(f" Grid (stride=256): {len(grid_tiles)} tiles")
print(f" Grid (stride=128): {len(overlap_tiles)} tiles")
print(f" Flip-n-Slide: {len(fns_tiles)} tiles")
Using tiling_strategy parameter¶
The existing export_geotiff_tiles function now supports a tiling_strategy parameter to switch between standard grid tiling and Flip-n-Slide.
# Use Flip-n-Slide via the tiling_strategy parameter
output_strategy = "flipnslide_demo/strategy_demo"
geoai.export_geotiff_tiles(
sample_image,
output_strategy,
tile_size=256,
tiling_strategy="flipnslide", # Use Flip-n-Slide instead of grid
)
strategy_tiles = list(Path(output_strategy, "images").glob("*.tif"))
print(f"Tiles generated with tiling_strategy='flipnslide': {len(strategy_tiles)}")
Visualize tile spatial distribution¶
Let's visualize a few exported GeoTIFF tiles to confirm they have proper spatial metadata.
tile_dir = Path(output_flipnslide, "images")
tile_files = sorted(tile_dir.glob("*.tif"))[:6]
fig, axes = plt.subplots(1, min(6, len(tile_files)), figsize=(18, 3.5))
if len(tile_files) == 1:
axes = [axes]
for ax, tile_path in zip(axes, tile_files):
with rasterio.open(tile_path) as src:
tile = src.read()
crs = src.crs
tile_rgb = np.transpose(tile[:3], (1, 2, 0)).astype(float)
if tile_rgb.max() > 1:
tile_rgb = tile_rgb / 255.0
tile_rgb = np.clip(tile_rgb, 0, 1)
ax.imshow(tile_rgb)
ax.set_title(tile_path.name, fontsize=9)
ax.axis("off")
plt.suptitle(f"Sample Flip-n-Slide tiles (CRS: {crs})", fontsize=13)
plt.tight_layout()
plt.show()
Clean up¶
import shutil
if os.path.exists("flipnslide_demo"):
shutil.rmtree("flipnslide_demo")
print("Cleaned up demo files.")
Summary¶
This notebook demonstrated:
flipnslide_augmentation()— Apply Flip-n-Slide directly to numpy arrays or PyTorch tensorsexport_flipnslide_tiles()— Export georeferenced tiles with CRS and geotransform preservedtiling_strategy="flipnslide"— Use Flip-n-Slide via the existingexport_geotiff_tilesfunction
Benefits of Flip-n-Slide¶
- Spatial context preservation: Overlapping tiles maintain context across boundaries
- Built-in augmentation diversity: 8 different augmentation types without external libraries
- No redundancy: Each pixel appears in multiple tiles but with different transformations
- Geospatial-aware: Preserves CRS and geotransform metadata for each tile
Reference¶
Abrahams, E., Snow, T., Siegfried, M. R., & Perez, F. (2024). A Concise Tiling Strategy for Preserving Spatial Context in Earth Observation Imagery. ML4RS Workshop @ ICLR 2024. arXiv:2404.10927