enhancement: extract binary data from FLIR radiometric jpg

Started by tomas123, March 20, 2013, 12:49:46 PM

Previous topic - Next topic

tomas123

thanks for your post from vacation and sorry for disturbing you :-[

I know. how to delete thumbnails.
My question is, how do you changed the full size images inside the jpg in your Meta Information Repository with white images 8x8 px.
( https://exiftool.org/sample_images.html )




I have some basic approaches with good old awk for change the size of a existing FFF-Tag (splitted in 64KB-parts) and fill it with an other image.
After tests with an Hex-Editor I think that I can change the size of a single "FF E1" APP1 Flir-FFF-segment without recalculate the adresses from other APP1-segments. There no absolute offsets.
(off course I edited the segment size in the FFE1 header of affected Flir FFF segment)
I'm not sure...  I must read the APP1 marker specification and look for terrible checksums etc.

You changed with some write commands of exiftool also the size of APP1 segments.
It is sufficient to only change the segment size in the APP1 header?

Phil Harvey

Ah.  I have a script I wrote that does this (attached).  It takes the image in t/images/Writer.jpg and uses it to replace the main image in the specified JPEG.  Other changes I do by hand.  From the JPEG point of view, changing the segment size word is sufficient, but depending on the format of the data within the segment some other changes may be necessary.

- Phil
...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).

tomas123

thanks for clarification
I will come back, if I have new infos.

tomas123

Hi Phil,

I'm writing a script for expanding PiP images.

The definition of tags with a config file "exiftool -config ..." works great, but I can't change the byte order in the first tag with address 0x00.

https://exiftool.org/forum/index.php/topic,4898.msg24156.html#msg24156
%Image::ExifTool::FLIR::PIP = (
    GROUPS => { 0 => 'APP1', 2 => 'Image' },
    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
    FIRST_ENTRY => 0,
    NOTES => q{
        Picture in Picture
    },
     0x00 => {
    # determine the byte order
        RawConv => 'ToggleByteOrder()',
        Name => 'Real2IR',
Format => 'float',
#PrintConv => 'sprintf("%.2f",$val)'
    },


This doesn't work.
RawConv => 'ToggleByteOrder()' change the byte order for the next following tags.
Do you have an idea?

Thanks

Phil Harvey

#79
Hi Tomas,

Thanks for reminding me about this thread now that I'm back again.

Are all of the PiP tags a specific byte order?  If so, you should be able to set the byte order in the SubDirectory definition:

    0x002a => {
        Name => 'PiP',
        SubDirectory => {
            TagTable => 'Image::ExifTool::FLIR::PiP',
            ByteOrder => 'LittleEndian',
        },
    },


Otherwise, you could do this for a single tag:

    0x00 => {
        Name => 'Real2IR',
        Format => 'undef[4]',
        RawConv => 'ToggleByteOrder(); $val = GetFloat(\$val, 0); ToggleByteOrder(); $val',
    },


- Phil

Edit: I have attached an updated FLIR.pm that may do what you want (provided the PiP is always little endian).  BTW, what is the meaning of the Real2IR tag?
...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).

tomas123

your FLIR.pm works fine, ByteOrder => 'LittleEndian' was the solution :-)

Real2IR is the proportion between the "Field Of View" of real image to thermal image.
You need the factor for overlay two images with different focal length and pixel resolution.

I described real2IR here:
Quote from: tomas123 on May 06, 2013, 04:21:17 AM

Quote$ exiftool -config config.txt Aqua_Tower_thermal_image.jpg -real2ir -RawThermalImageWidth -EmbeddedImageWidth -Offset*
real 2 IR                       : 1,7187 // please correct my config file
...

calculate the resize factor
480/1,7187=279 Pixel horizontal for resized raw image

Quote$ convert raw.png palette.png -clut -resize 279x Tower.jpg +swap -gravity Center -geometry +2-10 -compose over -composite TowerPiP.jpg


tomas123

don't forget

Focus Distance RAW          : Flir E40/E50/E60 value proportional to rotation angle of focus ring
Focus Distance                  : with focus ring adjusted distance in [m]

%Image::ExifTool::UserDefined = (
    'Image::ExifTool::FLIR::CameraInfo' => {
          0x390 => { Name => 'FocusDistanceRAW', Format => 'int16u', Groups => { 2 => 'Image' } },
          0x45c => { Name => 'FocusDistance', Format => 'float', PrintConv => 'sprintf("%.1f m",$val)' },
     },


you can check "Focus Distance RAW" with this image
https://exiftool.org/forum/index.php?action=dlattach;topic=4898.0;attach=615

here you see the function of "Focus Distance RAW" to Distance [meter] for a FLIR E40
(I read it out with exiftool from hundreds of images)


Phil Harvey

Hi Tomas,

Thanks!  I missed this.  I think I'll call it FocusStepCount instead of FocusDistanceRAW, since this corresponds to the name used for Olympus cameras.

- Phil
...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).

tomas123

FocusStepCount is nice

Focus Distance is also tricky, there are more distance tags

Flir E40
Subject Distance                : 0 m
Object Distance                 : 0.00 m
FocusStepCount                : 2000
Focus Distance                  : 0.5 m


Flir B60
Subject Distance                : 1 m
Object Distance                 : 1.00 m
FocusStepCount              : 0
Focus Distance                  : 4.5 m


mostly SubjectDistance=ObjectDistance,
but a nice sample from your https://exiftool.org/sample_images.html
======== FLIR_ThermaCAM_T-400.jpg
Subject Distance                : 8.01 m
Object Distance                 : 2.44 m
FocusStepCount              : 0
Focus Distance                  : 0.6 m
======== FLIR_ThermaCamP660Wes.jpg
Subject Distance                : 5 m
Object Distance                 : 1.52 m
FocusStepCount              : 0
Focus Distance                  : 5.4 m

tomas123

with the new tags I could finished my new script for extracting flir images

I use php so that windows users do not require some large installation (like perl)
it runs under Mac/Windows/Linux (only edit the path inside the script)

needs:
- Imagemagick convert
- Exiftool  :)

flir.php
#!/usr/bin/php
<?php
//------------- set necessary paramters -------------------------------------

if (strtoupper(substr(PHP_OS03)) === 'WIN') {
    
//WINDOWS user: set path to tools
    
$convert='"C:\Program Files\ImageMagick-6.8.5-Q16\convert.exe"';
    
$exiftool='"C:\Program Files\exiftool\exiftool.exe"';
    
//set font variable as needed (Mac/Win) for color scale
    
$font='-font c:\windows\Fonts\arialbd.ttf';

} else {
    
//Unix/Mac: set path to tools here 
    
$convert='/opt/ImageMagick/bin/convert';
    
$exiftool='/usr/bin/exiftool';
    
//set font variable as needed (Mac/Win) for color scale
    
$font='-font /Library/Fonts/Arial\ Bold.ttf';
}

//color scale
$font_color='white';
$frame_color='black';

//extract embedded palette to
$embpal='palette.png';

//--------------------------------------------------------------------------

$shortopts  "";
$shortopts .= "i:";  // Required value
$shortopts .= "o:";  
$shortopts .= "h";  

$longopts  = array(
    
"resize:",     // Required value
    
"rmin:",     
    
"rmax:",    
    
"pal:",
    
"tref:",
    
"emis:",
    
"pip::",       // opt. value
    
"clut",        // No value
    
"scale",      
    
"help",       
);
$options getopt($shortopts$longopts);
#var_dump($options);

function help()
{
global 
$argv;
echo <<<EOT

usage: 
$argv[0] [options] -i ir_file.jpg -o outputimage

Settings:
-i ir_file.jpg      flir radiometric image
-o output.jpg       save  8 Bit image jpg
-o output.png       save 16 Bit image png

Options Summary:
--resize val        scale sensor size with "convert -resize val" (val i.e. 600x or 100%, default is 200%)
--tref temp         overwrite embedded Reflected Apparent Temperature (degree Celsius) 
--emis val          overwrite embedded Emissivity (val i.e. 0.95)
--rmin raw_min      set min RAW value instead embedded value (set scale min temp)
--rmax raw_ma       set max RAW value instead embedded value (set scale max temp)
--pal iron.png      use own palette (instead of embedded palette.png)
--clut              disable "Color LookUp Table" and color scale (save a grayscale image)
--scale             disable color scale on the right edge
--pip[=AxB]         input image is a flir PiP radiometric image
                    overlay embedded "real image" with "ir image"
                    [optional] crop ir image to size AxB (i.e. --pip=90x90 )
--help              print this help
  
# source: 
# [1] https://exiftool.org/forum/index.php/topic,4898.0.html

EOT;
}

if (isset(
$options['help']) || isset($options['h']))
{
 
help();
};

if (isset(
$options['i']))
{
    
$flirimg="\"".$options['i']."\"";
    if (isset(
$options['o']))
    {
        
$destimg="\"".$options['o']."\"";    
    } else {
      print 
'Error: No output file specified! "-o filename"'."\n";
      exit(
1);
    }
} else {     
    
help();
    exit(
1);
};

if (isset(
$options['pal']))
{
    
$pal="\"".$options['pal']."\"";  
} else {
    
$pal=$embpal;
}

if (isset(
$options['resize']))
{
    
$resize='-resize '.$options['resize'];    
} else {
    
// default
    
$resize="-resize 200%";
}

//get Exif values (syntax for Unix and Windows)
eval('$exif='.shell_exec($exiftool.' -php -flir:all -q '.$flirimg));
//var_dump($exif);

if (isset($options['tref']))
{
    
$Temp_ref=$options['tref'];  
} else {
    
$tmp=explode(" ",$exif[0]['ReflectedApparentTemperature']);
    
$Temp_ref $tmp[0];
}
if (isset(
$options['emis']))
{
    
$Emissivity=$options['emis'];  
} else {
    
$Emissivity=$exif[0]['Emissivity'];
}
print(
"\nReflected Apparent Temperature: ".$Temp_ref." degree Celsius\nEmissivity: ".$Emissivity."\n");

// save Flir values for Plancks Law for better reading in short variables
$R1=$exif[0]['PlanckR1'];
$R2=$exif[0]['PlanckR2'];
$B$exif[0]['PlanckB'];
$O$exif[0]['PlanckO'];
$F$exif[0]['PlanckF'];

print(
'Plancks values: '.$R1.' '.$R2.' '.$B.' '.$O.' '.$F."\n\n");

// get displayed temp range in RAW values
$RAWmax=$exif[0]['RawValueMedian']+$exif[0]['RawValueRange']/2;
$RAWmin=$RAWmax-$exif[0]['RawValueRange'];

print(
'RAW Temp Range from sensor : '.exec($convert.' raw.png -format "%[min] %[max]" info:')."\n");
printf("RAW Temp Range FLIR setting: %d %d\n",$RAWmin,$RAWmax);

//overwrite with settings
if (isset($options['rmin'])) $RAWmin=$options['rmin'];
if (isset(
$options['rmax'])) $RAWmax=$options['rmax'];

printf("RAW Temp Range select      : %d %d\n",$RAWmin,$RAWmax);

// calc amount of radiance of reflected objects ( Emissivity < 1 )
$RAWrefl=$R1/($R2*(exp($B/($Temp_ref+273.15))-$F))-$O;
printf("RAW reflected: %d\n",$RAWrefl); 

// get displayed object temp max/min and convert to "%.1f" for printing
$RAWmaxobj=($RAWmax-(1-$Emissivity)*$RAWrefl)/$Emissivity;
$RAWminobj=($RAWmin-(1-$Emissivity)*$RAWrefl)/$Emissivity;
$Temp_min=sprintf("%.1f"$B/log($R1/($R2*($RAWminobj+$O))+$F)-273.15);
$Temp_max=sprintf("%.1f"$B/log($R1/($R2*($RAWmaxobj+$O))+$F)-273.15);

// extract color table, swap Cb Cr and expand video pal color table from [16,235] to [0,255]
// best results: Windows -colorspace sRGB | MAC -colorspace RGB
exec($exiftool.' '.$flirimg.' -b -Palette | '.$convert.' -size "'.$exif[0]['PaletteColors'].'X1" -depth 8 YCbCr:- -separate -swap 1,2 -set colorspace YCbCr -combine -colorspace RGB -auto-level '.$embpal);

// draw color scale
exec($convert." -size 30x256 gradient: $pal -clut -mattecolor ".$frame_color.' -frame 5x5 -set colorspace rgb gradient.png');

// if your imagemagick have no freetype library remove the next line
exec($convert." gradient.png -background ".$frame_color." ".$font." -fill ".$font_color." -pointsize 15 label:\"$Temp_max C\" +swap -gravity Center -append  label:\"$Temp_min\" -append gradient.png");

if (
$exif[0]['RawThermalImageType'] != "TIFF")
{
  
//16 bit PNG: change byte order
   
$size=$exif[0]['RawThermalImageWidth']."x".$exif[0]['RawThermalImageHeight'];
   
exec($exiftool." -b -RawThermalImage $flirimg | ".$convert." - gray:- | ".$convert." -depth 16 -endian msb -size ".$size." gray:- raw.png");   
}else{
   
exec($exiftool." -b -RawThermalImage $flirimg | ".$convert." - raw.png");      
}

// convert every RAW-16-Bit Pixel with Planck's Law to a Temperature Grayscale value and append temp scale
$Smax=$B/log($R1/($R2*($RAWmax+$O))+$F);
$Smin=$B/log($R1/($R2*($RAWmin+$O))+$F);
$Sdelta=$Smax-$Smin;
exec($convert." raw.png -fx \"($B/ln($R1/($R2*(65535*u+$O))+$F)-$Smin)/$Sdelta\" ir.png");

if ( !isset(
$options['pip']) )
{    
    if ( !isset(
$options['clut']) )
    {
        if ( !isset(
$options['scale']) )
            {
            
// with color scale
            
exec($convert." ir.png ".$resize.$pal -clut -background ".$frame_color." -flatten +matte gradient.png -gravity East +append $destimg");
        }else{
            
exec($convert." ir.png ".$resize.$pal -clut ".$destimg);
        }
    }else{
        
// only gray picture
            
exec($convert." ir.png ".$resize." ".$destimg);
    }    
}else{
//make PiP
    //read embedded image
    
exec($exiftool." -b -EmbeddedImage $flirimg | ".$convert." - -set colorspace YCbCr -colorspace RGB embedded.png");
    
$geometrie=$exif[0]['OffsetX'].$exif[0]['OffsetY'];
    if ( 
is_string($options['pip']) )
    {
        
$crop="-gravity Center -crop ".$options['pip']."+0+0";
    }  
    
$resize=100*$exif[0]['EmbeddedImageWidth']/$exif[0]['Real2IR']/$exif[0]['RawThermalImageWidth'];
    
$resize="-resize ".$resize.'%';
    
exec($convert." ir.png $crop +repage ".$resize.$pal -clut embedded.png +swap -gravity Center -geometry $geometrie -compose over -composite -background ".$frame_color." -flatten +matte gradient.png -gravity East +append ".$destimg);
}

print(
"wrote $destimg with Temp-Range: $Temp_min / $Temp_max degree Celsius\n");

?>




using
$ ./flir.php

usage: ./flir.php [options] -i ir_file.jpg -o outputimage

Settings:
-i ir_file.jpg      flir radiometric image
-o output.jpg       save  8 Bit image jpg
-o output.png       save 16 Bit image png

Options Summary:
--resize val        scale sensor size with "convert -resize val" (val i.e. 600x or 100%, default is 200%)
--tref temp         overwrite embedded Reflected Apparent Temperature (degree Celsius)
--emis val          overwrite embedded Emissivity (val i.e. 0.95)
--rmin raw_min      set min RAW value instead embedded value (set scale min temp)
--rmax raw_ma       set max RAW value instead embedded value (set scale max temp)
--pal iron.png      use own palette (instead of embedded palette.png)
--clut              disable "Color LookUp Table" and color scale (save a grayscale image)
--scale             disable color scale on the right edge
--pip[=AxB]         input image is a flir PiP radiometric image
                    overlay embedded "real image" with "ir image"
                    [optional] crop ir image to size AxB (i.e. --pip=90x90 )
--help              print this help
 
# source:
# [1] https://exiftool.org/forum/index.php/topic,4898.0.html




samples with a picure from Flir B60:
download http://commons.wikimedia.org/wiki/File:Aqua_Tower_thermal_image.jpg

cropped PiP
./flir.php -i Aqua_Tower.jpg -o test1.jpg --pip=120x120
Reflected Apparent Temperature: 20.0 degree Celsius
Emissivity: 1
Plancks values: 13559.122 0.010364772 1368.1 -5202 1

RAW Temp Range from sensor : 6953 13994
RAW Temp Range FLIR setting: 12104 14099
RAW Temp Range select      : 12104 14099
RAW reflected: 17617
wrote "test1.jpg" with Temp-Range: -12.5 / 0.6 degree Celsius


full sized PiP
./flir.php -i Aqua_Tower.jpg -o test2.jpg --pip

only Thermal Image
./flir.php -i Aqua_Tower.jpg -o test3.jpg

set min/max temp with related raw values (see above "RAW Temp Range FLIR setting: 12104 14099" for orientation)
./flir.php -i Aqua_Tower.jpg -o test4.jpg --rmin 13000 --rmax 13500

use an other palette:
./flir.php -i Aqua_Tower.jpg -o rain.jpg --pal rainbow.png

see result in attachments





hint for windows user for using php as a portable version without some installations:

  • download from http://windows.php.net/download/#php-5.4 the current version 5.4.xy
http://windows.php.net/downloads/releases/php-5.4.xy-nts-Win32-VC9-x86.zip
  • extract zip to a folder php-5.4.xy-nts-Win32-VC9-x86
  • run > php-5.4.xy-nts-Win32-VC9-x86\php.exe flir.php ... options...

Phil Harvey

Hi Tomas,

You really are the FLIR master.  Great work!

- Phil
...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).

b0rja

Thank you very much for sharing your work on FLIR image data.

I have noticed that the temperature calculus in the script flir.php does not take into account the interaction with the atmosphere (ObjectDistance, RelativeHumidity, AtmosphericTemperature).

My research focuses on the analysis of distant bodies where this correction may be important.

If you can point me the physical formulation of this correction, I would be glad to modify the script to include it.

Best regards,
Borja.

Phil Harvey

Hi Borja,

You can find this information in online FLIR documents by googling FLIR "the measurement formula".

For example, page 155 of this manual.

- Phil
...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).

tomas123

@b0rja
you are right:
Quote from: tomas123 on April 18, 2013, 05:23:46 PM
I ignore the Emission of atmosphera. The influence is low by short distance. The calculation effort is high (distance, humidity).
Our calculated temperature is 100% identical with Flir temperature if you set the object distance to zero and let external optics transmission by 1.0!!
read my full post here:
https://exiftool.org/forum/index.php/topic,4898.msg23944.html#msg23944
you can extend the formula for "ObjectDistance, RelativeHumidity, Atmospheric Temperature" but is not simple

read a fluke manual of Ti100, Ti105, Ti110, Ti125 - there is also no compensation of air influence

Quote from: b0rja on May 20, 2013, 06:46:45 AM
My research focuses on the analysis of distant bodies where this correction may be important.

here you find more informations (a sample with 100m (!) distance):
http://qirt.gel.ulaval.ca/archives/qirt2010/papers/QIRT%202010-092.pdf
from the book "Infrared Thermography Errors and Uncertainties" (google for preview, page 55)

hansss

I find this thread VERY interesting. I have a flir i3 camera and would like to automatically generate cvs files with raw sensor data. My question is if this: Can I do this with exiftool.

When I run this:

exiftool -common -csv test1.jpg > test1.csv

I get this result:

SourceFile,FileName,FileSize,Model,DateTimeOriginal,ImageSize,FocalLength,ShutterSpeed
test1.jpg,test1.jpg,21 kB,FLIR_i3,2003:01:11 00:29:07.919+01:00,240x240,6.7 mm,1/29

However what I would like is this:

24.332;24.419;24.419;24.364;24.31;24.386;24.332;24.

I am enclosing the original flir picture test1.jpg  (my foot) and test1flir.csv which I created by using the export feature in Flir tools.

Today I called FLIR and they told me that none of their software presently has batch processing for creating csv files. They are however working on adding batch processing to ResearchIR however this product costs around $3000!

I would appreciate any help!

kind regards,
Hans
Zurich