How do I write a matrix as a raw bitmap in R - r

Not the problem:
There are lots of folks who ask variations on "how do I save a figure" where the figure has borders, annotations, and style; I'm not looking for any of that because I can do it in base, lattice, or ggsave. If you need me to, then I can make a list of 20 SO questions this isn't the same as.
tl;dr
I want to have a bitmap file where my matrix is the values. In python, using OpenCV I can read a matrix, and the pixel [1,1] is going to have a particular value. If I change it, and save it, then that intensity value has changed. How do I get that?
Details:
When I run this code:
set.seed(1)
img_data <- matrix(sample(x = 0:255, size = 228*228,replace = T),nrow = 228,ncol = 228)
image(img_data)
I get this image:
You can see the default annotation. Annotation can be removed.
set.seed(1)
img_data <- matrix(sample(x = 0:255, size = 228*228,replace = T),nrow = 228,ncol = 228)
image(img_data, xaxt='n',yaxt='n')
And this looks less bad.
but opened in mspaint, it shows the problem.
Problems:
White border around actual information
image size is 789x503
I want an image that is 228x228, and the value [1,1] of the image is value [1,1] of the matrix.
How in base, lattice, ggplot, or something else R, does one make that?
Update:
This almost works.
set.seed(1)
img_data <- matrix(sample(x = 0:255, size = 228*228,replace = T),nrow = 228,ncol = 228)
mar_old <- par("mar") #lets not permanently change values
xpd_old <- par("xpd") #lets not permanently change values
bmp(filename = "mytest.bmp", width = 227, height = 228, units = "px")
par(mar=rep(0, 4), xpd = NA)
image(img_data, bty ="n",axes=F,frame.plot=F, xaxt='n', ann=FALSE, yaxt='n', asp=1)
dev.off()
par(mar=mar_old, xpd=xpd_old)
It makes this image
It still leaves a white line on the right and lower edges when viewed in mspaint.
Perhaps bitmaps start their counting at zero??
Update2:
This almost works, and might be what I have to go with.
library(magick)
set.seed(1)
img_data <- array(sample(x = 0:255, size = 228*228*3,replace = T),dim = c(228,228,3))
img <- magick::image_read(img_data/255)
image_write(img, path = "mystes3.bmp", format = "bmp")
It gives this:
And in mspaint:
It has to have 3 layers, RGB (rgba?), to get converted. This means it is a 3d array and not a 2d matrix. It gets the size right in that it doesn't add padding.

You need to set the useRaster parameter to TRUE in image():
set.seed(1)
img_data <- matrix(sample(x = 0:255, size = 228*228,replace = T),nrow = 228,ncol = 228)
mar_old <- par("mar") #lets not permanently change values
xpd_old <- par("xpd") #lets not permanently change values
bmp(filename = "mytest.bmp", width = 228, height = 228, units = "px")
par(mar=rep(0, 4), xpd = NA)
image(img_data, bty ="n",axes=F,frame.plot=F, xaxt='n', ann=FALSE, yaxt='n', asp=1, useRaster = T)
dev.off()
par(mar=mar_old, xpd=xpd_old)
I also corrected the height to 228 instead of 227.
Did a smaller image:
With useRaster = T
Without useRaster:
Without useRaster you even lose a row and a column.

Related

Plotting 3 columns of rasters and saving into pdf with base R

Sorry this example is not entirely reproducible (I do not provide exact input data), but hopefully the example will be clear.
In short, I would like to save three maps in a wide pdf format, so All three maps can be shown with a desired extent + there is an overarching title above (but I don't want it to take up half of the page).
I am really struggling with setting it up properly:
pdf("plots1.pdf",width = 30/2.54,height = 20/2.54)
par(mfrow = c(2,3))
layout(matrix(c(1,1,1,2,3,4), 2, 3, byrow = TRUE))
plot.new()
text(0.5,0.5,"Africa, Params_1",cex=2,font=2)
# plot.new()
# plot.new()
plot(r2_list[[1]], xlim = Region[[g]][1:2], ylim = Region[[g]][3:4],
breaks=cuts, col = plasma(21), main = variable[1],legend=FALSE)
plot(wrld_simpl,add=TRUE)
plot(r2_list[[2]], xlim = Region[[g]][1:2], ylim = Region[[g]][3:4],
breaks=cuts, col = plasma(21), main = variable[2],legend=FALSE)
lines(wrld_simpl)
plot(r2_list[[3]], xlim = Region[[g]][1:2], ylim = Region[[g]][3:4],
breaks=cuts, col = plasma(21), main = variable[3])
lines(wrld_simpl)
dev.off()
I would also like to try to print 6 plots, again - with overarching titles but I fail miserably.
Help will be much appreciated.
Add heights= to your layout call.
layout(matrix(c(1,1,1, 2,3,4), byrow=TRUE, nr=2), heights = c(1, 8))
opar <- par(mar=c(0,0,0,0))
plot.new()
text(0.5,0.5,"Africa, Params_1",cex=2,font=2)
par(opar)
plot(1:20)
plot(3:99)
plot(1:2)
(Blue boundaries below added externally, for reference only.)
Without heights
If I remove the heights= from the code above, I see this:
With heights
(You will want to play with the actual values based on your canvas size, etc.)

spplot issue with legend range and colors distribution

I have problem with correct color range on my plot and legend.
This is code which I use:
data.ch4 <- read.csv2("v42_CH4_1970_TOT.txt",skip = 3,stringsAsFactors = FALSE, header = F)
num_data <- data.frame(data.matrix(data.ch4))
library(maptools)
library(lattice)
library(png)
#map loading
map1 <- readShapePoly("CNTR_2014_03M_SH/Data/CNTR_RG_03M_2014.shp")
coordinates(num_data) <- ~V2+V1
gridded(num_data) <- TRUE
#plotting
png(file="Map2.png",width=35,height=30,unit="cm", res=200, type = "cairo")
spplot(num_data["V3"], xlim=c(-5,35), ylim=c(35,70),
sp.layout = list("sp.polygons",map1),contour=F)
dev.off()
Here is the file with data: https://www.sendspace.com/file/hjtatp (compressed beacuse normally it weights 57 mb)
Map from here (but map has secondary priority, it can be skipped)
This is how it looks like without any scale modifications:
So everything is blue. Obviously there is to big scale distance, from min to max value. I would like to fix the scal, for e.g. last value would by "higher than x". I tried to do this like this:
So now this looks much better. This is how I did it:
#Fixed breakpoints (?)
at <- c(0e+0, 1.5e-5, 1.0e-4, 1.0e-3, 1.0e-2, 1.0e-1, 1.0e+0, 2.0e+0, 1.0e+1, 1.0e+2, 2.0e+2,5.0e+2)
spplot(num_data["V3"], xlim=c(-5,35), ylim=c(35,70),
sp.layout = list("sp.polygons",map1),
contour=F,
at=at) #right there
So I added manually the at values (but not accurate scale). Everything looks much better but..
As you can see, scale on the right is not uniformly distributed. I cannot see any blue-purple colors, only orange and yellow.
Also some spots on the map are bright yellow (Germany area), because the values are highest here, but sadly there is no such color on the scale.
Probably I didn't do it properly. I don't know how to set the scale to looks good. I would like to have scale like this:
I achieved this by adding:
spplot(num_data["V3"], xlim=c(-5,35), ylim=c(35,70),
sp.layout = list("sp.polygons",map1),
contour=F,at=at,
colorkey=list(at=seq(0, 400, 30)) #right there
)
But again, this is just fake scale, it won't work.
And the second fast question: How to add country contours on top of spplotted data? Because now contours are burried under the colorful data :c
The data converted to factor gives regular intervals to the legend. And you can change labels and its positions by colorkey = list(labels = list(at = ..., labels = ...)).
[Edited; (I noticed that some values are over 500, sorry)]
## convert numeric to factor
num_data#data$cutV3 <- cut(num_data#data$V3, breaks = c(at, Inf)) # I modified the breaks
spplot(num_data["cutV3"], xlim=c(-5, 35), ylim=c(35, 70),
colorkey = list(height = 1, labels = list(at = seq(0.5, length(at) -0.5), labels = at)),
sp.layout = list("sp.polygons", map1, first = F), contour = F) # drawn after main plot

How to change line width of map.scale from package maps?

I'm using map.scale() from package maps to create a scale in a map. The following code creates an example of what I'm looking for:
library(maps)
png("example.png", width = 6000, height = 6000)
map(database = "world", regions = 'Brazil',fill = T)
map.scale(-43, -30,
ratio = F,
cex = 9,
lheight= 1,
pos =1,
offset = 1)
dev.off()
As I'm creating a high resolution png image, the line width of the scale produced is not appropriate. You can check it by zooming in the lower right corner of the map.
Is there any parameter that I'm missing to increase the width of the scale?
The reason you can't modify the line width (it's the lwd argument you should be looking for not line height here) is that it isn't applied in the map.scale() function and passed on to the lines() call for that part of the scale. We can, however, programmatically add this functionality to the function by doing something like this:
library(maps)
t <- capture.output(map.scale)
## Strip trailing text:
t <- t[-length(t)]
## Find lines() call and add in a line width argument:
t <- gsub("(lines\\(linexy)", "\\1, lwd=lwd", t)
t <- gsub("(\\.\\.\\.)", "\\1, lwd=10", t)
t <- paste(t, collapse="\n")
eval( parse(text=paste0( "map.scale.new <- ", t )) )
## Use our new map.scale.new() function with lwd= parameter:
png("example.png", width = 6000, height = 6000)
map(database = "world", regions = 'Brazil',fill = T)
map.scale.new(
-43, -30,
ratio = F,
cex = 9,
lwd= 10,
pos =1,
offset = 1
)
dev.off()
You can modify the lwd= to now suit your fancy.

Set plot margin of png plot device using par

I've created a choropleth of Brazil. When saving the plot in .png, the upper and the lower part of the plot are lost (covered). Here are the lines to save the plot.
plot.new()
par(omi=c(0,0,0,0), mgp=c(0,0,0),mar=c(0,0,0,0) , family = "D")
par(mfrow=c(1,1),cex=1,cex.lab = 0.75,cex.main=0.2,cex.axis=0.2)
png(filename = "map_cons_g.png", width = 6,height = 6, units = "in", res = 600)
plot(c(-75,-35),c(0,-30),type="n",axes=FALSE,xlab="",ylab="",asp=1.2)
plot(Brazil,col=cols[Brazil$Cons.g_ri],add=TRUE,border="black",lwd=0.5)
dev.off()
For saving the plot without losing the upper and the lower part of the map, I must change the coordinates to add white space at the bottom and at the top (i.e. replace c(0,-30) by c(5,-33)):
plot.new()
par(omi=c(0,0,0,0), mgp=c(0,0,0),mar=c(0,0,0,0) , family = "D")
par(mfrow=c(1,1),cex=1,cex.lab = 0.75,cex.main=0.2,cex.axis=0.2)
png(filename = "map_cons_g.png", width = 6,height = 6, units = "in", res = 600)
plot(c(-75,-35),c(5,-33),type="n",axes=FALSE,xlab="",ylab="",asp=1.2)
plot(Brazil,col=cols[Brazil$Cons.g_ri],add=TRUE,border="black",lwd=0.5)
dev.off()
This works in the sense that I can see the full map but the map then does not use all the available area in the figure. It seems that there are some margin in the upper and the lower part of the figure when saving the plot. I've never had that problem with other types of plot.
Sorry, I don't have enough "reputation" to post images to show you how the maps look like.
Any idea of how to fix this?
Edit:
The comments below got me searching more into the problem and I finally found a fix. I apologize as I now realized that I did not understand the source of the problem and thus did not explain as best as I could have,
It seems that png resets the outer margin of the plot. Thus, even though I had set omi=c(0,0,0,0), those were not the value used by the png command in saving the plot. The solution was to set the plot parameters after calling png so save the figure.
plot.new()
png(filename = "map_cons_g.png", width = 6,height = 6, units = "in", res = 600)
par(omi=c(0,0,0,0), mgp=c(0,0,0),mar=c(0,0,0,0) , family = "D")
par(mfrow=c(1,1),cex=1,cex.lab = 0.75,cex.main=0.2,cex.axis=0.2)
plot(c(-75,-35),c(5,-33),type="n",axes=FALSE,xlab="",ylab="",asp=1.2)
plot(Brazil,col=cols[Brazil$Cons.g_ri],add=TRUE,border="black",lwd=0.5)
dev.off()
From Details in ?par:
Each device has its own set of graphical parameters.
Thus, even though I had set the outer margin of the plot in par (omi = c(0,0,0,0)), those value were overwritten by the parameters in png when saving the plot.
The solution was to set the margin parameters in par after calling png
plot.new()
# first open png device...
png(filename = "map_cons_g.png", width = 6,height = 6, units = "in", res = 600)
# ...then set par
par(omi = c(0,0,0,0), mgp = c(0,0,0), mar = c(0,0,0,0), family = "D")
par(mfrow = c(1, 1), cex = 1, cex.lab = 0.75, cex.main = 0.2, cex.axis = 0.2)
plot(c(-75, -35), c(5, -33), type = "n", axes = FALSE, xlab = "", ylab = "", asp = 1.2)
plot(Brazil, col = cols[Brazil$Cons.g_ri], add = TRUE, border = "black", lwd = 0.5)
dev.off()

R legend issue, symbols of points are masked by lines

Is there a way to draw the lines in such a way that they would start on the side of the points, or allow the symbols to be in foreground?
My solution was to make the symbols bigger and more visible.
Edit 1: it's for plot {graphics} of the R program.
Edit 2: the code per popular request.
legend(2,.4,bty='n', c('sugar','citrus','none'), pch=c('s','c','u'), pt.bg='white',lty= c(1,2,3), lwd=1.5, title="Condition",pt.cex=c(1.5),cex=1.5)
Edit 3: This is solved for plot(type='b') but somehow not for legend.
Thanks for reading!
The only thing I can come up with is to manually finagle the dash lengths until they end up looking the way you want them. For instance, this:
> plot(1,1)
> legend(c("A", "B"), col = 1:2, x = 1, y = .8, lty="99", pch=1:2)
produces the image below.
The lty parameter allows you to specify the lengths of lines and dashes as hex characters. In this case, it's saying to create a line of length 9 then create a space of length 9 then repeat. It looks like 9 is about the best fit to space around a normal pch symbol.
Note that you'd probably need to adjust this depending on the size of the image, symbol, etc. My advice ultimately would be to export the image from R and touch up the image to meet your needs in graphic editing software.
Going with the suggestion by #JeffAllen, here is a way to get what I think you might want. It requires modifying the legend() function to return the position of the points (these are given by x1 and y1 in body(legend)[[46]]).
legend2 <- legend
body(legend2)[[49]] <- quote(
invisible(list(rect = list(w = w, h = h, left = left, top = top),
text = list(x = xt, y = yt), points = list(x = x1, y = y1)))
)
Make a plot:
plot(-100:100, -100:100, type = "b")
While drawing the legend, draw white circles (pch = 21 with pt.bg = 'white') over the lines, and assign the values invisibly returned by legend2() to an object. Note also the changes to pt.lwd and pt.cex.
myLegend <- legend2(1, .8, bty = 'n', c('sugar','citrus','none'), pch = 21,
pt.bg = 'white', pt.lwd = 0, lty = c(1, 2, 3), lwd = 1.5, title = "Condition",
pt.cex = c(1.8), cex = 1.5)
Finally, draw the characters you'd like to use in the legend using points(), supplying the x and y values from the object myLegend.
points(myLegend$points$x, myLegend$points$y, pch = c('s','c','u'), cex = 1.5)
And this should get you something like:
You could also use the filled points offered by R (pch=21:25) and specify the fill color using pc.bg which gets passed to the points call when creating a legend.
plot(1,1)
legend(c("A", "B"), col = 1:2, x = 1, y = .8, lty=1, pt.bg=1:2, pch=21:22)
generates the following:

Resources