Page 1 of 1

Colorspace round trips

Posted: 2013-06-16T19:28:51-07:00
by snibgo
I'm not convinced about the round-trip capabilities of some of the colorspaces.

In Windows 7, IM v6.8.6-0, I did the following:

Code: Select all

convert hald:8 h8.png
convert h8.png -colorspace %%a -colorspace sRGB x.png
compare -metric RMSE h8.png x.png NULL: 2>>csrt.lis
The results, sorted with worst RMSE at the top, are:

Code: Select all

18181.7 (0.277435) Gray
18181.7 (0.277435) Rec709Luma
16972 (0.258977) Rec601Luma
7613.53 (0.116175) LCHab
7613.53 (0.116175) LCH
7191.13 (0.10973) YCC
6176.26 (0.0942437) LCHuv
5912.42 (0.0902177) YDbDr
265.687 (0.00405413) YUV
156.837 (0.00239318) YIQ
152.622 (0.00232887) XYZ
71.7158 (0.00109431) LMS
8.19112 (0.000124989) Rec601YCbCr
8.15981 (0.000124511) YCbCr
8.15981 (0.000124511) YPbPr
1.69933 (2.59302e-005) Luv
1.64528 (2.51054e-005) Rec709YCbCr
1.43594 (2.1911e-005) Lab
1.43594 (2.1911e-005) CIELab
1.40868 (2.14951e-005) Log
1.11803 (1.70601e-005) scRGB
1.11803 (1.70601e-005) RGB
0.995072 (1.51838e-005) OHTA
0.745473 (1.13752e-005) HSL
0.675326 (1.03048e-005) HSI
0.644634 (9.83648e-006) HCLp
0.612231 (9.34205e-006) HCL
0.583683 (8.90643e-006) HSB
0.583683 (8.90643e-006) HSV
0.578735 (8.83093e-006) HWB
0 (0) Transparent
0 (0) sRGB
0 (0) CMYK
0 (0) CMY
The worst three are monochrome colorspaces, so the round-trip should fail. The results from YUV onwards are less than 1%, so we might put this down to rounding errors.

But can anyone explain the 11% difference for LCHab, etc?

Re: Colorspace round trips

Posted: 2013-06-17T00:13:04-07:00
by GreenKoopa
Those colorspaces should be capable of a round trip without clipping. And that does seem like a lot to be rounding error. Have you tried using HDRI ? I don't know where to get a build, or how to create one for Windows. It may give insight. On the other hand, HDRI working wouldn't necessarily mean that this is caused by rounding error alone.

Looking at the math for these, I see nothing exciting. And the ones with larger error don't have anything in common. I do know that with integer math, these functions can be tricky to implement. Intermediate steps can easily cause clipping. For example, multiplying a value by 8 then dividing by 10 is fine in math or HDRI, but severely clips with computer integers. Reversing to dividing then multiplying introduces noise, but at least not the clipping. Other methods yield better results. I'm sure the programmers know all of this well, but it can be a complex area for bugs to creep in.

Or maybe it's rounding error after all. Not tonight, but I'll try to experiment later.

Re: Colorspace round trips

Posted: 2013-06-17T05:56:59-07:00
by snibgo
Thanks, GK.

Floating-point IM is available from http://www.imagemagick.org/download/alpha/ . This is actually IM v7, but seems compatible with v6 usage. For Windows, I install ImageMagick-7.0.0-0-Q16-x64-dll.exe to a directory of my choice, and "set IMG7={that_directory}".

Sadly, IM7 "-colorspace" seems to be currently broken.

I repeated the test (on v6) with an ordinary photograph. Apart from YCC, the results are far better.

Code: Select all

4000.05 (0.0610369) YCC
2313.23 (0.0352976) Rec601Luma
2295.13 (0.0350215) Rec709Luma
2295.13 (0.0350215) Gray
184.861 (0.0028208) LCHab
184.861 (0.0028208) LCH
144.18 (0.00220005) LCHuv
5.26625 (8.03579e-005) XYZ
4.46201 (6.8086e-005) LMS
2.10566 (3.21303e-005) scRGB
2.10566 (3.21303e-005) RGB
1.88689 (2.87921e-005) Rec601YCbCr
1.53452 (2.34153e-005) YCbCr
1.53452 (2.34153e-005) YPbPr
1.53389 (2.34056e-005) YUV
1.53298 (2.33917e-005) YIQ
1.47786 (2.25507e-005) YDbDr
0.986905 (1.50592e-005) OHTA
0.901452 (1.37553e-005) Luv
0.889155 (1.35676e-005) Rec709YCbCr
0.775572 (1.18345e-005) Log
0.650785 (9.93034e-006) Lab
0.650785 (9.93034e-006) CIELab
0.507796 (7.74847e-006) HSL
0.228193 (3.48201e-006) HCLp
0.22819 (3.48196e-006) HCL
0.215116 (3.28246e-006) HSI
0.00485913 (7.41455e-008) HSB
0.00485913 (7.41455e-008) HSV
0 (0) Transparent
0 (0) sRGB
0 (0) HWB
0 (0) CMYK
0 (0) CMY
One hypothesis: some RGB colours cannot be represented in IM's implementation of LCHab, etc. Or, as you suggest, clipping.

Re: Colorspace round trips

Posted: 2013-06-17T10:37:27-07:00
by GreenKoopa
LCHab and LCHuv are simply Lab and Luv, except cylindrical like HSL and HSV are to RGB. Since Lab, Luv, HSL, and HSV all made the round trip correctly, I believe there is no reason that LCHab and LCHuv couldn't. Maybe cylindrical plus noise causes the problem? Or LCHab and LCHuv are more truly cylindrical, trig and all. Internally it is possible that a round trip looks like sRGB->Lab->LCHab->Lab->sRGB. I don't think this helps explain YCC or YDbDr.

It still feels like something is wrong, or at least could be done better. My next step would be to stop thinking and actually look at what color ranges don't survive the round trip.

Re: Colorspace round trips

Posted: 2013-06-17T11:10:34-07:00
by fmw42
This may be related to the gamut ranges. Some colorspace are outside the range of sRGB or vice-versa. But since LCHab and LCHuv are relatively new in IM, there could still be bugs.

Re: Colorspace round trips

Posted: 2013-06-17T13:40:43-07:00
by GreenKoopa
gamut range, good point to keep in mind.

As the worst offender, I'm going to focus on LHCab. This is my understanding:
The sRGB gamut fits easily within Lab (even implementation-clipped Lab).
LHCab has the same gamut as Lab (mathematically, but implementation?).
L's range is 0..1 (scaled to 0..QuantumRange)
a & b's range is -128..127 in Q8, which is a small clip of the full space. Q16 implementations vary slightly, as we can see by converting with -colorspace vs -profile.
C's range is 0..181 (when staying within the clip of a & b)
H's range is a full circle. (scaled to 0..QuantumRange)

Conclusions:
IM appears to limit C to 0..127. Even for the small gamut of sRGB this probably results in some clipping.
For being a circle, H has far too many QuantumRange values. This must be a wrapping problem in the code.

Re: Colorspace round trips

Posted: 2013-06-17T17:30:57-07:00
by magick
LCHab includes a 0.5 offset to shift -127 to 0 and 0 to 127 to cover the full range [0..255]. Here is the actual code. If you spot an error, let us know:

Code: Select all

static inline void ConvertXYZToLCHab(const double X,const double Y,const double Z,
  double *luma,double *chroma,double *hue)
{
  double
    a,
    b;

  ConvertXYZToLab(X,Y,Z,luma,&a,&b);
  *chroma=hypot(255.0*(a-0.5),255.0*(b-0.5));
  *hue=180.0*atan2(255.0*(b-0.5),255.0*(a-0.5))/MagickPI;
  *chroma=(*chroma)/255.0+0.5;
  *hue=(*hue)/255.0+0.5;
  if (*hue < 0.0)
    *hue+=1.0;
}

MagickPrivate void ConvertRGBToLCHab(const double red,const double green,const double blue,
  double *luma,double *chroma,double *hue)
{
  double
    X,
    Y,
    Z;

  ConvertRGBToXYZ(red,green,blue,&X,&Y,&Z);
  ConvertXYZToLCHab(X,Y,Z,luma,chroma,hue);
}

static inline void ConvertLCHabToXYZ(const double luma,const double chroma,const double hue,
  double *X,double *Y,double *Z)
{
  ConvertLabToXYZ(luma,chroma*cos(hue*MagickPI/180.0),chroma*
    sin(hue*MagickPI/180.0),X,Y,Z);
}

MagickPrivate void ConvertLCHabToRGB(const double luma,const double chroma,const double hue,
  double *red,double *green,double *blue)
{
  double
    X,
    Y,
    Z;

  ConvertLCHabToXYZ(100.0*luma,255.0*(chroma-0.5),255.0*(hue-0.5),&X,&Y,&Z);
  ConvertXYZToRGB(X,Y,Z,red,green,blue);
}

Re: Colorspace round trips

Posted: 2013-06-17T18:46:09-07:00
by GreenKoopa
I see both my C and H channel concerns.

Code: Select all

// From ConvertXYZToLCHab()

// a in [0.0,1.0]
// b in [0.0,1.0]
// 255.0*(a-0.5) in [-127.5,127.5]
// 255.0*(b-0.5) in [-127.5,127.5]

// ----- C -----
*chroma=hypot(255.0*(a-0.5),255.0*(b-0.5));
// chroma in [0.0,181)
*chroma=(*chroma)/255.0+0.5;
// chroma in [0.5,1.21)


// ----- H -----
*hue=180.0*atan2(255.0*(b-0.5),255.0*(a-0.5))/MagickPI;
// atan2() in (-PI,PI], 0.0 when undefined?
// hue in (-180.0,180.0]
*hue=(*hue)/255.0+0.5;
// OOps?

Re: Colorspace round trips

Posted: 2013-06-17T20:52:40-07:00
by GreenKoopa
Thank you for the code magick!
magick wrote:LCHab includes a 0.5 offset to shift -127 to 0 and 0 to 127 to cover the full range [0..255].
Lab's a and b need offset, but LCHab's C is a non-negative number roughly in the range [0,181].

For LCHuv, you will find the same hue problem. C is non-negative.

Why offset hue? The very next line adds 360 degrees to negative angles.

Code: Select all

  *hue=(*hue)/255.0+0.5;
  if (*hue < 0.0)
    *hue+=1.0;

Re: Colorspace round trips

Posted: 2013-06-19T01:05:37-07:00
by GreenKoopa
Moving on to YDbDr. Clipping is occurring on the Db and Dr channels. The clipping is heavy off both ends. I don't know anything about this colorspace but Wikipedia says their range is [-1.333,1.333]. Wikipedia says this is an encoding of RGB, meaning that there is no gamut conversion to cause the clipping. There is likely a bug to be found here too.

Re: Colorspace round trips

Posted: 2013-06-19T05:17:51-07:00
by magick
Why offset hue? The very next line adds 360 degrees to negative angles.
We have a 16-million color image covering every RGB color. A round-trip from LCHab to sRGB returns this distortion:
  • convert reference.tif -colorspace LCHab -colorspace sRGB colorspace.png
    compare -metric rmse colorspace.png 16MillionColors.tif null:
    7538.72 (0.115033)
Now, let's remove the 0.5 offset from hue:
  • compare -metric rmse colorspace.png 16MillionColors.tif null:
    15661.3 (0.238977)
Increased distortion suggests the offset is needed.

The same with YDbDr. If we scale by 1.333, the distortion increases.

We'll review the algorithms in case there are bugs we overlooked.

Re: Colorspace round trips

Posted: 2013-06-19T08:05:00-07:00
by GreenKoopa
magick wrote:Increased distortion suggests the offset is needed.
You are dividing by 255.0 when it should be 360.0 for hue. See my code walk-through above. Offsetting helps because it centers the clipping, but it will be unnecessary when you fix the scaling problem.

Re: Colorspace round trips

Posted: 2013-06-19T08:39:49-07:00
by magick
We can reproduce the problem you posted and have a patch in ImageMagick 6.8.6-1 Beta available by sometime tomorrow. Thanks.