├── .github ├── .gitignore ├── ISSUE_TEMPLATE │ ├── config.yml │ └── issue_template.md ├── dependabot.yml └── workflows │ ├── issue.yml │ ├── pkgdown.yaml │ ├── stale-actions.yml │ └── R-CMD-check.yaml ├── .gitignore ├── .Rbuildignore ├── inst ├── images │ ├── airplane.jpg │ └── BSR_bsds500_image.jpg └── CITATION ├── tests ├── testthat.R └── testthat │ └── test-spx_im_segmentation.R ├── R ├── SuperpixelImageSegmentation.R ├── RcppExports.R └── px_utils.R ├── _pkgdown.yml ├── src ├── Makevars.win ├── Makevars ├── init.c ├── RcppExports.cpp └── superPx_AP_Kmeans_Segment.cpp ├── NAMESPACE ├── DESCRIPTION ├── NEWS.md ├── README.md └── man └── Image_Segmentation.Rd /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | docs/ 2 | .Rhistory 3 | docs 4 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.git$ 2 | ^.github$ 3 | ^\.ccache$ 4 | ^\.github$ 5 | ^_pkgdown\.yml$ 6 | ^docs$ 7 | ^pkgdown$ 8 | -------------------------------------------------------------------------------- /inst/images/airplane.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlampros/SuperpixelImageSegmentation/HEAD/inst/images/airplane.jpg -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(SuperpixelImageSegmentation) 3 | 4 | test_check("SuperpixelImageSegmentation") 5 | -------------------------------------------------------------------------------- /inst/images/BSR_bsds500_image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mlampros/SuperpixelImageSegmentation/HEAD/inst/images/BSR_bsds500_image.jpg -------------------------------------------------------------------------------- /R/SuperpixelImageSegmentation.R: -------------------------------------------------------------------------------- 1 | #' @useDynLib SuperpixelImageSegmentation, .registration = TRUE 2 | #' @importFrom Rcpp evalCpp 3 | NULL 4 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: https://mlampros.github.io/SuperpixelImageSegmentation/ 2 | 3 | template: 4 | bootstrap: 5 5 | bootswatch: cerulean 6 | 7 | -------------------------------------------------------------------------------- /src/Makevars.win: -------------------------------------------------------------------------------- 1 | PKG_CXXFLAGS = -I../inst/include -I. $(SHLIB_OPENMP_CXXFLAGS) -DARMA_64BIT_WORD -DARMA_USE_CURRENT 2 | PKG_LIBS = $(LAPACK_LIBS) $(BLAS_LIBS) $(FLIBS) $(SHLIB_OPENMP_CXXFLAGS) -------------------------------------------------------------------------------- /src/Makevars: -------------------------------------------------------------------------------- 1 | PKG_CXXFLAGS = -I../inst/include -I. $(SHLIB_OPENMP_CXXFLAGS) -DARMA_64BIT_WORD -DARMA_USE_CURRENT 2 | PKG_LIBS = $(LAPACK_LIBS) $(BLAS_LIBS) $(FLIBS) $(SHLIB_OPENMP_CXXFLAGS) 3 | 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | # For more info see: https://docs.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository#configuring-the-template-chooser 2 | 3 | blank_issues_enabled: true 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Monitor GitHub Actions (if you have any workflows) 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "monthly" 8 | open-pull-requests-limit: 5 9 | reviewers: 10 | - "mlampros" 11 | assignees: 12 | - "mlampros" 13 | labels: 14 | - "dependencies" 15 | - "github-actions" 16 | commit-message: 17 | prefix: "ci" 18 | include: "scope" 19 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(Image_Segmentation) 4 | importFrom(OpenImageR,NormalizeObject) 5 | importFrom(OpenImageR,imageShow) 6 | importFrom(OpenImageR,readImage) 7 | importFrom(OpenImageR,rotateFixed) 8 | importFrom(R6,R6Class) 9 | importFrom(Rcpp,evalCpp) 10 | importFrom(grDevices,dev.off) 11 | importFrom(grDevices,png) 12 | importFrom(grDevices,rainbow) 13 | importFrom(lattice,levelplot) 14 | useDynLib(SuperpixelImageSegmentation, .registration = TRUE) 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue_template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report or feature request 3 | about: Describe a bug you've encountered or make a case for a new feature 4 | --- 5 | 6 | Please briefly describe your problem and what output you expect. If you have a question, you also have the option of (but I'm flexible if it's not too complicated) 7 | 8 | Please include a minimal reproducible example 9 | 10 | Please give a brief description of the problem 11 | 12 | Please add your Operating System (e.g., Windows10, Macintosh, Linux) and the R version that you use (e.g., 3.6.2) 13 | 14 | If my package uses Python (via 'reticulate') then please add also the Python version (e.g., Python 3.8) and the 'reticulate' version (e.g., 1.18.0) 15 | -------------------------------------------------------------------------------- /.github/workflows/issue.yml: -------------------------------------------------------------------------------- 1 | # For more info see: https://github.com/Renato66/auto-label 2 | # for the 'secrets.GITHUB_TOKEN' see: https://docs.github.com/en/actions/reference/authentication-in-a-workflow#about-the-github_token-secret 3 | 4 | name: Labeling new issue 5 | on: 6 | issues: 7 | types: ['opened'] 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: Renato66/auto-label@v3 13 | with: 14 | repo-token: ${{ secrets.GITHUB_TOKEN }} 15 | ignore-comments: true 16 | labels-synonyms: '{"bug":["error","need fix","not working"],"enhancement":["upgrade"],"question":["help"]}' 17 | labels-not-allowed: '["good first issue"]' 18 | default-labels: '["help wanted"]' 19 | -------------------------------------------------------------------------------- /inst/CITATION: -------------------------------------------------------------------------------- 1 | citHeader("Please cite both the package and the original articles / software in your publications:") 2 | 3 | year <- sub("-.*", "", meta$Date) 4 | note <- sprintf("R package version %s", meta$Version) 5 | 6 | bibentry( 7 | bibtype = "Manual", 8 | title = "{SuperpixelImageSegmentation}: Image Segmentation using Superpixels, Affinity Propagation and Kmeans Clustering", 9 | author = person("Lampros", "Mouselimis"), 10 | year = year, 11 | note = note, 12 | url = "https://CRAN.R-project.org/package=SuperpixelImageSegmentation" 13 | ) 14 | 15 | bibentry( 16 | bibtype = "Article", 17 | title = "Image Segmentation using SLIC Superpixels and Affinity Propagation Clustering", 18 | author = person("Bao", "Zhou"), 19 | journal = "International Journal of Science and Research (IJSR)", 20 | year = "2013", 21 | note = "ISSN (Online): 2319-7064", 22 | url = "https://www.ijsr.net/archive/v4i4/SUB152869.pdf" 23 | ) 24 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: SuperpixelImageSegmentation 2 | Type: Package 3 | Title: Superpixel Image Segmentation 4 | Version: 1.0.6 5 | Date: 2025-09-15 6 | Authors@R: c( person(given = "Lampros", family = "Mouselimis", email = "mouselimislampros@gmail.com", role = c("aut", "cre"), comment = c(ORCID = "0000-0002-8024-1546"))) 7 | BugReports: https://github.com/mlampros/SuperpixelImageSegmentation/issues 8 | URL: https://github.com/mlampros/SuperpixelImageSegmentation, https://mlampros.github.io/SuperpixelImageSegmentation/ 9 | Description: Image Segmentation using Superpixels, Affinity Propagation and Kmeans Clustering. The R code is based primarily on the article "Image Segmentation using SLIC Superpixels and Affinity Propagation Clustering, Bao Zhou, International Journal of Science and Research (IJSR), 2013" . 10 | License: GPL-3 11 | Encoding: UTF-8 12 | Depends: R(>= 3.2) 13 | Imports: Rcpp (>= 0.12.10), R6, OpenImageR, grDevices, lattice 14 | LinkingTo: Rcpp, RcppArmadillo (>= 0.9.1), ClusterR, OpenImageR 15 | Suggests: 16 | testthat, 17 | covr, 18 | knitr, 19 | rmarkdown 20 | RoxygenNote: 7.3.2 21 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | release: 8 | types: [published] 9 | workflow_dispatch: 10 | 11 | name: pkgdown.yaml 12 | 13 | permissions: read-all 14 | 15 | jobs: 16 | pkgdown: 17 | runs-on: ubuntu-latest 18 | # Only restrict concurrency for non-PR jobs 19 | concurrency: 20 | group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} 21 | env: 22 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 23 | permissions: 24 | contents: write 25 | steps: 26 | - uses: actions/checkout@v5 27 | 28 | - uses: r-lib/actions/setup-pandoc@v2 29 | 30 | - uses: r-lib/actions/setup-r@v2 31 | with: 32 | use-public-rspm: true 33 | 34 | - uses: r-lib/actions/setup-r-dependencies@v2 35 | with: 36 | extra-packages: any::pkgdown, local::. 37 | needs: website 38 | 39 | - name: Build site 40 | run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) 41 | shell: Rscript {0} 42 | 43 | - name: Deploy to GitHub pages 🚀 44 | if: github.event_name != 'pull_request' 45 | uses: JamesIves/github-pages-deploy-action@v4.7.3 46 | with: 47 | clean: false 48 | branch: gh-pages 49 | folder: docs 50 | -------------------------------------------------------------------------------- /.github/workflows/stale-actions.yml: -------------------------------------------------------------------------------- 1 | # for the 'secrets.GITHUB_TOKEN' see: https://docs.github.com/en/actions/reference/authentication-in-a-workflow#about-the-github_token-secret 2 | 3 | name: "Mark or close stale issues and PRs" 4 | 5 | on: 6 | schedule: 7 | - cron: "00 * * * *" 8 | 9 | jobs: 10 | stale: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/stale@v10 14 | with: 15 | repo-token: ${{ secrets.GITHUB_TOKEN }} 16 | days-before-stale: 12 17 | days-before-close: 7 18 | stale-issue-message: "This is Robo-lampros because the Human-lampros is lazy. This issue has been automatically marked as stale because it has not had recent activity. It will be closed after 7 days if no further activity occurs. Feel free to re-open a closed issue and the Human-lampros will respond." 19 | stale-pr-message: "This is Robo-lampros because the Human-lampros is lazy. This PR has been automatically marked as stale because it has not had recent activity. It will be closed after 7 days if no further activity occurs." 20 | close-issue-message: "This issue was automatically closed because of being stale. Feel free to re-open a closed issue and the Human-lampros will respond." 21 | close-pr-message: "This PR was automatically closed because of being stale." 22 | stale-pr-label: "stale" 23 | stale-issue-label: "stale" 24 | exempt-issue-labels: "bug,enhancement,pinned,security,pending,work_in_progress" 25 | exempt-pr-labels: "bug,enhancement,pinned,security,pending,work_in_progress" 26 | -------------------------------------------------------------------------------- /src/init.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include // for NULL 4 | #include 5 | 6 | /* FIXME: 7 | Check these declarations against the C/Fortran source code. 8 | */ 9 | 10 | /* .Call calls */ 11 | extern SEXP _SuperpixelImageSegmentation_apply_rcpp(SEXP); 12 | extern SEXP _SuperpixelImageSegmentation_image_segmentation(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP); 13 | extern SEXP _SuperpixelImageSegmentation_is_mt_finite(SEXP); 14 | extern SEXP _SuperpixelImageSegmentation_NAs_matrix(SEXP); 15 | extern SEXP _SuperpixelImageSegmentation_simil_A(SEXP, SEXP, SEXP, SEXP, SEXP); 16 | 17 | static const R_CallMethodDef CallEntries[] = { 18 | {"_SuperpixelImageSegmentation_apply_rcpp", (DL_FUNC) &_SuperpixelImageSegmentation_apply_rcpp, 1}, 19 | {"_SuperpixelImageSegmentation_image_segmentation", (DL_FUNC) &_SuperpixelImageSegmentation_image_segmentation, 27}, 20 | {"_SuperpixelImageSegmentation_is_mt_finite", (DL_FUNC) &_SuperpixelImageSegmentation_is_mt_finite, 1}, 21 | {"_SuperpixelImageSegmentation_NAs_matrix", (DL_FUNC) &_SuperpixelImageSegmentation_NAs_matrix, 1}, 22 | {"_SuperpixelImageSegmentation_simil_A", (DL_FUNC) &_SuperpixelImageSegmentation_simil_A, 5}, 23 | {NULL, NULL, 0} 24 | }; 25 | 26 | void R_init_SuperpixelImageSegmentation(DllInfo *dll) 27 | { 28 | R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); 29 | R_useDynamicSymbols(dll, FALSE); 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | 8 | name: R-CMD-check.yaml 9 | 10 | permissions: read-all 11 | 12 | jobs: 13 | R-CMD-check: 14 | runs-on: ${{ matrix.config.os }} 15 | 16 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 17 | 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | config: 22 | - {os: macos-latest, r: 'release'} 23 | - {os: windows-latest, r: 'release'} 24 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 25 | - {os: ubuntu-latest, r: 'release'} 26 | - {os: ubuntu-latest, r: 'oldrel-1'} 27 | 28 | env: 29 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 30 | R_KEEP_PKG_SOURCE: yes 31 | 32 | steps: 33 | - uses: actions/checkout@v5 34 | 35 | - uses: r-lib/actions/setup-pandoc@v2 36 | 37 | - uses: r-lib/actions/setup-r@v2 38 | with: 39 | r-version: ${{ matrix.config.r }} 40 | http-user-agent: ${{ matrix.config.http-user-agent }} 41 | use-public-rspm: true 42 | 43 | - uses: r-lib/actions/setup-r-dependencies@v2 44 | with: 45 | extra-packages: any::rcmdcheck 46 | needs: check 47 | 48 | - uses: r-lib/actions/check-r-package@v2 49 | with: 50 | upload-snapshots: true 51 | build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' 52 | -------------------------------------------------------------------------------- /R/RcppExports.R: -------------------------------------------------------------------------------- 1 | # Generated by using Rcpp::compileAttributes() -> do not edit by hand 2 | # Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 3 | 4 | simil_A <- function(spx_vec1, spx_vec2, wL = 3L, wA = 10L, wB = 10L) { 5 | .Call(`_SuperpixelImageSegmentation_simil_A`, spx_vec1, spx_vec2, wL, wA, wB) 6 | } 7 | 8 | apply_rcpp <- function(input) { 9 | .Call(`_SuperpixelImageSegmentation_apply_rcpp`, input) 10 | } 11 | 12 | NAs_matrix <- function(x) { 13 | .Call(`_SuperpixelImageSegmentation_NAs_matrix`, x) 14 | } 15 | 16 | is_mt_finite <- function(x) { 17 | .Call(`_SuperpixelImageSegmentation_is_mt_finite`, x) 18 | } 19 | 20 | image_segmentation <- function(input_image, method = "slic", num_superpixel = 200L, kmeans_method = "", AP_data = FALSE, use_median = TRUE, minib_kmeans_batch = 10L, minib_kmeans_init_fraction = 0.5, kmeans_num_init = 3L, kmeans_max_iters = 100L, kmeans_initializer = "kmeans++", colour_type = "RGB", compactness_factor = 20, adjust_centroids_and_return_masks = FALSE, return_labels_2_dimensionsional = FALSE, sim_normalize = FALSE, sim_wL = 3L, sim_wA = 10L, sim_wB = 10L, sim_color_radius = 20L, ap_maxits = 1000L, ap_convits = 100L, ap_dampfact = 0.9, ap_details = FALSE, ap_nonoise = 0.0, ap_time = FALSE, verbose = FALSE) { 21 | .Call(`_SuperpixelImageSegmentation_image_segmentation`, input_image, method, num_superpixel, kmeans_method, AP_data, use_median, minib_kmeans_batch, minib_kmeans_init_fraction, kmeans_num_init, kmeans_max_iters, kmeans_initializer, colour_type, compactness_factor, adjust_centroids_and_return_masks, return_labels_2_dimensionsional, sim_normalize, sim_wL, sim_wA, sim_wB, sim_color_radius, ap_maxits, ap_convits, ap_dampfact, ap_details, ap_nonoise, ap_time, verbose) 22 | } 23 | 24 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | 2 | ## SuperpixelImageSegmentation 1.0.6 3 | 4 | * I updated the `Makevars` and `Makevars.win` files by adding `-DARMA_USE_CURRENT` (see issue: https://github.com/RcppCore/RcppArmadillo/issues/476) 5 | * I removed the `-mthreads` compilation option from the "Makevars.win" file 6 | * I removed the "CXX_STD = CXX11" from the "Makevars" files, and the "[[Rcpp::plugins(cpp11)]]" from the "superPx_AP_Kmeans_Segment.cpp" file due to the following NOTE from CRAN, "NOTE Specified C++11: please drop specification unless essential" (see also: https://www.tidyverse.org/blog/2023/03/cran-checks-compiled-code/#note-regarding-systemrequirements-c11) 7 | 8 | 9 | ## SuperpixelImageSegmentation 1.0.5 10 | 11 | * I updated the documentation 12 | 13 | 14 | ## SuperpixelImageSegmentation 1.0.4 15 | 16 | * I've added the *CITATION* file in the *inst* directory 17 | 18 | 19 | ## SuperpixelImageSegmentation 1.0.3 20 | 21 | * I've included the Affinity Propagation Clustering parameters in the *spixel_segmentation* method of the *Image_Segmentation* R6 class and especially the *ap_maxits* (maximum number of iterations), *ap_convits* (convits iterations), *ap_dampfact* (update equation damping level) and *ap_nonoise* (small amount of noise to prevent degenerate cases). Although the default parameter values work for the majority of Image Segmentation tasks, adjustments might be necessary for specific use cases. 22 | 23 | 24 | ## SuperpixelImageSegmentation 1.0.2 25 | 26 | * I've added the *return_labels_2_dimensionsional* parameter to the *spixel_segmentation* R6 Method, so that if TRUE then the 2-dimensional (matrix) superpixel labels will be returned 27 | * I've added the *spixel_clusters_show* method to visualize the superpixel clusters in case the *return_labels_2_dimensionsional* parameter is set to TRUE 28 | 29 | 30 | ## SuperpixelImageSegmentation 1.0.1 31 | 32 | * I've added an error case if the *kmeans_initializer* parameter is not one of 'kmeans++', 'random', 'optimal_init' or 'quantile_init' 33 | 34 | 35 | ## SuperpixelImageSegmentation 1.0.0 36 | 37 | * The *spixel_segmentation* method allows also the user to return the kmeans clusters too (in case that the *kmeans_method* is set to "kmeans" or "mini_batch_kmeans") 38 | -------------------------------------------------------------------------------- /tests/testthat/test-spx_im_segmentation.R: -------------------------------------------------------------------------------- 1 | 2 | #-------------------------------------------------------------------------------------------------- 3 | 4 | #----------- 5 | # image data 6 | #----------- 7 | 8 | path = system.file("images", "BSR_bsds500_image.jpg", package = "SuperpixelImageSegmentation") 9 | im = OpenImageR::readImage(path) 10 | 11 | #-------------------------------------------------------------------------------------------------- 12 | 13 | 14 | context('superpixel image segmentation') 15 | 16 | 17 | 18 | testthat::test_that("the 'spixel_segmentation' function returns an error if the 'kmeans_method' parameter is an empty string and the 'AP_data' parameter is FALSE", { 19 | 20 | init = Image_Segmentation$new() 21 | 22 | testthat::expect_error( init$spixel_segmentation(input_image = im, method = "slic", superpixel = 200, kmeans_method = "", AP_data = FALSE, use_median = TRUE, 23 | minib_kmeans_batch = 10, minib_kmeans_init_fraction = 0.5, kmeans_num_init = 3, kmeans_max_iters = 100, 24 | kmeans_initializer = "kmeans++", colour_type = "RGB", compactness_factor = 20, 25 | adjust_centroids_and_return_masks = FALSE, sim_normalize = FALSE, sim_wL = 3, sim_wA = 10, sim_wB = 10, 26 | sim_color_radius = 20, verbose = FALSE) ) 27 | }) 28 | 29 | 30 | testthat::test_that("the 'spixel_segmentation' function returns the expected output if AP_data is TRUE", { 31 | 32 | init = Image_Segmentation$new() 33 | 34 | dat = init$spixel_segmentation(input_image = im, method = "slic", superpixel = 200, kmeans_method = "", AP_data = T, use_median = TRUE, 35 | minib_kmeans_batch = 10, minib_kmeans_init_fraction = 0.5, kmeans_num_init = 3, kmeans_max_iters = 100, 36 | kmeans_initializer = "kmeans++", colour_type = "RGB", compactness_factor = 20, 37 | adjust_centroids_and_return_masks = FALSE, sim_normalize = FALSE, sim_wL = 3, sim_wA = 10, sim_wB = 10, 38 | sim_color_radius = 20, verbose = FALSE) 39 | 40 | testthat::expect_true( length(dat$KMeans_image_data) == 0 && dim(dat$AP_image_data)[3] == 3 ) 41 | }) 42 | 43 | 44 | 45 | testthat::test_that("the 'spixel_segmentation' function returns the expected output if AP_data is TRUE and kmeans_method == 'kmeans'", { 46 | 47 | init = Image_Segmentation$new() 48 | 49 | dat = init$spixel_segmentation(input_image = im, method = "slic", superpixel = 200, kmeans_method = "kmeans", AP_data = T, use_median = TRUE, 50 | minib_kmeans_batch = 10, minib_kmeans_init_fraction = 0.5, kmeans_num_init = 3, kmeans_max_iters = 100, 51 | kmeans_initializer = "kmeans++", colour_type = "RGB", compactness_factor = 20, 52 | adjust_centroids_and_return_masks = FALSE, sim_normalize = FALSE, sim_wL = 3, sim_wA = 10, sim_wB = 10, 53 | sim_color_radius = 20, verbose = FALSE) 54 | 55 | testthat::expect_true( dim(dat$KMeans_image_data)[3] == 3 && dim(dat$AP_image_data)[3] == 3 ) 56 | }) 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![R-CMD-check](https://github.com/mlampros/SuperpixelImageSegmentation/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/mlampros/SuperpixelImageSegmentation/actions/workflows/R-CMD-check.yaml) 3 | [![CRAN_Status_Badge](http://www.r-pkg.org/badges/version/SuperpixelImageSegmentation)](http://cran.r-project.org/package=SuperpixelImageSegmentation) 4 | [![Downloads](http://cranlogs.r-pkg.org/badges/grand-total/SuperpixelImageSegmentation?color=blue)](http://www.r-pkg.org/pkg/SuperpixelImageSegmentation) 5 | [![status](https://tinyverse.netlify.app/badge/SuperpixelImageSegmentation)](https://CRAN.R-project.org/package=SuperpixelImageSegmentation) 6 | 7 | 8 | ## SuperpixelImageSegmentation 9 |
10 | 11 | The R / Rcpp code of the *SuperpixelImageSegmentation* package is based primarily on the article ["Image Segmentation using SLIC Superpixels and Affinity Propagation Clustering", Bao Zhou, International Journal of Science and Research (IJSR), 2013](https://www.ijsr.net/archive/v4i4/SUB152869.pdf). 12 | 13 | I wrote a [blog post](http://mlampros.github.io/2018/11/09/Image_Segmentation_Superpixels_Clustering/) explaining how to take advantage of the R / Rcpp code of the *SuperpixelImageSegmentation* package. 14 | 15 |
16 | 17 | System / Software Requirements: 18 | 19 | * [OpenImageR ](https://github.com/mlampros/OpenImageR) 20 | * [ClusterR ](https://github.com/mlampros/ClusterR) 21 | * a C++ 11 compiler 22 |

23 | 24 | 25 | The *SuperpixelImageSegmentation* package can be installed from CRAN using, 26 | 27 |
28 | 29 | 30 | ```R 31 | 32 | install.packages("SuperpixelImageSegmentation") 33 | 34 | 35 | ``` 36 |
37 | 38 | **or** download the latest version from Github using the *pak* package, 39 |

40 | 41 | ```R 42 | 43 | pak::pak('mlampros/SuperpixelImageSegmentation') 44 | 45 | 46 | ``` 47 |
48 | 49 | **or** by directly downloading the .zip file using the **Clone or download** button in the [repository page](https://github.com/mlampros/SuperpixelImageSegmentation), extracting it locally (renaming it to *SuperpixelImageSegmentation* if necessary) and running, 50 | 51 |
52 | 53 | ```R 54 | 55 | #-------- 56 | # on Unix 57 | #-------- 58 | 59 | setwd('/your_folder/SuperpixelImageSegmentation/') 60 | Rcpp::compileAttributes(verbose = TRUE) 61 | setwd('/your_folder/') 62 | system("R CMD build SuperpixelImageSegmentation") 63 | system("R CMD INSTALL SuperpixelImageSegmentation_1.0.0.tar.gz") 64 | 65 | 66 | #----------- 67 | # on Windows 68 | #----------- 69 | 70 | setwd('C:/your_folder/SuperpixelImageSegmentation/') 71 | Rcpp::compileAttributes(verbose = TRUE) 72 | setwd('C:/your_folder/') 73 | system("R CMD build SuperpixelImageSegmentation") 74 | system("R CMD INSTALL SuperpixelImageSegmentation_1.0.0.tar.gz") 75 | 76 | ``` 77 | 78 | 79 |
80 | 81 | Use the following link to report bugs/issues, 82 |

83 | 84 | [https://github.com/mlampros/SuperpixelImageSegmentation/issues](https://github.com/mlampros/SuperpixelImageSegmentation/issues) 85 | 86 |
87 | 88 | ### **Citation:** 89 | 90 | If you use the code of this repository in your paper or research please cite both **SuperpixelImageSegmentation** and the **original articles / software** `https://CRAN.R-project.org/package=SuperpixelImageSegmentation`: 91 | 92 |
93 | 94 | ```R 95 | @Manual{, 96 | title = {{SuperpixelImageSegmentation}: Image Segmentation using 97 | Superpixels, Affinity Propagation and Kmeans Clustering}, 98 | author = {Lampros Mouselimis}, 99 | year = {2025}, 100 | note = {R package version 1.0.6}, 101 | url = 102 | {https://CRAN.R-project.org/package=SuperpixelImageSegmentation}, 103 | } 104 | ``` 105 | 106 |
107 | -------------------------------------------------------------------------------- /src/RcppExports.cpp: -------------------------------------------------------------------------------- 1 | // Generated by using Rcpp::compileAttributes() -> do not edit by hand 2 | // Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 3 | 4 | #include 5 | #include 6 | 7 | using namespace Rcpp; 8 | 9 | #ifdef RCPP_USE_GLOBAL_ROSTREAM 10 | Rcpp::Rostream& Rcpp::Rcout = Rcpp::Rcpp_cout_get(); 11 | Rcpp::Rostream& Rcpp::Rcerr = Rcpp::Rcpp_cerr_get(); 12 | #endif 13 | 14 | // simil_A 15 | double simil_A(arma::rowvec spx_vec1, arma::rowvec spx_vec2, int wL, int wA, int wB); 16 | RcppExport SEXP _SuperpixelImageSegmentation_simil_A(SEXP spx_vec1SEXP, SEXP spx_vec2SEXP, SEXP wLSEXP, SEXP wASEXP, SEXP wBSEXP) { 17 | BEGIN_RCPP 18 | Rcpp::RObject rcpp_result_gen; 19 | Rcpp::RNGScope rcpp_rngScope_gen; 20 | Rcpp::traits::input_parameter< arma::rowvec >::type spx_vec1(spx_vec1SEXP); 21 | Rcpp::traits::input_parameter< arma::rowvec >::type spx_vec2(spx_vec2SEXP); 22 | Rcpp::traits::input_parameter< int >::type wL(wLSEXP); 23 | Rcpp::traits::input_parameter< int >::type wA(wASEXP); 24 | Rcpp::traits::input_parameter< int >::type wB(wBSEXP); 25 | rcpp_result_gen = Rcpp::wrap(simil_A(spx_vec1, spx_vec2, wL, wA, wB)); 26 | return rcpp_result_gen; 27 | END_RCPP 28 | } 29 | // apply_rcpp 30 | arma::mat apply_rcpp(arma::cube& input); 31 | RcppExport SEXP _SuperpixelImageSegmentation_apply_rcpp(SEXP inputSEXP) { 32 | BEGIN_RCPP 33 | Rcpp::RObject rcpp_result_gen; 34 | Rcpp::RNGScope rcpp_rngScope_gen; 35 | Rcpp::traits::input_parameter< arma::cube& >::type input(inputSEXP); 36 | rcpp_result_gen = Rcpp::wrap(apply_rcpp(input)); 37 | return rcpp_result_gen; 38 | END_RCPP 39 | } 40 | // NAs_matrix 41 | arma::uvec NAs_matrix(arma::mat& x); 42 | RcppExport SEXP _SuperpixelImageSegmentation_NAs_matrix(SEXP xSEXP) { 43 | BEGIN_RCPP 44 | Rcpp::RObject rcpp_result_gen; 45 | Rcpp::RNGScope rcpp_rngScope_gen; 46 | Rcpp::traits::input_parameter< arma::mat& >::type x(xSEXP); 47 | rcpp_result_gen = Rcpp::wrap(NAs_matrix(x)); 48 | return rcpp_result_gen; 49 | END_RCPP 50 | } 51 | // is_mt_finite 52 | bool is_mt_finite(arma::mat x); 53 | RcppExport SEXP _SuperpixelImageSegmentation_is_mt_finite(SEXP xSEXP) { 54 | BEGIN_RCPP 55 | Rcpp::RObject rcpp_result_gen; 56 | Rcpp::RNGScope rcpp_rngScope_gen; 57 | Rcpp::traits::input_parameter< arma::mat >::type x(xSEXP); 58 | rcpp_result_gen = Rcpp::wrap(is_mt_finite(x)); 59 | return rcpp_result_gen; 60 | END_RCPP 61 | } 62 | // image_segmentation 63 | Rcpp::List image_segmentation(arma::cube input_image, std::string method, int num_superpixel, std::string kmeans_method, bool AP_data, bool use_median, int minib_kmeans_batch, double minib_kmeans_init_fraction, int kmeans_num_init, int kmeans_max_iters, std::string kmeans_initializer, std::string colour_type, double compactness_factor, bool adjust_centroids_and_return_masks, bool return_labels_2_dimensionsional, bool sim_normalize, int sim_wL, int sim_wA, int sim_wB, int sim_color_radius, int ap_maxits, int ap_convits, double ap_dampfact, bool ap_details, double ap_nonoise, bool ap_time, bool verbose); 64 | RcppExport SEXP _SuperpixelImageSegmentation_image_segmentation(SEXP input_imageSEXP, SEXP methodSEXP, SEXP num_superpixelSEXP, SEXP kmeans_methodSEXP, SEXP AP_dataSEXP, SEXP use_medianSEXP, SEXP minib_kmeans_batchSEXP, SEXP minib_kmeans_init_fractionSEXP, SEXP kmeans_num_initSEXP, SEXP kmeans_max_itersSEXP, SEXP kmeans_initializerSEXP, SEXP colour_typeSEXP, SEXP compactness_factorSEXP, SEXP adjust_centroids_and_return_masksSEXP, SEXP return_labels_2_dimensionsionalSEXP, SEXP sim_normalizeSEXP, SEXP sim_wLSEXP, SEXP sim_wASEXP, SEXP sim_wBSEXP, SEXP sim_color_radiusSEXP, SEXP ap_maxitsSEXP, SEXP ap_convitsSEXP, SEXP ap_dampfactSEXP, SEXP ap_detailsSEXP, SEXP ap_nonoiseSEXP, SEXP ap_timeSEXP, SEXP verboseSEXP) { 65 | BEGIN_RCPP 66 | Rcpp::RObject rcpp_result_gen; 67 | Rcpp::RNGScope rcpp_rngScope_gen; 68 | Rcpp::traits::input_parameter< arma::cube >::type input_image(input_imageSEXP); 69 | Rcpp::traits::input_parameter< std::string >::type method(methodSEXP); 70 | Rcpp::traits::input_parameter< int >::type num_superpixel(num_superpixelSEXP); 71 | Rcpp::traits::input_parameter< std::string >::type kmeans_method(kmeans_methodSEXP); 72 | Rcpp::traits::input_parameter< bool >::type AP_data(AP_dataSEXP); 73 | Rcpp::traits::input_parameter< bool >::type use_median(use_medianSEXP); 74 | Rcpp::traits::input_parameter< int >::type minib_kmeans_batch(minib_kmeans_batchSEXP); 75 | Rcpp::traits::input_parameter< double >::type minib_kmeans_init_fraction(minib_kmeans_init_fractionSEXP); 76 | Rcpp::traits::input_parameter< int >::type kmeans_num_init(kmeans_num_initSEXP); 77 | Rcpp::traits::input_parameter< int >::type kmeans_max_iters(kmeans_max_itersSEXP); 78 | Rcpp::traits::input_parameter< std::string >::type kmeans_initializer(kmeans_initializerSEXP); 79 | Rcpp::traits::input_parameter< std::string >::type colour_type(colour_typeSEXP); 80 | Rcpp::traits::input_parameter< double >::type compactness_factor(compactness_factorSEXP); 81 | Rcpp::traits::input_parameter< bool >::type adjust_centroids_and_return_masks(adjust_centroids_and_return_masksSEXP); 82 | Rcpp::traits::input_parameter< bool >::type return_labels_2_dimensionsional(return_labels_2_dimensionsionalSEXP); 83 | Rcpp::traits::input_parameter< bool >::type sim_normalize(sim_normalizeSEXP); 84 | Rcpp::traits::input_parameter< int >::type sim_wL(sim_wLSEXP); 85 | Rcpp::traits::input_parameter< int >::type sim_wA(sim_wASEXP); 86 | Rcpp::traits::input_parameter< int >::type sim_wB(sim_wBSEXP); 87 | Rcpp::traits::input_parameter< int >::type sim_color_radius(sim_color_radiusSEXP); 88 | Rcpp::traits::input_parameter< int >::type ap_maxits(ap_maxitsSEXP); 89 | Rcpp::traits::input_parameter< int >::type ap_convits(ap_convitsSEXP); 90 | Rcpp::traits::input_parameter< double >::type ap_dampfact(ap_dampfactSEXP); 91 | Rcpp::traits::input_parameter< bool >::type ap_details(ap_detailsSEXP); 92 | Rcpp::traits::input_parameter< double >::type ap_nonoise(ap_nonoiseSEXP); 93 | Rcpp::traits::input_parameter< bool >::type ap_time(ap_timeSEXP); 94 | Rcpp::traits::input_parameter< bool >::type verbose(verboseSEXP); 95 | rcpp_result_gen = Rcpp::wrap(image_segmentation(input_image, method, num_superpixel, kmeans_method, AP_data, use_median, minib_kmeans_batch, minib_kmeans_init_fraction, kmeans_num_init, kmeans_max_iters, kmeans_initializer, colour_type, compactness_factor, adjust_centroids_and_return_masks, return_labels_2_dimensionsional, sim_normalize, sim_wL, sim_wA, sim_wB, sim_color_radius, ap_maxits, ap_convits, ap_dampfact, ap_details, ap_nonoise, ap_time, verbose)); 96 | return rcpp_result_gen; 97 | END_RCPP 98 | } 99 | -------------------------------------------------------------------------------- /man/Image_Segmentation.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/px_utils.R 3 | \docType{class} 4 | \name{Image_Segmentation} 5 | \alias{Image_Segmentation} 6 | \title{Segmentation of images based on Superpixels, Affinity propagation and Kmeans clustering} 7 | \usage{ 8 | # init <- Image_Segmentation$new() 9 | } 10 | \description{ 11 | Segmentation of images based on Superpixels, Affinity propagation and Kmeans clustering 12 | 13 | Segmentation of images based on Superpixels, Affinity propagation and Kmeans clustering 14 | } 15 | \details{ 16 | \emph{sim_wL}, \emph{sim_wA}, \emph{sim_wB} are the weights of the three channels. They keep balance so as to be consistent with human perception. 17 | 18 | The quantity \emph{colorradius} adjusts the number of clusters, and if its value is low, the number of targets would increase, which leads to more detailed segmentation results. 19 | 20 | If the \emph{adjust_centroids_and_return_masks} parameter is set to FALSE then the output \emph{kmeans_image_data} will be an RGB image, otherwise it will be a black-and-white image. 21 | 22 | \emph{colour_type} parameter: RGB (Red-Green-Blue), LAB (Lightness, A-colour-dimension, B-colour-dimension) or HSV (Hue, Saturation, Value) colour. 23 | 24 | Higher resolution images give better results. 25 | 26 | The \emph{affinity propagation} algorithm is used here with default parameter values. 27 | 28 | By setting the \emph{sim_normalize} parameter to TRUE, the affinity propagation algorithm requires less iterations to complete. However, the \emph{colorradius} parameter does not have an 29 | effect if the similarity matrix is normalized. 30 | 31 | Regarding the \emph{use_median} parameter in the Rcpp I use the following steps: \emph{1st.} I compute the superpixels and extract the labels, \emph{2nd.} each superpixel label consists of multiple pixels and for these superpixels I have 32 | to compute a dissimilarity matrix therefore each superpixel must correspond to a single value, \emph{3rd.} to come to this single value for each superpixel the R user has the option to either use the 'mean' or the 'median of multiple 33 | image pixels (per superpixel) 34 | 35 | 36 | ---------------kmeans initializers---------------------- 37 | 38 | \strong{optimal_init} : this initializer adds rows of the data incrementally, while checking that they do not already exist in the centroid-matrix 39 | 40 | \strong{quantile_init} : initialization of centroids by using the cummulative distance between observations and by removing potential duplicates 41 | 42 | \strong{kmeans++} : kmeans++ initialization. Reference : http://theory.stanford.edu/~sergei/papers/kMeansPP-soda.pdf AND http://stackoverflow.com/questions/5466323/how-exactly-does-k-means-work 43 | 44 | \strong{random} : random selection of data rows as initial centroids 45 | } 46 | \section{Methods}{ 47 | 48 | 49 | \describe{ 50 | \item{\code{Image_Segmentation$new()}}{} 51 | 52 | \item{\code{--------------}}{} 53 | 54 | \item{\code{spixel_segmentation()}}{} 55 | 56 | \item{\code{--------------}}{} 57 | 58 | \item{\code{spixel_masks_show()}}{} 59 | 60 | \item{\code{--------------}}{} 61 | 62 | \item{\code{spixel_clusters_show()}}{} 63 | 64 | \item{\code{--------------}}{} 65 | 66 | } 67 | } 68 | 69 | \examples{ 70 | 71 | library(SuperpixelImageSegmentation) 72 | 73 | path = system.file("images", "BSR_bsds500_image.jpg", package = "SuperpixelImageSegmentation") 74 | 75 | im = OpenImageR::readImage(path) 76 | 77 | init = Image_Segmentation$new() 78 | 79 | num_spix = 10 # for illustration purposes 80 | # num_spix = 600 # recommended number of superpixels 81 | 82 | spx = init$spixel_segmentation(input_image = im, 83 | superpixel = num_spix, 84 | AP_data = TRUE, 85 | use_median = TRUE, 86 | return_labels_2_dimensionsional = TRUE, 87 | sim_color_radius = 10) 88 | 89 | 90 | #........................... 91 | # plot the superpixel labels 92 | #........................... 93 | 94 | plt = init$spixel_clusters_show(spix_labels = spx$spix_labels, 95 | color_palette = grDevices::rainbow, 96 | parameter_list_png = NULL) 97 | 98 | # plt 99 | 100 | 101 | #.................................................... 102 | # create a binary image for a specified cluster label 103 | #.................................................... 104 | 105 | pix_values = spx$spix_labels 106 | 107 | target_cluster = 3 # determine clusters visually ('plt' variable) 108 | 109 | pix_values[pix_values != target_cluster] = 0 # set all other values to 0 (background) 110 | pix_values[pix_values == target_cluster] = 1 # set the target_cluster to 1 (binary image) 111 | 112 | # OpenImageR::imageShow(pix_values) 113 | 114 | } 115 | \references{ 116 | https://www.ijsr.net/archive/v4i4/SUB152869.pdf , "Image Segmentation using SLIC Superpixels and Affinity Propagation Clustering", Bao Zhou, 2013, International Journal of Science and Research (IJSR) 117 | } 118 | \section{Methods}{ 119 | \subsection{Public methods}{ 120 | \itemize{ 121 | \item \href{#method-Image_Segmentation-new}{\code{Image_Segmentation$new()}} 122 | \item \href{#method-Image_Segmentation-spixel_segmentation}{\code{Image_Segmentation$spixel_segmentation()}} 123 | \item \href{#method-Image_Segmentation-spixel_masks_show}{\code{Image_Segmentation$spixel_masks_show()}} 124 | \item \href{#method-Image_Segmentation-spixel_clusters_show}{\code{Image_Segmentation$spixel_clusters_show()}} 125 | \item \href{#method-Image_Segmentation-clone}{\code{Image_Segmentation$clone()}} 126 | } 127 | } 128 | \if{html}{\out{
}} 129 | \if{html}{\out{}} 130 | \if{latex}{\out{\hypertarget{method-Image_Segmentation-new}{}}} 131 | \subsection{Method \code{new()}}{ 132 | \subsection{Usage}{ 133 | \if{html}{\out{
}}\preformatted{Image_Segmentation$new()}\if{html}{\out{
}} 134 | } 135 | 136 | } 137 | \if{html}{\out{
}} 138 | \if{html}{\out{}} 139 | \if{latex}{\out{\hypertarget{method-Image_Segmentation-spixel_segmentation}{}}} 140 | \subsection{Method \code{spixel_segmentation()}}{ 141 | \subsection{Usage}{ 142 | \if{html}{\out{
}}\preformatted{Image_Segmentation$spixel_segmentation( 143 | input_image, 144 | method = "slic", 145 | superpixel = 200, 146 | kmeans_method = "", 147 | AP_data = FALSE, 148 | use_median = TRUE, 149 | minib_kmeans_batch = 10, 150 | minib_kmeans_init_fraction = 0.5, 151 | kmeans_num_init = 3, 152 | kmeans_max_iters = 100, 153 | kmeans_initializer = "kmeans++", 154 | colour_type = "RGB", 155 | compactness_factor = 20, 156 | adjust_centroids_and_return_masks = FALSE, 157 | return_labels_2_dimensionsional = FALSE, 158 | sim_normalize = FALSE, 159 | sim_wL = 3, 160 | sim_wA = 10, 161 | sim_wB = 10, 162 | sim_color_radius = 20, 163 | ap_maxits = 1000, 164 | ap_convits = 100, 165 | ap_dampfact = 0.9, 166 | ap_nonoise = 0, 167 | verbose = FALSE 168 | )}\if{html}{\out{
}} 169 | } 170 | 171 | \subsection{Arguments}{ 172 | \if{html}{\out{
}} 173 | \describe{ 174 | \item{\code{input_image}}{a 3-dimensional input image (the range of the pixel values should be preferably in the range 0 to 255)} 175 | 176 | \item{\code{method}}{a character string specifying the superpixel method. It can be either "slic" or "slico"} 177 | 178 | \item{\code{superpixel}}{a numeric value specifying the number of superpixels} 179 | 180 | \item{\code{kmeans_method}}{a character string specifying the kmeans method. If not empty ("") then it can be either "kmeans" or "mini_batch_kmeans"} 181 | 182 | \item{\code{AP_data}}{a boolean. If TRUE then the affinity propagation image data will be computed and returned} 183 | 184 | \item{\code{use_median}}{a boolean. If TRUE then the median will be used rather than the mean value for the inner computations (see the details section for more information)} 185 | 186 | \item{\code{minib_kmeans_batch}}{the size of the mini batches} 187 | 188 | \item{\code{minib_kmeans_init_fraction}}{percentage of data to use for the initialization centroids (applies if initializer is \emph{kmeans++} or \emph{optimal_init}). Should be a float number between 0.0 and 1.0.} 189 | 190 | \item{\code{kmeans_num_init}}{number of times the algorithm will be run with different centroid seeds} 191 | 192 | \item{\code{kmeans_max_iters}}{the maximum number of clustering iterations} 193 | 194 | \item{\code{kmeans_initializer}}{the method of initialization. One of, \emph{optimal_init}, \emph{quantile_init}, \emph{kmeans++} and \emph{random}. See details for more information} 195 | 196 | \item{\code{colour_type}}{a character string specifying the colour type. It can be one of "RGB", "LAB" or "HSV"} 197 | 198 | \item{\code{compactness_factor}}{a numeric value specifying the compactness parameter in case that \emph{method} is "slic"} 199 | 200 | \item{\code{adjust_centroids_and_return_masks}}{a boolean. If TRUE and the \emph{kmeans_method} parameter is NOT empty ("") then the centroids will be adjusted and image-masks will be returned. This will allow me to plot the masks using the \emph{spixel_masks_show} method.} 201 | 202 | \item{\code{return_labels_2_dimensionsional}}{a boolean. If TRUE then a matrix of labels based on the output superpixels in combination with the Affinity Propagation clusters will be returned} 203 | 204 | \item{\code{sim_normalize}}{a boolean. If TRUE then the constructed similarity matrix will be normalised to have unit p-norm (see the armadillo documentation for more details)} 205 | 206 | \item{\code{sim_wL}}{a numeric value specifying the weight for the \emph{"L"} channel of the image (see the details section for more information)} 207 | 208 | \item{\code{sim_wA}}{a numeric value specifying the weight for the \emph{"A"} channel of the image (see the details section for more information)} 209 | 210 | \item{\code{sim_wB}}{a numeric value specifying the weight for the \emph{"B"} channel of the image (see the details section for more information)} 211 | 212 | \item{\code{sim_color_radius}}{a numeric value specifying the \emph{colorradius} (see the details section for more information)} 213 | 214 | \item{\code{ap_maxits}}{a numeric value specifying the maximum number of iterations for the Affinity Propagation Clustering (defaults to 1000)} 215 | 216 | \item{\code{ap_convits}}{a numeric value. If the estimated exemplars stay fixed for convits iterations, the affinity propagation algorithm terminates early (defaults to 100)} 217 | 218 | \item{\code{ap_dampfact}}{a float number specifying the update equation damping level in [0.5, 1). Higher values correspond to heavy damping, which may be needed if oscillations occur in the Affinity Propagation Clustering (defaults to 0.9)} 219 | 220 | \item{\code{ap_nonoise}}{a float number. The affinity propagation algorithm adds a small amount of noise to \emph{data} to prevent degenerate cases; this disables that.} 221 | 222 | \item{\code{verbose}}{a boolean. If TRUE then information will be printed in the console (spixel_masks_show method)} 223 | } 224 | \if{html}{\out{
}} 225 | } 226 | } 227 | \if{html}{\out{
}} 228 | \if{html}{\out{}} 229 | \if{latex}{\out{\hypertarget{method-Image_Segmentation-spixel_masks_show}{}}} 230 | \subsection{Method \code{spixel_masks_show()}}{ 231 | \subsection{Usage}{ 232 | \if{html}{\out{
}}\preformatted{Image_Segmentation$spixel_masks_show( 233 | delay_display_seconds = 3, 234 | display_all = FALSE, 235 | margin_btw_plots = 0.15, 236 | verbose = FALSE 237 | )}\if{html}{\out{
}} 238 | } 239 | 240 | \subsection{Arguments}{ 241 | \if{html}{\out{
}} 242 | \describe{ 243 | \item{\code{delay_display_seconds}}{a numeric value specifying the seconds to delay the display of the next image (It displays the images consecutively). This parameter applies only if the \emph{display_all} is set to FALSE (spixel_masks_show method)} 244 | 245 | \item{\code{display_all}}{a boolean. If TRUE then all images will be displayed in a grid (spixel_masks_show method)} 246 | 247 | \item{\code{margin_btw_plots}}{a float number specifying the margins between the plots if the \emph{display_all} parameter is set to TRUE (spixel_masks_show method)} 248 | 249 | \item{\code{verbose}}{a boolean. If TRUE then information will be printed in the console (spixel_masks_show method)} 250 | } 251 | \if{html}{\out{
}} 252 | } 253 | } 254 | \if{html}{\out{
}} 255 | \if{html}{\out{}} 256 | \if{latex}{\out{\hypertarget{method-Image_Segmentation-spixel_clusters_show}{}}} 257 | \subsection{Method \code{spixel_clusters_show()}}{ 258 | \subsection{Usage}{ 259 | \if{html}{\out{
}}\preformatted{Image_Segmentation$spixel_clusters_show( 260 | spix_labels, 261 | color_palette = grDevices::rainbow, 262 | parameter_list_png = NULL 263 | )}\if{html}{\out{
}} 264 | } 265 | 266 | \subsection{Arguments}{ 267 | \if{html}{\out{
}} 268 | \describe{ 269 | \item{\code{spix_labels}}{a matrix. I can retrieve the "spix_labels" parameter by setting the "return_labels_2_dimensionsional" parameter to TRUE in the "spixel_segmentation" method (spixel_clusters_show method)} 270 | 271 | \item{\code{color_palette}}{one of the color palettes. Use ?grDevices::topo.colors to see the available color palettes} 272 | 273 | \item{\code{parameter_list_png}}{either NULL or a list of parameters passed to the ?grDevices::png function, such as list(filename = 'img.png', width = 100, height = 100, units = "px", pointsize = 12, bg = "white", type = "quartz")} 274 | } 275 | \if{html}{\out{
}} 276 | } 277 | } 278 | \if{html}{\out{
}} 279 | \if{html}{\out{}} 280 | \if{latex}{\out{\hypertarget{method-Image_Segmentation-clone}{}}} 281 | \subsection{Method \code{clone()}}{ 282 | The objects of this class are cloneable with this method. 283 | \subsection{Usage}{ 284 | \if{html}{\out{
}}\preformatted{Image_Segmentation$clone(deep = FALSE)}\if{html}{\out{
}} 285 | } 286 | 287 | \subsection{Arguments}{ 288 | \if{html}{\out{
}} 289 | \describe{ 290 | \item{\code{deep}}{Whether to make a deep clone.} 291 | } 292 | \if{html}{\out{
}} 293 | } 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /src/superPx_AP_Kmeans_Segment.cpp: -------------------------------------------------------------------------------- 1 | # include 2 | # include 3 | # include 4 | # include 5 | // [[Rcpp::depends("RcppArmadillo")]] 6 | // [[Rcpp::depends(ClusterR)]] 7 | // [[Rcpp::depends(OpenImageR)]] 8 | 9 | 10 | # include 11 | # include 12 | # include 13 | # include 14 | # include 15 | 16 | 17 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 18 | // similarity function [ negative euclidean -- which gives the best results in the article ] 19 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 20 | 21 | // [[Rcpp::export]] 22 | double simil_A(arma::rowvec spx_vec1, arma::rowvec spx_vec2, int wL = 3, int wA = 10, int wB = 10) { 23 | 24 | double value = -( wL * std::pow((spx_vec1(0) - spx_vec2(0)), 2.0) + wA * std::pow((spx_vec1(1) - spx_vec2(1)), 2.0) + wB * std::pow((spx_vec1(2) - spx_vec2(2)), 2.0) ); 25 | 26 | return value; 27 | } 28 | 29 | 30 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 31 | // apply(im, 3, as.vector) in rcpp 32 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 33 | 34 | // [[Rcpp::export]] 35 | arma::mat apply_rcpp(arma::cube &input) { 36 | 37 | arma::mat res(input.n_rows * input.n_cols, 3); 38 | 39 | for (unsigned int i = 0; i < (input.n_rows * input.n_cols); i++) { 40 | res(i,0) = input.slice(0)(i); 41 | res(i,1) = input.slice(1)(i); 42 | res(i,2) = input.slice(2)(i); 43 | } 44 | 45 | return res; 46 | } 47 | 48 | 49 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 50 | // find which rows of the kmeans-centroids include NA's 51 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 52 | 53 | // [[Rcpp::export]] 54 | arma::uvec NAs_matrix(arma::mat &x) { 55 | 56 | arma::uvec all_rows; 57 | int item = 0; 58 | 59 | if (x.has_nan()) { 60 | for (unsigned int j = 0; j < x.n_cols; j++) { 61 | arma::uvec find_row = arma::find_nonfinite(x.col(j)); 62 | for (unsigned int k = 0; k < find_row.n_elem; k++) { 63 | item++; 64 | all_rows.resize(item); 65 | all_rows(item-1) = find_row(k); 66 | } 67 | } 68 | all_rows = arma::unique(all_rows); 69 | } 70 | return all_rows; 71 | } 72 | 73 | 74 | //------------------------------ 75 | // check if the matrix is finite 76 | //------------------------------ 77 | 78 | // [[Rcpp::export]] 79 | bool is_mt_finite(arma::mat x) { 80 | 81 | clustR::ClustHeader clsh; 82 | 83 | return clsh.check_NaN_Inf(x); 84 | } 85 | 86 | 87 | //---------------------------- 88 | // image segmentation function [ superpixels, affinity propagation, kmeans ] 89 | //---------------------------- 90 | 91 | // [[Rcpp::export]] 92 | Rcpp::List image_segmentation(arma::cube input_image, 93 | std::string method = "slic", 94 | int num_superpixel = 200, 95 | std::string kmeans_method = "", 96 | bool AP_data = false, 97 | bool use_median = true, 98 | int minib_kmeans_batch = 10, 99 | double minib_kmeans_init_fraction = 0.5, 100 | int kmeans_num_init = 3, 101 | int kmeans_max_iters = 100, 102 | std::string kmeans_initializer = "kmeans++", 103 | std::string colour_type = "RGB", 104 | double compactness_factor = 20, 105 | bool adjust_centroids_and_return_masks = false, 106 | bool return_labels_2_dimensionsional = false, 107 | bool sim_normalize = false, 108 | int sim_wL = 3, 109 | int sim_wA = 10, 110 | int sim_wB = 10, 111 | int sim_color_radius = 20, 112 | int ap_maxits = 1000, 113 | int ap_convits = 100, 114 | double ap_dampfact = 0.9, 115 | bool ap_details = false, 116 | double ap_nonoise = 0.0, 117 | bool ap_time = false, 118 | bool verbose = false) { 119 | 120 | Affinity_Propagation afpr; 121 | clustR::ClustHeader clsh; 122 | 123 | if (kmeans_method == "" && !AP_data) { Rcpp::stop("Either the 'kmeans_method' should be set to 'kmeans' / 'mini_batch_kmeans' or the 'AP' parameter should be set to TRUE!"); } 124 | if (adjust_centroids_and_return_masks && kmeans_method == "") { Rcpp::Rcout << "WARNING: Set the 'kmeans_method' parameter to either 'kmeans' or 'mini_batch_kmeans' to receive image-masks data!" << std::endl; } 125 | if (verbose) { Rcpp::Rcout << "Sequential computation starts ..." << std::endl; } 126 | 127 | oimageR::Utility_functions UTLF; 128 | Rcpp::List lst_obj = UTLF.interface_superpixels(input_image, method, num_superpixel, compactness_factor, true, true, true, "", false); 129 | 130 | if (verbose) { Rcpp::Rcout << "The super-pixel " << method << " method as pre-processing step was used / completed!" << std::endl; } 131 | 132 | arma::cube im_3d_obj = Rcpp::as(lst_obj["slic_data"]); 133 | arma::mat labels_obj = Rcpp::as(lst_obj["labels"]); 134 | arma::cube lab_obj = Rcpp::as(lst_obj["lab_data"]); 135 | 136 | arma::colvec unq_labels = arma::unique(arma::vectorise(labels_obj)); 137 | int ITEMS = unq_labels.n_elem; 138 | 139 | arma::mat lst_3d_vecs(ITEMS, 3, arma::fill::zeros); 140 | int inner_iters = 3; 141 | 142 | for (int i = 0; i < ITEMS; i++) { 143 | arma::uvec idx = arma::find(labels_obj == unq_labels(i)); 144 | if (idx.n_elem > 0) { // it is possible that the unique number of labels is less than the ITEMS 145 | arma::rowvec inner_vec(3, arma::fill::zeros); 146 | for (int k = 0; k < inner_iters; k++) { 147 | arma::mat in_mt = lab_obj.slice(k); // use 'rgb_2lab' as input, as the similarity function requires 'Lab' input data 148 | if (use_median) { 149 | inner_vec(k) = arma::median(arma::vectorise(in_mt(idx))); 150 | } 151 | else { 152 | inner_vec(k) = arma::mean(arma::vectorise(in_mt(idx))); 153 | } 154 | } 155 | lst_3d_vecs.row(unq_labels(i)) = inner_vec; // keep track of the 'unq_labels' because after calculating the distance matrix I have to retrieve the initial labels 156 | } 157 | } 158 | 159 | arma::rowvec vec_avg((ITEMS * ITEMS) - ITEMS); // number of elements of the similarity matrix excluding the main diagonal 160 | 161 | arma::mat simil_mt(ITEMS,ITEMS, arma::fill::zeros); 162 | for (int i = 0; i < ITEMS; i++) { 163 | for (int j = 0; j < ITEMS; j++) { 164 | if (i != j) { // calculate the distance only for the off-diagonal items (items not in the main diagonal) 165 | double tmp_dist = simil_A(lst_3d_vecs.row(i), lst_3d_vecs.row(j), sim_wL, sim_wA, sim_wB); // same note for 'unq_labels' as before 166 | vec_avg = tmp_dist; // use the similarities both to construct the similarity matrix and to extract the preference value 167 | simil_mt(i,j) = tmp_dist; 168 | } 169 | } 170 | } 171 | 172 | double norm_avg = 0; // recalculate the preference value based on the center-scaled data 173 | if (sim_normalize) { // normalize the similarity matrix 174 | simil_mt = arma::normalise(simil_mt); 175 | if (!use_median) { 176 | int norm_items = 0; 177 | for (unsigned int f = 0; f < simil_mt.n_rows; f++) { 178 | for (unsigned int ff = 0; ff < simil_mt.n_cols; ff++) { 179 | if (f != ff) { 180 | norm_avg += simil_mt(f,ff); 181 | norm_items++; 182 | } 183 | } 184 | } 185 | norm_avg /= norm_items; 186 | } 187 | } 188 | 189 | double avg_off_diag = 0; // set the preference value taking into account the 'colorradius' [ 'use_median' option too, in contrary to the mean value of the article ] 190 | if (use_median) { 191 | avg_off_diag = arma::median(arma::vectorise(simil_mt)); 192 | } 193 | else { 194 | if (sim_normalize) { 195 | avg_off_diag = norm_avg; 196 | } 197 | else { 198 | avg_off_diag = arma::mean(vec_avg); // the mean value is based on the off-diagonal elements only 199 | } 200 | } 201 | double set_preference_value = sim_color_radius * avg_off_diag; 202 | 203 | if (verbose) { Rcpp::Rcout << "The similarity matrix based on super-pixels was computed!" << std::endl; } 204 | 205 | 206 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 207 | // affinity propagation with default values [ use affinity propagation on super-pixels because it does not scale well in high dimensions ] 208 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 209 | 210 | std::vector preference; 211 | preference.push_back(set_preference_value); 212 | 213 | Rcpp::List afpr_res = afpr.affinity_propagation(simil_mt, preference, ap_maxits, ap_convits, ap_dampfact, ap_details, ap_nonoise, 2.2204e-16, ap_time); // the affinity propagation algorithm returns clusters for the superpixels, thus it's not possible to 'adjust_centroids_and_return_masks' as is the case in kmeans 214 | 215 | int affinty_num_clusters = Rcpp::as(afpr_res["K"]); // number of clusters 216 | std::string aff_str_num = std::to_string(affinty_num_clusters); 217 | int AP_iterations = Rcpp::as(afpr_res["iterations"]); 218 | std::string str_iterations = std::to_string(AP_iterations); 219 | std::vector exemplars = Rcpp::as >(afpr_res["exemplars"]); // exemplars 220 | 221 | std::unordered_map > clusters; // workaround to get the clusters and their indices as an unordered_map because it can't be done directly using Rcpp::as(clust_nams[n]); 226 | std::vector vec_in = Rcpp::as >(tmp_clusts[n]); 227 | int val_in = std::stoi(str_in); 228 | clusters[val_in] = vec_in; 229 | } 230 | 231 | if (verbose) { Rcpp::Rcout << "It took " + str_iterations + " iterations for affinity propagation to complete!" << std::endl; } 232 | if (verbose) { Rcpp::Rcout << aff_str_num + " clusters were chosen based on super-pixels and affinity propagation!" << std::endl; } 233 | 234 | arma::cube hsv_tmp; 235 | if (colour_type == "HSV") { 236 | hsv_tmp = UTLF.RGB_to_HSV(input_image); 237 | } 238 | 239 | arma::cube ap_new_im; 240 | 241 | if (AP_data) { 242 | 243 | ap_new_im.set_size(input_image.n_rows, input_image.n_cols, input_image.n_slices); 244 | std::vector ap_keys; // obtain the clusters 245 | int CLUSTS = clusters.size(); 246 | ap_keys.reserve(CLUSTS); 247 | 248 | for(auto kv : clusters) { 249 | ap_keys.push_back(kv.first); 250 | } 251 | std::unordered_map > idx_clusters; 252 | for (unsigned int d = 0; d < ap_keys.size(); d++) { 253 | std::vector sub_clust = clusters[ap_keys[d]]; 254 | for (unsigned int dd = 0; dd < sub_clust.size(); dd++) { 255 | arma::uvec idx = arma::find(labels_obj == sub_clust[dd]); 256 | if (idx.size() > 0) { 257 | for (unsigned int ddd = 0; ddd < idx.size(); ddd++) { 258 | idx_clusters[ap_keys[d]].push_back(idx[ddd]); // assign the indices of each sub-cluster 259 | } 260 | } 261 | } 262 | } 263 | for (int f = 0; f < CLUSTS; f++) { 264 | arma::uvec tmp_cl_idx = arma::conv_to::from(idx_clusters[ap_keys[f]]); 265 | for (unsigned int ff = 0; ff < input_image.n_slices; ff++) { 266 | double avg_clust_idx; // return the transformed image based on the specified colour-type 267 | if (colour_type == "RGB") { 268 | if (use_median) { 269 | avg_clust_idx = arma::median(arma::vectorise(input_image.slice(ff)(tmp_cl_idx))); 270 | } 271 | else { 272 | avg_clust_idx = arma::mean(arma::vectorise(input_image.slice(ff)(tmp_cl_idx))); 273 | } 274 | } 275 | else if (colour_type == "LAB") { 276 | if (use_median) { 277 | avg_clust_idx = arma::median(arma::vectorise(lab_obj.slice(ff)(tmp_cl_idx))); 278 | } 279 | else { 280 | avg_clust_idx = arma::mean(arma::vectorise(lab_obj.slice(ff)(tmp_cl_idx))); 281 | } 282 | } 283 | else if (colour_type == "HSV") { 284 | if (use_median) { 285 | avg_clust_idx = arma::median(arma::vectorise(hsv_tmp.slice(ff)(tmp_cl_idx))); 286 | } 287 | else { 288 | avg_clust_idx = arma::mean(arma::vectorise(hsv_tmp.slice(ff)(tmp_cl_idx))); 289 | } 290 | } 291 | else { 292 | Rcpp::stop("Invalid 'colour_type' parameter!"); 293 | } 294 | ap_new_im.slice(ff)(tmp_cl_idx).fill(avg_clust_idx); // use the subset of cluster-indices to assign the average value 295 | } 296 | } 297 | 298 | if (verbose) { Rcpp::Rcout << "Image data based on Affinity Propagation clustering ('AP_image_data') will be returned!" << std::endl; } 299 | } 300 | 301 | 302 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 303 | // process the initial data for vector quantization 304 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 305 | 306 | if (input_image.empty()) { Rcpp::stop("the initial 3-dimensional data is an empty object!"); } 307 | 308 | arma::mat im2; 309 | 310 | if (colour_type == "RGB") { 311 | im2 = apply_rcpp(input_image); 312 | } 313 | else if (colour_type == "LAB") { 314 | im2 = apply_rcpp(lab_obj); 315 | } 316 | else if (colour_type == "HSV") { 317 | im2 = apply_rcpp(hsv_tmp); 318 | } 319 | else { 320 | Rcpp::stop("Invalid 'colour_type' parameter!"); 321 | } 322 | 323 | 324 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 325 | // use the affinity-propagation number of clusters as 326 | // input to the k-means or mini-batch-kmeans algorithm 327 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 328 | 329 | Rcpp::List kmeans_obj; 330 | std::string which_km; 331 | arma::mat getcent; 332 | arma::rowvec getclust; 333 | arma::cube new_im; 334 | arma::field masks_lst; 335 | 336 | if (kmeans_method != "") { 337 | 338 | if (kmeans_method == "mini_batch_kmeans") { 339 | 340 | kmeans_obj = clsh.mini_batch_kmeans(im2, affinty_num_clusters, minib_kmeans_batch, kmeans_max_iters, kmeans_num_init, minib_kmeans_init_fraction, 341 | kmeans_initializer, minib_kmeans_batch, false, R_NilValue, 1e-4, 0.5, 1); 342 | 343 | getcent = Rcpp::as(kmeans_obj[0]); 344 | Rcpp::List preds_lst = clsh.Predict_mini_batch_kmeans(im2, getcent, false, 1.0e-6); 345 | getclust = Rcpp::as(preds_lst[0]); 346 | which_km = "mini-batch-kmeans"; 347 | } 348 | else if (kmeans_method == "kmeans") { 349 | kmeans_obj = clsh.KMEANS_rcpp(im2, affinty_num_clusters, kmeans_num_init, kmeans_max_iters, 350 | kmeans_initializer, false, false, R_NilValue, 1e-4, 1.0e-6, 0.5, 1); 351 | 352 | getcent = Rcpp::as(kmeans_obj["centers"]); 353 | getclust = Rcpp::as(kmeans_obj["clusters"]); 354 | which_km = "kmeans"; 355 | } 356 | else { 357 | Rcpp::stop("Invalid method for the 'kmeans_method' parameter!"); 358 | } 359 | if (verbose) { Rcpp::Rcout << "The " + which_km + " algorithm based on the number of affinity-propagation-clusters was completed!" << std::endl; } 360 | 361 | 362 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 363 | // use centroids and clusters to create the output-kmeans-image 364 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 365 | 366 | arma::uvec idx_NA = NAs_matrix(getcent); 367 | if (!idx_NA.empty()) { 368 | // arma::uvec all_idx = arma::regspace(0, 1, getcent.n_rows-1); // create vector of indices 369 | // arma::uvec keep_dat = afpr.matlab_setdiff(all_idx, idx_NA); // find which indices do not have NA's 370 | // getcent = getcent.rows(keep_dat); // overwrite initial centroids by keeping those who do not include NA's. NOT A GOOD APPROACH because getcent.n_rows != getclust.max() and this raises an error in "new_im.slice(0)(f) = getcent(getclust(f), 0);" 371 | getcent.rows(idx_NA).fill(0); // better fill the centroid-rows including NA's with 0's rather than overwritting the initial centroids matrix (see comment previous line) 372 | std::string tmp_str = std::to_string(idx_NA.n_elem); 373 | Rcpp::Rcout << "WARNING: " + tmp_str + " row(s) of the output centroids include missing values (NA's)!. The missing values will be replaced / filled with 0's! To overcome this warning an option would be to experiment with a different 'kmeans_initializer'!" << std::endl; 374 | } 375 | 376 | if (adjust_centroids_and_return_masks) { // here I solely adjust the centroids 377 | if (verbose) { Rcpp::Rcout << "NOTE: The 'KMeans_image_data' will be returned in black & white colour due to the fact that the 'adjust_centroids_and_return_masks' parameter was set to TRUE!" << std::endl; } 378 | int true_clusters = getcent.n_rows; // it is possible that there are NA's in the centroids 379 | getcent.set_size(true_clusters, 3); // n_cols = 3 because I have an RGB image 380 | for (int i = 0; i < true_clusters; i++) { 381 | arma::rowvec tmp_row(3); 382 | tmp_row.fill(i); 383 | getcent.row(i) = tmp_row; // in case that 'adjust_centroids_and_return_masks' is set to TRUE the KMeans-centroids will be overwritten. Better quality of the 'KMeans_image_data' output can be obtained if 'adjust_centroids_and_return_masks' is set to FALSE. 384 | } 385 | } 386 | 387 | new_im.set_size(input_image.n_rows, input_image.n_cols, input_image.n_slices); // see for a similar way to create the cluster-masks in R : https://github.com/mlampros/ClusterR/issues/14#issuecomment-457692420 388 | new_im.fill(0); 389 | for (unsigned int f = 0; f < getclust.n_elem; f++) { 390 | new_im.slice(0)(f) = getcent(getclust(f), 0); // each observation is associated with the nearby centroid 391 | new_im.slice(1)(f) = getcent(getclust(f), 1); 392 | new_im.slice(2)(f) = getcent(getclust(f), 2); 393 | } 394 | 395 | if (verbose) { Rcpp::Rcout << "Pre-processing of the " + which_km + "-output-image is completed!" << std::endl; } 396 | 397 | 398 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 399 | // display segments of the 'new_im' image 400 | //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 401 | 402 | masks_lst.set_size(affinty_num_clusters, 1); 403 | 404 | if (adjust_centroids_and_return_masks) { // adjust the centroids and return the masks 405 | for (int IM = 0; IM < affinty_num_clusters; IM++) { 406 | arma::Cube mask_3d(new_im.n_rows, new_im.n_cols, 3); 407 | for (int f = 0; f < 3; f++) { 408 | arma::Mat mask1(new_im.n_rows, new_im.n_cols, arma::fill::zeros); 409 | arma::uvec tmp_uvec = arma::find(new_im.slice(f) == IM); 410 | mask1(tmp_uvec).fill(1); 411 | mask_3d.slice(f) = mask1; 412 | } 413 | 414 | arma::cube tmp_show; 415 | 416 | if (colour_type == "RGB") { 417 | tmp_show = input_image % mask_3d; 418 | } 419 | else if (colour_type == "LAB") { 420 | tmp_show = lab_obj % mask_3d; 421 | } 422 | else if (colour_type == "HSV") { 423 | tmp_show = hsv_tmp % mask_3d; 424 | } 425 | else { 426 | Rcpp::stop("Invalid 'colour_type' parameter!"); 427 | } 428 | masks_lst(IM,0) = tmp_show; 429 | } 430 | if (verbose) { Rcpp::Rcout << "The centroids were adjusted and the image-masks will be returned!" << std::endl; } 431 | } 432 | } 433 | 434 | Rcpp::List tmp_lst = Rcpp::List::create(Rcpp::Named("KMeans_image_data") = new_im, 435 | Rcpp::Named("KMeans_clusters") = getclust, 436 | Rcpp::Named("masks") = masks_lst, 437 | Rcpp::Named("centr") = getcent, 438 | Rcpp::Named("AP_image_data") = ap_new_im); 439 | 440 | if (return_labels_2_dimensionsional) { 441 | tmp_lst["spix_labels"] = labels_obj; 442 | tmp_lst["AP_clusters"] = clusters; 443 | } 444 | 445 | return tmp_lst; 446 | } 447 | 448 | -------------------------------------------------------------------------------- /R/px_utils.R: -------------------------------------------------------------------------------- 1 | 2 | 3 | #' Segmentation of images based on Superpixels, Affinity propagation and Kmeans clustering 4 | #' 5 | #' 6 | #' @param input_image a 3-dimensional input image (the range of the pixel values should be preferably in the range 0 to 255) 7 | #' @param method a character string specifying the superpixel method. It can be either "slic" or "slico" 8 | #' @param superpixel a numeric value specifying the number of superpixels 9 | #' @param kmeans_method a character string specifying the kmeans method. If not empty ("") then it can be either "kmeans" or "mini_batch_kmeans" 10 | #' @param AP_data a boolean. If TRUE then the affinity propagation image data will be computed and returned 11 | #' @param use_median a boolean. If TRUE then the median will be used rather than the mean value for the inner computations (see the details section for more information) 12 | #' @param minib_kmeans_batch the size of the mini batches 13 | #' @param kmeans_num_init number of times the algorithm will be run with different centroid seeds 14 | #' @param kmeans_max_iters the maximum number of clustering iterations 15 | #' @param minib_kmeans_init_fraction percentage of data to use for the initialization centroids (applies if initializer is \emph{kmeans++} or \emph{optimal_init}). Should be a float number between 0.0 and 1.0. 16 | #' @param kmeans_initializer the method of initialization. One of, \emph{optimal_init}, \emph{quantile_init}, \emph{kmeans++} and \emph{random}. See details for more information 17 | #' @param colour_type a character string specifying the colour type. It can be one of "RGB", "LAB" or "HSV" 18 | #' @param compactness_factor a numeric value specifying the compactness parameter in case that \emph{method} is "slic" 19 | #' @param adjust_centroids_and_return_masks a boolean. If TRUE and the \emph{kmeans_method} parameter is NOT empty ("") then the centroids will be adjusted and image-masks will be returned. This will allow me to plot the masks using the \emph{spixel_masks_show} method. 20 | #' @param return_labels_2_dimensionsional a boolean. If TRUE then a matrix of labels based on the output superpixels in combination with the Affinity Propagation clusters will be returned 21 | #' @param sim_normalize a boolean. If TRUE then the constructed similarity matrix will be normalised to have unit p-norm (see the armadillo documentation for more details) 22 | #' @param sim_wL a numeric value specifying the weight for the \emph{"L"} channel of the image (see the details section for more information) 23 | #' @param sim_wA a numeric value specifying the weight for the \emph{"A"} channel of the image (see the details section for more information) 24 | #' @param sim_wB a numeric value specifying the weight for the \emph{"B"} channel of the image (see the details section for more information) 25 | #' @param sim_color_radius a numeric value specifying the \emph{colorradius} (see the details section for more information) 26 | #' @param ap_maxits a numeric value specifying the maximum number of iterations for the Affinity Propagation Clustering (defaults to 1000) 27 | #' @param ap_convits a numeric value. If the estimated exemplars stay fixed for convits iterations, the affinity propagation algorithm terminates early (defaults to 100) 28 | #' @param ap_dampfact a float number specifying the update equation damping level in [0.5, 1). Higher values correspond to heavy damping, which may be needed if oscillations occur in the Affinity Propagation Clustering (defaults to 0.9) 29 | #' @param ap_nonoise a float number. The affinity propagation algorithm adds a small amount of noise to \emph{data} to prevent degenerate cases; this disables that. 30 | #' @param delay_display_seconds a numeric value specifying the seconds to delay the display of the next image (It displays the images consecutively). This parameter applies only if the \emph{display_all} is set to FALSE (spixel_masks_show method) 31 | #' @param display_all a boolean. If TRUE then all images will be displayed in a grid (spixel_masks_show method) 32 | #' @param margin_btw_plots a float number specifying the margins between the plots if the \emph{display_all} parameter is set to TRUE (spixel_masks_show method) 33 | #' @param verbose a boolean. If TRUE then information will be printed in the console (spixel_masks_show method) 34 | #' @param spix_labels a matrix. I can retrieve the "spix_labels" parameter by setting the "return_labels_2_dimensionsional" parameter to TRUE in the "spixel_segmentation" method (spixel_clusters_show method) 35 | #' @param color_palette one of the color palettes. Use ?grDevices::topo.colors to see the available color palettes 36 | #' @param parameter_list_png either NULL or a list of parameters passed to the ?grDevices::png function, such as list(filename = 'img.png', width = 100, height = 100, units = "px", pointsize = 12, bg = "white", type = "quartz") 37 | #' 38 | #' @export 39 | #' @references 40 | #' 41 | #' https://www.ijsr.net/archive/v4i4/SUB152869.pdf , "Image Segmentation using SLIC Superpixels and Affinity Propagation Clustering", Bao Zhou, 2013, International Journal of Science and Research (IJSR) 42 | #' 43 | #' @details 44 | #' 45 | #' \emph{sim_wL}, \emph{sim_wA}, \emph{sim_wB} are the weights of the three channels. They keep balance so as to be consistent with human perception. 46 | #' 47 | #' The quantity \emph{colorradius} adjusts the number of clusters, and if its value is low, the number of targets would increase, which leads to more detailed segmentation results. 48 | #' 49 | #' If the \emph{adjust_centroids_and_return_masks} parameter is set to FALSE then the output \emph{kmeans_image_data} will be an RGB image, otherwise it will be a black-and-white image. 50 | #' 51 | #' \emph{colour_type} parameter: RGB (Red-Green-Blue), LAB (Lightness, A-colour-dimension, B-colour-dimension) or HSV (Hue, Saturation, Value) colour. 52 | #' 53 | #' Higher resolution images give better results. 54 | #' 55 | #' The \emph{affinity propagation} algorithm is used here with default parameter values. 56 | #' 57 | #' By setting the \emph{sim_normalize} parameter to TRUE, the affinity propagation algorithm requires less iterations to complete. However, the \emph{colorradius} parameter does not have an 58 | #' effect if the similarity matrix is normalized. 59 | #' 60 | #' Regarding the \emph{use_median} parameter in the Rcpp I use the following steps: \emph{1st.} I compute the superpixels and extract the labels, \emph{2nd.} each superpixel label consists of multiple pixels and for these superpixels I have 61 | #' to compute a dissimilarity matrix therefore each superpixel must correspond to a single value, \emph{3rd.} to come to this single value for each superpixel the R user has the option to either use the 'mean' or the 'median of multiple 62 | #' image pixels (per superpixel) 63 | #' 64 | #' 65 | #' ---------------kmeans initializers---------------------- 66 | #' 67 | #' \strong{optimal_init} : this initializer adds rows of the data incrementally, while checking that they do not already exist in the centroid-matrix 68 | #' 69 | #' \strong{quantile_init} : initialization of centroids by using the cummulative distance between observations and by removing potential duplicates 70 | #' 71 | #' \strong{kmeans++} : kmeans++ initialization. Reference : http://theory.stanford.edu/~sergei/papers/kMeansPP-soda.pdf AND http://stackoverflow.com/questions/5466323/how-exactly-does-k-means-work 72 | #' 73 | #' \strong{random} : random selection of data rows as initial centroids 74 | #' 75 | #' @docType class 76 | #' @importFrom R6 R6Class 77 | #' @importFrom OpenImageR NormalizeObject rotateFixed imageShow readImage 78 | #' @importFrom grDevices png rainbow dev.off 79 | #' @importFrom lattice levelplot 80 | #' 81 | #' @section Methods: 82 | #' 83 | #' \describe{ 84 | #' \item{\code{Image_Segmentation$new()}}{} 85 | #' 86 | #' \item{\code{--------------}}{} 87 | #' 88 | #' \item{\code{spixel_segmentation()}}{} 89 | #' 90 | #' \item{\code{--------------}}{} 91 | #' 92 | #' \item{\code{spixel_masks_show()}}{} 93 | #' 94 | #' \item{\code{--------------}}{} 95 | #' 96 | #' \item{\code{spixel_clusters_show()}}{} 97 | #' 98 | #' \item{\code{--------------}}{} 99 | #' 100 | #' } 101 | #' @usage # init <- Image_Segmentation$new() 102 | #' @examples 103 | #' 104 | #' library(SuperpixelImageSegmentation) 105 | #' 106 | #' path = system.file("images", "BSR_bsds500_image.jpg", package = "SuperpixelImageSegmentation") 107 | #' 108 | #' im = OpenImageR::readImage(path) 109 | #' 110 | #' init = Image_Segmentation$new() 111 | #' 112 | #' num_spix = 10 # for illustration purposes 113 | #' # num_spix = 600 # recommended number of superpixels 114 | #' 115 | #' spx = init$spixel_segmentation(input_image = im, 116 | #' superpixel = num_spix, 117 | #' AP_data = TRUE, 118 | #' use_median = TRUE, 119 | #' return_labels_2_dimensionsional = TRUE, 120 | #' sim_color_radius = 10) 121 | #' 122 | #' 123 | #' #........................... 124 | #' # plot the superpixel labels 125 | #' #........................... 126 | #' 127 | #' plt = init$spixel_clusters_show(spix_labels = spx$spix_labels, 128 | #' color_palette = grDevices::rainbow, 129 | #' parameter_list_png = NULL) 130 | #' 131 | #' # plt 132 | #' 133 | #' 134 | #' #.................................................... 135 | #' # create a binary image for a specified cluster label 136 | #' #.................................................... 137 | #' 138 | #' pix_values = spx$spix_labels 139 | #' 140 | #' target_cluster = 3 # determine clusters visually ('plt' variable) 141 | #' 142 | #' pix_values[pix_values != target_cluster] = 0 # set all other values to 0 (background) 143 | #' pix_values[pix_values == target_cluster] = 1 # set the target_cluster to 1 (binary image) 144 | #' 145 | #' # OpenImageR::imageShow(pix_values) 146 | #' 147 | 148 | Image_Segmentation <- R6::R6Class("Image_Segmentation", 149 | 150 | lock_objects = FALSE, 151 | 152 | public = list( 153 | 154 | initialize = function() { 155 | 156 | }, 157 | 158 | 159 | #................................................................................. 160 | # image segmentation using superpixels, Affinity propagation and Kmeans clustering 161 | #................................................................................. 162 | 163 | spixel_segmentation = function(input_image, 164 | method = "slic", 165 | superpixel = 200, 166 | kmeans_method = "", 167 | AP_data = FALSE, 168 | use_median = TRUE, 169 | minib_kmeans_batch = 10, 170 | minib_kmeans_init_fraction = 0.5, 171 | kmeans_num_init = 3, 172 | kmeans_max_iters = 100, 173 | kmeans_initializer = "kmeans++", 174 | colour_type = "RGB", 175 | compactness_factor = 20, 176 | adjust_centroids_and_return_masks = FALSE, 177 | return_labels_2_dimensionsional = FALSE, 178 | sim_normalize = FALSE, 179 | sim_wL = 3, 180 | sim_wA = 10, 181 | sim_wB = 10, 182 | sim_color_radius = 20, 183 | ap_maxits = 1000, 184 | ap_convits = 100, 185 | ap_dampfact = 0.9, 186 | ap_nonoise = 0.0, 187 | verbose = FALSE) { 188 | 189 | if (!kmeans_initializer %in% c('kmeans++', 'random', 'optimal_init', 'quantile_init')) stop("available initializer methods are 'kmeans++', 'random', 'optimal_init' and 'quantile_init'", call. = F) 190 | 191 | if (verbose) { 192 | t_start = proc.time() 193 | } 194 | if (adjust_centroids_and_return_masks) { 195 | private$masks_flag = T 196 | } 197 | private$lst_obj = image_segmentation(input_image = input_image, 198 | method = method, 199 | num_superpixel = superpixel, 200 | kmeans_method = kmeans_method, 201 | AP_data = AP_data, 202 | use_median = use_median, 203 | minib_kmeans_batch = minib_kmeans_batch, 204 | minib_kmeans_init_fraction = minib_kmeans_init_fraction, 205 | kmeans_num_init = kmeans_num_init, 206 | kmeans_max_iters = kmeans_max_iters, 207 | kmeans_initializer = kmeans_initializer, 208 | colour_type = colour_type, 209 | compactness_factor = compactness_factor, 210 | adjust_centroids_and_return_masks = adjust_centroids_and_return_masks, 211 | return_labels_2_dimensionsional = return_labels_2_dimensionsional, 212 | sim_normalize = sim_normalize, 213 | sim_wL = sim_wL, 214 | sim_wA = sim_wA, 215 | sim_wB = sim_wB, 216 | sim_color_radius = sim_color_radius, 217 | ap_maxits = ap_maxits, 218 | ap_convits = ap_convits, 219 | ap_dampfact = ap_dampfact, 220 | ap_details = verbose, # use 'verbose' also for the affinity propagation 'details' 221 | ap_nonoise = ap_nonoise, 222 | ap_time = verbose, # use 'verbose' also for the affinity propagation 'time' (this might also give an indication if the affinity propagation algorithm is a bottleneck for specific use cases - especially if the dissimilarity matrix becomes too big due to the specified number of super-pixels) 223 | verbose = verbose) 224 | 225 | if (return_labels_2_dimensionsional) { 226 | lbs_out = matrix(0, nrow = nrow(private$lst_obj$spix_labels), ncol = ncol(private$lst_obj$spix_labels)) # initialize a new matrix because the pixel values of the 'spix_labels' matrix might overlap with the cluster numbers (iterations: 'i') 227 | for (i in 1:length(private$lst_obj$AP_clusters)) { 228 | idx = which(private$lst_obj$spix_labels %in% private$lst_obj$AP_clusters[[i]]) # more efficient than using c++ 229 | lbs_out[idx] = i 230 | } 231 | 232 | private$lst_obj$spix_labels = NULL # remove the 'spix_labels' object 233 | private$lst_obj$AP_clusters = NULL # remove the 'AP-clusters' object 234 | private$lst_obj$spix_labels = lbs_out # assign the initialized and populated matrix to the output list 235 | } 236 | 237 | if (verbose) { 238 | t_end = proc.time() 239 | time_total = as.numeric((t_end - t_start)['elapsed']) 240 | time_ = private$elapsed_time(time_total) 241 | cat(time_, "\n") 242 | } 243 | 244 | return(private$lst_obj) 245 | }, 246 | 247 | 248 | #................................................................. 249 | # plot the image segmentation masks (based on the output clusters) 250 | #................................................................. 251 | 252 | spixel_masks_show = function(delay_display_seconds = 3, 253 | display_all = FALSE, 254 | margin_btw_plots = 0.15, 255 | verbose = FALSE) { 256 | 257 | if (is.null(private$lst_obj)) { stop("First run the 'spixel_segmentation' method with the 'adjust_centroids_and_return_masks' parameter set to TRUE!", call. = F) } 258 | if (!private$masks_flag) { stop("The 'adjust_centroids_and_return_masks' parameter of the 'spixel_segmentation' method should be set to TRUE!", call. = F) } 259 | 260 | if (display_all) { 261 | 262 | grid_r_c = private$calc_grid_rows_cols(length(private$lst_obj$masks)) 263 | par(mfrow = c(grid_r_c$rows, grid_r_c$cols), mar = c(margin_btw_plots, margin_btw_plots, margin_btw_plots, margin_btw_plots)) 264 | 265 | for (plt in 1:length(private$lst_obj$masks)) { 266 | tmp_im = OpenImageR::NormalizeObject(private$lst_obj$masks[[plt]]) 267 | has_nan = unlist(lapply(1:dim(tmp_im)[3], function(x) is_mt_finite(tmp_im[,,x]))) 268 | if (sum(has_nan) != 3) { 269 | cat("WARNING: The image-mask", plt, "includes non-finite data and won't be plotted!", "\n") 270 | } 271 | else { 272 | tmp_im = grDevices::as.raster(tmp_im) 273 | graphics::plot(tmp_im) 274 | } 275 | } 276 | } 277 | else { 278 | for (i in 1:length(private$lst_obj$masks)) { 279 | has_nan = unlist(lapply(1:dim(private$lst_obj$masks[[i]])[3], function(x) is_mt_finite(private$lst_obj$masks[[i]][,,x]))) 280 | if (verbose) { cat("Image:", i, "\n") } 281 | if (sum(as.vector(private$lst_obj$masks[[i]])) == 0) { 282 | cat("WARNING: The image mask", i, "is an all-0's-image!", "\n") 283 | } 284 | else if (sum(has_nan) != 3) { 285 | cat("WARNING: The image-mask", i, "includes non-finite data and won't be plotted!", "\n") 286 | } 287 | else { 288 | OpenImageR::imageShow(private$lst_obj$masks[[i]]) 289 | Sys.sleep(delay_display_seconds) 290 | } 291 | } 292 | } 293 | }, 294 | 295 | 296 | #...................................................................................................... 297 | # plot 2-dimensional superpixel clusters along with a legend so that I'll be in place to distinguish 298 | # between the cluster labels 299 | # 300 | # I can retrieve the "spix_labels" parameter by setting the "return_labels_2_dimensionsional" parameter 301 | # to TRUE in the "spixel_segmentation" method of this R6 class 302 | #...................................................................................................... 303 | 304 | spixel_clusters_show = function(spix_labels, 305 | color_palette = grDevices::rainbow, 306 | parameter_list_png = NULL) { 307 | 308 | if (!inherits(spix_labels, 'matrix')) stop("The 'spix_labels' parameter must be of type matrix (set the 'return_labels_2_dimensionsional' parameter to TRUE in the 'spixel_segmentation' method)!", call. = F) 309 | 310 | LEN_UNQ = length(unique(as.vector(spix_labels))) 311 | spix_labels = OpenImageR::rotateFixed(spix_labels, angle = 90) # rotate the output labels to visualize the image 312 | 313 | if (!is.null(parameter_list_png)) { 314 | do.call(grDevices::png, parameter_list_png) 315 | } 316 | 317 | lat_plt = lattice::levelplot(spix_labels, 318 | xlab = NULL, 319 | ylab = NULL, 320 | cuts = LEN_UNQ - 1, 321 | col.regions = do.call(color_palette, list(n = LEN_UNQ)), # use a color-palette with default parameters 322 | useRaster = TRUE) # for the 'lattice::levelplot()' see: https://stackoverflow.com/a/15188726/8302386 + using 'useRaster' the plot is returned faster 323 | 324 | if (!is.null(parameter_list_png)) { 325 | print(lat_plt) 326 | grDevices::dev.off() 327 | } 328 | 329 | return(lat_plt) 330 | } 331 | 332 | ), 333 | 334 | private = list( 335 | 336 | lst_obj = NULL, 337 | masks_flag = F, 338 | 339 | #..................................... 340 | # divisors calculation 341 | # https://stackoverflow.com/a/19465753 342 | #..................................... 343 | 344 | divisors = function(x){ 345 | y <- seq_len(x) 346 | y[ x%%y == 0 ] 347 | }, 348 | 349 | 350 | #................................................................................. 351 | # calculate the grid-rows and grid-cols (for specific number of sub-matrices < 53) 352 | #................................................................................. 353 | 354 | calc_grid_rows_cols = function(num_masks) { 355 | 356 | if (length(num_masks) > 52) stop("Plotting of grid of images using the 'calc_grid_rows_cols' function is restricted to 52 objects!", call. = F) 357 | 358 | rows = sqrt(num_masks) 359 | cols = 0 360 | DIVS = private$divisors(num_masks) 361 | LEN = length(DIVS) 362 | if (LEN > 2 && (num_masks %% rows) != 0) { 363 | rows = DIVS[LEN/2] 364 | cols = DIVS[(LEN/2)+1] 365 | } 366 | else { 367 | if ((num_masks %% rows) == 0) { # square int's 368 | cols = rows 369 | } 370 | else { 371 | if ((num_masks %% rows) == 0) { 372 | rows = round(rows) 373 | cols = num_masks / rows 374 | } 375 | else { 376 | rows = floor(rows) 377 | cols = ceiling(num_masks / rows) 378 | } 379 | } 380 | if (((rows * cols) - num_masks) > 1) { # neither square nor divisors [ primes ] 381 | DIF = ((rows * cols) - num_masks) 382 | rows = rows - DIF + 1 383 | if ((num_masks %% rows) == 0) { 384 | cols = rows 385 | } 386 | else { 387 | if ((num_masks %% rows) == 0) { 388 | rows = round(rows) 389 | cols = num_masks / rows 390 | } 391 | else { 392 | rows = floor(rows) 393 | cols = ceiling(num_masks / rows) 394 | } 395 | } 396 | } 397 | } 398 | return(list(rows = rows, cols = cols)) 399 | }, 400 | 401 | 402 | #.......................................... 403 | # elapsed time in hours & minutes & seconds 404 | #.......................................... 405 | 406 | elapsed_time = function(secs) { 407 | tmp_hours = as.integer((secs / 60) / 60) 408 | tmp_hours_minutes = (secs / 60) %% 60 409 | tmp_seconds = secs %% 60 410 | res_out = paste(c("Elapsed time: ", tmp_hours, " hours and ", as.integer(tmp_hours_minutes), " minutes and ", as.integer(tmp_seconds), " seconds."), collapse = "") 411 | return(res_out) 412 | } 413 | ) 414 | ) 415 | --------------------------------------------------------------------------------