colour-science / colour

Colour Science for Python
https://www.colour-science.org
BSD 3-Clause "New" or "Revised" License
2.12k stars 262 forks source link

"colour.HSL_to_RGB" definition provides incorrect RGB values for S = 1. #639

Closed nadersadoughi closed 4 years ago

nadersadoughi commented 4 years ago

When one creates an array of hsl values HSL = numpy.array([[0, 0, .5], [0, 1, .5]]), and then one converts HSL to RGB using RGB = colour.HSL_to_RGB(HSL) one finds that RGB[0] = RGB[1].

This should not be the case, as it should be: RGB[1] = array([1, 0, 0]), while the package gives back: RGB[1] = array([0.5, 0.5, 0.5])

If one creates an HSL hue-page, as shown with the problem above, the problem is clearly demonstrated as simply this package reverts values of S = 1 to S = 0.

I created a function to create and save HSL hue-pages through the process below:

import numpy as np
import colour
from PIL import Image as im

def plot_HSL_huepage(hue):
    s = np.arange(0, 100.1, .1) / 100
    l = np.arange(0, 100.1, .1) / 100
    mesh = np.array(np.meshgrid(s, l))
    combinations = mesh.T.reshape(-1, 2)
    h = np.full((combinations.shape[0]), hue)/360
    hsl = np.stack([h, combinations[:,0], combinations[:,1]], axis = -1)
    srgb = colour.HSL_to_RGB(hsl)
    srgb = (srgb*255).astype(np.uint8)
    RGB = np.reshape(srgb, (s.size, l.size, 3))
    data = im.fromarray(RGB)
    data = data.rotate(90)
    data.save('hsl_huepage_h={}.png'.format(hue))

When one then runs the function for HSL hues of 0, 60, 120, 240, and 300 as shown below:

h_values = np.array([0, 60, 120, 240, 300])
for x in h_values:
     plot_HSL_huepage(x)

when one opens the hue page png, no matter the hue, one sees that the values for S = 1 are the same grey values as S = 0.

I hope this issue submission is clear, I have never written one before. I only learned python because I wanted to use this package, and I am grateful for the amount of work put into making this package.

What can I do to get the correct RGB values using the HSL_to_RGB function?

KelSolaar commented 4 years ago

Hi @nadersadoughi,

HSL stands for Hue, Saturation, Lightness thus if you input a triplet that has 50% lightness, no hue and either no saturation or full saturation, you are defining a colour on the gray scale.

>>> colour.models.HSL_to_RGB([[0, 0, 0.5], [0, 1, 0.5]])
array([[ 0.5,  0.5,  0.5],
       [ 0.5,  0.5,  0.5]])

I haven't looked at your code but I think given your first paragraph you have your axes swapped.

Here is a How-To to produce a colour wheel: https://colab.research.google.com/drive/1NRcdXSCshivkwoU2nieCvC3y14fx1X4X#scrollTo=Sv4Yn4_AbUnE

image

nadersadoughi commented 4 years ago

Hi @KelSolaar ,

The HSL standard implemented in this package requires an input of values H, S, and L, in an array.

The first column of the array is the Hue, "H," defined in the domain 0-1 which can be determined by dividing any angle in degrees by 360 to get Hue degree in that domain.

The Saturation, "S" is in the second column of the array, and is in the domain of 0-1 as well, 0 being completely unsaturated, and 1 being 100% saturated. Lightness, "L" is in the 3rd column, and is in the same domain of 0-1 defining 0-100%.

the first element of the 2D array HSL defined below:

HSL = numpy.array([[0, 0, .5], [0, 1, .5]]

is a color with 0degrees hue (red, which is a hue) but 0% saturation, so grey, and .5 lightness, so 50% lightness.

The second element is 0 degrees hue (red), 100% saturation, and 50% lightness, which in HSL amounts to RGB values of [1, 0, 0]. In HSV, to make that same RGB value, one needs H=0, S=1, V=1. Do not mistake HSL with HSV.

Please try out the function I created as it does in fact create proper arrangements of HSL, except for one line of pixels where S = 1. Everything approaching 1 has the correct RGB values. HSV_H=0_HSL

Above image represents a page of HSL with Hue = 0 created with code snippet in initial issue submission

HSV_H=0_HSV

Above image represents a page of HSV with Hue = 0 created with similar code to code before, swapping the colour.HSL_to_RGB function with colour.HSV_to_RGB

If you were to open the PNG with the HSL array, you will see that in the far right of the image, where S=1 the very last of the pixels, the RGB values match those of S = 0 at the far left.

If you were to create an array of colors at fixed lightness of 0.5, hue of 0, and saturation ranging from 0%, 10%, 20%, 30%, 40%, 50, 60, 70%, 80%, 90%, and 100% (which are inputed as S = 0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9 and 1.0) like the following code snippet, and then convert them to RGB using HSL_to_RGB, one gets the following RGB values that when made into an image, show the following colors.

Code snippet:

>>import colour
>>import numpy as np

>>S = np.arange(0, 1.1, .1)
>>L = np.full((S.size,), 0.5)
>>H = np.full((S.size,), 0)

#the following below creates an array with each element being an HSL value
>>HSL = np.stack([H, S, L], axis = -1)
>>print(HSL)
[[ 0.   0.   0.5]
 [ 0.   0.1  0.5]
 [ 0.   0.2  0.5]
 [ 0.   0.3  0.5]
 [ 0.   0.4  0.5]
 [ 0.   0.5  0.5]
 [ 0.   0.6  0.5]
 [ 0.   0.7  0.5]
 [ 0.   0.8  0.5]
 [ 0.   0.9  0.5]
 [ 0.   1.   0.5]]

#the following below is the function that has the issue
>>RGB = colour.HSL_to_RGB(HSL)
>>print(RGB)
[[ 0.5   0.5   0.5 ]
 [ 0.55  0.45  0.45]
 [ 0.6   0.4   0.4 ]
 [ 0.65  0.35  0.35]
 [ 0.7   0.3   0.3 ]
 [ 0.75  0.25  0.25]
 [ 0.8   0.2   0.2 ]
 [ 0.85  0.15  0.15]
 [ 0.9   0.1   0.1 ]
 [ 0.95  0.05  0.05]
 [ 0.5   0.5   0.5 ]]

These RGB values in an image look like so:

Screen Shot 2020-11-01 at 17 31 26

The final color should be a color that is pure red, which it is not with this function.

Please try out this implementation along with the code implementation I provided in my original issue submission that demonstrates that this code needs to be fixed.

I respectfully ask that next time before closing an issue, you try to run the code snippet inserted that demonstrates the issue.

The code is not functioning properly, so please reopen the issue.

KelSolaar commented 4 years ago

I respectfully ask that next time before closing an issue, you try to run the code snippet inserted that demonstrates the issue.

Thanks, point taken! I read the issue quickly before heading to work and I missed some of the important details and it is much clearer with your image. I will take a look tonight.

KelSolaar commented 4 years ago

Looking at it quickly, it seems like the comparison here is incorrect: https://github.com/colour-science/colour/blob/develop/colour/models/rgb/cylindrical.py#L334

    R = np.where(S == 1, L, R)
    G = np.where(S == 1, L, G)
    B = np.where(S == 1, L, B)

Should be

    R = np.where(S == 0, L, R)
    G = np.where(S == 0, L, G)
    B = np.where(S == 0, L, B)
KelSolaar commented 4 years ago

Thanks again @nadersadoughi, and sorry for not having read properly! This should be fixed now!

nadersadoughi commented 4 years ago

@KelSolaar In fact, thank you for very quickly finding the solution! This is the very reason why I switched over from Matlab color packages to yours in python, because of this open-source community where we can make color better, together.

KelSolaar commented 4 years ago

Ah! Thanks for the kind words @nadersadoughi and glad to have people like you on-board! A key motivation for Colour was to democratize colour science and have it escape the Matlab sandbox! :)