diff --git a/DESCRIPTION b/DESCRIPTION index e76511002..51264e84a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: sits Type: Package -Version: 1.5.0 +Version: 1.5.0-1 Title: Satellite Image Time Series Analysis for Earth Observation Data Cubes Authors@R: c(person('Rolf', 'Simoes', role = c('aut'), email = 'rolf.simoes@inpe.br'), person('Gilberto', 'Camara', role = c('aut', 'cre'), email = 'gilberto.camara.inpe@gmail.com'), @@ -17,7 +17,7 @@ Description: An end-to-end toolkit for land use and land cover classification applied to satellite image data cubes, as described in Simoes et al (2021) . Builds regular data cubes from collections in AWS, Microsoft Planetary Computer, Brazil Data Cube, and Digital Earth Africa using the Spatio-temporal Asset Catalog (STAC) - protocol ( and the 'gdalcubes' R package + protocol () and the 'gdalcubes' R package developed by Appel and Pebesma (2019) . Supports visualization methods for images and time series and smoothing filters for dealing with noisy time series. @@ -28,10 +28,12 @@ Description: An end-to-end toolkit for land use and land cover classification temporal convolutional neural networks proposed by Pelletier et al (2019) , residual networks by Fawaz et al (2019) , and temporal attention encoders by Garnot and Landrieu (2020) . + Supports GPU processing of deep learning models using torch . Performs efficient classification of big Earth observation data cubes and includes functions for post-classification smoothing based on Bayesian inference, and - methods for uncertainty assessment. Enables best - practices for estimating area and assessing accuracy of land change as + methods for active learning and uncertainty assessment. Supports object-based + time series analysis using package supercells . + Enables best practices for estimating area and assessing accuracy of land change as recommended by Olofsson et al (2014) . Minimum recommended requirements: 16 GB RAM and 4 CPU dual-core. Encoding: UTF-8 @@ -58,7 +60,7 @@ Imports: sysfonts, slider (>= 0.2.0), stats, - terra (>= 1.7-71), + terra (>= 1.7-65), tibble (>= 3.1), tidyr (>= 1.2.0), torch (>= 0.11.0), @@ -130,7 +132,9 @@ Collate: 'api_cube.R' 'api_data.R' 'api_debug.R' + 'api_detect_changes.R' 'api_download.R' + 'api_dtw.R' 'api_environment.R' 'api_factory.R' 'api_file_info.R' @@ -146,6 +150,7 @@ Collate: 'api_mosaic.R' 'api_opensearch.R' 'api_parallel.R' + 'api_patterns.R' 'api_period.R' 'api_plot_time_series.R' 'api_plot_raster.R' @@ -215,6 +220,9 @@ Collate: 'sits_cube_copy.R' 'sits_clean.R' 'sits_cluster.R' + 'sits_detect_change.R' + 'sits_detect_change_method.R' + 'sits_dtw.R' 'sits_factory.R' 'sits_filters.R' 'sits_geo_dist.R' diff --git a/NAMESPACE b/NAMESPACE index 57ab03535..9f7e1f11d 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -241,6 +241,7 @@ S3method(.tile_ncols,raster_cube) S3method(.tile_nrows,default) S3method(.tile_nrows,raster_cube) S3method(.tile_path,default) +S3method(.tile_path,derived_cube) S3method(.tile_path,raster_cube) S3method(.tile_paths,default) S3method(.tile_paths,raster_cube) @@ -274,8 +275,6 @@ S3method(.view_add_overlay_grps,class_cube) S3method(.view_add_overlay_grps,derived_cube) S3method(.view_add_overlay_grps,raster_cube) S3method(.view_add_overlay_grps,vector_cube) -S3method(.view_adjust_palette,default) -S3method(.view_adjust_palette,sar_cube) S3method(plot,class_cube) S3method(plot,class_vector_cube) S3method(plot,geo_distances) diff --git a/NEWS.md b/NEWS.md index 5b6ba7d4b..12557665c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,17 @@ # SITS Release History # What's new in SITS version 1.5 + +### Hotfix version 1.5.0-1 +* Add multicores processing support for active learning sampling methods +* Remove tapply from `.reg_cube_split_assets()` for R 4.X compatibility +* Fix `sits_merge()` function that was not merging `SAR` and `OPTICAL` cubes +* Rename n_input_pixels back to input_pixels for compatibility with models trained in old versions of the package +* Fix torch usage in Apple M3 by turning off MPS technology +* Fix date parameter usage in `sits_view()` +* Improve `plot()` performance using raster overviews +* Include support for PLANET Mosaic product + ### New features in SITS version 1.5.0 * Support for SENTINEL-1-RTC and SENTINEL-2-L2A in CDSE * Include support for DEA products SENTINEL-1-RTC, LS5-SR, LS7-SR, LS9-SR, ALOS-PALSAR-MOSAIC, NDVI ANOMALY, DAILY CHIRPS, MONTHLY CHIRPS and DEM-30 diff --git a/R/RcppExports.R b/R/RcppExports.R index 9f2d83cc3..d126d0cc6 100644 --- a/R/RcppExports.R +++ b/R/RcppExports.R @@ -9,6 +9,10 @@ weighted_uncert_probs <- function(data_lst, unc_lst) { .Call(`_sits_weighted_uncert_probs`, data_lst, unc_lst) } +dtw_distance <- function(ts1, ts2) { + .Call(`_sits_dtw_distance`, ts1, ts2) +} + C_kernel_median <- function(x, ncols, nrows, band, window_size) { .Call(`_sits_C_kernel_median`, x, ncols, nrows, band, window_size) } diff --git a/R/api_check.R b/R/api_check.R index 4c4ba74e3..9cb19c345 100644 --- a/R/api_check.R +++ b/R/api_check.R @@ -1364,14 +1364,14 @@ #' @title Does the result have the same number of pixels as the input values? #' @name .check_processed_values #' @param values a matrix of processed values -#' @param n_input_pixels number of pixels in input matrix +#' @param input_pixels number of pixels in input matrix #' @return Called for side effects. #' @keywords internal #' @noRd -.check_processed_values <- function(values, n_input_pixels) { +.check_processed_values <- function(values, input_pixels) { .check_set_caller(".check_processed_values") .check_that( - !(is.null(nrow(values))) && nrow(values) == n_input_pixels + !(is.null(nrow(values))) && nrow(values) == input_pixels ) return(invisible(values)) } diff --git a/R/api_classify.R b/R/api_classify.R index 3eff643a1..e2abdb526 100755 --- a/R/api_classify.R +++ b/R/api_classify.R @@ -115,7 +115,7 @@ # Fill with zeros remaining NA pixels values <- C_fill_na(values, 0) # Used to check values (below) - n_input_pixels <- nrow(values) + input_pixels <- nrow(values) # Log here .debug_log( event = "start_block_data_classification", @@ -127,7 +127,7 @@ # Are the results consistent with the data input? .check_processed_values( values = values, - n_input_pixels = n_input_pixels + input_pixels = input_pixels ) # Log .debug_log( diff --git a/R/api_combine_predictions.R b/R/api_combine_predictions.R index 3bb915a8e..33d20326f 100644 --- a/R/api_combine_predictions.R +++ b/R/api_combine_predictions.R @@ -219,13 +219,13 @@ # Average probability calculation comb_fn <- function(values, uncert_values = NULL) { # Check values length - n_input_pixels <- nrow(values[[1]]) + input_pixels <- nrow(values[[1]]) # Combine by average values <- weighted_probs(values, weights) # get the number of labels n_labels <- length(sits_labels(cubes[[1]])) # Are the results consistent with the data input? - .check_processed_values(values, n_input_pixels) + .check_processed_values(values, input_pixels) .check_processed_labels(values, n_labels) # Return values values @@ -244,13 +244,13 @@ # Average probability calculation comb_fn <- function(values, uncert_values) { # Check values length - n_input_pixels <- nrow(values[[1]]) + input_pixels <- nrow(values[[1]]) # Combine by average values <- weighted_uncert_probs(values, uncert_values) # get the number of labels n_labels <- length(sits_labels(cubes[[1]])) # Are the results consistent with the data input? - .check_processed_values(values, n_input_pixels) + .check_processed_values(values, input_pixels) .check_processed_labels(values, n_labels) # Return values values diff --git a/R/api_detect_changes.R b/R/api_detect_changes.R new file mode 100644 index 000000000..0fc96a293 --- /dev/null +++ b/R/api_detect_changes.R @@ -0,0 +1,235 @@ +#' @title Detect changes in time-series using various methods. +#' @name .detect_change_ts +#' @keywords internal +#' @noRd +.detect_change_ts <- function(samples, + cd_method, + filter_fn, + multicores, + progress) { + # Start parallel workers + .parallel_start(workers = multicores) + on.exit(.parallel_stop(), add = TRUE) + # Get bands from model + bands <- .ml_bands(cd_method) + # Update samples bands order + if (any(bands != .samples_bands(samples))) { + samples <- .samples_select_bands(samples = samples, + bands = bands) + } + # Apply time series filter + if (.has(filter_fn)) { + samples <- .apply_across(data = samples, + fn = filter_fn) + } + # Divide samples in chunks to parallel processing + parts <- .pred_create_partition(pred = samples, partitions = multicores) + # Detect changes! + .jobs_map_parallel_dfr(parts, function(part) { + # Get samples + values <- .pred_part(part) + # Detect changes! For detection, models can be time-aware. So, the + # complete data, including dates, must be passed as argument. + detections <- cd_method(values[["time_series"]], "ts") + detections <- tibble::tibble(detections) + # Prepare result + result <- tibble::tibble(data.frame(values, detections = detections)) + class(result) <- class(values) + # return + result + }, progress = progress) +} + +#' @title Detect changes from a chunk of raster data using multicores +#' @name .detect_change_tile +#' @keywords internal +#' @noRd +#' @param tile Single tile of a data cube. +#' @param band Band to be produced. +#' @param cd_method Change Detection Model. +#' @param block Optimized block to be read into memory. +#' @param roi Region of interest. +#' @param filter_fn Smoothing filter function to be applied to the data. +#' @param impute_fn Imputation function. +#' @param output_dir Output directory. +#' @param version Version of result. +#' @param verbose Print processing information? +#' @param progress Show progress bar? +#' @return List of the classified raster layers. +.detect_change_tile <- function(tile, + band, + cd_method, + block, + roi, + filter_fn, + impute_fn, + output_dir, + version, + verbose, + progress) { + # Output file + out_file <- .file_derived_name( + tile = tile, + band = band, + version = version, + output_dir = output_dir + ) + # Resume feature + if (file.exists(out_file)) { + if (.check_messages()) { + .check_recovery(out_file) + } + detected_changes_tile <- .tile_derived_from_file( + file = out_file, + band = band, + base_tile = tile, + labels = .ml_labels_code(cd_method), + derived_class = "detections_cube", + update_bbox = TRUE + ) + return(detected_changes_tile) + } + # Show initial time for tile classification + tile_start_time <- .tile_classif_start( + tile = tile, + verbose = verbose + ) + # Tile timeline + tile_timeline <- .tile_timeline(tile) + # Create chunks as jobs + chunks <- .tile_chunks_create( + tile = tile, + overlap = 0, + block = block + ) + # By default, update_bbox is FALSE + update_bbox <- FALSE + if (.has(roi)) { + # How many chunks there are in tile? + nchunks <- nrow(chunks) + # Intersecting chunks with ROI + chunks <- .chunks_filter_spatial( + chunks = chunks, + roi = roi + ) + # Should bbox of resulting tile be updated? + update_bbox <- nrow(chunks) != nchunks + } + # Process jobs in parallel + block_files <- .jobs_map_parallel_chr(chunks, function(chunk) { + # Job block + block <- .block(chunk) + # Block file name + block_file <- .file_block_name( + pattern = .file_pattern(out_file), + block = block, + output_dir = output_dir + ) + # Resume processing in case of failure + if (.raster_is_valid(block_file)) { + return(block_file) + } + # Read and preprocess values + values <- .classify_data_read( + tile = tile, + block = block, + bands = .ml_bands(cd_method), + ml_model = cd_method, + impute_fn = impute_fn, + filter_fn = filter_fn + ) + # Get mask of NA pixels + na_mask <- C_mask_na(values) + # Fill with zeros remaining NA pixels + values <- C_fill_na(values, 0) + # Used to check values (below) + input_pixels <- nrow(values) + # Include names in cube predictors + colnames(values) <- .pred_features_name( + .ml_bands(cd_method), tile_timeline + ) + # Prepare values + values <- .pred_as_ts(values, .ml_bands(cd_method), tile_timeline) |> + tidyr::nest(.by = "sample_id", .key = "time_series") + # Log here + .debug_log( + event = "start_block_data_detection", + key = "model", + value = .ml_class(cd_method) + ) + # Detect changes! + values <- cd_method(values[["time_series"]], "cube") |> + dplyr::as_tibble() + # Are the results consistent with the data input? + .check_processed_values( + values = values, + input_pixels = input_pixels + ) + # Log here + .debug_log( + event = "end_block_data_detection", + key = "model", + value = .ml_class(cd_method) + ) + # Prepare probability to be saved + band_conf <- .conf_derived_band( + derived_class = "detections_cube", + band = band + ) + offset <- .offset(band_conf) + if (.has(offset) && offset != 0) { + values <- values - offset + } + scale <- .scale(band_conf) + if (.has(scale) && scale != 1) { + values <- values / scale + } + # Mask NA pixels with same probabilities for all classes + values[na_mask, ] <- 0 # event detection = 1, no event = 0 + # Log + .debug_log( + event = "start_block_data_save", + key = "file", + value = block_file + ) + # Prepare and save results as raster + .raster_write_block( + files = block_file, + block = block, + bbox = .bbox(chunk), + values = values, + data_type = .data_type(band_conf), + missing_value = .miss_value(band_conf), + crop_block = NULL + ) + # Log + .debug_log( + event = "end_block_data_save", + key = "file", + value = block_file + ) + # Free memory + gc() + # Returned block file + block_file + }, progress = progress) + # Merge blocks into a new detections_cube tile + detections_tile <- .tile_derived_merge_blocks( + file = out_file, + band = band, + labels = .ml_labels_code(cd_method), + base_tile = tile, + block_files = block_files, + derived_class = "detections_cube", + multicores = .jobs_multicores(), + update_bbox = update_bbox + ) + # show final time for detection + .tile_classif_end( + tile = tile, + start_time = tile_start_time, + verbose = verbose + ) + # Return detections tile + detections_tile +} diff --git a/R/api_dtw.R b/R/api_dtw.R new file mode 100644 index 000000000..b94b243f3 --- /dev/null +++ b/R/api_dtw.R @@ -0,0 +1,128 @@ +# ---- Distances ---- +#' @title Calculate the DTW distance between temporal patterns and time-series. +#' @name .dtw_distance +#' @description This function calculates the DTW distance between label patterns +#' and real data (e.g., sample data, data cube data). The distance is calculated +#' without a window. It's use is recommended for big datasets. +#' @keywords internal +#' @noRd +.dtw_distance <- function(data, patterns) { + # Prepare input data + data <- as.matrix(.ts_values(data)) + # Calculate the DTW distance between `data` and `patterns` + purrr::map_dfc(patterns, function(pattern) { + # Prepare pattern data + pattern_ts <- as.matrix(.ts_values(pattern)) + # Calculate distance + stats::setNames( + data.frame(distance = dtw_distance(data, pattern_ts)), + pattern[["label"]][[1]] + ) + }) +} +#' @title Calculate the DTW distance between temporal patterns and time-series. +#' @name .dtw_distance_windowed +#' @description This function calculates the DTW distance between label patterns +#' and real data (e.g., sample data, data cube data). The distance is calculated +#' using windows. +#' @keywords internal +#' @noRd +.dtw_distance_windowed <- function(data, patterns, windows) { + # Calculate the DTW distance between `data` and `patterns` + purrr::map_dfc(patterns, function(pattern) { + # Get pattern data + pattern_ts <- as.matrix(.ts_values(pattern)) + # Windowed search + distances <- purrr::map_df(windows, function(window) { + # Get time-series in the window + data_in_window <- + dplyr::filter(data, + .data[["Index"]] >= window[["start"]], + .data[["Index"]] <= window[["end"]]) + # Remove the time reference column + data_in_window <- as.matrix(.ts_values(data_in_window)) + # Calculate distance + data.frame(distance = dtw_distance(data_in_window, pattern_ts)) + }) + # Associate the pattern name with the distances + stats::setNames(distances, pattern[["label"]][[1]]) + }) +} +# ---- Operation mode ---- +#' @title Search for events in time series using complete data (no windowing). +#' @name .dtw_complete_ts +#' @description This function searches for events in time series without +#' windowing. +#' @keywords internal +#' @noRd +.dtw_complete_ts <- function(values, patterns, threshold, ...) { + # Do the change detection for each time-series + purrr::map_vec(values, function(value_row) { + # Search for the patterns + patterns_distances <- .dtw_distance( + data = value_row, + patterns = patterns + ) + # Remove distances out the user-defined threshold + as.numeric(any(patterns_distances <= threshold)) + }) +} +#' @title Search for events in time series using windowing. +#' @name .dtw_windowed_ts +#' @description This function searches for events in time series with windowing. +#' @keywords internal +#' @noRd +.dtw_windowed_ts <- function(values, patterns, window, threshold) { + # Extract dates + dates_min <- .ts_min_date(values[[1]]) + dates_max <- .ts_max_date(values[[1]]) + # Assume time-series are regularized, then use the period + # as the step of the moving window. As a result, we have + # one step per iteration. + dates_step <- lubridate::as.period( + lubridate::int_diff(.ts_index(values[[1]])) + ) + dates_step <- dates_step[[1]] + # Create comparison windows + comparison_windows <- .period_windows( + period = window, + step = dates_step, + start_date = dates_min, + end_date = dates_max + ) + # Do the change detection for each time-series + purrr::map(values, function(value_row) { + # Search for the patterns + patterns_distances <- .dtw_distance_windowed( + data = value_row, + patterns = patterns, + windows = comparison_windows + ) + # Remove distances out the user-defined threshold + patterns_distances[patterns_distances > threshold] <- NA + # Define where each label was detected. For this, first + # get from each label the minimal distance + detections_idx <- apply(patterns_distances, 2, which.min) + detections_name <- names(detections_idx) + # For each label, extract the metadata where they had + # minimal distance + purrr::map_df(seq_len(length(detections_idx)), function(idx) { + # Extract detection name and inced + detection_name <- detections_name[idx] + detection_idx <- detections_idx[idx] + # Extract detection distance (min one defined above) + detection_distance <- patterns_distances[detection_idx,] + detection_distance <- detection_distance[detection_name] + detection_distance <- as.numeric(detection_distance) + # Extract detection dates + detection_dates <- comparison_windows[[detection_idx]] + # Prepare result and return it! + tibble::tibble( + change = detection_name, + distance = detection_distance, + from = detection_dates[["start"]], + to = detection_dates[["end"]] + ) + }) + }) +} diff --git a/R/api_gdal.R b/R/api_gdal.R index 44ae413e2..a462623b3 100644 --- a/R/api_gdal.R +++ b/R/api_gdal.R @@ -92,22 +92,30 @@ ) return(invisible(file)) } -#' @title Run gdal_warp for SAR GRD files +#' @title Run gdal_warp_file #' @noRd #' @param raster_file File to be copied from (with path) -#' @param size Size of output file +#' @param sizes Sizes of output file +#' @param t_srs Target spatial reference system #' @returns Name of output file -.gdal_warp_grd <- function(raster_file, size) { +.gdal_warp_file <- function(raster_file, sizes, t_srs = NULL) { + # create a temporary file temp_file <- tempfile(fileext = ".tif") + # basic parameters + params = list( + "-ts" = list(sizes[["xsize"]], sizes[["ysize"]]), + "-multi" = FALSE, + "-q" = TRUE, + "-overwrite" = FALSE + ) + # additional param for target SRS + if (.has(t_srs)) + params <- append(params, c("t_srs" = t_srs)) + # warp the data .gdal_warp( file = temp_file, base_files = raster_file, - params = list( - "-ts" = list(size[["xsize"]], size[["ysize"]]), - "-multi" = FALSE, - "-q" = TRUE, - "-overwrite" = FALSE - ), + params = params, quiet = TRUE) return(temp_file) } @@ -156,7 +164,7 @@ ), params = list( "-ot" = data_type, - "-of" = .conf("gdal_presets", "block", "of"), + "-of" = .conf("gdal_presets", "image", "of"), "-b" = rep(1, nlayers), "-outsize" = list(.ncols(block), .nrows(block)), "-scale" = list(0, 1, miss_value, miss_value), @@ -165,7 +173,7 @@ .xmin(bbox), .ymax(bbox), .xmax(bbox), .ymin(bbox) ), "-a_nodata" = miss_value, - "-co" = .conf("gdal_presets", "block", "co") + "-co" = .conf("gdal_presets", "image", "co") ), quiet = TRUE ) diff --git a/R/api_label_class.R b/R/api_label_class.R index 5327df35a..fadb3fa86 100644 --- a/R/api_label_class.R +++ b/R/api_label_class.R @@ -148,10 +148,10 @@ .label_fn_majority <- function() { label_fn <- function(values) { # Used to check values (below) - n_input_pixels <- nrow(values) + input_pixels <- nrow(values) values <- C_label_max_prob(values) # Are the results consistent with the data input? - .check_processed_values(values, n_input_pixels) + .check_processed_values(values, input_pixels) # Return values values } diff --git a/R/api_mixture_model.R b/R/api_mixture_model.R index 91de3d077..aec2630f7 100644 --- a/R/api_mixture_model.R +++ b/R/api_mixture_model.R @@ -160,7 +160,7 @@ em_mtx <- .endmembers_as_matrix(em) mixture_fn <- function(values) { # Check values length - n_input_pixels <- nrow(values) + input_pixels <- nrow(values) # Process NNLS solver and return values <- C_nnls_solver_batch( x = as.matrix(values), @@ -168,7 +168,7 @@ rmse = rmse ) # Are the results consistent with the data input? - .check_processed_values(values, n_input_pixels) + .check_processed_values(values, input_pixels) # Return values values } diff --git a/R/api_patterns.R b/R/api_patterns.R new file mode 100644 index 000000000..721eb393a --- /dev/null +++ b/R/api_patterns.R @@ -0,0 +1,20 @@ + +#' @title Extract temporal pattern from samples data. +#' @name .pattern_temporal_median +#' @keywords internal +#' @noRd +#' @param samples Samples data. +.pattern_temporal_median <- function(samples) { + samples |> + dplyr::group_by(.data[["label"]]) |> + dplyr::group_map(function(data, name) { + ts_median <- dplyr::bind_rows(data[["time_series"]]) |> + dplyr::group_by(.data[["Index"]]) |> + dplyr::summarize(dplyr::across(dplyr::everything(), + stats::median, na.rm = TRUE)) |> + dplyr::select(-.data[["Index"]]) + + ts_median["label"] <- name + ts_median + }) +} diff --git a/R/api_period.R b/R/api_period.R index e44cae447..0cf94d99a 100644 --- a/R/api_period.R +++ b/R/api_period.R @@ -47,3 +47,37 @@ NULL unit <- c(D = "day", M = "month", Y = "year") unit[[gsub("^P[0-9]+([DMY])$", "\\1", period)]] } + +#' @describeIn period_api Create period windows. +#' @returns \code{.period_windows()}: Period windows. +#' @noRd +.period_windows <- function(period, step, start_date, end_date) { + # Transform `period` and `step` strings in duration + period_duration <- lubridate::as.duration(period) + step_duration <- lubridate::as.duration(step) + # Transform `start_date` and `end_date` to date + start_date <- as.Date(start_date) + end_date <- as.Date(end_date) + # Final period windows + period_windows <- list() + # Define first time period (used as part of the step) + current_start <- start_date + # Create period windows + while(current_start < end_date) { + # Create the window: current start date + step + current_end <- current_start + period_duration + # Avoid window definition beyond the end date + if (current_end > end_date) { + current_end <- end_date + } + # Save period window + period_windows <- + c(period_windows, list(c( + start = as.Date(current_start), + end = as.Date(current_end) + ))) + # Move to the next window date: current start date + step + current_start <- current_start + step_duration + } + period_windows +} diff --git a/R/api_plot_raster.R b/R/api_plot_raster.R index 783aae059..0ba6e9a37 100644 --- a/R/api_plot_raster.R +++ b/R/api_plot_raster.R @@ -1,5 +1,5 @@ #' @title Plot a false color image -#' @name .plot_raster.false_color +#' @name .plot_false_color #' @author Gilberto Camara, \email{gilberto.camara@@inpe.br} #' @description plots a set of false color image #' @keywords internal @@ -11,14 +11,11 @@ #' @param seg_color Color to use for segment borders #' @param line_width Line width to plot the segments boundary #' @param palette A sequential RColorBrewer palette -#' @param style Method to process the color scale -#' ("cont", "order", "quantile", "fisher", -#' "jenks", "log10") -#' @param n_colors Number of colors to be plotted +#' @param main_title Main title for the plot #' @param rev Reverse the color palette? #' @param scale Scale to plot map (0.4 to 1.0) -#' -#' @return A plot object +#' @param style Style for plotting continuous data +#' @return A list of plot objects .plot_false_color <- function(tile, band, date, @@ -26,59 +23,51 @@ seg_color, line_width, palette, - style, - n_colors, + main_title, rev, - scale) { - # verifies if stars package is installed - .check_require_packages("stars") - # verifies if tmap package is installed - .check_require_packages("tmap") - # check palette - .check_palette(palette) - # check style - .check_chr_within( - style, - within = .conf("tmap_continuous_style"), - discriminator = "any_of" - ) - # check number of colors - .check_int_parameter(n_colors, min = 4) - # check rev - .check_lgl_parameter(rev) - # check scale parameter - .check_num_parameter(scale, min = 0.2) - # check SAR Cube - palette <- .view_adjust_palette(tile, palette) - # reverse the color palette? - if (rev) - palette <- paste0("-", palette) + scale, + style) { # select the file to be plotted bw_file <- .tile_path(tile, band, date) # size of data to be read - max_size <- .conf("plot_max_size") + max_size <- .conf("view_max_size") sizes <- .tile_overview_size(tile = tile, max_size) - # used for SAR images without tiling system - if (tile[["tile"]] == "NoTilingSystem") { - bw_file <- .gdal_warp_grd(bw_file, sizes) - } - # read file - stars_obj <- stars::read_stars( - bw_file, - RasterIO = list( - nBufXSize = sizes[["xsize"]], - nBufYSize = sizes[["ysize"]] - ), - proxy = FALSE - ) - - # rescale the stars object - band_conf <- .tile_band_conf(tile = tile, band = band) + # scale and offset + band_conf <- .tile_band_conf(tile, band) band_scale <- .scale(band_conf) band_offset <- .offset(band_conf) - stars_obj <- stars_obj * band_scale + band_offset - stars_obj <- stars_obj[stars_obj <= 1.0] + max_value <- .max_value(band_conf) + # retrieve the overview if COG + bw_file <- .gdal_warp_file(bw_file, sizes) + # open the file in terra + rast <- terra::rast(bw_file) + # apply scale and offset + rast <- rast * band_scale + band_offset + # extract the values + vals <- terra::values(rast) + # obtain the quantiles + quantiles <- stats::quantile( + vals, + probs = c(0, 0.02, 0.98, 1), + na.rm = TRUE + ) + minv <- quantiles[[1]] + minq <- quantiles[[2]] + maxq <- quantiles[[3]] + maxv <- quantiles[[4]] + # get the full range of values + range <- maxv - minv + # get the range btw 2% and 98% + rangeq <- maxq - minq + # calculate the stretch factor + stretch <- rangeq / range + # stretch the image + rast <- stretch * (rast - minv) + minq + # readjust palette (bug in tmap?) + if (minq < 0.0) { + palette <- sub("-","",palette) + } # tmap params labels_size <- as.numeric(.conf("tmap", "graticules_labels_size")) @@ -88,33 +77,216 @@ legend_text_size <- as.numeric(.conf("tmap", "legend_text_size")) # generate plot - p <- suppressMessages( - tmap::tm_shape(stars_obj, raster.downsample = FALSE) + - tmap::tm_raster( - style = style, - n = n_colors, - palette = palette, - title = band, - midpoint = NA, - style.args = list(na.rm = TRUE) - ) + - tmap::tm_graticules( - labels.size = labels_size - ) + - tmap::tm_compass() + - tmap::tm_layout( - scale = scale, - legend.bg.color = legend_bg_color, - legend.bg.alpha = legend_bg_alpha, - legend.title.size = legend_title_size, - legend.text.size = legend_text_size - ) + p <- tmap::tm_shape(rast, raster.downsample = FALSE) + + tmap::tm_raster( + palette = palette, + title = band, + style = style, + style.args = list(na.rm = TRUE) + ) + + tmap::tm_graticules( + labels.size = labels_size + ) + + tmap::tm_compass() + + tmap::tm_layout( + main.title = main_title, + main.title.size = 1, + main.title.position = "center", + legend.bg.color = legend_bg_color, + legend.bg.alpha = legend_bg_alpha, + legend.title.size = legend_title_size, + legend.text.size = legend_text_size, + scale = scale + ) + # include segments + if (.has(sf_seg)) { + p <- p + tmap::tm_shape(sf_seg) + + tmap::tm_borders(col = seg_color, lwd = line_width) + } + return(p) + +} + +#' @title Plot a multi-date band as RGB +#' @name .plot_band_multidate +#' @author Gilberto Camara, \email{gilberto.camara@@inpe.br} +#' @description plots a set of false color image +#' @keywords internal +#' @noRd +#' @param tile Tile to be plotted. +#' @param band Band to be plotted. +#' @param dates Dates to be plotted. +#' @param palette A sequential RColorBrewer palette +#' @param main_title Main title for the plot +#' @param rev Reverse the color palette? +#' @param scale Scale to plot map (0.4 to 1.0) +#' +#' @return A list of plot objects +#' +.plot_band_multidate <- function(tile, + band, + dates, + palette, + main_title, + rev, + scale) { + # select the files to be plotted + red_file <- .tile_path(tile, band, dates[[1]]) + green_file <- .tile_path(tile, band, dates[[2]]) + blue_file <- .tile_path(tile, band, dates[[3]]) + # size of data to be read + max_size <- .conf("plot_max_size") + sizes <- .tile_overview_size(tile = tile, max_size) + # get the max values + band_params <- .tile_band_conf(tile, band) + max_value <- .max_value(band_params) + # used for SAR images without tiling system + if (tile[["tile"]] == "NoTilingSystem") { + red_file <- .gdal_warp_file(red_file, sizes) + green_file <- .gdal_warp_file(green_file, sizes) + blue_file <- .gdal_warp_file(blue_file, sizes) + } + # plot multitemporal band as RGB + p <- .plot_rgb_stars( + red_file = red_file, + green_file = green_file, + blue_file = blue_file, + sizes = sizes, + max_value = max_value, + main_title = main_title, + sf_seg = NULL, + seg_color = NULL, + line_width = NULL, + scale = scale + ) + return(p) +} +#' @title Plot a RGB image +#' @name .plot_rgb +#' @author Gilberto Camara, \email{gilberto.camara@@inpe.br} +#' @keywords internal +#' @noRd +#' @param tile Tile to be plotted +#' @param red Band to be plotted in red +#' @param green Band to be plotted in green +#' @param blue Band to be plotted in blue +#' @param date Date to be plotted +#' @param main_title Main title for the plot +#' @param sf_seg Segments (sf object) +#' @param seg_color Color to use for segment borders +#' @param line_width Line width to plot the segments boundary +#' @param scale Scale to plot map (0.4 to 1.0) +#' @return A plot object +#' +.plot_rgb <- function(tile, + red, + green, + blue, + date, + main_title, + sf_seg, + seg_color, + line_width, + scale) { + # get RGB files for the requested timeline + red_file <- .tile_path(tile, red, date) + green_file <- .tile_path(tile, green, date) + blue_file <- .tile_path(tile, blue, date) + + # get the max values + band_params <- .tile_band_conf(tile, red) + max_value <- .max_value(band_params) + # size of data to be read + max_size <- .conf("plot_max_size") + sizes <- .tile_overview_size(tile = tile, max_size) + # used for SAR images + if (tile[["tile"]] == "NoTilingSystem") { + red_file <- .gdal_warp_file(red_file, sizes) + green_file <- .gdal_warp_file(green_file, sizes) + blue_file <- .gdal_warp_file(blue_file, sizes) + } + p <- .plot_rgb_stars( + red_file = red_file, + green_file = green_file, + blue_file = blue_file, + sizes = sizes, + max_value = max_value, + main_title = main_title, + sf_seg = sf_seg, + seg_color = seg_color, + line_width = line_width, + scale = scale ) + return(p) +} +#' @title Plot a RGB image using stars and tmap +#' @name .plot_rgb_stars +#' @author Gilberto Camara, \email{gilberto.camara@@inpe.br} +#' @keywords internal +#' @noRd +#' @param red_file File to be plotted in red +#' @param green_file File to be plotted in green +#' @param blue_file File to be plotted in blue +#' @param sizes Image sizes for overview +#' @param max_value Maximum value +#' @param main_title Main title +#' @param sf_seg Segments (sf object) +#' @param seg_color Color to use for segment borders +#' @param line_width Line width to plot the segments boundary +#' @param scale Scale to plot map (0.4 to 1.0) +#' @return A plot object +#' +.plot_rgb_stars <- function(red_file, + green_file, + blue_file, + sizes, + max_value, + main_title, + sf_seg, + seg_color, + line_width, + scale) { + + # read raster data as a stars object with separate RGB bands + rgb_st <- stars::read_stars( + c(red_file, green_file, blue_file), + along = "band", + RasterIO = list( + nBufXSize = sizes[["xsize"]], + nBufYSize = sizes[["ysize"]] + ), + proxy = FALSE + ) + # open RGB stars + rgb_st <- stars::st_rgb(rgb_st[, , , 1:3], + dimension = "band", + maxColorValue = max_value, + use_alpha = FALSE, + probs = c(0.05, 0.95), + stretch = TRUE + ) + # tmap params + labels_size <- as.numeric(.conf("tmap", "graticules_labels_size")) + + p <- tmap::tm_shape(rgb_st, raster.downsample = FALSE) + + tmap::tm_raster() + + tmap::tm_graticules( + labels.size = labels_size + ) + + tmap::tm_layout( + main.title = main_title, + main.title.size = 1, + main.title.position = "center", + scale = scale + ) + + tmap::tm_compass() + # include segments if (.has(sf_seg)) { p <- p + tmap::tm_shape(sf_seg) + tmap::tm_borders(col = seg_color, lwd = line_width) } + return(p) } #' @title Plot a classified image @@ -196,87 +368,6 @@ ) return(p) } -#' @title Plot a RGB image -#' @name .plot_rgb -#' @author Gilberto Camara, \email{gilberto.camara@@inpe.br} -#' @keywords internal -#' @noRd -#' @param tile Tile to be plotted -#' @param red Band to be plotted in red -#' @param green Band to be plotted in green -#' @param blue Band to be plotted in blue -#' @param date Date to be plotted -#' @param sf_seg Segments (sf object) -#' @param seg_color Color to use for segment borders -#' @param line_width Line width to plot the segments boundary -#' @return A plot object -#' -.plot_rgb <- function(tile, - red, - green, - blue, - date, - sf_seg = NULL, - seg_color = NULL, - line_width = 0.2) { - # verifies if stars package is installed - .check_require_packages("stars") - # verifies if tmap package is installed - .check_require_packages("tmap") - - # get RGB files for the requested timeline - red_file <- .tile_path(tile, red, date) - green_file <- .tile_path(tile, green, date) - blue_file <- .tile_path(tile, blue, date) - - # size of data to be read - max_size <- .conf("plot_max_size") - sizes <- .tile_overview_size(tile = tile, max_size) - # used for SAR images - if (tile[["tile"]] == "NoTilingSystem") { - red_file <- .gdal_warp_grd(red_file, sizes) - green_file <- .gdal_warp_grd(green_file, sizes) - blue_file <- .gdal_warp_grd(blue_file, sizes) - } - # read raster data as a stars object with separate RGB bands - rgb_st <- stars::read_stars( - c(red_file, green_file, blue_file), - along = "band", - RasterIO = list( - nBufXSize = sizes[["xsize"]], - nBufYSize = sizes[["ysize"]] - ), - proxy = FALSE - ) - # get the max values - band_params <- .tile_band_conf(tile, red) - max_value <- .max_value(band_params) - - rgb_st <- stars::st_rgb(rgb_st[, , , 1:3], - dimension = "band", - maxColorValue = max_value, - use_alpha = FALSE, - probs = c(0.05, 0.95), - stretch = TRUE - ) - # tmap params - labels_size <- as.numeric(.conf("tmap", "graticules_labels_size")) - - p <- tmap::tm_shape(rgb_st, raster.downsample = FALSE) + - tmap::tm_raster() + - tmap::tm_graticules( - labels.size = labels_size - ) + - tmap::tm_compass() - - # include segments - if (.has(sf_seg)) { - p <- p + tmap::tm_shape(sf_seg) + - tmap::tm_borders(col = seg_color, lwd = line_width) - } - - return(p) -} #' @title Plot probs #' @name .plot_probs #' @author Gilberto Camara, \email{gilberto.camara@@inpe.br} @@ -285,10 +376,6 @@ #' @param tile Probs cube to be plotted. #' @param labels_plot Labels to be plotted #' @param palette A sequential RColorBrewer palette -#' @param style Method to process the color scale -#' ("cont", "order", "quantile", "fisher", -#' "jenks", "log10") -#' @param n_colors Number of colors to be shown #' @param rev Reverse the color palette? #' @param scale Global scale for plot #' @return A plot object @@ -296,8 +383,6 @@ .plot_probs <- function(tile, labels_plot, palette, - style, - n_colors, rev, scale) { # set caller to show in errors @@ -355,9 +440,8 @@ p <- tmap::tm_shape(probs_st[, , , bds]) + tmap::tm_raster( - style = style, + style = "cont", palette = palette, - n = n_colors, midpoint = NA, title = labels[labels %in% labels_plot] ) + diff --git a/R/api_plot_time_series.R b/R/api_plot_time_series.R index f013e5b6c..e44841df8 100644 --- a/R/api_plot_time_series.R +++ b/R/api_plot_time_series.R @@ -161,8 +161,7 @@ group = .data[["variable"]] )) + ggplot2::geom_line(ggplot2::aes(color = .data[["variable"]])) + - ggplot2::labs(title = plot_title) + - ggplot2::scale_fill_manual(palette = colors) + ggplot2::labs(title = plot_title) return(g) } #' @title Plot one time series with NAs using ggplot diff --git a/R/api_plot_vector.R b/R/api_plot_vector.R index 39efc4498..509005639 100644 --- a/R/api_plot_vector.R +++ b/R/api_plot_vector.R @@ -136,9 +136,7 @@ #' @noRd #' @param tile Tile to be plotted. #' @param palette A sequential RColorBrewer palette -#' @param style Method to process the color scale -#' ("cont", "order", "quantile", "fisher", -#' "jenks", "log10") +#' @param main_title Main title for the cube #' @param rev Revert the color of the palette? #' @param scale Global map scale #' @@ -146,7 +144,7 @@ #' .plot_uncertainty_vector <- function(tile, palette, - style, + main_title, rev, scale) { # verifies if stars package is installed @@ -167,12 +165,15 @@ p <- tmap::tm_shape(sf_seg) + tmap::tm_polygons(uncert_type, palette = palette, - style = style) + + style = "cont") + tmap::tm_graticules( labels.size = as.numeric(.conf("tmap", "graticules_labels_size")) ) + tmap::tm_compass() + tmap::tm_layout( + main.title = main_title, + main.title.size = 1, + main.title.position = "center", scale = scale, legend.bg.color = .conf("tmap", "legend_bg_color"), legend.bg.alpha = as.numeric(.conf("tmap", "legend_bg_alpha")) diff --git a/R/api_predictors.R b/R/api_predictors.R index d0912cbdb..31d945476 100644 --- a/R/api_predictors.R +++ b/R/api_predictors.R @@ -149,6 +149,31 @@ dplyr::ungroup() return(frac) } +#' @title Convert predictors to ts +#' @keywords internal +#' @noRd +#' @param data Predictor data to be converted. +#' @param bands Name of the bands available in the given predictor data. +#' @param timeline Timeline of the predictor data. +#' @return Predictor data as ts. +.pred_as_ts <- function(data, bands, timeline) { + data |> + dplyr::as_tibble() |> + tidyr::pivot_longer( + cols = dplyr::everything(), + cols_vary = "fastest", + names_to = ".value", + names_pattern = paste0( + "^(", paste(bands, collapse = "|"), ")" + ) + ) |> + dplyr::mutate( + sample_id = rep( seq_len(nrow(data)), each = dplyr::n() / nrow(data) ), + label = "NoClass", + Index = rep(timeline, nrow(data)), + .before = 1 + ) +} # ---- Partitions ---- #' @title Get predictors of a given partition #' @keywords internal @@ -157,3 +182,6 @@ .pred_part <- function(part) { .default(part[["predictors"]][[1]]) } + + + diff --git a/R/api_raster.R b/R/api_raster.R index de8f2282b..0169f9c75 100644 --- a/R/api_raster.R +++ b/R/api_raster.R @@ -168,6 +168,7 @@ #' locations are guaranteed to be separated by a certain number of pixels. #' #' @param r_obj A raster object. +#' @param block Individual block that will be processed. #' @param band A numeric band index used to read bricks. #' @param n Number of values to extract. #' @param sampling_window Window size to collect a point (in pixels). @@ -175,6 +176,7 @@ #' @return A point `tibble` object. #' .raster_get_top_values <- function(r_obj, + block, band, n, sampling_window) { @@ -182,18 +184,24 @@ # Get top values # filter by median to avoid borders # Process window - values <- .raster_get_values(r_obj) + values <- .raster_get_values( + r_obj, + row = block[["row"]], + col = block[["col"]], + nrows = block[["nrows"]], + ncols = block[["ncols"]] + ) values <- C_kernel_median( x = values, - ncols = .raster_ncols(r_obj), - nrows = .raster_nrows(r_obj), + nrows = block[["nrows"]], + ncols = block[["ncols"]], band = 0, window_size = sampling_window ) samples_tb <- C_max_sampling( x = values, - nrows = .raster_nrows(r_obj), - ncols = .raster_ncols(r_obj), + nrows = block[["nrows"]], + ncols = block[["ncols"]], window_size = sampling_window ) samples_tb <- dplyr::slice_max( diff --git a/R/api_reclassify.R b/R/api_reclassify.R index bec3ba41b..cbe40ea9f 100644 --- a/R/api_reclassify.R +++ b/R/api_reclassify.R @@ -158,7 +158,7 @@ stop(.conf("messages", ".reclassify_fn_cube_mask")) } # Used to check values (below) - n_input_pixels <- nrow(values) + input_pixels <- nrow(values) # Convert to character vector values <- as.character(values) mask_values <- as.character(mask_values) @@ -185,12 +185,12 @@ # Get values as numeric values <- matrix( data = labels_code[match(values, labels)], - nrow = n_input_pixels + nrow = input_pixels ) # Mask NA values values[is.na(env[["mask"]])] <- NA # Are the results consistent with the data input? - .check_processed_values(values, n_input_pixels) + .check_processed_values(values, input_pixels) # Return values values } diff --git a/R/api_regularize.R b/R/api_regularize.R index 6f6eb6d62..068ec1622 100644 --- a/R/api_regularize.R +++ b/R/api_regularize.R @@ -42,7 +42,7 @@ breaks = timeline, labels = FALSE ) - fi_groups <- unname(tapply(fi, groups, list)) + fi_groups <- unname(split(fi, groups)) assets <- .common_size( .discard(tile, "file_info"), feature = timeline[unique(groups)], diff --git a/R/api_smooth.R b/R/api_smooth.R index 3e42d8a6a..d30d924f4 100644 --- a/R/api_smooth.R +++ b/R/api_smooth.R @@ -172,7 +172,7 @@ # Define smooth function smooth_fn <- function(values, block) { # Check values length - n_input_pixels <- nrow(values) + input_pixels <- nrow(values) # Compute logit values <- log(values / (rowSums(values) - values)) # Process Bayesian @@ -187,7 +187,7 @@ # Compute inverse logit values <- exp(values) / (exp(values) + 1) # Are the results consistent with the data input? - .check_processed_values(values, n_input_pixels) + .check_processed_values(values, input_pixels) # Return values values } diff --git a/R/api_tile.R b/R/api_tile.R index fc93f2a6e..b653f8e41 100644 --- a/R/api_tile.R +++ b/R/api_tile.R @@ -412,6 +412,17 @@ NULL path } #' @export +.tile_path.derived_cube <- function(tile, band = NULL, date = NULL) { + tile <- .tile(tile) + if (.has(band)) { + tile <- .tile_filter_bands(tile = tile, bands = band[[1]]) + } + # Get path of first asset + path <- .fi_path(.fi(tile)) + # Return path + path +} +#' @export .tile_path.default <- function(tile, band = NULL, date = NULL) { tile <- tibble::as_tibble(tile) tile <- .cube_find_class(tile) diff --git a/R/api_uncertainty.R b/R/api_uncertainty.R index 6cecad65a..1dc622b18 100644 --- a/R/api_uncertainty.R +++ b/R/api_uncertainty.R @@ -239,12 +239,12 @@ # Define uncertainty function uncert_fn <- function(values) { # Used in check (below) - n_input_pixels <- nrow(values) + input_pixels <- nrow(values) # Process least confidence # return a matrix[rows(values),1] values <- C_least_probs(values) # Are the results consistent with the data input? - .check_processed_values(values, n_input_pixels) + .check_processed_values(values, input_pixels) # Return data values } @@ -260,11 +260,11 @@ # Define uncertainty function uncert_fn <- function(values) { # Used in check (below) - n_input_pixels <- nrow(values) + input_pixels <- nrow(values) # Process least confidence values <- C_entropy_probs(values) # return a matrix[rows(values),1] # Are the results consistent with the data input? - .check_processed_values(values, n_input_pixels) + .check_processed_values(values, input_pixels) # Return data values } @@ -280,11 +280,11 @@ # Define uncertainty function uncert_fn <- function(values) { # Used in check (below) - n_input_pixels <- nrow(values) + input_pixels <- nrow(values) # Process margin values <- C_margin_probs(values) # return a matrix[rows(data),1] # Are the results consistent with the data input? - .check_processed_values(values, n_input_pixels) + .check_processed_values(values, input_pixels) # Return data values } diff --git a/R/api_variance.R b/R/api_variance.R index 63d13f92d..99e390ba3 100644 --- a/R/api_variance.R +++ b/R/api_variance.R @@ -176,7 +176,7 @@ # Define smooth function smooth_fn <- function(values, block) { # Check values length - n_input_pixels <- nrow(values) + input_pixels <- nrow(values) # Compute logit values <- log(values / (rowSums(values) - values)) # Process variance @@ -188,7 +188,7 @@ neigh_fraction = neigh_fraction ) # Are the results consistent with the data input? - .check_processed_values(values, n_input_pixels) + .check_processed_values(values, input_pixels) # Return values values } diff --git a/R/api_view.R b/R/api_view.R index c2d0242e9..729e7c796 100644 --- a/R/api_view.R +++ b/R/api_view.R @@ -32,15 +32,8 @@ cube <- .view_filter_tiles(cube, tiles) # get the dates dates <- .view_set_dates(cube, dates) - # find out if resampling is required (for big images) - output_size <- .view_resample_size( - cube = cube, - ndates = max(length(dates), 1) - ) # create a leaflet and add providers - leaf_map <- .view_add_basic_maps() - # get names of basic maps - base_maps <- .view_get_base_maps(leaf_map) + leaf_map <- .view_add_base_maps() # add B/W band if required # create a leaflet for B/W bands if (.has(band)) { @@ -49,7 +42,6 @@ cube = cube, band = band, dates = dates, - output_size = output_size, palette = palette ) } else { @@ -61,8 +53,7 @@ red = red, green = green, blue = blue, - dates = dates, - output_size = output_size + dates = dates ) } # include class cube if available @@ -72,8 +63,7 @@ tiles = tiles, legend = legend, palette = palette, - opacity = opacity, - output_size = output_size + opacity = opacity ) # get overlay groups overlay_groups <- .view_add_overlay_grps( @@ -84,9 +74,12 @@ # add layers control to leafmap leaf_map <- leaf_map |> leaflet::addLayersControl( - baseGroups = base_maps, + baseGroups = c("ESRI", "GeoPortalFrance", + "Sentinel-2-2020", "OSM"), overlayGroups = overlay_groups, - options = leaflet::layersControlOptions(collapsed = FALSE) + options = leaflet::layersControlOptions( + collapsed = FALSE, + autoZIndex = TRUE) ) |> # add legend to leaf_map .view_add_legend( @@ -133,15 +126,8 @@ line_width) { # filter the tiles to be processed cube <- .view_filter_tiles(cube, tiles) - # find out if resampling is required (for big images) - output_size <- .view_resample_size( - cube = cube, - ndates = max(length(dates), 1) - ) # create a leaflet and add providers - leaf_map <- .view_add_basic_maps() - # get names of basic maps - base_maps <- .view_get_base_maps(leaf_map) + leaf_map <- .view_add_base_maps() # add B/W band if required # create a leaflet for B/W bands if (.has(band)) { @@ -152,7 +138,6 @@ cube = cube, band = band, dates = dates, - output_size = output_size, palette = palette ) } @@ -168,8 +153,7 @@ red = red, green = green, blue = blue, - dates = dates, - output_size = output_size + dates = dates ) } # include segments (and class cube if available) @@ -188,8 +172,7 @@ tiles = tiles, legend = legend, palette = palette, - opacity = opacity, - output_size = output_size + opacity = opacity ) # have we included base images? @@ -202,7 +185,8 @@ # add layers control to leafmap leaf_map <- leaf_map |> leaflet::addLayersControl( - baseGroups = base_maps, + baseGroups = c("ESRI", "GeoPortalFrance", + "Sentinel-2-2020", "OSM"), overlayGroups = overlay_groups, options = leaflet::layersControlOptions(collapsed = FALSE) ) |> @@ -214,34 +198,6 @@ ) return(leaf_map) } -#' @title Return the size of the imaged to be resamples for visulization -#' @name .view_resample_size -#' @keywords internal -#' @noRd -#' @author Gilberto Camara, \email{gilberto.camara@@inpe.br} -#' -#' @param cube Cube with tiles to be merged. -#' @param ndates Number of dates to be viewed. -#' @return Number of rows and cols to be visualized. -#' -#' -.view_resample_size <- function(cube, ndates) { - # check number of tiles - ntiles <- nrow(cube) - # get the compression factor - comp <- .conf("leaflet_comp_factor") - # size of data to be read - max_size <- .conf("view_max_size") - sizes <- .tile_overview_size(tile = .tile(cube), max_size) - xsize <- sizes[["xsize"]] - ysize <- sizes[["ysize"]] - leaflet_maxbytes <- 4 * xsize * ysize * ndates * ntiles * comp - return(c( - xsize = xsize, - ysize = ysize, - leaflet_maxbytes = leaflet_maxbytes - )) -} #' @title Visualize a set of samples #' @name .view_samples #' @keywords internal @@ -295,7 +251,7 @@ domain = labels ) # create a leaflet and add providers - leaf_map <- .view_add_basic_maps() + leaf_map <- .view_add_base_maps() leaf_map <- leaf_map |> leaflet::flyToBounds( lng1 = samples_bbox[["xmin"]], @@ -312,7 +268,8 @@ group = "Samples" ) |> leaflet::addLayersControl( - baseGroups = c("ESRI", "GeoPortalFrance", "OSM"), + baseGroups = c("ESRI", "GeoPortalFrance", + "Sentinel-2-2020", "OSM"), overlayGroups = "Samples", options = leaflet::layersControlOptions(collapsed = FALSE) ) |> @@ -324,15 +281,15 @@ ) return(leaf_map) } -#' @title Create a leafmap to view basic background maps -#' @name .view_add_basic_maps +#' @title Create a leafmap to view base background maps +#' @name .view_add_base_maps #' @keywords internal #' @noRd #' @author Gilberto Camara, \email{gilberto.camara@@inpe.br} #' #' @return Leafmap with maps from providers #' -.view_add_basic_maps <- function() { +.view_add_base_maps <- function() { # create a leaflet and add providers leaf_map <- leaflet::leaflet() |> leaflet::addProviderTiles( @@ -355,31 +312,6 @@ leafem::addMouseCoordinates() return(leaf_map) } -#' @title Set the maximum megabyte value for leafmaps -#' @name .view_set_max_mb -#' @keywords internal -#' @noRd -#' @author Gilberto Camara, \email{gilberto.camara@@inpe.br} -#' @param view_max_mb Leafmap size set by user -#' @return Valid leafmap size -#' -.view_set_max_mb <- function(view_max_mb) { - # get the maximum number of bytes to be displayed (total) - if (.has_not(view_max_mb)) { - view_max_mb <- .conf("leaflet_megabytes") - } else { - .check_num( - x = view_max_mb, - is_integer = TRUE, - min = .conf("leaflet_min_megabytes"), - max = .conf("leaflet_max_megabytes"), - msg = paste(.conf("messages", ".view_set_max_mb"), - .conf("leaflet_min_megabytes"), "MB & ", - .conf("leaflet_max_megabytes"), "MB") - ) - } - return(view_max_mb) -} #' @title Include leaflet to view segments #' @name .view_segments #' @keywords internal @@ -471,18 +403,16 @@ #' @param band Band to be shown #' @param dates Dates to be plotted #' @param palette Palette to show false colors -#' @param output_size Controls size of leaflet to be visualized #' @return A leaflet object # .view_bw_band <- function(leaf_map, cube, band, dates, - palette, - output_size) { + palette) { - # adjust palette for SAR images - palette <- .view_adjust_palette(cube, palette) + # calculate maximum size in MB + max_bytes <- as.numeric(.conf("leaflet_megabytes")) * 1024^2 # obtain the raster objects for the dates chosen for (i in seq_along(dates)) { date <- as.Date(dates[[i]]) @@ -498,15 +428,72 @@ # filter by date and band band_file <- .tile_path(tile, band, date) # plot a single file - leaf_map <- .view_add_stars_image( - leaf_map = leaf_map, - band_file = band_file, - tile = tile, - band = band, - date = date, - palette = palette, - output_size = output_size + # determine size of data to be read + max_size <- .conf("view_max_size") + # find if file supports COG overviews + sizes <- .tile_overview_size(tile = tile, max_size) + band_file <- .gdal_warp_file( + raster_file = band_file, + sizes = sizes) + # create a stars object + st_obj <- stars::read_stars( + band_file, + along = "band", + RasterIO = list( + nBufXSize = sizes[["xsize"]], + nBufYSize = sizes[["ysize"]] + ), + proxy = FALSE + ) + # get scale and offset + band_conf <- .tile_band_conf(tile, band) + band_scale <- .scale(band_conf) + band_offset <- .offset(band_conf) + max_value <- .max_value(band_conf) + # scale the image + st_obj <- st_obj * band_scale + band_offset + # get the values + vals <- as.vector(st_obj[[1]]) + # obtain the quantiles + quantiles <- stats::quantile( + vals, + probs = c(0, 0.02, 0.98, 1), + na.rm = TRUE + ) + minv <- quantiles[[1]] + minq <- quantiles[[2]] + maxq <- quantiles[[3]] + maxv <- quantiles[[4]] + # get the full range of values + range <- maxv - minv + # get the range btw 2% and 98% + rangeq <- maxq - minq + # calculate the stretch factor + stretch <- rangeq / range + # stretch the image + st_obj <- stretch * (st_obj - minv) + minq + + if (.has(date)) { + group <- paste(tile[["tile"]], date) + } else { + group <- paste(tile[["tile"]], band) + } + # resample and warp the image + st_obj <- stars::st_warp( + src = st_obj, + crs = sf::st_crs("EPSG:3857") ) + # add stars to leaflet + leaf_map <- leafem::addStarsImage( + leaf_map, + x = st_obj, + band = 1, + colors = palette, + project = FALSE, + group = group, + maxBytes = max_bytes, + ) + return(leaf_map) } } return(leaf_map) @@ -523,7 +510,6 @@ #' @param green Band to be shown in green color #' @param blue Band to be shown in blue color #' @param dates Dates to be plotted -#' @param output_size Controls size of leaflet to be visualized #' @return A leaflet object # .view_rgb_bands <- function(leaf_map, @@ -531,8 +517,11 @@ red, green, blue, - dates, - output_size) { + dates) { + # determine size of data to be read + max_size <- .conf("view_max_size") + # calculate maximum size in MB + max_bytes <- as.numeric(.conf("leaflet_megabytes")) * 1024^2 # obtain the raster objects for the dates chosen for (i in seq_along(dates)) { date <- as.Date(dates[[i]]) @@ -551,40 +540,41 @@ green_file <- .tile_path(tile, green, date) blue_file <- .tile_path(tile, blue, date) - # do we need to warp the image - # used for SAR images without tiling system - if (tile[["tile"]] == "NoTilingSystem") { - red_file <- .gdal_warp_grd(red_file, output_size) - green_file <- .gdal_warp_grd(green_file, output_size) - blue_file <- .gdal_warp_grd(blue_file, output_size) - } + # find if file supports COG overviews + sizes <- .tile_overview_size(tile = tile, max_size) + # warp the image + red_file <- .gdal_warp_file(red_file, sizes) + green_file <- .gdal_warp_file(green_file, sizes) + blue_file <- .gdal_warp_file(blue_file, sizes) + # compose RGB files rgb_files <- c(r = red_file, g = green_file, b = blue_file) st_obj <- stars::read_stars( rgb_files, along = "band", RasterIO = list( - nBufXSize = output_size[["xsize"]], - nBufYSize = output_size[["ysize"]] + nBufXSize = sizes[["xsize"]], + nBufYSize = sizes[["ysize"]] ), proxy = FALSE ) # resample and warp the image - st_obj_new <- stars::st_warp( + st_obj <- stars::st_warp( src = st_obj, crs = sf::st_crs("EPSG:3857") ) # add raster RGB to leaflet + group <- paste(tile[["tile"]], date) leaf_map <- leafem::addRasterRGB( leaf_map, - x = st_obj_new, + x = st_obj, r = 1, g = 2, b = 3, quantiles = c(0.1, 0.9), project = FALSE, - group = paste(tile[["tile"]], date), - maxBytes = output_size[["leaflet_maxbytes"]] + group = group, + maxBytes = max_bytes ) } } @@ -610,8 +600,7 @@ tiles, legend, palette, - opacity, - output_size) { + opacity) { # set caller to show in errors .check_set_caller(".view_class_cube") # should we overlay a classified image? @@ -637,7 +626,10 @@ .data[["tile"]] %in% tiles ) } - + # determine size of data to be read + max_size <- .conf("view_max_size") + # find if file supports COG overviews + sizes <- .tile_overview_size(tile = class_cube, max_size) # create the stars objects that correspond to the tiles st_objs <- slider::slide(class_cube, function(tile) { # obtain the raster stars object @@ -645,8 +637,8 @@ .tile_path(tile), RAT = labels, RasterIO = list( - nBufXSize = output_size[["xsize"]], - nBufYSize = output_size[["ysize"]] + nBufXSize = sizes[["xsize"]], + nBufYSize = sizes[["ysize"]] ), proxy = FALSE ) @@ -666,6 +658,8 @@ src = st_merge, crs = sf::st_crs("EPSG:3857") ) + # calculate maximum size in MB + max_bytes <- as.numeric(.conf("leaflet_megabytes")) * 1024^2 # add the classified image object leaf_map <- leaf_map |> leafem::addStarsImage( @@ -675,54 +669,12 @@ method = "ngb", group = "classification", project = FALSE, - maxBytes = output_size[["leaflet_maxbytes"]] + maxBytes = max_bytes ) } return(leaf_map) } -.view_add_stars_image <- function(leaf_map, - band_file, - tile, - band, - date, - palette, - output_size) { - # do we need to warp the image - # used for SAR images without tiling system - if (inherits(tile, "grd_cube")) { - band_file <- .gdal_warp_grd(band_file, output_size) - } - # create a stars object - st_obj <- stars::read_stars( - band_file, - along = "band", - RasterIO = list( - nBufXSize = output_size[["xsize"]], - nBufYSize = output_size[["ysize"]] - ), - proxy = FALSE - ) - # clip the image - if (inherits(tile, "rtc_cube")) - st_obj <- st_obj[st_obj <= 1.0] - if (.has(date)) { - group <- paste(tile[["tile"]], date) - } else { - group <- paste(tile[["tile"]], band) - } - # add stars to leaflet - leaf_map <- leafem::addStarsImage( - leaf_map, - x = st_obj, - band = 1, - colors = palette, - project = TRUE, - group = group, - maxBytes = output_size[["leaflet_maxbytes"]] - ) - return(leaf_map) -} #' @title Set the dates for visualisation #' @name .view_set_dates #' @keywords internal @@ -883,7 +835,6 @@ dates = NULL, class_cube = NULL) { # set caller to show in errors - .check_set_caller(".view_add_overlay_grps_raster_cube") overlay_groups <- NULL # raster cube needs dates .check_that(.has(dates)) @@ -939,41 +890,3 @@ overlay_groups <- c(overlay_groups, "classification") return(overlay_groups) } -#' @title Add base maps to leaflet map -#' @name .view_get_base_maps -#' @keywords internal -#' @noRd -#' @author Gilberto Camara, \email{gilberto.camara@@inpe.br} -#' -#' @param leaf_map Leaflet -#' @return Base maps used in leaflet map -#' -.view_get_base_maps <- function(leaf_map) { - base_maps <- purrr::map_chr(leaf_map[["x"]][["calls"]], function(bm) { - return(bm[["args"]][[3]]) - }) - return(base_maps) -} -#' @title Adjust palette for SAR maps -#' @name .view_adjust_palette -#' @keywords internal -#' @noRd -#' @author Gilberto Camara, \email{gilberto.camara@@inpe.br} -#' -#' @param leaf_map Leaflet -#' @return Base maps used in leaflet map -#' -.view_adjust_palette <- function(cube, palette){ - UseMethod(".view_adjust_palette", cube) -} -#' @export -.view_adjust_palette.sar_cube <- function(cube, palette) { - n_grey_colors <- .conf("sar_cube_grey_colors") - rgb_vals <- log(1:n_grey_colors, n_grey_colors) - palette <- grDevices::rgb(red = rgb_vals, green = rgb_vals, blue = rgb_vals) - return(palette) -} -#' @export -.view_adjust_palette.default <- function(cube, palette) { - return(palette) -} diff --git a/R/sits_active_learning.R b/R/sits_active_learning.R index c67654503..04f3d6e8e 100644 --- a/R/sits_active_learning.R +++ b/R/sits_active_learning.R @@ -31,6 +31,10 @@ #' @param min_uncert Minimum uncertainty value to select a sample. #' @param sampling_window Window size for collecting points (in pixels). #' The minimum window size is 10. +#' @param multicores Number of workers for parallel processing +#' (integer, min = 1, max = 2048). +#' @param memsize Maximum overall memory (in GB) to run the +#' function. #' #' @return #' A tibble with longitude and latitude in WGS84 with locations @@ -75,23 +79,64 @@ sits_uncertainty_sampling <- function(uncert_cube, n = 100L, min_uncert = 0.4, - sampling_window = 10L) { + sampling_window = 10L, + multicores = 1L, + memsize = 1L) { .check_set_caller("sits_uncertainty_sampling") - # Pre-conditions .check_is_uncert_cube(uncert_cube) .check_int_parameter(n, min = 1, max = 10000) .check_num_parameter(min_uncert, min = 0.2, max = 1.0) .check_int_parameter(sampling_window, min = 10L) - + .check_int_parameter(multicores, min = 1, max = 2048) + .check_int_parameter(memsize, min = 1, max = 16384) + # Get block size + block <- .raster_file_blocksize(.raster_open_rast(.tile_path(uncert_cube))) + # Overlapping pixels + overlap <- ceiling(sampling_window / 2) - 1 + # Check minimum memory needed to process one block + job_memsize <- .jobs_memsize( + job_size = .block_size(block = block, overlap = overlap), + npaths = sampling_window, + nbytes = 8, + proc_bloat = .conf("processing_bloat_cpu") + ) + # Update multicores parameter + multicores <- .jobs_max_multicores( + job_memsize = job_memsize, + memsize = memsize, + multicores = multicores + ) + # Update block parameter + block <- .jobs_optimal_block( + job_memsize = job_memsize, + block = block, + image_size = .tile_size(.tile(uncert_cube)), + memsize = memsize, + multicores = multicores + ) + # Prepare parallel processing + .parallel_start(workers = multicores) + on.exit(.parallel_stop(), add = TRUE) # Slide on cube tiles samples_tb <- slider::slide_dfr(uncert_cube, function(tile) { - path <- .tile_path(tile) + # Create chunks as jobs + chunks <- .tile_chunks_create( + tile = tile, + overlap = overlap, + block = block + ) + # Tile path + tile_path <- .tile_path(tile) # Get a list of values of high uncertainty - top_values <- .raster_open_rast(path) |> + # Process jobs in parallel + top_values <- .jobs_map_parallel_dfr(chunks, function(chunk) { + # Read and preprocess values + .raster_open_rast(tile_path) |> .raster_get_top_values( - band = 1, - n = n, + block = .block(chunk), + band = 1, + n = n, sampling_window = sampling_window ) |> dplyr::mutate( @@ -105,6 +150,7 @@ sits_uncertainty_sampling <- function(uncert_cube, c("longitude", "latitude", "value") )) |> tibble::as_tibble() + }) # All the cube's uncertainty images have the same start & end dates. top_values[["start_date"]] <- .tile_start_date(tile) top_values[["end_date"]] <- .tile_end_date(tile) @@ -174,6 +220,10 @@ sits_uncertainty_sampling <- function(uncert_cube, #' @param min_margin Minimum margin of confidence to select a sample #' @param sampling_window Window size for collecting points (in pixels). #' The minimum window size is 10. +#' @param multicores Number of workers for parallel processing +#' (integer, min = 1, max = 2048). +#' @param memsize Maximum overall memory (in GB) to run the +#' function. #' #' @return #' A tibble with longitude and latitude in WGS84 with locations @@ -204,54 +254,92 @@ sits_uncertainty_sampling <- function(uncert_cube, sits_confidence_sampling <- function(probs_cube, n = 20L, min_margin = 0.90, - sampling_window = 10L) { + sampling_window = 10L, + multicores = 1L, + memsize = 1L) { .check_set_caller("sits_confidence_sampling") - # Pre-conditions .check_is_probs_cube(probs_cube) .check_int_parameter(n, min = 20) .check_num_parameter(min_margin, min = 0.01, max = 1.0) .check_int_parameter(sampling_window, min = 10) - + .check_int_parameter(multicores, min = 1, max = 2048) + .check_int_parameter(memsize, min = 1, max = 16384) + # Get block size + block <- .raster_file_blocksize(.raster_open_rast(.tile_path(probs_cube))) + # Overlapping pixels + overlap <- ceiling(sampling_window / 2) - 1 + # Check minimum memory needed to process one block + job_memsize <- .jobs_memsize( + job_size = .block_size(block = block, overlap = overlap), + npaths = sampling_window, + nbytes = 8, + proc_bloat = .conf("processing_bloat_cpu") + ) + # Update multicores parameter + multicores <- .jobs_max_multicores( + job_memsize = job_memsize, + memsize = memsize, + multicores = multicores + ) + # Update block parameter + block <- .jobs_optimal_block( + job_memsize = job_memsize, + block = block, + image_size = .tile_size(.tile(probs_cube)), + memsize = memsize, + multicores = multicores + ) + # Prepare parallel processing + .parallel_start(workers = multicores) + on.exit(.parallel_stop(), add = TRUE) # get labels labels <- sits_labels(probs_cube) - # Slide on cube tiles samples_tb <- slider::slide_dfr(probs_cube, function(tile) { - # Open raster - r_obj <- .raster_open_rast(.tile_path(tile)) - - # Get samples for each label - purrr::map2_dfr(labels, seq_along(labels), function(lab, i) { - # Get a list of values of high confidence & apply threshold - top_values <- r_obj |> - .raster_get_top_values( - band = i, - n = n, - sampling_window = sampling_window - ) |> - dplyr::mutate( - value = .data[["value"]] * - .conf("probs_cube_scale_factor") - ) |> - dplyr::filter( - .data[["value"]] >= min_margin - ) |> - dplyr::select(dplyr::matches( - c("longitude", "latitude", "value") - )) |> - tibble::as_tibble() + # Create chunks as jobs + chunks <- .tile_chunks_create( + tile = tile, + overlap = overlap, + block = block + ) + # Tile path + tile_path <- .tile_path(tile) + # Get a list of values of high uncertainty + # Process jobs in parallel + .jobs_map_parallel_dfr(chunks, function(chunk) { + # Get samples for each label + purrr::map2_dfr(labels, seq_along(labels), function(lab, i) { + # Get a list of values of high confidence & apply threshold + top_values <- .raster_open_rast(tile_path) |> + .raster_get_top_values( + block = .block(chunk), + band = i, + n = n, + sampling_window = sampling_window + ) |> + dplyr::mutate( + value = .data[["value"]] * + .conf("probs_cube_scale_factor") + ) |> + dplyr::filter( + .data[["value"]] >= min_margin + ) |> + dplyr::select(dplyr::matches( + c("longitude", "latitude", "value") + )) |> + tibble::as_tibble() - # All the cube's uncertainty images have the same start & - # end dates. - top_values[["start_date"]] <- .tile_start_date(tile) - top_values[["end_date"]] <- .tile_end_date(tile) - top_values[["label"]] <- lab + # All the cube's uncertainty images have the same start & + # end dates. + top_values[["start_date"]] <- .tile_start_date(tile) + top_values[["end_date"]] <- .tile_end_date(tile) + top_values[["label"]] <- lab - return(top_values) + return(top_values) + }) }) }) - # Slice result samples result_tb <- samples_tb |> dplyr::group_by(.data[["label"]]) |> diff --git a/R/sits_apply.R b/R/sits_apply.R index d86d2cb58..6665b98ed 100644 --- a/R/sits_apply.R +++ b/R/sits_apply.R @@ -128,7 +128,7 @@ sits_apply.raster_cube <- function(data, ..., .check_is_raster_cube(data) .check_that(.cube_is_regular(data)) # Check window size - .check_int_parameter(window_size, min = 3, is_odd = TRUE) + .check_int_parameter(window_size, min = 1, is_odd = TRUE) # Check normalized index .check_lgl_parameter(normalized) # Check memsize @@ -158,11 +158,10 @@ sits_apply.raster_cube <- function(data, ..., bands = bands, expr = expr ) - # Check memory and multicores - # Get block size - block <- .raster_file_blocksize(.raster_open_rast(.tile_path(data))) # Overlapping pixels overlap <- ceiling(window_size / 2) - 1 + # Get block size + block <- .raster_file_blocksize(.raster_open_rast(.tile_path(data))) # Check minimum memory needed to process one block job_memsize <- .jobs_memsize( job_size = .block_size(block = block, overlap = overlap), @@ -170,6 +169,16 @@ sits_apply.raster_cube <- function(data, ..., nbytes = 8, proc_bloat = .conf("processing_bloat_cpu") ) + # Update block parameter + block <- .jobs_optimal_block( + job_memsize = job_memsize, + block = block, + image_size = .tile_size(.tile(data)), + memsize = memsize, + multicores = multicores + ) + # adjust for blocks of size 1 + block <- .block_regulate_size(block) # Update multicores parameter multicores <- .jobs_max_multicores( job_memsize = job_memsize, diff --git a/R/sits_detect_change.R b/R/sits_detect_change.R new file mode 100644 index 000000000..9bf7ffa0a --- /dev/null +++ b/R/sits_detect_change.R @@ -0,0 +1,182 @@ +#' @title Detect changes in time series +#' @name sits_detect_change +#' +#' @author Gilberto Camara, \email{gilberto.camara@@inpe.br} +#' @author Felipe Carlos, \email{efelipecarlos@@gmail.com} +#' @author Felipe Carvalho, \email{felipe.carvalho@@inpe.br} +#' +#' @description Given a set of time series or an image, this function compares +#' each time series with a set of change/no-change patterns, and indicates +#' places and dates where change has been detected. +#' +#' @param data Set of time series. +#' @param cd_method Change detection method (with parameters). +#' @param ... Other parameters for specific functions. +#' @param roi Region of interest (either an sf object, shapefile, +#' or a numeric vector with named XY values +#' ("xmin", "xmax", "ymin", "ymax") or +#' named lat/long values +#' ("lon_min", "lat_min", "lon_max", "lat_max"). +#' @param filter_fn Smoothing filter to be applied - optional +#' (clousure containing object of class "function"). +#' @param impute_fn Imputation function to remove NA. +#' @param start_date Start date for the classification +#' (Date in YYYY-MM-DD format). +#' @param end_date End date for the classification +#' (Date im YYYY-MM-DD format). +#' @param memsize Memory available for classification in GB +#' (integer, min = 1, max = 16384). +#' @param multicores Number of cores to be used for classification +#' (integer, min = 1, max = 2048). +#' @param output_dir Valid directory for output file. +#' (character vector of length 1). +#' @param version Version of the output +#' (character vector of length 1). +#' @param verbose Logical: print information about processing time? +#' @param progress Logical: Show progress bar? +#' @return Time series with detection labels for +#' each point (tibble of class "sits") +#' or a data cube indicating detections in each pixel +#' (tibble of class "detections_cube"). +sits_detect_change <- function(data, + cd_method, + ..., + filter_fn = NULL, + multicores = 2L, + progress = TRUE) { + UseMethod("sits_detect_change", data) +} + +#' @rdname sits_detect_change +sits_detect_change.sits <- function(data, + cd_method, + ..., + filter_fn = NULL, + multicores = 2L, + progress = TRUE) { + # set caller for error messages + .check_set_caller("sits_detect_change_sits") + # Pre-conditions + data <- .check_samples_ts(data) + .check_is_sits_model(cd_method) + .check_int_parameter(multicores, min = 1, max = 2048) + .check_progress(progress) + # Detect changes + .detect_change_ts( + samples = data, + cd_method = cd_method, + filter_fn = filter_fn, + multicores = multicores, + progress = progress + ) +} + +#' @rdname sits_detect_change +sits_detect_change.raster_cube <- function(data, + cd_method, ..., + roi = NULL, + filter_fn = NULL, + impute_fn = impute_linear(), + start_date = NULL, + end_date = NULL, + memsize = 8L, + multicores = 2L, + output_dir, + version = "v1", + verbose = FALSE, + progress = TRUE) { + # set caller for error messages + .check_set_caller("sits_detect_change_raster") + # preconditions + .check_is_raster_cube(data) + .check_that(.cube_is_regular(data)) + .check_is_sits_model(cd_method) + .check_int_parameter(memsize, min = 1, max = 16384) + .check_int_parameter(multicores, min = 1, max = 2048) + .check_output_dir(output_dir) + # version is case-insensitive in sits + version <- .check_version(version) + .check_progress(progress) + # Get default proc bloat + proc_bloat <- .conf("processing_bloat_cpu") + # Spatial filter + if (.has(roi)) { + roi <- .roi_as_sf(roi) + data <- .cube_filter_spatial(cube = data, roi = roi) + } + # Temporal filter + if (.has(start_date) || .has(end_date)) { + data <- .cube_filter_interval( + cube = data, start_date = start_date, end_date = end_date + ) + } + if (.has(filter_fn)) + .check_filter_fn(filter_fn) + # Retrieve the samples from the model + samples <- .ml_samples(cd_method) + # Do the samples and tile match their timeline length? + .check_samples_tile_match_timeline(samples = samples, tile = data) + # Do the samples and tile match their bands? + .check_samples_tile_match_bands(samples = samples, tile = data) + # Check memory and multicores + # Get block size + block <- .raster_file_blocksize(.raster_open_rast(.tile_path(data))) + # Check minimum memory needed to process one block + job_memsize <- .jobs_memsize( + job_size = .block_size(block = block, overlap = 0), + npaths = length(.tile_paths(data)) + length(.ml_labels(cd_method)), + nbytes = 8, + proc_bloat = proc_bloat + ) + # Update multicores parameter + multicores <- .jobs_max_multicores( + job_memsize = job_memsize, + memsize = memsize, + multicores = multicores + ) + # Update block parameter + block <- .jobs_optimal_block( + job_memsize = job_memsize, + block = block, + image_size = .tile_size(.tile(data)), + memsize = memsize, + multicores = multicores + ) + # Terra requires at least two pixels to recognize an extent as valid + # polygon and not a line or point + block <- .block_regulate_size(block) + # Prepare parallel processing + .parallel_start( + workers = multicores, log = verbose, + output_dir = output_dir + ) + on.exit(.parallel_stop(), add = TRUE) + # Show block information + start_time <- .classify_verbose_start(verbose, block) + # Process each tile sequentially + detections_cube <- .cube_foreach_tile(data, function(tile) { + # Detect changes + detections_tile <- .detect_change_tile( + tile = tile, + band = "detection", + cd_method = cd_method, + block = block, + roi = roi, + filter_fn = filter_fn, + impute_fn = impute_fn, + output_dir = output_dir, + version = version, + verbose = verbose, + progress = progress + ) + return(detections_tile) + }) + # Show block information + .classify_verbose_end(verbose, start_time) + return(detections_cube) +} + +#' @rdname sits_detect_change +sits_detect_change.default <- function(data, cd_method, ...) { + stop("Input should be a sits tibble or a data cube") +} diff --git a/R/sits_detect_change_method.R b/R/sits_detect_change_method.R new file mode 100644 index 000000000..e92cb843b --- /dev/null +++ b/R/sits_detect_change_method.R @@ -0,0 +1,33 @@ +#' @title Create detect change method. +#' @name sits_detect_change_method +#' +#' @author Gilberto Camara, \email{gilberto.camara@@inpe.br} +#' @author Felipe Carlos, \email{efelipecarlos@@gmail.com} +#' +#' @description Prepare detection change method. Currently, sits supports the +#' following methods: 'dtw' (see \code{\link[sits]{sits_dtw}}) +#' +#' @param samples Time series with the training samples. +#' @param cd_method Change detection method. +#' @return Change detection method prepared +#' to be passed to +#' \code{\link[sits]{sits_detect_change}} +sits_detect_change_method <- function(samples, cd_method = sits_dtw()) { + # set caller to show in errors + .check_set_caller("sits_detect_change_method") + # check if samples are valid + .check_samples_train(samples) + # is the train method a function? + .check_that(inherits(cd_method, "function"), + msg = .conf("messages", "sits_detect_change_method_model") + ) + # are the timelines OK? + timeline_ok <- .timeline_check(samples) + .check_that(timeline_ok, + msg = .conf("messages", "sits_detect_change_method_timeline") + ) + # compute the training method by the given data + result <- cd_method(samples) + # return a valid detect change method + return(result) +} diff --git a/R/sits_dtw.R b/R/sits_dtw.R new file mode 100644 index 000000000..18617381f --- /dev/null +++ b/R/sits_dtw.R @@ -0,0 +1,64 @@ +#' @title Dynamic Time Warping for Detect changes. +#' @name sits_dtw +#' +#' @author Felipe Carlos, \email{efelipecarlos@@gmail.com} +#' @author Felipe Carvalho, \email{felipe.carvalho@@inpe.br} +#' @author Gilberto Camara, \email{gilberto.camara@@inpe.br} +#' @author Rolf Simoes, \email{rolf.simoes@@inpe.br} +#' +#' @description Create a Dynamic Time Warping (DTW) method for the +#' \code{\link[sits]{sits_detect_change_method}}. +#' +#' @param samples Time series with the training samples. +#' @param ... Other relevant parameters. +#' @param threshold Threshold used to define if an event was detected. +#' @param window ISO8601-compliant time period used to define the +#' DTW moving window, with number and unit, +#' where "D", "M" and "Y" stands for days, month and +#' year; e.g., "P16D" for 16 days. This parameter is not +#' used in operations with data cubes. +#' @return Change detection method prepared to be passed to +#' \code{\link[sits]{sits_detect_change_method}} +sits_dtw <- + function(samples = NULL, + ..., + threshold = NULL, + window = NULL) { + .check_set_caller("sits_dtw") + train_fun <- + function(samples) { + # Check parameters + .check_period(window) + .check_null_parameter(threshold) + # Sample labels + labels <- .samples_labels(samples) + # Get samples patterns (temporal median) + train_samples <- .predictors(samples) + patterns <- .pattern_temporal_median(samples) + # Define detection function + detect_change_fun <- function(values, type) { + # Define the type of the operation + dtw_fun <- .dtw_windowed_ts + if (type == "cube") { + dtw_fun <- .dtw_complete_ts + } + # Detect changes + dtw_fun( + values = values, + patterns = patterns, + window = window, + threshold = threshold + ) + } + # Set model class + detect_change_fun <- .set_class(detect_change_fun, + "dtw_model", + "sits_model", + class(detect_change_fun)) + return(detect_change_fun) + } + # If samples is informed, train a model and return a predict function + # Otherwise give back a train function to train model further + result <- .factory_function(samples, train_fun) + return(result) + } diff --git a/R/sits_lighttae.R b/R/sits_lighttae.R index 36532a71f..ce5799a0b 100644 --- a/R/sits_lighttae.R +++ b/R/sits_lighttae.R @@ -267,6 +267,11 @@ sits_lighttae <- function(samples = NULL, return(out) } ) + # torch 12.0 not working with Apple MPS + if (torch::backends_mps_is_available()) + cpu_train <- TRUE + else + cpu_train <- FALSE # Train the model using luz torch_model <- luz::setup( @@ -300,6 +305,7 @@ sits_lighttae <- function(samples = NULL, gamma = lr_decay_rate ) ), + accelerator = luz::accelerator(cpu = cpu_train), dataloader_options = list(batch_size = batch_size), verbose = verbose ) @@ -322,7 +328,7 @@ sits_lighttae <- function(samples = NULL, # Unserialize model torch_model[["model"]] <- .torch_unserialize_model(serialized_model) # Used to check values (below) - n_input_pixels <- nrow(values) + input_pixels <- nrow(values) # Transform input into a 3D tensor # Reshape the 2D matrix into a 3D array n_samples <- nrow(values) @@ -356,7 +362,7 @@ sits_lighttae <- function(samples = NULL, ) # Are the results consistent with the data input? .check_processed_values( - values = values, n_input_pixels = n_input_pixels + values = values, input_pixels = input_pixels ) # Update the columns names to labels colnames(values) <- labels diff --git a/R/sits_machine_learning.R b/R/sits_machine_learning.R index d1f2e6ce2..c8b6dccef 100644 --- a/R/sits_machine_learning.R +++ b/R/sits_machine_learning.R @@ -79,13 +79,13 @@ sits_rfor <- function(samples = NULL, num_trees = 100, mtry = NULL, ...) { # Verifies if randomForest package is installed .check_require_packages("randomForest") # Used to check values (below) - n_input_pixels <- nrow(values) + input_pixels <- nrow(values) # Do classification values <- stats::predict( object = model, newdata = values, type = "prob" ) # Are the results consistent with the data input? - .check_processed_values(values, n_input_pixels) + .check_processed_values(values, input_pixels) # Reorder matrix columns if needed if (any(labels != colnames(values))) { values <- values[, labels] @@ -193,7 +193,7 @@ sits_svm <- function(samples = NULL, formula = sits_formula_linear(), # Verifies if e1071 package is installed .check_require_packages("e1071") # Used to check values (below) - n_input_pixels <- nrow(values) + input_pixels <- nrow(values) # Performs data normalization values <- .pred_normalize(pred = values, stats = ml_stats) # Do classification @@ -203,7 +203,7 @@ sits_svm <- function(samples = NULL, formula = sits_formula_linear(), # Get the predicted probabilities values <- attr(values, "probabilities") # Are the results consistent with the data input? - .check_processed_values(values, n_input_pixels) + .check_processed_values(values, input_pixels) # Reorder matrix columns if needed if (any(labels != colnames(values))) { values <- values[, labels] @@ -337,14 +337,14 @@ sits_xgboost <- function(samples = NULL, learning_rate = 0.15, # Verifies if xgboost package is installed .check_require_packages("xgboost") # Used to check values (below) - n_input_pixels <- nrow(values) + input_pixels <- nrow(values) # Do classification values <- stats::predict( object = model, as.matrix(values), ntreelimit = ntreelimit, reshape = TRUE ) # Are the results consistent with the data input? - .check_processed_values(values, n_input_pixels) + .check_processed_values(values, input_pixels) # Update the columns names to labels colnames(values) <- labels return(values) diff --git a/R/sits_merge.R b/R/sits_merge.R index 7598d881b..dd4b6c137 100644 --- a/R/sits_merge.R +++ b/R/sits_merge.R @@ -132,10 +132,10 @@ sits_merge.raster_cube <- function(data1, data2, ...) { .data[["tile"]] ) - if (inherits(data2, "raster_cube")) { - return(.merge_equal_cube(data1, data2)) - } else { + if (inherits(data2, "sar_cube")) { return(.merge_distinct_cube(data1, data2)) + } else { + return(.merge_equal_cube(data1, data2)) } } @@ -152,8 +152,8 @@ sits_merge.raster_cube <- function(data1, data2, ...) { .merge_distinct_cube <- function(data1, data2) { # Get cubes timeline - d1_tl <- unique(as.Date(unlist(.cube_timeline(data1)))) - d2_tl <- unique(as.Date(unlist(.cube_timeline(data2)))) + d1_tl <- unique(as.Date(.cube_timeline(data1)[[1]])) + d2_tl <- unique(as.Date(.cube_timeline(data2)[[1]])) # get intervals d1_period <- as.numeric( diff --git a/R/sits_mlp.R b/R/sits_mlp.R index cd2815635..624e87cec 100644 --- a/R/sits_mlp.R +++ b/R/sits_mlp.R @@ -227,7 +227,7 @@ sits_mlp <- function(samples = NULL, # output layer tensors[[length(tensors) + 1]] <- torch::nn_linear(layers[length(layers)], y_dim) - # add softmax tensor + # add softmax tensor tensors[[length(tensors) + 1]] <- torch::nn_softmax(dim = 2) # create a sequential module that calls the layers in the same # order. @@ -237,6 +237,11 @@ sits_mlp <- function(samples = NULL, self$model(x) } ) + # torch 12.0 not working with Apple MPS + if (torch::backends_mps_is_available()) + cpu_train <- TRUE + else + cpu_train <- FALSE # Train the model using luz torch_model <- luz::setup( @@ -262,6 +267,8 @@ sits_mlp <- function(samples = NULL, patience = patience, min_delta = min_delta )), + dataloader_options = list(batch_size = batch_size), + accelerator = luz::accelerator(cpu = cpu_train), verbose = verbose ) # Serialize model @@ -277,7 +284,7 @@ sits_mlp <- function(samples = NULL, # Unserialize model torch_model[["model"]] <- .torch_unserialize_model(serialized_model) # Used to check values (below) - n_input_pixels <- nrow(values) + input_pixels <- nrow(values) # Performs data normalization values <- .pred_normalize(pred = values, stats = ml_stats) # Transform input into matrix @@ -305,7 +312,7 @@ sits_mlp <- function(samples = NULL, ) # Are the results consistent with the data input? .check_processed_values( - values = values, n_input_pixels = n_input_pixels + values = values, input_pixels = input_pixels ) # Update the columns names to labels colnames(values) <- labels diff --git a/R/sits_plot.R b/R/sits_plot.R index e1a9902c7..bed973f0b 100644 --- a/R/sits_plot.R +++ b/R/sits_plot.R @@ -321,22 +321,22 @@ plot.predicted <- function(x, y, ..., #' @param green Band for green color. #' @param blue Band for blue color. #' @param tile Tile to be plotted. -#' @param date Date to be plotted. +#' @param dates Dates to be plotted. #' @param palette An RColorBrewer palette -#' @param style Method to process the color scale -#' ("cont", "order", "quantile", "fisher", -#' "jenks", "log10") -#' @param n_colors Number of colors to be shown #' @param rev Reverse the color order in the palette? #' @param scale Scale to plot map (0.4 to 1.0) +#' @param style Style for plotting continuous objects #' #' @return A plot object with an RGB image -#' or a B/W image on a color -#' scale using the pallete +#' or a B/W image on a color scale #' -#' @note To see which colors are supported, please run \code{sits_colors()} +#' @note #' Use \code{scale} parameter for general output control. -#' If required, then set the other params individually +#' The \code{dates} parameter indicates the date allows plotting of different dates when +#' a single band and three dates are provided, `sits` will plot a +#' multi-temporal RGB image for a single band (useful in the case of +#' SAR data). For RGB bands with multi-dates, multiple plots will be +#' produced. #' @examples #' if (sits_run_examples()) { #' # create a data cube from local files @@ -347,7 +347,8 @@ plot.predicted <- function(x, y, ..., #' data_dir = data_dir #' ) #' # plot NDVI band of the second date date of the data cube -#' plot(cube, band = "NDVI", date = sits_timeline(cube)[1]) +#' plot(cube, band = "NDVI", dates = sits_timeline(cube)[1]) +#' # plot NDVI band as an RGB composite for the three bands #' } #' @export plot.raster_cube <- function(x, ..., @@ -356,13 +357,19 @@ plot.raster_cube <- function(x, ..., green = NULL, blue = NULL, tile = x[["tile"]][[1]], - date = NULL, + dates = NULL, palette = "RdYlGn", - style = "cont", - n_colors = 10, rev = FALSE, - scale = 1) { + scale = 0.9, + style = "order") { + # check caller .check_set_caller(".plot_raster_cube") + # retrieve dots + dots <- list(...) + # deal with wrong parameter "date" + if ("date" %in% names(dots) && missing(dates)) { + dates <- as.Date(dots[["date"]]) + } # is tile inside the cube? .check_chr_contains( x = x[["tile"]], @@ -372,48 +379,95 @@ plot.raster_cube <- function(x, ..., can_repeat = FALSE, msg = .conf("messages", ".plot_raster_cube_tile") ) + # verifies if stars package is installed + .check_require_packages("stars") + # verifies if tmap package is installed + .check_require_packages("tmap") + if (.has(band)) { + # check palette + .check_palette(palette) + # check rev + .check_lgl_parameter(rev) + } + # check scale parameter + .check_num_parameter(scale, min = 0.2) + # reverse the color palette? + if (rev || palette == "Greys") + palette <- paste0("-", palette) # filter the tile to be processed tile <- .cube_filter_tiles(cube = x, tiles = tile) - if (.has(date)) { + if (.has(dates)) { # is this a valid date? - date <- as.Date(date) - .check_that(date %in% .tile_timeline(tile), + dates <- as.Date(dates) + .check_that(all(dates %in% .tile_timeline(tile)), msg = .conf("messages", ".plot_raster_cube_date") ) } else { - date <- .tile_timeline(tile)[[1]] + dates <- .tile_timeline(tile)[[1]] } - # only one date at a time - .check_that(length(date) == 1, - msg = .conf("messages", ".plot_raster_cube_single_date")) # BW or color? .check_bw_rgb_bands(band, red, green, blue) .check_available_bands(x, band, red, green, blue) - if (.has(band)) + + if (.has(band) && length(dates) == 3) { + main_title <- paste0(.tile_collection(tile), " ", band, " ", + as.Date(dates[[1]]), "(R) ", + as.Date(dates[[2]]), "(G) ", + as.Date(dates[[3]]), "(B) " + ) + p <- .plot_band_multidate( + tile = tile, + band = band, + dates = dates, + palette = palette, + main_title = main_title, + rev = rev, + scale = scale + ) + return(p) + } + if (length(dates) > 1) { + warning(.conf("messages", ".plot_raster_cube_single_date")) + } + if (.has(band)) { + main_title <- paste0(.tile_collection(tile), " ", band, + " ", as.Date(dates[[1]])) p <- .plot_false_color( tile = tile, band = band, - date = date, + date = dates[[1]], sf_seg = NULL, seg_color = NULL, line_width = NULL, palette = palette, - style = style, - n_colors = n_colors, + main_title = main_title, rev = rev, - scale = scale + scale = scale, + style = style ) - else + } else { # plot RGB + main_title <- paste0(.tile_satellite(tile)," ", + tile[["tile"]], " ", + red, "(R) ", + green, "(G) ", + blue, "(B) ", + as.Date(dates[[1]]) + ) p <- .plot_rgb( tile = tile, red = red, green = green, blue = blue, - date = date, + date = dates[[1]], + main_title = main_title, sf_seg = NULL, - seg_color = NULL + seg_color = NULL, + line_width = NULL, + scale = scale ) + } + return(p) } #' @title Plot RGB vector data cubes @@ -429,16 +483,13 @@ plot.raster_cube <- function(x, ..., #' @param green Band for green color. #' @param blue Band for blue color. #' @param tile Tile to be plotted. -#' @param date Date to be plotted. +#' @param dates Dates to be plotted. #' @param seg_color Color to show the segment boundaries #' @param line_width Line width to plot the segments boundary (in pixels) #' @param palette An RColorBrewer palette -#' @param style Method to process the color scale -#' ("cont", "order", "quantile", "fisher", -#' "jenks", "log10") -#' @param n_colors Number of colors to be shown #' @param rev Reverse the color order in the palette? -#' @param scale Scale to plot map (0.4 to 1.0) +#' @param scale Scale to plot map (0.4 to 1.5) +#' @param style Style for plotting continuous objects #' #' @return A plot object with an RGB image #' or a B/W image on a color @@ -471,15 +522,20 @@ plot.vector_cube <- function(x, ..., green = NULL, blue = NULL, tile = x[["tile"]][[1]], - date = NULL, + dates = NULL, seg_color = "black", line_width = 1, palette = "RdYlGn", - style = "cont", - n_colors = 10, rev = FALSE, - scale = 0.8) { + scale = 1.0, + style = "order") { .check_set_caller(".plot_vector_cube") + # retrieve dots + dots <- list(...) + # deal with wrong parameter "date" + if ("date" %in% names(dots) && missing(dates)) { + dates <- as.Date(dots[["date"]]) + } # is tile inside the cube? .check_chr_contains( x = x[["tile"]], @@ -491,24 +547,24 @@ plot.vector_cube <- function(x, ..., ) # filter the tile to be processed tile <- .cube_filter_tiles(cube = x, tiles = tile) - if (!.has(date)) { - date <- .tile_timeline(tile)[[1]] + if (.has(dates)) { + # is this a valid date? + dates <- as.Date(dates)[[1]] + .check_that(all(dates %in% .tile_timeline(tile)), + msg = .conf("messages", ".plot_raster_cube_date") + ) + } else { + dates <- .tile_timeline(tile)[[1]] } - # only one date at a time - .check_that(length(date) == 1, - msg = .conf("messages", ".plot_raster_cube_single_date") - ) - # is this a valid date? - date <- as.Date(date) - .check_that(date %in% .tile_timeline(tile), - msg = .conf("messages", ".plot_raster_cube_date") - ) # retrieve the segments for this tile sf_seg <- .segments_read_vec(tile) # BW or color? .check_bw_rgb_bands(band, red, green, blue) .check_available_bands(x, band, red, green, blue) if (.has(band)) { + main_title <- paste0( + .tile_collection(tile), " ", band, " ", as.Date(date) + ) # plot the band as false color p <- .plot_false_color( tile = tile, @@ -518,12 +574,19 @@ plot.vector_cube <- function(x, ..., seg_color = seg_color, line_width = line_width, palette = palette, - style = style, - n_colors = n_colors, + main_title = main_title, rev = rev, - scale = scale + scale = scale, + style = style ) } else { + main_title <- paste0(.tile_collection(tile)," ", + tile[["tile"]], + red, "(R) ", + green, "(G) ", + blue, "(B) ", + as.Date(date) + ) # plot RGB p <- .plot_rgb( tile = tile, @@ -531,9 +594,11 @@ plot.vector_cube <- function(x, ..., green = green, blue = blue, date = date, + main_title = main_title, sf_seg = sf_seg, seg_color = seg_color, - line_width = line_width + line_width = line_width, + scale = scale ) } return(p) @@ -548,10 +613,6 @@ plot.vector_cube <- function(x, ..., #' @param tile Tile to be plotted. #' @param labels Labels to plot (optional). #' @param palette RColorBrewer palette -#' @param style Method to process the color scale -#' ("cont", "order", "quantile", "fisher", -#' "jenks", "log10") -#' @param n_colors Number of colors to be shown #' @param rev Reverse order of colors in palette? #' @param scale Scale to plot map (0.4 to 1.0) #' @return A plot containing probabilities associated @@ -583,8 +644,6 @@ plot.probs_cube <- function(x, ..., tile = x[["tile"]][[1]], labels = NULL, palette = "YlGn", - style = "cont", - n_colors = 10, rev = FALSE, scale = 0.8) { .check_set_caller(".plot_probs_cube") @@ -611,8 +670,6 @@ plot.probs_cube <- function(x, ..., p <- .plot_probs(tile = tile, labels_plot = labels, palette = palette, - style = style, - n_colors = n_colors, rev = rev, scale = scale) @@ -628,9 +685,6 @@ plot.probs_cube <- function(x, ..., #' @param tile Tile to be plotted. #' @param labels Labels to plot (optional). #' @param palette RColorBrewer palette -#' @param style Method to process the color scale -#' ("cont", "order", "quantile", "fisher", -#' "jenks", "log10") #' @param rev Reverse order of colors in palette? #' @param scale Scale to plot map (0.4 to 1.0) #' @return A plot containing probabilities associated @@ -676,7 +730,6 @@ plot.probs_vector_cube <- function(x, ..., tile = x[["tile"]][[1]], labels = NULL, palette = "YlGn", - style = "cont", rev = FALSE, scale = 0.8) { .check_set_caller(".plot_probs_vector") @@ -703,7 +756,6 @@ plot.probs_vector_cube <- function(x, ..., p <- .plot_probs_vector(tile = tile, labels_plot = labels, palette = palette, - style = style, rev = rev, scale = scale) @@ -719,10 +771,6 @@ plot.probs_vector_cube <- function(x, ..., #' @param tile Tile to be plotted. #' @param labels Labels to plot (optional). #' @param palette RColorBrewer palette -#' @param style Method to process the color scale -#' ("cont", "order", "quantile", "fisher", -#' "jenks", "log10") -#' @param n_colors Number of colors to be shown #' @param rev Reverse order of colors in palette? #' @param type Type of plot ("map" or "hist") #' @param scale Scale to plot map (0.4 to 1.0) @@ -757,8 +805,6 @@ plot.variance_cube <- function(x, ..., tile = x[["tile"]][[1]], labels = NULL, palette = "YlGnBu", - style = "cont", - n_colors = 10, rev = FALSE, type = "map", scale = 0.8) { @@ -788,8 +834,6 @@ plot.variance_cube <- function(x, ..., p <- .plot_probs(tile = tile, labels_plot = labels, palette = palette, - style = style, - n_colors = n_colors, rev = rev, scale = scale) } else { @@ -808,10 +852,6 @@ plot.variance_cube <- function(x, ..., #' @param ... Further specifications for \link{plot}. #' @param tile Tiles to be plotted. #' @param palette An RColorBrewer palette -#' @param style Method to process the color scale -#' ("cont", "order", "quantile", "fisher", -#' "jenks", "log10") -#' @param n_colors Number of colors to be shown #' @param rev Reverse the color order in the palette? #' @param scale Scale to plot map (0.4 to 1.0) #' @@ -844,10 +884,8 @@ plot.variance_cube <- function(x, ..., plot.uncertainty_cube <- function(x, ..., tile = x[["tile"]][[1]], palette = "RdYlGn", - style = "cont", rev = TRUE, - n_colors = 10, - scale = 0.8) { + scale = 1.0) { .check_set_caller(".plot_uncertainty_cube") # check for color_palette parameter (sits 1.4.1) dots <- list(...) @@ -868,6 +906,7 @@ plot.uncertainty_cube <- function(x, ..., # filter the cube tile <- .cube_filter_tiles(cube = x, tiles = tile[[1]]) band <- sits_bands(tile) + main_title <- paste0(.tile_collection(tile), " uncertainty ", band) # plot the data using tmap p <- .plot_false_color( tile = tile, @@ -877,10 +916,10 @@ plot.uncertainty_cube <- function(x, ..., seg_color = NULL, line_width = NULL, palette = palette, - style = style, - n_colors = n_colors, + main_title = main_title, rev = rev, - scale = scale + scale = scale, + style = "order" ) return(p) @@ -894,9 +933,6 @@ plot.uncertainty_cube <- function(x, ..., #' @param ... Further specifications for \link{plot}. #' @param tile Tile to be plotted. #' @param palette RColorBrewer palette -#' @param style Method to process the color scale -#' ("cont", "order", "quantile", "fisher", -#' "jenks", "log10") #' @param rev Reverse order of colors in palette? #' @param scale Scale to plot map (0.4 to 1.0) #' @return A plot containing probabilities associated @@ -947,7 +983,6 @@ plot.uncertainty_cube <- function(x, ..., plot.uncertainty_vector_cube <- function(x, ..., tile = x[["tile"]][[1]], palette = "RdYlGn", - style = "cont", rev = TRUE, scale = 0.8) { .check_set_caller(".plot_uncertainty_vector_cube") @@ -969,13 +1004,15 @@ plot.uncertainty_vector_cube <- function(x, ..., # filter the cube tile <- .cube_filter_tiles(cube = x, tiles = tile) - + # set the title + band <- sits_bands(tile) + main_title <- paste0(.tile_collection(tile), " uncertainty ", band) # plot the probs vector cube p <- .plot_uncertainty_vector(tile = tile, - palette = palette, - style = style, - rev = rev, - scale = scale) + palette = palette, + main_title = main_title, + rev = rev, + scale = scale) return(p) } diff --git a/R/sits_resnet.R b/R/sits_resnet.R index e602fc37d..68f53ecb8 100644 --- a/R/sits_resnet.R +++ b/R/sits_resnet.R @@ -319,6 +319,11 @@ sits_resnet <- function(samples = NULL, self$softmax() } ) + # torch 12.0 not working with Apple MPS + if (torch::backends_mps_is_available()) + cpu_train <- TRUE + else + cpu_train <- FALSE # train the model using luz torch_model <- luz::setup( @@ -354,6 +359,7 @@ sits_resnet <- function(samples = NULL, gamma = lr_decay_rate ) ), + accelerator = luz::accelerator(cpu = cpu_train), dataloader_options = list(batch_size = batch_size), verbose = verbose ) @@ -370,7 +376,7 @@ sits_resnet <- function(samples = NULL, # Unserialize model torch_model[["model"]] <- .torch_unserialize_model(serialized_model) # Used to check values (below) - n_input_pixels <- nrow(values) + input_pixels <- nrow(values) # Transform input into a 3D tensor # Reshape the 2D matrix into a 3D array n_samples <- nrow(values) @@ -403,7 +409,7 @@ sits_resnet <- function(samples = NULL, x = torch::torch_tensor(values, device = "cpu") ) .check_processed_values( - values = values, n_input_pixels = n_input_pixels + values = values, input_pixels = input_pixels ) # Update the columns names to labels colnames(values) <- labels diff --git a/R/sits_tae.R b/R/sits_tae.R index 5f9c18b90..cccdfb9c2 100644 --- a/R/sits_tae.R +++ b/R/sits_tae.R @@ -240,6 +240,11 @@ sits_tae <- function(samples = NULL, return(x) } ) + # torch 12.0 not working with Apple MPS + if (torch::backends_mps_is_available()) + cpu_train <- TRUE + else + cpu_train <- FALSE # train the model using luz torch_model <- luz::setup( @@ -273,6 +278,7 @@ sits_tae <- function(samples = NULL, gamma = lr_decay_rate ) ), + accelerator = luz::accelerator(cpu = cpu_train), dataloader_options = list(batch_size = batch_size), verbose = verbose ) @@ -289,7 +295,7 @@ sits_tae <- function(samples = NULL, # Unserialize model torch_model[["model"]] <- .torch_unserialize_model(serialized_model) # Used to check values (below) - n_input_pixels <- nrow(values) + input_pixels <- nrow(values) # Transform input into a 3D tensor # Reshape the 2D matrix into a 3D array n_samples <- nrow(values) @@ -323,7 +329,7 @@ sits_tae <- function(samples = NULL, ) # Are the results consistent with the data input? .check_processed_values( - values = values, n_input_pixels = n_input_pixels + values = values, input_pixels = input_pixels ) # Update the columns names to labels colnames(values) <- labels diff --git a/R/sits_tempcnn.R b/R/sits_tempcnn.R index 7e2e3ef1e..f91318182 100644 --- a/R/sits_tempcnn.R +++ b/R/sits_tempcnn.R @@ -59,7 +59,8 @@ #' @examples #' if (sits_run_examples()) { #' # create a TempCNN model -#' torch_model <- sits_train(samples_modis_ndvi, sits_tempcnn()) +#' torch_model <- sits_train(samples_modis_ndvi, +#' sits_tempcnn(verbose = TRUE)) #' # plot the model #' plot(torch_model) #' # create a data cube from local files @@ -285,6 +286,11 @@ sits_tempcnn <- function(samples = NULL, self$softmax() } ) + # torch 12.0 not working with Apple MPS + if (torch::backends_mps_is_available()) + cpu_train <- TRUE + else + cpu_train <- FALSE # Train the model using luz torch_model <- luz::setup( @@ -323,6 +329,7 @@ sits_tempcnn <- function(samples = NULL, gamma = lr_decay_rate ) ), + accelerator = luz::accelerator(cpu = cpu_train), dataloader_options = list(batch_size = batch_size), verbose = verbose ) @@ -339,7 +346,7 @@ sits_tempcnn <- function(samples = NULL, # Unserialize model torch_model[["model"]] <- .torch_unserialize_model(serialized_model) # Used to check values (below) - n_input_pixels <- nrow(values) + input_pixels <- nrow(values) # Transform input into a 3D tensor # Reshape the 2D matrix into a 3D array n_samples <- nrow(values) @@ -356,7 +363,7 @@ sits_tempcnn <- function(samples = NULL, if (torch::cuda_is_available()) { values <- .as_dataset(values) # We need to transform in a dataloader to use the batch size - values <- torch::dataloader( + values <- torch::dataloader( values, batch_size = 2^15 ) # Do GPU classification @@ -374,7 +381,7 @@ sits_tempcnn <- function(samples = NULL, ) # Are the results consistent with the data input? .check_processed_values( - values = values, n_input_pixels = n_input_pixels + values = values, input_pixels = input_pixels ) # Update the columns names to labels colnames(values) <- labels diff --git a/R/sits_view.R b/R/sits_view.R index b4a6e3252..d4e98b46b 100644 --- a/R/sits_view.R +++ b/R/sits_view.R @@ -164,6 +164,12 @@ sits_view.raster_cube <- function(x, ..., # pre-condition for bands .check_bw_rgb_bands(band, red, green, blue) .check_available_bands(x, band, red, green, blue) + # retrieve dots + dots <- list(...) + # deal with wrong parameter "date" + if ("date" %in% names(dots) && missing(dates)) { + dates <- as.Date(dots[["date"]]) + } # view image raster leaf_map <- .view_image_raster( cube = x, @@ -203,6 +209,12 @@ sits_view.vector_cube <- function(x, ..., # pre-condition for bands .check_bw_rgb_bands(band, red, green, blue) .check_available_bands(x, band, red, green, blue) + # retrieve dots + dots <- list(...) + # deal with wrong parameter "date" + if ("date" %in% names(dots) && missing(dates)) { + dates <- as.Date(dots[["date"]]) + } # view vector cube leaf_map <- .view_image_vector( cube = x, @@ -241,58 +253,20 @@ sits_view.uncertainty_cube <- function(x, ..., if (nrow(cube) > 1) { .check_that(.cube_is_regular(cube)) } - # find out if resampling is required (for big images) - output_size <- .view_resample_size( - cube = cube, - ndates = 1 - ) - # create a leaflet and add providers - leaf_map <- .view_add_basic_maps() - # get names of base maps - base_maps <- .view_get_base_maps(leaf_map) - # obtain the raster objects for the dates chosen - for (row in seq_len(nrow(cube))) { - # get tile - tile <- cube[row, ] - band_file <- .tile_path(tile, band) - leaf_map <- .view_add_stars_image( - leaf_map = leaf_map, - band_file = band_file, - tile = tile, - band = .cube_bands(cube), - date = NULL, - palette = palette, - output_size = output_size - ) - } - # include class cube, if available - leaf_map <- .view_class_cube( - leaf_map = leaf_map, + # view image raster + leaf_map <- .view_image_raster( + cube = x, class_cube = class_cube, tiles = tiles, + dates = NULL, + band = band, + red = NULL, + green = NULL, + blue = NULL, legend = legend, palette = palette, - opacity = opacity, - output_size = output_size - ) - # add overlay groups - overlay_groups <- .view_add_overlay_grps( - cube = cube, - class_cube = class_cube + opacity = opacity ) - # add layers control to leafmap - leaf_map <- leaf_map |> - leaflet::addLayersControl( - baseGroups = base_maps, - overlayGroups = overlay_groups, - options = leaflet::layersControlOptions(collapsed = FALSE) - ) |> - # add legend to leaf_map - .view_add_legend( - cube = class_cube, - legend = legend, - palette = palette - ) return(leaf_map) } #' @rdname sits_view @@ -309,15 +283,8 @@ sits_view.class_cube <- function(x, ..., # deal with tiles # filter the tiles to be processed cube <- .view_filter_tiles(x, tiles) - # find out if resampling is required (for big images) - output_size <- .view_resample_size( - cube = cube, - ndates = 1 - ) # create a leaflet and add providers - leaf_map <- .view_add_basic_maps() - # get names of basic maps - base_maps <- .view_get_base_maps(leaf_map) + leaf_map <- .view_add_base_maps() # add a leafmap for class cube leaf_map <- leaf_map |> .view_class_cube( @@ -325,8 +292,7 @@ sits_view.class_cube <- function(x, ..., tiles = tiles, legend = legend, palette = palette, - opacity = opacity, - output_size = output_size + opacity = opacity ) # add overlay groups overlay_groups <- .view_add_overlay_grps( @@ -335,7 +301,8 @@ sits_view.class_cube <- function(x, ..., # add layers control leaf_map <- leaf_map |> leaflet::addLayersControl( - baseGroups = base_maps, + baseGroups = c("ESRI", "GeoPortalFrance", + "Sentinel-2-2020", "OSM"), overlayGroups = overlay_groups, options = leaflet::layersControlOptions(collapsed = FALSE) ) |> diff --git a/inst/extdata/config_internals.yml b/inst/extdata/config_internals.yml index 2d58c2902..f23768763 100644 --- a/inst/extdata/config_internals.yml +++ b/inst/extdata/config_internals.yml @@ -171,6 +171,17 @@ derived_cube : offset_value : 0 scale_factor : 1 + detections_cube : + s3_class : [ "detections_cube", "derived_cube", "raster_cube" ] + bands : + detection : + data_type : "INT1U" + missing_value: 255 + minimum_value: 0 + maximum_value: 254 + offset_value : 0 + scale_factor : 1 + # Vector cube definitions vector_cube : segs_cube : @@ -215,9 +226,6 @@ gdal_presets : cog : overviews : [2, 4, 8, 16] method : "NEAREST" - block : - of : "GTiff" - co : ["COMPRESS=NONE", "BIGTIFF=NO"] # cannot be tiled! image : of : "GTiff" co : ["COMPRESS=LZW", "PREDICTOR=2", "BIGTIFF=YES", @@ -263,6 +271,7 @@ plot_max_Mbytes: 10 # maximum size of COG overview for visualisation plot_max_size: 1200 view_max_size: 2500 +view_resolution: 300 # parameters to show B/W SAR bands for RTC cube sar_cube_grey_colors: 128 @@ -270,7 +279,7 @@ sar_cube_grey_colors: 128 # tmap configurations tmap: max_cells: 1e+06 - graticules_labels_size: 0.7 + graticules_labels_size: 0.8 legend_outside: False legend_outside_position: "right" legend_title_size: 1.0 diff --git a/inst/extdata/config_messages.yml b/inst/extdata/config_messages.yml index 6e2e6e464..2fb27077d 100644 --- a/inst/extdata/config_messages.yml +++ b/inst/extdata/config_messages.yml @@ -193,7 +193,7 @@ .plot_probs_vector: "some requested labels are not present in cube" .plot_raster_cube: "wrong input parameters - see example in documentation" .plot_raster_cube_tile: "tile is not included in the cube" -.plot_raster_cube_single_date: "plot handles one date at a time" +.plot_raster_cube_single_date: "plotting only the first requested date" .plot_raster_cube_date: "date is not part of the cube timeline" .plot_raster_false_color: "plotting false color image" .plot_sits: "wrong input parameters - see example in documentation" @@ -352,6 +352,11 @@ sits_cube_default: "requested source has not been registered in sits\n - if poss sits_cube_copy: "wrong input parameters - see example in documentation" sits_cube_local_cube: "wrong input parameters - see example in documentation" sits_cube_local_cube_vector_band: "one vector_band must be provided (either segments, class, or probs)" +sits_detect_change_method: "wrong input parameters - see example in documentation" +sits_detect_change_method_model: "cd_method is not a valid function" +sits_detect_change_method_timeline: "samples have different timeline lengths" +sits_detect_change_sits: "wrong input parameters - see example in documentation" +sits_dtw: "wrong input parameters - see example in documentation" sits_filter: "input should be a valid set of training samples or a non-classified data cube" sits_formula_linear: "invalid input data" sits_formula_logref: "invalid input data" diff --git a/inst/extdata/sources/config_source_mpc.yml b/inst/extdata/sources/config_source_mpc.yml index 320b3abbc..74a7fad0f 100644 --- a/inst/extdata/sources/config_source_mpc.yml +++ b/inst/extdata/sources/config_source_mpc.yml @@ -193,7 +193,7 @@ sources: VV : &mspc_rtc_10m missing_value : -32768.0 minimum_value : 0 - maximum_value : 65534 + maximum_value : 65534.0 scale_factor : 1 offset_value : 0 resolution : 10 diff --git a/inst/extdata/sources/config_source_planet.yaml b/inst/extdata/sources/config_source_planet.yaml new file mode 100644 index 000000000..1d49df023 --- /dev/null +++ b/inst/extdata/sources/config_source_planet.yaml @@ -0,0 +1,30 @@ +sources: + PLANET : + s3_class : ["planet_cube", "stac_cube", "eo_cube", + "raster_cube"] + collections : + MOSAIC : + bands : + B01 : &planet_mosaic_4m + missing_value : 0 + minimum_value : 1 + maximum_value : 65534 + scale_factor : 0.01 + offset_value : 0 + resolution : 4.77 + band_name : "blue" + data_type : "INT2U" + B02 : + <<: *planet_mosaic_4m + band_name : "green" + B03 : + <<: *planet_mosaic_4m + band_name : "red" + B04 : + <<: *planet_mosaic_4m + band_name : "nir" + satellite : "PLANETSCOPE" + sensor : "MOSAIC" + collection_name: "planet-mosaic" + ext_tolerance: 0 + grid_system : "" diff --git a/man/plot.probs_cube.Rd b/man/plot.probs_cube.Rd index 1d298d909..c0b3fa4fe 100644 --- a/man/plot.probs_cube.Rd +++ b/man/plot.probs_cube.Rd @@ -10,8 +10,6 @@ tile = x[["tile"]][[1]], labels = NULL, palette = "YlGn", - style = "cont", - n_colors = 10, rev = FALSE, scale = 0.8 ) @@ -27,12 +25,6 @@ \item{palette}{RColorBrewer palette} -\item{style}{Method to process the color scale -("cont", "order", "quantile", "fisher", - "jenks", "log10")} - -\item{n_colors}{Number of colors to be shown} - \item{rev}{Reverse order of colors in palette?} \item{scale}{Scale to plot map (0.4 to 1.0)} diff --git a/man/plot.probs_vector_cube.Rd b/man/plot.probs_vector_cube.Rd index 34184ee89..68db13e81 100644 --- a/man/plot.probs_vector_cube.Rd +++ b/man/plot.probs_vector_cube.Rd @@ -10,7 +10,6 @@ tile = x[["tile"]][[1]], labels = NULL, palette = "YlGn", - style = "cont", rev = FALSE, scale = 0.8 ) @@ -26,10 +25,6 @@ \item{palette}{RColorBrewer palette} -\item{style}{Method to process the color scale -("cont", "order", "quantile", "fisher", - "jenks", "log10")} - \item{rev}{Reverse order of colors in palette?} \item{scale}{Scale to plot map (0.4 to 1.0)} diff --git a/man/plot.raster_cube.Rd b/man/plot.raster_cube.Rd index 4baa98d40..eb2769de4 100644 --- a/man/plot.raster_cube.Rd +++ b/man/plot.raster_cube.Rd @@ -12,12 +12,11 @@ green = NULL, blue = NULL, tile = x[["tile"]][[1]], - date = NULL, + dates = NULL, palette = "RdYlGn", - style = "cont", - n_colors = 10, rev = FALSE, - scale = 1 + scale = 0.9, + style = "order" ) } \arguments{ @@ -35,32 +34,30 @@ \item{tile}{Tile to be plotted.} -\item{date}{Date to be plotted.} +\item{dates}{Dates to be plotted.} \item{palette}{An RColorBrewer palette} -\item{style}{Method to process the color scale -("cont", "order", "quantile", "fisher", - "jenks", "log10")} - -\item{n_colors}{Number of colors to be shown} - \item{rev}{Reverse the color order in the palette?} \item{scale}{Scale to plot map (0.4 to 1.0)} + +\item{style}{Style for plotting continuous objects} } \value{ A plot object with an RGB image - or a B/W image on a color - scale using the pallete + or a B/W image on a color scale } \description{ Plot RGB raster cube } \note{ -To see which colors are supported, please run \code{sits_colors()} - Use \code{scale} parameter for general output control. - If required, then set the other params individually +Use \code{scale} parameter for general output control. + The \code{dates} parameter indicates the date allows plotting of different dates when + a single band and three dates are provided, `sits` will plot a + multi-temporal RGB image for a single band (useful in the case of + SAR data). For RGB bands with multi-dates, multiple plots will be + produced. } \examples{ if (sits_run_examples()) { @@ -72,7 +69,8 @@ if (sits_run_examples()) { data_dir = data_dir ) # plot NDVI band of the second date date of the data cube - plot(cube, band = "NDVI", date = sits_timeline(cube)[1]) + plot(cube, band = "NDVI", dates = sits_timeline(cube)[1]) + # plot NDVI band as an RGB composite for the three bands } } \author{ diff --git a/man/plot.uncertainty_cube.Rd b/man/plot.uncertainty_cube.Rd index e46db4e6a..60235cb68 100644 --- a/man/plot.uncertainty_cube.Rd +++ b/man/plot.uncertainty_cube.Rd @@ -9,10 +9,8 @@ ..., tile = x[["tile"]][[1]], palette = "RdYlGn", - style = "cont", rev = TRUE, - n_colors = 10, - scale = 0.8 + scale = 1 ) } \arguments{ @@ -24,14 +22,8 @@ \item{palette}{An RColorBrewer palette} -\item{style}{Method to process the color scale -("cont", "order", "quantile", "fisher", - "jenks", "log10")} - \item{rev}{Reverse the color order in the palette?} -\item{n_colors}{Number of colors to be shown} - \item{scale}{Scale to plot map (0.4 to 1.0)} } \value{ diff --git a/man/plot.uncertainty_vector_cube.Rd b/man/plot.uncertainty_vector_cube.Rd index 381ad8a18..eb88749b6 100644 --- a/man/plot.uncertainty_vector_cube.Rd +++ b/man/plot.uncertainty_vector_cube.Rd @@ -9,7 +9,6 @@ ..., tile = x[["tile"]][[1]], palette = "RdYlGn", - style = "cont", rev = TRUE, scale = 0.8 ) @@ -23,10 +22,6 @@ \item{palette}{RColorBrewer palette} -\item{style}{Method to process the color scale -("cont", "order", "quantile", "fisher", - "jenks", "log10")} - \item{rev}{Reverse order of colors in palette?} \item{scale}{Scale to plot map (0.4 to 1.0)} diff --git a/man/plot.variance_cube.Rd b/man/plot.variance_cube.Rd index 1259f3111..7a0c5bc68 100644 --- a/man/plot.variance_cube.Rd +++ b/man/plot.variance_cube.Rd @@ -10,8 +10,6 @@ tile = x[["tile"]][[1]], labels = NULL, palette = "YlGnBu", - style = "cont", - n_colors = 10, rev = FALSE, type = "map", scale = 0.8 @@ -28,12 +26,6 @@ \item{palette}{RColorBrewer palette} -\item{style}{Method to process the color scale -("cont", "order", "quantile", "fisher", - "jenks", "log10")} - -\item{n_colors}{Number of colors to be shown} - \item{rev}{Reverse order of colors in palette?} \item{type}{Type of plot ("map" or "hist")} diff --git a/man/plot.vector_cube.Rd b/man/plot.vector_cube.Rd index e964c7ca4..c57d1e821 100644 --- a/man/plot.vector_cube.Rd +++ b/man/plot.vector_cube.Rd @@ -12,14 +12,13 @@ green = NULL, blue = NULL, tile = x[["tile"]][[1]], - date = NULL, + dates = NULL, seg_color = "black", line_width = 1, palette = "RdYlGn", - style = "cont", - n_colors = 10, rev = FALSE, - scale = 0.8 + scale = 1, + style = "order" ) } \arguments{ @@ -37,7 +36,7 @@ \item{tile}{Tile to be plotted.} -\item{date}{Date to be plotted.} +\item{dates}{Dates to be plotted.} \item{seg_color}{Color to show the segment boundaries} @@ -45,15 +44,11 @@ \item{palette}{An RColorBrewer palette} -\item{style}{Method to process the color scale -("cont", "order", "quantile", "fisher", - "jenks", "log10")} - -\item{n_colors}{Number of colors to be shown} - \item{rev}{Reverse the color order in the palette?} -\item{scale}{Scale to plot map (0.4 to 1.0)} +\item{scale}{Scale to plot map (0.4 to 1.5)} + +\item{style}{Style for plotting continuous objects} } \value{ A plot object with an RGB image diff --git a/man/sits_confidence_sampling.Rd b/man/sits_confidence_sampling.Rd index 531f0f48f..4a76bed20 100644 --- a/man/sits_confidence_sampling.Rd +++ b/man/sits_confidence_sampling.Rd @@ -8,7 +8,9 @@ sits_confidence_sampling( probs_cube, n = 20L, min_margin = 0.9, - sampling_window = 10L + sampling_window = 10L, + multicores = 1L, + memsize = 1L ) } \arguments{ @@ -22,6 +24,12 @@ See \code{\link[sits]{sits_classify}} and \item{sampling_window}{Window size for collecting points (in pixels). The minimum window size is 10.} + +\item{multicores}{Number of workers for parallel processing +(integer, min = 1, max = 2048).} + +\item{memsize}{Maximum overall memory (in GB) to run the +function.} } \value{ A tibble with longitude and latitude in WGS84 with locations diff --git a/man/sits_detect_change.Rd b/man/sits_detect_change.Rd new file mode 100644 index 000000000..27ea46345 --- /dev/null +++ b/man/sits_detect_change.Rd @@ -0,0 +1,104 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/sits_detect_change.R +\name{sits_detect_change} +\alias{sits_detect_change} +\alias{sits_detect_change.sits} +\alias{sits_detect_change.raster_cube} +\alias{sits_detect_change.default} +\title{Detect changes in time series} +\usage{ +sits_detect_change( + data, + cd_method, + ..., + filter_fn = NULL, + multicores = 2L, + progress = TRUE +) + +\method{sits_detect_change}{sits}( + data, + cd_method, + ..., + filter_fn = NULL, + multicores = 2L, + progress = TRUE +) + +\method{sits_detect_change}{raster_cube}( + data, + cd_method, + ..., + roi = NULL, + filter_fn = NULL, + impute_fn = impute_linear(), + start_date = NULL, + end_date = NULL, + memsize = 8L, + multicores = 2L, + output_dir, + version = "v1", + verbose = FALSE, + progress = TRUE +) + +\method{sits_detect_change}{default}(data, cd_method, ...) +} +\arguments{ +\item{data}{Set of time series.} + +\item{cd_method}{Change detection method (with parameters).} + +\item{...}{Other parameters for specific functions.} + +\item{filter_fn}{Smoothing filter to be applied - optional +(clousure containing object of class "function").} + +\item{multicores}{Number of cores to be used for classification +(integer, min = 1, max = 2048).} + +\item{progress}{Logical: Show progress bar?} + +\item{roi}{Region of interest (either an sf object, shapefile, +or a numeric vector with named XY values +("xmin", "xmax", "ymin", "ymax") or +named lat/long values +("lon_min", "lat_min", "lon_max", "lat_max").} + +\item{impute_fn}{Imputation function to remove NA.} + +\item{start_date}{Start date for the classification +(Date in YYYY-MM-DD format).} + +\item{end_date}{End date for the classification +(Date im YYYY-MM-DD format).} + +\item{memsize}{Memory available for classification in GB +(integer, min = 1, max = 16384).} + +\item{output_dir}{Valid directory for output file. +(character vector of length 1).} + +\item{version}{Version of the output +(character vector of length 1).} + +\item{verbose}{Logical: print information about processing time?} +} +\value{ +Time series with detection labels for + each point (tibble of class "sits") + or a data cube indicating detections in each pixel + (tibble of class "detections_cube"). +} +\description{ +Given a set of time series or an image, this function compares +each time series with a set of change/no-change patterns, and indicates +places and dates where change has been detected. +} +\author{ +Gilberto Camara, \email{gilberto.camara@inpe.br} + +Felipe Carlos, \email{efelipecarlos@gmail.com} + +Felipe Carvalho, \email{felipe.carvalho@inpe.br} +} diff --git a/man/sits_detect_change_method.Rd b/man/sits_detect_change_method.Rd new file mode 100644 index 000000000..c5279f886 --- /dev/null +++ b/man/sits_detect_change_method.Rd @@ -0,0 +1,27 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/sits_detect_change_method.R +\name{sits_detect_change_method} +\alias{sits_detect_change_method} +\title{Create detect change method.} +\usage{ +sits_detect_change_method(samples, cd_method = sits_dtw()) +} +\arguments{ +\item{samples}{Time series with the training samples.} + +\item{cd_method}{Change detection method.} +} +\value{ +Change detection method prepared + to be passed to + \code{\link[sits]{sits_detect_change}} +} +\description{ +Prepare detection change method. Currently, sits supports the +following methods: 'dtw' (see \code{\link[sits]{sits_dtw}}) +} +\author{ +Gilberto Camara, \email{gilberto.camara@inpe.br} + +Felipe Carlos, \email{efelipecarlos@gmail.com} +} diff --git a/man/sits_dtw.Rd b/man/sits_dtw.Rd new file mode 100644 index 000000000..390040c22 --- /dev/null +++ b/man/sits_dtw.Rd @@ -0,0 +1,38 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/sits_dtw.R +\name{sits_dtw} +\alias{sits_dtw} +\title{Dynamic Time Warping for Detect changes.} +\usage{ +sits_dtw(samples = NULL, ..., threshold = NULL, window = NULL) +} +\arguments{ +\item{samples}{Time series with the training samples.} + +\item{...}{Other relevant parameters.} + +\item{threshold}{Threshold used to define if an event was detected.} + +\item{window}{ISO8601-compliant time period used to define the +DTW moving window, with number and unit, +where "D", "M" and "Y" stands for days, month and +year; e.g., "P16D" for 16 days. This parameter is not +used in operations with data cubes.} +} +\value{ +Change detection method prepared to be passed to + \code{\link[sits]{sits_detect_change_method}} +} +\description{ +Create a Dynamic Time Warping (DTW) method for the +\code{\link[sits]{sits_detect_change_method}}. +} +\author{ +Felipe Carlos, \email{efelipecarlos@gmail.com} + +Felipe Carvalho, \email{felipe.carvalho@inpe.br} + +Gilberto Camara, \email{gilberto.camara@inpe.br} + +Rolf Simoes, \email{rolf.simoes@inpe.br} +} diff --git a/man/sits_tempcnn.Rd b/man/sits_tempcnn.Rd index 1b8b718d9..08619f1ff 100644 --- a/man/sits_tempcnn.Rd +++ b/man/sits_tempcnn.Rd @@ -92,7 +92,8 @@ Please refer to the sits documentation available in \examples{ if (sits_run_examples()) { # create a TempCNN model - torch_model <- sits_train(samples_modis_ndvi, sits_tempcnn()) + torch_model <- sits_train(samples_modis_ndvi, + sits_tempcnn(verbose = TRUE)) # plot the model plot(torch_model) # create a data cube from local files diff --git a/man/sits_uncertainty_sampling.Rd b/man/sits_uncertainty_sampling.Rd index fbfaca79c..820f8517c 100644 --- a/man/sits_uncertainty_sampling.Rd +++ b/man/sits_uncertainty_sampling.Rd @@ -8,7 +8,9 @@ sits_uncertainty_sampling( uncert_cube, n = 100L, min_uncert = 0.4, - sampling_window = 10L + sampling_window = 10L, + multicores = 1L, + memsize = 1L ) } \arguments{ @@ -21,6 +23,12 @@ See \code{\link[sits]{sits_uncertainty}}.} \item{sampling_window}{Window size for collecting points (in pixels). The minimum window size is 10.} + +\item{multicores}{Number of workers for parallel processing +(integer, min = 1, max = 2048).} + +\item{memsize}{Maximum overall memory (in GB) to run the +function.} } \value{ A tibble with longitude and latitude in WGS84 with locations diff --git a/src/RcppExports.cpp b/src/RcppExports.cpp index 83181fc6f..e3dcefca1 100644 --- a/src/RcppExports.cpp +++ b/src/RcppExports.cpp @@ -36,6 +36,18 @@ BEGIN_RCPP return rcpp_result_gen; END_RCPP } +// dtw_distance +double dtw_distance(const NumericMatrix& ts1, const NumericMatrix& ts2); +RcppExport SEXP _sits_dtw_distance(SEXP ts1SEXP, SEXP ts2SEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::RNGScope rcpp_rngScope_gen; + Rcpp::traits::input_parameter< const NumericMatrix& >::type ts1(ts1SEXP); + Rcpp::traits::input_parameter< const NumericMatrix& >::type ts2(ts2SEXP); + rcpp_result_gen = Rcpp::wrap(dtw_distance(ts1, ts2)); + return rcpp_result_gen; +END_RCPP +} // C_kernel_median NumericVector C_kernel_median(const NumericMatrix& x, int ncols, int nrows, int band, int window_size); RcppExport SEXP _sits_C_kernel_median(SEXP xSEXP, SEXP ncolsSEXP, SEXP nrowsSEXP, SEXP bandSEXP, SEXP window_sizeSEXP) { @@ -672,6 +684,7 @@ END_RCPP static const R_CallMethodDef CallEntries[] = { {"_sits_weighted_probs", (DL_FUNC) &_sits_weighted_probs, 2}, {"_sits_weighted_uncert_probs", (DL_FUNC) &_sits_weighted_uncert_probs, 2}, + {"_sits_dtw_distance", (DL_FUNC) &_sits_dtw_distance, 2}, {"_sits_C_kernel_median", (DL_FUNC) &_sits_C_kernel_median, 5}, {"_sits_C_kernel_mean", (DL_FUNC) &_sits_C_kernel_mean, 5}, {"_sits_C_kernel_sd", (DL_FUNC) &_sits_C_kernel_sd, 5}, diff --git a/src/detect_change_distances.cpp b/src/detect_change_distances.cpp new file mode 100644 index 000000000..7cf5fd0ed --- /dev/null +++ b/src/detect_change_distances.cpp @@ -0,0 +1,58 @@ +#include +#include "./dtw.h" + +using namespace Rcpp; + +/** + * Convert `NumericMatrix` to 2D `std::vector`. + * + * @description + * This function converts a `NumericMatrix` into a 2D `std::vector`. + * + * @param mat A `NumericMatrix` with single or multi variate time-series. + */ +std::vector> to_cpp_vector(NumericMatrix mat) { + size_t rows = mat.nrow(); + size_t cols = mat.ncol(); + + std::vector> result(rows, std::vector(cols)); + + for(size_t i = 0; i < rows; ++i) { + for(size_t j = 0; j < cols; ++j) { + result[i][j] = mat(i, j); + } + } + + return result; +} + +/** + * Dynamic Time Warping (DTW) distance. + * + * @description + * This function calculates the Dynamic Time Warping (DTW) distance between + * two time-series. + * + * @param x A `double *` Time-series data. + * @param y A `double *` Self-Organizing Maps (SOM) codebook. + * @param np `int` Number of points in arrays `p1` and `p2`. + * @param nNA `int` Number of `NA` values in the arrays `p1` and `p2`. + * + * @reference + * Giorgino, T. (2009). Computing and Visualizing Dynamic Time Warping + * Alignments in R: The dtw Package. Journal of Statistical Software, 31(7), + * 1–24. https://doi.org/10.18637/jss.v031.i07 + * + * @return DTW distance. + */ +// [[Rcpp::export]] +double dtw_distance( + const NumericMatrix& ts1, + const NumericMatrix& ts2 +) +{ + std::vector> ts1_vec = to_cpp_vector(ts1); + std::vector> ts2_vec = to_cpp_vector(ts2); + + return (distance_dtw_op(ts1_vec, ts2_vec, 2)); +} diff --git a/src/dtw.cpp b/src/dtw.cpp new file mode 100644 index 000000000..6149345c5 --- /dev/null +++ b/src/dtw.cpp @@ -0,0 +1,100 @@ +#include +#include "./dtw.h" + +using namespace Rcpp; + +/** + * Compute the p-norm between two time-series. + * + * @description + * The `p-norm`, also known as the `Minkowski space`, is a generalized norm + * calculation that includes several types of distances based on the value + * of `p`. + * + * Common values of `p` include: + * + * - `p = 1` for the Manhattan (city block) distance; + * - `p = 2` for the Euclidean norm (distance). + * + * More details about p-norms can be found on Wikipedia: + * https://en.wikipedia.org/wiki/Norm_(mathematics)#p-norm + * + * @param a A `std::vector` with time-series values. + * @param b A `std::vector` with time-series values. + * @param p A `double` value of the norm to use, determining the type of + * distance calculated. + * + * @note + * Both vectors `a` and `b` must have the same length. + * + * @note + * The implementation of this DTW distance calculation was adapted from the + * `DTW_cpp` single header library (https://github.com/cjekel/DTW_cpp). + * + * @return The `p-norm` value between vectors `a` and `b`. + */ +double p_norm(std::vector a, std::vector b, double p) +{ + double d = 0; + + size_t index; + size_t a_size = a.size(); + + for (index = 0; index < a_size; index++) + { + d += std::pow(std::abs(a[index] - b[index]), p); + } + return std::pow(d, 1.0 / p); +} + +/** + * Dynamic Time Warping (DTW) distance. + * + * @description + * This function calculates the Dynamic Time Warping (DTW) distance between + * two time-series. + * + * @param x A `std::vector>` with time-series values. + * @param y A `std::vector>` with time-series values. + * + * @reference + * Giorgino, T. (2009). Computing and Visualizing Dynamic Time Warping + * Alignments in R: The dtw Package. Journal of Statistical Software, 31(7), + * 1–24. https://doi.org/10.18637/jss.v031.i07 + * + * @note + * The implementation of this DTW distance calculation was adapted from the + * `DTW_cpp` single header library (https://github.com/cjekel/DTW_cpp). + * + * @return DTW distance. + */ +double distance_dtw_op(std::vector> a, + std::vector> b, + double p) +{ + int n = a.size(); + int o = b.size(); + + std::vector> d(n, std::vector(o, 0.0)); + + d[0][0] = p_norm(a[0], b[0], p); + + for (int i = 1; i < n; i++) + { + d[i][0] = d[i - 1][0] + p_norm(a[i], b[0], p); + } + for (int i = 1; i < o; i++) + { + d[0][i] = d[0][i - 1] + p_norm(a[0], b[i], p); + } + for (int i = 1; i < n; i++) + { + for (int j = 1; j < o; j++) + { + d[i][j] = p_norm(a[i], b[j], p) + std::fmin( + std::fmin(d[i - 1][j], d[i][j - 1]), d[i - 1][j - 1] + ); + } + } + return d[n - 1][o - 1]; +} diff --git a/src/dtw.h b/src/dtw.h new file mode 100644 index 000000000..18c592c76 --- /dev/null +++ b/src/dtw.h @@ -0,0 +1,5 @@ + +#pragma once +double distance_dtw_op(std::vector> a, + std::vector> b, + double p); diff --git a/src/kohonen_distances.cpp b/src/kohonen_distances.cpp index 03023a45c..7deb919e6 100644 --- a/src/kohonen_distances.cpp +++ b/src/kohonen_distances.cpp @@ -1,105 +1,10 @@ #include #include "./sits_types.h" +#include "./dtw.h" using namespace Rcpp; -/** - * Compute the p-norm between two time-series. - * - * @description - * The `p-norm`, also known as the `Minkowski space`, is a generalized norm - * calculation that includes several types of distances based on the value - * of `p`. - * - * Common values of `p` include: - * - * - `p = 1` for the Manhattan (city block) distance; - * - `p = 2` for the Euclidean norm (distance). - * - * More details about p-norms can be found on Wikipedia: - * https://en.wikipedia.org/wiki/Norm_(mathematics)#p-norm - * - * @param a A `std::vector` with time-series values. - * @param b A `std::vector` with time-series values. - * @param p A `double` value of the norm to use, determining the type of - * distance calculated. - * - * @note - * Both vectors `a` and `b` must have the same length. - * - * @note - * The implementation of this DTW distance calculation was adapted from the - * `DTW_cpp` single header library (https://github.com/cjekel/DTW_cpp). - * - * @return The `p-norm` value between vectors `a` and `b`. - */ -double p_norm(std::vector a, std::vector b, double p) -{ - double d = 0; - - size_t index; - size_t a_size = a.size(); - - for (index = 0; index < a_size; index++) - { - d += std::pow(std::abs(a[index] - b[index]), p); - } - return std::pow(d, 1.0 / p); -} - -/** - * Dynamic Time Warping (DTW) distance. - * - * @description - * This function calculates the Dynamic Time Warping (DTW) distance between - * two time-series. - * - * @param x A `std::vector>` with time-series values. - * @param y A `std::vector>` with time-series values. - * - * @reference - * Giorgino, T. (2009). Computing and Visualizing Dynamic Time Warping - * Alignments in R: The dtw Package. Journal of Statistical Software, 31(7), - * 1–24. https://doi.org/10.18637/jss.v031.i07 - * - * @note - * The implementation of this DTW distance calculation was adapted from the - * `DTW_cpp` single header library (https://github.com/cjekel/DTW_cpp). - * - * @return DTW distance. - */ -double distance_dtw_op(std::vector> a, - std::vector> b, - double p) -{ - int n = a.size(); - int o = b.size(); - - std::vector> d(n, std::vector(o, 0.0)); - - d[0][0] = p_norm(a[0], b[0], p); - - for (int i = 1; i < n; i++) - { - d[i][0] = d[i - 1][0] + p_norm(a[i], b[0], p); - } - for (int i = 1; i < o; i++) - { - d[0][i] = d[0][i - 1] + p_norm(a[0], b[i], p); - } - for (int i = 1; i < n; i++) - { - for (int j = 1; j < o; j++) - { - d[i][j] = p_norm(a[i], b[j], p) + std::fmin( - std::fmin(d[i - 1][j], d[i][j - 1]), d[i - 1][j - 1] - ); - } - } - return d[n - 1][o - 1]; -} - /** * Dynamic Time Warping (DTW) distance. * @@ -163,7 +68,6 @@ double kohonen_euclidean_op(double *data, double *codes, int n, int nNA) { return d; } - /** * Shared pointer factory of the Dynamic Time Warping (DTW) distance function. * diff --git a/tests/testthat/test-active_learning.R b/tests/testthat/test-active_learning.R index 471079197..f72c8b1ed 100644 --- a/tests/testthat/test-active_learning.R +++ b/tests/testthat/test-active_learning.R @@ -34,7 +34,9 @@ test_that("Suggested samples have low confidence, high entropy", { uncert_cube, min_uncert = 0.3, n = 100, - sampling_window = 10 + sampling_window = 10, + multicores = 2, + memsize = 2 )) expect_true(nrow(samples_df) <= 100) @@ -80,7 +82,9 @@ test_that("Increased samples have high confidence, low entropy", { probs_cube = probs_cube, n = 20, min_margin = 0.5, - sampling_window = 10 + sampling_window = 10, + multicores = 2, + memsize = 2 ) ) expect_warning( @@ -88,7 +92,9 @@ test_that("Increased samples have high confidence, low entropy", { probs_cube = probs_cube, n = 60, min_margin = 0.5, - sampling_window = 10 + sampling_window = 10, + multicores = 2, + memsize = 2 ) ) labels <- sits_labels(probs_cube) diff --git a/tests/testthat/test-cube-mpc.R b/tests/testthat/test-cube-mpc.R index 7085006cd..0dab297c3 100644 --- a/tests/testthat/test-cube-mpc.R +++ b/tests/testthat/test-cube-mpc.R @@ -151,14 +151,14 @@ test_that("Creating Sentinel-1 RTC cubes from MPC", { cube_s1_rtc_reg <- sits_regularize( cube = cube_s1_rtc, - period = "P1M", + period = "P16D", res = 240, tiles = c("21LXJ", "21LYJ"), multicores = 1, output_dir = output_dir, progress = TRUE ) - expect_equal(length(sits_timeline(cube_s1_rtc_reg)), 3) + expect_equal(length(sits_timeline(cube_s1_rtc_reg)), 5) expect_true(all(c("21LXJ", "21LYJ") %in% cube_s1_rtc_reg$tile)) expect_true("EPSG:32721" %in% cube_s1_rtc_reg$crs) diff --git a/tests/testthat/test-plot.R b/tests/testthat/test-plot.R index 8932d2a62..d9775ce81 100644 --- a/tests/testthat/test-plot.R +++ b/tests/testthat/test-plot.R @@ -12,7 +12,6 @@ test_that("Plot Time Series and Images", { ) p2 <- plot(sits_patterns(cerrado_2classes)) - expect_equal(p2$guides$colour$title, "Bands") expect_equal(p2$theme$legend.position, "bottom") p3 <- cerrado_2classes |> @@ -22,7 +21,6 @@ test_that("Plot Time Series and Images", { expect_equal(as.Date(p3$data$Time[1]), as.Date("2000-09-13")) expect_equal(p3$data$Pattern[1], "Cerrado") expect_equal(p3$data$name[1], "EVI") - expect_equal(p3$guides$colour$title, "Bands") p4 <- cerrado_2classes |> sits_patterns() |> @@ -30,7 +28,6 @@ test_that("Plot Time Series and Images", { expect_equal(as.Date(p4$data$Time[1]), as.Date("2000-09-13")) expect_equal(p4$data$Pattern[1], "Cerrado") expect_equal(p4$data$name[1], "NDVI") - expect_equal(p4$guides$colour$title, "Bands") point_ndvi <- sits_select(point_mt_6bands, bands = "NDVI") set.seed(290356) @@ -49,7 +46,7 @@ test_that("Plot Time Series and Images", { progress = FALSE ) p <- plot(sinop, band = "NDVI", palette = "RdYlGn", rev = TRUE) - expect_equal(p$tm_shape$shp_name, "stars_obj") + expect_equal(p$tm_shape$shp_name, "rast") expect_equal(p$tm_raster$palette, "-RdYlGn") expect_equal(p$tm_grid$grid.projection, 4326) diff --git a/tests/testthat/test-view.R b/tests/testthat/test-view.R index 9bc1f4444..08d16c381 100644 --- a/tests/testthat/test-view.R +++ b/tests/testthat/test-view.R @@ -125,12 +125,6 @@ test_that("View", { expect_equal(v7$x$calls[[1]]$args[[1]], "GeoportailFrance.orthos") expect_equal(v7$x$calls[[5]]$method, "addRasterImage") - v8 <- sits_view(segments, band = "NDVI") - expect_true(grepl("EPSG3857", v8$x$options$crs$crsClass)) - expect_identical(v8$x$calls[[1]]$method, "addProviderTiles") - expect_identical(v8$x$calls[[1]]$args[[1]], "GeoportailFrance.orthos") - expect_identical(v8$x$calls[[5]]$method, "addRasterImage") - expect_identical(v8$x$calls[[6]]$method, "addPolygons") probs_segs <- sits_classify( data = segments, @@ -186,7 +180,10 @@ test_that("View", { green = "B16", blue = "B13", dates = "2018-08-29") - + v_cb2 <- sits_view(cbers_cube, + tiles = c("007004", "007005"), + band = "B16", + dates = "2018-08-29") expect_identical(v_cb$x$options$crs$crsClass, "L.CRS.EPSG3857") expect_identical(v_cb$x$calls[[1]]$args[[1]], "GeoportailFrance.orthos") expect_identical(v_cb$x$calls[[5]]$method, "addRasterImage")