diff --git a/renv.lock b/renv.lock new file mode 100644 index 0000000000000000000000000000000000000000..3cfba30ad40b9241ba57b7bb18d953397a1d49b6 --- /dev/null +++ b/renv.lock @@ -0,0 +1,1359 @@ +{ + "R": { + "Version": "4.4.1", + "Repositories": [ + { + "Name": "CRAN", + "URL": "https://cloud.r-project.org" + } + ] + }, + "Packages": { + "DBI": { + "Package": "DBI", + "Version": "1.2.3", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "methods" + ], + "Hash": "065ae649b05f1ff66bb0c793107508f5" + }, + "KernSmooth": { + "Package": "KernSmooth", + "Version": "2.23-24", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "stats" + ], + "Hash": "9f33a1ee37bbe8919eb2ec4b9f2473a5" + }, + "MASS": { + "Package": "MASS", + "Version": "7.3-60.2", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "grDevices", + "graphics", + "methods", + "stats", + "utils" + ], + "Hash": "2f342c46163b0b54d7b64d1f798e2c78" + }, + "Matrix": { + "Package": "Matrix", + "Version": "1.7-0", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "grDevices", + "graphics", + "grid", + "lattice", + "methods", + "stats", + "utils" + ], + "Hash": "1920b2f11133b12350024297d8a4ff4a" + }, + "Momocs": { + "Package": "Momocs", + "Version": "1.4.1", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "MASS", + "R", + "RColorBrewer", + "cluster", + "dendextend", + "dplyr", + "geometry", + "geomorph", + "ggplot2", + "grDevices", + "graphics", + "jpeg", + "magrittr", + "progress", + "sf", + "sp", + "tibble", + "utils", + "vegan" + ], + "Hash": "f24c88b653f3cabdabe158e0b4da49ba" + }, + "R6": { + "Package": "R6", + "Version": "2.5.1", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R" + ], + "Hash": "470851b6d5d0ac559e9d01bb352b4021" + }, + "RColorBrewer": { + "Package": "RColorBrewer", + "Version": "1.1-3", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R" + ], + "Hash": "45f0398006e83a5b10b72a90663d8d8c" + }, + "RRPP": { + "Package": "RRPP", + "Version": "2.0.3", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "Matrix", + "R", + "ape", + "ggplot2", + "parallel" + ], + "Hash": "83f8ff73aa9c5790c4d81879305caf16" + }, + "Rcpp": { + "Package": "Rcpp", + "Version": "1.0.12", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "methods", + "utils" + ], + "Hash": "5ea2700d21e038ace58269ecdbeb9ec0" + }, + "RcppProgress": { + "Package": "RcppProgress", + "Version": "0.4.2", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "1c0aa18b97e6aaa17f93b8b866c0ace5" + }, + "abind": { + "Package": "abind", + "Version": "1.4-5", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "methods", + "utils" + ], + "Hash": "4f57884290cc75ab22f4af9e9d4ca862" + }, + "ape": { + "Package": "ape", + "Version": "5.8", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "Rcpp", + "digest", + "graphics", + "lattice", + "methods", + "nlme", + "parallel", + "stats", + "utils" + ], + "Hash": "16b5ff4dff0ead9ea955f62f794b1535" + }, + "base64enc": { + "Package": "base64enc", + "Version": "0.1-3", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R" + ], + "Hash": "543776ae6848fde2f48ff3816d0628bc" + }, + "bmp": { + "Package": "bmp", + "Version": "0.3", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "c71b569d923cdc420491907928595aca" + }, + "bslib": { + "Package": "bslib", + "Version": "0.7.0", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "base64enc", + "cachem", + "fastmap", + "grDevices", + "htmltools", + "jquerylib", + "jsonlite", + "lifecycle", + "memoise", + "mime", + "rlang", + "sass" + ], + "Hash": "8644cc53f43828f19133548195d7e59e" + }, + "cachem": { + "Package": "cachem", + "Version": "1.1.0", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "fastmap", + "rlang" + ], + "Hash": "cd9a672193789068eb5a2aad65a0dedf" + }, + "class": { + "Package": "class", + "Version": "7.3-22", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "MASS", + "R", + "stats", + "utils" + ], + "Hash": "f91f6b29f38b8c280f2b9477787d4bb2" + }, + "classInt": { + "Package": "classInt", + "Version": "0.4-10", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "KernSmooth", + "R", + "class", + "e1071", + "grDevices", + "graphics", + "stats" + ], + "Hash": "f5a40793b1ae463a7ffb3902a95bf864" + }, + "cli": { + "Package": "cli", + "Version": "3.6.3", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "utils" + ], + "Hash": "b21916dd77a27642b447374a5d30ecf3" + }, + "cluster": { + "Package": "cluster", + "Version": "2.1.6", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "grDevices", + "graphics", + "stats", + "utils" + ], + "Hash": "0aaa05204035dc43ea0004b9c76611dd" + }, + "colorspace": { + "Package": "colorspace", + "Version": "2.1-0", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "grDevices", + "graphics", + "methods", + "stats" + ], + "Hash": "f20c47fd52fae58b4e377c37bb8c335b" + }, + "cpp11": { + "Package": "cpp11", + "Version": "0.4.7", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R" + ], + "Hash": "5a295d7d963cc5035284dcdbaf334f4e" + }, + "crayon": { + "Package": "crayon", + "Version": "1.5.3", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "grDevices", + "methods", + "utils" + ], + "Hash": "859d96e65ef198fd43e82b9628d593ef" + }, + "dendextend": { + "Package": "dendextend", + "Version": "1.17.1", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "datasets", + "ggplot2", + "magrittr", + "stats", + "utils", + "viridis" + ], + "Hash": "043fafb791081fc553f29021bd0a9a01" + }, + "digest": { + "Package": "digest", + "Version": "0.6.35", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "utils" + ], + "Hash": "698ece7ba5a4fa4559e3d537e7ec3d31" + }, + "downloader": { + "Package": "downloader", + "Version": "0.4", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "digest", + "utils" + ], + "Hash": "f4f2a915e0dedbdf016a83b63477349f" + }, + "dplyr": { + "Package": "dplyr", + "Version": "1.1.4", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "R6", + "cli", + "generics", + "glue", + "lifecycle", + "magrittr", + "methods", + "pillar", + "rlang", + "tibble", + "tidyselect", + "utils", + "vctrs" + ], + "Hash": "fedd9d00c2944ff00a0e2696ccf048ec" + }, + "e1071": { + "Package": "e1071", + "Version": "1.7-14", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "class", + "grDevices", + "graphics", + "methods", + "proxy", + "stats", + "utils" + ], + "Hash": "4ef372b716824753719a8a38b258442d" + }, + "evaluate": { + "Package": "evaluate", + "Version": "0.24.0", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "methods" + ], + "Hash": "a1066cbc05caee9a4bf6d90f194ff4da" + }, + "fansi": { + "Package": "fansi", + "Version": "1.0.6", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "grDevices", + "utils" + ], + "Hash": "962174cf2aeb5b9eea581522286a911f" + }, + "farver": { + "Package": "farver", + "Version": "2.1.2", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "680887028577f3fa2a81e410ed0d6e42" + }, + "fastmap": { + "Package": "fastmap", + "Version": "1.2.0", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "aa5e1cd11c2d15497494c5292d7ffcc8" + }, + "fontawesome": { + "Package": "fontawesome", + "Version": "0.5.2", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "htmltools", + "rlang" + ], + "Hash": "c2efdd5f0bcd1ea861c2d4e2a883a67d" + }, + "fs": { + "Package": "fs", + "Version": "1.6.4", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "methods" + ], + "Hash": "15aeb8c27f5ea5161f9f6a641fafd93a" + }, + "generics": { + "Package": "generics", + "Version": "0.1.3", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "methods" + ], + "Hash": "15e9634c0fcd294799e9b2e929ed1b86" + }, + "geometry": { + "Package": "geometry", + "Version": "0.4.7", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "Rcpp", + "RcppProgress", + "linprog", + "lpSolve", + "magic" + ], + "Hash": "8e5ba8a115dee2730bab618934db4b85" + }, + "geomorph": { + "Package": "geomorph", + "Version": "4.0.8", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "Matrix", + "R", + "RRPP", + "ape", + "ggplot2", + "grDevices", + "graphics", + "jpeg", + "parallel", + "rgl", + "stats", + "utils" + ], + "Hash": "e2fff8d6687edcac3bc86979ed4da3b4" + }, + "ggplot2": { + "Package": "ggplot2", + "Version": "3.5.1", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "MASS", + "R", + "cli", + "glue", + "grDevices", + "grid", + "gtable", + "isoband", + "lifecycle", + "mgcv", + "rlang", + "scales", + "stats", + "tibble", + "vctrs", + "withr" + ], + "Hash": "44c6a2f8202d5b7e878ea274b1092426" + }, + "glue": { + "Package": "glue", + "Version": "1.7.0", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "methods" + ], + "Hash": "e0b3a53876554bd45879e596cdb10a52" + }, + "gridExtra": { + "Package": "gridExtra", + "Version": "2.3", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "grDevices", + "graphics", + "grid", + "gtable", + "utils" + ], + "Hash": "7d7f283939f563670a697165b2cf5560" + }, + "gtable": { + "Package": "gtable", + "Version": "0.3.5", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "cli", + "glue", + "grid", + "lifecycle", + "rlang" + ], + "Hash": "e18861963cbc65a27736e02b3cd3c4a0" + }, + "highr": { + "Package": "highr", + "Version": "0.11", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "xfun" + ], + "Hash": "d65ba49117ca223614f71b60d85b8ab7" + }, + "hms": { + "Package": "hms", + "Version": "1.1.3", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "lifecycle", + "methods", + "pkgconfig", + "rlang", + "vctrs" + ], + "Hash": "b59377caa7ed00fa41808342002138f9" + }, + "htmltools": { + "Package": "htmltools", + "Version": "0.5.8.1", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "base64enc", + "digest", + "fastmap", + "grDevices", + "rlang", + "utils" + ], + "Hash": "81d371a9cc60640e74e4ab6ac46dcedc" + }, + "htmlwidgets": { + "Package": "htmlwidgets", + "Version": "1.6.4", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "grDevices", + "htmltools", + "jsonlite", + "knitr", + "rmarkdown", + "yaml" + ], + "Hash": "04291cc45198225444a397606810ac37" + }, + "igraph": { + "Package": "igraph", + "Version": "2.0.3", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "Matrix", + "R", + "cli", + "cpp11", + "grDevices", + "graphics", + "lifecycle", + "magrittr", + "methods", + "pkgconfig", + "rlang", + "stats", + "utils", + "vctrs" + ], + "Hash": "c3b7d801d722e26e4cd888e042bf9af5" + }, + "imager": { + "Package": "imager", + "Version": "1.0.2", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "Rcpp", + "downloader", + "grDevices", + "igraph", + "jpeg", + "magrittr", + "methods", + "png", + "purrr", + "readbitmap", + "stringr" + ], + "Hash": "cd7b885ad219b89e96523c1205670ccd" + }, + "import": { + "Package": "import", + "Version": "1.3.2", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "e9b8f8b1861b31fd11b4b98d818050c7" + }, + "isoband": { + "Package": "isoband", + "Version": "0.2.7", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "grid", + "utils" + ], + "Hash": "0080607b4a1a7b28979aecef976d8bc2" + }, + "jpeg": { + "Package": "jpeg", + "Version": "0.1-10", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R" + ], + "Hash": "031a0b683d001a7519202f0628fc0358" + }, + "jquerylib": { + "Package": "jquerylib", + "Version": "0.1.4", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "htmltools" + ], + "Hash": "5aab57a3bd297eee1c1d862735972182" + }, + "jsonlite": { + "Package": "jsonlite", + "Version": "1.8.8", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "methods" + ], + "Hash": "e1b9c55281c5adc4dd113652d9e26768" + }, + "knitr": { + "Package": "knitr", + "Version": "1.47", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "evaluate", + "highr", + "methods", + "tools", + "xfun", + "yaml" + ], + "Hash": "7c99b2d55584b982717fcc0950378612" + }, + "labeling": { + "Package": "labeling", + "Version": "0.4.3", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "graphics", + "stats" + ], + "Hash": "b64ec208ac5bc1852b285f665d6368b3" + }, + "lattice": { + "Package": "lattice", + "Version": "0.22-6", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "grDevices", + "graphics", + "grid", + "stats", + "utils" + ], + "Hash": "cc5ac1ba4c238c7ca9fa6a87ca11a7e2" + }, + "lifecycle": { + "Package": "lifecycle", + "Version": "1.0.4", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "cli", + "glue", + "rlang" + ], + "Hash": "b8552d117e1b808b09a832f589b79035" + }, + "linprog": { + "Package": "linprog", + "Version": "0.9-4", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "lpSolve" + ], + "Hash": "66e9d4ebd71ddcd6f86a2a9a34f5cdc5" + }, + "lpSolve": { + "Package": "lpSolve", + "Version": "5.6.20", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "2801c8082e89ed84cc0dbe43de850d31" + }, + "magic": { + "Package": "magic", + "Version": "1.6-1", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "abind" + ], + "Hash": "1da6217cea8a3ef496258819b80770e1" + }, + "magrittr": { + "Package": "magrittr", + "Version": "2.0.3", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R" + ], + "Hash": "7ce2733a9826b3aeb1775d56fd305472" + }, + "mclust": { + "Package": "mclust", + "Version": "6.1.1", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "grDevices", + "graphics", + "stats", + "utils" + ], + "Hash": "aa9cfd45e2c3297213e270d000d80655" + }, + "memoise": { + "Package": "memoise", + "Version": "2.0.1", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "cachem", + "rlang" + ], + "Hash": "e2817ccf4a065c5d9d7f2cfbe7c1d78c" + }, + "mgcv": { + "Package": "mgcv", + "Version": "1.9-1", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "Matrix", + "R", + "graphics", + "methods", + "nlme", + "splines", + "stats", + "utils" + ], + "Hash": "110ee9d83b496279960e162ac97764ce" + }, + "mime": { + "Package": "mime", + "Version": "0.12", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "tools" + ], + "Hash": "18e9c28c1d3ca1560ce30658b22ce104" + }, + "munsell": { + "Package": "munsell", + "Version": "0.5.1", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "colorspace", + "methods" + ], + "Hash": "4fd8900853b746af55b81fda99da7695" + }, + "nlme": { + "Package": "nlme", + "Version": "3.1-164", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "graphics", + "lattice", + "stats", + "utils" + ], + "Hash": "a623a2239e642806158bc4dc3f51565d" + }, + "permute": { + "Package": "permute", + "Version": "0.9-7", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "stats" + ], + "Hash": "abf0ca85c1c752e0d04f46334e635046" + }, + "pillar": { + "Package": "pillar", + "Version": "1.9.0", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "cli", + "fansi", + "glue", + "lifecycle", + "rlang", + "utf8", + "utils", + "vctrs" + ], + "Hash": "15da5a8412f317beeee6175fbc76f4bb" + }, + "pkgconfig": { + "Package": "pkgconfig", + "Version": "2.0.3", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "utils" + ], + "Hash": "01f28d4278f15c76cddbea05899c5d6f" + }, + "png": { + "Package": "png", + "Version": "0.1-8", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R" + ], + "Hash": "bd54ba8a0a5faded999a7aab6e46b374" + }, + "prettyunits": { + "Package": "prettyunits", + "Version": "1.2.0", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R" + ], + "Hash": "6b01fc98b1e86c4f705ce9dcfd2f57c7" + }, + "progress": { + "Package": "progress", + "Version": "1.2.3", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "R6", + "crayon", + "hms", + "prettyunits" + ], + "Hash": "f4625e061cb2865f111b47ff163a5ca6" + }, + "proxy": { + "Package": "proxy", + "Version": "0.4-27", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "stats", + "utils" + ], + "Hash": "e0ef355c12942cf7a6b91a6cfaea8b3e" + }, + "purrr": { + "Package": "purrr", + "Version": "1.0.2", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "cli", + "lifecycle", + "magrittr", + "rlang", + "vctrs" + ], + "Hash": "1cba04a4e9414bdefc9dcaa99649a8dc" + }, + "rappdirs": { + "Package": "rappdirs", + "Version": "0.3.3", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R" + ], + "Hash": "5e3c5dc0b071b21fa128676560dbe94d" + }, + "readbitmap": { + "Package": "readbitmap", + "Version": "0.1.5", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "bmp", + "jpeg", + "png", + "tiff" + ], + "Hash": "ec19e2be05f06df441857070a456dbe5" + }, + "renv": { + "Package": "renv", + "Version": "1.0.7", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "utils" + ], + "Hash": "397b7b2a265bc5a7a06852524dabae20" + }, + "rgl": { + "Package": "rgl", + "Version": "1.3.1", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "R6", + "base64enc", + "grDevices", + "graphics", + "htmltools", + "htmlwidgets", + "jsonlite", + "knitr", + "magrittr", + "mime", + "stats", + "utils" + ], + "Hash": "54f8dcdef54a2c7737c0eec27d394dfd" + }, + "rlang": { + "Package": "rlang", + "Version": "1.1.4", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "utils" + ], + "Hash": "3eec01f8b1dee337674b2e34ab1f9bc1" + }, + "rmarkdown": { + "Package": "rmarkdown", + "Version": "2.27", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "bslib", + "evaluate", + "fontawesome", + "htmltools", + "jquerylib", + "jsonlite", + "knitr", + "methods", + "tinytex", + "tools", + "utils", + "xfun", + "yaml" + ], + "Hash": "27f9502e1cdbfa195f94e03b0f517484" + }, + "s2": { + "Package": "s2", + "Version": "1.1.6", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "Rcpp", + "wk" + ], + "Hash": "32f7b1a15bb01ae809022960abad5363" + }, + "sass": { + "Package": "sass", + "Version": "0.4.9", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R6", + "fs", + "htmltools", + "rappdirs", + "rlang" + ], + "Hash": "d53dbfddf695303ea4ad66f86e99b95d" + }, + "scales": { + "Package": "scales", + "Version": "1.3.0", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "R6", + "RColorBrewer", + "cli", + "farver", + "glue", + "labeling", + "lifecycle", + "munsell", + "rlang", + "viridisLite" + ], + "Hash": "c19df082ba346b0ffa6f833e92de34d1" + }, + "sf": { + "Package": "sf", + "Version": "1.0-16", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "DBI", + "R", + "Rcpp", + "classInt", + "grDevices", + "graphics", + "grid", + "magrittr", + "methods", + "s2", + "stats", + "tools", + "units", + "utils" + ], + "Hash": "ad57b543f7c3fca05213ba78ff63df9b" + }, + "sp": { + "Package": "sp", + "Version": "2.1-4", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "grDevices", + "graphics", + "grid", + "lattice", + "methods", + "stats", + "utils" + ], + "Hash": "75940133cca2e339afce15a586f85b11" + }, + "stringi": { + "Package": "stringi", + "Version": "1.8.4", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "stats", + "tools", + "utils" + ], + "Hash": "39e1144fd75428983dc3f63aa53dfa91" + }, + "stringr": { + "Package": "stringr", + "Version": "1.5.1", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "cli", + "glue", + "lifecycle", + "magrittr", + "rlang", + "stringi", + "vctrs" + ], + "Hash": "960e2ae9e09656611e0b8214ad543207" + }, + "tibble": { + "Package": "tibble", + "Version": "3.2.1", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "fansi", + "lifecycle", + "magrittr", + "methods", + "pillar", + "pkgconfig", + "rlang", + "utils", + "vctrs" + ], + "Hash": "a84e2cc86d07289b3b6f5069df7a004c" + }, + "tidyselect": { + "Package": "tidyselect", + "Version": "1.2.1", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "cli", + "glue", + "lifecycle", + "rlang", + "vctrs", + "withr" + ], + "Hash": "829f27b9c4919c16b593794a6344d6c0" + }, + "tiff": { + "Package": "tiff", + "Version": "0.1-12", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R" + ], + "Hash": "74b5f7f00e466398fdc0af8889add582" + }, + "tinytex": { + "Package": "tinytex", + "Version": "0.51", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "xfun" + ], + "Hash": "d44e2fcd2e4e076f0aac540208559d1d" + }, + "units": { + "Package": "units", + "Version": "0.8-5", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "Rcpp" + ], + "Hash": "119d19da480e873f72241ff6962ffd83" + }, + "utf8": { + "Package": "utf8", + "Version": "1.2.4", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R" + ], + "Hash": "62b65c52671e6665f803ff02954446e9" + }, + "vctrs": { + "Package": "vctrs", + "Version": "0.6.5", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "cli", + "glue", + "lifecycle", + "rlang" + ], + "Hash": "c03fa420630029418f7e6da3667aac4a" + }, + "vegan": { + "Package": "vegan", + "Version": "2.6-6.1", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "MASS", + "R", + "cluster", + "lattice", + "mgcv", + "permute" + ], + "Hash": "46a520e3fd3286168c3e4dc5e6fbb5b1" + }, + "viridis": { + "Package": "viridis", + "Version": "0.6.5", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "ggplot2", + "gridExtra", + "viridisLite" + ], + "Hash": "acd96d9fa70adeea4a5a1150609b9745" + }, + "viridisLite": { + "Package": "viridisLite", + "Version": "0.4.2", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R" + ], + "Hash": "c826c7c4241b6fc89ff55aaea3fa7491" + }, + "withr": { + "Package": "withr", + "Version": "3.0.0", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "grDevices", + "graphics" + ], + "Hash": "d31b6c62c10dcf11ec530ca6b0dd5d35" + }, + "wk": { + "Package": "wk", + "Version": "0.9.1", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R" + ], + "Hash": "5d4545e140e36476f35f20d0ca87963e" + }, + "xfun": { + "Package": "xfun", + "Version": "0.45", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "grDevices", + "stats", + "tools" + ], + "Hash": "ca59c87fe305b16a9141a5874c3a7889" + }, + "yaml": { + "Package": "yaml", + "Version": "2.3.8", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "29240487a071f535f5e5d5a323b7afbd" + } + } +} diff --git a/renv/.gitignore b/renv/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..0ec0cbba2d7755cbd91956afaa585449906a1e16 --- /dev/null +++ b/renv/.gitignore @@ -0,0 +1,7 @@ +library/ +local/ +cellar/ +lock/ +python/ +sandbox/ +staging/ diff --git a/renv/activate.R b/renv/activate.R new file mode 100644 index 0000000000000000000000000000000000000000..d13f9932a16a92986a514d8cb439662d85db3add --- /dev/null +++ b/renv/activate.R @@ -0,0 +1,1220 @@ + +local({ + + # the requested version of renv + version <- "1.0.7" + attr(version, "sha") <- NULL + + # the project directory + project <- Sys.getenv("RENV_PROJECT") + if (!nzchar(project)) + project <- getwd() + + # use start-up diagnostics if enabled + diagnostics <- Sys.getenv("RENV_STARTUP_DIAGNOSTICS", unset = "FALSE") + if (diagnostics) { + start <- Sys.time() + profile <- tempfile("renv-startup-", fileext = ".Rprof") + utils::Rprof(profile) + on.exit({ + utils::Rprof(NULL) + elapsed <- signif(difftime(Sys.time(), start, units = "auto"), digits = 2L) + writeLines(sprintf("- renv took %s to run the autoloader.", format(elapsed))) + writeLines(sprintf("- Profile: %s", profile)) + print(utils::summaryRprof(profile)) + }, add = TRUE) + } + + # figure out whether the autoloader is enabled + enabled <- local({ + + # first, check config option + override <- getOption("renv.config.autoloader.enabled") + if (!is.null(override)) + return(override) + + # if we're being run in a context where R_LIBS is already set, + # don't load -- presumably we're being run as a sub-process and + # the parent process has already set up library paths for us + rcmd <- Sys.getenv("R_CMD", unset = NA) + rlibs <- Sys.getenv("R_LIBS", unset = NA) + if (!is.na(rlibs) && !is.na(rcmd)) + return(FALSE) + + # next, check environment variables + # TODO: prefer using the configuration one in the future + envvars <- c( + "RENV_CONFIG_AUTOLOADER_ENABLED", + "RENV_AUTOLOADER_ENABLED", + "RENV_ACTIVATE_PROJECT" + ) + + for (envvar in envvars) { + envval <- Sys.getenv(envvar, unset = NA) + if (!is.na(envval)) + return(tolower(envval) %in% c("true", "t", "1")) + } + + # enable by default + TRUE + + }) + + # bail if we're not enabled + if (!enabled) { + + # if we're not enabled, we might still need to manually load + # the user profile here + profile <- Sys.getenv("R_PROFILE_USER", unset = "~/.Rprofile") + if (file.exists(profile)) { + cfg <- Sys.getenv("RENV_CONFIG_USER_PROFILE", unset = "TRUE") + if (tolower(cfg) %in% c("true", "t", "1")) + sys.source(profile, envir = globalenv()) + } + + return(FALSE) + + } + + # avoid recursion + if (identical(getOption("renv.autoloader.running"), TRUE)) { + warning("ignoring recursive attempt to run renv autoloader") + return(invisible(TRUE)) + } + + # signal that we're loading renv during R startup + options(renv.autoloader.running = TRUE) + on.exit(options(renv.autoloader.running = NULL), add = TRUE) + + # signal that we've consented to use renv + options(renv.consent = TRUE) + + # load the 'utils' package eagerly -- this ensures that renv shims, which + # mask 'utils' packages, will come first on the search path + library(utils, lib.loc = .Library) + + # unload renv if it's already been loaded + if ("renv" %in% loadedNamespaces()) + unloadNamespace("renv") + + # load bootstrap tools + `%||%` <- function(x, y) { + if (is.null(x)) y else x + } + + catf <- function(fmt, ..., appendLF = TRUE) { + + quiet <- getOption("renv.bootstrap.quiet", default = FALSE) + if (quiet) + return(invisible()) + + msg <- sprintf(fmt, ...) + cat(msg, file = stdout(), sep = if (appendLF) "\n" else "") + + invisible(msg) + + } + + header <- function(label, + ..., + prefix = "#", + suffix = "-", + n = min(getOption("width"), 78)) + { + label <- sprintf(label, ...) + n <- max(n - nchar(label) - nchar(prefix) - 2L, 8L) + if (n <= 0) + return(paste(prefix, label)) + + tail <- paste(rep.int(suffix, n), collapse = "") + paste0(prefix, " ", label, " ", tail) + + } + + heredoc <- function(text, leave = 0) { + + # remove leading, trailing whitespace + trimmed <- gsub("^\\s*\\n|\\n\\s*$", "", text) + + # split into lines + lines <- strsplit(trimmed, "\n", fixed = TRUE)[[1L]] + + # compute common indent + indent <- regexpr("[^[:space:]]", lines) + common <- min(setdiff(indent, -1L)) - leave + paste(substring(lines, common), collapse = "\n") + + } + + startswith <- function(string, prefix) { + substring(string, 1, nchar(prefix)) == prefix + } + + bootstrap <- function(version, library) { + + friendly <- renv_bootstrap_version_friendly(version) + section <- header(sprintf("Bootstrapping renv %s", friendly)) + catf(section) + + # attempt to download renv + catf("- Downloading renv ... ", appendLF = FALSE) + withCallingHandlers( + tarball <- renv_bootstrap_download(version), + error = function(err) { + catf("FAILED") + stop("failed to download:\n", conditionMessage(err)) + } + ) + catf("OK") + on.exit(unlink(tarball), add = TRUE) + + # now attempt to install + catf("- Installing renv ... ", appendLF = FALSE) + withCallingHandlers( + status <- renv_bootstrap_install(version, tarball, library), + error = function(err) { + catf("FAILED") + stop("failed to install:\n", conditionMessage(err)) + } + ) + catf("OK") + + # add empty line to break up bootstrapping from normal output + catf("") + + return(invisible()) + } + + renv_bootstrap_tests_running <- function() { + getOption("renv.tests.running", default = FALSE) + } + + renv_bootstrap_repos <- function() { + + # get CRAN repository + cran <- getOption("renv.repos.cran", "https://cloud.r-project.org") + + # check for repos override + repos <- Sys.getenv("RENV_CONFIG_REPOS_OVERRIDE", unset = NA) + if (!is.na(repos)) { + + # check for RSPM; if set, use a fallback repository for renv + rspm <- Sys.getenv("RSPM", unset = NA) + if (identical(rspm, repos)) + repos <- c(RSPM = rspm, CRAN = cran) + + return(repos) + + } + + # check for lockfile repositories + repos <- tryCatch(renv_bootstrap_repos_lockfile(), error = identity) + if (!inherits(repos, "error") && length(repos)) + return(repos) + + # retrieve current repos + repos <- getOption("repos") + + # ensure @CRAN@ entries are resolved + repos[repos == "@CRAN@"] <- cran + + # add in renv.bootstrap.repos if set + default <- c(FALLBACK = "https://cloud.r-project.org") + extra <- getOption("renv.bootstrap.repos", default = default) + repos <- c(repos, extra) + + # remove duplicates that might've snuck in + dupes <- duplicated(repos) | duplicated(names(repos)) + repos[!dupes] + + } + + renv_bootstrap_repos_lockfile <- function() { + + lockpath <- Sys.getenv("RENV_PATHS_LOCKFILE", unset = "renv.lock") + if (!file.exists(lockpath)) + return(NULL) + + lockfile <- tryCatch(renv_json_read(lockpath), error = identity) + if (inherits(lockfile, "error")) { + warning(lockfile) + return(NULL) + } + + repos <- lockfile$R$Repositories + if (length(repos) == 0) + return(NULL) + + keys <- vapply(repos, `[[`, "Name", FUN.VALUE = character(1)) + vals <- vapply(repos, `[[`, "URL", FUN.VALUE = character(1)) + names(vals) <- keys + + return(vals) + + } + + renv_bootstrap_download <- function(version) { + + sha <- attr(version, "sha", exact = TRUE) + + methods <- if (!is.null(sha)) { + + # attempting to bootstrap a development version of renv + c( + function() renv_bootstrap_download_tarball(sha), + function() renv_bootstrap_download_github(sha) + ) + + } else { + + # attempting to bootstrap a release version of renv + c( + function() renv_bootstrap_download_tarball(version), + function() renv_bootstrap_download_cran_latest(version), + function() renv_bootstrap_download_cran_archive(version) + ) + + } + + for (method in methods) { + path <- tryCatch(method(), error = identity) + if (is.character(path) && file.exists(path)) + return(path) + } + + stop("All download methods failed") + + } + + renv_bootstrap_download_impl <- function(url, destfile) { + + mode <- "wb" + + # https://bugs.r-project.org/bugzilla/show_bug.cgi?id=17715 + fixup <- + Sys.info()[["sysname"]] == "Windows" && + substring(url, 1L, 5L) == "file:" + + if (fixup) + mode <- "w+b" + + args <- list( + url = url, + destfile = destfile, + mode = mode, + quiet = TRUE + ) + + if ("headers" %in% names(formals(utils::download.file))) + args$headers <- renv_bootstrap_download_custom_headers(url) + + do.call(utils::download.file, args) + + } + + renv_bootstrap_download_custom_headers <- function(url) { + + headers <- getOption("renv.download.headers") + if (is.null(headers)) + return(character()) + + if (!is.function(headers)) + stopf("'renv.download.headers' is not a function") + + headers <- headers(url) + if (length(headers) == 0L) + return(character()) + + if (is.list(headers)) + headers <- unlist(headers, recursive = FALSE, use.names = TRUE) + + ok <- + is.character(headers) && + is.character(names(headers)) && + all(nzchar(names(headers))) + + if (!ok) + stop("invocation of 'renv.download.headers' did not return a named character vector") + + headers + + } + + renv_bootstrap_download_cran_latest <- function(version) { + + spec <- renv_bootstrap_download_cran_latest_find(version) + type <- spec$type + repos <- spec$repos + + baseurl <- utils::contrib.url(repos = repos, type = type) + ext <- if (identical(type, "source")) + ".tar.gz" + else if (Sys.info()[["sysname"]] == "Windows") + ".zip" + else + ".tgz" + name <- sprintf("renv_%s%s", version, ext) + url <- paste(baseurl, name, sep = "/") + + destfile <- file.path(tempdir(), name) + status <- tryCatch( + renv_bootstrap_download_impl(url, destfile), + condition = identity + ) + + if (inherits(status, "condition")) + return(FALSE) + + # report success and return + destfile + + } + + renv_bootstrap_download_cran_latest_find <- function(version) { + + # check whether binaries are supported on this system + binary <- + getOption("renv.bootstrap.binary", default = TRUE) && + !identical(.Platform$pkgType, "source") && + !identical(getOption("pkgType"), "source") && + Sys.info()[["sysname"]] %in% c("Darwin", "Windows") + + types <- c(if (binary) "binary", "source") + + # iterate over types + repositories + for (type in types) { + for (repos in renv_bootstrap_repos()) { + + # retrieve package database + db <- tryCatch( + as.data.frame( + utils::available.packages(type = type, repos = repos), + stringsAsFactors = FALSE + ), + error = identity + ) + + if (inherits(db, "error")) + next + + # check for compatible entry + entry <- db[db$Package %in% "renv" & db$Version %in% version, ] + if (nrow(entry) == 0) + next + + # found it; return spec to caller + spec <- list(entry = entry, type = type, repos = repos) + return(spec) + + } + } + + # if we got here, we failed to find renv + fmt <- "renv %s is not available from your declared package repositories" + stop(sprintf(fmt, version)) + + } + + renv_bootstrap_download_cran_archive <- function(version) { + + name <- sprintf("renv_%s.tar.gz", version) + repos <- renv_bootstrap_repos() + urls <- file.path(repos, "src/contrib/Archive/renv", name) + destfile <- file.path(tempdir(), name) + + for (url in urls) { + + status <- tryCatch( + renv_bootstrap_download_impl(url, destfile), + condition = identity + ) + + if (identical(status, 0L)) + return(destfile) + + } + + return(FALSE) + + } + + renv_bootstrap_download_tarball <- function(version) { + + # if the user has provided the path to a tarball via + # an environment variable, then use it + tarball <- Sys.getenv("RENV_BOOTSTRAP_TARBALL", unset = NA) + if (is.na(tarball)) + return() + + # allow directories + if (dir.exists(tarball)) { + name <- sprintf("renv_%s.tar.gz", version) + tarball <- file.path(tarball, name) + } + + # bail if it doesn't exist + if (!file.exists(tarball)) { + + # let the user know we weren't able to honour their request + fmt <- "- RENV_BOOTSTRAP_TARBALL is set (%s) but does not exist." + msg <- sprintf(fmt, tarball) + warning(msg) + + # bail + return() + + } + + catf("- Using local tarball '%s'.", tarball) + tarball + + } + + renv_bootstrap_download_github <- function(version) { + + enabled <- Sys.getenv("RENV_BOOTSTRAP_FROM_GITHUB", unset = "TRUE") + if (!identical(enabled, "TRUE")) + return(FALSE) + + # prepare download options + pat <- Sys.getenv("GITHUB_PAT") + if (nzchar(Sys.which("curl")) && nzchar(pat)) { + fmt <- "--location --fail --header \"Authorization: token %s\"" + extra <- sprintf(fmt, pat) + saved <- options("download.file.method", "download.file.extra") + options(download.file.method = "curl", download.file.extra = extra) + on.exit(do.call(base::options, saved), add = TRUE) + } else if (nzchar(Sys.which("wget")) && nzchar(pat)) { + fmt <- "--header=\"Authorization: token %s\"" + extra <- sprintf(fmt, pat) + saved <- options("download.file.method", "download.file.extra") + options(download.file.method = "wget", download.file.extra = extra) + on.exit(do.call(base::options, saved), add = TRUE) + } + + url <- file.path("https://api.github.com/repos/rstudio/renv/tarball", version) + name <- sprintf("renv_%s.tar.gz", version) + destfile <- file.path(tempdir(), name) + + status <- tryCatch( + renv_bootstrap_download_impl(url, destfile), + condition = identity + ) + + if (!identical(status, 0L)) + return(FALSE) + + renv_bootstrap_download_augment(destfile) + + return(destfile) + + } + + # Add Sha to DESCRIPTION. This is stop gap until #890, after which we + # can use renv::install() to fully capture metadata. + renv_bootstrap_download_augment <- function(destfile) { + sha <- renv_bootstrap_git_extract_sha1_tar(destfile) + if (is.null(sha)) { + return() + } + + # Untar + tempdir <- tempfile("renv-github-") + on.exit(unlink(tempdir, recursive = TRUE), add = TRUE) + untar(destfile, exdir = tempdir) + pkgdir <- dir(tempdir, full.names = TRUE)[[1]] + + # Modify description + desc_path <- file.path(pkgdir, "DESCRIPTION") + desc_lines <- readLines(desc_path) + remotes_fields <- c( + "RemoteType: github", + "RemoteHost: api.github.com", + "RemoteRepo: renv", + "RemoteUsername: rstudio", + "RemotePkgRef: rstudio/renv", + paste("RemoteRef: ", sha), + paste("RemoteSha: ", sha) + ) + writeLines(c(desc_lines[desc_lines != ""], remotes_fields), con = desc_path) + + # Re-tar + local({ + old <- setwd(tempdir) + on.exit(setwd(old), add = TRUE) + + tar(destfile, compression = "gzip") + }) + invisible() + } + + # Extract the commit hash from a git archive. Git archives include the SHA1 + # hash as the comment field of the tarball pax extended header + # (see https://www.kernel.org/pub/software/scm/git/docs/git-archive.html) + # For GitHub archives this should be the first header after the default one + # (512 byte) header. + renv_bootstrap_git_extract_sha1_tar <- function(bundle) { + + # open the bundle for reading + # We use gzcon for everything because (from ?gzcon) + # > Reading from a connection which does not supply a 'gzip' magic + # > header is equivalent to reading from the original connection + conn <- gzcon(file(bundle, open = "rb", raw = TRUE)) + on.exit(close(conn)) + + # The default pax header is 512 bytes long and the first pax extended header + # with the comment should be 51 bytes long + # `52 comment=` (11 chars) + 40 byte SHA1 hash + len <- 0x200 + 0x33 + res <- rawToChar(readBin(conn, "raw", n = len)[0x201:len]) + + if (grepl("^52 comment=", res)) { + sub("52 comment=", "", res) + } else { + NULL + } + } + + renv_bootstrap_install <- function(version, tarball, library) { + + # attempt to install it into project library + dir.create(library, showWarnings = FALSE, recursive = TRUE) + output <- renv_bootstrap_install_impl(library, tarball) + + # check for successful install + status <- attr(output, "status") + if (is.null(status) || identical(status, 0L)) + return(status) + + # an error occurred; report it + header <- "installation of renv failed" + lines <- paste(rep.int("=", nchar(header)), collapse = "") + text <- paste(c(header, lines, output), collapse = "\n") + stop(text) + + } + + renv_bootstrap_install_impl <- function(library, tarball) { + + # invoke using system2 so we can capture and report output + bin <- R.home("bin") + exe <- if (Sys.info()[["sysname"]] == "Windows") "R.exe" else "R" + R <- file.path(bin, exe) + + args <- c( + "--vanilla", "CMD", "INSTALL", "--no-multiarch", + "-l", shQuote(path.expand(library)), + shQuote(path.expand(tarball)) + ) + + system2(R, args, stdout = TRUE, stderr = TRUE) + + } + + renv_bootstrap_platform_prefix <- function() { + + # construct version prefix + version <- paste(R.version$major, R.version$minor, sep = ".") + prefix <- paste("R", numeric_version(version)[1, 1:2], sep = "-") + + # include SVN revision for development versions of R + # (to avoid sharing platform-specific artefacts with released versions of R) + devel <- + identical(R.version[["status"]], "Under development (unstable)") || + identical(R.version[["nickname"]], "Unsuffered Consequences") + + if (devel) + prefix <- paste(prefix, R.version[["svn rev"]], sep = "-r") + + # build list of path components + components <- c(prefix, R.version$platform) + + # include prefix if provided by user + prefix <- renv_bootstrap_platform_prefix_impl() + if (!is.na(prefix) && nzchar(prefix)) + components <- c(prefix, components) + + # build prefix + paste(components, collapse = "/") + + } + + renv_bootstrap_platform_prefix_impl <- function() { + + # if an explicit prefix has been supplied, use it + prefix <- Sys.getenv("RENV_PATHS_PREFIX", unset = NA) + if (!is.na(prefix)) + return(prefix) + + # if the user has requested an automatic prefix, generate it + auto <- Sys.getenv("RENV_PATHS_PREFIX_AUTO", unset = NA) + if (is.na(auto) && getRversion() >= "4.4.0") + auto <- "TRUE" + + if (auto %in% c("TRUE", "True", "true", "1")) + return(renv_bootstrap_platform_prefix_auto()) + + # empty string on failure + "" + + } + + renv_bootstrap_platform_prefix_auto <- function() { + + prefix <- tryCatch(renv_bootstrap_platform_os(), error = identity) + if (inherits(prefix, "error") || prefix %in% "unknown") { + + msg <- paste( + "failed to infer current operating system", + "please file a bug report at https://github.com/rstudio/renv/issues", + sep = "; " + ) + + warning(msg) + + } + + prefix + + } + + renv_bootstrap_platform_os <- function() { + + sysinfo <- Sys.info() + sysname <- sysinfo[["sysname"]] + + # handle Windows + macOS up front + if (sysname == "Windows") + return("windows") + else if (sysname == "Darwin") + return("macos") + + # check for os-release files + for (file in c("/etc/os-release", "/usr/lib/os-release")) + if (file.exists(file)) + return(renv_bootstrap_platform_os_via_os_release(file, sysinfo)) + + # check for redhat-release files + if (file.exists("/etc/redhat-release")) + return(renv_bootstrap_platform_os_via_redhat_release()) + + "unknown" + + } + + renv_bootstrap_platform_os_via_os_release <- function(file, sysinfo) { + + # read /etc/os-release + release <- utils::read.table( + file = file, + sep = "=", + quote = c("\"", "'"), + col.names = c("Key", "Value"), + comment.char = "#", + stringsAsFactors = FALSE + ) + + vars <- as.list(release$Value) + names(vars) <- release$Key + + # get os name + os <- tolower(sysinfo[["sysname"]]) + + # read id + id <- "unknown" + for (field in c("ID", "ID_LIKE")) { + if (field %in% names(vars) && nzchar(vars[[field]])) { + id <- vars[[field]] + break + } + } + + # read version + version <- "unknown" + for (field in c("UBUNTU_CODENAME", "VERSION_CODENAME", "VERSION_ID", "BUILD_ID")) { + if (field %in% names(vars) && nzchar(vars[[field]])) { + version <- vars[[field]] + break + } + } + + # join together + paste(c(os, id, version), collapse = "-") + + } + + renv_bootstrap_platform_os_via_redhat_release <- function() { + + # read /etc/redhat-release + contents <- readLines("/etc/redhat-release", warn = FALSE) + + # infer id + id <- if (grepl("centos", contents, ignore.case = TRUE)) + "centos" + else if (grepl("redhat", contents, ignore.case = TRUE)) + "redhat" + else + "unknown" + + # try to find a version component (very hacky) + version <- "unknown" + + parts <- strsplit(contents, "[[:space:]]")[[1L]] + for (part in parts) { + + nv <- tryCatch(numeric_version(part), error = identity) + if (inherits(nv, "error")) + next + + version <- nv[1, 1] + break + + } + + paste(c("linux", id, version), collapse = "-") + + } + + renv_bootstrap_library_root_name <- function(project) { + + # use project name as-is if requested + asis <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT_ASIS", unset = "FALSE") + if (asis) + return(basename(project)) + + # otherwise, disambiguate based on project's path + id <- substring(renv_bootstrap_hash_text(project), 1L, 8L) + paste(basename(project), id, sep = "-") + + } + + renv_bootstrap_library_root <- function(project) { + + prefix <- renv_bootstrap_profile_prefix() + + path <- Sys.getenv("RENV_PATHS_LIBRARY", unset = NA) + if (!is.na(path)) + return(paste(c(path, prefix), collapse = "/")) + + path <- renv_bootstrap_library_root_impl(project) + if (!is.null(path)) { + name <- renv_bootstrap_library_root_name(project) + return(paste(c(path, prefix, name), collapse = "/")) + } + + renv_bootstrap_paths_renv("library", project = project) + + } + + renv_bootstrap_library_root_impl <- function(project) { + + root <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT", unset = NA) + if (!is.na(root)) + return(root) + + type <- renv_bootstrap_project_type(project) + if (identical(type, "package")) { + userdir <- renv_bootstrap_user_dir() + return(file.path(userdir, "library")) + } + + } + + renv_bootstrap_validate_version <- function(version, description = NULL) { + + # resolve description file + # + # avoid passing lib.loc to `packageDescription()` below, since R will + # use the loaded version of the package by default anyhow. note that + # this function should only be called after 'renv' is loaded + # https://github.com/rstudio/renv/issues/1625 + description <- description %||% packageDescription("renv") + + # check whether requested version 'version' matches loaded version of renv + sha <- attr(version, "sha", exact = TRUE) + valid <- if (!is.null(sha)) + renv_bootstrap_validate_version_dev(sha, description) + else + renv_bootstrap_validate_version_release(version, description) + + if (valid) + return(TRUE) + + # the loaded version of renv doesn't match the requested version; + # give the user instructions on how to proceed + dev <- identical(description[["RemoteType"]], "github") + remote <- if (dev) + paste("rstudio/renv", description[["RemoteSha"]], sep = "@") + else + paste("renv", description[["Version"]], sep = "@") + + # display both loaded version + sha if available + friendly <- renv_bootstrap_version_friendly( + version = description[["Version"]], + sha = if (dev) description[["RemoteSha"]] + ) + + fmt <- heredoc(" + renv %1$s was loaded from project library, but this project is configured to use renv %2$s. + - Use `renv::record(\"%3$s\")` to record renv %1$s in the lockfile. + - Use `renv::restore(packages = \"renv\")` to install renv %2$s into the project library. + ") + catf(fmt, friendly, renv_bootstrap_version_friendly(version), remote) + + FALSE + + } + + renv_bootstrap_validate_version_dev <- function(version, description) { + expected <- description[["RemoteSha"]] + is.character(expected) && startswith(expected, version) + } + + renv_bootstrap_validate_version_release <- function(version, description) { + expected <- description[["Version"]] + is.character(expected) && identical(expected, version) + } + + renv_bootstrap_hash_text <- function(text) { + + hashfile <- tempfile("renv-hash-") + on.exit(unlink(hashfile), add = TRUE) + + writeLines(text, con = hashfile) + tools::md5sum(hashfile) + + } + + renv_bootstrap_load <- function(project, libpath, version) { + + # try to load renv from the project library + if (!requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) + return(FALSE) + + # warn if the version of renv loaded does not match + renv_bootstrap_validate_version(version) + + # execute renv load hooks, if any + hooks <- getHook("renv::autoload") + for (hook in hooks) + if (is.function(hook)) + tryCatch(hook(), error = warnify) + + # load the project + renv::load(project) + + TRUE + + } + + renv_bootstrap_profile_load <- function(project) { + + # if RENV_PROFILE is already set, just use that + profile <- Sys.getenv("RENV_PROFILE", unset = NA) + if (!is.na(profile) && nzchar(profile)) + return(profile) + + # check for a profile file (nothing to do if it doesn't exist) + path <- renv_bootstrap_paths_renv("profile", profile = FALSE, project = project) + if (!file.exists(path)) + return(NULL) + + # read the profile, and set it if it exists + contents <- readLines(path, warn = FALSE) + if (length(contents) == 0L) + return(NULL) + + # set RENV_PROFILE + profile <- contents[[1L]] + if (!profile %in% c("", "default")) + Sys.setenv(RENV_PROFILE = profile) + + profile + + } + + renv_bootstrap_profile_prefix <- function() { + profile <- renv_bootstrap_profile_get() + if (!is.null(profile)) + return(file.path("profiles", profile, "renv")) + } + + renv_bootstrap_profile_get <- function() { + profile <- Sys.getenv("RENV_PROFILE", unset = "") + renv_bootstrap_profile_normalize(profile) + } + + renv_bootstrap_profile_set <- function(profile) { + profile <- renv_bootstrap_profile_normalize(profile) + if (is.null(profile)) + Sys.unsetenv("RENV_PROFILE") + else + Sys.setenv(RENV_PROFILE = profile) + } + + renv_bootstrap_profile_normalize <- function(profile) { + + if (is.null(profile) || profile %in% c("", "default")) + return(NULL) + + profile + + } + + renv_bootstrap_path_absolute <- function(path) { + + substr(path, 1L, 1L) %in% c("~", "/", "\\") || ( + substr(path, 1L, 1L) %in% c(letters, LETTERS) && + substr(path, 2L, 3L) %in% c(":/", ":\\") + ) + + } + + renv_bootstrap_paths_renv <- function(..., profile = TRUE, project = NULL) { + renv <- Sys.getenv("RENV_PATHS_RENV", unset = "renv") + root <- if (renv_bootstrap_path_absolute(renv)) NULL else project + prefix <- if (profile) renv_bootstrap_profile_prefix() + components <- c(root, renv, prefix, ...) + paste(components, collapse = "/") + } + + renv_bootstrap_project_type <- function(path) { + + descpath <- file.path(path, "DESCRIPTION") + if (!file.exists(descpath)) + return("unknown") + + desc <- tryCatch( + read.dcf(descpath, all = TRUE), + error = identity + ) + + if (inherits(desc, "error")) + return("unknown") + + type <- desc$Type + if (!is.null(type)) + return(tolower(type)) + + package <- desc$Package + if (!is.null(package)) + return("package") + + "unknown" + + } + + renv_bootstrap_user_dir <- function() { + dir <- renv_bootstrap_user_dir_impl() + path.expand(chartr("\\", "/", dir)) + } + + renv_bootstrap_user_dir_impl <- function() { + + # use local override if set + override <- getOption("renv.userdir.override") + if (!is.null(override)) + return(override) + + # use R_user_dir if available + tools <- asNamespace("tools") + if (is.function(tools$R_user_dir)) + return(tools$R_user_dir("renv", "cache")) + + # try using our own backfill for older versions of R + envvars <- c("R_USER_CACHE_DIR", "XDG_CACHE_HOME") + for (envvar in envvars) { + root <- Sys.getenv(envvar, unset = NA) + if (!is.na(root)) + return(file.path(root, "R/renv")) + } + + # use platform-specific default fallbacks + if (Sys.info()[["sysname"]] == "Windows") + file.path(Sys.getenv("LOCALAPPDATA"), "R/cache/R/renv") + else if (Sys.info()[["sysname"]] == "Darwin") + "~/Library/Caches/org.R-project.R/R/renv" + else + "~/.cache/R/renv" + + } + + renv_bootstrap_version_friendly <- function(version, shafmt = NULL, sha = NULL) { + sha <- sha %||% attr(version, "sha", exact = TRUE) + parts <- c(version, sprintf(shafmt %||% " [sha: %s]", substring(sha, 1L, 7L))) + paste(parts, collapse = "") + } + + renv_bootstrap_exec <- function(project, libpath, version) { + if (!renv_bootstrap_load(project, libpath, version)) + renv_bootstrap_run(version, libpath) + } + + renv_bootstrap_run <- function(version, libpath) { + + # perform bootstrap + bootstrap(version, libpath) + + # exit early if we're just testing bootstrap + if (!is.na(Sys.getenv("RENV_BOOTSTRAP_INSTALL_ONLY", unset = NA))) + return(TRUE) + + # try again to load + if (requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) { + return(renv::load(project = getwd())) + } + + # failed to download or load renv; warn the user + msg <- c( + "Failed to find an renv installation: the project will not be loaded.", + "Use `renv::activate()` to re-initialize the project." + ) + + warning(paste(msg, collapse = "\n"), call. = FALSE) + + } + + renv_json_read <- function(file = NULL, text = NULL) { + + jlerr <- NULL + + # if jsonlite is loaded, use that instead + if ("jsonlite" %in% loadedNamespaces()) { + + json <- tryCatch(renv_json_read_jsonlite(file, text), error = identity) + if (!inherits(json, "error")) + return(json) + + jlerr <- json + + } + + # otherwise, fall back to the default JSON reader + json <- tryCatch(renv_json_read_default(file, text), error = identity) + if (!inherits(json, "error")) + return(json) + + # report an error + if (!is.null(jlerr)) + stop(jlerr) + else + stop(json) + + } + + renv_json_read_jsonlite <- function(file = NULL, text = NULL) { + text <- paste(text %||% readLines(file, warn = FALSE), collapse = "\n") + jsonlite::fromJSON(txt = text, simplifyVector = FALSE) + } + + renv_json_read_default <- function(file = NULL, text = NULL) { + + # find strings in the JSON + text <- paste(text %||% readLines(file, warn = FALSE), collapse = "\n") + pattern <- '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]' + locs <- gregexpr(pattern, text, perl = TRUE)[[1]] + + # if any are found, replace them with placeholders + replaced <- text + strings <- character() + replacements <- character() + + if (!identical(c(locs), -1L)) { + + # get the string values + starts <- locs + ends <- locs + attr(locs, "match.length") - 1L + strings <- substring(text, starts, ends) + + # only keep those requiring escaping + strings <- grep("[[\\]{}:]", strings, perl = TRUE, value = TRUE) + + # compute replacements + replacements <- sprintf('"\032%i\032"', seq_along(strings)) + + # replace the strings + mapply(function(string, replacement) { + replaced <<- sub(string, replacement, replaced, fixed = TRUE) + }, strings, replacements) + + } + + # transform the JSON into something the R parser understands + transformed <- replaced + transformed <- gsub("{}", "`names<-`(list(), character())", transformed, fixed = TRUE) + transformed <- gsub("[[{]", "list(", transformed, perl = TRUE) + transformed <- gsub("[]}]", ")", transformed, perl = TRUE) + transformed <- gsub(":", "=", transformed, fixed = TRUE) + text <- paste(transformed, collapse = "\n") + + # parse it + json <- parse(text = text, keep.source = FALSE, srcfile = NULL)[[1L]] + + # construct map between source strings, replaced strings + map <- as.character(parse(text = strings)) + names(map) <- as.character(parse(text = replacements)) + + # convert to list + map <- as.list(map) + + # remap strings in object + remapped <- renv_json_read_remap(json, map) + + # evaluate + eval(remapped, envir = baseenv()) + + } + + renv_json_read_remap <- function(json, map) { + + # fix names + if (!is.null(names(json))) { + lhs <- match(names(json), names(map), nomatch = 0L) + rhs <- match(names(map), names(json), nomatch = 0L) + names(json)[rhs] <- map[lhs] + } + + # fix values + if (is.character(json)) + return(map[[json]] %||% json) + + # handle true, false, null + if (is.name(json)) { + text <- as.character(json) + if (text == "true") + return(TRUE) + else if (text == "false") + return(FALSE) + else if (text == "null") + return(NULL) + } + + # recurse + if (is.recursive(json)) { + for (i in seq_along(json)) { + json[i] <- list(renv_json_read_remap(json[[i]], map)) + } + } + + json + + } + + # load the renv profile, if any + renv_bootstrap_profile_load(project) + + # construct path to library root + root <- renv_bootstrap_library_root(project) + + # construct library prefix for platform + prefix <- renv_bootstrap_platform_prefix() + + # construct full libpath + libpath <- file.path(root, prefix) + + # run bootstrap code + renv_bootstrap_exec(project, libpath, version) + + invisible() + +}) diff --git a/renv/settings.json b/renv/settings.json new file mode 100644 index 0000000000000000000000000000000000000000..ffdbb3200f779343ad1aa1a2fb6c74a02fd9b365 --- /dev/null +++ b/renv/settings.json @@ -0,0 +1,19 @@ +{ + "bioconductor.version": null, + "external.libraries": [], + "ignored.packages": [], + "package.dependency.fields": [ + "Imports", + "Depends", + "LinkingTo" + ], + "ppm.enabled": null, + "ppm.ignored.urls": [], + "r.version": null, + "snapshot.type": "implicit", + "use.cache": true, + "vcs.ignore.cellar": true, + "vcs.ignore.library": true, + "vcs.ignore.local": true, + "vcs.manage.ignores": true +} diff --git a/scripts/draw.R b/scripts/draw.R new file mode 100644 index 0000000000000000000000000000000000000000..78e48360ef468128f8e0abe2aeaf954366150c1e --- /dev/null +++ b/scripts/draw.R @@ -0,0 +1,67 @@ +#### +# Plotting functions. +#### + +# global imports +import::here("dplyr", "filter", .character_only = TRUE) +import::here( + "ggplot2", + c( + "aes", "facet_wrap", "geom_histogram", "geom_raster", "ggplot", + "scale_x_continuous", "scale_y_continuous", "scale_fill_viridis_c" + ), + .character_only = TRUE +) +import::here("magrittr", "%>%", .character_only = TRUE) + +# local import +import::here("image.R", "img_to_df", .character_only = TRUE) + + +#' Channel histogram +#' +#' Histogram ggplot of each color channel of an image. +#' +#' @param img an imager::cimg +#' @param cc a character vector, subset of ("R", "G", "B"). +#' +#' @returns a ggplot2 graph +channel_hist <- function(img, cc = c("R", "G", "B")) { + if (all(cc %in% c("R", "G", "B"))) { + gg <- img %>% + img_to_df() %>% + filter(channel %in% cc) %>% + ggplot(aes(value, col = channel)) + + geom_histogram(bins = 30L) + + facet_wrap(~channel) + } else { + stop("Invalid 'cc' argument.") + } + return(gg) +} + + +#' Raster channel +#' +#' Raster ggplot of an image channel. +#' +#' @param img an imager::cimg +#' @param cc character, specify the image channel, one of "R", "G", "B". +#' +#' @returns a ggplot2 graph +raster_channel <- function(img, cc = NULL) { + if (cc %in% c("R", "G", "B")) { + gg <- img %>% + img_to_df() %>% + filter(channel == cc) %>% + ggplot(aes(x, y, fill = value)) + + geom_raster() + + scale_x_continuous(expand = c(0L, 0L)) + + scale_y_continuous(expand = c(0L, 0L), trans = scales::reverse_trans()) + + scale_fill_viridis_c(direction = 1L, option = "plasma") + } else { + stop("Invalid 'cc' argument.") + } + + return(gg) +} diff --git a/scripts/image.R b/scripts/image.R new file mode 100644 index 0000000000000000000000000000000000000000..60cd445ad928de5b55c7a5a2d2a0283660efe175 --- /dev/null +++ b/scripts/image.R @@ -0,0 +1,147 @@ +#### +# Utility functions for imager::cimg objects. +#### + +# global imports +import::here("dplyr", c("mutate", "sample_n"), .character_only = TRUE) +import::here( + "imager", + c("grayscale", "HSVtoRGB", "imappend", "imsplit", "map_il", "RGBtoHSV"), + .character_only = TRUE +) +import::here("magrittr", "%>%", .character_only = TRUE) +import::here("mclust", "densityMclust", .character_only = TRUE) +import::here("purrr", "modify_at", .character_only = TRUE) + +# local imports +import::here("utils.R", "sample_histogram", .character_only = TRUE) + + +#' DataFrame conversion +#' +#' Convert image to dataframe and expose color channel. +#' +#' @param img an imager::cimg +#' +#' @returns a data.frame +img_to_df <- function(img) { + out <- img %>% + as.data.frame() %>% + mutate(channel = factor(cc, labels = c("R", "G", "B"))) + return(out) +} + + +#' Histogram equalization +#' +#' Flatten histogram by replacing the pixel value of an image by their rank. +#' +#' @param img an imager::cimg +#' +#' @returns an imager::cimg +hist_eq <- function(img) { + return(as.cimg(ecdf(img)(img), dim = dim(img))) +} + + +#' Enhance contrast +#' +#' Enhance the contrasts of an image by running an histogram equalization +#' separately on each channel and combining the results. +#' +#' @param img an imager::cimg +#' +#' @returns an imager::cimg +enhance_contrast <- function(img) { + out <- img %>% + imsplit("cc") %>% + map_il(hist_eq) %>% + imappend("cc") + return(out) +} + + +#' Reduce saturation +#' +#' Reduce the saturation of an image through HSV conversion. +#' +#' @param img an imager::cimg +#' @param ratio an integer, how much to divide the saturation by. +#' +#' @returns an imager::cimg +desaturation <- function(img, ratio = 2L) { + out <- img %>% + RGBtoHSV() %>% + imsplit("cc") %>% + modify_at(2L, ~ . / ratio) %>% + imappend("cc") %>% + HSVtoRGB() + return(out) +} + + +#' Correct illumination +#' +#' Correct a gray-scaled image illumination by fitting a linear model and +#' removing the spatial trend. +#' +#' @param img an imager::cimg +#' @param nsamples an integer, pixel subsampling value. +#' +#' @returns an imager::cimg object +correct_illumination <- function(img, nsamples = 1e4L) { + # convert to grayscale if needed + if (rev(dim(img))[1L] > 1L) { + img <- grayscale(img) + } + # linear regression trend + trend <- img %>% + as.data.frame() %>% + sample_n(nsamples) %>% + lm(value ~ x * y, data = .) %>% + predict(img) + out <- img - trend + return(out) +} + + +#' Invert grayscale image +#' +#' @param img an imager::cimg +#' +#' @returns an imager::cimg +invert_grayscale <- function(img) { + # convert to grayscale if needed + if (rev(dim(img))[1L] > 1L) { + img <- grayscale(img) + } + out <- max(img) - img + return(out) +} + + +#' Binarize +#' +#' Binarize a grayscale image. +#' +#' @param img an imager::cimg +#' @param quantile a real, the quantile level used for thresholding. +#' @param ... mclust::densityMclust parameters +#' +#' @returns an imager::cimg +binarize <- function(img, quantile = 0.95, ...) { + # convert to grayscale if needed and invert + if (rev(dim(img))[1L] > 1L) { + stop("A grayscale image is expected.") + } + # sample + sample <- sample_histogram(img) + # fit Gaussian mixture + gm <- densityMclust(sample, G = 1L, plot = FALSE, ...) + # threshold based on 95% quantile + threshold <- qnorm( + quantile, gm$parameters$mean[1L], sqrt(gm$parameters$variance$sigmasq[1L]) + ) + out <- img > threshold + return(out) +} diff --git a/scripts/main.R b/scripts/main.R new file mode 100644 index 0000000000000000000000000000000000000000..e76dc1c8fcb31b33fc87a4dc529da1e288df08e6 --- /dev/null +++ b/scripts/main.R @@ -0,0 +1,241 @@ +#### +# Test steps on single image +#### + +# global imports +import::from("cluster", "ellipsoidhull", .character_only = TRUE) +import::from( + "imager", + c("grabRect", "grayscale", "grow", "load.image", "shrink", + "split_connected", "%inr%"), + .character_only = TRUE +) +import::from("magrittr", c("%>%", "%$%"), .character_only = TRUE) +import::from("mclust", "densityMclust", .character_only = TRUE) +import::from("Momocs", c("import_jpg", "Out"), .character_only = TRUE) +import::from("purrr", c("discard", "keep", "map"), .character_only = TRUE) + +# local imports +import::from( + file.path("scripts", "image.R"), + c("binarize", "correct_illumination", "invert_grayscale"), + .character_only = TRUE +) +import::from( + file.path("scripts", "pixset.R"), + c("combine", "intersect_borders", "combine_bis", "rotation_angle"), + .character_only = TRUE +) +import::from( + file.path("scripts", "utils.R"), + c("ehull", "cm_to_pixel"), + .character_only = TRUE +) + + +# Load image ------------------------------------------------------------------- + +fpath <- file.path("data", "Example", "DSC_6177.JPG") +img <- load.image(fpath) + + +# Remove tray edges manually --------------------------------------------------- + +# try to isolate tray edges +img_reduc <- grabRect(img, output = "im") + + +# Binarize --------------------------------------------------------------------- + +# convert to grayscale +img_gr <- grayscale(img_reduc) +plot(img_gr, main = "grayscale") + +# correct illumination and invert grayscale +img_gr_inv <- img_gr %>% + correct_illumination() %>% + invert_grayscale() +plot(img_gr_inv, main = "grayscale inverted") + +# binarize (alternative is to use the imager::threshold function) +img_pix <- binarize(img_gr_inv, quantile = 0.95) +plot(img_pix, main = "binarized") + + +# Extract connected components and ruler --------------------------------------- + +set_pix <- img_pix %>% + # small dilatation to improve components detection + grow(2L) %>% + # extract components + split_connected(., high_connectivity = TRUE) + +# there are 3 kind of components: small disconnected bits (antenna, legs), +# medium (geriss), one large (ruler) +# fit mixture of gaussian distribution with 2 comps +set_size <- set_pix %>% lapply(sum) %>% unlist() +gm <- densityMclust(log10(set_size), G = 2L, plot = FALSE) +thresholds <- qnorm( + c(5e-3, 1L - 5e-3), + gm$parameters$mean[2L], + sqrt(rev(gm$parameters$variance$sigmasq)[1L]) +) +plot(gm, what = "density", data = log10(set_size), breaks = 200L) +abline(v = thresholds, col = "purple", lty = 2L) + +# split components then shrink back +set_pix_small <- set_pix %>% + keep(~ sum(.) %inr% c(2L, 10L**thresholds[1L])) %>% + map(shrink, 2L) +set_pix_med <- set_pix %>% + keep(~ (sum(.) %inr% 10L**thresholds)) %>% + map(shrink, 2L) +ruler <- set_pix %>% + keep((~ sum(.) > 10L**thresholds[2L])) %>% + map(shrink, 2L) +rm(set_pix) + +# remove pixsets that intersect the image borders +set_pix_med <- set_pix_med %>% + discard(~ intersect_borders(., img_gr)) + +# plot first medium component +set_pix_med[[1L]] %>% + imager::autocrop() %>% + imager::pad(50L, "xy") %>% + plot(main = "first medium component") + + +# Re-attach small component to medium ones ------------------------------------- + +set_pix_med <- set_pix_med %>% + map(~ combine(., set_pix_small)) +rm(set_pix_small) + +set_pix_med[[1L]] %>% + imager::autocrop() %>% + imager::pad(50L, "xy") %>% + plot(main = "re-attached first component") + + +# Get conversion factor from ruler -------------------------------------------- + +# get the conversion factor cm to pixel +conv_factor <- cm_to_pixel(ruler[[1L]]) + +ruler_ct <- ruler[[1L]] %>% + imager::contours(nlevels = 1L) +plot( + ruler[[1L]], main = "ruler", xlim = c(1200L, 1800L), ylim = c(1500L, 1000L) +) +ruler_ct[[1L]] %$% {points(x, y, col = "red")} +ruler_ct[[3L]] %$% {points(x, y, col = "green")} + + +# Split superimposed components ----------------------------------------------- + +# TODO create function that split connected pixset using morphological opening +# illustration with two elements + +# morphological opening (erosion o dilation) to isolate abdomen +pixset_l <- set_pix_med[[4L]] %>% + imager::mopening_square(6L) %>% + imager::split_connected(high_connectivity = TRUE) %>% + keep(~ (sum(.) > 1e2L)) +plot( + pixset_l %>% imager::parany() %>% + imager::autocrop() %>% imager::pad(50L, "xy") +) + +# further split apart the remaining parts +pixset_part <- (set_pix_med[[4L]] - pixset_l %>% imager::parany()) %>% + imager::split_connected(high_connectivity = TRUE) +plot( + (set_pix_med[[4L]] - pixset_l %>% imager::parany()) %>% + imager::autocrop() %>% imager::pad(50L, "xy") +) + +# try to re-attach parts to each component +pixset_l <- pixset_l %>% + map(~ combine_bis(., pixset_part, padding = 25L)) +rm(pixset_part) +plot(pixset_l[[1L]] %>% imager::autocrop() %>% imager::pad(50L, "xy")) +plot(pixset_l[[2L]] %>% imager::autocrop() %>% imager::pad(50L, "xy")) + +# TODO better alternative: fit an ellipsoid hull around each abdomen (also +# increase both semi-axis length) then check which part satisfy the ellipsoid +# equation -> re-attach back if so + + +# Rotate along x-axis ---------------------------------------------------------- + +# get the rotation angle along x-axis +theta <- rotation_angle(set_pix_med[[1L]], size = 6L) +eh <- ehull(set_pix_med[[1L]], 6L) + +set_pix_med[[1L]] %>% + plot(main = "ellipsoid hull", xlim = c(800L, 950L), ylim = c(150L, 1L)) +set_pix_med[[1L]] %>% + imager::mopening_square(6L) %>% imager::as.pixset() %>% imager::where() %$% + {points(x, y, cex = 0.25, col = "green")} +lines(predict(eh), col = "red") + +# rotated pixset +pixset %>% + imager::imrotate(theta * 180L / pi, ehull$loc[1L], ehull$loc[2L], 1L) %>% + plot(main = "rotated", xlim = c(800L, 950L), ylim = c(150L, 1L)) + + +# TODO: Need to further orientate the head towards the y-axis (±pi/2). + + +# Export component and ruler to JPG (for Momocs) ------------------------------- + +# create temp directory +tmp_dir <- file.path("data", "tmp") +dir.create(tmp_dir, showWarnings = FALSE) +# export component +for (idx in seq_along(set_pix_med)) { + set_pix_med[[idx]] %>% + imager::autocrop(.) %>% + imager::pad(50L, "xy") %>% + {max(.) - .} %>% + imager::as.cimg(.) %>% + imager::save.image( + file.path(tmp_dir, paste0("comp", "_", idx, ".jpg")), + quality = 1.0 + ) +} +# export ruler +ruler %>% + .[[1L]] %>% + imager::autocrop(.) %>% + imager::pad(20L, "xy") %>% + {max(.) - .} %>% + imager::as.cimg(.) %>% + imager::save.image(file.path(tmp_dir, "ruler.jpg"), quality = 1.0) + + +# Measurements through Momocs -------------------------------------------------- + +# import jpg file +coo <- import_jpg(jpg.paths = file.path(tmp_dir, "comp_1.jpg")) +# find contour +ct_bis <- Out(coo) +# plot contour +ct_bis[1L] %>% Momocs::coo_plot() + +# TODO: some useful functions to investigate: +# * Momocs::coo_oscillo : shape analysis (Fourier elliptic and the likes) +# +# * Momocs::coo_intersect_segment, Momocs::coo_intersect_angle : find points of +# a contour that intersect with a line defined by a segment (or an angle) +# +# * Momocs::coo_right, Momocs::coo_left, Momocs::coo_top, Momocs::coo_down : +# to keep only the right (left, top or down) part of the contour +# +# * Momocs::coo_untiltx : correct rotational biases appearing after applying +# sliding methods. +# +# There are more interesting functions, see the doc: +# https://cran.r-project.org/web/packages/Momocs/Momocs.pdf diff --git a/scripts/pixset.R b/scripts/pixset.R new file mode 100644 index 0000000000000000000000000000000000000000..46a2e84a6f74849760b8dd140b77e4cf002cef82 --- /dev/null +++ b/scripts/pixset.R @@ -0,0 +1,160 @@ +#### +# Utility functions for imager::pixset objects. +#### + +import::here("dplyr", "inner_join", .character_only = TRUE) +import::here( + "imager", + c( + "as.cimg", "as.pixset", "get.stencil", "imshift", "is.pixset", "parany", + "px.borders", "where" + ), + .character_only = TRUE +) +import::here("magrittr", "%>%", .character_only = TRUE) + +# local imports +import::here("utils.R", c("ehull", "square_stencil"), .character_only = TRUE) + + +#' Get centroid of a pixset. +#' +#' @param pixset an imager::pixset +#' +#' @returns an integer vector +get_centroid <- function(pixset) { + centroid <- pixset %>% + where() %>% + colMeans() %>% + as.integer() + return(centroid) +} + + +#' Center a pixset. +#' +#' @param pixset an imager::pixset +#' +#' @returns an imager::pixset +px_center <- function(pixset) { + centroid <- get_centroid(pixset) + delta <- dim(pixset)[1L:2L] / 2L - centroid + out <- pixset %>% + as.cimg() %>% + imshift(delta_x = delta[1L], delta_y = delta[2L]) %>% + as.pixset() + return(out) +} + + +#' Intersect borders +#' +#' Test whether a pixset intersect an image borders. +#' +#' @param pixset an imager::pixset +#' @param img an imager::cimg +#' +#' @returns a vector of boolean +intersect_borders <- function(pixset, img) { + # extract the image borders (as pixels coordinates) + borders <- img %>% + px.borders() %>% + where() + # count the number of pixels intersecting the borders + pix_on_borders <- suppressMessages( + pixset %>% + where() %>% + inner_join(borders) %>% + nrow() + ) + return(pix_on_borders > 0L) +} + + +#' Combine pixset +#' +#' Combine pixsets into a reference pixset according to a shared neighbourhood. +#' The neighbourhood is defined as a squared stencil centered around each +#' candidate pixset. +#' +#' @param pixset_ref an imager::pixset, the reference. +#' @param pixset_list a list of imager::pixset, the list of pixsets to combine +#' with the reference. +#' @param padding an integer, the stencil length +#' +#' @returns a vector of boolean +combine <- function(pixset_ref, pixset_list, padding = 50L) { + # convert candidat to list if there is only one + if (!is.list(pixset_list) & is.pixset(pixset_list)) { + pixset_list <- list(pixset_list) + } + # iterate over parts + for (idx in seq_along(pixset_list)) { + # get centroid + centroid <- get_centroid(pixset_list[[idx]]) + # define stencil for neighborhood + stencil <- square_stencil(centroid, padding, pixset_ref) + # check overlap + overlap <- pixset_ref %>% + get.stencil(stencil, x = centroid[1L], y = centroid[2L]) %>% + sum() + # merge if overlap + if (overlap > 0L) { + pixset_ref <- parany(list(pixset_ref, pixset_list[[idx]])) + } + } + return(pixset_ref) +} + +combine_bis <- function(pixset_ref, pixset_list, padding = 25L) { + # convert candidat to list if there is only one + if (!is.list(pixset_list) & is.pixset(pixset_list)) { + pixset_list <- list(pixset_list) + } + # get centroid + centroid <- get_centroid(pixset_ref) + # define stencil for neighborhood + stencil <- square_stencil(centroid, padding, pixset_ref) + # iterate over parts + for (idx in seq_along(pixset_list)) { + # check overlap + overlap <- pixset_list[[idx]] %>% + get.stencil(stencil, x = centroid[1L], y = centroid[2L]) %>% + sum() + # merge if overlap + if (overlap > 0L) { + pixset_ref <- parany(list(pixset_ref, pixset_list[[idx]])) + } + } + return(pixset_ref) +} + + +#' Rotation angle +#' +#' Get the rotation angle to align the abdomen along the x-axis. The function +#' fits an ellipsoid hull around the pixset to derive the rotation angle. +#' +#' @param pixset an imager::pixset +#' @param size an integer, the morphological opening factor (optional) +#' +#' @returns a real +rotation_angle <- function(pixset, size = 6L) { + # compute ellipsoid hull + eh <- ehull(pixset, size) + # get semi-axis lengths + l_term <- (eh$cov[1L, 1L] + eh$cov[2L, 2L]) / 2L + r_term <- sum( + c((eh$cov[1L, 1L] - eh$cov[2L, 2L]) / 2L, eh$cov[1L, 2L]) ** 2L + ) + lambda_1 <- l_term + sqrt(r_term) + lambda_2 <- l_term - sqrt(r_term) + axis_l <- c(sqrt(lambda_1), sqrt(lambda_2)) + # get rotation angle (from x-axis) + if (eh$cov[1L, 2L] != 0L) { + theta <- pi - atan2(lambda_1 - eh$cov[1L, 1L], eh$cov[1L, 2L]) + } else { + theta <- ifelse(eh$cov[1L, 1L] >= eh$cov[2L, 2L], 0L, pi / 2L) + } + return(theta) +} diff --git a/scripts/utils.R b/scripts/utils.R new file mode 100644 index 0000000000000000000000000000000000000000..6a025ba6d50c3a8e4a1aa302b95bcf9458bb89de --- /dev/null +++ b/scripts/utils.R @@ -0,0 +1,120 @@ +#### +# Additional utility functions. +#### + +# global imports +import::here("cluster", "ellipsoidhull", .character_only = TRUE) +import::here( + "imager", + c( + "as.pixset", "contours", "is.cimg", "is.pixset", "%inr%", + "mopening_square", "where" + ), + .character_only = TRUE +) +import::here("mclust", "densityMclust", .character_only = TRUE) +import::here("purrr", c("keep", "map"), .character_only = TRUE) + + +#' Sample pixel values from histogram +#' +#' @param obj an array-like object (img, pixset) +#' @param ratio a real, the sampling ratio. +#' +#' @returns an integer vector +sample_histogram <- function(obj, ratio = 0.02) { + n_samples <- min(1e4L, prod(dim(obj)[1L:2L])) + n_breaks <- as.integer(n_samples * ratio) + hist <- hist(obj, breaks = n_breaks, plot = FALSE) + bins <- with( + hist, sample(length(mids), n_samples, p = density, replace = TRUE) + ) + sample <- runif( + length(bins), hist$breaks[bins], hist$breaks[bins + 1L] + ) + return(sample) +} + + +#' Square stencil +#' +#' Define a square stencil within the boundaries of an image cropping points +#' that are out of bounds. +#' +#' @param centroid an integer vector, the stencil centroid. +#' @param pad an integer, the padding around the stencil centroid. +#' @param obj an imager::cimg or an imager::pixset +#' +#' @returns a data.frame +square_stencil <- function(centroid, pad, obj) { + + # get image dimensions + if (is.cimg(obj) | is.pixset(obj)) { + dims <- as.integer(dim(obj)[1L:2L]) + } else { + stop("Invalid 'obj' argument.") + } + # check bounds + bounds <- c( + ifelse(centroid - pad < 1L, -centroid + 1L, -pad), + ifelse(pad + centroid > dims, dims - centroid, pad) + ) + # define stencil + stencil <- expand.grid( + dx = seq(bounds[1L], bounds[3L]), + dy = seq(bounds[2L], bounds[4L]) + ) + return(stencil) +} + + +#' Centimeter to pixel +#' +#' Get the centimeter to pixel conversion factor from a ruler defined as +#' a pixset. +#' +#' @param ruler an imager::pixset +#' @param quantile a real, the quantile level used for thresholding. +#' +#' @returns a data.frame +cm_to_pixel <- function(ruler, quantile = 5e-3) { + + # get ruler contours size + ct_size <- ruler %>% + contours(nlevels = 1L) %>% + map(~ length(.$x)) %>% + unlist() + # estimate the distribution by a Gaussian + gm <- densityMclust(log10(ct_size), G = 1L, plot = FALSE) + # threshold to discriminate square contours + thresholds <- qnorm( + c(quantile, 1L - quantile), + gm$parameters$mean[1L], + sqrt(gm$parameters$variance$sigmasq[1L]) + ) + # get the conversion factor cm to pixel + conv_factor <- ct_size %>% + keep(~ (. %inr% 10L ** thresholds)) %>% + {. / 4L} %>% + mean() + return(conv_factor) +} + + +#' Ellipsoid hull +#' +#' Fit an ellipsoid hull for a pixset with optional morphological opening. +#' +#' @param pixset an imager::pixset +#' @param size an integer, the morphological opening factor. +#' +#' @returns a data.frame +ehull <- function(pixset, size = 6L) { + out <- pixset %>% + mopening_square(size) %>% + as.pixset() %>% + where() %>% + as.matrix() %>% + ellipsoidhull() + return(out) +}