Skip to content

Commit cec4085

Browse files
authored
Merge pull request #82 from SMPSCodeClub/master
Improvements to easy_light_color_sensor class
2 parents 25fe283 + 4bcf944 commit cec4085

File tree

1 file changed

+41
-46
lines changed

1 file changed

+41
-46
lines changed

Python/di_sensors/easy_light_color_sensor.py

Lines changed: 41 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
# MUTEX SUPPORT WHEN NEEDED
1414

15+
# LJM: for rgb to hsv conversion (see usage below where it's mentioned)
16+
import colorsys
1517

1618
from di_sensors import light_color_sensor
1719
from di_sensors import VL53L0X
@@ -34,16 +36,18 @@
3436
class EasyLightColorSensor(light_color_sensor.LightColorSensor):
3537
"""
3638
Class for interfacing with the `Light Color Sensor`_.
37-
3839
This class compared to :py:class:`~di_sensors.light_color_sensor.LightColorSensor` uses mutexes that allows a given
3940
object to be accessed simultaneously from multiple threads/processes.
4041
Apart from this difference, there may also be functions that are more user-friendly than the latter.
41-
4242
"""
4343

44+
# lJM: I've added <black> and <white> back in to these lists as I have a different way of dealing with them
45+
4446
#: The 6 colors that :py:meth:`~di_sensors.easy_light_color_sensor.EasyLightColorSensor.guess_color_hsv`
4547
#: method may return upon reading and interpreting a new set of color values.
4648
known_colors = {
49+
"black": (0,0,0),
50+
"white": (255,255,255),
4751
"red": (255,0,0),
4852
"green": (0,255,0),
4953
"blue": (0,0,255),
@@ -53,6 +57,8 @@ class EasyLightColorSensor(light_color_sensor.LightColorSensor):
5357
}
5458

5559
known_hsv = {
60+
"black": (0,0,0),
61+
"white": (0,0,100),
5662
"red": (0,100,100),
5763
"green": (120,100,100),
5864
"blue": (240,100,100),
@@ -64,13 +70,11 @@ class EasyLightColorSensor(light_color_sensor.LightColorSensor):
6470
def __init__(self, port="I2C", led_state = False, use_mutex=False):
6571
"""
6672
Constructor for initializing a link to the `Light Color Sensor`_.
67-
6873
:param str port = "I2C": The port to which the distance sensor is connected to. Can also be connected to ports ``"AD1"`` or ``"AD2"`` of the `GoPiGo3`_. If you're passing an **invalid port**, then the sensor resorts to an ``"I2C"`` connection. Check the :ref:`hardware specs <hardware-interface-section>` for more information about the ports.
6974
:param bool led_state = False: The LED state. If it's set to ``True``, then the LED will turn on, otherwise the LED will stay off. By default, the LED is turned off.
7075
:param bool use_mutex = False: When using multiple threads/processes that access the same resource/device, mutexes should be enabled.
7176
:raises ~exceptions.OSError: When the `Light Color Sensor`_ is not reachable.
7277
:raises ~exceptions.RuntimeError: When the chip ID is incorrect. This happens when we have a device pointing to the same address, but it's not a `Light Color Sensor`_.
73-
7478
"""
7579

7680
self.use_mutex = use_mutex
@@ -99,19 +103,17 @@ def __init__(self, port="I2C", led_state = False, use_mutex=False):
99103

100104
self.led_state = led_state
101105

106+
# LJM: This is not required if we import <colorsys> module and use <colorsys.rgb_to_hsv> function
107+
# for backwards compatibility with existing code, we could just make this function a wrapper for <colorsys.rgb_to_hsv>
102108
def translate_to_hsv(self, in_color):
103109
"""
104110
Standard algorithm to switch from one color system (**RGB**) to another (**HSV**).
105-
106111
:param tuple(float,float,float) in_color: The RGB tuple list that gets translated to HSV system. The values of each element of the tuple is between **0** and **1**.
107112
:return: The translated HSV tuple list. Returned values are *H(0-360)*, *S(0-100)*, *V(0-100)*.
108113
:rtype: tuple(int, int, int)
109-
110114
.. important::
111-
112115
For finding out the differences between **RGB** *(Red, Green, Blue)* color scheme and **HSV** *(Hue, Saturation, Value)*
113116
please check out `this link <https://www.kirupa.com/design/little_about_color_hsv_rgb.htm>`__.
114-
115117
"""
116118
r,g,b = in_color
117119

@@ -143,16 +145,12 @@ def translate_to_hsv(self, in_color):
143145

144146
return (h,s*100,v*100)
145147

146-
147148
def safe_raw_colors(self):
148149
"""
149150
Returns the color as read by the `Light Color Sensor`_.
150-
151151
The colors detected vary depending on the lighting conditions of the nearby environment.
152-
153152
:returns: The RGBA values from the sensor. RGBA = Red, Green, Blue, Alpha (or Clear). Range of each element is between **0** and **1**. **-1** means an error occured.
154153
:rtype: tuple(float,float,float,float)
155-
156154
"""
157155
ifMutexAcquire(self.use_mutex)
158156
try:
@@ -168,7 +166,6 @@ def safe_raw_colors(self):
168166
def safe_rgb(self):
169167
"""
170168
Detect the RGB color off of the `Light Color Sensor`_.
171-
172169
:returns: The RGB color in 8-bit format.
173170
:rtype: tuple(int,int,int)
174171
"""
@@ -178,58 +175,56 @@ def safe_rgb(self):
178175
else:
179176
r,g,b,c = colors
180177
return r,g,b
181-
178+
179+
# LJM: a new function that calibrates for a single color
180+
# (suggestion: for feedback, take a subsequent reading, identify color and illuminate eyes to prove it has worked)
181+
# Rather than devise rules for black and white as exceptions, which may not be applicable to all environments
182+
# it might be better instead to adjust the robot's view of a typical white or black for the current environment
183+
# You could also choose to run a calibration for all colors that you are interested in, using task & environment specific examples
184+
def calibrate(self, color):
185+
"""
186+
Replace the HSV centroid for a given color with the sensor reading obtained from an example of that color in the current lighting environment
187+
<color> can be one of black | white | red | green | blue | yellow | cyan | fuschia
188+
"""
189+
190+
if color in self.known_hsv:
191+
r, g, b, c = self.safe_raw_colors()
192+
h, s, v = colorsys.rgb_to_hsv(r/c, g/c, b/c)
193+
self.known_hsv[color] = [360*h, 100*s, 100*v]
194+
else:
195+
print (f"Invalid color name: [{color}].")
196+
colorlist = ', '.join(self.known_hsv.keys())
197+
print (f"color can only be one of {colorlist}.")
198+
182199
def guess_color_hsv(self, in_color):
183200
"""
184201
Determines which color `in_color` parameter is closest to in the :py:attr:`~di_sensors.easy_light_color_sensor.EasyLightColorSensor.known_colors` list.
185-
186202
This method uses the euclidean algorithm for detecting the nearest center to it out of :py:attr:`~di_sensors.easy_light_color_sensor.EasyLightColorSensor.known_colors` list.
187203
It does work exactly the same as KNN (K-Nearest-Neighbors) algorithm, where `K = 1`.
188-
189204
:param tuple(float,float,float,float) in_color: A 4-element tuple list for the *Red*, *Green*, *Blue* and *Alpha* channels. The elements are all valued between **0** and **1**.
190205
:returns: The detected color in string format and then a 3-element tuple describing the color in RGB format. The values of the RGB tuple are between **0** and **1**.
191206
:rtype: tuple(str,(float,float,float))
192-
193207
.. important::
194-
195208
For finding out the differences between **RGB** *(Red, Green, Blue)* color scheme and **HSV** *(Hue, Saturation, Value)*
196209
please check out `this link <https://www.kirupa.com/design/little_about_color_hsv_rgb.htm>`__.
197-
198210
"""
199211

200212
r,g,b,c = in_color
201213
# print("incoming: {} {} {} {}".format(r,g,b,c))
202214

203-
# handle black
204-
# luminosity is too low, or all color readings are too low
205-
if c < 0.04 or (r/c < 0.10 and g/c < 0.10 and b/c < 0.10):
206-
return ("black",(0,0,0))
207-
208-
# handle white
209-
# luminosity is high, or all color readings are high
210-
if c > 0.95 or (r/c > 0.9 and g/c > 0.9 and b/c > 0.9):
211-
return ("white",(255,255,255))
212-
213215
# divide by luminosity(clarity) to minimize variations
214-
h,s,v = self.translate_to_hsv((r/c, g/c, b/c))
215-
216-
# another black is possible
217-
# black has a value of 0 and a saturation of 100
218-
# values of 15 and 95 chosen randomly. They may need to be tweaked
219-
if v < 15 and s > 95:
220-
return ("Black",(0,0,0))
221-
222-
# so is another white
223-
# white has a value of 100 and a saturation of 0
224-
# values of 95 and 10 chosen randomly. They may need to be tweaked
225-
if v > 95 and s < 10:
226-
return ("White",(255,255,255))
227-
216+
# LJM: I've replaced code for HSV conversion but perhaps not necessary if we convert existing function to colorsys.rgb_to_hsv wrapper
217+
h, s, v = colorsys.rgb_to_hsv(r/c, g/c, b/c)
218+
228219
min_distance = 255
229220
for color in self.known_hsv:
230-
# print ("Testing {}".format(color))
231-
distance_to_hsv = sqrt((h - self.known_hsv[color][0])**2 )
232-
# print((h,s,v), distance_to_hsv)
221+
# LJM: extra line for improved readability in calculation below
222+
centroid = self.known_hsv[color]
223+
#print ("Testing {}".format(color))
224+
# only <h> term in distance_to_hsv was previously used. I can see why when you had dealt with black and white separately.
225+
# Added <s> and <v> terms back in to cater for black and white.
226+
distance_to_hsv = sqrt( ((360*h) - centroid[0])**2 + ((100*s) - centroid[1])**2 + ((100*v) - centroid[2])**2 )
227+
#print((h,s,v), distance_to_hsv)
233228
if distance_to_hsv < min_distance:
234229
min_distance = distance_to_hsv
235230
candidate = color

0 commit comments

Comments
 (0)