changeset 31190:a91f2f79e58c

Tiff: added internal handler for imread using the Tiff interface * __tiff__.cc (F__tiff_imread__): implemented internal function to implement imread functionality using the Tiff interface, except for the PixelRegion option.
author magedrifaat <magedrifaat@gmail.com>
date Fri, 26 Aug 2022 19:03:43 +0200
parents 6a9d985e7474
children 8ada1e68d961
files libinterp/corefcn/__tiff__.cc
diffstat 1 files changed, 180 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/libinterp/corefcn/__tiff__.cc	Thu Aug 25 20:17:59 2022 +0200
+++ b/libinterp/corefcn/__tiff__.cc	Fri Aug 26 19:03:43 2022 +0200
@@ -120,6 +120,12 @@
       }
   }
 
+  bool
+  is_numeric_scalar (octave_value ov)
+  {
+    return ov.isnumeric () && ov.isreal () && ov.is_scalar_type ();
+  }
+
   // A map of tag names supported by matlab, there are some differences
   // than LibTIFF's names (e.g. Photometric vs PhotometricInerpretation)
   static const std::map<std::string, ttag_t> tag_name_map = {
@@ -1597,6 +1603,8 @@
             error ("Failed to obtain the number of samples per pixel");
           
           uint16NDArray data_array = tag_ov.uint16_array_value ();
+          // FIXME: this only works for RGB images. Need to handle grayscale,
+          // palette, cmyk and ycbcr
           if (data_array.numel () > samples_per_pixel - 3)
             error ("Failed to set field, too many values");
           
@@ -3459,7 +3467,7 @@
     check_closed (tiff_handle);
     
     set_internal_handlers ();
-
+    
     TIFF *tif = tiff_handle->get_file ();
     
     if (! args(1).is_double_type () && ! args(1).is_uint32_type ()
@@ -3536,4 +3544,175 @@
     err_disabled_feature ("F__tiff_make_tagid__", "Tiff");
 #endif
   }
+
+  DEFUN (__tiff_imread__, args, nargout,
+         "Handler for imread that uses Tiff interface")
+  {
+#if defined (HAVE_TIFF)
+    int nargin = args.length ();
+
+    if (nargin == 0 || ! args(0).is_string ())
+      error ("No filename provided\n");
+    
+    uint16_t offset = 1;
+    
+    TIFF *tif = TIFFOpen (args(0).string_value ().c_str (), "r");
+    uint16_t dir_count = TIFFNumberOfDirectories (tif);
+    uint16_t page = 1;
+
+    // Handle unpaired index parameter
+    if (nargin > 1 && ! args(1).is_string ())
+      {
+        if (is_numeric_scalar (args(1)))
+          page = args(1).uint16_scalar_value ();
+        else
+          error ("imread: index must be a numeric scalar");
+        offset++;
+      }
+    
+    if ((nargin - offset) % 2 != 0)
+      error ("imread: PARAM/VALUE arguments must occur in pairs");
+    
+    // Handle all index/frames params
+    bool found_index = false;
+    for (uint16_t arg_idx = offset; arg_idx < nargin; arg_idx+=2)
+      {
+        if (! args(arg_idx).is_string ())
+          error ("imread: PARAM in PARAM/VALUE pair must be string");
+        
+        const char *param_cstr = args(arg_idx).string_value ().c_str ();
+        if (strcasecmp (param_cstr, "index") == 0
+            || strcasecmp (param_cstr, "frames") == 0)
+          {
+            if (found_index)
+              error ("imread: Index or Frames may only be specified once");
+            
+            found_index = true;
+            octave_value val = args(arg_idx + 1);
+            if (is_numeric_scalar (val))
+              page = val.uint16_scalar_value ();
+            else
+              error ("imread: %s must be a scalar", param_cstr);
+          }
+      }
+    
+    // validate frame numbers
+    if (page < 1 || page > dir_count)
+      error ("imread: index/frames specified are outside the number of images");
+    
+    // Convert to zero-based indexing
+    page = page - 1;
+
+    // Go to the first page
+    if (! TIFFSetDirectory (tif, page))
+      error ("imread: failed to read page %d", page);
+    
+    // Obtain image info
+    tiff_image_data image_data (tif);
+    
+    // Set the default region
+    uint32NDArray row_region (dim_vector (1, 3));
+    row_region(0) = 0;
+    row_region(1) = 1;
+    row_region(2) = image_data.height;
+    uint32NDArray col_region (dim_vector (1, 3));
+    col_region(0) = 0;
+    col_region(1) = 1;
+    col_region(2) = image_data.width;
+
+    // Obtain and validate other params (pixelregion, info)
+    for (uint16_t arg_idx = offset; arg_idx < nargin; arg_idx+=2)
+      {
+        if (! args(arg_idx).is_string ())
+          error ("imread: PARAM in PARAM/VALUE pair must be string");
+        
+        const char *param_cstr = args(arg_idx).string_value ().c_str ();
+        if (strcasecmp (param_cstr, "index") == 0
+            || strcasecmp (param_cstr, "frames") == 0)
+          {
+            // Already handled
+          }
+        else if (strcasecmp (param_cstr, "pixelregion") == 0)
+          {
+            octave_value region_ov = args(arg_idx + 1);
+            
+            if (! region_ov.iscell () || region_ov.numel () != 2)
+              error ("imread: %s must be a 2-element cell array", param_cstr);
+            
+            Cell region_cell = region_ov.cell_value ();
+            row_region = region_cell(0).floor ().uint32_array_value ();
+            col_region = region_cell(1).floor ().uint32_array_value ();
+            
+            if (row_region.numel () < 2 || row_region.numel () > 3
+                || col_region.numel () < 2 || col_region.numel () > 3)
+              error ("imread: range for %s must be a 2 or 3 element vector",
+                     param_cstr);
+            
+            if (row_region.numel () == 2)
+              {
+                row_region(2) = row_region(1);
+                row_region(1) = 1;
+              }
+            if (col_region.numel () == 2)
+              {
+                col_region(2) = col_region(1);
+                col_region(1) = 1;
+              }
+            
+            if (static_cast<uint32_t> (row_region(2)) > image_data.height)
+              error ("imread: end ROWS for PixelRegions option is larger than image height");
+            if (static_cast<uint32_t> (col_region(2)) > image_data.width)
+              error ("imread: end COLS for PixelRegions option is larger than image width");
+          }
+        else if (strcasecmp (param_cstr, "info") == 0)
+          {
+            // FIXME: is this useful for our use case?
+          }
+        else
+          error ("imread: invalid PARAMETER '%s'", param_cstr);
+      }
+
+    // Read image according to params
+    // FIXME: this should convert YCbCr images to RGB
+    uint16_t sample_format;
+    TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLEFORMAT, &sample_format);
+
+    octave_value_list retval (3, Matrix ());
+    switch (sample_format)
+      {
+      case 1:
+      case 4:
+        retval (0) = read_unsigned_image (tif, &image_data);
+        break;
+      case 2:
+        retval (0) = read_signed_image (tif, &image_data);
+        break;
+      case 3:
+        retval (0) = read_float_image (tif, &image_data);
+        break;
+      default:
+        // FIXME: should this fallback to magick instead?
+        error ("Unsupported sample format");
+      }
+    
+    if (nargout > 1)
+      {
+        // Also return the color map if available
+        uint16_t photometric;
+        if (TIFFGetField (tif, TIFFTAG_PHOTOMETRIC, &photometric)
+            && photometric == PHOTOMETRIC_PALETTE)
+          {
+            const TIFFField *fip
+              = TIFFFieldWithTag (tif, TIFFTAG_COLORMAP);
+            if (fip)
+              retval(1) = get_field_data (tif, fip);
+          }
+      }
+    // FIXME: matlab returns all channels in the first argout
+    // and doesnt separate the alpha
+    return retval;
+#else
+    err_disabled_feature ("imread", "Tiff");
+#endif
+  }
 }