Pixel values modified after Tiff creation

Hi all,

my colleague and I found a weird problem that we can't explain. Our goal is simply to create a tiff from a PhaseOne IIQ format without any processing done except the interpolation for the demosaicing.
While trying to use the tiff creation functionality of libraw we couldn't get the pixel value we wanted and came to the conclusion that something is modifying the pixel values during the tiff creation process.
When I try to do a simple test, I set all the pixel values to 1000 directly in the code but while opening the tiff I was not able to get the value 1000, instead it was 4500 for some reason. Saving the image using openCV produced the correct value. The range of the pixel value is to 65536 for 16 bit unsigned type.

Maybe I m missing something here while activating some option. Any help is welcome.

Thanks in advance

Greg

/** Output options *//
RawProcessor.imgdata.params.output_tiff = 1; // TIFF output
RawProcessor.imgdata.params.output_bps = 16; // 16-bit output
RawProcessor.imgdata.params.no_auto_bright = 1; // We do not want auto brightness
 
...
 
/** Part changing the pixel values */
 
for (int rc = 0; rc < cols * rows; ++rc)
{
  RawProcessor.imgdata.image[rc][0] = 1000;
  RawProcessor.imgdata.image[rc][1] = 1000;
  RawProcessor.imgdata.image[rc][2] = 1000;
}
 
...
 
/** This part will create the tiff **/
int res = RawProcessor.dcraw_ppm_tiff_writer(sTiffName.c_str());
 
<cpp>

Forums: 

dcraw_ppm_tiff_writer()

dcraw_ppm_tiff_writer() applies gamma curve on write (imgdata.image[] is linear, while output is usually not).

To set gamma curve to linear use
imgdata.params.gamm[0] = imgdata.params.gamm[1] = 1.0;

-- Alex Tutubalin @LibRaw LLC

Please also note that LibRaw

Please also note that LibRaw will typically scale your raw values between the black and white levels during the linearization step (e.g. 65535*(x - black)/(white-black)), so you might get larger values even for gamma=1.0.

Unfortunately I found that if you set no_auto_scale as well to disable this, you also kill LibRaw's white balance for some reason, which is typically not good for the demosaicing step, see https://github.com/letmaik/rawpy/issues/101

There are two options for

There are two options for (no) auto-scaling:

1) params.no_auto_bright - disables ETTR(-like) automated brightness correction, entire image is scaled to 65535/(metadata_derived_maximum-black) instead of 65535/(real_data_max_by_histogram - black).

2) params.no_auto_scale - disables entire scale_colors() call (for example, to get not modified data in image array).

Second case is special use (e.g. someone may want to do own interpolation via callback and want to see unchanged data on this step).

In (normal) processing case scaling is significant to get same scaled data from all different sensor bit-counts.

-- Alex Tutubalin @LibRaw LLC

I guess what confused me was

I guess what confused me was that scale_colors() also includes (or rather excludes when disabled) white balance, which is usually important in exactly the advanced interpolation use case you described... no biggie, because the advanced user could easily do the white balance before their custom interpolation, it was just unexpected/undocumented, so at least making it somewhat more explicit in the API docs would help avoid having to find out the hard way ;)

This is for some special case

This is for some special case, asked by our users (I can’t remember the specific details), not targeted for 'normal' use, so not documented in details.

The documentation could be better, it's true :)

P.S. Yes, scale_colors() do range scale and WB in single step.

-- Alex Tutubalin @LibRaw LLC

It works using your

It works using your suggestion. Thank you. Do you happen to know what is the exact mathematical formula f(x) for the gamma correction and how the two following parameters are used ?

imgdata.params.gamm[0] 
imgdata.params.gamm[1] 

I reckon the first one is the power and the second one is the toe. I have no clues for the second one. Also I suppose that the variable x range should be between 0 and 1. I may have to reuse the gamma correction in a separate process later down the processing pipeline. Thank you very much.

Thanks. I was able to find

Thanks. I was able to find the function in curves.cpp. Its more complicated that I thought.

I m guessing that gamma[0] is pwr and gamma[1] is ts. I don't know what mode is but 1 seems to work for me.

void LibRaw::gamma_curve(double pwr, double ts, int mode, int imax)
{
  int i;
  double g[6], bnd[2] = {0, 0}, r;
 
  g[0] = pwr;
  g[1] = ts;
  g[2] = g[3] = g[4] = 0;
  bnd[g[1] >= 1] = 1;
  if (g[1] && (g[1] - 1) * (g[0] - 1) <= 0)
  {
    for (i = 0; i < 48; i++)
    {
      g[2] = (bnd[0] + bnd[1]) / 2;
      if (g[0])
        bnd[(pow(g[2] / g[1], -g[0]) - 1) / g[0] - 1 / g[2] > -1] = g[2];
      else
        bnd[g[2] / exp(1 - 1 / g[2]) < g[1]] = g[2];
    }
    g[3] = g[2] / g[1];
    if (g[0])
      g[4] = g[2] * (1 / g[0] - 1);
  }
  if (g[0])
    g[5] = 1 / (g[1] * SQR(g[3]) / 2 - g[4] * (1 - g[3]) +
                (1 - pow(g[3], 1 + g[0])) * (1 + g[4]) / (1 + g[0])) -
           1;
  else
    g[5] = 1 / (g[1] * SQR(g[3]) / 2 + 1 - g[2] - g[3] -
                g[2] * g[3] * (log(g[3]) - 1)) -
           1;
  if (!mode--)
  {
    memcpy(gamm, g, sizeof gamm);
    return;
  }
  for (i = 0; i < 0x10000; i++)
  {
    curve[i] = 0xffff;
    if ((r = (double)i / imax) < 1)
      curve[i] =
          0x10000 *
          (mode ? (r < g[3] ? r * g[1]
                            : (g[0] ? pow(r, g[0]) * (1 + g[4]) - g[4]
                                    : log(r) * g[2] + 1))
                : (r < g[2] ? r / g[1]
                            : (g[0] ? pow((r + g[4]) / (1 + g[4]), 1 / g[0])
                                    : exp((r - 1) / g[2]))));
  }
}