HDR -> SDR conversion

These CUDA filters are packaged into DGDecodeNV, which is part of DGDecNV.
User avatar
Selur
Posts: 134
Joined: Mon Nov 05, 2012 3:49 pm
Location: Germany
Contact:

Re: HDR -> SDR tonemapping

Post by Selur »

Small note about nvhsp: It only patches the first header and is only meant for the output of nvencc, but is not really needed nowadays since nvencc itself supports hdr related signaling.

@admin: Are you planning to port DGTonemap to Vapoursynth? (I'd like a gpu based alternative to tonemap)

Cu Selur
DAE avatar
Guest

Re: HDR -> SDR tonemapping

Post by Guest »

NVEncC does accept the HDR signalling through the command line since a few versions ago.
Both work, so they are both good, depending on workflow
User avatar
admin
Posts: 4551
Joined: Thu Sep 09, 2010 3:08 pm

Re: HDR -> SDR tonemapping

Post by admin »

Thanks for the useful info, guys.
Selur wrote:
Sun Apr 01, 2018 6:41 am
Are you planning to port DGTonemap to Vapoursynth? (I'd like a gpu based alternative to tonemap)
Yes, I will. And I'm depackaging filters so they can be open-sourced. Only DGSource() will remain closed.
DAE avatar
Nginx
Posts: 26
Joined: Fri Mar 23, 2018 12:48 am

Re: HDR -> SDR tonemapping

Post by Nginx »

admin wrote:
Sun Apr 01, 2018 7:40 am
Thanks for the useful info, guys.
Selur wrote:
Sun Apr 01, 2018 6:41 am
Are you planning to port DGTonemap to Vapoursynth? (I'd like a gpu based alternative to tonemap)
Yes, I will. And I'm depackaging filters so they can be open-sourced. Only DGSource() will remain closed.
Sounds real great. :hat:
User avatar
admin
Posts: 4551
Joined: Thu Sep 09, 2010 3:08 pm

Re: HDR -> SDR tonemapping

Post by admin »

Ready for a splash of ice water in the face, guys? I already took one. :(

Here's the deal. A CUDA version of DGTonemap is currently useless, for two reasons: a) the overhead of transferring three (R, G, B) float images to/from the GPU makes the CUDA version perform worse than simply doing things in SW with a decent prefetch, and b) the overall time is heavily dominated by the SW conversions, so that even if the tonemapping part performed better with CUDA, it would have little overall effect.

So, what is the morale of this story? Everything has to be done on the GPU, i.e, ship up one 16-bit frame, convert to 709 and tonemap, and ship back the frame. Then we could expect a large speedup. Got my work cut out for me. :scratch:
DAE avatar
Guest

Re: HDR -> SDR tonemapping

Post by Guest »

Ready for a splash of ice water in the face, guys? I already took one. :(
Don't go swimming in Lake Erie yet ;)
As for the rest, if anyone can do it you can
Don't forget your paper reviews, or to relax every so often.
The software version already gives us a good solution
User avatar
admin
Posts: 4551
Joined: Thu Sep 09, 2010 3:08 pm

Re: HDR -> SDR tonemapping

Post by admin »

Reviews are done, but I'm going to take your advice to relax. :lol:
User avatar
hydra3333
Posts: 394
Joined: Wed Oct 06, 2010 3:34 am
Contact:

Re: HDR -> SDR tonemapping

Post by hydra3333 »

8-)
I really do like it here.
DAE avatar
dmcs
Posts: 36
Joined: Sat Oct 21, 2017 9:40 pm

Re: HDR -> SDR tonemapping

Post by dmcs »

I have a question. In the DGTonemap's manual, the formula for Hable is

Code: Select all

hable(x) = ((x*(a*x+c*b)+d*e) / (x*(a*x+b)+d*f)) - e/f
Can you tell me what the "x" variable means? Thank you.
User avatar
admin
Posts: 4551
Joined: Thu Sep 09, 2010 3:08 pm

Re: HDR -> SDR tonemapping

Post by admin »

It's easiest just to give you the code.

Code: Select all

#include "windows.h"
#include "avisynth.h"
#include "stdio.h"

class DGReinhard : public GenericVideoFilter
{
	float contrast;
	float bright;

public:
	DGReinhard(PClip _child, float _contrast, float _bright, IScriptEnvironment* env) : GenericVideoFilter(_child)
	{
		if (vi.pixel_type != VideoInfo::CS_RGBPS)
		{
			env->ThrowError("DGReinhard: input must be CS_RGBPS");
		}
		contrast = _contrast;
		bright = _bright;
	}
    PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* env);
};

PVideoFrame __stdcall DGReinhard::GetFrame(int n, IScriptEnvironment* env)
{
    PVideoFrame src = child->GetFrame(n, env);
	PVideoFrame dst = env->NewVideoFrame(vi);

	float *srcpR, *srcpG, *srcpB;
	float *dstpR, *dstpG, *dstpB;

	const int src_pitch = src->GetPitch(PLANAR_R) / 4;
	const int dst_pitch = dst->GetPitch(PLANAR_R) / 4;
	const int row_size = dst->GetRowSize(PLANAR_R);
	const int width = vi.width;
	const int height = dst->GetHeight(PLANAR_R);

	const float offset = (1.0f - contrast) / contrast;
	const float factor = (bright + offset) / bright;

	srcpR = (float *)src->GetReadPtr(PLANAR_R);
	dstpR = (float *)dst->GetWritePtr(PLANAR_R);
	srcpG = (float *)src->GetReadPtr(PLANAR_G);
	dstpG = (float *)dst->GetWritePtr(PLANAR_G);
	srcpB = (float *)src->GetReadPtr(PLANAR_B);
	dstpB = (float *)dst->GetWritePtr(PLANAR_B);
	for (int y = 0; y < height; y++)
	{
		for (int x = 0; x < width; x++)
		{
			dstpR[x] = srcpR[x] / (srcpR[x] + offset) * factor;
			dstpG[x] = srcpG[x] / (srcpG[x] + offset) * factor;
			dstpB[x] = srcpB[x] / (srcpB[x] + offset) * factor;
		}
		srcpR += src_pitch;
		srcpG += src_pitch;
		srcpB += src_pitch;
		dstpR += dst_pitch;
		dstpG += dst_pitch;
		dstpB += dst_pitch;
	}

	return dst;
}

class DGHable : public GenericVideoFilter
{
	float exposure, a, b, c, d, e, f, w;

public:
	DGHable(PClip _child, float _exposure,
		float _a,
		float _b,
		float _c,
		float _d,
		float _e,
		float _f,
		float _w,
		IScriptEnvironment* env) : GenericVideoFilter(_child)
	{
		if (vi.pixel_type != VideoInfo::CS_RGBPS)
		{
			env->ThrowError("DGHable: input must be CS_RGBPS");
		}
		exposure = _exposure;
		a = _a;
		b = _b;
		c = _c;
		d = _d;
		e = _e;
		f = _f;
		w = _w;
	}

	float hable(float in)
	{
		return (in * (in * a + b * c) + d * e) / (in * (in * a + b) + d * f) - e / f;
	}

	PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* env);
};

PVideoFrame __stdcall DGHable::GetFrame(int n, IScriptEnvironment* env)
{
	PVideoFrame src = child->GetFrame(n, env);
	PVideoFrame dst = env->NewVideoFrame(vi);

	float *srcpR, *srcpG, *srcpB;
	float *dstpR, *dstpG, *dstpB;

	const int src_pitch = src->GetPitch(PLANAR_R) / 4;
	const int dst_pitch = dst->GetPitch(PLANAR_R) / 4;
	const int row_size = dst->GetRowSize(PLANAR_R);
	const int width = vi.width;
	const int height = dst->GetHeight(PLANAR_R);

	srcpR = (float *)src->GetReadPtr(PLANAR_R);
	dstpR = (float *)dst->GetWritePtr(PLANAR_R);
	srcpG = (float *)src->GetReadPtr(PLANAR_G);
	dstpG = (float *)dst->GetWritePtr(PLANAR_G);
	srcpB = (float *)src->GetReadPtr(PLANAR_B);
	dstpB = (float *)dst->GetWritePtr(PLANAR_B);

	float whitescale = 1.0f / hable(w);

	for (int y = 0; y < height; y++)
	{
		for (int x = 0; x < width; x++)
		{
			dstpR[x] = hable(exposure * srcpR[x]) * whitescale;
			dstpG[x] = hable(exposure * srcpG[x]) * whitescale;
			dstpB[x] = hable(exposure * srcpB[x]) * whitescale;
		}
		srcpR += src_pitch;
		srcpG += src_pitch;
		srcpB += src_pitch;
		dstpR += dst_pitch;
		dstpG += dst_pitch;
		dstpB += dst_pitch;
	}

	return dst;
}

AVSValue __cdecl Create_DGReinhard(AVSValue args, void* user_data, IScriptEnvironment* env)
{
	return new DGReinhard(args[0].AsClip(),
		(float)args[1].AsFloat(0.3f),
		(float)args[2].AsFloat(5.0f),
		env);
}

AVSValue __cdecl Create_DGHable(AVSValue args, void* user_data, IScriptEnvironment* env)
{
	return new DGHable(args[0].AsClip(),
		(float)args[1].AsFloat(2.0f),
		(float)args[2].AsFloat(0.15f),
		(float)args[3].AsFloat(0.50f),
		(float)args[4].AsFloat(0.10f),
		(float)args[5].AsFloat(0.20f),
		(float)args[6].AsFloat(0.02f),
		(float)args[7].AsFloat(0.30f),
		(float)args[8].AsFloat(11.20f),
		env);
}

const AVS_Linkage *AVS_linkage = 0;
extern "C" __declspec(dllexport) const char* __stdcall AvisynthPluginInit3(IScriptEnvironment* env, AVS_Linkage* vectors)
{
	AVS_linkage = vectors;
	env->AddFunction("DGReinhard", "c[contrast]f[bright]f", Create_DGReinhard, 0);
	env->AddFunction("DGHable", "c[exposure]f[a]f[b]f[c]f[d]f[e]f[f]f[w]f", Create_DGHable, 0);
	return 0;
}
User avatar
admin
Posts: 4551
Joined: Thu Sep 09, 2010 3:08 pm

Re: HDR -> SDR tonemapping

Post by admin »

I'm recommending a small change to the Avisynth+ script that improves things a bit. Note the ConvertBits() call:

loadplugin("DGDecodeNV.dll")
loadplugin("DGTonemap.dll")
loadplugin("avsresize.dll")
SetFilterMTMode("z_ConvertFormat", MT_MULTI_INSTANCE) # May not be needed.
DGSource("THE GREAT WALL.dgi",fulldepth=true)
ConvertBits(10) # use 12 for 12-bit
z_ConvertFormat(pixel_type="RGBPS",colorspace_op="2020ncl:st2084:2020:l=>rgb:linear:2020:l", dither_type="none")
# Choose one of these:
DGReinhard()
#DGHable()
z_ConvertFormat(pixel_type="YV12",colorspace_op="rgb:linear:2020:l=>709:709:709:l",dither_type="ordered")
prefetch(4)
DAE avatar
Guest

Re: HDR -> SDR tonemapping

Post by Guest »

Do you require any testing or comparisons done with the change?
User avatar
admin
Posts: 4551
Joined: Thu Sep 09, 2010 3:08 pm

Re: HDR -> SDR tonemapping

Post by admin »

No thank you, gonca, I've tested it and confirmed to my satisfaction that it works a little better. If you stumble across anything, though, please report it.

I got an email from someone thinking there is no difference between fulldepth=true and fulldepth=false. The difference between 8 and 10 bits is just 2 bits, i.e., a maximum difference of 4. So if you just look at the frames, you may not see a gross difference, especially if there are no large flat areas where you would see banding. However, if you do a subtract between the two and then do levels in VirtualDub(2) and preview/sample frame, you can see the spike of differences that is 4 pixels wide. If you zoom in the two ends of the Input levels slider to just bracket the spike, then you can see the detail that is being retained in those lower two bits. Those 2 bits break what would have been a difference of 1 to a difference of 4, or to say it another way, the granularity is improved by a factor of 4, and that's why banding is significantly reduced.
DAE avatar
Guest

Re: HDR -> SDR tonemapping

Post by Guest »

Okay.
I asked you before if fulldepth=True operated the same as fulldepth=false on 8 bits sources.
I believe you said yes, so I always include True.
10 bit sources get passed as 10 bit, 8 bit sources get passed as 8 bit
I realize that on some sources 8 or 10 bit makes little difference, but on some the extra 2 bits can make a lot of difference on detail and color retention.
One question though
The ConvertBits(10) call comes after DGSource(xxx, fulldepth=True)
Shouldn't the output of DGSource already be 10 bit?
I do realize that if the source is 8 bit running it in 10 bit can be beneficial, especially in terms of banding, but the source should already be 10 bit as this should be a hdr source.
I am not questioning you so much as I am asking for info/knowledge on the why
Either way thank you for your continuous efforts and improvements to your software :salute:
User avatar
admin
Posts: 4551
Joined: Thu Sep 09, 2010 3:08 pm

Re: HDR -> SDR tonemapping

Post by admin »

DGSource(fulldepth=true) outputs 16-bit always, with the extra bits zeroed, i.e., for 10-bit source, the lower 6 bits are zero. So the ConvertBits() call gets rid of the extra lower bits. I did a subtract+levels test between the frames with and without the ConvertBits() and was able to see some extra detail being retained with the ConvertBits().

I welcome being questioned because I am a seeker after truth and things should stand up to scrutiny and healthy skepticism.

Off to the pool!
DAE avatar
Guest

Re: HDR -> SDR tonemapping

Post by Guest »

Quick 2 questions
On hdr to hdr cases would you recommend using ConvertBits(10) for 10 bit sources to 10 bit output

On sdr to sdr cases would you recommend using ConvertBits(8) or fulldepth=False (8bit source to 8 bit output) :?:
User avatar
admin
Posts: 4551
Joined: Thu Sep 09, 2010 3:08 pm

Re: HDR -> SDR tonemapping

Post by admin »

gonca wrote:
Wed Apr 18, 2018 5:59 pm
On hdr to hdr cases would you recommend using ConvertBits(10) for 10 bit sources to 10 bit output?
It depends on what your encoder wants to receive. Otherwise, it doesn't matter, unless you doing some gamma adjustment or something unusual like that. In that case, umm, it depends. :lol:
On sdr to sdr cases would you recommend using ConvertBits(8) or fulldepth=False (8bit source to 8 bit output)?
Neither one is necessary as 8-bit will always be delivered as 8-bit. Anyway, fulldepth=false by default.
DAE avatar
Guest

Re: HDR -> SDR tonemapping

Post by Guest »

Do you know how to use ConvertBits() in VS? Is it doable?
Would fmtconv work as well as ConvertBits()?
I guess I can always convert my scrips to avs+ again
User avatar
DJATOM
Posts: 176
Joined: Fri Oct 16, 2015 6:14 pm

Re: HDR -> SDR tonemapping

Post by DJATOM »

fmtc can do bits conversion. If you don't want to convert with fmtc, internal resizers can perform bits conversion as well:

Code: Select all

from vapoursynth import core, YUV420P16
...
clip = core.resize.Point(clip, format=YUV420P16)
PC: RTX 2070 | Ryzen R9 5950X (no OC) | 64 GB RAM
Notebook: RTX 4060 | Ryzen R9 7945HX | 32 GB RAM
DAE avatar
Guest

Re: HDR -> SDR tonemapping

Post by Guest »

Thank you
ConvertBits apparently drops the padded (zeroed) bits
fmtc dithers down
Would this affect the quality?
How do the internal resizers function, dithering, dropping padded zeroes?
User avatar
admin
Posts: 4551
Joined: Thu Sep 09, 2010 3:08 pm

Re: HDR -> SDR tonemapping

Post by admin »

Now, that is a great question, because if they are dithering down, then the "extra detail" I saw may be just dithering. I will check the Avisynth+ and Vapoursynth source code today.
User avatar
DJATOM
Posts: 176
Joined: Fri Oct 16, 2015 6:14 pm

Re: HDR -> SDR tonemapping

Post by DJATOM »

ConvertBits(8) just drops the Least significant bit, so you need to add dither=0 or 1, whatever method you want to use there.
PC: RTX 2070 | Ryzen R9 5950X (no OC) | 64 GB RAM
Notebook: RTX 4060 | Ryzen R9 7945HX | 32 GB RAM
DAE avatar
Guest

Re: HDR -> SDR tonemapping

Post by Guest »

According to this link the default is to not add dither
http://avisynth.nl/index.php/ConvertBits

dither = -1
User avatar
admin
Posts: 4551
Joined: Thu Sep 09, 2010 3:08 pm

Re: HDR -> SDR tonemapping

Post by admin »

You beat me to it, I was just looking over there. That's what we want, no dithering. Thanks, guys.
User avatar
admin
Posts: 4551
Joined: Thu Sep 09, 2010 3:08 pm

Re: HDR -> SDR tonemapping

Post by admin »

DJATOM wrote:
Thu Apr 19, 2018 4:42 am

Code: Select all

clip = core.resize.Point(clip, format=YUV420P16)
That doesn't convert to 10-bit. I think this is correct:

import vapoursynth as vs
core = vs.get_core()
...
clip = core.resize.Point(clip, format=vs.YUV420P10)
...
Post Reply