Create multi-panel figure using PNG/JPEG images
Asked Answered
M

4

5

Problem:

I want to make a multi-panel figure using PNG or JPEG images. The images were not created in R but I want to patch them together in R to form one figure. All the images are the same size/dimensions.

What I've tried:

library(png)

img1 <- readPNG("filepath/img1.png")
img2 <- readPNG("filepath/img2.png")

library(patchwork)

patch <- img1 + img2
patch

When I run this, I get the following error:

[ reached getOption("max.print") -- omitted 3 matrix slice(s) ]

I increased the max print multiple times (to ridiculously high numbers):

options(maxprint = 1000000000000)

But still get the same error.

I then tried making each image into a ggplot (without the points) using:

library(ggplot2)

img1plot <- ggplot() + 
background_image(img1) +
theme(plot.margin = margin(t=1, l=1, r=1, b=1, unit = "cm"))

Which returns the following error:

Error in background_image(d311) : 
  could not find function "background_image"

Is there another way to patch images together in R to make a figure?

Edit:

Based on the comment from @davidnortes, I tried the following:

p1 <- ggplot2::annotation_custom(grid::rasterGrob(img1,
                                            width=ggplot2::unit(1,"npc"),
                                            height=ggplot2::unit(1,"npc")),
                           -Inf, Inf, -Inf, Inf)

p2 <- ggplot2::annotation_custom(grid::rasterGrob(img2,
                                                  width=ggplot2::unit(1,"npc"),
                                                  height=ggplot2::unit(1,"npc")),
                               -Inf, Inf, -Inf, Inf)


library(cowplot)

plots <- plot_grid(
  p1, p2,
  labels = c('A', 'B'),
  align="hv"
)
plots

I get the following warning messages and the plot doesn't form:

Warning messages:
1: In as_grob.default(plot) :
  Cannot convert object of class LayerInstanceLayerggprotogg into a grob.
2: In as_grob.default(plot) :
  Cannot convert object of class LayerInstanceLayerggprotogg into a grob.
Moisten answered 22/6, 2020 at 14:27 Comment(3)
Sorry man! there was a bug in my answer with ggplot2; that's why you got the errors. I'm editing it nowDepersonalize
Your first attempt would've almost worked, BTW. With library(patchwork) you can do patch <- wrap_elements(img1) + wrap_elements(img2). You can't directly view it, but ggsave works fine. Also, make sure to set native = TRUE in the readPNG.Plumb
Thanks for adding this @Plumb !Moisten
D
5

I'm giving you the couple of alternatives that I Use the most:

Alternative 1: combination of ggplot2, grid and cowplot.

Each of your PNG image can be embedded in a ggplot object using:

ggplot2::ggplot() + ggplot2::annotation_custom(grid::rasterGrob(YourPNGimage,
                                                width=ggplot2::unit(1,"npc"),
                                                height=ggplot2::unit(1,"npc")),
                               -Inf, Inf, -Inf, Inf)

Then you can use cowplot::plot_grid() to arrange your plots.

Alternative 2: using magick package.

The package counts on its own functions to read images, thus we need to tweak your code a bit:

img1 <- magick::image_read("filepath/img1.png")
img2 <- magick::image_read("filepath/img2.png")

Then using functions like magick::image_append(c(img1, img2)) you can combine them. See the magick package documentation for more info.

Depersonalize answered 22/6, 2020 at 14:46 Comment(10)
Thank you, the second option using magick worked! However, the resolution is quite low (96 dpi). Is there a way to specify resolution (e.g. 300 dpi) like in ggsave?Moisten
In magick::image_write you have parameters like density and quality that help to to specify output qualities. Though it is possible that you cannot make high resolution renders from low resolution PNGs. The quality of the input is going to setup the quality of the output; Let's see if someone knows better than me on such regard :)Depersonalize
@jl748795 have a look: #56061125Depersonalize
Thanks @davidnortes! I imported 300 dpi PNG files, so theoretically the file size should be larger than 96 dpi when they are combined. But using "density =" allows me to account for this.Moisten
One more question, is it possible to choose the number of columns using this function? I can only find information on stack = TRUE, which stacks photos vertically instead of horizontally, but I want to have 3 columnsMoisten
@jl748795 most likely you are going to have to define as many objects as columns (using image_append), then append them altogetherDepersonalize
Sorry, I meant multiple rows and columns (I have 7 photos total), which I don't think is possible based on my searches, so I may need to find another solutionMoisten
I did not explain myself then. I meant that, as a workaround, you can create, for example, your rows using image_append() storing them as magick image objects. Then you stack those objects with image_append(c(row1,row2,...,row n), stack = TRUE)Depersonalize
@davidnortes, when I convert the png image to a ggplot object it results in loss of quality? is there a better way to read multiple png images and use the wrap_images function in patchwork to maintain high quality images ? My answer below results in high quality image, but adding text annotation such as subheads within a collage is complicated with magick. Thought converting to ggplot gives a better control . Any suggestions regarding this ?Oleaceous
I've looking around and I got to tell you that, sadly, I have no answer for you (yet)Depersonalize
H
2

You can also rbind image arrays. But as they are 3D (x,y,RGB) you must use abind function from abind package. along=1 to bind vertically, 2 horizontally.

Works because image have same size.

img1 <- readPNG("filepath/img1.png")
img2 <- readPNG("filepath/img2.png")
img12 <- abind::abind(img1,img2,along=1)
png::writePNG(img12,"filepath/img12.png")
Heterosexual answered 22/6, 2020 at 16:59 Comment(0)
O
2

You can use magick package in R to do a collage.

# read the the png files into a list
  pngfiles <-
  list.files(
    path = here::here("png_ouput_folder"),
    recursive = TRUE,
    pattern = "\\.png$",
    full.names = T
  )
 
 # read images and then create a montage
 # tile =2 , means arrange the images in 2 columns
 # geometry controls the pixel sixe, spacing between each image in the collage output. 

 magick::image_read(pngfiles) %>%
      magick::image_montage(tile = "2", geometry = "x500+10+5") %>%
      magick::image_convert("jpg") %>%
      magick::image_write(
        format = ".jpg", path = here::here(paste(name,"_collage.jpg",sep="")),
        quality = 100
      )
Oleaceous answered 16/11, 2020 at 8:4 Comment(1)
This works perfectly. Thank you!Pyridoxine
D
2

As others have suggested the magick package is much simpler than a low-level solutions using grobs and related. magick is powerful but IMHO the documentation is poor and very circular.

However, there is a simple solution to your question in the page for image_montage(). The most important argument is the geometry specification, which governs the spacing between the "tiles."

library(magick)
input <- rep(logo, 12)
image_montage(input, geometry = 'x100+10+10', tile = '4x3', bg = 'pink', shadow = TRUE)

To get no spacing at all, use geometry = '0x100+0+0', shadow = FALSE".

Despite answered 20/3, 2021 at 5:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.