|
2 | 2 | #' @name .torch_smooth_bayes_fraction |
3 | 3 | #' @keywords internal |
4 | 4 | #' @noRd |
5 | | -#' @author Gilberto Camara, \email{gilberto.camara@@inpe.br} |
| 5 | +#' @author Alexandre Assuncao, \email{alexcarssuncao@@gmail.com} |
6 | 6 | #' |
7 | 7 | #' @description |
8 | 8 | #' Drop-in replacement for the C++ \code{bayes_smoother_fraction()} function. |
|
34 | 34 | window_size, |
35 | 35 | smoothness, |
36 | 36 | neigh_fraction) { |
37 | | - # Select compute device |
| 37 | + # Select device |
38 | 38 | device <- if (torch::cuda_is_available()) { |
39 | 39 | "cuda" |
40 | 40 | } else if (torch::backends_mps_is_available()) { |
|
43 | 43 | "cpu" |
44 | 44 | } |
45 | 45 |
|
46 | | - nbands <- ncol(logits) |
47 | | - npix <- nrows * ncols |
48 | | - leg <- window_size %/% 2L |
49 | | - win_sq <- as.integer(window_size * window_size) |
| 46 | + nbands <- ncol(logits) # Number of bands in data |
| 47 | + npix <- nrows * ncols # Number of pixels in data |
| 48 | + leg <- window_size %/% 2L # Floor of window size over two |
| 49 | + win_sq <- as.integer(window_size * window_size) # Number of pixels in window |
50 | 50 |
|
51 | | - # ---- Build input tensor [1, nbands, nrows, ncols] ---- |
| 51 | + # Build input tensor [1, nbands, nrows, ncols] |
52 | 52 | x <- torch::torch_tensor( |
53 | 53 | t(logits), |
54 | 54 | dtype = torch::torch_float32(), |
55 | 55 | device = device |
56 | 56 | )$view(c(nbands, nrows, ncols))$unsqueeze(1L) |
57 | 57 |
|
58 | | - # ---- Reflect-pad spatial dimensions (all bands at once — cheap) ---- |
| 58 | + # Padding spatial dimensions with reflect [1, nbands, nrows+2*leg, ncols+2*leg] |
| 59 | + # NOTE: Due to overlapping tiles, the padded values are later discarded |
59 | 60 | x_pad <- torch::nnf_pad( |
60 | 61 | x, |
61 | 62 | pad = c(leg, leg, leg, leg), |
62 | 63 | mode = "reflect" |
63 | 64 | ) |
64 | | - # x_pad: [1, nbands, nrows+2*leg, ncols+2*leg] |
65 | 65 |
|
66 | | - # ---- Reusable scalar tensors and position index ---- |
67 | | - zero_t <- torch::torch_tensor( |
68 | | - 0.0, dtype = torch::torch_float32(), device = device |
69 | | - ) |
70 | | - neginf_t <- torch::torch_tensor( |
71 | | - -Inf, dtype = torch::torch_float32(), device = device |
72 | | - ) |
73 | | - # Position index for top-fraction selection [1, win_sq] |
| 66 | + # Aux zero tensor |
| 67 | + zero_t <- torch::torch_tensor(0.0, dtype = torch::torch_float32(), device = device) |
| 68 | + # Aux minus inf tensor |
| 69 | + neginf_t <- torch::torch_tensor(-Inf, dtype = torch::torch_float32(), device = device) |
| 70 | + # Aux index tensor for neighbor selection [1, win_sq] |
74 | 71 | pos <- torch::torch_tensor( |
75 | 72 | seq_len(win_sq), |
76 | 73 | dtype = torch::torch_float32(), |
77 | 74 | device = device |
78 | 75 | )$view(c(1L, win_sq)) |
79 | 76 |
|
80 | | - # ---- Original pixel values for all bands [npix, nbands] ---- |
81 | | - x0_all <- torch::torch_tensor( |
82 | | - logits, dtype = torch::torch_float32(), device = device |
83 | | - ) |
| 77 | + # Original pixel values for all bands [npix, nbands] |
| 78 | + x0_all <- torch::torch_tensor(logits, dtype = torch::torch_float32(), device = device) |
84 | 79 |
|
85 | | - # ---- Process one band at a time to limit GPU memory ---- |
86 | | - # Without this, nnf_unfold on all bands produces a |
87 | | - # [nbands, npix, win_sq] tensor that can easily exceed GPU memory. |
| 80 | + # Storage for results per-band (Avoid blowing up GPU memory) |
88 | 81 | band_results <- vector("list", nbands) |
| 82 | + |
| 83 | + # --- MAIN LOOP |
89 | 84 | for (b in seq_len(nbands)) { |
90 | | - # Extract single band from padded tensor: [1, 1, H_pad, W_pad] |
| 85 | + # Get band b from padded tensor, i.e. [1, 1, H_pad, W_pad] |
91 | 86 | x_b <- x_pad[, b, , , drop = FALSE] |
92 | | - |
93 | | - # Unfold windows: [1, win_sq, npix] → [npix, win_sq] |
| 87 | + # Unfold windows, i.e. [1, win_sq, npix] to [npix, win_sq] |
94 | 88 | wins_b <- torch::nnf_unfold( |
95 | 89 | x_b, |
96 | 90 | kernel_size = window_size, |
97 | 91 | stride = 1L, |
98 | 92 | padding = 0L |
99 | 93 | )$squeeze(1L)$t() |
100 | | - # wins_b: [npix, win_sq] |
101 | | - |
102 | | - # ---- NaN handling ---- |
| 94 | + # Dealing with NA |
103 | 95 | nan_mask_b <- torch::torch_isnan(wins_b) |
104 | | - |
105 | | - # ---- Select neighbourhood values ---- |
| 96 | + # Select neighbourhood values |
106 | 97 | if (neigh_fraction == 1.0) { |
107 | 98 | selected_b <- torch::torch_where(nan_mask_b, zero_t, wins_b) |
108 | 99 | sel_mask_b <- !nan_mask_b |
109 | | - n_b <- (!nan_mask_b)$sum(dim = -1L)$to( |
110 | | - dtype = torch::torch_float32() |
111 | | - ) |
| 100 | + n_b <- (!nan_mask_b)$sum(dim = -1L)$to(dtype = torch::torch_float32()) |
112 | 101 | } else { |
113 | 102 | wins_sort_b <- torch::torch_where(nan_mask_b, neginf_t, wins_b) |
114 | | - sorted_b <- torch::torch_sort( |
115 | | - wins_sort_b, dim = -1L, descending = TRUE |
116 | | - )[[1L]] |
| 103 | + sorted_b <- torch::torch_sort(wins_sort_b, dim = -1L, descending = TRUE)[[1L]] |
117 | 104 |
|
118 | | - valid_b <- (!nan_mask_b)$sum(dim = -1L)$to( |
119 | | - dtype = torch::torch_float32() |
120 | | - ) |
121 | | - neigh_hi_b <- torch::torch_ceil( |
122 | | - neigh_fraction * valid_b |
123 | | - )$clamp_min(1L) |
| 105 | + valid_b <- (!nan_mask_b)$sum(dim = -1L)$to(dtype = torch::torch_float32()) |
| 106 | + neigh_hi_b <- torch::torch_ceil(neigh_fraction * valid_b)$clamp_min(1L) |
124 | 107 |
|
125 | 108 | sel_mask_b <- pos$le(neigh_hi_b$unsqueeze(-1L)) |
126 | 109 | selected_b <- torch::torch_where(sel_mask_b, sorted_b, zero_t) |
127 | 110 | n_b <- neigh_hi_b |
128 | 111 | } |
129 | | - |
130 | | - # ---- Unbiased mean and variance ---- |
| 112 | + # Calculating empirical mean |
131 | 113 | m0_b <- selected_b$sum(dim = -1L) / n_b |
132 | | - |
| 114 | + # Calculating empirical variance |
133 | 115 | diff_sq_b <- torch::torch_where( |
134 | 116 | sel_mask_b, |
135 | 117 | (selected_b - m0_b$unsqueeze(-1L))$pow(2L), |
|
140 | 122 | diff_sq_b$sum(dim = -1L) / (n_b - 1.0), |
141 | 123 | zero_t |
142 | 124 | ) |
143 | | - |
144 | | - # ---- Bayesian update for this band ---- |
145 | | - x0_b <- x0_all[, b] |
146 | | - w_b <- s0_b / (s0_b + smoothness[b]) |
| 125 | + # Bayesian update for band b |
| 126 | + x0_b <- x0_all[, b] |
| 127 | + w_b <- s0_b / (s0_b + smoothness[b]) |
147 | 128 | bayes_b <- w_b * x0_b + (1.0 - w_b) * m0_b |
148 | 129 | use_m0 <- torch::torch_isnan(x0_b) | s0_b$lt(1e-4) |
149 | | - |
| 130 | + # Store results |
150 | 131 | band_results[[b]] <- torch::torch_where(use_m0, m0_b, bayes_b) |
151 | 132 | } |
152 | 133 |
|
153 | | - # ---- Stack bands and convert to R matrix [npix, nbands] ---- |
| 134 | + # Stack all bands and convert to matrix with dim [npix, nbands] |
154 | 135 | result <- torch::torch_stack(band_results, dim = 2L) |
155 | 136 | as.matrix( |
156 | 137 | result$cpu()$to(dtype = torch::torch_float64()) |
157 | 138 | ) |
158 | 139 | } |
159 | 140 |
|
160 | 141 | #' @title Torch Bayesian smoother closure factory |
161 | | -#' @name .smooth_fn_bayes_torch |
| 142 | +#' @name .smooth_fn_bayes_torch |
162 | 143 | #' @keywords internal |
163 | 144 | #' @noRd |
164 | 145 | #' |
|
240 | 221 | output_dir, |
241 | 222 | version, |
242 | 223 | progress) { |
| 224 | + # Define Bayesian Smoothing function |
243 | 225 | smooth_fn <- .smooth_fn_bayes_torch( |
244 | 226 | window_size = window_size, |
245 | 227 | neigh_fraction = neigh_fraction, |
246 | 228 | smoothness = smoothness |
247 | 229 | ) |
| 230 | + # The overlap makes sure that the padded values added for torch |
| 231 | + # computations are discarded |
248 | 232 | overlap <- ceiling(window_size / 2L) - 1L |
| 233 | + # Use sits cube api to iterate over tiles |
249 | 234 | .cube_foreach_tile(cube, function(tile) { |
250 | 235 | .smooth_tile( |
251 | 236 | tile = tile, |
|
0 commit comments