def __init__(self, image, position=(0, 0), rgb_should_ascend=True, max_valid_back_movement=0, cyclic=False, enabled=True): """ Constructor - invoked when you create a new object by writing MoveByGradientValidator() :param image: Name of a BMP file, or the actual image (rectangular matrix of colors) :param position: See :attr:`~trajtracker.validators.MoveByGradientValidator.enabled` :param position: See :attr:`~trajtracker.validators.MoveByGradientValidator.position` :param rgb_should_ascend: See :attr:`~trajtracker.validators.MoveByGradientValidator.rgb_should_ascend` :param max_valid_back_movement: See :attr:`~trajtracker.validators.MoveByGradientValidator.max_valid_back_movement` :param cyclic: See :attr:`~trajtracker.validators.MoveByGradientValidator.cyclic` """ trajtracker.TTrkObject.__init__(self) EnabledDisabledObj.__init__(self, enabled=enabled) self._lcm = LocationColorMap(image, position=position, use_mapping=True, colormap="RGB") self.rgb_should_ascend = rgb_should_ascend self.max_valid_back_movement = max_valid_back_movement self.cyclic = cyclic self.single_color = None self.reset() self._calc_min_max_colors()
def __init__(self, image, position=(0, 0), rgb_should_ascend=True, max_valid_back_movement=0, last_validated_rgb=None, enabled=True): """ Constructor :param image: Name of a BMP file, or the actual image (rectangular matrix of colors) :param position: See :attr:`~trajtracker.movement.MoveByGradientValidator.enabled` :param position: See :attr:`~trajtracker.movement.MoveByGradientValidator.position` :param rgb_should_ascend: See :attr:`~trajtracker.movement.MoveByGradientValidator.rgb_should_ascend` :param max_valid_back_movement: See :attr:`~trajtracker.movement.MoveByGradientValidator.max_valid_back_movement` :param last_validated_rgb: See :attr:`~trajtracker.movement.MoveByGradientValidator.last_validated_rgb` """ super(MoveByGradientValidator, self).__init__(enabled=enabled) self._lcm = LocationColorMap(image, position=position, use_mapping=True, colormap="RGB") self.rgb_should_ascend = rgb_should_ascend self.max_valid_back_movement = max_valid_back_movement self.last_validated_rgb = last_validated_rgb self.reset()
def test_colormap_default(self): lcm = LocationColorMap(testimage) lcm.colormap = "default" print("Default colormap:") print(lcm.colormap) self.assertEqual(lcm.colormap[(0,)], 0) self.assertEqual(lcm.colormap[(2,)], 1) self.assertEqual(lcm.colormap[(30,)], len(all_colors)-1)
def test_colormap_rgb(self): lcm = LocationColorMap([[(0,0,255), (0,255,0), (255,0,0)]]) lcm.colormap = "RGB" print("RGB colormap:") print(lcm.colormap) self.assertEqual(lcm.colormap[(0,0,255)], 255) self.assertEqual(lcm.colormap[(0,255,0)], 255*256) self.assertEqual(lcm.colormap[(255,0,0)], 255*256*256)
def test_colormap_missing_colors(self): lcm = LocationColorMap(testimage) codes = {} i = 0 for c in {0, 2, 5, 10}: codes[(c,)] = i i += 1 try: lcm.colormap = codes self.fail("Succeeded setting an invalid value") except ValueError: pass
def test_use_mapping_invalid(self): lcm = LocationColorMap(testimage) lcm.use_mapping = True lcm.use_mapping = False try: lcm.use_mapping = "" self.fail("Succeeded setting an invalid value for LocationColorMap.use_mapping") except TypeError: pass try: lcm.use_mapping = None self.fail("Succeeded setting an invalid value for LocationColorMap.use_mapping") except TypeError: pass
def test_get_raw_colors(self): lcm = LocationColorMap(testimage) self.assertEqual(lcm.get_color_at(-3, -2), (0,)) self.assertEqual(lcm.get_color_at(0, 0), (30,)) self.assertEqual(lcm.get_color_at(2, 1), (15,)) self.assertIsNone(lcm.get_color_at(-4, 0)) self.assertIsNone(lcm.get_color_at(4, 0)) self.assertIsNone(lcm.get_color_at(0, -3)) self.assertIsNone(lcm.get_color_at(0, 3))
def test_invalid_get_color_at_args(self): lcm = LocationColorMap(testimage) self.assertRaises(TypeError, lambda: lcm.get_color_at("", 0)) self.assertRaises(TypeError, lambda: lcm.get_color_at(0.5, 0)) self.assertRaises(TypeError, lambda: lcm.get_color_at(0, "")) self.assertRaises(TypeError, lambda: lcm.get_color_at(0, 0.5)) self.assertRaises(TypeError, lambda: lcm.get_color_at(0, 0, use_mapping=""))
def test_get_mapped_colors(self): lcm = LocationColorMap(testimage) codes = dict(zip([(c,) for c in all_colors], all_colors)) # map each (c,) to c lcm.colormap = codes self.assertEqual(lcm.get_color_at(-3, -2, use_mapping=True), 0) lcm.use_mapping = True self.assertEqual(lcm.get_color_at(0, 0), 30) self.assertEqual(lcm.get_color_at(2, 1), 15) self.assertIsNone(lcm.get_color_at(4, 0)) self.assertIsNone(lcm.get_color_at(4, 0, use_mapping=True))
def test_get_with_coord(self): lcm = LocationColorMap(testimage, position=(3,2)) self.assertEqual(lcm.get_color_at(0, 0), (0,)) self.assertEqual(lcm.get_color_at(3, 2), (30,)) self.assertEqual(lcm.get_color_at(3, 4), (17,)) self.assertIsNone(lcm.get_color_at(7, 0)) self.assertIsNone(lcm.get_color_at(-1, 0)) self.assertIsNone(lcm.get_color_at(0, -1)) self.assertIsNone(lcm.get_color_at(0, 5))
def __init__(self, image, enabled=True, position=None, default_valid=False): """ Constructor :param image: Name of a BMP file, or the actual image (rectangular matrix of colors) :param enabled: See :attr:`~trajtracker.validators.LocationsValidator.enabled` :param position: See :attr:`~trajtracker.validators.LocationsValidator.position` :param default_valid: See :attr:`~trajtracker.validators.LocationsValidator.default_valid` """ super(LocationsValidator, self).__init__(enabled=enabled) self._lcm = LocationColorMap(image, position=position, use_mapping=True, colormap="RGB") self.default_valid = default_valid self.valid_colors = set() self.invalid_colors = set()
def test_rgb_mapping(self): lcm = LocationColorMap([[(0,0,255), (0,255,0), (255,0,0), (1,2,4), (0,0,0)]]) lcm.colormap = "RGB" lcm.use_mapping = True self.assertEqual(lcm.get_color_at(-2, 0), 255) self.assertEqual(lcm.get_color_at(-1, 0), 255*256) self.assertEqual(lcm.get_color_at(0, 0), 255*256*256) self.assertEqual(lcm.get_color_at(1, 0), 4 + 2*256 + 1*256*256)
def __init__(self, image, enabled=True, position=None, default_valid=False): """ Constructor - invoked when you create a new object by writing LocationsValidator() :param image: Name of a BMP file, or the actual image (rectangular matrix of colors) :param enabled: See :attr:`~trajtracker.validators.LocationsValidator.enabled` :param position: See :attr:`~trajtracker.validators.LocationsValidator.position` :param default_valid: See :attr:`~trajtracker.validators.LocationsValidator.default_valid` """ trajtracker.TTrkObject.__init__(self) EnabledDisabledObj.__init__(self, enabled=enabled) self._lcm = LocationColorMap(image, position=position, use_mapping=True, colormap="RGB") self.default_valid = default_valid self.valid_colors = set() self.invalid_colors = set()
class LocationsValidator(_BaseValidator): """ This validator gets an image, and validates that the mouse/finger would be placed only on pixels of certain color(s). You can define either the valid colors or the invalid colors. """ err_invalid_coordinates = "invalid_coords" arg_color = 'color' # ValidationFailed exception argument: the color in the invalid location #------------------------------------------------------------ def __init__(self, image, enabled=True, position=None, default_valid=False): """ Constructor :param image: Name of a BMP file, or the actual image (rectangular matrix of colors) :param enabled: See :attr:`~trajtracker.validators.LocationsValidator.enabled` :param position: See :attr:`~trajtracker.validators.LocationsValidator.position` :param default_valid: See :attr:`~trajtracker.validators.LocationsValidator.default_valid` """ super(LocationsValidator, self).__init__(enabled=enabled) self._lcm = LocationColorMap(image, position=position, use_mapping=True, colormap="RGB") self.default_valid = default_valid self.valid_colors = set() self.invalid_colors = set() #====================================================================== # Properties #====================================================================== #------------------------------------------------- @property def position(self): """ The position of the image: (x,y) tuple/list, indicating the image center For even-sized images, use the Expyriment standard. The position is used to align the image's coordinate space with that of check_xy() """ return self._lcm.position @position.setter def position(self, value): self._lcm.position = value self._log_setter("position") #------------------------------------------------- @property def default_valid(self): """ Indicates whether by default, all points are valid or not. If True: use :func:`~trajtracker.misc.LocationColorMap.invalid_colors` to indicate exceptions If False: use :func:`~trajtracker.misc.LocationColorMap.valid_colors` to indicate exceptions """ return self._default_valid @default_valid.setter def default_valid(self, value): _u.validate_attr_type(self, "default_valid", value, bool) self._default_valid = value self._log_setter("default_valid") #------------------------------------------------- @property def valid_colors(self): return self._valid_colors @valid_colors.setter def valid_colors(self, value): self._valid_colors = self._get_colors_as_ints(value, "valid_colors") self._log_setter("valid_colors") #------------------------------------------------- @property def invalid_colors(self): return self._invalid_colors @invalid_colors.setter def invalid_colors(self, value): self._invalid_colors = self._get_colors_as_ints(value, "valid_colors") self._log_setter("invalid_colors") def _get_colors_as_ints(self, value, attr_name): if u.is_rgb(value): value = (value, ) _u.validate_attr_type(self, attr_name, value, (list, tuple, set)) colors = set() for c in value: if not u.is_rgb(c): raise ValueError( _u.ErrMsg.attr_invalid_type(type(self), attr_name, "color", value)) colors.add(u.color_rgb_to_num(c)) return colors #====================================================================== # Validate #====================================================================== def reset(self, time0=None): pass def check_xyt(self, x_coord, y_coord, time=None): """ Check whether the given coordinate is a valid one :param time: ignored :return: None if all OK, ValidationFailed if error """ self._check_xyt_validate_and_log(x_coord, y_coord, time, False) if not self._enabled: return None color = self._lcm.get_color_at(x_coord, y_coord) if self._default_valid: ok = color not in self._invalid_colors else: ok = color in self._valid_colors if ok: return None else: return self._create_validation_error( self.err_invalid_coordinates, "You moved to an invalid location", {self.arg_color: color})
class MoveByGradientValidator(trajtracker.TTrkObject, EnabledDisabledObj): max_irrelevant_color_value = 10 cyclic_ratio = 5 err_gradient = "GradientViolation" def __init__(self, image, position=(0, 0), rgb_should_ascend=True, max_valid_back_movement=0, cyclic=False, enabled=True): """ Constructor - invoked when you create a new object by writing MoveByGradientValidator() :param image: Name of a BMP file, or the actual image (rectangular matrix of colors) :param position: See :attr:`~trajtracker.validators.MoveByGradientValidator.enabled` :param position: See :attr:`~trajtracker.validators.MoveByGradientValidator.position` :param rgb_should_ascend: See :attr:`~trajtracker.validators.MoveByGradientValidator.rgb_should_ascend` :param max_valid_back_movement: See :attr:`~trajtracker.validators.MoveByGradientValidator.max_valid_back_movement` :param cyclic: See :attr:`~trajtracker.validators.MoveByGradientValidator.cyclic` """ trajtracker.TTrkObject.__init__(self) EnabledDisabledObj.__init__(self, enabled=enabled) self._lcm = LocationColorMap(image, position=position, use_mapping=True, colormap="RGB") self.rgb_should_ascend = rgb_should_ascend self.max_valid_back_movement = max_valid_back_movement self.cyclic = cyclic self.single_color = None self.reset() self._calc_min_max_colors() #====================================================================== # Properties #====================================================================== #------------------------------------------------- @property def position(self): """ The position of the image: (x,y) tuple/list, indicating the image center For even-sized images, use the Expyriment standard. The position is used to align the image's coordinate space with that of update_xyt() """ return self._lcm.position @position.setter def position(self, value): self._lcm.position = value #------------------------------------------------- @property def rgb_should_ascend(self): """ Whether the valid movement is from lower RGB codes to higher RGB codes (True) or vice versa (False) """ return self._rgb_should_ascend @rgb_should_ascend.setter def rgb_should_ascend(self, value): _u.validate_attr_type(self, "rgb_should_ascend", value, bool) self._rgb_should_ascend = value self._log_property_changed("rgb_should_ascend") #------------------------------------------------- @property def max_valid_back_movement(self): """ The maximal valid delta of color-change in the opposite direction that would still be allowed """ return self._max_valid_back_movement @max_valid_back_movement.setter def max_valid_back_movement(self, value): _u.validate_attr_numeric(self, "max_valid_back_movement", value) _u.validate_attr_not_negative(self, "max_valid_back_movement", value) self._max_valid_back_movement = value self._log_property_changed("max_valid_back_movement") #------------------------------------------------- @property def single_color(self): """ Consider only one color out of the three (red / green / blue) available in the image. Each pixel in the image has a value between 0 and 255 for each of the 3 colors. If you set a single color (by setting this attribute to "R"/"G"/"B"), the validator will consider only the value of the selected color. Furthermore, the validator considers only pixels that are purely of this color (e.g., if you select "B", it means that only pixels with blue=0-255, red=0 and green=0 are relevant for validation). To accomodate small possible mistakes in the generation of the BMP image, the validator allows for miniscule presence of the irrelevant colors: i.e., if you set single_color="B", the validator will consider only pixels with blue=0-255, red<10, and green<10 (the treshold 10 can be changed by setting MoveByGradientValidator.max_irrelevant_color_value); and for this pixels, the validator will consider only the blue value. """ return self._single_color @single_color.setter def single_color(self, value): _u.validate_attr_type(self, "single_color", value, str, none_allowed=True) if value is not None and value not in self._colormaps: raise trajtracker.ValueError( "invalid value for {:}.single_color ({:}) - valid values are {:}" .format(_u.get_type_name(self), value, ",".join(self._colormaps.keys()))) self._single_color = value self._lcm.colormap = u.color_rgb_to_num if value is None else self._colormaps[ value] self._calc_min_max_colors() self._log_property_changed("single_color") def _calc_min_max_colors(self): if self._single_color is None: mapping_func = u.color_rgb_to_num else: mapping_func = self._colormaps[self._single_color] colors = [mapping_func(color) for color in self._lcm.available_colors] colors = [c for c in colors if c is not None] self._min_available_color = None if len(colors) == 0 else min(colors) self._max_available_color = None if len(colors) == 0 else max(colors) _colormaps = { 'R': lambda color: None if color[ 1] > MoveByGradientValidator.max_irrelevant_color_value or color[2] > MoveByGradientValidator.max_irrelevant_color_value else color[0], 'G': lambda color: None if color[ 0] > MoveByGradientValidator.max_irrelevant_color_value or color[2] > MoveByGradientValidator.max_irrelevant_color_value else color[1], 'B': lambda color: None if color[ 0] > MoveByGradientValidator.max_irrelevant_color_value or color[1] > MoveByGradientValidator.max_irrelevant_color_value else color[2], } #------------------------------------------------- @property def cyclic(self): """ Whether the gradient is cyclic, i.e., allows moving between the darkest to the lightest color """ return self._cyclic @cyclic.setter def cyclic(self, value): _u.validate_attr_type(self, "cyclic", value, bool) self._cyclic = value self._log_property_changed("cyclic") #====================================================================== # Validate #====================================================================== #----------------------------------------------------------------- def reset(self, time0=None): """ Reset the movement validation """ self._log_func_enters("reset", [time0]) self._last_color = None #----------------------------------------------------------------- def update_xyt(self, position, time_in_trial=None, time_in_session=None): """ Validate the movement :param position: (x,y) coordinates :param time_in_trial: ignored :param time_in_session: ignored :return: None if all OK, ExperimentError if error """ if not self._enabled: return None _u.update_xyt_validate_and_log(self, position) color = self._lcm.get_color_at(position[0], position[1]) if color is None: # color N/A -- can't validate self._last_color = None return None else: # casting "color" to int because get_color_at() may return uint8, and subtracting uint variables # would always yield a positive value color = int(color) if self._last_color is None: #-- Nothing to validate self._last_color = color return None expected_direction = 1 if self._rgb_should_ascend else -1 rgb_delta = (color - self._last_color) * expected_direction if rgb_delta >= 0: #-- All is OK self._last_color = color return None if rgb_delta >= -self._max_valid_back_movement: #-- The movement was in the opposite color diredction, but only slightly: #-- Don't issue an error, but also don't update "last_color" - remember the previous one return None if self._cyclic and self._min_available_color is not None: color_range = self._max_available_color - self._min_available_color if np.abs(rgb_delta) >= self.cyclic_ratio * (color_range - np.abs(rgb_delta)): # It's much more likely to interpret this movement as a "cyclic" movement - i.e., one that crossed # the boundary of lightest-to-darkest (or the other way around, depending on the ascend/descend direction) self._last_color = color return None if self._should_log(ttrk.log_debug): self._log_write( "InvalidDirection,last_color={:},curr_color={:}".format( self._last_color, color), True) return trajtracker.validators.create_experiment_error( self, self.err_gradient, "You moved in an invalid direction")
class LocationsValidator(trajtracker.TTrkObject, EnabledDisabledObj): err_invalid_coordinates = "InvalidCoords" arg_color = 'color' # ExperimentError argument: the color in the invalid location #------------------------------------------------------------ def __init__(self, image, enabled=True, position=None, default_valid=False): """ Constructor - invoked when you create a new object by writing LocationsValidator() :param image: Name of a BMP file, or the actual image (rectangular matrix of colors) :param enabled: See :attr:`~trajtracker.validators.LocationsValidator.enabled` :param position: See :attr:`~trajtracker.validators.LocationsValidator.position` :param default_valid: See :attr:`~trajtracker.validators.LocationsValidator.default_valid` """ trajtracker.TTrkObject.__init__(self) EnabledDisabledObj.__init__(self, enabled=enabled) self._lcm = LocationColorMap(image, position=position, use_mapping=True, colormap="RGB") self.default_valid = default_valid self.valid_colors = set() self.invalid_colors = set() #====================================================================== # Properties #====================================================================== #------------------------------------------------- @property def position(self): """ The position of the image: (x,y) tuple/list, indicating the image center For even-sized images, use the Expyriment standard. The position is used to align the image's coordinate space with that of update_xyt() """ return self._lcm.position @position.setter def position(self, value): self._lcm.position = value self._log_property_changed("position") #------------------------------------------------- @property def default_valid(self): """ Indicates whether by default, all points are valid or not. If True: use :func:`~trajtracker.misc.LocationColorMap.invalid_colors` to indicate exceptions If False: use :func:`~trajtracker.misc.LocationColorMap.valid_colors` to indicate exceptions """ return self._default_valid @default_valid.setter def default_valid(self, value): _u.validate_attr_type(self, "default_valid", value, bool) self._default_valid = value self._log_property_changed("default_valid") #------------------------------------------------- @property def valid_colors(self): return self._valid_colors @valid_colors.setter def valid_colors(self, value): self._valid_colors = self._get_colors_as_ints(value, "valid_colors") self._log_property_changed("valid_colors") #------------------------------------------------- @property def invalid_colors(self): return self._invalid_colors @invalid_colors.setter def invalid_colors(self, value): self._invalid_colors = self._get_colors_as_ints(value, "valid_colors") self._log_property_changed("invalid_colors") def _get_colors_as_ints(self, value, attr_name): if u.is_rgb(value): value = (value, ) _u.validate_attr_is_collection(self, attr_name, value, allow_set=True) colors = set() for c in value: if not u.is_rgb(c): raise trajtracker.ValueError( _u.ErrMsg.attr_invalid_type(type(self), attr_name, "color", value)) colors.add(u.color_rgb_to_num(c)) return colors #====================================================================== # Validate #====================================================================== #---------------------------------------------------------- def reset(self, time0=None): pass #---------------------------------------------------------- def update_xyt(self, position, time_in_trial=None, time_in_session=None): """ Check whether the given coordinate is a valid one :param time_in_trial: ignored :param time_in_session: ignored :return: None if all OK, ExperimentError if error """ _u.update_xyt_validate_and_log(self, position) if not self._enabled: return None color = self._lcm.get_color_at(position[0], position[1]) if self._default_valid: ok = color not in self._invalid_colors else: ok = color in self._valid_colors if ok: return None else: return trajtracker.validators.create_experiment_error( self, self.err_invalid_coordinates, "You moved to an invalid location", {self.arg_color: color})
class MoveByGradientValidator(_BaseValidator): """ This validator gets an image, and allows mouse to move only according to it - from a light color to a darker color (or vice versa). """ err_gradient = "gradient_violation" def __init__(self, image, position=(0, 0), rgb_should_ascend=True, max_valid_back_movement=0, last_validated_rgb=None, enabled=True): """ Constructor :param image: Name of a BMP file, or the actual image (rectangular matrix of colors) :param position: See :attr:`~trajtracker.movement.MoveByGradientValidator.enabled` :param position: See :attr:`~trajtracker.movement.MoveByGradientValidator.position` :param rgb_should_ascend: See :attr:`~trajtracker.movement.MoveByGradientValidator.rgb_should_ascend` :param max_valid_back_movement: See :attr:`~trajtracker.movement.MoveByGradientValidator.max_valid_back_movement` :param last_validated_rgb: See :attr:`~trajtracker.movement.MoveByGradientValidator.last_validated_rgb` """ super(MoveByGradientValidator, self).__init__(enabled=enabled) self._lcm = LocationColorMap(image, position=position, use_mapping=True, colormap="RGB") self.rgb_should_ascend = rgb_should_ascend self.max_valid_back_movement = max_valid_back_movement self.last_validated_rgb = last_validated_rgb self.reset() #====================================================================== # Properties #====================================================================== #------------------------------------------------- @property def position(self): """ The position of the image: (x,y) tuple/list, indicating the image center For even-sized images, use the Expyriment standard. The position is used to align the image's coordinate space with that of check_xy() """ return self._lcm.position @position.setter def position(self, value): self._lcm.position = value #------------------------------------------------- @property def rgb_should_ascend(self): """ Whether the valid movement is from lower RGB codes to higher RGB codes (True) or vice versa (False) """ return self._rgb_should_ascend @rgb_should_ascend.setter def rgb_should_ascend(self, value): _u.validate_attr_type(self, "rgb_should_ascend", value, bool) self._rgb_should_ascend = value self._log_setter("rgb_should_ascend") #------------------------------------------------- @property def last_validated_rgb(self): """ The last RGB color code to be validated (number between 0 and 65535; when assigning, you can also specify a list/tuple of 3 integers, each 0-255). If the last mouse position was on a color with RGB higher than this (or lower for rgb_should_ascend=False), validation is not done. This is intended to allow for "cyclic" movement, i.e., allow to "cross" the 0 (e.g. from 0 to 65535 and then back to 0). Setting the value to None disables this feature """ return self._last_validated_rgb @last_validated_rgb.setter def last_validated_rgb(self, value): if u.is_rgb(value): self._last_validated_rgb = u.color_rgb_to_num(value) else: if value is not None: _u.validate_attr_numeric(self, "last_validated_rgb", value) _u.validate_attr_not_negative(self, "last_validated_rgb", value) self._last_validated_rgb = value self._log_setter("last_validated_rgb") #------------------------------------------------- @property def max_valid_back_movement(self): """ The maximal valid delta of color-change in the opposite direction that would still be allowed """ return self._max_valid_back_movement @max_valid_back_movement.setter def max_valid_back_movement(self, value): _u.validate_attr_numeric(self, "max_valid_back_movement", value) _u.validate_attr_not_negative(self, "max_valid_back_movement", value) self._max_valid_back_movement = value self._log_setter("max_valid_back_movement") #====================================================================== # Validate #====================================================================== #----------------------------------------------------------------- def reset(self, time0=None): """ Reset the movement validation """ self._last_color = None #----------------------------------------------------------------- def check_xyt(self, x_coord, y_coord, time=None): """ Validate the movement :return: None if all OK, ValidationFailed if error """ self._check_xyt_validate_and_log(x_coord, y_coord, time, False) if not self._enabled: return None color = self._lcm.get_color_at(x_coord, y_coord) if color is None: return None # can't validate if self._last_color is None: #-- Nothing to validate self._last_color = color return None expected_direction = 1 if self._rgb_should_ascend else -1 rgb_delta = (color - self._last_color) * expected_direction if rgb_delta >= 0: #-- All is OK self._last_color = color return None if rgb_delta >= -self._max_valid_back_movement: #-- The movement was in the opposite color diredction, but only slightly: #-- Don't issue an error, but also don't update "last_color" - remember the previous one return None #-- Invalid situation! if self._last_validated_rgb is not None and \ ((self._rgb_should_ascend and self._last_color > self._last_validated_rgb) or (not self._rgb_should_ascend and self._last_color < self._last_validated_rgb)): #-- Previous color is very close to 0 - avoid validating, in order to allow "crossing the 0 color" return None return self._create_validation_error( self.err_gradient, "You moved in an invalid direction")
def test_get_even_size(self): img1 = [r[:-1] for r in testimage[:-1]] # remove last row and column lcm = LocationColorMap(img1) self.assertEqual(lcm.get_color_at(-1, 0), (5,))
def test_colormap_missing(self): lcm = LocationColorMap(testimage) self.assertRaises(ValueError, lambda: lcm.get_color_at(0, 0, use_mapping=True))
def test_get_default_props(self): lcm = LocationColorMap(testimage) self.assertEqual(frozenset([(c,) for c in all_colors]), lcm.available_colors) self.assertIsNone(lcm.colormap)
def test_colormap_good(self): lcm = LocationColorMap(testimage) lcm.colormap = self.get_good_colormap()