Is there any R function to convert grey scale image to binary image. There is one to convert from RGB to Grey but I want to convert Grey to Binary.
This is called thresholding or binarization. The most robust in my experience is adaptive thresholding. This is implemented in EBImage as the thresh method
x = readImage(system.file('images', 'nuclei.tif', package='EBImage'))
if (interactive()) display(x)
y = thresh(x, 10, 10, 0.05)
if (interactive()) display(y)
You didn't say what class or "typeof" your data is, so I'm going to provide an answer in a simple case. Suppose your image is an array of integers. These integers range from 0 to, say 512 for a 9-bit greyscale image. You need to decide what the cutoff point is for 0 vs. 1 in your binary image. Then
bin_image <- round(grey_image/max(grey_image),0)
should do it. If your data range from 0 to 1, do a similar operation but adjust the rounding parameters.
Edit: ooops, I left out a choice of cutoff level. Replace max(grey_image) with K*max(grey_image) where K = 1 for cutting at half-max, K>1 to cut higher and K<1 to cut lower.
The EBImage Bioconductor package is a handy tool for performing image analysis in R.
A basic example taken from the package's Vignette:
lena = readImage(system.file("images", "lena.gif", package="EBImage"))
display(lena>0.5)
Related
I have a SpatVect consisting of points and I want to rasterize them into a SpatRaster with a given resolution. Is there a way of specifying a function taking in the points that are within a buffer of each raster cell?
Many thanks
Joao
-- Update --
Maybe a figure would help understand what I'm after with my question. The red square will have to be run over the center of each pixel to calculate some statistics using the ovelaying points. Apologies for the clumsy question, but I hope the figure is clear enough...
terra version 1.6-28 supports rasterization of points with a rectangular moving window.
Example data
library(terra)
#terra 1.6.33
r <- rast(ncol=100, nrow=100, crs="local", xmin=0, xmax=50, ymin=0, ymax=50)
set.seed(100)
x <- runif(50, 5, 45)
y <- runif(50, 5, 45)
z <- sample(50)
v <- vect(data.frame(x,y,z), geom=c("x", "y"))
Solution
r1 <- rasterizeWin(v, r, field="z", fun="count", pars=10, win="rectangle")
plot(r1)
points(x, y)
You can change fun to another function that works for you, and you can change the size of the moving window with pars.
Instead of a rectangle, you can also use a circle or an ellipse. The border of a circular window is equidistant from the center of the cells. In contrast, the border of rectangles are at a constant distance from the border of the grid cells in most directions (not at the corners). Here is an example.
r2 <- rasterizeWin(v, r, field="z", fun="count", pars=5.25, win="circle")
plot(r2)
You can also use buffers around each cell to get a window that is truly equidistant from each cell border.
r3 <- rasterizeWin(v, r, field="z", fun=length, pars=5, win="buf")
plot(r3)
In this case, because the buffer size is large relative to the cell size, the result is very similar to what you get when using a circular window. Using "circle" should be the fastest, and using "buffer" should be the slowest in most cases. The function should now in all cases be memory-safe, except, perhaps when using very large buffers (more could be done if need be).
Version 1.6-28 is currently the development version. You can install it with
install.packages('terra', repos='https://rspatial.r-universe.dev')
The approach you take seems to depend on what result you're looking for from the above and the relationship they have with each other.
library(terra)
`terra::buffer(` # both SpatVectx/SpatRastery, to distance in 'm'
`terra::buffer(` # that is meaningful
#take Rasty to SpatVecty
`terra::as.polygons(`, #then
`z<-terra::intersection(SpatVectx, SpatVecty)`
then back to SpatRastz? terra::mask or crop, might also be useful, again depending on where things are going next.
I'm currently struggling with some image analysis. I have images of zebrafish embryo vasculature, and I want to measure the distance between certain features (the highest point to the lowest etc).
I have processed the images to be more visible (higher contrast) using EBImage
.
I would appreciate any guidance.
Since you are using R and EBImage, I would presume that there is more analysis intended than just extracting measurements from an image. If that is all you intend, other software such as Fiji or the more streamline precursor, ImageJ, may be more user-friendly.
To answer the question, don't use display() for the image as you show here. Rather, use the plot() method that uses the option method = raster as the default. With the image plotted in a graphic window, you can use all the tools of R to interact with the plot. The resolution you have is determined by the size of your image and display. All values are returned in pixels and obviously need to be scaled appropriately.
This example uses locator() in a small helper function to measure diagonal distances between vascular junctions (?) in the image.
This simple helper function marks two points and measures the distance between the points. End the call to locator() with a right-click control-click or the escape key. In RStudio, you may have to explicitly press another button in the window and the points/lines may not be drawn until all calls to locator() are terminated.
p2p <- function(n = 512) # end with ctrl-click or Esc
{
ans <- numeric()
while (n > 0) {
# this call to locator places 2 points as crosses
# and connects them with a line
p <- locator(2, type = "o", pch = 3, col = "magenta")
if (is.null(p)) break
ans <- c(ans, sqrt(sum(sapply(p, diff)^2)))
n <- n - 1
}
return(ans) # return the vector of point-to-point distances
}
Now replot the image in the question (without the elements from the browser display) and then interact with the image.
plot(img) # not 'display(img)'
d <- p2p() # interact with the image, collecting distances
Here's the image after selecting six pairs of points with the distances measured between each pair of points.
round(d, 1)
> [1] 113.4 99.2 109.4 110.8 120.6 122.7
mean(d)
> 112.6736
Have fun!
Not dumb at all. Yes, it is in pixels—EBImage and R gives you fractional pixels.
I am brand new to R and in some desperate need of help. I have created a random matrix and need to re-classify it. Each pixel is randomly generated from 0-255 and I need to able to classify the 0-255 digits into 8 classifications. How would I do this? Any help would be greatly appreciated and I have placed my code below. I know I could use a raster but I am unsure on how to use them.
Thanks
par(mar=rep(0,4))
m=matrix(runif(100),10,10)
image(m,axes=FALSE,col=grey(seq(0,1,length=255)))
I didn't think your example adequately fit your description of the problem (since runif only ranges from 0-1 if the limits are not specified) so I modified it to fit the natural language features:
m=matrix(runif(100, 0, 255),10,10)
m[] <- findInterval(m, seq(0, 256, length=8) )
image(m,axes=FALSE,col=grey(seq(0,1,length=255)))
The "[]" with no indices preserves the matrix structure of the m object. The findInterval function lets you do the same sort of binning as cut, but it returns a numeric vector rather than the factor that cut would give.
I am trying to convey the concentration of lines in 2D space by showing the number of crossings through each pixel in a grid. I am picturing something similar to a density plot, but with more intuitive units. I was drawn to the spatstat package and its line segment class (psp) as it allows you to define line segments by their end points and incorporate the entire line in calculations. However, I'm struggling to find the right combination of functions to tally these counts and would appreciate any suggestions.
As shown in the example below with 50 lines, the density function produces values in (0,140), the pixellate function tallies the total length through each pixel and takes values in (0, 0.04), and as.mask produces a binary indictor of whether a line went through each pixel. I'm hoping to see something where the scale takes integer values, say 0..10.
require(spatstat)
set.seed(1234)
numLines = 50
# define line segments
L = psp(runif(numLines),runif(numLines),runif(numLines),runif(numLines), window=owin())
# image with 2-dimensional kernel density estimate
D = density.psp(L, sigma=0.03)
# image with total length of lines through each pixel
P = pixellate.psp(L)
# binary mask giving whether a line went through a pixel
B = as.mask.psp(L)
par(mfrow=c(2,2), mar=c(2,2,2,2))
plot(L, main="L")
plot(D, main="density.psp(L)")
plot(P, main="pixellate.psp(L)")
plot(B, main="as.mask.psp(L)")
The pixellate.psp function allows you to optionally specify weights to use in the calculation. I considered trying to manipulate this to normalize the pixels to take a count of one for each crossing, but the weight is applied uniquely to each line (and not specific to the line/pixel pair). I also considered calculating a binary mask for each line and adding the results, but it seems like there should be an easier way. I know that you can sample points along a line, and then do a count of the points by pixel. However, I am concerned about getting the sampling right so that there is one and only one point per line crossing of a pixel.
Is there is a straight-forward way to do this in R? Otherwise would this be an appropriate suggestion for a future package enhancement? Is this more easily accomplished in another language such as python or matlab?
The example above and my testing has been with spatstat 1.40-0, R 3.1.2, on x86_64-w64-mingw32.
You are absolutely right that this is something to put in as a future enhancement. It will be done in one of the next versions of spatstat. It will probably be an option in pixellate.psp to count the number of crossing lines rather than measure the total length.
For now you have to do something a bit convoluted as e.g:
require(spatstat)
set.seed(1234)
numLines = 50
# define line segments
L <- psp(runif(numLines),runif(numLines),runif(numLines),runif(numLines), window=owin())
# split into individual lines and use as.mask.psp on each
masklist <- lapply(1:nsegments(L), function(i) as.mask.psp(L[i]))
# convert to 0-1 image for easy addition
imlist <- lapply(masklist, as.im.owin, na.replace = 0)
rslt <- Reduce("+", imlist)
# plot
plot(rslt, main = "")
When using the image function in R it normalized the length of the dimensions of the input matrix so X and Y axes go from 0 to 1.
Is there a way of telling the image function not to normalize these numbers?
I need to do so in order to overlay different kinds of data and normalizing all these coordinates into the [0,1] space is very tedious.
EDIT: The answer provided by Greg explains the situation.
A reproducible example would be very helpful here. Generally if you only give image a z matrix then the function chooses default x and y values that work, I think this is what you are seeing. On the other hand if you give image an x vector and a y vector then it uses that information to construct the graph. If the x/y vectors have a length equal to the corresponding dimension of z then those values represent the centers of the rectangles, if x/y is 1 longer than the corresponding dimension of z then they represent the corners of the rectangles. This gives you a lot of control over the things that you mention.
If this does not answer the question then give us a self contained reproducible example to work with.
I am going to answer my question based on the answer Greg Snow provided in order to follow the best practice of this site as anything that provides information should be an answer.
If you do not provide the x nor y parameters to the image() function, then the range of the axes is from 0 to 1 as in the next example.
> image(volcano)
Then, if you want to locate a point of interest in the matrix in use, for the element of the matrix with [x,y] coordinates of [10,40] you need to do something like:
> points(x=10/length(volcano[,1]),y=40/length(volcano[1,]))
If the x and y parameters are specified, and (as Greg mentioned) they fit the dimensions of the matrix, then the axes will range withing the specified x and y vectors.
> dim(volcano)
[1] 87 61
> image(x=1:87, y=1:61, z=volcano)
> points(10,40)