Skip to content

Cloud Plot ROIs

This tutorial shows a practical cloud-oriented workflow for storing a full orthomosaic, extracting plot-level regions of interest, computing NDVI for each plot, and saving the outputs for upload to a bucket.

The recommended default for the full orthomosaic is a Cloud Optimized GeoTIFF (COG). A COG keeps normal GeoTIFF compatibility while adding internal tiling and overviews, which helps cloud readers request only the byte ranges they need instead of downloading the full raster.

For small plot ROI files, a regular georeferenced GeoTIFF is often enough. The files are small, so compression and COG conversion may not be worth the extra CPU time unless you expect remote window reads, map tiling, or many repeated reads directly from object storage.

Storage Choices

Use a COG for the canonical orthomosaic:

1
orthos/{project_id}/{flight_id}/orthomosaic.cog.tif

Use regular GeoTIFFs for small plot ROIs:

1
2
plots/{project_id}/{flight_id}/roi/{plot_id}.tif
plots/{project_id}/{flight_id}/ndvi/{plot_id}_ndvi.tif

Keep a manifest beside the files:

1
plots/{project_id}/{flight_id}/manifest.parquet

The manifest should record plot_id, source orthomosaic URI, ROI URI, NDVI URI, CRS, bounds, band order, flight date, and any treatment or replication metadata. This makes thousands of small plot files discoverable without relying only on object names.

Compression

ZSTD and DEFLATE are both lossless, so they preserve analytical pixel values.

  • ZSTD is usually faster and often smaller, but requires GDAL/libtiff builds with ZSTD support.
  • DEFLATE is more widely supported by older geospatial software.

For internal cloud pipelines, ZSTD is a good default when your GDAL stack supports it. For maximum compatibility with external tools, use DEFLATE.

For small plot ROIs, you can skip compression:

1
plot.save("plot_001.tif", overwrite=True)

Avoid lossy JPEG compression for NDVI, multispectral bands, and other analytical rasters. It is only appropriate for visual RGB products where exact pixel values are not required.

Tiling

tiled=True stores a GeoTIFF internally as rectangular blocks instead of long scanlines. This helps when readers need a small window from a large raster because GDAL can read only the intersecting internal blocks.

For large orthomosaics, tiling is important. For small plot ROIs, it is optional because the whole file may be small enough to read directly.

Extract ROIs And NDVI

Choose the correct red and NIR band numbers for your orthomosaic before computing NDVI. UavPy uses one-based band numbers in SpectralIndex.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from pathlib import Path

from uavpy.artifacts import Orthomosaic, ShapeFile
from uavpy.tools import SpectralIndex

mosaic = Orthomosaic.from_path("orthomosaic.tif")
optimized = mosaic.save_cog(
    "orthomosaic.cog.tif",
    overwrite=True,
    compress="ZSTD",  # Use DEFLATE when maximum compatibility matters.
)

shape_file = ShapeFile("plots.shp")
plots = await optimized.extract_plots(shape_file, plot_id_field="plot_id")

ndvi = SpectralIndex.ndvi(nir=6, red=4)

for plot in plots:
    plot_id = str(plot.attrs["plot_id"])

    roi_path = Path("plot_rois") / f"{plot_id}.tif"
    plot.save(roi_path, overwrite=True)
    # upload roi_path to gs://... or s3://...

    ndvi_plot = await ndvi(plot)
    ndvi_path = Path("plot_rois_ndvi") / f"{plot_id}_ndvi.tif"
    ndvi_plot.save(ndvi_path, overwrite=True, dtype="float32")
    # upload ndvi_path to gs://... or s3://...

If your raster uses a different band order, change the NDVI constructor. For example, if red is band 1 and NIR is band 4:

1
ndvi = SpectralIndex.ndvi(nir=4, red=1)

When To Save Compressed Plot Files

For many small ROIs, start with uncompressed GeoTIFFs because the files are simple and fast to write:

1
plot.save(roi_path, overwrite=True)

If the plot rasters become large or will be read repeatedly from cloud storage, add lossless compression and internal tiling:

1
2
3
4
5
6
7
8
plot.save(
    roi_path,
    overwrite=True,
    compress="ZSTD",
    tiled=True,
    blockxsize=512,
    blockysize=512,
)

This creates a compressed tiled GeoTIFF. If you need strict COG validation for every plot ROI, save the ROI first and then run COG conversion as a separate step.