changeset 31158:f2ae7763739a

Tiff: added writeEncodedTile method to read tiles from image * __tiff__.cc(F__tiff_read_encoded_tile__): added internal function to process readEncodedTile arguments. * __tiff__.cc(read_tile): implemented logic for reading tile from image file. * __tiff__.cc(handle_read_strip_or_tile): refactores common logic between read_strip and read_tile into a function. * Tiff.m: added readEncodedTile method to the Tiff class and added the necessary unit tests.
author magedrifaat <magedrifaat@gmail.com>
date Sat, 06 Aug 2022 21:59:26 +0200
parents dc3d2744916d
children e960e3a3b3f6
files libinterp/dldfcn/__tiff__.cc scripts/io/Tiff.m
diffstat 2 files changed, 354 insertions(+), 59 deletions(-) [+]
line wrap: on
line diff
--- a/libinterp/dldfcn/__tiff__.cc	Fri Aug 05 22:44:55 2022 +0200
+++ b/libinterp/dldfcn/__tiff__.cc	Sat Aug 06 21:59:26 2022 +0200
@@ -136,10 +136,47 @@
       error ("Unsupported bit depth");
     
     T strip_data (strip_dims);
+    uint8_t *strip_fvec
+      = reinterpret_cast<uint8_t *> (strip_data.fortran_vec ());
 
-    if (TIFFReadEncodedStrip (tif, strip_no,
-                              strip_data.fortran_vec (), -1) == -1)
-      error ("Failed to read strip data");
+    if (image_data->bits_per_sample == 8
+        || image_data->bits_per_sample == 16
+        || image_data->bits_per_sample == 32
+        || image_data->bits_per_sample == 64)
+      {
+        if (TIFFReadEncodedStrip (tif, strip_no, strip_fvec, -1) == -1)
+          error ("Failed to read strip data");
+      }
+    else if (image_data->bits_per_sample == 1)
+      {
+        if (image_data->samples_per_pixel != 1)
+          error ("Bi-Level images must have one channel only");
+        
+        // Create a buffer to hold the packed strip data
+        // Unique pointers are faster than vectors for constant size buffers
+        std::unique_ptr<uint8_t []> strip_ptr
+          = std::make_unique<uint8_t []> (TIFFStripSize (tif));
+        uint8_t *strip_buf = strip_ptr.get ();
+        if (TIFFReadEncodedStrip (tif, strip_no, strip_buf, -1) == -1)
+          error ("Failed to read strip data");
+        
+        // According to the format specification, the row should be byte
+        // aligned so the number of bytes is rounded up to the nearest byte
+        uint32_t padded_width = (image_data->width + 7) / 8;
+        // Packing the pixel data into bits
+        for (uint32_t row = 0; row < rows_in_strip; row++)
+          {
+            for (uint32_t col = 0; col < image_data->width; col++)
+            {
+              uint8_t shift = 7 - col % 8;
+              strip_fvec[col] = (strip_buf[col / 8] >> shift) & 0x1;
+            }
+            strip_fvec += image_data->width;
+            strip_buf += padded_width;
+          }
+      }
+    else
+      error ("Unsupported bit depth");
     
     Array<octave_idx_type> perm (dim_vector (3, 1));
     if (image_data->planar_configuration == PLANARCONFIG_CONTIG)
@@ -161,6 +198,184 @@
 
   template <typename T>
   octave_value
+  read_tile (TIFF *tif, uint32_t tile_no, tiff_image_data *image_data)
+  {
+    // ASSUMES tiled image and tile_no is a valid zero-based tile
+    // index for the tif image
+
+    // TODO(maged): check matlab behavior
+    // TODO(maged): refactor into a function?
+    uint32_t tile_width, tile_height;
+    if (! TIFFGetField (tif, TIFFTAG_TILELENGTH, &tile_height))
+      error ("Filed to read tile length");
+    
+    if (! TIFFGetField (tif, TIFFTAG_TILEWIDTH, &tile_width))
+      error ("Filed to read tile length");
+
+    if (tile_height == 0 || tile_height % 16 != 0
+        || tile_width == 0 || tile_width % 16 != 0)
+      error ("Tile dimesion tags are invalid");
+
+    dim_vector tile_dims;
+    if (image_data->planar_configuration == PLANARCONFIG_CONTIG)
+      {
+        tile_dims = dim_vector (image_data->samples_per_pixel, tile_width,
+                                tile_height);
+      }
+    else if (image_data->planar_configuration == PLANARCONFIG_SEPARATE)
+      {
+        tile_dims = dim_vector (tile_width, tile_height, 1);
+      }
+    else
+      error ("Unsupported planar configuration");
+    
+    T tile_data (tile_dims);
+    uint8_t *tile_fvec
+      = reinterpret_cast<uint8_t *> (tile_data.fortran_vec ());
+
+    if (image_data->bits_per_sample == 8
+        || image_data->bits_per_sample == 16
+        || image_data->bits_per_sample == 32
+        || image_data->bits_per_sample == 64)
+        {      
+          if (TIFFReadEncodedTile (tif, tile_no, tile_fvec, -1) == -1)
+            error ("Failed to read tile data");
+        }
+    else if (image_data->bits_per_sample == 1)
+      {
+        if (image_data->samples_per_pixel != 1)
+          error ("Bi-Level images must have one channel only");
+        
+        // Create a buffer to hold the packed tile data
+        // Unique pointers are faster than vectors for constant size buffers
+        std::unique_ptr<uint8_t []> tile_ptr
+          = std::make_unique<uint8_t []> (TIFFTileSize (tif));
+        uint8_t *tile_buf = tile_ptr.get ();
+        if (TIFFReadEncodedTile (tif, tile_no, tile_buf, -1) == -1)
+            error ("Failed to read tile data");
+        
+        // unpack tile bits into output matrix cells
+        for (uint32_t row = 0; row < tile_height; row++)
+          {
+            for (uint32_t col = 0; col < tile_width; col++)
+              {
+                uint8_t shift = 7 - col % 8;
+                tile_fvec[col] = (tile_buf [col / 8] >> shift) & 0x1;
+              }  
+            tile_fvec += tile_width;
+            tile_buf += tile_width / 8;
+          }
+      }
+    else
+      error ("Unsupported bit depth");
+    
+    Array<octave_idx_type> perm (dim_vector (3, 1));
+    if (image_data->planar_configuration == PLANARCONFIG_CONTIG)
+      {
+        perm(0) = 2;
+        perm(1) = 1;
+        perm(2) = 0;
+      }
+    else if (image_data->planar_configuration == PLANARCONFIG_SEPARATE)
+      {
+        perm(0) = 1;
+        perm(1) = 0;
+        perm(2) = 2;
+      }
+    
+    tile_data = tile_data.permute (perm);
+
+    // Get the actual tile dimensions
+    uint32_t tiles_across = (image_data->width + tile_width - 1)
+                            / tile_width;
+    uint32_t tiles_down = (image_data->height + tile_height - 1)
+                          / tile_height;
+    uint32_t corrected_width = tile_width;
+    uint32_t corrected_height = tile_height;
+    if (tile_no % tiles_across == tiles_across - 1)
+      corrected_width = image_data->width - tile_width * (tiles_across - 1);
+    if ((tile_no / tiles_across) % tiles_down == tiles_down - 1)
+      corrected_height = image_data->height - tile_height * (tiles_down - 1);
+    
+    dim_vector corrected_dims;
+    if (image_data->planar_configuration == PLANARCONFIG_CONTIG)
+      corrected_dims = dim_vector (corrected_height, corrected_width,
+                                   image_data->samples_per_pixel);
+    else if (image_data->planar_configuration == PLANARCONFIG_SEPARATE)
+      corrected_dims = dim_vector (corrected_height, corrected_width);
+    // TODO(maged): confirm matlab behavior
+    tile_data.resize (corrected_dims);
+
+    return octave_value (tile_data);
+  }
+
+  template <typename T>
+  octave_value
+  read_strip_or_tile (TIFF *tif, uint32_t strip_tile_no,
+                      tiff_image_data *image_data)
+  {
+    if (image_data->is_tiled)
+      return read_tile<T> (tif, strip_tile_no, image_data);
+    else
+      return read_strip<T> (tif, strip_tile_no, image_data);
+  }
+
+  octave_value
+  handle_read_strip_or_tile (TIFF *tif, uint32_t strip_tile_no)
+  {
+    // Obtain all necessary tags
+    tiff_image_data image_data (tif);
+
+    uint16_t sample_format;
+    if (! TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLEFORMAT, &sample_format))
+      error ("Failed to obtain a value for sample format");
+
+    if (sample_format == 3)
+      {
+        if (image_data.bits_per_sample != 32
+            && image_data.bits_per_sample != 64)
+          error ("Floating point images are only supported for bit depths of 32 and 64");
+      }
+    else if (sample_format != 1 && sample_format != 4)
+      error ("Unsupported sample format");
+    
+    switch (image_data.bits_per_sample)
+      {
+      case 1:
+        return read_strip_or_tile<boolNDArray> (tif, strip_tile_no,
+                                                &image_data);
+        break;
+      case 8:
+        return read_strip_or_tile<uint8NDArray> (tif, strip_tile_no,
+                                                 &image_data);
+        break;
+      case 16:
+        return read_strip_or_tile<uint16NDArray> (tif, strip_tile_no,
+                                                  &image_data);
+        break;
+      case 32:
+        if (sample_format == 3)
+          return read_strip_or_tile<FloatNDArray> (tif, strip_tile_no,
+                                                   &image_data);
+        else
+          return read_strip_or_tile<uint32NDArray> (tif, strip_tile_no,
+                                                    &image_data);
+        break;
+      case 64:
+        if (sample_format == 3)
+          return read_strip_or_tile<NDArray> (tif, strip_tile_no,
+                                              &image_data);
+        else
+          return read_strip_or_tile<uint64NDArray> (tif, strip_tile_no,
+                                                    &image_data);
+        break;
+      default:
+        error ("Unsupported bit depth");
+      }
+  }
+
+  template <typename T>
+  octave_value
   read_stripped_image (TIFF *tif, tiff_image_data *image_data)
   {
     typedef typename T::element_type P;
@@ -1187,8 +1402,9 @@
   }
 
   void
-  process_strip_or_tile (TIFF *tif, uint32_t strip_tile_no,
-                         octave_value data_ov, tiff_image_data *image_data)
+  handle_write_strip_or_tile (TIFF *tif, uint32_t strip_tile_no,
+                              octave_value data_ov,
+                              tiff_image_data *image_data)
   {
 
     // SampleFormat tag is not a required field and has a default value of 1
@@ -1737,7 +1953,7 @@
   }
 
   DEFUN_DLD (__tiff_read_encoded_strip__, args, nargout,
-             "Read the image in the current IFD")
+             "Read a strip from the image in the current IFD")
   {
 #if defined (HAVE_TIFF)
     int nargin = args.length ();
@@ -1766,50 +1982,43 @@
     // Convert from Octave's 1-based indexing to zero-based indexing
     strip_no--;
 
-    // Obtain all necessary tags
-    tiff_image_data image_data (tif);
-
-    uint16_t sample_format;
-    if (! TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLEFORMAT, &sample_format))
-      error ("Failed to obtain a value for sample format");
+    return octave_value_list (handle_read_strip_or_tile (tif, strip_no));
+#else
+    err_disabled_feature ("readEncodedStrip", "Tiff");
+#endif
+  }
 
-    if (sample_format == 3)
-      {
-        if (image_data.bits_per_sample != 32 && image_data.bits_per_sample != 64)
-          error ("Floating point images are only supported for bit depths of 32 and 64");
-      }
-    else if (sample_format != 1 && sample_format != 4)
-      error ("Unsupported sample format");
+  DEFUN_DLD (__tiff_read_encoded_tile__, args, nargout,
+             "Read a tile from the image in the current IFD")
+  {
+#if defined (HAVE_TIFF)
+    int nargin = args.length ();
+
+    if (nargin != 2)
+      error ("rong number of arguments");
     
-    octave_value_list retval;
-    switch (image_data.bits_per_sample)
-      {
-      case 1:
-        retval(0) = read_strip<boolNDArray> (tif, strip_no, &image_data);
-        break;
-      case 8:
-        retval(0) = read_strip<uint8NDArray> (tif, strip_no, &image_data);
-        break;
-      case 16:
-        retval(0) = read_strip<uint16NDArray> (tif, strip_no, &image_data);
-        break;
-      case 32:
-        if (sample_format == 3)
-          retval(0) = read_strip<FloatNDArray> (tif, strip_no, &image_data);
-        else
-          retval(0) = read_strip<uint32NDArray> (tif, strip_no, &image_data);
-        break;
-      case 64:
-        if (sample_format == 3)
-          retval(0) = read_strip<NDArray> (tif, strip_no, &image_data);
-        else
-          retval(0) = read_strip<uint64NDArray> (tif, strip_no, &image_data);
-        break;
-      default:
-        error ("Unsupported bit depth");
-      }
+    TIFF *tif = (TIFF *)(args(0).uint64_value ());
+
+    // TODO(maged): nargout and ycbcr
+    octave_unused_parameter (nargout);
+
+    if (! TIFFIsTiled (tif))
+      error ("The image is stripped not tiled");
     
-    return retval;
+    // TODO(maged): what is the behavior in matlab
+    uint32_t tile_no;
+    if (args(1).is_scalar_type ())
+      tile_no = args(1).uint32_scalar_value ();
+    else
+      error ("Expected scalar for tile number");
+    
+    if (tile_no < 1 || tile_no > TIFFNumberOfTiles (tif))
+      error ("Tile number out of bounds");
+    
+    // Convert from Octave's 1-based indexing to zero-based indexing
+    tile_no--;
+
+    return octave_value_list (handle_read_strip_or_tile (tif, tile_no));
 #else
     err_disabled_feature ("readEncodedStrip", "Tiff");
 #endif
@@ -1934,7 +2143,7 @@
     if (strip_no < 1 || strip_no > TIFFNumberOfStrips (tif))
       error ("Strip number out of range");
     
-    process_strip_or_tile (tif, strip_no, args(2), &image_data);
+    handle_write_strip_or_tile (tif, strip_no, args(2), &image_data);
     
     return octave_value_list ();
 #else
@@ -1968,7 +2177,7 @@
     if (tile_no < 1 || tile_no > TIFFNumberOfTiles (tif))
       error ("Tile number out of range");
 
-    process_strip_or_tile (tif, tile_no, args(2), &image_data);
+    handle_write_strip_or_tile (tif, tile_no, args(2), &image_data);
     
     return octave_value_list ();
 #else
--- a/scripts/io/Tiff.m	Fri Aug 05 22:44:55 2022 +0200
+++ b/scripts/io/Tiff.m	Sat Aug 06 21:59:26 2022 +0200
@@ -123,6 +123,13 @@
       stripData = __tiff_read_encoded_strip__ (t.tiff_handle, stripNumber);
     endfunction
 
+    function tileData = readEncodedTile (t, tileNumber)
+      if (t.closed)
+        error ("Image file was closed");
+      endif
+      tileData = __tiff_read_encoded_tile__ (t.tiff_handle, tileNumber);
+    endfunction
+
     function write (t, imageData)
       if (t.closed)
         error ("Image file was closed");
@@ -1176,6 +1183,21 @@
 %!  endfunction
 %!  file_wrapper (@test_fn);
 
+## test readEncodedStrip BiLevel image
+%!testif HAVE_TIFF
+%!  function test_fn (filename)
+%!    img = Tiff (filename, "w");
+%!    setTag(img, struct ("ImageLength", 10, "ImageWidth", 10,
+%!                        "RowsPerStrip", 6));
+%!    data = logical (repmat ([1,0,0,0,1,0,1,1,1,0], [10, 1]));
+%!    write (img, data);
+%!    s1 = readEncodedStrip (img, 1);
+%!    s2 = readEncodedStrip (img, 2);
+%!    assert ([s1;s2], data);
+%!    img.close();
+%!  endfunction
+%!  file_wrapper (@test_fn);
+
 ## test readEncodedStrip RGB Chunky
 %!testif HAVE_TIFF
 %!  function test_fn (filename)
@@ -1203,16 +1225,80 @@
 %!                        "PlanarConfiguration", 2));
 %!    data = uint16 (reshape (1:300, [10, 10, 3]));
 %!    write (img, data);
-%!    s1 = readEncodedStrip (img, 1);
-%!    s2 = readEncodedStrip (img, 2);
-%!    s3 = readEncodedStrip (img, 3);
-%!    s4 = readEncodedStrip (img, 4);
-%!    s5 = readEncodedStrip (img, 5);
-%!    s6 = readEncodedStrip (img, 6);
-%!    s = [s1;s2];
-%!    s(:, :, 2) = [s3;s4];
-%!    s(:, :, 3) = [s5;s6];
-%!    assert (s, data);
+%!    s = cell (6, 1);
+%!    for i=1:6
+%!      s{i} = readEncodedStrip (img, i);
+%!    endfor
+%!    data2 = cat (3, [s{1}; s{2}], [s{3}; s{4}], [s{5}; s{6}]);
+%!    assert (data2, data);
+%!    img.close();
+%!  endfunction
+%!  file_wrapper (@test_fn);
+
+## test readEncodedTile BiLevel image
+%!testif HAVE_TIFF
+%!  function test_fn (filename)
+%!    img = Tiff (filename, "w");
+%!    setTag(img, struct ("ImageLength", 40, "ImageWidth", 40,
+%!                        "TileWidth", 16, "TileLength", 16));
+%!    data = logical (repmat ([1,0,0,0,1,0,1,1,1,0], [40, 4]));
+%!    write (img, data);
+%!    t = cell (9, 1);
+%!    for i=1:9
+%!      t{i} = readEncodedTile (img, i);
+%!    endfor
+%!    data2 = cat(1, cat (2, t{1}, t{2}, t{3}), cat (2, t{4}, t{5}, t{6}),
+%!                cat (2, t{7}, t{8}, t{9}));
+%!    assert (data2, data);
 %!    img.close();
 %!  endfunction
 %!  file_wrapper (@test_fn);
+
+## test readEncodedTile RGB Chunky
+%!testif HAVE_TIFF
+%!  function test_fn (filename)
+%!    img = Tiff (filename, "w");
+%!    setTag(img, struct ("ImageLength", 40, "ImageWidth", 40,
+%!                        "TileWidth", 16, "TileLength", 16,
+%!                        "BitsPerSample", 16, "SamplesPerPixel", 3,
+%!                        "PhotometricInterpretation", 2,
+%!                        "PlanarConfiguration", 1));
+%!    data = uint16 (reshape (1:4800, [40, 40, 3]));
+%!    write (img, data);
+%!    t = cell (9, 1);
+%!    for i=1:9
+%!      t{i} = readEncodedTile (img, i);
+%!    endfor
+%!    data2 = cat(1, cat (2, t{1}, t{2}, t{3}), cat (2, t{4}, t{5}, t{6}),
+%!                cat (2, t{7}, t{8}, t{9}));
+%!    assert (data2, data);
+%!    img.close();
+%!  endfunction
+%!  file_wrapper (@test_fn);
+
+## test readEncodedTile RGB Separated
+%!testif HAVE_TIFF
+%!  function test_fn (filename)
+%!    img = Tiff (filename, "w");
+%!    setTag(img, struct ("ImageLength", 40, "ImageWidth", 40,
+%!                        "TileWidth", 16, "TileLength", 16,
+%!                        "BitsPerSample", 16, "SamplesPerPixel", 3,
+%!                        "PhotometricInterpretation", 2,
+%!                        "PlanarConfiguration", 2));
+%!    data = uint16 (reshape (1:4800, [40, 40, 3]));
+%!    write (img, data);
+%!    data2 = uint16 (zeros (40, 40, 3));
+%!    tile = 1;
+%!    for sample=1:3
+%!      for row=1:16:40
+%!        for col=1:16:40
+%!          data2(row: min(40, row + 15), col: min(40, col + 15), sample)...
+%!            = readEncodedTile(img, tile);
+%!          tile += 1;
+%!        endfor
+%!      endfor
+%!    endfor
+%!    assert (data2, data);
+%!    img.close();
+%!  endfunction
+%!  file_wrapper (@test_fn);