rvanwijnen / spectral.js

Spectral.js is a paint like color mixing library utilizing the Kubelka-Munk theory.
https://onedayofcrypto.art/
MIT License
748 stars 16 forks source link

Specrtal range and Color-matching functions #12

Open Roger-Breton opened 5 months ago

Roger-Breton commented 5 months ago

First, what is the spectral range of the SPD_C and so on variables? Is it 380 to 730? Second, what CIE_CMF_X / Y / Z are you using? Is it the 1931 2 degree CMF or the 1964 10 degree CMF? Comparing with CIE published data, I can't make where you got the numbers from?

Roger-Breton commented 5 months ago

OK, I established that the CMF are indeed the 1931/2. But the values are incorrect. Still, it would help if you disclosed the spectral range. Right now, I'm going to have to replace your CMFs with the "proper" CIE XYZ CMF. Otherwise, I can't follow your logic. Also, I looked at SPD_C/M/Y/R/G/B and it appears you used "Block dye" approach. Again, I'm going to replace those with real pigment information.

rvanwijnen commented 5 months ago

Hi Roger,

I'm very interested in the things you are planning to do with real pigment data using the spectral.js library as reference, please keep me posted on your progress.

To get in some more technical details and provide some answers for your questions; The CMF are CIE 1931 2deg. The spectral range is 380 to 730 in 10 nm steps. As this library is specifically aimed for sRGB output the D65 standard illuminant is used.

The CIE CMF arrays are multiplied with the D65 illuminant and then normalized by the summed value of Y to get the tristimulus (XYZ bar) values used for further calculations. (The summed values for the XYZ bar are 0.95, 1.00 and 1.08 which should be familiar: Standard illuminant - Wikipedia) This is similar to what ColorPy does: ColorPy.

I have created an online spreadsheet for you to see how I calculated the arrays: Spreadsheet.

The sources I used are coming directly from CIE: CMF D65

The SPD's used for the different colors are generated based on work done by Scott Burns and are not representative of real world pigments. They are 'best fit' for the specific color using the CMF with the D65 illuminant: Generating Reflectance Curves from sRGB Triplets.

Roger-Breton commented 5 months ago

Hi rvan,

As I searched my file library and CIE documentation for references with regards to your "CMF", it dawned on me that you could have pre-multiplied the functions by the normalization constant "k", in an effort to simplify and speed up the code, since your code is aimed at "performance". But I wasn't entirely sure. Your approach is sound and I will lookup ColorPy for curiosity :-)

My orientation is "graphic arts". I live in a world of Photoshop and ICC profiling color management. I took a seminar at Munsell Color Science Institute in Rochester, many ears ago, and I recently became intersted in "acrylic prigments" mixing. To make a long story short, I ended up on this web site, https://goldenartistcolors.com/mixer/acrylic, where visitors can experiment color mixing based on Kubelka-Monk. I use paints by Liquitex but they seem to have much in common with these guys's paints. The part I was especially interested was the ability to load up an RGB image, click on some pixels and have the system suggest tubes of paints with quanitites of each to match the color. Wow! I still don't know how I am going to introduce this "notion" into my Photoshop class at the university where I teach (www.uqam.com) in Montréal.

At this point, my goal is to understand the application of the Kulbelka-Monk theory in practice and your Spectral.js implementation is the closest thing I found on the internet to step by step application.

You see, when I look at an image and try to match the color, I may get the intuition of which paints to mix but it is not obvious especially with certain colors? That is why I was looking for a way to overcome this lack of experience at mixing artists acrylic paints. One of the ways I think I'm going to introduce this technique to my students is to select a number of colors out of an sRGB image and use the Golden system to figure which paints to mix in order to match the color.

I realize with experience, a person could evolve a way to mix paints without the need to use a computerized matching system. But with my Graphic design Photoshop students, I am trying to come up with approaches that will speed up their learning.

So, one of the things I intend to do is to substite your "block dyes" (CMYRGB) with actual spectral reflectances and I think the best way to experiement would be to use ColorChecker spectral reflectances for the Cyan, Magenta, Yellow, Red, Green and Blue patches.

Another thing I will do is to come up with D50 "CMF" because Photoshop is D50 and all the student's "interactions" with color is through Photoshop. I want to avoid the complications of chromatic adaptation.

I'm curious about your RGB "Spectral Reflectance Estimation" technique? At the begining of your code, you ask to "spectral_mix([0, 33, 133], [252, 210, 0], 0.5)". In other words, to mix 50% of two RGB colors. I will have to study how the linear_to_reflectance(lrgb) function constructs spectral reflectances from RGB values: Spectral Estimatino of RGB values

Roger-Breton commented 5 months ago

Found the Scott Burns "Color Projects" web page. I think I would use my own custom-measured 1931/2 D50 Munsell spectral reflectances... But the RGB -> spectral reflectances is incredible!!

Roger-Breton commented 5 months ago

Hi Ronald,

I am struggling with understanding your Kulbelka-Monk code. This line of code, for example

KS = (1 - t) * ((1 - R1[i]) 2 / (2 R1[i])) + t ((1 - R2[i]) 2 / (2 * R2[i]))

corresponds to this equation [cid:ac379e4e-5f7b-4800-a8ce-19ffbd64d381]

Right?

The (1 - t) term allows creating a KS that is a linear combination of more than one colorant, if I am not mistaking and could be extended to more than two colorants.

This expression

KM = 1 + KS - (KS * 2 + 2 KS) ** 0.5

computes the spectral reflectance?

Did you find any of this on ColorPy (ColorPyhttps://github.com/markkness/ColorPy/blob/master/colorpy/illuminants.py#L634)?

I will take a look.

Thank you so much for your kind help and patience,

/ Roger


From: Ronald van Wijnen @.> Sent: Friday, March 29, 2024 5:16 AM To: rvanwijnen/spectral.js @.> Cc: Roger-Breton @.>; Author @.> Subject: Re: [rvanwijnen/spectral.js] Specrtal range and Color-matching functions (Issue #12)

Hi Roger,

I'm very interested in the things you are planning to do with real pigment data using the spectral.js library as reference, please keep me posted on your progress.

To get in some more technical details and provide some answers for your questions; The CMF are CIE 1931 2deg. The spectral range is 380 to 730 in 10 nm steps. As this library is specifically aimed for sRGB output the D65 standard illuminant is used.

The CIE CMF arrays are multiplied with the D65 illuminant and then normalized by the summed value of Y to get the tristimulus (XYZ bar) values used for further calculations. (The summed values for the XYZ bar are 0.95, 1.00 and 1.08 which should be familiar: Standard illuminant - Wikipediahttps://en.wikipedia.org/wiki/Standard_illuminant#D65_values) This is similar to what ColorPy does: ColorPyhttps://github.com/markkness/ColorPy/blob/master/colorpy/illuminants.py#L634.

I have created an online spreadsheet for you to see how I calculated the arrays: Spreadsheethttps://docs.google.com/spreadsheets/d/1U6utrBNvuvXn9kt8DQi32kT8WjnUSMnUmuBDTp5y9y0/edit?usp=sharing.

The sources I used are coming directly from CIE: CMFhttps://cie.co.at/datatable/cie-1931-colour-matching-functions-2-degree-observer D65https://cie.co.at/datatable/cie-standard-illuminant-d65

The SPD's used for the different colors are generated based on work done by Scott Burns and are not representative of real world pigments. They are 'best fit' for the specific color using the CMF with the D65 illuminant: Generating Reflectance Curves from sRGB Tripletshttp://scottburns.us/reflectance-curves-from-srgb-10/.

— Reply to this email directly, view it on GitHubhttps://github.com/rvanwijnen/spectral.js/issues/12#issuecomment-2026926131, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AEVBK5F3EZKXYWY4RUXBM3DY2UPM5AVCNFSM6AAAAABFNY5H6GVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDAMRWHEZDMMJTGE. You are receiving this because you authored the thread.Message ID: @.***>

Roger-Breton commented 5 months ago

Hi Ronald,

After much reading and research, it seems that this equation

for i in range(SIZE): KS = (1 - t) * ((1 - R1[i]) 2 / (2 R1[i])) + t ((1 - R2[i]) 2 / (2 * R2[i])) KM = 1 + KS - (KS * 2 + 2 KS) ** 0.5 R[i] = KM

Corresponds to the Single-constant form of the theory and you have it as multicomponent system (Henry Kang, Technology for electronic devices, page 49).

/ Roger


From: Ronald van Wijnen @.> Sent: Friday, March 29, 2024 5:16 AM To: rvanwijnen/spectral.js @.> Cc: Roger-Breton @.>; Author @.> Subject: Re: [rvanwijnen/spectral.js] Specrtal range and Color-matching functions (Issue #12)

Hi Roger,

I'm very interested in the things you are planning to do with real pigment data using the spectral.js library as reference, please keep me posted on your progress.

To get in some more technical details and provide some answers for your questions; The CMF are CIE 1931 2deg. The spectral range is 380 to 730 in 10 nm steps. As this library is specifically aimed for sRGB output the D65 standard illuminant is used.

The CIE CMF arrays are multiplied with the D65 illuminant and then normalized by the summed value of Y to get the tristimulus (XYZ bar) values used for further calculations. (The summed values for the XYZ bar are 0.95, 1.00 and 1.08 which should be familiar: Standard illuminant - Wikipediahttps://en.wikipedia.org/wiki/Standard_illuminant#D65_values) This is similar to what ColorPy does: ColorPyhttps://github.com/markkness/ColorPy/blob/master/colorpy/illuminants.py#L634.

I have created an online spreadsheet for you to see how I calculated the arrays: Spreadsheethttps://docs.google.com/spreadsheets/d/1U6utrBNvuvXn9kt8DQi32kT8WjnUSMnUmuBDTp5y9y0/edit?usp=sharing.

The sources I used are coming directly from CIE: CMFhttps://cie.co.at/datatable/cie-1931-colour-matching-functions-2-degree-observer D65https://cie.co.at/datatable/cie-standard-illuminant-d65

The SPD's used for the different colors are generated based on work done by Scott Burns and are not representative of real world pigments. They are 'best fit' for the specific color using the CMF with the D65 illuminant: Generating Reflectance Curves from sRGB Tripletshttp://scottburns.us/reflectance-curves-from-srgb-10/.

— Reply to this email directly, view it on GitHubhttps://github.com/rvanwijnen/spectral.js/issues/12#issuecomment-2026926131, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AEVBK5F3EZKXYWY4RUXBM3DY2UPM5AVCNFSM6AAAAABFNY5H6GVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDAMRWHEZDMMJTGE. You are receiving this because you authored the thread.Message ID: @.***>

Roger-Breton commented 5 months ago

Found the exact equations you use

[cid:9af6ec22-36b4-4378-9c03-f88014b4d5c6]

Which, apparently, reduces to

[cid:399c3857-6967-4449-8c46-164f261b0199]

Complicated stuff. Slowly getting there.

/ Roger


From: Ronald van Wijnen @.> Sent: Friday, March 29, 2024 5:16 AM To: rvanwijnen/spectral.js @.> Cc: Roger-Breton @.>; Author @.> Subject: Re: [rvanwijnen/spectral.js] Specrtal range and Color-matching functions (Issue #12)

Hi Roger,

I'm very interested in the things you are planning to do with real pigment data using the spectral.js library as reference, please keep me posted on your progress.

To get in some more technical details and provide some answers for your questions; The CMF are CIE 1931 2deg. The spectral range is 380 to 730 in 10 nm steps. As this library is specifically aimed for sRGB output the D65 standard illuminant is used.

The CIE CMF arrays are multiplied with the D65 illuminant and then normalized by the summed value of Y to get the tristimulus (XYZ bar) values used for further calculations. (The summed values for the XYZ bar are 0.95, 1.00 and 1.08 which should be familiar: Standard illuminant - Wikipediahttps://en.wikipedia.org/wiki/Standard_illuminant#D65_values) This is similar to what ColorPy does: ColorPyhttps://github.com/markkness/ColorPy/blob/master/colorpy/illuminants.py#L634.

I have created an online spreadsheet for you to see how I calculated the arrays: Spreadsheethttps://docs.google.com/spreadsheets/d/1U6utrBNvuvXn9kt8DQi32kT8WjnUSMnUmuBDTp5y9y0/edit?usp=sharing.

The sources I used are coming directly from CIE: CMFhttps://cie.co.at/datatable/cie-1931-colour-matching-functions-2-degree-observer D65https://cie.co.at/datatable/cie-standard-illuminant-d65

The SPD's used for the different colors are generated based on work done by Scott Burns and are not representative of real world pigments. They are 'best fit' for the specific color using the CMF with the D65 illuminant: Generating Reflectance Curves from sRGB Tripletshttp://scottburns.us/reflectance-curves-from-srgb-10/.

— Reply to this email directly, view it on GitHubhttps://github.com/rvanwijnen/spectral.js/issues/12#issuecomment-2026926131, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AEVBK5F3EZKXYWY4RUXBM3DY2UPM5AVCNFSM6AAAAABFNY5H6GVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDAMRWHEZDMMJTGE. You are receiving this because you authored the thread.Message ID: @.***>

rvanwijnen commented 5 months ago

Hi Roger,

The images are not showing for me.

A lot of the information for creating this library comes from the Mixbox paper, this can be found on their repositorie: https://github.com/scrtwpns/mixbox

Roger-Breton commented 4 months ago

Good news!

I modified your python code to use D50 instead of D65 illuminant. In addition to your python code, I manually created an Excel spreadsheet to manually carry essentially the same Kubelka-Munk calculations as your code.

For my comparison, I used two acrylic paints, Rose Madder and Artic Blue, mixed 40% Rose Madder with 60% Artic Blue.

Instead of using your t value based on some Luminance factor developed by your code, I chose to set the t value equal to the "proportion" of each color in the mix, what's often called "concentration".

Your code and my Excel sheet arrived at the same CIE XYZ values.

[cid:7b1d8398-2307-4156-a0e7-ce61026fe749]

At this point, I say "case closed", Ronald. I owe you a beer!

Now your implementation is called "Single-Constant" but I'm told, for acrylic paints, I need the "Two-Constant" approach.

Kindest regards / Roger Breton


From: Ronald van Wijnen @.> Sent: Monday, April 8, 2024 4:31 AM To: rvanwijnen/spectral.js @.> Cc: Roger-Breton @.>; Author @.> Subject: Re: [rvanwijnen/spectral.js] Specrtal range and Color-matching functions (Issue #12)

Hi Roger,

The images are not showing for me.

A lot of the information for creating this library comes from the Mixbox paper, this can be found on their repositorie: https://github.com/scrtwpns/mixbox

— Reply to this email directly, view it on GitHubhttps://github.com/rvanwijnen/spectral.js/issues/12#issuecomment-2042169534, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AEVBK5DOXZXKR4452L67C5DY4JIXRAVCNFSM6AAAAABFNY5H6GVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDANBSGE3DSNJTGQ. You are receiving this because you authored the thread.Message ID: @.***>

Roger-Breton commented 4 months ago

Hello Ronald,

I made a few changes to your code for my needs but the Kubelka-Munk part remains the same. It took me a while to figure out the difference between your results and my results. In the end, the difference stems from the estimated spectral curves used between my real pigments and the RGB -> Spectral you use. I took me a long time to figure it out. On your demo site, I typed in the RGB values corresponding to my two test color, a blue (22 61 199) and a yellow (255, 226, 0). Your blend looked like this

[cid:eac699cc-72c1-4de4-bd28-84f67b497dc6] But my blend looked like this, "dull"? [cid:fe0b5e29-e7cd-49eb-a8a5-e05ec6e807c7] I was convinced there was something wrong with my code but it turned out that the "culprit" is the spectral estimation technique. Here is the difference between our two spectral reflectance curves:

[cid:f23ef3a8-cb0d-4096-baa1-490d6918c5e9]

Our blues are about the same: [cid:79439fe4-116d-4d22-82de-0f9c5e31d1a7]

I learned a lot through your code. In particular, you may be interested in using ASTM E308 Table 5.17 for your "CMF". I attached a copy in case you're interested. I would rather use this table than the complicated "pre-multiplied" table you use. This, anyone can follow, because it is a "standard".

Best / Roger Breton

P.S. On my D50 (5000K) calibrated monitor, my blend looks "yellowish" but on your D65 calibrated monitor, it may look "better". There is a technique I should use to "adapt" the colors from D65 to D50 called "Chromatic adaptation" but that would be the cherry on the icing. It might be interesting to my students... If ever I decide to apply it to my results, I'll share it with you.


From: Ronald van Wijnen @.> Sent: Friday, March 29, 2024 5:16 AM To: rvanwijnen/spectral.js @.> Cc: Roger-Breton @.>; Author @.> Subject: Re: [rvanwijnen/spectral.js] Specrtal range and Color-matching functions (Issue #12)

Hi Roger,

I'm very interested in the things you are planning to do with real pigment data using the spectral.js library as reference, please keep me posted on your progress.

To get in some more technical details and provide some answers for your questions; The CMF are CIE 1931 2deg. The spectral range is 380 to 730 in 10 nm steps. As this library is specifically aimed for sRGB output the D65 standard illuminant is used.

The CIE CMF arrays are multiplied with the D65 illuminant and then normalized by the summed value of Y to get the tristimulus (XYZ bar) values used for further calculations. (The summed values for the XYZ bar are 0.95, 1.00 and 1.08 which should be familiar: Standard illuminant - Wikipediahttps://en.wikipedia.org/wiki/Standard_illuminant#D65_values) This is similar to what ColorPy does: ColorPyhttps://github.com/markkness/ColorPy/blob/master/colorpy/illuminants.py#L634.

I have created an online spreadsheet for you to see how I calculated the arrays: Spreadsheethttps://docs.google.com/spreadsheets/d/1U6utrBNvuvXn9kt8DQi32kT8WjnUSMnUmuBDTp5y9y0/edit?usp=sharing.

The sources I used are coming directly from CIE: CMFhttps://cie.co.at/datatable/cie-1931-colour-matching-functions-2-degree-observer D65https://cie.co.at/datatable/cie-standard-illuminant-d65

The SPD's used for the different colors are generated based on work done by Scott Burns and are not representative of real world pigments. They are 'best fit' for the specific color using the CMF with the D65 illuminant: Generating Reflectance Curves from sRGB Tripletshttp://scottburns.us/reflectance-curves-from-srgb-10/.

— Reply to this email directly, view it on GitHubhttps://github.com/rvanwijnen/spectral.js/issues/12#issuecomment-2026926131, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AEVBK5F3EZKXYWY4RUXBM3DY2UPM5AVCNFSM6AAAAABFNY5H6GVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDAMRWHEZDMMJTGE. You are receiving this because you authored the thread.Message ID: @.***>

Roger-Breton commented 4 months ago

Hi Ronald,

I finally found a moment to paint and measure a swatch of Titanium White. Now I can generate a "palette" like yours. It's interesting to compare colors between the KM approach and Adobe's approach. See below: [cid:8d8ddd08-2970-4822-8d87-aa764f6f472e] Regards / Roger


From: Ronald van Wijnen @.> Sent: Friday, March 29, 2024 5:16 AM To: rvanwijnen/spectral.js @.> Cc: Roger-Breton @.>; Author @.> Subject: Re: [rvanwijnen/spectral.js] Specrtal range and Color-matching functions (Issue #12)

Hi Roger,

I'm very interested in the things you are planning to do with real pigment data using the spectral.js library as reference, please keep me posted on your progress.

To get in some more technical details and provide some answers for your questions; The CMF are CIE 1931 2deg. The spectral range is 380 to 730 in 10 nm steps. As this library is specifically aimed for sRGB output the D65 standard illuminant is used.

The CIE CMF arrays are multiplied with the D65 illuminant and then normalized by the summed value of Y to get the tristimulus (XYZ bar) values used for further calculations. (The summed values for the XYZ bar are 0.95, 1.00 and 1.08 which should be familiar: Standard illuminant - Wikipediahttps://en.wikipedia.org/wiki/Standard_illuminant#D65_values) This is similar to what ColorPy does: ColorPyhttps://github.com/markkness/ColorPy/blob/master/colorpy/illuminants.py#L634.

I have created an online spreadsheet for you to see how I calculated the arrays: Spreadsheethttps://docs.google.com/spreadsheets/d/1U6utrBNvuvXn9kt8DQi32kT8WjnUSMnUmuBDTp5y9y0/edit?usp=sharing.

The sources I used are coming directly from CIE: CMFhttps://cie.co.at/datatable/cie-1931-colour-matching-functions-2-degree-observer D65https://cie.co.at/datatable/cie-standard-illuminant-d65

The SPD's used for the different colors are generated based on work done by Scott Burns and are not representative of real world pigments. They are 'best fit' for the specific color using the CMF with the D65 illuminant: Generating Reflectance Curves from sRGB Tripletshttp://scottburns.us/reflectance-curves-from-srgb-10/.

— Reply to this email directly, view it on GitHubhttps://github.com/rvanwijnen/spectral.js/issues/12#issuecomment-2026926131, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AEVBK5F3EZKXYWY4RUXBM3DY2UPM5AVCNFSM6AAAAABFNY5H6GVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDAMRWHEZDMMJTGE. You are receiving this because you authored the thread.Message ID: @.***>

rvanwijnen commented 4 months ago

I can’t see the images you posted, I DM’d you on X.

Roger-Breton commented 4 months ago

Sorry about emailing you directly. KM vs Adobe Here is one image that speaks volume. I suppose, to be able to make a true generalization, I would have to try the comparison using a number of other pigment colors. Ideally, I would like to write a javascript that runs inside of Adobe InDesign. Adapting your javascript. But I confess I have not wrote much javascript in my life. When I introduce my students to the concept of "color blending", you can be sure I will send them to "spectral.js" where they will be able to simply enter two RGB colors and use the generated blends in their creations.

I am continuing my KM research with the "Two-Constants" approach. Much harder... But I have you to thank for showing me how "Single-Constant" works.