pyllusion 1.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyllusion/Autostereogram/Autostereogram.py +109 -0
- pyllusion/Autostereogram/__init__.py +5 -0
- pyllusion/Contrast/Contrast.py +159 -0
- pyllusion/Contrast/__init__.py +5 -0
- pyllusion/Contrast/contrast_image.py +45 -0
- pyllusion/Contrast/contrast_parameters.py +73 -0
- pyllusion/Contrast/contrast_psychopy.py +24 -0
- pyllusion/Delboeuf/Delboeuf.py +211 -0
- pyllusion/Delboeuf/__init__.py +5 -0
- pyllusion/Delboeuf/delboeuf_image.py +51 -0
- pyllusion/Delboeuf/delboeuf_parameters.py +118 -0
- pyllusion/Delboeuf/delboeuf_psychopy.py +44 -0
- pyllusion/Ebbinghaus/Ebbinghaus.py +194 -0
- pyllusion/Ebbinghaus/__init__.py +5 -0
- pyllusion/Ebbinghaus/ebbinghaus_image.py +64 -0
- pyllusion/Ebbinghaus/ebbinghaus_parameters.py +127 -0
- pyllusion/Ebbinghaus/ebbinghaus_psychopy.py +58 -0
- pyllusion/MullerLyer/MullerLyer.py +193 -0
- pyllusion/MullerLyer/__init__.py +5 -0
- pyllusion/MullerLyer/mullerlyer_image.py +56 -0
- pyllusion/MullerLyer/mullerlyer_parameters.py +42 -0
- pyllusion/MullerLyer/mullerlyer_psychopy.py +38 -0
- pyllusion/Pareidolia/Pareidolia.py +86 -0
- pyllusion/Pareidolia/__init__.py +5 -0
- pyllusion/Poggendorff/Poggendorff.py +160 -0
- pyllusion/Poggendorff/__init__.py +5 -0
- pyllusion/Poggendorff/poggendorff_image.py +43 -0
- pyllusion/Poggendorff/poggendorff_parameters.py +46 -0
- pyllusion/Poggendorff/poggendorff_psychopy.py +27 -0
- pyllusion/Ponzo/Ponzo.py +199 -0
- pyllusion/Ponzo/__init__.py +5 -0
- pyllusion/Ponzo/ponzo_image.py +54 -0
- pyllusion/Ponzo/ponzo_parameters.py +139 -0
- pyllusion/Ponzo/ponzo_psychopy.py +36 -0
- pyllusion/RodFrame/RodFrame.py +156 -0
- pyllusion/RodFrame/__init__.py +5 -0
- pyllusion/RodFrame/rodframe_image.py +59 -0
- pyllusion/RodFrame/rodframe_parameters.py +25 -0
- pyllusion/RodFrame/rodframe_psychopy.py +39 -0
- pyllusion/VerticalHorizontal/VerticalHorizontal.py +167 -0
- pyllusion/VerticalHorizontal/__init__.py +5 -0
- pyllusion/VerticalHorizontal/verticalhorizontal_image.py +34 -0
- pyllusion/VerticalHorizontal/verticalhorizontal_parameters.py +64 -0
- pyllusion/VerticalHorizontal/verticalhorizontal_psychopy.py +22 -0
- pyllusion/White/White.py +166 -0
- pyllusion/White/__init__.py +5 -0
- pyllusion/White/white_image.py +50 -0
- pyllusion/White/white_parameters.py +36 -0
- pyllusion/White/white_psychopy.py +27 -0
- pyllusion/Zollner/Zollner.py +178 -0
- pyllusion/Zollner/__init__.py +5 -0
- pyllusion/Zollner/zollner_image.py +51 -0
- pyllusion/Zollner/zollner_parameters.py +88 -0
- pyllusion/Zollner/zollner_psychopy.py +39 -0
- pyllusion/__init__.py +36 -0
- pyllusion/image/__init__.py +27 -0
- pyllusion/image/image_blob.py +152 -0
- pyllusion/image/image_circle.py +218 -0
- pyllusion/image/image_line.py +134 -0
- pyllusion/image/image_mosaic.py +53 -0
- pyllusion/image/image_noise.py +67 -0
- pyllusion/image/image_rectangle.py +136 -0
- pyllusion/image/image_scramble.py +112 -0
- pyllusion/image/image_text.py +95 -0
- pyllusion/image/rescale.py +52 -0
- pyllusion/image/utilities.py +226 -0
- pyllusion/movement/__init__.py +18 -0
- pyllusion/movement/motiontransparency.py +87 -0
- pyllusion/movement/movement_circles.py +24 -0
- pyllusion/movement/movement_matrix.py +63 -0
- pyllusion/movement/utilities.py +18 -0
- pyllusion/psychopy/__init__.py +12 -0
- pyllusion/psychopy/psychopy_circle.py +124 -0
- pyllusion/psychopy/psychopy_line.py +105 -0
- pyllusion/psychopy/psychopy_rectangle.py +139 -0
- pyllusion/utilities/__init__.py +13 -0
- pyllusion/utilities/analyze_color.py +90 -0
- pyllusion/utilities/analyze_image.py +73 -0
- pyllusion/utilities/analyze_luminance.py +73 -0
- pyllusion-1.4.dist-info/METADATA +553 -0
- pyllusion-1.4.dist-info/RECORD +85 -0
- pyllusion-1.4.dist-info/WHEEL +5 -0
- pyllusion-1.4.dist-info/licenses/LICENSE +21 -0
- pyllusion-1.4.dist-info/top_level.txt +2 -0
- tests/__init__.py +500 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import PIL.Image, PIL.ImageDraw, PIL.ImageFilter, PIL.ImageFont, PIL.ImageOps
|
|
3
|
+
|
|
4
|
+
from ..image import image_noise, image_text
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Autostereogram:
|
|
8
|
+
"""
|
|
9
|
+
A class to generate Autostereograms based on a given depth map.
|
|
10
|
+
|
|
11
|
+
Autostereograms are images made of a pattern that is horizontally repeated (with slight variations)
|
|
12
|
+
which, when watched with the appropriate focus, will generate an illusion of depth.
|
|
13
|
+
"""
|
|
14
|
+
def __init__(
|
|
15
|
+
self, stimulus="Hello", pattern=None, n_repetitions=14, depth=1, invert=False, **kwargs
|
|
16
|
+
):
|
|
17
|
+
|
|
18
|
+
self.stimulus = stimulus
|
|
19
|
+
self.pattern = pattern
|
|
20
|
+
self.n_repetitions = n_repetitions
|
|
21
|
+
self.depth = depth
|
|
22
|
+
self.invert = invert
|
|
23
|
+
|
|
24
|
+
# If '/' and '.' in string, we assume it's a path
|
|
25
|
+
if "/" in self.stimulus and "." in self.stimulus:
|
|
26
|
+
depth_map = PIL.Image.open(self.stimulus)
|
|
27
|
+
else: # Else a text
|
|
28
|
+
depth_map = image_text(text=self.stimulus, **kwargs)
|
|
29
|
+
|
|
30
|
+
# Convert to black and white
|
|
31
|
+
depth_map = depth_map.convert('L')
|
|
32
|
+
depth_map = PIL.ImageOps.autocontrast(depth_map)
|
|
33
|
+
|
|
34
|
+
if invert is False:
|
|
35
|
+
self.depth_map = PIL.ImageOps.invert(depth_map)
|
|
36
|
+
else:
|
|
37
|
+
self.depth_map = depth_map
|
|
38
|
+
|
|
39
|
+
# Get size of depth map
|
|
40
|
+
self.width, self.height = self.depth_map.size
|
|
41
|
+
|
|
42
|
+
# We want the strip width to be a multiple of the tile
|
|
43
|
+
# width so it repeats cleanly.
|
|
44
|
+
self.strip_width = int(self.width / self.n_repetitions)
|
|
45
|
+
|
|
46
|
+
# Fix conflicting arguments
|
|
47
|
+
conflicting_args = ["width", "height", "font"]
|
|
48
|
+
kwargs = {key: kwargs[key] for key in kwargs if key not in conflicting_args}
|
|
49
|
+
|
|
50
|
+
# Create strip of pattern
|
|
51
|
+
if pattern is None:
|
|
52
|
+
strip = image_noise(width=self.strip_width, height=self.height, **kwargs)
|
|
53
|
+
else:
|
|
54
|
+
strip = self.pattern(width=self.strip_width, height=self.height, **kwargs)
|
|
55
|
+
self.strip_pixels = strip.load()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def draw(self, guide=True):
|
|
59
|
+
"""Create a PIL image of Autostereograms.
|
|
60
|
+
|
|
61
|
+
Parameters
|
|
62
|
+
----------
|
|
63
|
+
guide : bool
|
|
64
|
+
Defaults to 'True' to activate two red dots as guidance, and 'False' to disable the guide.
|
|
65
|
+
|
|
66
|
+
Returns
|
|
67
|
+
-------
|
|
68
|
+
Image
|
|
69
|
+
Image of the Autostereograms illusion, defaults to 800 x 600 pixels.
|
|
70
|
+
Can be resized
|
|
71
|
+
(`resize()`, See https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.resize)
|
|
72
|
+
and saved in different file formats
|
|
73
|
+
(`save()` See https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html).
|
|
74
|
+
|
|
75
|
+
Examples
|
|
76
|
+
---------
|
|
77
|
+
>>> import pyllusion
|
|
78
|
+
>>>
|
|
79
|
+
>>> autostereograms = pyllusion.Autostereogram(stimulus="3D", width=1000, height=500, font="arialbd.ttf")
|
|
80
|
+
>>> autostereograms.draw(guide=True)
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
image = PIL.Image.new("RGB", (self.width, self.height))
|
|
84
|
+
|
|
85
|
+
# Load pixels for easy replacement
|
|
86
|
+
depth_pixels = self.depth_map.load()
|
|
87
|
+
image_pixels = image.load()
|
|
88
|
+
|
|
89
|
+
for x in range(self.width):
|
|
90
|
+
for y in range(self.height):
|
|
91
|
+
# Need one full strip's worth to borrow from.
|
|
92
|
+
if x < self.strip_width:
|
|
93
|
+
image_pixels[x, y] = self.strip_pixels[x, y]
|
|
94
|
+
else:
|
|
95
|
+
shift_amplitude = self.depth * (depth_pixels[x, y] / self.n_repetitions)
|
|
96
|
+
# Clip to stay within the image (can happen for narrow strips)
|
|
97
|
+
source_x = int(x - self.strip_width + shift_amplitude)
|
|
98
|
+
source_x = min(max(source_x, 0), self.width - 1)
|
|
99
|
+
image_pixels[x, y] = image_pixels[source_x, y]
|
|
100
|
+
|
|
101
|
+
# Add guide
|
|
102
|
+
if guide is True:
|
|
103
|
+
draw = PIL.ImageDraw.Draw(image)
|
|
104
|
+
for i in [-2, 0]:
|
|
105
|
+
diameter = 0.005 * self.width
|
|
106
|
+
center_x = (self.width / 2) + (i * self.strip_width / 2)
|
|
107
|
+
center_y = 0.5 * self.height
|
|
108
|
+
draw.ellipse([center_x-diameter, center_y-diameter, center_x+diameter, center_y+diameter], fill=(255, 0, 0))
|
|
109
|
+
return image
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
from .contrast_image import _contrast_image
|
|
2
|
+
from .contrast_parameters import _contrast_parameters
|
|
3
|
+
from .contrast_psychopy import _contrast_psychopy
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Contrast:
|
|
7
|
+
"""
|
|
8
|
+
A class to generate the Simultaneous Contrast illusion.
|
|
9
|
+
|
|
10
|
+
Simultaneous contrast, identified by Michel Eugène Chevreul, refers to the
|
|
11
|
+
manner in which the colors of two different objects affect each other.
|
|
12
|
+
Specifically, when comparing two targets with the same shade of grey, the one that is
|
|
13
|
+
embedded against a darker background appears lighter than the other target embedded against a lighter background.
|
|
14
|
+
|
|
15
|
+
Each instance of **Contrast** contains attributes corresponding to the parameters of the illusion.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
illusion_strength : float
|
|
20
|
+
The strength of the background, i.e., contrasting colours, in biasing the perception of inner rectangles of different grey shades.
|
|
21
|
+
Specifically, the difference in background colours, where large values create greater contrast in the two
|
|
22
|
+
grey backgrounds.
|
|
23
|
+
A positive sign means that the illusion will enhance the perception of the actual **difference** in brightness contrast
|
|
24
|
+
of the two inner rectangles, whereas a negative sign reduces this perception.
|
|
25
|
+
difference : float
|
|
26
|
+
The objective difference of the grey shades of the two inner rectangles.
|
|
27
|
+
Large positive signs reflect a darker lower rectangle relative to the upper rectangle, and negative signs reflect a darker upper
|
|
28
|
+
rectangle relative to the lower rectangle.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self, illusion_strength=0, difference=0
|
|
33
|
+
):
|
|
34
|
+
"""
|
|
35
|
+
Compute parameters for the Simultaneous Contrast illusion.
|
|
36
|
+
|
|
37
|
+
Parameters
|
|
38
|
+
----------
|
|
39
|
+
illusion_strength : float
|
|
40
|
+
The strength of the background, i.e., contrasting colours, in biasing the perception of inner rectangles of different grey shades.
|
|
41
|
+
Specifically, the difference in background colours, where large values create greater contrast in the two
|
|
42
|
+
grey backgrounds.
|
|
43
|
+
A positive sign means that the illusion will enhance the perception of the actual **difference** in brightness contrast
|
|
44
|
+
of the two inner rectangles, whereas a negative sign reduces this perception.
|
|
45
|
+
difference : float
|
|
46
|
+
The objective difference of the grey shades of the two inner rectangles.
|
|
47
|
+
Large positive signs reflect a darker lower rectangle relative to the upper rectangle, and negative signs reflect a darker upper
|
|
48
|
+
rectangle relative to the lower rectangle.
|
|
49
|
+
"""
|
|
50
|
+
self.parameters = _contrast_parameters(
|
|
51
|
+
illusion_strength=illusion_strength,
|
|
52
|
+
difference=difference,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def get_parameters(self):
|
|
56
|
+
"""
|
|
57
|
+
Returns a dictionary of parameters passed into the Simultaneous Contrast illusion.
|
|
58
|
+
|
|
59
|
+
Returns
|
|
60
|
+
-------
|
|
61
|
+
dict
|
|
62
|
+
Dictionary of parameters of the Simultaneous Contrast illusion, including:
|
|
63
|
+
|
|
64
|
+
- **Difference** : Objective difference in the target features, by modifying `difference` of contrast_parameters().
|
|
65
|
+
- **Illusion** : Name of the illusion, Contrast.
|
|
66
|
+
- **Illusion_Strength** : The strength of the surrounding context in biasing illusion, by modifying `illusion_strength` of contrast_parameters().
|
|
67
|
+
- **Illusion_Type** : `Congruent` if the illusion enhances the perception of the objective difference in the illusion, and `Incongruent` if it reduces the perceived difference.
|
|
68
|
+
- **Rectangle_Top** : Luminance of the top inner rectangle.
|
|
69
|
+
- **Rectangle_Bottom** : Luminance of the bottom inner rectangle.
|
|
70
|
+
- **Background_Top** : Luminance of the top half of the background.
|
|
71
|
+
- **Background_Bottom** : Luminance of the bottom half of the background.
|
|
72
|
+
- **Rectangle_Top_RGB** : RGB value of the top inner rectangle.
|
|
73
|
+
- **Rectangle_Bottom_RGB** : RGB value of the bottom inner rectangle.
|
|
74
|
+
- **Background_Top_RGB** : RGB value of the top half of the background.
|
|
75
|
+
- **Background_Bottom_RGB** : RGB value of the bottom half of the background.
|
|
76
|
+
"""
|
|
77
|
+
return self.parameters
|
|
78
|
+
|
|
79
|
+
def to_dict(self):
|
|
80
|
+
"""
|
|
81
|
+
Alias for `get_parameters()`.
|
|
82
|
+
"""
|
|
83
|
+
return self.get_parameters()
|
|
84
|
+
|
|
85
|
+
def to_image(self, width=800, height=600, **kwargs):
|
|
86
|
+
"""Create a PIL image of the Simultaneous Contrast illusion.
|
|
87
|
+
|
|
88
|
+
Parameters
|
|
89
|
+
----------
|
|
90
|
+
width : int
|
|
91
|
+
Width of the returned image.
|
|
92
|
+
height : int
|
|
93
|
+
Height of the returned image.
|
|
94
|
+
**kwargs
|
|
95
|
+
Additional arguments passed into `contrast_parameters()`.
|
|
96
|
+
|
|
97
|
+
Returns
|
|
98
|
+
-------
|
|
99
|
+
Image
|
|
100
|
+
Image of the Simultaneous Contrast illusion, defaults to 800 x 600 pixels.
|
|
101
|
+
Can be resized
|
|
102
|
+
(`resize()`, See https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.resize)
|
|
103
|
+
and saved in different file formats
|
|
104
|
+
(`save()` See https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html).
|
|
105
|
+
|
|
106
|
+
Examples
|
|
107
|
+
---------
|
|
108
|
+
>>> import pyllusion
|
|
109
|
+
>>>
|
|
110
|
+
>>> contrast = pyllusion.Contrast(illusion_strength=-50, difference=0)
|
|
111
|
+
>>> contrast.to_image()
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
img = _contrast_image(
|
|
115
|
+
parameters=self.parameters,
|
|
116
|
+
width=width,
|
|
117
|
+
height=height,
|
|
118
|
+
**kwargs
|
|
119
|
+
)
|
|
120
|
+
return img
|
|
121
|
+
|
|
122
|
+
def to_psychopy(self, window, **kwargs):
|
|
123
|
+
"""Create a PsychoPy stimulus of the Simultaneous Contrast illusion.
|
|
124
|
+
|
|
125
|
+
Parameters
|
|
126
|
+
----------
|
|
127
|
+
window : object
|
|
128
|
+
The window object in which the stimulus will be rendered.
|
|
129
|
+
**kwargs
|
|
130
|
+
Additional arguments passed into `contrast_parameters()`.
|
|
131
|
+
|
|
132
|
+
Returns
|
|
133
|
+
-------
|
|
134
|
+
In-place modification of the PsychoPy window (No explicit return).
|
|
135
|
+
|
|
136
|
+
Examples
|
|
137
|
+
---------
|
|
138
|
+
>>> import pyllusion
|
|
139
|
+
>>> from psychopy import visual, event
|
|
140
|
+
|
|
141
|
+
>>> # Create parameters
|
|
142
|
+
>>> contrast = pyllusion.Contrast(difference=0, illusion_strength=-50)
|
|
143
|
+
>>> parameters = contrast.get_parameters()
|
|
144
|
+
|
|
145
|
+
>>> # Initiate Window
|
|
146
|
+
>>> window = visual.Window(size=[800, 600], winType='pygame',
|
|
147
|
+
color=parameters["Background_Top_RGB"],
|
|
148
|
+
colorSpace='rgb255')
|
|
149
|
+
|
|
150
|
+
>>> # Display illusion
|
|
151
|
+
>>> contrast.to_psychopy(window)
|
|
152
|
+
|
|
153
|
+
>>> # Refresh and close window
|
|
154
|
+
>>> window.flip()
|
|
155
|
+
>>> event.waitKeys() # Press any key to close
|
|
156
|
+
>>> window.close()
|
|
157
|
+
|
|
158
|
+
"""
|
|
159
|
+
_contrast_psychopy(window, self.parameters, **kwargs)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import PIL.Image, PIL.ImageDraw, PIL.ImageFilter, PIL.ImageFont, PIL.ImageOps
|
|
3
|
+
|
|
4
|
+
from ..image import image_rectangle
|
|
5
|
+
from .contrast_parameters import _contrast_parameters
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _contrast_image(parameters=None, width=800, height=600, **kwargs):
|
|
9
|
+
|
|
10
|
+
# Create white canvas and get drawing context
|
|
11
|
+
if parameters is None:
|
|
12
|
+
parameters = _contrast_parameters(**kwargs)
|
|
13
|
+
|
|
14
|
+
# Background upper
|
|
15
|
+
image = PIL.Image.new(
|
|
16
|
+
"RGB", (width, height), color=parameters["Background_Top_RGB"]
|
|
17
|
+
)
|
|
18
|
+
# Background lower
|
|
19
|
+
image = image_rectangle(
|
|
20
|
+
image=image,
|
|
21
|
+
y=-0.5,
|
|
22
|
+
size_height=1,
|
|
23
|
+
size_width=2,
|
|
24
|
+
color=parameters["Background_Bottom_RGB"],
|
|
25
|
+
antialias=False,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
image = image_rectangle(
|
|
29
|
+
image=image,
|
|
30
|
+
y=0.5,
|
|
31
|
+
size_height=0.5,
|
|
32
|
+
size_width=1,
|
|
33
|
+
color=parameters["Rectangle_Top_RGB"],
|
|
34
|
+
antialias=False,
|
|
35
|
+
)
|
|
36
|
+
image = image_rectangle(
|
|
37
|
+
image=image,
|
|
38
|
+
y=-0.5,
|
|
39
|
+
size_height=0.5,
|
|
40
|
+
size_width=1,
|
|
41
|
+
color=parameters["Rectangle_Bottom_RGB"],
|
|
42
|
+
antialias=False,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
return image
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from ..image import rescale
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _contrast_parameters(illusion_strength=0, difference=0):
|
|
7
|
+
|
|
8
|
+
colors, rgb = _contrast_parameters_internal(
|
|
9
|
+
difference=difference, illusion_strength=illusion_strength
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
parameters = {
|
|
13
|
+
"Illusion": "Contrast",
|
|
14
|
+
"Illusion_Strength": illusion_strength,
|
|
15
|
+
"Difference": difference,
|
|
16
|
+
"Illusion_Type": "Congruent" if illusion_strength > 0 else "Incongruent",
|
|
17
|
+
"Rectangle_Top": colors[0],
|
|
18
|
+
"Rectangle_Bottom": colors[1],
|
|
19
|
+
"Background_Top": colors[2],
|
|
20
|
+
"Background_Bottom": colors[3],
|
|
21
|
+
"Rectangle_Top_RGB": rgb[0],
|
|
22
|
+
"Rectangle_Bottom_RGB": rgb[1],
|
|
23
|
+
"Background_Top_RGB": rgb[2],
|
|
24
|
+
"Background_Bottom_RGB": rgb[3],
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return parameters
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _contrast_parameters_internal(difference=0, illusion_strength=0):
|
|
31
|
+
# Targets
|
|
32
|
+
# A difference of 1 means a difference of 1%
|
|
33
|
+
up = 0.5 + difference / 200
|
|
34
|
+
low = 0.5 - difference / 200
|
|
35
|
+
|
|
36
|
+
# Backgrounds
|
|
37
|
+
# A illusion_strength of 1 means a difference of 1%
|
|
38
|
+
mod = np.sign(difference) if difference != 0 else 1
|
|
39
|
+
background_up = 0.5 + mod * illusion_strength / 200
|
|
40
|
+
background_low = 0.5 - mod * illusion_strength / 200
|
|
41
|
+
|
|
42
|
+
# Adjustments in case of same contrast between rectangle and background
|
|
43
|
+
if background_up == up:
|
|
44
|
+
if background_up > background_low:
|
|
45
|
+
background_up += 0.01
|
|
46
|
+
elif background_up < background_low:
|
|
47
|
+
background_up -= 0.01
|
|
48
|
+
else:
|
|
49
|
+
background_up -= 0.01
|
|
50
|
+
background_low -= 0.01
|
|
51
|
+
if background_low == low:
|
|
52
|
+
if background_low > background_up:
|
|
53
|
+
background_low += 0.01
|
|
54
|
+
elif background_low < background_up:
|
|
55
|
+
background_low -= 0.01
|
|
56
|
+
else:
|
|
57
|
+
background_low -= 0.01
|
|
58
|
+
background_up -= 0.01
|
|
59
|
+
|
|
60
|
+
# Transform to RGB tuples
|
|
61
|
+
background_up_rgb = tuple(
|
|
62
|
+
np.rint(rescale([background_up] * 3, scale=[0, 1], to=[0, 255])).astype(int)
|
|
63
|
+
)
|
|
64
|
+
background_low_rgb = tuple(
|
|
65
|
+
np.rint(rescale([background_low] * 3, scale=[0, 1], to=[0, 255])).astype(int)
|
|
66
|
+
)
|
|
67
|
+
up_rgb = tuple(np.rint(rescale([up] * 3, scale=[0, 1], to=[0, 255])).astype(int))
|
|
68
|
+
low_rgb = tuple(np.rint(rescale([low] * 3, scale=[0, 1], to=[0, 255])).astype(int))
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
(up, low, background_up, background_low),
|
|
72
|
+
(up_rgb, low_rgb, background_up_rgb, background_low_rgb),
|
|
73
|
+
)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from ..psychopy.psychopy_rectangle import psychopy_rectangle
|
|
4
|
+
from .contrast_parameters import _contrast_parameters
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _contrast_psychopy(window, parameters=None, **kwargs):
|
|
8
|
+
|
|
9
|
+
# Create white canvas and get drawing context
|
|
10
|
+
if parameters is None:
|
|
11
|
+
parameters = _contrast_parameters(**kwargs)
|
|
12
|
+
|
|
13
|
+
# Background lower
|
|
14
|
+
psychopy_rectangle(window, x=0, y=-0.5, size_height=1, size_width=2, color=parameters["Background_Bottom_RGB"],
|
|
15
|
+
outline_color=parameters["Background_Bottom_RGB"],
|
|
16
|
+
fillColorSpace='rgb255', lineColorSpace='rgb255')
|
|
17
|
+
|
|
18
|
+
psychopy_rectangle(window, x=0, y=0.5, size_height=0.5, size_width=1, color=parameters["Rectangle_Top_RGB"],
|
|
19
|
+
outline_color=parameters["Rectangle_Top_RGB"],
|
|
20
|
+
fillColorSpace='rgb255', lineColorSpace='rgb255')
|
|
21
|
+
|
|
22
|
+
psychopy_rectangle(window, x=0, y=-0.5, size_height=0.5, size_width=1, color=parameters["Rectangle_Bottom_RGB"],
|
|
23
|
+
outline_color=parameters["Rectangle_Bottom_RGB"],
|
|
24
|
+
fillColorSpace='rgb255', lineColorSpace='rgb255')
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
from .delboeuf_image import _delboeuf_image
|
|
2
|
+
from .delboeuf_parameters import _delboeuf_parameters
|
|
3
|
+
from .delboeuf_psychopy import _delboeuf_psychopy
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Delboeuf:
|
|
7
|
+
"""
|
|
8
|
+
A class to generate the Delboeuf Illusion.
|
|
9
|
+
|
|
10
|
+
The Delboeuf illusion is an optical illusion of relative size perception,
|
|
11
|
+
where circles of identical size appear as different because of their surrounding context.
|
|
12
|
+
Specifically, the circle that is closely surrounded by a ring appears larger than the circle surrounded by a distant ring.
|
|
13
|
+
|
|
14
|
+
Each instance of Delboeuf contains attributes corresponding to the parameters of the illusion.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
illusion_strength : float
|
|
19
|
+
The strength of the surrounding context, i.e. outer circles, in biasing perception of unequally sized inner circles.
|
|
20
|
+
Specifically, the size of the outer circle relative to the inner circle (in percentage, e.g, if illusion_strength=1,
|
|
21
|
+
it means that the outer circle will be 100% bigger, i.e., 2 times bigger than the inner circle).
|
|
22
|
+
A negative sign means that the illusion will enhance the perception of the actual difference in circle sizes
|
|
23
|
+
whereas a positive sign reduces this perception.
|
|
24
|
+
difference : float
|
|
25
|
+
The objective size difference of the inner circles. Specifically, the size of left inner
|
|
26
|
+
circle relative to the right (in percentage, e.g., if difference=1, it means that the left
|
|
27
|
+
inner circle will be 100% bigger, i.e., 2 times bigger than the right). A negative sign
|
|
28
|
+
would make the left inner circle smaller than the right inner circle.
|
|
29
|
+
size_min : float
|
|
30
|
+
Size of smaller inner circle. Defaults to 0.25.
|
|
31
|
+
distance : float
|
|
32
|
+
Distance between circles. Defaults to 1.
|
|
33
|
+
distance_auto : bool
|
|
34
|
+
If true, distance is between edges (fixed spacing), if false (default), between centers (fixed location).
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
illusion_strength=0,
|
|
40
|
+
difference=0,
|
|
41
|
+
size_min=0.25,
|
|
42
|
+
distance=1,
|
|
43
|
+
distance_auto=False,
|
|
44
|
+
):
|
|
45
|
+
"""
|
|
46
|
+
Compute attributes for the Delboeuf Illusion.
|
|
47
|
+
|
|
48
|
+
Parameters
|
|
49
|
+
----------
|
|
50
|
+
illusion_strength : float
|
|
51
|
+
The strength of the surrounding context, i.e. outer circles, in biasing perception of unequally sized inner circles.
|
|
52
|
+
Specifically, the size of the outer circle relative to the inner circle (in percentage, e.g, if illusion_strength=1,
|
|
53
|
+
it means that the outer circle will be 100% bigger, i.e., 2 times bigger than the inner circle).
|
|
54
|
+
A negative sign means that the illusion will enhance the perception of the actual difference in circle sizes
|
|
55
|
+
whereas a positive sign reduces this perception.
|
|
56
|
+
difference : float
|
|
57
|
+
The objective size difference of the inner circles.
|
|
58
|
+
Specifically, the size of left inner circle relative to the right (in percentage, e.g., if difference=1,
|
|
59
|
+
it means that the left inner circle will be 100% bigger, i.e., 2 times bigger than the right).
|
|
60
|
+
A negative sign would make the left inner circle smaller than the right inner circle.
|
|
61
|
+
size_min : float
|
|
62
|
+
Size of smaller inner circle. Defaults to 0.25.
|
|
63
|
+
distance : float
|
|
64
|
+
Distance between circles. Defaults to 1.
|
|
65
|
+
distance_auto : bool
|
|
66
|
+
If true, distance is between edges (fixed spacing), if false (default), between centers (fixed location).
|
|
67
|
+
"""
|
|
68
|
+
self.parameters = _delboeuf_parameters(
|
|
69
|
+
illusion_strength=illusion_strength,
|
|
70
|
+
difference=difference,
|
|
71
|
+
size_min=size_min,
|
|
72
|
+
distance=distance,
|
|
73
|
+
distance_auto=distance_auto,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def get_parameters(self):
|
|
77
|
+
"""
|
|
78
|
+
Returns a dictionary of parameters passed into the Delboeuf illusion.
|
|
79
|
+
|
|
80
|
+
Returns
|
|
81
|
+
--------
|
|
82
|
+
dict
|
|
83
|
+
A dictionary of all parameters passed into the Delboeuf illusion, including:
|
|
84
|
+
|
|
85
|
+
- **Difference** : Objective difference in the target features, by modifying `difference` of delboeuf_parameters()
|
|
86
|
+
- **Illusion** : Name of the illusion, Delboeuf.
|
|
87
|
+
- **Illusion_Strength** : The strength of the surrounding context in biasing illusion, by modifying `illusion_strength` of delboeuf_parameters().
|
|
88
|
+
- **Illusion_Type** : `Congruent` if the illusion enhances the perception of the objective difference in the illusion, and `Incongruent` if it reduces the perceived difference.
|
|
89
|
+
- **Size_Inner_Left** : Size of the inner left circle.
|
|
90
|
+
- **Size_Inner_Right** : Size of the inner right circle.
|
|
91
|
+
- **Sine_Inner_Difference** : Difference in areas of the left and right inner circles.
|
|
92
|
+
- **Size_Outer_Left** : Size of the outer left rim.
|
|
93
|
+
- **Size_Outer_Right** : Size of the outer right rim.
|
|
94
|
+
- **Distance** : Distance between the circles, by modifying `distance` of delboeuf_parameters().
|
|
95
|
+
- **Distance_Reference** : Distance between circles is computed 'Between Edges' or `Between Centers`, by modifying distance_auto of delboeuf_parameters().
|
|
96
|
+
- **Distance_Edges_Inner** : Distance between the edges of the inner left and right circles.
|
|
97
|
+
- **Distance_Edges_Outer** : Distance between the edges of the outer left and right rims.
|
|
98
|
+
- **Size_Inner_Smaller** : Size of the smaller inner circle, equates to size_min of delboeuf_parameters().
|
|
99
|
+
- **Size_Inner_Larger** : Size of the larger inner circle.
|
|
100
|
+
- **Size_Outer_Smaller** : Size of the smaller outer rim.
|
|
101
|
+
- **Size_Outer_Larger** : Size of the larger outer rim.
|
|
102
|
+
- **Position_Left** : Position of the left circle.
|
|
103
|
+
- **Position_Right** : Position of the right circle.
|
|
104
|
+
"""
|
|
105
|
+
return self.parameters
|
|
106
|
+
|
|
107
|
+
def to_dict(self):
|
|
108
|
+
"""
|
|
109
|
+
Alias for `get_parameters()`.
|
|
110
|
+
"""
|
|
111
|
+
return self.get_parameters()
|
|
112
|
+
|
|
113
|
+
def to_image(
|
|
114
|
+
self, width=800, height=600, outline=10, background="white", target_only=False, **kwargs
|
|
115
|
+
):
|
|
116
|
+
"""Create a PIL image of the Delboeuf illusion.
|
|
117
|
+
|
|
118
|
+
Parameters
|
|
119
|
+
----------
|
|
120
|
+
width : int
|
|
121
|
+
Width of the returned image.
|
|
122
|
+
height : int
|
|
123
|
+
Height of the returned image.
|
|
124
|
+
outline : float
|
|
125
|
+
The width of the outline of the circles in the illusion, passed into `image_circle()`.
|
|
126
|
+
background : str
|
|
127
|
+
Color of the background.
|
|
128
|
+
distractor_fill : str or tuple
|
|
129
|
+
Color of the outer distractor circles. Set to ``(0, 0, 0, 0)`` (transparent) by
|
|
130
|
+
default.
|
|
131
|
+
target_only : bool
|
|
132
|
+
If true, only draw the red circle.
|
|
133
|
+
**kwargs
|
|
134
|
+
Additional arguments passed into `delboeuf_parameters()`.
|
|
135
|
+
|
|
136
|
+
Returns
|
|
137
|
+
-------
|
|
138
|
+
Image
|
|
139
|
+
Image of the Delboeuf illusion, defaults to 800 x 600 pixels.
|
|
140
|
+
Can be resized
|
|
141
|
+
(`resize()`, See https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.resize)
|
|
142
|
+
and saved in different file formats
|
|
143
|
+
(`save()` See https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html).
|
|
144
|
+
|
|
145
|
+
Examples
|
|
146
|
+
---------
|
|
147
|
+
>>> import pyllusion
|
|
148
|
+
>>>
|
|
149
|
+
>>> delboeuf = pyllusion.Delboeuf(illusion_strength=3)
|
|
150
|
+
>>> delboeuf.to_image()
|
|
151
|
+
>>> delboeuf.get_parameters()
|
|
152
|
+
>>>
|
|
153
|
+
>>> # Display only the target (no illusion)
|
|
154
|
+
>>> delboeuf = pyllusion.Delboeuf(difference=2)
|
|
155
|
+
>>> delboeuf.to_image(target_only=True)
|
|
156
|
+
>>>
|
|
157
|
+
>>> # Alternative version
|
|
158
|
+
>>> delboeuf = pyllusion.Delboeuf(illusion_strength=3)
|
|
159
|
+
>>> delboeuf.to_image(background="grey", distractor_fill="white", outline=0)
|
|
160
|
+
>>>
|
|
161
|
+
|
|
162
|
+
"""
|
|
163
|
+
img = _delboeuf_image(
|
|
164
|
+
parameters=self.parameters,
|
|
165
|
+
width=width,
|
|
166
|
+
height=height,
|
|
167
|
+
outline=outline,
|
|
168
|
+
background=background,
|
|
169
|
+
target_only=target_only,
|
|
170
|
+
**kwargs
|
|
171
|
+
)
|
|
172
|
+
return img
|
|
173
|
+
|
|
174
|
+
def to_psychopy(self, window, target_only=False, **kwargs):
|
|
175
|
+
"""Create a PsychoPy stimulus of the Delboeuf illusion.
|
|
176
|
+
|
|
177
|
+
Parameters
|
|
178
|
+
----------
|
|
179
|
+
window : object
|
|
180
|
+
The window object initiated by `psychopy.visual.Window` in which the stimulus will be
|
|
181
|
+
rendered.
|
|
182
|
+
target_only : bool
|
|
183
|
+
If true, only draw the red circle.
|
|
184
|
+
**kwargs
|
|
185
|
+
Additional arguments passed into `delboeuf_parameters()`.
|
|
186
|
+
|
|
187
|
+
Returns
|
|
188
|
+
-------
|
|
189
|
+
In-place modification of the PsychoPy window (No explicit return).
|
|
190
|
+
|
|
191
|
+
Examples
|
|
192
|
+
---------
|
|
193
|
+
>>> import pyllusion
|
|
194
|
+
>>> from psychopy import visual, event
|
|
195
|
+
|
|
196
|
+
>>> # Create parameters
|
|
197
|
+
>>> delboeuf = pyllusion.Delboeuf(difference=2, illusion_strength=3)
|
|
198
|
+
|
|
199
|
+
>>> # Initiate Window
|
|
200
|
+
>>> window = visual.Window(size=[800, 600], winType='pygame', color='white')
|
|
201
|
+
|
|
202
|
+
>>> # Display illusion
|
|
203
|
+
>>> delboeuf.to_psychopy(window)
|
|
204
|
+
|
|
205
|
+
>>> # Refresh and close window
|
|
206
|
+
>>> window.flip()
|
|
207
|
+
>>> event.waitKeys() # Press any key to close
|
|
208
|
+
>>> window.close()
|
|
209
|
+
|
|
210
|
+
"""
|
|
211
|
+
_delboeuf_psychopy(window, self.parameters, target_only=target_only, **kwargs)
|