diff --git a/DESCRIPTION b/DESCRIPTION index 5bdee0f..5dd3cdb 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: EBImage -Version: 4.19.7 +Version: 4.19.8 Title: Image processing and analysis toolbox for R Encoding: UTF-8 Author: Andrzej OleÅ›, Gregoire Pau, Mike Smith, Oleg Sklyar, Wolfgang Huber, with contributions from Joseph Barry and Philip A. Marais diff --git a/R/Image.R b/R/Image.R index a84e06f..8cae54c 100644 --- a/R/Image.R +++ b/R/Image.R @@ -45,12 +45,11 @@ Image = function(data = array(0, dim=c(1,1)), dim, colormode) { else parseColorMode(colormode) - if (missing(dim)) { + if (missing(dim)) dim = setdim(data) - if ( colormode==Color ) - dim = c(dim[1:2], 3L, dim[-(1:2)]) - } + if ( colormode==Color ) + dim = c(dim[1:2], 3L, dim[-(1:2)]) dimnames = dimnames(data) data = col2rgb(data)/255 @@ -507,6 +506,10 @@ numberOfFrames = function(y, type = c('total', 'render')) { .Call(C_numberOfFrames, y, type) } +numberOfChannels = function(y, d = dim(y), cm = colorMode(y)) { + if ( cm==Grayscale || is.na(d[3L]) ) 1L else d[3L] +} + ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - showImage = function (object, short=FALSE) { nd = dim(object) diff --git a/R/display.R b/R/display.R index 2593b04..11d7aba 100644 --- a/R/display.R +++ b/R/display.R @@ -207,13 +207,10 @@ displayWidget <- function(x, title, embed = !interactiveMode(), tempDir = tempfi imageData(x) = abind(x, Image(0, fd), along = 3L) } - nf = numberOfFrames(x, type='render') - colormode = colorMode(x) - x = clipImage(x) ## clip the image and change storage mode to double x = transpose(x) - frames = seq_len(nf) + frames = seq_len(numberOfFrames(x, type='render')) dependencies = NULL if ( isTRUE(embed) ) { diff --git a/R/floodFill.R b/R/floodFill.R index ed3b056..5d22907 100644 --- a/R/floodFill.R +++ b/R/floodFill.R @@ -17,15 +17,44 @@ ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - floodFill = function(x, pt, col, tolerance=0) { validImage(x) - - n = numberOfFrames(x, 'total') - if ( is.list(pt) ) pt = unlist(pt, use.names=FALSE) - pt = as.integer(pt) - if ( is.character(col) ) col = as.numeric(col2rgb(col)/255) - col = if ( typeof(x)=="double" ) as.double(col) else as.integer(col) - if ( any( pt<1L || pt>dim(x)[1:2] ) ) stop("coordinates 'pt' of the starting point(s) must be inside the image boundaries") - - return( .Call(C_floodFill, x, matrix(pt, nrow=n, ncol=2L, byrow=TRUE), matrix(col, nrow=n, ncol=1L), as.numeric(tolerance))) + nf = numberOfFrames(x, 'render') + nc = numberOfChannels(x) + + ## make sure that `pt` and `col` are lists of length matching the number of frames + if ( is.list(pt) ) { + if ( length(pt) != nf ) stop("length of 'pt' must match the number of 'render' frames") + } else { + pt = rep(list(pt), nf) + } + if ( is.list(col) ) { + if ( length(col) != nf ) stop("length of 'col' must match the number of 'render' frames") + } else { + col = rep(list(col), nf) + } + + for (i in seq_len(nf) ) { + pti = pt[[i]] + if ( is.list(pti) ) + pti = unlist(pti, use.names=FALSE) + storage.mode(pti) = "integer" + if ( any(pti<1L) || any(pti>dim(x)[1:2]) ) + stop("coordinates of starting point(s) 'pt' must be inside image boundaries") + if ( !is.matrix(pti) || dim(pti)[2L]!=2L ) + pti = matrix(pti, nrow=length(pti)/2, ncol=2L, byrow=TRUE) + np = dim(pti)[1L] + pt[[i]] = pti + + cli = col[[i]] + cli = if ( is.character(cli) ) + unlist(lapply(cli, function(col) rep_len(col2rgb(col, alpha=TRUE)/255, nc))) + else + rep(cli, each=nc) + storage.mode(cli) = storage.mode(x) + cli = matrix(rep_len(cli, np * nc), nrow=np, ncol=nc, byrow=TRUE) + col[[i]] = cli + } + + return( .Call(C_floodFill, x, pt, col, as.numeric(tolerance))) } ## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/man/bwlabel.Rd b/man/bwlabel.Rd index da15624..06a9495 100644 --- a/man/bwlabel.Rd +++ b/man/bwlabel.Rd @@ -60,7 +60,7 @@ bwlabel(x) nbnuclei = apply(z, 3, max) cat('Number of nuclei=', paste(nbnuclei, collapse=','),'\n') - ## recolor nuclei in colors + ## paint nuclei in color cols = c('black', sample(rainbow(max(z)))) zrainbow = Image(cols[1+z], dim=dim(z)) display(zrainbow, title='Cell nuclei (recolored)') diff --git a/man/floodFill.Rd b/man/floodFill.Rd index 215711d..831e41e 100644 --- a/man/floodFill.Rd +++ b/man/floodFill.Rd @@ -15,10 +15,10 @@ floodFill(x, pt, col, tolerance=0) \arguments{ \item{x}{An \code{Image} object or an array.} - \item{pt}{Coordinates of the start filling point.} + \item{pt}{Coordinates of the start filling points provided as a matrix where rows represent points and columns are the x and y cordinates. For image stacks different points for each frame can be specified by providing a list whose length matches the number of 'render' frames.} \item{col}{Fill color. This argument should be a numeric for Grayscale images - and an R color for Color images.} + and an R color for Color images. Values are recycled such that their length matches the number of points in `pt`. Can be a list similarly as `pt`.} \item{tolerance}{Color tolerance used during the fill.} } @@ -41,15 +41,33 @@ floodFill(x, pt, col, tolerance=0) \examples{ x = readImage(system.file("images", "shapes.png", package="EBImage")) + + ## fill a shape with 50% shade of gray y = floodFill(x, c(67, 146), 0.5) display(y) - - y = channel(y, 'rgb') - y = floodFill(y, c(48, 78), 'red') - y = floodFill(y, c(156, 52), 'orange') + + ## fill with color + y = toRGB(y) + y = floodFill(y, c(48, 78), 'orange') display(y) - + + ## fill multiple shapes with different colors + y = y[110:512,1:130,] + points = rbind(c(50, 50), c(100, 50), c(150, 50)) + colors = c("red", "green", "blue") + y = floodFill(y, points, colors) + display(y) + + ## area fill x = readImage(system.file("images", "sample.png", package="EBImage")) - y = floodFill(x, c(226, 121), 1, tolerance=0.1) + y = floodFill(x, rbind(c(200, 400), c(200, 325)), 1, tolerance=0.1) + display(y) + + ## application to image stacks + f = system.file("images", "nuclei.tif", package="EBImage") + x = readImage(f)[1:250,1:250,] + x = opening(thresh(x, 12, 12), makeBrush(5, shape='disc')) + xy = lapply(getFrames(bwlabel(x)), function(x) computeFeatures.moment(x)[,1:2]) + y = floodFill(toRGB(x), xy, c("red", "green", "blue")) display(y) } diff --git a/src/floodFill.cpp b/src/floodFill.cpp index acdc0c0..af90913 100644 --- a/src/floodFill.cpp +++ b/src/floodFill.cpp @@ -24,38 +24,46 @@ template void _bwlabel(T *, int *, XYPoint); /* -------------------------------------------------------------------------- */ SEXP -floodFill(SEXP x, SEXP point, SEXP col, SEXP tol) { - int i, nz, *dim, *pts; +floodFill(SEXP x, SEXP _pts, SEXP _col, SEXP _tol) { + int i, p, c, nf, np, nc, *dim, *pts; + double tol = REAL(_tol)[0]; XYPoint pt; - SEXP res; - + SEXP res, points, colors; // check image validity - validImage(x,0); - nz = getNumberOfFrames(x, 0); + validImage(x, 0); + nf = getNumberOfFrames(x, 1); + nc = getNumberOfChannels(x, COLOR_MODE(x)); dim = INTEGER(GET_DIM(x)); XYPoint size(dim[0], dim[1]); if (size.x <= 0 || size.y <= 0) error("image must have positive dimensions"); - if (LENGTH(point) != 2*nz) error("point must have a size of two times the number of frames"); - if (LENGTH(col) != nz) error("color must have the same size as the number of frames"); + if (LENGTH(_pts) != nf) error("length of points list must match the number of 'render' frames"); + if (LENGTH(_col) != nf) error("length of color list must match the number of 'render' frames"); // initialize result PROTECT(res = Rf_duplicate(x)); - pts = INTEGER(point); - - // do the job over images - for (i=0; i(&(INTEGER(res)[i*size.x*size.y]), size, pt, INTEGER(col)[i], REAL(tol)[0]); - break; - case REALSXP: - _floodFill(&(REAL(res)[i*size.x*size.y]), size, pt, REAL(col)[i], REAL(tol)[0]); - break; + // iterate over images + for (i=0; i(&(INTEGER(res)[(i*nc+c)*size.x*size.y]), size, pt, INTEGER(colors)[c*np+p], tol); + break; + case REALSXP: + _floodFill(&(REAL(res)[(i*nc+c)*size.x*size.y]), size, pt, REAL(colors)[c*np+p], tol); + break; + } + } } } @@ -386,4 +394,3 @@ _fillHullT(T *_m, const XYPoint &srcsize) { delete[] canvas; delete[] bbox; } - diff --git a/tests/test.Rout.save b/tests/test.Rout.save index a645079..9b6be6b 100644 --- a/tests/test.Rout.save +++ b/tests/test.Rout.save @@ -700,7 +700,7 @@ checking 'blackTopHat' ........................ PASS (af7e8fad58a0f587) 202411.9 checking 'selfComplementaryTopHat' ............ PASS (729954d933215c8d) 2151192 checking 'distmap' ............................ PASS (8ba0b0fb6770e8a3) 3856630 checking 'watershed' .......................... PASS (0bd6a31bc6197067) 1443978 -checking 'floodFill' .......................... PASS (45775cc98ccde7d0) 1055560 +checking 'floodFill' .......................... PASS (7a8a06c4be73e522) 2057384 checking 'fillHull' ........................... PASS (d3904950b8acdb73) 1096352 checking 'propagate' .......................... PASS (6c610fe4376714f6) 1613920 checking 'toRGB' .............................. PASS (57b9a77f5a12af4f) 8499672 diff --git a/vignettes/EBImage-introduction.Rmd b/vignettes/EBImage-introduction.Rmd index 6b092f3..b25c820 100644 --- a/vignettes/EBImage-introduction.Rmd +++ b/vignettes/EBImage-introduction.Rmd @@ -646,12 +646,12 @@ display(filled_logo) ```{r floodFill-logo, fig.width=dim(logo)[1L]/.dpi, fig.height=dim(logo)[2L]/.dpi, dpi=.dpi} rgblogo = toRGB(logo) -rgblogo = floodFill(rgblogo, c(50, 50), "red") -rgblogo = floodFill(rgblogo, c(100, 50), "green") -rgblogo = floodFill(rgblogo, c(150, 50), "blue") +points = rbind(c(50, 50), c(100, 50), c(150, 50)) +colors = c("red", "green", "blue") +rgblogo = floodFill(rgblogo, points, colors) display( rgblogo ) ```{r floodFill-img, fig.width=dim(img)[1L]/.dpi, fig.height=dim(img)[2L]/.dpi, dpi=.dpi/2} -display( floodFill(img, c(444, 222), col=0.2, tolerance=0.2) ) +display(floodFill(img, rbind(c(200, 300), c(444, 222)), col=0.2, tolerance=0.2)) ``` ## Highlighting objects