Skip to contents

This vignette demonstrates the functions you can use to resize, rotate, crop, pad, and align images. Templates are also changed with the images.

# load packages and set maximum plot width
library(webmorphR)
library(webmorphR.stim) # for extra stimulus sets
wm_opts(plot.maxwidth = 850)

Load your images

Load the demo stimuli, or read in your own images with read_stim().

stimuli <- demo_stim()

Resize

Resizing your images at the start of a chain can make everything go faster if you’re working with very large images, but can make the results blurry if you go too small.

stimuli |>
  resize(.1)
Resized Images

Resized Images

You can set the resize dimensions by proportions or pixels. Numbers less than 10 are interpreted as proportions, so the code above resized the images to 0.1 of their original size. Numbers larger than 10 are interpreted as pixels. If you set just width or just height, the other dimension is calculated proportionally to keep the aspect ratio.

stimuli |>
  resize(height = 200)
Resized Images by pixel

Resized Images by pixel

If you set both width and height, you may alter the aspect ratio. Make sure you really want to do this! Use to_size() instead if you want to resize and crop/pad all of your images to the same size without changing the image contents’ aspect ratio.

stimuli |>
  resize(width = 200, height = 100)
Resized Images by width and height

Resized Images by width and height

Rotate

Rotate images by degrees. Set the fill argument to fill in the corners with a specific colour, or use patch(stimuli) to sample the colour from 10x10 pixel square at the top left corner.

stimuli |>
  rotate(degrees = 45, fill = patch(stimuli))
Rotated Images and Templates

Rotated Images and Templates

If images aren’t centered, you may want to rotate around the center of the image or the center of the face, and may want to keep the original image dimensions or expand to the full rotated image dimensions.

To demonstrate, we’ll add some blue padding to the right side of the first demo image to make it off center. The contrasting fills used below are just to help you understand how each rotation option works; you’ll usually set fill to the image background colour.

offset <- stimuli[1] |> 
  draw_tem() |>
  pad(0, 500, 0, 0, fill = "dodgerblue") |>
  rep(4)

# set the rotations to show
degrees = seq(0, 90, by = 30)

By default, the rotate happens around the centre of the image. This can be undesirable if the face is off-centre.

rotate(offset, degrees, origin = "image", fill = "pink")

Set origin = "tem" to rotate around the centre of the template.

rotate(offset, degrees, origin = "tem", fill = "pink")

If you set keep_size = FALSE, the image and template will be rotated and keep the maximum rotated size (so it doesn’t matter what you set origin to).

rotate(offset, degrees, keep_size = FALSE, fill = "pink")

Horizontal Eyes

If you want to rotate each image by the amount that makes the eyes horizontal, use horiz_eyes().

unstandard <- demo_unstandard(1:5)
horiz <- horiz_eyes(unstandard, fill = patch(unstandard))

c(unstandard, horiz) |> plot(nrow = 2)
Make eyes horizontal with horiz_eyes()

Make eyes horizontal with horiz_eyes()

Crop

Crop images to a new width and height. You can specify sizes in proportions or pixels. Values < 2 will be interpreted as proportions.

crop(stimuli, width = .6, height = .8)
Cropped images with default center cropping.

Cropped images with default center cropping.

The x- and y-offsets default to NULL, which centres the cropping. You can set the offsets to a proportion or pixel value and the cropping will start there. The origin is the upper left corner, so setting x_off = 0 starts the cropping from the left side, and setting y_off = 0 starts the cropping from the top.

crop(stimuli, 
     width = 250, height = 300, 
    x_off = 0, y_off = 0)
Cropped images with offsets.

Cropped images with offsets.

Pad

Pad images with a border. Values less than 1 are interpreted as proportions. Set the colour with fill, otherwise its value defaults to the value of wm_opts("fill"), which is white unless you reset it.

pad(stimuli, 0.05, fill = "black")
Padded images with the same value for each side.

Padded images with the same value for each side.

Pad the top, right, bottom and left borders with different values, and set a different colour for each fill.

pad(stimuli,
    top = 10, right = 20, bottom = 30, left = 40,
    fill = c("dodgerblue", "hotpink"))
Padded images with different values for each side.

Padded images with different values for each side.

Or you can use the patch function to get the median colour from a patch of the image. The patch() function defaults to the top left 10 pixel square of each image.

rb <- load_stim_rainbow(1:8)

rb |>
  pad(50, fill = patch(rb)) |>
  plot(nrow = 2)
Cropped images with background matched to top left 10-pixel square

Cropped images with background matched to top left 10-pixel square

Or you can set the boundaries of the patch manually. The code below sets the patch colour to the average colour from to 10% of width and height in the centre of each image.

patch_fills <- patch(rb, 
                     width = .1, 
                     height = .1, 
                     x_off = .45, 
                     y_off = .45)

rb |>
  pad(50, fill = patch_fills) |>
  plot(nrow = 2)
Cropped Images with background matched to centre.

Cropped Images with background matched to centre.

Crop to Template

You can use the crop_tem() function to get the minimum and maximum x- and y-values from all of the image templates, then use that to set the cropping. In the example below, the images are cropped so there is 100 pixels of padding around the boundaries, calculated across all images.

Set each to TRUE to calculate the boundaries separately for each template in the list instead of the full set.

load_stim_lisa() |>
  crop_tem(20, each = TRUE) |>
  draw_tem()

To Size

The functionto_size() combines crop and resize to get a batch of images to the same size.

multisize <- demo_unstandard(1:5) |> pad(10, fill = "hotpink")

A 10-pixel pink border has been added to these original images so you can see what has been cropped in the subsequent images.

The images created above, with their width and height.

The images created above, with their width and height.

The code below resizes each image to fit into a 300 x 400 pixel image and fills in any extra background with blue. You’ll probably choose your background colour for a real stimulus set, but this makes the demo clearer.

to_size(multisize, 300, 400, 
        fill = "dodgerblue")
By default, to_size() will change relative sizes and cover the background without cropping any original image content.

By default, to_size() will change relative sizes and cover the background without cropping any original image content.

Set keep_rels = TRUE to bring the largest image to fit in the new size, resize the others proportionally, and pad.

to_size(multisize, 300, 400, 
        fill = "dodgerblue", 
        keep_rels = TRUE)
Set keep_rels = TRUE to keep relative size differences between original images.

Set keep_rels = TRUE to keep relative size differences between original images.

Set crop = TRUE to resize all images until they cover the new size and crop the “overhang”. There is no need to set the fill color for padding when crop is TRUE.

to_size(multisize, 400, 400, 
        crop = TRUE)
Set crop = TRUE to cover the new image size and crop off overhang.

Set crop = TRUE to cover the new image size and crop off overhang.

Set keep_rels = TRUE to bring the smallest image to cover the new size, resize the others proportionally, and crop any overhang.

to_size(multisize, 400, 400, 
        crop = TRUE, 
        keep_rels = TRUE)

Align

You can align a set of images on two points. By default, these are the first two points in the template, which correspond to the left and right eye in the standard FRL template, the 106-point Face++ templates, and the dlib auto_delin() templates. For chaotic historical reasons, the 83-point Face++ template has the eye points as 63 and 81 (pupils) or 57 and 50 (eye centre).

If you don’t set the x and y coordinates, the images will align to the average value across the set. The value of fill will fill in any borders from the rotation and cropping so the images stay the same size as the originals.

unstandard_men <- demo_unstandard(6:10)

unstandard_men |>
  align(fill = "dodgerblue")
Two-point alignment with align()

Two-point alignment with align()

You can manually set the values for the x and y coordinates, as well as the resulting width and height of the images. These values must be in pixels.

unstandard_men |>
  align(x1 = 350, y1 = 350, x2 = 450, y2 = 450,
        width = 800, height = 800, fill = "dodgerblue")
Two-point alignment with align(); setting values manually

Two-point alignment with align(); setting values manually

Instead of aligning to the average of all the images, you may want to align to a reference image. Set ref_img to the index or name of the chosen image.

aligned5 <- align(unstandard_men, ref_img = 5, 
                  fill = "dodgerblue") 

plot_rows(
  "Original images" = unstandard_men,
  "Aligned to image 5" = aligned5,
  top_label = TRUE
)
Align images to a reference image.

Align images to a reference image.

You can align images to a single point by setting pt1 and pt2 the same. For example, the tip of the nose is point 55 in this template, so the following code moves the tip of the nose of all images to the average position, and alters the image dimensions to the average, but doesn’t resize or rotate any of the faces

align(unstandard_men, pt1 = 55, pt2 = 55, 
      fill = "dodgerblue")
One-point alignment.

One-point alignment.

Procrustes Align

You can set the procrustes argument to TRUE to use Procrustes analysis to align the images and templates to fit the average shape (or a reference image) as closely as possible.

align(unstandard_men, procrustes = TRUE, fill = "dodgerblue")
Procrustes alignment.

Procrustes alignment.

Currently, webmorph does not have this capability, but you can download your images and tems, procrustes align them with the R package, save the images and templates, and upload them back to webmorph.

Chaining

You can also chain image manipulation commands.

stimuli |>
  rotate(c(45, -45)) |>
  crop(.5, .5) |>
  pad(0.1, fill = "dodgerblue")

Repeating images

You can use rep() to repeat images in a stimuli. Here, we repeat the faces 3 times each, apply 6 different rotations with different background colours, crop them to the same size, and plot them with 6 different template point colours.

rainbow <- c(pink = "#983E82",
             orange = "#E2A458",
             yellow = "#F5DC70",
             green = "#59935B",
             blue = "#467AAC",
             purple = "#61589C")

stimuli |>
  resize(0.5) |>
  rep(each = 3) |>
  rotate(seq(10, 60, 10), fill = rainbow) |>
  crop(250, 250, fill = rainbow) |>
  draw_tem(pt.color = rainbow, pt.alpha = 1, pt.size = 3) |>
  plot(nrow = 2)

Image functions

You can do so many things with the {magick} package that is installed with {webmorph}. The function image_func() is just a convenient wrapper that allows you to apply {magick} functions (or any other function that takes a magick image as the first argument) to each image in a stimulus list.


stimuli <- demo_stim("m_")
imglist <- c(
  image_func(stimuli, "blur", radius = 10, sigma = 5),
  image_func(stimuli, "charcoal", radius = 5, sigma = 2),
  image_func(stimuli, "oilpaint", radius = 5),
  image_func(stimuli, "implode", factor = 0.25),
  image_func(stimuli, "implode", factor = -0.25),
  image_func(stimuli, "negate"),
  # canny edge detection format is {radius}x{sigma}+{lower%}+{upper%}
  image_func(stimuli, "canny", geometry = "0x1+10%+30%"),
  image_func(stimuli, "edge", radius = 2),
  image_func(stimuli, "modulate", brightness = 80),
  image_func(stimuli, "modulate", brightness = 120),
  image_func(stimuli, "modulate", saturation = 0),
  image_func(stimuli, "modulate", saturation = 110)
)

labs <- c("Blur", "Charcoal", "Oilpaint", "Implode",
          "Explode", "Negate", "Canny", "Edge", 
          "20% Darker", "20% Brighter", "Greyscale", "Saturated")

imglist |>
  pad(70, 0, 0, 0, fill = "dodgerblue3") |>
  label(labs, color = "white") |>
  plot(nrow = 3)

The possible {magick} functions are listed by image_func_types(). Check the magick package help for more info on each (e.g., ?magick::image_oilpaint.

image_func_types()
#>  [1] "annotate"       "apply"          "average"        "background"    
#>  [5] "blur"           "canny"          "channel"        "charcoal"      
#>  [9] "colorize"       "combine"        "composite"      "contrast"      
#> [13] "convert"        "convolve"       "despeckle"      "edge"          
#> [17] "emboss"         "enhance"        "equalize"       "fill"          
#> [21] "flatten"        "fuzzycmeans"    "fx"             "fx_sequence"   
#> [25] "implode"        "lat"            "level"          "map"           
#> [29] "median"         "modulate"       "morphology"     "motion_blur"   
#> [33] "negate"         "noise"          "normalize"      "oilpaint"      
#> [37] "ordered_dither" "page"           "quantize"       "reducenoise"   
#> [41] "repage"         "separate"       "set_defines"    "shade"         
#> [45] "strip"          "threshold"      "transparent"

A potentially useful one is “fill” if your backgrounds are clean enough (the mask() functions are better for this, though). You need to experiment with the fuzz a bit. These functions are vectorised, so you can set a different point to start the flood fill or a different fuzz for each image.

demo_stim(1) |>
  rep(5)  |>
  image_func("fill", "white", point="+10+10", 
             fuzz = seq(5, 25, 5)) |> 
  label(text = paste("fuzz =", seq(5, 25, 5)))


This script took 0.3 minutes to render all the included images from scratch.