Welcome to Doom9's Forum, THE in-place to be for everyone interested in DVD conversion. Before you start posting please read the forum rules. By posting to this forum you agree to abide by the rules.

 Doom9's Forum U and V ranges for valid RGB
 Register FAQ Calendar Search Today's Posts Mark Forums Read

 23rd May 2010, 17:53 #1  |  Link Gavino Avisynth language lover   Join Date: Dec 2007 Location: Spain Posts: 3,433 U and V ranges for valid RGB Following a conversation with WorBry about the U and V ranges which correspond to valid RGB, I have put together this script which shows, for each value of Y, the valid ranges of U and V (in itself an interesting exercise in the use of MaskTools expressions ). Code: ```# Demonstration of valid YUV values, showing (for both TV ranges and PC ranges): # - ranges of U and V for each possible value of Y; # - ranges of Y and V for each possible value of U; # - ranges of Y and U for each possible value of V. # Requires MaskTools v2 # Support function. # Given MaskTools expressions for Y, U, and V, # returns expression which yields 255 for valid RGB and 0 for invalid. function ValidRGB(string y, string u, string v, bool "pcRange") { pcRange = Default(pcRange, false) # Normalise Y to [0,1] and U, V to [-1,+1]: y = pcRange ? y+" 255 /" : y+" 16 - 219 /" u = pcRange ? u+" 128 / 1 -" : u+" 16 - 112 / 1 -" v = pcRange ? v+" 128 / 1 -" : v+" 16 - 112 / 1 -" # Rec 601 coefficients: Kr = " 0.299" Kg = " 0.587" Kb = " 0.114" Kr1 = " 1"+Kr+" -" Kb1 = " 1"+Kb+" -" # From http://avisynth.org/mediawiki/Color_conversions: # R = Y + V*(1-Kr) # G = Y - U*(1-Kb)*Kb/Kg - V*(1-Kr)*Kr/Kg # B = Y + U*(1-Kb) r = y + v + Kr1 + " * +" g = y + u + Kb1 + Kb + Kg + " / * * -" + v + Kr1 + Kr + Kg + " / * * -" b = y + u + Kb1 + " * +" lwb = " 0 >=" upb = " 1 <=" and = " &" good = r+lwb+r+upb+and+g+lwb+and+g+upb+and+b+lwb+and+b+upb+and return good+" 255 0 ?" } function PlotYUV(clip c, string y, string u, string v, bool "pcRange") { plot = c.mt_lutspa(yexpr=y, uexpr=u, vexpr=v, U=3, V=3) mask = c.mt_lutspa(expr=ValidRGB(y, u, v, pcRange), U=3, V=3) c.mt_merge(plot, mask, U=3, V=3) } function PlotUV(clip c, int yIn, bool "pcRange") { # Plot valid U and V for given Y pcRange = Default(pcRange, false) title = "UV plot V ^-> U "+(pcRange?"PC":"TV")+" Range\nY = "+string(yIn) y = " " + string(yIn) u = " x 256 *" v = " 1 y - 256 *" c.PlotYUV(y, u, v, pcRange) pcRange ? NOP : LetterBox(16, 16, 16, 16, color_gray) Histogram("levels") StackVertical(last, BlankClip(last, height=4, color=color_gray10), \ BlankClip(last, height=44).Subtitle(title, lsp=0)) } function PlotYU(clip c, int vIn, bool "pcRange") { # Plot valid Y and U for given V pcRange = Default(pcRange, false) title = "YU plot Y ^-> U "+(pcRange?"PC":"TV")+" Range\nV = "+string(vIn) y = " 1 y - 255 *" u = " x 256 *" v = " " + string(vIn) c.PlotYUV(y, u, v, pcRange) pcRange ? NOP : LetterBox(20, 16, 16, 16, color_gray) Histogram("levels") StackVertical(last, BlankClip(last, height=4, color=color_gray10), \ BlankClip(last, height=44).Subtitle(title, lsp=0)) } function PlotYV(clip c, int uIn, bool "pcRange") { # Plot valid Y and V for given U pcRange = Default(pcRange, false) title = "YV plot Y ^-> V "+(pcRange?"PC":"TV")+" Range\nU = "+string(uIn) y = " 1 y - 255 *" u = " " + string(uIn) v = " x 256 *" c.PlotYUV(y, u, v, pcRange) pcRange ? NOP : LetterBox(20, 16, 16, 16, color_gray) Histogram("levels") StackVertical(last, BlankClip(last, height=4, color=color_gray10), \ BlankClip(last, height=44).Subtitle(title, lsp=0)) } b = BlankClip(256, 256, 256, pixel_type="YV12") b1 = b.Trim(16, 235) b2 = b.Trim(16, 240) bpc = b.ColorYUV(levels="TV->PC") tv = Animate(b1, 0, 219, "PlotUV", 16, 235) \ + Animate(b2, 0, 224, "PlotYU", 16, 240) \ + Animate(b2, 0, 224, "PlotYV", 16, 240) pc = Animate(bpc, 0, 255, "PlotUV", 0, true, 255, true) \ + Animate(bpc, 0, 255, "PlotYU", 0, true, 255, true) \ + Animate(bpc, 0, 255, "PlotYV", 0, true, 255, true) return tv.ConvertToRGB32() + pc.ConvertToRGB32("PC.601") ``` The top-left 256x256 area shows the UV-plane, with invalid values replaced by black. It shows strikingly that most of the YUV space is in fact unused (ie does not correspond to a valid RGB color), especially for low and high values of Y, and that for no value of Y is the entire range of U or V valid. For Y=0 or Y=255 of course, it shrinks to a single point (black or white) with U=V=128. None of this is a problem, of course - it's just the way it works, but it might surprise some people. And it's something to be aware of when generating YUV values from scratch, such as when creating YUV color gradients, the topic of WorBry's post. An 'invalid' YUV value will produce clipping to 0 or 255 on one or more colors when converted to RGB. Edit: Script extended to add YU and YV plots, and to show plots for both TV range and PC range. Some selected examples (orig script): Attached Images       Last edited by Gavino; 27th May 2010 at 11:21. Reason: extended to add YU and YV plots, and for both TV range and PC range
24th May 2010, 05:40   #2  |  Link
WorBry
Registered User

Join Date: Jan 2004
Location: Here, there and everywhere
Posts: 1,197
Thanks for that. Very interesting......and revealing.

Quote:
 Originally Posted by Gavino It shows strikingly that most of the YUV space is in fact unused (ie does not correspond to a valid RGB color), especially for low and high values of Y, and that for no value of Y is the entire range of U or V valid.For Y=0 or Y=255 of course, it shrinks to a single point (black or white) with U=V=128.
I'd reached a similar conclusion looking at U and V spectra with a superimposed greyscale gradient i.e. (using my 'round-about' script):

Code:
```colorbars().converttoyv12().mt_lutspa(relative=true,expr="x 255 *").greyscale()
GSG    =  colorbars().converttoyv12().mt_lutspa(relative=true,expr="x 256 * ").Greyscale().LanczosResize(640,640)
GSG2   =  GSG.Reduceby2()
GSGL   =  GSG.TurnLeft()
NGry2  =  GSG.mt_lut("x 128 ", y=3).Reduceby2()
UspecG =  YtoUV(GSG2, NGry2, GSGL)
VSpecG =  YtoUV(NGry2, GSG2, GSGL)
Return Interleave(USpecG, VSpecG)```
Assuming you can see the 'clipping':

.......But your function, with the 'invalid RGB value' mask, really nails it.

Quote:
 .......And it's something to be aware of when generating YUV values from scratch, such as when creating YUV color gradients, the topic of WorBry's post. An 'invalid' YUV value will produce clipping to 0 or 255 on one or more colors when converted to RGB.
With that, it would be interesting to see a composite picture for the entire (0 - 255) luma scale i.e. like the 'UV spectra-greyscale gradient' thing I did, but with the 'invalid value' mask applied.

In fact, what would be really useful is if the function could be developed to apply the analysis to 'real' YV12 image frames and map (maybe with separate color coding for U and V) those areas where the chroma values are rendered 'RGB invalid' and clipped. That would be of great practical value. I wonder how many folks, like myself, apply all manner of luma and chroma pixel transformations to their YUV videos, blissfully unaware that the resulting colors may not be faithfully reproduced in RGB display i.e. what they are seeing on the screen may not be the 'true' YUV colors. Pretty significant I think.
__________________
Nostalgia's not what it used to be

Last edited by WorBry; 24th May 2010 at 06:13.

 24th May 2010, 06:04 #3  |  Link Dark Shikari x264 developer     Join Date: Sep 2005 Posts: 8,666 Animated GIF version. Last edited by Wilbert; 29th December 2014 at 23:34. Reason: corrected link
 24th May 2010, 06:06 #4  |  Link WorBry Registered User   Join Date: Jan 2004 Location: Here, there and everywhere Posts: 1,197 Way-Hey !!! Where's the pause button ? __________________ Nostalgia's not what it used to be Last edited by WorBry; 24th May 2010 at 06:12.
24th May 2010, 14:27   #5  |  Link
pbristow
Registered User

Join Date: Jun 2009
Location: UK
Posts: 263
Quote:
 Originally Posted by WorBry Way-Hey !!! Where's the pause button ?
It's built into VirtualDub.

(I've never tried loading an animated GIF straight into Vdub before. Works like a charm! )

 24th May 2010, 15:11 #6  |  Link 2Bdecided Registered User   Join Date: Dec 2002 Location: UK Posts: 1,673 Note to anyone who hasn't actually read the script: It defaults to PC range, with TV range part commented out. However, valid YUV is always in TV range, so the default behaviour is misleading (to say the least)! Very cool script though. Nice use of masktools! Cheers, David.
24th May 2010, 15:13   #7  |  Link
WorBry
Registered User

Join Date: Jan 2004
Location: Here, there and everywhere
Posts: 1,197
Quote:
 Originally Posted by pbristow It's built into VirtualDub. (I've never tried loading an animated GIF straight into Vdub before. Works like a charm! )
I was joking. The function script itself is animated.
__________________
Nostalgia's not what it used to be

24th May 2010, 17:31   #8  |  Link
Gavino
Avisynth language lover

Join Date: Dec 2007
Location: Spain
Posts: 3,433
Quote:
 Originally Posted by 2Bdecided It defaults to PC range, with TV range part commented out. However, valid YUV is always in TV range, so the default behaviour is misleading (to say the least)!
Yes, the values displayed in the histograms and the subtitle are PC range, 0-255 for both luma and chroma. I left it that way because WorBry was using PC range in his work.

However, the calculation for valid/invalid RGB takes that into account, being done on normalised/scaled values in the range [0, 1] for luma and [-1, +1] for chroma. And the colors are converted with matrix PC.601 for output. So the general behaviour is exactly the same for TV range YUV, if you take the UV plot to represent the entire available range (16-240 for TV) and the displayed Y value in brackets to be the fraction of 'full-range'.
Quote:
 Originally Posted by WorBry it would be interesting to see a composite picture for the entire (0 - 255) luma scale i.e. like the 'UV spectra-greyscale gradient' thing I did, but with the 'invalid value' mask applied. In fact, what would be really useful is if the function could be developed to apply the analysis to 'real' YV12 image frames and map (maybe with separate color coding for U and V) those areas where the chroma values are rendered 'RGB invalid' and clipped.
I'll have a go at both those. Watch this space.

Last edited by Gavino; 24th May 2010 at 17:45. Reason: typo (had a -1 instead of +1)

 27th May 2010, 11:26 #9  |  Link Gavino Avisynth language lover   Join Date: Dec 2007 Location: Spain Posts: 3,433 I have now extended the script in the first post to add plots of YU (for each valid V) and YV (for each valid U), and to show plots for both TV range and PC range. The TV range plots have a grey border marking the smaller overall ranges available (16-235 for luma, 16-240 for chroma). Next job will be to create a function (as requested by Worbry) to analyse arbitrary YV12 images and map the areas with 'RGB invalid' chroma. Examples from new script: Attached Images
 27th May 2010, 22:07 #10  |  Link WorBry Registered User   Join Date: Jan 2004 Location: Here, there and everywhere Posts: 1,197 Once again, a very enlightening demonstration, not only of the 'RGB-invalid chroma effect', but also the psycho-visual basis of YUV color perception. I've made it my new screen-saver Thanks for your work - looking forward to the 'applied' function __________________ Nostalgia's not what it used to be Last edited by WorBry; 28th May 2010 at 00:08.
 29th May 2010, 07:50 #12  |  Link Die*wrek*show DefaU1tABuser   Join Date: Sep 2003 Location: North America Posts: 73 woooo! I love this. for making that, D.S. __________________ all of my friends and loved ones are doomed... Last edited by Wilbert; 29th December 2014 at 23:35. Reason: corrected link
 29th May 2010, 14:27 #13  |  Link WorBry Registered User   Join Date: Jan 2004 Location: Here, there and everywhere Posts: 1,197 Also came across this, more recent, article, proposing an improved approach to the resolution of out-of-gamut YUV values: http://sas-origin.onstreammedia.com/...r%201/Chan.pdf I wonder if his 'in-range chroma reconstruction' model is something than could be tested with AVISynth (MaskTools maybe), although, in my minds-eye, both methods (Spill and Proportion) seem tantamount to chroma blur. __________________ Nostalgia's not what it used to be
 29th May 2010, 23:50 #14  |  Link Gavino Avisynth language lover   Join Date: Dec 2007 Location: Spain Posts: 3,433 Here are two functions which can be used to test any YV12 clip for 'invalid' RGB values. See the comments for details. Code: ```# Returns a mask with 255 where YV12 clip has valid RGB and 0 elsewhere function RGBMask(clip c, bool "pcRange") { c2 = c.BilinearResize(2*c.width, 2*c.height) return c.mt_lutxyz(c2.UToY(), c2.VToY(), ValidRGB(" x", " y", " z", pcRange)) } # Shows the areas of a YV12 clip which contain 'invalid RGB'; # good pixels are replaced by 'color', default black. function ShowBadRGB(clip c, bool "pcRange", int "color") { mask = c.RGBMask(pcRange) c.mt_merge(BlankClip(c, color=color), mask, U=3, V=3, luma=true) }``` Both require the ValidRGB() function from the first post. Script loading is very slow because of the mt_lutxyz function (which has to create a lookup table with 2^24 entries). Below I have shown the results of applying ShowBadRGB to (the equivalent of) your USpec and VSpec clips, using the following code: Code: ```BlankClip(2, 320, 320, pixel_type="YV12") USpec = mt_lutspa(yexpr="1 y - 255 *", uexpr="x 255 *", U=3, V=-128) VSpec = mt_lutspa(yexpr="1 y - 255 *", vexpr="x 255 *", U=-128, V=3) Interleave(USpec, VSpec) ShowBadRGB(pcRange=true) return ConvertToRGB("PC.601")``` In effect, the result is basically the converse of the 'valid' area plots shown earlier. The papers you referenced look interesting, although I haven't had time to study them in detail. The BBC one gives a fairly good explanation of the shape of the plots shown earlier. Results from ShowBadRGB(): Attached Images
 30th May 2010, 04:17 #15  |  Link WorBry Registered User   Join Date: Jan 2004 Location: Here, there and everywhere Posts: 1,197 Excellent. As you say, very slow to load, but it gets there. Thanks so much for doing this. Now to set about doing some testing. __________________ Nostalgia's not what it used to be
 1st June 2010, 04:54 #16  |  Link WorBry Registered User   Join Date: Jan 2004 Location: Here, there and everywhere Posts: 1,197 For testing, I'm using clips from a variety of sources, including some AVCHD camcorders (Panasonic HMC-41 and HMC-151). For compatability with the test system, they need to be converted from Rec.709 to Rec.601 and scaled for 'TV' and 'PC' ranges. Trouble is that, like many consumer/prosumer camcorders, the luma scale is effectively 16 - 255 i.e. allows 235-overshoot (super-white). Here's an example: http://www.mediafire.com/file/qm5yti...20Original.png (Probably not the best example, in terms of black-point, but it serves to illustrate the 235-overshoot) The following offers a solution: Code: ```AVCSource("C:\...........Test clip.dga") clp=last PCY = clp.YLevels(16, 1.0, 255, 0, 255) UV601 = clp.ColorMatrix(mode="Rec.709->Rec.601") PCUV = UV601.ColorYUV(levels="TV->PC") mt_lutxy(PCY, PCUV, y=2, u=4, v=4) #LanczosResize(1024,576) #Resized for Histogram #Histogram(mode="levels")``` Result: http://www.mediafire.com/file/yiqdmm...9to601toPC.png ShowBadRGB analysis: http://www.mediafire.com/file/xiwowt...ge%20PC601.png But, it seems a little convoluted and I am wondering if there is a simpler, more direct solution. Similar considerations apply for 'TV range'. A simple ColorMatrix(mode="Rec.709->Rec.601") conversion clips the overshot luma to 235: Result: http://www.mediafire.com/file/dymdt4...ec709to601.png ShowBadRGB analysis: http://www.mediafire.com/file/uzinjx...e%20Rec601.png So, again, would it be valid (for these tests) to scale down the luma from 255 and 235 and take the chroma from Rec.709->Rec.601 Code: ```AVCSource("C:\...........Test clip.dga") clp=last TVY = clp.YLevels(16, 1.0, 255, 16, 235) TVUV = clp.ColorMatrix(mode="Rec.709->Rec.601") mt_lutxy(TVY, TVUV, y=2, u=4, v=4) #LanczosResize(1024,576) #Resized for Histogram #Histogram(mode="levels")``` Result: http://www.mediafire.com/file/nxmnjb...20709to601.png ShowBadRGB analysis: http://www.mediafire.com/file/mywkfc...e%20Rec601.png Would appreciate advice. Edit: Also added the results of the 'ShowBadRGB' analysis for each of the above. Interesting that the 'PC Range' and 'TV Range' (using the 255->235 downscaled clip) analyses give very similar patterns - RGB-invalid values associated with the shiny glare on the knights armour (presumably the source of the overshot luma). The straight 'Rec.709->Rec.601 converted' clip however shows more extensive 'bad-RGB', presumably due to the 235 clipping. I've started to do some tests with applied luma contrast curves. Predictably, invalid values then begin to appear on the more saturated colors e.g. the bright reds on the knights tunics, and to a lesser extent the greens.......Edit: not surprising since the rec.601 YUV values for (true) red are stated to be 81, 90, 240 Is it just me, or are the knights getting shorter? __________________ Nostalgia's not what it used to be Last edited by WorBry; 1st June 2010 at 17:01.
 1st June 2010, 17:37 #17  |  Link Gavino Avisynth language lover   Join Date: Dec 2007 Location: Spain Posts: 3,433 I'm not sure your approach is valid as you seem to be mixing Rec.709 luma with Rec.601 chroma, which does not seem correct. Assuming the input chroma is TV range (am I right to assume this?), then for TV range output, I think you want: YLevels(16, 1.0, 255, 16, 235) ColorMatrix(mode="Rec.709->Rec.601") and for PC range, simply extend this with: ColorYUV(levels="TV->PC") Another possibility is to extend my function to work with Rec.709 and avoid doing any colorimetry conversions at all. A reminder in case you need it: for viewing the results of all these things, or producing screenshots, be sure to add ConvertToRGB(matrix=whatever) to your script unless you are viewing them in something that knows it is not dealing with TV-range Rec.601 input. PS. Once a knight, always a knight; ... Last edited by Gavino; 1st June 2010 at 17:39.
1st June 2010, 17:45   #18  |  Link
WorBry
Registered User

Join Date: Jan 2004
Location: Here, there and everywhere
Posts: 1,197
Quote:
 Originally Posted by WorBry The following offers a solution: Code: ```AVCSource("C:\...........Test clip.dga") clp=last PCY = clp.YLevels(16, 1.0, 255, 0, 255) UV601 = clp.ColorMatrix(mode="Rec.709->Rec.601") PCUV = UV601.ColorYUV(levels="TV->PC") mt_lutxy(PCY, PCUV, y=2, u=4, v=4) #LanczosResize(1024,576) #Resized for Histogram #Histogram(mode="levels")``` .......But, it seems a little convoluted and I am wondering if there is a simpler, more direct solution.
I guess an alternative would be to convert the Rec.601 chroma to luma and use YLevels to upscale from 16-240 -> 0-255 and then back to chroma, but again, that's rather convoluted (more rounding errors etc). I dunno, maybe I'm over-complicating matters. What is a/the 'correct' way to re-scale YUV chroma 'directly' with MaskTools ?
__________________
Nostalgia's not what it used to be

Last edited by WorBry; 1st June 2010 at 17:53.

 1st June 2010, 17:58 #19  |  Link Gavino Avisynth language lover   Join Date: Dec 2007 Location: Spain Posts: 3,433 Not sure if your last post was in response to mine or if you missed it in passing.
1st June 2010, 18:30   #20  |  Link
WorBry
Registered User

Join Date: Jan 2004
Location: Here, there and everywhere
Posts: 1,197
Just missed it in passing.

Quote:
 Originally Posted by Gavino I'm not sure your approach is valid as you seem to be mixing Rec.709 luma with Rec.601 chroma, which does not seem correct.

Quote:
 Assuming the input chroma is TV range (am I right to assume this?), then for TV range output, I think you want: YLevels(16, 1.0, 255, 16, 235) ColorMatrix(mode="Rec.709->Rec.601") and for PC range, simply extend this with: ColorYUV(levels="TV->PC")
Ah yes, that way the luma overshoot is 'preserved' (i.e. not clipped, and then crushed in the upscale to 255). AFAIK, the ('TV') chroma range (16-240) is the same in Rec.709 and Rec.601, and I assume that these AVCHD camcorders are Rec.709 compliant.

Quote:
 Another possibility is to extend my function to work with Rec.709 and avoid doing any colorimetry conversions at all.
True, especially if folks are post-processing their AVCHD videos in Rec.709 with a view to displaying on HDTV.

Quote:
 A reminder in case you need it: for viewing the results of all these things, or producing screenshots, be sure to add ConvertToRGB(matrix=whatever) to your script unless you are viewing them in something that knows it is not dealing with TV-range Rec.601 input.
True, I didn't do that with the clip + histogram frame-shots. Thanks for the advice.

Quote:
 PS. Once a knight, always a knight; ...
Yes, but the downside to medieval pageantry is having to work the knights on weekends.
__________________
Nostalgia's not what it used to be

Last edited by WorBry; 2nd June 2010 at 01:06.