Source code for stimupy.papers.murray2020

"""Stimuli from Murray (2020) https://doi.org/10/gh57gf

This module reproduces most of the stimuli used by Murray (2020)
as they were provided to the model described in that paper but
normalized between 0 and 1.
The stimuli are show in Fig 1 of the paper.
NOTE that the Haze illusion (Fig 1m) is not provided.

Each stimulus is provided by a separate function,
a full list can be found as stimupy.papers.murray2020.__all__

The output of each of these functions is a stimulus dictionary.

For a visual representation of all the stimuli and their mask,
simply run this module as a script:

    $ python stimuli/papers/murray2020.py

Attributes
----------
__all__ (list of str): list of all stimulus-functions
    that are exported by this module when executing
        >>> from stimupy.papers.murray2020 import *

References
-----------
Murray, R. F. (2020).
    A model of lightness perception
    guided by probabilistic assumptions about lighting and reflectance.
    Journal of Vision, 20(7), 28.
    https://doi.org/10/gh57gf
"""

import os.path

import numpy as np
import scipy.io

import stimupy
from stimupy.utils import pad_dict_by_visual_size, rotate_dict

__all__ = [
    "argyle",
    "argyle_control",
    "argyle_long",
    "snake",
    "snake_control",
    "koffka_adelson",
    "koffka_broken",
    "koffka_connected",
    "checkassim",
    "simcon",
    "simcon_articulated",
    "white",
]

data_dir = os.path.dirname(__file__)
mat_fname = os.path.join(data_dir, "murray2020.mat")
mat_content = scipy.io.loadmat(mat_fname)

PPD = 16 / 8.0
PAD = True


def gen_all(skip=False):
    stims = {}  # save the stimulus-dicts in a larger dict, with name as key
    for stim_name in __all__:
        print(f"Generating murray2020.{stim_name}")

        # Get a reference to the actual function
        func = globals()[stim_name]
        try:
            stim = func()

            # Accumulate
            stims[stim_name] = stim
        except NotImplementedError as e:
            if not skip:
                raise e
            # Skip stimuli that aren't implemented
            print("-- not implemented")
            pass

    return stims


def get_mask(target1, target2, shape):
    mask = np.zeros(shape)

    # Convert MATLAB 1-based index to numpy 0-based index
    target1 = target1 - 1
    target2 = target2 - 1

    # Fill target1 mask
    for x in range(target1[1], target1[3] + 1):
        for y in range(target1[0], target1[2] + 1):
            mask[y][x] = 1

    # Fill target2 mask
    for x in range(target2[1], target2[3] + 1):
        for y in range(target2[0], target2[2] + 1):
            mask[y, x] = 2

    mask = np.array(mask, dtype=int)

    return mask


[docs]def argyle(ppd=PPD): """Argyle illusion, Murray (2020) Fig 1a Parameters ---------- ppd : int Resolution of stimulus in pixels per degree. (default: 32) Returns ------- dict of str dict with the stimulus (key: "img") and target mask (key: "target_mask") and additional keys containing stimulus parameters References ---------- Murray, R. F. (2020). A model of lightness perception guided by probabilistic assumptions about lighting and reflectance. Journal of Vision, 20(7), 28. https://doi.org/10/gh57gf """ a = mat_content["argyle"] img = np.array(((a[0])[0])[0]) target1 = np.array((((a[0])[0])[1])[0]) target2 = np.array((((a[0])[0])[2])[0]) mask = get_mask(target1, target2, img.shape) img = img.repeat(repeats=int(ppd / PPD), axis=0).repeat(repeats=int(ppd / PPD), axis=1) mask = mask.repeat(repeats=int(ppd / PPD), axis=0).repeat(repeats=int(ppd / PPD), axis=1) # Normalize intensity values to [0, 1] original_range = (img.min(), img.max()) normed_img = (img - img.min()) / (img.max() - img.min()) experimental_data = { "mean_proportion_expected": 0.4875, "CI95_proportion_expected": (-0.154903, 0.154903), } params = { "visual_size": np.array(normed_img.shape) / ppd, "shape": normed_img.shape, "ppd": ppd, "intensity_range": (0.0, 1.0), "original_range": original_range, "experimental_data": experimental_data, } return {"img": normed_img, "target_mask": mask, **params}
[docs]def argyle_control(ppd=PPD): """Argyle control figure, Murray (2020) Fig 1c Parameters ---------- ppd : int Resolution of stimulus in pixels per degree. (default: 32) Returns ------- dict of str dict with the stimulus (key: "img") and target mask (key: "target_mask") and additional keys containing stimulus parameters References ---------- Murray, R. F. (2020). A model of lightness perception guided by probabilistic assumptions about lighting and reflectance. Journal of Vision, 20(7), 28. https://doi.org/10/gh57gf """ a = mat_content["argyle_control"] img = np.array(((a[0])[0])[0]) target1 = np.array((((a[0])[0])[1])[0]) target2 = np.array((((a[0])[0])[2])[0]) mask = get_mask(target1, target2, img.shape) img = img.repeat(repeats=int(ppd / PPD), axis=0).repeat(repeats=int(ppd / PPD), axis=1) mask = mask.repeat(repeats=int(ppd / PPD), axis=0).repeat(repeats=int(ppd / PPD), axis=1) # Normalize intensity values to [0, 1] original_range = (img.min(), img.max()) normed_img = (img - img.min()) / (img.max() - img.min()) experimental_data = { "mean_proportion_expected": 0.35, "CI95_proportion_expected": (-0.147814, 0.147814), } stim = { "img": normed_img, "target_mask": mask.astype(int), "visual_size": np.array(normed_img.shape) / ppd, "shape": normed_img.shape, "ppd": ppd, "intensity_range": (0.0, 1.0), "original_range": original_range, "experimental_data": experimental_data, } return stim
[docs]def argyle_long(ppd=PPD): """Long-range Argyle illusion, Murray (2020) Fig 1b Parameters ---------- ppd : int Resolution of stimulus in pixels per degree. (default: 32) Returns ------- dict of str dict with the stimulus (key: "img") and target mask (key: "target_mask") and additional keys containing stimulus parameters References ---------- Murray, R. F. (2020). A model of lightness perception guided by probabilistic assumptions about lighting and reflectance. Journal of Vision, 20(7), 28. https://doi.org/10/gh57gf """ a = mat_content["argyle_long"] img = np.array(((a[0])[0])[0]) target1 = np.array((((a[0])[0])[1])[0]) target2 = np.array((((a[0])[0])[2])[0]) mask = get_mask(target1, target2, img.shape) img = img.repeat(repeats=int(ppd / PPD), axis=0).repeat(repeats=int(ppd / PPD), axis=1) mask = mask.repeat(repeats=int(ppd / PPD), axis=0).repeat(repeats=int(ppd / PPD), axis=1) # Normalize intensity values to [0, 1] original_range = (img.min(), img.max()) normed_img = (img - img.min()) / (img.max() - img.min()) experimental_data = { "mean_proportion_expected": 0.6, "CI95_proportion_expected": (-0.151821, 0.151821), } stim = { "img": normed_img, "target_mask": mask.astype(int), "visual_size": np.array(normed_img.shape) / ppd, "shape": normed_img.shape, "ppd": ppd, "intensity_range": (0.0, 1.0), "original_range": original_range, "experimental_data": experimental_data, } return stim
[docs]def snake(ppd=PPD): """Snake illusion, Murray (2020) Fig 1i Parameters ---------- ppd : int Resolution of stimulus in pixels per degree. (default: 32) Returns ------- dict of str dict with the stimulus (key: "img") and target mask (key: "target_mask") and additional keys containing stimulus parameters References ---------- Murray, R. F. (2020). A model of lightness perception guided by probabilistic assumptions about lighting and reflectance. Journal of Vision, 20(7), 28. https://doi.org/10/gh57gf """ a = mat_content["snake"] img = np.array(((a[0])[0])[0]) target1 = np.array((((a[0])[0])[1])[0]) target2 = np.array((((a[0])[0])[2])[0]) mask = get_mask(target1, target2, img.shape) img = img.repeat(repeats=int(ppd / PPD), axis=0).repeat(repeats=int(ppd / PPD), axis=1) mask = mask.repeat(repeats=int(ppd / PPD), axis=0).repeat(repeats=int(ppd / PPD), axis=1) # Normalize intensity values to [0, 1] original_range = (img.min(), img.max()) normed_img = (img - img.min()) / (img.max() - img.min()) experimental_data = { "mean_proportion_expected": 0.9625, "CI95_proportion_expected": (-0.0588765, 0.0588765), } stim = { "img": normed_img, "target_mask": mask.astype(int), "visual_size": np.array(normed_img.shape) / ppd, "shape": normed_img.shape, "ppd": ppd, "intensity_range": (0.0, 1.0), "original_range": original_range, "experimental_data": experimental_data, } return stim
[docs]def snake_control(ppd=PPD): """Snake control figure, Murray (2020) Fig 1j Parameters ---------- ppd : int Resolution of stimulus in pixels per degree. (default: 32) Returns ------- dict of str dict with the stimulus (key: "img") and target mask (key: "target_mask") and additional keys containing stimulus parameters References ---------- Murray, R. F. (2020). A model of lightness perception guided by probabilistic assumptions about lighting and reflectance. Journal of Vision, 20(7), 28. https://doi.org/10/gh57gf """ a = mat_content["snake_control"] img = np.array(((a[0])[0])[0]) target1 = np.array((((a[0])[0])[1])[0]) target2 = np.array((((a[0])[0])[2])[0]) mask = get_mask(target1, target2, img.shape) img = img.repeat(repeats=int(ppd / PPD), axis=0).repeat(repeats=int(ppd / PPD), axis=1) mask = mask.repeat(repeats=int(ppd / PPD), axis=0).repeat(repeats=int(ppd / PPD), axis=1) # Normalize intensity values to [0, 1] original_range = (img.min(), img.max()) normed_img = (img - img.min()) / (img.max() - img.min()) experimental_data = { "mean_proportion_expected": 0.8, "CI95_proportion_expected": (-0.123961, 0.123961), } stim = { "img": normed_img, "target_mask": mask.astype(int), "visual_size": np.array(normed_img.shape) / ppd, "shape": normed_img.shape, "ppd": ppd, "intensity_range": (0.0, 1.0), "original_range": original_range, "experimental_data": experimental_data, } return stim
[docs]def koffka_adelson(ppd=PPD): """Koffka-Adelson figure, Murray (2020) Fig 1e Parameters ---------- ppd : int Resolution of stimulus in pixels per degree. (default: 32) Returns ------- dict of str dict with the stimulus (key: "img") and target mask (key: "target_mask") and additional keys containing stimulus parameters References ---------- Murray, R. F. (2020). A model of lightness perception guided by probabilistic assumptions about lighting and reflectance. Journal of Vision, 20(7), 28. https://doi.org/10/gh57gf """ a = mat_content["koffka_adelson"] img = np.array(((a[0])[0])[0]) target1 = np.array((((a[0])[0])[1])[0]) target2 = np.array((((a[0])[0])[2])[0]) mask = get_mask(target1, target2, img.shape) img = img.repeat(repeats=int(ppd / PPD), axis=0).repeat(repeats=int(ppd / PPD), axis=1) mask = mask.repeat(repeats=int(ppd / PPD), axis=0).repeat(repeats=int(ppd / PPD), axis=1) # Normalize intensity values to [0, 1] original_range = (img.min(), img.max()) normed_img = (img - img.min()) / (img.max() - img.min()) experimental_data = { "mean_proportion_expected": 0.8375, "CI95_proportion_expected": (-0.114326, 0.114326), } stim = { "img": normed_img, "target_mask": mask.astype(int), "visual_size": np.array(normed_img.shape) / ppd, "shape": normed_img.shape, "ppd": ppd, "intensity_range": (0.0, 1.0), "original_range": original_range, "experimental_data": experimental_data, } return stim
[docs]def koffka_broken(ppd=PPD): """Koffka ring, broken, Murray (2020) Fig 1d Parameters ---------- ppd : int Resolution of stimulus in pixels per degree. (default: 32) Returns ------- dict of str dict with the stimulus (key: "img") and target mask (key: "target_mask") and additional keys containing stimulus parameters References ---------- Murray, R. F. (2020). A model of lightness perception guided by probabilistic assumptions about lighting and reflectance. Journal of Vision, 20(7), 28. https://doi.org/10/gh57gf """ a = mat_content["koffka_broken"] img = np.array(((a[0])[0])[0]) target1 = np.array((((a[0])[0])[1])[0]) target2 = np.array((((a[0])[0])[2])[0]) mask = get_mask(target1, target2, img.shape) img = img.repeat(repeats=int(ppd / PPD), axis=0).repeat(repeats=int(ppd / PPD), axis=1) mask = mask.repeat(repeats=int(ppd / PPD), axis=0).repeat(repeats=int(ppd / PPD), axis=1) # Normalize intensity values to [0, 1] original_range = (img.min(), img.max()) normed_img = (img - img.min()) / (img.max() - img.min()) experimental_data = { "mean_proportion_expected": 0.8, "CI95_proportion_expected": (-0.123961, 0.123961), } stim = { "img": normed_img, "target_mask": mask.astype(int), "visual_size": np.array(normed_img.shape) / ppd, "shape": normed_img.shape, "ppd": ppd, "intensity_range": (0.0, 1.0), "original_range": original_range, "experimental_data": experimental_data, } return stim
[docs]def koffka_connected(ppd=PPD): """Koffka ring, connected, Murray (2020) Fig 1f Parameters ---------- ppd : int Resolution of stimulus in pixels per degree. (default: 32) Returns ------- dict of str dict with the stimulus (key: "img") and target mask (key: "target_mask") and additional keys containing stimulus parameters References ---------- Murray, R. F. (2020). A model of lightness perception guided by probabilistic assumptions about lighting and reflectance. Journal of Vision, 20(7), 28. https://doi.org/10/gh57gf """ a = mat_content["koffka_connected"] img = np.array(((a[0])[0])[0]) target1 = np.array((((a[0])[0])[1])[0]) target2 = np.array((((a[0])[0])[2])[0]) mask = get_mask(target1, target2, img.shape) img = img.repeat(repeats=int(ppd / PPD), axis=0).repeat(repeats=int(ppd / PPD), axis=1) mask = mask.repeat(repeats=int(ppd / PPD), axis=0).repeat(repeats=int(ppd / PPD), axis=1) # Normalize intensity values to [0, 1] original_range = (img.min(), img.max()) normed_img = (img - img.min()) / (img.max() - img.min()) experimental_data = { "mean_proportion_expected": 0.6, "CI95_proportion_expected": (-0.151821, 0.151821), } stim = { "img": normed_img, "target_mask": mask.astype(int), "visual_size": np.array(normed_img.shape) / ppd, "shape": normed_img.shape, "ppd": ppd, "intensity_range": (0.0, 1.0), "original_range": original_range, "experimental_data": experimental_data, } return stim
[docs]def checkassim(ppd=PPD, pad=PAD): """Checkerboard assimilation, Murray (2020) Fig 1h Parameters ---------- ppd : int Resolution of stimulus in pixels per degree. (default: 32) Returns ------- dict of str dict with the stimulus (key: "img") and target mask (key: "target_mask") and additional keys containing stimulus parameters References ---------- Murray, R. F. (2020). A model of lightness perception guided by probabilistic assumptions about lighting and reflectance. Journal of Vision, 20(7), 28. https://doi.org/10/gh57gf """ params = { "ppd": ppd, "board_shape": (7, 10), "check_visual_size": 1 / PPD, "target_indices": ((3, 6), (3, 3)), "extend_targets": False, "intensity_checks": (17.5, 70.0), "intensity_target": 35.0, } stim = stimupy.checkerboards.checkerboard( **params, ) if pad: padding = np.array(((4, 5), (3, 3))) / PPD stim = pad_dict_by_visual_size(stim, padding, ppd=ppd, pad_value=35.0) params["padding"] = padding # Normalize intensity values to [0, 1] original_range = (stim["img"].min(), stim["img"].max()) stim["img"] = (stim["img"] - stim["img"].min()) / (stim["img"].max() - stim["img"].min()) experimental_data = { "mean_proportion_expected": 0.8625, "CI95_proportion_expected": (-0.106723, 0.106723), } params.update( visual_size=np.array(stim["img"].shape) / ppd, shape=stim["img"].shape, original_range=original_range, intensity_range=(0.0, 1.0), experimental_data=experimental_data, ) return {**stim, **params}
[docs]def simcon(ppd=PPD): """Classic simultaneous contrast figure, Murray (2020) Fig 1k Parameters ---------- ppd : int Resolution of stimulus in pixels per degree. (default: 32) Returns ------- dict of str dict with the stimulus (key: "img") and target mask (key: "target_mask") and additional keys containing stimulus parameters References ---------- Murray, R. F. (2020). A model of lightness perception guided by probabilistic assumptions about lighting and reflectance. Journal of Vision, 20(7), 28. https://doi.org/10/gh57gf """ a = mat_content["simcon"] img = np.array(((a[0])[0])[0]) target1 = np.array((((a[0])[0])[1])[0]) target2 = np.array((((a[0])[0])[2])[0]) mask = get_mask(target1, target2, img.shape) img = img.repeat(repeats=int(ppd / PPD), axis=0).repeat(repeats=int(ppd / PPD), axis=1) mask = mask.repeat(repeats=int(ppd / PPD), axis=0).repeat(repeats=int(ppd / PPD), axis=1) # Normalize intensity values to [0, 1] original_range = (img.min(), img.max()) normed_img = (img - img.min()) / (img.max() - img.min()) experimental_data = { "mean_proportion_expected": 0.925, "CI95_proportion_expected": (-0.0816258, 0.0816258), } stim = { "img": normed_img, "target_mask": mask.astype(int), "visual_size": np.array(normed_img.shape) / ppd, "shape": normed_img.shape, "ppd": ppd, "intensity_range": (0.0, 1.0), "original_range": original_range, "experimental_data": experimental_data, } return stim
[docs]def simcon_articulated(ppd=PPD): """Articulated simultaneous contrast figure, Murray (2020) Fig 1l Parameters ---------- ppd : int Resolution of stimulus in pixels per degree. (default: 32) Returns ------- dict of str dict with the stimulus (key: "img") and target mask (key: "target_mask") and additional keys containing stimulus parameters References ---------- Murray, R. F. (2020). A model of lightness perception guided by probabilistic assumptions about lighting and reflectance. Journal of Vision, 20(7), 28. https://doi.org/10/gh57gf """ a = mat_content["simcon_articulated"] img = np.array(((a[0])[0])[0]) target1 = np.array((((a[0])[0])[1])[0]) target2 = np.array((((a[0])[0])[2])[0]) mask = get_mask(target1, target2, img.shape) img = img.repeat(repeats=int(ppd / PPD), axis=0).repeat(repeats=int(ppd / PPD), axis=1) mask = mask.repeat(repeats=int(ppd / PPD), axis=0).repeat(repeats=int(ppd / PPD), axis=1) # Normalize intensity values to [0, 1] original_range = (img.min(), img.max()) normed_img = (img - img.min()) / (img.max() - img.min()) experimental_data = { "mean_proportion_expected": 1.0, "CI95_proportion_expected": (-0.0, 0.0), } stim = { "img": normed_img, "target_mask": mask.astype(int), "visual_size": np.array(normed_img.shape) / ppd, "shape": normed_img.shape, "ppd": ppd, "intensity_range": (0.0, 1.0), "original_range": original_range, "experimental_data": experimental_data, } return stim
[docs]def white(ppd=PPD): """White's illusion, Murray (2020) Fig 1A Parameters ---------- ppd : int Resolution of stimulus in pixels per degree. (default: 32) Returns ------- dict of str dict with the stimulus (key: "img") and target mask (key: "target_mask") and additional keys containing stimulus parameters References ---------- Murray, R. F. (2020). A model of lightness perception guided by probabilistic assumptions about lighting and reflectance. Journal of Vision, 20(7), 28. https://doi.org/10/gh57gf """ params = { "ppd": ppd, "visual_size": (8.0, 8.0), "frequency": 4.0 / 8.0, "target_indices_top": (2, 4, 6), "target_indices_bottom": (3, 5, 7), "target_center_offset": 2, "target_heights": 2, "intensity_bars": (17.5, 70), "intensity_target": 35.0, "period": "even", } stim = stimupy.whites.white_two_rows(**params) stim = rotate_dict(stim) reduced_mask = np.where(stim["target_mask"] == 2, 2, 0) reduced_mask = np.where(stim["target_mask"] == 5, 1, reduced_mask).astype(int) # Normalize intensity values to [0, 1] img = stim["img"] original_range = (img.min(), img.max()) normed_img = (img - img.min()) / (img.max() - img.min()) experimental_data = { "mean_proportion_expected": 0.95, "CI95_proportion_expected": (-0.0675418, 0.0675418), } params.update( visual_size=np.array(normed_img.shape) / ppd, shape=normed_img.shape, original_range=original_range, intensity_range=(0.0, 1.0), experimental_data=experimental_data, ) return {"img": normed_img, "target_mask": reduced_mask, **params}
if __name__ == "__main__": from stimupy.utils import plot_stimuli # Generate all stimuli exported in __all__ stims = gen_all(skip=True) plot_stimuli(stims, mask=True)