A zero-dependency .NET CLI tool that analyzes DJI drone photo datasets. Extracts flight timelines, camera settings, overlap geometry, gimbal configuration, altitude/GSD, and terrain elevation from EXIF + XMP metadata.
Takes one or more directories of photos and produces a comprehensive mission report in seconds — no image processing, no external libraries, just raw metadata parsing.
- Capture Group Classification — Automatically groups flights by altitude band (stable-altitude flights clustered, varying-altitude flights in a catch-all group) with per-group analysis; preliminary relative-altitude bands are reconciled against measured AGL, merging bands that are the same capture plan flown over different terrain and labeling groups by AGL (relative altitude is indicative only)
- Flight Timeline — Segments photos into flights by detecting power cycles (timestamp gaps + DJI sequence resets)
- Camera Settings — Shutter speed, aperture, ISO, focal length per flight
- Forward & Side Overlap — Computed from consecutive GPS positions and flight-line geometry, per capture group
- Smart Oblique Analysis — Correlates gimbal pitch, roll, and yaw to determine the real oblique angle setting
- GSD & Footprint — Ground sample distance and footprint from DJI calibrated focal length, computed from the measured height above ground (not the takeoff-relative altitude, which is wrong under terrain follow and on sloped sites)
- Terrain Elevation & Datum-Correct AGL — Queries Open-Meteo SRTM API for ground elevation; resolves the DJI
AbsoluteAltitudevertical datum (RTK platforms write WGS84 ellipsoidal heights, consumer platforms EGM96 MSL) and corrects with an embedded EGM96 geoid grid before computing AGL - Terrain-Follow Detection — Per capture group, detects when the takeoff-relative altitude tracks the terrain (slope ≈ 1 regression against SRTM ground elevation), flagging missions where relative altitude is not height above ground
- Speed Profile — Ground-speed statistics (min/max/mean/median/percentiles) per capture group and mission-wide, densely sampled from the overlap block, with capture-cruise vs transit-leg regime split
- Equipment ID — Drone model, serial numbers, sensor specs from XMP metadata
- Per-Flight Breakdown — Day-by-day tables with photo counts, durations, camera parameters, and group labels
- Multi-Directory Support — Merge photos from multiple folders (common when flights are split across subfolders)
- Recursive Search — Automatically finds all JPEG files in subdirectories
# Clone and build
git clone https://github.com/Alos-no/DroneDatasetAnalyzer.git
cd DroneDatasetAnalyzer
dotnet build src/DroneDatasetAnalyzer.csproj
# Analyze a dataset (searches recursively)
dotnet run --project src/DroneDatasetAnalyzer.csproj -- "/path/to/drone/photos"Or download a pre-built release from the Releases page:
DroneDatasetAnalyzer "/path/to/drone/photos"DroneDatasetAnalyzer <directory> [directory2 ...] [options]
Arguments:
<directory> One or more directories containing DJI drone photos.
Each directory is searched recursively for JPEG files.
Photos from all directories are merged and analyzed together.
Options:
-o, --output <path> Output report path (default: MISSION-REPORT.md in first dir)
-s, --samples <n> Samples per flight for metadata (default: 9)
-b, --overlap-block <n> Consecutive photos for overlap (default: 500)
--skip-elevation Skip elevation API query (faster, no AGL)
--altitude-datum <mode> AbsoluteAltitude datum: auto | ellipsoidal | msl (default: auto)
RTK-capable platforms (M4E, M3E, M300/350, P4RTK) write WGS84
ellipsoidal heights; consumer platforms write EGM96 MSL.
Auto detects via the RtkFlag XMP tag, with a relative-altitude
consistency heuristic as fallback.
-h, --help Show help
# Analyze a dataset with photos in subfolders (common DJI folder structure)
DroneDatasetAnalyzer "D:\Flights\2026-05-03_Site\M4E"
# Analyze multiple flight directories together
DroneDatasetAnalyzer "D:\Flight_001" "D:\Flight_002" "D:\Flight_003" -o report.md
# Custom output path, more samples per flight
DroneDatasetAnalyzer "D:\Flights\Flight_001" -o report.md --samples 15
# Skip elevation API (no internet needed)
DroneDatasetAnalyzer "D:\Flights\Flight_001" --skip-elevation═══ MISSION SUMMARY ═══
Platform: DJI Matrice 4E
Photos: 5,394 across 1 day(s)
Flights: 3
Capture Groups: 1
AGL: 63.7 m mean (53.3-82.3 m range, WGS84 ellipsoidal datum)
Speed: 4.8 m/s median (0.2-14.7 m/s range)
Capture Time: 1h 5m
── Capture at ~64 m AGL (5,394 photos, 3 flight(s)) ──
Altitude: 64 m AGL (rel 31–64 m) | GSD: 1.71 cm/px (terrain follow)
Overlap: 77% forward, 79% side
Speed: 4.8 m/s cruise | transit 9.5 m/s (17%)
Gimbal: Smart Oblique at 45°
The tool generates a full Markdown report with dynamic sections: Equipment, Location, Mission Overview (with capture group summary table), Elevation & Terrain (global), one section per capture group (with full Altitude/GSD, Overlap, and Gimbal detail), Flight Details (per-day tables with group labels), Camera Summary, and Methodology Notes.
The analysis pipeline is designed for speed — it reads as few files as possible:
- Directory Scanning (0 file reads) — Recursively finds all JPEG files across input directories
- Filename Parsing (0 file reads) — Extracts timestamps and sequence numbers from DJI's
DJI_YYYYMMDDHHMMSS_NNNN_V.jpgnaming convention - Flight Segmentation (0 file reads) — Splits at >30s gaps, merges brief pauses unless the sequence counter resets (= power cycle)
- Metadata Sampling (~45 file reads) — Reads EXIF + XMP from 9 evenly-spaced photos per flight
- Capture Group Classification (0 file reads) — Groups flights by altitude band using sampled metadata
- Elevation & AGL (~5 HTTP requests) — Queries SRTM ground elevation for the sampled photos, resolves the
AbsoluteAltitudevertical datum (subtracting the EGM96 geoid undulation for ellipsoidal platforms), and stamps a datum-corrected AGL on each sampled photo - Per-Group Analysis (~500 file reads per classified group) — Computes AGL statistics + terrain-follow detection, gimbal configuration, and reads consecutive photos for overlap computation within each altitude band — with GSD/footprint driven by the measured AGL
DJI photos carry two altitudes, and neither is "height above ground" by default:
| XMP field | Meaning | Pitfall |
|---|---|---|
RelativeAltitude |
Height above the takeoff point | Diverges from height above ground under terrain follow or when terrain slopes away from the takeoff point — GSD/overlap computed from it are wrong |
AbsoluteAltitude |
Drone altitude in a vertical datum that varies by platform | RTK-capable platforms (M4E, M3E, M300/350, P4RTK) write WGS84 ellipsoidal heights; consumer platforms write EGM96 MSL. Differencing an ellipsoidal height against orthometric SRTM elevations inflates AGL by the geoid undulation (~17–40 m over most land) |
The analyzer resolves the datum (RtkFlag tag → ellipsoidal; consistency heuristic as fallback; --altitude-datum to force), subtracts the EGM96 geoid undulation where needed using an embedded 15-minute EGM96 grid (GeographicLib's egm96-15.pgm, derived from public-domain NGA data — no internet needed for the geoid), and uses the resulting measured AGL for GSD, footprint, and overlap.
Total: ~1,500+ file reads for a 5,000-photo multi-group dataset. Analysis completes in ~30 seconds over a network share, or a few seconds on local SSD.
| Source | Data Extracted |
|---|---|
| DJI Filename | Capture timestamp, sequence number |
| EXIF IFD | GPS coordinates, altitude (MSL), exposure, aperture, ISO, focal length, image dimensions |
| DJI XMP | Relative altitude, gimbal pitch/yaw/roll, flight yaw, ground speed, calibrated focal length, RTK status, sensor temperature, device serial numbers |
EXIF is parsed from raw TIFF/IFD binary format. XMP is extracted via regex on the embedded XML. No external image libraries required.
- .NET 10 SDK (for building from source)
- Or any of the pre-built releases (self-contained, no SDK needed)
- DJI drone photos with standard naming convention and EXIF/XMP metadata
- Internet connection for elevation API (optional — use
--skip-elevationto skip)
Tested with:
- DJI Matrice 4E (M4E)
Should work with any DJI drone that uses the standard DJI_YYYYMMDDHHMMSS_NNNN_V.jpg filename convention and drone-dji:* XMP namespace (Mavic, Phantom, Matrice, Mini series, etc.).
# Debug build
dotnet build src/DroneDatasetAnalyzer.csproj
# Release build
dotnet build src/DroneDatasetAnalyzer.csproj -c Release
# Self-contained single-file executable
dotnet publish src/DroneDatasetAnalyzer.csproj -c Release -r win-x64 --self-contained -p:PublishSingleFile=true -o publish/Apache License 2.0 — see LICENSE.
The embedded EGM96 geoid grid (src/Resources/egm96-15.pgm) comes from the
GeographicLib geoid distribution
and is derived from public-domain NGA EGM96 data.
Built by Alos — a Norwegian drone services company specializing in imaging, mapping, inspection, and reality capture.