spectrakit Package – Part 4: Creating a Composite Image Grid in R with makeComposite()

This is part 4 of a series on the spectrakit R package. Start here for an introduction.

Overview

The makeComposite() function from the spectrakit R package creates a composite image by arranging multiple sub-images into a structured grid with flexible control over layout, resizing, spacing and multi-layer labeling. This function is useful when combining figures, such as spectra, plots or image panels, into a single publication-ready output, especially when consistent sizing and precise annotation are required. It helps automate what would otherwise be a manual and error-prone process of aligning images, adjusting dimensions and adding labels for reports, presentations or scientific figures.

Syntax

makeComposite(
  folder = ".",
  custom_order,
  rows,
  cols,
  spacing = 15,
  resize_mode = c("none", "fit", "fill", "width", "height", "both"),
  labels = list(),
  label_settings = list(),
  background_color = "white",
  desired_width = 15,
  width_unit = "cm",
  ppi = 300,
  output_format = "tiff",
  output_folder = NULL
)

Arguments

Argument name Type Description Default value (if provided, argument is optional)
folder Character Path to the folder containing images "." (working directory)
custom_order Character vector Ordered set of filenames (use NA for blank slots) Argument is required
rows Integer Number of rows in the grid Argument is required
cols Integer Number of columns in the grid Argument is required
spacing Integer Spacing (in pixels) between tiles
    15
    resize_mode Character Method to resize panels in the composite. One of:
    • "none" (keeps each panel at its original size)
    • "fit" (scales each panel to fit within the smallest width and height among all images, preserving aspect ratio; no cropping occurs, empty space may remain)
    • "fill" (scales each panel to completely cover the smallest width and height among all images, preserving aspect ratio, then crops any excess)
    • "width" (resizes each panel to match the minimum width among all images, preserving aspect ratio; height scales accordingly)
    • "height" (resizes each panel to match the minimum height among all images, preserving aspect ratio; width scales accordingly)
    • "both" (resizes each panel to exactly match the minimum width and height among all images, without preserving aspect ratio; may cause distortion)
    "none"
    labels List of up to 4 character vectors Labels to apply to each panel. Each vector corresponds to one label layer and must be the same length as the number of non-NA images. Use empty strings "" or NULL entries to omit specific labels
        list() (no labels)
        label_settings List of named lists Each named list specifies styling options for a label layer. Options include:
        • size (font size, e.g., 100)
        • color (font color, e.g., "black")
        • font (font family, e.g., "Arial")
        • boxcolor (background color behind text, e.g., "white", or NA for none)
        • location (offset from the gravity anchor, e.g., "+10+10")
        • gravity (placement anchor for the label, e.g., "northwest")
        • weight (font weight, e.g., 400 = normal, 700 = bold)
          list() (default styling is used)
          background_color Character Background color used for blank tiles and borders. Use "none" for transparency
            "white"
            desired_width Numeric Desired width of final image (in centimeters, inches or pixels) 15
            width_unit Character One of:
            • "cm" (centimeters)
            • "in" (inches)
            • "px" (pixels)
            "cm"
            ppi Numeric Resolution (pixels per inch) for output file
              300
              output_format Character File format for saving image. Examples: "tiff", "png", "pdf" "tiff"
              output_folder Character Path to folder where the composite image is saved. If NULL, the image is not saved and a magick image object is returned. If specified, the image is saved automatically; if ".", the image is saved in the working directory NULL

              Return value

              If output_folder = NULL, the function returns a magick image object. When output_folder is specified, the composite image is written directly to disk and returned invisibly. This function is therefore typically used in workflows where the generated composite is saved for external use (e.g., inclusion in reports, presentations or publications) rather than further manipulated within R.

              Practical examples

              The function takes a set of specified image files and generates a composite image grid. In this example, we generate 12 near-infrared (NIR) sample spectra using the NIRsoil dataset included in the prospectr package. The code below randomly selects 12 spectra from the dataset and saves each as a CSV file in a temporary folder on the Desktop, with named columns for wavelength and reflectance:
              # ---- Install & load package if needed ----
              # install.packages("prospectr")
              library(prospectr)
              
              # ---- Load dataset ----
              data(NIRsoil)
              
              # Spectral matrix and wavelength axis
              spectra_matrix <- NIRsoil$spc
              wavelength <- as.numeric(colnames(spectra_matrix))  # wavelength in nm
              
              # ---- Create TEMP folder on Desktop ----
              desktop_path <- file.path(path.expand("~"), "Desktop")
              temp_dir <- file.path(desktop_path, "TEMP")
              
              if (!dir.exists(temp_dir)) {
                  dir.create(temp_dir, recursive = TRUE)
              }
              
              # ---- Randomly select 12 spectra ----
              set.seed(260329)  # for reproducibility
              n_available <- nrow(spectra_matrix)
              if (n_available < 12) stop("Dataset contains fewer than 12 spectra.")
              
              selected_rows <- sample(seq_len(n_available), 12)
              
              # ---- Save each spectrum as a CSV file ----
              for (i in seq_along(selected_rows)) {
                  
                  reflectance <- spectra_matrix[selected_rows[i], ]
                  
                  output_df <- data.frame(
                      Wavelength = wavelength,
                      Reflectance = as.numeric(reflectance)
                  )
                  
                  file_path <- file.path(
                      temp_dir,
                      paste0("NIRsoil_spectrum_", i, ".csv")
                  )
                  
                  write.csv(
                      output_df,
                      file = file_path,
                      row.names = FALSE,
                      quote = FALSE
                  )
              }
              
              cat("12 random NIR spectra saved in:", temp_dir, "\n")
              

              Now we can use the plotSpectra() function (see spectrakit Package – Part 1) to read spectral data from the 12 files, and generate and save the corresponding plots, which we will then combine using makeComposite():
              # ---- Install & load package if needed ----
              # install.packages("spectrakit")
              library(spectrakit)
              
              setwd(temp_dir)
              
              plotSpectra(
                      normalization = "min-max",
                      x_config = c(1200, 2400, 200),
                      x_label = "Wavelength (nm)",
                      y_label = "Reflectance (a.u.)",
                      plot_mode = "individual",
                      display_names = TRUE,
                      output_folder = "."
              )

              The minimal working example of the makeComposite() function requires specifying only the custom_order, rows and cols arguments, along with the output_folder argument (otherwise the function returns the composite image), while all other arguments use their default values:
              makeComposite(custom_order = c("Spec_1.tiff", "Spec_2.tiff", "Spec_3.tiff", "Spec_4.tiff",
                                             "Spec_5.tiff", "Spec_6.tiff", "Spec_7.tiff", "Spec_8.tiff",
                                             "Spec_9.tiff", "Spec_10.tiff", "Spec_11.tiff", "Spec_12.tiff"),
                            rows = 4, cols = 3,           
                            output_folder = ".")

              This produces an image grid with 12 panels or tiles (4 rows × 3 columns), one for each plotted spectrum, and saves it as a TIFF file with the date and time of export included in the filename:


              By default, the spacing between tiles is set to 15 pixels, but it can be adjusted using the spacing argument.
              In this example, all images have identical dimensions, so no resizing is necessary (the default resize_mode is "none"). When images have different dimensions, the resize_mode argument can be set to one of five different methods to control how they are resized:
              • "fit" – scale panels to fit within the smallest width and height, preserving aspect ratio.
              • "fill" – scale panels to fully cover the smallest width and height, preserving aspect ratio.
              • "width" – resize panels to the minimum width, scaling height proportionally.
              • "height" – resize panels to the minimum height, scaling width proportionally.
              • "both" – resize panels to the exact minimum width and height (aspect ratio not preserved).
              One of the most powerful features of this function is the ability to add multiple labels to the tiles in the grid. For example, to add a letter to each panel for easy referencing of sub-figures, we can use the labels and label_settings arguments to control their appearance and placement:
              makeComposite(custom_order = c("Spec_1.tiff", "Spec_2.tiff", "Spec_3.tiff", "Spec_4.tiff",
                                             "Spec_5.tiff", "Spec_6.tiff", "Spec_7.tiff", "Spec_8.tiff",
                                             "Spec_9.tiff", "Spec_10.tiff", "Spec_11.tiff", "Spec_12.tiff"),
                            rows = 4, cols = 3,
                            labels = list(c("A","B","C","D",
                                            "E","F","G","H",   # A cleaner way to do this
                                            "I","J","K","L")), # would be: LETTERS[1:12]
                            label_settings = list(list(gravity = "northeast")),
                            output_folder = ".")

              Here, we chose to apply a single label layer (although up to four layers are possible) and leave all label settings at their default values, except for the placement (gravity), which we set to the upper-right corner ("northeast"):


              Finally, we can control the background color, width, resolution, and file format of the saved image using the background_color, desired_width, width_unit, ppi, and output_format arguments.
              The makeComposite() function can, of course, be used not only for grids of spectra but for any type of images. For example, the composite image below, created for comparing elemental maps, shows how various arguments can be adjusted, how NA can be used for blank slots, and how NULL entries can omit specific labels:
              makeComposite(
                custom_order = c(
                  "VIS.png", "Pb L.png", "Pb M.png", "Ca K.png",
                  "P K.png", "Cu K.png", "Fe K.png", "Mn K.png",
                  "K K.png", NA, NA, "Hg L.png"
                ),
                rows = 3,
                cols = 4,
              spacing = 12, resize_mode = "fit", # All images resized to smallest dimensions labels = list( c( "VIS", "Pb L", "Pb M", "Ca K", "P K", "Cu K", "Fe K", "Mn K", "K K", NULL, NULL, "Hg L" ), c( "a", "b", "c", "d", "e", "f", "g", "h", "i", NULL, NULL, "j" ) ), label_settings = list( # Top labels list( size = 140, font = "Arial", color = "black", boxcolor = "white", gravity = "northwest", location = "+15+15", weight = 400 ), # Bottom labels list( size = 204, font = "Arial", color = "white", gravity = "southeast", location = "+15+15", weight = 700 ) ), background_color = "gray", desired_width = 6, width_unit = "in", ppi = 150, output_format = "png", output_folder = "." )

              Rembrandt van Rijn. Portrait of a 39-year-old Woman, 1632. The Nivaagaard Collection, Denmark.

              Quick recap

              The makeComposite() function generates a labeled image grid from a set of images, allowing flexible control over layout, spacing, resizing, labels, background, and output settings. Key arguments include custom_order (the ordered image files), rows and cols (grid dimensions), resize_mode (to handle images of different sizes), labels and label_settings (for adding and customizing labels), and output_format/output_folder (for saving the composite). A typical usage pattern involves specifying the images and grid dimensions, adjusting spacing and resizing, optionally adding labels, and saving the final image to a desired format. In sum, makeComposite() streamlines the creation of clean, publication-ready image grids with minimal code.


              About the author
              Gianluca Pastorelli is a Heritage Scientist (Senior Researcher) working at the National Gallery of Denmark (SMK).

              Comments