FLIR.pm 55 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533
  1. #------------------------------------------------------------------------------
  2. # File: FLIR.pm
  3. #
  4. # Description: Read FLIR meta information
  5. #
  6. # Revisions: 2013/03/28 - P. Harvey Created
  7. #
  8. # References: 1) http://u88.n24.queensu.ca/exiftool/forum/index.php/topic,4898.0.html
  9. # 2) http://www.nuage.ch/site/flir-i7-some-analysis/
  10. # 3) http://www.workswell.cz/manuals/flir/hardware/A3xx_and_A6xx_models/Streaming_format_ThermoVision.pdf
  11. # 4) http://support.flir.com/DocDownload/Assets/62/English/1557488%24A.pdf
  12. # 5) http://code.google.com/p/dvelib/source/browse/trunk/flirPublicFormat/fpfConverter/Fpfimg.h?spec=svn3&r=3
  13. # 6) http://u88.n24.queensu.ca/exiftool/forum/index.php/topic,5538.0.html
  14. # JD) Jens Duttke private communication
  15. #
  16. # Glossary: FLIR = Forward Looking Infra Red
  17. #------------------------------------------------------------------------------
  18. package Image::ExifTool::FLIR;
  19. use strict;
  20. use vars qw($VERSION);
  21. use Image::ExifTool qw(:DataAccess :Utils);
  22. use Image::ExifTool::Exif;
  23. $VERSION = '1.13';
  24. sub ProcessFLIR($$;$);
  25. sub ProcessFLIRText($$$);
  26. sub ProcessMeasInfo($$$);
  27. sub GetImageType($$$);
  28. my %temperatureInfo = (
  29. Writable => 'rational64u',
  30. Format => 'rational64s', # (have seen negative values)
  31. );
  32. # tag information for floating point Kelvin tag
  33. my %floatKelvin = (
  34. Format => 'float',
  35. ValueConv => '$val - 273.15',
  36. PrintConv => 'sprintf("%.1f C",$val)',
  37. );
  38. # commonly used tag information elements
  39. my %float1f = ( Format => 'float', PrintConv => 'sprintf("%.1f",$val)' );
  40. my %float2f = ( Format => 'float', PrintConv => 'sprintf("%.2f",$val)' );
  41. my %float6f = ( Format => 'float', PrintConv => 'sprintf("%.6f",$val)' );
  42. my %float8g = ( Format => 'float', PrintConv => 'sprintf("%.8g",$val)' );
  43. # FLIR makernotes tags (ref PH)
  44. %Image::ExifTool::FLIR::Main = (
  45. WRITE_PROC => \&Image::ExifTool::Exif::WriteExif,
  46. CHECK_PROC => \&Image::ExifTool::Exif::CheckExif,
  47. GROUPS => { 0 => 'MakerNotes', 2 => 'Camera' },
  48. WRITABLE => 1,
  49. PRIORITY => 0, # (unreliable)
  50. NOTES => q{
  51. Information extracted from the maker notes of JPEG images from thermal
  52. imaging cameras by FLIR Systems Inc.
  53. },
  54. 0x01 => { #2
  55. Name => 'ImageTemperatureMax',
  56. %temperatureInfo,
  57. Notes => q{
  58. these temperatures may be in Celcius, Kelvin or Fahrenheit, but there is no
  59. way to tell which
  60. },
  61. },
  62. 0x02 => { Name => 'ImageTemperatureMin', %temperatureInfo }, #2
  63. 0x03 => { #1
  64. Name => 'Emissivity',
  65. Writable => 'rational64u',
  66. PrintConv => 'sprintf("%.2f",$val)',
  67. PrintConvInv => '$val',
  68. },
  69. # 0x04 does not change with temperature units; often 238, 250 or 457
  70. 0x04 => { Name => 'UnknownTemperature', %temperatureInfo, Unknown => 1 },
  71. # 0x05,0x06 are unreliable. As written by FLIR tools, these are the
  72. # CameraTemperatureRangeMax/Min, but the units vary depending on the
  73. # options settings. But as written by some cameras, the values are different.
  74. 0x05 => { Name => 'CameraTemperatureRangeMax', %temperatureInfo, Unknown => 1 },
  75. 0x06 => { Name => 'CameraTemperatureRangeMin', %temperatureInfo, Unknown => 1 },
  76. # 0x07 - string[33] (some sort of image ID?)
  77. # 0x08 - string[33]
  78. # 0x09 - undef (tool info)
  79. # 0x0a - int32u: 1
  80. # 0x0f - rational64u: 0/1000
  81. # 0x10,0x11,0x12 - int32u: 0
  82. # 0x13 - rational64u: 0/1000
  83. );
  84. # FLIR FFF tag table (ref PH)
  85. %Image::ExifTool::FLIR::FFF = (
  86. GROUPS => { 0 => 'APP1', 2 => 'Image' },
  87. PROCESS_PROC => \&ProcessFLIR,
  88. VARS => { ALPHA_FIRST => 1 },
  89. NOTES => q{
  90. Information extracted from FLIR FFF images and the FLIR APP1 segment of JPEG
  91. images. These tags may also be extracted from the first frame of an FLIR
  92. SEQ file.
  93. },
  94. "_header" => {
  95. Name => 'FFFHeader',
  96. SubDirectory => { TagTable => 'Image::ExifTool::FLIR::Header' },
  97. },
  98. # 0 = free (ref 3)
  99. 0x01 => {
  100. Name => 'RawData',
  101. SubDirectory => { TagTable => 'Image::ExifTool::FLIR::RawData' },
  102. },
  103. # 2 = GainMap (ref 3)
  104. # 3 = OffsMap (ref 3)
  105. # 4 = DeadMap (ref 3)
  106. 0x05 => { #6
  107. Name => 'GainDeadData',
  108. SubDirectory => { TagTable => 'Image::ExifTool::FLIR::GainDeadData' },
  109. },
  110. 0x06 => { #6
  111. Name => 'CoarseData',
  112. SubDirectory => { TagTable => 'Image::ExifTool::FLIR::CoarseData' },
  113. },
  114. # 7 = ImageMap (ref 3)
  115. 0x0e => {
  116. Name => 'EmbeddedImage',
  117. SubDirectory => { TagTable => 'Image::ExifTool::FLIR::EmbeddedImage' },
  118. },
  119. 0x20 => {
  120. Name => 'CameraInfo', # (BasicData - ref 3)
  121. SubDirectory => { TagTable => 'Image::ExifTool::FLIR::CameraInfo' },
  122. },
  123. 0x21 => { #6
  124. Name => 'MeasurementInfo',
  125. SubDirectory => { TagTable => 'Image::ExifTool::FLIR::MeasInfo' },
  126. },
  127. 0x22 => {
  128. Name => 'PaletteInfo', # (ColorPal - ref 3)
  129. SubDirectory => { TagTable => 'Image::ExifTool::FLIR::PaletteInfo' },
  130. },
  131. 0x23 => {
  132. Name => 'TextInfo',
  133. SubDirectory => { TagTable => 'Image::ExifTool::FLIR::TextInfo' },
  134. },
  135. 0x24 => {
  136. Name => 'EmbeddedAudioFile',
  137. # (sometimes has an unknown 8-byte header)
  138. RawConv => q{
  139. return \$val if $val =~ s/^.{0,16}?RIFF/RIFF/s;
  140. $self->Warn('Unknown EmbeddedAudioFile format');
  141. return undef;
  142. },
  143. },
  144. # 0x27: 01 00 08 00 10 00 00 00
  145. 0x28 => {
  146. Name => 'PaintData',
  147. SubDirectory => { TagTable => 'Image::ExifTool::FLIR::PaintData' },
  148. },
  149. 0x2a => {
  150. Name => 'PiP',
  151. SubDirectory => {
  152. TagTable => 'Image::ExifTool::FLIR::PiP',
  153. ByteOrder => 'LittleEndian',
  154. },
  155. },
  156. 0x2b => {
  157. Name => 'GPSInfo',
  158. SubDirectory => { TagTable => 'Image::ExifTool::FLIR::GPSInfo' },
  159. },
  160. 0x2c => {
  161. Name => 'MeterLink',
  162. SubDirectory => {
  163. TagTable => 'Image::ExifTool::FLIR::MeterLink' ,
  164. ByteOrder => 'LittleEndian'
  165. },
  166. },
  167. 0x2e => {
  168. Name => 'ParameterInfo',
  169. SubDirectory => { TagTable => 'Image::ExifTool::FLIR::ParamInfo' },
  170. },
  171. );
  172. # FFF file header (ref PH)
  173. %Image::ExifTool::FLIR::Header = (
  174. GROUPS => { 0 => 'APP1', 2 => 'Image' },
  175. PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
  176. FIRST_ENTRY => 0,
  177. NOTES => 'Tags extracted from the FLIR FFF/AFF header.',
  178. 4 => { Name => 'CreatorSoftware', Format => 'string[16]' },
  179. );
  180. # FLIR raw data record (ref PH)
  181. %Image::ExifTool::FLIR::RawData = (
  182. GROUPS => { 0 => 'APP1', 2 => 'Image' },
  183. PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
  184. FORMAT => 'int16u',
  185. FIRST_ENTRY => 0,
  186. NOTES => q{
  187. The thermal image data may be stored either as raw data, or in PNG format.
  188. If stored as raw data, ExifTool adds a TIFF header to allow the data to be
  189. viewed as a TIFF image. If stored in PNG format, the PNG image is extracted
  190. as-is. Note that most FLIR cameras using the PNG format seem to write the
  191. 16-bit raw image data in the wrong byte order.
  192. },
  193. 0x00 => {
  194. # use this tag only to determine the byte order of the raw data
  195. # (the value should be 0x0002 if the byte order is correct)
  196. # - always "II" when RawThermalImageType is "TIFF"
  197. # - seen both "II" and "MM" when RawThermalImageType is "PNG"
  198. Name => 'RawDataByteOrder',
  199. Hidden => 1,
  200. RawConv => 'ToggleByteOrder() if $val >= 0x0100; undef',
  201. },
  202. 0x01 => {
  203. Name => 'RawThermalImageWidth',
  204. RawConv => '$$self{RawThermalImageWidth} = $val',
  205. },
  206. 0x02 => {
  207. Name => 'RawThermalImageHeight',
  208. RawConv => '$$self{RawThermalImageHeight} = $val',
  209. },
  210. # 0x03-0x05: 0
  211. # 0x06: raw image width - 1
  212. # 0x07: 0
  213. # 0x08: raw image height - 1
  214. # 0x09: 0,15,16
  215. # 0x0a: 0,2,3,11,12,13,30
  216. # 0x0b: 0,2
  217. # 0x0c: 0 or a large number
  218. # 0x0d: 0,3,4,6
  219. # 0x0e-0x0f: 0
  220. 16 => {
  221. Name => 'RawThermalImageType',
  222. Format => 'undef[$size-0x20]',
  223. RawConv => 'Image::ExifTool::FLIR::GetImageType($self, $val, "RawThermalImage")',
  224. },
  225. 16.1 => {
  226. Name => 'RawThermalImage',
  227. Groups => { 2 => 'Preview' },
  228. RawConv => '\$$self{RawThermalImage}',
  229. },
  230. );
  231. # GainDeadMap record (ref 6) (see RawData above)
  232. %Image::ExifTool::FLIR::GainDeadData = (
  233. GROUPS => { 0 => 'APP1', 2 => 'Image' },
  234. PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
  235. FORMAT => 'int16u',
  236. FIRST_ENTRY => 0,
  237. NOTES => 'Information found in FFF-format .GAN calibration image files.',
  238. 0x00 => {
  239. Name => 'GainDeadMapByteOrder',
  240. Hidden => 1,
  241. RawConv => 'ToggleByteOrder() if $val >= 0x0100; undef',
  242. },
  243. 0x01 => {
  244. Name => 'GainDeadMapImageWidth',
  245. RawConv => '$$self{GainDeadMapImageWidth} = $val',
  246. },
  247. 0x02 => {
  248. Name => 'GainDeadMapImageHeight',
  249. RawConv => '$$self{GainDeadMapImageHeight} = $val',
  250. },
  251. 16 => {
  252. Name => 'GainDeadMapImageType',
  253. Format => 'undef[$size-0x20]',
  254. RawConv => 'Image::ExifTool::FLIR::GetImageType($self, $val, "GainDeadMapImage")',
  255. },
  256. 16.1 => {
  257. Name => 'GainDeadMapImage',
  258. RawConv => '\$$self{GainDeadMapImage}',
  259. },
  260. );
  261. # CoarseMap record (ref 6) (see RawData above)
  262. %Image::ExifTool::FLIR::CoarseData = (
  263. GROUPS => { 0 => 'APP1', 2 => 'Image' },
  264. PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
  265. FORMAT => 'int16u',
  266. FIRST_ENTRY => 0,
  267. NOTES => 'Information found in FFF-format .CRS correction image files.',
  268. 0x00 => {
  269. Name => 'CoarseMapByteOrder',
  270. Hidden => 1,
  271. RawConv => 'ToggleByteOrder() if $val >= 0x0100; undef',
  272. },
  273. 0x01 => {
  274. Name => 'CoarseMapImageWidth',
  275. RawConv => '$$self{CoarseMapImageWidth} = $val',
  276. },
  277. 0x02 => {
  278. Name => 'CoarseMapImageHeight',
  279. RawConv => '$$self{CoarseMapImageHeight} = $val',
  280. },
  281. 16 => {
  282. Name => 'CoarseMapImageType',
  283. Format => 'undef[$size-0x20]',
  284. RawConv => 'Image::ExifTool::FLIR::GetImageType($self, $val, "CoarseMapImage")',
  285. },
  286. 16.1 => {
  287. Name => 'CoarseMapImage',
  288. RawConv => '\$$self{CoarseMapImage}',
  289. },
  290. );
  291. # "Paint colors" record (ref PH)
  292. %Image::ExifTool::FLIR::PaintData = (
  293. GROUPS => { 0 => 'APP1', 2 => 'Image' },
  294. PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
  295. FORMAT => 'int16u',
  296. FIRST_ENTRY => 0,
  297. NOTES => 'Information generated by FLIR Tools "Paint colors" tool.',
  298. 0x01 => {
  299. Name => 'PaintByteOrder',
  300. Hidden => 1,
  301. RawConv => 'ToggleByteOrder() if $val >= 0x0100; undef',
  302. },
  303. 0x05 => {
  304. Name => 'PaintImageWidth',
  305. RawConv => '$$self{PaintImageWidth} = $val',
  306. },
  307. 0x06 => {
  308. Name => 'PaintImageHeight',
  309. RawConv => '$$self{PaintImageHeight} = $val',
  310. },
  311. 20 => {
  312. Name => 'PaintImageType',
  313. Format => 'undef[$size-0x28]',
  314. RawConv => 'Image::ExifTool::FLIR::GetImageType($self, $val, "PaintImage")',
  315. },
  316. 20.1 => {
  317. Name => 'PaintImage',
  318. RawConv => '\$$self{PaintImage}',
  319. },
  320. );
  321. # FLIR embedded image (ref 1)
  322. %Image::ExifTool::FLIR::EmbeddedImage = (
  323. GROUPS => { 0 => 'APP1', 2 => 'Image' },
  324. PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
  325. FORMAT => 'int16u',
  326. FIRST_ENTRY => 0,
  327. 0 => {
  328. # use this tag only to determine the byte order
  329. # (the value should be 0x0003 if the byte order is correct)
  330. Name => 'EmbeddedImageByteOrder',
  331. Format => 'int16u',
  332. Hidden => 1,
  333. RawConv => 'ToggleByteOrder() if $val >= 0x0100; undef',
  334. },
  335. 1 => 'EmbeddedImageWidth',
  336. 2 => 'EmbeddedImageHeight',
  337. 16 => {
  338. Name => 'EmbeddedImageType',
  339. Format => 'undef[4]',
  340. RawConv => '$val =~ /^\x89PNG/s ? "PNG" : ($val =~ /^\xff\xd8\xff/ ? "JPG" : "DAT")',
  341. Notes => q{
  342. "PNG" for PNG image in Y Cb Cr colors, "JPG" for a JPEG image, or "DAT" for
  343. other image data
  344. },
  345. },
  346. 16.1 => {
  347. Name => 'EmbeddedImage',
  348. Groups => { 2 => 'Preview' },
  349. Format => 'undef[$size-0x20]',
  350. Binary => 1,
  351. },
  352. );
  353. # FLIR camera record (ref PH)
  354. %Image::ExifTool::FLIR::CameraInfo = (
  355. GROUPS => { 0 => 'APP1', 2 => 'Camera' },
  356. PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
  357. FIRST_ENTRY => 0,
  358. NOTES => q{
  359. FLIR camera information. The Planck tags are variables used in the
  360. temperature calculation. See
  361. L<http://u88.n24.queensu.ca/exiftool/forum/index.php?topic=4898.msg23972#msg23972>
  362. for details.
  363. },
  364. 0x00 => {
  365. # use this tag only to determine the byte order
  366. # (the value should be 0x0002 if the byte order is correct)
  367. Name => 'CameraInfoByteOrder',
  368. Format => 'int16u',
  369. Hidden => 1,
  370. RawConv => 'ToggleByteOrder() if $val >= 0x0100; undef',
  371. },
  372. # 0x02 - int16u: image width
  373. # 0x04 - int16u: image height
  374. # 0x0c - int32u: image width - 1
  375. # 0x10 - int32u: image height - 1
  376. 0x20 => { Name => 'Emissivity', %float2f },
  377. 0x24 => { Name => 'ObjectDistance', Format => 'float', PrintConv => 'sprintf("%.2f m",$val)' },
  378. 0x28 => { Name => 'ReflectedApparentTemperature', %floatKelvin },
  379. 0x2c => { Name => 'AtmosphericTemperature', %floatKelvin },
  380. 0x30 => { Name => 'IRWindowTemperature', %floatKelvin },
  381. 0x34 => { Name => 'IRWindowTransmission', %float2f },
  382. # 0x38: 0
  383. 0x3c => {
  384. Name => 'RelativeHumidity',
  385. Format => 'float',
  386. ValueConv => '$val > 2 ? $val / 100 : $val', # have seen value expressed as percent in FFF file
  387. PrintConv => 'sprintf("%.1f %%",$val*100)',
  388. },
  389. # 0x40 - float: 0,6
  390. # 0x44,0x48,0x4c: 0
  391. # 0x50 - int32u: 1
  392. # 0x54: 0
  393. 0x58 => { Name => 'PlanckR1', %float8g }, #1
  394. 0x5c => { Name => 'PlanckB', %float8g }, #1
  395. 0x60 => { Name => 'PlanckF', %float8g }, #1
  396. # 0x64,0x68,0x6c: 0
  397. 0x070 => { Name => 'AtmosphericTransAlpha1', %float6f }, #1 (value: 0.006569)
  398. 0x074 => { Name => 'AtmosphericTransAlpha2', %float6f }, #1 (value: 0.012620)
  399. 0x078 => { Name => 'AtmosphericTransBeta1', %float6f }, #1 (value: -0.002276)
  400. 0x07C => { Name => 'AtmosphericTransBeta2', %float6f }, #1 (value: -0.006670)
  401. 0x080 => { Name => 'AtmosphericTransX', %float6f }, #1 (value: 1.900000)
  402. # 0x84,0x88: 0
  403. # 0x8c - float: 0,4,6
  404. 0x90 => { Name => 'CameraTemperatureRangeMax', %floatKelvin },
  405. 0x94 => { Name => 'CameraTemperatureRangeMin', %floatKelvin },
  406. 0x98 => { Name => 'UnknownTemperature1', %floatKelvin, Unknown => 1 }, # 50 degrees over camera max
  407. 0x9c => { Name => 'UnknownTemperature2', %floatKelvin, Unknown => 1 }, # usually 10 or 20 degrees below camera min
  408. 0xa0 => { Name => 'UnknownTemperature3', %floatKelvin, Unknown => 1 }, # same as camera max
  409. 0xa4 => { Name => 'UnknownTemperature4', %floatKelvin, Unknown => 1 }, # same as camera min
  410. 0xa8 => { Name => 'UnknownTemperature5', %floatKelvin, Unknown => 1 }, # usually 50 or 88 degrees over camera max
  411. 0xac => { Name => 'UnknownTemperature6', %floatKelvin, Unknown => 1 }, # usually 10, 20 or 40 degrees below camera min
  412. 0xd4 => { Name => 'CameraModel', Format => 'string[32]' },
  413. 0xf4 => { Name => 'CameraPartNumber', Format => 'string[16]' }, #1
  414. 0x104 => { Name => 'CameraSerialNumber',Format => 'string[16]' }, #1
  415. 0x114 => { Name => 'CameraSoftware', Format => 'string[16]' }, #1/PH (NC)
  416. 0x170 => { Name => 'LensModel', Format => 'string[32]' },
  417. # note: it seems that FLIR updated their lenses at some point, so lenses with the same
  418. # name may have different part numbers (eg. the FOL38 is either 1196456 or T197089)
  419. 0x190 => { Name => 'LensPartNumber', Format => 'string[16]' },
  420. 0x1a0 => { Name => 'LensSerialNumber', Format => 'string[16]' },
  421. 0x1b4 => { Name => 'FieldOfView', Format => 'float', PrintConv => 'sprintf("%.1f deg", $val)' }, #1
  422. # 0x1d0 - int16u: 0,12,24,25,46
  423. # 0x1d2 - int16u: 170,180,190,380,760,52320
  424. 0x1ec => { Name => 'FilterModel', Format => 'string[16]' },
  425. 0x1fc => { Name => 'FilterPartNumber', Format => 'string[32]' },
  426. 0x21c => { Name => 'FilterSerialNumber',Format => 'string[32]' },
  427. 0x308 => { Name => 'PlanckO', Format => 'int32s' }, #1
  428. 0x30c => { Name => 'PlanckR2', %float8g }, #1
  429. 0x338 => { Name => 'RawValueMedian', Format => 'int16u', Groups => { 2 => 'Image' } },
  430. 0x33c => { Name => 'RawValueRange', Format => 'int16u', Groups => { 2 => 'Image' } },
  431. 0x384 => {
  432. Name => 'DateTimeOriginal',
  433. Description => 'Date/Time Original',
  434. Format => 'undef[10]',
  435. Groups => { 2 => 'Time' },
  436. RawConv => q{
  437. my $tm = Get32u(\$val, 0);
  438. my $ss = Get32u(\$val, 4) & 0xffff;
  439. my $tz = Get16s(\$val, 8);
  440. ConvertUnixTime($tm - $tz * 60) . sprintf('.%.3d', $ss) . TimeZoneString(-$tz);
  441. },
  442. PrintConv => '$self->ConvertDateTime($val)',
  443. },
  444. 0x390 => { Name => 'FocusStepCount', Format => 'int16u' },
  445. 0x45c => { Name => 'FocusDistance', Format => 'float', PrintConv => 'sprintf("%.1f m",$val)' },
  446. # 0x43c - string: either "Live" or the file name
  447. );
  448. # FLIR measurement tools record (ref 6)
  449. %Image::ExifTool::FLIR::MeasInfo = (
  450. GROUPS => { 0 => 'APP1', 2 => 'Image' },
  451. PROCESS_PROC => \&ProcessMeasInfo,
  452. FORMAT => 'int16u',
  453. VARS => { NO_ID => 1 },
  454. NOTES => q{
  455. Tags listed below are only for the first measurement tool, however multiple
  456. measurements may be added, and information is extracted for all of them.
  457. Tags for subsequent measurements are generated as required with the prefixes
  458. "Meas2", "Meas3", etc.
  459. },
  460. Meas1Type => {
  461. PrintConv => {
  462. 1 => 'Spot',
  463. 2 => 'Area',
  464. 3 => 'Ellipse',
  465. 4 => 'Line',
  466. 5 => 'Endpoint', #PH (NC, FLIR Tools v2.0 for Mac generates an empty one of these after each Line)
  467. 6 => 'Alarm', #PH seen params: "0 1 0 1 9142 0 9142 0" (called "Isotherm" by Mac version)
  468. 7 => 'Unused', #PH (NC) (or maybe "Free"?)
  469. 8 => 'Difference',
  470. },
  471. },
  472. Meas1Params => {
  473. Notes => 'Spot=X,Y; Area=X1,Y1,W,H; Ellipse=XC,YC,X1,Y1,X2,Y2; Line=X1,Y1,X2,Y2',
  474. },
  475. Meas1Label => { },
  476. );
  477. # FLIR palette record (ref PH/JD)
  478. %Image::ExifTool::FLIR::PaletteInfo = (
  479. GROUPS => { 0 => 'APP1', 2 => 'Image' },
  480. PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
  481. FIRST_ENTRY => 0,
  482. 0x00 => { #JD
  483. Name => 'PaletteColors',
  484. RawConv => '$$self{PaletteColors} = $val',
  485. },
  486. 0x06 => { Name => 'AboveColor', Format => 'int8u[3]', Notes => 'Y Cr Cb color components' }, #JD
  487. 0x09 => { Name => 'BelowColor', Format => 'int8u[3]' }, #JD
  488. 0x0c => { Name => 'OverflowColor', Format => 'int8u[3]' }, #JD
  489. 0x0f => { Name => 'UnderflowColor', Format => 'int8u[3]' }, #JD
  490. 0x12 => { Name => 'Isotherm1Color', Format => 'int8u[3]' }, #JD
  491. 0x15 => { Name => 'Isotherm2Color', Format => 'int8u[3]' }, #JD
  492. 0x1a => { Name => 'PaletteMethod' }, #JD
  493. 0x1b => { Name => 'PaletteStretch' }, #JD
  494. 0x30 => {
  495. Name => 'PaletteFileName',
  496. Format => 'string[32]',
  497. # (not valid for all images)
  498. RawConv => q{
  499. $val =~ s/\0.*//;
  500. $val =~ /^[\x20-\x7e]{3,31}$/ ? $val : undef;
  501. },
  502. },
  503. 0x50 => {
  504. Name => 'PaletteName',
  505. Format => 'string[32]',
  506. # (not valid for all images)
  507. RawConv => q{
  508. $val =~ s/\0.*//;
  509. $val =~ /^[\x20-\x7e]{3,31}$/ ? $val : undef;
  510. },
  511. },
  512. 0x70 => {
  513. Name => 'Palette',
  514. Format => 'undef[3*$$self{PaletteColors}]',
  515. Notes => 'Y Cr Cb byte values for each palette color',
  516. Binary => 1,
  517. },
  518. );
  519. # FLIR text information record (ref PH)
  520. %Image::ExifTool::FLIR::TextInfo = (
  521. GROUPS => { 0 => 'APP1', 2 => 'Image' },
  522. PROCESS_PROC => \&ProcessFLIRText,
  523. VARS => { NO_ID => 1 },
  524. Label0 => { },
  525. Value0 => { },
  526. Label1 => { },
  527. Value1 => { },
  528. Label2 => { },
  529. Value2 => { },
  530. Label3 => { },
  531. Value3 => { },
  532. # (there could be more, and we will generate these on the fly if necessary)
  533. );
  534. # FLIR parameter information record (ref PH)
  535. %Image::ExifTool::FLIR::ParamInfo = (
  536. GROUPS => { 0 => 'APP1', 2 => 'Image' },
  537. PROCESS_PROC => \&ProcessFLIRText,
  538. VARS => { NO_ID => 1 },
  539. Generated => {
  540. Name => 'DateTimeGenerated',
  541. Description => 'Date/Time Generated',
  542. Groups => { 2 => 'Time' },
  543. ValueConv => '$val =~ tr/-/:/; $val',
  544. PrintConv => '$self->ConvertDateTime($val)',
  545. },
  546. Param0 => { },
  547. Param1 => { },
  548. Param2 => { },
  549. Param3 => { },
  550. # (there could be more, and we will generate these on the fly if necessary)
  551. );
  552. # FLIR Picture in Picture record (ref 1)
  553. %Image::ExifTool::FLIR::PiP = (
  554. GROUPS => { 0 => 'APP1', 2 => 'Image' },
  555. PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
  556. FIRST_ENTRY => 0,
  557. NOTES => 'FLIR Picture in Picture tags.',
  558. FORMAT => 'int16s',
  559. 0x00 => {
  560. Name => 'Real2IR',
  561. Format => 'float',
  562. },
  563. 2 => {
  564. Name => 'OffsetX',
  565. Notes => 'offset from of insertion point from center',
  566. PrintConv => 'sprintf("%+d",$val)', # (add sign for direct use with IM convert)
  567. },
  568. 3 => {
  569. Name => 'OffsetY',
  570. PrintConv => 'sprintf("%+d",$val)',
  571. },
  572. 4 => {
  573. Name => 'PiPX1',
  574. Description => 'PiP X1',
  575. Notes => 'crop size for radiometric image',
  576. },
  577. 5 => { Name => 'PiPX2', Description => 'PiP X2' },
  578. 6 => { Name => 'PiPY1', Description => 'PiP Y1' },
  579. 7 => { Name => 'PiPY2', Description => 'PiP Y2' },
  580. );
  581. # FLIR GPS record (ref PH/JD)
  582. %Image::ExifTool::FLIR::GPSInfo = (
  583. GROUPS => { 0 => 'APP1', 2 => 'Image' },
  584. PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
  585. FIRST_ENTRY => 0,
  586. 0x58 => {
  587. Name => 'GPSMapDatum',
  588. Format => 'string[16]',
  589. },
  590. );
  591. # humidity meter information
  592. # (ref http://u88.n24.queensu.ca/exiftool/forum/index.php/topic,5325.0.html)
  593. # The %Image::ExifTool::UserDefined hash defines new tags to be added to existing tables.
  594. %Image::ExifTool::FLIR::MeterLink = (
  595. GROUPS => { 0 => 'APP1', 2 => 'Image' },
  596. PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
  597. FIRST_ENTRY => 0,
  598. NOTES => 'Tags containing Meterlink humidity meter information.',
  599. 26 => {
  600. Name => 'Reading1Units',
  601. DataMember => 'Reading1Units',
  602. RawConv => '$$self{Reading1Units} = $val',
  603. PrintHex => 1,
  604. PrintConv => {
  605. 0x0d => 'C',
  606. 0x1b => '%',
  607. 0x1d => 'Relative',
  608. 0x24 => 'g/kg',
  609. },
  610. },
  611. 28 => {
  612. Name => 'Reading1Description',
  613. DataMember => 'Reading1Description',
  614. RawConv => '$$self{Reading1Description} = $val',
  615. PrintConv => {
  616. 0 => 'Humidity',
  617. 3 => 'Moisture', # Pinless Moisture Readings with INTernal sensor
  618. 7 => 'Dew Point',
  619. 8 => 'Air Temperature',
  620. 9 => 'IR Temperature',
  621. 11 => 'Difference Temperature', # Difference Temp: IR-Temp and DewPoint
  622. },
  623. },
  624. 32 => {
  625. Name => 'Reading1Device',
  626. Format => 'string[16]',
  627. },
  628. 96 => {
  629. Name => 'Reading1Value',
  630. Format => 'double',
  631. # convert Kelvin -> Celcius and kg/kg -> g/kg
  632. ValueConv => q{
  633. return $val - 273.15 if $$self{Reading1Units} == 0x0d and $$self{Reading1Description} != 11;
  634. return $val *= 1000 if $$self{Reading1Units} == 0x24;
  635. return $val;
  636. },
  637. },
  638. # add 100 for subsequent readings
  639. 126 => {
  640. Name => 'Reading2Units',
  641. DataMember => 'Reading2Units',
  642. RawConv => '$$self{Reading2Units} = $val',
  643. PrintHex => 1,
  644. PrintConv => {
  645. 0x0d => 'C',
  646. 0x1b => '%',
  647. 0x1d => 'rel',
  648. 0x24 => 'g/kg',
  649. },
  650. },
  651. 128 => {
  652. Name => 'Reading2Description',
  653. DataMember => 'Reading2Description',
  654. RawConv => '$$self{Reading2Description} = $val',
  655. PrintConv => {
  656. 0 => 'Humidity',
  657. 3 => 'Moisture',
  658. 7 => 'Dew Point',
  659. 8 => 'Air Temperature',
  660. 9 => 'IR Temperature',
  661. 11 => 'Difference Temperature', # Difference Temp: IR-Temp and DewPoint
  662. },
  663. },
  664. 132 => {
  665. Name => 'Reading2Device',
  666. Format => 'string[16]',
  667. },
  668. 196 => {
  669. Name => 'Reading2Value',
  670. Format => 'double',
  671. # convert Kelvin -> Celcius and kg/kg -> g/kg
  672. ValueConv => q{
  673. return $val - 273.15 if $$self{Reading2Units} == 0x0d and $$self{Reading2Description} != 11;
  674. return $val *= 1000 if $$self{Reading2Units} == 0x24;
  675. return $val;
  676. },
  677. },
  678. 226 => {
  679. Name => 'Reading3Units',
  680. DataMember => 'Reading3Units',
  681. RawConv => '$$self{Reading3Units} = $val',
  682. PrintHex => 1,
  683. PrintConv => {
  684. 0x0d => 'C',
  685. 0x1b => '%',
  686. 0x1d => 'rel',
  687. 0x24 => 'g/kg',
  688. },
  689. },
  690. 228 => {
  691. Name => 'Reading3Description',
  692. DataMember => 'Reading3Description',
  693. RawConv => '$$self{Reading3Description} = $val',
  694. PrintConv => {
  695. 0 => 'Humidity',
  696. 3 => 'Moisture',
  697. 7 => 'Dew Point',
  698. 8 => 'Air Temperature',
  699. 9 => 'IR Temperature',
  700. 11 => 'Difference Temperature', # Difference Temp: IR-Temp and DewPoint
  701. },
  702. },
  703. 232 => {
  704. Name => 'Reading3Device',
  705. Format => 'string[16]',
  706. },
  707. 296 => {
  708. Name => 'Reading3Value',
  709. Format => 'double',
  710. # convert Kelvin -> Celcius and kg/kg -> g/kg
  711. ValueConv => q{
  712. return $val - 273.15 if $$self{Reading3Units} == 0x0d and $$self{Reading3Description} != 11;
  713. return $val *= 1000 if $$self{Reading3Units} == 0x24;
  714. return $val;
  715. },
  716. },
  717. 326 => {
  718. Name => 'Reading4Units',
  719. DataMember => 'Reading4Units',
  720. RawConv => '$$self{Reading4Units} = $val',
  721. PrintHex => 1,
  722. PrintConv => {
  723. 0x0d => 'C',
  724. 0x1b => '%',
  725. 0x1d => 'rel',
  726. 0x24 => 'g/kg',
  727. },
  728. },
  729. 328 => {
  730. Name => 'Reading4Description',
  731. DataMember => 'Reading4Description',
  732. RawConv => '$$self{Reading4Description} = $val',
  733. PrintConv => {
  734. 0 => 'Humidity',
  735. 3 => 'Moisture',
  736. 7 => 'Dew Point',
  737. 8 => 'Air Temperature',
  738. 9 => 'IR Temperature',
  739. 11 => 'Difference Temperature', # Difference Temp: IR-Temp and DewPoint
  740. },
  741. },
  742. 332 => {
  743. Name => 'Reading4Device',
  744. Format => 'string[16]',
  745. },
  746. 396 => {
  747. Name => 'Reading4Value',
  748. Format => 'double',
  749. # convert Kelvin -> Celcius and kg/kg -> g/kg
  750. ValueConv => q{
  751. return $val - 273.15 if $$self{Reading4Units} == 0x0d and $$self{Reading4Description} != 11;
  752. return $val *= 1000 if $$self{Reading4Units} == 0x24;
  753. return $val;
  754. },
  755. },
  756. );
  757. # FLIR public image format (ref 4/5)
  758. %Image::ExifTool::FLIR::FPF = (
  759. GROUPS => { 0 => 'FLIR', 2 => 'Image' },
  760. PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
  761. NOTES => 'Tags extracted from FLIR Public image Format (FPF) files.',
  762. 0x20 => { Name => 'FPFVersion', Format => 'int32u' },
  763. 0x24 => { Name => 'ImageDataOffset', Format => 'int32u' },
  764. 0x28 => {
  765. Name => 'ImageType',
  766. Format => 'int16u',
  767. PrintConv => {
  768. 0 => 'Temperature',
  769. 1 => 'Temperature Difference',
  770. 2 => 'Object Signal',
  771. 3 => 'Object Signal Difference',
  772. },
  773. },
  774. 0x2a => {
  775. Name => 'ImagePixelFormat',
  776. Format => 'int16u',
  777. PrintConv => {
  778. 0 => '2-byte short integer',
  779. 1 => '4-byte long integer',
  780. 2 => '4-byte float',
  781. 3 => '8-byte double',
  782. },
  783. },
  784. 0x2c => { Name => 'ImageWidth', Format => 'int16u' },
  785. 0x2e => { Name => 'ImageHeight', Format => 'int16u' },
  786. 0x30 => { Name => 'ExternalTriggerCount',Format => 'int32u' },
  787. 0x34 => { Name => 'SequenceFrameNumber',Format => 'int32u' },
  788. 0x78 => { Name => 'CameraModel', Format => 'string[32]', Groups => { 2 => 'Camera' } },
  789. 0x98 => { Name => 'CameraPartNumber', Format => 'string[32]', Groups => { 2 => 'Camera' } },
  790. 0xb8 => { Name => 'CameraSerialNumber', Format => 'string[32]', Groups => { 2 => 'Camera' } },
  791. 0xd8 => { Name => 'CameraTemperatureRangeMin', %floatKelvin, Groups => { 2 => 'Camera' } },
  792. 0xdc => { Name => 'CameraTemperatureRangeMax', %floatKelvin, Groups => { 2 => 'Camera' } },
  793. 0xe0 => { Name => 'LensModel', Format => 'string[32]', Groups => { 2 => 'Camera' } },
  794. 0x100 => { Name => 'LensPartNumber', Format => 'string[32]', Groups => { 2 => 'Camera' } },
  795. 0x120 => { Name => 'LensSerialNumber', Format => 'string[32]', Groups => { 2 => 'Camera' } },
  796. 0x140 => { Name => 'FilterModel', Format => 'string[32]', Groups => { 2 => 'Camera' } },
  797. 0x150 => { Name => 'FilterPartNumber', Format => 'string[32]', Groups => { 2 => 'Camera' } },
  798. 0x180 => { Name => 'FilterSerialNumber',Format => 'string[32]', Groups => { 2 => 'Camera' } },
  799. 0x1e0 => { Name => 'Emissivity', %float2f },
  800. 0x1e4 => { Name => 'ObjectDistance', Format => 'float', PrintConv => 'sprintf("%.2f m",$val)' },
  801. 0x1e8 => { Name => 'ReflectedApparentTemperature', %floatKelvin },
  802. 0x1ec => { Name => 'AtmosphericTemperature', %floatKelvin },
  803. 0x1f0 => { Name => 'RelativeHumidity', Format => 'float', PrintConv => 'sprintf("%.1f %%",$val*100)' },
  804. 0x1f4 => { Name => 'ComputedAtmosphericTrans', %float2f },
  805. 0x1f8 => { Name => 'EstimatedAtmosphericTrans',%float2f },
  806. 0x1fc => { Name => 'ReferenceTemperature', %floatKelvin },
  807. 0x200 => { Name => 'IRWindowTemperature', %floatKelvin, Groups => { 2 => 'Camera' } },
  808. 0x204 => { Name => 'IRWindowTransmission', %float2f, Groups => { 2 => 'Camera' } },
  809. 0x248 => {
  810. Name => 'DateTimeOriginal',
  811. Description => 'Date/Time Original',
  812. Groups => { 2 => 'Time' },
  813. Format => 'int32u[7]',
  814. ValueConv => 'sprintf("%.4d:%.2d:%.2d %.2d:%.2d:%.2d.%.3d",split(" ",$val))',
  815. PrintConv => '$self->ConvertDateTime($val)',
  816. },
  817. # Notes (based on ref 4):
  818. # 1) The above date/time structure is documented to be 32 bytes for FPFVersion 1, but in
  819. # fact it is only 28. Maybe this is why the full header length of my FPFVersion 2
  820. # sample is 892 bytes instead of 896. If this was a documentation error, we are OK,
  821. # but if the alignment was really different in version 1, then the temperatures below
  822. # will be mis-aligned. I don't have any version 1 samples to check this.
  823. # 2) The following temperatures may not always be in Kelvin
  824. 0x2a4 => { Name => 'CameraScaleMin', %float1f },
  825. 0x2a8 => { Name => 'CameraScaleMax', %float1f },
  826. 0x2ac => { Name => 'CalculatedScaleMin',%float1f },
  827. 0x2b0 => { Name => 'CalculatedScaleMax',%float1f },
  828. 0x2b4 => { Name => 'ActualScaleMin', %float1f },
  829. 0x2b8 => { Name => 'ActualScaleMax', %float1f },
  830. );
  831. # top-level user data written by FLIR cameras in MP4 videos
  832. %Image::ExifTool::FLIR::UserData = (
  833. GROUPS => { 1 => 'FLIR', 2 => 'Camera' },
  834. NOTES => q{
  835. Tags written by some FLIR cameras in a top-level (!) "udta" atom of MP4
  836. videos.
  837. },
  838. uuid => [
  839. {
  840. Name => 'FLIR_Parts',
  841. Condition => '$$valPt=~/^\x43\xc3\x99\x3b\x0f\x94\x42\x4b\x82\x05\x6b\x66\x51\x3f\x48\x5d/s',
  842. SubDirectory => {
  843. TagTable => 'Image::ExifTool::FLIR::Parts',
  844. Start => 16,
  845. },
  846. },
  847. {
  848. Name => 'FLIR_Serial',
  849. Condition => '$$valPt=~/^\x57\xf5\xb9\x3e\x51\xe4\x48\xaf\xa0\xd9\xc3\xef\x1b\x37\xf7\x12/s',
  850. SubDirectory => {
  851. TagTable => 'Image::ExifTool::FLIR::SerialNums',
  852. Start => 16,
  853. },
  854. },
  855. {
  856. Name => 'FLIR_Params',
  857. Condition => '$$valPt=~/^\x41\xe5\xdc\xf9\xe8\x0a\x41\xce\xad\xfe\x7f\x0c\x58\x08\x2c\x19/s',
  858. SubDirectory => {
  859. TagTable => 'Image::ExifTool::FLIR::Params',
  860. Start => 16,
  861. },
  862. },
  863. {
  864. Name => 'FLIR_UnknownUUID',
  865. Condition => '$$valPt=~/^\x57\x45\x20\x50\x2c\xbb\x44\xad\xae\x54\x15\xe9\xb8\x39\xd9\x03/s',
  866. SubDirectory => {
  867. TagTable => 'Image::ExifTool::FLIR::UnknownUUID',
  868. Start => 16,
  869. },
  870. },
  871. {
  872. Name => 'FLIR_GPS',
  873. Condition => '$$valPt=~/^\x7f\x2e\x21\x00\x8b\x46\x49\x18\xaf\xb1\xde\x70\x9a\x74\xf6\xf5/s',
  874. SubDirectory => {
  875. TagTable => 'Image::ExifTool::FLIR::GPS_UUID',
  876. Start => 16,
  877. },
  878. },
  879. {
  880. Name => 'FLIR_MoreInfo',
  881. Condition => '$$valPt=~/^\x2b\x45\x2f\xdc\x74\x35\x40\x94\xba\xee\x22\xa6\xb2\x3a\x7c\xf8/s',
  882. SubDirectory => {
  883. TagTable => 'Image::ExifTool::FLIR::MoreInfo',
  884. Start => 16,
  885. },
  886. },
  887. {
  888. Name => 'SoftwareComponents',
  889. Condition => '$$valPt=~/^\x78\x3f\xc7\x83\x0c\x95\x4b\x00\x8c\xc7\xac\xf1\xec\xb4\xd3\x9a/s',
  890. Unknown => 1,
  891. ValueConv => 'join " ", unpack "x20N4xZ*", $val',
  892. },
  893. {
  894. Name => 'FLIR_Unknown',
  895. Condition => '$$valPt=~/^\x52\xae\xda\x45\x17\x1e\x48\xb1\x92\x47\x93\xa4\x21\x4e\x43\xf5/s',
  896. Unknown => 1,
  897. ValueConv => 'unpack "x20C*", $val',
  898. },
  899. {
  900. Name => 'Units',
  901. Condition => '$$valPt=~/^\xf8\xab\x72\x1e\x84\x73\x44\xa0\xb8\xc8\x1b\x04\x82\x6e\x07\x24/s',
  902. List => 1,
  903. RawConv => 'my @a = split "\0", substr($val, 20); \@a',
  904. },
  905. {
  906. Name => 'ThumbnailImage',
  907. Groups => { 2 => 'Preview' },
  908. Condition => '$$valPt=~/^\x91\xaf\x9b\x93\x45\x9b\x44\x56\x98\xd1\x5e\x76\xea\x01\x04\xac....\xff\xd8\xff/s',
  909. RawConv => 'substr($val, 20)',
  910. Binary => 1,
  911. },
  912. ],
  913. );
  914. # uuid 43c3993b0f94424b82056b66513f485d box of MP4 videos (ref PH)
  915. %Image::ExifTool::FLIR::Parts = (
  916. GROUPS => { 0 => 'MakerNotes', 1 => 'FLIR', 2 => 'Camera' },
  917. PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
  918. FORMAT => 'undef',
  919. NOTES => q{
  920. Tags extracted from the "uuid" box with ID 43c3993b0f94424b82056b66513f485d
  921. in FLIR MP4 videos.
  922. },
  923. 4 => [
  924. {
  925. Name => 'BAHPVer',
  926. Condition => '$$valPt =~ /^bahpver\0/',
  927. Format => 'undef[$size]',
  928. RawConv => 'join " ", split "\0", substr($val, 8)',
  929. },
  930. {
  931. Name => 'BALPVer',
  932. Condition => '$$valPt =~ /^balpver\0/',
  933. Format => 'undef[$size]',
  934. ValueConv => 'join " ", split "\0", substr($val, 8)',
  935. },
  936. {
  937. Name => 'Battery',
  938. Condition => '$$valPt =~ /^battery\0/',
  939. Format => 'undef[$size]',
  940. ValueConv => 'join " ", split "\0", substr($val, 8)',
  941. },
  942. {
  943. Name => 'BAVPVer',
  944. Condition => '$$valPt =~ /^bavpver\0/',
  945. Format => 'undef[$size]',
  946. ValueConv => 'join " ", split "\0", substr($val, 8)',
  947. # (the first string corresponds with a lens part number)
  948. },
  949. {
  950. Name => 'CamCore',
  951. Condition => '$$valPt =~ /^camcore\0/',
  952. Format => 'undef[$size]',
  953. ValueConv => 'join " ", split "\0", substr($val, 8)',
  954. },
  955. {
  956. Name => 'DetectorBoard',
  957. Condition => '$$valPt =~ /^det_board\0/',
  958. Format => 'undef[$size]',
  959. ValueConv => 'join " ", split "\0", substr($val, 10)',
  960. },
  961. {
  962. Name => 'Detector',
  963. Condition => '$$valPt =~ /^detector\0/',
  964. Format => 'undef[$size]',
  965. ValueConv => 'join " ", split "\0", substr($val, 9)',
  966. },
  967. {
  968. Name => 'GIDCVer',
  969. Condition => '$$valPt =~ /^gidcver\0/',
  970. Format => 'undef[$size]',
  971. ValueConv => 'join " ", split "\0", substr($val, 8)',
  972. },
  973. {
  974. Name => 'GIDPVer',
  975. Condition => '$$valPt =~ /^gidpver\0/',
  976. Format => 'undef[$size]',
  977. ValueConv => 'join " ", split "\0", substr($val, 8)',
  978. },
  979. {
  980. Name => 'GIPC_CPLD',
  981. Condition => '$$valPt =~ /^gipccpld\0/',
  982. Format => 'undef[$size]',
  983. ValueConv => 'join " ", split "\0", substr($val, 9)',
  984. },
  985. {
  986. Name => 'GIPCVer',
  987. Condition => '$$valPt =~ /^gipcver\0/',
  988. Format => 'undef[$size]',
  989. ValueConv => 'join " ", split "\0", substr($val, 8)',
  990. },
  991. {
  992. Name => 'GIXIVer',
  993. Condition => '$$valPt =~ /^gixiver\0/',
  994. Format => 'undef[$size]',
  995. ValueConv => 'join " ", split "\0", substr($val, 8)',
  996. },
  997. {
  998. Name => 'MainBoard',
  999. Condition => '$$valPt =~ /^mainboard\0/',
  1000. Format => 'undef[$size]',
  1001. ValueConv => 'join " ", split "\0", substr($val, 10)',
  1002. },
  1003. {
  1004. Name => 'Optics',
  1005. Condition => '$$valPt =~ /^optics\0/',
  1006. Format => 'undef[$size]',
  1007. ValueConv => 'join " ", split "\0", substr($val, 7)',
  1008. },
  1009. {
  1010. Name => 'PartNumber',
  1011. Format => 'undef[$size]',
  1012. ValueConv => 'join " ", split "\0", $val',
  1013. },
  1014. ],
  1015. );
  1016. # uuid 57f5b93e51e448afa0d9c3ef1b37f712 box of MP4 videos (ref PH)
  1017. %Image::ExifTool::FLIR::SerialNums = (
  1018. GROUPS => { 0 => 'MakerNotes', 1 => 'FLIR', 2 => 'Camera' },
  1019. PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
  1020. FIRST_ENTRY => 0,
  1021. NOTES => q{
  1022. Tags extracted from the "uuid" box with ID 57f5b93e51e448afa0d9c3ef1b37f712
  1023. in FLIR MP4 videos.
  1024. },
  1025. # (not sure if these offsets are constant)
  1026. 0x0c => { Name => 'UnknownSerial1', Format => 'string[33]', Unknown => 1 },
  1027. 0x2d => { Name => 'UnknownSerial2', Format => 'string[33]', Unknown => 1 },
  1028. 0x4e => { Name => 'UnknownSerial3', Format => 'string[33]', Unknown => 1 },
  1029. 0x6f => { Name => 'UnknownSerial4', Format => 'string[11]', Unknown => 1 },
  1030. 0x7b => { Name => 'UnknownNumber', Format => 'string[3]', Unknown => 1 },
  1031. 0x7e => { Name => 'CameraSerialNumber', Format => 'string[9]' },
  1032. );
  1033. # uuid 41e5dcf9e80a41ceadfe7f0c58082c19 box of MP4 videos (ref PH)
  1034. %Image::ExifTool::FLIR::Params = (
  1035. GROUPS => { 0 => 'MakerNotes', 1 => 'FLIR', 2 => 'Camera' },
  1036. PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
  1037. FORMAT => 'float',
  1038. FIRST_ENTRY => 0,
  1039. NOTES => q{
  1040. Tags extracted from the "uuid" box with ID 41e5dcf9e80a41ceadfe7f0c58082c19
  1041. in FLIR MP4 videos.
  1042. },
  1043. 1 => { Name => 'ReflectedApparentTemperature', %floatKelvin },
  1044. 2 => { Name => 'AtmosphericTemperature', %floatKelvin },
  1045. 3 => { Name => 'Emissivity', %float2f },
  1046. 4 => { Name => 'ObjectDistance', PrintConv => 'sprintf("%.2f m",$val)' },
  1047. 5 => { Name => 'RelativeHumidity', PrintConv => 'sprintf("%.1f %%",$val*100)' },
  1048. 6 => { Name => 'EstimatedAtmosphericTrans', %float2f },
  1049. 7 => { Name => 'IRWindowTemperature', %floatKelvin },
  1050. 8 => { Name => 'IRWindowTransmission', %float2f },
  1051. );
  1052. # uuid 574520502cbb44adae5415e9b839d903 box of MP4 videos (ref PH)
  1053. %Image::ExifTool::FLIR::UnknownUUID = (
  1054. GROUPS => { 0 => 'MakerNotes', 1 => 'FLIR', 2 => 'Camera' },
  1055. PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
  1056. FORMAT => 'float',
  1057. FIRST_ENTRY => 0,
  1058. NOTES => q{
  1059. Tags extracted from the "uuid" box with ID 574520502cbb44adae5415e9b839d903
  1060. in FLIR MP4 videos.
  1061. },
  1062. # 1 - 1
  1063. # 2 - 0
  1064. # 3 - 0
  1065. );
  1066. # uuid 7f2e21008b464918afb1de709a74f6f5 box of MP4 videos (ref PH)
  1067. %Image::ExifTool::FLIR::GPS_UUID = (
  1068. GROUPS => { 0 => 'MakerNotes', 1 => 'FLIR', 2 => 'Location' },
  1069. PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
  1070. FORMAT => 'float',
  1071. FIRST_ENTRY => 0,
  1072. NOTES => q{
  1073. Tags extracted from the "uuid" box with ID 7f2e21008b464918afb1de709a74f6f5
  1074. in FLIR MP4 videos.
  1075. },
  1076. 1 => {
  1077. Name => 'GPSLatitude',
  1078. RawConv => 'require Image::ExifTool::GPS; $val', # to load Composite tags and routines
  1079. PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
  1080. },
  1081. 2 => {
  1082. Name => 'GPSLongitude',
  1083. PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
  1084. },
  1085. 3 => {
  1086. Name => 'GPSAltitude',
  1087. PrintConv => '$val=int($val*100+0.5)/100;"$val m"',
  1088. },
  1089. # 4 - int32u: 0x0001bf74
  1090. # 5 - int32u: 0
  1091. # 6 - int32u: 1
  1092. );
  1093. # uuid 2b452fdc74354094baee22a6b23a7cf8 box of MP4 videos (ref PH)
  1094. %Image::ExifTool::FLIR::MoreInfo = (
  1095. GROUPS => { 0 => 'MakerNotes', 1 => 'FLIR', 2 => 'Camera' },
  1096. PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
  1097. FIRST_ENTRY => 0,
  1098. NOTES => q{
  1099. Tags extracted from the "uuid" box with ID 2b452fdc74354094baee22a6b23a7cf8
  1100. in FLIR MP4 videos.
  1101. },
  1102. 5 => { Name => 'LensModel', Format => 'string[6]' },
  1103. 11 => { Name => 'UnknownTemperature1', %floatKelvin, Unknown => 1 }, # (-14.9 C)
  1104. 15 => { Name => 'UnknownTemperature2', %floatKelvin, Unknown => 1 }, # (60.0 C)
  1105. );
  1106. # FLIR AFF tag table (ref PH)
  1107. %Image::ExifTool::FLIR::AFF = (
  1108. GROUPS => { 0 => 'FLIR', 1 => 'FLIR', 2 => 'Image' },
  1109. NOTES => 'Tags extracted from FLIR "AFF" SEQ images.',
  1110. VARS => { ALPHA_FIRST => 1 },
  1111. "_header" => {
  1112. Name => 'AFFHeader',
  1113. SubDirectory => { TagTable => 'Image::ExifTool::FLIR::Header' },
  1114. },
  1115. 0x01 => {
  1116. Name => 'AFF1',
  1117. SubDirectory => { TagTable => 'Image::ExifTool::FLIR::AFF1' },
  1118. },
  1119. 0x05 => {
  1120. Name => 'AFF5',
  1121. SubDirectory => { TagTable => 'Image::ExifTool::FLIR::AFF5' },
  1122. },
  1123. );
  1124. # AFF record type 1 (ref forum?topic=4898.msg27627)
  1125. %Image::ExifTool::FLIR::AFF1 = (
  1126. GROUPS => { 0 => 'FLIR', 1 => 'FLIR', 2 => 'Camera' },
  1127. PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
  1128. FORMAT => 'int16u',
  1129. FIRST_ENTRY => 0,
  1130. 0x00 => {
  1131. # use this tag only to determine the byte order of the raw data
  1132. # (the value should be 0x0002 if the byte order is correct)
  1133. Name => 'RawDataByteOrder',
  1134. Hidden => 1,
  1135. RawConv => 'ToggleByteOrder() if $val >= 0x0100; undef',
  1136. },
  1137. 0x01 => { Name => 'SensorWidth', Format => 'int16u' },
  1138. 0x02 => { Name => 'SensorHeight', Format => 'int16u' },
  1139. );
  1140. # AFF record type 5 (ref forum?topic=4898.msg27628)
  1141. %Image::ExifTool::FLIR::AFF5 = (
  1142. GROUPS => { 0 => 'FLIR', 1 => 'FLIR', 2 => 'Camera' },
  1143. PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
  1144. FORMAT => 'int16u',
  1145. FIRST_ENTRY => 0,
  1146. 0x12 => {
  1147. # use this tag only to determine the byte order of the raw data
  1148. # (the value should be 0x0002 if the byte order is correct)
  1149. Name => 'RawDataByteOrder',
  1150. Hidden => 1,
  1151. RawConv => 'ToggleByteOrder() if $val >= 0x0100; undef',
  1152. },
  1153. 0x13 => { Name => 'SensorWidth', Format => 'int16u' },
  1154. 0x14 => { Name => 'SensorHeight', Format => 'int16u' },
  1155. );
  1156. # FLIR composite tags (ref 1)
  1157. %Image::ExifTool::FLIR::Composite = (
  1158. GROUPS => { 1 => 'FLIR', 2 => 'Camera' },
  1159. PeakSpectralSensitivity => {
  1160. Require => 'FLIR:PlanckB',
  1161. ValueConv => '14387.6515/$val',
  1162. PrintConv => 'sprintf("%.1f um", $val)',
  1163. },
  1164. );
  1165. # add our composite tags
  1166. Image::ExifTool::AddCompositeTags('Image::ExifTool::FLIR');
  1167. #------------------------------------------------------------------------------
  1168. # Get image type from raw image data
  1169. # Inputs: 0) ExifTool ref, 1) image data, 2) tag name
  1170. # Returns: image type (PNG, JPG, TIFF or undef)
  1171. # - image itself is stored in $$et{$tag}
  1172. sub GetImageType($$$)
  1173. {
  1174. my ($et, $val, $tag) = @_;
  1175. my ($w, $h) = @$et{"${tag}Width","${tag}Height"};
  1176. my $type = 'DAT';
  1177. # add TIFF header only if this looks like 16-bit raw data
  1178. # (note: MakeTiffHeader currently works only for little-endian,
  1179. # and I haven't seen any big-endian samples, but check anwyay)
  1180. if ($val =~ /^\x89PNG\r\n\x1a\n/) {
  1181. $type = 'PNG';
  1182. } elsif ($val =~ /^\xff\xd8\xff/) { # (haven't seen this, but just in case - PH)
  1183. $type = 'JPG';
  1184. } elsif (length $val != $w * $h * 2) {
  1185. $et->Warn("Unrecognized FLIR $tag data format");
  1186. } elsif (GetByteOrder() eq 'II') {
  1187. require Image::ExifTool::Sony;
  1188. $val = Image::ExifTool::Sony::MakeTiffHeader($w,$h,1,16) . $val;
  1189. $type = 'TIFF';
  1190. } else {
  1191. $et->Warn("Don't yet support big-endian TIFF $tag");
  1192. }
  1193. # save image data
  1194. $$et{$tag} = $val;
  1195. return $type;
  1196. }
  1197. #------------------------------------------------------------------------------
  1198. # Unescape FLIR Unicode character
  1199. # Inputs: 0) escaped character code
  1200. # Returns: UTF8 character
  1201. sub UnescapeFLIR($)
  1202. {
  1203. my $char = shift;
  1204. return $char unless length $char eq 4; # escaped ASCII char (eg. '\\')
  1205. my $val = hex $char;
  1206. return chr($val) if $val < 0x80; # simple ASCII
  1207. return pack('C0U', $val) if $] >= 5.006001;
  1208. return Image::ExifTool::PackUTF8($val);
  1209. }
  1210. #------------------------------------------------------------------------------
  1211. # Process FLIR text info record (ref PH)
  1212. # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
  1213. # Returns: 1 on success
  1214. sub ProcessFLIRText($$$)
  1215. {
  1216. my ($et, $dirInfo, $tagTablePtr) = @_;
  1217. my $dataPt = $$dirInfo{DataPt};
  1218. my $dirStart = $$dirInfo{DirStart} || 0;
  1219. my $dirLen = $$dirInfo{DirLen};
  1220. return 0 if $dirLen < 12;
  1221. $et->VerboseDir('FLIR Text');
  1222. my $dat = substr($$dataPt, $dirStart+12, $dirLen-12);
  1223. $dat =~ s/\0.*//s; # truncate at null
  1224. # the parameter text contains an additional header entry...
  1225. if ($tagTablePtr eq \%Image::ExifTool::FLIR::ParamInfo and
  1226. $dat =~ /# (Generated) at (.*?)[\n\r]/)
  1227. {
  1228. $et->HandleTag($tagTablePtr, $1, $2);
  1229. }
  1230. for (;;) {
  1231. $dat =~ /.(\d+).(label|value|param) (unicode|text) "(.*)"/g or last;
  1232. my ($tag, $val) = (ucfirst($2) . $1, $4);
  1233. if ($3 eq 'unicode' and $val =~ /\\/) {
  1234. # convert escaped Unicode characters (backslash followed by 4 hex digits)
  1235. $val =~ s/\\([0-9a-fA-F]{4}|.)/UnescapeFLIR($1)/sge;
  1236. $et->Decode($val, 'UTF8');
  1237. }
  1238. $$tagTablePtr{$tag} or AddTagToTable($tagTablePtr, $tag, { Name => $tag });
  1239. $et->HandleTag($tagTablePtr, $tag, $val);
  1240. }
  1241. return 1;
  1242. }
  1243. #------------------------------------------------------------------------------
  1244. # Process FLIR measurement tool record (ref 6)
  1245. # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
  1246. # Returns: 1 on success
  1247. # (code-driven decoding isn't pretty, but sometimes it is necessary)
  1248. sub ProcessMeasInfo($$$)
  1249. {
  1250. my ($et, $dirInfo, $tagTablePtr) = @_;
  1251. my $dataPt = $$dirInfo{DataPt};
  1252. my $dirStart = $$dirInfo{DirStart} || 0;
  1253. my $dataPos = $$dirInfo{DataPos};
  1254. my $dirEnd = $dirStart + $$dirInfo{DirLen};
  1255. my $verbose = $et->Options('Verbose');
  1256. my $pos = $dirStart + 12;
  1257. return 0 if $pos > $dirEnd;
  1258. ToggleByteOrder() if Get16u($dataPt, $dirStart) >= 0x100;
  1259. my ($i, $t, $p);
  1260. for ($i=1; ; ++$i) {
  1261. last if $pos + 2 > $dirEnd;
  1262. my $recLen = Get16u($dataPt, $pos);
  1263. last if $recLen < 0x28 or $pos + $recLen > $dirEnd;
  1264. my $pre = 'Meas' . $i;
  1265. $et->VerboseDir("MeasInfo $i", undef, $recLen);
  1266. if ($verbose > 2) {
  1267. HexDump($dataPt, $recLen,
  1268. Start=>$pos, Prefix=>$$et{INDENT}, DataPos=>$dataPos);
  1269. }
  1270. my $coordLen = Get16u($dataPt, $pos+4);
  1271. # generate tag table entries for this tool if necessary
  1272. foreach $t ('Type', 'Params', 'Label') {
  1273. my $tag = $pre . $t;
  1274. last if $$tagTablePtr{$tag};
  1275. my $tagInfo = { Name => $tag };
  1276. $$tagInfo{PrintConv} = $$tagTablePtr{"Meas1$t"}{PrintConv};
  1277. AddTagToTable($tagTablePtr, $tag, $tagInfo);
  1278. }
  1279. # extract measurement tool type
  1280. $et->HandleTag($tagTablePtr, "${pre}Type", undef,
  1281. DataPt=>$dataPt, DataPos=>$dataPos, Start=>$pos+0x0a, Size=>2);
  1282. last if $pos + 0x24 + $coordLen > $dirEnd;
  1283. # extract measurement parameters
  1284. $et->HandleTag($tagTablePtr, "${pre}Params", undef,
  1285. DataPt=>$dataPt, DataPos=>$dataPos, Start=>$pos+0x24, Size=>$coordLen);
  1286. my @uni;
  1287. # extract label (sometimes-null-terminated Unicode)
  1288. for ($p=0x24+$coordLen; $p<$recLen-1; $p+=2) {
  1289. my $ch = Get16u($dataPt, $p+$pos);
  1290. # FLIR Tools v2.0 for Mac doesn't properly null-terminate these strings,
  1291. # so end the string at any funny character
  1292. last if $ch < 0x20 or $ch > 0x7f;
  1293. push @uni, $ch;
  1294. }
  1295. # convert to the ExifTool character set
  1296. require Image::ExifTool::Charset;
  1297. my $val = Image::ExifTool::Charset::Recompose($et, \@uni);
  1298. $et->HandleTag($tagTablePtr, "${pre}Label", $val,
  1299. DataPt=>$dataPt, DataPos=>$dataPos, Start=>$pos+0x24+$coordLen, Size=>2*scalar(@uni));
  1300. $pos += $recLen; # step to next record
  1301. }
  1302. return 1;
  1303. }
  1304. #------------------------------------------------------------------------------
  1305. # Process FLIR FFF record (ref PH/1/3)
  1306. # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
  1307. # Returns: 1 if this was a valid FFF record
  1308. sub ProcessFLIR($$;$)
  1309. {
  1310. my ($et, $dirInfo, $tagTablePtr) = @_;
  1311. my $raf = $$dirInfo{RAF} || new File::RandomAccess($$dirInfo{DataPt});
  1312. my $verbose = $et->Options('Verbose');
  1313. my $out = $et->Options('TextOut');
  1314. my ($i, $hdr, $buff, $rec);
  1315. # read and verify FFF header
  1316. $raf->Read($hdr, 0x40) == 0x40 and $hdr =~ /^([AF]FF)\0/ or return 0;
  1317. my $type = $1;
  1318. # set file type if reading from FFF or SEQ file ($tagTablePtr will not be defined)
  1319. $et->SetFileType($type eq 'FFF' ? 'FLIR' : 'SEQ') unless $tagTablePtr;
  1320. # FLIR file header (ref 3)
  1321. # 0x00 - string[4] file format ID = "FFF\0"
  1322. # 0x04 - string[16] file creator: seen "\0","MTX IR\0","CAMCTRL\0"
  1323. # 0x14 - int32u file format version = 100
  1324. # 0x18 - int32u offset to record directory
  1325. # 0x1c - int32u number of entries in record directory
  1326. # 0x20 - int32u next free index ID = 2
  1327. # 0x24 - int16u swap pattern = 0 (?)
  1328. # 0x28 - int16u[7] spares
  1329. # 0x34 - int32u[2] reserved
  1330. # 0x3c - int32u checksum
  1331. # determine byte ordering by validating version number
  1332. # (in my samples FLIR APP1 is big-endian, FFF files are little-endian)
  1333. for ($i=0; ; ++$i) {
  1334. my $ver = Get32u(\$hdr, 0x14);
  1335. last if $ver >= 100 and $ver < 200; # (have seen 100 and 101 - PH)
  1336. ToggleByteOrder();
  1337. $i and $et->Warn("Unsupported FLIR $type version"), return 1;
  1338. }
  1339. # read the FLIR record directory
  1340. my $pos = Get32u(\$hdr, 0x18);
  1341. my $num = Get32u(\$hdr, 0x1c);
  1342. unless ($raf->Seek($pos) and $raf->Read($buff, $num * 0x20) == $num * 0x20) {
  1343. $et->Warn('Truncated FLIR FFF directory');
  1344. return 1;
  1345. }
  1346. unless ($tagTablePtr) {
  1347. $tagTablePtr = GetTagTable("Image::ExifTool::FLIR::$type");
  1348. $$et{SET_GROUP0} = 'FLIR'; # (set group 0 to 'FLIR' for FFF files)
  1349. }
  1350. # process the header data
  1351. $et->HandleTag($tagTablePtr, '_header', $hdr);
  1352. my $oldIndent = $$et{INDENT};
  1353. $$et{INDENT} .= '| ';
  1354. $et->VerboseDir($type, $num);
  1355. for ($i=0; $i<$num; ++$i) {
  1356. # FLIR record entry (ref 3):
  1357. # 0x00 - int16u record type
  1358. # 0x02 - int16u record subtype: RawData 1=BE, 2=LE, 3=PNG; 1 for other record types
  1359. # 0x04 - int32u record version: seen 0x64,0x66,0x67,0x68,0x6f,0x104
  1360. # 0x08 - int32u index id = 1
  1361. # 0x0c - int32u record offset from start of FLIR data
  1362. # 0x10 - int32u record length
  1363. # 0x14 - int32u parent = 0 (?)
  1364. # 0x18 - int32u object number = 0 (?)
  1365. # 0x1c - int32u checksum: 0 for no checksum
  1366. my $entry = $i * 0x20;
  1367. my $recType = Get16u(\$buff, $entry);
  1368. next if $recType == 0; # ignore free records
  1369. my $recPos = Get32u(\$buff, $entry + 0x0c);
  1370. my $recLen = Get32u(\$buff, $entry + 0x10);
  1371. $verbose and printf $out "%sFLIR Record 0x%.2x, offset 0x%.4x, length 0x%.4x\n",
  1372. $$et{INDENT}, $recType, $recPos, $recLen;
  1373. unless ($raf->Seek($recPos) and $raf->Read($rec, $recLen) == $recLen) {
  1374. $et->Warn('Invalid FLIR record');
  1375. last;
  1376. }
  1377. if ($$tagTablePtr{$recType}) {
  1378. $et->HandleTag($tagTablePtr, $recType, undef,
  1379. DataPt => \$rec,
  1380. DataPos => $recPos,
  1381. Start => 0,
  1382. Size => $recLen,
  1383. Index => $i,
  1384. );
  1385. } elsif ($verbose > 2) {
  1386. my %parms = ( DataPos => $recPos, Prefix => $$et{INDENT} );
  1387. $parms{MaxLen} = 96 if $verbose < 4;
  1388. HexDump(\$rec, $recLen, %parms);
  1389. }
  1390. }
  1391. delete $$et{SET_GROUP0};
  1392. $$et{INDENT} = $oldIndent;
  1393. return 1;
  1394. }
  1395. #------------------------------------------------------------------------------
  1396. # Process FLIR public image format (FPF) file (ref PH/4)
  1397. # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
  1398. # Returns: 1 if this was a valid FFF file
  1399. sub ProcessFPF($$)
  1400. {
  1401. my ($et, $dirInfo) = @_;
  1402. my $raf = $$dirInfo{RAF};
  1403. my $buff;
  1404. $raf->Read($buff, 892) == 892 and $buff =~ /^FPF Public Image Format\0/ or return 0;
  1405. # I think these are always little-endian, but check FPFVersion just in case
  1406. SetByteOrder('II');
  1407. ToggleByteOrder() unless Get32u(\$buff, 0x20) & 0xffff;
  1408. my $tagTablePtr = GetTagTable('Image::ExifTool::FLIR::FPF');
  1409. $et->SetFileType();
  1410. $et->ProcessDirectory( { DataPt => \$buff, Parent => 'FPF' }, $tagTablePtr);
  1411. return 1;
  1412. }
  1413. 1; # end
  1414. __END__
  1415. =head1 NAME
  1416. Image::ExifTool::FLIR - Read FLIR meta information
  1417. =head1 SYNOPSIS
  1418. This module is loaded automatically by Image::ExifTool when required.
  1419. =head1 DESCRIPTION
  1420. This module contains the definitions to read meta information from FLIR
  1421. Systems Inc. thermal image files (FFF, FPF and JPEG format).
  1422. =head1 AUTHOR
  1423. Copyright 2003-2016, Phil Harvey (phil at owl.phy.queensu.ca)
  1424. This library is free software; you can redistribute it and/or modify it
  1425. under the same terms as Perl itself.
  1426. =head1 REFERENCES
  1427. =over 4
  1428. =item L<http://u88.n24.queensu.ca/exiftool/forum/index.php/topic,4898.0.html>
  1429. =item L<http://www.nuage.ch/site/flir-i7-some-analysis/>
  1430. =item L<http://www.workswell.cz/manuals/flir/hardware/A3xx_and_A6xx_models/Streaming_format_ThermoVision.pdf>
  1431. =item L<http://support.flir.com/DocDownload/Assets/62/English/1557488%24A.pdf>
  1432. =item L<http://code.google.com/p/dvelib/source/browse/trunk/flirPublicFormat/fpfConverter/Fpfimg.h?spec=svn3&r=3>
  1433. =item L<http://u88.n24.queensu.ca/exiftool/forum/index.php/topic,5538.0.html>
  1434. =back
  1435. =head1 ACKNOWLEDGEMENTS
  1436. Thanks to Tomas for his hard work in decoding much of this information, and
  1437. to Jens Duttke for getting me started on this format.
  1438. =head1 SEE ALSO
  1439. L<Image::ExifTool::TagNames/FLIR Tags>,
  1440. L<Image::ExifTool(3pm)|Image::ExifTool>
  1441. =cut