Module micromelon.colour

Collection of functions for manipulating colour representations.
Includes functions for reading from the robot's colour sensors.

Expand source code
"""
Collection of functions for manipulating colour representations.
Includes functions for reading from the robot's colour sensors.
"""
from ._colour import *

__all__ = [
    "CS",
    "COLOURS",
    "random",
    "randomHue",
    "rgb",
    "pick",
    "hsv",
    "hue",
    "blend",
    "readAllSensors",
    "readSensor",
    "sensorSees",
    "rgbToHsv",
    "hsvToRgb",
    "hexToRgb",
    "rgbToHex",
]

Functions

def blend(c1, c2, ratio, isHSV=False)

Combine the colours c1 and c2 with the given ratio in the rgb space
Colour arguments are treated as rgb arrays by default but can be treated as hsv by setting the flag

Args

c1 : array
colour array of [r, g, b] or [h, s, v]
c2 : array
colour array of [r, g, b] or [h, s, v]
ratio : float
blend ratio - 0 results in all c1, 1 in all c2
isHSV : boolean
if true c1 and c2 are treated as hsv arrays

Raises

Exception if the blend ratio is not between 0 and 1 inclusive
Exception if the colours are not valid (0-255 for r, g, and b values, 0-360 for h, 0-1 for s and v)

Returns

Colour array [r, g, b] of c1 and c2 combined with the given ratio

Expand source code
def blend(c1, c2, ratio, isHSV=False):
    """
    Combine the colours c1 and c2 with the given ratio in the rgb space
    Colour arguments are treated as rgb arrays by default but can be treated as hsv by setting the flag

    Args:
      c1 (array): colour array of [r, g, b] or [h, s, v]
      c2 (array): colour array of [r, g, b] or [h, s, v]
      ratio (float): blend ratio - 0 results in all c1, 1 in all c2
      isHSV (boolean): if true c1 and c2 are treated as hsv arrays

    Raises:
      Exception if the blend ratio is not between 0 and 1 inclusive
      Exception if the colours are not valid (0-255 for r, g, and b values, 0-360 for h, 0-1 for s and v)

    Returns:
      Colour array [r, g, b] of c1 and c2 combined with the given ratio
    """
    if ratio > 1 or ratio < 0:
        raise Exception("Blend ratio must be between 0 and 1")
    if isHSV:
        c1 = hsv(c1[0], c1[1], c1[2])
        c2 = hsv(c2[0], c2[1], c2[2])

    _checkRGB(c1)
    _checkRGB(c2)

    r = round(c1[0] * (1 - ratio) + c2[0] * ratio)
    g = round(c1[1] * (1 - ratio) + c2[1] * ratio)
    b = round(c1[2] * (1 - ratio) + c2[2] * ratio)
    return [r, g, b]
def hexToRgb(hex)

Converts hex colour codes eg. #FFF or #00FF0F to rgb array

Args

hex : string
colour code # followed by 3 or 6 hexadecimal digits

Returns

Array [r, g, b] each in the range of 0 - 255 inclusive

Expand source code
def hexToRgb(hex):
    """
    Converts hex colour codes eg. #FFF or #00FF0F to rgb array

    Args:
      hex (string): colour code # followed by 3 or 6 hexadecimal digits

    Returns:
      Array [r, g, b] each in the range of 0 - 255 inclusive
    """
    # strip '#'
    if hex[0] == "#":
        hex = hex[1:]
    if len(hex) == 3:
        # Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
        return [int(hex[i] * 2, 16) for i in (0, 1, 2)]
    return [int(hex[i : i + 2], 16) for i in (0, 2, 4)]
def hsv(h, s, v)

Validates hsv colour and converts to rgb

Args

h : int
hue value - must be between 0 and 360 inclusive
s : float
saturation value - must be between 0 and 1 inclusive
v : float
brightness value - must be between 0 and 1 inclusive

Raises

Exception if the values are outside valid ranges for hsv
Valid range is 0 - 359 inclusive for hue and 0 - 1 inclusive for saturation and value

Returns

Array of three integers between 0 and 255 in the form [r, g, b] that is equivalent to the hsv arguments

Expand source code
def hsv(h, s, v):
    """
    Validates hsv colour and converts to rgb

    Args:
      h (int): hue value - must be between 0 and 360 inclusive
      s (float): saturation value - must be between 0 and 1 inclusive
      v (float): brightness value - must be between 0 and 1 inclusive

    Raises:
      Exception if the values are outside valid ranges for hsv
        Valid range is 0 - 359 inclusive for hue and 0 - 1 inclusive for saturation and value

    Returns:
      Array of three integers between 0 and 255 in the form [r, g, b] that is equivalent to the hsv arguments
    """
    c = _checkHSV(h, s, v)
    return hsvToRgb(c[0], c[1], c[2])
def hsvToRgb(h, s, v)

Converts an HSV color value to RGB. Conversion formula
adapted from http://en.wikipedia.org/wiki/HSV_color_space.

Args

h : int
hue value between 0 and 360
s : float
saturation between 0 and 1
v : float
brightness (value) between 0 and 1

Returns

Array [red, green, blue]
red, green, and blue values between 0 and 255 inclusive
hue between 0 and 360 inclusive, s and v between 0 and 1 inclusive

Expand source code
def hsvToRgb(h, s, v):
    """
    Converts an HSV color value to RGB. Conversion formula
    adapted from http://en.wikipedia.org/wiki/HSV_color_space.
    Args:
      h (int): hue value between 0 and 360
      s (float): saturation between 0 and 1
      v (float): brightness (value) between 0 and 1

    Returns:
      Array [red, green, blue]
        red, green, and blue values between 0 and 255 inclusive
        hue between 0 and 360 inclusive, s and v between 0 and 1 inclusive
    """
    r = None
    g = None
    b = None

    h60 = h / 60.0
    h60f = math.floor(h60)
    hi = int(h60f) % 6
    f = h60 - h60f
    p = v * (1 - s)
    q = v * (1 - f * s)
    t = v * (1 - (1 - f) * s)
    r, g, b = 0, 0, 0
    if hi == 0:
        r, g, b = v, t, p
    elif hi == 1:
        r, g, b = q, v, p
    elif hi == 2:
        r, g, b = p, v, t
    elif hi == 3:
        r, g, b = p, q, v
    elif hi == 4:
        r, g, b = t, p, v
    elif hi == 5:
        r, g, b = v, p, q
    r, g, b = round(r * 255), round(g * 255), round(b * 255)
    return [r, g, b]
def hue(hue)

Validates and calculates rgb colour with full saturation and brightness for the given hue

Args

hue : int
hue value - must be between 0 and 360 inclusive

Raises

Exception if the hue is outside the valid range of 0 - 360 inclusive

Returns

Array of three integers between 0 and 255 in the form [r, g, b]
that is equivalent to the hsv(hue, 1, 1)

Expand source code
def hue(hue):
    """
    Validates and calculates rgb colour with full saturation and brightness for the given hue

    Args:
      hue (int): hue value - must be between 0 and 360 inclusive

    Raises:
      Exception if the hue is outside the valid range of 0 - 360 inclusive

    Returns:
      Array of three integers between 0 and 255 in the form [r, g, b]
        that is equivalent to the hsv(hue, 1, 1)
    """
    return hsv(hue, 1, 1)
def pick(r, g, b)

Alias for Colour.rgb

Expand source code
def pick(r, g, b):
    """
    Alias for Colour.rgb
    """
    return rgb(r, g, b)
def random()

Generate a random colour in the rgb colour space

Returns

Array of three integers between 0 and 255 in the form [r, g, b]

Expand source code
def random():
    """
    Generate a random colour in the rgb colour space

    Returns:
      Array of three integers between 0 and 255 in the form [r, g, b]
    """
    r = _rand.randint(0, 255)
    g = _rand.randint(0, 255)
    b = _rand.randint(0, 255)
    return [r, g, b]
def randomHue()

Generate a random hue colour with full saturation and brightness

Returns

Array of three integers between 0 and 255 in the form [r, g, b]

Expand source code
def randomHue():
    """
    Generate a random hue colour with full saturation and brightness

    Returns:
      Array of three integers between 0 and 255 in the form [r, g, b]
    """
    return hsvToRgb(_rand.randint(0, 359), 1, 1)
def readAllSensors(option=CS.ALL)

Reads values from the three colour sensors.
You'll need to calibrate with these values for your situation

Args

option : int
one of CS.HUE, CS.RED, CS.GREEN, CS.BLUE, CS.BRIGHT, and CS.ALL
defaults to CS.ALL

Raises

Exception if the colour sensor read fails
Exception if the option argument isn't valid

Returns

Array of sensor readings in the form [left, middle, right]
CS.RED, CS.GREEN, CS.BLUE, and CS.BRIGHT options give integer readings between 0 and 255 inclusive
CS.HUE gives integer readings between 0 and 360 inclusive
CS.ALL gives an array of all readings in the form [HUE, RED, GREEN, BLUE, BRIGHT]

Expand source code
def readAllSensors(option=CS.ALL):
    """
    Reads values from the three colour sensors.
    You'll need to calibrate with these values for your situation

    Args:
      option (int): one of CS.HUE, CS.RED, CS.GREEN, CS.BLUE, CS.BRIGHT, and CS.ALL
                defaults to CS.ALL

    Raises:
      Exception if the colour sensor read fails
      Exception if the option argument isn't valid

    Returns:
      Array of sensor readings in the form [left, middle, right]
        CS.RED, CS.GREEN, CS.BLUE, and CS.BRIGHT options give integer readings between 0 and 255 inclusive
        CS.HUE gives integer readings between 0 and 360 inclusive
        CS.ALL gives an array of all readings in the form [HUE, RED, GREEN, BLUE, BRIGHT]
    """
    if isinstance(option, CS):
        option = option.value
    if option < 0 or option > 5:
        raise Exception("Invalid Colour Sensor read option")

    reading = _readRawColourFromRobot()
    if not reading:
        raise Exception("Colour sensor read failed")

    if option == CS.ALL.value:
        return reading

    return [reading[0][option], reading[1][option], reading[2][option]]
def readSensor(option=CS.HUE, sensor=1)

Reads a value from one of the colour sensors.
You'll need to calibrate with these values for your situation

Args

option : int
one of CS.HUE, CS.RED, CS.GREEN, CS.BLUE, CS.BRIGHT, and CS.ALL
defaults to CS.HUE
sensor : int
0, 1, or 2 for left, middle, and right sensors

Raises

Exception if the colour sensor read fails
Exception if the option or sensor arguments aren't valid

Returns

Either integer or array depending on option
CS.RED, CS.GREEN, CS.BLUE, and CS.BRIGHT options give integer readings between 0 and 255 inclusive
CS.HUE gives integer readings between 0 and 360 inclusive
CS.ALL gives an array of all readings in the form [HUE, RED, GREEN, BLUE, BRIGHT]

Expand source code
def readSensor(option=CS.HUE, sensor=1):
    """
    Reads a value from one of the colour sensors.
    You'll need to calibrate with these values for your situation

    Args:
      option (int): one of CS.HUE, CS.RED, CS.GREEN, CS.BLUE, CS.BRIGHT, and CS.ALL
                defaults to CS.HUE
      sensor (int): 0, 1, or 2 for left, middle, and right sensors

    Raises:
      Exception if the colour sensor read fails
      Exception if the option or sensor arguments aren't valid

    Returns:
      Either integer or array depending on option
        CS.RED, CS.GREEN, CS.BLUE, and CS.BRIGHT options give integer readings between 0 and 255 inclusive
        CS.HUE gives integer readings between 0 and 360 inclusive
        CS.ALL gives an array of all readings in the form [HUE, RED, GREEN, BLUE, BRIGHT]
    """
    if sensor < 0 or sensor > 2:
        raise Exception("Argument for sensor must be 0, 1, or 2")

    reading = readAllSensors(option)
    return reading[sensor]
def rgb(r, g, b)

Check the values are valid for an rgb colour and return

Args

r : int
red value - must be between 0 and 255 inclusive
g : int
green value - must be between 0 and 255 inclusive
b : int
blue value - must be between 0 and 255 inclusive

Raises

Exception if the values aren't all between 0 and 255 inclusive

Returns

Array of three integers between 0 and 255 in the form [r, g, b]

Expand source code
def rgb(r, g, b):
    """
    Check the values are valid for an rgb colour and return

    Args:
      r (int): red value - must be between 0 and 255 inclusive
      g (int): green value - must be between 0 and 255 inclusive
      b (int): blue value - must be between 0 and 255 inclusive

    Raises:
      Exception if the values aren't all between 0 and 255 inclusive

    Returns:
      Array of three integers between 0 and 255 in the form [r, g, b]
    """
    c = [r, g, b]
    _checkRGB(c)
    return c
def rgbToHex(r, g, b)

Converts r, g, b colour to hex colour codes

Args

r, g, b (int): red, green, and blue values between 0 and 255 inclusive

Returns

hexadecimal string beginning with '#' eg. #00FF0F

Expand source code
def rgbToHex(r, g, b):
    """
    Converts r, g, b colour to hex colour codes

    Args:
      r, g, b (int): red, green, and blue values between 0 and 255 inclusive

    Returns:
      hexadecimal string beginning with '#' eg. #00FF0F
    """
    return "#{0:02x}{1:02x}{2:02x}".format(r, g, b)
def rgbToHsv(r, g, b)

Converts an RGB color value to HSV. Conversion formula
adapted from http://en.wikipedia.org/wiki/HSV_color_space.

Args

r, g, b (int): red, green, and blue values between 0 and 255 inclusive

Returns

Array [hue, saturation, value]
hue between 0 and 360 inclusive, s and v between 0 and 1 inclusive

Expand source code
def rgbToHsv(r, g, b):
    """
    Converts an RGB color value to HSV. Conversion formula
    adapted from http://en.wikipedia.org/wiki/HSV_color_space.

    Args:
      r, g, b (int): red, green, and blue values between 0 and 255 inclusive

    Returns:
      Array [hue, saturation, value]
        hue between 0 and 360 inclusive, s and v between 0 and 1 inclusive
    """
    r /= 255.0
    g /= 255.0
    b /= 255.0

    mx = max(r, g, b)
    mn = min(r, g, b)
    v = mx
    d = mx - mn
    s = 0 if mx == 0 else d / mx
    h = None

    if mx == mn:
        h = 0
        # achromatic
        return [h, s, v]

    if r == mx:
        h = (60 * ((g - b) / d) + 360) % 360
    if g == mx:
        h = (60 * ((b - r) / d) + 120) % 360
    if b == mx:
        h = (60 * ((r - g) / d) + 240) % 360

    return [round(h) % 360, s, v]
def sensorSees(rgb, sensor=1, tolerance=20)

Determines whether or not the specified sensor sees the colour within the tolerance

Args

rgb : array
[r, g, b] - An rgb colour in the form of an array [r, g, b]
with each value between 0 and 255 inclusive or a COLOURS enum element
sensor : int
0, 1, or 2 for left, middle, right. Defaults to middle
tolerance : int
How close the colour needs to be to what the sensor detects.
If the detected colour hue is within the tolerance and the saturations
are within 0.65 then it will return true.
For shades (saturation < 0.04 or brightness < 25) the brightness is compared
and if they are within a scaled brightness range it will return true.
Must be between 0 and 255

Raises

Exception for invalid arguments
Exception if the colour sensor read fails

Returns

True if the colour matches what the sensor detects within the tolerance range.
False otherwise

Expand source code
def sensorSees(rgb, sensor=1, tolerance=20):
    """
    Determines whether or not the specified sensor sees the colour within the tolerance

    Args:
      rgb (array): [r, g, b] - An rgb colour in the form of an array [r, g, b] 
                    with each value between 0 and 255 inclusive or a COLOURS enum element
      sensor (int): 0, 1, or 2 for left, middle, right. Defaults to middle
      tolerance (int): How close the colour needs to be to what the sensor detects.
                    If the detected colour hue is within the tolerance and the saturations
                    are within 0.65 then it will return true.
                    For shades (saturation < 0.04 or brightness < 25) the brightness is compared
                    and if they are within a scaled brightness range it will return true.
                    Must be between 0 and 255

    Raises:
      Exception for invalid arguments
      Exception if the colour sensor read fails

    Returns:
      True if the colour matches what the sensor detects within the tolerance range.
      False otherwise
    """
    if sensor not in [0, 1, 2]:
        raise Exception("Argument for sensor must be 0, 1, or 2")
    if isNumber(tolerance) and (tolerance < 0 or tolerance > 255):
        raise Exception("Tolerance must be a number between 0 and 255")
    if isinstance(rgb, COLOURS):
        rgb = rgb.value
    _checkRGB(rgb)

    def colourDetected(sensorRawReading, rgbColour, tolerance):
        sh = sensorRawReading[CS.HUE.value]
        sr = sensorRawReading[CS.RED.value]
        sg = sensorRawReading[CS.GREEN.value]
        sb = sensorRawReading[CS.BLUE.value]
        sw = sensorRawReading[CS.BRIGHT.value]
        hsvReading = rgbToHsv(sr, sg, sb)
        sSat = hsvReading[1]
        
        cr = rgbColour[0]
        cg = rgbColour[1]
        cb = rgbColour[2]
        hsvColour = rgbToHsv(cr, cg, cb)
        ch = hsvColour[0]
        cSat = hsvColour[1]
        cBright = hsvColour[2] * 255
        
        # Need a bit more tolerance around white than black
        rangeScaledWithBrightness = constrain(
            tolerance * scale(cBright, 0, 255, 1.5, 3), 0, 255)
            
        if cBright < 25 or cSat < 0.04:
            # Looking for a shade or close enough
            brightnessMatch = abs(sw - cBright) < rangeScaledWithBrightness
            saturationOrBrightnessLowEnough = sSat < 0.3 or sw < 32
            return saturationOrBrightnessLowEnough and brightnessMatch
        
        # No useful brightness to see a colour
        if sw <= 32:
            return False
        # No useful saturation to see a colour
        if sSat < 0.04:
            return False
        
        hueMatch = mathModuloDistance(ch, sh, 360) < tolerance
        # Be very generous on saturation match
        saturationMatch = abs(sSat - cSat) < 0.65
        return hueMatch and saturationMatch

    reading = readAllSensors()
    return colourDetected(reading[sensor], rgb, tolerance)

Classes

class COLOURS (value, names=None, *, module=None, qualname=None, type=None, start=1)

A collection of predefined colour names mapped to an RGB array

Expand source code
class COLOURS(Enum):
    """
    A collection of predefined colour names mapped to an RGB array
    """

    WHITE = [255, 255, 255]
    GREY = [128, 128, 128]
    BLACK = [0, 0, 0]
    LAVENDER = [220, 190, 255]
    MAGENTA = [240, 50, 230]
    PURPLE = [163, 53, 232]
    CYAN = [70, 240, 240]
    BLUE = [0, 128, 255]
    NAVY = [0, 0, 128]
    MINT = [170, 255, 195]
    GREEN = [0, 255, 0]
    TEAL = [0, 128, 128]
    LIME = [210, 245, 60]
    YELLOW = [255, 255, 0]
    OLIVE = [128, 128, 0]
    APRICOT = [255, 215, 180]
    ORANGE = [255, 128, 0]
    BROWN = [170, 110, 40]
    PINK = [250, 190, 212]
    RED = [255, 0, 34]
    MAROON = [128, 0, 0]

Ancestors

  • enum.Enum

Class variables

var APRICOT
var BLACK
var BLUE
var BROWN
var CYAN
var GREEN
var GREY
var LAVENDER
var LIME
var MAGENTA
var MAROON
var MINT
var NAVY
var OLIVE
var ORANGE
var PINK
var PURPLE
var RED
var TEAL
var WHITE
var YELLOW
class CS (value, names=None, *, module=None, qualname=None, type=None, start=1)

A collection of attributes that can be read from the colour sensor
Values correspond to attribute position in the sensor read array

CS.HUE = 0
CS.RED = 1
CS.GREEN = 2
CS.BLUE = 3
CS.BRIGHT = 4
CS.ALL = 5

Expand source code
class CS(Enum):
    """
    A collection of attributes that can be read from the colour sensor
    Values correspond to attribute position in the sensor read array

    CS.HUE = 0
    CS.RED = 1
    CS.GREEN = 2
    CS.BLUE = 3
    CS.BRIGHT = 4
    CS.ALL = 5
    """

    HUE = 0
    RED = 1
    GREEN = 2
    BLUE = 3
    BRIGHT = 4
    ALL = 5

Ancestors

  • enum.Enum

Class variables

var ALL
var BLUE
var BRIGHT
var GREEN
var HUE
var RED