---
title: "Getting started"
subtitle: "imagefluency: Image Statistics Based on Processing Fluency"
description: "Getting started with the imagefluency package."
date: "`r format(Sys.Date(), '%B %d, %Y')`"
output:
  rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{getting-started}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
bibliography: imagefluency.bib
csl: https://www.zotero.org/styles/springer-socpsych-brackets
link-citations: yes
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
options(rmarkdown.html_vignette.check_title = FALSE)
```

<style>
figure {
    display: inline-block;
    horizontal-align: center;
    margin: 2px;
}
figure img {
    vertical-align: bottom;
}
figure figcaption {
    text-align: center;
}
</style>

```{r setup, include=FALSE}
library(imagefluency)

bike <- 'example_images/bike.jpg'
berries <- 'example_images/berries.jpg'
bridge <- 'example_images/bridge.jpg'
fireworks <- 'example_images/fireworks.jpg'
office <- 'example_images/office.jpg'
rails <- 'example_images/rails.jpg'
romanesco <- 'example_images/romanesco.jpg'
sky <- 'example_images/sky.jpg'
trees <- 'example_images/trees.jpg'
valley_green <- 'example_images/valley_green.jpg'
valley_white <- 'example_images/valley_white.jpg'

imglist <- list(img_read(valley_white), img_read(fireworks),
                img_read(valley_green))
```


## Introduction

### Motivation: Why create yet another R package

Over the last decades, the amount of data generated is growing rapidly, predominantly due to digitalization.
Most of today’s data is unstructured, and this share is increasing.
Given that unstructured data is rich, these data could provide rich insights for scientific research from a variety of fields and practice alike.
Especially images (i.e., visual stimuli) are recognized as valuable source of information.

At the same time, vision research and research in psychology shows that even simple changes in low-level image features (like symmetry, contrast, or complexity) can have a tremendous effect on a variety of human judgments.
As an example, a statement like "Nut bread is healthier than potato bread" is more likely to be perceived as true when presented in a color that is easy to read against a white background (high contrast) instead of being presented in a color that is difficult to read against a white background (low contrast; cf. @Hansen2008).
Thus, it might be useful to estimate and control for differences in such low-level visual features in any research that includes visual stimuli.

**imagefluency** is an `R` package for such low-level image scores based on processing fluency theory.
The package allows to get scores for several basic aesthetic principles that facilitate fluent cognitive processing of images:
contrast, complexity / simplicity, self-similarity, symmetry, and typicality.


### Why and when to use the package

Possible applications include:

* stimulus selection in experiments (e.g., testing brand logos, evaluate product designs, online display ads)
* as control variables in statistical or prediction models
* linking image fluency scores to outcomes of interest (e.g., how should a typical product packaging look like, do simpler images get more or less attention on a website, ...)
* (interpretable) image features in simple machine learning models, e.g. SVM image classifier


## Theoretical background

The most prevailing explanation for how low-level image features affect human judgments is based on processing fluency theory [@Reber2004].
Processing fluency describes the ease of processing a stimulus [@Schwarz2004], which happens instantaneously and automatically [@Graf2015].
Higher processing fluency results in a gut-level positive affective response [@Winkielman2001].
Notably, a rich body of literature has shown that processing fluency effects have an impact on a variety of judgmental domains in our everyday life, including how much we like things, how much we consider statements to be true, how trustworthy we judge a person, how risky we think something is, or whether we buy a product or not (for a review, see @Alter2009).

Several stimulus features have been proposed that result in increased fluency.
In particular, visual symmetry, simplicity, (proto-)typicality, and contrast were identified to facilitate processing [@Reber2004].
Recent studies further discuss self-similarity in light of fluency-based aesthetics [@Joye2016; @MayerPACA2018], a concept which has been studied for example in images of natural scenes [@Simoncelli2003].
Self-similarity can be described as self-repeating patterns within a stimulus.
A typical example are the leaves of ferns that feature the same shape regardless of any magnification or reduction (i.e., scale invariance).
Another prominent example is romanesco broccoli with its self-similar surface.

Extracting image features for contrast, self-similarity, simplicity, symmetry, and typicality therefore constitute the core purpose of the `imagefluency` package.

## Package overview

### Main functions

* `img_contrast()` visual contrast of an image
* `img_complexity()` visual complexity of an image (opposite of simplicity)
* `img_self_similarity()` visual self-similarity of an image
* `img_simplicity()` visual simplicity of an image (opposite of complexity)
* `img_symmetry()` vertical and horizontal symmetry of an image
* `img_typicality()` visual typicality of a list of images relative to each other

### Other helpful functions

* `img_read()`  reads bitmap images into R
* `rgb2gray()`  converts images from RGB into grayscale (might speed up computation)
* `run_imagefluency()`  to launch a Shiny app locally on your computer for an interactive demo of the main functions (alternatively, visit the online version at [posit.cloud](https://019d45ea-53cc-6381-9ecf-5151194cda1f.share.connect.posit.cloud/))


### Installation

You can install the current stable version from CRAN.
```{r, eval=FALSE}
install.packages('imagefluency')
```

To download the latest development version from Github use the `install_github` function of the `remotes` package.
```{r, eval=FALSE}
# install remotes if necessary
if (!require('remotes')) install.packages('remotes')
# install imagefluency from github
remotes::install_github('stm/imagefluency')
```

After installation, the `imagefluency` package is loaded the usual way by calling `library(imagefluency)`.
The `img_read()` function can be used to read an image into R.
Just like with reading in a dataset, `img_read()` expects the path to the file as input,
e.g., `img_read('C:/Users/myname/Documents/myimage.jpg')`.
Currently supported file formats are bmp, jpg, png, and tif.

Use the following link to report bugs/issues: <https://github.com/stm/imagefluency/issues>

## Using imagefluency

`imagefluency` allows to get scores for five image features that facilitate fluent processing of images:
contrast, complexity / simplicity, self-similarity, symmetry, and typicality.

To use the imagefluency package, first load the library.

```{r, eval=FALSE}
library(imagefluency)
```

### Contrast

The function `img_contrast()` returns the contrast of an image.
Most research defines contrast in images as the root-mean-squared (RMS) contrast which is the standard deviation of the normalized pixel intensity values [@Peli1990]: $\sqrt{\frac{1}{M N}\sum_{i=0}^{N-1}\sum_{j=0}^{M - 1}(I_{ij} - \bar{I})^2}$.
The RMS of an image as a measure for visual contrast has been shown to predict human contrast detection thresholds well [@Frazor2006].
Therefore, the function calculates contrast by computing the RMS contrast of the input image.
Consequently, a higher value indicates higher contrast.
The image is normalized if necessary (i.e., normalization into range [0, 1]).
For color images, a weighted average between color the channels is computed (cf. @MayerPACA2018).

Note that in the following, example images that come with the package are used.
Moreover, the images can be displayed using the `grid.raster()` function from the `grid` package.

```{r, eval=FALSE}
# Example image with relatively high contrast: berries
berries <- img_read(system.file('example_images', 'berries.jpg', package = 'imagefluency'))
# display image
grid::grid.raster(berries)
# get contrast
img_contrast(berries)
```

```{r, eval=FALSE}
# Example image with relatively low contrast: bike
bike <- img_read(system.file('example_images', 'bike.jpg', package = 'imagefluency'))
# display image
grid::grid.raster(bike)
# get contrast
img_contrast(bike)
```

Calculating the contrast scores for the two images gives the following result:

<center>
<figure>
  <img src="`r bike`"/>
  <figcaption>score: `r sprintf("%.2f",img_contrast(img_read(bike)))`</figcaption>
</figure>
<figure>
  <img src="`r berries`"/>
  <figcaption>score: `r sprintf("%.2f",img_contrast(img_read(berries)))`</figcaption>
</figure> 
</center>


### Complexity / Simplicity

The function `img_complexity()` returns the visual complexity of an image. Algorithmic information theory indicates that picture complexity can be measured accurately by image compression rates because complex images are denser and have fewer redundancies [@Donderi2006; @Landwehr2011].
Therefore, the function calculates the visual complexity of an image as the ratio between the compressed and uncompressed image file size. Thus, the value does not depend on image size.

The function takes the file path of an image file (or URL) or a pre-loaded image as input argument and returns the ratio of the compressed divided by the uncompressed image file size.
The complexity values are naturally interpretable and can range between almost 0 (virtually completely compressed image, thus extremely simple image) and 1 (no compression possible, thus extremely complex image).
The function offers to use different image compression algorithms like `jpg`, `gif`, or `png` with `algorithm = 'zip'` as default (for a discussion about the different algorithms, see @MayerPACA2018).

As most compression algorithms do not depict horizontal and vertical redundancies equally, the function includes an optional `rotate` parameter (default: `FALSE`).
Setting this parameter to `TRUE` additionally creates a compressed version of the *rotated* image.
The overall compressed image's file size is computed as the minimum of the original image's file size and the file size of the rotated image.

The function `img_simplicity()` returns the visual simplicity of an image.
Image simplicity is the complement to image complexity and therefore calculated as 1 minus the complexity score (i.e., the compression rate).
Values can range between 0 (no compression possible, thus extremely complex image) and almost 1 (virtually completely compressed image, thus extremely simple image).

```{r, eval=FALSE}
# Example image with high complexity: trees
trees <- img_read(system.file('example_images', 'trees.jpg', package = 'imagefluency'))
# display image
grid::grid.raster(trees)
# get complexity
img_complexity(trees)
```

```{r, eval=FALSE}
# Example image with low complexity: sky
sky <- img_read(system.file('example_images', 'sky.jpg', package = 'imagefluency'))
# display image
grid::grid.raster(sky)
# get complexity
img_complexity(sky)
```

Calculating the complexity scores for the two images gives the following result:

<center>
<figure>
  <img src="`r trees`"/>
  <figcaption>score: `r sprintf("%.2f",img_complexity(img_read(trees)))`</figcaption>
</figure>
<figure>
  <img src="`r sky`"/>
  <figcaption>score: `r sprintf("%.2f",img_complexity(img_read(sky)))`</figcaption>
</figure> 
</center>



### Self-similarity

The function `img_self_similarity()` returns the self-similarity of an image.
Self-similarity can be measured with the Fourier power spectrum of an image.
Previous research has identified that the spectral power of natural scenes falls with spatial frequencies ($f$) according to a power law ($\frac{1}{f^p}$) with values of $p$ near the value 2, which indicates scale invariance (for a review, see @Simoncelli2001).
Therefore, the function computes self-similarity via the slope of the log-log power spectrum of the image using OLS.

The value for self-similarity that is returned by the function is calculated as $\text{self-similarity} = |\text{slope} + 2| * (-1)$.
That is, the measure reaches its maximum value of 0 for a slope of $-2$, and any deviation from $-2$ results in negative values that are more negative the higher the deviation from $-2$.
Thus, the range of the self-similarity scores is $-\infty$ to $0$. For color images, the weighted average between each color channel's values is computed.

It is possible to get the raw regression slope (instead of the transformed value which indicates self-similarity) by using the option `raw = TRUE`. More options include the possibility to plot the log-log power spectrum (`logplot = TRUE`) and to base the computation of the slope on the full frequency spectrum (`full = TRUE`). See the function's help file for details (i.e., `?img_self_similarity`).


```{r, eval=FALSE}
# Example image with high self-similarity: romanesco
romanesco <- img_read(system.file('example_images', 'romanesco.jpg', package = 'imagefluency'))
# display image
grid::grid.raster(romanesco)
# get self-similarity
img_self_similarity(romanesco)
```

```{r, eval=FALSE}
# Example image with low self-similarity: office
office <- img_read(system.file('example_images', 'office.jpg', package = 'imagefluency'))
# display image
grid::grid.raster(office)
# get self-similarity
img_self_similarity(office)
```

Calculating the self-similarity scores for the two images gives the result below. The score for the romanesco broccoli is much closer to the maximum possible value of 0, hence much more self-similar.

<center>
<figure>
  <img src="`r romanesco`"/>
  <figcaption>score: `r sprintf("%.2f",img_self_similarity(img_read(romanesco)))`</figcaption>
</figure>
<figure>
  <img src="`r office`"/>
  <figcaption>score: `r sprintf("%.2f",img_self_similarity(img_read(office)))`</figcaption>
</figure> 
</center>


### Symmetry

The function `img_symmetry()` returns the vertical and horizontal symmetry of an image as a numeric value between 0 (not symmetrical) and 1 (perfectly symmetrical). 

Symmetry is computed as the correlation of corresponding image halves (i.e., the pairwise correlation of the corresponding pixels, cf. @Mayer2014).
As the perceptual mirror axis is not necessarily exactly in the middle of a picture, the function detects in a first step the 'optimal' mirror axis by estimating several symmetry values with different positions for the mirror axis.
To this end, the mirror axis is automatically shifted up to 5% and to the right (in the case of vertical symmetry; analogously for horizontal symmetry).
In the second step, the overall symmetry score is computed as the maximum of the symmetry scores given the different mirror axes.
For color images, the weighted average between each color channel's values is computed.
See @MayerPACA2018 for details.

The function further has two optional logical parameters: `vertical` and `horizontal` (both `TRUE` by default). If one of the parameter is set to `FALSE`, the vertical or horizontal symmetry is not computed, respectively. See the function's help file (i.e., `?img_symmetry`) for information about the additional options `shift_range` and `per_channel`.

```{r, eval=FALSE}
# Example image with high vertical symmetry: rails
rails <- img_read(system.file('example_images', 'rails.jpg', package = 'imagefluency'))
# display image
grid::grid.raster(rails)
# get only vertical symmetry
img_symmetry(rails, horizontal = FALSE)
```

```{r, eval=FALSE}
# Example image with low vertical symmetry: bridge
bridge <- img_read(system.file('example_images', 'bridge.jpg', package = 'imagefluency'))
# display image
grid::grid.raster(bridge)
# get only vertical symmetry
img_symmetry(bridge, horizontal = FALSE)
```

Calculating the vertical symmetry scores for the two images gives the following result:

<center>
<figure>
  <img src="`r rails`"/>
  <figcaption>score: `r sprintf("%.2f",img_symmetry(img_read(rails), horizontal = FALSE))`</figcaption>
</figure>
<figure>
  <img src="`r bridge`"/>
  <figcaption>score: `r sprintf("%.2f",img_symmetry(img_read(bridge), horizontal = FALSE))`</figcaption>
</figure> 
</center>


### Image typicality

The function `img_typicality()` returns the visual typicality of a *set* of images relative to
each other.
Values can range between -1 (inversely typical) over 0 (not typical) to 1 (perfectly typical).
That is, higher absolute values indicate a larger typicality.
  
The typicality score is computed as the correlation of a particular image with the average representation of all images, i.e., the mean of all images [@Perrett1994].
That is, the typicality of an image can not be assessed in isolation, but only in comparison to set of images from the same category.

For color images, the weighted average between each color channel's values is computed.
If the images have different dimensions they are automatically resized to the smallest height and width. 
Rescaling of the images prior to computing the typicality scores can be specified with the optional rescaling parameter (must be a numeric value).
With rescaling it is possible to assess typicality at different perceptual levels (see @MayerDS2018 for details).
Most users won't need any rescaling and can use the default (`rescale = NULL`).

The following example shows three images of which two depict valleys in the mountains and one depicts fireworks.
Therefore, the fireworks image is in comparison rather low in typicality in this set of images (i.e., atypical).
It is important to note that an image's typicality score highly depends on the reference set to which the image is compared to.


```{r, eval=FALSE}
# Example images depicting valleys: valley_white, valley_green
# Example image depicting fireworks: fireworks
valley_white <- img_read(system.file('example_images', 'valley_white.jpg', package = 'imagefluency'))
valley_green <- img_read(system.file('example_images', 'valley_green.jpg', package = 'imagefluency'))
fireworks <- img_read(system.file('example_images', 'fireworks.jpg', package = 'imagefluency'))

# create image set as list
imglist <- list(valley_white, fireworks, valley_green)

# get typicality
img_typicality(imglist)
```

Calculating the typicality scores for the three images gives the following result:

<center>
<figure>
  <img src="`r valley_white`"/>
  <figcaption>score: `r sprintf("%.2f",img_typicality(imglist)[1])`</figcaption>
</figure>
<figure>
  <img src="`r fireworks`"/>
  <figcaption>score: `r sprintf("%.2f",img_typicality(imglist)[2])`</figcaption>
</figure>
<figure>
  <img src="`r valley_green`"/>
  <figcaption>score: `r sprintf("%.2f",img_typicality(imglist)[3])`</figcaption>
</figure> 
</center>

## Summary

`imagefluency` is an simple `R` package for image fluency scores.
The package allows to get scores for several basic aesthetic principles that facilitate fluent cognitive processing of images.
It straightforward to use and allows for an easy conversion of unstructured data into structured image features.
These structured image features are naturally interpretable (i.e., no black-box-model). Finally, including such image information in statistical models might increase a model's statistical and predictive power.


## References

<small>
<div id="refs"></div>
</small>
