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.

 

Go Back   Doom9's Forum > Capturing and Editing Video > VapourSynth

Reply
 
Thread Tools Search this Thread Display Modes
Old 23rd January 2018, 19:57   #1  |  Link
kolak
Registered User
 
Join Date: Nov 2004
Location: Poland
Posts: 2,843
Out of gamut detection

Anyone willing to write something to detect out of gamut (with some threshold) script? I may be able to pay some money for it.

This is the spec which it should follow:

https://tech.ebu.ch/docs/r/r103.pdf

Video would be piped through ffmpeg I just need some indication if file has passed or not check.
kolak is offline   Reply With Quote
Old 23rd January 2018, 21:42   #2  |  Link
kolak
Registered User
 
Join Date: Nov 2004
Location: Poland
Posts: 2,843
Hmm...looks to me like it's exactly what I want.

I know bit of Python itself, but struggle link vs with it.

Understand 2nd half to the script.
How do I check every frame while it's processed?
Or do I need script to process all frames and then check result in stored values?
kolak is offline   Reply With Quote
Old 24th January 2018, 00:45   #3  |  Link
ChaosKing
Registered User
 
Join Date: Dec 2005
Location: Germany
Posts: 1,795
Use FrameEval to check on each frame http://www.vapoursynth.com/doc/functions/frameeval.html
__________________
AVSRepoGUI // VSRepoGUI - Package Manager for AviSynth // VapourSynth
VapourSynth Portable FATPACK || VapourSynth Database
ChaosKing is offline   Reply With Quote
Old 24th January 2018, 10:31   #4  |  Link
kolak
Registered User
 
Join Date: Nov 2004
Location: Poland
Posts: 2,843
Thank you.
I've looked at vs engine's doc yesterday and I was close, very close.
Myrsloik said that vspipe is better approach and should be faster.

Now everything makes perfect sense, except
c = core.std.Convolution(c, matrix=[1, 2, 3, 4, 3, 2, 1], mode='h')
c = core.std.Convolution(c, matrix=[1, 2, 1], mode='v')

which I assume is low pass filtering. Looking at numbers I get them, but I don't fully understand it.

What if file is interlaced? We need to process each filed separately?
Where is Rec.709 or Rec.601 here which I assumed needs to be specified when you go from YUV back to RGB?

Last edited by kolak; 24th January 2018 at 12:00.
kolak is offline   Reply With Quote
Old 24th January 2018, 11:40   #5  |  Link
kolak
Registered User
 
Join Date: Nov 2004
Location: Poland
Posts: 2,843
Script itself works, but actual out of gamut detection doesn't.
Where is RGB here as when I do:
f.format it shows me YUV?

There has to be somewhere place where we convert it to RGB and then check if RGB planes are out of range.
Gamut errors are detected when YUV is converted back to RGB and creates illegal RGB values.

Last edited by kolak; 24th January 2018 at 12:02.
kolak is offline   Reply With Quote
Old 24th January 2018, 11:52   #6  |  Link
kolak
Registered User
 
Join Date: Nov 2004
Location: Poland
Posts: 2,843
After converting to RGB now it seems to work.
Should I convert it before or after low pass filtering?

Is simple:
c = core.resize.Bicubic(clip=c,format=vs.RGB30)

good enough or should I use something better? ( this is for 10bit video).

I also realised that I have to check each RGB plane separately.

I've made files which have issues only on R, G or B channel only and it seems to be detecting it fine. Now I have to simulate below and above 1% frame coverage.
kolak is offline   Reply With Quote
Old 24th January 2018, 17:24   #7  |  Link
kolak
Registered User
 
Join Date: Nov 2004
Location: Poland
Posts: 2,843
I've verified that all seems to be working fine for 8bit.
For 10bit there may be an issue as reported max plane values are 32768, not like expected e.g. 768 (some strange scaling is happening- I assume to 16bit?).

My test file with 920x80 pixels bad area did produce perfect score of 3.549% of bad pixels for HD frame.
With low pass filtering it's 3.493% which suggest it's all working well (I expected bit lower value).

Last edited by kolak; 24th January 2018 at 17:30.
kolak is offline   Reply With Quote
Old 24th January 2018, 17:30   #8  |  Link
kolak
Registered User
 
Join Date: Nov 2004
Location: Poland
Posts: 2,843
Quote:
Originally Posted by Stephen R. Savage View Post
std.Convolution implements the "Video Signal Filtering" section of the document. As described in the document, interlaced images are filtered per-field. You are right that in-range YUV signals can produce out-of-range RGB signals. To handle this, convert the YUV to RGB with limited range. When applying std.PlaneStats, repeat it once with each plane as the "plane" argument and a different prefix for "prop".

Code:
c = ... # Source.

c = core.std.CropAbs(c, ...) # Crop to active region.
c = core.resize.Bicubic(c, format=vs.RGB24, range='limited')
I have issue with range='limited'. Using old or latest ffms2 throws error. I have to use range=0.

File "src\cython\vapoursynth.pyx", line 528, in vapoursynth.typedDictToMap
ValueError: invalid literal for int() with base 10: 'limited'

Looks like a bug.

Last edited by kolak; 25th January 2018 at 00:12.
kolak is offline   Reply With Quote
Old 24th January 2018, 23:16   #9  |  Link
kolak
Registered User
 
Join Date: Nov 2004
Location: Poland
Posts: 2,843
It was PlaneStatMax when I used RGB30 in resize, but without std.Expr stage. I was checking what values are provided by vs. I was expecting 10bit levels (e.g. 64-940), but saw things like 32768.

I was using old vs, so will try again with current one.

My other note is that:

std.Expr(c, 'x 5 < x 246 > or 255 0 ?')
and PlaneStats

operate on integers? This means precision is lost a bit.
kolak is offline   Reply With Quote
Old 24th January 2018, 23:34   #10  |  Link
kolak
Registered User
 
Join Date: Nov 2004
Location: Poland
Posts: 2,843
Quote:
Originally Posted by Stephen R. Savage View Post
Where do you observe the max plane value to reach 32768? After the std.Expr stage, only 0 and 255 (or 1023) should exist in the image. If std.PlaneStats after std.Expr reports this, it may be a VS bug.
VS R42 is fine- for RGB30 I see correct values for planestats.
kolak is offline   Reply With Quote
Old 24th January 2018, 23:57   #11  |  Link
kolak
Registered User
 
Join Date: Nov 2004
Location: Poland
Posts: 2,843
Quite interesting.
Even ProRes files with bars can show as low as e.g. 14 values where you would expect 64. Then if you turn on filtering this is not anymore detected and lowest values is e.g. 56.
I was never expecting ProRes to affect simple things like bars that much

In the same time YUV values are almost perfect (some 60 values can be found for 10bit). This suggests that issue for RGB is at bars edges and it's due to chroma up sampling during conversion to RGB (at least in my opinion). It also shows that low pass filtering does well its role

Last edited by kolak; 25th January 2018 at 00:05.
kolak is offline   Reply With Quote
Old 25th January 2018, 00:19   #12  |  Link
kolak
Registered User
 
Join Date: Nov 2004
Location: Poland
Posts: 2,843
Quote:
Originally Posted by Stephen R. Savage View Post

Code:
c = core.std.PlaneStats(c, plane=0, props='PlaneStatsR') # Once for each plane if RGB.
c = core.std.PlaneStats(c, plane=1, props='PlaneStatsG')
c = core.std.PlaneStats(c, plane=2, props='PlaneStatsB')
There should be prop here not props.
kolak is offline   Reply With Quote
Old 25th January 2018, 11:46   #13  |  Link
kolak
Registered User
 
Join Date: Nov 2004
Location: Poland
Posts: 2,843
Quote:
Originally Posted by Stephen R. Savage View Post
This operation is exact. The purpose is to binarize the image at the thresholds specified in the document. PlaneStats then counts the number of pixels that were outside the threshold.
What do you mean by exact (x is float)?

For example my file has area which R is coming at eg. 8.56.

Looks like I can't have:

std.Expr(c, 'x 8.57 < x 246 > or 255 0 ?')

it's either x < 8 or x <9

I understand logic behind and I like this logic

Last edited by kolak; 25th January 2018 at 12:09.
kolak is offline   Reply With Quote
Old 25th January 2018, 13:46   #14  |  Link
kolak
Registered User
 
Join Date: Nov 2004
Location: Poland
Posts: 2,843
Another question.

This R103 check requires RGB to be in specified values, but also Y out of YUV signal.

Can we do both checks in one go (RGB and original Y)?

How can we pass Y into checking function?
Looks like this has to be 2nd pass, no?

Also, where low pass filtering should happen: on YUY or RGB? ( I would say YUV)

Last edited by kolak; 25th January 2018 at 13:57.
kolak is offline   Reply With Quote
Old 25th January 2018, 14:07   #15  |  Link
Myrsloik
Professional Code Monkey
 
Myrsloik's Avatar
 
Join Date: Jun 2003
Location: Kinnarps Chair
Posts: 2,548
It's so vague about things. Is it talking about limited range rgb as well? I know you've mentioned other products that check these things and throwing carefully constructed samples at once of those solutions may be the only way we'll ever find out...
__________________
VapourSynth - proving that scripting languages and video processing isn't dead yet
Myrsloik is offline   Reply With Quote
Old 25th January 2018, 14:19   #16  |  Link
kolak
Registered User
 
Join Date: Nov 2004
Location: Poland
Posts: 2,843
It's not end of the world as difference between low pass filtered on YUV v RGB is very small.
I assume it's limited range as otherwise it would not make sense?

How can I check original Y (if it's in limits) during the same check as RGB?

Last edited by kolak; 25th January 2018 at 14:21.
kolak is offline   Reply With Quote
Old 25th January 2018, 14:20   #17  |  Link
Myrsloik
Professional Code Monkey
 
Myrsloik's Avatar
 
Join Date: Jun 2003
Location: Kinnarps Chair
Posts: 2,548
Quote:
Originally Posted by kolak View Post
It's not end of the world as difference between low pass filtered on YUV v RGB is very small.

How can I check original Y (if it's in limits) during the same check as RGB?
Post the script so far and I'll show you, it's a quite simple addition...
__________________
VapourSynth - proving that scripting languages and video processing isn't dead yet
Myrsloik is offline   Reply With Quote
Old 25th January 2018, 14:25   #18  |  Link
kolak
Registered User
 
Join Date: Nov 2004
Location: Poland
Posts: 2,843
Code:
def check_gamut(n, f, c):
	if f.props['PlaneStatsRAverage'] > 0.01 or f.props['PlaneStatsGAverage'] > 0.01 or f.props['PlaneStatsBAverage'] > 0.01:
		print("Bad frame: "+str(n))
		exit()
	return c

c = core.ffms2.Source()
c = core.std.Convolution(c, matrix=[1, 2, 3, 4, 3, 2, 1], mode='h')
c = core.std.Convolution(c, matrix=[1, 2, 1], mode='v')

c = core.resize.Bicubic(c, format=vs.RGB30, range_s="limited")

c = core.std.Expr(c, 'x 20 < x 984 > or 1023 0 ?')

c = core.std.PlaneStats(c, plane=0, prop='PlaneStatsR')
c = core.std.PlaneStats(c, plane=1, prop='PlaneStatsG')
c = core.std.PlaneStats(c, plane=2, prop='PlaneStatsB')
	
c = core.std.FrameEval(c, functools.partial(check_gamut, c=c), prop_src=c)
c.set_output()
kolak is offline   Reply With Quote
Old 25th January 2018, 14:25   #19  |  Link
Myrsloik
Professional Code Monkey
 
Myrsloik's Avatar
 
Join Date: Jun 2003
Location: Kinnarps Chair
Posts: 2,548
Basically the idea is to convert the input to yuv/rgb (opposite of what it is).

Then run the original and opposite format clips through the appropriate checks.

Use stackhorizontal last to make both branches of the script be fetched. Done. Probably.
__________________
VapourSynth - proving that scripting languages and video processing isn't dead yet
Myrsloik is offline   Reply With Quote
Old 25th January 2018, 14:28   #20  |  Link
kolak
Registered User
 
Join Date: Nov 2004
Location: Poland
Posts: 2,843
I need to somehow do this:

Code:

c = core.ffms2.Source()
c = core.std.Convolution(c, matrix=[1, 2, 3, 4, 3, 2, 1], mode='h')
c = core.std.Convolution(c, matrix=[1, 2, 1], mode='v')
...

y=c
y = core.std.Expr(y, 'x 20 < x 984 > or 1023 0 ?')
y = core.std.PlaneStats(y, plane=0, prop='PlaneStatsY')
	
c = core.std.FrameEval(c, functools.partial(check_gamut, c=c), prop_src=c)
c.set_output()
but I have no way to pass it to FrameEval as it takes prop_src=c only?

I need this:
c = core.std.FrameEval(c, functools.partial(check_gamut, c=c, y=y), prop_src=[c,y])

Last edited by kolak; 25th January 2018 at 14:31.
kolak is offline   Reply With Quote
Reply

Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT +1. The time now is 12:55.


Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2024, vBulletin Solutions Inc.