Skip to content

Commit

Permalink
Bumped to v0.8.3. Fixed overly strict type checking. Also fixed error…
Browse files Browse the repository at this point in the history
…s in sphinx documentation
  • Loading branch information
jbkinney committed Jan 28, 2025
1 parent a5dd872 commit 52573c2
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 147 deletions.
16 changes: 7 additions & 9 deletions logomaker/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Specify version
__version__ = '0.8.2' #bumped to 0.8.2 on 2025.01.19
__version__ = '0.8.3' #bumped to 0.8.3 on 2025.01.28

# Classes / functions imported with logomaker
from logomaker.src.Logo import Logo
Expand All @@ -18,27 +18,25 @@
from logomaker.tests.functional_tests_logomaker import run_tests

# demo functions for logomaker
import matplotlib.pyplot as pltx
import matplotlib.pyplot as plt
import os
import re
from logomaker.src.error_handling import check, handle_errors

@handle_errors
def demo(name='fig1b'):

"""
Performs a demonstration of the Logomaker software.
parameters
-----------
Parameters
----------
name: (str)
Must be one of {'fig1b', 'fig1c', 'fig1d', 'fig1e', 'fig1f', 'logo'}.
returns
Returns
-------
None.
matplotlib.figure.Figure
The current matplotlib Figure object.
"""

# build list of demo names and corresponding file names
Expand Down
56 changes: 12 additions & 44 deletions logomaker/src/Glyph.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from logomaker.src.error_handling import check, handle_errors
from logomaker.src.colors import get_rgb
import numpy as np
from logomaker.src.validate import validate_numeric

# Create global list of valid font weights
VALID_FONT_WEIGHT_STRINGS = [
Expand Down Expand Up @@ -73,7 +74,7 @@ class Glyph:
width: (number > 0)
x-coordinate span of the Glyph.
vpad: (number in [0,1])
vpad: (number in [0,1))
Amount of whitespace to leave within the Glyph bounding box above
and below the actual Glyph. Specifically, in a glyph with
height h = ceiling-floor, a margin of size h*vpad/2 will be left blank
Expand Down Expand Up @@ -319,30 +320,20 @@ def _make_patch(self):
self.ax.add_patch(self.patch)

def _input_checks(self):

"""
check input parameters in the Logo constructor for correctness
"""

from numbers import Number
# validate p
check(isinstance(int(self.p), (float, int)),
'type(p) = %s must be a number' % type(self.p))
self.p = validate_numeric(self.p, 'p')

# check c is of type str
check(isinstance(self.c, str),
'type(c) = %s; must be of type str ' %
type(self.c))

# validate floor
check(isinstance(self.floor, (float, int)),
'type(floor) = %s must be a number' % type(self.floor))
self.floor = float(self.floor)

# validate ceiling
check(isinstance(self.ceiling, (float, int)),
'type(ceiling) = %s must be a number' % type(self.ceiling))
self.ceiling = float(self.ceiling)
# validate floor and ceiling
self.floor = validate_numeric(self.floor, 'floor')
self.ceiling = validate_numeric(self.ceiling, 'ceiling')

# check floor <= ceiling
check(self.floor <= self.ceiling,
Expand All @@ -354,18 +345,10 @@ def _input_checks(self):
'ax must be either a matplotlib Axes object or None.')

# validate width
check(isinstance(self.width, (float, int)),
'type(width) = %s; must be of type float or int ' %
type(self.width))
check(self.width > 0, "width = %d must be > 0 " %
self.width)
self.width = validate_numeric(self.width, 'width', min_val=0.0)

# validate vpad
check(isinstance(self.vpad, (float, int)),
'type(vpad) = %s; must be of type float or int ' %
type(self.vpad))
check(0 <=self.vpad <1, "vpad = %d must be >= 0 and < 1 " %
self.vpad)
self.vpad = validate_numeric(self.vpad, 'vpad', min_val=0.0, max_val=1.0, min_inclusive=True, max_inclusive=False)

# validate font_name
check(isinstance(self.font_name, str),
Expand All @@ -389,13 +372,7 @@ def _input_checks(self):
self.edgecolor = get_rgb(self.edgecolor)

# Check that edgewidth is a number
check(isinstance(self.edgewidth, (float, int)),
'type(edgewidth) = %s must be a number' % type(self.edgewidth))
self.edgewidth = float(self.edgewidth)

# Check that edgewidth is nonnegative
check(self.edgewidth >= 0,
' edgewidth must be >= 0; is %f' % self.edgewidth)
self.edgewidth = validate_numeric(self.edgewidth, 'edgewidth', min_val=0.0)

# check dont_stretch_more_than is of type str
check(isinstance(self.dont_stretch_more_than, str),
Expand All @@ -419,20 +396,11 @@ def _input_checks(self):
self.mirror = bool(self.mirror)

# validate zorder
if self.zorder is not None :
check(isinstance(self.zorder, (float, int)),
'type(zorder) = %s; must be of type float or int ' %
type(self.zorder))
if self.zorder is not None:
self.zorder = validate_numeric(self.zorder, 'zorder')

# Check alpha is a number
check(isinstance(self.alpha, (float, int)),
'type(alpha) = %s must be a float or int' %
type(self.alpha))
self.alpha = float(self.alpha)

# Check 0 <= alpha <= 1.0
check(0 <= self.alpha <= 1.0,
'alpha must be between 0.0 and 1.0 (inclusive)')
self.alpha = validate_numeric(self.alpha, 'alpha', min_val=0.0, max_val=1.0)

# validate that figsize is array=like
check(isinstance(self.figsize, (tuple, list, np.ndarray)),
Expand Down
102 changes: 22 additions & 80 deletions logomaker/src/Logo.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

# Import stuff from logomaker
from logomaker.src.Glyph import Glyph
from logomaker.src.validate import validate_matrix
from logomaker.src.validate import validate_matrix, validate_numeric
from logomaker.src.error_handling import check, handle_errors
from logomaker.src.colors import get_color_dict, get_rgb
from logomaker.src.matrix import transform_matrix
Expand All @@ -27,12 +27,12 @@ class Logo:
must be single characters and row indices must be integers.
color_scheme: (str, dict, or array with length 3)
Specification of logo colors. Default is 'gray'. Can take a variety of
forms.
- (str) A built-in Logomaker color scheme in which the color of each character is determined by that character's identity. Options for DNA/RNA: 'classic', 'grays', or 'base_paring'. Options for protein: 'hydrophobicity', 'chemistry', or 'charge'.
- (str) A built-in matplotlib color name such as 'k' or 'tomato'
- (list) An RGB array, i.e., 3 floats with values in the interval [0,1]
- (dict) A dictionary that maps characters to colors, E.g., {'A': 'blue', 'C': 'yellow', 'G': 'green', 'T': 'red'}
Specification of logo colors. Default is 'gray'. Can take several forms:
For DNA/RNA, built-in schemes include 'classic', 'grays', or 'base_paring'.
For protein, built-in schemes include 'hydrophobicity', 'chemistry', or 'charge'.
Can also be a matplotlib color name like 'k' or 'tomato', an RGB array with
3 floats in [0,1], or a dictionary mapping characters to colors like
{'A': 'blue', 'C': 'yellow', 'G': 'green', 'T': 'red'}.
font_name: (str)
The character font to use when rendering the logo. For a list of
Expand Down Expand Up @@ -88,7 +88,7 @@ class Logo:
existing opacity values.
show_spines: (None or bool)
Whether a box should be drawn around the logo. For additional
Whether a box should be drawn around the logo. For additional
customization of spines, use Logo.style_spines().
ax: (matplotlib Axes object)
Expand Down Expand Up @@ -133,14 +133,14 @@ def __init__(self,
self.font_name = font_name
self.stack_order = stack_order
self.center_values = center_values
self.baseline_width = baseline_width
self.baseline_width = validate_numeric(baseline_width, 'baseline_width', min_val=0.0)
self.flip_below = flip_below
self.shade_below = shade_below
self.fade_below = fade_below
self.shade_below = validate_numeric(shade_below, 'shade_below', min_val=0.0, max_val=1.0)
self.fade_below = validate_numeric(fade_below, 'fade_below', min_val=0.0, max_val=1.0)
self.fade_probabilities = fade_probabilities
self.vpad = vpad
self.vsep = vsep
self.alpha = alpha
self.vpad = validate_numeric(vpad, 'vpad', min_val=0.0, max_val=1.0)
self.vsep = validate_numeric(vsep, 'vsep', min_val=0.0)
self.alpha = validate_numeric(alpha, 'alpha', min_val=0.0, max_val=1.0)
self.show_spines = show_spines
self.zorder = zorder
self.figsize = figsize
Expand Down Expand Up @@ -208,8 +208,6 @@ def _input_checks(self):
# validate dataframe
self.df = validate_matrix(self.df)

# CANNOT validate color_scheme here; this is done in Logo constructor.

# validate that font_name is a str
check(isinstance(self.font_name, str),
'type(font_name) = %s must be of type str' % type(self.font_name))
Expand All @@ -225,66 +223,16 @@ def _input_checks(self):
'type(center_values) = %s; must be of type bool.' %
type(self.center_values))

# check baseline_width is a number
check(isinstance(self.baseline_width, (int, float)),
'type(baseline_width) = %s must be of type number' %
(type(self.baseline_width)))

# check baseline_width >= 0.0
check(self.baseline_width >= 0.0,
'baseline_width = %s must be >= 0.0' % self.baseline_width)

# check that flip_below is boolean
check(isinstance(self.flip_below, bool),
'type(flip_below) = %s; must be of type bool ' %
type(self.flip_below))

# validate that shade_below is a number
check(isinstance(self.shade_below, (float, int)),
'type(shade_below) = %s must be of type float' %
type(self.shade_below))

# validate that shade_below is between 0 and 1
check(0.0 <= self.shade_below <= 1.0,
'shade_below must be between 0 and 1')

# validate that fade_below is a number
check(isinstance(self.fade_below, (float, int)),
'type(fade_below) = %s must be of type float' %
type(self.fade_below))

# validate that fade_below is between 0 and 1
check(0.0 <= self.fade_below <= 1.0,
'fade_below must be between 0 and 1')

# validate that fade_probabilities is boolean
check(isinstance(self.fade_probabilities, bool),
'type(fade_probabilities) = %s; must be of type bool '
% type(self.fade_probabilities))

# validate that vpad is a number
check(isinstance(self.vpad, (float, int)),
'type(vpad) = %s must be of type float' % type(self.vpad))

# validate that vpad is between 0 and 1
check(0.0 <= self.vpad <= 1.0, 'vpad must be between 0 and 1')

# validate that vsep is a number
check(isinstance(self.vsep, (float, int)),
'type(vsep) = %s; must be of type float or int ' %
type(self.vsep))

# validate that vsep is >= 0
check(self.vsep >= 0,
"vsep = %d must be greater than 0 " % self.vsep)

# validate that alpha is a number
check(isinstance(self.alpha, (float, int)),
'type(alpha) = %s must be of type float' % type(self.alpha))

# validate that alpha is between 0 and 1
check(0.0 <= self.alpha <= 1.0, 'alpha must be between 0 and 1')

# validate show_spines is None or boolean
check(isinstance(self.show_spines, bool) or (self.show_spines is None),
'show_spines = %s; show_spines must be None or boolean.'
Expand All @@ -296,9 +244,7 @@ def _input_checks(self):
repr(self.ax))

# validate zorder
check(isinstance(self.zorder, (float, int)),
'type(zorder) = %s; zorder must be a number.' %
type(self.zorder))
self.zorder = validate_numeric(self.zorder, 'zorder')

# validate that figsize is array=like
check(isinstance(self.figsize, (tuple, list, np.ndarray)),
Expand All @@ -322,17 +268,13 @@ def style_glyphs(self, color_scheme=None, **kwargs):
parameters
----------
color_scheme: (str, dict, or array with length 3)
Specification of logo colors. Default is 'gray'. Can take a variety of
forms:
- A built-in Logomaker color scheme in which the color of each
character is determined that character's identity. Options are:
'classic', 'grays', 'base_paring' for DNA/RNA; 'hydrophobicity',
'chemistry', 'charge' for protein.
- A built-in matplotlib color name such as 'k' or 'tomato'
- An RGB array, i.e., 3 floats with values in the interval [0,1]
- A dictionary that maps characters to colors, E.g.,
{'A': 'blue', 'C': 'yellow', 'G': 'green', 'T': 'red'}
Specification of logo colors. Default is 'gray'. Can take several forms:
For DNA/RNA, built-in schemes include 'classic', 'grays', or
'base_paring'. For protein, built-in schemes include 'hydrophobicity',
'chemistry', or 'charge'. Can also be a matplotlib color name like 'k'
or 'tomato', an RGB array with 3 floats in [0,1], or a dictionary
mapping characters to colors like {'A': 'blue', 'C': 'yellow',
'G': 'green', 'T': 'red'}.
**kwargs:
Keyword arguments to pass to Glyph.set_attributes()
Expand Down
55 changes: 55 additions & 0 deletions logomaker/src/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,58 @@ def validate_probability_mat(df):

# Return validated probability matrix to user
return prob_df


@handle_errors
def validate_numeric(value, name, min_val=None, max_val=None, allow_none=False,
min_inclusive=True, max_inclusive=True):
"""Validate numeric parameters with optional range checking
Parameters
----------
value : object
Value to validate
name : str
Parameter name for error messages
min_val : float, optional
Minimum allowed value
max_val : float, optional
Maximum allowed value
allow_none : bool, optional
Whether to allow None as a valid value
min_inclusive : bool, optional
Whether minimum bound is inclusive (default True)
max_inclusive : bool, optional
Whether maximum bound is inclusive (default True)
Returns
-------
float
Validated numeric value
"""
if allow_none and value is None:
return None

check(isinstance(value, (int, float, np.number)),
f'type({name}) = {type(value)}; must be numeric')

value_float = float(value)

if min_val is not None:
if min_inclusive:
check(value_float >= min_val,
f'{name} = {value} must be >= {min_val}')
else:
check(value_float > min_val,
f'{name} = {value} must be > {min_val}')

if max_val is not None:
if max_inclusive:
check(value_float <= max_val,
f'{name} = {value} must be <= {max_val}')
else:
check(value_float < max_val,
f'{name} = {value} must be < {max_val}')

return value_float

Loading

0 comments on commit 52573c2

Please sign in to comment.