diff --git a/docker/main/requirements-wheels.txt b/docker/main/requirements-wheels.txt index 6c8a8629e..84c5a867c 100644 --- a/docker/main/requirements-wheels.txt +++ b/docker/main/requirements-wheels.txt @@ -4,7 +4,6 @@ Flask_Limiter == 3.7.* imutils == 0.5.* joserfc == 0.11.* markupsafe == 2.1.* -matplotlib == 3.8.* mypy == 1.6.1 numpy == 1.26.* onvif_zeep == 0.2.12 diff --git a/frigate/config.py b/frigate/config.py index 301aeeb95..0e2d2b6cd 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -7,7 +7,6 @@ from enum import Enum from pathlib import Path from typing import Any, Dict, List, Optional, Tuple, Union -import matplotlib.pyplot as plt import numpy as np from pydantic import ( BaseModel, @@ -43,6 +42,7 @@ from frigate.plus import PlusApi from frigate.util.builtin import ( deep_merge, escape_special_characters, + generate_color_palette, get_ffmpeg_arg_list, load_config_with_no_duplicates, ) @@ -1033,10 +1033,11 @@ class CameraConfig(FrigateBaseModel): def __init__(self, **config): # Set zone colors if "zones" in config: - colors = plt.cm.get_cmap("tab10", len(config["zones"])) + colors = generate_color_palette(len(config["zones"])) + config["zones"] = { - name: {**z, "color": tuple(round(255 * c) for c in colors(idx)[:3])} - for idx, (name, z) in enumerate(config["zones"].items()) + name: {**z, "color": color} + for (name, z), color in zip(config["zones"].items(), colors) } # add roles to the input if there is only one diff --git a/frigate/detectors/detector_config.py b/frigate/detectors/detector_config.py index 0952fff88..d8c841f42 100644 --- a/frigate/detectors/detector_config.py +++ b/frigate/detectors/detector_config.py @@ -5,13 +5,12 @@ import os from enum import Enum from typing import Dict, Optional, Tuple -import matplotlib.pyplot as plt import requests from pydantic import BaseModel, ConfigDict, Field from pydantic.fields import PrivateAttr from frigate.plus import PlusApi -from frigate.util.builtin import load_labels +from frigate.util.builtin import generate_color_palette, load_labels logger = logging.getLogger(__name__) @@ -128,10 +127,9 @@ class ModelConfig(BaseModel): def create_colormap(self, enabled_labels: set[str]) -> None: """Get a list of colors for enabled labels.""" - cmap = plt.cm.get_cmap("tab10", len(enabled_labels)) + colors = generate_color_palette(len(enabled_labels)) - for key, val in enumerate(enabled_labels): - self._colormap[val] = tuple(int(round(255 * c)) for c in cmap(key)[:3]) + self._colormap = {label: color for label, color in zip(enabled_labels, colors)} model_config = ConfigDict(extra="forbid", protected_namespaces=()) diff --git a/frigate/util/builtin.py b/frigate/util/builtin.py index 4d6ef75f8..6a0c706a7 100644 --- a/frigate/util/builtin.py +++ b/frigate/util/builtin.py @@ -349,3 +349,39 @@ def empty_and_close_queue(q: mp.Queue): q.close() q.join_thread() return + + +def generate_color_palette(n): + # mimic matplotlib's color scheme + base_colors = [ + (31, 119, 180), # blue + (255, 127, 14), # orange + (44, 160, 44), # green + (214, 39, 40), # red + (148, 103, 189), # purple + (140, 86, 75), # brown + (227, 119, 194), # pink + (127, 127, 127), # gray + (188, 189, 34), # olive + (23, 190, 207), # cyan + ] + + def interpolate(color1, color2, factor): + return tuple(int(c1 + (c2 - c1) * factor) for c1, c2 in zip(color1, color2)) + + if n <= len(base_colors): + return base_colors[:n] + + colors = base_colors.copy() + step = 1 / (n - len(base_colors) + 1) + extra_colors_needed = n - len(base_colors) + + # interpolate between the base colors to generate more if needed + for i in range(extra_colors_needed): + index = i % (len(base_colors) - 1) + factor = (i + 1) * step + color1 = base_colors[index] + color2 = base_colors[index + 1] + colors.append(interpolate(color1, color2, factor)) + + return colors