1111# ' @param dest_dir Directory for output GeoTIFFs. Created if it does not
1212# ' exist.
1313# ' @param overwrite If `FALSE` (default), skip files that already exist.
14+ # ' @param srcnodata Source nodata value passed to GDAL warp. Black pixels
15+ # ' matching this value are treated as transparent (alpha=0 for RGB,
16+ # ' nodata for grayscale). Default `"0"` masks camera frame borders and
17+ # ' film holder edges at the cost of losing real black pixels — acceptable
18+ # ' for thumbnails but may need adjustment for full-resolution scans.
19+ # ' Set to `NULL` to disable source nodata detection entirely.
1420# ' @return A tibble with columns `airp_id`, `source`, `dest`, and `success`.
1521# '
1622# ' @details
1925# ' translates the image with GCPs then warps to the target CRS using
2026# ' bilinear resampling.
2127# '
22- # ' **Nodata handling:** Warping a rectangular thumbnail into a rotated
23- # ' footprint creates fill pixels outside the source frame. Band count is
24- # ' read from each file header to choose the masking strategy: RGB
25- # ' thumbnails (3+ bands) get an alpha band (`-dstalpha`) so fill areas
26- # ' are transparent; grayscale thumbnails (1 band) use nodata=0. This
27- # ' only masks GDAL warp fill — black camera frame borders within the
28- # ' original image (from film holder edges, fiducial marks, or scanning
29- # ' artifacts) are preserved as valid pixels.
28+ # ' **Nodata handling:** Two sources of unwanted black pixels are masked:
29+ # '
30+ # ' 1. **Warp fill** — GDAL creates black pixels outside the rotated source
31+ # ' frame. RGB thumbnails get an alpha band (`-dstalpha`); grayscale use
32+ # ' `dstnodata=0`.
33+ # ' 2. **Camera frame borders** — film holder edges, fiducial marks, and
34+ # ' scanning artifacts produce black (value 0) pixels within the source
35+ # ' image. The `srcnodata` parameter (default `"0"`) tells GDAL to treat
36+ # ' these as transparent before warping.
37+ # '
38+ # ' **Tradeoff:** `srcnodata = "0"` also masks real black pixels (deep
39+ # ' shadows). At thumbnail resolution (~1250x1250) this is acceptable —
40+ # ' shadow detail is minimal. For full-resolution scans where shadow
41+ # ' detail matters, set `srcnodata = NULL` and handle frame masking
42+ # ' downstream (e.g., circle detection).
3043# '
3144# ' **Accuracy:** footprints assume flat terrain and nadir camera angle.
3245# ' The georeferenced thumbnails are approximate — useful for visual context,
4558# '
4659# ' @export
4760fly_thumb_georef <- function (fetch_result , photos_sf ,
48- dest_dir = " georef" , overwrite = FALSE ) {
61+ dest_dir = " georef" , overwrite = FALSE ,
62+ srcnodata = " 0" ) {
4963 if (! all(c(" airp_id" , " dest" , " success" ) %in% names(fetch_result ))) {
5064 stop(" `fetch_result` must be output from `fly_fetch()`." , call. = FALSE )
5165 }
@@ -85,7 +99,7 @@ fly_thumb_georef <- function(fetch_result, photos_sf,
8599 fp <- footprints [fp_idx [1 ], ]
86100
87101 results $ success [i ] <- tryCatch(
88- georef_one(src , fp , out_file ),
102+ georef_one(src , fp , out_file , srcnodata = srcnodata ),
89103 error = function (e ) {
90104 message(" Failed to georef " , basename(src ), " : " , e $ message )
91105 FALSE
@@ -100,7 +114,7 @@ fly_thumb_georef <- function(fetch_result, photos_sf,
100114
101115# ' Georeference a single thumbnail to a footprint polygon
102116# ' @noRd
103- georef_one <- function (src , fp , out_file ) {
117+ georef_one <- function (src , fp , out_file , srcnodata = " 0 " ) {
104118 # Get footprint corner coordinates
105119 # fly_footprint builds: BL, BR, TR, TL, BL (closing)
106120 coords <- sf :: st_coordinates(fp )[1 : 4 , , drop = FALSE ]
@@ -138,9 +152,18 @@ georef_one <- function(src, fp, out_file) {
138152 )
139153
140154 # Step 2: warp to target CRS with nodata handling
141- # RGB: add alpha band (-dstalpha) for clean masking in mosaics
142- # Grayscale: set nodata=0 (losing some shadow detail is acceptable)
155+ # srcnodata: masks black source pixels (camera frame borders)
156+ # RGB: alpha band (-dstalpha) for transparent fill in mosaics
157+ # Grayscale: dstnodata=0 for nodata metadata
143158 warp_opts <- c(" -t_srs" , " EPSG:3005" , " -r" , " bilinear" )
159+ if (! is.null(srcnodata )) {
160+ src_val <- if (is_rgb ) {
161+ paste(rep(srcnodata , n_bands ), collapse = " " )
162+ } else {
163+ srcnodata
164+ }
165+ warp_opts <- c(warp_opts , " -srcnodata" , src_val )
166+ }
144167 if (is_rgb ) {
145168 warp_opts <- c(warp_opts , " -dstalpha" )
146169 } else {
0 commit comments