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)
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)
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)
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.
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)
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)
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)
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")
Pad the top, right, bottom and left borders with different values, and set a different colour for each fill.
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)
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)
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.
load_stim_lisa() |>
crop_tem(100) |>
draw_tem()
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 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")
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 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 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")
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")
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
)
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")
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")
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.
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.