changeset 31194:0cdb7f35641e

Tiff: added handler for imfinfo that uses the Tiff interface * __tiff__.cc (F__tiff_imfinfo__): implemented handler for imfinfo function that uses the Tiff interface to retrieve format specific information about tiff images.
author magedrifaat <magedrifaat@gmail.com>
date Sun, 28 Aug 2022 21:34:03 +0200
parents c142c153034c
children f8baeb850b36
files libinterp/corefcn/__tiff__.cc
diffstat 1 files changed, 335 insertions(+), 17 deletions(-) [+]
line wrap: on
line diff
--- a/libinterp/corefcn/__tiff__.cc	Sat Aug 27 23:06:54 2022 +0200
+++ b/libinterp/corefcn/__tiff__.cc	Sun Aug 28 21:34:03 2022 +0200
@@ -4,6 +4,7 @@
 
 #include <string>
 #include <iostream>
+#include <fstream>
 
 #include "defun.h"
 #include "ov.h"
@@ -13,7 +14,10 @@
 #include "errwarn.h"
 
 #include "fcntl-wrappers.h"
+#include "file-stat.h"
 #include "file-ops.h"
+#include "str-vec.h"
+#include "oct-time.h"
 
 #if defined (HAVE_TIFF)
 #  include <tiffio.h>
@@ -100,6 +104,9 @@
         // This doesn't completely solve the issue as LibTIFF will refuse
         // to write data to images of the tag is not set.
         // see: https://www.asmail.be/msg0054918184.html
+        // This is now solved by this commit:
+        // https://gitlab.com/libtiff/libtiff/-/commit/ac6ddaf678fff597610cb217705b55508240f065
+        // So this shouldn't be needed for future LibTIFF releases > 4.4.0
         planar_configuration = 1;
       
       is_tiled = TIFFIsTiled(tif);
@@ -3120,7 +3127,7 @@
     TIFF *tif = tiff_handle->get_file ();
     
     bool is_tiled = static_cast<bool> (TIFFIsTiled (tif));
-    return octave_value_list (octave_value (is_tiled));
+    return ovl (is_tiled);
 #else
     err_disabled_feature ("isTiled", "Tiff");
 #endif
@@ -3147,7 +3154,7 @@
       error ("The image is tiled not stripped");
     
     double strip_count = static_cast<double> (TIFFNumberOfStrips (tif));
-    return octave_value_list (octave_value (strip_count));
+    return ovl (strip_count);
 #else
     err_disabled_feature ("numberOfStrips", "Tiff");
 #endif
@@ -3174,7 +3181,7 @@
       error ("The image is stripped not tiled");
     
     double tile_count = static_cast<double> (TIFFNumberOfTiles (tif));
-    return octave_value_list (octave_value (tile_count));
+    return ovl (tile_count);
 #else
     err_disabled_feature ("numberOfTiles", "Tiff");
 #endif
@@ -3231,7 +3238,7 @@
     double strip_number = TIFFComputeStrip (tif, row, plane) + 1;
     if (strip_number > TIFFNumberOfStrips (tif))
       strip_number = TIFFNumberOfStrips (tif);
-    return octave_value_list (octave_value (strip_number));
+    return ovl (strip_number);
 #else
     err_disabled_feature ("computeStrip", "Tiff");
 #endif
@@ -3297,7 +3304,7 @@
     double tile_number = TIFFComputeTile (tif, col, row, 0, plane) + 1;
     if (tile_number > TIFFNumberOfTiles (tif))
       tile_number = TIFFNumberOfTiles (tif);
-    return octave_value_list (octave_value (tile_number));
+    return ovl (tile_number);
 #else
     err_disabled_feature ("computeTile", "Tiff");
 #endif
@@ -3323,7 +3330,7 @@
     uint16_t dir = TIFFCurrentDirectory (tif);
     dir++;
     
-    return octave_value_list (octave_value (static_cast<double> (dir)));
+    return ovl (static_cast<double> (dir));
 #else
     err_disabled_feature ("currentDirectory", "Tiff");
 #endif
@@ -3348,7 +3355,7 @@
 
     bool is_last = TIFFLastDirectory (tif);
     
-    return octave_value_list (octave_value (is_last));
+    return ovl (is_last);
 #else
     err_disabled_feature ("lastDirectory", "Tiff");
 #endif
@@ -3519,7 +3526,7 @@
   {
 #if defined (HAVE_TIFF)
     std::string version = std::string (TIFFGetVersion ());
-    return octave_value_list (octave_value (version));
+    return ovl (version);
 #else
     err_disabled_feature ("getVersion", "Tiff");
 #endif
@@ -3686,7 +3693,7 @@
           }
         else if (strcasecmp (param_cstr, "info") == 0)
           {
-            // FIXME: is this useful for our use case?
+            // This isn't very useful here, ignoring it
           }
         else
           error ("imread: invalid PARAMETER '%s'", param_cstr);
@@ -3711,7 +3718,6 @@
         retval (0) = read_float_image (tif, &image_data);
         break;
       default:
-        // FIXME: should this fallback to magick instead?
         error ("Unsupported sample format");
       }
     
@@ -3721,7 +3727,7 @@
     // than the image size because the entire image will be read first
     octave_value_list idx (3);
     // Need to use range because normal idx_vector constuctor handles steps
-    // in a wrong way (FIXME?)
+    // in a wrong way
     idx(0) = idx_vector (range<double> (row_region (0), row_region (1),
                                         row_region(2)));
     idx(1) = idx_vector (range<double> (col_region (0), col_region (1),
@@ -3762,7 +3768,11 @@
     if (! (args(0).isnumeric () || args(0).is_bool_matrix ()
            || args(0).is_bool_scalar ()))
       error ("imwrite: Expected image matrix as first argument");
-  
+
+    // The argument checks here are very similar to the ones found in
+    // __imwrite__.m and imwrite_filename.m, the code is duplicated here since
+    // the original code is tailored to the generic library graphicsmagick
+    // so it is not suitable to be used directly for this case
     uint16_t offset = 1;
     std::string filename;
     octave_value cmap = octave_value (NDArray ());
@@ -3789,12 +3799,14 @@
     if ((nargin - offset) % 2 != 0)
       error ("imwrite: no pair for all arguments (odd number left)");
 
+    // Setting the default value for the rest of the parameters
     bool append = false;
     uint16_t compression = COMPRESSION_NONE;
     uint32_t rows_per_strip = UINT32_MAX;
     float x_res = 72, y_res = 72;
     std::string description = "";
 
+    // Extract the values for the paramters provided
     for (uint16_t arg_idx = offset; arg_idx < nargin; arg_idx+=2)
       {
         if (! args(arg_idx).is_string ())
@@ -3818,13 +3830,11 @@
             else if (comp == "lzw")
               compression = COMPRESSION_LZW;
             else if (comp == "deflate")
-              // FIXME: check matlab if this is adobe deflate
               compression = COMPRESSION_DEFLATE;
             else if (comp == "jpeg")
               compression = COMPRESSION_JPEG;
             else if (comp == "ccitt")
-              // FIXME: check matlab which one this is
-              compression = COMPRESSION_CCITT_T4;
+              compression = COMPRESSION_CCITTRLE;
             else if (comp == "fax3")
               compression = COMPRESSION_CCITTFAX3;
             else if (comp == "fax4")
@@ -3885,17 +3895,20 @@
           error ("imread: invalid PARAMETER '%s'", param_cstr);
       }
     
-    // FIXME: does matlab error if appending to non existant file?
+    // Matlab specifies that JPEG compression must also specify RowsPerStrip
+    // and must be a value divisible by 8
     if (compression == COMPRESSION_JPEG
         && (rows_per_strip == UINT32_MAX || rows_per_strip % 8 != 0))
       error ("imwrite: RowsPerStrip option must be specified for jpeg compression and must be divisible by 8");
     
+    // These compression schemes are only available for binary images
     if (! (args(0).is_bool_matrix () || args(0).is_bool_scalar ())
-        && (compression == COMPRESSION_CCITT_T4
+        && (compression == COMPRESSION_CCITTRLE
             || compression == COMPRESSION_CCITTFAX3
             || compression == COMPRESSION_CCITTFAX4))
       error ("imwrite: the specified compression scheme is for binary images only");
 
+    // Matlab rejects 4D data for TIFF images
     dim_vector img_dims = args(0).dims ();
     if (args(0).ndims () != 2 && args(0).ndims () != 3)
       error ("imwrite: expected 2-dimensional or 3-dimensional image matrix");
@@ -3963,6 +3976,8 @@
     if (! TIFFSetField (tif, TIFFTAG_BITSPERSAMPLE, bits_per_sample))
       error ("Failed to set BitsPerSample tag");
     
+    // Infer the photometric interpretation of the image from the color map
+    // and the number of channels of the input
     uint16_t photometric = PHOTOMETRIC_MINISBLACK;
     if (cmap.numel () > 0)
       {
@@ -3995,4 +4010,307 @@
     err_disabled_feature ("imwrite", "Tiff");
 #endif
   }
+
+  DEFUN (__tiff_imfinfo__, args, ,
+         "Handler for imfinfo that uses Tiff interface")
+  {
+#if defined (HAVE_TIFF)
+    int nargin = args.length ();
+
+    if (nargin < 1)
+      error ("imfinfo: missing filename argument");
+    
+    if (! args(0).is_string ())
+      error ("imfinfo: filename must be a string");
+    
+    std::string filename = args(0).string_value ();
+
+    const sys::file_stat fs (filename);
+    if (! fs)
+      error ("imfinfo: error reading '%s': %s", filename.c_str (),
+             fs.error ().c_str ());
+    
+    TIFF *tif = TIFFOpen (filename.c_str (), "rc");
+    if (! tif)
+      error ("imfinfo: error reading '%s': LibTIFF failed to read file",
+             filename.c_str ());
+    
+    // The destructor for this object will be called when the function returnd
+    // or in case of an error so the file will always get closed at the end
+    octave_tiff_handle tiff_handle (tif);
+
+    // A lot of the logic here is copied from __magick_finfo__ due to the
+    // great similarity between the two functions but this function is
+    // format specific so a lot of the details are different
+    uint16_t dir_count = TIFFNumberOfDirectories (tif);
+    
+    // Null terminated char* list to be used to create a string_vector
+    static const char *fields[] = {
+      "Filename",
+      "FileModDate",
+      "FileSize",
+      "Format",
+      "FormatVersion",
+      "Width",
+      "Height",
+      "BitDepth",
+      "ColorType",
+      "FormatSignature",
+      "ByteOrder",
+      "NewSubFileType",
+      "BitsPerSample",
+      "Compression",
+      "PhotometricInterpretation",
+      "StripOffsets",
+      "SamplesPerPixel",
+      "RowsPerStrip",
+      "StripByteCounts",
+      "XResolution",
+      "YResolution",
+      "ResolutionUnit",
+      "Colormap",
+      "PlanarConfiguration",
+      "TileWidth",
+      "TileLength",
+      "TileOffsets",
+      "TileByteCounts",
+      "Orientation",
+      "FillOrder",
+      "GrayResponseUnit",
+      "MaxSampleValue",
+      "MinSampleValue",
+      "Thresholding",
+      "Offset",
+      "ImageDescription",
+      nullptr
+    };
+    
+    // A map to be used as a struct array to hold a scalar_map for each
+    // directory
+    octave_map info (dim_vector (dir_count, 1), string_vector (fields));
+
+    // populate template_info with the info that is common between all
+    // directories in the file
+    octave_scalar_map template_info = (string_vector (fields));
+    template_info.setfield ("Format", octave_value ("tif"));
+    template_info.setfield ("FormatVersion", octave_value (""));
+    const sys::localtime mtime (fs.mtime ());
+    const std::string filetime = mtime.strftime ("%e-%b-%Y %H:%M:%S");
+    template_info.setfield ("Filename",    octave_value (filename));
+    template_info.setfield ("FileModDate", octave_value (filetime));
+    template_info.setfield ("FileSize",    octave_value (fs.size ()));
+
+    // Extract the image signature (first 4 bytes in file)
+    std::ifstream tif_file;
+#if defined (OCTAVE_USE_WINDOWS_API)
+    std::wstring wname = sys::u8_to_wstring (filename);
+    tif_file.open (wname.c_str (), std::ios_base::binary);
+#else
+    tif_file.open (filename.c_str (), std::ios_base::binary);
+#endif
+    uint8NDArray signature (dim_vector (1, 4));;
+    tif_file.read (reinterpret_cast<char *> (signature.fortran_vec ()), 4);
+    tif_file.close ();
+    template_info.setfield ("FormatSignature", octave_value (signature));
+
+    std::string byte_order
+      = TIFFIsBigEndian (tif)? "big-endian": "little-endian";
+    template_info.setfield ("ByteOrder", octave_value (byte_order));
+
+    // Extract directory specific information
+    for (uint16_t dir = 0; dir < dir_count; dir++)
+      {
+        octave_scalar_map dir_info (template_info);
+
+        // Switch to the directory
+        if (! TIFFSetDirectory (tif, dir))
+          error ("imfinfo: Failed to access frame %d\n", dir);
+        
+        tiff_image_data image_data (tif);
+        dir_info.setfield ("Width", octave_value (image_data.width));
+        
+        dir_info.setfield ("Height", octave_value (image_data.height));
+        
+        uint16_t bit_depth = image_data.samples_per_pixel
+                             * image_data.bits_per_sample;
+        dir_info.setfield ("BitDepth", octave_value (bit_depth));
+        
+        std::string planar = "unrecognized";
+        if (image_data.planar_configuration == 1)
+          planar = "Chunky";
+        else if (image_data.planar_configuration == 2)
+          planar = "Separate";
+        dir_info.setfield ("PlanarConfiguration", octave_value (planar));
+
+        // Extract photometric information as well as color map if exists
+        std::string color_str, photometric_str;
+        uint16_t photometric = PHOTOMETRIC_MINISBLACK;
+        octave_value cmap = octave_value (Matrix ());
+        const TIFFField *fip;
+        TIFFGetField (tif, TIFFTAG_PHOTOMETRIC, &photometric);
+        switch (photometric)
+          {
+          case PHOTOMETRIC_MINISBLACK:
+            color_str = "grayscale";
+            photometric_str = "BlasckIsZero";
+            break;
+          case PHOTOMETRIC_MINISWHITE:
+            color_str = "grayscale";
+            photometric_str = "WhiteIsZero";
+            break;
+          case PHOTOMETRIC_RGB:
+            color_str = "truecolor";
+            photometric_str = "RGB";
+            break;
+          case PHOTOMETRIC_PALETTE:
+            color_str = "indexed";
+            photometric_str = "RGB Palette";
+            fip = TIFFFieldWithTag (tif, TIFFTAG_COLORMAP);
+            cmap = get_field_data (tif, fip);
+            break;
+          case PHOTOMETRIC_SEPARATED:
+            color_str = "CMYK";
+            photometric_str = "CMYK";
+            break;
+          default:
+            color_str = "undefined";
+            photometric_str = "undefined";
+          }
+        dir_info.setfield ("ColorType", octave_value(color_str));
+        dir_info.setfield ("PhotometricInterpretation",
+                           octave_value(photometric_str));
+        dir_info.setfield ("Colormap", octave_value (cmap));
+
+        fip = TIFFFieldWithTag (tif, TIFFTAG_SUBFILETYPE);
+        dir_info.setfield ("NewSubFileType", get_field_data (tif, fip));
+
+        fip = TIFFFieldWithTag (tif, TIFFTAG_BITSPERSAMPLE);
+        dir_info.setfield ("BitsPerSample", get_field_data (tif, fip));
+
+        fip = TIFFFieldWithTag (tif, TIFFTAG_SAMPLESPERPIXEL);
+        dir_info.setfield ("SamplesPerPixel", get_field_data (tif, fip));
+
+        // Use LibTIFF's compression codec to extract compression scheme name
+        uint16_t compression;
+        TIFFGetFieldDefaulted (tif, TIFFTAG_COMPRESSION, &compression);
+        std::string comp_str = "unrecognized";
+        const TIFFCodec *codec = TIFFFindCODEC (compression);
+        if (codec)
+          comp_str = codec->name;
+        dir_info.setfield ("Compression", octave_value (comp_str));
+
+        // Set strip-specific and tile-specific fields accordingly
+        bool tiled = TIFFIsTiled (tif);
+        fip = TIFFFieldWithTag (tif, TIFFTAG_TILELENGTH);
+        dir_info.setfield ("TileLength", tiled? get_field_data (tif, fip)
+                                         : octave_value (Matrix ()));
+        fip = TIFFFieldWithTag (tif, TIFFTAG_TILEWIDTH);
+        dir_info.setfield ("TileWidth", tiled? get_field_data (tif, fip)
+                                        : octave_value (Matrix ()));
+        fip = TIFFFieldWithTag (tif, TIFFTAG_TILEOFFSETS);
+        dir_info.setfield ("TileOffsets", tiled? get_field_data (tif, fip)
+                                          : octave_value (Matrix ()));
+        fip = TIFFFieldWithTag (tif, TIFFTAG_TILEBYTECOUNTS);
+        dir_info.setfield ("TileByteCounts", tiled? get_field_data (tif, fip)
+                                             : octave_value (Matrix ()));
+        fip = TIFFFieldWithTag (tif, TIFFTAG_ROWSPERSTRIP);
+        dir_info.setfield ("RowsPerStrip", tiled? octave_value (Matrix ())
+                                           : get_field_data (tif, fip));
+        fip = TIFFFieldWithTag (tif, TIFFTAG_STRIPOFFSETS);
+        dir_info.setfield ("StripOffsets", tiled? octave_value (Matrix ())
+                                           : get_field_data (tif, fip));
+        fip = TIFFFieldWithTag (tif, TIFFTAG_STRIPBYTECOUNTS);
+        dir_info.setfield ("StripByteCounts", tiled? octave_value (Matrix ())
+                                              : get_field_data (tif, fip));
+        
+        uint16_t res;
+        if (TIFFGetField (tif, TIFFTAG_XRESOLUTION, &res))
+          dir_info.setfield ("XResolution", octave_value (res));
+        else
+          dir_info.setfield ("XResolution", octave_value (Matrix ()));
+
+        if (TIFFGetField (tif, TIFFTAG_YRESOLUTION, &res))
+          dir_info.setfield ("YResolution", octave_value (res));
+        else
+          dir_info.setfield ("YResolution", octave_value (Matrix ()));
+        
+        TIFFGetFieldDefaulted (tif, TIFFTAG_RESOLUTIONUNIT, &res);
+        std::string res_unit = "Inch";
+        if (res == 1)
+          res_unit = "None";
+        else if (res == 3)
+          res_unit = "Centimeter";
+        dir_info.setfield ("ResolutionUnit", octave_value(res_unit));
+
+        fip = TIFFFieldWithTag (tif, TIFFTAG_ORIENTATION);
+        dir_info.setfield ("Orientation", get_field_data (tif, fip));
+
+        fip = TIFFFieldWithTag (tif, TIFFTAG_FILLORDER);
+        dir_info.setfield ("FillOrder", get_field_data (tif, fip));
+        
+        // The current version of LibTIFF (4.4.0) doesn't set the deafult
+        // value for GrayResponseUnit corectly, so we can't use
+        // TIFFGetFieldDefaulted, instead we set the default value ourselves
+        double gray_response_unit = 0.01;
+        uint16_t gray_unit_val;
+        if (TIFFGetField (tif, TIFFTAG_GRAYRESPONSEUNIT, &gray_unit_val))
+          {
+            switch (gray_unit_val)
+              {
+              case GRAYRESPONSEUNIT_10S:
+                gray_response_unit = 0.1;
+                break;
+              case GRAYRESPONSEUNIT_100S:
+                gray_response_unit = 0.01;
+                break;
+              case GRAYRESPONSEUNIT_1000S:
+                gray_response_unit = 0.001;
+                break;
+              case GRAYRESPONSEUNIT_10000S:
+                gray_response_unit = 0.0001;
+                break;
+              case GRAYRESPONSEUNIT_100000S:
+                gray_response_unit = 0.00001;
+                break;
+              }
+          }
+        dir_info.setfield ("GrayResponseUnit",
+                           octave_value (gray_response_unit));
+
+        // The current version of LibTIFF (4.4.0) doesn't set the deafult
+        // value for MinSampleValue and MaxSampleValue, so we can't use
+        // TIFFGetFieldDefaulted, instead we set the default value ourselves
+        uint16_t min_sample_value = 0;
+        uint16_t max_sample_value = (1<<image_data.bits_per_sample) - 1;
+        TIFFGetField (tif, TIFFTAG_MINSAMPLEVALUE, &min_sample_value);
+        TIFFGetField (tif, TIFFTAG_MAXSAMPLEVALUE, &max_sample_value);
+        dim_vector vector_dims = dim_vector (1, image_data.samples_per_pixel);
+        NDArray min_sample_values (vector_dims, min_sample_value);
+        NDArray max_sample_values (vector_dims, max_sample_value);
+        dir_info.setfield ("MinSampleValue",
+                           octave_value (min_sample_values));
+        dir_info.setfield ("MaxSampleValue",
+                           octave_value (max_sample_values));
+
+        fip = TIFFFieldWithTag (tif, TIFFTAG_THRESHHOLDING);
+        dir_info.setfield ("Thresholding", get_field_data (tif, fip));
+
+        dir_info.setfield ("Offset",
+                           octave_value (TIFFCurrentDirOffset (tif)));
+        
+        char *desc = NULL;
+        if (TIFFGetField (tif, TIFFTAG_IMAGEDESCRIPTION, &desc))
+          dir_info.setfield ("ImageDescription", octave_value (desc));
+        else
+          dir_info.setfield ("ImageDescription", octave_value (""));
+
+        // Insert the directory information into the map
+        info.fast_elem_insert (dir, dir_info);
+      }
+
+    return ovl (info);
+#else
+    err_disabled_feature ("imfinfo", "Tiff");
+#endif
+  }
 }