Skip to content

Add typing to LEDs #30

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Apr 28, 2023
Merged
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 49 additions & 22 deletions adafruit_fancyled/adafruit_fancyled.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

* Author(s): PaintYourDragon
"""
from __future__ import annotations

# imports

Expand All @@ -21,6 +22,12 @@

from math import floor

try:
from typing import Tuple, Union, Optional, List, Any
from circuitpython_typing.led import FillBasedColorUnion
except ImportError:
pass


# FancyLED provides color- and palette-related utilities for LED projects,
# offering a buttery smooth look instead of the usual 8-bit-like "blip blip"
Expand All @@ -46,7 +53,7 @@ class CRGB:
c = CRGB(CHSV(0.0, 1.0, 1.0))
"""

def __init__(self, red, green=0.0, blue=0.0):
def __init__(self, red: float, green: float = 0.0, blue: float = 0.0) -> None:
# pylint: disable=too-many-branches
if isinstance(red, CHSV):
# If first/only argument is a CHSV type, perform HSV to RGB
Expand Down Expand Up @@ -81,17 +88,17 @@ def __init__(self, red, green=0.0, blue=0.0):
self.green = clamp_norm(green)
self.blue = clamp_norm(blue)

def __repr__(self): # pylint: disable=invalid-repr-returned
def __repr__(self) -> Tuple[int, int, int]: # pylint: disable=invalid-repr-returned
return (self.red, self.green, self.blue)

def __str__(self):
def __str__(self) -> str:
return "(%s, %s, %s)" % (self.red, self.green, self.blue)

def __len__(self):
def __len__(self) -> int:
"""Retrieve total number of color-parts available."""
return 3

def __getitem__(self, key):
def __getitem__(self, key: int) -> float:
"""Retrieve red, green or blue value as iterable."""
if key == 0:
return self.red
Expand All @@ -101,7 +108,7 @@ def __getitem__(self, key):
return self.blue
raise IndexError

def pack(self, white=None):
def pack(self, white: Optional[float] = None) -> FillBasedColorUnion:
"""'Pack' a `CRGB` color into a 24-bit RGB integer, OR, optionally
assign a white element for RGBW NeoPixels and return as a 4-tuple,
either of which can be passed to the NeoPixel setter.
Expand Down Expand Up @@ -181,25 +188,27 @@ class CHSV:
"""

# pylint: disable=invalid-name
def __init__(self, h, s=1.0, v=1.0):
def __init__(self, h: float, s: float = 1.0, v: float = 1.0) -> None:
if isinstance(h, float):
self.hue = h # Don't clamp! Hue can wrap around forever.
else:
self.hue = float(h) / 256.0
self.saturation = clamp_norm(s)
self.value = clamp_norm(v)

def __repr__(self): # pylint: disable=invalid-repr-returned
def __repr__( # pylint: disable=invalid-repr-returned
self,
) -> Tuple[float, float, float]:
return (self.hue, self.saturation, self.value)

def __str__(self):
def __str__(self) -> str:
return "(%s, %s, %s)" % (self.hue, self.saturation, self.value)

def __len__(self):
def __len__(self) -> int:
"""Retrieve total number of 'color-parts' available."""
return 3

def __getitem__(self, key):
def __getitem__(self, key: int) -> float:
"""Retrieve hue, saturation or value as iterable."""
if key == 0:
return self.hue
Expand All @@ -209,7 +218,7 @@ def __getitem__(self, key):
return self.value
raise IndexError

def pack(self, white=None):
def pack(self, white: Optional[float] = None) -> FillBasedColorUnion:
"""'Pack' a `CHSV` color into a 24-bit RGB integer, OR, optionally
assign a white element for RGBW NeoPixels and return as a 4-tuple,
either of which can be passed to the NeoPixel setter.
Expand All @@ -228,12 +237,16 @@ def pack(self, white=None):
return CRGB(self).pack(white)


def clamp(val, lower, upper):
def clamp(
val: Union[int, float], lower: Union[int, float], upper: Union[int, float]
) -> Union[int, float]:
"""Constrain value within a numeric range (inclusive)."""
return max(lower, min(val, upper))


def normalize(val, inplace=False):
def normalize(
val: int, inplace: Optional[bool] = False
) -> Union[None, float, List[float]]:
"""Convert 8-bit (0 to 255) value to normalized (0.0 to 1.0) value.

Accepts integer, 0 to 255 range (input is clamped) or a list or tuple
Expand All @@ -259,7 +272,7 @@ def normalize(val, inplace=False):
return [normalize(n) for n in val]


def clamp_norm(val):
def clamp_norm(val: Union[float, int]) -> Union[float, int]:
"""Clamp or normalize a value as appropriate to its type. If a float is
received, the return value is the input clamped to a 0.0 to 1.0 range.
If an integer is received, a range of 0-255 is scaled to a float value
Expand All @@ -270,7 +283,9 @@ def clamp_norm(val):
return normalize(val)


def denormalize(val, inplace=False):
def denormalize(
val: Union[float, List[float], Tuple[float]], inplace=False
) -> Union[int, List[int]]:
"""Convert normalized (0.0 to 1.0) value to 8-bit (0 to 255) value

Accepts float, 0.0 to 1.0 range or a list or tuple of floats. In
Expand Down Expand Up @@ -300,7 +315,7 @@ def denormalize(val, inplace=False):
return [denormalize(n) for n in val]


def unpack(val):
def unpack(val: int) -> CRGB:
"""'Unpack' a 24-bit color into a `CRGB` instance.

:param int val: 24-bit integer a la ``0x00RRGGBB``.
Expand All @@ -318,7 +333,9 @@ def unpack(val):
) # Blue


def mix(color1, color2, weight2=0.5):
def mix(
color1: Union[CRGB, CHSV], color2: Union[CRGB, CHSV], weight2: float = 0.5
) -> CRGB:
"""Blend between two colors using given ratio. Accepts two colors (each
may be `CRGB`, `CHSV` or packed integer), and weighting (0.0 to 1.0)
of second color.
Expand All @@ -327,7 +344,7 @@ def mix(color1, color2, weight2=0.5):
"""

clamp(weight2, 0.0, 1.0)
weight1 = 1.0 - weight2
weight1: float = 1.0 - weight2

if isinstance(color1, CHSV):
if isinstance(color2, CHSV):
Expand Down Expand Up @@ -369,7 +386,9 @@ def mix(color1, color2, weight2=0.5):
GFACTOR = 2.7 # Default gamma-correction factor for function below


def gamma_adjust(val, gamma_value=None, brightness=1.0, inplace=False):
def gamma_adjust(
val: Any, gamma_value: Any = None, brightness: Any = 1.0, inplace=False
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

brightness can be typed float and inplace can be typed with bool

val and gamma_value might be able to have something more specific with Union and a few different types, but I'm not certain. I'd need to read over the docstring some more to grok the different modes of operation.

Copy link
Contributor

@FoamyGuy FoamyGuy Apr 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hadn't seen that you asked about this function in the other comment until after I posted these.

I'll try to take a closer look tomorrow and offer what guidance I can.

I think It's also okay if we're not 100% perfect on this round of typing, things can always be refined and improved as time goes on.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@FoamyGuy I've made the mentioned changes (made brightness obey the cases in the docstring) excluding the situation that will arise with the three unique cases on the gamma_adjust function. Case one and two are easy enough to solve, adding case 3 to the mix makes type hints difficult to build in and probably difficult for a newer user to understand.

If you have any more input on this I can go back and try to add the typing, or I can just keep it with Any.

) -> Union[float, CRGB, List[Union[float, CRGB]]]:
"""Provides gamma adjustment for single values, `CRGB` and `CHSV` types
and lists of any of these.

Expand Down Expand Up @@ -506,7 +525,9 @@ def gamma_adjust(val, gamma_value=None, brightness=1.0, inplace=False):
)


def palette_lookup(palette, position):
def palette_lookup(
palette: Union[List[CRGB], List[CHSV], List[int]], position: float
) -> Union[CRGB, CHSV]:
"""Fetch color from color palette, with interpolation.

:param palette: color palette (list of CRGB, CHSV and/or packed integers)
Expand All @@ -528,7 +549,13 @@ def palette_lookup(palette, position):
return mix(color1, color2, weight2)


def expand_gradient(gradient, length):
def expand_gradient(
gradient: Union[
List[List[float, Union[int, CRGB, CHSV]]],
Tuple[Tuple[float, Union[int, CRGB, CHSV]]],
],
length: float,
) -> List[CRGB]:
"""Convert gradient palette into standard equal-interval palette.

:param sequence gradient: List or tuple of of 2-element lists/tuples
Expand Down