Skip to content

utils.benchmark

Utils - Benchmark¤

ColorsBenchmark ¤

A base class to create charts to benchmark the color palettes.

Parameters:

Name Type Description Default
colors

teller.Colors objects which has properties such as hex.

required

LightnessBenchmark (ColorsBenchmark) ¤

Benchmark color palette based on lightness.

Lightness

If a color is too light, it would be very hard to read on white background. If a color is too dark, it would be hard to read on black background.

Parameters:

Name Type Description Default
colors

teller.Colors object which has properties such as hex.

required

_bounded_by_min_max(self, color, min_lightness=25, max_lightness=85) private ¤

Wheter the color lightness is bounded by min and max.

Parameters:

Name Type Description Default
color

a color in Lab color space with a lab_l property

required
min_lightness

the min lightness value, defaults to 25

25
max_lightness

the max lightness value, defaults to 85

85
Source code in colorteller/utils/benchmark.py
def _bounded_by_min_max(self, color, min_lightness=25, max_lightness=85):
    """Wheter the color lightness is bounded by min and max.

    :param color: a color in Lab color space with a `lab_l` property
    :param min_lightness: the min lightness value, defaults to 25
    :param max_lightness: the max lightness value, defaults to 85
    """
    return self._greater_than_min(color, min_lightness) and self._smaller_than_max(
        color, max_lightness
    )

_greater_than_min(self, color, min_lightness=25) private ¤

Whether the lightness of the color is lighter than the min value set here.

Parameters:

Name Type Description Default
color

a color in Lab color space with a lab_l property

required
min_lightness int

the min lightness value, defaults to 25

25
Source code in colorteller/utils/benchmark.py
def _greater_than_min(self, color, min_lightness: int = 25):
    """Whether the lightness of the color is lighter than the min value set here.

    :param color: a color in Lab color space with a `lab_l` property
    :param min_lightness: the min lightness value, defaults to 25
    """
    return color.lab_l >= min_lightness

_lightness_benchmark(self, colors, min_lightness=25, max_lightness=85) private ¤

_lightness_benchmark calculates all the benchmarks based on lightness.

Parameters:

Name Type Description Default
colors list

a list of colors in Lab color space

required
min_lightness int, optional

the min lightness value, defaults to 25

25
max_lightness int, optional

the max lightness value, defaults to 85

85

Returns:

Type Description
dict

the benchmark results

Source code in colorteller/utils/benchmark.py
def _lightness_benchmark(self, colors, min_lightness=25, max_lightness=85):
    """_lightness_benchmark calculates all the benchmarks based on lightness.

    :param colors: a list of colors in Lab color space
    :type colors: list
    :param min_lightness: the min lightness value, defaults to 25
    :type min_lightness: int, optional
    :param max_lightness: the max lightness value, defaults to 85
    :type max_lightness: int, optional
    :return: the benchmark results
    :rtype: dict
    """
    return {
        "lightness": [c.lab_l for c in colors],
        "min_lightness": min_lightness,
        "max_lightness": max_lightness,
        "smaller_than_max": [
            self._smaller_than_max(c, max_lightness) for c in colors
        ],
        "greater_than_min": [
            self._greater_than_min(c, min_lightness) for c in colors
        ],
        "bounded_by_min_max": [
            self._bounded_by_min_max(c, min_lightness, max_lightness)
            for c in colors
        ],
    }

_smaller_than_max(self, color, max_lightness=85) private ¤

Whether the lightness of the color is larger than the max value set here

Parameters:

Name Type Description Default
color

a color in Lab color space with a lab_l property

required
max_lightness int

the max lightness value, defaults to 85

85
Source code in colorteller/utils/benchmark.py
def _smaller_than_max(self, color, max_lightness: int = 85):
    """Whether the lightness of the color is larger than the max value set here

    :param color: a color in Lab color space with a `lab_l` property
    :param max_lightness: the max lightness value, defaults to 85
    """
    return color.lab_l <= max_lightness

metric(self) ¤

calculate the metrics of the current benchmark

Source code in colorteller/utils/benchmark.py
def metric(self):
    """calculate the metrics of the current benchmark"""
    return {
        "method": "lightness",
        "data": self._lightness_benchmark(
            self.LabColor, min_lightness=25, max_lightness=85
        ),
    }

PerceptualDistanceBenchmark (ColorsBenchmark) ¤

Create benchmark based on perceptual distances.

Used in teller.Colors.metrics

While this class can be used independently, it is mostly designed for the methods argument of teller.Colors.metrics, e.g., methods=[PerceptualDistanceBenchmark].

Parameters:

Name Type Description Default
colors

teller.Colors object which has properties such as hex.

required

_delta_e_noticable_distance(self, distance, threshold=5) private ¤

Decide whether the two colors are noticable based on deltaE distance.

If the distance is larger than threshold, the two colors are noticable.

References

The choice of the threshold is based on the following paper:

Mokrzycki WS, Tatol M. Color difference Delta E - A survey. Machine Graphics and Vision. 2011;20: 383–411. Available: https://www.semanticscholar.org/paper/Color-difference-Delta-E-A-survey/67d9178f7bad9686c002b721138e26124f6e2e35

Parameters:

Name Type Description Default
distance float

the deltaE distance

required
threshold Union[int, float]

the threshold to decide whether the two colors are noticable, defaults to 5

5

Returns:

Type Description
bool

whether the two colors are noticable

Source code in colorteller/utils/benchmark.py
def _delta_e_noticable_distance(
    self, distance: float, threshold: Union[int, float] = 5
):
    """Decide whether the two colors are noticable based on deltaE distance.

    If the distance is larger than threshold, the two colors are noticable.

    !!! note "References"
        The choice of the threshold is based on the following paper:

        Mokrzycki WS, Tatol M. Color difference Delta E - A survey. Machine Graphics and Vision. 2011;20: 383–411. Available: https://www.semanticscholar.org/paper/Color-difference-Delta-E-A-survey/67d9178f7bad9686c002b721138e26124f6e2e35

    :param distance: the deltaE distance
    :type distance: float
    :param threshold: the threshold to decide whether the two colors are noticable, defaults to 5
    :type threshold: int, optional
    :return: whether the two colors are noticable
    :rtype: bool
    """
    if distance > threshold:
        return True
    else:
        return False

_perceptual_distance(self, colors, matrix=True) private ¤

_perceptual_distance takes a Colors object and returns a dict with the perceptual distance between each color in it.

Parameters:

Name Type Description Default
colors Colors

a Colors object

required
matrix bool, optional

whether to create a distance matrix, defaults to True

True

Returns:

Type Description
dict

a dictionary of the benchmark result

Source code in colorteller/utils/benchmark.py
def _perceptual_distance(self, colors: Colors, matrix=True):
    """_perceptual_distance takes a Colors object and returns a dict with the perceptual distance between each color in it.

    :param colors: a Colors object
    :param matrix: whether to create a distance matrix, defaults to True
    :type matrix: bool, optional
    :return: a dictionary of the benchmark result
    :rtype: dict
    """
    if matrix is False:
        return self._perceptual_distance_list(colors)
    else:
        return self._perceptual_distance_matrix(colors)

_perceptual_distance_list(self, colors, sort=False) private ¤

Calculates a list of perceptual distance

Parameters:

Name Type Description Default
colors

a Colors object

required

Returns:

Type Description
dict

a dictionary of the benchmark result

Source code in colorteller/utils/benchmark.py
def _perceptual_distance_list(self, colors, sort=False):
    """Calculates a list of perceptual distance

    :param colors: a Colors object
    :return: a dictionary of the benchmark result
    :rtype: dict
    """
    logger.debug(
        f"Calculating perceptual distance for {len(colors)} colors: {colors}."
    )
    if sort is True:
        logger.debug("Sorting colors by perceptual distance.")
        sorted_lab_colors_ = self._sort_on_distance(colors, delta_e_cie2000)
        logger.debug(f"Sorted colors by perceptual distance: {sorted_lab_colors_}")
        sorted_lab_colors = sorted_lab_colors_["colors"]
        logger.debug(f"Sorted colors by perceptual distance: {sorted_lab_colors}")
        sorted_hex = [self.hex[i] for i in sorted_lab_colors_["indices"]]
        logger.debug(f"Sorted colors by perceptual distance: {sorted_hex}")
        distances = [
            delta_e_cie2000(c1, c2)
            for c1, c2 in zip(sorted_lab_colors[:-1], sorted_lab_colors[1:])
        ]
        res = {
            "hex": sorted_hex,
            "lab": [c.get_value_tuple() for c in sorted_lab_colors],
            "distances": distances,
        }
    else:
        distances = [
            delta_e_cie2000(c1, c2) for c1, c2 in zip(colors[:-1], colors[1:])
        ]
        res = {
            "hex": self.hex,
            "lab": [c.get_value_tuple() for c in colors],
            "distances": distances,
        }

    res["noticable"] = [
        self._delta_e_noticable_distance(d) for d in res["distances"]
    ]

    return res

_perceptual_distance_matrix(self, colors) private ¤

Calculates the perceptual distance matrix

Parameters:

Name Type Description Default
colors

a Colors object

required

Returns:

Type Description
dict

a dictionary of the benchmark result

Source code in colorteller/utils/benchmark.py
def _perceptual_distance_matrix(self, colors):
    """Calculates the perceptual distance matrix

    :param colors: a Colors object
    :return: a dictionary of the benchmark result
    :rtype: dict
    """
    pd = []
    for ci in colors:
        ci_pd = []
        for cj in colors:
            ci_pd.append(delta_e_cie2000(ci, cj))
        pd.append(ci_pd)

    pd_noticable = []
    for pd_row in pd:
        pd_noticable_row = []
        for d in pd_row:
            pd_noticable_row.append(self._delta_e_noticable_distance(d))
        pd_noticable.append(pd_noticable_row)

    return {
        "colors": self.hex,
        "lab": [c.get_value_tuple() for c in colors],
        "distances": pd,
        "noticable": pd_noticable,
    }

_sort_on_distance(lab_colors, distance_metric, reference_color=None) private staticmethod ¤

sort the colors based on distance between each other.

Parameters:

Name Type Description Default
lab_colors list

a list of colors in lab color space

required
distance_metric

the distance metric to use, it should be a function that takes two colors as arguments and returns the distance.

required
reference_color

the reference color to use, defaults to None

None
Source code in colorteller/utils/benchmark.py
@staticmethod
def _sort_on_distance(lab_colors: list, distance_metric, reference_color=None):
    """sort the colors based on distance between each other.

    :param lab_colors: a list of colors in lab color space
    :type lab_colors: list
    :param distance_metric: the distance metric to use, it should be a function that takes two colors as arguments and returns the distance.
    :param reference_color: the reference color to use, defaults to None
    """
    if reference_color is None:
        reference_color_rgb = sRGBColor(rgb_r=255, rgb_g=255, rgb_b=255)
        reference_color = convert_color(reference_color_rgb, LabColor)
    elif isinstance(reference_color, str):
        if reference_color == "white":
            reference_color = convert_color(
                sRGBColor(rgb_r=255, rgb_g=255, rgb_b=255), LabColor
            )
        elif reference_color == "black":
            reference_color = convert_color(
                sRGBColor(rgb_r=0, rgb_g=0, rgb_b=0), LabColor
            )

    ref_distances = [distance_metric(c, reference_color) for c in lab_colors]
    sorted_index = sorted(range(len(ref_distances)), key=ref_distances.__getitem__)

    return {
        "colors": [lab_colors[i] for i in sorted_index],
        "indices": sorted_index,
    }

metric(self) ¤

calculate the metrics of the current benchmark

Source code in colorteller/utils/benchmark.py
def metric(self):
    """calculate the metrics of the current benchmark"""
    return {
        "method": "perceptual_distance",
        "data": self._perceptual_distance(self.LabColor),
    }