Skip to content

Commit

Permalink
added circles (lines and shapes)
Browse files Browse the repository at this point in the history
  • Loading branch information
LynnSchmittwilken committed Mar 14, 2023
1 parent 956abd7 commit 6f82203
Show file tree
Hide file tree
Showing 2 changed files with 140 additions and 20 deletions.
97 changes: 78 additions & 19 deletions stimupy/components/lines.py
Expand Up @@ -4,12 +4,13 @@
import numpy as np
from PIL import Image, ImageDraw

from stimupy.components.shapes import ring
from stimupy.components.shapes import ellipse as ellipse_shape
from stimupy.utils import resolution

__all__ = [
"line",
"dipole",
"ellipse",
"circle",
]

Expand Down Expand Up @@ -104,9 +105,6 @@ def line(
),
)

if any(num < 0 for num in coords[0]) or any(num < 0 for num in coords[1]):
raise ValueError("Line does not fully fit into image")

# Create line image
ImageDraw.Draw(img).line(coords, width=int(line_width * ppd[0]))

Expand Down Expand Up @@ -230,7 +228,7 @@ def dipole(
return stim1


def circle(
def ellipse(
visual_size=None,
ppd=None,
shape=None,
Expand All @@ -239,7 +237,7 @@ def circle(
intensity_line=1,
intensity_background=0,
):
"""Draw a circle given the input parameters
"""Draw an ellipse given the input parameters
Parameters
----------
Expand All @@ -249,8 +247,8 @@ def circle(
pixels per degree [vertical, horizontal]
shape : Sequence[Number, Number], Number, or None (default)
shape [height, width] of image, in pixels
radius : Number
radius of circle in degrees visual angle
radius : Sequence[Number, Number], Number or None (default)
ellipse radius [ry, rx] in degrees visual angle
line_width : Number
width of the line, in degrees visual angle;
if line_width=0 (default), line will be one pixel wide
Expand All @@ -267,25 +265,87 @@ def circle(
and additional keys containing stimulus parameters
"""
if radius is None:
raise ValueError("circle() missing argument 'radius' which is not 'None'")
raise ValueError("ellipse() missing argument 'radius' which is not 'None'")

# Resolve resolution
shape, visual_size, ppd = resolution.resolve(shape=shape, visual_size=visual_size, ppd=ppd)
if line_width * ppd[0] == 0:
line_width = 1 / ppd[0]

stim = ring(
radii=(radius, radius + line_width),
intensity_ring=intensity_line,
stim = ellipse_shape(
radius=np.array(radius),
intensity_ellipse=intensity_line,
visual_size=visual_size,
ppd=ppd,
shape=shape,
intensity_background=intensity_background,
origin="mean",
)
stim["ring_mask"] = np.where(stim["ring_mask"] == 2, 1, 0)
stim["line_mask"] = stim["ring_mask"]
del stim["ring_mask"]

stim2 = ellipse_shape(
radius=np.array(radius) - line_width,
visual_size=visual_size,
ppd=ppd,
shape=shape,
origin="mean",
)

stim["img"] = np.where(stim2["shape_mask"]==1, intensity_background, stim["img"])
stim["line_mask"] = np.where(stim2["shape_mask"]==1, 0, stim["shape_mask"])
del stim["shape_mask"]
return stim


def circle(
visual_size=None,
ppd=None,
shape=None,
radius=None,
line_width=0,
intensity_line=1,
intensity_background=0,
):
"""Draw a circle given the input parameters
Parameters
----------
visual_size : Sequence[Number, Number], Number, or None (default)
visual size [height, width] of image, in degrees
ppd : Sequence[Number, Number], Number, or None (default)
pixels per degree [vertical, horizontal]
shape : Sequence[Number, Number], Number, or None (default)
shape [height, width] of image, in pixels
radius : Number
radius of circle in degrees visual angle
line_width : Number
width of the line, in degrees visual angle;
if line_width=0 (default), line will be one pixel wide
intensity_line : Number
intensity value of the line (default: 1)
intensity_background : Number
intensity value of the background (default: 0)
Returns
----------
dict[str, Any]
dict with the stimulus (key: "img"),
mask with integer index for each line (key: "line_mask"),
and additional keys containing stimulus parameters
"""
if radius is None:
raise ValueError("circle() missing argument 'radius' which is not 'None'")
if not isinstance(radius, (int, float)):
raise ValueError("radius should be a single number")

stim = ellipse(
visual_size=visual_size,
ppd=ppd,
shape=shape,
radius=radius,
line_width=line_width,
intensity_line=intensity_line,
intensity_background=intensity_background,
)
return stim


Expand All @@ -298,13 +358,12 @@ def circle(
"line_length": 2,
"line_width": 0.01,
"rotation": 30,
# "line_position": (5, 1),
# "origin": "center"
}

stims = {
"line": line(**p1),
"line": line(**p1, origin="center", line_position=(-1, -1)),
"dipole": dipole(**p1, line_gap=1),
"circle": circle(visual_size=10, ppd=10, radius=3),
"ellipse": ellipse(visual_size=10, ppd=10, radius=(3, 4)),
}
plot_stimuli(stims, mask=False)
plot_stimuli(stims, mask=True)
63 changes: 62 additions & 1 deletion stimupy/components/shapes.py
Expand Up @@ -2,7 +2,7 @@

from stimupy.components import image_base
from stimupy.components.angulars import wedge
from stimupy.components.circulars import annulus, disc, ring
from stimupy.components.circulars import annulus, ring, disc
from stimupy.utils import resolution

__all__ = [
Expand All @@ -11,6 +11,7 @@
"cross",
"parallelogram",
"ellipse",
"circle",
"wedge",
"annulus",
"disc",
Expand Down Expand Up @@ -535,6 +536,65 @@ def ellipse(
}


def circle(
visual_size=None,
ppd=None,
shape=None,
radius=None,
intensity_circle=1.0,
intensity_background=0.0,
origin="mean",
restrict_size=True,
):
"""Draw an ellipse
Parameters
----------
visual_size : Sequence[Number, Number], Number, or None (default)
visual size [height, width] of image, in degrees visual angle
ppd : Sequence[Number, Number], Number, or None (default)
pixels per degree [vertical, horizontal]
shape : Sequence[Number, Number], Number, or None (default)
shape [height, width] of image, in pixels
radius : Number or None (default)
circle radius in degrees visual angle
intensity_circle : float, optional
intensity value for circle, by default 1.0
intensity_background : float, optional
intensity value of background, by default 0.0
origin : "corner", "mean" or "center"
if "corner": set origin to upper left corner
if "mean": set origin to hypothetical image center (default)
if "center": set origin to real center (closest existing value to mean)
restrict_size : Bool
if False, allow circle to reach beyond image size (default: True)
Returns
----------
dict[str, Any]
dict with the stimulus (key: "img"),
mask with integer index for the shape (key: "shape_mask"),
and additional keys containing stimulus parameters
"""
if radius is None:
raise ValueError("circle() missing argument 'radius' which is not 'None'")
if not isinstance(radius, (int, float)):
raise ValueError("radius should be a single number")

stim = ellipse(
visual_size=visual_size,
ppd=ppd,
shape=shape,
radius=radius,
intensity_ellipse=intensity_circle,
intensity_background=intensity_background,
rotation=0,
origin=origin,
restrict_size=restrict_size,
)
return stim


if __name__ == "__main__":
from stimupy.utils.plotting import plot_stimuli

Expand All @@ -551,6 +611,7 @@ def ellipse(
"parallelogram": parallelogram(**p, parallelogram_size=(5.2, 3.1, 0.9)),
"parallelogram2": parallelogram(shape=(100, 100), ppd=10, parallelogram_size=(10, 9, -1)),
"ellipse": ellipse(**p, radius=(4, 3)),
"circle": circle(visual_size=(10, 8), ppd=50, radius=3),
}

plot_stimuli(stims, mask=False)

0 comments on commit 6f82203

Please sign in to comment.