R/SOptim_SegmentationFunctions.R

Defines functions segmentationGeneric segmentation_OTB_LSMS2 segmentation_OTB_LSMS segmentation_RSGISLib_Shep segmentation_Terralib_MRGrow segmentation_Terralib_Baatz segmentation_ArcGIS_MShift segmentation_GRASS_RG segmentation_SAGA_SRG segmentationParamNames

Documented in segmentation_ArcGIS_MShift segmentationGeneric segmentation_GRASS_RG segmentation_OTB_LSMS segmentation_OTB_LSMS2 segmentationParamNames segmentation_RSGISLib_Shep segmentation_SAGA_SRG segmentation_Terralib_Baatz segmentation_Terralib_MRGrow

## ---------------------------------------------------------------------------------------------------------------- ##
## --- SEGMENTATION FUNCTIONS ----------------------------------------------------------------------------------------
## ---------------------------------------------------------------------------------------------------------------- ##


#' Names of segmentation parameters that will be optimized
#' 
#' Outputs a list containing the names of segmentation parameters that will be optimized by 
#' the package for each available segmentation algorithm: 
#' \itemize{
#'    \item SAGA_SRG - SAGA Seeded Region Growing, 
#'    \item GRASS_RG - GRASS Region Growing, 
#'    \item ArcGIS_MShift - Mean Shift algorithm from ArcGIS, 
#'    \item TerraLib_Baatz and TerraLib_MRGrow - Baatz algorithm and Mean Region Growing from TerraLib,  
#'    \item RSGISLib_Shep - Shepherd's algorithm from RSGISLib,and,
#'    \item Orfeo Toolbox (OTB) - Large-scale Mean-shift (single and two parameter set)
#'    
#' }
#' 
#' @return Returns the parameter names as \code{list} object.
#'  
#' @seealso 
#' \itemize{
#'    \item \code{\link{segmentation_SAGA_SRG}}, 
#'    \item \code{\link{segmentation_GRASS_RG}}, 
#'    \item \code{\link{segmentation_ArcGIS_MShift}}, 
#'    \item \code{\link{segmentation_Terralib_Baatz}}, 
#'    \item \code{\link{segmentation_Terralib_MRGrow}}, 
#'    \item \code{\link{segmentation_RSGISLib_Shep}},
#'    \item \code{\link{segmentation_OTB_LSMS}},
#'    \item \code{\link{segmentation_OTB_LSMS2}}
#' }
#' 
#' @export

segmentationParamNames <- function(){
  
  segmOptimParamList<-
    list(
      SAGA_SRG        = c("Bandwidth",
                          "GaussianWeightingBW",
                          "VarFeatSpace",
                          "VarPosSpace"),
      
      GRASS_RG        = c("Threshold",
                          "MinSize"),
      
      ArcGIS_MShift   = c("SpectralDetail",
                          "SpatialDetail",
                          "MinSegmentSize"),
      
      Terralib_Baatz  = c("CompactnessWeight",
                          "SpectralWeight",
                          "Threshold",
                          "MinSize"),
      
      Terralib_MRGrow = c("Threshold",
                          "MinSize") ,
      
      RSGISLib_Shep   = c("NumClust",
                          "MinSize",
                          "SpectralThresh"),
      
      OTB_LSMS   =      c("SpectralRange",
                          "SpatialRange",
                          "MinSize"),
      
      OTB_LSMS2   =      c("MS_SpectralRange",
                          "MS_SpatialRange",
                          "LSS_SpectralRange",
                          "LSS_SpatialRange",
                          "MinSize")
      )
  
  return(segmOptimParamList)
}


#' SAGA Seeded Region Growing segmentation algorithm
#' 
#' A function to access SAGA's Seeded Region Growing segmentation algorithm 
#' through CLI and optimize some of its parameters using genetic algorithms. The segmentation algorithm has two 
#' stages: 1) seed generation and, 2) seeded region growing.
#' 
#' @param x A vector containing the parameters to be optimized by package GA: 
#' \itemize{
#'  \item [1] BAND_WIDTH - bandwidth (cells; default: 10), 
#'  \item [2] DW_BANDWIDTH - Gaussian and Exponential Weighting Bandwidth (default: 5) (both for seed generation in stage 1) and, 
#'  \item [3] SIG_1 - variance in feature space (default: 1), 
#'  \item [4] SIG_2 - variance in position space (default: 1) (both for region growing - stage 2).#' 
#' }
#' If you are using this function outside an optimization context it is better to directly define the 
#' segmentation parameters in \code{Bandwidth}, \code{GaussianWeightingBW}, \code{VarFeatSpace} and 
#' \code{VarPosSpace}. 
#' 
#' @param rstList File paths to the SAGA header files (format: .sgrd) used to perform image segmentation.
#' File names should be provided either by a single string with each file separated by a ';'
#' (e.g. \code{red_band.sgrd;green_band.sgrd;nir_band.sgrd}), or a vector of strings with one file name
#' for each element (e.g. resulting from a \code{list.files} call). 
#' 
#' @param outputSegmRst A path to the output segmented image in SAGA .sdat/.srgd format (default: \code{NULL}; in this 
#' case the segmenter will use the working directory).
#' 
#' @param Bandwidth Segmentation bandwidth.
#' 
#' @param GaussianWeightingBW Gaussian and Exponential Weighting Bandwidth. 
#' 
#' @param VarFeatSpace Variance in feature space.
#' 
#' @param VarPosSpace Variance in position space.
#' 
#' @param seedType Seed type options: [0] minima of variance [1] maxima of variance (default: 0).
#' 
#' @param method1 Method 1: [0] band width smoothing [1] band width search (default: 0).
#' 
#' @param DWeighting Weighting function: [0] no distance weighting [1] inverse distance to a power [2] exponential 
#' [3] gaussian weighting (default: 3) - note: only exponential or Gaussian will work! Otherwise it will fail to 
#' optimize DW_BANDWIDTH parameter.
#' 
#' @param normalize Normalize inputs? [0] No [1] Yes (default: 0).
#' 
#' @param neighbour Select the neighbourhood type: [0] 4 connected pixels (von Neumann), [1] 8 connected pixels 
#' (Moore) (default is 0).
#' 
#' @param method2 Method 2: [0] feature space and position [1] feature space (default: 0).
#' 
#' @param thresh Similarity threshold (default: 0).
#' 
#' @param leafSize Leaf size (for speed optimisation) (default: 1024).
#' 
#' @param SAGApath Path to SAGA command-line interface (default: NULL; meaning that PATH env variable is used).
#' 
#' @param verbose Output messages? (default: TRUE)
#' 
#' @details 
#' Only accepts SAGA raster file format (problems encontered during segmentation when using other file formats).       
#' Only parameters in \code{x} will be optimized while the remaining will maintain their default values.     
#'  
#' This function uses the following SAGA commands: \emph{saga_cmd imagery_segmentation 2} and 
#' \emph{saga_cmd imagery_segmentation 3}. See more info at:         
#' \url{http://www.saga-gis.org/saga_tool_doc/3.0.0/imagery_segmentation_2.html}      
#' \url{http://www.saga-gis.org/saga_tool_doc/3.0.0/imagery_segmentation_3.html}      
#' \url{http://www.saga-gis.org/saga_tool_doc/}
#' 
#' @references 
#' Adams, R. & Bischof, L. (1994): Seeded Region Growing. IEEE Transactions on Pattern Analysis 
#' and Machine Intelligence, Vol.16, No.6, p.641-647.     
#'       
#' Bechtel, B., Ringeler, A. & Boehner, J. (2008): Segmentation for Object Extraction of Trees 
#' using MATLAB and SAGA. In: Boehner, J., Blaschke, T., Montanarella, L. [Eds.]: SAGA - Seconds 
#' Out. Hamburger Beitraege zur Physischen Geographie und Landschaftsoekologie, 19:59-70.      
#' \href{http://downloads.sourceforge.net/saga-gis/hbpl19_01.pdf}{download}
#' 
#' @return A list object containing output file paths resulting from the segmentation run. 
#' (these files will be cleaned after each GA iteration). 
#' 
#' @importFrom tools file_ext
#' 
#' @export


segmentation_SAGA_SRG <- function(x, rstList, outputSegmRst=NULL, 
                                  Bandwidth=NULL, GaussianWeightingBW=NULL, VarFeatSpace=NULL, VarPosSpace=NULL, 
                                  seedType=0, method1=0, DWeighting=3, normalize=0, neighbour=0, 
                                  method2=0,thresh=0, leafSize=1024, SAGApath=NULL, verbose=TRUE){

  if(missing(x)){
    if(is.null(Bandwidth) || is.null(GaussianWeightingBW) || is.null(VarFeatSpace) || is.null(VarPosSpace)){
      stop("If segmentation parameters are not defined in x then these must be passed on all the following 
           parameters: Bandwidth, GaussianWeightingBW, VarFeatSpace and VarPosSpace! Check documentation for 
           more details.")
    }else{
      x<-c(Bandwidth, GaussianWeightingBW, VarFeatSpace, VarPosSpace)
    }
  }
  
  # Generate file names (user-defined or temporary)
  if(!is.null(outputSegmRst)){
    
    extOutFile <- tools::file_ext(outputSegmRst)
    if(!(extOutFile %in% c("sgrd","sdat")))
      stop("The output file extension in outputSegmRst must be SAGA binary format: .sdat or .sgrd")
    
    fname <- gsub(".sgrd$","",basename(outputSegmRst))
    dname <- dirname(outputSegmRst)
    tmpFile.seeds <- paste(dname,"/",fname,"_seeds.sgrd",sep="")
    tmpFile.var   <- paste(dname,"/",fname,"_var.sgrd",sep="")
    tmpFile.segm  <- outputSegmRst
  }else{
    tmpFile.seeds<-repBSlash(tempfile("saga_seeds_",getwd(),".sgrd"))
    tmpFile.var<-repBSlash(tempfile("saga_var_",getwd(),".sgrd"))
    tmpFile.segm <- repBSlash(tempfile("saga_segm_",getwd(),".sgrd"))
  }
  
  
  # If binary SAGA path is defined then use to make the command line
  if(is.null(SAGApath)){
    prefix<-""
  }else{
    prefix<-paste(SAGApath,"/",sep="")
  }
  
  # If rstList is a vector with length > 1, then collapse into a single string
  if(length(rstList) > 1){
    rstList <- paste(rstList, collapse=';')
  }
  
  # Create SAGA comand line strings
  # Stage 1 - seed generation
  segmSAGA_Stage1<-paste(prefix,"saga_cmd imagery_segmentation 2 ",
                         "-FEATURES \"",rstList,"\" ",
                         "-VARIANCE ",tmpFile.var," ",
                         "-SEED_GRID ",tmpFile.seeds, " ",
                         "-SEED_TYPE ",seedType," ",
                         "-METHOD ",method1," ", 
                         "-BAND_WIDTH ",x[1]," ",
                         "-NORMALIZE ",normalize," ",
                         "-DW_WEIGHTING ",DWeighting," ",
                         "-DW_BANDWIDTH ",x[2],
                         sep="") 
  
  # Stage 2 - image segmentation
  segmSAGA_Stage2<-paste(prefix,"saga_cmd imagery_segmentation 3 ",
                         "-FEATURES \"",rstList,"\" ", 
                         "-SEEDS ",tmpFile.seeds," ",
                         "-SEGMENTS ",tmpFile.segm," ",
                         "-NORMALIZE ",normalize," ",
                         "-NEIGHBOUR ",neighbour," ",
                         "-METHOD ",method2," ",
                         "-SIG_1 ",x[3], " ",
                         "-SIG_2 ",x[4]," ",
                         "-THRESHOLD ",thresh," ",
                         "-LEAFSIZE ",leafSize,
                         sep="")
  
  if(verbose) checkPrintSegmentationParams(x,segmentMethod="SAGA_SRG")
  
  # Run SAGA commands
  #
  if(.Platform$OS.type=="windows"){
    system(segmSAGA_Stage1,ignore.stdout = TRUE, ignore.stderr = TRUE, show.output.on.console = FALSE)
    system(segmSAGA_Stage2,ignore.stdout = TRUE, ignore.stderr = TRUE, show.output.on.console = FALSE)
  }else{
    system(segmSAGA_Stage1,ignore.stdout = TRUE, ignore.stderr = TRUE)
    system(segmSAGA_Stage2,ignore.stdout = TRUE, ignore.stderr = TRUE)
  }
  
  out<-list(

    segm=gsub(".sgrd",".sdat",tmpFile.segm),
    segm.sgrd=tmpFile.segm,
    segm.meta1=gsub(".sgrd",".prj",tmpFile.segm),
    segm.meta1=gsub(".sgrd",".sdat.aux.xml",tmpFile.segm),
    segm.meta1=gsub(".sgrd",".mgrd",tmpFile.segm),
    
    seeds=gsub(".sgrd",".sdat",tmpFile.seeds),
    seeds.sgrd=tmpFile.seeds,
    seeds.meta1=gsub(".sgrd",".prj",tmpFile.seeds),
    seeds.meta2=gsub(".sgrd",".sdat.aux.xml",tmpFile.seeds),
    seeds.meta3=gsub(".sgrd",".mgrd",tmpFile.seeds),
    
    var=gsub(".sgrd",".sdat",tmpFile.var),
    var.sgrd=tmpFile.var,
    var.meta1=gsub(".sgrd",".prj",tmpFile.var),
    var.meta2=gsub(".sgrd",".sdat.aux.xml",tmpFile.var),
    var.meta3=gsub(".sgrd",".mgrd",tmpFile.var)
  )
  
  class(out) <- "SOptim.SegmentationResult"
  
  return(out)
  
}



#' GRASS Region Growing image segmentation
#' 
#' A function providing CLI access to GRASS Region Growing image segmentation algorithm 
#' and optimize parameters using genetic algorithms. To use this function, image features used in 
#' segmentation must be imported into a GRASS database (\code{\link{importToGRASS}}).
#' 
#' @param x A vector with size two defining the values of segmentation parameters optimized by GA: 
#' \itemize{
#'   \item [1] threshold (float) - difference threshold between 0 and 1. Threshold: 0 merges only 
#' identical segments; threshold: 1 merges all. 
#'   \item [2] minimum number of cells in a segment (integer) - the final step will merge small 
#'  segments with their best neighbor; options: 1-100000 (default: 1).
#' }
#' If you are using this function outside an optimization context it is better to directly define the 
#' segmentation parameters in \code{Threshold} and \code{MinSize}. 
#' 
#' @param GRASS.path Defines the initialization path to GRASS .bat file for windows (for example: `C:/GRASS/grass70`) 
#' or the .sh or .py file for Linux (usually this should be set to `/usr/bin/grass`). 
#' The default is "grass70" which assumes that GRASS is in the path environmental variables and version 
#' 7.0.x (which may not be appropriate in most cases and therefore this parameter should be set explicitly).
#' 
#' @param GRASS.inputRstName Name of the raster dataset that will be segmented (typically a 
#' multi-layered raster dataset with segmentation features in each band).
#' 
#' @param GRASS.GISDBASE Name of GRASS GIS database.
#' 
#' @param GRASS.LOCATION_NAME Name of GRASS location name (default: "demolocation").
#' 
#' @param GRASS.MAPSET Name of GRASS mapset (default: "PERMANENT").
#' 
#' @param outputSegmRst A path to the output segmented image exported by GRASS \code{r.out.gdal}. The output file can 
#' be in any of GDAL formats  (default: \code{NULL}; in this case the segmenter will internally determine a temporary 
#' output folder and file path inside the working directory). 
#' 
#' @param Threshold Difference threshold between 0 and 1. Threshold: 0 merges only identical segments; 
#' threshold: 1 merges all
#' @param MinSize Minimum number of cells in a segment (integer) - the final step will merge small 
#' segments with their best neighbor
#' 
#' @param memory Memory used to run i.segment (default: 1024Mb).
#' 
#' @param iterations Maximum number of iterations (default: 20).
#' 
#' @param verbose Output messages? (default: TRUE)
#' 
#' @details 
#' This function uses a batch file defining the batch job (GRASS_BATCH_JOB) and starts GRASS with the 
#' appropriate parametrization. The \code{i.segment} command is used to run the segmentation.    
#' The similarity calculation method uses the default value ("euclidean").     
#' See more info at GRASS manual pages online:
#' \code{i.segment} at \url{https://grass.osgeo.org/grass72/manuals/i.segment.html}   
#' Access to GRASS GIS manuals \url{https://grass.osgeo.org/documentation/manuals/}
#' 
#' 
#' @return A list object containing output file paths resulting from the segmentation run. 
#' These files will be cleaned after each GA iteration. 
#' 
#' @export

segmentation_GRASS_RG <- function(x, GRASS.path="grass", GRASS.inputRstName, GRASS.GISDBASE, GRASS.LOCATION_NAME="demolocation",
                                GRASS.MAPSET="PERMANENT", outputSegmRst=NULL, Threshold=NULL, MinSize=NULL, memory=1024, 
                                iterations=20,verbose=TRUE){
  
  if(missing(x)){
    if(is.null(Threshold) || is.null(MinSize)){
      stop("If segmentation parameters are not defined in x then these must be passed on all the following 
           parameters: Threshold and MinSize! Check documentation for more details.")
    }else{
      x<-c(Threshold, MinSize)
    }
  }
  
  
  GRASS.GISDBASE<-gsub("\\\\","/",GRASS.GISDBASE)
  GRASSinitPath<-paste(GRASS.GISDBASE,GRASS.LOCATION_NAME,GRASS.MAPSET,sep="/")
  
  # Generate user-defined or temporary file names/paths
  tmpFile.GRASSsegm <- gsub("\\\\|/","",tempfile("GRASS_RG_Segm_",""))
  extType <- ifelse(.Platform$OS.type == "windows",".bat",".sh")
  
  if(is.null(outputSegmRst)){
    tmpFile.batchJob <- repBSlash(tempfile("GRASS_Segm_BatchCmd_",getwd(),extType))
    tmpFile.batchRun <- repBSlash(tempfile("GRASS_Segm_BatchCmdRun_",getwd(),".bat"))
    tmpFile.outRasterSegm <- repBSlash(tempfile("GRASS_Segm_",getwd(),".tif"))
  }else{
    #tmpFile.batchJob <- repBSlash(tempfile("GRASS_Segm_BatchCmd_",dirname(outputSegmRst),extType))
    #tmpFile.batchRun <- repBSlash(tempfile("GRASS_Segm_BatchCmdRun_",dirname(outputSegmRst),".bat"))
    #  
    tmpFile.batchJob <- repBSlash(tempfile("GRASS_Segm_BatchCmd_",getwd(),extType))
    tmpFile.batchRun <- repBSlash(tempfile("GRASS_Segm_BatchCmdRun_",getwd(),".bat"))
    tmpFile.outRasterSegm <- outputSegmRst
  }

  # Create the batch file to run segmentation in GRASS GIS
  cat("g.region raster=",GRASS.inputRstName,".1@",GRASS.MAPSET,"\n\n
      i.segment --verbose --overwrite group=",GRASS.inputRstName,"@",GRASS.MAPSET," output=",tmpFile.GRASSsegm," threshold=",x[1]," minsize=",as.integer(x[2])," memory=",memory," iterations=",iterations,"\n\n
      r.out.gdal -c --overwrite input=",tmpFile.GRASSsegm,"@",GRASS.MAPSET," output=",tmpFile.outRasterSegm," format=GTiff type=UInt32\n\n
      g.remove -f -b type=raster name=",tmpFile.GRASSsegm,"@",GRASS.MAPSET,"\n\n",
      sep="", file=tmpFile.batchJob)
  
  # Creates a batch file defining the batch job and starts GRASS
  # Change the batch file head if on Linux (chmod to allow .sh file to be executed...?!)
  prefix <- ifelse(.Platform$OS.type == "windows","",paste("#!/bin/sh\nchmod +x ",tmpFile.batchJob,"\n",sep=""))
  
  # Change the command according to the OS used to set the GRASS_BATCH_JOB variable
  setWord <- ifelse(.Platform$OS.type == "windows", "set", "export")
  
  # Define the second script file for running the GRASS_BATCH_JOB 
  cat(prefix,
      setWord," GRASS_BATCH_JOB=",tmpFile.batchJob,"\n\n",
      "\"",GRASS.path,"\" ",GRASSinitPath," -text",
      sep = "", 
      file = tmpFile.batchRun)
  
  if(verbose) checkPrintSegmentationParams(x,segmentMethod="GRASS_RG")

  # Run the batch files
  if(.Platform$OS.type == "windows"){
    # In windows shell function apparently gives less problems...
    # 2020-06-13: Changed both to FALSE after checking this 
    # was generating errors
    out <- try(shell(tmpFile.batchRun, 
                      ignore.stdout = FALSE, ignore.stderr = FALSE)) 
  }else{
    out <- try(system(paste("sh",tmpFile.batchRun), 
                      ignore.stdout = TRUE, ignore.stderr = TRUE))
  }
  
  # On-error clean up files
  if(inherits(out, "try-error") || out != 0){
    on.exit(doCleanUpActions(c(segm=tmpFile.outRasterSegm,
                               segmMeta=gsub(".tif",".tif.aux.xml",tmpFile.outRasterSegm),
                               batFile1=tmpFile.batchJob,
                               batFile2=tmpFile.batchRun)))

    stop("An error occurred while performing GRASS image segmentation! Check input parameters. 
         (On Linux/Mac check user permissions and perhaps try starting R (or RStudio) without root user...)")
  }else{
    out <- list(segm=tmpFile.outRasterSegm,
                segmMeta=gsub(".tif",".tif.aux.xml",tmpFile.outRasterSegm),
                batFile1=tmpFile.batchJob,
                batFile2=tmpFile.batchRun)
    
    class(out) <- "SOptim.SegmentationResult"
    
    return(out)
  }
}




#' ArcGIS Mean Shift segmentation algorithm
#' 
#' A function providing CLI access to ArcGIS Mean Shift segmentation and optimize its parameters 
#' using genetic algorithms.
#' 
#' @param x A vector with size three defining the parameters that will be optimized by GA:
#' \itemize{
#'   \item [1] spectral detail - level of importance given to the spectral differences of features in your imagery, 
#'   \item [2] spatial detail - level of importance given to the proximity between features in your imagery, and, 
#'   \item [3] minimum segment size - in pixels.
#' }
#' If you are using this function outside an optimization context it is better to directly define the 
#' segmentation parameters in \code{SpectralDetail}, \code{SpatialDetail} and \code{MinSegmentSize}. 
#' 
#' @param inputRstPath Path to the input raster file (typically a multi-layered raster dataset with 
#' segmentation features in each band).
#' 
#' @param outputSegmRst A path to the output segmented image (default: \code{NULL}; in this case the segmenter will 
#' internally determine a temporary output folder and file path inside the working directory)
#' 
#' @param SpectralDetail Spectral detail - level of importance given to the spectral differences of features in your imagery
#' @param SpatialDetail Spatial detail - level of importance given to the proximity between features in your imagery
#' @param MinSegmentSize Minimum segment size - in pixels
#' 
#' @param pythonPath Path to the python interpreter (default: NULL; meaning that it uses the one in the 
#' PATH variable).
#' 
#' @param verbose Print messages while running? (default: TRUE)
#' 
#' @details 
#' Some more info regarding the parameters that will be optimized:     
#' 
#' \describe{
#'     \item{\emph{Spectral detail}}{valid values range from 1.0 to 20.0 (double). A higher value is appropriate when 
#' you have features you want to classify separately but have somewhat similar spectral characteristics. 
#' Smaller values create spectrally smoother outputs. For example, with higher spectral detail in a forested 
#' scene, you will be able to have greater discrimination between the different tree species};
#' 
#'     \item{\emph{Spatial detail}}{valid values range from 1 to 20 (integer). A higher value is appropriate for a scene 
#' where your features of interest are small and clustered together. Smaller values create spatially 
#' smoother outputs. For example, in an urban scene, you could classify an impervious surface using a 
#' smaller spatial detail, or you could classify buildings and roads as separate classes using a higher 
#' spatial detail};
#' 
#'     \item{\emph{Minimum segment size}}{define the minimum size (in pixels; integer) of a given segment. This threshold is 
#' used to merge segments smaller than the defined size with their best fitting neighbor segment}.
#' }
#' 
#' @note 
#' Uses a predefined python script for accessing ArcGIS API and run the segmentation. Check 
#' \code{SegOptim/inst} folder.        
#' Each ArcGIS/Python instance works with a separate scratch directory to avoid  
#' disk I/O conflicts. However, some ArcGIS/Python undetermined problems may still occur 
#' during segmentation. 
#' 
#' @seealso ArcGIS manual on Mean Shift segmentation: 
#' \url{http://desktop.arcgis.com/en/arcmap/10.3/tools/spatial-analyst-toolbox/segment-mean-shift.htm}
#' 
#' @return A list object containing output file paths resulting from the segmentation run. 
#' These files will be cleaned after each GA iteration. 
#' 
#' @export

segmentation_ArcGIS_MShift <- function(x, inputRstPath, outputSegmRst=NULL, 
                                       SpectralDetail=NULL, SpatialDetail=NULL, MinSegmentSize=NULL, 
                                       pythonPath=NULL, verbose=TRUE){
  
  
  if(missing(x)){
    if(is.null(SpectralDetail) || is.null(SpatialDetail) || is.null(MinSegmentSize)){
      stop("If segmentation parameters are not defined in x then these must be passed on all the following 
           parameters: SpectralDetail, SpatialDetail and MinSegmentSize! Check documentation for more details.")
    }else{
      x <- c(SpectralDetail, SpatialDetail, MinSegmentSize)
    }
  }
  
  
  if(is.null(pythonPath)){
    prefix <- ""
  }else{
    prefix <- paste(pythonPath,"/",sep="")
  }

  # Check if an output file was defined otherwise use a temporary
  if(!is.null(outputSegmRst)){
    tmpDirScratch     <- dirname(outputSegmRst)
    tmpFileSegmOutRst <- basename(outputSegmRst)
  }else{
    tmpDirScratch     <- repBSlash(tempfile("",getwd()))
    tmpFileSegmOutRst <- gsub("\\\\","",tempfile("ArcGIS_SegmIdx_","",".tif"))
  }

  # Get the python script used to perform ArcGIS Mean-shift segmentation algorithm
  PyScriptPath <- getPythonFile("ArcGIS_MShift.py",
                              "SegOptim",
                              c("D:/MyDocs/R-dev",getwd()))

  if(is.null(PyScriptPath)){
    stop("Unable to find the path to the ArcGIS python segmentation script (ArcGIS_MShift.py) in dev or pkg paths!")
  }
    
  # Assemble command string to run
  cmd <- paste(prefix,
             "python \"",PyScriptPath,"\" ",
             tmpDirScratch," ",
             "\"",inputRstPath,"\" ",
             x[1]," ",
             as.integer(x[2])," ",
             as.integer(x[3])," ",
             tmpFileSegmOutRst,
             sep="")
  
  #if(verbose) cat(cmd,"\n\n")
  if(verbose) checkPrintSegmentationParams(x, segmentMethod = "ArcGIS_MShift")
  
  ## Run python script for ArcGIS Mean-shift image segmentation from the command line
  sysOut <- try(system(cmd,ignore.stdout = TRUE, ignore.stderr = TRUE, show.output.on.console = FALSE))
  
  if(sysOut != 0){
    on.exit(doCleanUpActions(tmpDirScratch))
    warning("Unable to perform segmentaton with function segmentation_ArcGIS_MShift!")
    return(NA)
  }
  
  
  # Append the temporary scratch directory to the the file names
  tmpFileSegmOutRst<-paste(tmpDirScratch,tmpFileSegmOutRst,sep="/")
  
  
  out <- list(
    segm=tmpFileSegmOutRst,
    segmMeta1=gsub(".tif",".tif.aux.xml",tmpFileSegmOutRst),
    segmMeta2=gsub(".tif",".tfw",tmpFileSegmOutRst),
    segmMeta3=gsub(".tif",".tif.ovr",tmpFileSegmOutRst),
    segmMeta4=gsub(".tif",".tif.vat.cpg",tmpFileSegmOutRst),
    segmMeta5=gsub(".tif",".tif.vat.dbf",tmpFileSegmOutRst),
    segmMeta6=gsub(".tif",".tif.xml",tmpFileSegmOutRst),
    tempDir = tmpDirScratch)
  
  class(out) <- "SOptim.SegmentationResult"
  
  return(out)
  
}


#' TerraLib Baatz-Shcape segmentation algorithm 
#' 
#' A function providing CLI access to TerraLib 5 Baatz-Shcape segmentation and optimize its parameters 
#' using genetic algorithms.
#' 
#' @param x A vector with size four defining the segmentation parameters that will be optimized using 
#' genetic algorithms from GA package: 
#' \itemize{
#'   \item [1] Compactness weight (double), 
#'   \item [2] Spectral / color weight (double), 
#'   \item [3] Threshold parameter (double), 
#'   \item [4] Minimum size of the segments (integer).
#' }
#' If you are using this function outside an optimization context it is better to directly define the 
#' segmentation parameters in \code{CompactnessWeight}, \code{SpectralWeight}, \code{Threshold} and 
#' \code{MinSize}.
#' 
#' @param inputRstPath Path to the input raster file (typically a multi-layered raster dataset with 
#' segmentation features in each band).
#' 
#' @param outputSegmRst A path to the output segmented image (default: \code{NULL}; in this case the segmenter will 
#' internally determine a temporary output folder and file path inside the working directory)
#' 
#' @param CompactnessWeight Compactness weight (double)
#' @param SpectralWeight Spectral / color weight (double)
#' @param Threshold Threshold parameter (double)
#' @param MinSize Minimum size of the segments (integer)
#' 
#' @param verbose Print output messages? (default: TRUE)
#' 
#' @param TerraLib.path Path to the TerraLib binaries (locating \code{terralib_cli_baatz} executable). 
#' If not set it uses the PATH env var to find it.
#' 
#' @return A list object containing output file paths resulting from the segmentation run. 
#' These files will be cleaned after each GA iteration.  
#' 
#' @details 
#' By default all bands have the same weight (bw), i.e., bw = 1 / number of bands. Also, by default, 
#' the executable file for running this segmentation algorithm (named \code{terralib_cli_baatz}) should 
#' be accessible by the PATH env variable. 
#' 
#' @note 
#' Windows binaries for CLI access to TerraLib v5.1.1 Baatz-Schape segmenter are available at: 
#' \url{https://bitbucket.org/joao_goncalves/terralib5_cli_segmentation_mod/downloads} 
#' (unfortunetaley not yet tested in Linux environment...). Source could be used to build TerraLib for this 
#' effect, check source location \code{/examples/segmentationBaatz} and TerraLib instructions 
#' (\href{http://www.dpi.inpe.br/terralib5/wiki/doku.php?id=wiki:terralib50_build}{link}).    
#' The executable file for running this segmentation algorithm is named: \code{terralib_cli_baatz}    
#' 
#' @references 
#' Baatz, M.; Schape, A. Multiresolution segmentation: an optimization approach for high quality 
#' multi-scale image segmentation. In: XII Angewandte Geographische Informationsverarbeitung, 
#' Wichmann Verlag, Heidelberg, 2000.
#' 
#' @importFrom terra rast
#' @importFrom terra nlyr
#' @export
#' 

segmentation_Terralib_Baatz <- function(x, inputRstPath, outputSegmRst=NULL, CompactnessWeight=NULL, SpectralWeight=NULL, Threshold=NULL, MinSize=NULL, 
                                      verbose=TRUE, TerraLib.path=NULL){
  
  if(missing(x)){
    if(is.null(CompactnessWeight) || is.null(SpectralWeight) || is.null(Threshold) || is.null(MinSize)){
      stop("If segmentation parameters are not defined in x then these must be passed on all the following 
           parameters: CompactnessWeight, SpectralWeight, Threshold and MinSize! Check documentation for more details.")
    }else{
      x <- c(CompactnessWeight, SpectralWeight, Threshold, MinSize)
    }
  }
  
  # Output segmentation raster file
  if(is.null(outputSegmRst)){
    tmpFile.outRasterSegm <- repBSlash(tempfile("Terralib_Baatz_Segm_",getwd(),".tif")) 
  }else{
    tmpFile.outRasterSegm <- outputSegmRst
  }
  
  # Read inputRstPath metadata to extract the number of bands (required as segmentation input)
  inputRstMeta <- terra::rast(inputRstPath)
  nb <- (terra::nlyr(inputRstMeta)) - 1 # Changed this because terralib starts band counting at 0
  
  if(is.null(TerraLib.path))
    TerraLib.path<-""
  else
    TerraLib.path<-paste(TerraLib.path,"/",sep="")
  
  cmd<-paste(
    TerraLib.path, "terralib_cli_baatz ", # Command line exec file
    inputRstPath," ",         # Input raster path
    0, " ",    # Band interval to use - first band index nr
    nb, " ",   # Band interval to use - last band index nr
    x[1], " ", # Compactness Weight
    x[2], " ", # Spectral / color Weight
    x[3], " ", # Threshold parameter
    x[4], " ", # Min size of the segments
    tmpFile.outRasterSegm, # Output file
    sep=""
  )
  
  if(verbose) checkPrintSegmentationParams(x,segmentMethod="Terralib_Baatz")
  
  ## Run command line
  sysOut<-try(system(cmd,ignore.stdout = TRUE, ignore.stderr = TRUE, show.output.on.console = FALSE))

  
  if(!inherits(sysOut,"try-error") && sysOut == 0){
    
    out <- list(segm=tmpFile.outRasterSegm)
    class(out) <- "SOptim.SegmentationResult"
    return(out)
    
  }else{
    return(NA)
  }
  
}


#' TerraLib Mean Region Growing segmentation algorithm
#' 
#' A function providing CLI access to TerraLib 5 Mean Region Growing segmentation and optimize 
#' its parameters using genetic algorithms.
#' 
#' @param x A vector with size two defining the segmentation parameters that will be optimized using 
#' genetic algorithms from GA package: 
#' \itemize{
#'   \item [1] Threshold (double), 
#'   \item [2] Minimum size of the segments (integer).
#' }
#' If you are using this function outside an optimization context it is better to directly define the 
#' segmentation parameters in \code{Threshold} and \code{MinSize}.
#' 
#' @param inputRstPath Path to the input raster file (typically a multi-layered raster dataset with 
#' segmentation features in each band).
#' 
#' @param outputSegmRst A path to the output segmented image (default: \code{NULL}; in this case the segmenter will 
#' internally determine a temporary output folder and file path inside the working directory)
#' 
#' @param Threshold Threshold (double)
#' @param MinSize Minimum size of the segments (integer)
#' 
#' @param verbose Print output messages? (default: TRUE).
#' 
#' @param TerraLib.path Path to the TerraLib binaries (locating \code{terralib_cli_rg} executable). 
#' If not set it uses the PATH env var to find it.
#' 
#' @return A list object containing output file paths resulting from the segmentation run. 
#' These files will be cleaned after each GA iteration. 
#'  
#' @details 
#' By default all bands have the same weight (bw), i.e., bw = 1 / number of bands. Also, by default, 
#' the executable file for running this segmentation algorithm (named \code{terralib_cli_rg}) should 
#' be accessible by the PATH env variable.   
#' 
#' @note  
#' Windows binaries for CLI access to TerraLib v5.1.1 Mean Region Growing segmenter are available at: 
#' \url{https://bitbucket.org/joao_goncalves/terralib5_cli_segmentation_mod/downloads} 
#' (unfortunetaley not yet tested in Linux environment...). Source could be used to build TerraLib for this 
#' effect, check source location \code{/examples/segmentationRegionGrowing} and TerraLib instructions 
#' (\href{http://www.dpi.inpe.br/terralib5/wiki/doku.php?id=wiki:terralib50_build}{link}).
#' 
#' @importFrom terra rast
#' @importFrom terra nlyr
#' @export

segmentation_Terralib_MRGrow <- function(x, inputRstPath, outputSegmRst=NULL, Threshold=NULL, MinSize=NULL, verbose=TRUE, TerraLib.path=NULL){
  
  if(missing(x)){
    if(is.null(Threshold) || is.null(MinSize)){
      stop("If segmentation parameters are not defined in x then these must be passed on all the following 
           parameters: Threshold and MinSize! Check documentation for more details.")
    }else{
      x <- c(Threshold, MinSize)
    }
  }
  
  if(is.null(outputSegmRst)){
    # Output segmentation raster file
    tmpFile.outRasterSegm <- repBSlash(tempfile("Terralib_MRGrow_Segm_",getwd(),".tif"))
  }else{
    tmpFile.outRasterSegm <- outputSegmRst
  }

  
  # Read inputRstPath metadata to extract the number of bands (required as segmentation input)
  inputRstMeta <- terra::rast(inputRstPath)
  nb <- (terra::nlyr(inputRstMeta)) - 1 # Changed this because terralib starts band counting at 0
  
  if(is.null(TerraLib.path))
    TerraLib.path<-""
  else
    TerraLib.path<-paste(TerraLib.path,"/",sep="")
  
  cmd<-paste(
    TerraLib.path, "terralib_cli_rg ", # Command line exec file
    inputRstPath, " ",      # Input raster
    0, " ",    # Band interval to use - first band index nr
    nb, " ",   # Band interval to use - last band index nr
    x[1], " ", # Threshold parameter
    x[2], " ", # Min size of the segments
    tmpFile.outRasterSegm, # Output file
    sep=""
  )
  
  if(verbose) checkPrintSegmentationParams(x,segmentMethod="Terralib_MRGrow")
  
  ## Run command line
  sysOut<-try(system(cmd,ignore.stdout = TRUE, ignore.stderr = TRUE, show.output.on.console = FALSE))

  
  if(!inherits(sysOut,"try-error") && sysOut == 0){
    out <- list(segm=tmpFile.outRasterSegm)
    class(out) <- "SOptim.SegmentationResult"
    return(out)
    
  }else{
    return(NA)
  }
  
}

#' Shepherd segmentation algorithm from RSGISLib
#' 
#' A function providing CLI access to RSGISLib Shepherd segmentation and optimize its parameters 
#' using genetic algorithms.
#' 
#' @param x A vector of size three containing the parameters that will be optimized by the genetic algorithms 
#' from package GA: 
#' \itemize{
#'   \item [1] Number of clusters for running the k-means algorithm (integer)
#'   \item [2] Minimum segment size, if smaller it will be merged to neighbouring segments (in pixels; integer)
#'   \item [3] Spectral threshold used to merge objects (float), is a value providing the maximum (Euclidean 
#' distance) spectral separation for which to merge clumps. Set to a large value to ignore spectral 
#' separation and always merge.
#' }
#' If you are using this function outside an optimization context it is better to directly define the 
#' segmentation parameters in \code{NumClust}, \code{MinSize} and \code{SpectralThresh}.
#' 
#' @param inputRstPath Path to the input raster file (typically a multi-layered raster dataset with 
#' segmentation features in each band).
#' 
#' @param outputSegmRst A path to the output segmented image (default: \code{NULL}; in this case the segmenter will 
#' internally determine a temporary output folder and file path inside the working directory)
#' 
#' @param NumClust Number of clusters for running the k-means algorithm (integer)
#' @param MinSize Minimum segment size (in pixels; integer)
#' @param SpectralThresh Spectral threshold used to merge objects (float)
#' 
#' @param pythonPath Path to the Python interpreter (by default uses the one in the PATH env var). In windows it 
#' should be used for defining the path to the conda distribution of RSGISLib. 
#' 
#' @param verbose Print output messages (default: TRUE).
#' 
#' @return A list object containing output file paths resulting from the segmentation run. 
#' These files will be cleaned after each GA iteration.
#' 
#' @details 
#' By default the k-means algorithm running inside the segmenter will use \code{"INITCLUSTER_DIAGONAL_FULL_ATTACH"} 
#' for the initialization stage, 250 iterations and 0.0025 for the degree of change stopping condition.
#' 
#' @note 
#' Unfortunetelly these functionalities where not yet tested in Linux or Mac environments.
#' 
#' @seealso  
#' Also check out the RSGISLib manual pages at: \url{http://www.rsgislib.org/rsgislib_segmentation.html}.
#' 
#' @references 
#' Clewley, D., Bunting, P., Shepherd, J., Gillingham, S., Flood, N., Dymond, J., Lucas, R., 
#' Armston, J., Moghaddam, M., 2014. A Python-Based Open Source System for Geographic 
#' Object-Based Image Analysis (GEOBIA) Utilizing Raster Attribute Tables. Remote 
#' Sensing 6, 6111.        
#' 
#' @export

segmentation_RSGISLib_Shep <- function(x, inputRstPath, outputSegmRst=NULL, NumClust=NULL, MinSize=NULL, SpectralThresh=NULL, 
                                     pythonPath=NULL, verbose=TRUE){
  
  if(missing(x)){
    if(is.null(NumClust) || is.null(MinSize) || is.null(SpectralThresh)){
      stop("If segmentation parameters are not defined in x then these must be passed on all the following 
           parameters: NumClust, MinSize and SpectralThresh! Check documentation for more details.")
    }else{
      x <- c(NumClust, MinSize, SpectralThresh)
    }
  }
  
  if(is.null(pythonPath) || is.na(pythonPath)){
    stop("pythonPath is not defined! Unable to find RSGISLib...")
  }else{
    prefix=paste(pythonPath,"/",sep="")
  }
  
  # Find python script with CLI access to RSGISLib segmentation
  PyScriptPath<-getPythonFile("RSGISLib_Shep.py",
                              "SegOptim",
                              c("/media/kamo/HDD1/MyDocs/R-dev", # only for testing!!
                                "D:/MyDocs/R-dev/SegOptim/inst/PyScripts",
                                "E:/JG_Dropbox/Dropbox/SharedWorkFolder/SegOptim/inst/PyScripts",
                                "/media/sf_MyDocs/R-dev/SegOptim/inst/PyScripts",
                                getwd()))
  
  # If the the script can not be found stop execution
  if(is.null(PyScriptPath)){
    stop("Unable to find the path to the RSGISLib python script (RSGISLib_Shep.py) in dev or pkg paths!")
  }
  
  # Output segmentation raster file

  
  if(is.null(outputSegmRst)){
    tmpDir <- getwd()
    tmpFileSegmOutRst <- repBSlash(paste(tmpDir,"/RSGISLib_Shep_Segm_",randString(8),".tif",sep=""))
    tmpBatchFile <- repBSlash(paste(tmpDir,"/RSGISLib_Shep_Segm_",randString(8),".bat",sep=""))
  }else{
    tmpDir <- getwd()
    tmpFileSegmOutRst <- outputSegmRst
    tmpBatchFile <- repBSlash(paste(tmpDir,"/RSGISLib_Shep_Segm_",randString(8),".bat",sep=""))
  }
  
  # Assemble command string to run
  cmd<-paste(prefix,
             "python \"",PyScriptPath,"\" ",
             "\"",inputRstPath,"\" ",
             tmpDir," ",
             x[1]," ", # this will be converted to integer by python
             x[2]," ", # this one too
             x[3]," ",
             tmpFileSegmOutRst, sep="")
    
  if(.Platform$OS.type=="windows"){
    
    # If OS is windows-like then set the envir paths to the python path defined
    # Typically this would be Anaconda or Miniconda system
    cmd_paths<-paste("@echo off\n",
                     "set PYTHONPATH=",pythonPath,"/Lib\n",
                     "set PYTHONHOME=",pythonPath,"\n",
                     "set PATH=",pythonPath,";%PATH%", sep="")
  }else{
    cmd_paths<-"#!/bin/sh"
  }

  cat(cmd_paths,"\n",cmd, sep="", file = tmpBatchFile)
    
  if(verbose) checkPrintSegmentationParams(x,segmentMethod="RSGISLib_Shep")
  
  ## Run command line (don't print RSGISLib output to console)
  ## Changed this to run via system using cmd (win) or sh (unix)
  
  if(.Platform$OS.type == "windows"){
    #sysOut<-try(shell(tmpBatchFile, ignore.stdout = TRUE, ignore.stderr = TRUE, intern = FALSE))
    sysOut<-try(shell(tmpBatchFile, 
                       ignore.stdout = TRUE, ignore.stderr = TRUE, intern = FALSE))
  }else{
    sysOut<-try(system(paste("sh",tmpBatchFile), 
                       ignore.stdout = TRUE, ignore.stderr = TRUE, intern = FALSE))
  }
  
  if(!inherits(sysOut,"try-error") && sysOut == 0){
    out <-list( # The remaining temp files generated by RSGISLib were already removed by python
                segm=tmpFileSegmOutRst,
                batchFile=tmpBatchFile)
    class(out) <- "SOptim.SegmentationResult"
    return(out)
  }else{
    return(NA)
  }
}

#' Large Scale Mean Shift segmentation algorithm from Orfeo Toolbox (OTB)
#' 
#' A function providing CLI access to OTB's LSMS segmentation and optimize its parameters 
#' using genetic algorithms. This function uses the same values for the spectral and spatial 
#' range parameters in the mean-shift smoothing and large-scale segmentation step. If you wish 
#' to use different values in each step use function \code{segmentation_OTB_LSMS2}.
#' 
#' @param x A vector of size three containing the parameters that will be optimized by the 
#' genetic algorithms from package GA:    
#' \itemize{
#'     \item [1] Range radius defining the radius (expressed in radiometry units) in the 
#'     multi-spectral space (float);
#'     \item [2] Spatial radius of the neighborhood (float);
#'     \item [3] Minimum segment/region size. If, after the segmentation, a region is of 
#'     size lower than this criterion, 
#'     the region is merged with the "nearest" region (radiometrically) (integer).
#' }
#' If you are using this function outside an optimization context it is better to directly 
#' define the segmentation parameters in \code{SpectralRange}, \code{SpatialRange} and 
#' \code{MinSize}.
#' 
#' @param inputRstPath The input raster dataset used to perform the image segmentation using 
#' OTB LSMS algorithm (typically a multi-layered raster dataset with segmentation features 
#' in each band).
#' 
#' @param outputSegmRst A path to the output segmented image (default: \code{NULL}; in this 
#' case the segmenter will internally determine a temporary output folder and file path 
#' inside the working directory).
#' 
#' @param SpectralRange Range radius defining the radius (expressed in radiometry units) in 
#' the multi-spectral space (float).
#' 
#' @param SpatialRange Spatial radius of the neighborhood (float).
#' 
#' @param MinSize Minimum segment/region size.
#' 
#' @param lsms_maxiter Algorithm iterative scheme will stop if convergence hasn't been 
#' reached after the maximum number of iterations (integer).
#' 
#' @param tilesizex Size of tiles along the X-axis (integer).
#' 
#' @param tilesizey Size of tiles along the Y-axis (integer).
#' 
#' @param otbBinPath The system path to OTB binaries (e.g., where otbcli_MeanShiftSmoothing 
#' is located). By default this is equal to \code{NULL} which means that the system path is 
#' used.
#' 
#' @param RAM Available memory for processing the image data (in MB).
#' 
#' @param verbose Print output messages (default: TRUE).
#' 
#' @note 
#' Changed system() option to \code{show.output.on.console = TRUE} to avoid for errors 
#' happening on windows-10...
#' 
#' Check [OTB documentation](https://www.orfeo-toolbox.org/CookBook/Applications/app_LargeScaleMeanShift.html) 
#' for more info.
#'  
#' @return A list object containing output file paths resulting from the segmentation run. 
#' These files will be cleaned after each GA iteration or if an error occurs. Also, notice 
#' that some auxiliary files will be created: otb_filt_range_, otb_filt_spatial_, 
#' otb_segm_init_.
#' 
#' @references 
#' J. Michel, D. Youssefi and M. Grizonnet, 2015. Stable Mean-Shift Algorithm and Its 
#' Application to the Segmentation of Arbitrarily Large Remote Sensing Images. IEEE 
#' Transactions on Geoscience and Remote Sensing, 53: 2, 952-964.
#' 
#' @export
#' 

segmentation_OTB_LSMS <- function(x, inputRstPath, outputSegmRst=NULL, 
                                  SpectralRange=NULL, SpatialRange=NULL, MinSize=NULL, 
                                  lsms_maxiter=10, tilesizex = 2500, tilesizey = 2500, 
                                  otbBinPath=NULL, RAM=3072, verbose=TRUE){
  
  if(missing(x)){
    if(is.null(SpectralRange) || is.null(SpatialRange) || is.null(MinSize)){
      stop("If segmentation parameters are not defined in x then these must be passed on all the following 
           parameters: SpectralRange, SpatialRange and MinSize! Check documentation for more details.")
    }else{
      # Set x from detailed parameter list
      x <- c(SpectralRange, SpatialRange, MinSize)
    }
  }
  
  # Generate a random file suffix
  rndFileStr <- randString(6)
  
  if(is.null(outputSegmRst)){
    # Output segmentation raster file
    tempDir <- getwd()
    
    # Make temp files path
    tmp_FilteredRange <- repBSlash(paste(tempDir,"/otb_filt_range_",rndFileStr,".tif",sep=""))
    tmp_FilteredSpatial <- repBSlash(paste(tempDir,"/otb_filt_spatial_",rndFileStr,".tif",sep=""))
    tmp_Segmentation <- repBSlash(paste(tempDir,"/otb_segm_init_",rndFileStr,".tif",sep=""))
    tmp_SegmentationMerged <- repBSlash(paste(tempDir,"/otb_segm_merge_",rndFileStr,".tif",sep=""))
    
  }else{
    # Output segmentation raster file
    tempDir <- dirname(outputSegmRst)
    
    # Make temp files path
    tmp_FilteredRange <- repBSlash(paste(tempDir,"/otb_filt_range_",rndFileStr,".tif",sep=""))
    tmp_FilteredSpatial <- repBSlash(paste(tempDir,"/otb_filt_spatial_",rndFileStr,".tif",sep=""))
    tmp_Segmentation <- repBSlash(paste(tempDir,"/otb_segm_init_",rndFileStr,".tif",sep=""))
    tmp_SegmentationMerged <- outputSegmRst
    
  }
  
  # Use OTB binary path?
  if(is.null(otbBinPath)){
    prefix <- ""
  }else{
    prefix <- paste(otbBinPath,"/",sep="")
  }
  
  cmd_1<-paste(prefix,"otbcli_MeanShiftSmoothing",
               " -in ",inputRstPath,
               " -fout ",tmp_FilteredRange,
               " -foutpos ",tmp_FilteredSpatial,
               " -ranger ",x[1],
               " -spatialr ",x[2],
               " -maxiter ",lsms_maxiter,
               " -ram ",RAM,
               " -modesearch 0", sep="")
  
  cmd_2<-paste(prefix,"otbcli_LSMSSegmentation",
              " -in ",tmp_FilteredRange,
              " -inpos ",tmp_FilteredSpatial,
              " -out ",tmp_Segmentation," uint32",
              " -ranger ",x[1],
              " -spatialr ",x[2],
              " -minsize 0",
              " -tilesizex ",tilesizex,
              " -tilesizey ",tilesizey, sep="")
  
  cmd_3<-paste(prefix,"otbcli_LSMSSmallRegionsMerging",
              " -in ",tmp_FilteredRange,
              " -inseg ",tmp_Segmentation,
              " -out ",tmp_SegmentationMerged," uint32",
              " -minsize ",x[3],
              " -tilesizex ",tilesizex,
              " -tilesizey ",tilesizey, 
              " -ram ",RAM, sep="")
  
  
  if(verbose) checkPrintSegmentationParams(x,segmentMethod="OTB_LSMS")
  
  ## Run command lines #1 - Mean shift ... #2 LSMS Segmentation ... #3 Region merging
  if(.Platform$OS.type=="windows"){
    ## Changed system() option to \code{show.output.on.console = TRUE} to avoid 
    ## errors happening on windows-10...
    sysOut_1 <- try(system(cmd_1, ignore.stdout = TRUE, ignore.stderr = TRUE, 
                         show.output.on.console = TRUE))
    sysOut_2 <- try(system(cmd_2, ignore.stdout = TRUE, ignore.stderr = TRUE, 
                         show.output.on.console = TRUE))
    sysOut_3 <- try(system(cmd_3, ignore.stdout = TRUE, ignore.stderr = TRUE, 
                         show.output.on.console = TRUE))
  }else{
    sysOut_1 <- try(system(cmd_1, ignore.stdout = TRUE, ignore.stderr = TRUE))
    sysOut_2 <- try(system(cmd_2, ignore.stdout = TRUE, ignore.stderr = TRUE))
    sysOut_3 <- try(system(cmd_3, ignore.stdout = TRUE, ignore.stderr = TRUE))
  } 

  if(!inherits(sysOut_1,"try-error") && 
     !inherits(sysOut_2,"try-error") && 
     !inherits(sysOut_2,"try-error")){
    
    out <- list(
      FilteredRange     = tmp_FilteredRange,
      FilteredSpatial   = tmp_FilteredSpatial,
      Segmentation      = tmp_Segmentation,
      SegmentationFinal = repBSlash(paste(tempDir,"/",list.files(pattern=paste("_init_",rndFileStr,"_",sep="")),sep="")),
      segm              = tmp_SegmentationMerged
    )
    
    class(out) <- "SOptim.SegmentationResult"
    return(out)
  }
  else{
    
    filesToClean <- c(
      FilteredRange     = tmp_FilteredRange,
      FilteredSpatial   = tmp_FilteredSpatial,
      Segmentation      = tmp_Segmentation,
      SegmentationFinal = repBSlash(paste(tempDir,"/",list.files(pattern=paste("_init_",rndFileStr,"_",sep="")),sep="")),
      segm              = tmp_SegmentationMerged
    )
    on.exit(doCleanUpActions(filesToClean))
    
    warning("Unable to perform segmentaton with method OTB_LSMS!")
    return(NA)
  }
}



#' Large Scale Mean Shift segmentation algorithm from Orfeo Toolbox (OTB) with two sets 
#' of parameters
#' 
#' A function providing CLI access to OTB's LSMS segmentation and optimize its parameters 
#' using genetic algorithms. This function differs from \code{segmentation_OTB_LSMS} by 
#' implementing two sets of parameters for the spectral and spatial range, the first for 
#' running mean-shift smoothing (MS_ prefix) and, the second, for the large-scale 
#' segmentation step (LSS_ prefix).
#' 
#' @param x A vector of size five containing the parameters that will be optimized by the 
#' genetic algorithms from package GA:    
#' \itemize{
#'     \item [1] Range radius defining the radius (expressed in radiometry units) in the 
#'     multi-spectral space for the mean-shift smoothing step (float);
#'     \item [2] Spatial radius of the neighborhood for mean-shift smoothing (float);
#'     \item [3] Range radius defining the radius (expressed in radiometry units) in the 
#'     multi-spectral space for the large-scale segmentation step (float);
#'     \item [4] Spatial radius of the neighborhood for large-scale segmentation (float);
#'     \item [5] Minimum segment/region size. If, after the segmentation, a region is of 
#'     size lower than this criterion, the region is merged with the "nearest" region 
#'     (radiometrically) (integer).
#' }
#' If you are using this function outside an optimization context it is better to directly 
#' define the segmentation parameters in \code{MS_SpectralRange}, \code{MS_SpatialRange} 
#' \code{LSS_SpectralRange}, \code{LSS_SpatialRange} and \code{MinSize}.
#' 
#' @param inputRstPath The input raster dataset used to perform the image segmentation using 
#' OTB LSMS algorithm (typically a multi-layer raster dataset with segmentation features in 
#' each band).
#' 
#' @param outputSegmRst A path to the output segmented image (default: \code{NULL}; in this 
#' case the segmenter will internally determine a temporary output folder and file path 
#' inside the working directory).
#' 
#' @param MS_SpectralRange Threshold on spectral signature euclidean distance (expressed in 
#' radiometry unit) to consider neighborhood pixel for averaging. Higher values will be less 
#' edge-preserving (more similar to simple average in neighborhood), whereas lower values 
#' will result in less noise smoothing. Note that this parameter has no effect on processing 
#' time (float).
#' 
#' @param MS_SpatialRange Radius of the spatial neighborhood for averaging. Higher values 
#' will result in more smoothing and higher processing time (float).
#' 
#' @param LSS_SpectralRange Threshold on spectral signature euclidean distance (expressed 
#' in radiometry unit) to consider pixels in the same segment. A good value is half the 
#' range radius used in the MeanShiftSmoothing application (ranger parameter; float).
#' 
#' @param LSS_SpatialRange Threshold on Spatial distance to consider pixels in the same 
#' segment. A good value is half the spatial radius used in the MeanShiftSmoothing 
#' application (spatialr parameter; float).
#' 
#' @param MinSize Minimum segment/region size.
#' 
#' @param lsms_maxiter Algorithm iterative scheme will stop if convergence hasn't been 
#' reached after the maximum number of iterations (integer).
#' 
#' @param tilesizex Size of tiles along the X-axis (integer).
#' 
#' @param tilesizey Size of tiles along the Y-axis (integer).
#' 
#' @param otbBinPath The system path to OTB binaries (e.g., where otbcli_MeanShiftSmoothing 
#' is located). By default this is equal to \code{NULL} which means that the system path is 
#' used.
#' 
#' @param RAM Available memory for processing the image data (in MB).
#' 
#' @param verbose Print output messages (default: TRUE).
#' 
#' @note 
#' Check [OTB documentation](https://www.orfeo-toolbox.org/CookBook/Applications/app_LargeScaleMeanShift.html) 
#' for more info.
#' 
#' @return A list object containing output file paths resulting from the segmentation run. 
#' These files will be cleaned after each GA iteration or if an error occurs. Also, notice 
#' that some auxiliary files will be created: otb_filt_range_, otb_filt_spatial_, 
#' otb_segm_init_.
#' 
#' @references 
#' J. Michel, D. Youssefi and M. Grizonnet, 2015. Stable Mean-Shift Algorithm and Its 
#' Application to the Segmentation of Arbitrarily Large Remote Sensing Images. IEEE 
#' Transactions on Geoscience and Remote Sensing, 53: 2, 952-964.
#' 
#' @export
#' 

segmentation_OTB_LSMS2 <- function(x, inputRstPath, outputSegmRst = NULL, 
                                  MS_SpectralRange = NULL, MS_SpatialRange = NULL, 
                                  LSS_SpectralRange = NULL, LSS_SpatialRange = NULL, 
                                  MinSize = NULL, lsms_maxiter = 10, tilesizex = 2500, 
                                  tilesizey = 2500, otbBinPath = NULL, RAM = 3072, 
                                  verbose = TRUE){
  
  if(missing(x)){
    if(is.null(MS_SpectralRange) || is.null(MS_SpatialRange) || 
       is.null(LSS_SpectralRange) || is.null(LSS_SpatialRange) || is.null(MinSize)){
      stop("If segmentation parameters are not defined in x then these must be passed on all the following 
           parameters: MS_SpectralRange, MS_SpatialRange, LSS_SpectralRange, LSS_SpatialRange, and MinSize! 
           Check documentation for more details.")
    }else{
      # Set x from detailed parameter list
      x <- c(MS_SpectralRange, MS_SpatialRange, 
             LSS_SpectralRange, LSS_SpatialRange, MinSize)
    }
  }
  
  # Generate a random file suffix
  rndFileStr <- randString(6)
  
  if(is.null(outputSegmRst)){
    # Output segmentation raster file
    tempDir <- getwd()
    
    # Make temp files path
    tmp_FilteredRange <- repBSlash(paste(tempDir,"/otb_filt_range_",rndFileStr,".tif",sep=""))
    tmp_FilteredSpatial <- repBSlash(paste(tempDir,"/otb_filt_spatial_",rndFileStr,".tif",sep=""))
    tmp_Segmentation <- repBSlash(paste(tempDir,"/otb_segm_init_",rndFileStr,".tif",sep=""))
    tmp_SegmentationMerged <- repBSlash(paste(tempDir,"/otb_segm_merge_",rndFileStr,".tif",sep=""))
    
  }else{
    # Output segmentation raster file
    tempDir <- dirname(outputSegmRst)
    
    # Make temp files path
    tmp_FilteredRange <- repBSlash(paste(tempDir,"/otb_filt_range_",rndFileStr,".tif",sep=""))
    tmp_FilteredSpatial <- repBSlash(paste(tempDir,"/otb_filt_spatial_",rndFileStr,".tif",sep=""))
    tmp_Segmentation <- repBSlash(paste(tempDir,"/otb_segm_init_",rndFileStr,".tif",sep=""))
    tmp_SegmentationMerged <- outputSegmRst
    
  }
  
  # Use OTB binary path?
  if(is.null(otbBinPath)){
    prefix=""
  }else{
    prefix=paste(otbBinPath,"/",sep="")
  }
  
  cmd_1 <- paste(prefix,"otbcli_MeanShiftSmoothing",
               " -in ",inputRstPath,
               " -fout ",tmp_FilteredRange,
               " -foutpos ",tmp_FilteredSpatial,
               " -ranger ",x[1],
               " -spatialr ",x[2],
               " -maxiter ",lsms_maxiter,
               " -ram ",RAM,
               " -modesearch 0", sep="")
  
  cmd_2 <- paste(prefix,"otbcli_LSMSSegmentation",
               " -in ",tmp_FilteredRange,
               " -inpos ",tmp_FilteredSpatial,
               " -out  ",tmp_Segmentation," uint32",
               " -ranger ",x[3],
               " -spatialr ",x[4],
               " -minsize 0",
               " -tilesizex ",tilesizex,
               " -tilesizey ",tilesizey, sep="")
  
  cmd_3 <- paste(prefix,"otbcli_LSMSSmallRegionsMerging",
               " -in ",tmp_FilteredRange,
               " -inseg ",tmp_Segmentation,
               " -out ",tmp_SegmentationMerged," uint32",
               " -minsize ",x[5],
               " -tilesizex ",tilesizex,
               " -tilesizey ",tilesizey, 
               " -ram ",RAM, sep="")
  
  
  if(verbose) checkPrintSegmentationParams(x,segmentMethod="OTB_LSMS2")
  
  ## Run command lines #1 - Mean shift ... #2 LSMS Segmentation ... #3 Region merging
  if(.Platform$OS.type=="windows"){
    ## Changed system() option to \code{show.output.on.console = TRUE} to avoid 
    ## errors happening on windows-10...
    sysOut_1 <- try(system(cmd_1, ignore.stdout = TRUE, ignore.stderr = TRUE, 
                           show.output.on.console = TRUE))
    sysOut_2 <- try(system(cmd_2, ignore.stdout = TRUE, ignore.stderr = TRUE, 
                           show.output.on.console = TRUE))
    sysOut_3 <- try(system(cmd_3, ignore.stdout = TRUE, ignore.stderr = TRUE, 
                           show.output.on.console = TRUE))
  }else{
    sysOut_1 <- try(system(cmd_1, ignore.stdout = TRUE, ignore.stderr = TRUE))
    sysOut_2 <- try(system(cmd_2, ignore.stdout = TRUE, ignore.stderr = TRUE))
    sysOut_3 <- try(system(cmd_3, ignore.stdout = TRUE, ignore.stderr = TRUE))
  } 
  
  if(!inherits(sysOut_1,"try-error") && 
     !inherits(sysOut_2,"try-error") && 
     !inherits(sysOut_2,"try-error")){
    
    out <- list(
      FilteredRange     = tmp_FilteredRange,
      FilteredSpatial   = tmp_FilteredSpatial,
      Segmentation      = tmp_Segmentation,
      SegmentationFinal = repBSlash(paste(tempDir,"/",list.files(pattern=paste("_init_",rndFileStr,"_",sep="")),sep="")),
      segm              = tmp_SegmentationMerged
      )
    
    class(out) <- "SOptim.SegmentationResult"
    return(out)
  }
  else{
    
    filesToClean <- c(
      FilteredRange     = tmp_FilteredRange,
      FilteredSpatial   = tmp_FilteredSpatial,
      Segmentation      = tmp_Segmentation,
      SegmentationFinal = repBSlash(paste(tempDir,"/",list.files(pattern=paste("_init_",rndFileStr,"_",sep="")),sep="")),
      segm              = tmp_SegmentationMerged
    )
    on.exit(doCleanUpActions(filesToClean))
    
    warning("Unable to perform segmentaton with method OTB_LSMS (two-parameter set)!")
    return(NA)
  }
}



#' A generic dispatcher/wrapper function for running multiple segmentation methods
#' 
#' Runs the appropriate segmentation method with the parameters defined in \code{...}.
#' 
#' @param segmentMethod Character string used to define the segmentation method. 
#' Available options are:     
#' \itemize{
#'   \item \code{"SAGA_SRG"} - SAGA Simple Region Growing;
#'   \item \code{"GRASS_RG"} - GRASS Region Growing;
#'   \item \code{"ArcGIS_MShift"} - ArcGIS Mean Shift algorithm;
#'   \item \code{"Terralib_Baatz"} - TerraLib Baatz algorithm;
#'   \item \code{"Terralib_MRGrow"} - TerraLib Mean Region Growing;
#'   \item \code{"RSGISLib_Shep"} - RSGISLib Shepherd algorithm;
#'   \item \code{"OTB_LSMS"} - OTB Large Scale Mean Shift algorithm;
#'   \item \code{"OTB_LSMS2"} - OTB Large Scale Mean Shift algorithm 
#'   with two separate sets of parameters, one for mean-shift smoothing 
#'   and another for large-scale segmentation step;
#' }
#' 
#' @param ... Specific parameters to pass to each segmentation method.
#' 
#' @seealso 
#' Check out more information about each specific segmentation method at: 
#' \itemize{
#'     \item \code{\link{segmentation_SAGA_SRG}} 
#'     \item \code{\link{segmentation_GRASS_RG}} 
#'     \item \code{\link{segmentation_ArcGIS_MShift}} 
#'     \item \code{\link{segmentation_Terralib_Baatz}} 
#'     \item \code{\link{segmentation_Terralib_MRGrow}}
#'     \item \code{\link{segmentation_RSGISLib_Shep}}
#'     \item \code{\link{segmentation_OTB_LSMS}}
#'     \item \code{\link{segmentation_OTB_LSMS2}}
#' }
#' 
#' @export
#' 

segmentationGeneric <- function(segmentMethod, ...){
  
  if(segmentMethod == "SAGA_SRG"){
    segmentationOutput <- segmentation_SAGA_SRG(...)
    return(segmentationOutput)
  }
  
  else if(segmentMethod == "GRASS_RG"){
    segmentationOutput <- segmentation_GRASS_RG(...)
    return(segmentationOutput)
  }
  
  else if(segmentMethod == "ArcGIS_MShift"){
    segmentationOutput <- segmentation_ArcGIS_MShift(...)
    return(segmentationOutput)
  }
  
  else if(segmentMethod == "Terralib_Baatz"){
    segmentationOutput <- segmentation_Terralib_Baatz(...)
    return(segmentationOutput)
  }
  
  else if(segmentMethod == "Terralib_MRGrow"){
    segmentationOutput <- segmentation_Terralib_MRGrow(...)
    return(segmentationOutput)
  }
  
  else if(segmentMethod == "RSGISLib_Shep"){
    segmentationOutput <- segmentation_RSGISLib_Shep(...)
    return(segmentationOutput)
  }
  
  else if(segmentMethod == "OTB_LSMS"){
    segmentationOutput <- segmentation_OTB_LSMS(...)
    return(segmentationOutput)
  }
  
  else if(segmentMethod == "OTB_LSMS2"){
    segmentationOutput <- segmentation_OTB_LSMS2(...)
    return(segmentationOutput)
  }
  
  else{
    stop("Unknown segmentation method defined! Please review input parameters")
  }
}
joaofgoncalves/SegOptim documentation built on Feb. 5, 2024, 11:10 p.m.