Solving dynamic range problems in a linear way


What about something like this
So you could try a custom log by eyes :slight_smile:


Curious as to what you think the Python sample above does? :wink:

(Aurélien Pierre) #23

Does that work in RGB space ? I get weird results with that algorithm (very dark).


It works on RGB encoding models. Should end up with a loggy flat looking image. Check to make sure you are using log2?

(Aurélien Pierre) #25

I use that log function in C :

static inline float Log2( float x)
  return logf(x) / logf(2);


for(size_t k = 0; k < (size_t)ch * roi_out->width * roi_out->height; k++)
    if (((float *)ivoid)[k] < 0.f)
      ((float *)ovoid)[k] = 0.f;
      float lg2 = Log2( ((float *)ivoid)[k] / avg );
      lg2 = (lg2 - min) / (max - min);

      if (lg2 >= 0.f)
        ((float *)ovoid)[k] = lg2;
        ((float *)ovoid)[k] = 0.f;

Note ivoid is the RGB input, ovoid is the RGB output, min, max and avg are computed on the whole RGB input.

I get that in dt :

(Ingo Weyrich) #26

How about x <= 0 ? You should exclude that at least

(Aurélien Pierre) #27

I do that check on x before calling Log2, see my post edit

(Ingo Weyrich) #28

Hmm, if I’m not totally wrong there is a log2(0) case still possible…

(Aurélien Pierre) #29

I would see NaN in the output, wouldn’t I ?

(Aurélien Pierre) #30

Duh, log2(x) > 0 for all x > 1. That’s the first error


lg2 = numpy.log2(inValue / middleGrey + 1)

for starters. Always check the boundaries…


The Python pasted works fine.

MiddleGrey there is scene linear middle grey of course.

(Aurélien Pierre) #32

Confirmed in both python and darktable, you have to offset the log2 by 1, otherwise you get negative values in lg2 for inValue < middleGrey


Not on this end. Also matches exactly what is in the ACES 1.0.3 configuration CTL files.

It normalizes fine.

(Aurélien Pierre) #34

given that my inputs are in [0;1] and that, that’s basic math : if I don’t offset by 2, I get only negative values.

Now I get


Your inputs will be scene referred 0.0 to infinity. You have to scale your linear camera values according to your camera’s native response and where it pegs the linear middle grey value.

Remember it is a normalized log.

(Aurélien Pierre) #36

What do you call a normalized log and the camera native response ?


The normalized log ends up an output referred encoding 0.0 to 1.0, which maps to some arbitrarily low to high scene referred set of values.

The native camera encoding raw will be a device referred encoding 0.0 to 1.0, but is encoded linear. That means the middle grey value will be mapped to some low, unknown value. Typically, with a 12 stop camera, you will need to apply a multiplier of somewhere around one to three stops to land the camera middle grey encoded value to 0.18, camera depending. If you perform a bracketed merge, the value range will depend on the brackets used.

(Aurélien Pierre) #38

Oh boy, where do I find this info ? To be frank, it works pretty well as it is.


Put the whole normalized log in and you should see good results.

Every camera will have an arbitrary encoded value range covering an arbitrary dynamic range. Given that the values are linear, that tells you that the encoded value at 0.18 out of camera isn’t a fixed value. That is, the value that we would photograph off of a grey card will end up encoded to some arbitrary value.

To properly scale that value to a known middle grey scene referred value for the purpose of a fixed camera rendering transform, it is a pure multiply. So for example, a Canon might produce a linear normalized 16 bit TIFF that will require a 1.5 stop adjustment to slide the middle grey value to 0.18. The math would be 2^1.5. This would yield a series of scene referred values ranging from the low value up to 2.828427124746.

That is why normalized log inputs are zero to infinity.

(Aurélien Pierre) #40

So, taking care of your 1.5 factor, that would lead to :

        lg2 = 1.5 * numpy.log2(inValue / middleGrey)
        outLog[index] = (lg2 - minExposure) / (maxExposure - minExposure)


In my case, minExposure = 0 (the black level is adjusted in a previous module), so that let me with (maxExposure - minExposure) which is the dynamic range set by the user, so I guess I don’t need to mess with this factor.

I still don’t get what magic gets log2(x) > 0 when x = ]0 ; 1]…