Skip to content

Commit

Permalink
Resolve grating params in 1D
Browse files Browse the repository at this point in the history
Closes #47
  • Loading branch information
JorisVincent committed Dec 6, 2022
1 parent 463069c commit ebd02e5
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 41 deletions.
103 changes: 75 additions & 28 deletions stimuli/components/grating.py
Expand Up @@ -6,8 +6,8 @@


def resolve_grating_params(
shape=None,
visual_size=None,
length=None,
visual_angle=None,
ppd=None,
frequency=None,
n_phases=None,
Expand Down Expand Up @@ -56,11 +56,13 @@ def resolve_grating_params(

# Try to resolve resolution
try:
shape, visual_size, ppd = resolution.resolve(shape=shape, visual_size=visual_size, ppd=ppd)
length, visual_angle, ppd = resolution.resolve_1D(
length=length, visual_angle=visual_angle, ppd=ppd
)
except ValueError:
ppd = resolution.validate_ppd(ppd)
shape = resolution.validate_shape(shape)
visual_size = resolution.validate_visual_size(visual_size)
ppd = ppd
length = length
visual_angle = visual_angle

# Try to resolve number and width(s) of phases:
# Logic here is that phase_width expresses "degrees per phase",
Expand All @@ -82,26 +84,29 @@ def resolve_grating_params(
# pix = ppd * n_degrees
# Thus we can resolve the number and spacing of phases also as a resolution
try:
n_phases, visual_angle, phases_pd = resolution.resolve_1D(
length=n_phases, visual_angle=visual_size.width, ppd=phases_pd
n_phases, min_angle, phases_pd = resolution.resolve_1D(
length=n_phases,
visual_angle=visual_angle,
ppd=phases_pd,
round=False,
)
phase_width = 1 / phases_pd
frequency = phases_pd / 2
except Exception as e:
raise Exception("Could not resolve grating frequency, phase_width, n_phases") from e

# Convert to frequency
phase_width = 1 / phases_pd
frequency = phases_pd / 2

# Now resolve resolution
visual_width = visual_size.width if visual_size.width is not None else visual_angle
visual_height = visual_size.height if visual_size.height is not None else visual_angle
shape, visual_size, ppd = resolution.resolve(
shape=shape, visual_size=(visual_height, visual_width), ppd=ppd
visual_angle = min_angle if visual_angle is None else visual_angle
length, visual_angle, ppd = resolution.resolve_1D(
length=length, visual_angle=visual_angle, ppd=ppd
)

# Check that frequency does not exceed Nyquist limit:
if frequency > ppd.horizontal / 2:
if frequency > (ppd / 2):
raise ValueError(
f"Grating frequency ({frequency}) should not exceed Nyquist limit"
f" {ppd.horizontal/2} (ppd/2)"
f"Grating frequency ({frequency}) should not exceed Nyquist limit {ppd/2} (ppd/2)"
)

# Ensure full/half period:
Expand All @@ -116,7 +121,6 @@ def resolve_grating_params(
# f" from {frequency_old} to {frequency},"
# " to ensure an even-numbered cycle width"
# )

# length = shape.width
# if period == "full":
# length = (length // pixels_per_period) * pixels_per_period
Expand All @@ -125,8 +129,8 @@ def resolve_grating_params(
# length = int(length)

return {
"shape": shape,
"visual_size": visual_size,
"length": length,
"visual_angle": visual_angle,
"ppd": ppd,
"frequency": frequency,
"phase_width": phase_width,
Expand All @@ -145,19 +149,55 @@ def mask_bars(
period="ignore",
orientation="horizontal",
):

# Try to resolve resolution
try:
shape, visual_size, ppd = resolution.resolve(shape=shape, visual_size=visual_size, ppd=ppd)
except ValueError:
ppd = resolution.validate_ppd(ppd)
shape = resolution.validate_shape(shape)
visual_size = resolution.validate_visual_size(visual_size)

# Orientation
if orientation == "horizontal":
length = shape.width
visual_angle = visual_size.width
ppd_1D = ppd.horizontal
elif orientation == "vertical":
length = shape.height
visual_angle = visual_size.height
ppd_1D = ppd.vertical

# Resolve params
params = resolve_grating_params(
shape=shape,
visual_size=visual_size,
length=length,
visual_angle=visual_angle,
n_phases=n_bars,
phase_width=bar_width,
ppd=ppd,
ppd=ppd_1D,
frequency=frequency,
period=period,
)
shape = params["shape"]
visual_size = params["visual_size"]
ppd = params["ppd"]
length = params["length"]
ppd_1D = params["ppd"]
visual_angle = params["visual_angle"]

# Orientation switch
if orientation == "horizontal":
shape = (shape.height, length) if shape.height is not None else length
visual_size = (
(visual_size.height, visual_angle) if visual_size.height is not None else visual_angle
)
ppd = (ppd.vertical, ppd_1D) if ppd.vertical is not None else ppd_1D
elif orientation == "vertical":
shape = (length, shape.width) if shape.width is not None else length
visual_size = (
(visual_angle, visual_size.width) if visual_size.width is not None else visual_angle
)
ppd = (ppd_1D, ppd.horizontal) if ppd.horizontal is not None else ppd_1D
shape = resolution.validate_shape(shape)
visual_size = resolution.validate_visual_size(visual_size)
ppd = resolution.validate_ppd(ppd)

# Create image-base:
x = np.linspace(0, visual_size.width, shape.width)
Expand All @@ -171,13 +211,20 @@ def mask_bars(
]

# Mask bars
distances = xx
distances = xx if orientation == "horizontal" else yy
for idx, edge in zip(reversed(range(len(bar_edges))), reversed(bar_edges)):
mask[distances <= edge] = int(idx + 1)

return {
"mask": mask,
**params,
"shape": shape,
"visual_size": visual_size,
"ppd": ppd,
"frequency": params["frequency"],
"bar_width": params["phase_width"],
"n_bars": params["n_phases"],
"period": params["period"],
"orientation": orientation,
}


Expand Down
26 changes: 13 additions & 13 deletions tests/test_gratings.py
Expand Up @@ -4,24 +4,24 @@


@pytest.mark.parametrize(
"ppd, shape, visual_size, n_phases, phase_width, frequency",
"ppd, length, visual_angle, n_phases, phase_width, frequency",
(
((32, 32), (1024, 1024), (32, 32), 8, 2, 1 / 4),
((32, 32), (1024, 1024), (32, 32), 4, 1, 1 / 2),
((32, 32), (1024, 1024), (32, 32), 8, None, 1 / 2),
((32, 32), (1024, 1024), (32, 32), None, 2, None),
((32, 32), (1024, 1024), (32, 32), None, None, 1 / 2),
((None, None), (1024, 1024), (32, 32), 8, 2, None),
(None, (1024, 1024), (32, 32), 8, 2, None),
(None, (1024, 1024), (32, 32), None, None, 1),
(None, (1024, 1024), None, 8, None, 1),
(32, 1024, 32, 8, 2, 1 / 4),
(32, 1024, 32, 4, 1, 1 / 2),
(32, 1024, 32, 8, None, 1 / 2),
(32, 1024, 32, None, 2, None),
(32, 1024, 32, None, None, 1 / 2),
(None, 1024, 32, 8, 2, None),
(None, 1024, 32, 8, 2, None),
(None, 1024, 32, None, None, 1),
(None, 1024, None, 8, None, 1),
),
)
def test_valid_params(ppd, shape, visual_size, n_phases, phase_width, frequency):
def test_valid_params(ppd, length, visual_angle, n_phases, phase_width, frequency):
resolve_grating_params(
ppd=ppd,
shape=shape,
visual_size=visual_size,
length=length,
visual_angle=visual_angle,
n_phases=n_phases,
phase_width=phase_width,
frequency=frequency,
Expand Down

0 comments on commit ebd02e5

Please sign in to comment.