Skip to content

Bradford transformation support #278

Closed

Description

@kimikage

As README.md says, Colors.jl already has a useful function for chromatic adaptation named whitebalance.
whitebalance uses the CAT02 transformation matrix to convert colors into LMS.
However, not only the CAT02 but also the linear Bradford transformation is commonly used in the field of color management because of the interoperability.
In fact, to minimize the risk of conflict, the Bradford model is recommended in the ICC specification.

We can obtain the adaptation result in the Bradford model by converting the colors into LMS with the Bradford transformation in advance of calling whitebalance, and converting the result back into the original color space.
I think it's too much bother to do that every time, though.

A simple solution is to add a new function such as the following:

# the Bradford transformation matrix from v4 ICC Specification
const BFD = [ 0.8951  0.2664 -0.1614
             -0.7502  1.7135  0.0367
              0.0389 -0.0685  1.0296 ]
const BFD_INV = inv(BFD)

function bradford{T <: Color}(c::T, src_white::Color, ref_white::Color)
    c_xyz = convert(XYZ, c)
    src_xyz = convert(XYZ, src_white)
    dest_xyz = convert(XYZ, ref_white)
    
    c_lms = BFD * [c_xyz.x, c_xyz.y, c_xyz.z]
    src_wp = BFD * [src_xyz.x, src_xyz.y, src_xyz.z]
    dest_wp = BFD * [dest_xyz.x, dest_xyz.y, dest_xyz.z]
    ans = BFD_INV * (c_lms .* dest_wp ./ src_wp)

    convert(T, XYZ(ans[1], ans[2], ans[3]))
end

But I don't know whether it makes sense to propose such an ad-hoc function.

Another solution is to make changes to cnvt{T}(::Type{LMS{T}}, c::XYZ) and whitebalance to take the CAT matrix as an optional argument.

Do you have any ideas?

Activity

  1. m-lohmann commented on Mar 8, 2017

    @m-lohmann

    I have been thinking about implementing the handful of most important CATs a while ago, including the BFD (linear or non-linear), simple vonKries etc.

    As far as I remember CAT02 is basically the BFD, but I might be wrong.

    The problem is that there is no strict color conversion pipeline enforced in the whole package.
    Another problem is that RGB is not strictly enforced to be a device color space with potentially negatve values or values greater than 1 for later processing etc.
    I was quitce swamped in a lot of other stuff so I lost track of the whole thing. I wanted to take a deeper look into it because the lack of all this e.g. leads to problems in the DIN99d color space conversions.

    Yes, optional arguments would be a good idea in my eyes.

    Optional arguments in the whole RGB use as well, to define e.g. if it’s sRGB, ARGB or a different RGB color space, for potentiall necessary Gamut compression. Just clipping negative RGB values or values above 1 is pretty much the worst idea in color metrics.

    Color metrics wise, XYZ is the intermediate color space for all conversions, which is also how conversions/gamut manipulations are done with ICC profiles.

    Edit:

    Another possibility would be a function adaptation(CAT,source_whitepoint,destination_whitepoint,color), setting D_65 for CIE64 or CIE31 standard observer as default values. This way, anyone who does not care or know about whitepoint adaptation, gets reasonable default values, and everyone else could use e.g. tabulated whitepoints for both 2° and 10° observers, maybe using named values, like D65_31 for a Daylight 6500K whitepoint using the (CIE31) 2° standard observer.

    As an example, a whitepoint adaptation from D50 to D65 for a 10° observer using the linear Bradford transformation would look like

    adaptation(BFD, D50_64, D65_64, XYZ(0.3,0.56,0.05))

    or, using a whitepoint from the Fluorescent light sources and a different CAT:

    adaptation(Kries, F9_31, D93_31, Lab(64,-20,7))
  2. kimikage commented on Mar 11, 2017

    @kimikage
    CollaboratorAuthor

    I'm in favor of the adaptation(), though there is room for consideration of the name and order of arguments.

  3. kimikage commented on Aug 18, 2019

    @kimikage
    CollaboratorAuthor

    The time passed while I was waiting for Julia v1.x and its packages to be stable.

    I'm going to implement adaptation() by taking the following steps.

    1. Add the white point support for all conversions from/to XYZ.
    2. Extend conversion from/to LMS
      2.1. Add types to represent CATs.
      2.2. Add core conversion functions: xyz_to_lms() and lms_to_xyz().
      2.3. Add convert()s which wraps the core conversion functions.
    3. Add adaptation(color; src_white, ref_white, cat).
    4. Add tests and docs.
  4. kimikage commented on Aug 18, 2019

    @kimikage
    CollaboratorAuthor

    About step 1

    Current whitebalance() has a problem with the pre-/post-conversions which depend on the white points.
    For example, a Lab color should be converted to LMS via XYZ with src_white, and the adapted LMS color should be converted to Lab via XYZ with ref_white.
    However, there is no general option to pass the white point to convert(), and it results in wrong usage of WP_DEFAULT.
    Well, I'm not going to fix whitebalance() for backward compatibility, and I'll add a new function adaptation().

    Another related problem is that the Lab/Luv-based color types, i.e. LCHab, LCHuv, DIN99 and DIN99o, have no direct path for converting with the white point.

    To simplify the codes, it might be a good idea to add the interface to support the white point for all conversions from/to XYZ, including from RGB to XYZ and so on.
    In this idea, the following types of fallbacks may be useful:

    convert(::Type{C}, c, wp::XYZ) where {C<:Color} = cnvt(C, c)
    cnvt(::Type{XYZ{T}}, c, wp::XYZ) where {T} = cnvt(XYZ{T}, c)

    But I'm afraid of the outbreak of ambiguity error.
    Are the fallbacks safe?
    Is it ok not to specify or restrict the type of c? (cf. this)
    What should I do to test the complicated conversion chains?

  5. changed the title [-]Bradford transformation support[/-] [+]Bradford transformation (or other CATs) support[/+] on Aug 18, 2019
  6. kimikage commented on Aug 26, 2019

    @kimikage
    CollaboratorAuthor
  7. kimikage commented on Aug 26, 2019

    @kimikage
    CollaboratorAuthor

    About step 2-3

    Let's decide the function name!

    • cat
      • 😸 short
      • 😿 kitty? catenate?
    • adapt
      • 😸 short
      • 😿 confusing with adopt
    • adaptate
      • 😸 verb
      • 😿 uncommon
    • adaptation
      • 😸 agrees with technical term
      • 😿 noun
    • coloradapt
      • 😸 analogous to colordiff
      • 😿 long, not readable
    • whitebalance
      • 😸 backward-compatible in interface
      • 😿 breaking change in behavior
    • chromatic_adaptation
      • 😸 exact term
      • 😿 too long
    • (other)

    Then, let's decide the function or method interfaces.

    1. adaptation(c, src_white, ref_white, cat)
      • This is simple and analogous to whitebalance.
    2. adaptation(c, src_white, ref_white; cat=default)
      • This is analogous to colordiff (v0.9.6, lexically) and whitebalance.
      • What is good for the default cat? CAT_02?
    3. adaptation(c; src_white=def_src, ref_white=def_ref, cat=default)
      • This is useful for broadcasting and analogous to colordiff (v0.9.6, semantically).
      • adaptation(something, src_white=Colors.WP_D65, ref_white=Colors.WP_D50, cat=CAT_BFD()) is tedious.
      • What are good for the default white points? WP_DEFAULT?
    4. adaptation(c; src_white=def_src, ref_white=def_ref, cat=default) where CAT object holds the default white points
      • This is useful for broadcasting.
      • It is a bother to implement (new) CATs.
      • This may have overhead. (needs benchmarking)
      • Who needs such a high-level interface? The experts may not need the information hiding.

    I'm writing codes in the style ”2.” "3." provisionally.

  8. kimikage commented on Aug 26, 2019

    @kimikage
    CollaboratorAuthor

    About step 4

    Although I can write tests for the behavioral specifications such as the round-trip testing or type checking, for the quantitative testing, the numerical and concrete examples from credible sources are needed.
    What should I refer to?

  9. thomasmansencal commented on Aug 26, 2019

    @thomasmansencal

    What you are doing here is really Von Kries chromatic adaptation, the HVS has three recognised types of adaptation: light, dark and chromatic. The two formers are rarely modelled but who knows :) I would at least go for chromatic_adaptation and for completeness I would add it is a Von Kries type. To give you an example as why it could be useful, we have the following definitions in Colour:

    • colour.adaptation.chromatic_adaptation_VonKries
    • colour.adaptation.chromatic_adaptation_Fairchild1990
    • colour.adaptation.chromatic_adaptation_CMCCAT2000
    • colour.adaptation.chromatic_adaptation_CIE1994

    and a wrapper at the top API namespace where you can choose between the 4 methods:

    • colour.chromatic_adaptation
  10. johnnychen94 commented on Aug 26, 2019

    @johnnychen94
    Member

    About the interface, I think supporting broadcasting is important. FYI, in #338 changes the API of colordiff from style "2." to "3.".

    As for names, CAT really isn't a good choice IMO; since only developers and maintainers will use CAT (If I understand it right), it's okay to keep it long and explicit. Perhaps AbstractAdaptation?

    Also, I think it's better to be LinearCAT(instead of MatrixCAT) vs NonLinearCAT 😄

  11. kimikage commented on Aug 26, 2019

    @kimikage
    CollaboratorAuthor

    What you are doing here is really Von Kries chromatic adaptation

    As you (@kelsolaar) have mentioned, the current whitebalance and the very function what I need (i.e. linear Bradford transform) are based on Von Kries model.
    However, I feel the need to enhance the interface for supporting general transform models.
    Thank you for the information about "Colour Science for Python".

    Edit:
    I think the light and dark adaptation is not suitable with color systems handled by Colors.jl or defined in ColorTypes.jl, because they do not strictly handle the luminous flux or luminous intensity.
    On the other hand, some complex chromatic adaption models take account of the luminous intensity.
    So, in this comment, ”general” means that there is no restriction.

  12. thomasmansencal commented on Aug 26, 2019

    @thomasmansencal

    @kimikage : Complex chromatic adaption models have much more inputs, e.g. CMCCAT2000:

        XYZ : array_like
            *CIE XYZ* tristimulus values of the stimulus to adapt.
        XYZ_w : array_like
            Test viewing condition *CIE XYZ* tristimulus values of the whitepoint.
        XYZ_wr : array_like
            Reference viewing condition *CIE XYZ* tristimulus values of the
            whitepoint.
        L_A1 : numeric or array_like
            Luminance of test adapting field :math:`L_{A1}` in :math:`cd/m^2`.
        L_A2 : numeric or array_like
            Luminance of reference adapting field :math:`L_{A2}` in :math:`cd/m^2`.
        surround : CMCCAT2000_InductionFactors, optional
            Surround viewing conditions induction factors.
    

    Or CIE (1994):

        XYZ_1 : array_like
            *CIE XYZ* tristimulus values of test sample / stimulus.
        xy_o1 : array_like
            Chromaticity coordinates :math:`x_{o1}` and :math:`y_{o1}` of test
            illuminant and background.
        xy_o2 : array_like
            Chromaticity coordinates :math:`x_{o2}` and :math:`y_{o2}` of reference
            illuminant and background.
        Y_o : numeric
            Luminance factor :math:`Y_o` of achromatic background as percentage
            normalised to domain [18, 100] in **'Reference'** domain-range scale.
        E_o1 : numeric
            Test illuminance :math:`E_{o1}` in :math:`cd/m^2`.
        E_o2 : numeric
            Reference illuminance :math:`E_{o2}` in :math:`cd/m^2`.
        n : numeric, optional
            Noise component in fundamental primary system.
    
  13. kimikage commented on Aug 26, 2019

    @kimikage
    CollaboratorAuthor

    Complex chromatic adaption models have much more inputs

    Yes. So I'm going to pass the buck to input CAT object. Not Type but CAT struct object is passed into the last argument cat. This is an analogy to colordiff.

  14. kimikage commented on Aug 26, 2019

    @kimikage
    CollaboratorAuthor

    @johnnychen94, I also think supporting broadcasting is important. On the other hand, I don't think it is a fatal problem for users to write the wrapper functions or lambdas, though. The same may be said of other disadvantages.

    Edit: I'm testing the style "3.".

    FYI, in #338 changes the API of colordiff from style "2." to "3.".

    Exactly. I edited the former comment (but I don't know if it is comprehensible enough).

    I think AbstractCAT is good for the alternative if its subtypes end with CAT. 😸
    Edit: I renamed CAT->AbstractCAT.

    I purposely chose MatrixCAT (instead of LinearCAT) because the relationship between them and NonLinearCATs will not so simple.

  15. kimikage commented on Oct 3, 2019

    @kimikage
    CollaboratorAuthor

    I will do the step 1 (adding the white point argument), after the cleaning up the conversion methods (issue #354).

    And then, what about replacing the wp::XYZ to something like ctx::AbstractConversionContext instead of the current step 2.3 (adding convert(::Type{XYZ}, c::LMS, wp::XYZ; cat::AbstractCAT=CAT_02()))?
    The context (which currently I need) is like the following.

    struct ConversionContext <: AbstractConversionContext
        src_wp::XYZ
        dest_wp::XYZ
        cat::AbstractCAT
    end

    In the future, we may add something like src_flavor/dest_flavor (e.g. "LinearRGB", "AdobeRGB", "BT.709" and "DCI-P3" for RGB, "BT.601", "BT.709" and "JFIF" for YCbCr) or src_gamut/dest_gamut. I think it must be a trouble maker, though.

  16. kimikage commented on Dec 14, 2019

    @kimikage
    CollaboratorAuthor

    It is not easy to be consistent with the current convert, and Colors.jl does not necessarily support chromatic adaptation. So, I will try to find another approach.

  17. kimikage commented on Oct 18, 2020

    @kimikage
    CollaboratorAuthor

    "I" have declined to support comprehensive color conversions "in Colors.jl".
    However, as mentioned in the OP, the linear Bradford transform is often used, so I'm beginning to feel that bradford() is worth adding ad-hoc. After three and a half years, I decided to go back to my origin. 😄

    I'm interested in the CSS Color Module Level 5 API, for which the First Public Working Draft was released this year. (That's just the beginning of the discussion. Also, I think the current proposal is too complicated, though. 😅)
    It is strongly dependent on Lab colors etc. of CSS Color Module Level 4. The Level 4 is still in the Working Draft stage, but the Level 5 seems to be setting the course for the Level 4, IMO. So, the linear Bradford transform is important.
    (cf. https://drafts.csswg.org/css-color/#lab-to-rgb)

  18. changed the title [-]Bradford transformation (or other CATs) support[/-] [+]Bradford transformation support[/+] on Mar 3, 2021
  19. added this to the 0.13 milestone on Mar 3, 2021
  20. kimikage commented on Apr 15, 2021

    @kimikage
    CollaboratorAuthor

    In the scope of colors, I think there is almost no ambiguity in bradford referring to the "bradford transform". However, since Bradford is a name derived from a university, I could not rule out the possibility that it has different meanings in other contexts.
    Therefore, there are roughly three possible options.

    1. name the function bradford and export it
    2. name the function bradford_transform and export it.
    3. name the function bradford and do not export it

    In general, I think avoiding name collisions is the responsibility of the using (importing) side. However, considering that Colors.jl can be used in any technical field within visualization (plotting), and that Colors.jl is used with the reexport mechanism, I think 2. is safer.

  21. removed this from the 0.13 milestone on Apr 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions