Reputation: 31
I have a raster image (20231031_110019.png, although it could be any format, jpg, png, bmp) and would like to individually calculate the areas of discrete regions (I will call them 'islands' although they are actually areas of fungal growth on a petri dish) in this raster.
I am trying to use the spatstat package in R to do this. I found a post stating the following code can do this:
library(spatstat)
## create example data
dd <- dilation(redwood, 0.5, polygonal=FALSE)
## find connected components
P <- connected(dd)
## convert to a tessellation
B <- tess(image=P)
## compute area of each tile
answer <- tile.areas(B)
An owin object (redwood in the above instance) is needed to execute the dilation command and I cannot find a way to convert a raster into an owin object without the maptools package, which is no longer available on CRAN. I also have been unable to install an archived version maptools using devtools.
What would a workaround be for this?
Is there another way to convert a raster into a useable object?
Is there an equally straightforward method for calculating the areas of the fungal 'islands' in my raster image?
Many thanks!
Emily
Upvotes: 0
Views: 452
Reputation: 47016
With "terra" you can first identify the patches, and then count the number of cells in each patch. You could then multiply that number with the actual size of each pixel to get areas. "patches" are defined as islands of values in a sea of missing values; so in this case 255 needs to be set as the missing values flag.
library(terra)
x <- rast("https://i.sstatic.net/XjIVG.png")
NAflag(x) <- 255
p <- patches(x)
f <- freq(p)
f
# layer value count
#1 1 1 36015
#2 1 2 17132
#3 1 3 15571
#4 1 4 15345
#5 1 5 38977
#6 1 6 48821
#7 1 7 49660
#8 1 8 41246
Some illustration
bnds <- as.polygons(p)
area <- subst(p, f$value, f$count)
plot(trim(p, 100), main="ID")
plot(trim(area, 100), main="area")
lines(bnds, col="dark gray")
text(bnds, cex=.75)
To go from pixel counts to area you need to know the size of your image in the real world. Let's say it has a height of 20 and a width of 15 cm. In that case you could do
ext(x) <- c(0, 15, 0, 20)
cell_size <- prod(res(x))
f$area <- f$count * cell_size
head(f)
# layer value count area
#1 1 1 36015 0.900375
#2 1 2 17132 0.428300
#3 1 3 15571 0.389275
#4 1 4 15345 0.383625
#5 1 5 38977 0.974425
#6 1 6 48821 1.220525
With area expressed in cm2.
At that point you could also go the polygons-route suggested by Eduardo like this:
library(terra)
x <- rast("https://i.sstatic.net/XjIVG.png")
NAflag(x) <- 255
ext(x) <- c(0, 15, 0, 20)
crs(x) <- "local"
a <- as.polygons(x) |> disagg()
a$id <- 1:nrow(a)
a$area <- expanse(a, transform=FALSE)
plot(a, "area")
text(a)
head(data.frame(a))
# XjIVG id area
#1 0 1 0.900375
#2 0 2 0.428300
#3 0 3 0.389275
#4 0 4 0.383625
#5 0 5 0.974425
#6 0 6 1.220525
PS: Note that if this were geographic data you would want to set the coordinate reference system with crs<-
and use cellSize
to get the actual size of the cells in the real world and then use zonal
to compute the area of each patch.
Upvotes: 4
Reputation: 4507
You don’t need maptools
since your data is in png format.
The following modification of the spatstat
workflow should work
(png
package must be installed):
library(spatstat)
# Read in png as a matrix:
mat_png <- png::readPNG("XjIVG.png")
# There are different conventions of how a matrix should be layed out to
# represent points in a Cartesian coordinate system, so a conversion is needed:
mat_im <- transmat(mat_png, from = "European", to = "spatstat")
i <- as.im(mat_im)
# White pixels are 1 and black 0, so 1 should be treated as background:
cc <- connected.im(i, background = 1)
B <- tess(image=cc)
answer <- tile.areas(B)
# Areas of the components in number of pixels (multiply by pixel area to get
# physical area):
answer
#> 1 2 3 4 5 6 7 8
#> 41246 49660 48821 38977 15345 15571 36015 17132
# Visual confirmation of components:
plot(B)
Upvotes: 3
Reputation: 6524
If you want to use {spatstat}
then try this.
I couldn't get it to work with owin
but a point pattern seemed feasible.
IMG <- "XjIVG-smaller.png"
library(spatstat)
library(png)
image <- readPNG(IMG)
coords <- which(image == 1, arr.ind = TRUE)
coords <- as.data.frame(coords)
points <- ppp(coords$row, coords$col, window=owin(range(coords$row), range(coords$col)))
radius <- 5
dilated_points <- dilation(points, radius)
plot(dilated_points)
Upvotes: 0
Reputation: 6524
Try using the {mmand}
package.
IMG <- "XjIVG.png"
library(png)
library(mmand)
image <- readPNG(IMG)
display(image)
kernel <- shapeKernel(c(10, 10), type="box")
erode(image, kernel)
dilate(image, kernel)
The eroded image:
The dilated image:
🚨 The operations are being applied to the white areas of the image. If you want to erode/dilate the black bits then you need to either negate the image or just use the "other" operation (dilation on black is like erosion on white).
Upvotes: 0
Reputation: 101
I suggest a different approach. Instead of 'spatstat', consider using the 'terra' package to read the raster and vectorise it, with the functions 'rast', and as 'as.polygons'. Then you can convert to an 'sf' object, with the function 'st_as_sf', and use the function 'st_cast', which will convert the multipart polygon into multiple singlepart polygons. Finally, you can use the function 'st_area' to calculate the area of all "islands". Please see the example code below.
library(terra)
library(sf)
# Read the raster into a SpatRaster object
raster_i <- rast("XjIVG.png")
# Chek the raster resolution
# It by defaut shoud be 1x1
res(raster_i)
# Polygonyze the raster
poly_i <- as.polygons(raster_i, aggregate=T)
# Separate the polygons into individual features
poly_i <- st_cast(st_as_sf(poly_i[1]), "POLYGON")
# Calculate the features area
# Since the resolution is 1x1 the area will be in pixel squared.
# You can multiply by your scale factor squared to convert the pixel squared units to metric units.
poly_i$area <- st_area(poly_i)
# write the area values of all "islands" in a .csv table
write.csv(data.frame(poly_i)[,c(1,3)], "poly_i.csv")
# Plot the features with a color scale by area
plot(poly_i["area"])
The result of this code will be a 'sf' vector object with the islands' area in pixel-squared units. To convert to your measurement units, you can multiply these values by your scale factor squared. See the results plotted with a colour scale by the "islands" area:
If this was helpful, please remember to recommend the answer.
Upvotes: 5
Reputation: 263301
You can still download and install the {maptools}
package from the CRAN archive.
> install.packages("https://cran.r-project.org/src/contrib/Archive/maptools/maptools_1.1-8.tar.gz", repo=NULL, type="source")
> library(maptools)
Upvotes: 1