Skip to content

traitlab/closeup-ortho

Repository files navigation

🌿 closeup-ortho

Orthorectify DJI nadir close-up drone photos and project Labelbox annotations to ground coordinates.

Overview

Left: raw close-up photo with Labelbox bounding box. Centre: all 105 photo footprints over the DSM, with 84 projected label polygons (red). Right: the same photo georeferenced as a COG GeoTIFF with its label polygon in the DSM CRS.


What it does

closeup-ortho performs a simple planar projection of DJI nadir close-up photos onto a DSM. Each photo is placed on the ground as a rectangle whose:

  • 📍 position comes from the photo's RTK GPS (WGS84 lat/lon)
  • 🧭 heading comes from the gimbal yaw
  • 📐 size comes from the camera-to-ground distance — AbsoluteAltitude − max(DSM elevation in a small buffer) — combined with the lens geometry (calibrated focal length for the wide camera, FocalLengthIn35mmFilm for the tele camera)

It assumes photos are taken straight down (gimbal pitch ≈ −90°) and that the DSM uses ellipsoidal heights, matching the drone's AbsoluteAltitude. All outputs are in the CRS of the input DSM.

Two commands:

Command What it does
orthorectify Projects photos onto a DSM → GeoPackage footprint polygons + optional COG GeoTIFFs
labels Reprojects Labelbox bounding-box annotations to ground coordinates, linked to footprints

Example output in QGIS

QGIS example

A single georeferenced close-up photo (COG GeoTIFF) with its projected label polygon, displayed in QGIS over the DSM.


🛠 Install

python -m venv .venv

# Windows
.venv\Scripts\python -m pip install -e .

# macOS / Linux
.venv/bin/python -m pip install -e .

Dependencies: rasterio, geopandas, pyproj, shapely, numpy, Pillow


🚀 Usage

1 — Project photos onto the DSM

closeup-ortho orthorectify \
  --photos   data/examples/enearehab1/closeup/20260127_enearehab1_wpt169_m3e \
  --dsm      data/examples/enearehab1/dsm/20260225_enearehab1_10m169_m3e_aoi.tif \
  --out      out/examples/enearehab1/footprints.gpkg \
  --geotiffs out/examples/enearehab1/tiff
Option Default Description
--lenses tele Which lenses to process: tele, wide, tele,wide, or all
--geotiffs DIR Also write projected COG GeoTIFFs to this folder
--buffer-radius M auto DSM sampling buffer radius in meters (default: ~50–75 cm)
--no-recursive Scan only the top folder, not subfolders
from closeup_ortho import orthorectify_folder

gdf = orthorectify_folder(
    photo_dir="data/examples/enearehab1/closeup/20260127_enearehab1_wpt169_m3e",
    dsm_path="data/examples/enearehab1/dsm/20260225_enearehab1_10m169_m3e_aoi.tif",
    out_gpkg="out/examples/enearehab1/footprints.gpkg",
    lenses={"tele"},
    write_geotiffs=True,
    geotiff_dir="out/examples/enearehab1/tiff",
)

2 — Project Labelbox annotations

closeup-ortho labels \
  --ndjson     "data/examples/enearehab1/labelbox/2025_wa_roberge - 6_12_2026.ndjson" \
  --footprints out/examples/enearehab1/footprints.gpkg \
  --out        out/examples/enearehab1/labels.gpkg
from closeup_ortho import project_labels

gdf = project_labels(
    ndjson_path="data/examples/enearehab1/labelbox/2025_wa_roberge - 6_12_2026.ndjson",
    footprints_gpkg="out/examples/enearehab1/footprints.gpkg",
    out_gpkg="out/examples/enearehab1/labels.gpkg",
)

The output layer contains one polygon per bounding box. Join it back to footprints via footprint_filenamefilename.


📦 Output layers

footprints — one polygon per photo

Column Description
path Full path to the photo
filename Filename with extension
directory Parent directory of the photo
camera tele or wide
longitude, latitude Camera position, WGS84
abs_alt_m AbsoluteAltitude (ellipsoidal), meters
dist_m Camera-to-ground distance used for projection, meters
gsd_m Ground sample distance, meters/pixel
footprint_w_m, footprint_h_m Footprint dimensions on the ground, meters
yaw, pitch, roll Gimbal angles, degrees
nadir_ok Gimbal within nadir tolerance
focal_source calib (wide camera's CalibratedFocalLength) or fl35 (FocalLengthIn35mmFilm)
timestamp Capture time

labels — one polygon per bounding box

Column Description
footprint_filename 🔑 Foreign key → filename in the footprints layer
datarow_id Labelbox data row ID
label_id Labelbox label ID
feature_id Labelbox feature ID for this bounding box
url Full URL of the source JPEG on the object store
object_name Annotation class name (e.g. Plants)
object_value Annotation class value (e.g. plants)
taxon_name Taxon name from the radio classification
taxon_value Taxon ID (PlantNet ID)
bbox_top_px, bbox_left_px, bbox_height_px, bbox_width_px Original bbox in image pixels
dataset_name Labelbox dataset name
project_name Labelbox project name
workflow_status Labelbox workflow status (e.g. IN_REVIEW, TO_LABEL)
label_created_by Annotator email
label_created_at Annotation timestamp
lb_created_by Data row creator email

🔭 Not yet implemented

  • Using the Matrice 4 Enterprise laser rangefinder for the distance term (currently DSM-derived for all drones)
  • Handling photos that fall outside the DSM extent (currently skipped with a warning)

About

Orthorectify drone close-up photos and associated labels, if available.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages