Skip to content

Commit

Permalink
Human readable colors (#27)
Browse files Browse the repository at this point in the history
* Add human-readable colours (#18)
* Add human-readable colours to output
* Update test to work with human readable colors
* Add webcolors dependency
* docs: update ColorDetect method documentation for human readability

Co-authored-by: MarvinKweyu <mkweyu1@gmail.com>
Co-authored-by: Clifford Onyonka <jsjamessakho@gmail.com>
  • Loading branch information
MarvinKweyu and onyonkaclifford authored Jan 18, 2021
1 parent 5ad573f commit 1689200
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 18 deletions.
15 changes: 15 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@
ColorDetect Changelog
=====================

.. _1.3.0rc:
1.3.0rc (18-01-2021)
====================

Features
--------

- Add a return of human readable colors.

Documentation
-------------

- Update ColorDetect module documentation to show method params
- Move to version ``1.3.0rc`` due to error in ``1.1.1`` packaging

.. _1.1.1:
1.1.1 (17-01-2021)
==================
Expand Down
31 changes: 27 additions & 4 deletions colordetect/color_detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import cv2
import matplotlib.colors as mcolors
import numpy as np
import webcolors
from sklearn.cluster import KMeans

from . import col_share
Expand All @@ -46,7 +47,9 @@ def __init__(self, image):

self.color_description = {}

def get_color_count(self, color_count: int = 5, color_format: str = "rgb") -> dict:
def get_color_count(
self, color_count: int = 5, color_format: str = "human_readable"
) -> dict:
"""
.. _get_color_count:
get_color_count
Expand All @@ -64,6 +67,7 @@ def get_color_count(self, color_count: int = 5, color_format: str = "rgb") -> di
* hsv - (60°,100%,100%)
* rgb - rgb(255, 255, 0) for yellow
* hex - #FFFF00 for yellow
* human_readable - yellow for yellow
:return: color description
"""

Expand All @@ -79,7 +83,7 @@ def get_color_count(self, color_count: int = 5, color_format: str = "rgb") -> di

unique_colors = self._find_unique_colors(cluster, cluster.cluster_centers_)

color_format_options = ["rgb", "hex", "hsv"]
color_format_options = ["rgb", "hex", "hsv", "human_readable"]

if color_format not in color_format_options:
raise ValueError(f"Invalid color format: {color_format}")
Expand Down Expand Up @@ -108,6 +112,20 @@ def _format_color(self, rgb_value, color_format: str):
rgb_value = np.divide(rgb_value, 255) # give a scale from 0-1
return mcolors.to_hex(rgb_value)

elif color_format == "human_readable":
r0, g0, b0 = int(rgb_value[0]), int(rgb_value[1]), int(rgb_value[2])
try:
nearest = webcolors.rgb_to_name((r0, g0, b0))
except ValueError: # Calculate distances between rgb value and CSS3 rgb colours to determine the closest
distances = {}
for k, v in webcolors.CSS3_HEX_TO_NAMES.items():
r1, g1, b1 = webcolors.hex_to_rgb(k)
distances[
((r0 - r1) ** 2 + (g0 - g1) ** 2 + (b0 - b1) ** 2)
] = v # Ignore sqrt as it has no significant effect
nearest = distances[min(distances.keys())]
return nearest

def _find_unique_colors(self, cluster, centroids) -> dict:

# Get the number of different clusters, create histogram, and normalize
Expand Down Expand Up @@ -136,6 +154,7 @@ def write_color_count(
line_type: int = 1,
):
"""
.. _write_color_count:
write_color_count
-----------------
Write the number of colors found to the image
Expand Down Expand Up @@ -188,8 +207,10 @@ def write_text(
font_scale: float = 1.0,
font_thickness: float = 1.0,
line_type: int = 1,
line_spacing: int = 0
):
"""
.. _write_text:
write_text
----------
Write text onto an image
Expand All @@ -213,6 +234,7 @@ def write_text(
font_thickness:
Thickness of the text
line_type: int = 1,
Space betweeen the lines
:return:
"""
if type(text) != str:
Expand All @@ -232,12 +254,13 @@ def write_text(
font_color,
font_thickness,
line_type,
line_spacing,
)

def save_image(self, location=".", file_name: str = "out.jpg"):
"""
.. _save_color_count:
save_color_count
.. _save_image:
save_image
----------------
Save the resultant image file to the local directory
Expand Down
25 changes: 18 additions & 7 deletions docs/colordetect.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,19 @@ this sample image.
>>> from colordetect import ColorDetect
>>>
>>> my_image = ColorDetect("<image_path>")
>>> my_image.get_color_count()
>>> my_image.get_color_count(color_format="rgb")
'[2.0, 2.0, 249.0]': 6.2, '[5.0, 211.0, 212.0]': 7.15, '[173.0, 25.0, 98.0]': 17.49, '[146.0, 155.0, 9.0]': 18.62, '[253.0, 253.0, 253.0]': 50.54}

A dictionary, with the RGB value of the color as the key and its percentage occurrence in the image
as the value is returned.
To get a more human readable format, one would call ``get_color_count()`` parsing the parameter
for ``color_format`` as **human_readable**.

Our line to obtain colors would be replaced by::

>>> my_image.get_color_count()
{'blue': 6.2, 'darkturquoise': 7.15, 'mediumvioletred': 17.49, 'olive': 18.62, 'white': 50.54}


.. note:: As of the ColorDetect 0.1.7, the percentage changed from being presented as a
key to being presented as a value. This attributed to the uniqueness of python
Expand All @@ -65,7 +73,7 @@ on the different arguments it accepts including the different color format retur
Now suppose you want to take it a step further and write the result to the image itself.

.. warning:: Take note of the difference in saving the image to storage from the previous
`save_color_count<save_color_count>` to `save_image<save_color_count>`
`save_color_count<save_color_count>` to `save_image<save_image>`

::

Expand Down Expand Up @@ -97,16 +105,19 @@ Additionally, to enable the use of custom text on an image:

>>> from colordetect import ColorDetect
>>> my_image = ColorDetect("<image_path>")
>>> my_image.write_text(text="a random string", line_spacing=10)
>>> my_image.write_text(text="a random string", font_color=(0,0,0))


.. image:: _static/out_random_string.jpg

To appropriately place the text onto the image and ensure the text does not fade over the object
on the image with the same color, a font color can be parsed as an RGB tuple. This defaults to
`(0,0,0)` , which would be black.
More customization features over the text, including text margin, font thickness and line
spacing (the space between lines of text) can be found on the :ref:`write_text<module_ColorDetect>`
method documentation.

Here, `line_spacing` would be the space between the lines, depending on how many characters are
input, you want. By default, this value is an integer, zero, `0`, denoted as values on the Y axis scale

Whether using `write_text` or `write_color_count`, the image has to be saved using `save_image`.
Whether using ``write_text`` or ``write_color_count``, the image has to be saved using `save_image`.

Video color recognition can be done using :ref:`VideoColor<video_color_recognition>`

Expand Down
5 changes: 1 addition & 4 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@
#
import os
import sys
from datetime import date

from colordetect import ColorDetect

file_loc = os.path.split(__file__)[0]
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(file_loc), ".")))
Expand All @@ -27,7 +24,7 @@
author = "Marvin Kweyu"

# The full version, including alpha/beta/rc tags
release = "1.2.0"
release = "1.3.0rc"

# -- General configuration ---------------------------------------------------

Expand Down
13 changes: 12 additions & 1 deletion requirements/requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
alabaster>==0.7.12
appdirs>==1.4.4
argh>==0.26.2
attrs>==19.3.0
autodoc>==0.5.0
autopep8>==1.5
Babel>==2.8.0
beautifulsoup4>==4.8.2
certifi>==2019.11.28
cfgv>==3.2.0
chardet>==3.0.4
commonmark>==0.9.1
cycler>==0.10.0
decorator>==4.4.2
distlib>==0.3.1
docutils>==0.16
filelock>==3.0.12
identify>==1.5.12
idna>==2.9
imagesize>==1.2.0
importlib-metadata>==1.5.0
importlib-resources>==5.0.0
imutils>==0.5.3
Jinja2>==2.11.1
joblib>==0.14.1
Expand All @@ -23,11 +29,13 @@ MarkupSafe>==1.1.1
matplotlib>==3.2.1
mock>==4.0.2
more-itertools>==8.2.0
opencv-python
numpy>==1.18.1
opencv-python>==4.2.0.32
packaging>==20.1
pathtools>==0.1.2
pluggy>==0.13.1
port-for>==0.3.1
pre-commit>==2.9.0
py>==1.8.1
pycodestyle>==2.5.0
Pygments>==2.6.1
Expand Down Expand Up @@ -59,11 +67,14 @@ sphinxcontrib-htmlhelp>==1.0.3
sphinxcontrib-jsmath>==1.0.1
sphinxcontrib-qthelp>==1.0.3
sphinxcontrib-serializinghtml>==1.1.4
toml>==0.10.2
tornado>==6.0.4
urllib3>==1.25.8
virtualenv>==20.3.0
waitress>==1.4.3
watchdog>==0.10.2
wcwidth>==0.1.8
webcolors>==1.11.1
WebOb>==1.8.6
WebTest>==2.0.34
zipp>==3.0.0
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setuptools.setup(
name="ColorDetect",
version="1.1.1",
version="1.3.0rc",
author="Marvin Kweyu",
author_email="mkweyu1@gmail.com",
description="Detect and recognize colors in images or video",
Expand All @@ -20,6 +20,7 @@
"matplotlib>==3.2.1",
"opencv-python>==4.2.0.32",
"scikit-learn>==0.22.2.post1",
"webcolors>==1.11.1"
],
classifiers=[
"Programming Language :: Python :: 3",
Expand Down
2 changes: 1 addition & 1 deletion tests/test_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def test_get_color_count_has_correct_color_and_count(image):
user_image = ColorDetect(image)
# since the image is plain 255,255,255
assert len(user_image.get_color_count(color_count=1)) == 1
assert user_image.get_color_count(color_count=1) == {"[255.0, 255.0, 255.0]": 100.0}
assert user_image.get_color_count(color_count=1) == {"white": 100.0}


def test_what_is_in_dictionary_is_being_written(datadir, image):
Expand Down

0 comments on commit 1689200

Please sign in to comment.