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

Implement ICtCp color space per BT.2100 #69

Closed
dzung-hoang opened this issue Mar 15, 2017 · 57 comments
Closed

Implement ICtCp color space per BT.2100 #69

dzung-hoang opened this issue Mar 15, 2017 · 57 comments

Comments

@dzung-hoang
Copy link
Contributor

BT.2100 defines a new color space ICtCp. I can help add support for this to zimg.

@sekrit-twc
Copy link
Owner

Patches welcome. Is there an enumerated value for this system (matrix_coefficients)?

@dzung-hoang
Copy link
Contributor Author

dzung-hoang commented Mar 16, 2017 via email

@sekrit-twc
Copy link
Owner

I mean for the standard colorspace triplet (matrix-transfer-primaries). Has a code value been specified by ITU/MPEG?

As for the details of ICtCp, I see that the transfer characteristics can be either PG or HLG.

@dzung-hoang
Copy link
Contributor Author

dzung-hoang commented Mar 16, 2017 via email

@sekrit-twc
Copy link
Owner

sekrit-twc commented Mar 16, 2017

If a matrix_coeffs value has been reserved, that is sufficient. The constants described in BT.2100 (or E-62 to E-64) appear to be specific to the Rec.2020 primaries. Is there a generalization?

@dzung-hoang
Copy link
Contributor Author

ICtCp was designed with Rec.2020 colorimetry in mind. It is possible, but unlikely, that ICtCp will be used with another colorimetry.

@sekrit-twc
Copy link
Owner

Thank you for answering. I will review your patch if/when you submit it.

@dzung-hoang
Copy link
Contributor Author

I finished coding and am testing.

@dzung-hoang
Copy link
Contributor Author

I just checked in my code changes. Please review and post comments here.

https://github.com/dzung-hoang/zimg

@sekrit-twc
Copy link
Owner

Can you upload a ICtCp test image with reference RGB image? I have left my remarks on your commit.

@dzung-hoang
Copy link
Contributor Author

I updated the code per your comments.

dzung-hoang@aa29d54

I am working on generating ICtCp test image using HDRConvert used by JCT-VC/JVET.

@dzung-hoang
Copy link
Contributor Author

unit test pushed

dzung-hoang@a4b3406

@sekrit-twc
Copy link
Owner

Once you upload the ICtCp test image, I can begin verification and merging.

PLEASE NOTE: You must agree to either assign copyright of code to the project owner or license contributions under WTFPL (see COPYING).

@dzung-hoang
Copy link
Contributor Author

I agree to WTFPL.

Regarding the ICtCp test image, I have a question on the image file format. I used testapp for my own testing and came across a limitation in the file format. This line of code in colorspaceapp.cpp colorspace_main() is a concern for me.

ImageFrame src_frame = imageframe::read(args.inpath, "i444s", args.width, args.height, zimg::PixelType::FLOAT, !!args.fullrange_in);
When studying the code, it looks to me like this read function overrides the pixel type in the format specification with FLOAT. Why do you not allow other values of PixelType?

@sekrit-twc
Copy link
Owner

sekrit-twc commented Mar 24, 2017

The colorspaceapp is only for testing the colorspace module, which works in float. The depthapp can generate floating-point images for use with colorspaceapp. You can upload the test image in any format you like, e.g. 10/16-bit yuv dump, BMP, etc.

EDIT: Also, I have forgotten some details of this code, but I believe the second argument to imageframe::read is a default. The path specifier (e.g. 'bmp@') has higher precedence, and the FLOAT argument specifiers the output of imageframe::read.

@sekrit-twc
Copy link
Owner

Any updates?

@dzung-hoang
Copy link
Contributor Author

dzung-hoang commented Mar 31, 2017 via email

@dzung-hoang
Copy link
Contributor Author

I finished testing on an SDR image and an HDR image. The files are attached.

test_images.zip

@sekrit-twc
Copy link
Owner

sekrit-twc commented Apr 16, 2017

I tried to verify the code with your EXR images, but when I convert 'cosmos_2048x858_ictcp' to RGB as such:

testapp.exe colorspace -w 2048 -h 858 --visualise x.bmp i444h@cosmos_2048x858_ictcp_rgbph.bin rgbph@test.bin ictcp:st_2084:2020 rgb:st_2084:2020

The output image is all white pixels. I inspected the contents of the ICtCp EXR, and it appears all Y pixel values are near 1.0, which would be unusual for ST.2084. Can you explain what colorspaces the images you provided are in?

EDIT: The conversion also appears to suffer from poor accuracy. When I converted 'color_320x320_bt2020' to ICtCp and back, the recovered image had many artifacts on the right-hand (blue) side.

testapp.exe colorspace -w 320 -h 320 rgbph@color_320x320_bt2020_rgbph.bin i444h@test.bin rgb:st_2084:2020 ictcp:st_2084:2020
testapp.exe colorspace -w 320 -h 320 --visualise x.bmp --fullrange-out i444h@test.bin i444h@recons.bin ictcp:st_2084:2020 rgb:st_2084:2020

@dzung-hoang
Copy link
Contributor Author

BT.2100 assumes that the R/G/B value of 1.0 corresponds to 10000 nits. Also, the floating point representation assumes full-range samples. Therefore you need to specify the following options: "--fullrange-in --fullrange-out --peak-luminance=10000"

The cosmos image is an image taken from Cosmos Laundromat and remastered in HDR by Netflix.

https://gooseberry.blender.org/

The color square is a sRGB image, and you need to set "--peak-luminance=100."

I am attaching the script I used for testing.
test_cosmos.zip

@sekrit-twc
Copy link
Owner

sekrit-twc commented Apr 16, 2017

Thank you for the update. I was able to verify that the conversion from ICtCp to RGB matched the reference image with '--peak-luminance=10000'.

Can you comment on the numerical issues I mentioned in my edit? I reproduced the steps used in the pipeline as a unit test and observed severe error in the green channel for certain test vectors. The attached code can be used to inspect the issue in a debugger.

https://pastebin.com/XAKPnzpb

You can also observe these issues if you attempt to round-trip the color square, storing the intermediate result in half-precision (EXR).

EDIT: Looking at the error growth, it appears that the LMS-RGB (inverse RGB-LMS) matrix is not stable. The relative error increases dramatically from 1E-5 to over 100%.

@dzung-hoang
Copy link
Contributor Author

I am in the process of implementing a DoubleToHalf conversion in Excel VBA to try to find a stable matrix.

@sekrit-twc
Copy link
Owner

I have not done this investigation yet, but it may be necessary to use gaussian solvers (e.g. sgesv) instead of an inverse matrix. From what I can tell, an LU solution requires no more FLOPs (9 muls) than a matrix-vector product in the 3x3 case.

@dzung-hoang
Copy link
Contributor Author

I am looking into the effect of rounding modes when converting from float to half-float. Hopefully this can give a simpler solution.

@sekrit-twc
Copy link
Owner

Do the standards or reference software provide any guidance on decoding ICtCp? On my end, I tried several approaches, but none of them were able to eliminate the problem. In fact, I noticed the issue can also be reproduced with the test vector (R=0, G=1.0, B=0). Even without any quantization of the intermediate result, this vector comes out as (R=0.09, G=1.0, B=0.014).

Part of the issue appears to be the dynamic range of the ST.2084 function, as even 1E-5 absolute error near zero in the linear domain maps to 0.1 in the transformed domain.

@sekrit-twc
Copy link
Owner

sekrit-twc commented Apr 19, 2017

I just realized the aforementioned error would be invisible in PQ domain, since 1E-5 linear is 1 nit and 1.0 linear is 10000 nits. Perhaps there is no issue.

@dzung-hoang
Copy link
Contributor Author

I cannot reproduce your result for RGB=[0,1,0]. See the attached spreadsheet. One round trip with 10-bit quantization of ICtCp gives me the reconstructed RGB=[-0.0039, 0.997691, -5.93973E-05].

ICtCp_Worksheet.zip

@sekrit-twc
Copy link
Owner

sekrit-twc commented Apr 20, 2017

The discrepancy appears to be that you are using linear RGB values, but I am using R'G'B'. Additionally, for the 10-bit quantization function, I get a positive R error of the same magnitude (0.0039 -> 39 nits) that you reported. Perhaps this is not such a big deal, compared to the 9977 nits in G, but it appears troubling to me that pure colors can not be round-tripped.

@dzung-hoang
Copy link
Contributor Author

I updated the spreadsheet to fix the quantization function to reflect A.2 in SMPTE-2084-2014, and also to allow selecting the bit-depth.

ICtCp_Matrix.zip

Not surprisingly, the accuracy depends upon the selected bit-depth. Bit-depth of 11 seems to be the sweet spot for preserving the primary colors after clipping.

For each bit-depth, we can tweak the LMS-to-RGB matrix to get exact round-trip of the primary colors. I'll attempt that next.

@dzung-hoang
Copy link
Contributor Author

It was easier than I thought to derive a LMS-to-RGB matrix to get exact round-trip of the primary colors. We basically just compute round-trip LMS of the identity RGB matrix, and then compute the matrix inverse of the round-trip LMS. See the attached spreadsheet.

ICtCp_Matrix_v2.zip

@sekrit-twc
Copy link
Owner

sekrit-twc commented Apr 20, 2017

That method only accounts for errors caused by quantization, but in a real workflow, numerical errors can come from many sources, particularly data compression. Consider this scenario:

  1. Movie in R'G'B' received from distributor or studio.
  2. Movie is converted to HDR10+/ICtCp and compressed with HEVC Main 10
  3. Movie is converted back to R'G'B' at display interface.

The general principle that should hold is that errors in the transformed space should result in proportional errors in RGB.

@dzung-hoang
Copy link
Contributor Author

I would disagree with the proportional RGB error metric. The right metric should be perceptual-based. The attached document set has a good treatment of compression errors of IPT-PQ (ICtCp with SMPTE-2084 PQ) compared to Y'CbCr.

m37266-v2-m37266_IPTPQ_v2.zip

@sekrit-twc
Copy link
Owner

I agree that dE2000 is a better measure than RGB per-channel error. Can you provide a reference for computing dE2000 of HDR values? There are only 2^30 code points in 10-bit PQ space, so it should be possible to exhaustively test the conversion error.

@dzung-hoang
Copy link
Contributor Author

This has a DeltaE computation module.

https://gitlab.com/standards/HDRTools

Look for DistortionMetricDeltaE.cpp.

@sekrit-twc
Copy link
Owner

I noticed that I was using the float-int cast in my quantization function, which was increasing the error considerably. After I applied correct rounding, I am seeing dE2000 of ~0.05 for (R=0,G=1,B=0) with 10-bit quantization and ~0.1 for (R=0,G=1,B=1). (R=0,G=1,B=1) appears to be the worst-case input. The simple YCbCr system has dE2000 of ~0.03 and ~0.01, respectively. The error appears to be stable and does not increase significantly with additional round-trips. Is this a reasonable result?

@dzung-hoang
Copy link
Contributor Author

That seems even better than published results.

@sekrit-twc
Copy link
Owner

I will need to investigate again to ensure I am measuring dE correctly.

@sekrit-twc
Copy link
Owner

I did an exhaustive analysis and found that the overall dE was not worse than NCL for ICtCp as implemented in the commit.

@dzung-hoang
Copy link
Contributor Author

Since one of the aims of ICtCp is to improve color gamut mapping, I suggest you allow ICtCp to be used with any gamut and not just BT.2020. I will use zimg for color gamut mapping experiments and I'm sure others would too if this restriction were removed.

@sekrit-twc
Copy link
Owner

That would require a different RGB-LMS matrix, would it not? I noticed the document in m37266-v2-m37266_IPTPQ_v2.zip proposed for LMS to be a value of color_primaries, but that appears to not have been accepted by JCT-VC.

@dzung-hoang
Copy link
Contributor Author

Now that I think about it, generating ICtCp output with a different input color primaries is already supported by zimg. The input color primaries are first converted to BT.2020 and then ICtCp conversion is performed. The challenge for color gamut mapping is to figure out how to manipulate the ICtCp color space to reduce or expand the color volume. Then the inverse conversion is performed from ICtCp to linear BT.2020 RGB and then to the target color primaries.

@sekrit-twc
Copy link
Owner

@ValZapod I do not understand. Is there some action required from z.lib here?

@ghost
Copy link

ghost commented Nov 10, 2019

That's a problem with ffmpeg or ffmpeg's zimg wrapper (I assume you mean using zimg through ffmpeg's zscale filter).

I tried that sample, and ffmpeg returns "unspecified" for all relevant colorspace parameters. Is that file even valid?

@dzung-hoang
Copy link
Contributor Author

@ValZapod
Dolby Vision uses the IPT colorspace, which is different from ICtCp. See Here and Here. IMHO, supporting IPT should be a feature request. However, given that IPT is Dolby-proprietary and is likely to be covered by patents, this decision should be made after careful deliberation.

@sekrit-twc
Copy link
Owner

Hello again, @dzung-hoang . Is there any information about the IPT colorspace? The linked document only states that "proprietary IPT [is] similar to BT.2100 ICtCp."

@dzung-hoang
Copy link
Contributor Author

@sekrit-twc, there is some technical detail in the linked Dolby white paper.

@sekrit-twc
Copy link
Owner

sekrit-twc commented Nov 10, 2019

I found the information on page 7.

[I P T] = [[0.4 0.2 0.2] [4.4550 -4.8510 0.3960] [0.8056 0.3572 -1.1628]] * [L M S]
Rotation = [[1 0 0] [0 0.4226 -0.9063] [0 0.9063 0.4226]]
Scalar = [[1 1 1] [1.4 1.4 1.4] [1 1 1]]

Is the takeaway that IPT differs from ICtCp in the matrix coefficients step, using the product of the aforementioned three matrices instead of the LMS-ICtCp matrix?

@dzung-hoang
Copy link
Contributor Author

dzung-hoang commented Nov 10, 2019 via email

@dzung-hoang
Copy link
Contributor Author

I found the information on page 7.

[I P T] = [[0.4 0.2 0.2] [4.4550 -4.8510 0.3960] [0.8056 0.3572 -1.1628]] * [L M S]
Rotation = [[1 0 0] [0 0.4226 -0.9063] [0 0.9063 0.4226]]
Scalar = [[1 1 1] [1.4 1.4 1.4] [1 1 1]]

Is the takeaway that IPT differs from ICtCp in the matrix coefficients step, using the product of the aforementioned three matrices instead of the LMS-ICtCp matrix?

There is a typo in your L'M'S' to IPT conversion matrix. As I already replied, Rotation and Scaler conversions are there to convert IPT to ICtCp.

The following link confirms the above conversion matrix. https://gitlab.com/standards/HDRTools/blob/master/common/src/ToneMappingBT2390IPT.cpp

This should be sufficient information to add support for IPT.

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

3 participants
@sekrit-twc @dzung-hoang and others