Skip to content

Commit

Permalink
Extract 1D resolvers
Browse files Browse the repository at this point in the history
  • Loading branch information
JorisVincent committed Sep 23, 2022
1 parent d7d8f90 commit 7d13041
Showing 1 changed file with 82 additions and 29 deletions.
111 changes: 82 additions & 29 deletions stimuli/utils/resolution.py
Expand Up @@ -39,6 +39,61 @@ def resolve(shape=None, visual_size=None, ppd=None):
return shape, visual_size, ppd


def resolve_1D(length=None, visual_angle=None, ppd=None):
# How many unknowns passed in?
n_unknowns = (length is None) + (visual_angle is None) + (ppd is None)

# Triage based on number of unknowns
if n_unknowns > 1: # More than 1 unknown we cannot resolve
raise ValueError(
f"Too many unkowns to resolve resolution; {length},{visual_angle},{ppd}"
)
else: # 1 unknown, so need to resolve
# Which unknown?
if length is None:
length = pix_from_visual_angle_ppd_1D(visual_angle=visual_angle, ppd=ppd)
elif visual_angle is None:
visual_angle = visual_angle_from_length_ppd_1D(length=length, ppd=ppd)
elif ppd is None:
ppd = ppd_from_length_visual_angle_1D(
length=length, visual_angle=visual_angle
)

return length, visual_angle, ppd


def valid_1D(length, visual_angle, ppd):
"""Asserts that the combined specification of resolution is geometrically valid.
Asserts the combined specification of shape (in pixels), visual_size (deg) and ppd.
If this makes sense, i.e. (roughly), int(visual_size * ppd) == shape,
this function passes without output.
If the specification does not make sense, raises a ResolutionError.
Note that the resolution specification has to be fully resolved,
i.e., none of the parameters can be None
Parameters
----------
length : int, length in pixels
visual_angle : float, size in degrees
ppd : int, resolution in pixels-per-degree
Raises
------
ResolutionError
if resolution specification is invalid,
i.e. (roughly), if int(visual_angle * ppd) != length
"""

# TODO: Validate inputs

# Check by calculating one component
calculated = pix_from_visual_angle_ppd_1D(visual_angle=visual_angle, ppd=ppd)
if calculated != length:
raise ResolutionError(f"Invalid resolution; {visual_angle},{length},{ppd}")


def valid_resolution(shape, visual_size, ppd):
"""Asserts that the combined specification of resulation is geometrically valid.
Expand Down Expand Up @@ -77,6 +132,14 @@ def valid_resolution(shape, visual_size, ppd):
#############################
# Resolve components #
#############################
def visual_angle_from_length_ppd_1D(length, ppd):
if length is not None and ppd is not None:
visual_angle = length / ppd
else:
visual_angle = None
return visual_angle


def visual_size_from_shape_ppd(shape, ppd):
"""Calculate visual size (degrees) from given shape (pixels) and pixels-per-degree
Expand All @@ -101,14 +164,6 @@ def visual_size_from_shape_ppd(shape, ppd):
shape = validate_shape(shape)
ppd = validate_ppd(ppd)

# Define function for 1D case:
def visual_angle_from_length_ppd_1D(length, ppd):
if length is not None and ppd is not None:
visual_angle = length / ppd
else:
visual_angle = None
return visual_angle

# Calculate width and height in pixels
width = visual_angle_from_length_ppd_1D(shape.width, ppd.horizontal)
height = visual_angle_from_length_ppd_1D(shape.height, ppd.vertical)
Expand All @@ -119,6 +174,17 @@ def visual_angle_from_length_ppd_1D(length, ppd):
return visual_size


def pix_from_visual_angle_ppd_1D(visual_angle, ppd):
if visual_angle is not None and ppd is not None:
fpix = visual_angle * ppd
pix = int(fpix)
if fpix % pix:
warnings.warn(f"Rounding shape; {visual_angle} * {ppd} = {fpix} -> {pix}")
else:
pix = None
return pix


def shape_from_visual_size_ppd(visual_size, ppd):
"""Calculate shape (pixels) from given visual size (degrees) and pixels-per-degree
Expand All @@ -142,19 +208,6 @@ def shape_from_visual_size_ppd(visual_size, ppd):
visual_size = validate_visual_size(visual_size)
ppd = validate_ppd(ppd)

# Define function for 1D case:
def pix_from_visual_angle_ppd_1D(visual_angle, ppd):
if visual_angle is not None and ppd is not None:
fpix = visual_angle * ppd
pix = int(fpix)
if fpix % pix:
warnings.warn(
f"Rounding shape; {visual_angle} * {ppd} = {fpix} -> {pix}"
)
else:
pix = None
return pix

# Calculate width and height in pixels
width = pix_from_visual_angle_ppd_1D(visual_size.width, ppd.horizontal)
height = pix_from_visual_angle_ppd_1D(visual_size.height, ppd.vertical)
Expand Down Expand Up @@ -189,14 +242,6 @@ def ppd_from_shape_visual_size(shape, visual_size):
shape = validate_shape(shape)
visual_size = validate_visual_size(visual_size)

# Define function for 1D case:
def ppd_from_length_visual_angle_1D(length, visual_angle):
if visual_angle is not None and length is not None:
ppd = length / visual_angle
else:
ppd = None
return ppd

# Calculate horizontal and vertical ppds
horizontal = ppd_from_length_visual_angle_1D(shape.width, visual_size.width)
vertical = ppd_from_length_visual_angle_1D(shape.height, visual_size.height)
Expand All @@ -207,6 +252,14 @@ def ppd_from_length_visual_angle_1D(length, visual_angle):
return ppd


def ppd_from_length_visual_angle_1D(length, visual_angle):
if visual_angle is not None and length is not None:
ppd = length / visual_angle
else:
ppd = None
return ppd


#############################
# Validate components #
#############################
Expand Down

0 comments on commit 7d13041

Please sign in to comment.