Skip to content

Commit

Permalink
Optionally round number of phases
Browse files Browse the repository at this point in the history
  • Loading branch information
JorisVincent committed Dec 6, 2022
1 parent ebd02e5 commit ce11bd4
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 20 deletions.
52 changes: 33 additions & 19 deletions stimuli/components/grating.py
@@ -1,4 +1,5 @@
import itertools
import warnings

import numpy as np

Expand Down Expand Up @@ -93,10 +94,40 @@ def resolve_grating_params(
except Exception as e:
raise Exception("Could not resolve grating frequency, phase_width, n_phases") from e

# Ensure full/half period?
if period != "ignore":
# Round n_phases
if period == "full": # n_phases has to be even
n_phases = np.round(n_phases / 2) * 2
elif period == "half": # n_phases can be odd
n_phases = np.round(n_phases)

# Check if n_phases fit in length
if length is not None and n_phases > 0 and length % n_phases:
raise resolution.ResolutionError(f"Cannot fit {n_phases} phases in {length} pix")

# Recalculate phases_pd
n_phases, min_angle, phases_pd = resolution.resolve_1D(
length=n_phases,
visual_angle=visual_angle,
ppd=None,
round=False,
)

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

if (old_phase_width is not None and phase_width != old_phase_width) or (
old_frequency is not None and frequency != old_frequency
):
warnings.warn(
f"Adjusted frequency and phase width to ensure {period} period: {old_frequency} ->"
f" {frequency}, {old_phase_width} -> {phase_width}"
)

# Now resolve resolution
visual_angle = min_angle if visual_angle is None else visual_angle
length, visual_angle, ppd = resolution.resolve_1D(
Expand All @@ -109,25 +140,6 @@ def resolve_grating_params(
f"Grating frequency ({frequency}) should not exceed Nyquist limit {ppd/2} (ppd/2)"
)

# Ensure full/half period:
# pixels_per_period = resolution.pix_from_visual_angle_ppd_1D(
# visual_angle=phase_width * 2, ppd=ppd.horizontal
# )
# if pixels_per_period % 2:
# frequency_old = frequency
# frequency = 1.0 / pixels_per_period * ppd.horizontal
# raise ValueError(
# "Warning: Square-wave frequency changed"
# 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
# elif period == "half":
# length = (length // pixels_per_period) * pixels_per_period + pixels_per_period / 2
# length = int(length)

return {
"length": length,
"visual_angle": visual_angle,
Expand Down Expand Up @@ -209,6 +221,8 @@ def mask_bars(
bar_edges = [
*itertools.accumulate(itertools.repeat(params["phase_width"], int(params["n_phases"])))
]
if params["period"] == "ignore":
bar_edges += [visual_angle]

# Mask bars
distances = xx if orientation == "horizontal" else yy
Expand Down
9 changes: 8 additions & 1 deletion tests/test_gratings.py
@@ -1,6 +1,6 @@
import pytest

from stimuli.components.grating import resolve_grating_params
from stimuli.components.grating import resolve_grating_params, square_wave


@pytest.mark.parametrize(
Expand All @@ -26,3 +26,10 @@ def test_valid_params(ppd, length, visual_angle, n_phases, phase_width, frequenc
phase_width=phase_width,
frequency=frequency,
)


def test_rounding():
ppd = 36
visual_size = (1.0, 1.0)
frequency = 2.80
stim = square_wave(ppd=ppd, visual_size=visual_size, frequency=frequency, period="half")

0 comments on commit ce11bd4

Please sign in to comment.