Skip to content

Commit 374a871

Browse files
committed
initial commit
1 parent f2037a7 commit 374a871

24 files changed

+1318
-1
lines changed

.Rbuildignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
^mincountr\.Rproj$
2+
^\.Rproj\.user$
3+
^LICENSE\.md$

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.Rproj.user
2+
.Rhistory
3+
.RData
4+
.Ruserdata

DESCRIPTION

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Package: mincountr
2+
Title: A simple point counter for mineral volume estimates
3+
Version: 0.0.0.9000
4+
Authors@R:
5+
person(given = "Soeren",
6+
family = "Wilke",
7+
role = c("aut", "cre"),
8+
email = "s.wilke@gmx.de",
9+
)
10+
Description: mincountr groups images from e.g. electron microprobes by brightness an then counts the number of pixels per group.
11+
License: GPL-3
12+
Encoding: UTF-8
13+
LazyData: true
14+
RoxygenNote: 6.1.1
15+
Imports:
16+
magrittr,
17+
dplyr,
18+
ggplot2,
19+
imager,
20+
paletteer,
21+
tibble

LICENSE.md

+595
Large diffs are not rendered by default.

NAMESPACE

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Generated by roxygen2: do not edit by hand
2+
3+
export("%>%")
4+
export(mcr_autoconstrain)
5+
export(mcr_herd_minerals)
6+
export(mcr_inspect_assignement)
7+
export(mcr_inspect_phases)
8+
export(mcr_load_image)
9+
importFrom(magrittr,"%>%")

R/classify_image.R

+269
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
#' Load image from file or URL
2+
#'
3+
#' @description simple wrapper around \code{\link[imager]{load.image}} included
4+
#' here for convenience and consitency reasons only.
5+
#'
6+
#' @param file path to file of URL
7+
#'
8+
#' @return an object of class \code{\link[imager]{cimg}}
9+
#'
10+
#' @examples
11+
#' mcr_load_image(system.file("extdata", "testim.png", package = "mincountr"))
12+
#'
13+
#' @export
14+
#'
15+
mcr_load_image <- function(file){
16+
imager::load.image(file)
17+
}
18+
19+
#' Plot an images brightness density distribution
20+
#'
21+
#' @description Inspecting the density distribution of an electron
22+
#' microscope image of minerals effectively allows to identify different phases
23+
#' as their distinct brightness is a key giveaway of their different chemical
24+
#' composition (and conductivity).
25+
#'
26+
#' @param x an image loaded with \code{\link{mcr_load_image}}
27+
#'
28+
#' @examples
29+
#' myimage <- mcr_load_image(system.file("extdata", "testim.png", package = "mincountr"))
30+
#' mcr_inspect_phases(myimage)
31+
#'
32+
#' @export
33+
34+
mcr_inspect_phases <- function(x){
35+
imgr <- imager::grayscale(x) #Making sure image is in greyscale
36+
imtb <- as.data.frame(imgr)
37+
imtidy <- tibble::as.tibble(imtb)
38+
ggplot2::ggplot(data = imtidy, ggplot2::aes(value)) +
39+
ggplot2::geom_density(kernel="gaussian") +
40+
ggplot2::scale_x_continuous(breaks=c(seq(0,1,0.1)))
41+
}
42+
43+
#' Plot group assignment of a phase in an image
44+
#'
45+
#' @description Plots a false color image using groups of brightness that can
46+
#' either be determined from a graphical inspection of the result of
47+
#' \code{\link{mcr_inspect_phases}} or automaticalle determined by
48+
#' \code{\link{mcr_herd_minerals}}
49+
#'
50+
#' @param x an image loaded with \code{\link{mcr_load_image}}
51+
#' @param lhs a vector with the left-hand-side position of peaks observed in \code{\link{mcr_inspect_phases}}
52+
#' @param rhs a vector with the right-hand-side position of peaks observed in \code{\link{mcr_inspect_phases}}
53+
#'
54+
#' @examples
55+
#' myimage <- mcr_load_image(system.file("extdata", "testim.png", package = "mincountr"))
56+
#'
57+
#' # (Semi-)Automatic approach:
58+
#' mypeaks <- mcr_autoconstrain(myimage)
59+
#' mcr_inspect_assignement(myimage, mypeaks$x1, mypeaks$x2)
60+
#'
61+
#' # Manual assignements of brightnessgrouplimits:
62+
#' mcr_inspect_assignement(
63+
#' myimage,
64+
#' lhs = c(0, 0.3, 0.5, 0.92),
65+
#' rhs = c(0.05, 0.45, 0.65, 1)
66+
#' )
67+
#'
68+
#' @export
69+
70+
mcr_inspect_assignement <- function(x, lhs, rhs) {
71+
72+
#Some input testing
73+
if(!all(is.numeric(c(lhs, rhs)))){
74+
stop("both 'lhs' and 'rhs' must be vectors of type 'numeric'")
75+
}
76+
77+
if(length(lhs) != length(rhs)){
78+
stop("numeric vectors 'lhs' and 'rhs' must be of the same length")
79+
}
80+
81+
if(any(c(lhs, rhs) < 0)){
82+
stop("elements in numeric vectors 'lhs' ans 'rhs' must be >= 0 & <= 1")
83+
}
84+
85+
if(any(c(lhs, rhs) > 1)){
86+
stop("elements in numeric vectors 'lhs' ans 'rhs' must be >= 0 & <= 1")
87+
}
88+
89+
imgr <- imager::grayscale(x) #Making sure image is in greyscale
90+
imtb <- as.data.frame(imgr)
91+
imtidy <- tibble::as.tibble(imtb)
92+
93+
minbins <- as.vector(rbind(lhs, rhs))
94+
95+
# assining image pixels to phases, respectively their phases
96+
minlev<-.bincode(imtidy$value,minbins,TRUE,TRUE)
97+
lvldimg<-cbind(imtidy,minlev)
98+
99+
ggplot2::ggplot(
100+
data = lvldimg,
101+
ggplot2::aes(
102+
x = x, y = y, fill = as.factor(minlev)
103+
)
104+
) +
105+
ggplot2::geom_raster() +
106+
ggplot2::labs(fill = "Phase_ID") +
107+
paletteer::scale_fill_paletteer_d(package = "ggsci", palette = "default_igv")
108+
}
109+
110+
#' Automatic peak detection
111+
#'
112+
#' @description This function automatically determines the position of peaks
113+
#' and their half-height-width in brightness density distribution of an image.
114+
#' This is key input information necessary to assign brightness-niveaus to
115+
#' certain (mineral) phases an can be used in \code{\link{mcr_herd_minerals}}
116+
#'
117+
#' @param x an image loaded with \code{\link{mcr_load_image}}
118+
#'
119+
#' @return A \code{\link[tibble]{tibble}} containing the positions of peak maxima and half-height-width both to the left and to the right of the peak
120+
#'
121+
#' @examples
122+
#' myimage <- mcr_load_image(system.file("extdata", "testim.png", package = "mincountr"))
123+
#' mcr_autoconstrain(myimage)
124+
#'
125+
#' @export
126+
#'
127+
128+
mcr_autoconstrain <- function(x){
129+
imgr <- imager::grayscale(x) #Making sure image is in greyscale
130+
imtb <- as.data.frame(imgr)
131+
imtidy <- tibble::as.tibble(imtb)
132+
133+
img_density <- stats::density(imtidy$value)
134+
135+
# identifying turning points in the density line
136+
extreme_points <- tibble::tibble(
137+
x = img_density$x, y = img_density$y,
138+
extr = c(0, diff(sign(diff(img_density$y))), 0)
139+
)
140+
141+
peaks <- extreme_points %>%
142+
dplyr::select(x, extr) %>%
143+
dplyr::filter(extr == -2) %>%
144+
.$x
145+
146+
peaks[peaks<0] <- 0
147+
peaks[peaks>1] <- 1
148+
149+
valleys<- extreme_points %>%
150+
dplyr::select(x, extr) %>%
151+
dplyr::filter(extr == 2) %>%
152+
.$x
153+
154+
valleys[valleys<0] <- 0
155+
valleys[valleys>1] <- 1
156+
157+
# processing each peak individually
158+
catcher <- vector("list", length = length(peaks))
159+
for(i in seq_along(catcher)){
160+
161+
# cut out the single peak
162+
if(peaks[i] == 0){
163+
cutoff_left <- 0
164+
cutoff_right <- valleys[valleys > peaks[i]] %>% min()
165+
if(is.infinite(cutoff_right)){cutoff_right <- 1}
166+
} else if (peaks[i] == 1){
167+
cutoff_right <- 1
168+
cutoff_left <- valleys[valleys < peaks[i]] %>% max()
169+
if(is.infinite(cutoff_left)){cutoff_left <- 0}
170+
} else {
171+
cutoff_left <- valleys[valleys < peaks[i]] %>% max()
172+
if(is.infinite(cutoff_left)){cutoff_left <- 0}
173+
cutoff_right <- valleys[valleys > peaks[i]] %>% min()
174+
if(is.infinite(cutoff_right)){cutoff_right <- 1}
175+
}
176+
df <- extreme_points %>%
177+
dplyr::filter(x >= cutoff_left) %>%
178+
dplyr::filter(x <= cutoff_right) %>%
179+
dplyr::mutate(Peak_id = i)
180+
181+
# calculate half-height-width
182+
183+
# left "border" of the actual peak
184+
if(cutoff_left == 0){
185+
x1 <- 0
186+
} else {
187+
x1 <- df$x[df$x < peaks[i]][which.min(abs(df$y[df$x < peaks[i]]-max(df$y)/2))]
188+
}
189+
190+
# right "border" of the actual peak
191+
if(cutoff_right == 1){
192+
x2 <- 1
193+
} else {
194+
x2 <- df$x[df$x > peaks[i]][which.min(abs(df$y[df$x > peaks[i]]-max(df$y)/2))]
195+
}
196+
197+
catcher[[i]] <- tibble::tibble(
198+
x1 = x1,
199+
peakpos = peaks[i],
200+
x2 = x2,
201+
ID = i
202+
)
203+
204+
}
205+
do.call("rbind", catcher)
206+
}
207+
208+
209+
#' Calculate phase area share
210+
#'
211+
#' @description calculate the percentage share of mineral phases of the area of
212+
#' an image.
213+
#'
214+
#' @param x an image loaded with \code{\link{mcr_load_image}}
215+
#' @param lhs a vector with the left-hand-side position of peaks observed in \code{\link{mcr_inspect_phases}}
216+
#' @param rhs a vector with the right-hand-side position of peaks observed in \code{\link{mcr_inspect_phases}}
217+
#'
218+
#' @return A \code{\link[tibble]{tibble}} containing the number of pixels and their relative share of total image area per phase
219+
#'
220+
#' @examples
221+
#' myimage <- mcr_load_image(system.file("extdata", "testim.png", package = "mincountr"))
222+
#' mypeaks <- mcr_autoconstrain(myimage)
223+
#' mcr_herd_minerals(myimage, mypeaks$x1, mypeaks$x2)
224+
#'
225+
#' @export
226+
#'
227+
228+
mcr_herd_minerals <- function(x, lhs, rhs){
229+
230+
#Some input testing
231+
if(!all(is.numeric(c(lhs, rhs)))){
232+
stop("both 'lhs' and 'rhs' must be vectors of type 'numeric'")
233+
}
234+
235+
if(length(lhs) != length(rhs)){
236+
stop("numeric vectors 'lhs' and 'rhs' must be of the same length")
237+
}
238+
239+
if(any(c(lhs, rhs) < 0)){
240+
stop("elements in numeric vectors 'lhs' ans 'rhs' must be >= 0 & <= 1")
241+
}
242+
243+
if(any(c(lhs, rhs) > 1)){
244+
stop("elements in numeric vectors 'lhs' ans 'rhs' must be >= 0 & <= 1")
245+
}
246+
247+
imgr <- imager::grayscale(x) #Making sure image is in greyscale
248+
imtb <- as.data.frame(imgr)
249+
imtidy <- tibble::as.tibble(imtb)
250+
251+
252+
minbins <- as.vector(rbind(lhs, rhs))
253+
254+
# assining image pixels to phases, respectively their phases
255+
minlev<-.bincode(imtidy$value,minbins,TRUE,TRUE)
256+
lvldimg<-cbind(imtidy,minlev)
257+
258+
sumup <- lvldimg %>%
259+
dplyr::group_by(minlev) %>%
260+
dplyr::summarize(count=dplyr::n()) %>%
261+
dplyr::ungroup() %>%
262+
dplyr::rename(Phase_ID = minlev, pixels = count) %>%
263+
dplyr::mutate(
264+
`proportion_percentage` = (pixels/sum(pixels))*100
265+
)
266+
267+
return(sumup)
268+
}
269+

R/mincountr-package.R

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#' mincountr
2+
#'
3+
#' simple point counting methods to estimate mineral volume shares in electron microscope images
4+
#'
5+
#' @name mincountr
6+
#' @docType package
7+
NULL

R/utils-pipe.R

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#' Pipe operator
2+
#'
3+
#' See \code{magrittr::\link[magrittr:pipe]{\%>\%}} for details.
4+
#'
5+
#' @name %>%
6+
#' @rdname pipe
7+
#' @keywords internal
8+
#' @export
9+
#' @importFrom magrittr %>%
10+
#' @usage lhs \%>\% rhs
11+
NULL

README-check assignement-1.png

36.6 KB
Loading

README-illustrate peakborders-1.png

6.05 KB
Loading

README-load image-1.png

136 KB
Loading

README-plot brightness-1.png

5.92 KB
Loading

README-sum up-1.png

36.6 KB
Loading

0 commit comments

Comments
 (0)