afre's G'MIC diary

@snibgo, could you share a link to your code ?
(I’m not capable of understanding all the intricacies of the Matlab code).

@afre @snibgo @garagecoder I am very much interested in this cross-check! I have the plan to replace Lab by Jzazbz everywhere in the Photoflow code, particularly where reliable Hue and Chroma values are needed.

As a first step, I am adding the Jzazbz values to the color sampler, so that one can easily compare RGB, Lab and Jzazbz with test images. I will let you know as soon as the code is committed and packages are available for testing.

http://im.snibgo.com/jzazbz.htm

The explanation for the extra 0.5s on az and bz is

@afre @snibgo looking at Alan’s code it seems to be equivalent to what I have in Photoflow, apart for the 0.5s added to az and bz.

I would propose to compare few simple numerical examples, before considering a more complex image. Here is what I obtain starting from a pure sRGB red (using Alan’s terminology for the intermediate variables and a peak luminance of 10000):

RGB: 255, 0, 0
XYZ:    41.2417, 21.2657, 1.9312
XpYpZp: 47.1383, 28.0575, 1.9312

LMS:    35.8541, 22.0463, 7.93806
LpMpSp: 0.218877, 0.180661, 0.116117

Izazbz: 0.199769, 0.0996431, 0.0912486
Jzazbz: 0.0989701, 0.0996431, 0.0912486

Had a quick look also, and one question for @afre.
The @snibgo’s function ConvertXYZToJzazbz() seems to be quite straightfoward to convert into a single G’MIC fill command, so why didn’t you choose this path ?
Possible advantages:

  1. The G’MIC math parser does all its computation using double-precision values (64bits), whereas a classical G’MIC pipeline has to store manipulated values in float-valued images (32bits), so using a single call to a fill would allow more precision in the calculus.

  2. The color conversion is obviously done pixel by pixel, which means doing it in a single fill would allow easy parallelization of the calculus.

  3. Not checked entirely, but it seems to me that (almost) a simple copy/paste of the @snibgo’s function could be enough, as the G’MIC math parser is inspired from the C language.

Maybe something like:

#@cli rgb2jzazbz : illuminant={ 0=D50 | 1=D65 } : (no arg)
#@cli : Convert color representation of selected images from RGB to Jzazbz.
#@cli : Default value: 'illuminant=1'.
rgb2jzazbz : skip "${1=,}"
  l[] if isnum("$1") illu={"$1?1:0"} else if ["'$1'"]!=',' noarg fi illu=1 fi onfail noarg illu=1 endl
  e[^-1] "Convert color representation of image$? from RGB to Jzazbz, using the D"{arg(1+$illu,50,65)}" illuminant."
  rgb2xyz $illu xyz2jzazbz

#@cli xyz2jzazbz
#@cli : Convert color representation of selected images from XYZ to RGB.
xyz2jzazbz :
  e[^-1] "Convert color representation of image$? from XYZ to Jzazbz."
  f ${-_jzazbz_const}"
    Xp = Jzazbz_b*i0 - (Jzazbz_b - 1)*i2;
    Yp = Jzazbz_g*i1 - (Jzazbz_g - 1)*i0;
    Zp = i2;
    L = 0.41478972*Xp + 0.579999*Yp + 0.0146480*Zp;
    M = -0.2015100*Xp + 1.120649*Yp + 0.0531008*Zp;
    S = -0.0166008*Xp + 0.264800*Yp + 0.6684799*Zp;
    tmp = (L/peakLum)^Jzazbz_n;
    Lp = ((Jzazbz_c1 + Jzazbz_c2*tmp)/(1 + Jzazbz_c3*tmp))^Jzazbz_p;
    tmp = (M/peakLum)^Jzazbz_n;
    Mp = ((Jzazbz_c1 + Jzazbz_c2*tmp)/(1 + Jzazbz_c3*tmp))^Jzazbz_p;
    tmp = (S/peakLum)^Jzazbz_n;
    Sp = ((Jzazbz_c1 + Jzazbz_c2*tmp)/(1 + Jzazbz_c3*tmp))^Jzazbz_p;
    Iz  = 0.5*Lp + 0.5*Mp;
    az = 3.52400*Lp - 4.066708*Mp + 0.542708*Sp;
    bz = 0.199076*Lp + 1.096799*Mp - 1.295875*Sp;
    Jz = (1 + Jzazbz_d)*Iz/(1 + Jzazbz_d*Iz) - Jzazbz_d0;
    [ Jz,az,bz,Jz ]"

#@cli jzazbz2rgb : illuminant={ 0=D50 | 1=D65 } : (no arg)
#@cli : Convert color representation of selected images from RGB to Jzazbz.
#@cli : Default value: 'illuminant=1'.
jzazbz2rgb : skip "${1=,}"
  l[] if isnum("$1") illu={"$1?1:0"} else if ["'$1'"]!=',' noarg fi illu=1 fi onfail noarg illu=1 endl
  e[^-1] "Convert color representation of image$? from Jzazbz to RGB, using the D"{arg(1+$illu,50,65)}" illuminant."
  jzazbz2xyz xyz2rgb $illu

#@cli jzazbz2xyz
#@cli : Convert color representation of selected images from RGB to XYZ.
jzazbz2xyz :
  e[^-1] "Convert color representation of image$? from Jzazbz to XYZ."
  f ${-_jzazbz_const}"
    tmp = i0 + Jzazbz_d0;
    Iz = tmp/(1 + Jzazbz_d - Jzazbz_d*tmp);
    azz = i1;
    bzz = i2;
    Lp = Iz + 0.138605043271539*azz + 0.0580473161561189*bzz;
    Mp = Iz - 0.138605043271539*azz - 0.0580473161561189*bzz;
    Sp = Iz - 0.0960192420263189*azz - 0.811891896056039*bzz;
    tmp = Lp^(1/Jzazbz_p);
    L = peakLum*((Jzazbz_c1 - tmp)/(Jzazbz_c3*tmp-Jzazbz_c2))^(1/Jzazbz_n);
    tmp = Mp^(1/Jzazbz_p);
    M = peakLum*((Jzazbz_c1 - tmp)/(Jzazbz_c3*tmp-Jzazbz_c2))^(1/Jzazbz_n);
    tmp = Sp^(1/Jzazbz_p);
    S = peakLum*((Jzazbz_c1 - tmp)/(Jzazbz_c3*tmp-Jzazbz_c2))^(1/Jzazbz_n);
    Xp = 1.92422643578761*L - 1.00479231259537*M + 0.037651404030618*S;
    Yp = 0.350316762094999*L + 0.726481193931655*M - 0.065384422948085*S;
    Zp = -0.0909828109828476*L - 0.312728290523074*M + 1.52276656130526*S;
    X = (Xp + (Jzazbz_b - 1)*Zp)/Jzazbz_b;
    Y = (Yp + (Jzazbz_g - 1)*X)/Jzazbz_g;
    Z = Zp;
    [ X,Y,Z ]"

_jzazbz_const :
  u "const Jzazbz_b = 1.15;
     const Jzazbz_g = 0.66;
     const Jzazbz_c1 = 3424/4096;
     const Jzazbz_c2 = 2413/128;
     const Jzazbz_c3 = 2392/128;
     const Jzazbz_n = 2610/16384;
     const Jzazbz_p = 1.7*2523/32;
     const Jzazbz_d = -0.56;
     const Jzazbz_d0 = 1.6295499532821566e-11;
     const peakLum = 10000;"

not tested thoroughly, but at least, the transform seems to be correctly reversible, with

$ gmic sp lena rgb2jzazbz jzazbz2rgb

As afre says, Jzazbz colorspace has my C code for converting from XYZ to Jzazbz, and from Jzazbz to XYZ. My code is deliberately simple (for verification purposes), and could be optimised. In normal use, the parameter “peakLum” should be 10000, and “debug” should be false. The pixel values X, Y, Z, Jz, az and bz should be in the nominal range 0.0 to 1.0.

As I show on that page, the round-trip RGB->XYZ->Jzazbz->XYZ->RGB is successful.

If anyone is interested, three diff files have my proposed changes to ImageMagick. They include the polar colorspace JzCzhz, but the IM developers decided not to include that in IM.

http://snibgo.com/imforums/jz_colorspace_c.diff
http://snibgo.com/imforums/jz_colorspace_h.diff
http://snibgo.com/imforums/jz_option_c.diff

1 Like

Thanks for chiming in everyone. Makes my hobby more relevant. :slight_smile:
And I get to see people’s approach in doing what is essentially the same thing.

My result is 0.00816744, 0.0161597, 0.0114079.
David’s is the same as well. I am confused. Is insomnia messing with me?

I realized this yesterday. Initially chose mix_channels to speed up the command. It is faster due to less precision!

In G’MIC, XYZ colors are roughly in range [0,1], so not the same range than what @Carmelo_DrRaw shows.

I’ve added commands rgb2jzazbz', xyz2jzazbz, jzazbz2rgbandjzazbz2xyzin the G'MICstdlib`.
Not sure they are correct right now, but I guess we can still correct them afterwards.
I’ve included the XYZ normalization in those commands.

https://github.com/dtschump/gmic/commit/fc070710de1e5b6eb0356e2625d573e1e437c485

Thanks for adding it to stdlib. Guess I made enough noise and you finally found the time. :stuck_out_tongue:

At first glance, I see a typo.

Before retiring afre_jabz and friends, I might give the math and code another look, so I am still interested in your opinions and developments @snibgo and @Carmelo_DrRaw if you have any going forward.

1 Like

Updates

1 Finalized afre_texture. It now accentuates or smooths 3 detail scales in the luminance space.

afre_texture:
    -100<=coarse<=100,-100<=_medium<=100,-100<=_fine<=100

  Enhance texture with detail scales.
  Default values: 'coarse=0', 'medium=0' and 'fine=0'.

 
2 Fixed bugs where radii of commands end up even.


As usual, update your G’MICs and their filters.

2 Likes

This broke afre_localcontrast (and other commands). :face_with_hand_over_mouth: Committed fix: wait a moment before updating.


Improvement

3 Added a weight map to afre_contrast, which afre_localcontrast already has. As a result, it no longer produces flat tonality at contrast extremes.

1 Like

Another improvement

Resolved. No more haloing at max settings (radius=10 and amount=100).

p9

sample

2 Likes

Hmm, it seems that you are using min(old,filtered) for the result. I have not see lighter areas for the output.

The weighting favours intensities just below middle grey to prevent smooth areas from being flat and bright areas from being crunchy.


New commands

1 afre_brightness is the big sibling of afre_contrast. Enhances the luminance brightness.

2 afre_maxmin and afre_minmax return the “inner” min and max of the selected multichannel image, respectively. E.g. sp tiger has Rmin=4 Gmin=6 Bmin=3; therefore, its afre_maxmin=6.


PS: Question “minmax” has other connotations. Should I use another term?

As usual, update your G’MICs and their filters in a couple of hours after the diary entry.

1 Like

Gives me a idea: Apply filter, boost lighter area, apply filter again.

FixesAs usual, update your G’MICs and their filters in a couple of hours after the diary entry.

1 afre_brightness Copy-pasted the wrong curve.
2 Improved afre_brightness and afre_contrast.

1 Like

3 Deprecated afre_jabz afre_ijabz (in stdlib now); afre_jchz afre_ijchz still available.
4 Improved afre_localcontrast. Tweaked weights and uses Jzazbz now. Edit 3 Smooth before enhancement, which slows the command but yields better results. Sorry, took 3 commits!


Update your G’MICs and their filters in a couple of hours after the diary entry. Again

1 Like

5 Rewrote afre_softlight. It is ready to graduate from “Testing”: instead of “Layers”, I may put it in “Colors” because it belongs with afre_brightness afre_contrast afre_localcontrast afre_darksky Thoughts?

Edit The range is a little off compared to the builtin version. That minuscule difference is enough to make afre_darksky go :crazy_face:. Investigating…

Edit Apparently, the code works when in user.gmic but not in update***.gmic. Any idea why that is? Is insomnia getting in the way again?

1 Like

That seems to work here. What kind of errors do you get ?