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

Antonio Valentino (@antonio.valentino) gitlab at salsa.debian.org
Sat Dec 2 21:34:34 GMT 2023



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


Commits:
c9ede96a by Antonio Valentino at 2023-12-02T21:29:24+00:00
New upstream version 0.12.9
- - - - -


21 changed files:

- .gitignore
- CHANGES.txt
- appveyor.yml
- + ci/cforge-travis-312.yaml
- + docs/source/api.rst
- docs/source/conf.py
- docs/source/how_do_i.rst
- docs/source/index.rst
- docs/source/introduction.rst
- glymur/codestream.py
- glymur/jp2box.py
- glymur/jp2k.py
- glymur/version.py
- pyproject.toml
- setup.cfg
- + tests/data/issue626.j2k
- tests/test_codestream.py
- tests/test_jp2k.py
- tests/test_printing.py
- tests/test_threading.py
- tests/test_tiff2jp2.py


Changes:

=====================================
.gitignore
=====================================
@@ -4,3 +4,4 @@
 docs/build
 cover
 Glymur.egg-info
+dist


=====================================
CHANGES.txt
=====================================
@@ -1,3 +1,8 @@
+Nov 26, 2023 - v0.12.9
+    Fix handling of null-bytes with XML data
+    Add API references to docs
+    Qualify on python 3.12
+
 Jul 12, 2023 - v0.12.8
     Fix printing issue on 3.12beta, Fedora rawhide
 


=====================================
appveyor.yml
=====================================
@@ -47,6 +47,13 @@ environment:
       CONDA_NPY: "23"
       USE_PATH_FOR_GDAL_PYTHON: "YES"
 
+    - CONDA_ROOT: "C:\\Miniconda3_64"
+      PYTHON_VERSION: "3.12"
+      PYTHON_ARCH: "64"
+      CONDA_PY: "312"
+      CONDA_NPY: "26"
+      USE_PATH_FOR_GDAL_PYTHON: "YES"
+
 # We always use a 64-bit machine, but can build x86 distributions
 # with the PYTHON_ARCH variable (which is used by CMD_IN_ENV).
 platform:


=====================================
ci/cforge-travis-312.yaml
=====================================
@@ -0,0 +1,12 @@
+name: glymur
+channels:
+    - conda-forge
+dependencies:
+    - python=3.12.*
+    - gdal
+    - libtiff
+    - lxml
+    - numpy
+    - openjpeg
+    - pytest-xdist
+    - scikit-image


=====================================
docs/source/api.rst
=====================================
@@ -0,0 +1,9 @@
+#############
+API reference
+#############
+
+.. autoclass:: glymur.Jp2k
+    :members:
+
+.. autoclass:: glymur.jp2box.Jp2kBox
+    :members:


=====================================
docs/source/conf.py
=====================================
@@ -78,7 +78,7 @@ copyright = '2013-2023, John Evans'
 # The short X.Y version.
 version = '0.12'
 # The full version, including alpha/beta/rc tags.
-release = '0.12.8'
+release = '0.12.9'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
@@ -269,3 +269,5 @@ texinfo_documents = [('index', 'glymur', 'glymur Documentation',
 
 # Example configuration for intersphinx: refer to the Python standard library.
 intersphinx_mapping = {'http://docs.python.org/': None}
+
+numpydoc_show_class_members = False


=====================================
docs/source/how_do_i.rst
=====================================
@@ -1,11 +1,17 @@
-############
-How do I...?
-############
+########
+Examples
+########
+
+***************
+Basic Image I/O
+***************
 
+How do I...?
+============
 
-****************
 ... read images?
-****************
+----------------
+
 Jp2k implements slicing via the :py:meth:`__getitem__` method and
 hooks it into the multiple resolution property of JPEG 2000 imagery.
 This allows you to retrieve multiresolution imagery via
@@ -26,9 +32,10 @@ second lower-resolution image.  It's always powers of two. ::
     >>> thumbnail.shape
     (200, 120, 3)
 
-****************************
+
 ... read really large images
-****************************
+----------------------------
+
 JPEG 2000 images can be much larger than what can fit into your
 computer's memory.  While you can use strides that align with the
 JPEG 2000 decomposition levels to retrieve lower resolution images,
@@ -52,9 +59,9 @@ thumbnail.::
     >>> thumbnail.shape
     (25, 15, 3)
 
-************************
 ... read an image layer?
-************************
+------------------------
+
 JPEG2000 has layers which allow you to specify images with different
 levels of quality.  Different layers may be specified by utilizing 
 the layer property.  The default layer value is 0, which specifies the
@@ -67,9 +74,26 @@ first layer. ::
     >>> jpx.layer = 3
     >>> d3 = jpx[:] # third layer
 
-*********************************************************
+... write images?
+-----------------
+
+The easiest way is just to assign the entire image, similar to what might
+be done with NumPy. ::
+    
+    >>> import glymur, skimage.data
+    >>> jp2 = glymur.Jp2k('astronaut.jp2')
+    >>> jp2[:] = skimage.data.astronaut()
+
+******************
+Advanced Image I/O
+******************
+
+How do I...?
+============
+
 ... make use of OpenJPEG's thread support to read images?
-*********************************************************
+---------------------------------------------------------
+
 If you have glymur 0.8.13 or higher
 and OpenJPEG 2.2.0 or higher,
 you can make use of OpenJPEG's thread support to speed-up read operations.
@@ -88,9 +112,10 @@ you really should look into this. ::
     >>> t1 - t0
     0.4060473537445068
 
-**************************************************
+
 ... efficiently read just one band of a big image?
-**************************************************
+--------------------------------------------------
+
 For really large images, before v0.9.4 you had to read in all bands of an
 image, even if you were only interested in just one of those bands.  With
 v0.9.4 or higher, you can make use of the :py:meth:`decoded_components`
@@ -121,20 +146,9 @@ bands.
     >>> data.shape
     (1456, 2592, 3)
 
-*****************
-... write images?
-*****************
-The easiest way is just to assign the entire image, similar to what might
-be done with NumPy. ::
-    
-    >>> import glymur, skimage.data
-    >>> jp2 = glymur.Jp2k('astronaut.jp2')
-    >>> jp2[:] = skimage.data.astronaut()
-
-
-**********************************************
 ... write images using multithreaded encoding?
-**********************************************
+----------------------------------------------
+
 If you have glymur 0.9.3 or higher
 and OpenJPEG 2.4.0 or higher,
 you can make use of OpenJPEG's thread support to speed-up read operations.
@@ -156,9 +170,9 @@ With a puny 2015 macbook, just two cores, and a 5824x10368x3 image, we get::
     7.24 seconds
 
 
-*********************************************
 ... write images that cannot fit into memory?
-*********************************************
+---------------------------------------------
+
 If you have glymur 0.9.4 or higher, you can write out an image tile-by-tile.
 In this example, we take a 512x512x3 image and tile it into a 20x20 grid, 
 resulting in a 10240x10240x3 image.
@@ -180,9 +194,9 @@ resulting in a 10240x10240x3 image.
 Note that the tiles are written out left-to-right, tile-row-by-tile-row.  You must
 have image data ready to feed each tile writer, you cannot skip a tile.
 
-****************************************
 ... force the generation of PLT markers?
-****************************************
+----------------------------------------
+
 With glymur 0.9.5 or higher, you can instruct the encoder to generate PLT markers
 by using the plt keyword. ::
 
@@ -195,9 +209,9 @@ by using the plt keyword. ::
         Index:  0
         Iplt:  [271, 201, 208, 749, 551, 548, 2569, 1852, 1814, 8300, 6370, 6061, 26987, 23437, 21431, 88511, 86763, 77253]
 
-************************************************************************
 ... write images with different compression ratios for different layers?
-************************************************************************
+------------------------------------------------------------------------
+
 Different compression factors may be specified with the cratios parameter ::
 
     >>> import skimage.data, glymur
@@ -210,9 +224,9 @@ Different compression factors may be specified with the cratios parameter ::
     >>> jp2.layer = 2
     >>> data = jp2[:]
 
-*************************************************************************
 ... write images with different PSNR (or "quality") for different layers?
-*************************************************************************
+-------------------------------------------------------------------------
+
 Different PSNR values may be specified with the psnr parameter.  Please read
 https://en.wikipedia.org/wiki/Peak_signal-to-noise_ratio
 for a basic understanding of PSNR.  
@@ -231,9 +245,8 @@ the layers to make the first layer lossless, not the last. ::
     >>> print(psnr)
     [inf, 29.028560403833303, 39.206919416670402, 47.593129828702246]
 
-*************************************
 ... convert TIFF images to JPEG 2000?
-*************************************
+-------------------------------------
 
 Many TIFFs can be converted to tiled JPEG 2000 files using glymur.
 A command line utility **tiff2jp2** is provided for this task.
@@ -254,9 +267,73 @@ inefficient no matter how you tile the JPEG 2000 file.
 The TIFF metadata is stored in a UUID box appended to the end of the
 JPEG 2000 file.
 
-*********************
+... create an image with an alpha layer?
+----------------------------------------
+
+OpenJPEG can create JP2 files with more than 3 components (use version 2.1.0+ 
+for this), but by default, any extra components are not described
+as such.  In order to do so, we need to re-wrap such an image in a
+set of boxes that includes a channel definition box.  The following example
+creates an ellipical mask. ::
+
+    >>> import numpy as np
+    >>> import glymur
+    >>> from glymur import Jp2k
+    >>> rgb = Jp2k(glymur.data.goodstuff())[:]
+    >>> ny, nx = rgb.shape[:2]
+    >>> Y, X = np.ogrid[:ny, :nx]
+    >>> mask = nx ** 2 * (Y - ny / 2) ** 2 + ny ** 2 * (X - nx / 2) ** 2 > (nx * ny / 2)**2
+    >>> alpha = 255 * np.ones((ny, nx, 1), dtype=np.uint8)
+    >>> alpha[mask] = 0
+    >>> rgba = np.concatenate((rgb, alpha), axis=2)
+    >>> jp2 = Jp2k('myfile.jp2', data=rgba)
+
+Next we need to specify what types of channels we have.
+The first three channels are color channels, but we identify the fourth as
+an alpha channel::
+
+    >>> from glymur.core import COLOR, OPACITY
+    >>> ctype = [COLOR, COLOR, COLOR, OPACITY]
+
+And finally we have to specify just exactly how each channel is to be
+interpreted.  The color channels are straightforward, they correspond to R-G-B,
+but the alpha (or opacity) channel in this case is to be applied against the 
+entire image (it is possible to apply an alpha channel to a single color 
+channel, but we aren't doing that). ::
+
+    >>> from glymur.core import RED, GREEN, BLUE, WHOLE_IMAGE
+    >>> asoc = [RED, GREEN, BLUE, WHOLE_IMAGE]
+    >>> cdef = glymur.jp2box.ChannelDefinitionBox(ctype, asoc)
+    >>> print(cdef)
+    Channel Definition Box (cdef) @ (0, 0)
+        Channel 0 (color) ==> (1)
+        Channel 1 (color) ==> (2)
+        Channel 2 (color) ==> (3)
+        Channel 3 (opacity) ==> (whole image)
+
+It's easiest to take the existing jp2 jacket and just add the channel
+definition box in the appropriate spot.  The channel definition box **must**
+go into the jp2 header box, and then we can rewrap the image. ::
+
+    >>> boxes = jp2.box  # The box attribute is the list of JP2 boxes
+    >>> boxes[2].box.append(cdef)
+    >>> jp2_rgba = jp2.wrap("goodstuff_rgba.jp2", boxes=boxes)
+
+Here's how the Preview application on the mac shows the RGBA image.
+
+.. image:: goodstuff_alpha.png
+
+
+**************
+Basic Metadata
+**************
+
+How do I...?
+============
+
 ... display metadata?
-*********************
+---------------------
+
 There are two ways.  From the command line, the console script **jp2dump** is
 available. ::
 
@@ -410,9 +487,12 @@ object, i.e. ::
             CME marker segment @ (3305, 37)
                 "Created by OpenJPEG version 2.0.0"
      
-That's fairly overwhelming, and perhaps lost in the flood of information
-is the fact that the codestream metadata is limited to just what's in the
-main codestream header.  You can suppress the codestream and XML details by
+... display less metadata?
+--------------------------
+
+The amount of metadata in a JPEG 2000 file can be overwhelming, mostly due
+to the codestream and XML and UUID boxes.  
+You can suppress the codestream and XML details by
 making use of the :py:meth:`set_option` function::
 
     >>> glymur.set_option('print.codestream', False)
@@ -439,13 +519,109 @@ making use of the :py:meth:`set_option` function::
         UUID:  be7acfcb-97a9-42e8-9c71-999491e3afac (XMP)
     Contiguous Codestream Box (jp2c) @ (3223, 1132296)
 
-It is possible to easily print the codestream header details as well, i.e. ::
+... display the codestream in all its gory glory?
+-------------------------------------------------
+
+The codestream details are limited to the codestream header because
+by default that's all the codestream metadata that is retrieved. It is, howver,
+possible to print the full codestream.::
+
+    >>> glymur.set_option('print.codestream', True)
+    >>> c = j.get_codestream(header_only=False)
+    >>> print(c)
+    Codestream:
+    SOC marker segment @ (3231, 0)
+    SIZ marker segment @ (3233, 47)
+        Profile:  no profile
+        Reference Grid Height, Width:  (1456 x 2592)
+        Vertical, Horizontal Reference Grid Offset:  (0 x 0)
+        Reference Tile Height, Width:  (1456 x 2592)
+        Vertical, Horizontal Reference Tile Offset:  (0 x 0)
+        Bitdepth:  (8, 8, 8)
+        Signed:  (False, False, False)
+        Vertical, Horizontal Subsampling:  ((1, 1), (1, 1), (1, 1))
+    COD marker segment @ (3282, 12)
+        Coding style:
+            Entropy coder, without partitions
+            SOP marker segments:  False
+            EPH marker segments:  False
+        Coding style parameters:
+            Progression order:  LRCP
+            Number of layers:  2
+            Multiple component transformation usage:  reversible
+            Number of decomposition levels:  1
+            Code block height, width:  (64 x 64)
+            Wavelet transform:  5-3 reversible
+            Precinct size:  (32768, 32768)
+            Code block context:
+                Selective arithmetic coding bypass:  False
+                Reset context probabilities on coding pass boundaries:  False
+                Termination on each coding pass:  False
+                Vertically stripe causal context:  False
+                Predictable termination:  False
+                Segmentation symbols:  False
+    QCD marker segment @ (3296, 7)
+        Quantization style:  no quantization, 2 guard bits
+        Step size:  [(0, 8), (0, 9), (0, 9), (0, 10)]
+    CME marker segment @ (3305, 37)
+        "Created by OpenJPEG version 2.0.0"
+    SOT marker segment @ (3344, 10)
+        Tile part index:  0
+        Tile part length:  1132173
+        Tile part instance:  0
+        Number of tile parts:  1
+    COC marker segment @ (3356, 9)
+        Associated component:  1
+        Coding style for this component:  Entropy coder, PARTITION = 0
+        Coding style parameters:
+            Number of decomposition levels:  1
+            Code block height, width:  (64 x 64)
+            Wavelet transform:  5-3 reversible
+            Precinct size:  (32768, 32768)
+            Code block context:
+                Selective arithmetic coding bypass:  False
+                Reset context probabilities on coding pass boundaries:  False
+                Termination on each coding pass:  False
+                Vertically stripe causal context:  False
+                Predictable termination:  False
+                Segmentation symbols:  False
+    QCC marker segment @ (3367, 8)
+        Associated Component:  1
+        Quantization style:  no quantization, 2 guard bits
+        Step size:  [(0, 8), (0, 9), (0, 9), (0, 10)]
+    COC marker segment @ (3377, 9)
+        Associated component:  2
+        Coding style for this component:  Entropy coder, PARTITION = 0
+        Coding style parameters:
+            Number of decomposition levels:  1
+            Code block height, width:  (64 x 64)
+            Wavelet transform:  5-3 reversible
+            Precinct size:  (32768, 32768)
+            Code block context:
+                Selective arithmetic coding bypass:  False
+                Reset context probabilities on coding pass boundaries:  False
+                Termination on each coding pass:  False
+                Vertically stripe causal context:  False
+                Predictable termination:  False
+                Segmentation symbols:  False
+    QCC marker segment @ (3388, 8)
+        Associated Component:  2
+        Quantization style:  no quantization, 2 guard bits
+        Step size:  [(0, 8), (0, 9), (0, 9), (0, 10)]
+    SOD marker segment @ (3398, 0)
+    EOC marker segment @ (1135517, 0)
 
-    >>> print(j.codestream)   # details not show
 
-*********************
+*****************
+Advanced Metadata
+*****************
+
+How do I...?
+============
+
 ... add XML metadata?
-*********************
+---------------------
+
 You can append any number of XML boxes to a JP2 file (not to a raw codestream).
 Consider the following XML file `data.xml` : ::
 
@@ -475,11 +651,45 @@ The :py:meth:`append` method can add an XML box as shown below::
     >>> jp2.append(xmlbox)
     >>> print(jp2)
 
-*******************************************
-... add metadata in a more general fashion?
-*******************************************
-An existing raw codestream (or JP2 file) can be wrapped (re-wrapped) in a 
-user-defined set of JP2 boxes.  To get just a minimal JP2 jacket on the 
+... create display and/or capture resolution boxes?
+---------------------------------------------------
+
+Capture and display resolution boxes are part of the JPEG 2000 standard.  You
+may create such metadata boxes via keyword arguments.::
+
+    >>> import numpy as np, glymur
+    >>> vresc, hresc = 0.1, 0.2
+    >>> vresd, hresd = 0.3, 0.4
+    >>> j = glymur.Jp2k(
+            'my.jp2', data=np.zeros([256, 256, 3], dtype=np.uint8),
+            capture_resolution=[vresc, hresc],
+            display_resolution=[vresd, hresd],
+        )
+    >>> glymur.set_printoptions(short=True)
+    >>> print(j)
+    File:  my.jp2
+    JPEG 2000 Signature Box (jP  ) @ (0, 12)
+    File Type Box (ftyp) @ (12, 20)
+    JP2 Header Box (jp2h) @ (32, 89)
+        Image Header Box (ihdr) @ (40, 22)
+        Colour Specification Box (colr) @ (62, 15)
+        Resolution Box (res ) @ (77, 44)
+            Capture Resolution Box (resc) @ (85, 18)
+            Display Resolution Box (resd) @ (103, 18)
+    Contiguous Codestream Box (jp2c) @ (121, 174)
+
+
+... reinterpret a codestream (say what)?
+----------------------------------------
+
+An existing raw codestream (or JP2 file) can be re-wrapped in a 
+user-defined set of JP2 boxes.  The point to doing this might be
+to provide a different interpretation of an image.  For example,
+a raw codestream has no concept of a color model, whereas a JP2
+file with a 3-channel codestream will by default consider that to
+be an RGB image.
+
+To get just a minimal JP2 jacket on the 
 codestream provided by `goodstuff.j2k` (a file consisting of a raw codestream),
 you can use the :py:meth:`wrap` method with no box argument: ::
 
@@ -560,9 +770,8 @@ As to the question of which method you should use, :py:meth:`append` or
 produces a new JP2 file, while :py:meth:`append` modifies an existing file and
 is currently limited to XML and UUID boxes.
 
-***************************
 ... work with ICC profiles?
-***************************
+---------------------------
 
 A detailed answer is beyond my capabilities.  What I can tell you is how to
 gain access to ICC profiles that JPEG 2000 images may or may not provide for
@@ -588,68 +797,8 @@ are wrapped in a BytesIO object, which is fed to the most-excellent Pillow packa
 To go any further with this, you will want to consult
 `the Pillow documentation <https://pillow.readthedocs.io/en/stable/>`_.
 
-****************************************
-... create an image with an alpha layer?
-****************************************
-
-OpenJPEG can create JP2 files with more than 3 components (use version 2.1.0+ 
-for this), but by default, any extra components are not described
-as such.  In order to do so, we need to re-wrap such an image in a
-set of boxes that includes a channel definition box.  The following example
-creates an ellipical mask. ::
-
-    >>> import numpy as np
-    >>> import glymur
-    >>> from glymur import Jp2k
-    >>> rgb = Jp2k(glymur.data.goodstuff())[:]
-    >>> ny, nx = rgb.shape[:2]
-    >>> Y, X = np.ogrid[:ny, :nx]
-    >>> mask = nx ** 2 * (Y - ny / 2) ** 2 + ny ** 2 * (X - nx / 2) ** 2 > (nx * ny / 2)**2
-    >>> alpha = 255 * np.ones((ny, nx, 1), dtype=np.uint8)
-    >>> alpha[mask] = 0
-    >>> rgba = np.concatenate((rgb, alpha), axis=2)
-    >>> jp2 = Jp2k('myfile.jp2', data=rgba)
-
-Next we need to specify what types of channels we have.
-The first three channels are color channels, but we identify the fourth as
-an alpha channel::
-
-    >>> from glymur.core import COLOR, OPACITY
-    >>> ctype = [COLOR, COLOR, COLOR, OPACITY]
-
-And finally we have to specify just exactly how each channel is to be
-interpreted.  The color channels are straightforward, they correspond to R-G-B,
-but the alpha (or opacity) channel in this case is to be applied against the 
-entire image (it is possible to apply an alpha channel to a single color 
-channel, but we aren't doing that). ::
-
-    >>> from glymur.core import RED, GREEN, BLUE, WHOLE_IMAGE
-    >>> asoc = [RED, GREEN, BLUE, WHOLE_IMAGE]
-    >>> cdef = glymur.jp2box.ChannelDefinitionBox(ctype, asoc)
-    >>> print(cdef)
-    Channel Definition Box (cdef) @ (0, 0)
-        Channel 0 (color) ==> (1)
-        Channel 1 (color) ==> (2)
-        Channel 2 (color) ==> (3)
-        Channel 3 (opacity) ==> (whole image)
-
-It's easiest to take the existing jp2 jacket and just add the channel
-definition box in the appropriate spot.  The channel definition box **must**
-go into the jp2 header box, and then we can rewrap the image. ::
-
-    >>> boxes = jp2.box  # The box attribute is the list of JP2 boxes
-    >>> boxes[2].box.append(cdef)
-    >>> jp2_rgba = jp2.wrap("goodstuff_rgba.jp2", boxes=boxes)
-
-Here's how the Preview application on the mac shows the RGBA image.
-
-.. image:: goodstuff_alpha.png
-
-
-    
-************************
 ... work with XMP UUIDs?
-************************
+------------------------
 
 `Wikipedia <http://en.wikipedia.org/wiki/Extensible_Metadata_Platform>`_ states
 that "The Extensible Metadata Platform (XMP) is an ISO standard,


=====================================
docs/source/index.rst
=====================================
@@ -10,11 +10,12 @@ Welcome to glymur's documentation!
 Contents:
 
 .. toctree::
-   :maxdepth: 2
+   :maxdepth: 4
 
    introduction
    detailed_installation
    how_do_i
+   api
    whatsnew/index
    roadmap
 
@@ -26,4 +27,3 @@ Indices and tables
 * :ref:`genindex`
 * :ref:`modindex`
 * :ref:`search`
-


=====================================
docs/source/introduction.rst
=====================================
@@ -12,7 +12,8 @@ In regards to metadata, most JP2 boxes are properly interpreted.
 Certain optional JP2 boxes can also be written, including XML boxes and
 XMP UUIDs.  There is incomplete support for reading JPX metadata.
 
-The current version of glymur is supported on Python versions 3.8, 3.9, 3.10, and 3.11.
+The current version of glymur is supported on Python versions 3.9,
+3.10, 3.11, and 3.12.
 
 For more information about OpenJPEG, please consult http://www.openjpeg.org.
 


=====================================
glymur/codestream.py
=====================================
@@ -118,6 +118,10 @@ _CAPABILITIES_DISPLAY = {
 _KNOWN_PROFILES = _CAPABILITIES_DISPLAY.keys()
 
 
+class J2KParseError(struct.error):
+    pass
+
+
 class Codestream(object):
     """Container for codestream information.
 
@@ -163,7 +167,7 @@ class Codestream(object):
         #     ITU-T Rec. T.87
         # We really don't know what to do with them, so they are treated as if
         # they are reserved markers.
-        parse_marker_segment_fcn = {
+        self.parse_marker_segment_fcn = {
 
             # The following are definitively reserved markers according to
             # table A-1 in ISO/IEC FCD15444-1.
@@ -227,9 +231,16 @@ class Codestream(object):
 
         self.offset = fptr.tell()
         self.length = length
+        self.header_only = header_only
 
         self.segment = []
 
+        self._parse(fptr)
+
+    def _parse(self, fptr):
+        """
+        Parse the codestream.
+        """
         # First two bytes are the SOC marker.  We already know that.
         read_buffer = fptr.read(2)
         segment = SOCsegment(offset=fptr.tell() - 2, length=0)
@@ -241,7 +252,16 @@ class Codestream(object):
         while True:
 
             read_buffer = fptr.read(2)
-            self._marker_id, = struct.unpack('>H', read_buffer)
+
+            try:
+                self._marker_id, = struct.unpack('>H', read_buffer)
+            except struct.error as e:
+                msg = (
+                    f"Unable to read an expected marker in the codestream "
+                    f"at byte offset {fptr.tell()}.  \"{e}\"."
+                )
+                raise J2KParseError(msg)
+
             if self._marker_id < 0xff00:
                 offset = fptr.tell() - 2
                 msg = (
@@ -254,13 +274,13 @@ class Codestream(object):
 
             self._offset = fptr.tell() - 2
 
-            if self._marker_id == 0xff90 and header_only:
+            if self._marker_id == 0xff90 and self.header_only:
                 # Start-of-tile (SOT) means that we are out of the main header
                 # and there is no need to go further.
                 break
 
             try:
-                segment = parse_marker_segment_fcn[self._marker_id](fptr)
+                segment = self.parse_marker_segment_fcn[self._marker_id](fptr)
             except KeyError:
                 segment = self._parse_reserved_segment(fptr)
 
@@ -270,13 +290,16 @@ class Codestream(object):
                 # end of codestream, should break.
                 break
 
+            # If the marker ID is SOD, then we need to seek past the tile part
+            # bit stream.
             if self._marker_id == 0xff93:
-                # If SOD, then we need to seek past the tile part bit stream.
-                if self._parse_tpart_flag and not header_only:
+
+                if self._parse_tpart_flag and not self.header_only:
                     # But first parse the tile part bit stream for SOP and
                     # EPH segments.
-                    self._parse_tile_part_bit_stream(fptr, segment,
-                                                     self._tile_length[-1])
+                    self._parse_tile_part_bit_stream(
+                        fptr, segment, self._tile_length[-1]
+                    )
 
                 new_offset = self._tile_offset[-1] + self._tile_length[-1]
                 fptr.seek(new_offset)


=====================================
glymur/jp2box.py
=====================================
@@ -3433,9 +3433,15 @@ class UUIDBox(Jp2kBox):
     def _parse_raw_data(self):
         """Private function for parsing UUID payloads if possible."""
         if self.uuid == _XMP_UUID:
+
             txt = self.raw_data.decode('utf-8')
-            elt = ET.fromstring(txt)
+
+            # If XMP comes from a TIFF tag, then it should be terminated
+            # by a null byte.  libxml2 now requires that null byte to be
+            # stripped off before being fed into lxml.
+            elt = ET.fromstring(txt.strip('\x00'))
             self.data = ET.ElementTree(elt)
+
         elif self.uuid == _GEOTIFF_UUID:
             self.data = tiff_header(self.raw_data)
         elif self.uuid == _EXIF_UUID:


=====================================
glymur/jp2k.py
=====================================
@@ -40,41 +40,54 @@ class Jp2k(Jp2kBox):
 
     Parameters
     ----------
-    filename : str or path
-        The path to JPEG 2000 file.  If you are only reading JPEG 2000 files,
-        this is the only argument you need to supply.
+    filename : str or Path
+        If you are reading JPEG 2000 files instead of writing, this is the only
+        argument you need to supply.
     data : np.ndarray, optional
-        Image data to be written to file.
+        Image data to be written.  This should not be specified when writing by
+        tile.
     shape : Tuple[int, int, ...], optional
-        Size of image data, only required when image_data is not provided.
+        Size of the image to be written.  This should only be specified when
+        the JPEG 2000 file is to be written by tiles.
     capture_resolution : Tuple[int, int], optional
-        Capture solution (VRES, HRES).  This appends a capture resolution
-        box onto the end of the JP2 file when it is created.
+        Capture solution (VRES, HRES).  Specifying this option will append a
+        capture resolution box onto the end of the JP2 file.
     cbsize : Tuple[int, int], optional
-        Code block size (NROWS, NCOLS)
+        Code block size (NROWS, NCOLS).  This parameter is for advanced use
+        cases.
     cinema2k : int, optional
-        Frames per second, either 24 or 48.
+        Frames per second, either 24 or 48.  This option is required to
+        generate a codestream compliant to the digital cinema specifications
+        for 2K resolution content.
     cinema4k : bool, optional
-        Set to True to specify Cinema4K mode, defaults to false.
+        Set to True to specify Cinema4K mode, defaults to false.  This option
+        is required to generate a codestream compliant to the digital cinema
+        specifications for 4K resolution content.
     colorspace : {'rgb', 'gray'}, optional
-        The image color space.  If not supplied, it will be inferred.
+        The image color space.  If not supplied, it will be interpreted from
+        other parameters.
     cratios : Tuple[int, ...], optional
-        Compression ratios for successive layers.
+        Each value is a factor of compression and each value represents a
+        quality layer.  If a lossless layer is intended, the last value should
+        be 1.  The default action is to write a single lossless quality layer.
     display_resolution : Tuple[int, int], optional
-        Display solution (VRES, HRES).  This appends a display resolution
-        box onto the end of the JP2 file when it is created.
+        Display solution (VRES, HRES).  Specifying this option will append a
+        display resolution box onto the end of the JP2.
     eph : bool, optional
-        If true, write EPH marker after each header packet.
+        If true, write an EPH marker after each packet header.
     grid_offset : Tuple[int, int], optional
-        Offset (DY, DX) of the origin of the image in the reference grid.
+        Offset (dY, dX) of the origin of the image in the reference grid.
     irreversible : bool, optional
-        If true, use the irreversible DWT 9-7 transform.
+        If true, use the irreversible DWT 9-7 transform in place of the
+        reversible 5-3 transform.
     mct : bool, optional
-        Usage of the multi component transform to write an image.  If not
+        If true, use the multi component transform to write an image.  If not
         specified, defaults to True if the color space is RGB, false if the
         color space is grayscale.
     modesw : int, optional
-        mode switch
+        This is an advanced option, it allows the possibility to use a mode
+        switch during the encoding process.  There are the following
+        possibilities:
             1 = BYPASS(LAZY)
             2 = RESET
             4 = RESTART(TERMALL)
@@ -85,25 +98,26 @@ class Jp2k(Jp2kBox):
         Number of resolutions, defaults to 6.  This number will be equal to
         the number of thumbnails plus the original image.
     plt : bool, optional
-        Generate PLT markers.
+        Write PLT markers in the tile-part header.
     prog : {'LRCP', 'RLCP', 'RPCL', 'PCRL', 'CPRL'}, optional
-        Progression order.  If not specified, the chosen progression order
-        will be 'CPRL' if either cinema2k or cinema4k is specified,
+        Progression order.  The default is 'LRCP'.  If cinema2k or cinema4k is
+        specified, the choice must be 'CPRL'.
         otherwise defaulting to 'LRCP'.
     psizes : List[Tuple[int, int]], optional
         Precinct sizes, each precinct size tuple is defined as
         (height, width).
     psnr : Tuple[int, ...] or None
         Different PSNR for successive layers.  If the last layer is desired
-        to be lossless, specify 0 for the last value.
+        to be lossless, specify 0 for the last value.  Do not specify psnr with
+        cratios.
     sop : bool, optional
-        If true, write SOP marker before each packet.
+        If true, write a SOP marker before each packet.
     subsam : Tuple[int, int], optional
         Subsampling factors (dy, dx).
     tilesize : Tuple[int, int], optional
         Tile size in terms of (numrows, numcols), not (X, Y).
     tlm : bool, optional
-        Generate TLM markers.
+        Write TLM markers in the main header.
     verbose : bool, optional
         Print informational messages produced by the OpenJPEG library.
 


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


=====================================
pyproject.toml
=====================================
@@ -1,3 +1,8 @@
 [build-system]
 requires = ["setuptools", "wheel"]
 build-backend = "setuptools.build_meta"
+
+[tool.pytest.ini_options]
+filterwarnings = [
+    "ignore::DeprecationWarning:skimage.util.*",
+]


=====================================
setup.cfg
=====================================
@@ -15,6 +15,7 @@ classifiers =
     Programming Language :: Python :: 3.9
     Programming Language :: Python :: 3.10
     Programming Language :: Python :: 3.11
+    Programming Language :: Python :: 3.12
     Programming Language :: Python :: Implementation :: CPython
     License :: OSI Approved :: MIT License
     Intended Audience :: Science/Research


=====================================
tests/data/issue626.j2k
=====================================
Binary files /dev/null and b/tests/data/issue626.j2k differ


=====================================
tests/test_codestream.py
=====================================
@@ -16,10 +16,12 @@ from glymur import Jp2k
 from . import fixtures
 
 
-class TestSuite(unittest.TestCase):
+class TestSuite(fixtures.TestCommon):
     """Test suite for ICC Profile code."""
 
     def setUp(self):
+        super().setUp()
+
         self.p0_03 = ir.files('tests.data').joinpath('p0_03.j2k')
         self.p0_06 = ir.files('tests.data').joinpath('p0_06.j2k')
         self.p1_06 = ir.files('tests.data').joinpath('p1_06.j2k')
@@ -72,6 +74,7 @@ class TestSuite(unittest.TestCase):
             # Lots of things wrong with this file.
             warnings.simplefilter('ignore')
             jp2 = Jp2k(self.edf_c2_1178956)
+
         c = jp2.get_codestream()
         self.assertEqual(c.segment[2].zppm, 0)
         self.assertEqual(len(c.segment[2].data), 9)
@@ -97,6 +100,55 @@ class TestSuite(unittest.TestCase):
         self.assertEqual(c.segment[-1].srgn, 0)
         self.assertEqual(c.segment[-1].sprgn, 11)
 
+    def test_reserved_marker_segment(self):
+        """
+        SCENARIO:  Rewrite a J2K file to include a marker segment with a
+        reserved marker 0xff6f (65391).
+
+        EXPECTED RESULT:  The marker segment should be properly parsed.
+        """
+
+        with open(self.temp_j2k_filename, 'wb') as tfile:
+            with open(self.j2kfile, 'rb') as ifile:
+                # Everything up until the first QCD marker.
+                read_buffer = ifile.read(65)
+                tfile.write(read_buffer)
+
+                # Write the new marker segment, 0xff6f = 65391
+                read_buffer = struct.pack('>HHB', int(65391), int(3), int(0))
+                tfile.write(read_buffer)
+
+                # Get the rest of the input file.
+                read_buffer = ifile.read()
+                tfile.write(read_buffer)
+                tfile.flush()
+
+        codestream = Jp2k(tfile.name).get_codestream()
+
+        self.assertEqual(codestream.segment[3].marker_id, '0xff6f')
+        self.assertEqual(codestream.segment[3].length, 3)
+        self.assertEqual(codestream.segment[3].data, b'\x00')
+
+    def test_siz_segment_ssiz_unsigned(self):
+        """ssiz attribute to be removed in future release"""
+        j = Jp2k(self.jp2file)
+        codestream = j.get_codestream()
+
+        # The ssiz attribute was simply a tuple of raw bytes.
+        # The first 7 bits are interpreted as the bitdepth, the MSB determines
+        # whether or not it is signed.
+        self.assertEqual(codestream.segment[1].ssiz, (7, 7, 7))
+
+    def test_626(self):
+        """
+        Scenario:
+
+        Expected result:  J2KParseError
+        """
+        path = ir.files('tests.data').joinpath('issue626.j2k')
+        with self.assertRaises(glymur.codestream.J2KParseError):
+            Jp2k(path)
+
 
 class TestCodestreamRepr(unittest.TestCase):
 
@@ -141,46 +193,3 @@ class TestCodestreamRepr(unittest.TestCase):
         self.assertEqual(newseg.yrsiz, (1, 1, 1))
         self.assertEqual(newseg.bitdepth, (8, 8, 8))
         self.assertEqual(newseg.signed, (False, False, False))
-
-
-class TestCodestream(fixtures.TestCommon):
-    """Test suite for unusual codestream cases."""
-
-    def test_reserved_marker_segment(self):
-        """
-        SCENARIO:  Rewrite a J2K file to include a marker segment with a
-        reserved marker 0xff6f (65391).
-
-        EXPECTED RESULT:  The marker segment should be properly parsed.
-        """
-
-        with open(self.temp_j2k_filename, 'wb') as tfile:
-            with open(self.j2kfile, 'rb') as ifile:
-                # Everything up until the first QCD marker.
-                read_buffer = ifile.read(65)
-                tfile.write(read_buffer)
-
-                # Write the new marker segment, 0xff6f = 65391
-                read_buffer = struct.pack('>HHB', int(65391), int(3), int(0))
-                tfile.write(read_buffer)
-
-                # Get the rest of the input file.
-                read_buffer = ifile.read()
-                tfile.write(read_buffer)
-                tfile.flush()
-
-        codestream = Jp2k(tfile.name).get_codestream()
-
-        self.assertEqual(codestream.segment[3].marker_id, '0xff6f')
-        self.assertEqual(codestream.segment[3].length, 3)
-        self.assertEqual(codestream.segment[3].data, b'\x00')
-
-    def test_siz_segment_ssiz_unsigned(self):
-        """ssiz attribute to be removed in future release"""
-        j = Jp2k(self.jp2file)
-        codestream = j.get_codestream()
-
-        # The ssiz attribute was simply a tuple of raw bytes.
-        # The first 7 bits are interpreted as the bitdepth, the MSB determines
-        # whether or not it is signed.
-        self.assertEqual(codestream.segment[1].ssiz, (7, 7, 7))


=====================================
tests/test_jp2k.py
=====================================
@@ -944,7 +944,8 @@ class TestJp2k(fixtures.TestCommon):
         j.layer = 1
         d1 = j[:]
 
-        np.alltrue(d0 != d1)
+        # if the arrays were equal, their difference would not sum to 0
+        self.assertNotEqual(np.abs(d0 - d1).sum(), 0)
 
     def test_invalid_layers(self):
         """


=====================================
tests/test_printing.py
=====================================
@@ -1067,7 +1067,7 @@ class TestPrinting(fixtures.TestCommon):
                 "                    (   'TileOffsets',\n"
                 "                        "
                 "array([ 0, 10, 20, ..., 70, 80, 90], dtype=uint32)),\n"
-                "                    ('ExifTag', OrderedDict({'Make': 'HTC'}))])"
+                "                    ('ExifTag', OrderedDict({'Make': 'HTC'}))])"  # noqa : E501
             )
         else:
             expected = (
@@ -1078,7 +1078,7 @@ class TestPrinting(fixtures.TestCommon):
                 "                    (   'TileOffsets',\n"
                 "                        "
                 "array([ 0, 10, 20, ..., 70, 80, 90], dtype=uint32)),\n"
-                "                    ('ExifTag', OrderedDict([('Make', 'HTC')]))])"
+                "                    ('ExifTag', OrderedDict([('Make', 'HTC')]))])"  # noqa : E501
             )
         self.assertEqual(actual, expected)
 
@@ -1829,8 +1829,7 @@ class TestJp2dump(unittest.TestCase):
             "    y1: 0\n"
             "    numcomps: 0\n"
             "    color_space: 0\n"
-            "    icc_profile_buf: "
-            "<glymur.lib.openjp2.LP_c_ubyte object at 0x[0-9A-Fa-f]*>\n"
+            "    icc_profile_buf: <(glymur.lib.openjp2|ctypes.wintypes).LP_c_ubyte object at 0x[0-9A-Fa-f]*>\n"  # noqa : E501
             "    icc_profile_len: 0")
         self.assertRegex(actual, expected)
 


=====================================
tests/test_threading.py
=====================================
@@ -1,5 +1,7 @@
 # Standard library imports ...
+import importlib.resources as ir
 import os
+import sys
 import time
 import unittest
 from unittest.mock import patch
@@ -11,12 +13,14 @@ import numpy as np
 # Local imports
 import glymur
 from glymur import Jp2k
+from glymur import command_line
 
 from .fixtures import OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG
 
 from . import fixtures
 
 
+ at unittest.skipIf(os.cpu_count() < 2, "makes no sense if 2 cores not there")
 @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG)
 @unittest.skipIf(glymur.version.openjpeg_version < '2.3.0',
                  "Requires as least v2.3.0")
@@ -51,7 +55,6 @@ class TestSuite(fixtures.TestCommon):
 
         self.assertTrue(delta1 < delta0)
 
-    @unittest.skipIf(os.cpu_count() < 4, "makes no sense if 4 cores not there")
     def test_thread_support_on_openjpeg_lt_220(self):
         """
         SCENARIO:  Set number of threads on openjpeg < 2.2.0
@@ -60,9 +63,8 @@ class TestSuite(fixtures.TestCommon):
         """
         with patch('glymur.jp2k.version.openjpeg_version', new='2.1.0'):
             with self.assertRaises(RuntimeError):
-                glymur.set_option('lib.num_threads', 4)
+                glymur.set_option('lib.num_threads', 2)
 
-    @unittest.skipIf(os.cpu_count() < 4, "makes no sense if 4 cores not there")
     @patch('glymur.lib.openjp2.has_thread_support')
     def test_thread_support_not_compiled_into_library(self, mock_ts):
         """
@@ -74,9 +76,8 @@ class TestSuite(fixtures.TestCommon):
         mock_ts.return_value = False
         with patch('glymur.jp2k.version.openjpeg_version', new='2.2.0'):
             with self.assertRaises(RuntimeError):
-                glymur.set_option('lib.num_threads', 4)
+                glymur.set_option('lib.num_threads', 2)
 
-    @unittest.skipIf(os.cpu_count() < 2, "makes no sense if 2 cores not there")
     def test_threads_write_support(self):
         """
         SCENARIO:  Attempt to encode with threading support.  This feature is
@@ -96,3 +97,24 @@ class TestSuite(fixtures.TestCommon):
                 self.assertEqual(len(w), 0)
             else:
                 self.assertEqual(len(w), 1)
+
+    def test_tiff2jp2_num_threads(self):
+        """
+        Scenario:  The --num-threads option is given on the command line.
+
+        Expected Result.  No errors.  If openjpeg is earlier than 2.5.0, there
+        will be a warning.
+        """
+        path = ir.files('tests.data').joinpath('basn6a08.tif')
+
+        sys.argv = [
+            '', str(path), str(self.temp_jp2_filename), '--num-threads', '2',
+        ]
+        with warnings.catch_warnings(record=True) as w:
+            command_line.tiff2jp2()
+            if glymur.version.openjpeg_version < '2.4.0':
+                self.assertEqual(len(w), 1)
+
+        Jp2k(self.temp_jp2_filename)
+
+        self.assertTrue(True)


=====================================
tests/test_tiff2jp2.py
=====================================
@@ -1620,23 +1620,6 @@ class TestSuite(fixtures.TestCommon):
         # the exif UUID box does not have the profile
         self.assertNotIn('ICCProfile', j.box[-1].data)
 
-    def test_num_threads(self):
-        """
-        Scenario:  The --num-threads option is given on the command line.
-
-        Expected Result.  No errors.
-        """
-        path = ir.files('tests.data').joinpath('basn6a08.tif')
-
-        sys.argv = [
-            '', str(path), str(self.temp_jp2_filename), '--num-threads', '2',
-        ]
-        command_line.tiff2jp2()
-
-        Jp2k(self.temp_jp2_filename)
-
-        self.assertTrue(True)
-
     def test_not_a_tiff(self):
         """
         Scenario:  The input "TIFF" is not actually a TIFF.  This used to



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

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/glymur/-/commit/c9ede96a5a13ee74fdf8e7636d8431c5ad75c765
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/20231202/0af3e05d/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list