Accessing float32 pixels from tiff or tiff->RGB-raw in pure C language

I used tiffdump to get the bitmap offset from a float tiff I wrote from developing a Nikon .nef file in RawTherapee 5.9. I expected to be able to fseek past the headed, read the file from there into memory into a float array. The numbers are all mangled and not in the [0.0 … 1.0] range.

I converted the .tif to a .rgb with ImageMagick and then converted the .raw to a .jpg to make sure it worked. The .jpg looks perfect.

Is there something else you have to do other than read the RGB bytes 12 at a time into a float32 array in memory to look at the values? I expect to see the same values shown in RT, values in the [0.0 … 1.0] range. Surely, they are in the IEEE 754 floating point format used by the C language.

Does anybody have a simple C program which reads a single, 12 byte, RGB float pixel from either a tif or an RGB-raw image and prints out the values? Does anybody see the problem in this code?

    // =============================================================================
    // Read and decode a specific pixel in a float32 tiff
    /*
     gcc -g -O0 -m64  bln/float.pixel.test.c -lm -lz           -o bln/float.pixel.test
     
    */
    
    #include "stdio.h"
    #include "stdlib.h"
    #include "stdint.h"
    #include <math.h>
    
    int osrp(char *ifn, size_t soff, size_t numf);  // Open, Seek, Read, Print
    
    // =============================================================================
    void main(int argc, char *argv[])  {
    char tfn[]={ "pf-274887.edge.f32.tif" };      // From RawTherapee
    char rfn[]={ "pf-274887.edge.f32.tif.rgb" };  // From ImageMagick
    size_t toff=13022ll, roff=0, pixoff=0; // Tif and raw-RGB header Offsets 
    int imgx=3, imgy=3, imgw=4916, pix_bytes=12;
	    pixoff = (imgy * imgw + imgx) * pix_bytes;  // Byte offet to pixel (3,3)
	    pixoff = 0ll;  // Try pixel (0, 0)
	    osrp(tfn, toff+pixoff, 3);  // Open, seek, read, print
	    osrp(rfn, roff+pixoff, 3);  // FileName, seek_OFFset, #_floats per pixel
	    exit(0);                    // 
    }  // End Main(). 
    // =============================================================================
    
    
    // =============================================================================
    // Open, seek, read, print. Expect floats in (0.0 .. 1.0) range
    // Check for errors in number of floats read and NAN on their values
    int osrp(char *ifn, size_t soff, size_t numf)  {
    union  {  // Overlay floats and 32 bit unsigned ints
	    float    fpix[4];  // Holds 1 pixel
	    uint32_t ipix[4];  // UI32 and float share same bits	
    } fiu;             // Float-Int-Union
    FILE *ifp;         // Image_File_Pointer
    int ii, numnan=0;  // Number of NANs, Not_A_Numbers
    size_t numr;       // uint64_t
    
	    ifp = fopen(ifn, "rb");      // Read Image in Binary mode
	    fseek(ifp, soff, SEEK_SET);  // Seek to start of bitmap data
								     
	    // Read numf * 4 bytes from bitmap, store in uint/float union
	    numr = fread((void *)fiu.fpix, sizeof(float), numf, ifp);  // 3*4=12B  
	    fclose(ifp); 
	    if(numr != numf)  {  
		    printf("ERROR! NumR %llu != NumF %llu\n", numr, numf);  }
    
	    printf("%s: Seek -> %lld, Read %llu: \nfloat:  B %8.6f, G %8.6f, R %8.6f -> \nUINT32: B %10u, G %10u, R %10u\n",
		    ifn, soff, numf, 
		    fiu.fpix[0], fiu.fpix[1], fiu.fpix[2],
		    fiu.ipix[0], fiu.ipix[1], fiu.ipix[2]);
    
	    for(ii=0; ii < numf; ii++)  {  // Check for Non-Number floats
		    if(isnan(fiu.fpix[ii]))  {  numnan++;  }  // Count NANs
	    }
	    if(numnan == 0)  {  printf("\n");  return(0);  }
	    printf("%d floats, %d are NANs!\n\n", numf, numnan);
	    return(numnan);
    }

Running the program:

    float.pixel.test
    pf-274887.edge.f32.tif: Seek -> 13022, Read 3: 
    float:  B -0.000000, G -0.000003, R 0.000000 -> 
    UINT32: B 2634654270, G 3059326270, R  457414589
    
    pf-274887.edge.f32.tif.rgb: Seek -> 0, Read 3: 
    float:  B 35650027520.000000, G 1450022528.000000, R 0.000000 -> 
    UINT32: B 1359269508, G 1319951149, R          0


The RawTherapee values shown at pixel (0, 0) are (22.7%, 31.0% and 0%).

Mainly, you can’t just union same-sized floats and integers to produce equivalent values, you need to do a type-cast assignment.

Also keep in mind: Tiffs can be generated as little-endian on intel or big-endian on mac. The first two bytes tell you II for intel and MM for mac.

Not mac. (Well, not mac of today at least.) “MM” is for Motorola.

Anyhow, this, and casting is one thing.

Maybe there is compression as well?

The offset also looks weird, it’s usually even (word boundary). You get the offset to the data by inspecting the StripeOffset or TileOffset. But, there can be more than one stripe/tile, and it’s not guaranteed they will be stored back to back as one contiguous stream.

Finally, why are you writing your own TIFF parser? Just use libtiff.

2 Likes

Hiram,
The floats aren’t mangled by intel’s braindead byte swapping, just ints of 2+ bytes. I will never buy another cpu with this defect as long as I live.

Plus, I am reading them from a (void *) by the byte and stuffing directly into a float array.

I just happened to overlay the float array with a uint32 array so I could easily print them in hex and see the bit patterns.

I don’t see any possibility of byte swapping here.

B

KMilos,
There is no compression. There is an option in RawTherapee for uncompressed and I used it.

I converted the tif to rgb with ImageMagick and the IM file is exactly $xres * $yres * 12 bytes long. Then, I converted the .rgb file to a .jpg and it looked perfect so I am confident that the .rgb is correct.

IMAGEMAGICK CONVERSION: ------
convert pf-274887.edge.f32.tif -depth 32 RGB:pf-274887.edge.f32.tif.rgb
convert -size 4916x7370 -depth 32 RGB:pf-274887.edge.f32.tif.rgb pf-274887.edge.f32.jpg

The file sizes of the tif and the rgb are 434785284 and 434771040 for a difference of 14244. bytes. After allowing for the 13,022 byte tif header that still leaves
add 14244 -13022 → 1222 bytes left over at the end.

With the tiff spaghetti labyrinth file mal-format, there is no telling what it could be. Part of the data I am looking for could be here.

Exiftool shows the header and bitmap sizes. This is all I am trying to decode:
StripByteCounts : 434771040
StripOffsets : 13022 << Scalar,not array

tiffdump pf-274887.edge.f32.tif agrees:
StripOffsets (273) LONG (4) 1<13022>
StripByteCounts (279) LONG (4) 1<434771040>

I am seeking to byte [13022] and then dumping that memory directly into a float array. The values are not in the [0.0 … 1.0] range and look nothing like the RT gui shows when you put the mouse on pixel (0, 0).

I may need to call in Shaggy and Scoob on this case!

B

Glenn,
I read the memory after header offset 13022 into a float array which just happened to overlay the uint32 array. This way, I could easily look at the bit fields for the floats to see why they were so far out of the [0.0 … 1.0] expected range.

I wrote a Perl tool to decode the floats I read from the rgb file:

pf-274887.edge.f32.tif.rgb: Seek → 0, Read 3:
float: B 35650027520.000000, G 1450022528.000000, R 0.000000 →
UINT32: B 1359269508, G 1319951149, R 0

The blue float value is >> B 35650027520.000000 <<
The uint32 of the same bit is: >> UINT32: B 1359269508 <<

Putting this decimal value in my KCalc programmer’s calculator, I get this binary:
0b1010001000001001100111010000100

My secret binary to floater decoder ring:
bb.pl 0b1010001000001001100111010000100
prepended 1 zeros, length 31 → 32
Sign bit = 0 → ‘+’ << bit[0]
B2D: 0b10100010 → 162 decimal << Reading the exponent bits [1 … 8]
Fexp = 35 = raw 162 - exp_bias 127 << Applying -127 “bias” to calc exponent
CBF: 8 1s found in bits23, fraction = 0.03755236
<< The significand is in bits [9 … 31], [9] → 1/2, [10] → 1/4, etc.
+1.03755236E+35

The final value should be +1.03755236E+35, but it is not.

I wonder whether the floats are stored in some format other than IEEE 754.

B

I got the stripeOffset from both ExifTool and tiffdump and it is 130022 bytes.
They both also agree on the StripByteCounts : 434771040

mult 434771040 /4916 /7370 /3 = 4
The bitmap size divided by the XY resolution and number of channels gives 4 byte quanta. There is only one stripe.

And, I am reading the first pixel.

There are exactly 36,230,920 float pixels in 434,771,040 bytes of raw data. It should not be hard to extract them directly. I suspect my C code is of by a byte somewhere, but I can’t find it. Some ui64 truncated to 32 bits or a 64 bit read reading the garbage after a 32 bit variable. Or an array overwriting the next variable in your memory segment (so no segv). Haven’t you seen this type of bug a million times? Time for Valgrind to seek out memory overwrites!

Just use libtiff <<
I could go to Red Lobster and order the Salmon if I just wanted Salmon.
I want to know the guts of my data and catch the Salmon. :slight_smile:

Of course they are.

1 Like

They are not, as looking at the SampleFormat tag would tell you.

Without all the TIFF tags shared, all I can say is, good luck fishing! :wink:

KMilos,
I never suspected intel had degenerated the floats too.
I did a uint32 swap on the float/uint32 union and voila:

pf-274887.edge.f32.tif: Seek → 13022, Read 3: [ Pixel (0, 0) ]
Bich swapped: float: R 0.316480, G 0.307325, B -0.073858 →
RawTherapee gui reads pixel (0, 0) = 31.4%, 30.6%, 0%)

Try pixel (3, 3):
Img_xy=(3, 3) → offset_bytes = 177012
pf-274887.edge.f32.tif: Seek → 190034, Read 3:
Bich swapped: float: R 0.230569, G 0.309901, B 0.453831
RawTherapee gui reads pixel (3, 3) = 22.7%, 31.0%, 45.1%)

They are not “quite” right. The tif red is 0.316480 but the RawTherapee reads 31.4%.
And the only really bad one (so far) is the tif blue reading -0.073858 where the GUI reads 0.0%. Maybe the GUI has a hard bottom at 0.0%?

Could they be employing a Gamma function?

The raw-RGB file is still just as mangled with and without swapping and it should be the simple one! Darn.

This explains a lot! Thank you!

B

In TIFF files, floating-point pixel values are typically in the range [0,1].

In ImageMagick RGB files, floating-point [EDIT and integer] pixel values are typically in the range [0,QuantumRange] where QuantumRange is 255 if your IM is Q8, otherwise 65535.

EDIT Correction: 255 for 8-bit values, 65535 for 16-bit values, and 2**32-1 = 4294967295 for 32-bit numbers.

Does that explain the problem?

1 Like

Alan,

QuantumRange is 255 if your IM is Q8, otherwise 65535.
Does that explain the problem?

Her is my test results on the RGB file:
float.pixel.test (with printf %f replaced with %g)

pf-274887.edge.f32.tif.rgb: Seek → 0, Read 3:
float: R 35650027520.000000, G 1450022528.000000, B 0.000000 →
UINT32: R 1359269508, G 1319951149, B 0
Bich swapped:
float: R -4.84343e-36, G 2.49739e-11, G 0 →
UINT32: R 2228094033, G 769371214, B 0

The RGB file appears to be OK because the JPG made from it looks perfect:
convert -size 4916x7370 -depth 32 RGB:pf-274887.edge.f32.tif.rgb
pf-274887.edge.f32.jpg

Checking the jpg with RawTherapee:
(0, 0) = (32.2%, 29.0%, 0%); (3, 3) = (23.1%, 31.0%, 44.7%); // JPG
(0, 0) = (22.7%, 31.0%, 0%); (3, 3) = (22.7%, 31.0%, 45.1%); // TIF

cat_bin_bytes pf-274887.edge.f32.tif.rgb 0 12 > rgb.0-11.dat
FSeek to 0, cat 12 bytes

vim rgb.0-11.dat and convert to hex shows:
00000000: 84ce 0451 2ddb ac4e 0000 0000 0a

“84ce 0451” → 2228094033 → 0b10000100110011100000010001010001

Calculate the float from the 32 bits:

bb.pl 0b10000100110011100000010001010001  // "84ce 0451"
Sign bit = 1 -> '-'
B2D: 0b00001001 -> 9 decimal
Fexp = -118 = raw 9 - exp_bias 127
CBF: 8 1s found in bits[23](10011100000010001010001), fraction = 0.60950673
-1.60950673E-118  <- does not match the Red -4.84343e-36 above

Bit[0] → sign
bits[1 … 8] -127 = exponent
bits[9 … 31] → fraction, [9]==1 => +=1/2, [10]==1 => +=1/4, [11]==1 => +=1/8 …

To test the fraction part code, I pass in 23 1 bits and it returns 0.99999988

From wikevilpedia:
“The exponent is an 8-bit unsigned integer from 0 to 255, in biased form: an exponent value of 127 represents the actual zero.”

0b10000100110011100000010001010001
  SEEEEEEEEF->fraction
   00001001 -> 0b1001=9, 9-127 = -118; printf %g shows exp = -36

printf does not appear to be printing to the IEEE 754 float standard.
Did I break printf?

Above, you reported:

pf-274887.edge.f32.tif: Seek → 13022, Read 3: [ Pixel (0, 0) ]
Bich swapped: float: R 0.316480, G 0.307325, B -0.073858 →
RawTherapee gui reads pixel (0, 0) = 31.4%, 30.6%, 0%)

And your hex RGB values are:

84ce0451 2ddbac4e 00000000

When creating the RGB file, you didn’t use "-define quantum:format=floating-point ", so those values are 32-bit integers, on a scale of 0 to 2**32-1. (See my edit to my previous post.) The first byte is the least significant. (Note that you could use “-endian MSB” or “-endian LSB” when writing to the RGB: file.)

I reverse the bytes for the calculation:

Red is:

C:\web\im>numBinHex 0x5104ce84

decimal: 1359269508
 binary: 01010001000001001100111010000100
    hex: 0x5104ce84

C:\web\im>ccalc = 1359269508 / (2**32-1) = exit
0.3164795945204048

This is within spitting distance of your result 0.316480.

Green is:

C:\web\im>numBinHex 0x4eacdb2d

decimal: 1319951149
 binary: 01001110101011001101101100101101
    hex: 0x4eacdb2d

C:\web\im>ccalc = 1319951149 / (2**32-1) = exit
0.3073250756848895

This is within spitting distance of your result 0.307325.

Blue is zero. Integer RGB formats can’t record negative numbers.

Note that numBinHex and ccalc are my home-grown tools. Doubtless your O/S has similar tools such as awk and bc, or you could do the calculations in your program.

EDIT: I think you reversed the bytes to get the hex values 84ce0451 2ddbac4e 00000000. I have reversed them again, probably giving the original values you read from the RGB file.

2 Likes

This is the way. (or, for those looking for Python, tifffile)

Did I miss an answer to that question being provided? I fully agree, for something like this, tiffile or libtiff (depending on your language) are the way to go.

I finally fixed the quantum question for TIFF files with 32 bit quanta.
I wrote a small program to take a 32 bit tif file and the pixel XY coordinates to investigate.
It prints float, scaled uint32, int32, uint32 and hex values both plain and mangled.
At a glance, you can spot which “standard” format your data are in.

Here is the output

float.pixel.test_t pf-274887.ec0.5.tif 30 40
Cmd > /usr/local/bin/image_info_et.pl pf-274887.ec0.5.tif -sbwtfm < -> rtn > 13022 
217385520 4916 7370 0 0
 < [30] hdr_size=13022, bm_size=217385520, xres=4916, yres=7370, is_float=0,             is_mangled=0
Img_xy=(30, 40) -> offset_bytes = 2360040
pf-274887.ec0.5.tif: Seek -> 2373062, Read 3: 
float:  R -78996330445845561344.00000000, G 0.00000000, B 0.00000000 
Sui32:  R 0.87709099, G 0.11491743, B 0.10022888 
INT32:  R -527890069, G  493566631, B  430479754
UINT32: R 3767077227, G  493566631, B  430479754
Hex:    R 0XE089096B, G 0X1D6B3AA7, B 0X19A8998A
Bich swapped:
float:  R 1.6627393e+26, G -2.5870752e-15, B -1.4796595e-32  
***Sui32:  R 0.41811430, G 0.65323514, B 0.54140711***   << Winner
INT32:  R 1795787232, G -1489343715, B -1969641447
UINT32: R 1795787232, G 2805623581, B 2325325849
Hex:    R 0X6B0989E0, G 0XA73A6B1D, B 0X8A99A819

The only one falling in the [0.0 … 1.0) range is the __BSWAP, SUI32 (Scaled UI32).

I chose to use a Perl script to spoon feed the Exif parameters of hdr_bytes, bitmap_bytes, xres, yres, is_float and is_mangled to c in a string easily digested with an sscanf() call. I call it through a popen() call.

Accessing the bitmap is as simple as:

ifp = fopen(ifn, "rb");      // Read Image in Binary mode
fseek(ifp, soff, SEEK_SET);  // Seek to start of bitmap data							 
// Read numf * 4 bytes from bitmap, store in uint/float union
numr = fread((void *)fiu.bpix, sizeof(float), numf, ifp);  // 4*3=12B  
fclose(ifp); 

I read a single pixel into a { float, uint32, int32, uint8 } union :

union  {  // Overlay floats and 32 bit unsigned ints
  float    fpix[4];  // Holds 1 pixel
  uint32_t upix[4];  // UI32 and float share same bits	
  int32_t  ipix[4];  // UI32 and float share same bits	
  uint8_t  bpix[16]; // UI8  BYTE
} fiu;             // Float-Int-Union

The convert “-define quantum:format=floating-point” option eliminates the scaled uint32s and appears to generate IEEE 754 floats.

Andy,
I am definitely open to using standard libraries to generate predictable results in short order.

I already knew the tiff header size, the bitmap size, the XY resolution, the data format (IEEE 754 float) and the byte swapped status from ExifTool, tiffinfo, tiffdump and other, off the shelf, trusted utilities.

I was tripped up thinking that the “float” data were actually C Language floats when they were actually uint32s. And byte swapping only the first 1/3 of my array by using the number of pixels and not the number of “floats” so I was looking at partially swapped data. I will never buy an another intel processor!

The only functionality I needed was to read the bitmap into an allocated buffer. This was all done with not much more than 4 lines pure C and no compiling, versioning or linking entanglements:

tfp = fopen(tfn, "rb");          // Open the tif file
fseek(tfp, hdr_size, SEEK_SET);  // Seek past the header to the bitmap
bread = fread((void *)fu.tara, 1, bm_size, tfp); // Read all bitmap bytes
fclose(tfp); 

RIBM: Read 434.771040 MB, hdr=13022, 36.230920 MPix in 60 msec -> 7222 MB/s

I have it packaged in a concise function located in a library file:

    // Q_Size is Quantum size in Bytes: 6 for UI16 RGB, 12 for float RGB 
int read_img_bmap(char *ifname, int hdr_size, int64_t bm_size, int pix_size,
    uint8_t *rgbara, EV_TIME_STR *tsa, int debug);

And, I am seeing bare metal performance here, 7+GB / sec read speed from an M.2 SSD rated at 7GB/sec.

I would be impressed if libtiff or a Python module could simplify the opening, seeking, reading and closing of the tiff. Could you include an example of such code?

Some lines of code assembled from my tiffimage.cpp file:

https://github.com/butcherg/rawproc/blob/master/src/tiffimage.cpp

#include "tiffio.h"
TIFF* tif = TIFFOpen(filename, "r");

img = new char[w*h*c*(b/8)];
buf = (char *) _TIFFmalloc(TIFFScanlineSize(tif));
int stride = TIFFScanlineSize(tif);

TIFFGetField(tif, TIFFTAG_PLANARCONFIG, &config);
if (config != PLANARCONFIG_CONTIG) return NULL;
		
char * dst = (char *) img;
for (unsigned y = 0; y < h; y++){
	TIFFReadScanline(tif, buf, y, 0);
	memcpy(dst,buf,stride);
	dst += stride;
}

TIFFClose(tif);

Can also retrieve a wide variety of metadata with TIFFGetField() calls between TIFFOpen() and TIFFClose().

That PLANARCONFIG_CONTIG thing in the middle is important; TIFF image data isn’t necessarily row-column contiguous…

Glenn,
I am trying to follow your code. I see that you:

  • tiff_malloc a scan line buffer
  • reject non-planar configs
  • for loop to Y < H, but the only h is a new char, not a uint.
  • read one scanline
  • append it to the buffer
  • advance the buffer end pointer by one stride.

What is the “h” variable which limits the for loop?

for (unsigned y = 0; y < h; y++){

I found a tiff file which ExifTool told me had 891 strips. All of the others have 1 like the nef data from my camera.

What is the advantage of segmenting an image into hundreds of chunks? Was this feature written back when a 1.44 MB floppy was huge? :slight_smile:

I measure performance in my code with this:

stime=timer_nanosec_int64(0);  // NanoSeconds since Boot?
[do something, STAT!]
etime=timer_nanosec_int64(0);  // NanoSeconds since Boot?
delta_sec = delta_time_float_seconds(stime, etime); // Nano - a - Nano

What kind of performance does libtiff provide?

I just copy-pasted relevant parts to the post. Some variables are missing from it; it’s best to look at the full routine here:

https://github.com/butcherg/rawproc/blob/master/src/tiffimage.cpp#L80

h is the image height; it and w are obtained in calls to TIFFGetField().

I’ve never measured the import times of this routine. TIFFs are usually big, and the time spent drumming my fingers waiting for one to load seemed about right.

Using libtiff, libjpeg, libpng, libraw, LIttleCMS, exiv2, and lensfun let me concentrate on the particulars of building the raw processor I use every day:

Speed of a particular code segment (well, maybe denoise… :laughing: ) isn’t that important if you’re not getting the whole thing to work…