harpia is an open-source Python library to program automatic close-up photo missions to carry out with the DJI Mavic 3E or Matrice 4E drones. It generates an optimized flight path from feature locations (selected tree crowns for example) to acquire the close-up photos, supporting tropical botanical and ecological work.
Automatically generates optimized flight paths from tree/feature locations:
- Input: Features (centroids, points or polygons) from GeoPackage/Shapefile + Digital Surface Model (DSM)
- Process: Extracts elevations, solves Traveling Salesman Problem (TSP) for optimal route, creates path checkpoints above obstacles
- Output: Waypoints CSV with precise coordinates and flight heights above features
Converts waypoints into DJI-compatible mission files:
- Template KML: Defines mission structure and flight parameters
- Waylines WPML: Contains detailed waypoint sequences and actions
- KMZ Package: Complete mission file ready for DJI Pilot 2 app
Periodically ascends to a higher altitude to re-establish signal and transmit updated RTK corrections to the drone:
- Purpose: Fly to higher altitude periodically to restore connection between controller and drone before losing RTK signal
- Configurable: Set interval (every N waypoints) and altitude (up to 200m above DSM)
Optionally specify takeoff coordinates (--takeoff-coords or in YAML config). The first waypoint will be automatically selected as the closest one to the provided coordinates, optimizing the initial flight path from the takeoff location.
- Purpose: Start the mission from a defined first waypoint for improved route planning
- Configurable: Provide coordinates as
x y(projected CRS) orlat lon(WGS84)
Optionally shortlist candidate tree crowns before planning a mission, using scripts/select_crowns.py:
- Input: A candidate crowns layer, plus any combination of an AOI, a minimum area, an existing waypoints layer, a reference layer of known crowns, and a DSM
- Process: Applies up to five independent filters in sequence, reporting how many crowns each one removes
- Output: A filtered crowns layer ready to feed back in as the
--featuresinput
Clone the repository to your local machine:
git clone https://github.com/traitlab/harpia.git
cd harpiaInstall the required Python packages in a python 3.11 conda environment:
conda create -n harpia python=3.11
conda activate harpia
pip install -r requirements.txtIt is possible to pass a YAML configuration file or to use command-line arguments when running the pipeline.
Create a configuration file (e.g., config.yaml) with your settings. A detailed configuration template, including explicit descriptions for each argument, is available in the config folder.
# Input/Output settings
csv_path: /path/to/waypoints.csv # Optional
features_path: /path/to/features.gpkg # Optional
dsm_path: /path/to/dsm.tif # Optional
drone_model: m3e # 'm3e' and 'm4e' are currently supported
output_folder: /path/to/output # Optional
output_filename: my_mission # Optional
# Flight settings
buffer: 6
approach: 10
# Waypoint generation settings
buffer_path: 10 # Optional
buffer_feature: 3 # Optional
takeoff_coords: [45.5572, -73.5558] # Optional (lat lon for WGS84, or x y if projected)
takeoff_coords_projected: false # Optional
# Area of Interest (AOI) settings
aoi_path: /path/to/aoi.gpkg # Optional
aoi_index: 1 # Optional - selects the AOI polygon by 1-based position
aoi_qualifier: north # Optional - suffix for output filenames; used alone, selects the AOI by its 'qualifier' column
# Touch-sky settings
touch_sky: false
touch_sky_interval: 10
touch_sky_altitude: 100
# System settings
debug_mode: false--config, -c: Path to YAML configuration file--csv, -csv: Path to existing waypoints CSV file--features, -f: Path to input features file (GeoPackage, Shapefile)--dsm, -dsm: Path to DSM raster file--drone-model, -m: Model of the drone to carry out the mission ('m3e' or 'm4e')--output-path, -op: Output directory path (optional)--output-filename, -of: Custom output filename without extension (optional)
--aoi, -aoi: Path to AOI file for filtering features (optional)--aoi-index, -i: 1-based index of the AOI polygon to use; requires--aoi-qualifier(optional)--aoi-qualifier, -q: Qualifier appended to output filenames, up to 8 characters (optional)
There are two ways to select a single polygon from the AOI file:
- By position: pass both
--aoi-indexand--aoi-qualifier. - By attribute: pass
--aoi-qualifieron its own. The AOI file must then contain aqualifiercolumn, and the feature whose value matches is selected.
--takeoff-coords, -to: Takeoff site coordinates as two floats: x y OR lat lon (optional)--takeoff-coords-projected, -proj: Flag to indicate takeoff coordinates are in projected CRS (default: False (WGS84)) (optional)
--touch-sky, -ts: Enable touch-sky feature (default: False)--touch-sky-interval, -n: Number of features between touch-sky actions (default: 10, min: 5)--touch-sky-altitude, -alt: Touch-sky altitude in meters above DSM (default: 100, min: 16, max: 200)
--debug, -d: Run in debug mode
python main.py --config config.yaml(replace \ with ^ for Windows Command Prompt)
with custom output settings and --touch-sky option to enable periodic ascents
python main.py \
--csv waypoints.csv \
--output-path /path/to/output \
--output-filename my_mission \
--touch-skypython main.py \
--features data/site_centroids.gpkg \
--dsm data/dsm.tifSelect the AOI polygon by position:
python main.py \
--features data/site_polygons1.gpkg \
--dsm data/dsm.tif \
--aoi data/aoi.gpkg \
--aoi-index 2 \
--aoi-qualifier northOr select it by its qualifier attribute, without knowing its position:
python main.py \
--features data/site_polygons1.gpkg \
--dsm data/dsm.tif \
--aoi data/aoi.gpkg \
--aoi-qualifier north- Format: GeoPackage (.gpkg) or Shapefile (.shp)
- Geometry: Point, Polygon, or MultiPolygon
- Naming convention:
{site}_{centroids|points|polygons}[version].{ext}- Examples:
site_centroids.gpkg,area_polygons3.shp
- Examples:
- CRS: Any projected coordinate system that matches the DSM and AOI
- Unique Identifier: Each feature should have a unique
point_idorFIDthat will be used for naming output pictures
- Format: GeoTIFF (.tif, .tiff)
- Content: Ellipsoidal elevation values in meters
- CRS: Any projected coordinate system that matches the features and AOI
- Format: GeoPackage (.gpkg) or Shapefile (.shp)
- Geometry: Polygon or MultiPolygon
- Purpose: Filter features to specific areas
- CRS: Any projected coordinate system that matches the features and DSM
- Attributes: A
qualifiercolumn is required only when selecting the AOI with--aoi-qualifieralone
The pipeline generates several output files:
{site}_wpt[qualifier][version]_{drone_model}.csv: Waypoints for mission generation{site}_wpt[qualifier][version]_{drone_model}.gpkg: Spatial waypoints data to visualize in GIS software
When an existing waypoints CSV is passed with --csv, a trailing drone-model suffix is stripped from its name and the current model appended, so the same CSV can be re-run for another drone without stacking suffixes.
The output CSV from features-based waypoint generation contains the following columns:
point_id: Unique identifier for each pointcluster_id: Cluster identifier if available (default: 0)type: Point type ('wpt' for waypoints, 'cpt' for checkpoints)lon_x: Longitude in WGS84lat_y: Latitude in WGS84elevation_from_dsm: Ellipsoidal elevation from DSM in metersorder: Waypoint order for mission planning
CSV as input needs to respect the format above. It is validated on load: the columns listed above must be present, lon_x, lat_y and elevation_from_dsm must be numeric, and the file must contain at least one wpt row and one cpt row.
template.kml: KML template for DJI missionwaylines.wpml: WPML waylines for DJI dronemission.kmz: Complete mission package to upload to DJI Pilot 2 app
scripts/select_crowns.py narrows a layer of candidate tree crowns down to a shortlist worth visiting. It applies up to five filters, each of which runs only when its input is supplied:
| # | Filter | Enabled by | Keeps |
|---|---|---|---|
| 1 | Area of interest | --aoi |
Crowns inside the AOI polygon |
| 2 | Minimum area | --min-area |
Crowns at or above a minimum area in m² |
| 3 | Already visited | --waypoints |
Crowns that do not already contain a waypoint |
| 4 | Already known | --exclude |
Crowns not overlapping a reference layer |
| 5 | DSM relief | --dsm |
Crowns not overtopped by their immediate surroundings |
Filters run in the order above, each operating on the survivors of the previous one, and every step reports how many crowns it removed.
python scripts/select_crowns.py \
--crowns crowns.gpkg \
--output selected_crowns.gpkg \
--aoi drone_sites.gpkg --site-id bcipearson \
--min-area 25 \
--waypoints 2024_bci_wpt.gpkg \
--exclude predictions_above80.gpkg \
--dsm dsm.tif --buffer-large 8 --buffer-small 1 --max-dsm-diff 5Every setting can also come from a YAML file, with command-line arguments taking precedence:
python scripts/select_crowns.py --config config/select_crowns.yamlSee config/select_crowns_template.yaml for the full list of settings and their defaults.
The resulting layer can be used directly as the --features input to main.py. Name it to match the features naming convention (for example bcipearson_polygons.gpkg), or pass --output-filename to main.py to bypass that rule.
If you use harpia in your research, please cite our paper (bioRxiv preprint):
@misc{Lalibert2025harpia,
title={Seeing the forest and the trees: a workflow for automatic acquisition of ultra-high resolution drone photos of tropical forest canopies to support botanical and ecological studies},
author={Laliberté, Etienne and Caron-Guay, Antoine and Le Falher, Vincent and Tougas, Guillaume and Muller-Landau, Helene C. and Rivas-Torres, Gonzalo and Walla, Thomas R. and Baudchon, Hugo and Hernandez, Mélvin and Buenaño, Adrian and Weber, Anna and Chambers, Jeffrey and Inuma, Jomber and Araúz, Fernando and Valdes, Jorge and Hernández, Andrés and Brassfield, David and Sérgio, Paulo and Vasquez, Vicente and Simonetti, Adriana and Marra, Daniel M. and Vasconcelos, Caroline and Vaca, Jarol F. and Rivadeneyra, Geovanny and Illanes, José and Salagaje-Muela, Luis A. and Gualinga, Jefferson},
year={2025},
url={https://www.biorxiv.org/content/10.1101/2025.09.02.673753v1},
doi={10.1101/2025.09.02.673753},
keywords={Unoccupied aerial vehicle (UAV),biodiversity,monitoring,RGB imagery,canopy,remote tree-survey,Panama,Ecuador,Brazil,tropical tree diversity}
}