Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG]: Problems converting "sRGB" to "Munsell Renotation System". #1173

Open
notevenstickmen opened this issue Jun 23, 2023 · 5 comments
Open

Comments

@notevenstickmen
Copy link

Question

I'm having problems converting some colours from sRGB to Munsell. I'm no expert in this field so can well believe I'm doing something daft but I don't know what.

I have a spreadsheet (2547 rows) of the main Munsell colours with their sRGB equivalents (I think this came via Paul Centore's work; the numbers check back to this anyway). I've written routine to convert sRGB to Munsell & have been using this spreadsheet to check my results.

Code:
import colour
import numpy as np
rgb = np.array([141,116,121])
XYZ = colour.sRGB_to_XYZ(rgb / 255)
xyY = colour.XYZ_to_xyY(XYZ)
munsell = colour.xyY_to_munsell_colour(xyY, chroma_decimals=1)

Issues:

  1. I have about 55 Assertation errors e.g.
    Most of the colours here seem to be relatively low value & chroma but not all
    e.g. 02.5RP-9-02 RGB(241,222,239)
    AssertionError: ""array([ 3.08954081, 9.02276054, 1.84797444, 8. ])"" specification chroma must be normalised to domain [2, 50]!
    The 4th argument looks v strange to me & all cases have this format i.e. n.spaces.

  2. I'm getting quite a lot of hue discrepances in those areas where one hue family changes to another.
    e.g.

10.0R -7-08 RGB( 242,150,128) my result: 0.1YR 7.0/8.0
10.0R -7-10 RGB( 254,144,114) my result: 0.1YR 7.0/9.9
10.0R -7-12 RGB( 255,136,97) my result: 0.6YR 6.8/11.0

I most cases, the sub-hue group in my result is < 1 rather than 10.0.

Can anyone advise me?

@glenndavis52
Copy link

glenndavis52 commented Jun 24, 2023

The 5 principal and 5 intermediate hues form 10 arcs on the hue circle. The arcs do not intersect (except at the endpoints) and cover the hue circle. Each arc is parameterized from 0 to 10. Hues 10R and 0YR are endpoints of adjacent arcs and denote exactly the same hue. The Munsell system prefers the former notation. The 4'th number 8 in the assertion is the Hue Index of intermediate hue RP.

I claim that XYZ should really be chromatically adapted from Illuminant D65 (the sRGB standard) to Illuminant C (with which the Munsell system was designed). So there should be another conversion (a chromatic adaptation transform) between sRGB_to_XYZ() and XYZ_to_xyY(). As a test, when R=G=B, then xy should be about (0.3101,0.3163), and not (0.3127,0.3290).

I do not know anything about the apparent requirement that Chroma is in [2,50].

@KelSolaar KelSolaar changed the title [DISCUSSION]: Problems converting sRGB to Munsell [DISCUSSION]: Problems converting "sRGB" to "Munsell Renotation System". Jul 22, 2023
@jaimecorton
Copy link

jaimecorton commented May 9, 2024

I am having the same problems even using the Illuminant C mentioned by glenndavis52, my code is the following:

def RGB2Munsell(RGB):

    # RGB is expected to have the following format: (R, G, B), where RGB values range from 0 to 1
    # e.g: RGB = (0.96820063, 0.74966853, 0.60617991)
    C = colour.CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"]["C"]

    Munsell = colour.xyY_to_munsell_colour(colour.XYZ_to_xyY(colour.sRGB_to_XYZ(RGB, C)))

    return Munsell

By introducing as sRGB the following array: array([ 0.5, 0.5, 0.5]) (Trying to achieve the testing of R=G=B) I get the same error AssertionError: "array([ 5.42874897e+00, 5.23531097e+00, 1.08571452e-03,
7.00000000e+00])" specification chroma must be normalised to domain [2, 50]!

@jaimecorton
Copy link

jaimecorton commented May 10, 2024

@notevenstickmen I've been looking at it (https://colour.readthedocs.io/en/develop/_modules/colour/notation/munsell.html) and it seems that our error is comming from the fact that on the colour.xyY_to_munsell_colour() step, the library internally converts xyY to munsell specification and then munsell specification to munsell colour:

`

def xyY_to_munsell_colour(
xyY: ArrayLike,
hue_decimals: int = 1,
value_decimals: int = 1,
chroma_decimals: int = 1,
) -> str | NDArrayStr:
"""
Convert from CIE xyY colourspace to Munsell colour.

Parameters
----------
xyY
    *CIE xyY* colourspace array.
hue_decimals
    Hue formatting decimals.
value_decimals
    Value formatting decimals.
chroma_decimals
    Chroma formatting decimals.

Returns
-------
:class:`str` or :class:`numpy.NDArrayFloat`
    *Munsell* colour.

Notes
-----
+------------+-----------------------+---------------+
| **Domain** | **Scale - Reference** | **Scale - 1** |
+============+=======================+===============+
| ``xyY``    | [0, 1]                | [0, 1]        |
+------------+-----------------------+---------------+

References
----------
:cite:`Centorea`, :cite:`Centore2012a`

Examples
--------
>>> xyY = np.array([0.38736945, 0.35751656, 0.59362000])
>>> xyY_to_munsell_colour(xyY)
'4.2YR 8.1/5.3'
"""

specification = to_domain_10(
    xyY_to_munsell_specification(xyY), _munsell_scale_factor()
)
shape = list(specification.shape)
decimals = (hue_decimals, value_decimals, chroma_decimals)

munsell_colour = np.reshape(
    np.array(
        [
            munsell_specification_to_munsell_colour(a, *decimals)
            for a in np.reshape(specification, (-1, 4))
        ]
    ),
    shape[:-1],
)

return str(munsell_colour) if shape == [4] else munsell_colour

`

If we see the function munsell_specification_to_munsell_colour in detail we will find where the error is popping:

`

def munsell_specification_to_munsell_colour(
specification: ArrayLike,
hue_decimals: int = 1,
value_decimals: int = 1,
chroma_decimals: int = 1,
) -> str:
"""
Convert from Munsell Colorlab specification to given Munsell colour.

Parameters
----------
specification
    *Munsell* *Colorlab* specification.
hue_decimals
    Hue formatting decimals.
value_decimals
    Value formatting decimals.
chroma_decimals
    Chroma formatting decimals.

Returns
-------
:class:`str`
    *Munsell* colour.

Examples
--------
>>> munsell_specification_to_munsell_colour(np.array([np.nan, 5.2, np.nan, np.nan]))
'N5.2'
>>> munsell_specification_to_munsell_colour(np.array([10, 2.0, 4.0, 7]))
'10.0R 2.0/4.0'
"""

hue, value, chroma, code = tsplit(normalise_munsell_specification(specification))

if is_grey_munsell_colour(specification):
    return MUNSELL_GRAY_EXTENDED_FORMAT.format(value, value_decimals)
else:
    hue = round(hue, hue_decimals)
    attest(
        0 <= hue <= 10,
        f'"{specification!r}" specification hue must be normalised to '
        f"domain [0, 10]!",
    )

    value = round(value, value_decimals)
    attest(
        0 <= value <= 10,
        f'"{specification!r}" specification value must be normalised to '
        f"domain [0, 10]!",
    )

    chroma = round(chroma, chroma_decimals)
    attest(
        2 <= chroma <= 50,
        f'"{specification!r}" specification chroma must be normalised to '
        f"domain [2, 50]!",
    )

    code_values = MUNSELL_HUE_LETTER_CODES.values()
    code = round(code, 1)
    attest(
        code in code_values,
        f'"{specification!r}" specification code must one of "{code_values}"!',
    )

    if value == 0:
        return MUNSELL_GRAY_EXTENDED_FORMAT.format(value, value_decimals)
    else:
        hue_letter = MUNSELL_HUE_LETTER_CODES.first_key_from_value(code)

        return MUNSELL_COLOUR_EXTENDED_FORMAT.format(
            hue,
            hue_decimals,
            hue_letter,
            value,
            value_decimals,
            chroma,
            chroma_decimals,
        )

`

Specifically the error is produced here:

`

chroma = round(chroma, chroma_decimals)
attest(
2 <= chroma <= 50,
f'"{specification!r}" specification chroma must be normalised to '
f"domain [2, 50]!",
)

`

But I will need further assistance with why that is happening when going from sRGB to Munsell colour, some help with it wil be much appreciated. I am using the colour.CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"]["C"] illuminant. There was a ColourWarning regarding ColourUsageWarning: "array([ 0.37326798, 0.37285041, 0.09701717])" is not within "MacAdam" limits for illuminant "C"! For the moment what I am doing is the following modification since it solves the problem of a Chroma under 2 e.g: Chroma = 1.8, but I would like to have a better solution that requires less original colours modification:

`
def RGB2Munsell(RGB):

# RGB is expected to have the following format: (R, G, B), where RGB values range from 0 to 1
# e.g: RGB = (0.96820063, 0.74966853, 0.60617991)
C = colour.CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"]["C"] # C or D65

XYZ = colour.sRGB_to_XYZ(RGB, C)
xyY = colour.XYZ_to_xyY(XYZ)
MunsellSpecification = colour.notation.munsell.xyY_to_munsell_specification(xyY)
if MunsellSpecification[2]<2:
    MunsellSpecification[2] = np.ceil(MunsellSpecification[2])
Munsell = colour.notation.munsell.munsell_specification_to_munsell_colour(MunsellSpecification) 

return Munsell

`

@KelSolaar
Copy link
Member

Hello,

This is a side effect of using the colour.colour.sRGB_to_XYZ definition which introduces minor precision issues because our sRGB colourspace uses the 4 decimal places rounded matrices from IEC 61966-2-1:1999: https://github.com/colour-science/colour/blob/develop/colour/models/rgb/datasets/srgb.py#L67

The consequence is a loss of precision and the CIE xyY value becomes [ 0.31007559 0.31616194 0.21404116] instead of [ 0.31006 0.31616 0.21404114] which is perfectly neutral. This fails under the grey threshold test here: https://github.com/colour-science/colour/blob/develop/colour/notation/munsell.py#L1076 I think I will increase the threshold value and make it a global that can be changed also.

For the time being you could to the sRGB conversion as follows because BT.709 uses a computed matrix and will not suffer from precision issues:

xyY = colour.XYZ_to_xyY(colour.RGB_to_XYZ(colour.cctf_decoding([0.5, 0.5, 0.5]), colour.RGB_COLOURSPACES["ITU-R BT.709"], C))

@KelSolaar
Copy link
Member

The develop branch should now have reduced threshold value.

@KelSolaar KelSolaar changed the title [DISCUSSION]: Problems converting "sRGB" to "Munsell Renotation System". [BUG]: Problems converting "sRGB" to "Munsell Renotation System". May 10, 2024
@KelSolaar KelSolaar added this to the v0.4.5 milestone May 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants