Skip to content

Commit

Permalink
Merge pull request #84 from computational-psychology/dev_docs
Browse files Browse the repository at this point in the history
Quality improvements
  • Loading branch information
LynnSchmittwilken committed May 20, 2023
2 parents e054d55 + fc19d2a commit 69e0c47
Show file tree
Hide file tree
Showing 13 changed files with 162 additions and 10 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Expand Up @@ -13,14 +13,14 @@ jobs:
uses: dsaltares/fetch-gh-release-asset@1.1.0
with:
regex: true
file: 'stimupy-.*\\.whl'
file: 'stimupy-.[0-9]*\.[0-9]*\.[0-9]*\.whl'
target: 'dist/'

- name: Fetch sdist(s) from release
uses: dsaltares/fetch-gh-release-asset@1.1.0
with:
regex: true
file: 'stimupy-.*\\.tar.gz'
file: 'stimupy-.[0-9]*\.[0-9]*\.[0-9]*\.tar.gz'
target: 'dist/'

- name: Publish to TestPyPI
Expand Down
8 changes: 8 additions & 0 deletions manuscript/bibliography.bib
Expand Up @@ -56,6 +56,14 @@ @inproceedings{deng2009
organization = {{IEEE}}
}

@article{OCTA,
author = {Van Geert, E. and Bossens, C. and Wagemans, J.},
journal = {Behavior Research Methods},
title = {The Order & Complexity Toolbox for Aesthetics (OCTA): A systematic approach to study the relations between order, complexity, and aesthetic appreciation},
year = {2022},
doi = {10.3758/s13428-022-01900-w}
}

@article{makowski2021,
title = {A Parametric Framework to Generate Visual Illusions Using Python},
author = {Makowski, D. and Lau, Z. J. and Pham, T. and Paul B., W. and Annabel C., S.},
Expand Down
24 changes: 23 additions & 1 deletion manuscript/paper.md
Expand Up @@ -83,7 +83,8 @@ We are currently aware of

- Psychtoolbox [@brainard1997],
- Psychopy [@peirce2019],
- Pyllusion [@makowski2021].
- Pyllusion [@makowski2021],
- OCTA [@OCTA].

Psychtoolbox and Psychopy both provide functions to generate a number of visual stimuli.
However, stimulus generation is integrated into their main purpose
Expand Down Expand Up @@ -115,6 +116,27 @@ In contrast, `stimupy` provides a unified interface to stimulus creation,
where many functions share the same - intuitive - parameters.
This makes it easier to explore parameters and to create novel stimuli.

OCTA is also a Python package to generate stimuli,
specifically grids of multiple elements that can be show regularity and variety
along various stimulus dimensions.
These stimuli are of particular use to studies on Gestalt vision,
aesthetics and complexity.
The parametric variation of stimulus dimensions
as well as the compositionality of displays
are features found in both OCTA and `stimupy`.
Both packages also have a strong focus on ease-of-use, replicability, and Open Science.
`stimupy` currently focuses on a different class of stimuli:
mainly displays used to study early and low-level visual processes,
as well as visual features such as brightness, contrast, and orientation.
Thus, OCTA and `stimupy` cover complementary usecases.

Another design decision that sets `stimupy` apart from existing software such as OCTA and Pyllusion,
is that all `stimupy` stimuli are generated as `numpy`-arrays
representing pixel-based raster-graphics.
This has several advantages over existing, vector-graphics or custom object-based approaches,
mainly that any standard array-manipulation tooling can be used to further process
a stimulus.

# Statement of Need

Many visual stimuli are used time and again.
Expand Down
2 changes: 2 additions & 0 deletions stimupy/logos.py
Expand Up @@ -19,6 +19,8 @@ def logo(
----------
ppd : Number (default: 128)
pixels per degree along the axis of grating
intensity_background : Number
intensity value of background (default: 0.5)
Returns
-------
Expand Down
2 changes: 2 additions & 0 deletions stimupy/stimuli/gabors.py
Expand Up @@ -86,11 +86,13 @@ def gabor(
)
mean_int = (intensity_bars[0] + intensity_bars[1]) / 2
stim["img"] = (stim["img"] - mean_int) * gaussian_window["img"] + mean_int
del stim["intensities"]

return {
**stim,
"sigma": sigma,
"gaussian_mask": gaussian_window["gaussian_mask"],
"intensity_bars": intensity_bars,
}


Expand Down
11 changes: 8 additions & 3 deletions stimupy/stimuli/gratings.py
Expand Up @@ -6,7 +6,7 @@
from stimupy.components.shapes import parallelogram, rectangle
from stimupy.stimuli.waves import sine_linear as sinewave
from stimupy.stimuli.waves import square_linear as squarewave
from stimupy.utils import pad_dict_to_shape, pad_dict_to_visual_size, resolution
from stimupy.utils import pad_dict_to_shape, pad_dict_to_visual_size, resolution, strip_dict
from stimupy.utils.filters import convolve

__all__ = [
Expand Down Expand Up @@ -132,6 +132,8 @@ def on_uniform(
grating_shape=stim["shape"],
shape=stim["img"].shape,
visual_size=visual_size,
target_indices=target_indices,
intensity_target=intensity_target,
)

return stim
Expand Down Expand Up @@ -203,8 +205,10 @@ def on_grating_masked(
"target_mask": mask.astype(int),
"small_grating_mask": small_grating["grating_mask"],
"large_grating_mask": large_grating["grating_mask"],
"bar_width_small": small_grating["bar_width"],
"bar_width_large": large_grating["bar_width"],
"small_grating_params": strip_dict(small_grating, squarewave),
"large_grating_params": strip_dict(large_grating, squarewave),
"mask_size": mask_size,
"mask_rotation": mask_rotation,
}
return stim

Expand Down Expand Up @@ -354,6 +358,7 @@ def phase_shifted(
stim["target_mask"] = mask.astype(int)
stim["target_phase_shift"] = target_phasei
stim["target_size"] = stim_target["visual_size"]
stim["intensity_target"] = intensity_target
return stim


Expand Down
7 changes: 7 additions & 0 deletions stimupy/stimuli/mueller_lyers.py
Expand Up @@ -164,6 +164,13 @@ def mueller_lyer(
target_line["line_mask"] = np.where(target_line["line_mask"] > 5, 5, target_line["line_mask"])

target_line["target_mask"] = np.where(target_line["line_mask"] == 1, 1, 0).astype(int)
target_line["outer_lines_length"] = outer_lines_length
target_line["outer_lines_angle"] = outer_lines_angle
target_line["target_length"] = target_length
target_line["line_width"] = line_width
target_line["intensity_outer_lines"] = intensity_outer_lines
target_line["intensity_target"] = intensity_target
target_line["intensity_background"] = intensity_background
return target_line


Expand Down
6 changes: 6 additions & 0 deletions stimupy/stimuli/pinwheels.py
Expand Up @@ -101,6 +101,11 @@ def pinwheel(
)
radius = min(stim["visual_size"]) / 2

stim["target_indices"] = target_indices
stim["target_center"] = target_center
stim["target_width"] = target_width
stim["intensity_target"] = intensity_target

circle_mask = circle(
visual_size=visual_size,
ppd=ppd,
Expand Down Expand Up @@ -163,6 +168,7 @@ def pinwheel(
target_mask = np.where(condition1 & condition2, target_idx + 1, target_mask)
stim["img"] = np.where(target_mask == (target_idx + 1), intensity, stim["img"])
stim["target_mask"] = target_mask
stim["intensity_background"] = intensity_background
return stim


Expand Down
45 changes: 42 additions & 3 deletions stimupy/stimuli/plaids.py
Expand Up @@ -86,7 +86,20 @@ def gabors(
grating1 = gabors_stim.gabor(**gabor_parameters1)
grating2 = gabors_stim.gabor(**gabor_parameters2)
plaid = add_waves(grating1, grating2, weight1, weight2)
return plaid

out = {
"img": plaid["img"],
"grating_mask1": plaid["grating_mask"],
"grating_mask2": plaid["grating_mask2"],
"gabor_parameters1": gabor_parameters1,
"gabor_parameters2": gabor_parameters2,
"weight1": weight1,
"weight2": weight2,
"visual_size": plaid["visual_size"],
"shape": plaid["shape"],
"ppd": plaid["ppd"],
}
return out


def sine_waves(
Expand Down Expand Up @@ -120,7 +133,20 @@ def sine_waves(
grating1 = waves.sine_linear(**grating_parameters1)
grating2 = waves.sine_linear(**grating_parameters2)
plaid = add_waves(grating1, grating2, weight1, weight2)
return plaid

out = {
"img": plaid["img"],
"grating_mask1": plaid["grating_mask"],
"grating_mask2": plaid["grating_mask2"],
"grating_parameters1": grating_parameters1,
"grating_parameters2": grating_parameters2,
"weight1": weight1,
"weight2": weight2,
"visual_size": plaid["visual_size"],
"shape": plaid["shape"],
"ppd": plaid["ppd"],
}
return out


def square_waves(
Expand Down Expand Up @@ -154,7 +180,20 @@ def square_waves(
grating1 = waves.square_linear(**grating_parameters1)
grating2 = waves.square_linear(**grating_parameters2)
plaid = add_waves(grating1, grating2, weight1, weight2)
return plaid

out = {
"img": plaid["img"],
"grating_mask1": plaid["grating_mask"],
"grating_mask2": plaid["grating_mask2"],
"grating_parameters1": grating_parameters1,
"grating_parameters2": grating_parameters2,
"weight1": weight1,
"weight2": weight2,
"visual_size": plaid["visual_size"],
"shape": plaid["shape"],
"ppd": plaid["ppd"],
}
return out


def overview(**kwargs):
Expand Down
19 changes: 18 additions & 1 deletion stimupy/stimuli/ponzos.py
Expand Up @@ -137,7 +137,24 @@ def ponzo(
line1["img"] += line3["img"] + line4["img"] + intensity_background
line1["line_mask"] += line3["line_mask"] * 3 + line4["line_mask"] * 4
line1["target_mask"] = line3["line_mask"] + line4["line_mask"] * 2
return line1

stim = {}
stim["img"] = line1["img"]
stim["line_mask"] = line1["line_mask"]
stim["target_mask"] = line1["target_mask"]
stim["visual_size"] = line1["visual_size"]
stim["shape"] = line1["shape"]
stim["ppd"] = line1["ppd"]
stim["outer_lines_length"] = outer_lines_length
stim["outer_lines_width"] = outer_lines_width
stim["outer_lines_angle"] = outer_lines_angle
stim["target_lines_length"] = target_lines_length
stim["target_lines_width"] = target_lines_width
stim["target_distance"] = target_distance
stim["intensity_outer_lines"] = intensity_outer_lines
stim["intensity_target_lines"] = intensity_target_lines
stim["intensity_background"] = intensity_background
return stim


def overview(**kwargs):
Expand Down
3 changes: 3 additions & 0 deletions stimupy/stimuli/rings.py
Expand Up @@ -131,6 +131,7 @@ def circular_two_sided(
stim = stack_dicts(stim1, stim2)
stim["shape"] = shape
stim["visual_size"] = visual_size
stim["intensity_background"] = intensity_background
return stim


Expand Down Expand Up @@ -195,6 +196,7 @@ def rectangular_generalized(
origin=origin,
rotation=rotation,
)
stim["intensity_target"] = intensity_target

# Resolve target parameters
if isinstance(target_indices, (int)):
Expand All @@ -217,6 +219,7 @@ def rectangular_generalized(

# Update and return stimulus
stim["target_mask"] = targets_mask
stim["target_indices"] = target_indices
return stim


Expand Down
5 changes: 5 additions & 0 deletions stimupy/stimuli/todorovics.py
Expand Up @@ -429,6 +429,11 @@ def cross_generalized(
raise ValueError("Need as many x- as y-coordinates")
if isinstance(covers_size, (float, int)):
covers_size = (covers_size, covers_size)
if isinstance(cross_size, (float, int)):
cross_size = (cross_size, cross_size)

if cross_size[0] < cross_thickness or cross_size[1] < cross_thickness:
raise ValueError("cross_size needs to be larger than cross_thickness")

stim = cross_shape(
visual_size=cross_size,
Expand Down

0 comments on commit 69e0c47

Please sign in to comment.