[Git][debian-gis-team/glymur][upstream] New upstream version 0.11.1

Antonio Valentino (@antonio.valentino) gitlab at salsa.debian.org
Tue Aug 9 08:12:24 BST 2022



Antonio Valentino pushed to branch upstream at Debian GIS Project / glymur


Commits:
194a02ee by Antonio Valentino at 2022-08-09T06:46:25+00:00
New upstream version 0.11.1
- - - - -


9 changed files:

- CHANGES.txt
- docs/source/conf.py
- docs/source/whatsnew/0.11.rst
- glymur/tiff.py
- glymur/version.py
- setup.cfg
- tests/test_jp2k.py
- tests/test_libtiff.py
- tests/test_tiff2jp2.py


Changes:

=====================================
CHANGES.txt
=====================================
@@ -1,3 +1,6 @@
+August 6, 2022 - v0.11.1
+    Improve efficiency of striped TIFF to tiled JP2 conversion
+
 July 29, 2022 - v0.11.0
     Add options for supporting ResolutionBoxes
     Fix ctypes interface to C library on windows


=====================================
docs/source/conf.py
=====================================
@@ -78,7 +78,7 @@ copyright = '2013-2022, John Evans'
 # The short X.Y version.
 version = '0.11'
 # The full version, including alpha/beta/rc tags.
-release = '0.11.0'
+release = '0.11.1'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.


=====================================
docs/source/whatsnew/0.11.rst
=====================================
@@ -2,6 +2,12 @@
 Changes in glymur 0.11
 ######################
 
+*****************
+Changes in 0.11.1
+*****************
+    * Improve efficiency of striped TIFF to tiled JP2 conversion
+
+
 *****************
 Changes in 0.11.0
 *****************


=====================================
glymur/tiff.py
=====================================
@@ -495,45 +495,6 @@ class Tiff2Jp2k(object):
             offset, = struct.unpack(self.endian + 'I', buffer)
         tfp.seek(offset)
 
-    def _process_header(self, b, tfp):
-
-        buffer = tfp.read(4)
-        data = struct.unpack('BB', buffer[:2])
-
-        # big endian or little endian?
-        if data[0] == 73 and data[1] == 73:
-            # little endian
-            endian = '<'
-        elif data[0] == 77 and data[1] == 77:
-            # big endian
-            endian = '>'
-        else:
-            msg = (
-                f"The byte order indication in the TIFF header "
-                f"({data}) is invalid.  It should be either "
-                f"{bytes([73, 73])} or {bytes([77, 77])}."
-            )
-            raise RuntimeError(msg)
-
-        # version number and offset to the first IFD
-        version, = struct.unpack(endian + 'H', buffer[2:4])
-        self.version = _TIFF if version == 42 else _BIGTIFF
-
-        if self.version == _BIGTIFF:
-            buffer = tfp.read(12)
-            _, _, offset = struct.unpack(endian + 'HHQ', buffer)
-        else:
-            buffer = tfp.read(4)
-            offset, = struct.unpack(endian + 'I', buffer)
-        tfp.seek(offset)
-
-        # write this 32-bit header into the UUID, no matter if we had bigtiff
-        # or regular tiff or big endian
-        data = struct.pack('<BBHI', 73, 73, 42, 8)
-        b.write(data)
-
-        return endian
-
     def get_tag_value(self, tagnum):
         """
         Return the value associated with the tag.  Some tags are not actually
@@ -882,7 +843,7 @@ class Tiff2Jp2k(object):
     ):
         """
         The input TIFF image is striped and we are to create the output
-        JPEG2000 image as a single tile.
+        JPEG2000 image as a tiled JP2K.
 
         Parameters
         ----------
@@ -904,25 +865,15 @@ class Tiff2Jp2k(object):
             Write to this JPEG2000 file
         """
 
-        partial_jp2_tile_rows = (imageheight / jth) != (imageheight // jth)
-        partial_jp2_tile_cols = (imagewidth / jtw) != (imagewidth // jtw)
-
         self.logger.debug(f'image:  {imageheight} x {imagewidth}')
         self.logger.debug(f'jptile:  {jth} x {jtw}')
         num_strips = libtiff.numberOfStrips(self.tiff_fp)
 
         num_jp2k_tile_cols = int(np.ceil(imagewidth / jtw))
 
-        partial_jp2_tile_rows = (imageheight / jth) != (imageheight // jth)
-        partial_jp2_tile_cols = (imagewidth / jtw) != (imagewidth // jtw)
-
-        tiff_strip = np.zeros((rps, imagewidth, spp), dtype=dtype)
-        rgba_strip = np.zeros((rps, imagewidth, 4), dtype=np.uint8)
-
         for idx, tilewriter in enumerate(jp2.get_tilewriters()):
-            self.logger.info(f'Tile: #{idx}')
 
-            jp2k_tile = np.zeros((jth, jtw, spp), dtype=dtype)
+            self.logger.info(f'Tile: #{idx}')
 
             jp2k_tile_row = idx // num_jp2k_tile_cols
             jp2k_tile_col = idx % num_jp2k_tile_cols
@@ -930,84 +881,111 @@ class Tiff2Jp2k(object):
             # the coordinates of the upper left pixel of the jp2k tile
             julr, julc = jp2k_tile_row * jth, jp2k_tile_col * jtw
 
-            # Populate the jp2k tile with tiff strips.
-            # Move by strips from the start of the jp2k tile to the bottom
-            # of the jp2k tile.  That last strip may be partially empty,
-            # worry about that later.
-            #
-            # loop while the upper left corner of the current tiff file is
-            # less than the lower left corner of the jp2k tile
-            r = julr
-            while (r // rps) * rps < min(julr + jth, imageheight):
+            if jp2k_tile_col == 0:
 
-                stripnum = libtiff.computeStrip(self.tiff_fp, r, 0)
-                self.logger.debug(f'Strip: #{stripnum}')
+                tiff_multi_strip = self._construct_multi_strip(
+                    julr, num_strips, jth, imageheight, imagewidth, rps, spp,
+                    dtype, use_rgba_interface
+                )
 
-                if stripnum >= num_strips:
-                    # we've moved past the end of the tiff
-                    break
+            # Get the multi-strip coordinates of the upper left jp2k corner
+            stripnum = libtiff.computeStrip(self.tiff_fp, julr, 0)
+            tulr = stripnum * rps
 
-                if use_rgba_interface:
+            ms_ulr = julr - tulr
+            ms_ulc = julc
 
-                    # must use the first row in the strip
-                    libtiff.readRGBAStrip(
-                        self.tiff_fp, stripnum * rps, rgba_strip
-                    )
-                    # must flip the rows (!!) and get rid of the alpha
-                    # plane
-                    tiff_strip = np.flipud(rgba_strip[:, :, :spp])
+            # ms_lrr = ms_ulr + min(ms_ulr + jth, imageheight)
+            # ms_lrc = ms_ulc + min(ms_ulc + jtw, imagewidth)
+            ms_lrr = min(ms_ulr + jth, imageheight - tulr)
+            ms_lrc = min(ms_ulc + jtw, imagewidth)
 
-                else:
-                    libtiff.readEncodedStrip(
-                        self.tiff_fp, stripnum, tiff_strip
-                    )
+            rows = slice(ms_ulr, ms_lrr)
+            cols = slice(ms_ulc, ms_lrc)
 
-                # the coordinates of the upper left pixel of the TIFF
-                # strip
-                tulr = stripnum * rps
-                tulc = 0
+            tilewriter[:] = tiff_multi_strip[rows, cols, :]
 
-                # determine how to fit this tiff strip into the jp2k
-                # tile
-                #
-                # these are the section coordinates in image space
-                ulr = max(julr, tulr)
-                llr = min(julr + jth, tulr + rps)
+    def _construct_multi_strip(
+        self, julr, num_strips, jth, imageheight, imagewidth, rps, spp, dtype,
+        use_rgba_interface
+    ):
+        """
+        TIFF strips are pretty inefficient.  If our I/O was stupidly focused
+        solely on each JP2K tile, we would read in each TIFF strip multiple
+        times.  If instead, we read in ALL the strips that will encompass that
+        current row of JP2K tiles, we will save ourselves from pushing around
+        a lot of electrons.
 
-                ulc = max(julc, tulc)
-                urc = min(julc + jtw, tulc + imagewidth)
+        Parameters
+        ----------
+        imagewidth, imageheight, spp : int
+            TIFF tag values corresponding to the photometric interpretation,
+            image width and height, and samples per pixel.
+        jth : int
+            The number of rows in a JP2K tile.
+        rps : int
+            The number of rows per strip in the TIFF.
+        julr : int
+            The top row of the current JP2K tile row.
+        dtype : np.dtype
+            Datatype of the image.
+        use_rgba_interface : bool
+            If true, use the RGBA interface to read the TIFF image data.
 
-                # convert to JP2K tile coordinates
-                jrows = slice(ulr % jth, (llr - 1) % jth + 1)
-                jcols = slice(ulc % jtw, (urc - 1) % jtw + 1)
+        Returns
+        -------
+        tiff_multi_strip : np.array
+            Holds all the TIFF strips that tightly encompass the current JP2K
+            tile row.
+        """
+        # We need to create a TIFF "multi-strip" that can hold all of
+        # the JP2K tiles in a JP2K tile row.
+        r = julr
+        top_strip_num = libtiff.computeStrip(self.tiff_fp, r, 0)
+
+        # advance thru to the next tile row
+        while (r // rps) * rps < min(julr + jth, imageheight):
+            r += rps
+        bottom_strip_num = libtiff.computeStrip(self.tiff_fp, r, 0)
+
+        # compute the number of rows contained between the top strip
+        # and the bottom strip
+        num_rows = (bottom_strip_num - top_strip_num) * rps
+
+        if use_rgba_interface:
+            tiff_strip = np.zeros((rps, imagewidth, 4), dtype=np.uint8)
+            tiff_multi_strip = np.zeros(
+                (num_rows, imagewidth, spp), dtype=np.uint8
+            )
+        else:
+            tiff_strip = np.zeros((rps, imagewidth, spp), dtype=dtype)
+            tiff_multi_strip = np.zeros(
+                (num_rows, imagewidth, spp), dtype=dtype
+            )
 
-                # convert to TIFF strip coordinates
-                trows = slice(ulr % rps, (llr - 1) % rps + 1)
-                tcols = slice(ulc % imagewidth, (urc - 1) % imagewidth + 1)
+        # fill the multi-strip
+        for stripnum in range(top_strip_num, bottom_strip_num):
 
-                jp2k_tile[jrows, jcols, :] = tiff_strip[trows, tcols, :]
+            if use_rgba_interface:
 
-                r += rps
+                libtiff.readRGBAStrip(
+                    self.tiff_fp, stripnum * rps, tiff_strip
+                )
 
-            # last tile column?  If so, we may have a partial tile.
-            # j2k_cols is not sufficient here, must shorten it from 250
-            # to 230
-            if (
-                partial_jp2_tile_cols
-                and jp2k_tile_col == num_jp2k_tile_cols - 1
-            ):
-                # decrease the number of columns by however many it sticks
-                # over the image width
-                last_j2k_cols = slice(0, imagewidth - julc)
-                jp2k_tile = jp2k_tile[:, last_j2k_cols, :].copy()
+                # must flip the rows (!!) and get rid of the alpha
+                # plane
+                tiff_strip = np.flipud(tiff_strip[:, :, :spp])
 
-            if (
-                partial_jp2_tile_rows
-                and stripnum == num_strips - 1
-            ):
-                # decrease the number of rows by however many it sticks
-                # over the image height
-                last_j2k_rows = slice(0, imageheight - julr)
-                jp2k_tile = jp2k_tile[last_j2k_rows, :, :].copy()
+            else:
 
-            tilewriter[:] = jp2k_tile
+                libtiff.readEncodedStrip(
+                    self.tiff_fp, stripnum, tiff_strip
+                )
+
+            # push the strip into the multi-strip
+            top_row = (stripnum - top_strip_num) * rps
+            bottom_row = (stripnum - top_strip_num + 1) * rps
+            rows = slice(top_row, bottom_row)
+            tiff_multi_strip[rows, :, :] = tiff_strip
+
+        return tiff_multi_strip


=====================================
glymur/version.py
=====================================
@@ -21,7 +21,7 @@ from .lib import tiff
 
 # Do not change the format of this next line!  Doing so risks breaking
 # setup.py
-version = "0.11.0"
+version = "0.11.1"
 
 version_tuple = parse(version).release
 


=====================================
setup.cfg
=====================================
@@ -1,6 +1,6 @@
 [metadata]
 name = Glymur
-version = 0.11.0
+version = 0.11.1
 author = 'John Evans'
 author_email = "John Evans" <john.g.evans.ne at gmail.com>
 license = 'MIT'


=====================================
tests/test_jp2k.py
=====================================
@@ -1259,6 +1259,7 @@ class TestJp2k_write(fixtures.MetadataBase):
 
         EXPECTED RESULT:  A TLM segment is detected.
         """
+        breakpoint()
         kwargs = {
             'data': self.jp2_data,
             'tlm': True


=====================================
tests/test_libtiff.py
=====================================
@@ -18,6 +18,9 @@ class TestSuite(fixtures.TestCommon):
     def test_simple_tile(self):
         """
         SCENARIO:  create a simple monochromatic 2x2 tiled image
+
+        Expected result:  The image matches.  The number of tiles checks out.
+        The tile width and height checks out.
         """
         data = fixtures.skimage.data.moon()
         h, w = data.shape
@@ -58,6 +61,15 @@ class TestSuite(fixtures.TestCommon):
         libtiff.readEncodedTile(fp, 3, tile)
         actual_data[th:h, tw:w] = tile
 
-        libtiff.close(fp)
-
         np.testing.assert_array_equal(data, actual_data)
+
+        n = libtiff.numberOfTiles(fp)
+        self.assertEqual(n, 4)
+
+        actual_th = libtiff.getFieldDefaulted(fp, 'TileLength')
+        self.assertEqual(actual_th, th)
+
+        actual_tw = libtiff.getFieldDefaulted(fp, 'TileWidth')
+        self.assertEqual(actual_tw, tw)
+
+        libtiff.close(fp)


=====================================
tests/test_tiff2jp2.py
=====================================
@@ -383,6 +383,53 @@ class TestSuite(fixtures.TestCommon):
         cls.astronaut_uint16_data = actual_data
         cls.astronaut_uint16_filename = path
 
+    @classmethod
+    def setup_ycbcr_striped_jpeg(cls, path):
+        """
+        SCENARIO:  create a simple color 2x1 stripped image
+        """
+        data = fixtures.skimage.data.astronaut()
+        h, w, z = data.shape
+        rps = h // 2
+
+        fp = libtiff.open(path, mode='w')
+
+        libtiff.setField(fp, 'Photometric', libtiff.Photometric.YCBCR)
+        libtiff.setField(fp, 'Compression', libtiff.Compression.JPEG)
+
+        l, w = data.shape[:2]
+        libtiff.setField(fp, 'ImageLength', l)
+        libtiff.setField(fp, 'ImageWidth', w)
+        libtiff.setField(fp, 'RowsPerStrip', rps)
+
+        libtiff.setField(fp, 'BitsPerSample', 8)
+        libtiff.setField(fp, 'SamplesPerPixel', 3)
+        libtiff.setField(fp, 'PlanarConfig', libtiff.PlanarConfig.CONTIG)
+        libtiff.setField(fp, 'JPEGColorMode', libtiff.PlanarConfig.CONTIG)
+        libtiff.setField(fp, 'JPEGQuality', 100)
+
+        libtiff.writeEncodedStrip(fp, 0, data[:rps, :, :])
+        libtiff.writeEncodedStrip(fp, 1, data[rps:rps * 2, :, :])
+
+        libtiff.close(fp)
+
+        # now read it back
+        fp = libtiff.open(path)
+
+        strip = np.zeros((rps, w, 4), dtype=np.uint8)
+        actual_data = np.zeros((h, w, 3), dtype=np.uint8)
+
+        libtiff.readRGBAStrip(fp, 0, strip)
+        actual_data[:rps, :, :] = strip[::-1, :, :3]
+
+        libtiff.readRGBAStrip(fp, rps, strip)
+        actual_data[rps:rps * 2, :, :] = strip[::-1, :, :3]
+
+        libtiff.close(fp)
+
+        cls.astronaut_ycbcr_striped_jpeg_data = actual_data
+        cls.astronaut_ycbcr_striped_jpeg_tif = path
+
     @classmethod
     def setup_ycbcr_jpeg(cls, path):
         """
@@ -564,6 +611,10 @@ class TestSuite(fixtures.TestCommon):
             cls.test_tiff_path / 'astronaut_ycbcr_jpeg_tiled.tif'
         )
 
+        cls.setup_ycbcr_striped_jpeg(
+            cls.test_tiff_path / 'astronaut_ycbcr_striped_jpeg.tif'
+        )
+
         cls.setup_rgb_uint16(cls.test_tiff_path / 'astronaut_uint16.tif')
 
     @classmethod
@@ -667,6 +718,67 @@ class TestSuite(fixtures.TestCommon):
         self.assertEqual(j.box[-1].data['ImageWidth'], 512)
         self.assertEqual(j.box[-1].data['ImageLength'], 512)
 
+    def test_smoke_rgba(self):
+        """
+        SCENARIO:  Convert RGCA TIFF file to JP2
+
+        EXPECTED RESULT:  data matches, number of resolution is the default.
+        There should be just one layer.  The number of resolutions should be
+        the default (5).  There are not PLT segments.  There are no EPH
+        markers.  There are no SOP markers.  The progression order is LRCP.
+        The irreversible transform will NOT be used.  PSNR cannot be tested
+        if it is not applied.
+
+        There is a UUID box appended at the end containing the metadata.
+        """
+        with Tiff2Jp2k(
+            self.astronaut_ycbcr_striped_jpeg_tif, self.temp_jp2_filename,
+            tilesize=[256, 256]
+        ) as j:
+            j.run()
+
+        j = Jp2k(self.temp_jp2_filename)
+
+        actual = j[:]
+        self.assertEqual(actual.shape, (512, 512, 3))
+
+        c = j.get_codestream(header_only=False)
+
+        actual = c.segment[2].code_block_size
+        expected = (64, 64)
+        self.assertEqual(actual, expected)
+
+        self.assertEqual(c.segment[2].layers, 1)
+        self.assertEqual(c.segment[2].num_res, 5)
+
+        at_least_one_eph = any(
+            isinstance(seg, glymur.codestream.EPHsegment)
+            for seg in c.segment
+        )
+        self.assertFalse(at_least_one_eph)
+
+        at_least_one_plt = any(
+            isinstance(seg, glymur.codestream.PLTsegment)
+            for seg in c.segment
+        )
+        self.assertFalse(at_least_one_plt)
+
+        at_least_one_sop = any(
+            isinstance(seg, glymur.codestream.SOPsegment)
+            for seg in c.segment
+        )
+        self.assertFalse(at_least_one_sop)
+
+        self.assertEqual(c.segment[2].prog_order, glymur.core.LRCP)
+
+        self.assertEqual(
+            c.segment[2].xform, glymur.core.WAVELET_XFORM_5X3_REVERSIBLE
+        )
+
+        self.assertEqual(j.box[-1].box_id, 'uuid')
+        self.assertEqual(j.box[-1].data['ImageWidth'], 512)
+        self.assertEqual(j.box[-1].data['ImageLength'], 512)
+
     def test_geotiff(self):
         """
         SCENARIO:  Convert a one-component GEOTIFF file to JP2
@@ -961,7 +1073,7 @@ class TestSuite(fixtures.TestCommon):
         """
         with Tiff2Jp2k(
             self.minisblack_3strip_partial_last_strip, self.temp_jp2_filename,
-            tilesize=(240, 240)
+            tilesize=(240, 240), verbose='DEBUG'
         ) as j:
             j.run()
 



View it on GitLab: https://salsa.debian.org/debian-gis-team/glymur/-/commit/194a02ee4c053e700bf91bb1ec8f2222f1608f00

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/glymur/-/commit/194a02ee4c053e700bf91bb1ec8f2222f1608f00
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20220809/d88b4d98/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list