View Full Version : Interlaced primaries conversion
Blue_MiSfit
31st March 2021, 02:45
Hey gang!
I'm getting VapourSynth integrated into a system I work on for performing some content remastering tasks including typical things like deinterlacing, IVTC, upscaling, and (newer to me) color science like transfer function and color primaries conversions.
I've got two issues:
1) One of my use cases involves using VapourSynth to perform transfer and primaries conversions on interlaced masters, outputting 16 bit RGB DPX into a custom deinterlacing system. I _think_ separating the fields, doing the conversions, and then weaving back together is fine in this case, but would appreciate a double check. Am I crazy?
The output looks fine, but I want to make sure I'm not missing something obvious.
2) I've noticed that although the fmtc plugin has a preset transfer characteristic called "601" for BT.601 content (which is, essentially but not exactly a constant 2.4 gamma) , it's come to my attention that some SD content from NTSC land in fact uses SMPTE 170M transfer, which is actually pure gamma 2.2.
I can unroll this into linear light by doing something like this:
# Assuming 16 bit full range RGB input, using SMPTE 170M transfer (constant gamma 2.2)
c = core.fmtc.transfer(c, transs="linear", transd="linear", gcor=2.2, fulls=True, fulld=True)
# Now linear light, ready for primaries conversion
Anything wrong with this approach? Any particular reason there isn't a "170m" transfer preset? There is a "170m" preset for primaries.
On a related note, does anyone know how I might learn whether a given source truly uses BT.601 transfer to SMPTE 170M transfer? It seems hard to tell objectively :)
poisondeathray
31st March 2021, 04:40
1) One of my use cases involves using VapourSynth to perform transfer and primaries conversions on interlaced masters, outputting 16 bit RGB DPX into a custom deinterlacing system. I _think_ separating the fields, doing the conversions, and then weaving back together is fine in this case, but would appreciate a double check. Am I crazy?
The output looks fine, but I want to make sure I'm not missing something obvious.
Looks ok
If the "masters" began as subsampled YUV, then don't forget to convert to RGB in an interlaced aware manner
2) I've noticed that although the fmtc plugin has a preset transfer characteristic called "601" for BT.601 content (which is, essentially but not exactly a constant 2.4 gamma) , it's come to my attention that some SD content from NTSC land in fact uses SMPTE 170M transfer, which is actually pure gamma 2.2.
I can unroll this into linear light by doing something like this:
# Assuming 16 bit full range RGB input, using SMPTE 170M transfer (constant gamma 2.2)
c = core.fmtc.transfer(c, transs="linear", transd="linear", gcor=2.2, fulls=True, fulld=True)
# Now linear light, ready for primaries conversion
Anything wrong with this approach? Any particular reason there isn't a "170m" transfer preset? There is a "170m" preset for primaries.
Primaries are different between BT.601-7 625 "PAL", "BT.601-7 525 NTSC", and 709
Transfer function is the same.
If you look on Table E.4 in the REC-H.265 document, it's all the same transfer equation for ITU-R BT.709-6, ITU-R BT.601-7 525 or 625, SMPTE ST 170 (2004)
Another reference
ITU-R BT.709 transfer function. Gamma 2.2 curve with a linear segment in the lower range. This transfer function is used in BT.709, BT.601, SMPTE 296M, SMPTE 170M, BT.470, and SPMTE 274M. In addition BT-1361 uses this function within the range [0...1].
https://docs.microsoft.com/en-us/windows/win32/api/mfobjects/ne-mfobjects-mfvideotransferfunction
But I don't know if the fmtc code has it implemented the same way as the ITU documents, you'd have to check the code
You have transs="linear" , but it should be one of the others .
On a related note, does anyone know how I might learn whether a given source truly uses BT.601 transfer to SMPTE 170M transfer? It seems hard to tell objectively :)
In general for determining something - colorbars in the source ; or known colors such as a famous logo
But those use the same transfer function
Blue_MiSfit
31st March 2021, 18:31
If the "masters" began as subsampled YUV, then don't forget to convert to RGB in an interlaced aware manner
Yes they're often ProRes 422 HQ (captured from DigiBeta or older). I separated fields before doing the YCbCr to RGB conversion.
ITU-R BT.709 transfer function. Gamma 2.2 curve with a linear segment in the lower range. This transfer function is used in BT.709, BT.601, SMPTE 296M, SMPTE 170M, BT.470, and SPMTE 274M. In addition BT-1361 uses this function within the range [0...1].
I believe this reference is incorrect. sRGB uses Gamma 2.2 (approximately), and BT.601 et al use Gamma 2.4 (approximately). The linked Microsoft document says that sRGB is Gamma 2.4 which is absolutely wrong. Makes me think they got it backwards.
https://en.wikipedia.org/wiki/SRGB
https://en.wikipedia.org/wiki/Rec._709
You have transs="linear" , but it should be one of the others .
This is the workaround I found for specifying a pure power function that's not defined by any of the presets. You set linear as source and destination, and add gcor=<gamma>.
Here's my general purpose script:
# Interlaced processing of a YCbCr source
# High precision primaries conversion
# Outputs either in 16 bit RGB or a specified YCbCr variant
# Required Variables:
# Variable Name | Example
#------------------------------------
# inputFile ntsc.mov
# tffFlag 0
# sourceMatrix 601
# sourcePrimaries 170m
# sourceTransfer 1886
# outputPrimaries 709
# outputTransfer 1886
# Optional Variables:
# Variable Name | Example
#------------------------------------
# outputMatrix 709
# outputCss 422
# outputBits 10
# If you omit the above optional variables, you'll get 16 bit RGB output
# sourceGamma 2.2
# Including the above will treat the input as having a constant gamma (ignoring any sourceTransfer specified)
import vapoursynth as vs
core = vs.get_core()
source = core.ffms2.Source(inputFile)
## Split interlaced fields into interleaved frames
c = core.std.SeparateFields(source, tffFlag)
## Move into 16 bit full range RGB
c = core.fmtc.resample(c, css="444")
c = core.fmtc.matrix(c, mats=sourceMatrix, fulls=False, fulld=True)
## Move into linear light (no gamma correction)
if 'sourceGamma' in locals():
# Special case for constant gamma 2.2 in input
c = core.fmtc.transfer(c, transs="linear", transd="linear", gcor=sourceGamma, fulls=True, fulld=True)
else:
c = core.fmtc.transfer(c, transs=sourceTransfer, transd="linear", fulls=True, fulld=True)
## Convert primaries
c = core.fmtc.primaries(c, prims=sourcePrimaries, primd=outputPrimaries)
## Re-apply transfer function
c = core.fmtc.transfer(c, transs="linear", transd=outputTransfer, fulls=True, fulld=True)
## Go back to YCbCr if needed
if 'outputCss' in locals():
c = core.fmtc.matrix(c, mat=outputMatrix, fulls=True, fulld=False)
c = core.fmtc.resample(c, css=outputCss)
c = core.fmtc.bitdepth(c, bits=outputBits)
## Weave back into interlaced frames
c = core.std.DoubleWeave(c, tffFlag)
c = core.std.SelectEvery(c, 2, 0)
c.set_output()
poisondeathray
31st March 2021, 18:56
I believe this reference is incorrect. sRGB uses Gamma 2.2 (approximately), and BT.601 et al use Gamma 2.4 (approximately). The linked Microsoft document says that sRGB is Gamma 2.4 which is absolutely wrong. Makes me think they got it backwards.
I see no inconsistency. I think it's just bad phrasing
sRGB transfer function. Gamma 2.4 curve with a linear segment in the lower range.
The 2.4 refers to the curved portion only . The overall average is ~ 2.2 . It cannot be described with a single number if you look at the curve
The overall gamma is approximately 2.2, consisting of a linear (gamma 1.0) section near black, and a non-linear section elsewhere involving a 2.4 exponent and a gamma (slope of log output versus log input) changing from 1.0 through about 2.3.
If you plot the actual sRGB transfer function against actual 2.2 constant, you see it much more curved in the lower section compared to 2.2
scroll down to the sRGB vs. 2.2 svg
https://nick-shaw.github.io/cinematiccolor/common-rgb-color-spaces.html#x34-1330004.1
This is the workaround I found for specifying a pure power function that's not defined by any of the presets. You set linear as source and destination, and add gcor=<gamma>.
But mathematically that should give you different results than actual SMPTE 170 according to the function defined in the ITU documents, because it's not pure 2.2
Blue_MiSfit
31st March 2021, 21:08
I see. Very useful information.
The info I've been given by the color scientists I work with is that treating a SMTPE 170M source as pure gamma 2.2 when linearizing it for SMPTE 170M to BT. 709 primaries conversion, then going to BT.1886 after is the correct thing to do. I'll follow up and ask more questions.
Thanks for your thoughts :)
poisondeathray
31st March 2021, 22:10
It might be that the original source(s) used the incorrect 2.2 , instead of the actual OETF described in SMPTE170M. There is a note in SMPTE 170M-2004 that 2.2 not correct... but if you assume they actually used 2.2, then that's what I'd use, because that's what was actually used... not 170M specs
SMPTE170M-2004
The description above is a more technically correct definition of the transfer function (gamma correction), particularly in dark areas of the picture, than the form used in older documents, viz., ''having a transfer gradient (gamma exponent) of 2.2 associated with each primary'' and ''signals shall be gamma corrected through application of an exponential transfer gradient inverse to that assumed in the display; i.e., 1/2.2 (0.455...).''
vBulletin® v3.8.11, Copyright ©2000-2025, vBulletin Solutions Inc.