Log in

View Full Version : Transforms Pack v1.0 RC18


Dogway
8th May 2021, 11:55
Transforms Pack


GitHub: TransformsPack.v1.0.RC18.avsi (https://github.com/Dogway/Avisynth-Scripts/blob/master/TransformsPack.v1.0.RC18.avsi)

As a color geek myself I waited and waited through the years for AviSynth to catch up, but dissapointed to see almost no progress on the area aside from the old cretindesalpes' Dither tools I decided to create and port part of my work from last year.
The Transform Pack is a set of functions for color managing AviSynth+. From function transforms like proper and performant piecewise gamma functions (EOTF, OETF) to color gamut conversion and white point mapping with kelvin temperature inputs (this last in Grade pack).
While on it I also created YUV<->RGB conversion functions to expand on its possibilities like new matrix coefficients or chroma resamplers, and an array of building block Matrix operators.


So what differentiates this from the Dither tools or other solutions like avsresize?
To start with it has no dependencies, everything is done with internal expressions so it's faster than say the Dither tools along supporting HBD out of the box.
Compared to avsresize, it's 38% the speed of it for this first version, but in opposition it's easier to use, more straight forward, the code is simple to understand and fix, includes more color spaces and finally more accurate conversions (derivations and higher float precision constants).

v1.0 focuses on SDR color spaces, v2.0 will target HDR and tonemappers.



Description of the main functions:

ColorSpace(): Full featured color space conversion filter. Includes novel spaces like DCI-P3 (Theater), ACES2065 or ACEScg aside from AdobeRGB. Automatically does gamut and gamma conversions and matrix strings use a lazy mode input so you don't have to remember exactly the string name to use.

Display_Referred(): Very similar to ColorSpace() except it's designed to convert the video working space into your display space, with an option to input a .cube 3D LUT from your calibration software. Also includes arguments to control the up format conversion kernel along the b, c, and p coefficients for the upsampler.

ConvertFormat(): If you don't pretend to change colorimetry and only want to convert formats like RGB<->YUV or YUV <->YUV this function is best suited. It supports a fast version of feisty2's chroma reconstruction filter and allows to set resampling kernel with aliases; catrom, mitchell, hermite, etc. It's planned to replace LinearResize(), so upscaling/downscaling in HBD and linear light (or other spaces) can be achieved while reconstructing chroma at the same time optionally.

moncurve_f/moncurve_r: EOTF, OETF. Optimized piecewise gamma transfer functions from AMPAS that only require the alpha coefficient, beta and phi are properly derived in float precision.

Also includes YUV_to_RGB(), RGB_to_YUV(), YcCbcCrc_to_RGB(), RGB_to_YcCbcCrc(), RGB_to_XYZ(), XYZ_to_RGB(), matrix operators (invert, transpose, divide, multiply, dot, cross...) and some more helper functions.



Speed comparison on a i7-4790K non-OC (only gamut conversion) with Prefetch(6):

The main performance limiter in ColorSpace() is the use of 7 lutxyz and 2 lutxy expressions. Any improvement in speed in said AviSynth+ internal expression function or even its support for GPU processing will translate in a massive increase in speed.

Dither tools (but no color space/primaries conversion)
28fps
Dither_convert_8_to_16()
Dither_convert_yuv_to_rgb (matrix="601", output="rgb48y", lsb_in=true, tv_range=true)
r = SelectEvery (3, 0)
g = SelectEvery (3, 1)
b = SelectEvery (3, 2)
Dither_convert_rgb_to_yuv (r, g, b, matrix="709", tv_range=true, lsb=true, mode=6, output="YV12")


HDRTools (scarce in options, not user friendly)
52fps
ConvertBits(16)
ConvertYUVtoXYZ(Color=3)
ConvertXYZtoYUV(Color=2,pColor=2) # Color=3 was noop, strange logic

TransformsPack
62fps (Prefetch 8) (33fps in 32-bit)
ConvertBits(16)
ConvertFormat(1,1,"420","601","709",true,true,true,noring=false, scale_space="gamma",kernel="mitchell")

FrancesBB LUTs
82fps (scarce in options)
ConvertBits(16)
ConvertToPlanarRGB(matrix="Rec601",chromaresample="bicubic")
Cube("C:\Program Files (x86)\AviSynth+\LUTs\BT601_NTSC_to_BT709.cube")
ConvertToYUV420(matrix="Rec709",chromaresample="bicubic")
# LinearTransformation("Linear_BT601_NTSC", "Linear_BT709") # AIO pack for above, but misses the format conversion matrices.


avsresize (not 32-bit or RGB friendly)
160fps (version r7 not working with AVSmeter)
ConvertBits(16)
z_ConvertFormat(pixel_type="YUV420P16",colorspace_op="601:601:170M:l=>709:601:709:l", dither_type="none")

HDR Core
244fps (also scarce in options)
ConvertBits(16)
ConvertToDoubleWidth()
Matrix(from=601, to=709, rg=1.0, gg=1.0, bg=1.0, a=16, b=235, ao=16, bo=235, bitdepth=16)
ConvertFromDoubleWidth()


YUV 420 to RGB conversion benchmark:
ConvertBits(16)
300 fps z_ConvertFormat(pixel_type="RGBP16",colorspace_op="709:auto:auto:l=>rgb:same:same:f",resample_filter ="bicubic",filter_param_a=0.333,filter_param_b=0.333)
188 fps ConvertToPlanarRGB(matrix="Rec709")
164 fps YUV_to_RGB("709",kernel="mitchell")
135 fps ConvertYUVtoLinearRGB(Color=2) # no gamma reencode output


TODO


Fix/assess chroma placement
v2.0. Update AP1 and AP0 transformation matrices and include the RRT
v2.0. Add PQ and HLG functions and tonemappers (2446, Log flavours, Hable, Hejl, etc)

kedautinh12
8th May 2021, 14:39
Thanks

FranceBB
8th May 2021, 15:37
Looks very promising!
Well done! ;)

StvG
9th May 2021, 08:47
Quick test with two of the examples from op:
ColorBars(pixel_type="yuv420p16")
z_ConvertFormat(pixel_type="YUV420P16",colorspace_op="601:601:709:l=>709:709:709:l",resample_filter="spline36", dither_type="none")
Subtitle("""z_ConvertFormat(pixel_type="YUV420P16",resample_filter="spline36",""" + """\ncolorspace_op="601:601:709:l=>709:709:709:l", dither_type="none")""", lsp=1)
ColorBars(pixel_type="yuv420p16")
Import("TransformsPack.v1.0.RC1.avsi")
ColorSpace("601","709","spline36","spline36",true,false)
Subtitle(""" ColorSpace("601","709","spline36","spline36",true,false) """)
https://imgbox.com/g/eduZVZW9ZC

Dogway
9th May 2021, 10:45
Thanks for the comparison, it's easier to see the accuracy difference between ColorSpace and avsresize.
zlib is known to employ "shortcuts" for performance reasons (link (https://github.com/sekrit-twc/zimg#ColorSpace)).

As you can see from the code I use higher precision floats, be it for gamut transformation matrices, YUV<->RGB coefficients or D65 illuminant (0.312713, 0.329016) in my case.

Currently I'm on the development of the fast-mode, looking forward to reach at least 100fps for gamut conversion. As for the gamma conversion process currently ColorSpace() is 60% faster than avsresize

Dogway
9th May 2021, 15:38
Updated to RC2, small fix.
I tested running it through only 3 lutxyz expressions and curiously it is less than half the speed. Not sure why. Should optimize the math instead.

function ColorSpace_fast (clip clp, string "source", string "target", string "kernel_up", string "kernel_down", bool "gamut", bool "gamma" \
, string "cplace", bool "tv_range_in", bool "tv_range_out", float "b_up", float "c_up", float "p_up", float "b_down", float "c_down", float "p_down") {

gamut = Default (gamut, true) # convert gamut
gamma = Default (gamma, true) # convert gamma
tv_range_in = Default (tv_range_in, !isRGB(clp) )
tv_range_out= Default (tv_range_out, tv_range_in )
kernelu = Default (kernel_up, "bicubic")
kerneld = Default (kernel_down, kernelu)
cplace = Default (cplace, "MPEG2")
bu = Default (b_up, 0.00)
cu = Default (c_up, 0.75) # Precise Bicubic
pu = Default (p_up, 0.25)
bd = Default (b_down, -0.5)
cd = Default (c_down, 0.25) # Didée's Bicubic
pd = Default (p_down, 0.25)

source = Matrix_fuzzy_search (source)
target = Matrix_fuzzy_search (target)

s_num =
\ (source == "sRGB" ) ? 0
\ : (source == "Rec601" ) ? 1
\ : (source == "240M" ) ? 2
\ : (source == "470BG" ) ? 3
\ : (source == "Rec2020" ) ? 4
\ : (source == "Rec709" ) ? 5
\ : (source == "1886a" ) ? 6
\ : (source == "AdobeRGB" ) ? 7
\ : (source == "Display-P3") ? 0
\ : (source == "DCI-P3" ) ? 8
\ : (source == "ACEScg" ) ? 9
\ : (source == "ACES2065" ) ? 9
\ : Assert (false, "Unsupported Color Space.")

t_num =
\ (target == "sRGB" ) ? 0
\ : (target == "Rec601" ) ? 1
\ : (target == "240M" ) ? 2
\ : (target == "470BG" ) ? 3
\ : (target == "Rec2020" ) ? 4
\ : (target == "Rec709" ) ? 5
\ : (target == "1886a" ) ? 6
\ : (target == "AdobeRGB" ) ? 7
\ : (target == "Display-P3") ? 0
\ : (target == "DCI-P3" ) ? 8
\ : (target == "ACEScg" ) ? 9
\ : (target == "ACES2065" ) ? 9
\ : Assert (false, "Unsupported Color Space.")

bdpth = BitsPerComponent(clp) > 10 ? 0.09929682680944 : 0.099

# SMPTE-C Y’PbPr 601 PAL/SECAM BT-2020 Rec.709 BT-1886a AdobeRGB DCI-P3 AP1/AP0
# sRGB SMPTE 170M SMPTE 240M BT-470BG BT-2020 BT-1886 BT-1886a AdobeRGB DCI-P3 ACES
t_temp = Select (t_num, 6504 , 6504 , 6504 , 6504 , 6504, 6504 , 6504 , 6504 , 6305 , 6000 )
s_off = Select (s_num, 0.055 , 0.099 , 0.1115 , 0.099 , bdpth, 0 , 0 , 0 , 0 , 0 )
s_gamma = Select (s_num, 2.40 , 2.22222, 2.22222, 2.80 , 2.22222, 2.40 , 2.60 , 2.1992 , 2.60 , 1.0 )

t_off = Select (t_num, 0.055 , 0.099 , 0.1115 , 0.099 , bdpth, 0 , 0 , 0 , 0 , 0 )
t_gamma = Select (t_num, 2.40 , 2.22222, 2.22222, 2.80 , 2.22222, 2.40 , 2.60 , 2.1992 , 2.60 , 1.0 )


clp

# YUV to RGB
RGBc = YCbCrToRGB_mat(source)
RGB = ["x ymin - "+string(RGBc[0])+" * y range_half - "+string(RGBc[1])+" * + z range_half - "+string(RGBc[2])+" * +", \
"x ymin - "+string(RGBc[3])+" * y range_half - "+string(RGBc[4])+" * + z range_half - "+string(RGBc[5])+" * +", \
"x ymin - "+string(RGBc[6])+" * y range_half - "+string(RGBc[7])+" * + z range_half - "+string(RGBc[8])+" * +"]


# Linearization
fs = "{s_gamma} 1.0 - {s_off} / {s_off} {s_gamma} * {s_gamma} 1.0 - 1.0 {s_off} + * / {s_gamma} ^ *"
xb = "{s_off} {s_gamma} 1.0 - /"
outR = s_off > 0.0 ? Format(""+RGB[0] + " range_max / A@ "+xb+" > A {s_off} + 1.0 {s_off} + / {s_gamma} ^ range_max * A "+fs+" * range_max * ? ") : \
Format(""+RGB[0] + " range_max / {s_gamma} ^ range_max * ")
outG = s_off > 0.0 ? Format(""+RGB[1] + " range_max / A@ "+xb+" > A {s_off} + 1.0 {s_off} + / {s_gamma} ^ range_max * A "+fs+" * range_max * ? ") : \
Format(""+RGB[1] + " range_max / {s_gamma} ^ range_max * ")
outB = s_off > 0.0 ? Format(""+RGB[2] + " range_max / A@ "+xb+" > A {s_off} + 1.0 {s_off} + / {s_gamma} ^ range_max * A "+fs+" * range_max * ? ") : \
Format(""+RGB[2] + " range_max / {s_gamma} ^ range_max * ")

# Gamut conversion
mata = RGB_to_XYZ(source, list=true)
matb = XYZ_to_RGB(target, list=true)
mat = MatrixDot(mata, matb)

toMATR = outR + string(mat[0])+" * " + outG + string(mat[3])+" * + " + outB + string(mat[6])+" * +"
toMATG = outR + string(mat[1])+" * " + outG + string(mat[4])+" * + " + outB + string(mat[7])+" * +"
toMATB = outR + string(mat[2])+" * " + outG + string(mat[5])+" * + " + outB + string(mat[8])+" * +"

# Gamma conversion
yb = "{t_off} {t_gamma} * {t_gamma} 1.0 - 1.0 {t_off} + * / {t_gamma} ^"
rs = "{t_gamma} 1.0 - {t_off} / {t_gamma} 1.0 - ^ 1.0 {t_off} + {t_gamma} / {t_gamma} ^ *"
outR = t_off > 0.0 ? Format(""+toMATR+" range_max / A@ "+yb+" > 1.0 {t_off} + A 1.0 {t_gamma} / ^ * {t_off} - range_max * A "+rs+" * range_max * ? ") : \
Format(""+toMATR+" range_max / 1.0 {t_gamma} / ^ range_max * ")
outG = t_off > 0.0 ? Format(""+toMATG+" range_max / A@ "+yb+" > 1.0 {t_off} + A 1.0 {t_gamma} / ^ * {t_off} - range_max * A "+rs+" * range_max * ? ") : \
Format(""+toMATG+" range_max / 1.0 {t_gamma} / ^ range_max * ")
outB = t_off > 0.0 ? Format(""+toMATB+" range_max / A@ "+yb+" > 1.0 {t_off} + A 1.0 {t_gamma} / ^ * {t_off} - range_max * A "+rs+" * range_max * ? ") : \
Format(""+toMATB+" range_max / 1.0 {t_gamma} / ^ range_max * ")
# RGB to YUV
YUVc = RGBToYCbCr_mat(target)
YUV = [" ymin " + outR + string(YUVc[0])+" * + " + outG + string(YUVc[1])+" * + " + outB + string(YUVc[2])+" * + ", \
"range_half " + outR + string(YUVc[3])+" * + " + outG + string(YUVc[4])+" * + " + outB + string(YUVc[5])+" * + ", \
"range_half " + outR + string(YUVc[6])+" * + " + outG + string(YUVc[7])+" * + " + outB + string(YUVc[8])+" * + "]


w = width(clp)
h = height(clp)
cplace = is444(clp) ? ",src_left=0.0" : cplace=="MPEG2" ? ",src_left=0.25" : ",src_left=0.0"
resampler = kernelu == "nnedi3" ? "nnedi3_resize16(" + String(w) + "," + String(h) + cplace +")" : \
kernelu == "bicubic"? "BicubicResize(" + String(w) + "," + String(h) + cplace +",b=bu,c=cu)" : \
kernelu == "gauss" ? "GaussResize(" + String(w) + "," + String(h) + cplace +",p=pu)" : \
kernelu + "Resize(" + String(w) + "," + String(h) + cplace +")"

Y = ExtractY(clp)
U = Eval("ExtractU(clp)." + resampler)
V = Eval("ExtractV(clp)." + resampler)


Yo = Expr(Y, U, V, YUV[0])
Cbo = Expr(Y, U, V, YUV[1])
Cro = Expr(Y, U, V, YUV[2])

p_type = PixelType(clp)

p_type4 = FindStr(p_type, "44")>0
p_type2 = FindStr(p_type, "22")>0

nw = round(w/2.0)
nh = round(h/2.0)
cplace = p_type4 || cplace=="MPEG2" ? ",src_left=-0.50" : ",src_left=0.0"
resampler = kerneld == "nnedi3" ? "nnedi3_resize16(" + String(nw+nw%2) + "," + String(p_type2 ? h : nh+nh%2) + cplace + ")" : \
kerneld == "bicubic"? "BicubicResize(" + String(nw+nw%2) + "," + String(p_type2 ? h : nh+nh%2) + cplace + ",b=bd,c=cd)" : \
kerneld == "gauss" ? "GaussResize(" + String(nw+nw%2) + "," + String(p_type2 ? h : nh+nh%2) + cplace + ",p=pd)" : \
kerneld + "Resize(" + String(nw+nw%2) + "," + String(p_type2 ? h : nh+nh%2) + cplace + ")"

Cbo = p_type4 ? Cbo : Eval("Cbo." + resampler)
Cro = p_type4 ? Cro : Eval("Cro." + resampler)

CombinePlanes(Yo, Cbo, Cro, planes="YUV", pixel_type=p_type) }

StainlessS
9th May 2021, 15:59
Doggy, clarify please, what does one do with the addition "ColorSpace_fast", add it to the existing RC2 script ?

EDIT: Thanx Dog :)

StvG
9th May 2021, 17:08
Thanks for the comparison, it's easier to see the accuracy difference between ColorSpace and avsresize.
zlib is known to employ "shortcuts" for performance reasons (link (https://github.com/sekrit-twc/zimg#ColorSpace)).

As you can see from the code I use higher precision floats, be it for gamut transformation matrices, YUV<->RGB coefficients or D65 illuminant (0.312713, 0.329016) in my case.

Currently I'm on the development of the fast-mode, looking forward to reach at least 100fps for gamut conversion. As for the gamma conversion process currently ColorSpace() is 60% faster than avsresize

zlib is performing any colorspace_op=... in float.

ColorBars(pixel_type="yuv420p16")
z_ConvertFormat(pixel_type="YUV420P16",colorspace_op="601:601:709:l=>709:709:709:l",resample_filter="spline36", dither_type="none")

ColorBars(pixel_type="yuv420p16")
ConvertBits(32, fulls=false, fulld=true)
z_ConvertFormat(pixel_type="YUV420Ps",colorspace_op="601:601:709=>709:709:709",resample_filter="spline36", dither_type="none")
z_ConvertFormat(pixel_type="yuv420p16")

Both codes give identical result.


The only shortcut zlib has (performance reasons) is approximate_gamma for gamma<->linear conversion.

Dogway
9th May 2021, 17:28
@StainlessS: No, leave it alone, it was a proof of concept to optimize for speed, happens to be half the speed :S

@StvG: Kb and Kg might be floats but if you reference them from the standard values in ITU they are truncated to 3 or 4 digits. I derive them from the primaries with no limit in float precision, same for the gamut transformation matrices. Although if I'm not wrong avs+ will truncate them to single floats (?).

I will try to mix the YUV<->RGB conversion matrices with the gamut one to try to optimize it although I'm not optimistic since the format conversion should happen in gamma encoded sources. Also I will try to show some comparisons on color ramps since color bars are obviously designed with the standard in mind.

poisondeathray
9th May 2021, 17:58
The updated filename is "TransformsPack.v1.0.RC1.avsi" , even though the text shows "Transforms Pack v1.0 RC2 (09-05-2021) ", consider updating the filename when you have time


ColorBars(pixel_type="YV24")
YCbCrToRGB("601")



Script error: There is no function named 'Resize'
TransformsPack.., line 241

Dogway
9th May 2021, 18:22
This is the way:
YCbCrToRGB(YCbCrToRGB_mat("601"))

Didn't update the name since I wanted to keep the file history in GitHub for these mini changes, as I'm currently on debug mode. Things are prone to change so if people find your call easier I might change it no problem.

StvG
9th May 2021, 18:22
@StvG: Kb and Kg might be floats but if you reference them from the standard values in ITU they are truncated to 3 or 4 digits. I derive them from the primaries with no limit in float precision, same for the gamut transformation matrices. Although if I'm not wrong avs+ will truncate them to single floats (?).

Quick test changing kb (https://github.com/Dogway/Avisynth-Scripts/blob/master/TransformsPack.v1.0.RC1.avsi#L392) and kr (https://github.com/Dogway/Avisynth-Scripts/blob/master/TransformsPack.v1.0.RC1.avsi#L389) to 0.0722 and 0.2126.

https://imgbox.com/g/MClMislf6m

Results aren't identical but there is no visible difference.

Dogway
9th May 2021, 18:44
Quick test changing kb (https://github.com/Dogway/Avisynth-Scripts/blob/master/TransformsPack.v1.0.RC1.avsi#L392) and kr (https://github.com/Dogway/Avisynth-Scripts/blob/master/TransformsPack.v1.0.RC1.avsi#L389) to 0.0722 and 0.2126.

https://imgbox.com/g/MClMislf6m

Results aren't identical but there is no visible difference.

I get kr=0.212649 and kb=0.072182 for Rec709. Also this is the transformation matrix from Rec601 to Rec709, unless I got the SMPTE-C primaries wrong:

0.9395467042922974, 0.017773162573575974, -0.0016219262033700943
0.05017891526222229, 0.9657943844795227, -0.004370423033833504
0.010274499654769897, 0.01643248274922371, 1.0059921741485596


SMPTE-C Primaries:

[0.630, 0.340]
[0.310, 0.595]
[0.155, 0.070]


EDIT: By the way, plot twist. Rec601 doesn't use SMPTE-C based kb and kr coefficients, they kept those from SMPTE 470M, the old 1953 NTSC from the FCC. Those are kr=0.298967, kg=0.586421 and kb=0.114612. Updated the code according.

StvG
13th May 2021, 17:52
Shouldn't ColorSpace("601","709","spline36","spline36",true,false) be the same as YCbCrToRGB(YCbCrToRGB_mat("601")).RGBToYCbCr(RGBToYCbCr_mat("709")) ?

ColorBars(pixel_type="yv12")
z_ConvertFormat(colorspace_op="601:601:709:l=>709:709:709:l",resample_filter="spline36", dither_type="none")
Subtitle("""z_ConvertFormat(colorspace_op="601:601:709:l=>709:709:709:l",\n resample_filter="spline36", dither_type="none")""", lsp=1)

ColorBars(pixel_type="yv12")
Import("TransformsPack.v1.0.RC1.avsi")
ColorSpace("601","709","spline36","spline36",true,false)
Subtitle("""ColorSpace("601","709","spline36","spline36",true,false) """, lsp=1)

ColorBars(pixel_type="yv12")
Import("TransformsPack.v1.0.RC1.avsi")
YCbCrToRGB(YCbCrToRGB_mat("601")).RGBToYCbCr(RGBToYCbCr_mat("709"))
Subtitle(""" YCbCrToRGB(YCbCrToRGB_mat("601")) \n RGBToYCbCr(RGBToYCbCr_mat("709")) """, lsp=1)

https://imgbox.com/g/1QhTQhu3Gt

Dogway
13th May 2021, 19:55
I don't think so. RGB isn't space agnostic. When you convert from Rec601 YUV to RGB format you do it with Rec601 coefficients and end with RGB in Rec601 color space.
Assuming the RGB is in Rec709 as in RGBToYCbCr(RGBToYCbCr_mat("709")) is therefore wrong without a prior gamut conversion between 601 and 709.

In any case in RC3 I had some mistakes with the format conversion assuming PC levels where it shouldn't, so for the time being just avoid using Transforms Pack in production. I fixed it already internally in RC4 but it's not ready for release (tangled in modern HDR compliant formats like ICtCp and IPT-PQ).

StvG
13th May 2021, 22:00
I don't think so. RGB isn't space agnostic. When you convert from Rec601 YUV to RGB format you do it with Rec601 coefficients and end with RGB in Rec601 color space.
Assuming the RGB is in Rec709 as in RGBToYCbCr(RGBToYCbCr_mat("709")) is therefore wrong without a prior gamut conversion between 601 and 709.

Actually that way you're converting only matrix from 601(470bg) to 709 keeping transfer and primaries the same.
Here:
ColorBars(pixel_type="yuv444p16")
z_ConvertFormat(colorspace_op="601:709:709:l=>709:709:709:l",resample_filter="spline36", dither_type="none")
Subtitle(""" z_ConvertFormat(colorspace_op="601:709:709:l=>709:709:709:l",\nresample_filter="spline36", dither_type="none") """, lsp=1)

ColorBars(pixel_type="yuv444p16")
ConvertToRGB()
ConvertToYUV444(matrix="Rec709")
Subtitle(""" ConvertToRGB()\nConvertToYUV444(matrix="Rec709") """, lsp=1)

ColorBars(pixel_type="yuv444p16")
ConvertYUVToLinearRGB(Color=3, outputmode=2)
ConvertLinearRGBToYUV()
Subtitle(""" ConvertYUVToLinearRGB(Color=3, outputmode=2)\nConvertLinearRGBToYUV() """, lsp=1)

ColorBars(pixel_type="yuv444p16")
Import("TransformsPack.v1.0.RC1.avsi")
YCbCrToRGB(YCbCrToRGB_mat("601")).RGBToYCbCr(RGBToYCbCr_mat("709"))
Subtitle(""" YCbCrToRGB(YCbCrToRGB_mat("601")) \n RGBToYCbCr(RGBToYCbCr_mat("709")) """, lsp=1)

ColorBars(pixel_type="yuv444p16")
ConvertToStacked()
Dither_convert_yuv_to_rgb(matrix="601")
Dither_convert_rgb_to_yuv(matrix="709", output="YV24",lsb=true)
DitherPost(mode=-1)
ConvertFromStacked()
Subtitle("""Dither_convert_yuv_to_rgb(matrix="601")\nDither_convert_rgb_to_yuv(matrix="709", output="YV24",lsb=true)\nDitherPost(mode=-1) """, lsp=1)

Results: https://imgbox.com/g/nyDydHExob (z.lib, dither and internal functions have visible identical results)

So from the first post:
ConvertBits(16)
z_ConvertFormat(pixel_type="YUV420P16",colorspace_op="601:601:709:l=>709:709:709:l", dither_type="none")

Above is not equivalent of:

ConvertBits(16)
ColorSpace("601","709","bicubic","bicubic",true,false)

The last code doesn't do only matrix conversion but primaries and transfer too:
ColorBars(pixel_type="yuv444p16")
z_ConvertFormat(colorspace_op="601:470bg:470bg:l=>709:709:709:l",resample_filter="spline36", dither_type="none")
Subtitle(""" z_ConvertFormat(colorspace_op="601:470bg:470bg:l=>709:709:709:l",\nresample_filter="spline36", dither_type="none") """, lsp=1)

ColorBars(pixel_type="yuv444p16")
ConvertYUVtoXYZ(Color=3, outputmode=2).ConvertXYZtoYUV(Color=2, pColor=3)
Subtitle("""ConvertYUVtoXYZ(Color=3, outputmode=2).ConvertXYZtoYUV(Color=2, pColor=3) """, lsp=1)

ColorBars(pixel_type="yuv444p16")
Import("TransformsPack.v1.0.RC1.avsi")
ColorSpace("601","709","spline36","spline36",true,false)
Subtitle("""ColorSpace("601","709","spline36","spline36",true,false) """, lsp=1)

Results: https://imgbox.com/g/sTti4whkcx

Dogway
14th May 2021, 15:16
StvG:
Sorry if I can't follow you much. Feedback is nice so I appreciate your posts. The reason I didn't use the transfer curve in OP for comparison was probably because it wasn't a fair comparison, I'm using a power law gamma of 2.40 for Rec709 while I think other plugins use sRGB tranfer function, or simply 2.222.

z.lib, dither and internal functions have visible identical results
My eyes might deceive me but my solution also looks identical.

I'm not looking into other code much except for certain specific things to shorten times. I'm doing this as I understand it should be done because I wasn't satisfied with what was already available, either useability, options or accuracy.
470bg is PAL Rec601, 170M is NTSC Rec601, but as I noted above the matrix coefficients used for NTSC Rec601 are not the ones derived from 170M but from the old FCC 1953 standard.

I don't have much time today but I will correct the OP example tomorrow and run some tests. For the time being I uploaded RC4, or what I had at the moment so you can test with correct range conversions (hopefully).

StvG
14th May 2021, 18:54
z.lib is using the transfer by Rec.1886 when transfer 601/709/2020(_10/_12) is used.
HDRTools is using the same transfer by Rec.1886 when Color=2 (in XYZ) is used.

Edit:
z.lib, dither and internal functions have visible identical results

My eyes might deceive me but my solution also looks identical.

Right, your solution also looks identical (colors, brightness) but there is some shift.

Dogway
15th May 2021, 14:23
I checked the source code of zimg and Rec.1886 gamma function is listed but I couldn't see it as an option in either documentation:
http://avisynth.nl/index.php/Avsresize
http://www.vapoursynth.com/doc/functions/resize.html

As far as above documentation goes Rec.709 uses Rec.709 gamma, which is identical to Rec.2020 gamma for <12-bits (R-REC-BT.2020-2-201510). This is called Camera Gamma and it's not supposed to be used in authored content in either bitdepth. Rec.1886 gamma (in practice a pure power 2.4 gamma) is recommended as display gamma for Rec.709 (read EBU Tech 3320 v4.1 - 1.5.4). If what you mean is ITU-R BT.2390-2, that's for HDR not HBD.


constexpr float REC709_ALPHA = 1.09929682680944f;
constexpr float REC709_BETA = 0.018053968510807f;

float rec_709_oetf(float x) noexcept
{
if (x < REC709_BETA)
x = x * 4.5f;
else
x = REC709_ALPHA * zimg_x_powf(x, 0.45f) - (REC709_ALPHA - 1.0f);

return x;
}


float rec_1886_eotf(float x) noexcept
{
return x < 0.0f ? 0.0f : zimg_x_powf(x, 2.4f);
}




About the shift, do you mean pixel shift? It's subsampling artifacts of upsampling and downsampling. Since ColorSpace() ( and WhitePoint() ) is doing a roundtrip in RC4 I hard coded it to PointResize() with a known fix for PointResize() pixel shift of -1.0. Hope it's fine now.


Finally avsresize has an issue where I'm not able to use "fcc" for Matrix coefficients for Rec.601 primaries, as it should. Both PAL and NTSC in YCbCr use the coefficients derived from the FCC primaries. In avsresize these coefficients are not computed but constants with a precision of 3 decimal floats only.

StvG
15th May 2021, 20:11
I checked the source code of zimg and Rec.1886 gamma function is listed but I couldn't see it as an option in either documentation:
http://avisynth.nl/index.php/Avsresize
http://www.vapoursynth.com/doc/functions/resize.html

As far as above documentation goes Rec.709 uses Rec.709 gamma, which is identical to Rec.2020 gamma for <12-bits (R-REC-BT.2020-2-201510). This is called Camera Gamma and it's not supposed to be used in authored content in either bitdepth. Rec.1886 gamma (in practice a pure power 2.4 gamma) is recommended as display gamma for Rec.709 (read EBU Tech 3320 v4.1 - 1.5.4). If what you mean is ITU-R BT.2390-2, that's for HDR not HBD.

Have a look at https://github.com/sekrit-twc/zimg/issues/111 and https://github.com/sekrit-twc/zimg/issues/101#issuecomment-471823652
Here transfers - https://github.com/sekrit-twc/zimg/blob/master/src/zimg/api/zimg.h#L302

"Rec.1886 gamma (in practice a pure power 2.4 gamma)" - in some cases could be the same as 2.4 gamma but Rec.1886 is not 2.4 gamma.

About the shift, do you mean pixel shift? It's subsampling artifacts of upsampling and downsampling. Since ColorSpace() ( and WhitePoint() ) is doing a roundtrip in RC4 I hard coded it to PointResize() with a known fix for PointResize() pixel shift of -1.0. Hope it's fine now.

Right, pixel shift.


Finally avsresize has an issue where I'm not able to use "fcc" for Matrix coefficients for Rec.601 primaries, as it should. Both PAL and NTSC in YCbCr use the coefficients derived from the FCC primaries. In avsresize these coefficients are not computed but constants with a precision of 3 decimal floats only.

What do you mean you're not able to use fcc matrix with 470bg primaries?

"In avsresize these coefficients are not computed but constants with a precision of 3 decimal floats only."

Can you show comparison where there is visual difference?

Dogway
16th May 2021, 00:32
Why do you need visual difference to be accurate? I don't want a half baked color space function, that's why I made this and also as a learning reference.

Some other additions are ACES spaces, custom chroma resamplers for the YUV<->RGB conversion, or PC/TV range input/output for this conversion.

I will release v1.0 final for SDR, and target v2.0 for HDR (uncharted territory for me).

StvG
16th May 2021, 03:24
I appreciate your work and the time you spared but saying "half baked color space function" about zimg is not serious and someone reading this would get completely wrong impression (about zimg).

That precision you're talking about it would matter when you can get the final result in float (still questionable - no visible difference) but in the end you have to go lower integer and to use some rounding.

Also... you have intermediate result with source bit depth (the output from YCbCrToRGB and the intput in RGBToYCbCr) that wipes the precision you're trying to achieve if the input clip is not in float (input bit depth -> float -> input_depth -> float -> input_depth):
YCbCrToRGB(YCbCrToRGB_mat("601")).RGBToYCbCr(RGBToYCbCr_mat("709"))

avsresize equivalent would be (assuming 8-bit input) (8-bit -> float -> 8-bit -> float -> 8-bit);
z_ConvertFormat(pixel_type="rgbp", colorspace_op="601:709:709=>rgb:709:709", resample_filter="spline36")
z_ConvertFormat(pixel_type="yv12", colorspace_op="rgb:709:709=>709:709:709", resample_filter="spline36")

But here you have intermediate result in float and this leads to final result with higher precision than above examples (input bit depth -> float -> input_depth):
z_ConvertFormat(colorspace_op="601:709:709:l=>709:709:709:l",resample_filter="spline36", dither_type="none")

For ColorSpace("601","709","spline36","spline36",true,false) again the intermediate result is in input_depth.

Dogway
16th May 2021, 16:23
Sorry if it sounded like that, I wasn't talking about any particular plugin but my own code. Why would I limit myself to what others have done? If I'm not bringing anything new to the table there's no meaning in the project. As I noted above, new color spaces, new algos, range conversion, and so on. I have used zimg library before through ImageMagick and it's a great tool, I simply don't like how it has been presented as avs+ plugin.

About the precision:
You can use ConvertBits() to use any precision on your chain, I find this a feature and not a detriment, you can see how precision makes a difference for color space conversions.
RGB will turn into RGBP16 for ConvertBits(16), which is 16-bit integer, "is it the same as float?" NO, but that's the point of the project, it scales with improvements on the avs+ side (looking forward to 16 half float). Meanwhile I'm currently working on 32-bit float support.

I'm in the process of updating a bunch of scripts to replace masktools2 to Expr() (check my Github), and the same principle applies, any performance improvement over Expr() will automatically translate into the scripts running faster. Currently the only hindrance is convolutions (mt_edge, mt_xxpand, etc), there aren't fast alternatives for avs+ like in VS (https://forum.doom9.org/showpost.php?p=1608585&postcount=1) and thus it presents the bottleneck of most scripts.

kedautinh12
21st May 2021, 01:31
RC6: https://github.com/Dogway/Avisynth-Scripts/blob/master/TransformsPack.v1.0.RC1.avsi

Dogway
3rd June 2021, 18:15
I released RC9, this is a good candidate for 1.0 final so some feedback would be good, specially on argument coherency and chroma placement accuracy.
Recently added chroma placement option from/to RGB, linear_in/out options, and format out conversion.

The goal here is not speed as I can't compete with highly accelerated plugins but accuracy and reliance. The script is very straight forward, easy to use and easy to understand so it can be easily debugged. Also I want to make it a reference for accuracy, where all others compare against, for that I derived all the constants I could (except for RGB<->XYZ transforms), namely chroma placement shifts, kr, kb, kg coefficients, Pb, Pr, Nb and Nr coefficients, offset values for piecewise gamma function, etc.


Shouldn't ColorSpace("601","709","spline36","spline36",true,false) be the same as YCbCrToRGB(YCbCrToRGB_mat("601")).RGBToYCbCr(RGBToYCbCr_mat("709")) ?
Now back to the hot topic, is RGB a color space? as I said, I don't think so (https://forum.doom9.org/showthread.php?p=1942813#post1942813). I call RGB a color format, although in fact it is a color "model", just like YUV. This is independent from color spaces, if you think about it all color space conversions happen through RGB (and XYZ) space, if you work with Nuke, Resolve, Photoshop, Blender... they are RGB based and color management aware at the same time.

Following some quotes I found while researching for a side project.

tftcentral (https://www.tftcentral.co.uk/articles/pointers_gamut.htm#_Toc379132056):
Nearly every capture - or display device nowadays uses an RGB color model to describe the color for every pixel. (...). This data has a little meaning though without a color space assigned to it. A pixel with 8-bit color depth per channel with RGB value [255; 0; 0] would obviously be red. But without a color space assigned there would be no way to tell what the chromaticity for red should be.

Wikipedia: RGB (https://en.wikipedia.org/wiki/RGB_color_model#Additive_colors):
The RGB color model itself does not define what is meant by red, green, and blue colorimetrically, and so the results of mixing them are not specified as absolute, but relative to the primary colors. When the exact chromaticities of the red, green, and blue primaries are defined, the color model then becomes an absolute color space, such as sRGB or Adobe RGB; see RGB color space for more details.

Arx1meD
24th June 2021, 09:56
Dogway, earlier versions of the script had a WhitePoint function. I haven’t found information about this feature. Is it correcting the white balance? Can you explain what she does?

Dogway
24th June 2021, 11:24
Yes, I moved it to Grade Pack since it is a less technical and more a look function. In Transforms Pack there's only c_adaptation() which does chromatic adaptation of known color spaces, defined by the "source" and "target" arguments.

WhitePoint() doesn't correct anything per se, it is similar to the Tweak() internal function but instead of the HUE wheel it modifies color based on the daylight correlated color temperature (ie. cooler or warmer white point) as a mathematically accurate function, unlike most other solutions I have seen.

I also want to add that current RC17 of Transforms Pack is very outdated and not representative of final version. I'm finishing development of v1.0, it's actually finished but in debug stage so I hope to release it in less than a week time.

Arx1meD
24th June 2021, 13:21
Dogway, thanks.
Can your Transforms Pack be used to change the white balance?

Dogway
24th June 2021, 17:48
Use WhitePoint() for that in Grade Pack. It requires Transforms Pack though for the matrix functions and it's developed a bit ahead so you need to remove all the ", fulls=fs)" references.

EDIT: Topic moved to Filters Packs thread (https://forum.doom9.org/showthread.php?t=182881).