diff --git a/.gitignore b/.gitignore deleted file mode 100644 index c9b568f..0000000 --- a/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.pyc -*.swp diff --git a/.travis.yml b/.travis.yml index 406e470..5223dcf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,6 @@ language: python python: + - "2.6" - "2.7" - "3.3" - "3.4" @@ -12,13 +13,17 @@ before_install: # command to install dependencies install: - - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install lxml contextlib2 mock six; fi - - if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then pip install lxml numpy six; fi - - if [[ $TRAVIS_PYTHON_VERSION == '3.4' ]]; then pip install lxml numpy six; fi + - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install --use-mirrors contextlib2 mock ordereddict unittest2; fi + - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --use-mirrors contextlib2 mock; fi + - if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then pip install --use-mirrors numpy; fi + - if [[ $TRAVIS_PYTHON_VERSION == '3.4' ]]; then pip install --use-mirrors numpy; fi # command to run tests script: - - python -m unittest discover + - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then unit2 discover; fi + - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then python -m unittest discover; fi + - if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then python -m unittest discover; fi + - if [[ $TRAVIS_PYTHON_VERSION == '3.4' ]]; then python -m unittest discover; fi notifications: email: "john.g.evans.ne@gmail.com" diff --git a/CHANGES.txt b/CHANGES.txt index 042e351..82438ca 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,26 +1,7 @@ -Jan 10, 2015 - v0.8.0 Reduced number of steps required for writing - images. Deprecated old read and write methods in favor of - array-style slicing. Added ignore_pclr_cmap_cdef, verbose, - shape, codestream, layer properties. +May 18, 2014 - v0.5.12 Restored _v3 functions removed in 0.5.11 -Oct 06, 2014 - v0.7.2 Added ellipsis support in array-style slicing. - -Oct 02, 2014 - v0.7.1 Fixed README to mention Python 3.4 - -Oct 01, 2014 - v0.7.0 Added array-style slicing. - -August 03, 2014 - v0.6.0 Added Cinema2K, Cinema4K write support. - Changed constructor for ChannelDefinition box. Removed support - for Python 2.6. Added write support for JP2 UUID, DataEntryURL, - Palette and Component Mapping boxes, JPX Association, NumberList - and DataReference boxes. Added read support for JPX free, - number list, data reference, fragment table, and fragment list - boxes. Improved JPX Reader Requirements box support. Added - get_printoptions, set_printoptions functions. Palette box now - a 2D numpy array instead of a list of 1D arrays. JP2 super box - constructors now take optional box list argument. Fixed bug - where JPX files with more than one codestream but advertising - jp2 compatibility were not being read. +May 09, 2014 - v0.5.11 Added support for Python 3.4, OpenJPEG 2.0.1, and + OpenJPEG 2.1.0. Jan 28, 2014 - v0.5.10 Fixed bad warning when reader requirements box mask length is unsupported. diff --git a/README.md b/README.md index bdc84d4..457e456 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,6 @@ glymur: a Python interface for JPEG 2000 **glymur** contains a Python interface to the OpenJPEG library which allows one to read and write JPEG 2000 files. **glymur** works on -Python 2.7, 3.3, and 3.4. +Python 2.6, 2.7, 3.3, and 3.4. Please read the docs, https://glymur.readthedocs.org/en/latest/ diff --git a/bin/jp2dump b/bin/jp2dump new file mode 100755 index 0000000..cb05c44 --- /dev/null +++ b/bin/jp2dump @@ -0,0 +1,15 @@ +#!/usr/bin/env python + +import argparse +import sys +import glymur + +description='Print JPEG2000 metadata.' +parser = argparse.ArgumentParser(description=description) +parser.add_argument('-c', '--codestream', help='dump codestream', + action='store_true') +parser.add_argument('filename') +args = parser.parse_args() + +filename = args.filename +glymur.jp2dump(args.filename, codestream=args.codestream) diff --git a/docs/source/api.rst b/docs/source/api.rst new file mode 100644 index 0000000..7584718 --- /dev/null +++ b/docs/source/api.rst @@ -0,0 +1,116 @@ +--- +API +--- + +Jp2k +---- +.. autoclass:: glymur.Jp2k + :members: read, write, wrap, read_bands, get_codestream + +Individual Boxes +---------------- +Jp2kbox +''''''' +.. autoclass:: glymur.jp2box.Jp2kBox + :members: + +AssociationBox +'''''''''''''' +.. autoclass:: glymur.jp2box.AssociationBox + :members: + +ColourSpecificationBox +'''''''''''''''''''''' +.. autoclass:: glymur.jp2box.ColourSpecificationBox + :members: + +ChannelDefinitionBox +'''''''''''''''''''''' +.. autoclass:: glymur.jp2box.ChannelDefinitionBox + :members: + +ComponentMappingBox +''''''''''''''''''' +.. autoclass:: glymur.jp2box.ComponentMappingBox + :members: + +ContiguousCodestreamBox +''''''''''''''''''''''' +.. autoclass:: glymur.jp2box.ContiguousCodestreamBox + :members: + +DataEntryURLBox +''''''''''''''' +.. autoclass:: glymur.jp2box.DataEntryURLBox + :members: + +FileTypeBox +''''''''''' +.. autoclass:: glymur.jp2box.FileTypeBox + :members: + +ImageHeaderBox +'''''''''''''' +.. autoclass:: glymur.jp2box.ImageHeaderBox + :members: + +JP2HeaderBox +'''''''''''' +.. autoclass:: glymur.jp2box.JP2HeaderBox + :members: + +JPEG2000SignatureBox +'''''''''''''''''''' +.. autoclass:: glymur.jp2box.JPEG2000SignatureBox + :members: + +LabelBox +'''''''' +.. autoclass:: glymur.jp2box.LabelBox + :members: + +PaletteBox +'''''''''' +.. autoclass:: glymur.jp2box.PaletteBox + :members: + +ReaderRequirementsBox +''''''''''''''''''''' +.. autoclass:: glymur.jp2box.ReaderRequirementsBox + :members: + +ResolutionBox +''''''''''''' +.. autoclass:: glymur.jp2box.ResolutionBox + :members: + +CaptureResolutionBox +'''''''''''''''''''' +.. autoclass:: glymur.jp2box.CaptureResolutionBox + :members: + +DisplayResolutionBox +'''''''''''''''''''' +.. autoclass:: glymur.jp2box.DisplayResolutionBox + :members: + +UUIDBox +''''''' +.. autoclass:: glymur.jp2box.UUIDBox + :members: + +UUIDInfoBox +''''''''''' +.. autoclass:: glymur.jp2box.UUIDInfoBox + :members: + +UUIDListBox +''''''''''' +.. autoclass:: glymur.jp2box.UUIDListBox + :members: + +XMLBox +'''''' +.. autoclass:: glymur.jp2box.XMLBox + :members: + diff --git a/docs/source/conf.py b/docs/source/conf.py index 76f96f6..53d1c0b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -13,6 +13,7 @@ # serve to show the default. import sys +import os class Mock(object): @@ -41,12 +42,12 @@ for mod_name in MOCK_MODULES: # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -# sys.path.insert(0, os.path.abspath('.')) +#sys.path.insert(0, os.path.abspath('.')) # -- General configuration ---------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. -# needs_sphinx = '1.0' +#needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. @@ -61,7 +62,7 @@ templates_path = ['_templates'] source_suffix = '.rst' # The encoding of source files. -# source_encoding = 'utf-8-sig' +#source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' @@ -75,19 +76,19 @@ copyright = u'2013, John Evans' # built documents. # # The short X.Y version. -version = '0.8' +version = '0.5' # The full version, including alpha/beta/rc tags. -release = '0.8.0' +release = '0.5.12' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -# language = None +#language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -# today = '' +#today = '' # Else, today_fmt is used as the format for a strftime call. -# today_fmt = '%B %d, %Y' +#today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -95,24 +96,24 @@ exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. -# default_role = None +#default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. -# add_function_parentheses = True +#add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -# add_module_names = True +#add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -# show_authors = False +#show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. -# modindex_common_prefix = [] +#modindex_common_prefix = [] # -- Options for HTML output -------------------------------------------------- @@ -124,26 +125,26 @@ html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -# html_theme_options = {} +#html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -# html_theme_path = [] +#html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -# html_title = None +#html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -# html_short_title = None +#html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -# html_logo = None +#html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -# html_favicon = None +#html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, @@ -152,44 +153,44 @@ html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -# html_last_updated_fmt = '%b %d, %Y' +#html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -# html_use_smartypants = True +#html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -# html_sidebars = {} +#html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -# html_additional_pages = {} +#html_additional_pages = {} # If false, no module index is generated. -# html_domain_indices = True +#html_domain_indices = True # If false, no index is generated. -# html_use_index = True +#html_use_index = True # If true, the index is split into individual pages for each letter. -# html_split_index = False +#html_split_index = False # If true, links to the reST sources are added to the pages. html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -# html_show_sphinx = True +#html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -# html_show_copyright = True +#html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -# html_use_opensearch = '' +#html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -# html_file_suffix = None +#html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'glymurdoc' @@ -198,13 +199,13 @@ htmlhelp_basename = 'glymurdoc' # -- Options for LaTeX output ------------------------------------------------- # The paper size ('letterpaper' or 'a4paper'). -# 'papersize': 'letterpaper', +#'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). -# 'pointsize': '10pt', +#'pointsize': '10pt', # Additional stuff for the LaTeX preamble. -# 'preamble': '', +#'preamble': '', latex_elements = {} # Grouping the document tree into LaTeX files. List of tuples @@ -215,23 +216,23 @@ latex_documents = [('index', 'glymur.tex', u'glymur Documentation', # The name of an image file (relative to this directory) to place at the top of # the title page. -# latex_logo = None +#latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -# latex_use_parts = False +#latex_use_parts = False # If true, show page references after internal links. -# latex_show_pagerefs = False +#latex_show_pagerefs = False # If true, show URL addresses after external links. -# latex_show_urls = False +#latex_show_urls = False # Documents to append as an appendix to all manuals. -# latex_appendices = [] +#latex_appendices = [] # If false, no module index is generated. -# latex_domain_indices = True +#latex_domain_indices = True # -- Options for manual page output ------------------------------------------- @@ -244,7 +245,7 @@ man_pages = [ ] # If true, show URL addresses after external links. -# man_show_urls = False +#man_show_urls = False # -- Options for Texinfo output ----------------------------------------------- @@ -257,13 +258,13 @@ texinfo_documents = [('index', 'glymur', u'glymur Documentation', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. -# texinfo_appendices = [] +#texinfo_appendices = [] # If false, no module index is generated. -# texinfo_domain_indices = True +#texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -# texinfo_show_urls = 'footnote' +#texinfo_show_urls = 'footnote' # Example configuration for intersphinx: refer to the Python standard library. diff --git a/docs/source/detailed_installation.rst b/docs/source/detailed_installation.rst index 7068b56..cf855df 100644 --- a/docs/source/detailed_installation.rst +++ b/docs/source/detailed_installation.rst @@ -7,27 +7,26 @@ Glymur Configuration '''''''''''''''''''''' The default glymur installation process relies upon OpenJPEG being -properly installed on your system as a shared library. If you have -OpenJPEG installed through your system’s package manager on linux -or if you use MacPorts on the mac, you are probably already set to -go. But if you have OpenJPEG installed into a non-standard place -or if you use windows, then read on. +properly installed on your system as a shared library. If you have OpenJPEG +installed through your system's package manager on linux or if you use MacPorts +on the mac, you are probably already set to go. But if you have OpenJPEG +installed into a non-standard place or if you use windows, then read on. Glymur uses ctypes to access the openjp2/openjpeg libraries, and because ctypes accesses libraries in a platform-dependent manner, -it is recommended that **if** you compile and install OpenJPEG into a -non-standard location, you should then create a configuration file -to help Glymur properly find the openjpeg or openjp2 libraries -(linux users or macports users don’t need to bother with this if -you are using OpenJPEG as provided by your package manager). The -configuration format is the same as used by Python’s configparser -module, i.e. :: +it is recommended that if you compile and install OpenJPEG into a +non-standard location, you should then create a configuration file to +help Glymur properly find the openjpeg or openjp2 libraries (linux +users or macports users don't need to bother with this if you are +using OpenJPEG as provided by your package manager). The configuration +format is the same as used by Python's configparser module, +i.e. :: [library] - openjp2: /somewhere/lib/libopenjp2.so + openjp2: /opt/openjp2-svn/lib/libopenjp2.so This assumes, of course, that you've installed OpenJPEG into -/opt/openjpeg on a linux system. The location of the configuration file +/opt/openjp2-svn on a linux system. The location of the configuration file can vary as well. If you use either linux or mac, the path to the configuration file would normally be :: @@ -38,25 +37,22 @@ the path will be :: $XDG_CONFIG_HOME/glymur/glymurrc -On windows, the path to the configuration file can be determined by starting -up Python and typing :: +On windows, the path to the configuration file can be determined +by starting up Python and typing :: import os - os.path.join(os.path.expanduser('~', 'glymur', 'glymurrc') + os.path.join(os.path.expanduser('~'), 'glymur', 'glymurrc') + You may also include a line for the version 1.x openjpeg library if you have it installed in a non-standard place, i.e. :: [library] - openjpeg: /somewhere/lib/libopenjpeg.so - -Once again, you should not have to bother with a configuration file if you use -mac or linux and OpenJPEG is provided by your package manager. + openjpeg: /not/the/usual/location/lib/libopenjpeg.so ''''''' Testing ''''''' - It is not necessary, but you may wish to download OpenJPEG's test data for the purpose of configuring and running OpenJPEG's test suite. Check their instructions on how to do that. You can then diff --git a/docs/source/how_do_i.rst b/docs/source/how_do_i.rst index 88d94fa..db87774 100644 --- a/docs/source/how_do_i.rst +++ b/docs/source/how_do_i.rst @@ -3,220 +3,36 @@ How do I...? ------------ -... read images? -================ -Jp2k implements slicing via the :py:meth:`__getitem__` method, meaning that -multiple resolution imagery in a JPEG 2000 file can -easily be accessed via array-style slicing. For example here's how to -retrieve a full resolution and first lower-resolution image :: +... read the lowest resolution thumbnail? +========================================= +Printing the Jp2k object should reveal the number of resolutions (look in the +COD segment section), but you can take a shortcut by supplying -1 as the +resolution level. :: >>> import glymur - >>> jp2file = glymur.data.nemo() # just a path to a JPEG2000 file - >>> jp2 = glymur.Jp2k(jp2file) - >>> fullres = jp2[:] - >>> fullres.shape - (1456, 2592, 3) - >>> thumbnail = jp2[::2, ::2] - >>> thumbnail.shape - (728, 1296, 3) - -... write images? -================= -It's pretty simple, just supply the image data as the 2nd argument to the Jp2k -constructor. - - >>> import glymur, numpy as np - >>> jp2 = glymur.Jp2k('zeros.jp2', data=np.zeros((640, 480), dtype=np.uint8) - -You must have OpenJPEG version 1.5 or more recent in order to write JPEG 2000 -images with glymur. + >>> file = glymur.data.nemo() + >>> j = glymur.Jp2k(file) + >>> thumbnail = j.read(rlevel=-1) ... display metadata? ===================== -There are two ways. From the command line, the console script **jp2dump** is +There are two ways. From the unix command line, the script *jp2dump* is available. :: $ jp2dump /path/to/glymur/installation/data/nemo.jp2 -From within Python, the same result is obtained simply by printing the Jp2k -object, i.e. :: +From within Python, it is as simple as printing the Jp2k object, i.e. :: - >>> import glymur - >>> jp2file = glymur.data.nemo() # just a path to a JP2 file - >>> jp2 = glymur.Jp2k(jp2file) - >>> print(jp2) - File: nemo.jp2 - JPEG 2000 Signature Box (jP ) @ (0, 12) - Signature: 0d0a870a - File Type Box (ftyp) @ (12, 20) - Brand: jp2 - Compatibility: ['jp2 '] - JP2 Header Box (jp2h) @ (32, 45) - Image Header Box (ihdr) @ (40, 22) - Size: [1456 2592 3] - Bitdepth: 8 - Signed: False - Compression: wavelet - Colorspace Unknown: False - Colour Specification Box (colr) @ (62, 15) - Method: enumerated colorspace - Precedence: 0 - Colorspace: sRGB - UUID Box (uuid) @ (77, 3146) - UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) - UUID Data: - - - - Google - 2013-02-09T14:47:53 - - - 1 - 72/1 - 72/1 - 2 - HTC - HTC Glacier - 2592 - 1456 - - - 8 - 8 - 8 - - - 2 - 3 - - - 1343036288/4294967295 - 1413044224/4294967295 - - - - - 2748779008/4294967295 - 1417339264/4294967295 - 1288490240/4294967295 - 2576980480/4294967295 - 644245120/4294967295 - 257698032/4294967295 - - - - - 1 - 2528 - 1424 - 353/100 - 0 - 0/1 - WGS-84 - 2013-02-09T14:47:53 - - - 76 - - - 0220 - 0100 - - - 1 - 2 - 3 - 0 - - - 42,20.56N - 71,5.29W - 2013-02-09T19:47:53Z - NETWORK - - - 2013-02-09T14:47:53 - - - - - Glymur - Python XMP Toolkit - - - - - - Contiguous Codestream Box (jp2c) @ (3223, 1132296) - Main header: - SOC marker segment @ (3231, 0) - SIZ marker segment @ (3233, 47) - Profile: 2 - 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 resolutions: 2 - Code block height, width: (64 x 64) - Wavelet transform: 5-3 reversible - Precinct size: default, 2^15 x 2^15 - 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" - -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 -making use of the :py:meth:`set_printoptions` function:: + >>> from glymur import Jp2k + >>> file = glymur.data.nemo() + >>> j = Jp2k(file) + >>> print(j) - >>> glymur.set_printoptions(codestream=False, xml=False) - >>> print(jp2) - File: nemo.jp2 - JPEG 2000 Signature Box (jP ) @ (0, 12) - Signature: 0d0a870a - File Type Box (ftyp) @ (12, 20) - Brand: jp2 - Compatibility: ['jp2 '] - JP2 Header Box (jp2h) @ (32, 45) - Image Header Box (ihdr) @ (40, 22) - Size: [1456 2592 3] - Bitdepth: 8 - Signed: False - Compression: wavelet - Colorspace Unknown: False - Colour Specification Box (colr) @ (62, 15) - Method: enumerated colorspace - Precedence: 0 - Colorspace: sRGB - UUID Box (uuid) @ (77, 3146) - UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) - Contiguous Codestream Box (jp2c) @ (3223, 1132296) +This prints the metadata found in the JP2 boxes, but in the case of the +codestream box, only the main header is printed. It is possible to print +**only** the codestream information as well, i.e. :: -It is possible to easily print the codestream header details as well, i.e. :: - - >>> print(j.codestream) # details not show + >>> print(j.get_codestream()) ... add XML metadata? ===================== @@ -239,11 +55,12 @@ Consider the following XML file `data.xml` : :: -The :py:meth:`append` method can add an XML box as shown below:: +The **append** method can add an XML box as shown below:: >>> import shutil >>> import glymur >>> shutil.copyfile(glymur.data.nemo(), 'myfile.jp2') + >>> from xml.etree import cElementTree as ET >>> jp2 = glymur.Jp2k('myfile.jp2') >>> xmlbox = glymur.jp2box.XMLBox(filename='data.xml') >>> jp2.append(xmlbox) @@ -251,15 +68,15 @@ The :py:meth:`append` method can add an XML box as shown below:: ... 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 -codestream provided by `goodstuff.j2k` (a file consisting of a raw codestream), -you can use the :py:meth:`wrap` method with no box argument: :: +An existing raw codestream or JP2 file can be wrapped (re-wrapped in the case +of JP2) in a user-defined set of JP2 boxes. To get just a minimal +JP2 jacket on the codestream provided by `goodstuff.j2k` (a file +consisting of just a raw codestream), you can use the **wrap** method +with no box argument: :: >>> import glymur - >>> glymur.set_printoptions(codestream=False) - >>> jp2file = glymur.data.goodstuff() - >>> j2k = glymur.Jp2k(jp2file) + >>> jfile = glymur.data.goodstuff() + >>> j2k = glymur.Jp2k(jfile) >>> jp2 = j2k.wrap("newfile.jp2") >>> print(jp2) File: newfile.jp2 @@ -280,17 +97,22 @@ you can use the :py:meth:`wrap` method with no box argument: :: Precedence: 0 Colorspace: sRGB Contiguous Codestream Box (jp2c) @ (77, 115228) + Main header: + . + . (truncated) + . The raw codestream was wrapped in a JP2 jacket with four boxes in the outer layer (the signature, file type, JP2 header, and contiguous codestream), with two additional boxes (image header and color specification) contained in the JP2 header superbox. -XML boxes are not in the minimal set of box requirements for the JP2 format, so -in order to add an XML box into the mix before the codestream box, we'll need to -re-specify all of the boxes. If you already have a JP2 jacket in place, -you can just reuse that, though. Take the following example content in -an XML file `favorites.xml` : :: +XML boxes are not in the minimal set of box requirements for the +JP2 format, so in order to add an XML box into the mix before the +codestream box, we'll need to re-specify all of the boxes. If you +already have a JP2 jacket in place, you can just reuse that, though. +Take the following example content in an XML file `favorites.xml` +: :: @@ -326,20 +148,25 @@ the following will work. :: Light Ale + Contiguous Codestream Box (jp2c) @ (153, 115236) + Main header: + . + . (truncated) + . -As to the question of which method you should use, :py:meth:`append` or -:py:meth:`wrap`, to add metadata, you should keep in mind that :py:meth:`wrap` -produces a new JP2 file, while :py:meth:`append` modifies an existing file and -is currently limited to XML and UUID boxes. +As to the question of which method you should use, **append** or **wrap**, +to add metadata, you should keep in mind that **wrap** produces a new JP2 file, +while **append** modifies an existing file and is currently limited to XML +boxes. ... 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 rewrap such an image in a -set of boxes that includes a channel definition box. +OpenJPEG can create JP2 files with more than 3 components (requires version +2.1), but by default any extra components are not described as such by the JP2 +boxes created by OpenJPEG. In order to do so, we need to rewrap such +an image in a set of boxes that includes a channel definition box. This example is based on SciPy example code found at http://scipy-lectures.github.io/advanced/image_processing/#basic-manipulations . @@ -349,14 +176,15 @@ image isn't square. :: >>> import numpy as np >>> import glymur >>> from glymur import Jp2k - >>> rgb = Jp2k(glymur.data.goodstuff())[:] + >>> rgb = Jp2k(glymur.data.goodstuff()).read() >>> lx, ly = rgb.shape[0:2] >>> X, Y = np.ogrid[0:lx, 0:ly] >>> mask = ly**2*(X - lx / 2) ** 2 + lx**2*(Y - ly / 2) ** 2 > (lx * ly / 2)**2 >>> alpha = 255 * np.ones((lx, ly, 1), dtype=np.uint8) >>> alpha[mask] = 0 >>> rgba = np.concatenate((rgb, alpha), axis=2) - >>> jp2 = Jp2k('tmp.jp2', data=rgba) + >>> jp2 = Jp2k('tmp.jp2', 'wb') + >>> jp2.write(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 @@ -373,7 +201,7 @@ 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) + >>> cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=ctype, association=asoc) >>> print(cdef) Channel Definition Box (cdef) @ (0, 0) Channel 0 (color) ==> (1) @@ -396,211 +224,30 @@ Here's how the Preview application on the mac shows the RGBA image. ... work with XMP UUIDs? ======================== -`Wikipedia `_ states -that "The Extensible Metadata Platform (XMP) is an ISO standard, -originally created by Adobe Systems Inc., for the creation, processing -and interchange of standardized and custom metadata for all kinds -of resources." - The example JP2 file shipped with glymur has an XMP UUID. :: >>> import glymur >>> j = glymur.Jp2k(glymur.data.nemo()) - >>> print(j.box[3]) # formatting added to the XML below - + >>> print(j.box[4]) + UUID Box (uuid) @ (715, 2412) + UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) + UUID Data: + - - Google - 2013-02-09T14:47:53 - + + + - . - . - . - +Since the UUID data in this case is returned as an ElementTree instance, one can +use ElementTree to access the data. For example, to extract the +**CreatorTool** attribute value, the following would work:: -Since the UUID data in this case is returned as an lxml ElementTree -instance, one can use lxml to access the data. For example, to -extract the **CreatorTool** attribute value, one could do the -following - - >>> xmp = j.box[3].data - >>> rdf = '{http://www.w3.org/1999/02/22-rdf-syntax-ns#}' - >>> ns2 = '{http://ns.adobe.com/xap/1.0/}' - >>> name = '{0}RDF/{0}Description/{1}CreatorTool'.format(rdf, ns2) + >>> xmp = j.box[4].data + >>> ns0 = '{http://www.w3.org/1999/02/22-rdf-syntax-ns#}' + >>> ns1 = '{http://ns.adobe.com/xap/1.0/}' + >>> name = '{0}RDF/{0}Description'.format(ns0) >>> elt = xmp.find(name) >>> elt - - >>> elt.text - 'Google' - -But that would be painful. A better solution is to install the Python XMP -Toolkit (make sure it is at least version 2.0):: - - >>> from libxmp import XMPMeta - >>> from libxmp.consts import XMP_NS_XMP as NS_XAP - >>> meta = XMPMeta() - >>> meta.parse_from_str(j.box[3].raw_data.decode('utf-8')) - >>> meta.get_property(NS_XAP, 'CreatorTool') - 'Google' - -Where the Python XMP Toolkit can really shine, though, is when you are -converting an image from another format such as TIFF or JPEG into JPEG 2000. -For example, if you were to be converting the TIFF image found at -http://photojournal.jpl.nasa.gov/tiff/PIA17145.tif info JPEG 2000:: - - >>> import skimage.io - >>> image = skimage.io.imread('PIA17145.tif') - >>> from glymur import Jp2k - >>> jp2 = Jp2k('PIA17145.jp2', data=image) - -Next you can extract the XMP metadata. - - >>> from libxmp import XMPFiles - >>> xf = XMPFiles() - >>> xf.open_file('PIA17145.tif') - >>> xmp = xf.get_xmp() - >>> print(xmp) - - - - - 1016 - 1016 - - - 8 - - - 1 - 1 - 1 - 1 - 2 - - - - - converted PNM file - - - - - - - -If you are familiar with TIFF, you can verify that there's no XMP tag in the -TIFF file, but the Python XMP Toolkit takes advantage of the TIFF header -structure to populate an XMP packet for you. If you were working with a JPEG -file with Exif metadata, that information would be included in the XMP packet -as well. Now you can append the XMP packet in a UUIDBox. In order to do this, -though, you have to know the UUID that signifies XMP data.:: - - >>> import uuid - >>> xmp_uuid = uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac') - >>> box = glymur.jp2box.UUIDBox(xmp_uuid, str(xmp).encode()) - >>> jp2.append(box) - >>> print(jp2.box[-1]) - UUID Box (uuid) @ (592316, 1053) - UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) - UUID Data: - - - - 1016 - 1016 - - - 8 - - - 1 - 1 - 1 - 1 - 2 - - - - - converted PNM file - - - - - - -You can also build up XMP metadata from scratch. For instance, if we try to -wrap `goodstuff.j2k` again:: - - >>> import glymur - >>> j2kfile = glymur.data.goodstuff() - >>> j2k = glymur.Jp2k(j2kfile) - >>> jp2 = j2k.wrap("goodstuff.jp2") - -Now build up the metadata piece-by-piece. It would help to have the XMP -standard close at hand:: - - >>> from libxmp import XMPMeta - >>> from libxmp.consts import XMP_NS_TIFF as NS_TIFF - >>> from libxmp.consts import XMP_NS_DC as NS_DC - >>> xmp = XMPMeta() - >>> ihdr = jp2.box[2].box[0] - >>> xmp.set_property(NS_TIFF, "ImageWidth", str(ihdr.width)) - >>> xmp.set_property(NS_TIFF, "ImageHeight", str(ihdr.height)) - >>> xmp.set_property(NS_TIFF, "BitsPerSample", '3') - >>> xmp.set_property(NS_DC, "Title", u'Stürm und Drang') - >>> xmp.set_property(NS_DC, "Creator", 'Glymur') - -We can then append the XMP in a UUID box just as before:: - - >>> import uuid - >>> xmp_uuid = uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac') - >>> box = glymur.jp2box.UUIDBox(xmp_uuid, str(xmp).encode()) - >>> jp2.append(box) - >>> glymur.set_printoptions(codestream=False) - >>> print(jp2) - File: goodstuff.jp2 - JPEG 2000 Signature Box (jP ) @ (0, 12) - Signature: 0d0a870a - File Type Box (ftyp) @ (12, 20) - Brand: jp2 - Compatibility: ['jp2 '] - JP2 Header Box (jp2h) @ (32, 45) - Image Header Box (ihdr) @ (40, 22) - Size: [800 480 3] - Bitdepth: 8 - Signed: False - Compression: wavelet - Colorspace Unknown: False - Colour Specification Box (colr) @ (62, 15) - Method: enumerated colorspace - Precedence: 0 - Colorspace: sRGB - Contiguous Codestream Box (jp2c) @ (77, 115228) - UUID Box (uuid) @ (115305, 671) - UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) - UUID Data: - - - - 480 - 800 - 3 - - - Stürm und Drang - Glymur - - - - + + >>> elt.attrib['{0}CreatorTool'.format(ns1)] + 'glymur' diff --git a/docs/source/index.rst b/docs/source/index.rst index 62e118d..3c556d7 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -15,8 +15,10 @@ Contents: introduction detailed_installation how_do_i - whatsnew/index + api roadmap + whatsnew/index + ------------------ Indices and tables diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst index ae6d6ab..382f3c3 100644 --- a/docs/source/introduction.rst +++ b/docs/source/introduction.rst @@ -5,31 +5,44 @@ Glymur: a Python interface for JPEG 2000 **Glymur** is an interface to the OpenJPEG library which allows one to read and write JPEG 2000 files from Python. Glymur supports both reading and writing of JPEG 2000 images, but writing -JPEG 2000 images is currently limited to images that can fit in memory. -**Glymur** can read images using OpenJPEG library versions as far back as 1.3, -but it is strongly recommended to use version 2.1.0, which is the most recently -released version of OpenJPEG at this time. +JPEG 2000 images is currently limited to images that can fit in memory -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. +Of particular focus is retrieval of metadata. Reading Exif UUIDs is supported, +as is reading XMP UUIDs as the XMP data packet is just XML. There is +some very limited support for reading JPX metadata. For instance, +**asoc** and **labl** boxes are recognized, so GMLJP2 metadata can +be retrieved from such JPX files. -Glymur works on Python versions 2.7, 3.3 and 3.4. If you have Python 2.6, -you should use the 0.5 series of Glymur. +Glymur works on Python 2.6, 2.7, 3.3, and 3.4. -For more information about OpenJPEG, please consult http://www.openjpeg.org. +OpenJPEG Installation +===================== +Glymur will read JPEG 2000 images with versions 1.3, 1.4, 1.5, 2.0, and 2.1 of +OpenJPEG. Writing images is only supported with OpenJPEG 1.5 or better, however, +and version 2.1 is strongly recommended. For more information about OpenJPEG, +please consult http://www.openjpeg.org. + +If you use MacPorts or if you have a sufficiently recent version of +Linux, your package manager should already provide you with a version of +OpenJPEG 1.X which glymur can already use. If your platform is windows, +I suggest using the windows installers provided to you by the OpenJPEG +folks at https://code.google.com/p/openjpeg/downloads/list . Glymur Installation =================== You can retrieve the source for Glymur from either of * https://pypi.python.org/pypi/Glymur/ (stable releases) - * http://github.com/quintusdias/glymur (bleeding edge, use the devel branch) + * http://github.com/quintusdias/glymur (bleeding edge) but you should also be able to install Glymur via pip :: $ pip install glymur -In addition to the package, this also gives you a command line script -**jp2dump** that can be used from the command line line to print JPEG 2000 -metadata. +This will install the **jp2dump** script that can be used from the unix command +line, so you should adjust your **$PATH** +to take advantage of it. For example, if you install with pip's +`--user` option on linux :: + + $ export PATH=$HOME/.local/bin:$PATH + diff --git a/docs/source/roadmap.rst b/docs/source/roadmap.rst index f0d7017..1e336f1 100644 --- a/docs/source/roadmap.rst +++ b/docs/source/roadmap.rst @@ -1,11 +1,3 @@ ------------- -Known Issues ------------- - - * Creating a Jp2 file with the irreversible option does not work - on windows. - * Eval-ing a :py:meth:`repr` string does not work on windows. - ------- Roadmap ------- @@ -13,4 +5,8 @@ Roadmap Here's an incomplete list of what I'd like to focus on in the future. * continue to monitor upstream changes in the openjp2 library - * investigate JPIP (likely to be a big project) + * investigate using CFFI or cython instead of ctypes to wrap openjp2 + * investigate adding write support for UUID/XMP boxes (potentially a big project) + * investigate JPIP (likely to be an even bigger project) + +Support for Python 2.6 will be dropped with the 0.6.0 release. diff --git a/docs/source/whatsnew/0.6.rst b/docs/source/whatsnew/0.6.rst deleted file mode 100644 index 8fd49d0..0000000 --- a/docs/source/whatsnew/0.6.rst +++ /dev/null @@ -1,23 +0,0 @@ -===================== -Changes in glymur 0.6 -===================== - -Changes in 0.6.0 -================= - -* Added Cinema2K, Cinema4K write support. -* Added irreversible 9-7 transform write support. -* Added set_printoptions, get_printoptions functions. -* Added write support for JP2 UUID, data entry URL, palette, and component - mapping boxes. -* Added read/write support for JPX free, number list, and data reference boxes -* Added read support for JPX fragment list and fragment table boxes -* Incompatible change to channel definition box constructor, channel_type and - association are no longer keyword arguments -* Incompatible change to palette box constructor, it now takes a 2D numpy array - instead of a list of 1D arrays -* Dropped support for 1.3 and 1.4. -* Dropped support for Python 2.6. -* Dropped windows support. It might still work, I don't have access to a windows - box with which to test it. -* Added lxml as a package dependency, replacing ElementTree. diff --git a/docs/source/whatsnew/0.7.rst b/docs/source/whatsnew/0.7.rst deleted file mode 100644 index 3ed7715..0000000 --- a/docs/source/whatsnew/0.7.rst +++ /dev/null @@ -1,27 +0,0 @@ -===================== -Changes in glymur 0.7 -===================== - -Changes in 0.7.3 -================= - - * added read support back for metadata only when the OpenJPEG library is - not installed - -Changes in 0.7.2 -================= - - * added ellipsis support in array-style slicing - -Changes in 0.7.1 -================= - - * fixed release notes regarding Python 3.4 - -Changes in 0.7.0 -================= - - * implemented :py:meth:`__getitem__`, :py:meth:`__setitem__` support - * added back windows support - * box_id and longname are class attributes now instead of instance - attributes diff --git a/docs/source/whatsnew/0.8.rst b/docs/source/whatsnew/0.8.rst deleted file mode 100644 index e7215ad..0000000 --- a/docs/source/whatsnew/0.8.rst +++ /dev/null @@ -1,24 +0,0 @@ -===================== -Changes in glymur 0.8 -===================== - -Changes in 0.8.0 -================= - - * Simplified writing images by moving data and options into the - constructor. - * Deprecated :py:meth:`read` method in favor of array-style slicing. - In order to retain certain functionality, the following parameters - to the :py:meth:`read` method have become top-level properties - - * verbose - * layer - * ignore_pclr_cmap_cdef - - * Two additional properties were introduced. - - * codestream - * shape - - - diff --git a/docs/source/whatsnew/index.rst b/docs/source/whatsnew/index.rst index 4569abd..7a22fac 100644 --- a/docs/source/whatsnew/index.rst +++ b/docs/source/whatsnew/index.rst @@ -8,7 +8,4 @@ These document the changes between minor (or major) versions of glymur. .. toctree:: - 0.8 - 0.7 - 0.6 0.5 diff --git a/glymur/__init__.py b/glymur/__init__.py index d8971b2..bf2f625 100644 --- a/glymur/__init__.py +++ b/glymur/__init__.py @@ -1,25 +1,24 @@ """glymur - read, write, and interrogate JPEG 2000 files """ -import unittest +import sys from glymur import version __version__ = version.version from .jp2k import Jp2k -from .jp2box import (get_printoptions, - set_printoptions, - get_parseoptions, - set_parseoptions) +from .jp2dump import jp2dump from . import data +# unittest2 only in python-2.6 (pylint/python2.7 issue) +# pylint: disable=F0401 def runtests(): """Discover and run all tests for the glymur package. """ + if sys.hexversion <= 0x02070000: + import unittest2 as unittest + else: + import unittest suite = unittest.defaultTestLoader.discover(__path__[0]) unittest.TextTestRunner(verbosity=2).run(suite) - - -__all__ = [__version__, Jp2k, get_printoptions, set_printoptions, - get_parseoptions, set_parseoptions, data, runtests] diff --git a/glymur/_uuid_io.py b/glymur/_uuid_io.py deleted file mode 100644 index 3c63b0a..0000000 --- a/glymur/_uuid_io.py +++ /dev/null @@ -1,505 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Part of glymur. -""" -from collections import OrderedDict -import struct -import sys -import warnings - -import lxml.etree as ET - - -def xml(raw_data): - """ - XMP data to be parsed as XML. - """ - if sys.hexversion < 0x03000000: - elt = ET.fromstring(raw_data) - else: - text = raw_data.decode('utf-8') - elt = ET.fromstring(text) - - return ET.ElementTree(elt) - - -def tiff_header(read_buffer): - """ - Interpret the uuid raw data as a tiff header. - """ - # Ignore the first six bytes. - # Next 8 should be (73, 73, 42, 8) or (77, 77, 42, 8) - data = struct.unpack('' for little-endian. - num_tags : int - Number of tags in the IFD. - raw_ifd : dictionary - Maps tag number to "mildly-interpreted" tag value. - processed_ifd : dictionary - Maps tag name to "mildly-interpreted" tag value. - """ - datatype2fmt = {1: ('B', 1), - 2: ('B', 1), - 3: ('H', 2), - 4: ('I', 4), - 5: ('II', 8), - 7: ('B', 1), - 9: ('i', 4), - 10: ('ii', 8)} - - def __init__(self, endian, read_buffer, offset): - self.endian = endian - self.read_buffer = read_buffer - self.processed_ifd = OrderedDict() - - self.num_tags, = struct.unpack(endian + 'H', - read_buffer[offset:offset + 2]) - - fmt = self.endian + 'HHII' * self.num_tags - ifd_buffer = read_buffer[offset + 2:offset + 2 + self.num_tags * 12] - data = struct.unpack(fmt, ifd_buffer) - self.raw_ifd = OrderedDict() - for j, tag in enumerate(data[0::4]): - # The offset to the tag offset/payload is the offset to the IFD - # plus 2 bytes for the number of tags plus 12 bytes for each - # tag entry plus 8 bytes to the offset/payload itself. - toffp = read_buffer[offset + 10 + j * 12:offset + 10 + j * 12 + 4] - tag_data = self.parse_tag(data[j * 4 + 1], - data[j * 4 + 2], - toffp) - self.raw_ifd[tag] = tag_data - - def parse_tag(self, dtype, count, offset_buf): - """Interpret an Exif image tag data payload. - """ - try: - fmt = self.datatype2fmt[dtype][0] * count - payload_size = self.datatype2fmt[dtype][1] * count - except KeyError: - msg = 'Invalid TIFF tag datatype ({0}).'.format(dtype) - raise IOError(msg) - - if payload_size <= 4: - # Interpret the payload from the 4 bytes in the tag entry. - target_buffer = offset_buf[:payload_size] - else: - # Interpret the payload at the offset specified by the 4 bytes in - # the tag entry. - offset, = struct.unpack(self.endian + 'I', offset_buf) - target_buffer = self.read_buffer[offset:offset + payload_size] - - if dtype == 2: - # ASCII - if sys.hexversion < 0x03000000: - payload = target_buffer.rstrip('\x00') - else: - payload = target_buffer.decode('utf-8').rstrip('\x00') - - else: - payload = struct.unpack(self.endian + fmt, target_buffer) - if dtype == 5 or dtype == 10: - # Rational or Signed Rational. Construct the list of values. - rational_payload = [] - for j in range(count): - value = float(payload[j * 2]) / float(payload[j * 2 + 1]) - rational_payload.append(value) - payload = rational_payload - if count == 1: - # If just a single value, then return a scalar instead of a - # tuple. - payload = payload[0] - - return payload - - def post_process(self, tagnum2name): - """Map the tag name instead of tag number to the tag value. - """ - for tag, value in self.raw_ifd.items(): - try: - tag_name = tagnum2name[tag] - except KeyError: - # Ok, we don't recognize this tag. Just use the numeric id. - msg = 'Unrecognized Exif tag: {0}'.format(tag) - warnings.warn(msg, UserWarning) - tag_name = tag - self.processed_ifd[tag_name] = value - - -class _ExifImageIfd(_Ifd): - """ - Attributes - ---------- - tagnum2name : dict - Maps Exif image tag numbers to the tag names. - ifd : dict - Maps tag names to tag values. - """ - tagnum2name = {11: 'ProcessingSoftware', - 254: 'NewSubfileType', - 255: 'SubfileType', - 256: 'ImageWidth', - 257: 'ImageLength', - 258: 'BitsPerSample', - 259: 'Compression', - 262: 'PhotometricInterpretation', - 263: 'Threshholding', - 264: 'CellWidth', - 265: 'CellLength', - 266: 'FillOrder', - 269: 'DocumentName', - 270: 'ImageDescription', - 271: 'Make', - 272: 'Model', - 273: 'StripOffsets', - 274: 'Orientation', - 277: 'SamplesPerPixel', - 278: 'RowsPerStrip', - 279: 'StripByteCounts', - 282: 'XResolution', - 283: 'YResolution', - 284: 'PlanarConfiguration', - 290: 'GrayResponseUnit', - 291: 'GrayResponseCurve', - 292: 'T4Options', - 293: 'T6Options', - 296: 'ResolutionUnit', - 301: 'TransferFunction', - 305: 'Software', - 306: 'DateTime', - 315: 'Artist', - 316: 'HostComputer', - 317: 'Predictor', - 318: 'WhitePoint', - 319: 'PrimaryChromaticities', - 320: 'ColorMap', - 321: 'HalftoneHints', - 322: 'TileWidth', - 323: 'TileLength', - 324: 'TileOffsets', - 325: 'TileByteCounts', - 330: 'SubIFDs', - 332: 'InkSet', - 333: 'InkNames', - 334: 'NumberOfInks', - 336: 'DotRange', - 337: 'TargetPrinter', - 338: 'ExtraSamples', - 339: 'SampleFormat', - 340: 'SMinSampleValue', - 341: 'SMaxSampleValue', - 342: 'TransferRange', - 343: 'ClipPath', - 344: 'XClipPathUnits', - 345: 'YClipPathUnits', - 346: 'Indexed', - 347: 'JPEGTables', - 351: 'OPIProxy', - 512: 'JPEGProc', - 513: 'JPEGInterchangeFormat', - 514: 'JPEGInterchangeFormatLength', - 515: 'JPEGRestartInterval', - 517: 'JPEGLosslessPredictors', - 518: 'JPEGPointTransforms', - 519: 'JPEGQTables', - 520: 'JPEGDCTables', - 521: 'JPEGACTables', - 529: 'YCbCrCoefficients', - 530: 'YCbCrSubSampling', - 531: 'YCbCrPositioning', - 532: 'ReferenceBlackWhite', - 700: 'XMLPacket', - 18246: 'Rating', - 18249: 'RatingPercent', - 32781: 'ImageID', - 33421: 'CFARepeatPatternDim', - 33422: 'CFAPattern', - 33423: 'BatteryLevel', - 33432: 'Copyright', - 33434: 'ExposureTime', - 33437: 'FNumber', - 33723: 'IPTCNAA', - 34377: 'ImageResources', - 34665: 'ExifTag', - 34675: 'InterColorProfile', - 34850: 'ExposureProgram', - 34852: 'SpectralSensitivity', - 34853: 'GPSTag', - 34855: 'ISOSpeedRatings', - 34856: 'OECF', - 34857: 'Interlace', - 34858: 'TimeZoneOffset', - 34859: 'SelfTimerMode', - 36867: 'DateTimeOriginal', - 37122: 'CompressedBitsPerPixel', - 37377: 'ShutterSpeedValue', - 37378: 'ApertureValue', - 37379: 'BrightnessValue', - 37380: 'ExposureBiasValue', - 37381: 'MaxApertureValue', - 37382: 'SubjectDistance', - 37383: 'MeteringMode', - 37384: 'LightSource', - 37385: 'Flash', - 37386: 'FocalLength', - 37387: 'FlashEnergy', - 37388: 'SpatialFrequencyResponse', - 37389: 'Noise', - 37390: 'FocalPlaneXResolution', - 37391: 'FocalPlaneYResolution', - 37392: 'FocalPlaneResolutionUnit', - 37393: 'ImageNumber', - 37394: 'SecurityClassification', - 37395: 'ImageHistory', - 37396: 'SubjectLocation', - 37397: 'ExposureIndex', - 37398: 'TIFFEPStandardID', - 37399: 'SensingMethod', - 40091: 'XPTitle', - 40092: 'XPComment', - 40093: 'XPAuthor', - 40094: 'XPKeywords', - 40095: 'XPSubject', - 50341: 'PrintImageMatching', - 50706: 'DNGVersion', - 50707: 'DNGBackwardVersion', - 50708: 'UniqueCameraModel', - 50709: 'LocalizedCameraModel', - 50710: 'CFAPlaneColor', - 50711: 'CFALayout', - 50712: 'LinearizationTable', - 50713: 'BlackLevelRepeatDim', - 50714: 'BlackLevel', - 50715: 'BlackLevelDeltaH', - 50716: 'BlackLevelDeltaV', - 50717: 'WhiteLevel', - 50718: 'DefaultScale', - 50719: 'DefaultCropOrigin', - 50720: 'DefaultCropSize', - 50721: 'ColorMatrix1', - 50722: 'ColorMatrix2', - 50723: 'CameraCalibration1', - 50724: 'CameraCalibration2', - 50725: 'ReductionMatrix1', - 50726: 'ReductionMatrix2', - 50727: 'AnalogBalance', - 50728: 'AsShotNeutral', - 50729: 'AsShotWhiteXY', - 50730: 'BaselineExposure', - 50731: 'BaselineNoise', - 50732: 'BaselineSharpness', - 50733: 'BayerGreenSplit', - 50734: 'LinearResponseLimit', - 50735: 'CameraSerialNumber', - 50736: 'LensInfo', - 50737: 'ChromaBlurRadius', - 50738: 'AntiAliasStrength', - 50739: 'ShadowScale', - 50740: 'DNGPrivateData', - 50741: 'MakerNoteSafety', - 50778: 'CalibrationIlluminant1', - 50779: 'CalibrationIlluminant2', - 50780: 'BestQualityScale', - 50781: 'RawDataUniqueID', - 50827: 'OriginalRawFileName', - 50828: 'OriginalRawFileData', - 50829: 'ActiveArea', - 50830: 'MaskedAreas', - 50831: 'AsShotICCProfile', - 50832: 'AsShotPreProfileMatrix', - 50833: 'CurrentICCProfile', - 50834: 'CurrentPreProfileMatrix', - 50879: 'ColorimetricReference', - 50931: 'CameraCalibrationSignature', - 50932: 'ProfileCalibrationSignature', - 50934: 'AsShotProfileName', - 50935: 'NoiseReductionApplied', - 50936: 'ProfileName', - 50937: 'ProfileHueSatMapDims', - 50938: 'ProfileHueSatMapData1', - 50939: 'ProfileHueSatMapData2', - 50940: 'ProfileToneCurve', - 50941: 'ProfileEmbedPolicy', - 50942: 'ProfileCopyright', - 50964: 'ForwardMatrix1', - 50965: 'ForwardMatrix2', - 50966: 'PreviewApplicationName', - 50967: 'PreviewApplicationVersion', - 50968: 'PreviewSettingsName', - 50969: 'PreviewSettingsDigest', - 50970: 'PreviewColorSpace', - 50971: 'PreviewDateTime', - 50972: 'RawImageDigest', - 50973: 'OriginalRawFileDigest', - 50974: 'SubTileBlockSize', - 50975: 'RowInterleaveFactor', - 50981: 'ProfileLookTableDims', - 50982: 'ProfileLookTableData', - 51008: 'OpcodeList1', - 51009: 'OpcodeList2', - 51022: 'OpcodeList3', - 51041: 'NoiseProfile'} - - def __init__(self, endian, read_buffer, offset): - _Ifd.__init__(self, endian, read_buffer, offset) - self.post_process(self.tagnum2name) - - -class _ExifPhotoIfd(_Ifd): - """Represents tags found in the Exif sub ifd. - """ - tagnum2name = {33434: 'ExposureTime', - 33437: 'FNumber', - 34850: 'ExposureProgram', - 34852: 'SpectralSensitivity', - 34855: 'ISOSpeedRatings', - 34856: 'OECF', - 34864: 'SensitivityType', - 34865: 'StandardOutputSensitivity', - 34866: 'RecommendedExposureIndex', - 34867: 'ISOSpeed', - 34868: 'ISOSpeedLatitudeyyy', - 34869: 'ISOSpeedLatitudezzz', - 36864: 'ExifVersion', - 36867: 'DateTimeOriginal', - 36868: 'DateTimeDigitized', - 37121: 'ComponentsConfiguration', - 37122: 'CompressedBitsPerPixel', - 37377: 'ShutterSpeedValue', - 37378: 'ApertureValue', - 37379: 'BrightnessValue', - 37380: 'ExposureBiasValue', - 37381: 'MaxApertureValue', - 37382: 'SubjectDistance', - 37383: 'MeteringMode', - 37384: 'LightSource', - 37385: 'Flash', - 37386: 'FocalLength', - 37396: 'SubjectArea', - 37500: 'MakerNote', - 37510: 'UserComment', - 37520: 'SubSecTime', - 37521: 'SubSecTimeOriginal', - 37522: 'SubSecTimeDigitized', - 40960: 'FlashpixVersion', - 40961: 'ColorSpace', - 40962: 'PixelXDimension', - 40963: 'PixelYDimension', - 40964: 'RelatedSoundFile', - 40965: 'InteroperabilityTag', - 41483: 'FlashEnergy', - 41484: 'SpatialFrequencyResponse', - 41486: 'FocalPlaneXResolution', - 41487: 'FocalPlaneYResolution', - 41488: 'FocalPlaneResolutionUnit', - 41492: 'SubjectLocation', - 41493: 'ExposureIndex', - 41495: 'SensingMethod', - 41728: 'FileSource', - 41729: 'SceneType', - 41730: 'CFAPattern', - 41985: 'CustomRendered', - 41986: 'ExposureMode', - 41987: 'WhiteBalance', - 41988: 'DigitalZoomRatio', - 41989: 'FocalLengthIn35mmFilm', - 41990: 'SceneCaptureType', - 41991: 'GainControl', - 41992: 'Contrast', - 41993: 'Saturation', - 41994: 'Sharpness', - 41995: 'DeviceSettingDescription', - 41996: 'SubjectDistanceRange', - 42016: 'ImageUniqueID', - 42032: 'CameraOwnerName', - 42033: 'BodySerialNumber', - 42034: 'LensSpecification', - 42035: 'LensMake', - 42036: 'LensModel', - 42037: 'LensSerialNumber'} - - def __init__(self, endian, read_buffer, offset): - _Ifd.__init__(self, endian, read_buffer, offset) - self.post_process(self.tagnum2name) - - -class _ExifGPSInfoIfd(_Ifd): - """Represents information found in the GPSInfo sub IFD. - """ - tagnum2name = {0: 'GPSVersionID', - 1: 'GPSLatitudeRef', - 2: 'GPSLatitude', - 3: 'GPSLongitudeRef', - 4: 'GPSLongitude', - 5: 'GPSAltitudeRef', - 6: 'GPSAltitude', - 7: 'GPSTimeStamp', - 8: 'GPSSatellites', - 9: 'GPSStatus', - 10: 'GPSMeasureMode', - 11: 'GPSDOP', - 12: 'GPSSpeedRef', - 13: 'GPSSpeed', - 14: 'GPSTrackRef', - 15: 'GPSTrack', - 16: 'GPSImgDirectionRef', - 17: 'GPSImgDirection', - 18: 'GPSMapDatum', - 19: 'GPSDestLatitudeRef', - 20: 'GPSDestLatitude', - 21: 'GPSDestLongitudeRef', - 22: 'GPSDestLongitude', - 23: 'GPSDestBearingRef', - 24: 'GPSDestBearing', - 25: 'GPSDestDistanceRef', - 26: 'GPSDestDistance', - 27: 'GPSProcessingMethod', - 28: 'GPSAreaInformation', - 29: 'GPSDateStamp', - 30: 'GPSDifferential'} - - def __init__(self, endian, read_buffer, offset): - _Ifd.__init__(self, endian, read_buffer, offset) - self.post_process(self.tagnum2name) - - -class _ExifInteroperabilityIfd(_Ifd): - """Represents tags found in the Interoperability sub IFD. - """ - tagnum2name = {1: 'InteroperabilityIndex', - 2: 'InteroperabilityVersion', - 4096: 'RelatedImageFileFormat', - 4097: 'RelatedImageWidth', - 4098: 'RelatedImageLength'} - - def __init__(self, endian, read_buffer, offset): - _Ifd.__init__(self, endian, read_buffer, offset) - self.post_process(self.tagnum2name) diff --git a/glymur/codestream.py b/glymur/codestream.py index ea94afd..e83c5cc 100644 --- a/glymur/codestream.py +++ b/glymur/codestream.py @@ -6,13 +6,16 @@ codestreams. # The number of lines in the module is long and that's ok. It would not help # matters to move anything out to another file. +# pylint: disable=C0302 # "Too many instance attributes", "Too many arguments" # Some segments just have a lot of information. # It doesn't make sense to subclass just for that. +# pylint: disable=R0902,R0913 # "Too few public methods" Some segments don't define any new methods from # the base Segment class. +# pylint: disable=R0903 import math import struct @@ -21,37 +24,22 @@ import warnings import numpy as np -from .core import (LRCP, RLCP, RPCL, PCRL, CPRL, - WAVELET_XFORM_9X7_IRREVERSIBLE, - WAVELET_XFORM_5X3_REVERSIBLE, - _Keydefaultdict) +from .core import LRCP, RLCP, RPCL, PCRL, CPRL +from .core import WAVELET_XFORM_9X7_IRREVERSIBLE +from .core import WAVELET_XFORM_5X3_REVERSIBLE +from .core import _CAPABILITIES_DISPLAY from .lib import openjp2 as opj2 -_factory = lambda x: '{0} (invalid)'.format(x) -_PROGRESSION_ORDER_DISPLAY = _Keydefaultdict(_factory, {LRCP: 'LRCP', - RLCP: 'RLCP', - RPCL: 'RPCL', - PCRL: 'PCRL', - CPRL: 'CPRL'}) +_PROGRESSION_ORDER_DISPLAY = { + LRCP: 'LRCP', + RLCP: 'RLCP', + RPCL: 'RPCL', + PCRL: 'PCRL', + CPRL: 'CPRL'} -_keysvalues = {WAVELET_XFORM_9X7_IRREVERSIBLE: '9-7 irreversible', - WAVELET_XFORM_5X3_REVERSIBLE: '5-3 reversible'} -_WAVELET_TRANSFORM_DISPLAY = _Keydefaultdict(_factory, _keysvalues) - -_NO_PROFILE = 0 -_PROFILE_0 = 1 -_PROFILE_1 = 2 -_PROFILE_3 = 3 -_PROFILE_4 = 4 - -_KNOWN_PROFILES = [_NO_PROFILE, _PROFILE_0, _PROFILE_1, _PROFILE_3, _PROFILE_4] - -# How to display the codestream profile. -_CAPABILITIES_DISPLAY = _Keydefaultdict(_factory, {_NO_PROFILE: 'no profile', - _PROFILE_0: '0', - _PROFILE_1: '1', - _PROFILE_3: 'Cinema 2K', - _PROFILE_4: 'Cinema 4K'}) +_WAVELET_TRANSFORM_DISPLAY = { + WAVELET_XFORM_9X7_IRREVERSIBLE: '9-7 irreversible', + WAVELET_XFORM_5X3_REVERSIBLE: '5-3 reversible'} # Need a catch-all list of valid markers. # See table A-1 in ISO/IEC FCD15444-1. @@ -183,7 +171,15 @@ 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: + # Treat this as a warning. + msg = "Marker had length {0} instead of expected length of 2 " + msg += "bytes. Codestream parsing terminated." + warnings.warn(msg.format(len(read_buffer))) + break + self._offset = fptr.tell() - 2 if self._marker_id == 0xff90 and header_only: @@ -197,7 +193,7 @@ class Codestream(object): msg = 'Invalid marker id encountered at byte {0:d} ' msg += 'in codestream: "0x{1:x}"' msg = msg.format(self._offset, self._marker_id) - warnings.warn(msg, UserWarning) + warnings.warn(msg) break self.segment.append(segment) @@ -293,6 +289,7 @@ class Codestream(object): msg += ''.join(strs) return msg + # pylint: disable=R0201 def _parse_cme_segment(self, fptr): """Parse the CME marker segment. @@ -366,22 +363,14 @@ class Codestream(object): COD segment instance. """ offset = fptr.tell() - 2 + offset = fptr.tell() - 2 - read_buffer = fptr.read(2) - length, = struct.unpack('>H', read_buffer) + read_buffer = fptr.read(3) + length, scod = struct.unpack('>HB', read_buffer) - read_buffer = fptr.read(length - 2) - scod, = struct.unpack_from('>B', read_buffer, offset=0) - spcod = read_buffer[1:] + numbytes = offset + 2 + length - fptr.tell() + spcod = fptr.read(numbytes) spcod = np.frombuffer(spcod, dtype=np.uint8) - if spcod[0] not in [LRCP, RLCP, RPCL, PCRL, CPRL]: - msg = "Invalid progression order in COD segment: {0}." - warnings.warn(msg.format(spcod[0])) - - if spcod[8] not in [WAVELET_XFORM_9X7_IRREVERSIBLE, - WAVELET_XFORM_5X3_REVERSIBLE]: - msg = "Invalid wavelet transform in COD segment: {0}." - warnings.warn(msg.format(spcod[8])) sop = (scod & 2) > 0 eph = (scod & 4) > 0 @@ -569,21 +558,22 @@ class Codestream(object): read_buffer = fptr.read(2) length, = struct.unpack('>H', read_buffer) - read_buffer = fptr.read(length - 2) if self._csiz > 256: + read_buffer = fptr.read(3) fmt = '>HB' - mantissa_exponent_offset = 3 + mantissa_exponent_buffer_length = length - 5 else: + read_buffer = fptr.read(2) fmt = '>BB' - mantissa_exponent_offset = 2 - cqcc, sqcc = struct.unpack_from(fmt, read_buffer) + mantissa_exponent_buffer_length = length - 4 + cqcc, sqcc = struct.unpack(fmt, read_buffer) if cqcc >= self._csiz: msg = "Invalid component number ({0}), " msg += "number of components is only {1}." msg = msg.format(cqcc, self._csiz) warnings.warn(msg) - spqcc = read_buffer[mantissa_exponent_offset:] + spqcc = fptr.read(mantissa_exponent_buffer_length) return QCCsegment(cqcc, sqcc, spqcc, length, offset) @@ -636,7 +626,7 @@ class Codestream(object): srgn = data[1] sprgn = data[2] - return RGNsegment(crgn, srgn, sprgn, length, offset) + return RGNsegment(length, offset, crgn, srgn, sprgn) def _parse_siz_segment(self, fptr): """Parse the SIZ segment. @@ -655,63 +645,17 @@ class Codestream(object): read_buffer = fptr.read(2) length, = struct.unpack('>H', read_buffer) - read_buffer = fptr.read(length - 2) - data = struct.unpack_from('>HIIIIIIIIH', read_buffer) + xy_buffer = fptr.read(36) - rsiz = data[0] - if rsiz not in _KNOWN_PROFILES: - warnings.warn("Invalid profile: (Rsiz={0}).".format(rsiz)) + num_components, = struct.unpack('>H', xy_buffer[-2:]) - xysiz = (data[1], data[2]) - xyosiz = (data[3], data[4]) - xytsiz = (data[5], data[6]) - xytosiz = (data[7], data[8]) + component_buffer = fptr.read(num_components * 3) - # Csiz is the number of components - Csiz = data[9] - - data = struct.unpack_from('>' + 'B' * (length - 36 - 2), - read_buffer, offset=36) - - bitdepth = tuple(((x & 0x7f) + 1) for x in data[0::3]) - signed = tuple(((x & 0x80) > 0) for x in data[0::3]) - xrsiz = data[1::3] - yrsiz = data[2::3] - - for j, subsampling in enumerate(zip(xrsiz, yrsiz)): - if 0 in subsampling: - msg = "Invalid subsampling value for component {0}: " - msg += "dx={1}, dy={2}." - msg = msg.format(j, subsampling[0], subsampling[1]) - warnings.warn(msg) - - try: - num_tiles_x = (xysiz[0] - xyosiz[0]) / (xytsiz[0] - xytosiz[0]) - num_tiles_y = (xysiz[1] - xyosiz[1]) / (xytsiz[1] - xytosiz[1]) - except ZeroDivisionError: - warnings.warn("Invalid tile dimensions.") - else: - numtiles = math.ceil(num_tiles_x) * math.ceil(num_tiles_y) - if numtiles > 65535: - msg = "Invalid number of tiles ({0}).".format(numtiles) - warnings.warn(msg) - - kwargs = {'rsiz': rsiz, - 'xysiz': xysiz, - 'xyosiz': xyosiz, - 'xytsiz': xytsiz, - 'xytosiz': xytosiz, - 'Csiz': Csiz, - 'bitdepth': bitdepth, - 'signed': signed, - 'xyrsiz': (xrsiz, yrsiz), - 'length': length, - 'offset': offset} - segment = SIZsegment(**kwargs) + segment = SIZsegment(xy_buffer, component_buffer, length, offset) # Need to keep track of the number of components from SIZ for - # other markers. - self._csiz = Csiz + # other markers + self._csiz = len(segment.ssiz) return segment @@ -787,8 +731,8 @@ class Codestream(object): read_buffer = fptr.read(2) length, = struct.unpack('>H', read_buffer) - read_buffer = fptr.read(length - 2) - ztlm, stlm = struct.unpack_from('>BB', read_buffer) + read_buffer = fptr.read(2) + ztlm, stlm = struct.unpack('>BB', read_buffer) ttlm_st = (stlm >> 4) & 0x3 ptlm_sp = (stlm >> 6) & 0x1 @@ -798,6 +742,7 @@ class Codestream(object): else: ntiles = nbytes / (ttlm_st + (ptlm_sp + 1) * 2) + read_buffer = fptr.read(nbytes) if ttlm_st == 0: ttlm = None fmt = '' @@ -811,8 +756,7 @@ class Codestream(object): else: fmt += 'I' - data = struct.unpack_from('>' + fmt * int(ntiles), read_buffer, - offset=2) + data = struct.unpack('>' + fmt * int(ntiles), read_buffer) if ttlm_st == 0: ttlm = None ptlm = data @@ -822,6 +766,7 @@ class Codestream(object): return TLMsegment(length, offset, ztlm, ttlm, ptlm) + # pylint: disable=W0613 def _parse_reserved_marker(self, fptr): """Marker range between 0xff30 and 0xff39. """ @@ -1066,7 +1011,7 @@ class CMEsegment(Segment): 15444-1:2004 - Information technology -- JPEG 2000 image coding system: Core coding system """ - def __init__(self, rcme, ccme, length=-1, offset=-1): + def __init__(self, rcme, ccme, length, offset): Segment.__init__(self, marker_id='CME') self.rcme = rcme self.ccme = ccme @@ -1455,7 +1400,7 @@ class RGNsegment(Segment): 15444-1:2004 - Information technology -- JPEG 2000 image coding system: Core coding system """ - def __init__(self, crgn, srgn, sprgn, length=-1, offset=-1): + def __init__(self, length, offset, crgn, srgn, sprgn): Segment.__init__(self, marker_id='RGN') self.length = length self.offset = offset @@ -1496,8 +1441,8 @@ class SIZsegment(Segment): Width and height of reference tile with respect to the reference grid. xtosiz, ytosiz : int Horizontal and vertical offsets of tile from origin of reference grid. - Csiz : int - Number of components in image. + ssiz : iterable bytes + Encoded precision (depth) in bits and sign of each component. bitdepth : iterable bytes Precision (depth) in bits of each component. signed : iterable bool @@ -1512,45 +1457,48 @@ class SIZsegment(Segment): 15444-1:2004 - Information technology -- JPEG 2000 image coding system: Core coding system """ - def __init__(self, rsiz=-1, xysiz=None, xyosiz=-1, xytsiz=-1, xytosiz=-1, - Csiz=-1, bitdepth=None, signed=None, xyrsiz=-1, length=-1, - offset=-1): - Segment.__init__(self, marker_id='SIZ', length=length, offset=offset) + def __init__(self, xy_buffer, component_buffer, length, offset): + Segment.__init__(self, marker_id='SIZ') - self.rsiz = rsiz - self.xsiz, self.ysiz = xysiz - self.xosiz, self.yosiz = xyosiz - self.xtsiz, self.ytsiz = xytsiz - self.xtosiz, self.ytosiz = xytosiz - self.Csiz = Csiz - self.bitdepth = bitdepth - self.signed = signed - self.xrsiz, self.yrsiz = xyrsiz + data = struct.unpack('>HIIIIIIIIH', xy_buffer) - # ssiz attribute to be removed in 1.0.0 - lst = [] - for bitdepth, signed in zip(self.bitdepth, self.signed): - if signed: - lst.append((bitdepth - 1) | 0x80) - else: - lst.append(bitdepth - 1) - self.ssiz = tuple(lst) + self.rsiz = data[0] + self.xsiz = data[1] + self.ysiz = data[2] + self.xosiz = data[3] + self.yosiz = data[4] + self.xtsiz = data[5] + self.ytsiz = data[6] + self.xtosiz = data[7] + self.ytosiz = data[8] + # disregarding the last element in data - def __repr__(self): - msg = "glymur.codestream.SIZsegment(rsiz={rsiz}, xysiz={xysiz}, " - msg += "xyosiz={xyosiz}, xytsiz={xytsiz}, xytosiz={xytosiz}, " - msg += "Csiz={Csiz}, bitdepth={bitdepth}, signed={signed}, " - msg += "xyrsiz={xyrsiz})" - msg = msg.format(rsiz=self.rsiz, - xysiz=(self.xsiz, self.ysiz), - xyosiz=(self.xosiz, self.yosiz), - xytsiz=(self.xtsiz, self.ytsiz), - xytosiz=(self.xtosiz, self.ytosiz), - Csiz=self.Csiz, - bitdepth=self.bitdepth, - signed=self.signed, - xyrsiz=(self.xrsiz, self.yrsiz)) - return msg + num_tiles_x = (self.xsiz - self.xosiz) / (self.xtsiz - self.xtosiz) + num_tiles_y = (self.ysiz - self.yosiz) / (self.ytsiz - self.ytosiz) + numtiles = math.ceil(num_tiles_x) * math.ceil(num_tiles_y) + if numtiles > 65535: + msg = "Invalid number of tiles ({0}).".format(numtiles) + warnings.warn(msg) + + data = struct.unpack('>' + 'B' * len(component_buffer), + component_buffer) + + self.ssiz = data[0::3] + + for j, subsampling in enumerate(list(zip(data[1::3], data[2::3]))): + if 0 in subsampling: + msg = "Invalid subsampling value for component {0}: " + msg += "dx={1}, dy={2}." + msg = msg.format(j, subsampling[0], subsampling[1]) + warnings.warn(msg) + self.xrsiz = data[1::3] + self.yrsiz = data[2::3] + + self.bitdepth = tuple(((x & 0x7f) + 1) for x in self.ssiz) + self.signed = tuple(((x & 0xb0) > 0) for x in self.ssiz) + + self.length = length + self.offset = offset def __str__(self): msg = Segment.__str__(self) @@ -1601,10 +1549,6 @@ class SOCsegment(Segment): Segment.__init__(self, marker_id='SOC') self.__dict__.update(**kwargs) - def __repr__(self): - msg = "glymur.codestream.SOCsegment()" - return msg - class SODsegment(Segment): """Container for Start of Data (SOD) segment information. @@ -1719,7 +1663,7 @@ class SOTsegment(Segment): 15444-1:2004 - Information technology -- JPEG 2000 image coding system: Core coding system """ - def __init__(self, isot, psot, tpsot, tnsot, length=-1, offset=-1): + def __init__(self, isot, psot, tpsot, tnsot, length, offset): Segment.__init__(self, marker_id='SOT') self.isot = isot self.psot = psot diff --git a/glymur/command_line.py b/glymur/command_line.py deleted file mode 100644 index 3d1d57e..0000000 --- a/glymur/command_line.py +++ /dev/null @@ -1,78 +0,0 @@ -""" -Entry point for console script jp2dump. -""" -import argparse -import os -import warnings - -from . import Jp2k, set_printoptions, set_parseoptions, lib - - -def main(): - """ - Entry point for console script jp2dump. - """ - - kwargs = {'description': 'Print JPEG2000 metadata.', - 'formatter_class': argparse.ArgumentDefaultsHelpFormatter} - parser = argparse.ArgumentParser(**kwargs) - - parser.add_argument('-x', '--noxml', - help='suppress XML', - action='store_true') - parser.add_argument('-s', '--short', - help='only print box id, offset, and length', - action='store_true') - - chelp = 'Level of codestream information. 0 suppresses all details, ' - chelp += '1 prints the main header, 2 prints the full codestream.' - parser.add_argument('-c', '--codestream', - help=chelp, - metavar='LEVEL', - nargs=1, - type=int, - default=[1]) - - parser.add_argument('filename') - - args = parser.parse_args() - if args.noxml: - set_printoptions(xml=False) - if args.short: - set_printoptions(short=True) - - codestream_level = args.codestream[0] - if codestream_level not in [0, 1, 2]: - raise ValueError("Invalid level of codestream information specified.") - - if codestream_level == 0: - set_printoptions(codestream=False) - elif codestream_level == 2: - set_parseoptions(full_codestream=True) - - filename = args.filename - - with warnings.catch_warnings(record=True) as wctx: - - # JP2 metadata can be extensive, so don't print any warnings until we - # are done with the metadata. - jp2 = Jp2k(filename) - if jp2._codec_format == lib.openjp2.CODEC_J2K: - if codestream_level == 0: - print('File: {0}'.format(os.path.basename(filename))) - elif codestream_level == 1: - print(jp2) - elif codestream_level == 2: - print('File: {0}'.format(os.path.basename(filename))) - print(jp2.get_codestream(header_only=False)) - else: - print(jp2) - - # Re-emit any warnings that may have been suppressed. - if len(wctx) > 0: - print("\n") - for warning in wctx: - print("{0}:{1}: {2}: {3}".format(warning.filename, - warning.lineno, - warning.category.__name__, - warning.message)) diff --git a/glymur/core.py b/glymur/core.py index 644dcfd..22b5a19 100644 --- a/glymur/core.py +++ b/glymur/core.py @@ -1,21 +1,5 @@ """Core definitions to be shared amongst the modules. """ -import collections - - -class _Keydefaultdict(collections.defaultdict): - """Unlisted keys help form their own error message. - - Normally defaultdict uses a factory function with no input arguments, but - that's not quite the behavior we want. - """ - def __missing__(self, key): - if self.default_factory is None: - raise KeyError(key) - else: - ret = self[key] = self.default_factory(key) - return ret - # Progression order LRCP = 0 RLCP = 1 @@ -23,74 +7,6 @@ RPCL = 2 PCRL = 3 CPRL = 4 -STD = 0 -CINEMA2K = 3 -CINEMA4K = 4 - -RSIZ = { - 'STD': STD, - 'CINEMA2K': CINEMA2K, - 'CINEMA4K': CINEMA4K} - -OFF = 0 -CINEMA2K_24 = 1 -CINEMA2K_48 = 2 -CINEMA4K_24 = 3 - -OPJ_OFF = 0 # Not Digital Cinema -OPJ_CINEMA2K_24 = 1 # 2K Digital Cinema at 24 fps -OPJ_CINEMA2K_48 = 2 # 2K Digital Cinema at 48 fps -OPJ_CINEMA4K_24 = 3 # 4K Digital Cinema at 24 fps - -# no profile, conform to 15444-1 -OPJ_PROFILE_NONE = 0x0000 -# Profile 0 as described in 15444-1,Table A.45 -OPJ_PROFILE_0 = 0x0001 -# Profile 1 as described in 15444-1,Table A.45 -OPJ_PROFILE_1 = 0x0002 -# At least 1 extension defined in 15444-2 (Part-2) -OPJ_PROFILE_PART2 = 0x8000 -# 2K cinema profile defined in 15444-1 AMD1 -OPJ_PROFILE_CINEMA_2K = 0x0003 -# 4K cinema profile defined in 15444-1 AMD1 -OPJ_PROFILE_CINEMA_4K = 0x0004 -# Scalable 2K cinema profile defined in 15444-1 AMD2 -OPJ_PROFILE_CINEMA_S2K = 0x0005 -# Scalable 4K cinema profile defined in 15444-1 AMD2 -OPJ_PROFILE_CINEMA_S4K = 0x0006 -# Long term storage cinema profile defined in 15444-1 AMD2 -OPJ_PROFILE_CINEMA_LTS = 0x0007 -# Single Tile Broadcast profile defined in 15444-1 AMD3 -OPJ_PROFILE_BC_SINGLE = 0x0100 -# Multi Tile Broadcast profile defined in 15444-1 AMD3 -OPJ_PROFILE_BC_MULTI = 0x0200 -# Multi Tile Reversible Broadcast profile defined in 15444-1 AMD3 -OPJ_PROFILE_BC_MULTI_R = 0x0300 -# 2K Single Tile Lossy IMF profile defined in 15444-1 AMD 8 -OPJ_PROFILE_IMF_2K = 0x0400 -# 4K Single Tile Lossy IMF profile defined in 15444-1 AMD 8 -OPJ_PROFILE_IMF_4K = 0x0401 -# 8K Single Tile Lossy IMF profile defined in 15444-1 AMD 8 -OPJ_PROFILE_IMF_8K = 0x0402 -# 2K Single/Multi Tile Reversible IMF profile defined in 15444-1 AMD 8 -OPJ_PROFILE_IMF_2K_R = 0x0403 -# 4K Single/Multi Tile Reversible IMF profile defined in 15444-1 AMD 8 -OPJ_PROFILE_IMF_4K_R = 0x0800 -# 8K Single/Multi Tile Reversible IMF profile defined in 15444-1 AMD 8 -OPJ_PROFILE_IMF_8K_R = 0x0801 - -# JPEG 2000 codestream and component size limits in cinema profiles -# -# Maximum codestream length for 24fps -OPJ_CINEMA_24_CS = 1302083 -# Maximum codestream length for 48fps -OPJ_CINEMA_48_CS = 651041 -# Maximum size per color component for 2K & 4K @ 24fps -OPJ_CINEMA_24_COMP = 1041666 -# Maximum size per color component for 2K @ 48fps -OPJ_CINEMA_48_COMP = 520833 - - PROGRESSION_ORDER = { 'LRCP': LRCP, 'RLCP': RLCP, @@ -118,26 +34,24 @@ YCC = 18 E_SRGB = 20 ROMM_RGB = 21 -_factory = lambda x: '{0} (unrecognized)'.format(x) -_COLORSPACE_MAP_DISPLAY = _Keydefaultdict(_factory, - {CMYK: 'CMYK', - SRGB: 'sRGB', - GREYSCALE: 'greyscale', - YCC: 'YCC', - E_SRGB: 'e-sRGB', - ROMM_RGB: 'ROMM-RGB'}) +_COLORSPACE_MAP_DISPLAY = { + CMYK: 'CMYK', + SRGB: 'sRGB', + GREYSCALE: 'greyscale', + YCC: 'YCC', + E_SRGB: 'e-sRGB', + ROMM_RGB: 'ROMM-RGB'} # enumerated color channel types COLOR = 0 OPACITY = 1 PRE_MULTIPLIED_OPACITY = 2 _UNSPECIFIED = 65535 -_factory = lambda x: '{0} (invalid)'.format(x) -_dict = {COLOR: 'color', - OPACITY: 'opacity', - PRE_MULTIPLIED_OPACITY: 'pre-multiplied opacity', - _UNSPECIFIED: 'unspecified'} -_COLOR_TYPE_MAP_DISPLAY = _Keydefaultdict(_factory, _dict) +_COLOR_TYPE_MAP_DISPLAY = { + COLOR: 'color', + OPACITY: 'opacity', + PRE_MULTIPLIED_OPACITY: 'pre-multiplied opacity', + _UNSPECIFIED: 'unspecified'} # color channel definitions. RED = 1 @@ -152,3 +66,10 @@ _COLORSPACE = {SRGB: {"R": 1, "G": 2, "B": 3}, YCC: {"Y": 1, "Cb": 2, "Cr": 3}, E_SRGB: {"R": 1, "G": 2, "B": 3}, ROMM_RGB: {"R": 1, "G": 2, "B": 3}} + +# How to display the codestream profile. +_CAPABILITIES_DISPLAY = { + 0: '2', + 1: '0', + 2: '1', + 3: '3'} diff --git a/glymur/data/__init__.py b/glymur/data/__init__.py index 066edd2..c95b144 100644 --- a/glymur/data/__init__.py +++ b/glymur/data/__init__.py @@ -31,15 +31,3 @@ def goodstuff(): """ filename = pkg_resources.resource_filename(__name__, "goodstuff.j2k") return filename - - -def jpxfile(): - """Shortcut for specifying path to heliov.jpx. - - Returns - ------- - file : str - Platform-independent path to 12-v6.4.jpx - """ - filename = pkg_resources.resource_filename(__name__, "heliov.jpx") - return filename diff --git a/glymur/data/heliov.jpx b/glymur/data/heliov.jpx deleted file mode 100644 index c7d5cdb..0000000 Binary files a/glymur/data/heliov.jpx and /dev/null differ diff --git a/glymur/data/nemo.jp2 b/glymur/data/nemo.jp2 index 838583d..55d199c 100644 Binary files a/glymur/data/nemo.jp2 and b/glymur/data/nemo.jp2 differ diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 98b0e6a..2b40630 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -11,45 +11,47 @@ References Extensions """ -from collections import OrderedDict +# pylint: disable=C0302,R0903,R0913 + +import copy import datetime -import io import math import os import pprint import struct import sys -import textwrap -from uuid import UUID +import uuid import warnings +import xml.etree.cElementTree as ET +if sys.hexversion < 0x02070000: + # pylint: disable=F0401,E0611 + from ordereddict import OrderedDict + from xml.etree.cElementTree import XMLParserError as ParseError +else: + from xml.etree.cElementTree import ParseError + from collections import OrderedDict -import lxml.etree as ET import numpy as np from .codestream import Codestream -from .core import (_COLORSPACE_MAP_DISPLAY, _COLOR_TYPE_MAP_DISPLAY, - SRGB, GREYSCALE, YCC, - ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE, - ANY_ICC_PROFILE, VENDOR_COLOR_METHOD, - _Keydefaultdict) +from .core import _COLORSPACE_MAP_DISPLAY +from .core import _COLOR_TYPE_MAP_DISPLAY +from .core import ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE +from .core import ANY_ICC_PROFILE, VENDOR_COLOR_METHOD -from . import _uuid_io +_METHOD_DISPLAY = { + ENUMERATED_COLORSPACE: 'enumerated colorspace', + RESTRICTED_ICC_PROFILE: 'restricted ICC profile', + ANY_ICC_PROFILE: 'any ICC profile', + VENDOR_COLOR_METHOD: 'vendor color method'} -_factory = lambda x: '{0} (invalid)'.format(x) -_keysvalues = {ENUMERATED_COLORSPACE: 'enumerated colorspace', - RESTRICTED_ICC_PROFILE: 'restricted ICC profile', - ANY_ICC_PROFILE: 'any ICC profile', - VENDOR_COLOR_METHOD: 'vendor color method'} -_METHOD_DISPLAY = _Keydefaultdict(_factory, _keysvalues) - -_factory = lambda x: '{0} (invalid)'.format(x) -_keysvalues = {1: 'accurately represents correct colorspace definition', - 2: ('approximates correct colorspace definition, ' - 'exceptional quality'), - 3: ('approximates correct colorspace definition, ' - 'reasonable quality'), - 4: 'approximates correct colorspace definition, poor quality'} -_APPROX_DISPLAY = _Keydefaultdict(_factory, _keysvalues) +_APPROX_DISPLAY = {1: 'accurately represents correct colorspace definition', + 2: 'approximates correct colorspace definition, ' + + 'exceptional quality', + 3: 'approximates correct colorspace definition, ' + + 'reasonable quality', + 4: 'approximates correct colorspace definition, ' + + 'poor quality'} class Jp2kBox(object): @@ -63,143 +65,27 @@ class Jp2kBox(object): length of the box in bytes. offset : int offset of the box from the start of the file. - box : list - List of JPEG 2000 boxes. + longname : str + more verbose description of the box. """ - def __init__(self, offset=0, length=0): + def __init__(self, box_id='', offset=0, length=0, longname=''): + self.box_id = box_id self.length = length self.offset = offset - self.box = [] - - def __repr__(self): - msg = "glymur.jp2box.Jp2kBox(box_id='{0}', offset={1}, length={2}, " - msg += "longname='{3}')" - msg = msg.format(self.box_id, self.offset, self.length, self.longname) - return msg + self.longname = longname def __str__(self): msg = "{0} Box ({1})".format(self.longname, self.box_id) msg += " @ ({0}, {1})".format(self.offset, self.length) return msg - def _dispatch_validation_error(self, msg, writing=False): - """Issue either a warning or an error depending on circumstance. - - If writing to file, then error out, as we do not wish to create bad - JP2 files. If reading, then we should be more lenient and just warn. - """ - if writing: - raise IOError(msg) - else: - warnings.warn(msg) - def write(self, _): """Must be implemented in a subclass. """ msg = "Not supported for {0} box.".format(self.longname) raise NotImplementedError(msg) - def _str_superbox(self): - """__str__ method for all superboxes.""" - msg = Jp2kBox.__str__(self) - for box in self.box: - boxstr = str(box) - # Indent the child boxes to make the association clear. - msg += '\n' + self._indent(boxstr) - return msg - - def _indent(self, textstr, indent_level=4): - """ - Indent a string. - - Textwrap's indent method only exists for 3.3 or above. In 2.7 we have - to fake it. - - Parameters - ---------- - textstring : str - String to be indented. - indent_level : str - Number of spaces of indentation to add. - - Returns - ------- - indented_string : str - Possibly multi-line string indented a certain bit. - """ - if sys.hexversion >= 0x03030000: - return textwrap.indent(textstr, ' ' * indent_level) - else: - lst = [(' ' * indent_level + x) for x in textstr.split('\n')] - return '\n'.join(lst) - - def _write_superbox(self, fptr, box_id): - """Write a superbox. - - Parameters - ---------- - fptr : file or file object - Superbox (box of boxes) to be written to this file. - box_id : bytes - 4-byte sequence that identifies the superbox. - """ - # Write the contained boxes, then come back and write the length. - orig_pos = fptr.tell() - fptr.write(struct.pack('>I4s', 0, box_id)) - for box in self.box: - box.write(fptr) - - end_pos = fptr.tell() - fptr.seek(orig_pos) - fptr.write(struct.pack('>I', end_pos - orig_pos)) - fptr.seek(end_pos) - - def _parse_this_box(self, fptr, box_id, start, num_bytes): - """Parse the current box. - - Parameters - ---------- - fptr : file - Open file object, currently points to start of box payload, not the - start of the box. - box_id : str - 4-letter identifier for the current box. - start, num_bytes : int - Byte offset and length of the current box. - - Returns - ------- - box : Jp2kBox - object corresponding to the current box - """ - try: - parser = _BOX_WITH_ID[box_id].parse - - except KeyError: - # We don't recognize the box ID, so create an UnknownBox and be - # done with it. - msg = 'Unrecognized box ({0}) encountered.'.format(box_id) - warnings.warn(msg) - box = UnknownBox(box_id, offset=start, length=num_bytes, - longname='Unknown') - - return box - - try: - box = parser(fptr, start, num_bytes) - except ValueError as err: - msg = "Encountered an unrecoverable ValueError while parsing a " - msg += "{0} box at byte offset {1}. The original error message " - msg += "was \"{2}\"" - msg = msg.format(_BOX_WITH_ID[box_id].longname, start, str(err)) - warnings.warn(msg, UserWarning) - box = UnknownBox(box_id.decode('utf-8'), - length=num_bytes, - offset=start, longname='Unknown') - - return box - def parse_superbox(self, fptr): """Parse a superbox (box consisting of nothing but other boxes. @@ -224,12 +110,10 @@ class Jp2kBox(object): break read_buffer = fptr.read(8) - if len(read_buffer) < 8: - msg = "Extra bytes at end of file ignored." - warnings.warn(msg) - return superbox - (box_length, box_id) = struct.unpack('>I4s', read_buffer) + if sys.hexversion >= 0x03000000: + box_id = box_id.decode('utf-8') + if box_length == 0: # The length of the box is presumed to last until the end of # the file. Compute the effective length of the box. @@ -241,10 +125,16 @@ class Jp2kBox(object): num_bytes, = struct.unpack('>Q', read_buffer) else: - # The box_length value really is the length of the box! num_bytes = box_length - box = self._parse_this_box(fptr, box_id, start, num_bytes) + # Call the proper parser for the given box with ID "T". + try: + box = _BOX_WITH_ID[box_id].parse(fptr, start, num_bytes) + except KeyError: + msg = 'Unrecognized box ({0}) encountered.'.format(box_id) + warnings.warn(msg) + box = Jp2kBox(box_id, offset=start, length=num_bytes, + longname='Unknown box') superbox.append(box) @@ -296,69 +186,28 @@ class ColourSpecificationBox(Jp2kBox): ICC profile header according to ICC profile specification. If colorspace is not None, then icc_profile must be empty. """ - longname = 'Colour Specification' - box_id = 'colr' def __init__(self, method=ENUMERATED_COLORSPACE, precedence=0, approximation=0, colorspace=None, icc_profile=None, length=0, offset=-1): - Jp2kBox.__init__(self) + Jp2kBox.__init__(self, box_id='colr', longname='Colour Specification') + if colorspace is not None and icc_profile is not None: + raise IOError("colorspace and icc_profile cannot both be set.") + if method not in (1, 2, 3, 4): + raise IOError("Invalid method.") + if approximation not in (0, 1, 2, 3, 4): + raise IOError("Invalid approximation.") self.method = method self.precedence = precedence self.approximation = approximation - self.colorspace = colorspace self.icc_profile = icc_profile self.length = length self.offset = offset - self._validate(writing=False) - - def _validate(self, writing=False): - """Verify that the box obeys the specifications.""" - if self.colorspace is not None and self.icc_profile is not None: - msg = "Colorspace and icc_profile cannot both be set." - self._dispatch_validation_error(msg, writing=writing) - if self.method not in (1, 2, 3, 4): - msg = "Invalid method.".format(self.method) - self._dispatch_validation_error(msg, writing=writing) - if self.approximation not in (0, 1, 2, 3, 4): - msg = "Invalid approximation: {0}".format(self.approximation) - self._dispatch_validation_error(msg, writing=writing) - - def _write_validate(self): - """In addition to constructor validation steps, run validation steps - for writing.""" - if self.colorspace is None: - msg = "Writing Colour Specification boxes without enumerated " - msg += "colorspaces is not supported at this time." - self._dispatch_validation_error(msg, writing=True) - - if self.icc_profile is None: - if self.colorspace not in [SRGB, GREYSCALE, YCC]: - msg = "Colorspace should correspond to one of SRGB, " - msg += "GREYSCALE, or YCC." - self._dispatch_validation_error(msg, writing=True) - - self._validate(writing=True) - - def __repr__(self): - msg = "glymur.jp2box.ColourSpecificationBox(" - msg += "method={0}, precedence={1}, approximation={2}, " - msg += "colorspace={3}, " - msg += "icc_profile={4})" - msg = msg.format(self.method, - self.precedence, - self.approximation, - self.colorspace, - self.icc_profile) - return msg - def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] is True: - return msg msg += '\n Method: {0}'.format(_METHOD_DISPLAY[self.method]) msg += '\n Precedence: {0}'.format(self.precedence) @@ -373,25 +222,26 @@ class ColourSpecificationBox(Jp2kBox): else: # 2.7 has trouble pretty-printing ordered dicts so we just have # to print as a regular dict in this case. - if self.icc_profile is None: - msg += '\n ICC Profile: None' + if sys.hexversion < 0x03000000: + icc_profile = dict(self.icc_profile) else: - if sys.hexversion < 0x03000000: - icc_profile = dict(self.icc_profile) - else: - icc_profile = self.icc_profile - dispvalue = pprint.pformat(icc_profile) - lines = [' ' * 8 + y for y in dispvalue.split('\n')] - msg += '\n ICC Profile:\n{0}'.format('\n'.join(lines)) + icc_profile = self.icc_profile + dispvalue = pprint.pformat(icc_profile) + lines = [' ' * 8 + y for y in dispvalue.split('\n')] + msg += '\n ICC Profile:\n{0}'.format('\n'.join(lines)) return msg def write(self, fptr): """Write an Colour Specification box to file. """ - self._write_validate() + if self.colorspace is None: + msg = "Writing Colour Specification boxes without enumerated " + msg += "colorspaces is not supported at this time." + raise NotImplementedError(msg) length = 15 if self.icc_profile is None else 11 + len(self.icc_profile) - fptr.write(struct.pack('>I4s', length, b'colr')) + fptr.write(struct.pack('>I', length)) + fptr.write('colr'.encode()) read_buffer = struct.pack('>BBBI', self.method, @@ -400,8 +250,8 @@ class ColourSpecificationBox(Jp2kBox): self.colorspace) fptr.write(read_buffer) - @classmethod - def parse(cls, fptr, offset, length): + @staticmethod + def parse(fptr, offset, length): """Parse JPEG 2000 color specification box. Parameters @@ -417,40 +267,38 @@ class ColourSpecificationBox(Jp2kBox): ------- ColourSpecificationBox instance """ - num_bytes = offset + length - fptr.tell() - read_buffer = fptr.read(num_bytes) # Read the brand, minor version. - (method, precedence, approximation) = struct.unpack_from('>BBB', - read_buffer, - offset=0) + read_buffer = fptr.read(3) + (method, precedence, approximation) = struct.unpack('>BBB', + read_buffer) if method == 1: # enumerated colour space - colorspace, = struct.unpack_from('>I', read_buffer, offset=3) - if colorspace not in _COLORSPACE_MAP_DISPLAY.keys(): - msg = "Unrecognized colorspace: {0}".format(colorspace) - warnings.warn(msg) + read_buffer = fptr.read(4) + colorspace, = struct.unpack('>I', read_buffer) icc_profile = None else: # ICC profile colorspace = None - if (num_bytes - 3) < 128: + numbytes = offset + length - fptr.tell() + if numbytes < 128: msg = "ICC profile header is corrupt, length is " msg += "only {0} instead of 128." - warnings.warn(msg.format(num_bytes - 3), UserWarning) + warnings.warn(msg.format(numbytes), UserWarning) icc_profile = None else: - profile = _ICCProfile(read_buffer[3:]) + profile = _ICCProfile(fptr.read(numbytes)) icc_profile = profile.header - return cls(method=method, - precedence=precedence, - approximation=approximation, - colorspace=colorspace, - icc_profile=icc_profile, - length=length, - offset=offset) + box = ColourSpecificationBox(method=method, + precedence=precedence, + approximation=approximation, + colorspace=colorspace, + icc_profile=icc_profile, + length=length, + offset=offset) + return box class _ICCProfile(object): @@ -578,52 +426,45 @@ class ChannelDefinitionBox(Jp2kBox): offset of the box from the start of the file. longname : str more verbose description of the box. - index : list + index : int number of the channel. Defaults to monotonically increasing sequence, i.e. [0, 1, 2, ...] - channel_type : list + channel_type : int type of the channel - association : list + association : int index of the associated color """ - box_id = 'cdef' - longname = 'Channel Definition' + def __init__(self, index=None, channel_type=None, association=None, + **kwargs): + Jp2kBox.__init__(self, box_id='cdef', longname='Channel Definition') - def __init__(self, channel_type, association, index=None, **kwargs): - Jp2kBox.__init__(self) + # channel type and association must be specified. + if channel_type is None or association is None: + raise IOError("channel_type and association must be specified.") if index is None: - self.index = tuple(range(len(channel_type))) - else: - self.index = tuple(index) + index = list(range(len(channel_type))) - self.channel_type = tuple(channel_type) - self.association = tuple(association) - self.__dict__.update(**kwargs) - self._validate(writing=False) - - def _validate(self, writing=False): - """Verify that the box obeys the specifications.""" - # channel type and association must be specified. - if not ((len(self.index) == len(self.channel_type)) and - (len(self.channel_type) == len(self.association))): + if len(index) != len(channel_type) or len(index) != len(association): msg = "Length of channel definition box inputs must be the same." - self._dispatch_validation_error(msg, writing=writing) + raise IOError(msg) # channel types must be one of 0, 1, 2, 65535 - if any(x not in [0, 1, 2, 65535] for x in self.channel_type): + if any(x not in [0, 1, 2, 65535] for x in channel_type): msg = "Channel types must be in the set of\n\n" msg += " 0 - colour image data for associated color\n" msg += " 1 - opacity\n" msg += " 2 - premultiplied opacity\n" msg += " 65535 - unspecified" - self._dispatch_validation_error(msg, writing=writing) + raise IOError(msg) + + self.index = index + self.channel_type = channel_type + self.association = association + self.__dict__.update(**kwargs) def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] is True: - return msg - for j in range(len(self.association)): color_type_string = _COLOR_TYPE_MAP_DISPLAY[self.channel_type[j]] if self.association[j] == 0: @@ -634,18 +475,12 @@ class ChannelDefinitionBox(Jp2kBox): msg = msg.format(self.index[j], color_type_string, assn) return msg - def __repr__(self): - msg = "glymur.jp2box.ChannelDefinitionBox(" - msg += "index={0}, channel_type={1}, association={2})" - msg = msg.format(self.index, self.channel_type, self.association) - return msg - def write(self, fptr): """Write a channel definition box to file. """ - self._validate(writing=True) num_components = len(self.association) - fptr.write(struct.pack('>I4s', 8 + 2 + num_components * 6, b'cdef')) + fptr.write(struct.pack('>I', 8 + 2 + num_components * 6)) + fptr.write('cdef'.encode('utf-8')) fptr.write(struct.pack('>H', num_components)) for j in range(num_components): fptr.write(struct.pack('>' + 'H' * 3, @@ -653,8 +488,8 @@ class ChannelDefinitionBox(Jp2kBox): self.channel_type[j], self.association[j])) - @classmethod - def parse(cls, fptr, offset, length): + @staticmethod + def parse(fptr, offset, length): """Parse component definition box. Parameters @@ -670,22 +505,20 @@ class ChannelDefinitionBox(Jp2kBox): ------- ComponentDefinitionBox instance """ - num_bytes = offset + length - fptr.tell() - read_buffer = fptr.read(num_bytes) - # Read the number of components. - num_components, = struct.unpack_from('>H', read_buffer) + read_buffer = fptr.read(2) + num_components, = struct.unpack('>H', read_buffer) - data = struct.unpack_from('>' + 'HHH' * num_components, read_buffer, - offset=2) + read_buffer = fptr.read(num_components * 6) + data = struct.unpack('>' + 'HHH' * num_components, read_buffer) index = data[0:num_components * 6:3] channel_type = data[1:num_components * 6:3] association = data[2:num_components * 6:3] - return cls(index=tuple(index), - channel_type=tuple(channel_type), - association=tuple(association), - length=length, offset=offset) + box = ChannelDefinitionBox(index=index, channel_type=channel_type, + association=association, length=length, + offset=offset) + return box class CodestreamHeaderBox(Jp2kBox): @@ -704,30 +537,24 @@ class CodestreamHeaderBox(Jp2kBox): box : list List of boxes contained in this superbox. """ - box_id = 'jpch' - longname = 'Codestream Header' - - def __init__(self, box=None, length=0, offset=-1): - Jp2kBox.__init__(self) + def __init__(self, length=0, offset=-1): + Jp2kBox.__init__(self, box_id='jpch', longname='Codestream Header') self.length = length self.offset = offset - self.box = box if box is not None else [] - - def __repr__(self): - msg = "glymur.jp2box.CodestreamHeaderBox(box={0})".format(self.box) - return msg + self.box = [] def __str__(self): - msg = self._str_superbox() + msg = Jp2kBox.__str__(self) + for box in self.box: + boxstr = str(box) + + # Add indentation. + strs = [('\n ' + x) for x in boxstr.split('\n')] + msg += ''.join(strs) return msg - def write(self, fptr): - """Write a codestream header box to file. - """ - self._write_superbox(fptr, b'jpch') - - @classmethod - def parse(cls, fptr, offset, length): + @staticmethod + def parse(fptr, offset, length): """Parse codestream header box. Parameters @@ -743,7 +570,7 @@ class CodestreamHeaderBox(Jp2kBox): ------- CodestreamHeaderBox instance """ - box = cls(length=length, offset=offset) + box = CodestreamHeaderBox(length=length, offset=offset) # The codestream header box is a superbox, so go ahead and parse its # child boxes. @@ -752,78 +579,6 @@ class CodestreamHeaderBox(Jp2kBox): return box -class ColourGroupBox(Jp2kBox): - """Container for colour group box information. - - Attributes - ---------- - box_id : str - 4-character identifier for the box. - length : int - length of the box in bytes. - offset : int - offset of the box from the start of the file. - longname : str - more verbose description of the box. - box : list - List of boxes contained in this superbox. - """ - box_id = 'cgrp' - longname = 'Colour Group' - - def __init__(self, box=None, length=0, offset=-1): - Jp2kBox.__init__(self) - self.length = length - self.offset = offset - self.box = box if box is not None else [] - - def __repr__(self): - msg = "glymur.jp2box.ColourGroupBox(box={0})".format(self.box) - return msg - - def __str__(self): - msg = self._str_superbox() - return msg - - def _validate(self, writing=True): - """Verify that the box obeys the specifications.""" - if any([box.box_id != 'colr' for box in self.box]): - msg = "Colour group boxes can only contain colour specification " - msg += "boxes." - self._dispatch_validation_error(msg, writing=writing) - - def write(self, fptr): - """Write a colour group box to file. - """ - self._validate(writing=True) - self._write_superbox(fptr, b'cgrp') - - @classmethod - def parse(cls, fptr, offset, length): - """Parse colour group box. - - Parameters - ---------- - fptr : file - Open file object. - offset : int - Start position of box in bytes. - length : int - Length of the box in bytes. - - Returns - ------- - ColourGroupBox instance - """ - box = cls(length=length, offset=offset) - - # The colour group box is a superbox, so go ahead and parse its - # child boxes. - box.box = box.parse_superbox(fptr) - - return box - - class CompositingLayerHeaderBox(Jp2kBox): """Container for compositing layer header box information. @@ -840,31 +595,25 @@ class CompositingLayerHeaderBox(Jp2kBox): box : list List of boxes contained in this superbox. """ - box_id = 'jplh' - longname = 'Compositing Layer Header' - - def __init__(self, box=None, length=0, offset=-1): - Jp2kBox.__init__(self) + def __init__(self, length=0, offset=-1): + Jp2kBox.__init__(self, box_id='jplh', + longname='Compositing Layer Header') self.length = length self.offset = offset - self.box = box if box is not None else [] - - def __repr__(self): - msg = "glymur.jp2box.CompositingLayerHeaderBox(box={0})" - msg = msg.format(self.box) - return msg + self.box = [] def __str__(self): - msg = self._str_superbox() + msg = Jp2kBox.__str__(self) + for box in self.box: + boxstr = str(box) + + # Add indentation. + strs = [('\n ' + x) for x in boxstr.split('\n')] + msg += ''.join(strs) return msg - def write(self, fptr): - """Write a compositing layer header box to file. - """ - self._write_superbox(fptr, b'jplh') - - @classmethod - def parse(cls, fptr, offset, length): + @staticmethod + def parse(fptr, offset, length): """Parse compositing layer header box. Parameters @@ -880,7 +629,7 @@ class CompositingLayerHeaderBox(Jp2kBox): ------- CompositingLayerHeaderBox instance """ - box = cls(length=length, offset=offset) + box = CompositingLayerHeaderBox(length=length, offset=offset) # This box is a superbox, so go ahead and parse its # child boxes. box.box = box.parse_superbox(fptr) @@ -901,37 +650,24 @@ class ComponentMappingBox(Jp2kBox): Offset of the box from the start of the file. longname : str Verbose description of the box. - component_index : tuple + component_index : int Index of component in codestream that is mapped to this channel. - mapping_type : tuple + mapping_type : int mapping type, either direct use (0) or palette (1) - palette_index : tuple + palette_index : int Index component from palette """ - box_id = 'cmap' - longname = 'Component Mapping' - def __init__(self, component_index, mapping_type, palette_index, length=0, offset=-1): - Jp2kBox.__init__(self) + Jp2kBox.__init__(self, box_id='cmap', longname='Component Mapping') self.component_index = component_index self.mapping_type = mapping_type self.palette_index = palette_index self.length = length self.offset = offset - def __repr__(self): - msg = "glymur.jp2box.ComponentMappingBox(" - msg += "component_index={0}, mapping_type={1}, palette_index={2})" - msg = msg.format(self.component_index, - self.mapping_type, - self.palette_index) - return msg - def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] is True: - return msg for k in range(len(self.component_index)): if self.mapping_type[k] == 1: @@ -939,26 +675,12 @@ class ComponentMappingBox(Jp2kBox): msg = msg.format(self.component_index[k], self.palette_index[k]) else: - msg += '\n Component {0} ==> {1}' + msg += '\n Component %d ==> %d' msg = msg.format(self.component_index[k], k) return msg - def write(self, fptr): - """Write a Component Mapping box to file. - """ - length = 8 + 4 * len(self.component_index) - write_buffer = struct.pack('>I4s', length, b'cmap') - fptr.write(write_buffer) - - for j in range(len(self.component_index)): - write_buffer = struct.pack('>HBB', - self.component_index[j], - self.mapping_type[j], - self.palette_index[j]) - fptr.write(write_buffer) - - @classmethod - def parse(cls, fptr, offset, length): + @staticmethod + def parse(fptr, offset, length): """Parse component mapping box. Parameters @@ -980,12 +702,13 @@ class ComponentMappingBox(Jp2kBox): read_buffer = fptr.read(num_bytes) data = struct.unpack('>' + 'HBB' * num_components, read_buffer) - component_index = data[0:num_bytes:3] - mapping_type = data[1:num_bytes:3] - palette_index = data[2:num_bytes:3] + component_index = data[0:num_bytes:num_components] + mapping_type = data[1:num_bytes:num_components] + palette_index = data[2:num_bytes:num_components] - return cls(component_index, mapping_type, palette_index, - length=length, offset=offset) + box = ComponentMappingBox(component_index, mapping_type, palette_index, + length=length, offset=offset) + return box class ContiguousCodestreamBox(Jp2kBox): @@ -1001,59 +724,28 @@ class ContiguousCodestreamBox(Jp2kBox): offset of the box from the start of the file. longname : str more verbose description of the box. - codestream : Codestream object - Contains list of codestream marker/segments. By default, only the main - header is retrieved. - main_header_offset : int - offset of main header from start of file + main_header : list + List of segments in the codestream header. """ - box_id = 'jp2c' - longname = 'Contiguous Codestream' - - def __init__(self, codestream=None, main_header_offset=None, length=0, - offset=-1): - Jp2kBox.__init__(self) - self._codestream = codestream + def __init__(self, main_header=None, length=0, offset=-1): + Jp2kBox.__init__(self, box_id='jp2c', longname='Contiguous Codestream') + self.main_header = main_header self.length = length self.offset = offset - self.main_header_offset = main_header_offset - - # The filename can be set if lazy loading is desired. - self._filename = None - - @property - def codestream(self): - if _parseoptions['full_codestream'] is True: - header_only = False - else: - header_only = True - if self._codestream is None: - if self._filename is not None: - with open(self._filename, 'rb') as fptr: - fptr.seek(self.main_header_offset) - codestream = Codestream(fptr, self._length, - header_only=header_only) - self._codestream = codestream - return self._codestream - - def __repr__(self): - msg = "glymur.jp2box.ContiguousCodeStreamBox(codestream={0})" - return msg.format(repr(self.codestream)) def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] is True: - return msg - if _printoptions['codestream'] is False: - return msg - - for segment in self.codestream.segment: - msg += '\n' + self._indent(str(segment), indent_level=4) + msg += '\n Main header:' + for segment in self.main_header.segment: + segstr = str(segment) + # Add indentation. + strs = [('\n ' + x) for x in segstr.split('\n')] + msg += ''.join(strs) return msg - @classmethod - def parse(cls, fptr, offset=0, length=0): + @staticmethod + def parse(fptr, offset=0, length=0): """Parse a codestream box. Parameters @@ -1069,144 +761,12 @@ class ContiguousCodestreamBox(Jp2kBox): ------- ContiguousCodestreamBox instance """ - main_header_offset = fptr.tell() - if _parseoptions['full_codestream'] is True: - codestream = Codestream(fptr, length, header_only=False) - else: - codestream = None - box = cls(codestream, main_header_offset=main_header_offset, - length=length, offset=offset) - box._filename = fptr.name - box._length = length + main_header = Codestream(fptr, length, header_only=True) + box = ContiguousCodestreamBox(main_header, length=length, + offset=offset) return box -class DataReferenceBox(Jp2kBox): - """Container for Data Reference box information. - - Attributes - ---------- - box_id : str - 4-character identifier for the box. - length : int - length of the box in bytes. - offset : int - offset of the box from the start of the file. - longname : str - more verbose description of the box. - DR : list - Data Entry URL boxes. - """ - box_id = 'dtbl' - longname = 'Data Reference' - - def __init__(self, data_entry_url_boxes=None, length=0, offset=-1): - Jp2kBox.__init__(self) - if data_entry_url_boxes is None: - self.DR = [] - else: - self.DR = data_entry_url_boxes - self.length = length - self.offset = offset - self._validate(writing=False) - - def _validate(self, writing=False): - """Verify that the box obeys the specifications.""" - for box in self.DR: - if box.box_id != 'url ': - msg = 'All child boxes of a data reference box must be data ' - msg += 'entry URL boxes.' - self._dispatch_validation_error(msg, writing=writing) - - def _write_validate(self): - """Verify that the box obeys the specifications for writing. - """ - if len(self.DR) == 0: - msg = "A data reference box cannot be empty when written to a " - msg += "file." - self._dispatch_validation_error(msg, writing=True) - self._validate(writing=True) - - def write(self, fptr): - """Write a Data Reference box to file. - """ - self._write_validate() - - # Very similar to the way a superbox is written. - orig_pos = fptr.tell() - fptr.write(struct.pack('>I4s', 0, b'dtbl')) - - # Write the number of data entry url boxes. - write_buffer = struct.pack('>H', len(self.DR)) - fptr.write(write_buffer) - - for box in self.DR: - box.write(fptr) - - end_pos = fptr.tell() - fptr.seek(orig_pos) - fptr.write(struct.pack('>I', end_pos - orig_pos)) - fptr.seek(end_pos) - - def __str__(self): - msg = Jp2kBox.__str__(self) - if _printoptions['short'] is True: - return msg - - for box in self.DR: - msg += '\n ' + str(box) - return msg - - def __repr__(self): - msg = 'glymur.jp2box.DataReferenceBox()' - return msg - - @classmethod - def parse(cls, fptr, offset, length): - """Parse data reference box. - - Parameters - ---------- - fptr : file - Open file object. - offset : int - Start position of box in bytes. - length : int - Length of the box in bytes. - - Returns - ------- - DataReferenceBox instance - """ - num_bytes = offset + length - fptr.tell() - read_buffer = fptr.read(num_bytes) - - # Read the number of data references - ndr, = struct.unpack_from('>H', read_buffer, offset=0) - - # Need to keep track of where the next url box starts. - box_offset = 2 - - data_entry_url_box_list = [] - for j in range(ndr): - - # Create an in-memory binary stream for each URL box. - box_fptr = io.BytesIO(read_buffer[box_offset:]) - box_buffer = box_fptr.read(8) - (box_length, box_id) = struct.unpack_from('>I4s', box_buffer, - offset=0) - box = DataEntryURLBox.parse(box_fptr, 0, box_length) - - # Need to adjust the box start to that of the "real" file. - box.offset = offset + 8 + box_offset - data_entry_url_box_list.append(box) - - # Point to the next embedded URL box. - box_offset += box_length - - return cls(data_entry_url_box_list, length=length, offset=offset) - - class FileTypeBox(Jp2kBox): """Container for JPEG 2000 file type box information. @@ -1228,35 +788,21 @@ class FileTypeBox(Jp2kBox): compatibility_list: list List of file conformance profiles. """ - box_id = 'ftyp' - longname = 'File Type' - def __init__(self, brand='jp2 ', minor_version=0, compatibility_list=None, length=0, offset=-1): - Jp2kBox.__init__(self) + Jp2kBox.__init__(self, box_id='ftyp', longname='File Type') self.brand = brand self.minor_version = minor_version if compatibility_list is None: + # see W0102, pylint self.compatibility_list = ['jp2 '] else: self.compatibility_list = compatibility_list self.length = length self.offset = offset - self._validate(writing=False) - - def __repr__(self): - msg = "glymur.jp2box.FileTypeBox(brand='{0}', minor_version={1}, " - msg += "compatibility_list={2})" - msg = msg.format(self.brand, self.minor_version, - self.compatibility_list) - return msg def __str__(self): - msg = Jp2kBox.__str__(self) - if _printoptions['short'] is True: - return msg - - lst = [msg, + lst = [Jp2kBox.__str__(self), ' Brand: {0}', ' Compatibility: {1}'] msg = '\n'.join(lst) @@ -1264,35 +810,20 @@ class FileTypeBox(Jp2kBox): return msg - def _validate(self, writing=False): - """Validate the box before writing to file.""" - if self.brand not in ['jp2 ', 'jpx ']: - msg = "The file type brand was '{0}'. " - msg += "It should be either 'jp2 ' or 'jpx '." - self._dispatch_validation_error(msg.format(self.brand), - writing=writing) - valid_cls = ['jp2 ', 'jpx ', 'jpxb'] - for item in self.compatibility_list: - if item not in valid_cls: - msg = "The file type compatibility list item '{0}' is not " - msg += "valid: valid entries are {1}" - msg = msg.format(item, valid_cls) - self._dispatch_validation_error(msg, writing=writing) - def write(self, fptr): """Write a File Type box to file. """ - self._validate(writing=True) length = 16 + 4*len(self.compatibility_list) - fptr.write(struct.pack('>I4s', length, b'ftyp')) + fptr.write(struct.pack('>I', length)) + fptr.write('ftyp'.encode()) fptr.write(self.brand.encode()) fptr.write(struct.pack('>I', self.minor_version)) for item in self.compatibility_list: fptr.write(item.encode()) - @classmethod - def parse(cls, fptr, offset, length): + @staticmethod + def parse(fptr, offset, length): """Parse JPEG 2000 file type box. Parameters @@ -1308,269 +839,30 @@ class FileTypeBox(Jp2kBox): ------- FileTypeBox instance """ - num_bytes = offset + length - fptr.tell() - read_buffer = fptr.read(num_bytes) - # Extract the brand, minor version. - (brand, minor_version) = struct.unpack_from('>4sI', read_buffer, 0) + # Read the brand, minor version. + read_buffer = fptr.read(8) + (brand, minor_version) = struct.unpack('>4sI', read_buffer) if sys.hexversion >= 0x030000: brand = brand.decode('utf-8') - # Extract the compatibility list. Each entry has 4 bytes. - num_entries = int((length - 16) / 4) + # Read the compatibility list. Each entry has 4 bytes. + current_pos = fptr.tell() + num_bytes = (offset + length - current_pos) / 4 + read_buffer = fptr.read(int(num_bytes) * 4) compatibility_list = [] - for j in range(int(num_entries)): - entry, = struct.unpack_from('>4s', read_buffer, 8 + j * 4) + for j in range(int(num_bytes)): + entry, = struct.unpack('>4s', read_buffer[4*j:4*(j+1)]) if sys.hexversion >= 0x03000000: - try: - entry = entry.decode('utf-8') - except UnicodeDecodeError: - # The entry is invalid, but we've got code to catch this - # later on. - pass - + entry = entry.decode('utf-8') compatibility_list.append(entry) - return cls(brand=brand, minor_version=minor_version, - compatibility_list=compatibility_list, - length=length, offset=offset) - - -class FragmentListBox(Jp2kBox): - """Container for JPX fragment list box information. - - Attributes - ---------- - box_id : str - 4-character identifier for the box. - length : int - length of the box in bytes. - offset : int - offset of the box from the start of the file. - longname : str - more verbose description of the box. - """ - box_id = 'flst' - longname = 'Fragment List' - - def __init__(self, fragment_offset, fragment_length, data_reference, - length=0, offset=-1): - Jp2kBox.__init__(self) - self.fragment_offset = fragment_offset - self.fragment_length = fragment_length - self.data_reference = data_reference - self.length = length - self.offset = offset - self._validate(writing=False) - - def _validate(self, writing=False): - """Validate internal correctness.""" - if (((len(self.fragment_offset) != len(self.fragment_length)) or - (len(self.fragment_length) != len(self.data_reference)))): - msg = "The lengths of the fragment offsets, fragment lengths, and " - msg += "data reference items must be the same." - self._dispatch_validation_error(msg, writing=writing) - if any([x <= 0 for x in self.fragment_offset]): - msg = "Fragment offsets must all be positive." - self._dispatch_validation_error(msg, writing=writing) - if any([x <= 0 for x in self.fragment_length]): - msg = "Fragment lengths must all be positive." - self._dispatch_validation_error(msg, writing=writing) - - def __repr__(self): - msg = "glymur.jp2box.FragmentListBox({0}, {1}, {2})" - msg = msg.format(str(self.fragment_offset), - str(self.fragment_length), - str(self.data_reference)) - return msg - - def __str__(self): - msg = Jp2kBox.__str__(self) - if _printoptions['short'] is True: - return msg - - for j in range(len(self.fragment_offset)): - msg += "\n Offset {0}: {1}" - msg += "\n Fragment Length {2}: {3}" - msg += "\n Data Reference {4}: {5}" - msg = msg.format(j, self.fragment_offset[j], - j, self.fragment_length[j], - j, self.data_reference[j]) - - return msg - - def write(self, fptr): - """Write a fragment list box to file. - """ - self._validate(writing=True) - num_items = len(self.fragment_offset) - length = 8 + 2 + num_items * 14 - fptr.write(struct.pack('>I4s', length, b'flst')) - fptr.write(struct.pack('>H', num_items)) - for j in range(num_items): - write_buffer = struct.pack('>QIH', - self.fragment_offset[j], - self.fragment_length[j], - self.data_reference[j]) - fptr.write(write_buffer) - - @classmethod - def parse(cls, fptr, offset, length): - """Parse JPX free box. - - Parameters - ---------- - f : file - Open file object. - offset : int - Start position of box in bytes. - length : int - Length of the box in bytes. - - Returns - ------- - FragmentListBox instance - """ - num_bytes = offset + length - fptr.tell() - read_buffer = fptr.read(num_bytes) - num_fragments, = struct.unpack_from('>H', read_buffer, offset=0) - - lst = struct.unpack_from('>' + 'QIH' * num_fragments, - read_buffer, - offset=2) - frag_offset = lst[0::3] - frag_len = lst[1::3] - data_reference = lst[2::3] - return cls(frag_offset, frag_len, data_reference, - length=length, offset=offset) - - -class FragmentTableBox(Jp2kBox): - """Container for JPX fragment table box information. - - Attributes - ---------- - box_id : str - 4-character identifier for the box. - length : int - length of the box in bytes. - offset : int - offset of the box from the start of the file. - longname : str - more verbose description of the box. - """ - box_id = 'ftbl' - longname = 'Fragment Table' - - def __init__(self, box=None, length=0, offset=-1): - Jp2kBox.__init__(self) - self.length = length - self.offset = offset - self.box = box if box is not None else [] - - def __repr__(self): - msg = "glymur.jp2box.FragmentTableBox(box={0})" - if len(self.box) == 0: - msg = msg.format(None) - else: - msg = msg.format(self.box) - return msg - - def __str__(self): - msg = self._str_superbox() - return msg - - @classmethod - def parse(cls, fptr, offset, length): - """Parse JPX fragment table superbox box. - - Parameters - ---------- - f : file - Open file object. - offset : int - Start position of box in bytes. - length : int - Length of the box in bytes. - - Returns - ------- - FragmentTableBox instance - """ - box = cls(length=length, offset=offset) - - # The FragmentTable box is a superbox, so go ahead and parse its child - # boxes. - box.box = box.parse_superbox(fptr) + compatibility_list = compatibility_list + box = FileTypeBox(brand=brand, minor_version=minor_version, + compatibility_list=compatibility_list, + length=length, offset=offset) return box - def _validate(self, writing=False): - """Self-validate the box before writing.""" - box_ids = [box.box_id for box in self.box] - if len(box_ids) != 1 or box_ids[0] != 'flst': - msg = "Fragment table boxes must have a single fragment list " - msg += "box as a child box." - self._dispatch_validation_error(msg, writing=writing) - - def write(self, fptr): - """Write a fragment table box to file. - """ - self._validate(writing=True) - self._write_superbox(fptr, b'ftbl') - - -class FreeBox(Jp2kBox): - """Container for JPX free box information. - - Attributes - ---------- - box_id : str - 4-character identifier for the box. - length : int - length of the box in bytes. - offset : int - offset of the box from the start of the file. - longname : str - more verbose description of the box. - """ - box_id = 'free' - longname = 'Free' - - def __init__(self, length=0, offset=-1): - Jp2kBox.__init__(self) - self.length = length - self.offset = offset - - def __repr__(self): - msg = "glymur.jp2box.FreeBox()" - return msg - - def __str__(self): - msg = Jp2kBox.__str__(self) - if _printoptions['short'] is True: - return msg - - return msg - - @classmethod - def parse(cls, fptr, offset, length): - """Parse JPX free box. - - Parameters - ---------- - f : file - Open file object. - offset : int - Start position of box in bytes. - length : int - Length of the box in bytes. - - Returns - ------- - FreeBox instance - """ - return cls(length=length, offset=offset) - class ImageHeaderBox(Jp2kBox): """Container for JPEG 2000 image header box information. @@ -1601,9 +893,6 @@ class ImageHeaderBox(Jp2kBox): False if the file does not contain intellectual propery rights information. """ - box_id = 'ihdr' - longname = 'Image Header' - def __init__(self, height, width, num_components=1, signed=False, bits_per_component=8, compression=7, colorspace_unknown=False, ip_provided=False, length=0, offset=-1): @@ -1613,7 +902,7 @@ class ImageHeaderBox(Jp2kBox): >>> import glymur >>> box = glymur.jp2box.ImageHeaderBox(height=512, width=256) """ - Jp2kBox.__init__(self) + Jp2kBox.__init__(self, box_id='ihdr', longname='Image Header') self.height = height self.width = width self.num_components = num_components @@ -1625,27 +914,8 @@ class ImageHeaderBox(Jp2kBox): self.length = length self.offset = offset - def __repr__(self): - msg = "glymur.jp2box.ImageHeaderBox(" - msg += "{height}, {width}, num_components={num_components}, " - msg += "signed={signed}, bits_per_component={bits_per_component}, " - msg += "compression={compression}, " - msg += "colorspace_unknown={colorspace_unknown}, " - msg += "ip_provided={ip_provided})" - msg = msg.format(height=self.height, width=self.width, - num_components=self.num_components, - signed=self.signed, - bits_per_component=self.bits_per_component, - compression=self.compression, - colorspace_unknown=self.colorspace_unknown, - ip_provided=self.ip_provided) - return msg - def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] is True: - return msg - msg = "{0}" msg += '\n Size: [{1} {2} {3}]' msg += '\n Bitdepth: {4}' @@ -1663,7 +933,8 @@ class ImageHeaderBox(Jp2kBox): def write(self, fptr): """Write an Image Header box to file. """ - fptr.write(struct.pack('>I4s', 22, b'ihdr')) + fptr.write(struct.pack('>I', 22)) + fptr.write('ihdr'.encode()) # signedness and bps are stored together in a single byte bit_depth_signedness = 0x80 if self.signed else 0x00 @@ -1678,8 +949,8 @@ class ImageHeaderBox(Jp2kBox): 1 if self.ip_provided else 0) fptr.write(read_buffer) - @classmethod - def parse(cls, fptr, offset, length): + @staticmethod + def parse(fptr, offset, length): """Parse JPEG 2000 image header box. Parameters @@ -1707,13 +978,14 @@ class ImageHeaderBox(Jp2kBox): colorspace_unknown = True if params[5] else False ip_provided = True if params[6] else False - return cls(height, width, num_components=num_components, - bits_per_component=bits_per_component, - signed=signed, - compression=compression, - colorspace_unknown=colorspace_unknown, - ip_provided=ip_provided, - length=length, offset=offset) + box = ImageHeaderBox(height, width, num_components=num_components, + bits_per_component=bits_per_component, + signed=signed, + compression=compression, + colorspace_unknown=colorspace_unknown, + ip_provided=ip_provided, + length=length, offset=offset) + return box class AssociationBox(Jp2kBox): @@ -1732,25 +1004,24 @@ class AssociationBox(Jp2kBox): box : list List of boxes contained in this superbox. """ - box_id = 'asoc' - longname = 'Association' - - def __init__(self, box=None, length=0, offset=-1): - Jp2kBox.__init__(self) + def __init__(self, length=0, offset=-1): + Jp2kBox.__init__(self, box_id='asoc', longname='Association') self.length = length self.offset = offset - self.box = box if box is not None else [] - - def __repr__(self): - msg = "glymur.jp2box.AssociationBox(box={0})".format(self.box) - return msg + self.box = [] def __str__(self): - msg = self._str_superbox() + msg = Jp2kBox.__str__(self) + for box in self.box: + boxstr = str(box) + + # Add indentation. + strs = [('\n ' + x) for x in boxstr.split('\n')] + msg += ''.join(strs) return msg - @classmethod - def parse(cls, fptr, offset, length): + @staticmethod + def parse(fptr, offset, length): """Parse association box. Parameters @@ -1766,7 +1037,7 @@ class AssociationBox(Jp2kBox): ------- AssociationBox instance """ - box = cls(length=length, offset=offset) + box = AssociationBox(length=length, offset=offset) # The Association box is a superbox, so go ahead and parse its child # boxes. @@ -1774,11 +1045,6 @@ class AssociationBox(Jp2kBox): return box - def write(self, fptr): - """Write an association box to file. - """ - self._write_superbox(fptr, b'asoc') - class JP2HeaderBox(Jp2kBox): """Container for JP2 header box information. @@ -1796,30 +1062,39 @@ class JP2HeaderBox(Jp2kBox): box : list List of boxes contained in this superbox. """ - box_id = 'jp2h' - longname = 'JP2 Header' - - def __init__(self, box=None, length=0, offset=-1): - Jp2kBox.__init__(self) + def __init__(self, length=0, offset=-1): + Jp2kBox.__init__(self, box_id='jp2h', longname='JP2 Header') self.length = length self.offset = offset - self.box = box if box is not None else [] - - def __repr__(self): - msg = "glymur.jp2box.JP2HeaderBox(box={0})".format(self.box) - return msg + self.box = [] def __str__(self): - msg = self._str_superbox() + msg = Jp2kBox.__str__(self) + for box in self.box: + boxstr = str(box) + + # Add indentation. + strs = [('\n ' + x) for x in boxstr.split('\n')] + msg += ''.join(strs) return msg def write(self, fptr): """Write a JP2 Header box to file. """ - self._write_superbox(fptr, b'jp2h') + # Write the contained boxes, then come back and write the length. + orig_pos = fptr.tell() + fptr.write(struct.pack('>I', 0)) + fptr.write('jp2h'.encode()) + for box in self.box: + box.write(fptr) - @classmethod - def parse(cls, fptr, offset, length): + end_pos = fptr.tell() + fptr.seek(orig_pos) + fptr.write(struct.pack('>I', end_pos - orig_pos)) + fptr.seek(end_pos) + + @staticmethod + def parse(fptr, offset, length): """Parse JPEG 2000 header box. Parameters @@ -1835,7 +1110,7 @@ class JP2HeaderBox(Jp2kBox): ------- JP2HeaderBox instance """ - box = cls(length=length, offset=offset) + box = JP2HeaderBox(length=length, offset=offset) # The JP2 header box is a superbox, so go ahead and parse its child # boxes. @@ -1857,26 +1132,17 @@ class JPEG2000SignatureBox(Jp2kBox): offset of the box from the start of the file. longname : str more verbose description of the box. - signature : tuple + signature : byte Four-byte tuple identifying the file as JPEG 2000. """ - box_id = 'jP ' - longname = 'JPEG 2000 Signature' - def __init__(self, signature=(13, 10, 135, 10), length=0, offset=-1): - Jp2kBox.__init__(self) + Jp2kBox.__init__(self, box_id='jP ', longname='JPEG 2000 Signature') self.signature = signature self.length = length self.offset = offset - def __repr__(self): - return 'glymur.jp2box.JPEG2000SignatureBox()' - def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] is True: - return msg - msg += '\n Signature: {0:02x}{1:02x}{2:02x}{3:02x}' msg = msg.format(self.signature[0], self.signature[1], self.signature[2], self.signature[3]) @@ -1885,11 +1151,12 @@ class JPEG2000SignatureBox(Jp2kBox): def write(self, fptr): """Write a JPEG 2000 Signature box to file. """ - fptr.write(struct.pack('>I4s', 12, b'jP ')) + fptr.write(struct.pack('>I', 12)) + fptr.write(self.box_id.encode()) fptr.write(struct.pack('>BBBB', *self.signature)) - @classmethod - def parse(cls, fptr, offset, length): + @staticmethod + def parse(fptr, offset, length): """Parse JPEG 2000 signature box. Parameters @@ -1908,7 +1175,9 @@ class JPEG2000SignatureBox(Jp2kBox): read_buffer = fptr.read(4) signature = struct.unpack('>BBBB', read_buffer) - return cls(signature=signature, length=length, offset=offset) + box = JPEG2000SignatureBox(signature=signature, length=length, + offset=offset) + return box class PaletteBox(Jp2kBox): @@ -1924,86 +1193,26 @@ class PaletteBox(Jp2kBox): offset of the box from the start of the file. longname : str more verbose description of the box. - palette : ndarray - Colormap array. + palette : list + Colormap represented as list of 1D arrays, one per color component. """ - longname = 'Palette' - box_id = 'pclr' - def __init__(self, palette, bits_per_component, signed, length=0, offset=-1): - Jp2kBox.__init__(self) + Jp2kBox.__init__(self, box_id='pclr', longname='Palette') self.palette = palette self.bits_per_component = bits_per_component self.signed = signed self.length = length self.offset = offset - self._validate(writing=False) - - def _validate(self, writing=False): - """Verify that the box obeys the specifications.""" - if ((len(self.bits_per_component) != len(self.signed)) or - (len(self.signed) != self.palette.shape[1])): - msg = "The length of the 'bits_per_component' and the 'signed' " - msg += "members must equal the number of columns of the palette." - self._dispatch_validation_error(msg, writing=writing) - bps = self.bits_per_component - if writing and not all(b == bps[0] for b in bps): - # We don't support writing palettes with bit depths that are - # different. - msg = "Writing palettes with varying bit depths is not supported." - self._dispatch_validation_error(msg, writing=writing) - - def __repr__(self): - msg = "glymur.jp2box.PaletteBox({0}, bits_per_component={1}, " - msg += "signed={2})" - msg = msg.format(repr(self.palette), self.bits_per_component, - self.signed) - return msg def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] is True: - return msg - - msg += '\n Size: ({0} x {1})'.format(*self.palette.shape) + msg += '\n Size: ({0} x {1})'.format(len(self.palette[0]), + len(self.palette)) return msg - def write(self, fptr): - """Write a Palette box to file. - """ - self._validate(writing=True) - bytes_per_row = sum(self.bits_per_component) / 8 - bytes_per_palette = bytes_per_row * self.palette.shape[0] - box_length = 8 + 3 + self.palette.shape[1] + bytes_per_palette - - # Write the usual header. - write_buffer = struct.pack('>I4s', int(box_length), b'pclr') - fptr.write(write_buffer) - - write_buffer = struct.pack('>HB', self.palette.shape[0], - self.palette.shape[1]) - fptr.write(write_buffer) - - bps_signed = [x - 1 for x in self.bits_per_component] - for j, _ in enumerate(bps_signed): - if self.signed[j]: - bps_signed[j] |= 0x80 - write_buffer = struct.pack('>' + 'B' * self.palette.shape[1], - *bps_signed) - fptr.write(write_buffer) - - # All components are the same. Writing is straightforward. - if self.bits_per_component[0] <= 8: - write_buffer = memoryview(self.palette.astype(np.uint8)) - elif self.bits_per_component[0] <= 16: - write_buffer = memoryview(self.palette.astype(np.uint16)) - elif self.bits_per_component[0] <= 32: - write_buffer = memoryview(self.palette.astype(np.uint32)) - fptr.write(write_buffer) - - @classmethod - def parse(cls, fptr, offset, length): + @staticmethod + def parse(fptr, offset, length): """Parse palette box. Parameters @@ -2019,140 +1228,162 @@ class PaletteBox(Jp2kBox): ------- PaletteBox instance """ - num_bytes = offset + length - fptr.tell() - read_buffer = fptr.read(num_bytes) - nrows, ncols = struct.unpack_from('>HB', read_buffer, offset=0) + # Get the size of the palette. + read_buffer = fptr.read(3) + (num_entries, num_columns) = struct.unpack('>HB', read_buffer) - bps_signed = struct.unpack_from('>' + 'B' * ncols, read_buffer, - offset=3) - bps = [((x & 0x7f) + 1) for x in bps_signed] - signed = [((x & 0x80) > 1) for x in bps_signed] + # Need to determine bps and signed or not + read_buffer = fptr.read(num_columns) + data = struct.unpack('>' + 'B' * num_columns, read_buffer) + bps = [((x & 0x07f) + 1) for x in data] + signed = [((x & 0x80) > 1) for x in data] - if all(b == bps_signed[0] for b in bps_signed): - # Ok the palette has the same datatype for all columns. We should - # be able to efficiently read it. - if bps[0] <= 8: - dtype = np.uint8 - elif bps[0] <= 16: - dtype = np.uint16 - elif bps[0] <= 32: - dtype = np.uint32 + # Each palette component is padded out to the next largest byte. + # That means a list comprehension does this in one shot. + row_nbytes = sum([int(math.ceil(x/8.0)) for x in bps]) - palette = np.frombuffer(read_buffer[3 + ncols:], dtype=dtype) - palette = np.reshape(palette, (nrows, ncols)) + # Form the format string so that we can intelligently unpack the + # colormap. We have to do this because it is possible that the + # colormap columns could have different datatypes. + # + # This means that we store the palette as a list of 1D arrays, + # which reverses the usual indexing scheme. + read_buffer = fptr.read(num_entries * row_nbytes) + palette = _buffer2palette(read_buffer, num_entries, num_columns, bps) + box = PaletteBox(palette, bps, signed, length=length, offset=offset) + return box + + +def _buffer2palette(read_buffer, num_rows, num_cols, bps): + """Construct the palette from the buffer read from file. + + Parameters + ---------- + read_buffer : iterable + Byte array of palette information read from file. + num_rows, num_cols : int + Size of palette. + bps : iterable + Bits per sample for each channel. + + Returns + ------- + palette : list of 1D arrays + Each 1D array corresponds to a channel. + """ + row_nbytes = 0 + palette = [] + fmt = '>' + for j in range(num_cols): + if bps[j] <= 8: + row_nbytes += 1 + fmt += 'B' + palette.append(np.zeros(num_rows, dtype=np.uint8)) + elif bps[j] <= 16: + row_nbytes += 2 + fmt += 'H' + palette.append(np.zeros(num_rows, dtype=np.uint16)) + elif bps[j] <= 32: + row_nbytes += 4 + fmt += 'I' + palette.append(np.zeros(num_rows, dtype=np.uint32)) else: - # General case where the columns may not be the same width. - fmt = '>' - for bits in bps: - if bits <= 8: - fmt += 'B' - elif bits <= 16: - fmt += 'H' - elif bits <= 32: - fmt += 'I' + msg = 'Unsupported palette bitdepth (%d).'.format(bps[j]) + raise IOError(msg) - # Each palette component is padded out to the next largest byte. - # That means a list comprehension does this in one shot. - row_nbytes = sum([int(math.ceil(x/8.0)) for x in bps]) - - palette = np.zeros((nrows, ncols), dtype=np.int32) - for j in range(nrows): - poff = 3 + ncols + j * row_nbytes - palette[j] = struct.unpack_from(fmt, read_buffer, offset=poff) - - return cls(palette, bps, signed, length=length, offset=offset) + for j in range(num_rows): + row_buffer = read_buffer[(row_nbytes * j):(row_nbytes * (j + 1))] + row = struct.unpack(fmt, row_buffer) + for k in range(num_cols): + palette[k][j] = row[k] + return palette # Map rreq codes to display text. _READER_REQUIREMENTS_DISPLAY = { 0: 'File not completely understood', - 1: 'Deprecated - contains no extensions', + 1: 'Deprecated', 2: 'Contains multiple composition layers', - 3: 'Deprecated - codestream is compressed using JPEG 2000 and requires ' - + 'at least a Profile 0 decoder as defind in ITU-T Rec. T.800 ' - + '| ISO/IEC 15444-1, A.10 Table A.45', + 3: 'Deprecated', 4: 'JPEG 2000 Part 1 Profile 1 codestream', 5: 'Unrestricted JPEG 2000 Part 1 codestream, ITU-T Rec. T.800 ' + '| ISO/IEC 15444-1', 6: 'Unrestricted JPEG 2000 Part 2 codestream', 7: 'JPEG codestream as defined in ISO/IEC 10918-1', - 8: 'Deprecated - does not contain opacity', + 8: 'Deprecated', 9: 'Non-premultiplied opacity channel', - 10: 'Premultiplied opacity channel', - 11: 'Chroma-key based opacity', - 12: 'Deprecated - codestream is contiguous', - 13: 'Fragmented codestream where all fragments are in file and in order', - 14: ('Fragmented codestream where all fragments are in file ' - 'but are out of order'), - 15: ('Fragmented codestream where not all fragments are within the file ' - 'but are all in locally accessible files'), - 16: ('Fragmented codestream where some fragments may be accessible ' - 'only through a URL specified network connection'), - 17: ('Compositing required to produce rendered result from multiple ' - 'compositing layers'), - 18: 'Deprecated - support for compositing is not required', - 19: ('Deprecated - contains multiple, discrete layers that should not ' - 'be combined through either animation or compositing'), - 20: ('Deprecated - compositing layers each contain only a single ' - 'codestream'), - 21: 'At least one compositing layer consists of multiple codestreams', - 22: 'Deprecated - all compositing layers are in the same colourspace', - 23: ('Colourspace transformations are required to combine compositing ' - 'layers; not all compositing layers are in the same colourspace'), - 24: 'Deprecated - rendered result created without using animation', - 25: ('Deprecated - animated, but first layer covers entire area and is ' - 'opaque'), - 26: 'First animation layer does not cover entire rendered result', - 27: 'Deprecated - animated, and no layer is reused', - 28: 'Reuse of animation layers', - 29: 'Deprecated - animated, but layers are reused', - 30: 'Some animated frames are non-persistent', - 31: 'Deprecated - rendered result created without using scaling', - 32: 'Rendered result involves scaling within a layer', - 33: 'Rendered result involves scaling between layers', - 34: 'ROI metadata', - 35: 'IPR metadata', - 36: 'Content metadata', - 37: 'History metadata', - 38: 'Creation metadata', - 39: 'JPX digital signatures', - 40: 'JPX checksums', - 41: 'Desires Graphics Arts Reproduction specified', - 42: 'Deprecated - compositing layer uses palettized colour', - 43: 'Deprecated - compositing layer uses restricted ICC profile', - 44: 'Compositing layer uses Any ICC profile', - 45: 'Deprecated - compositing layer uses sRGB enumerated colourspace', - 46: 'Deprecated - compositing layer uses sRGB-grey enumerated colourspace', - 47: 'BiLevel 1 enumerated colourspace', - 48: 'BiLevel 2 enumerated colourspace', - 49: 'YCbCr 1 enumerated colourspace', - 50: 'YCbCr 2 enumerated colourspace', - 51: 'YCbCr 3 enumerated colourspace', - 52: 'PhotoYCC enumerated colourspace', - 53: 'YCCK enumerated colourspace', - 54: 'CMY enumerated colourspace', - 55: 'CMYK enumerated colorspace', - 56: 'CIELab enumerated colourspace with default parameters', - 57: 'CIELab enumerated colourspace with non-default parameters', - 58: 'CIEJab enumerated colourspace with default parameters', - 59: 'CIEJab enumerated colourspace with non-default parameters', - 60: 'e-sRGB enumerated colorspace', - 61: 'ROMM_RGB enumerated colorspace', - 62: 'Non-square samples', - 63: 'Deprecated - compositing layers have labels', - 64: 'Deprecated - codestreams have labels', - 65: 'Deprecated - compositing layers have different colour spaces', - 66: 'Deprecated - compositing layers have different metadata', - 67: 'GIS metadata XML box', - 68: 'JPSEC extensions in codestream as specified by ISO/IEC 15444-8', - 69: 'JP3D extensions in codestream as specified by ISO/IEC 15444-10', - 70: 'Deprecated - compositing layer uses sYCC enumerated colour space', - 71: 'e-sYCC enumerated colourspace', - 72: ('JPEG 2000 Part 2 codestream as restricted by baseline conformance ' - 'requirements in M.9.2.3'), - 73: 'YPbPr(1125/60) enumerated colourspace', - 74: 'YPbPr(1250/50) enumerated colourspace'} + 10: 'Premultiplied opacity channel', + 11: 'Chroma-key based opacity', + 12: 'Deprecated', + 13: 'Fragmented codestream where all fragments are in file and in order', + 14: 'Fragmented codestream where all fragments are in file ' + + 'but are out of order', + 15: 'Fragmented codestream where not all fragments are within the file ' + + 'but are all in locally accessible files', + 16: 'Fragmented codestream where some fragments may be accessible ' + + 'only through a URL specified network connection', + 17: 'Compositing required to produce rendered result from multiple ' + + 'compositing layers', + 18: 'Deprecated', + 19: 'Deprecated', + 20: 'Deprecated', + 21: 'At least one compositing layer consists of multiple codestreams', + 22: 'Deprecated', + 23: 'Colourspace transformations are required to combine compositing ' + + 'layers; not all compositing layers are in the same colourspace', + 24: 'Deprecated', + 25: 'Deprecated', + 26: 'First animation layer does not cover entire rendered result', + 27: 'Deprecated', + 28: 'Reuse of animation layers', + 29: 'Deprecated', + 30: 'Some animated frames are non-persistent', + 31: 'Deprecated', + 32: 'Rendered result involves scaling within a layer', + 33: 'Rendered result involves scaling between layers', + 34: 'ROI metadata', + 35: 'IPR metadata', + 36: 'Content metadata', + 37: 'History metadata', + 38: 'Creation metadata', + 39: 'JPX digital signatures', + 40: 'JPX checksums', + 41: 'Desires Graphics Arts Reproduction specified', + 42: 'Deprecated', + 43: '(Deprecated) compositing layer uses restricted ICC profile', + 44: 'Compositing layer uses Any ICC profile', + 45: 'Deprecated', + 46: 'Deprecated', + 47: 'BiLevel 1 enumerated colourspace', + 48: 'BiLevel 2 enumerated colourspace', + 49: 'YCbCr 1 enumerated colourspace', + 50: 'YCbCr 2 enumerated colourspace', + 51: 'YCbCr 3 enumerated colourspace', + 52: 'PhotoYCC enumerated colourspace', + 53: 'YCCK enumerated colourspace', + 54: 'CMY enumerated colourspace', + 55: 'CMYK enumerated colorspace', + 56: 'CIELab enumerated colourspace with default parameters', + 57: 'CIELab enumerated colourspace with non-default parameters', + 58: 'CIEJab enumerated colourspace with default parameters', + 59: 'CIEJab enumerated colourspace with non-default parameters', + 60: 'e-sRGB enumerated colorspace', + 61: 'ROMM_RGB enumerated colorspace', + 62: 'Non-square samples', + 63: 'Deprecated', + 64: 'Deprecated', + 65: 'Deprecated', + 66: 'Deprecated', + 67: 'GIS metadata XML box', + 68: 'JPSEC extensions in codestream as specified by ISO/IEC 15444-8', + 69: 'JP3D extensions in codestream as specified by ISO/IEC 15444-10', + 70: 'Deprecated', + 71: 'e-sYCC enumerated colourspace', + 72: 'JPEG 2000 Part 2 codestream as restricted by baseline conformance ' + + 'requirements in M.9.2.3', + 73: 'YPbPr(1125/60) enumerated colourspace', + 74: 'YPbPr(1250/50) enumerated colourspace'} class ReaderRequirementsBox(Jp2kBox): @@ -2183,47 +1414,26 @@ class ReaderRequirementsBox(Jp2kBox): Specifies the compatibility mask for each corresponding vendor feature. """ - box_id = 'rreq' - longname = 'Reader Requirements' - def __init__(self, fuam, dcm, standard_flag, standard_mask, vendor_feature, vendor_mask, length=0, offset=-1): - Jp2kBox.__init__(self) + Jp2kBox.__init__(self, box_id='rreq', longname='Reader Requirements') self.fuam = fuam self.dcm = dcm - self.standard_flag = tuple(standard_flag) - self.standard_mask = tuple(standard_mask) - self.vendor_feature = tuple(vendor_feature) - self.vendor_mask = tuple(vendor_mask) + self.standard_flag = standard_flag + self.standard_mask = standard_mask + self.vendor_feature = vendor_feature + self.vendor_mask = vendor_mask self.length = length self.offset = offset - def __repr__(self): - msg = "glymur.jp2box.ReaderRequirementsBox(fuam={fuam}, dcm={dcm}, " - msg += "standard_flag={standard_flag}, standard_mask={standard_mask}, " - msg += "vendor_feature={vendor_feature}, vendor_mask={vendor_mask})" - msg = msg.format(fuam=self.fuam, - dcm=self.dcm, - standard_flag=self.standard_flag, - standard_mask=self.standard_mask, - vendor_feature=self.vendor_feature, - vendor_mask=self.vendor_mask) - return msg - def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] is True: - return msg - msg += '\n Fully Understands Aspect Mask: 0x{0:x}' - msg = msg.format(self.fuam) - msg += '\n Display Completely Mask: 0x{0:x}'.format(self.dcm) - - msg += '\n Standard Features and Masks:' + msg += '\n Standard Features:' for j in range(len(self.standard_flag)): - args = (self.standard_flag[j], self.standard_mask[j], - _READER_REQUIREMENTS_DISPLAY[self.standard_flag[j]]) - msg += '\n Feature {0:03d}: 0x{1:x} {2}'.format(*args) + sfl = self.standard_flag[j] + rrdisp = _READER_REQUIREMENTS_DISPLAY[self.standard_flag[j]] + msg += '\n Feature {0:03d}: {1}'.format(sfl, rrdisp) msg += '\n Vendor Features:' for j in range(len(self.vendor_feature)): @@ -2231,8 +1441,8 @@ class ReaderRequirementsBox(Jp2kBox): return msg - @classmethod - def parse(cls, fptr, offset, length): + @staticmethod + def parse(fptr, offset, length): """Parse reader requirements box. Parameters @@ -2248,15 +1458,13 @@ class ReaderRequirementsBox(Jp2kBox): ------- ReaderRequirementsBox instance """ - num_bytes = offset + length - fptr.tell() - read_buffer = fptr.read(num_bytes) - mask_length, = struct.unpack_from('>B', read_buffer, offset=0) - - if mask_length == 3: - return _parse_rreq3(read_buffer, length, offset) + read_buffer = fptr.read(1) + mask_length, = struct.unpack('>B', read_buffer) # Fully Understands Aspect Mask # Decodes Completely Mask + read_buffer = fptr.read(2 * mask_length) + fuam = dcm = standard_flag = standard_mask = [] vendor_feature = vendor_mask = [] @@ -2264,19 +1472,11 @@ class ReaderRequirementsBox(Jp2kBox): # from the buffer read from file. try: mask_format = {1: 'B', 2: 'H', 4: 'I', 8: 'Q'}[mask_length] - fuam, dcm = struct.unpack_from('>' + mask_format * 2, read_buffer, - offset=1) - std_flg_offset = 1 + 2 * mask_length - data = _parse_standard_flag(read_buffer[std_flg_offset:], - mask_length) - standard_flag, standard_mask = data - - nflags = len(standard_flag) - vendor_offset = (1 + 2 * mask_length + 2 - + (2 + mask_length) * nflags) - data = _parse_vendor_features(read_buffer[vendor_offset:], - mask_length) - vendor_feature, vendor_mask = data + fuam, dcm = struct.unpack('>' + mask_format * 2, read_buffer) + standard_flag, standard_mask = _parse_standard_flag(fptr, + mask_length) + vendor_feature, vendor_mask = _parse_vendor_features(fptr, + mask_length) except KeyError: msg = 'The ReaderRequirements box (rreq) has a mask length of {0} ' @@ -2284,66 +1484,13 @@ class ReaderRequirementsBox(Jp2kBox): msg += 'The box contents will not be interpreted.' warnings.warn(msg.format(mask_length), UserWarning) - return cls(fuam, dcm, standard_flag, standard_mask, - vendor_feature, vendor_mask, - length=length, offset=offset) + box = ReaderRequirementsBox(fuam, dcm, standard_flag, standard_mask, + vendor_feature, vendor_mask, + length=length, offset=offset) + return box -def _parse_rreq3(read_buffer, length, offset): - """Parse a reader requirements box. Special case when mask length is 3.""" - # Fully Understands Aspect Mask - # Decodes Completely Mask - fuam = dcm = standard_flag = standard_mask = [] - vendor_feature = vendor_mask = [] - - # The mask length tells us the format string to use when unpacking - # from the buffer read from file. - lst = struct.unpack_from('>BBBBBB', read_buffer, offset=1) - fuam = lst[0] << 16 | lst[1] << 8 | lst[2] - dcm = lst[3] << 16 | lst[4] << 8 | lst[5] - - num_standard_features, = struct.unpack_from('>H', read_buffer, offset=7) - - fmt = '>' + 'HBBB' * num_standard_features - lst = struct.unpack_from(fmt, read_buffer, offset=9) - - standard_flag = lst[0::4] - standard_mask = [] - for j in range(num_standard_features): - items = lst[slice(j * 4 + 1, j * 4 + 4)] - mask = items[0] << 16 | items[1] << 8 | items[2] - standard_mask.append(mask) - - boffset = 9 + num_standard_features * 5 - num_vendor_features, = struct.unpack_from('>H', read_buffer, - offset=boffset) - - fmt = '>' + 'HBBB' * num_vendor_features - buffer_offset = 11 + num_standard_features * 5 - lst = struct.unpack_from(fmt, read_buffer, offset=buffer_offset) - - # Each vendor feature consists of a 16-byte UUID plus a mask whose - # length is specified by, you guessed it, "mask_length". - entry_length = 16 + 3 - vendor_feature = [] - vendor_mask = [] - read_buffer = read_buffer[9 + num_standard_features * 10:] - for j in range(num_vendor_features): - uslice = slice(j * entry_length, (j + 1) * entry_length) - ubuffer = read_buffer[uslice] - vendor_feature.append(UUID(bytes=ubuffer[0:16])) - - lst = struct.unpack('>BBB', ubuffer[16:]) - vmask = lst[0] << 16 | lst[1] << 8 | lst[2] - vendor_mask.append(vmask) - - box = ReaderRequirementsBox(fuam, dcm, standard_flag, standard_mask, - vendor_feature, vendor_mask, - length=length, offset=offset) - return box - - -def _parse_standard_flag(read_buffer, mask_length): +def _parse_standard_flag(fptr, mask_length): """Construct standard flag, standard mask data from the file. Specifically working on Reader Requirements box. @@ -2359,13 +1506,16 @@ def _parse_standard_flag(read_buffer, mask_length): # from the buffer read from file. mask_format = {1: 'B', 2: 'H', 4: 'I'}[mask_length] - num_standard_flags, = struct.unpack_from('>H', read_buffer, offset=0) + read_buffer = fptr.read(2) + num_standard_flags, = struct.unpack('>H', read_buffer) # Read in standard flags and standard masks. Each standard flag should # be two bytes, but the standard mask flag is as long as specified by # the mask length. + read_buffer = fptr.read(num_standard_flags * (2 + mask_length)) + fmt = '>' + ('H' + mask_format) * num_standard_flags - data = struct.unpack_from(fmt, read_buffer, offset=2) + data = struct.unpack(fmt, read_buffer) standard_flag = data[0:num_standard_flags * 2:2] standard_mask = data[1:num_standard_flags * 2:2] @@ -2373,7 +1523,7 @@ def _parse_standard_flag(read_buffer, mask_length): return standard_flag, standard_mask -def _parse_vendor_features(read_buffer, mask_length): +def _parse_vendor_features(fptr, mask_length): """Construct vendor features, vendor mask data from the file. Specifically working on Reader Requirements box. @@ -2389,17 +1539,18 @@ def _parse_vendor_features(read_buffer, mask_length): # from the buffer read from file. mask_format = {1: 'B', 2: 'H', 4: 'I'}[mask_length] - num_vendor_features, = struct.unpack_from('>H', read_buffer) + read_buffer = fptr.read(2) + num_vendor_features, = struct.unpack('>H', read_buffer) # Each vendor feature consists of a 16-byte UUID plus a mask whose # length is specified by, you guessed it, "mask_length". entry_length = 16 + mask_length + read_buffer = fptr.read(num_vendor_features * entry_length) vendor_feature = [] vendor_mask = [] for j in range(num_vendor_features): - uslice = slice(2 + j * entry_length, 2 + (j + 1) * entry_length) - ubuffer = read_buffer[uslice] - vendor_feature.append(UUID(bytes=ubuffer[0:16])) + ubuffer = read_buffer[j * entry_length:(j + 1) * entry_length] + vendor_feature.append(uuid.UUID(bytes=ubuffer[0:16])) vmask = struct.unpack('>' + mask_format, ubuffer[16:]) vendor_mask.append(vmask) @@ -2423,26 +1574,24 @@ class ResolutionBox(Jp2kBox): box : list List of boxes contained in this superbox. """ - box_id = 'res ' - longname = 'Resolution' - - def __init__(self, box=None, length=0, offset=-1): - Jp2kBox.__init__(self) + def __init__(self, length=0, offset=-1): + Jp2kBox.__init__(self, box_id='res ', longname='Resolution') self.length = length self.offset = offset - self.box = box if box is not None else [] - - def __repr__(self): - msg = "glymur.jp2box.ResolutionBox(box={0})" - msg = msg.format(self.box) - return msg + self.box = [] def __str__(self): - msg = self._str_superbox() + msg = Jp2kBox.__str__(self) + for box in self.box: + boxstr = str(box) + + # Add indentation. + strs = [('\n ' + x) for x in boxstr.split('\n')] + msg += ''.join(strs) return msg - @classmethod - def parse(cls, fptr, offset, length): + @staticmethod + def parse(fptr, offset, length): """Parse Resolution box. Parameters @@ -2458,7 +1607,7 @@ class ResolutionBox(Jp2kBox): ------- ResolutionBox instance """ - box = cls(length=length, offset=offset) + box = ResolutionBox(length=length, offset=offset) # The JP2 header box is a superbox, so go ahead and parse its child # boxes. @@ -2483,33 +1632,22 @@ class CaptureResolutionBox(Jp2kBox): vertical_resolution, horizontal_resolution : float Vertical, horizontal resolution. """ - box_id = 'resc' - longname = 'Capture Resolution' - def __init__(self, vertical_resolution, horizontal_resolution, length=0, offset=-1): - Jp2kBox.__init__(self) + Jp2kBox.__init__(self, box_id='resc', longname='Capture Resolution') self.vertical_resolution = vertical_resolution self.horizontal_resolution = horizontal_resolution self.length = length self.offset = offset - def __repr__(self): - msg = "glymur.jp2box.CaptureResolutionBox({0}, {1})" - msg = msg.format(self.vertical_resolution, self.horizontal_resolution) - return msg - def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] is True: - return msg - msg += '\n VCR: {0}'.format(self.vertical_resolution) msg += '\n HCR: {0}'.format(self.horizontal_resolution) return msg - @classmethod - def parse(cls, fptr, offset, length): + @staticmethod + def parse(fptr, offset, length): """Parse CaptureResolutionBox. Parameters @@ -2530,7 +1668,9 @@ class CaptureResolutionBox(Jp2kBox): vres = rn1 / rd1 * math.pow(10, re1) hres = rn2 / rd2 * math.pow(10, re2) - return cls(vres, hres, length=length, offset=offset) + box = CaptureResolutionBox(vres, hres, length=length, offset=offset) + + return box class DisplayResolutionBox(Jp2kBox): @@ -2549,33 +1689,22 @@ class DisplayResolutionBox(Jp2kBox): vertical_resolution, horizontal_resolution : float Vertical, horizontal resolution. """ - box_id = 'resd' - longname = 'Display Resolution' - def __init__(self, vertical_resolution, horizontal_resolution, length=0, offset=-1): - Jp2kBox.__init__(self) + Jp2kBox.__init__(self, box_id='resd', longname='Display Resolution') self.vertical_resolution = vertical_resolution self.horizontal_resolution = horizontal_resolution self.length = length self.offset = offset - def __repr__(self): - msg = "glymur.jp2box.DisplayResolutionBox({0}, {1})" - msg = msg.format(self.vertical_resolution, self.horizontal_resolution) - return msg - def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] is True: - return msg - msg += '\n VDR: {0}'.format(self.vertical_resolution) msg += '\n HDR: {0}'.format(self.horizontal_resolution) return msg - @classmethod - def parse(cls, fptr, offset, length): + @staticmethod + def parse(fptr, offset, length): """Parse display resolution box. Parameters @@ -2597,7 +1726,9 @@ class DisplayResolutionBox(Jp2kBox): vres = rn1 / rd1 * math.pow(10, re1) hres = rn2 / rd2 * math.pow(10, re2) - return cls(vres, hres, length=length, offset=offset) + box = DisplayResolutionBox(vres, hres, length=length, offset=offset) + + return box class LabelBox(Jp2kBox): @@ -2614,38 +1745,21 @@ class LabelBox(Jp2kBox): longname : str more verbose description of the box. label : str - Textual label. + Label """ - box_id = 'lbl ' - longname = 'Label' - def __init__(self, label, length=0, offset=-1): - Jp2kBox.__init__(self) + Jp2kBox.__init__(self, box_id='lbl ', longname='Label') self.label = label self.length = length self.offset = offset def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] is True: - return msg - msg += '\n Label: {0}'.format(self.label) return msg - def __repr__(self): - msg = 'glymur.jp2box.LabelBox("{0}")'.format(self.label) - return msg - - def write(self, fptr): - """Write a Label box to file. - """ - length = 8 + len(self.label.encode()) - fptr.write(struct.pack('>I4s', length, b'lbl ')) - fptr.write(self.label.encode()) - - @classmethod - def parse(cls, fptr, offset, length): + @staticmethod + def parse(fptr, offset, length): """Parse Label box. Parameters @@ -2664,93 +1778,8 @@ class LabelBox(Jp2kBox): num_bytes = offset + length - fptr.tell() read_buffer = fptr.read(num_bytes) label = read_buffer.decode('utf-8') - return cls(label, length=length, offset=offset) - - -class NumberListBox(Jp2kBox): - """Container for Number List box information. - - Attributes - ---------- - box_id : str - 4-character identifier for the box. - length : int - length of the box in bytes. - offset : int - offset of the box from the start of the file. - longname : str - more verbose description of the box. - AN : list - Descriptors of an entity with which the data contained within the same - Association box is associated. - """ - box_id = 'nlst' - longname = 'Number List' - - def __init__(self, associations, length=0, offset=-1): - Jp2kBox.__init__(self) - self.associations = associations - self.length = length - self.offset = offset - - def __str__(self): - msg = Jp2kBox.__str__(self) - if _printoptions['short'] is True: - return msg - - for j, association in enumerate(self.associations): - msg += '\n Association[{0}]: '.format(j) - if association == 0: - msg += 'the rendered result' - elif (association >> 24) == 1: - idx = association & 0x00FFFFFF - msg += 'codestream {0}' - msg = msg.format(idx) - elif (association >> 24) == 2: - idx = association & 0x00FFFFFF - msg += 'compositing layer {0}' - msg = msg.format(idx) - else: - msg += 'unrecognized' - return msg - - def __repr__(self): - msg = 'glymur.jp2box.NumberListBox(associations={0})' - msg = msg.format(self.associations) - return msg - - @classmethod - def parse(cls, fptr, offset, length): - """Parse number list box. - - Parameters - ---------- - fptr : file - Open file object. - offset : int - Start position of box in bytes. - length : int - Length of the box in bytes. - - Returns - ------- - LabelBox instance - """ - num_bytes = offset + length - fptr.tell() - raw_data = fptr.read(num_bytes) - num_associations = int(len(raw_data) / 4) - lst = struct.unpack('>' + 'I' * num_associations, raw_data) - return cls(lst, length=length, offset=offset) - - def write(self, fptr): - """Write a NumberList box to file. - """ - fptr.write(struct.pack('>I4s', - len(self.associations) * 4 + 8, b'nlst')) - - fmt = '>' + 'I' * len(self.associations) - write_buffer = struct.pack(fmt, *self.associations) - fptr.write(write_buffer) + box = LabelBox(label, length=length, offset=offset) + return box class XMLBox(Jp2kBox): @@ -2769,9 +1798,6 @@ class XMLBox(Jp2kBox): xml : ElementTree object XML section. """ - box_id = 'xml ' - longname = 'XML' - def __init__(self, xml=None, filename=None, length=0, offset=-1): """ Parameters @@ -2782,7 +1808,7 @@ class XMLBox(Jp2kBox): File from which to read XML. If filename is not None, then the xml keyword argument must be None. """ - Jp2kBox.__init__(self) + Jp2kBox.__init__(self, box_id='xml ', longname='XML') if filename is not None and xml is not None: msg = "Only one of either filename or xml should be provided." raise IOError(msg) @@ -2793,35 +1819,30 @@ class XMLBox(Jp2kBox): self.length = length self.offset = offset - def __repr__(self): - return "glymur.jp2box.XMLBox(xml={0})".format(self.xml) - def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] is True: - return msg - if _printoptions['xml'] is False: - return msg - - msg += '\n' + xml = self.xml if self.xml is not None: - xmlstring = ET.tostring(self.xml, - encoding='utf-8', - pretty_print=True).decode('utf-8') + msg += _pretty_print_xml(self.xml) else: - xmlstring = 'None' - msg += self._indent(xmlstring) + msg += '\n {0}'.format(xml) return msg def write(self, fptr): """Write an XML box to file. """ - read_buffer = ET.tostring(self.xml, encoding='utf-8') - fptr.write(struct.pack('>I4s', len(read_buffer) + 8, b'xml ')) + try: + read_buffer = ET.tostring(self.xml, encoding='utf-8') + except (AttributeError, AssertionError): + # AssertionError on 2.6 + read_buffer = ET.tostring(self.xml.getroot(), encoding='utf-8') + + fptr.write(struct.pack('>I', len(read_buffer) + 8)) + fptr.write(self.box_id.encode()) fptr.write(read_buffer) - @classmethod - def parse(cls, fptr, offset, length): + @staticmethod + def parse(fptr, offset, length): """Parse XML box. Parameters @@ -2841,51 +1862,37 @@ class XMLBox(Jp2kBox): read_buffer = fptr.read(num_bytes) try: text = read_buffer.decode('utf-8') - except UnicodeDecodeError as err: + except UnicodeDecodeError as ude: # Possibly bad string of bytes to begin with. # Try to search for -1: + text = read_buffer[decl_start:].decode('utf-8') + else: + raise # Let the user know that the XML box was problematic. msg = 'A UnicodeDecodeError was encountered parsing an XML box at ' msg += 'byte position {0} ({1}), but the XML was still recovered.' - msg = msg.format(offset, err.reason) + msg = msg.format(offset, ude.reason) warnings.warn(msg, UserWarning) + # Strip out any trailing nulls, as they can foul up XML parsing. text = text.rstrip(chr(0)) - # Remove any byte order markers. - if u'\ufeff' in text: - msg = 'An illegal BOM (byte order marker) was detected and ' - msg += 'removed from the XML contents in the box starting at byte ' - msg += 'offset {0}'.format(offset) - warnings.warn(msg) - text = text.replace(u'\ufeff', '') - - # Remove any encoding declaration. - if text.startswith(''): - text = text[38:] - try: - elt = ET.fromstring(text) + elt = ET.fromstring(text.encode('utf-8')) xml = ET.ElementTree(elt) - except ET.ParseError as err: + except ParseError as parse_error: msg = 'A problem was encountered while parsing an XML box:' msg += '\n\n\t"{0}"\n\nNo XML was retrieved.' - msg = msg.format(str(err)) + msg = msg.format(str(parse_error)) warnings.warn(msg, UserWarning) xml = None - return cls(xml=xml, length=length, offset=offset) + box = XMLBox(xml=xml, length=length, offset=offset) + return box class UUIDListBox(Jp2kBox): @@ -2904,30 +1911,20 @@ class UUIDListBox(Jp2kBox): ulst : list List of UUIDs. """ - box_id = 'ulst' - longname = 'UUID List' - def __init__(self, ulst, length=0, offset=-1): - Jp2kBox.__init__(self) + Jp2kBox.__init__(self, box_id='ulst', longname='UUID List') self.ulst = ulst self.length = length self.offset = offset - def __repr__(self): - msg = "glymur.jp2box.UUIDListBox({0})".format(self.ulst) - return msg - def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] is True: - return msg - for j, uuid_item in enumerate(self.ulst): msg += '\n UUID[{0}]: {1}'.format(j, uuid_item) - return msg + return(msg) - @classmethod - def parse(cls, fptr, offset, length): + @staticmethod + def parse(fptr, offset, length): """Parse UUIDList box. Parameters @@ -2943,17 +1940,16 @@ class UUIDListBox(Jp2kBox): ------- UUIDListBox instance """ - num_bytes = offset + length - fptr.tell() - read_buffer = fptr.read(num_bytes) - - num_uuids, = struct.unpack_from('>H', read_buffer) + read_buffer = fptr.read(2) + num_uuids, = struct.unpack('>H', read_buffer) ulst = [] - for j in range(num_uuids): - uuid_buffer = read_buffer[2 + j * 16:2 + (j + 1) * 16] - ulst.append(UUID(bytes=uuid_buffer)) + for _ in range(num_uuids): + read_buffer = fptr.read(16) + ulst.append(uuid.UUID(bytes=read_buffer)) - return cls(ulst, length=length, offset=offset) + box = UUIDListBox(ulst, length=length, offset=offset) + return(box) class UUIDInfoBox(Jp2kBox): @@ -2972,25 +1968,26 @@ class UUIDInfoBox(Jp2kBox): box : list List of boxes contained in this superbox. """ - box_id = 'uinf' - longname = 'UUIDInfo' - - def __init__(self, box=None, length=0, offset=-1): - Jp2kBox.__init__(self) + def __init__(self, length=0, offset=-1): + Jp2kBox.__init__(self, box_id='uinf', longname='UUIDInfo') self.length = length self.offset = offset - self.box = box if box is not None else [] - - def __repr__(self): - msg = "glymur.jp2box.UUIDInfoBox(box={0})".format(self.box) - return msg + self.box = [] def __str__(self): - msg = self._str_superbox() - return msg + msg = Jp2kBox.__str__(self) - @classmethod - def parse(cls, fptr, offset, length): + for box in self.box: + box_str = str(box) + + # Add indentation. + lst = [('\n ' + x) for x in box_str.split('\n')] + msg += ''.join(lst) + + return(msg) + + @staticmethod + def parse(fptr, offset, length): """Parse UUIDInfo super box. Parameters @@ -3007,7 +2004,7 @@ class UUIDInfoBox(Jp2kBox): UUIDInfoBox instance """ - box = cls(length=length, offset=offset) + box = UUIDInfoBox(length=length, offset=offset) # The UUIDInfo box is a superbox, so go ahead and parse its child # boxes. @@ -3036,44 +2033,16 @@ class DataEntryURLBox(Jp2kBox): URL : str Associated URL. """ - box_id = 'url ' - longname = 'Data Entry URL' - def __init__(self, version, flag, url, length=0, offset=-1): - Jp2kBox.__init__(self) + Jp2kBox.__init__(self, box_id='url ', longname='Data Entry URL') self.version = version self.flag = flag self.url = url self.length = length self.offset = offset - def write(self, fptr): - """Write a data entry url box to file. - """ - # Make sure it is written out as null-terminated. - url = self.url - if self.url[-1] != chr(0): - url = url + chr(0) - url = url.encode() - - length = 8 + 1 + 3 + len(url) - write_buffer = struct.pack('>I4sBBBB', - length, b'url ', - self.version, - self.flag[0], self.flag[1], self.flag[2]) - fptr.write(write_buffer) - fptr.write(url) - - def __repr__(self): - msg = "glymur.jp2box.DataEntryURLBox({0}, {1}, '{2}')" - msg = msg.format(self.version, self.flag, self.url) - return msg - def __str__(self): msg = Jp2kBox.__str__(self) - if _printoptions['short'] is True: - return msg - msg += '\n ' lines = ['Version: {0}', @@ -3085,8 +2054,8 @@ class DataEntryURLBox(Jp2kBox): self.url) return msg - @classmethod - def parse(cls, fptr, offset, length): + @staticmethod + def parse(fptr, offset, length): """Parse data entry URL box. Parameters @@ -3102,47 +2071,16 @@ class DataEntryURLBox(Jp2kBox): ------- DataEntryURLbox instance """ - num_bytes = offset + length - fptr.tell() - read_buffer = fptr.read(num_bytes) - data = struct.unpack_from('>BBBB', read_buffer) + read_buffer = fptr.read(4) + data = struct.unpack('>BBBB', read_buffer) version = data[0] flag = data[1:4] - url = read_buffer[4:].decode('utf-8').rstrip(chr(0)) - return cls(version, flag, url, length=length, offset=offset) - - -class UnknownBox(Jp2kBox): - """Container for unrecognized boxes. - - Attributes - ---------- - box_id : str - 4-character identifier for the box. - length : int - length of the box in bytes. - offset : int - offset of the box from the start of the file. - longname : str - more verbose description of the box. - """ - def __init__(self, box_id, length=0, offset=-1, longname=''): - Jp2kBox.__init__(self) - self.longname = longname - self.box_id = box_id - self.length = length - self.offset = offset - - def __repr__(self): - msg = "glymur.jp2box.UnknownBox({0})".format(self.box_id) - return msg - - def __str__(self): - if len(self.box) > 0: - msg = self._str_superbox() - else: - msg = Jp2kBox.__str__(self) - return msg + numbytes = offset + length - fptr.tell() + read_buffer = fptr.read(numbytes) + url = read_buffer.decode('utf-8') + box = DataEntryURLBox(version, flag, url, length=length, offset=offset) + return box class UUIDBox(Jp2kBox): @@ -3160,12 +2098,9 @@ class UUIDBox(Jp2kBox): more verbose description of the box. uuid : uuid.UUID 16-byte UUID - raw_data : byte array - Sequence of uninterpreted bytes as read from the file. - data : object - Specific to each type of UUID. There are handlers for XMP, Exif, and - generic (unknown) UUIDs. In the case of XMP and Exif UUIDs, this is - the interpreted version of raw_data. + data : bytes or dict or ElementTree.Element + Vendor-specific data. Exif UUIDs are interpreted as dictionaries. + XMP UUIDs are interpreted as standard XML. References ---------- @@ -3173,9 +2108,6 @@ class UUIDBox(Jp2kBox): 16684-1:2012 - Graphic technology -- Extensible metadata platform (XMP) specification -- Part 1: Data model, serialization and core properties """ - box_id = 'uuid' - longname = 'UUID' - def __init__(self, the_uuid, raw_data, length=0, offset=-1): """ Parameters @@ -3183,88 +2115,68 @@ class UUIDBox(Jp2kBox): the_uuid : uuid.UUID Identifies the type of UUID box. raw_data : byte array - Sequence of uninterpreted bytes as read from the UUID box. + This is the "payload" of data for the specified UUID. length : int length of the box in bytes. offset : int offset of the box from the start of the file. """ - Jp2kBox.__init__(self) + Jp2kBox.__init__(self, box_id='uuid', longname='UUID') self.uuid = the_uuid - self.raw_data = raw_data + + if the_uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'): + # XMP data. Parse as XML. Seems to be a difference between + # ElementTree in version 2.7 and 3.3. + if sys.hexversion < 0x03000000: + elt = ET.fromstring(raw_data) + else: + text = raw_data.decode('utf-8') + elt = ET.fromstring(text) + self.data = ET.ElementTree(elt) + elif the_uuid.bytes == b'JpgTiffExif->JP2': + exif_obj = Exif(raw_data) + ifds = OrderedDict() + ifds['Image'] = exif_obj.exif_image + ifds['Photo'] = exif_obj.exif_photo + ifds['GPSInfo'] = exif_obj.exif_gpsinfo + ifds['Iop'] = exif_obj.exif_iop + self.data = ifds + else: + self.data = raw_data + self.length = length self.offset = offset - self.data = None - - try: - self._parse_raw_data() - except KeyError as error: - # Such as when an Exif tag is unrecognized. - warnings.warn(str(error)) - except IOError as error: - # Such as when Exif byte order is unrecognized. - warnings.warn(str(error)) - - def _parse_raw_data(self): - """ - Private function for parsing UUID payloads if possible. - """ - if self.uuid == UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'): - self.data = _uuid_io.xml(self.raw_data) - elif self.uuid.bytes == b'JpgTiffExif->JP2': - self.data = _uuid_io.tiff_header(self.raw_data) - else: - self.data = self.raw_data - - def __repr__(self): - msg = "glymur.jp2box.UUIDBox(the_uuid={0}, " - msg += "raw_data=)" - return msg.format(repr(self.uuid), len(self.raw_data)) def __str__(self): - msg = Jp2kBox.__str__(self) - if _printoptions['short'] is True: - return msg + msg = '{0}\n' + msg += ' UUID: {1}{2}\n' + msg += ' UUID Data: {3}' - msg = '{0}\n UUID: {1}'.format(msg, self.uuid) - if self.uuid == UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'): - msg += ' (XMP)' + if self.uuid == uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'): + uuid_type = ' (XMP)' + uuid_data = _pretty_print_xml(self.data) elif self.uuid.bytes == b'JpgTiffExif->JP2': - msg += ' (EXIF)' + uuid_type = ' (Exif)' + # 2.7 has trouble pretty-printing ordered dicts, so print them + # as regular dicts. Not ideal, but at least it's good on 3.3+. + if sys.hexversion < 0x03000000: + data = dict(self.data) + else: + data = self.data + uuid_data = '\n' + pprint.pformat(data) else: - msg += ' (unknown)' + uuid_type = '' + uuid_data = '{0} bytes'.format(len(self.data)) - if (((_printoptions['xml'] is False) and - (self.uuid == UUID('be7acfcb-97a9-42e8-9c71-999491e3afac')))): - # If it's an XMP UUID, don't print the XML contents. - return msg - - if self.uuid == UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'): - line = '\n UUID Data:\n{0}' - xmlstring = ET.tostring(self.data, - encoding='utf-8', - pretty_print=True).decode('utf-8') - # indent it a bit - xmlstring = self._indent(xmlstring.rstrip()) - msg += line.format(xmlstring) - elif self.uuid.bytes == b'JpgTiffExif->JP2': - msg += '\n UUID Data: {0}'.format(str(self.data)) - else: - line = '\n UUID Data: {0} bytes' - msg += line.format(len(self.raw_data)) + msg = msg.format(Jp2kBox.__str__(self), + self.uuid, + uuid_type, + uuid_data) return msg - def write(self, fptr): - """Write a UUID box to file. - """ - write_buffer = struct.pack('>I4s', self.length, b'uuid') - fptr.write(write_buffer) - fptr.write(self.uuid.bytes) - fptr.write(self.raw_data) - - @classmethod - def parse(cls, fptr, offset, length): + @staticmethod + def parse(fptr, offset, length): """Parse UUID box. Parameters @@ -3280,145 +2192,584 @@ class UUIDBox(Jp2kBox): ------- UUIDBox instance """ - num_bytes = offset + length - fptr.tell() - read_buffer = fptr.read(num_bytes) - the_uuid = UUID(bytes=read_buffer[0:16]) - return cls(the_uuid, read_buffer[16:], length=length, offset=offset) + + read_buffer = fptr.read(16) + the_uuid = uuid.UUID(bytes=read_buffer) + + numbytes = offset + length - fptr.tell() + read_buffer = fptr.read(numbytes) + box = UUIDBox(the_uuid, read_buffer, length=length, offset=offset) + return box + + +class Exif(object): + """ + Attributes + ---------- + read_buffer : bytes + Raw byte stream consisting of the UUID data. + endian : str + Either '<' for big-endian, or '>' for little-endian. + """ + + def __init__(self, read_buffer): + """Interpret raw buffer consisting of Exif IFD. + """ + self.exif_image = None + self.exif_photo = None + self.exif_gpsinfo = None + self.exif_iop = None + + self.read_buffer = read_buffer + + # Ignore the first six bytes. + # Next 8 should be (73, 73, 42, 8) + data = struct.unpack('' for little-endian. + num_tags : int + Number of tags in the IFD. + raw_ifd : dictionary + Maps tag number to "mildly-interpreted" tag value. + processed_ifd : dictionary + Maps tag name to "mildly-interpreted" tag value. + """ + datatype2fmt = {1: ('B', 1), + 2: ('B', 1), + 3: ('H', 2), + 4: ('I', 4), + 5: ('II', 8), + 7: ('B', 1), + 9: ('i', 4), + 10: ('ii', 8)} + + def __init__(self, endian, read_buffer, offset): + self.endian = endian + self.read_buffer = read_buffer + self.processed_ifd = OrderedDict() + + self.num_tags, = struct.unpack(endian + 'H', + read_buffer[offset:offset + 2]) + + fmt = self.endian + 'HHII' * self.num_tags + ifd_buffer = read_buffer[offset + 2:offset + 2 + self.num_tags * 12] + data = struct.unpack(fmt, ifd_buffer) + self.raw_ifd = OrderedDict() + for j, tag in enumerate(data[0::4]): + # The offset to the tag offset/payload is the offset to the IFD + # plus 2 bytes for the number of tags plus 12 bytes for each + # tag entry plus 8 bytes to the offset/payload itself. + toffp = read_buffer[offset + 10 + j * 12:offset + 10 + j * 12 + 4] + tag_data = self.parse_tag(data[j * 4 + 1], + data[j * 4 + 2], + toffp) + self.raw_ifd[tag] = tag_data + + def parse_tag(self, dtype, count, offset_buf): + """Interpret an Exif image tag data payload. + """ + fmt = self.datatype2fmt[dtype][0] * count + payload_size = self.datatype2fmt[dtype][1] * count + + if payload_size <= 4: + # Interpret the payload from the 4 bytes in the tag entry. + target_buffer = offset_buf[:payload_size] + else: + # Interpret the payload at the offset specified by the 4 bytes in + # the tag entry. + offset, = struct.unpack(self.endian + 'I', offset_buf) + target_buffer = self.read_buffer[offset:offset + payload_size] + + if dtype == 2: + # ASCII + if sys.hexversion < 0x03000000: + payload = target_buffer.rstrip('\x00') + else: + payload = target_buffer.decode('utf-8').rstrip('\x00') + + else: + payload = struct.unpack(self.endian + fmt, target_buffer) + if dtype == 5 or dtype == 10: + # Rational or Signed Rational. Construct the list of values. + rational_payload = [] + for j in range(count): + value = float(payload[j * 2]) / float(payload[j * 2 + 1]) + rational_payload.append(value) + payload = rational_payload + if count == 1: + # If just a single value, then return a scalar instead of a + # tuple. + payload = payload[0] + + return payload + + def post_process(self, tagnum2name): + """Map the tag name instead of tag number to the tag value. + """ + for tag, value in self.raw_ifd.items(): + try: + tag_name = tagnum2name[tag] + except KeyError: + # Ok, we don't recognize this tag. Just use the numeric id. + msg = 'Unrecognized Exif tag "{0}".'.format(tag) + warnings.warn(msg, UserWarning) + tag_name = tag + self.processed_ifd[tag_name] = value + + +class _ExifImageIfd(_Ifd): + """ + Attributes + ---------- + tagnum2name : dict + Maps Exif image tag numbers to the tag names. + ifd : dict + Maps tag names to tag values. + """ + tagnum2name = {11: 'ProcessingSoftware', + 254: 'NewSubfileType', + 255: 'SubfileType', + 256: 'ImageWidth', + 257: 'ImageLength', + 258: 'BitsPerSample', + 259: 'Compression', + 262: 'PhotometricInterpretation', + 263: 'Threshholding', + 264: 'CellWidth', + 265: 'CellLength', + 266: 'FillOrder', + 269: 'DocumentName', + 270: 'ImageDescription', + 271: 'Make', + 272: 'Model', + 273: 'StripOffsets', + 274: 'Orientation', + 277: 'SamplesPerPixel', + 278: 'RowsPerStrip', + 279: 'StripByteCounts', + 282: 'XResolution', + 283: 'YResolution', + 284: 'PlanarConfiguration', + 290: 'GrayResponseUnit', + 291: 'GrayResponseCurve', + 292: 'T4Options', + 293: 'T6Options', + 296: 'ResolutionUnit', + 301: 'TransferFunction', + 305: 'Software', + 306: 'DateTime', + 315: 'Artist', + 316: 'HostComputer', + 317: 'Predictor', + 318: 'WhitePoint', + 319: 'PrimaryChromaticities', + 320: 'ColorMap', + 321: 'HalftoneHints', + 322: 'TileWidth', + 323: 'TileLength', + 324: 'TileOffsets', + 325: 'TileByteCounts', + 330: 'SubIFDs', + 332: 'InkSet', + 333: 'InkNames', + 334: 'NumberOfInks', + 336: 'DotRange', + 337: 'TargetPrinter', + 338: 'ExtraSamples', + 339: 'SampleFormat', + 340: 'SMinSampleValue', + 341: 'SMaxSampleValue', + 342: 'TransferRange', + 343: 'ClipPath', + 344: 'XClipPathUnits', + 345: 'YClipPathUnits', + 346: 'Indexed', + 347: 'JPEGTables', + 351: 'OPIProxy', + 512: 'JPEGProc', + 513: 'JPEGInterchangeFormat', + 514: 'JPEGInterchangeFormatLength', + 515: 'JPEGRestartInterval', + 517: 'JPEGLosslessPredictors', + 518: 'JPEGPointTransforms', + 519: 'JPEGQTables', + 520: 'JPEGDCTables', + 521: 'JPEGACTables', + 529: 'YCbCrCoefficients', + 530: 'YCbCrSubSampling', + 531: 'YCbCrPositioning', + 532: 'ReferenceBlackWhite', + 700: 'XMLPacket', + 18246: 'Rating', + 18249: 'RatingPercent', + 32781: 'ImageID', + 33421: 'CFARepeatPatternDim', + 33422: 'CFAPattern', + 33423: 'BatteryLevel', + 33432: 'Copyright', + 33434: 'ExposureTime', + 33437: 'FNumber', + 33723: 'IPTCNAA', + 34377: 'ImageResources', + 34665: 'ExifTag', + 34675: 'InterColorProfile', + 34850: 'ExposureProgram', + 34852: 'SpectralSensitivity', + 34853: 'GPSTag', + 34855: 'ISOSpeedRatings', + 34856: 'OECF', + 34857: 'Interlace', + 34858: 'TimeZoneOffset', + 34859: 'SelfTimerMode', + 36867: 'DateTimeOriginal', + 37122: 'CompressedBitsPerPixel', + 37377: 'ShutterSpeedValue', + 37378: 'ApertureValue', + 37379: 'BrightnessValue', + 37380: 'ExposureBiasValue', + 37381: 'MaxApertureValue', + 37382: 'SubjectDistance', + 37383: 'MeteringMode', + 37384: 'LightSource', + 37385: 'Flash', + 37386: 'FocalLength', + 37387: 'FlashEnergy', + 37388: 'SpatialFrequencyResponse', + 37389: 'Noise', + 37390: 'FocalPlaneXResolution', + 37391: 'FocalPlaneYResolution', + 37392: 'FocalPlaneResolutionUnit', + 37393: 'ImageNumber', + 37394: 'SecurityClassification', + 37395: 'ImageHistory', + 37396: 'SubjectLocation', + 37397: 'ExposureIndex', + 37398: 'TIFFEPStandardID', + 37399: 'SensingMethod', + 40091: 'XPTitle', + 40092: 'XPComment', + 40093: 'XPAuthor', + 40094: 'XPKeywords', + 40095: 'XPSubject', + 50341: 'PrintImageMatching', + 50706: 'DNGVersion', + 50707: 'DNGBackwardVersion', + 50708: 'UniqueCameraModel', + 50709: 'LocalizedCameraModel', + 50710: 'CFAPlaneColor', + 50711: 'CFALayout', + 50712: 'LinearizationTable', + 50713: 'BlackLevelRepeatDim', + 50714: 'BlackLevel', + 50715: 'BlackLevelDeltaH', + 50716: 'BlackLevelDeltaV', + 50717: 'WhiteLevel', + 50718: 'DefaultScale', + 50719: 'DefaultCropOrigin', + 50720: 'DefaultCropSize', + 50721: 'ColorMatrix1', + 50722: 'ColorMatrix2', + 50723: 'CameraCalibration1', + 50724: 'CameraCalibration2', + 50725: 'ReductionMatrix1', + 50726: 'ReductionMatrix2', + 50727: 'AnalogBalance', + 50728: 'AsShotNeutral', + 50729: 'AsShotWhiteXY', + 50730: 'BaselineExposure', + 50731: 'BaselineNoise', + 50732: 'BaselineSharpness', + 50733: 'BayerGreenSplit', + 50734: 'LinearResponseLimit', + 50735: 'CameraSerialNumber', + 50736: 'LensInfo', + 50737: 'ChromaBlurRadius', + 50738: 'AntiAliasStrength', + 50739: 'ShadowScale', + 50740: 'DNGPrivateData', + 50741: 'MakerNoteSafety', + 50778: 'CalibrationIlluminant1', + 50779: 'CalibrationIlluminant2', + 50780: 'BestQualityScale', + 50781: 'RawDataUniqueID', + 50827: 'OriginalRawFileName', + 50828: 'OriginalRawFileData', + 50829: 'ActiveArea', + 50830: 'MaskedAreas', + 50831: 'AsShotICCProfile', + 50832: 'AsShotPreProfileMatrix', + 50833: 'CurrentICCProfile', + 50834: 'CurrentPreProfileMatrix', + 50879: 'ColorimetricReference', + 50931: 'CameraCalibrationSignature', + 50932: 'ProfileCalibrationSignature', + 50934: 'AsShotProfileName', + 50935: 'NoiseReductionApplied', + 50936: 'ProfileName', + 50937: 'ProfileHueSatMapDims', + 50938: 'ProfileHueSatMapData1', + 50939: 'ProfileHueSatMapData2', + 50940: 'ProfileToneCurve', + 50941: 'ProfileEmbedPolicy', + 50942: 'ProfileCopyright', + 50964: 'ForwardMatrix1', + 50965: 'ForwardMatrix2', + 50966: 'PreviewApplicationName', + 50967: 'PreviewApplicationVersion', + 50968: 'PreviewSettingsName', + 50969: 'PreviewSettingsDigest', + 50970: 'PreviewColorSpace', + 50971: 'PreviewDateTime', + 50972: 'RawImageDigest', + 50973: 'OriginalRawFileDigest', + 50974: 'SubTileBlockSize', + 50975: 'RowInterleaveFactor', + 50981: 'ProfileLookTableDims', + 50982: 'ProfileLookTableData', + 51008: 'OpcodeList1', + 51009: 'OpcodeList2', + 51022: 'OpcodeList3', + 51041: 'NoiseProfile'} + + def __init__(self, endian, read_buffer, offset): + _Ifd.__init__(self, endian, read_buffer, offset) + self.post_process(self.tagnum2name) + + +class _ExifPhotoIfd(_Ifd): + """Represents tags found in the Exif sub ifd. + """ + tagnum2name = {33434: 'ExposureTime', + 33437: 'FNumber', + 34850: 'ExposureProgram', + 34852: 'SpectralSensitivity', + 34855: 'ISOSpeedRatings', + 34856: 'OECF', + 34864: 'SensitivityType', + 34865: 'StandardOutputSensitivity', + 34866: 'RecommendedExposureIndex', + 34867: 'ISOSpeed', + 34868: 'ISOSpeedLatitudeyyy', + 34869: 'ISOSpeedLatitudezzz', + 36864: 'ExifVersion', + 36867: 'DateTimeOriginal', + 36868: 'DateTimeDigitized', + 37121: 'ComponentsConfiguration', + 37122: 'CompressedBitsPerPixel', + 37377: 'ShutterSpeedValue', + 37378: 'ApertureValue', + 37379: 'BrightnessValue', + 37380: 'ExposureBiasValue', + 37381: 'MaxApertureValue', + 37382: 'SubjectDistance', + 37383: 'MeteringMode', + 37384: 'LightSource', + 37385: 'Flash', + 37386: 'FocalLength', + 37396: 'SubjectArea', + 37500: 'MakerNote', + 37510: 'UserComment', + 37520: 'SubSecTime', + 37521: 'SubSecTimeOriginal', + 37522: 'SubSecTimeDigitized', + 40960: 'FlashpixVersion', + 40961: 'ColorSpace', + 40962: 'PixelXDimension', + 40963: 'PixelYDimension', + 40964: 'RelatedSoundFile', + 40965: 'InteroperabilityTag', + 41483: 'FlashEnergy', + 41484: 'SpatialFrequencyResponse', + 41486: 'FocalPlaneXResolution', + 41487: 'FocalPlaneYResolution', + 41488: 'FocalPlaneResolutionUnit', + 41492: 'SubjectLocation', + 41493: 'ExposureIndex', + 41495: 'SensingMethod', + 41728: 'FileSource', + 41729: 'SceneType', + 41730: 'CFAPattern', + 41985: 'CustomRendered', + 41986: 'ExposureMode', + 41987: 'WhiteBalance', + 41988: 'DigitalZoomRatio', + 41989: 'FocalLengthIn35mmFilm', + 41990: 'SceneCaptureType', + 41991: 'GainControl', + 41992: 'Contrast', + 41993: 'Saturation', + 41994: 'Sharpness', + 41995: 'DeviceSettingDescription', + 41996: 'SubjectDistanceRange', + 42016: 'ImageUniqueID', + 42032: 'CameraOwnerName', + 42033: 'BodySerialNumber', + 42034: 'LensSpecification', + 42035: 'LensMake', + 42036: 'LensModel', + 42037: 'LensSerialNumber'} + + def __init__(self, endian, read_buffer, offset): + _Ifd.__init__(self, endian, read_buffer, offset) + self.post_process(self.tagnum2name) + + +class _ExifGPSInfoIfd(_Ifd): + """Represents information found in the GPSInfo sub IFD. + """ + tagnum2name = {0: 'GPSVersionID', + 1: 'GPSLatitudeRef', + 2: 'GPSLatitude', + 3: 'GPSLongitudeRef', + 4: 'GPSLongitude', + 5: 'GPSAltitudeRef', + 6: 'GPSAltitude', + 7: 'GPSTimeStamp', + 8: 'GPSSatellites', + 9: 'GPSStatus', + 10: 'GPSMeasureMode', + 11: 'GPSDOP', + 12: 'GPSSpeedRef', + 13: 'GPSSpeed', + 14: 'GPSTrackRef', + 15: 'GPSTrack', + 16: 'GPSImgDirectionRef', + 17: 'GPSImgDirection', + 18: 'GPSMapDatum', + 19: 'GPSDestLatitudeRef', + 20: 'GPSDestLatitude', + 21: 'GPSDestLongitudeRef', + 22: 'GPSDestLongitude', + 23: 'GPSDestBearingRef', + 24: 'GPSDestBearing', + 25: 'GPSDestDistanceRef', + 26: 'GPSDestDistance', + 27: 'GPSProcessingMethod', + 28: 'GPSAreaInformation', + 29: 'GPSDateStamp', + 30: 'GPSDifferential'} + + def __init__(self, endian, read_buffer, offset): + _Ifd.__init__(self, endian, read_buffer, offset) + self.post_process(self.tagnum2name) + + +class _ExifInteroperabilityIfd(_Ifd): + """Represents tags found in the Interoperability sub IFD. + """ + tagnum2name = {1: 'InteroperabilityIndex', + 2: 'InteroperabilityVersion', + 4096: 'RelatedImageFileFormat', + 4097: 'RelatedImageWidth', + 4098: 'RelatedImageLength'} + + def __init__(self, endian, read_buffer, offset): + _Ifd.__init__(self, endian, read_buffer, offset) + self.post_process(self.tagnum2name) # Map each box ID to the corresponding class. _BOX_WITH_ID = { - b'asoc': AssociationBox, - b'cdef': ChannelDefinitionBox, - b'cgrp': ColourGroupBox, - b'cmap': ComponentMappingBox, - b'colr': ColourSpecificationBox, - b'dtbl': DataReferenceBox, - b'ftyp': FileTypeBox, - b'ihdr': ImageHeaderBox, - b'jP ': JPEG2000SignatureBox, - b'jpch': CodestreamHeaderBox, - b'jplh': CompositingLayerHeaderBox, - b'jp2c': ContiguousCodestreamBox, - b'free': FreeBox, - b'flst': FragmentListBox, - b'ftbl': FragmentTableBox, - b'jp2h': JP2HeaderBox, - b'lbl ': LabelBox, - b'nlst': NumberListBox, - b'pclr': PaletteBox, - b'res ': ResolutionBox, - b'resc': CaptureResolutionBox, - b'resd': DisplayResolutionBox, - b'rreq': ReaderRequirementsBox, - b'uinf': UUIDInfoBox, - b'ulst': UUIDListBox, - b'url ': DataEntryURLBox, - b'uuid': UUIDBox, - b'xml ': XMLBox} - -_parseoptions = {'full_codestream': False} + 'asoc': AssociationBox, + 'cdef': ChannelDefinitionBox, + 'cmap': ComponentMappingBox, + 'colr': ColourSpecificationBox, + 'ftyp': FileTypeBox, + 'ihdr': ImageHeaderBox, + 'jP ': JPEG2000SignatureBox, + 'jpch': CodestreamHeaderBox, + 'jplh': CompositingLayerHeaderBox, + 'jp2c': ContiguousCodestreamBox, + 'jp2h': JP2HeaderBox, + 'lbl ': LabelBox, + 'pclr': PaletteBox, + 'res ': ResolutionBox, + 'resc': CaptureResolutionBox, + 'resd': DisplayResolutionBox, + 'rreq': ReaderRequirementsBox, + 'uinf': UUIDInfoBox, + 'ulst': UUIDListBox, + 'url ': DataEntryURLBox, + 'uuid': UUIDBox, + 'xml ': XMLBox} -def set_parseoptions(full_codestream=True): - """Set parsing options. +def _indent(elem, level=0): + """Recipe for pretty printing XML. Please see - These options determine the way JPEG 2000 boxes are parsed. - - Parameters - ---------- - full_codestream : bool, defaults to True - When False, only the codestream header is parsed for metadata. This - can results in faster JP2/JPX parsing. When True, the entire - codestream is parsed for metadata. - - See also - -------- - get_parseoptions - - Examples - -------- - To put back the default options, you can use: - - >>> import glymur - >>> glymur.set_parseoptions(full_codestream=True) + http://effbot.org/zone/element-lib.htm#prettyprint """ - _parseoptions['full_codestream'] = full_codestream + i = "\n" + level * " " + if len(elem): + if not elem.text or not elem.text.strip(): + elem.text = i + " " + if not elem.tail or not elem.tail.strip(): + elem.tail = i + for elem in elem: + _indent(elem, level + 1) + if not elem.tail or not elem.tail.strip(): + elem.tail = i + else: + if level and (not elem.tail or not elem.tail.strip()): + elem.tail = i -def get_parseoptions(): - """Return the current parsing options. - - Returns - ------- - print_opts : dict - Dictionary of current print options with keys - - - codestream : bool - - For a full description of these options, see `set_parseoptions`. - - See also - -------- - set_parseoptions +def _pretty_print_xml(xml, level=0): + """Pretty print XML data. """ - return _parseoptions + xml = copy.deepcopy(xml) + _indent(xml.getroot(), level=level) + xmltext = ET.tostring(xml.getroot(), encoding='utf-8').decode('utf-8') -_printoptions = {'short': False, 'xml': True, 'codestream': True} - - -def set_printoptions(**kwargs): - """Set printing options. - - These options determine the way JPEG 2000 boxes are displayed. - - Parameters - ---------- - short : bool, optional - When True, only the box ID, offset, and length are displayed. Useful - for displaying only the basic structure or skeleton of a JPEG 2000 - file. - xml : bool, optional - When False, printing of the XML contents of any XML boxes or UUID XMP - boxes is suppressed. - codestream : bool, optional - When False, only the codestream header is printed. When True, the - entire codestream is printed. This option has no effect when the - 'short' option is set to True. - - See also - -------- - get_printoptions - - Examples - -------- - To put back the default options, you can use: - - >>> import glymur - >>> glymur.set_printoptions(short=False, xml=True, codestream=True) - """ - for key, value in kwargs.items(): - if key not in ['short', 'xml', 'codestream']: - raise TypeError('"{0}" not a valid keyword parameter.'.format(key)) - _printoptions[key] = value - - -def get_printoptions(): - """Return the current print options. - - Returns - ------- - print_opts : dict - Dictionary of current print options with keys - - - short : bool - - xml : bool - - codestream : bool - - For a full description of these options, see `set_printoptions`. - - See also - -------- - set_printoptions - """ - return _printoptions + # Indent it a bit. + lst = [(' ' + x) for x in xmltext.split('\n')] + try: + xml = '\n'.join(lst) + return '\n{0}'.format(xml) + except UnicodeEncodeError: + # This can happen on python 2.x if the character set contains certain + # non-ascii characters. Just print out the corresponding xml char + # entities instead. + xml = u'\n'.join(lst) + text = u'\n{0}'.format(xml) + text = text.encode('ascii', 'xmlcharrefreplace') + return text diff --git a/glymur/jp2dump.py b/glymur/jp2dump.py new file mode 100644 index 0000000..c0f0f7f --- /dev/null +++ b/glymur/jp2dump.py @@ -0,0 +1,36 @@ +""" +Entry point for jp2dump script. +""" +import warnings + +from .jp2k import Jp2k + + +def jp2dump(filename, codestream=False): + """Prints JPEG2000 metadata. + + Parameters + ---------- + filename : string + The input JPEG2000 file. + codestream : optional, logical scalar + Whether or not to dump codestream contents. + """ + with warnings.catch_warnings(record=True) as wctx: + + # JP2 metadata can be extensive, so don't print any warnings until we + # are done with the metadata. + j = Jp2k(filename) + if codestream: + print(j.get_codestream(header_only=False)) + else: + print(j) + + # Re-emit any warnings that may have been suppressed. + if len(wctx) > 0: + print("\n") + for warning in wctx: + print("{0}:{1}: {2}: {3}".format(warning.filename, + warning.lineno, + warning.category.__name__, + warning.message)) diff --git a/glymur/jp2k.py b/glymur/jp2k.py index ee6eedc..da59ac1 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -10,30 +10,33 @@ License: MIT import sys # Exitstack not found in contextlib in 2.7 +# pylint: disable=E0611 if sys.hexversion >= 0x03030000: from contextlib import ExitStack - from itertools import filterfalse else: from contextlib2 import ExitStack - from itertools import ifilterfalse as filterfalse -from collections import Counter import ctypes import math import os import re import struct -from uuid import UUID import warnings import numpy as np from .codestream import Codestream -from . import core, version -from .jp2box import (Jp2kBox, JPEG2000SignatureBox, FileTypeBox, - JP2HeaderBox, ColourSpecificationBox, - ContiguousCodestreamBox, ImageHeaderBox) -from .lib import openjpeg as opj, openjp2 as opj2, c as libc +from .core import SRGB, GREYSCALE +from .core import PROGRESSION_ORDER +from .core import ENUMERATED_COLORSPACE, RESTRICTED_ICC_PROFILE +from .jp2box import Jp2kBox +from .jp2box import JPEG2000SignatureBox, FileTypeBox, JP2HeaderBox +from .jp2box import ColourSpecificationBox, ContiguousCodestreamBox +from .jp2box import ImageHeaderBox +from .lib import openjpeg as opj +from .lib import openjp2 as opj2 +from . import version +from .lib import c as libc class Jp2k(Jp2kBox): @@ -43,204 +46,32 @@ class Jp2k(Jp2kBox): ---------- filename : str The path to the JPEG 2000 file. + mode : str + The mode used to open the file. box : sequence List of top-level boxes in the file. Each box may in turn contain its own list of boxes. Will be empty if the file consists only of a raw codestream. - shape : tuple - Size of the image. - - Properties - ---------- - ignore_pclr_cmap_cdef : bool - whether or not to ignore the pclr, cmap, or cdef boxes during any - color transformation, defaults to False. - layer : int - zero-based number of quality layer to decode - verbose : bool - whether or not to print informational messages produced by the - OpenJPEG library, defaults to false - codestream : object - JP2 or J2K codestream object - - Examples - -------- - >>> import glymur - >>> jfile = glymur.data.nemo() - >>> jp2 = glymur.Jp2k(jfile) - >>> jp2.shape - (1456, 2592, 3) - >>> image = jp2[:] - >>> image.shape - (1456, 2592, 3) - - Read a lower resolution thumbnail. - - >>> thumbnail = jp2[::2, ::2] - >>> thumbnail.shape - (728, 1296, 3) """ - def __init__(self, filename, data=None, shape=None, **kwargs): + def __init__(self, filename, mode='rb'): """ - Only the filename parameter is required in order to read a JPEG 2000 - file. - Parameters ---------- filename : str or file - the path to JPEG 2000 file - image_data : ndarray, optional - image data to be written - shape : tuple - size of image data, only required when image_data is not provided - cbsize : tuple, optional - code block size (DY, DX) - cinema2k : int, optional - frames per second, either 24 or 48 - cinema4k : bool, optional - set to True to specify Cinema4K mode, defaults to false - colorspace : str, optional - either 'rgb' or 'gray' - cratios : iterable - compression ratios for successive layers - eph : bool, optional - if true, write SOP marker after each header packet - grid_offset : tuple, optional - 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 - mct : bool, optional - specifies usage of the multi component transform, if not - specified, defaults to True if the colorspace is RGB - modesw : int, optional - mode switch - 1 = BYPASS(LAZY) - 2 = RESET - 4 = RESTART(TERMALL) - 8 = VSC - 16 = ERTERM(SEGTERM) - 32 = SEGMARK(SEGSYM) - numres : int, optional - number of resolutions - prog : str, optional - progression order, one of "LRCP" "RLCP", "RPCL", "PCRL", "CPRL" - psnr : iterable, optional - different PSNR for successive layers - psizes : list, optional - list of precinct sizes, each precinct size tuple is defined in - (height x width) - sop : bool, optional - if true, write SOP marker before each packet - subsam : tuple, optional - subsampling factors (dy, dx) - tilesize : tuple, optional - numeric tuple specifying tile size in terms of (numrows, numcols), - not (X, Y) - verbose : bool, optional - print informational messages produced by the OpenJPEG library + The path to JPEG 2000 file. + mode : str, optional + The mode used to open the file. """ Jp2kBox.__init__(self) self.filename = filename - + self.mode = mode self.box = [] self._codec_format = None - self._colorspace = None - self._layer = 0 - self._codestream = None - if data is not None: - self._shape = data.shape - else: - self._shape = shape - - self._ignore_pclr_cmap_cdef = False - self._verbose = False # Parse the file for JP2/JPX contents only if we are reading it. - if data is None and shape is None: + if mode == 'rb': self.parse() - elif data is not None: - self._write(data, **kwargs) - - @property - def ignore_pclr_cmap_cdef(self): - return self._ignore_pclr_cmap_cdef - - @ignore_pclr_cmap_cdef.setter - def ignore_pclr_cmap_cdef(self, ignore_pclr_cmap_cdef): - self._ignore_pclr_cmap_cdef = ignore_pclr_cmap_cdef - - @property - def layer(self): - return self._layer - - @layer.setter - def layer(self, layer): - if version.openjpeg_version_tuple[0] < 2: - msg = "Layer property not supported unless the version of " - msg += "OpenJPEG is 2.0 or higher." - raise RuntimeError(msg) - self._layer = layer - - @property - def codestream(self): - if self._codestream is None: - self._codestream = self.get_codestream(header_only=True) - return self._codestream - - @codestream.setter - def codestream(self, the_codestream): - self._codestream = the_codestream - - @property - def verbose(self): - return self._verbose - - @verbose.setter - def verbose(self, verbose): - self._verbose = verbose - - @property - def shape(self): - if self._shape is not None: - return self._shape - - if self._codec_format == opj2.CODEC_J2K: - # get the image size from the codestream - cstr = self.codestream - height = cstr.segment[1].ysiz - width = cstr.segment[1].xsiz - num_components = len(cstr.segment[1].xrsiz) - else: - # try to get the image size from the IHDR box - jp2h = [box for box in self.box if box.box_id == 'jp2h'][0] - ihdr = [box for box in jp2h.box if box.box_id == 'ihdr'][0] - - height, width = ihdr.height, ihdr.width - num_components = ihdr.num_components - - if num_components == 1: - # but if there is a PCLR box, then we need to check that as - # well, as that turns a single-channel image into a - # multi-channel image - pclr = [box for box in jp2h.box if box.box_id == 'pclr'] - if len(pclr) > 0: - num_components = len(pclr[0].signed) - - if num_components == 1: - self.shape = (height, width) - else: - self.shape = (height, width, num_components) - - return self._shape - - @shape.setter - def shape(self, shape): - self._shape = shape - - def __repr__(self): - msg = "glymur.Jp2k('{0}')".format(self.filename) - return msg def __str__(self): metadata = ['File: ' + os.path.basename(self.filename)] @@ -248,7 +79,8 @@ class Jp2k(Jp2kBox): for box in self.box: metadata.append(str(box)) else: - metadata.append(str(self.codestream)) + codestream = self.get_codestream() + metadata.append(str(codestream)) return '\n'.join(metadata) def parse(self): @@ -305,83 +137,59 @@ class Jp2k(Jp2kBox): jp2h = [box for box in self.box if box.box_id == 'jp2h'][0] colrs = [box for box in jp2h.box if box.box_id == 'colr'] for colr in colrs: - if colr.method not in (core.ENUMERATED_COLORSPACE, - core.RESTRICTED_ICC_PROFILE): + if colr.method not in (ENUMERATED_COLORSPACE, + RESTRICTED_ICC_PROFILE): msg = "Color Specification box method must specify either " msg += "an enumerated colorspace or a restricted ICC " msg += "profile if the file type box brand is 'jp2 '." warnings.warn(msg) - def _set_cinema_params(self, cinema_mode, fps): - """Populate compression parameters structure for cinema2K. + def _populate_cparams(self, **kwargs): + """Populate compression parameters structure from input arguments. Parameters ---------- - params : ctypes struct - Corresponds to compression parameters structure used by the - library. - cinema_mode : str - Either 'cinema2k' or 'cinema4k' - fps : int - Frames per second, should be either 24 or 48. + cbsize : tuple, optional + Code block size (DY, DX). + cratios : iterable + Compression ratios for successive layers. + eph : bool, optional + If true, write SOP marker after each header packet. + grid_offset : tuple, optional + Offset (DY, DX) of the origin of the image in the reference grid. + mct : bool, optional + Specifies usage of the multi component transform. If not + specified, defaults to True if the colorspace is RGB. + modesw : int, optional + Mode switch. + 1 = BYPASS(LAZY) + 2 = RESET + 4 = RESTART(TERMALL) + 8 = VSC + 16 = ERTERM(SEGTERM) + 32 = SEGMARK(SEGSYM) + numres : int, optional + Number of resolutions. + prog : str, optional + Progression order, one of "LRCP" "RLCP", "RPCL", "PCRL", "CPRL". + psnr : iterable, optional + Different PSNR for successive layers. + psizes : list, optional + List of precinct sizes. Each precinct size tuple is defined in + (height x width). + sop : bool, optional + If true, write SOP marker before each packet. + subsam : tuple, optional + Subsampling factors (dy, dx). + tilesize : tuple, optional + Numeric tuple specifying tile size in terms of (numrows, numcols), + not (X, Y). + + Returns + ------- + cparams : CompressionParametersType(ctypes.Structure) + Corresponds to cparameters_t type in openjp2 headers. """ - if re.match("1.5|2.0.0", version.openjpeg_version) is not None: - msg = "Writing Cinema2K or Cinema4K files is not supported with " - msg += 'openjpeg library versions less than 2.0.1.' - raise IOError(msg) - - # Cinema modes imply MCT. - self._cparams.tcp_mct = 1 - - if cinema_mode == 'cinema2k': - if fps not in [24, 48]: - raise IOError('Cinema2K frame rate must be either 24 or 48.') - - if re.match("2.0", version.openjpeg_version) is not None: - # 2.0 API - if fps == 24: - self._cparams.cp_cinema = core.OPJ_CINEMA2K_24 - else: - self._cparams.cp_cinema = core.OPJ_CINEMA2K_48 - else: - # 2.1 API - if fps == 24: - self._cparams.rsiz = core.OPJ_PROFILE_CINEMA_2K - self._cparams.max_comp_size = core.OPJ_CINEMA_24_COMP - self._cparams.max_cs_size = core.OPJ_CINEMA_24_CS - else: - self._cparams.rsiz = core.OPJ_PROFILE_CINEMA_2K - self._cparams.max_comp_size = core.OPJ_CINEMA_48_COMP - self._cparams.max_cs_size = core.OPJ_CINEMA_48_CS - - else: - # cinema4k - if re.match("2.0", version.openjpeg_version) is not None: - # 2.0 API - self._cparams.cp_cinema = core.OPJ_CINEMA4K_24 - else: - # 2.1 API - self._cparams.rsiz = core.OPJ_PROFILE_CINEMA_4K - - def _populate_cparams(self, img_array, **kwargs): - """Directs processing of write method arguments. - - Parameters - ---------- - img_array : ndarray - image data to be written to file - kwargs : dictionary - non-image keyword inputs provided to write method - """ - if ((('cinema2k' in kwargs or 'cinema4k' in kwargs) and - (len(set(kwargs)) > 1))): - msg = "Cannot specify cinema2k/cinema4k along with other options." - raise IOError(msg) - - if 'cratios' in kwargs and 'psnr' in kwargs: - msg = "Cannot specify cratios and psnr together." - raise IOError(msg) - if version.openjpeg_version_tuple[0] == 1: cparams = opj.set_default_encoder_parameters() else: @@ -402,19 +210,6 @@ class Jp2k(Jp2kBox): cparams.tcp_numlayers = 1 cparams.cp_disto_alloc = 1 - if 'irreversible' in kwargs and kwargs['irreversible'] is True: - cparams.irreversible = 1 - - if 'cinema2k' in kwargs: - self._cparams = cparams - self._set_cinema_params('cinema2k', kwargs['cinema2k']) - return - - if 'cinema4k' in kwargs: - self._cparams = cparams - self._set_cinema_params('cinema4k', kwargs['cinema4k']) - return - if 'cbsize' in kwargs: cparams.cblockw_init = kwargs['cbsize'][1] cparams.cblockh_init = kwargs['cbsize'][0] @@ -443,7 +238,7 @@ class Jp2k(Jp2kBox): if 'prog' in kwargs: prog = kwargs['prog'].upper() - cparams.prog_order = core.PROGRESSION_ORDER[prog] + cparams.prog_order = PROGRESSION_ORDER[prog] if 'psnr' in kwargs: cparams.tcp_numlayers = len(kwargs['psnr']) @@ -470,9 +265,45 @@ class Jp2k(Jp2kBox): cparams.cp_tdy = kwargs['tilesize'][0] cparams.tile_size_on = opj2.TRUE + return cparams + + def _process_write_inputs(self, img_array, colorspace=None, **kwargs): + """Directs processing of write method arguments. + + It's somewhat awkward to process all the kwargs arguments at once. + The "colorspace" is not a parameter that gets processed into the + compression parameters structure, and it unfortunately must be handled + in the middle of the compression parameter processing. + + Parameters + ---------- + img_array : ndarray + Image data to be written to file. + colorspace : str, optional + Either 'rgb' or 'gray'. + + Returns + ------- + cparams : CompressionParametersType(ctypes.Structure) + Corresponds to cparameters_t type in openjp2 headers. + colorspace : int + Either CLRSPC_SRGB or CLRSPC_GRAY + """ + + if 'cratios' in kwargs and 'psnr' in kwargs: + msg = "Cannot specify cratios and psnr together." + raise IOError(msg) + + cparams = self._populate_cparams(**kwargs) + _validate_compression_params(img_array, cparams) + + colorspace = _unpack_colorspace(colorspace, img_array, cparams) + try: mct = kwargs['mct'] - if mct and self._colorspace == opj2.CLRSPC_GRAY: + if mct and colorspace == opj2.CLRSPC_GRAY: + # Cannot check for this in the validate routine, as we need + # to know what the target colorspace has been determined to be. msg = "Cannot specify usage of the multi component transform " msg += "if the colorspace is gray." raise IOError(msg) @@ -480,62 +311,116 @@ class Jp2k(Jp2kBox): except KeyError: # If the multi component transform was not specified, we infer # that it should be used if the color space is RGB. - if self._colorspace == opj2.CLRSPC_SRGB: + if colorspace == opj2.CLRSPC_SRGB: cparams.tcp_mct = 1 else: cparams.tcp_mct = 0 - self._validate_compression_params(img_array, cparams, **kwargs) + return cparams, colorspace - self._cparams = cparams - - def _write(self, img_array, verbose=False, **kwargs): + def write(self, img_array, verbose=False, **kwargs): """Write image data to a JP2/JPX/J2k file. Intended usage of the various parameters follows that of OpenJPEG's opj_compress utility. This method can only be used to create JPEG 2000 images that can fit in memory. + + Parameters + ---------- + img_array : ndarray + Image data to be written to file. + cbsize : tuple, optional + Code block size (DY, DX). + colorspace : str, optional + Either 'rgb' or 'gray'. + cratios : iterable + Compression ratios for successive layers. + eph : bool, optional + If true, write SOP marker after each header packet. + grid_offset : tuple, optional + Offset (DY, DX) of the origin of the image in the reference grid. + mct : bool, optional + Specifies usage of the multi component transform. If not + specified, defaults to True if the colorspace is RGB. + modesw : int, optional + Mode switch. + 1 = BYPASS(LAZY) + 2 = RESET + 4 = RESTART(TERMALL) + 8 = VSC + 16 = ERTERM(SEGTERM) + 32 = SEGMARK(SEGSYM) + numres : int, optional + Number of resolutions. + prog : str, optional + Progression order, one of "LRCP" "RLCP", "RPCL", "PCRL", "CPRL". + psnr : iterable, optional + Different PSNR for successive layers. + psizes : list, optional + List of precinct sizes. Each precinct size tuple is defined in + (height x width). + sop : bool, optional + If true, write SOP marker before each packet. + subsam : tuple, optional + Subsampling factors (dy, dx). + tilesize : tuple, optional + Numeric tuple specifying tile size in terms of (numrows, numcols), + not (X, Y). + verbose : bool, optional + Print informational messages produced by the OpenJPEG library. + + Examples + -------- + >>> import glymur + >>> jfile = glymur.data.nemo() + >>> jp2 = glymur.Jp2k(jfile) + >>> data = jp2.read(rlevel=1) + >>> from tempfile import NamedTemporaryFile + >>> tfile = NamedTemporaryFile(suffix='.jp2', delete=False) + >>> j = Jp2k(tfile.name, mode='wb') + >>> j.write(data.astype(np.uint8)) + + Raises + ------ + glymur.LibraryNotFoundError + If glymur is unable to load the openjp2 library. """ - if re.match("0|1.[0-4]", version.openjpeg_version) is not None: - msg = "You must have at least version 1.5 of OpenJPEG " - msg += "in order to write images." - raise RuntimeError(msg) - - self._determine_colorspace(**kwargs) - self._populate_cparams(img_array, **kwargs) - if opj2.OPENJP2 is not None: - self._write_openjp2(img_array, verbose=verbose) + self._write_openjp2(img_array, verbose=verbose, **kwargs) + elif opj.OPENJPEG is not None: + self._write_openjpeg(img_array, verbose=verbose, **kwargs) else: - self._write_openjpeg(img_array, verbose=verbose) + raise LibraryNotFoundError("You must have at least version 1.5 of " + "OpenJPEG before using this " + "functionality.") - def _write_openjpeg(self, img_array, verbose=False): + def _write_openjpeg(self, img_array, verbose=False, **kwargs): """ Write JPEG 2000 file using OpenJPEG 1.5 interface. """ + cparams, colorspace = self._process_write_inputs(img_array, **kwargs) + if img_array.ndim == 2: # Force the image to be 3D. Just makes things easier later on. img_array = img_array.reshape(img_array.shape[0], img_array.shape[1], 1) - self._populate_comptparms(img_array) + comptparms = _populate_comptparms(img_array, cparams) with ExitStack() as stack: - image = opj.image_create(self._comptparms, self._colorspace) + image = opj.image_create(comptparms, colorspace) stack.callback(opj.image_destroy, image) numrows, numcols, numlayers = img_array.shape # set image offset and reference grid - image.contents.x0 = self._cparams.image_offset_x0 - image.contents.y0 = self._cparams.image_offset_y0 - image.contents.x1 = (image.contents.x0 - + (numcols - 1) * self._cparams.subsampling_dx - + 1) - image.contents.y1 = (image.contents.y0 - + (numrows - 1) * self._cparams.subsampling_dy - + 1) + image.contents.x0 = cparams.image_offset_x0 + image.contents.y0 = cparams.image_offset_y0 + image.contents.x1 = image.contents.x0 \ + + (numcols - 1) * cparams.subsampling_dx + 1 + image.contents.y1 = image.contents.y0 \ + + (numrows - 1) * cparams.subsampling_dy + 1 # Stage the image data to the openjpeg data structure. for k in range(0, numlayers): @@ -545,7 +430,7 @@ class Jp2k(Jp2kBox): src = layer.ctypes.data ctypes.memmove(dest, src, layer.nbytes) - cinfo = opj.create_compress(self._cparams.codec_fmt) + cinfo = opj.create_compress(cparams.codec_fmt) stack.callback(opj.destroy_compress, cinfo) # Setup the info, warning, and error handlers. @@ -559,7 +444,7 @@ class Jp2k(Jp2kBox): event_mgr.error_handler = ctypes.cast(_ERROR_CALLBACK, ctypes.c_void_p) - opj.setup_encoder(cinfo, ctypes.byref(self._cparams), image) + opj.setup_encoder(cinfo, ctypes.byref(cparams), image) cio = opj.cio_open(cinfo) stack.callback(opj.cio_close, cio) @@ -576,159 +461,51 @@ class Jp2k(Jp2kBox): self.parse() - def _validate_compression_params(self, img_array, cparams, **kwargs): - """Check that the compression parameters are valid. - Parameters - ---------- - img_array : ndarray - Image data to be written to file. - cparams : CompressionParametersType(ctypes.Structure) - Corresponds to cparameters_t type in openjp2 headers. + def _write_openjp2(self, img_array, verbose=False, **kwargs): """ - # Cannot specify a colorspace with J2K. - if cparams.codec_fmt == opj2.CODEC_J2K and 'colorspace' in kwargs: - msg = 'Do not specify a colorspace when writing a raw ' - msg += 'codestream.' - raise IOError(msg) - - # Code block size - code_block_specified = False - if cparams.cblockw_init != 0 and cparams.cblockh_init != 0: - # These fields ARE zero if uninitialized. - width = cparams.cblockw_init - height = cparams.cblockh_init - code_block_specified = True - if height * width > 4096 or height < 4 or width < 4: - msg = "Code block area cannot exceed 4096. " - msg += "Code block height and width must be larger than 4." - raise IOError(msg) - if ((math.log(height, 2) != math.floor(math.log(height, 2)) or - math.log(width, 2) != math.floor(math.log(width, 2)))): - msg = "Bad code block size ({0}, {1}), " - msg += "must be powers of 2." - raise IOError(msg.format(height, width)) - - # Precinct size - if cparams.res_spec != 0: - # precinct size was not specified if this field is zero. - for j in range(cparams.res_spec): - prch = cparams.prch_init[j] - prcw = cparams.prcw_init[j] - if j == 0 and code_block_specified: - height, width = cparams.cblockh_init, cparams.cblockw_init - if height * 2 > prch or width * 2 > prcw: - msg = "Highest Resolution precinct size must be at " - msg += "least twice that of the code block dimensions." - raise IOError(msg) - if ((math.log(prch, 2) != math.floor(math.log(prch, 2)) or - math.log(prcw, 2) != math.floor(math.log(prcw, 2)))): - msg = "Bad precinct sizes ({0}, {1}), " - msg += "must be powers of 2." - raise IOError(msg.format(prch, prcw)) - - # What would the point of 1D images be? - if img_array.ndim == 1 or img_array.ndim > 3: - msg = "{0}D imagery is not allowed.".format(img_array.ndim) - raise IOError(msg) - - if re.match("2.0.0", version.openjpeg_version) is not None: - if (((img_array.ndim != 2) and - (img_array.shape[2] != 1 and img_array.shape[2] != 3))): - msg = "Writing images is restricted to single-channel " - msg += "greyscale images or three-channel RGB images when " - msg += "the OpenJPEG library version is the official 2.0.0 " - msg += "release." - raise IOError(msg) - - if img_array.dtype != np.uint8 and img_array.dtype != np.uint16: - msg = "Only uint8 and uint16 datatypes are currently supported " - msg += "when writing." - raise RuntimeError(msg) - - def _determine_colorspace(self, colorspace=None, **kwargs): - """Determine the colorspace from the supplied inputs. - - Parameters - ---------- - colorspace : str, optional - Either 'rgb' or 'gray'. + Write JPEG 2000 file using OpenJPEG 1.5 interface. """ - if colorspace is None: - # Must infer the colorspace from the image dimensions. - if len(self.shape) < 3: - # A single channel image is grayscale. - self._colorspace = opj2.CLRSPC_GRAY - elif self.shape[2] == 1 or self.shape[2] == 2: - # A single channel image or an image with two channels is going - # to be greyscale. - self._colorspace = opj2.CLRSPC_GRAY - else: - # Anything else must be RGB, right? - self._colorspace = opj2.CLRSPC_SRGB - else: - if colorspace.lower() not in ('rgb', 'grey', 'gray'): - msg = 'Invalid colorspace "{0}"'.format(colorspace) - raise IOError(msg) - elif colorspace.lower() == 'rgb' and self.shape[2] < 3: - msg = 'RGB colorspace requires at least 3 components.' - raise IOError(msg) + cparams, colorspace = self._process_write_inputs(img_array, **kwargs) - # Turn the colorspace from a string to the enumerated value that - # the library expects. - COLORSPACE_MAP = {'rgb': opj2.CLRSPC_SRGB, - 'gray': opj2.CLRSPC_GRAY, - 'grey': opj2.CLRSPC_GRAY, - 'ycc': opj2.CLRSPC_YCC} - - self._colorspace = COLORSPACE_MAP[colorspace.lower()] - - def _write_openjp2(self, img_array, verbose=False): - """ - Write JPEG 2000 file using OpenJPEG 2.x interface. - """ if img_array.ndim == 2: # Force the image to be 3D. Just makes things easier later on. numrows, numcols = img_array.shape img_array = img_array.reshape(numrows, numcols, 1) - self._populate_comptparms(img_array) + comptparms = _populate_comptparms(img_array, cparams) with ExitStack() as stack: - image = opj2.image_create(self._comptparms, self._colorspace) + image = opj2.image_create(comptparms, colorspace) stack.callback(opj2.image_destroy, image) - self._populate_image_struct(image, img_array) - - codec = opj2.create_compress(self._cparams.codec_fmt) + _populate_image_struct(cparams, image, img_array) + + codec = opj2.create_compress(cparams.codec_fmt) stack.callback(opj2.destroy_codec, codec) - - if self._verbose or verbose: - info_handler = _INFO_CALLBACK - else: - info_handler = None - + + info_handler = _INFO_CALLBACK if verbose else None opj2.set_info_handler(codec, info_handler) opj2.set_warning_handler(codec, _WARNING_CALLBACK) opj2.set_error_handler(codec, _ERROR_CALLBACK) - - opj2.setup_encoder(codec, self._cparams, image) - - if re.match("2.0", version.openjpeg_version) is not None: + + opj2.setup_encoder(codec, cparams, image) + + if re.match("2.0", version.openjpeg_version): fptr = libc.fopen(self.filename, 'wb') strm = opj2.stream_create_default_file_stream(fptr, False) stack.callback(opj2.stream_destroy, strm) stack.callback(libc.fclose, fptr) else: - # Introduced in 2.1 devel series. + # This routine introduced in 2.1. strm = opj2.stream_create_default_file_stream(self.filename, - False) + False) stack.callback(opj2.stream_destroy, strm) - + opj2.start_compress(codec, image, strm) opj2.encode(codec, strm) opj2.end_compress(codec, strm) - + # Refresh the metadata. self.parse() @@ -738,19 +515,14 @@ class Jp2k(Jp2kBox): Parameters ---------- box : Jp2Box - Instance of a JP2 box. Only UUID and XML boxes can currently be - appended. + Instance of a JP2 box. Currently only XML boxes are allowed. """ if self._codec_format == opj2.CODEC_J2K: msg = "Only JP2 files can currently have boxes appended to them." raise IOError(msg) - if not ((box.box_id == 'xml ') or - (box.box_id == 'uuid' and - box.uuid == UUID('be7acfcb-97a9-42e8-9c71-999491e3afac'))): - msg = "Only XML boxes and XMP UUID boxes can currently be " - msg += "appended." - raise IOError(msg) + if box.box_id != 'xml ': + raise IOError("Only XML boxes can currently be appended.") # Check the last box. If the length field is zero, then rewrite # the length field to reflect the true length of the box. @@ -774,12 +546,7 @@ class Jp2k(Jp2kBox): self.parse() def wrap(self, filename, boxes=None): - """Create a new JP2/JPX file wrapped in a new set of JP2 boxes. - - This method is primarily aimed at wrapping a raw codestream in a set of - of JP2 boxes (turning it into a JP2 file instead of just a raw - codestream), or rewrapping a codestream in a JP2 file in a new "jacket" - of JP2 boxes. + """Write the codestream back out to file, wrapped in new JP2 jacket. Parameters ---------- @@ -789,8 +556,6 @@ class Jp2k(Jp2kBox): JP2 box definitions to define the JP2 file format. If not provided, a default ""jacket" is assumed, consisting of JP2 signature, file type, JP2 header, and contiguous codestream boxes. - A JPX file rewrapped without the boxes argument results in a JP2 - file encompassing the first codestream. Returns ------- @@ -806,7 +571,19 @@ class Jp2k(Jp2kBox): >>> jp2 = j2k.wrap(tfile.name) """ if boxes is None: - boxes = self._get_default_jp2_boxes() + # Try to create a reasonable default. + boxes = [JPEG2000SignatureBox(), + FileTypeBox(), + JP2HeaderBox(), + ContiguousCodestreamBox()] + codestream = self.get_codestream() + height = codestream.segment[1].ysiz + width = codestream.segment[1].xsiz + num_components = len(codestream.segment[1].xrsiz) + boxes[2].box = [ImageHeaderBox(height=height, + width=width, + num_components=num_components), + ColourSpecificationBox(colorspace=SRGB)] _validate_jp2_box_sequence(boxes) @@ -815,218 +592,53 @@ class Jp2k(Jp2kBox): if box.box_id != 'jp2c': box.write(ofile) else: - self._write_wrapped_codestream(ofile, box) + # The codestream gets written last. + if len(self.box) == 0: + # Am I a raw codestream? If so, then it is pretty + # easy, just write the codestream box header plus all + # of myself out to file. + ofile.write(struct.pack('>I', self.length + 8)) + ofile.write('jp2c'.encode()) + with open(self.filename, 'rb') as ifile: + ofile.write(ifile.read()) + else: + # OK, I'm a jp2 file. Need to find out where the + # raw codestream actually starts. + jp2c = [box for box in self.box + if box.box_id == 'jp2c'] + jp2c = jp2c[0] + ofile.write(struct.pack('>I', jp2c.length + 8)) + ofile.write('jp2c'.encode()) + with open(self.filename, 'rb') as ifile: + # Seek 8 bytes past the L, T fields to get to the + # raw codestream. + ifile.seek(jp2c.offset + 8) + ofile.write(ifile.read(jp2c.length - 8)) + ofile.flush() jp2 = Jp2k(filename) return jp2 - def _write_wrapped_codestream(self, ofile, box): - """Write wrapped codestream.""" - # Codestreams require a bit more care. - # Am I a raw codestream? - if len(self.box) == 0: - # Yes, just write the codestream box header plus all - # of myself out to file. - ofile.write(struct.pack('>I', self.length + 8)) - ofile.write(b'jp2c') - with open(self.filename, 'rb') as ifile: - ofile.write(ifile.read()) - return - - # OK, I'm a jp2/jpx file. Need to find out where the raw codestream - # actually starts. - offset = box.offset - if offset == -1: - if self.box[1].brand == 'jpx ': - msg = "The codestream box must have its offset and " - msg += "length attributes fully specified if the file " - msg += "type brand is JPX." - raise IOError(msg) - - # Find the first codestream in the file. - jp2c = [_box for _box in self.box if _box.box_id == 'jp2c'] - offset = jp2c[0].offset - - # Ready to write the codestream. - with open(self.filename, 'rb') as ifile: - ifile.seek(offset) - - # Verify that the specified codestream is right. - read_buffer = ifile.read(8) - L, T = struct.unpack_from('>I4s', read_buffer, 0) - if T != b'jp2c': - msg = "Unable to locate the specified codestream." - raise IOError(msg) - if L == 0: - # The length of the box is presumed to last until the end of - # the file. Compute the effective length of the box. - L = os.path.getsize(ifile.name) - ifile.tell() + 8 - - elif L == 1: - # The length of the box is in the XL field, a 64-bit value. - read_buffer = ifile.read(8) - L, = struct.unpack('>Q', read_buffer) - - ifile.seek(offset) - read_buffer = ifile.read(L) - ofile.write(read_buffer) - - def _get_default_jp2_boxes(self): - """Create a default set of JP2 boxes.""" - # Try to create a reasonable default. - boxes = [JPEG2000SignatureBox(), - FileTypeBox(), - JP2HeaderBox(), - ContiguousCodestreamBox()] - height = self.codestream.segment[1].ysiz - width = self.codestream.segment[1].xsiz - num_components = len(self.codestream.segment[1].xrsiz) - if num_components < 3: - colorspace = core.GREYSCALE - else: - if len(self.box) == 0: - # Best guess is SRGB - colorspace = core.SRGB - else: - # Take whatever the first jp2 header / color specification - # says. - jp2hs = [box for box in self.box if box.box_id == 'jp2h'] - colorspace = jp2hs[0].box[1].colorspace - - boxes[2].box = [ImageHeaderBox(height=height, width=width, - num_components=num_components), - ColourSpecificationBox(colorspace=colorspace)] - - return boxes - - def __setitem__(self, index, data): - """ - Slicing protocol. - """ - if ((isinstance(index, slice) and - (index.start is None and - index.stop is None and - index.step is None)) or (index is Ellipsis)): - # Case of jp2[:] = data, i.e. write the entire image. - # - # Should have a slice object where start = stop = step = None - self._write(data) - else: - msg = "Partial write operations are currently not allowed." - raise TypeError(msg) - - def __getitem__(self, pargs): - """ - Slicing protocol. - """ - if len(self.shape) == 2: - numrows, numcols = self.shape - numbands = 1 - else: - numrows, numcols, numbands = self.shape - - if isinstance(pargs, int): - # Not a very good use of this protocol, but technically legal. - # This retrieves a single row. - row = pargs - area = (row, 0, row + 1, numcols) - return self._read(area=area).squeeze() - - if pargs is Ellipsis: - # Case of jp2[...] - return self._read() - - if isinstance(pargs, slice): - if (((pargs.start is None) and - (pargs.stop is None) and - (pargs.step is None))): - # Case of jp2[:] - return self._read() - - # Corner case of jp2[x] where x is a slice object with non-null - # members. Just augment it with an ellipsis and let the code - # below handle it. - pargs = (pargs, Ellipsis) - - if isinstance(pargs, tuple) and any(x is Ellipsis for x in pargs): - # Remove the first ellipsis we find. - rows = slice(0, numrows) - cols = slice(0, numcols) - bands = slice(0, numbands) - if pargs[0] is Ellipsis: - if len(pargs) == 2: - newindex = (rows, cols, pargs[1]) - else: - newindex = (rows, pargs[1], pargs[2]) - elif pargs[1] is Ellipsis: - if len(pargs) == 2: - newindex = (pargs[0], cols, bands) - else: - newindex = (pargs[0], cols, pargs[2]) - else: - # Assume that we don't have 4D imagery, of course. - newindex = (pargs[0], pargs[1], bands) - - # Run once again because it is possible that there's another - # Ellipsis object in the 2nd or 3rd position. - return self.__getitem__(newindex) - - if isinstance(pargs, tuple) and any(isinstance(x, int) for x in pargs): - # Replace the first such integer argument, replace it with a slice. - lst = list(pargs) - predicate = lambda x: not isinstance(x[1], int) - g = filterfalse(predicate, enumerate(pargs)) - idx = next(g)[0] - lst[idx] = slice(pargs[idx], pargs[idx] + 1) - newindex = tuple(lst) - - # Invoke array-based slicing again, as there may be additional - # integer argument remaining. - data = self.__getitem__(newindex) - - # Reduce dimensionality in the scalar dimension. - return np.squeeze(data, axis=idx) - - # Assuming pargs is a tuple of slices from now on. - rows = pargs[0] - cols = pargs[1] - if len(pargs) == 2: - bands = slice(None, None, None) - else: - bands = pargs[2] - - rows_step = 1 if rows.step is None else rows.step - cols_step = 1 if cols.step is None else cols.step - if rows_step != cols_step: - msg = "Row and column strides must be the same." - raise IndexError(msg) - - # Ok, reduce layer step is the same in both xy directions, so just take - # one of them. - step = rows_step - - # Check if the step size is a power of 2. - if np.abs(np.log2(step) - np.round(np.log2(step))) > 1e-6: - msg = "Row and column strides must be powers of 2." - raise IndexError(msg) - rlevel = np.int(np.round(np.log2(step))) - - area = (0 if rows.start is None else rows.start, - 0 if cols.start is None else cols.start, - numrows if rows.stop is None else rows.stop, - numcols if cols.stop is None else cols.stop - ) - data = self._read(area=area, rlevel=rlevel) - if len(pargs) == 2: - return data - - # Ok, 3 arguments in pargs. - return data[:, :, bands] - - def _read(self, **kwargs): + def read(self, **kwargs): """Read a JPEG 2000 image. + Parameters + ---------- + rlevel : int, optional + Factor by which to rlevel output resolution. Use -1 to get the + lowest resolution thumbnail. This is the only keyword option + available to use when only the OpenJPEG version 1.5.1 is present. + layer : int, optional + Number of quality layer to decode. + area : tuple, optional + Specifies decoding image area, + (first_row, first_col, last_row, last_col) + tile : int, optional + Number of tile to decode. + verbose : bool, optional + Print informational messages produced by the OpenJPEG library. + Returns ------- img_array : ndarray @@ -1034,67 +646,51 @@ class Jp2k(Jp2kBox): Raises ------ + glymur.LibraryNotFoundError + If glymur is unable to load either the openjpeg or openjp2 + libraries. IOError If the image has differing subsample factors. + + Examples + -------- + >>> import glymur + >>> jfile = glymur.data.nemo() + >>> jp = glymur.Jp2k(jfile) + >>> image = jp.read() + >>> image.shape + (1456, 2592, 3) + + Read the lowest resolution thumbnail. + + >>> thumbnail = jp.read(rlevel=-1) + >>> thumbnail.shape + (728, 1296, 3) """ - if version.openjpeg_version_tuple[0] < 2: + if opj2.OPENJP2 is not None: + img = self._read_openjp2(**kwargs) + elif opj.OPENJPEG is not None: img = self._read_openjpeg(**kwargs) else: - img = self._read_openjp2(**kwargs) - return img - - def read(self, **kwargs): - """ - """ - # Read a JPEG 2000 image. - # - # Parameters - # ---------- - # rlevel : int, optional - # Factor by which to rlevel output resolution. Use -1 to get the - # lowest resolution thumbnail. This is the only keyword option - # available to use when the OpenJPEG version is 1.5 or earlier. - # layer : int, optional - # Number of quality layer to decode. - # area : tuple, optional - # Specifies decoding image area, - # (first_row, first_col, last_row, last_col) - # tile : int, optional - # Number of tile to decode. - # verbose : bool, optional - # Print informational messages produced by the OpenJPEG library. - # - # Returns - # ------- - # img_array : ndarray - # The image data. - # - # Raises - # ------ - # IOError - # If the image has differing subsample factors. - - if 'ignore_pclr_cmap_cdef' in kwargs: - self.ignore_pclr_cmap_cdef = kwargs['ignore_pclr_cmap_cdef'] - warnings.warn("Use array-style slicing instead.", DeprecationWarning) - if version.openjpeg_version_tuple[0] < 2: - img = self._read_openjpeg(**kwargs) - else: - img = self._read_openjp2(**kwargs) + raise LibraryNotFoundError("You must have either a recent version " + "of OpenJPEG or the development " + "version of OpenJP2 installed before " + "using this functionality.") return img def _subsampling_sanity_check(self): """Check for differing subsample factors. """ - dxs = np.array(self.codestream.segment[1].xrsiz) - dys = np.array(self.codestream.segment[1].yrsiz) + codestream = self.get_codestream(header_only=True) + dxs = np.array(codestream.segment[1].xrsiz) + dys = np.array(codestream.segment[1].yrsiz) if np.any(dxs - dxs[0]) or np.any(dys - dys[0]): msg = "Components must all have the same subsampling factors " msg += "to use this method. Please consider using OPENJP2 and " msg += "the read_bands method instead." raise RuntimeError(msg) - def _read_openjpeg(self, rlevel=0, verbose=False, area=None): + def _read_openjpeg(self, rlevel=0, verbose=False): """Read a JPEG 2000 image using libopenjpeg. Parameters @@ -1104,9 +700,6 @@ class Jp2k(Jp2kBox): lowest resolution thumbnail. verbose : bool, optional Print informational messages produced by the OpenJPEG library. - area : tuple, optional - Specifies decoding image area, - (first_row, first_col, last_row, last_col) Returns ------- @@ -1120,27 +713,44 @@ class Jp2k(Jp2kBox): """ self._subsampling_sanity_check() - self._populate_dparams(rlevel) + if rlevel != 0: + # Must check the specified rlevel against the maximum. + # OpenJPEG 1.3 will segfault if rlevel is too high. + codestream = self.get_codestream() + max_rlevel = codestream.segment[2].spcod[4] + if rlevel == -1: + # -1 is shorthand for the largest rlevel + rlevel = max_rlevel + if rlevel < -1 or rlevel > max_rlevel: + msg = "rlevel must be in the range [-1, {0}] for this image." + msg = msg.format(max_rlevel) + raise IOError(msg) with ExitStack() as stack: try: - self._dparams.decod_format = self._codec_format + # Set decoding parameters. + dparameters = opj.DecompressionParametersType() + opj.set_default_decoder_parameters(ctypes.byref(dparameters)) + dparameters.cp_reduce = rlevel + dparameters.decod_format = self._codec_format - dinfo = opj.create_decompress(self._dparams.decod_format) + infile = self.filename.encode() + nelts = opj.PATH_LEN - len(infile) + infile += b'0' * nelts + dparameters.infile = infile + + dinfo = opj.create_decompress(dparameters.decod_format) event_mgr = opj.EventMgrType() info_handler = ctypes.cast(_INFO_CALLBACK, ctypes.c_void_p) - if verbose or self._verbose: - event_mgr.info_handler = info_handler - else: - event_mgr.info_handler = None + event_mgr.info_handler = info_handler if verbose else None event_mgr.warning_handler = ctypes.cast(_WARNING_CALLBACK, ctypes.c_void_p) event_mgr.error_handler = ctypes.cast(_ERROR_CALLBACK, ctypes.c_void_p) opj.set_event_mgr(dinfo, ctypes.byref(event_mgr)) - opj.setup_decoder(dinfo, self._dparams) + opj.setup_decoder(dinfo, dparameters) with open(self.filename, 'rb') as fptr: src = fptr.read() @@ -1162,21 +772,9 @@ class Jp2k(Jp2kBox): # data 2D instead of 3D. data.shape = data.shape[0:2] - if area is not None: - x0, y0, x1, y1 = area - extent = 2 ** rlevel - if x1 - x0 < extent or y1 - y0 < extent: - msg = "Decoded area is too small." - raise IOError(msg) - - area = [int(round(float(x)/extent + 2 ** -20)) for x in area] - rows = slice(area[0], area[2], None) - cols = slice(area[1], area[3], None) - data = data[rows, cols] - return data - def _read_openjp2(self, rlevel=0, layer=None, area=None, tile=None, + def _read_openjp2(self, rlevel=0, layer=0, area=None, tile=None, verbose=False): """Read a JPEG 2000 image using libopenjp2. @@ -1205,48 +803,43 @@ class Jp2k(Jp2kBox): RuntimeError If the image has differing subsample factors. """ - if layer is not None: - self._layer = layer - self._subsampling_sanity_check() - self._populate_dparams(rlevel, tile=tile, area=area) + dparam = self._populate_dparam(layer, rlevel, area, tile) with ExitStack() as stack: - if re.match("2.1", version.openjpeg_version): - filename = self.filename - stream = opj2.stream_create_default_file_stream(filename, True) - stack.callback(opj2.stream_destroy, stream) - else: + if re.match("2.0", version.openjpeg_version): fptr = libc.fopen(self.filename, 'rb') stack.callback(libc.fclose, fptr) stream = opj2.stream_create_default_file_stream(fptr, True) stack.callback(opj2.stream_destroy, stream) + else: + filename = self.filename + stream = opj2.stream_create_default_file_stream(filename, True) + stack.callback(opj2.stream_destroy, stream) + codec = opj2.create_decompress(self._codec_format) stack.callback(opj2.destroy_codec, codec) opj2.set_error_handler(codec, _ERROR_CALLBACK) opj2.set_warning_handler(codec, _WARNING_CALLBACK) - - if self._verbose or verbose: + if verbose: opj2.set_info_handler(codec, _INFO_CALLBACK) else: opj2.set_info_handler(codec, None) - opj2.setup_decoder(codec, self._dparams) + opj2.setup_decoder(codec, dparam) image = opj2.read_header(stream, codec) stack.callback(opj2.image_destroy, image) - if self._dparams.nb_tile_to_decode: - opj2.get_decoded_tile(codec, stream, image, - self._dparams.tile_index) + if dparam.nb_tile_to_decode: + opj2.get_decoded_tile(codec, stream, image, dparam.tile_index) else: opj2.set_decode_area(codec, image, - self._dparams.DA_x0, self._dparams.DA_y0, - self._dparams.DA_x1, self._dparams.DA_y1) + dparam.DA_x0, dparam.DA_y0, + dparam.DA_x1, dparam.DA_y1) opj2.decode(codec, stream, image) - - opj2.end_decompress(codec, stream) + opj2.end_decompress(codec, stream) img_array = extract_image_cube(image) @@ -1255,50 +848,41 @@ class Jp2k(Jp2kBox): return img_array - def _populate_dparams(self, rlevel, tile=None, area=None): + def _populate_dparam(self, layer, rlevel, area, tile): """Populate decompression structure with appropriate input parameters. Parameters ---------- - rlevel : int + layer : int, optional + Number of quality layer to decode. + rlevel : int, optional Factor by which to rlevel output resolution. - area : tuple + area : tuple, optional Specifies decoding image area, (first_row, first_col, last_row, last_col) - tile : int + tile : int, optional Number of tile to decode. + + Returns + ------- + dparam : DecompressionParametersType (ctypes) + Corresponds to openjp2 decompression parameters structure. """ - if opj2.OPENJP2 is not None: - dparam = opj2.set_default_decoder_parameters() - else: - dparam = opj.DecompressionParametersType() - opj.set_default_decoder_parameters(ctypes.byref(dparam)) + dparam = opj2.set_default_decoder_parameters() infile = self.filename.encode() nelts = opj2.PATH_LEN - len(infile) infile += b'0' * nelts dparam.infile = infile - if self.ignore_pclr_cmap_cdef: - # Return raw codestream components. - dparam.flags |= 1 - dparam.decod_format = self._codec_format - dparam.cp_layer = self._layer - - # Must check the specified rlevel against the maximum. - if rlevel != 0: - # Must check the specified rlevel against the maximum. - max_rlevel = self.codestream.segment[2].spcod[4] - if rlevel == -1: - # -1 is shorthand for the largest rlevel - rlevel = max_rlevel - elif rlevel < -1 or rlevel > max_rlevel: - msg = "rlevel must be in the range [-1, {0}] for this image." - msg = msg.format(max_rlevel) - raise IOError(msg) + dparam.cp_layer = layer + if rlevel == -1: + # Get the lowest resolution thumbnail. + codestream = self.get_codestream() + rlevel = codestream.segment[2].spcod[4] dparam.cp_reduce = rlevel if area is not None: @@ -1315,14 +899,10 @@ class Jp2k(Jp2kBox): dparam.tile_index = tile dparam.nb_tile_to_decode = 1 - if self.ignore_pclr_cmap_cdef: - # Return raw codestream components. - dparam.flags |= 1 + return dparam - self._dparams = dparam - - def read_bands(self, rlevel=0, layer=None, area=None, tile=None, - verbose=False, ignore_pclr_cmap_cdef=False): + def read_bands(self, rlevel=0, layer=0, area=None, tile=None, + verbose=False): """Read a JPEG 2000 image. The only time you should use this method is when the image has @@ -1340,9 +920,6 @@ class Jp2k(Jp2kBox): (first_row, first_col, last_row, last_col) tile : int, optional Number of tile to decode. - ignore_pclr_cmap_cdef : bool - Whether or not to ignore the pclr, cmap, or cdef boxes during any - color transformation. Defaults to False. verbose : bool, optional Print informational messages produced by the OpenJPEG library. @@ -1361,28 +938,29 @@ class Jp2k(Jp2kBox): >>> jfile = glymur.data.nemo() >>> jp = glymur.Jp2k(jfile) >>> components_lst = jp.read_bands(rlevel=1) + + Raises + ------ + glymur.LibraryNotFoundError + If glymur is unable to load the openjp2 library. """ if version.openjpeg_version_tuple[0] < 2: - raise RuntimeError("You must have at least version 2.0.0 of " - "OpenJPEG installed before using this " - "functionality.") + raise LibraryNotFoundError("You must have at least version 2.0.0 " + "of OpenJP2 installed before using " + "this functionality.") - self.ignore_pclr_cmap_cdef = ignore_pclr_cmap_cdef - if layer is not None: - self._layer = layer - self._populate_dparams(rlevel, tile=tile, area=area) + dparam = self._populate_dparam(layer, rlevel, area, tile) with ExitStack() as stack: - if re.match("2.1", version.openjpeg_version): - # API change in 2.1 - filename = self.filename - stream = opj2.stream_create_default_file_stream(filename, True) - stack.callback(opj2.stream_destroy, stream) - else: + if re.match("2.0", version.openjpeg_version): fptr = libc.fopen(self.filename, 'rb') stack.callback(libc.fclose, fptr) stream = opj2.stream_create_default_file_stream(fptr, True) stack.callback(opj2.stream_destroy, stream) + else: + filename = self.filename + stream = opj2.stream_create_default_file_stream(filename, True) + stack.callback(opj2.stream_destroy, stream) codec = opj2.create_decompress(self._codec_format) stack.callback(opj2.destroy_codec, codec) @@ -1393,17 +971,16 @@ class Jp2k(Jp2kBox): else: opj2.set_info_handler(codec, None) - opj2.setup_decoder(codec, self._dparams) + opj2.setup_decoder(codec, dparam) image = opj2.read_header(stream, codec) stack.callback(opj2.image_destroy, image) - if self._dparams.nb_tile_to_decode: - opj2.get_decoded_tile(codec, stream, image, - self._dparams.tile_index) + if dparam.nb_tile_to_decode: + opj2.get_decoded_tile(codec, stream, image, dparam.tile_index) else: opj2.set_decode_area(codec, image, - self._dparams.DA_x0, self._dparams.DA_y0, - self._dparams.DA_x1, self._dparams.DA_y1) + dparam.DA_x0, dparam.DA_y0, + dparam.DA_x1, dparam.DA_y1) opj2.decode(codec, stream, image) opj2.end_decompress(codec, stream) @@ -1431,8 +1008,8 @@ class Jp2k(Jp2kBox): >>> jp2 = glymur.Jp2k(jfile) >>> codestream = jp2.get_codestream() >>> print(codestream.segment[1]) - SIZ marker segment @ (3233, 47) - Profile: no profile + SIZ marker segment @ (3137, 47) + Profile: 2 Reference Grid Height, Width: (1456 x 2592) Vertical, Horizontal Reference Grid Offset: (0 x 0) Reference Tile Height, Width: (1456 x 2592) @@ -1440,6 +1017,11 @@ class Jp2k(Jp2kBox): Bitdepth: (8, 8, 8) Signed: (False, False, False) Vertical, Horizontal Subsampling: ((1, 1), (1, 1), (1, 1)) + + Raises + ------ + IOError + If the file is JPX with more than one codestream. """ with open(self.filename, 'rb') as fptr: if self._codec_format == opj2.CODEC_J2K: @@ -1447,6 +1029,9 @@ class Jp2k(Jp2kBox): header_only=header_only) else: box = [x for x in self.box if x.box_id == 'jp2c'] + if len(box) != 1: + msg = "JP2 files must have a single codestream." + raise RuntimeError(msg) fptr.seek(box[0].offset) read_buffer = fptr.read(8) (box_length, _) = struct.unpack('>I4s', read_buffer) @@ -1463,84 +1048,8 @@ class Jp2k(Jp2kBox): return codestream - def _populate_image_struct(self, image, imgdata): - """Populates image struct needed for compression. - Parameters - ---------- - image : ImageType(ctypes.Structure) - Corresponds to image_t type in openjp2 headers. - img_array : ndarray - Image data to be written to file. - """ - - numrows, numcols, num_comps = imgdata.shape - - # set image offset and reference grid - image.contents.x0 = self._cparams.image_offset_x0 - image.contents.y0 = self._cparams.image_offset_y0 - image.contents.x1 = (image.contents.x0 + - (numcols - 1) * self._cparams.subsampling_dx + 1) - image.contents.y1 = (image.contents.y0 + - (numrows - 1) * self._cparams.subsampling_dy + 1) - - # Stage the image data to the openjpeg data structure. - for k in range(0, num_comps): - if re.match("2.0", version.openjpeg_version) is not None: - # 2.0 API - if self._cparams.cp_cinema: - image.contents.comps[k].prec = 12 - image.contents.comps[k].bpp = 12 - else: - # 2.1 API - if self._cparams.rsiz in (core.OPJ_PROFILE_CINEMA_2K, - core.OPJ_PROFILE_CINEMA_4K): - image.contents.comps[k].prec = 12 - image.contents.comps[k].bpp = 12 - - layer = np.ascontiguousarray(imgdata[:, :, k], dtype=np.int32) - dest = image.contents.comps[k].data - src = layer.ctypes.data - ctypes.memmove(dest, src, layer.nbytes) - - return image - - def _populate_comptparms(self, img_array): - """Instantiate and populate comptparms structure. - - This structure defines the image components. - - Parameters - ---------- - img_array : ndarray - Image data to be written to file. - """ - # Only two precisions are possible. - if img_array.dtype == np.uint8: - comp_prec = 8 - else: - comp_prec = 16 - - numrows, numcols, num_comps = img_array.shape - if version.openjpeg_version_tuple[0] == 1: - comptparms = (opj.ImageComptParmType * num_comps)() - else: - comptparms = (opj2.ImageComptParmType * num_comps)() - for j in range(num_comps): - comptparms[j].dx = self._cparams.subsampling_dx - comptparms[j].dy = self._cparams.subsampling_dy - comptparms[j].w = numcols - comptparms[j].h = numrows - comptparms[j].x0 = self._cparams.image_offset_x0 - comptparms[j].y0 = self._cparams.image_offset_y0 - comptparms[j].prec = comp_prec - comptparms[j].bpp = comp_prec - comptparms[j].sgnd = 0 - - self._comptparms = comptparms - - -def _component2dtype(component): +def component2dtype(component): """Take an OpenJPEG component structure and determine the numpy datatype. Parameters @@ -1581,56 +1090,11 @@ def _validate_nonzero_image_size(nrows, ncols, component_index): raise IOError(msg) -JP2_IDS = ['colr', 'cdef', 'cmap', 'jp2c', 'ftyp', 'ihdr', 'jp2h', 'jP ', - 'pclr', 'res ', 'resc', 'resd', 'xml ', 'ulst', 'uinf', 'url ', - 'uuid'] - - def _validate_jp2_box_sequence(boxes): """Run through series of tests for JP2 box legality. This is non-exhaustive. """ - _validate_signature_compatibility(boxes) - _validate_jp2h(boxes) - _validate_jp2c(boxes) - if boxes[1].brand == 'jpx ': - _validate_jpx_box_sequence(boxes) - else: - # Validate the JP2 box IDs. - count = _collect_box_count(boxes) - for box_id in count.keys(): - if box_id not in JP2_IDS: - msg = "The presence of a '{0}' box requires that the file " - msg += "type brand be set to 'jpx '." - raise IOError(msg.format(box_id)) - - _validate_jp2_colr(boxes) - - -def _validate_jp2_colr(boxes): - """ - Validate JP2 requirements on colour specification boxes. - """ - lst = [box for box in boxes if box.box_id == 'jp2h'] - jp2h = lst[0] - for colr in [box for box in jp2h.box if box.box_id == 'colr']: - if colr.approximation != 0: - msg = "A JP2 colr box cannot have a non-zero approximation field." - raise IOError(msg) - - -def _validate_jpx_box_sequence(boxes): - """Run through series of tests for JPX box legality.""" - _validate_label(boxes) - _validate_jpx_brand(boxes, boxes[1].brand) - _validate_jpx_compatibility(boxes, boxes[1].compatibility_list) - _validate_singletons(boxes) - _validate_top_level(boxes) - - -def _validate_signature_compatibility(boxes): - """Validate the file signature and compatibility status.""" # Check for a bad sequence of boxes. # 1st two boxes must be 'jP ' and 'ftyp' if boxes[0].box_id != 'jP ' or boxes[1].box_id != 'ftyp': @@ -1638,14 +1102,6 @@ def _validate_signature_compatibility(boxes): msg += "must be the file type box." raise IOError(msg) - # The compatibility list must contain at a minimum 'jp2 '. - if 'jp2 ' not in boxes[1].compatibility_list: - msg = "The ftyp box must contain 'jp2 ' in the compatibility list." - raise IOError(msg) - - -def _validate_jp2c(boxes): - """Validate the codestream box in relation to other boxes.""" # jp2c must be preceeded by jp2h jp2h_lst = [idx for (idx, box) in enumerate(boxes) if box.box_id == 'jp2h'] @@ -1662,20 +1118,8 @@ def _validate_jp2c(boxes): msg = "The codestream box must be preceeded by a jp2 header box." raise IOError(msg) - -def _validate_jp2h(boxes): - """Validate the JP2 Header box.""" - _check_jp2h_child_boxes(boxes, 'top-level') - - jp2h_lst = [box for box in boxes if box.box_id == 'jp2h'] - jp2h = jp2h_lst[0] - - # 1st jp2 header box cannot be empty. - if len(jp2h.box) == 0: - msg = "The JP2 header superbox cannot be empty." - raise IOError(msg) - # 1st jp2 header box must be ihdr + jp2h = boxes[jp2h_idx] if jp2h.box[0].box_id != 'ihdr': msg = "The first box in the jp2 header box must be the image " msg += "header box." @@ -1689,164 +1133,41 @@ def _validate_jp2h(boxes): raise IOError(msg) colr = jp2h.box[colr_lst[0]] - _validate_channel_definition(jp2h, colr) + # Any cdef box must be in the jp2 header following the image header. + cdef_lst = [j for (j, box) in enumerate(boxes) if box.box_id == 'cdef'] + if len(cdef_lst) != 0: + msg = "Any channel defintion box must be in the JP2 header " + msg += "following the image header." + raise IOError(msg) - -def _validate_channel_definition(jp2h, colr): - """Validate the channel definition box.""" - cdef_lst = [j for (j, box) in enumerate(jp2h.box) if box.box_id == 'cdef'] + cdef_lst = [j for (j, box) in enumerate(jp2h.box) + if box.box_id == 'cdef'] if len(cdef_lst) > 1: msg = "Only one channel definition box is allowed in the " msg += "JP2 header." raise IOError(msg) elif len(cdef_lst) == 1: cdef = jp2h.box[cdef_lst[0]] - if colr.colorspace == core.SRGB: + if colr.colorspace == SRGB: if any([chan + 1 not in cdef.association or cdef.channel_type[chan] != 0 for chan in [0, 1, 2]]): msg = "All color channels must be defined in the " msg += "channel definition box." raise IOError(msg) - elif colr.colorspace == core.GREYSCALE: + elif colr.colorspace == GREYSCALE: if 0 not in cdef.channel_type: msg = "All color channels must be defined in the " msg += "channel definition box." raise IOError(msg) -JP2H_CHILDREN = set(['bpcc', 'cdef', 'cmap', 'ihdr', 'pclr']) - - -def _check_jp2h_child_boxes(boxes, parent_box_name): - """Certain boxes can only reside in the JP2 header.""" - box_ids = set([box.box_id for box in boxes]) - intersection = box_ids.intersection(JP2H_CHILDREN) - if len(intersection) > 0 and parent_box_name not in ['jp2h', 'jpch']: - msg = "A '{0}' box can only be nested in a JP2 header box." - raise IOError(msg.format(list(intersection)[0])) - - # Recursively check any contained superboxes. - for box in boxes: - if hasattr(box, 'box'): - _check_jp2h_child_boxes(box.box, box.box_id) - - -def _collect_box_count(boxes): - """Count the occurences of each box type.""" - count = Counter([box.box_id for box in boxes]) - - # Add the counts in the superboxes. - for box in boxes: - if hasattr(box, 'box'): - count.update(_collect_box_count(box.box)) - - return count - -TOP_LEVEL_ONLY_BOXES = set(['dtbl']) - - -def _check_superbox_for_top_levels(boxes): - """Several boxes can only occur at the top level.""" - # We are only looking at the boxes contained in a superbox, so if any of - # the blacklisted boxes show up here, it's an error. - box_ids = set([box.box_id for box in boxes]) - intersection = box_ids.intersection(TOP_LEVEL_ONLY_BOXES) - if len(intersection) > 0: - msg = "A '{0}' box cannot be nested in a superbox." - raise IOError(msg.format(list(intersection)[0])) - - # Recursively check any contained superboxes. - for box in boxes: - if hasattr(box, 'box'): - _check_superbox_for_top_levels(box.box) - - -def _validate_top_level(boxes): - """Several boxes can only occur at the top level.""" - # Add the counts in the superboxes. - for box in boxes: - if hasattr(box, 'box'): - _check_superbox_for_top_levels(box.box) - - count = _collect_box_count(boxes) - # Which boxes occur more than once? - multiples = [box_id for box_id, bcount in count.items() if bcount > 1] - if 'dtbl' in multiples: - raise IOError('There can only be one dtbl box in a file.') - - # If there is one data reference box, then there must also be one ftbl. - if 'dtbl' in count and 'ftbl' not in count: - msg = 'The presence of a data reference box requires the presence of ' - msg += 'a fragment table box as well.' - raise IOError(msg) - - -def _validate_singletons(boxes): - """Several boxes can only occur once.""" - count = _collect_box_count(boxes) - # Which boxes occur more than once? - multiples = [box_id for box_id, bcount in count.items() if bcount > 1] - if 'dtbl' in multiples: - raise IOError('There can only be one dtbl box in a file.') - -JPX_IDS = ['asoc', 'nlst'] - - -def _validate_jpx_brand(boxes, brand): - """ - If there is a JPX box then the brand must be 'jpx '. - """ - for box in boxes: - if box.box_id in JPX_IDS: - if brand != 'jpx ': - msg = "A JPX box requires that the file type box brand be " - msg += "'jpx '." - raise RuntimeError(msg) - if hasattr(box, 'box') != 0: - # Same set of checks on any child boxes. - _validate_jpx_brand(box.box, brand) - - -def _validate_jpx_compatibility(boxes, compatibility_list): - """ - If there is a JPX box then the compatibility list must also contain 'jpx '. - """ - jpx_cl = set(compatibility_list) - for box in boxes: - if box.box_id in JPX_IDS: - if len(set(['jpx ', 'jpxb']).intersection(jpx_cl)) == 0: - msg = "A JPX box requires that either 'jpx ' or 'jpxb' be " - msg += "present in the ftype compatibility list." - raise RuntimeError(msg) - if hasattr(box, 'box') != 0: - # Same set of checks on any child boxes. - _validate_jpx_compatibility(box.box, compatibility_list) - - -def _validate_label(boxes): - """ - Label boxes can only be inside association, codestream headers, or - compositing layer header boxes. - """ - for box in boxes: - if box.box_id != 'asoc': - if hasattr(box, 'box'): - for boxi in box.box: - if boxi.box_id == 'lbl ': - msg = "A label box cannot be nested inside a {0} box." - msg = msg.format(box.box_id) - raise IOError(msg) - # Same set of checks on any child boxes. - _validate_label(box.box) - - def extract_image_cube(image): """Extract 3D image from openjpeg data structure. """ ncomps = image.contents.numcomps component = image.contents.comps[0] - dtype = _component2dtype(component) + dtype = component2dtype(component) nrows = component.h ncols = component.w @@ -1880,7 +1201,7 @@ def extract_image_bands(image): for k in range(image.contents.numcomps): component = image.contents.comps[k] - dtype = _component2dtype(component) + dtype = component2dtype(component) nrows = component.h ncols = component.w @@ -1896,6 +1217,194 @@ def extract_image_bands(image): return data +def _unpack_colorspace(colorspace, img_array, cparams): + """Determine the colorspace from the supplied inputs. + + Parameters + ---------- + colorspace : int + Either CLRSPC_SRGB or CLRSPC_GRAY + img_array : ndarray + Image data to be written to file. + cparams : CompressionParametersType(ctypes.Structure) + Corresponds to cparameters_t type in openjp2 headers. + """ + if colorspace is None: + # Must infer the colorspace from the image dimensions. + if img_array.ndim < 3: + # A single channel image is grayscale. + colorspace = opj2.CLRSPC_GRAY + elif img_array.shape[2] == 1 or img_array.shape[2] == 2: + # A single channel image or an image with two channels is going + # to be greyscale. + colorspace = opj2.CLRSPC_GRAY + else: + # Anything else must be RGB, right? + colorspace = opj2.CLRSPC_SRGB + else: + # Supplied a string colorspace, so we must validate it. + if cparams.codec_fmt == opj2.CODEC_J2K: + msg = 'Do not specify a colorspace when writing a raw ' + msg += 'codestream.' + raise IOError(msg) + if colorspace.lower() not in ('rgb', 'grey', 'gray'): + msg = 'Invalid colorspace "{0}"'.format(colorspace) + raise IOError(msg) + elif colorspace.lower() == 'rgb' and img_array.shape[2] < 3: + msg = 'RGB colorspace requires at least 3 components.' + raise IOError(msg) + + # Turn the colorspace from a string to the enumerated value that + # the library expects. + colorspace = _COLORSPACE_MAP[colorspace.lower()] + + return colorspace + + +def _populate_comptparms(img_array, cparams): + """Instantiate and populate comptparms structure. + + This structure defines the image components. + + Parameters + ---------- + img_array : ndarray + Image data to be written to file. + cparams : CompressionParametersType(ctypes.Structure) + Corresponds to cparameters_t type in openjp2 headers. + + Returns + ------- + comptparms : ImageCompType(ctypes.Structure) + Corresponds to image_comp_t type in openjp2 headers. + """ + # Only two precisions are possible. + if img_array.dtype == np.uint8: + comp_prec = 8 + else: + comp_prec = 16 + + numrows, numcols, num_comps = img_array.shape + if version.openjpeg_version_tuple[0] == 1: + comptparms = (opj.ImageComptParmType * num_comps)() + else: + comptparms = (opj2.ImageComptParmType * num_comps)() + for j in range(num_comps): + comptparms[j].dx = cparams.subsampling_dx + comptparms[j].dy = cparams.subsampling_dy + comptparms[j].w = numcols + comptparms[j].h = numrows + comptparms[j].x0 = cparams.image_offset_x0 + comptparms[j].y0 = cparams.image_offset_y0 + comptparms[j].prec = comp_prec + comptparms[j].bpp = comp_prec + comptparms[j].sgnd = 0 + + return comptparms + + +def _populate_image_struct(cparams, image, imgdata): + """Populates image struct needed for compression. + + Parameters + ---------- + cparams : CompressionParametersType(ctypes.Structure) + Corresponds to cparameters_t type in openjp2 headers. + image : ImageType(ctypes.Structure) + Corresponds to image_t type in openjp2 headers. + imgarray : ndarray + Image data to be written to file. + """ + + numrows, numcols, num_comps = imgdata.shape + + # set image offset and reference grid + image.contents.x0 = cparams.image_offset_x0 + image.contents.y0 = cparams.image_offset_y0 + image.contents.x1 = (image.contents.x0 + + (numcols - 1) * cparams.subsampling_dx + 1) + image.contents.y1 = (image.contents.y0 + + (numrows - 1) * cparams.subsampling_dy + 1) + + # Stage the image data to the openjpeg data structure. + for k in range(0, num_comps): + layer = np.ascontiguousarray(imgdata[:, :, k], dtype=np.int32) + dest = image.contents.comps[k].data + src = layer.ctypes.data + ctypes.memmove(dest, src, layer.nbytes) + + return image + + +def _validate_compression_params(img_array, cparams): + """Check that the compression parameters are valid. + + Parameters + ---------- + img_array : ndarray + Image data to be written to file. + cparams : CompressionParametersType(ctypes.Structure) + Corresponds to cparameters_t type in openjp2 headers. + """ + + # Code block size + code_block_specified = False + if cparams.cblockw_init != 0 and cparams.cblockh_init != 0: + # These fields ARE zero if uninitialized. + width = cparams.cblockw_init + height = cparams.cblockh_init + code_block_specified = True + if height * width > 4096 or height < 4 or width < 4: + msg = "Code block area cannot exceed 4096. " + msg += "Code block height and width must be larger than 4." + raise IOError(msg) + if ((math.log(height, 2) != math.floor(math.log(height, 2)) or + math.log(width, 2) != math.floor(math.log(width, 2)))): + msg = "Bad code block size ({0}, {1}), " + msg += "must be powers of 2." + raise IOError(msg.format(height, width)) + + # Precinct size + if cparams.res_spec != 0: + # precinct size was not specified if this field is zero. + for j in range(cparams.res_spec): + prch = cparams.prch_init[j] + prcw = cparams.prcw_init[j] + if j == 0 and code_block_specified: + height, width = cparams.cblockh_init, cparams.cblockw_init + if height * 2 > prch or width * 2 > prcw: + msg = "Highest Resolution precinct size must be at " + msg += "least twice that of the code block dimensions." + raise IOError(msg) + if ((math.log(prch, 2) != math.floor(math.log(prch, 2)) or + math.log(prcw, 2) != math.floor(math.log(prcw, 2)))): + msg = "Bad precinct sizes ({0}, {1}), " + msg += "must be powers of 2." + raise IOError(msg.format(prch, prcw)) + + # What would the point of 1D images be? + if img_array.ndim == 1 or img_array.ndim > 3: + msg = "{0}D imagery is not allowed.".format(img_array.ndim) + raise IOError(msg) + + if re.match("2.0.0", version.openjpeg_version): + if (((img_array.ndim != 2) and + (img_array.shape[2] != 1 and img_array.shape[2] != 3))): + msg = "Writing images is restricted to single-channel " + msg += "greyscale images or three-channel RGB images when " + msg += "the OpenJPEG library version is the official 2.0.0 " + msg += "release." + raise IOError(msg) + + if img_array.dtype != np.uint8 and img_array.dtype != np.uint16: + msg = "Only uint8 and uint16 images are currently supported." + raise RuntimeError(msg) + +_COLORSPACE_MAP = {'rgb': opj2.CLRSPC_SRGB, + 'gray': opj2.CLRSPC_GRAY, + 'grey': opj2.CLRSPC_GRAY, + 'ycc': opj2.CLRSPC_YCC} + # Setup the default callback handlers. See the callback functions subsection # in the ctypes section of the Python documentation for a solid explanation of # what's going on here. @@ -1922,3 +1431,10 @@ def _default_warning_handler(library_msg, _): _ERROR_CALLBACK = _CMPFUNC(_default_error_handler) _INFO_CALLBACK = _CMPFUNC(_default_info_handler) _WARNING_CALLBACK = _CMPFUNC(_default_warning_handler) + + +class LibraryNotFoundError(IOError): + """Raised if functionality is requested without the necessary library. + """ + def __init__(self, msg): + IOError.__init__(self, msg) diff --git a/glymur/lib/__init__.py b/glymur/lib/__init__.py index ddce813..a283f7f 100644 --- a/glymur/lib/__init__.py +++ b/glymur/lib/__init__.py @@ -2,5 +2,3 @@ from . import openjp2 as openjp2 from . import openjpeg as openjpeg from . import c - -__all__ = [openjp2, openjpeg, c] diff --git a/glymur/lib/config.py b/glymur/lib/config.py index 8af038a..fdb5a92 100644 --- a/glymur/lib/config.py +++ b/glymur/lib/config.py @@ -1,6 +1,9 @@ """ Configure glymur to use installed libraries if possible. """ +# configparser is new in python3 (pylint/python-2.7) +# pylint: disable=F0401 + import ctypes from ctypes.util import find_library import os @@ -15,22 +18,6 @@ else: from configparser import ConfigParser from configparser import NoOptionError -# default library locations for MacPorts -_macports_default_location = {'openjp2': '/opt/local/lib/libopenjp2.dylib', - 'openjpeg': '/opt/local/lib/libopenjpeg.dylib'} - -# default library locations on Windows -_windows_default_location = {'openjp2': os.path.join('C:\\', - 'Program files', - 'OpenJPEG 2.0', - 'bin', - 'openjp2.dll'), - 'openjpeg': os.path.join('C:\\', - 'Program files', - 'OpenJPEG 1.5', - 'bin', - 'openjpeg.dll')} - def glymurrc_fname(): """Return the path to the configuration file. @@ -56,23 +43,52 @@ def glymurrc_fname(): return None -def load_openjpeg_library(libname): +def load_openjpeg(path): + """Load the openjpeg library, falling back on defaults if necessary. - path = read_config_file(libname) - if path is not None: - return load_library_handle(path) + Parameters + ---------- + path : str + Path to openjpeg 1.5 library as specified by configuration file. Will + be None if no configuration file specified. + """ + if path is None: + # Let ctypes try to find it. + path = find_library('openjpeg') - # No location specified by the configuration file, must look for it - # elsewhere. - path = find_library(libname) + # If we could not find it, then look in some likely locations on mac + # and win. + if path is None: + # Could not find a library via ctypes + if platform.system() == 'Darwin': + # MacPorts + path = '/opt/local/lib/libopenjpeg.dylib' + elif os.name == 'nt': + path = os.path.join('C:\\', 'Program files', 'OpenJPEG 1.5', + 'bin', 'openjpeg.dll') + + if path is not None and not os.path.exists(path): + # the mac/win default location does not exist. + return None + + return load_library_handle(path) + + +def load_openjp2(path): + """Load the openjp2 library, falling back on defaults if necessary. + """ + if path is None: + # No help from the config file, try to find it via ctypes. + path = find_library('openjp2') if path is None: # Could not find a library via ctypes if platform.system() == 'Darwin': # MacPorts - path = _macports_default_location[libname] + path = '/opt/local/lib/libopenjp2.dylib' elif os.name == 'nt': - path = _windows_default_location[libname] + path = os.path.join('C:\\', 'Program files', 'OpenJPEG 2.0', + 'bin', 'openjp2.dll') if path is not None and not os.path.exists(path): # the mac/win default location does not exist. @@ -84,11 +100,10 @@ def load_openjpeg_library(libname): def load_library_handle(path): """Load the library, return the ctypes handle.""" - if path is None or path in ['None', 'none']: - # Either could not find a library via ctypes or - # user-configuration-file, or we could not find it in any of the - # default locations, or possibly the user intentionally does not want - # one of the libraries to load. + if path is None: + # Either could not find a library via ctypes or user-configuration-file, + # or we could not find it in any of the default locations. + # This is probably a very old linux. return None try: @@ -97,59 +112,47 @@ def load_library_handle(path): else: opj_lib = ctypes.CDLL(path) except (TypeError, OSError): - msg = 'The library specified by configuration file at {0} could not ' - msg += 'be loaded.' - warnings.warn(msg.format(path), UserWarning) - opj_lib = None + msg = '"Library {0}" could not be loaded. Operating in degraded mode.' + msg = msg.format(path) + warnings.warn(msg, UserWarning) + opj_lib = None return opj_lib -def read_config_file(libname): +def read_config_file(): """ - Extract library locations from a configuration file. - - Parameters - ---------- - libname : str - One of either 'openjp2' or 'openjpeg' - - Returns - ------- - path : None or str - None if no location is specified, otherwise a path to the library + We must use a configuration file that the user must write. """ + lib = {'openjp2': None, 'openjpeg': None} filename = glymurrc_fname() - if filename is None: - # There's no library file path to return in this case. - return None + if filename is not None: + # Read the configuration file for the library location. + parser = ConfigParser() + parser.read(filename) + try: + lib['openjp2'] = parser.get('library', 'openjp2') + except NoOptionError: + pass + try: + lib['openjpeg'] = parser.get('library', 'openjpeg') + except NoOptionError: + pass - # Read the configuration file for the library location. - parser = ConfigParser() - parser.read(filename) - try: - path = parser.get('library', libname) - except NoOptionError: - path = None - return path + return lib def glymur_config(): + """Try to ascertain locations of openjp2, openjpeg libraries. """ - Try to ascertain locations of openjp2, openjpeg libraries. - - Returns - ------- - tpl : tuple - tuple of library handles - """ - lst = [] - for libname in ['openjp2', 'openjpeg']: - lst.append(load_openjpeg_library(libname)) - if all(handle is None for handle in lst): + libs = read_config_file() + libopenjp2_handle = load_openjp2(libs['openjp2']) + libopenjpeg_handle = load_openjpeg(libs['openjpeg']) + if libopenjp2_handle is None and libopenjpeg_handle is None: msg = "Neither the openjp2 nor the openjpeg library could be loaded. " - warnings.warn(msg) - return tuple(lst) + msg += "Operating in severely degraded mode." + warnings.warn(msg, UserWarning) + return libopenjp2_handle, libopenjpeg_handle def get_configdir(): diff --git a/glymur/lib/openjp2.py b/glymur/lib/openjp2.py index aed4db6..69589f5 100644 --- a/glymur/lib/openjp2.py +++ b/glymur/lib/openjp2.py @@ -2,23 +2,22 @@ Wraps individual functions in openjp2 library. """ +# pylint: disable=C0302,R0903,W0201 + import ctypes import re import sys -import textwrap from .config import glymur_config - OPENJP2, OPENJPEG = glymur_config() - def version(): """Wrapper for opj_version library routine.""" try: OPENJP2.opj_version.restype = ctypes.c_char_p - except: + except AttributeError: + # No library, so the version is zero. return "0.0.0" - library_version = OPENJP2.opj_version() if sys.hexversion >= 0x03000000: return library_version.decode('utf-8') @@ -26,10 +25,9 @@ def version(): return library_version if OPENJP2 is not None: - _MAJOR, _MINOR, _PATCH = version().split('.') + _MAJOR, _MINOR, _PATH = version().split('.') else: _MINOR = 0 - ERROR_MSG_LST = [] # Map certain atomic OpenJPEG datatypes to the ctypes equivalents. @@ -55,7 +53,6 @@ CLRSPC_UNSPECIFIED = 0 CLRSPC_SRGB = 1 CLRSPC_GRAY = 2 CLRSPC_YCC = 3 -CLRSPC_EYCC = 4 COLOR_SPACE_TYPE = ctypes.c_int # supported codec @@ -131,13 +128,6 @@ class PocType(ctypes.Structure): ("tx0_t", ctypes.c_uint32), ("ty0_t", ctypes.c_uint32)] - def __str__(self): - msg = "{0}:\n".format(self.__class__) - for field_name, _ in self._fields_: - msg += " {0}: {1}\n".format( - field_name, getattr(self, field_name)) - return msg - class DecompressionParametersType(ctypes.Structure): """Decompression parameters. @@ -201,13 +191,6 @@ class DecompressionParametersType(ctypes.Structure): # maximum number of tiles ("flags", ctypes.c_uint32)] - def __str__(self): - msg = "{0}:\n".format(self.__class__) - for field_name, _ in self._fields_: - msg += " {0}: {1}\n".format( - field_name, getattr(self, field_name)) - return msg - class CompressionParametersType(ctypes.Structure): """Compression parameters. @@ -399,46 +382,6 @@ class CompressionParametersType(ctypes.Structure): # values. _fields_.append(("rsiz", ctypes.c_uint16)) - def __str__(self): - msg = "{0}:\n".format(self.__class__) - for field_name, _ in self._fields_: - - if field_name == 'poc': - msg += " numpocs: {0}\n".format(self.numpocs) - for j in range(self.numpocs): - msg += " [#{0}]:".format(j) - msg += " {0}".format(str(self.poc[j])) - - elif field_name in ['tcp_rates', 'tcp_distoratio']: - lst = [] - arr = getattr(self, field_name) - lst = [arr[j] for j in range(self.tcp_numlayers)] - msg += " {0}: {1}\n".format(field_name, lst) - - elif field_name in ['prcw_init', 'prch_init']: - pass - - elif field_name == 'res_spec': - prcw_init = [self.prcw_init[j] for j in range(self.res_spec)] - prch_init = [self.prch_init[j] for j in range(self.res_spec)] - msg += " res_spec: {0}\n".format(self.res_spec) - msg += " prch_init: {0}\n".format(prch_init) - msg += " prcw_init: {0}\n".format(prcw_init) - - elif field_name in [ - 'jpwl_hprot_tph_tileno', 'jpwl_hprot_tph', - 'jpwl_pprot_tileno', 'jpwl_pprot_packno', 'jpwl_pprot', - 'jpwl_sens_tph_tileno', 'jpwl_sens_tph']: - arr = getattr(self, field_name) - lst = [arr[j] for j in range(JPWL_MAX_NO_TILESPECS)] - msg += " {0}: {1}\n".format(field_name, lst) - - else: - msg += " {0}: {1}\n".format( - field_name, getattr(self, field_name)) - return msg - - class ImageCompType(ctypes.Structure): """Defines a single image component. @@ -478,14 +421,7 @@ class ImageCompType(ctypes.Structure): ("data", ctypes.POINTER(ctypes.c_int32))] if _MINOR == '1': - _fields_.append(("alpha", ctypes.c_uint16)) - - def __str__(self): - msg = "{0}:\n".format(self.__class__) - for field_name, _ in self._fields_: - msg += " {0}: {1}\n".format( - field_name, getattr(self, field_name)) - return msg + _fields_.append(("alpha", ctypes.c_uint16)) class ImageType(ctypes.Structure): @@ -518,26 +454,6 @@ class ImageType(ctypes.Structure): # restricted ICC profile buffer length ("icc_profile_len", ctypes.c_uint32)] - def __str__(self): - msg = "{0}:\n".format(self.__class__) - for field_name, _ in self._fields_: - - if field_name == "numcomps": - msg += " numcomps: {0}\n".format(self.numcomps) - for j in range(self.numcomps): - msg += " comps[#{0}]:\n".format(j) - msg += textwrap.indent(str(self.comps[j]), ' ' * 12) - - elif field_name == "comps": - # handled above - pass - - else: - msg += " {0}: {1}\n".format( - field_name, getattr(self, field_name)) - - return msg - class ImageComptParmType(ctypes.Structure): """Component parameters structure used by image_create function. @@ -567,12 +483,106 @@ class ImageComptParmType(ctypes.Structure): # signed (1) / unsigned (0) ("sgnd", ctypes.c_uint32)] - def __str__(self): - msg = "{0}:\n".format(self.__class__) - for field_name, _ in self._fields_: - msg += " {0}: {1}\n".format( - field_name, getattr(self, field_name)) - return msg + +class TccpInfo(ctypes.Structure): + """Tile-component coding parameters information. + + Corresponds to tccp_info_t type in openjp2 header file. + """ + _fields_ = [ + # component index + ("compno", ctypes.c_uint32), + + # coding style + ("csty", ctypes.c_uint32), + + # number of resolutions + ("numresolutions", ctypes.c_uint32), + + # code-blocks width + ("cblkw", ctypes.c_uint32), + + # code-blocks height + ("cblkh", ctypes.c_uint32), + + # code-block coding style + ("cblksty", ctypes.c_uint32), + + # discrete wavelet transform identifier + ("qmfbid", ctypes.c_uint32), + + # quantization style + ("qntsty", ctypes.c_uint32), + + # stepsizes used for quantization + ("stepsizes_mant", ctypes.c_uint32 * J2K_MAXBANDS), + ("stepsizes_expn", ctypes.c_uint32 * J2K_MAXBANDS), + + # stepsizes used for quantization + ("numgbits", ctypes.c_uint32), + + # region of interest shift + ("roishift", ctypes.c_int32), + + # precinct width + ("prcw", ctypes.c_uint32 * J2K_MAXRLVLS), + + # precinct width + ("prch", ctypes.c_uint32 * J2K_MAXRLVLS)] + + +class TileInfoV2(ctypes.Structure): + """Tile coding parameters information + + Corresponds to tile_info_v2_t type in openjp2 headers. + """ + _fields_ = [ + # number (index) of tile + ("tileno", ctypes.c_int32), + + # coding style + ("csty", ctypes.c_uint32), + + # progression order + ("prg", PROG_ORDER_TYPE), + + # number of layers + ("numlayers", ctypes.c_uint32), + + # multi-component transform identifier + ("mct", ctypes.c_uint32), + + # information concerning tile component parameters + ("tccp_info", ctypes.POINTER(TccpInfo))] + + +class CodestreamInfoV2(ctypes.Structure): + """information about the codestream. + + Corresponds to codestream_info_v2_t type in openjp2 header files. + """ + _fields_ = [ + # tile info + # tile origin in x, y (XTOsiz, YTOsiz) + ("tx0", ctypes.c_uint32), + ("ty0", ctypes.c_uint32), + + # tile size in x, y = XTsiz, YTsiz + ("tdx", ctypes.c_uint32), + ("tdy", ctypes.c_uint32), + + # number of tiles in X, Y + ("tw", ctypes.c_uint32), + ("th", ctypes.c_uint32), + + # number of components + ("nbcomps", ctypes.c_uint32), + + # default information regarding tiles inside of image + ("m_default_tile_info", TileInfoV2), + + # information regarding tiles inside of image + ("tile_info", ctypes.POINTER(TileInfoV2))] def check_error(status): @@ -737,6 +747,28 @@ def encode(codec, stream): OPENJP2.opj_encode(codec, stream) +def get_cstr_info(codec): + """get the codestream information from the codec + + Wraps the openjp2 library function opj_get_cstr_info. + + Parameters + ---------- + codec : CODEC_TYPE + The jpeg2000 codec. + + Returns + ------- + cstr_info_p : CodestreamInfoV2 + Reference to codestream information. + """ + OPENJP2.opj_get_cstr_info.argtypes = [CODEC_TYPE] + OPENJP2.opj_get_cstr_info.restype = ctypes.POINTER(CodestreamInfoV2) + + cstr_info_p = OPENJP2.opj_get_cstr_info(codec) + return cstr_info_p + + def get_decoded_tile(codec, stream, imagep, tile_index): """get the decoded tile from the codec @@ -767,6 +799,23 @@ def get_decoded_tile(codec, stream, imagep, tile_index): OPENJP2.opj_get_decoded_tile(codec, stream, imagep, tile_index) +def destroy_cstr_info(cstr_info_p): + """destroy codestream information after compression or decompression + + Wraps the openjp2 library function opj_destroy_cstr_info. + + Parameters + ---------- + cstr_info_p : CodestreamInfoV2 pointer + Pointer to codestream info structure. + """ + ARGTYPES = [ctypes.POINTER(ctypes.POINTER(CodestreamInfoV2))] + OPENJP2.opj_destroy_cstr_info.argtypes = ARGTYPES + OPENJP2.opj_destroy_cstr_info.restype = ctypes.c_void_p + + OPENJP2.opj_destroy_cstr_info(ctypes.byref(cstr_info_p)) + + def end_compress(codec, stream): """End of compressing the current image. @@ -909,7 +958,7 @@ def read_header(stream, codec): ARGTYPES = [STREAM_TYPE_P, CODEC_TYPE, ctypes.POINTER(ctypes.POINTER(ImageType))] OPENJP2.opj_read_header.argtypes = ARGTYPES - OPENJP2.opj_read_header.restype = check_error + OPENJP2.opj_read_header.restype = check_error imagep = ctypes.POINTER(ImageType)() OPENJP2.opj_read_header(stream, codec, ctypes.byref(imagep)) @@ -1244,10 +1293,9 @@ def start_compress(codec, image, stream): def _stream_create_default_file_stream_2p0(fptr, isa_read_stream): - """Wraps openjp2 library function opj_stream_create_default_vile_stream. + """Wraps openjp2 library function opj_stream_create_default_file_stream. - Sets the stream to be a file stream. This is valid only for version 2.0.0 - of OpenJPEG. + Sets the stream to be a file stream. Parameters ---------- @@ -1270,10 +1318,9 @@ def _stream_create_default_file_stream_2p0(fptr, isa_read_stream): def _stream_create_default_file_stream_2p1(fname, isa_read_stream): - """Wraps openjp2 library function opj_stream_create_default_vile_stream. + """Wraps openjp2 library function opj_stream_create_default_file_stream. - Sets the stream to be a file stream. This function is only valid for the - 2.1 version of the openjp2 library. + Sets the stream to be a file stream. Parameters ---------- @@ -1296,12 +1343,6 @@ def _stream_create_default_file_stream_2p1(fname, isa_read_stream): read_stream) return stream -if re.match(r'''2.0''', version()): - stream_create_default_file_stream = _stream_create_default_file_stream_2p0 -else: - stream_create_default_file_stream = _stream_create_default_file_stream_2p1 - - def stream_destroy(stream): """Wraps openjp2 library function opj_stream_destroy. @@ -1316,6 +1357,51 @@ def stream_destroy(stream): OPENJP2.opj_stream_destroy.restype = ctypes.c_void_p OPENJP2.opj_stream_destroy(stream) +if re.match(r'''2.0''', version()): + # We must have the 2.0.x + stream_create_default_file_stream = _stream_create_default_file_stream_2p0 +else: + # We must have version 2.1. + stream_create_default_file_stream = _stream_create_default_file_stream_2p1 + +# The _v3 functions existed only in the SVN version of OpenJPEG, before 2.1.0 +# was released +stream_create_default_file_stream_v3 = _stream_create_default_file_stream_2p1 +stream_create_default_file_stream_v3.__doc__ = r""" +Wraps openjp2 library function opj_stream_create_default_file_stream_v3. + + Sets the stream to be a file stream. + + This function is deprecated and will be removed in the 0.6.0 version of + glymur. Please use stream_create_default_file_stream instead. + + Parameters + ---------- + fname : str + Specifies a file. + isa_read_stream: bool + True (read) or False (write) + + Returns + ------- + stream : stream_t + An OpenJPEG file stream. + """ + +stream_destroy_v3 = stream_destroy +stream_destroy_v3.__doc__ = r""" +Wraps openjp2 library function opj_stream_destroy. + + Destroys the stream created by create_stream. + + This function is deprecated and wil be removed in the 0.6.0 version of + glymur. Please use stream_destroy instead. + + Parameters + ---------- + stream : STREAM_TYPE_P + The file stream. + """ def write_tile(codec, tile_index, data, data_size, stream): """Wraps openjp2 library function opj_write_tile. @@ -1358,3 +1444,5 @@ def write_tile(codec, tile_index, data, data_size, stream): def set_error_message(msg): """The openjpeg error handler has recorded an error message.""" ERROR_MSG_LST.append(msg) + + diff --git a/glymur/lib/openjpeg.py b/glymur/lib/openjpeg.py index d2f156b..5b1183f 100644 --- a/glymur/lib/openjpeg.py +++ b/glymur/lib/openjpeg.py @@ -1,15 +1,18 @@ """Wraps library calls to openjpeg. """ +# pylint: disable=R0903 + import ctypes import sys -from .config import glymur_config +import numpy as np +from .config import glymur_config _, OPENJPEG = glymur_config() -# Maximum number of tile parts expected by JPWL: increase at your will -JPWL_MAX_NO_TILESPECS = 16 +# Maximum number of tile parts expected by JPWL: increase at your will +JPWL_MAX_NO_TILESPECS = 16 J2K_MAXRLVLS = 33 # Number of maximum resolution level authorized PATH_LEN = 4096 # maximum allowed size for filenames @@ -55,10 +58,8 @@ class CommonStructType(ctypes.Structure): ("mj2_handle", ctypes.c_void_p)] -STREAM_READ = 0x0001 # The stream was opened for reading. -STREAM_WRITE = 0x0002 # The stream was opened for writing. - - +STREAM_READ = 0x0001 # The stream was opened for reading. +STREAM_WRITE = 0x0002 # The stream was opened for writing. class CioType(ctypes.Structure): """Byte input-output stream (CIO) @@ -80,7 +81,7 @@ class CioType(ctypes.Structure): class CompressionInfoType(CommonStructType): - """Common fields between JPEG-2000 compression and decompression contexts. + """Common fields between JPEG-2000 compression and decompression contexts. This is for compression contexts. Corresponds to common_struct_t. """ pass @@ -89,57 +90,70 @@ class CompressionInfoType(CommonStructType): class PocType(ctypes.Structure): """Progression order changes.""" _fields_ = [("resno", ctypes.c_int), - # Resolution num start, Component num start, given by POC - ("compno0", ctypes.c_int), + # Resolution num start, Component num start, given by POC + ("compno0", ctypes.c_int), - # Layer num end,Resolution num end, Component num end, given - # by POC - ("layno1", ctypes.c_int), - ("resno1", ctypes.c_int), - ("compno1", ctypes.c_int), + # Layer num end,Resolution num end, Component num end, given by POC + ("layno1", ctypes.c_int), + ("resno1", ctypes.c_int), + ("compno1", ctypes.c_int), - # Layer num start,Precinct num start, Precinct num end - ("layno0", ctypes.c_int), - ("precno0", ctypes.c_int), - ("precno1", ctypes.c_int), + # Layer num start,Precinct num start, Precinct num end + ("layno0", ctypes.c_int), + ("precno0", ctypes.c_int), + ("precno1", ctypes.c_int), - # Progression order enum - # OPJ_PROG_ORDER prg1,prg; - ("prg1", ctypes.c_int), - ("prg", ctypes.c_int), + # Progression order enum + # OPJ_PROG_ORDER prg1,prg; + ("prg1", ctypes.c_int), + ("prg", ctypes.c_int), - # Progression order string - # char progorder[5]; - ("progorder", ctypes.c_char * 5), + # Progression order string + # char progorder[5]; + ("progorder", ctypes.c_char * 5), - # Tile number - # int tile; - ("tile", ctypes.c_int), + # Tile number + # int tile; + ("tile", ctypes.c_int), - ("tx0", ctypes.c_int), - ("tx1", ctypes.c_int), - ("ty0", ctypes.c_int), - ("ty1", ctypes.c_int), - ("layS", ctypes.c_int), - ("resS", ctypes.c_int), - ("compS", ctypes.c_int), - ("prcS", ctypes.c_int), - ("layE", ctypes.c_int), - ("resE", ctypes.c_int), - ("compE", ctypes.c_int), - ("prcE", ctypes.c_int), - ("txS", ctypes.c_int), - ("txE", ctypes.c_int), - ("tyS", ctypes.c_int), - ("tyE", ctypes.c_int), - ("dx", ctypes.c_int), - ("dy", ctypes.c_int), - ("lay_t", ctypes.c_int), - ("res_t", ctypes.c_int), - ("comp_t", ctypes.c_int), - ("prc_t", ctypes.c_int), - ("tx0_t", ctypes.c_int), - ("ty0_t", ctypes.c_int)] + # /** Start and end values for Tile width and height*/ + # int tx0,tx1,ty0,ty1; + ("tx0", ctypes.c_int), + ("tx1", ctypes.c_int), + ("ty0", ctypes.c_int), + ("ty1", ctypes.c_int), + + # /** Start value, initialised in pi_initialise_encode*/ + # int layS, resS, compS, prcS; + ("layS", ctypes.c_int), + ("resS", ctypes.c_int), + ("compS", ctypes.c_int), + ("prcS", ctypes.c_int), + + # /** End value, initialised in pi_initialise_encode */ + # int layE, resE, compE, prcE; + ("layE", ctypes.c_int), + ("resE", ctypes.c_int), + ("compE", ctypes.c_int), + ("prcE", ctypes.c_int), + + # Start and end values of Tile width and height, initialised in + # pi_initialise_encode int txS,txE,tyS,tyE,dx,dy; + ("txS", ctypes.c_int), + ("txE", ctypes.c_int), + ("tyS", ctypes.c_int), + ("tyE", ctypes.c_int), + ("dx", ctypes.c_int), + ("dy", ctypes.c_int), + + # Temporary values for Tile parts, initialised in pi_create_encode + # int lay_t, res_t, comp_t, prc_t,tx0_t,ty0_t; + ("lay_t", ctypes.c_int), + ("res_t", ctypes.c_int), + ("comp_t", ctypes.c_int), + ("prc_t", ctypes.c_int), + ("tx0_t", ctypes.c_int), + ("ty0_t", ctypes.c_int)] class CompressionParametersType(ctypes.Structure): @@ -360,47 +374,48 @@ class DecompressionParametersType(ctypes.Structure): class ImageComptParmType(ctypes.Structure): """Component parameters structure used by the opj_image_create function. """ - _fields_ = [("dx", ctypes.c_int), - # XRsiz: horizontal separation of a sample of ith component - # with respect to the reference grid + _fields_ = [ + # XRsiz: horizontal separation of a sample of ith component with + # respect to the reference grid + ("dx", ctypes.c_int), - # YRsiz: vertical separation of a sample of ith component with - # respect to the reference grid */ - ("dy", ctypes.c_int), + # YRsiz: vertical separation of a sample of ith component with + # respect to the reference grid */ + ("dy", ctypes.c_int), + + # data width, height + ("w", ctypes.c_int), + ("h", ctypes.c_int), - # data width, height - ("w", ctypes.c_int), - ("h", ctypes.c_int), + # x component offset compared to the whole image + # y component offset compared to the whole image + ("x0", ctypes.c_int), + ("y0", ctypes.c_int), - # x component offset compared to the whole image - # y component offset compared to the whole image - ("x0", ctypes.c_int), - ("y0", ctypes.c_int), + # precision + ('prec', ctypes.c_int), - # precision - ('prec', ctypes.c_int), + # image depth in bits + ('bpp', ctypes.c_int), - # image depth in bits - ('bpp', ctypes.c_int), - - # signed (1) / unsigned (0) - ('sgnd', ctypes.c_int)] + # signed (1) / unsigned (0) + ('sgnd', ctypes.c_int)] class ImageCompType(ctypes.Structure): """Defines a single image component. """ _fields_ = [("dx", ctypes.c_int), - ("dy", ctypes.c_int), - ("w", ctypes.c_int), - ("h", ctypes.c_int), - ("x0", ctypes.c_int), - ("y0", ctypes.c_int), - ("prec", ctypes.c_int), - ("bpp", ctypes.c_int), - ("sgnd", ctypes.c_int), - ("resno_decoded", ctypes.c_int), - ("factor", ctypes.c_int), - ("data", ctypes.POINTER(ctypes.c_int))] + ("dy", ctypes.c_int), + ("w", ctypes.c_int), + ("h", ctypes.c_int), + ("x0", ctypes.c_int), + ("y0", ctypes.c_int), + ("prec", ctypes.c_int), + ("bpp", ctypes.c_int), + ("sgnd", ctypes.c_int), + ("resno_decoded", ctypes.c_int), + ("factor", ctypes.c_int), + ("data", ctypes.POINTER(ctypes.c_int))] class ImageType(ctypes.Structure): @@ -452,7 +467,6 @@ def cio_tell(cio): pos = OPENJPEG.cio_tell(cio) return pos - def create_compress(fmt): """Wrapper for openjpeg library function opj_create_compress. @@ -497,7 +511,7 @@ def destroy_compress(cinfo): def encode(cinfo, cio, image): """Wrapper for openjpeg library function opj_encode. - Encodes an image into a JPEG-2000 codestream. + Encodes an image into a JPEG-2000 codestream. Parameters ---------- @@ -523,11 +537,56 @@ def destroy_decompress(dinfo): OPENJPEG.opj_destroy_decompress(dinfo) +def image_cmptparm_t_from_np(np_image): + """Return appropriate image_cmptparm_t based on given numpy array. + """ + try: + num_comps = np_image.shape[2] + except IndexError: + num_comps = 1 + + cmpt_parm_array_t = ImageCmptparmType * num_comps + tarr = cmpt_parm_array_t() + + if np_image.dtype == np.uint8: + prec = 8 + bpp = 8 + sgnd = 0 + elif np_image.dtype == np.int8: + prec = 8 + bpp = 8 + sgnd = 1 + elif np_image.dtype == np.uint16: + prec = 16 + bpp = 16 + sgnd = 0 + elif np_image.dtype == np.int16: + prec = 16 + bpp = 16 + sgnd = 1 + else: + raise(TypeError("unhandled")) + + for j in range(0, num_comps): + tarr[j].dx = 1 + tarr[j].dy = 1 + tarr[j].w = np_image.shape[1] + tarr[j].h = np_image.shape[0] + tarr[j].x0 = 0 + tarr[j].y0 = 0 + tarr[j].prec = prec + tarr[j].bpp = bpp + tarr[j].sgnd = sgnd + + return(tarr) + + def image_create(cmptparms, cspace): """Wrapper for openjpeg library function opj_image_create. """ - lst = [ctypes.c_int, ctypes.POINTER(ImageComptParmType), ctypes.c_int] - OPENJPEG.opj_image_create.argtypes = lst + OPENJPEG.opj_image_create.argtypes = [ctypes.c_int, + ctypes.POINTER(ImageComptParmType), + ctypes.c_int] OPENJPEG.opj_image_create.restype = ctypes.POINTER(ImageType) image = OPENJPEG.opj_image_create(len(cmptparms), cmptparms, cspace) diff --git a/glymur/lib/test/fixtures.py b/glymur/lib/test/fixtures.py deleted file mode 100644 index b5b9648..0000000 --- a/glymur/lib/test/fixtures.py +++ /dev/null @@ -1,144 +0,0 @@ -decompression_parameters_type = """: - cp_reduce: 0 - cp_layer: 0 - infile: b'' - outfile: b'' - decod_format: -1 - cod_format: -1 - DA_x0: 0 - DA_x1: 0 - DA_y0: 0 - DA_y1: 0 - m_verbose: 0 - tile_index: 0 - nb_tile_to_decode: 0 - jpwl_correct: 0 - jpwl_exp_comps: 0 - jpwl_max_tiles: 0 - flags: 0""" - -default_progression_order_changes_type = """: - resno0: 0 - compno0: 0 - layno1: 0 - resno1: 0 - compno1: 0 - layno0: 0 - precno0: 0 - precno1: 0 - prg1: 0 - prg: 0 - progorder: b'' - tile: 0 - tx0: 0 - tx1: 0 - ty0: 0 - ty1: 0 - layS: 0 - resS: 0 - compS: 0 - prcS: 0 - layE: 0 - resE: 0 - compE: 0 - prcE: 0 - txS: 0 - txE: 0 - tyS: 0 - tyE: 0 - dx: 0 - dy: 0 - lay_t: 0 - res_t: 0 - comp_t: 0 - prec_t: 0 - tx0_t: 0 - ty0_t: 0""" - -default_compression_parameters_type = """: - tile_size_on: 0 - cp_tx0: 0 - cp_ty0: 0 - cp_tdx: 0 - cp_tdy: 0 - cp_disto_alloc: 0 - cp_fixed_alloc: 0 - cp_fixed_quality: 0 - cp_matrice: None - cp_comment: None - csty: 0 - prog_order: 0 - numpocs: 0 - numpocs: 0 - tcp_numlayers: 0 - tcp_rates: [] - tcp_distoratio: [] - numresolution: 6 - cblockw_init: 64 - cblockh_init: 64 - mode: 0 - irreversible: 0 - roi_compno: -1 - roi_shift: 0 - res_spec: 0 - prch_init: [] - prcw_init: [] - infile: b'' - outfile: b'' - index_on: 0 - index: b'' - image_offset_x0: 0 - image_offset_y0: 0 - subsampling_dx: 1 - subsampling_dy: 1 - decod_format: -1 - cod_format: -1 - jpwl_epc_on: 0 - jpwl_hprot_mh: 0 - jpwl_hprot_tph_tileno: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - jpwl_hprot_tph: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - jpwl_pprot_tileno: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - jpwl_pprot_packno: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - jpwl_pprot: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - jpwl_sens_size: 0 - jpwl_sens_addr: 0 - jpwl_sens_range: 0 - jpwl_sens_mh: 0 - jpwl_sens_tph_tileno: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - jpwl_sens_tph: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - cp_cinema: 0 - max_comp_size: 0 - cp_rsiz: 0 - tp_on: 0 - tp_flag: 0 - tcp_mct: 0 - jpip_on: 0 - mct_data: None - max_cs_size: 0 - rsiz: 0""" - -default_image_component_parameters = """: - dx: 0 - dy: 0 - w: 0 - h: 0 - x0: 0 - y0: 0 - prec: 0 - bpp: 0 - sgnd: 0""" - -# The "icc_profile_buf" field is problematic as it is a pointer value, i.e. -# -# icc_profile_buf: -# -# Have to treat it as a regular expression. -default_image_type = """: - x0: 0 - y0: 0 - x1: 0 - y1: 0 - numcomps: 0 - color_space: 0 - icc_profile_buf: - icc_profile_len: 0""" diff --git a/glymur/lib/test/test_openjp2.py b/glymur/lib/test/test_openjp2.py index c32ee89..442a261 100644 --- a/glymur/lib/test/test_openjp2.py +++ b/glymur/lib/test/test_openjp2.py @@ -1,23 +1,37 @@ """ -Tests for libopenjp2 wrapping functions. +Tests for libopenjp2 wrapping functions. """ +# R0904: Seems like pylint is fooled in this situation +# W0142: using kwargs is ok in this context +# pylint: disable=R0904,W0142 + +# unittest2 is python-2.6 only (pylint/python-2.7) +# pylint: disable=F0401 + import os import re +import sys import tempfile -import unittest + +if sys.hexversion < 0x02070000: + import unittest2 as unittest +else: + import unittest import numpy as np import glymur from glymur.lib import openjp2 +if re.match("2.0", glymur.version.openjpeg_version): + OPENJP2_IS_V2_OFFICIAL = True +else: + OPENJP2_IS_V2_OFFICIAL = False @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") @unittest.skipIf(openjp2.OPENJP2 is None, "Missing openjp2 library.") -@unittest.skipIf(re.match(r'''(1|2.0)''', - glymur.version.openjpeg_version) is not None, - "Not to be run until 2.1.0") +@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL, "API followed here specific to V2.1") class TestOpenJP2(unittest.TestCase): """Test openjp2 library functionality. @@ -51,6 +65,53 @@ class TestOpenJP2(unittest.TestCase): self.assertEqual(dparams.DA_x1, 0) self.assertEqual(dparams.DA_y1, 0) + def tile_macro(self, codec, stream, imagep, tidx): + """called only by j2k_random_tile_access""" + openjp2.get_decoded_tile(codec, stream, imagep, tidx) + for j in range(imagep.contents.numcomps): + self.assertIsNotNone(imagep.contents.comps[j].data) + + def j2k_random_tile_access(self, filename, codec_format=None): + """fixture called by the test_rtaX methods""" + dparam = openjp2.set_default_decoder_parameters() + + infile = filename.encode() + nelts = openjp2.PATH_LEN - len(infile) + infile += b'0' * nelts + dparam.infile = infile + + dparam.decod_format = codec_format + + codec = openjp2.create_decompress(codec_format) + + openjp2.set_info_handler(codec, None) + openjp2.set_warning_handler(codec, None) + openjp2.set_error_handler(codec, None) + + stream = openjp2.stream_create_default_file_stream(filename, True) + + openjp2.setup_decoder(codec, dparam) + image = openjp2.read_header(stream, codec) + + cstr_info = openjp2.get_cstr_info(codec) + + tile_ul = 0 + tile_ur = cstr_info.contents.tw - 1 + tile_lr = cstr_info.contents.tw * cstr_info.contents.th - 1 + tile_ll = tile_lr - cstr_info.contents.tw + + self.tile_macro(codec, stream, image, tile_ul) + self.tile_macro(codec, stream, image, tile_ur) + self.tile_macro(codec, stream, image, tile_lr) + self.tile_macro(codec, stream, image, tile_ll) + + openjp2.destroy_cstr_info(cstr_info) + + openjp2.end_decompress(codec, stream) + openjp2.destroy_codec(codec) + openjp2.stream_destroy(stream) + openjp2.image_destroy(image) + def test_tte0(self): """Runs test designated tte0 in OpenJPEG test suite.""" with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: @@ -108,6 +169,15 @@ class TestOpenJP2(unittest.TestCase): tile_decoder(**kwargs) self.assertTrue(True) + def test_rta1(self): + """Runs test designated rta1 in OpenJPEG test suite.""" + with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: + self.xtx1_setup(tfile.name) + + codec_format = openjp2.CODEC_J2K + self.j2k_random_tile_access(tfile.name, codec_format) + self.assertTrue(True) + def test_tte2(self): """Runs test designated tte2 in OpenJPEG test suite.""" with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: @@ -129,25 +199,62 @@ class TestOpenJP2(unittest.TestCase): tile_decoder(**kwargs) self.assertTrue(True) + def test_rta2(self): + """Runs test designated rta2 in OpenJPEG test suite.""" + with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: + xtx2_setup(tfile.name) + + codec_format = openjp2.CODEC_JP2 + self.j2k_random_tile_access(tfile.name, codec_format) + def test_tte3(self): """Runs test designated tte3 in OpenJPEG test suite.""" with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: xtx3_setup(tfile.name) - self.assertTrue(True) + self.assertTrue(True) + + def test_rta3(self): + """Runs test designated rta3 in OpenJPEG test suite.""" + with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: + xtx3_setup(tfile.name) + + codec_format = openjp2.CODEC_J2K + self.j2k_random_tile_access(tfile.name, codec_format) + self.assertTrue(True) def test_tte4(self): """Runs test designated tte4 in OpenJPEG test suite.""" with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: xtx4_setup(tfile.name) - self.assertTrue(True) + self.assertTrue(True) + + def test_rta4(self): + """Runs test designated rta4 in OpenJPEG test suite.""" + with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: + xtx4_setup(tfile.name) + + codec_format = openjp2.CODEC_J2K + self.j2k_random_tile_access(tfile.name, codec_format) def test_tte5(self): """Runs test designated tte5 in OpenJPEG test suite.""" with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: xtx5_setup(tfile.name) - self.assertTrue(True) + self.assertTrue(True) + + def test_rta5(self): + """Runs test designated rta5 in OpenJPEG test suite.""" + with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: + xtx5_setup(tfile.name) + + codec_format = openjp2.CODEC_J2K + self.j2k_random_tile_access(tfile.name, codec_format) +#def tile_encoder(num_comps=None, tile_width=None, tile_height=None, +# filename=None, codec=None, comp_prec=None, +# image_width=None, image_height=None, +# irreversible=None): def tile_encoder(**kwargs): """Fixture used by many tests.""" num_tiles = ((kwargs['image_width'] / kwargs['tile_width']) * @@ -222,11 +329,10 @@ def tile_encoder(**kwargs): openjp2.destroy_codec(codec) openjp2.image_destroy(l_image) - def tile_decoder(**kwargs): """Fixture called with various configurations by many tests. - - Reads a tile. That's all it does. + + Reads a tile. That's all it does. """ stream = openjp2.stream_create_default_file_stream(kwargs['filename'], True) @@ -248,7 +354,7 @@ def tile_decoder(**kwargs): openjp2.setup_decoder(codec, dparam) image = openjp2.read_header(stream, codec) - openjp2.set_decode_area(codec, image, + openjp2.set_decode_area(codec, image, kwargs['x0'], kwargs['y0'], kwargs['x1'], kwargs['y1']) @@ -267,7 +373,6 @@ def tile_decoder(**kwargs): openjp2.stream_destroy(stream) openjp2.image_destroy(image) - def ttx0_setup(filename): """Runs tests tte0, tte0.""" kwargs = {'filename': filename, @@ -281,7 +386,6 @@ def ttx0_setup(filename): 'tile_width': 100} tile_encoder(**kwargs) - def xtx2_setup(filename): """Runs tests rta2, tte2, ttd2.""" kwargs = {'filename': filename, @@ -295,7 +399,6 @@ def xtx2_setup(filename): 'tile_width': 128} tile_encoder(**kwargs) - def xtx3_setup(filename): """Runs tests tte3, rta3.""" kwargs = {'filename': filename, @@ -309,7 +412,6 @@ def xtx3_setup(filename): 'tile_width': 128} tile_encoder(**kwargs) - def xtx4_setup(filename): """Runs tests rta4, tte4.""" kwargs = {'filename': filename, @@ -323,7 +425,6 @@ def xtx4_setup(filename): 'tile_width': 128} tile_encoder(**kwargs) - def xtx5_setup(filename): """Runs tests rta5, tte5.""" kwargs = {'filename': filename, @@ -336,3 +437,6 @@ def xtx5_setup(filename): 'tile_height': 256, 'tile_width': 256} tile_encoder(**kwargs) + +if __name__ == "__main__": + unittest.main() diff --git a/glymur/lib/test/test_openjp2_svn.py b/glymur/lib/test/test_openjp2_svn.py new file mode 100644 index 0000000..e52fe80 --- /dev/null +++ b/glymur/lib/test/test_openjp2_svn.py @@ -0,0 +1,443 @@ +""" +Tests for functions that wrap SVN version of libopenjp2 (before release of +2.1.0) +""" +# R0904: Seems like pylint is fooled in this situation +# W0142: using kwargs is ok in this context +# pylint: disable=R0904,W0142 + +# unittest2 is python-2.6 only (pylint/python-2.7) +# pylint: disable=F0401 + +import os +import re +import sys +import tempfile + +if sys.hexversion < 0x02070000: + import unittest2 as unittest +else: + import unittest + +import numpy as np + +import glymur +from glymur.lib import openjp2 + +if re.match("2.0", glymur.version.openjpeg_version): + OPENJP2_IS_V2_OFFICIAL = True +else: + OPENJP2_IS_V2_OFFICIAL = False + +@unittest.skipIf(os.name == "nt", "Temporary file issue on window.") +@unittest.skipIf(openjp2.OPENJP2 is None, + "Missing openjp2 library.") +@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL, "API followed here specific to V2.1") +class TestOpenJP2(unittest.TestCase): + """Test openjp2 library functionality. + + Some tests correspond to those in the openjpeg test suite. + """ + + def test_default_encoder_parameters(self): + """Ensure that the encoder structure is clean upon init.""" + cparams = openjp2.set_default_encoder_parameters() + + self.assertEqual(cparams.res_spec, 0) + self.assertEqual(cparams.cblockw_init, 64) + self.assertEqual(cparams.cblockh_init, 64) + self.assertEqual(cparams.numresolution, 6) + self.assertEqual(cparams.subsampling_dx, 1) + self.assertEqual(cparams.subsampling_dy, 1) + self.assertEqual(cparams.mode, 0) + self.assertEqual(cparams.prog_order, glymur.core.LRCP) + self.assertEqual(cparams.roi_shift, 0) + self.assertEqual(cparams.cp_tx0, 0) + self.assertEqual(cparams.cp_ty0, 0) + + self.assertEqual(cparams.irreversible, 0) + + def test_default_decoder_parameters(self): + """Tests that the structure is clean upon initialization""" + dparams = openjp2.set_default_decoder_parameters() + + self.assertEqual(dparams.DA_x0, 0) + self.assertEqual(dparams.DA_y0, 0) + self.assertEqual(dparams.DA_x1, 0) + self.assertEqual(dparams.DA_y1, 0) + + def tile_macro(self, codec, stream, imagep, tidx): + """called only by j2k_random_tile_access""" + openjp2.get_decoded_tile(codec, stream, imagep, tidx) + for j in range(imagep.contents.numcomps): + self.assertIsNotNone(imagep.contents.comps[j].data) + + def j2k_random_tile_access(self, filename, codec_format=None): + """fixture called by the test_rtaX methods""" + dparam = openjp2.set_default_decoder_parameters() + + infile = filename.encode() + nelts = openjp2.PATH_LEN - len(infile) + infile += b'0' * nelts + dparam.infile = infile + + dparam.decod_format = codec_format + + codec = openjp2.create_decompress(codec_format) + + openjp2.set_info_handler(codec, None) + openjp2.set_warning_handler(codec, None) + openjp2.set_error_handler(codec, None) + + stream = openjp2.stream_create_default_file_stream_v3(filename, True) + + openjp2.setup_decoder(codec, dparam) + image = openjp2.read_header(stream, codec) + + cstr_info = openjp2.get_cstr_info(codec) + + tile_ul = 0 + tile_ur = cstr_info.contents.tw - 1 + tile_lr = cstr_info.contents.tw * cstr_info.contents.th - 1 + tile_ll = tile_lr - cstr_info.contents.tw + + self.tile_macro(codec, stream, image, tile_ul) + self.tile_macro(codec, stream, image, tile_ur) + self.tile_macro(codec, stream, image, tile_lr) + self.tile_macro(codec, stream, image, tile_ll) + + openjp2.destroy_cstr_info(cstr_info) + + openjp2.end_decompress(codec, stream) + openjp2.destroy_codec(codec) + openjp2.stream_destroy_v3(stream) + openjp2.image_destroy(image) + + def test_tte0(self): + """Runs test designated tte0 in OpenJPEG test suite.""" + with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: + ttx0_setup(tfile.name) + self.assertTrue(True) + + def test_ttd0(self): + """Runs test designated ttd0 in OpenJPEG test suite.""" + with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: + + # Produce the tte0 output file for ttd0 input. + ttx0_setup(tfile.name) + + kwargs = {'x0': 0, + 'y0': 0, + 'x1': 1000, + 'y1': 1000, + 'filename': tfile.name, + 'codec_format': openjp2.CODEC_J2K} + tile_decoder(**kwargs) + self.assertTrue(True) + + def xtx1_setup(self, filename): + """Runs tests tte1, rta1.""" + kwargs = {'filename': filename, + 'codec': openjp2.CODEC_J2K, + 'comp_prec': 8, + 'irreversible': 1, + 'num_comps': 3, + 'image_height': 256, + 'image_width': 256, + 'tile_height': 128, + 'tile_width': 128} + tile_encoder(**kwargs) + self.assertTrue(True) + + def test_tte1(self): + """Runs test designated tte1 in OpenJPEG test suite.""" + with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: + self.xtx1_setup(tfile.name) + + def test_ttd1(self): + """Runs test designated ttd1 in OpenJPEG test suite.""" + with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: + + # Produce the tte0 output file for ttd0 input. + self.xtx1_setup(tfile.name) + + kwargs = {'x0': 0, + 'y0': 0, + 'x1': 128, + 'y1': 128, + 'filename': tfile.name, + 'codec_format': openjp2.CODEC_J2K} + tile_decoder(**kwargs) + self.assertTrue(True) + + def test_rta1(self): + """Runs test designated rta1 in OpenJPEG test suite.""" + with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: + self.xtx1_setup(tfile.name) + + codec_format = openjp2.CODEC_J2K + self.j2k_random_tile_access(tfile.name, codec_format) + self.assertTrue(True) + + def test_tte2(self): + """Runs test designated tte2 in OpenJPEG test suite.""" + with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: + xtx2_setup(tfile.name) + self.assertTrue(True) + + def test_ttd2(self): + """Runs test designated ttd2 in OpenJPEG test suite.""" + with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: + # Produce the tte0 output file for ttd0 input. + xtx2_setup(tfile.name) + + kwargs = {'x0': 0, + 'y0': 0, + 'x1': 128, + 'y1': 128, + 'filename': tfile.name, + 'codec_format': openjp2.CODEC_JP2} + tile_decoder(**kwargs) + self.assertTrue(True) + + def test_rta2(self): + """Runs test designated rta2 in OpenJPEG test suite.""" + with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: + xtx2_setup(tfile.name) + + codec_format = openjp2.CODEC_JP2 + self.j2k_random_tile_access(tfile.name, codec_format) + + def test_tte3(self): + """Runs test designated tte3 in OpenJPEG test suite.""" + with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: + xtx3_setup(tfile.name) + self.assertTrue(True) + + def test_rta3(self): + """Runs test designated rta3 in OpenJPEG test suite.""" + with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: + xtx3_setup(tfile.name) + + codec_format = openjp2.CODEC_J2K + self.j2k_random_tile_access(tfile.name, codec_format) + self.assertTrue(True) + + def test_tte4(self): + """Runs test designated tte4 in OpenJPEG test suite.""" + with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: + xtx4_setup(tfile.name) + self.assertTrue(True) + + def test_rta4(self): + """Runs test designated rta4 in OpenJPEG test suite.""" + with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: + xtx4_setup(tfile.name) + + codec_format = openjp2.CODEC_J2K + self.j2k_random_tile_access(tfile.name, codec_format) + + def test_tte5(self): + """Runs test designated tte5 in OpenJPEG test suite.""" + with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: + xtx5_setup(tfile.name) + self.assertTrue(True) + + def test_rta5(self): + """Runs test designated rta5 in OpenJPEG test suite.""" + with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: + xtx5_setup(tfile.name) + + codec_format = openjp2.CODEC_J2K + self.j2k_random_tile_access(tfile.name, codec_format) + + +#def tile_encoder(num_comps=None, tile_width=None, tile_height=None, +# filename=None, codec=None, comp_prec=None, +# image_width=None, image_height=None, +# irreversible=None): +def tile_encoder(**kwargs): + """Fixture used by many tests.""" + num_tiles = ((kwargs['image_width'] / kwargs['tile_width']) * + (kwargs['image_height'] / kwargs['tile_height'])) + tile_size = ((kwargs['tile_width'] * kwargs['tile_height']) * + (kwargs['num_comps'] * kwargs['comp_prec'] / 8)) + + data = np.random.random((kwargs['tile_height'], + kwargs['tile_width'], + kwargs['num_comps'])) + data = (data * 255).astype(np.uint8) + + l_param = openjp2.set_default_encoder_parameters() + + l_param.tcp_numlayers = 1 + l_param.cp_fixed_quality = 1 + l_param.tcp_distoratio[0] = 20 + + # position of the tile grid aligned with the image + l_param.cp_tx0 = 0 + l_param.cp_ty0 = 0 + + # tile size, we are using tile based encoding + l_param.tile_size_on = 1 + l_param.cp_tdx = kwargs['tile_width'] + l_param.cp_tdy = kwargs['tile_height'] + + # use irreversible encoding + l_param.irreversible = kwargs['irreversible'] + + l_param.numresolution = 6 + + l_param.prog_order = glymur.core.LRCP + + l_params = (openjp2.ImageComptParmType * kwargs['num_comps'])() + for j in range(kwargs['num_comps']): + l_params[j].dx = 1 + l_params[j].dy = 1 + l_params[j].h = kwargs['image_height'] + l_params[j].w = kwargs['image_width'] + l_params[j].sgnd = 0 + l_params[j].prec = kwargs['comp_prec'] + l_params[j].x0 = 0 + l_params[j].y0 = 0 + + codec = openjp2.create_compress(kwargs['codec']) + + openjp2.set_info_handler(codec, None) + openjp2.set_warning_handler(codec, None) + openjp2.set_error_handler(codec, None) + + cspace = openjp2.CLRSPC_SRGB + l_image = openjp2.image_tile_create(l_params, cspace) + + l_image.contents.x0 = 0 + l_image.contents.y0 = 0 + l_image.contents.x1 = kwargs['image_width'] + l_image.contents.y1 = kwargs['image_height'] + l_image.contents.color_space = openjp2.CLRSPC_SRGB + + openjp2.setup_encoder(codec, l_param, l_image) + + stream = openjp2.stream_create_default_file_stream_v3(kwargs['filename'], + False) + openjp2.start_compress(codec, l_image, stream) + + for j in np.arange(num_tiles): + openjp2.write_tile(codec, j, data, tile_size, stream) + + openjp2.end_compress(codec, stream) + openjp2.stream_destroy_v3(stream) + openjp2.destroy_codec(codec) + openjp2.image_destroy(l_image) + +def tile_decoder(**kwargs): + """Fixture called with various configurations by many tests. + + Reads a tile. That's all it does. + """ + stream = openjp2.stream_create_default_file_stream_v3(kwargs['filename'], + True) + dparam = openjp2.set_default_decoder_parameters() + + dparam.decod_format = kwargs['codec_format'] + + # Do not use layer decoding limitation. + dparam.cp_layer = 0 + + # do not use resolution reductions. + dparam.cp_reduce = 0 + + codec = openjp2.create_decompress(kwargs['codec_format']) + + openjp2.set_info_handler(codec, None) + openjp2.set_warning_handler(codec, None) + openjp2.set_error_handler(codec, None) + + openjp2.setup_decoder(codec, dparam) + image = openjp2.read_header(stream, codec) + openjp2.set_decode_area(codec, image, + kwargs['x0'], kwargs['y0'], + kwargs['x1'], kwargs['y1']) + + data = np.zeros((1150, 2048, 3), dtype=np.uint8) + while True: + rargs = openjp2.read_tile_header(codec, stream) + tidx = rargs[0] + size = rargs[1] + go_on = rargs[-1] + if not go_on: + break + openjp2.decode_tile_data(codec, tidx, data, size, stream) + + openjp2.end_decompress(codec, stream) + openjp2.destroy_codec(codec) + openjp2.stream_destroy_v3(stream) + openjp2.image_destroy(image) + +def ttx0_setup(filename): + """Runs tests tte0, tte0.""" + kwargs = {'filename': filename, + 'codec': openjp2.CODEC_J2K, + 'comp_prec': 8, + 'irreversible': 1, + 'num_comps': 3, + 'image_height': 200, + 'image_width': 200, + 'tile_height': 100, + 'tile_width': 100} + tile_encoder(**kwargs) + +def xtx2_setup(filename): + """Runs tests rta2, tte2, ttd2.""" + kwargs = {'filename': filename, + 'codec': openjp2.CODEC_JP2, + 'comp_prec': 8, + 'irreversible': 1, + 'num_comps': 3, + 'image_height': 256, + 'image_width': 256, + 'tile_height': 128, + 'tile_width': 128} + tile_encoder(**kwargs) + +def xtx3_setup(filename): + """Runs tests tte3, rta3.""" + kwargs = {'filename': filename, + 'codec': openjp2.CODEC_J2K, + 'comp_prec': 8, + 'irreversible': 1, + 'num_comps': 1, + 'image_height': 256, + 'image_width': 256, + 'tile_height': 128, + 'tile_width': 128} + tile_encoder(**kwargs) + +def xtx4_setup(filename): + """Runs tests rta4, tte4.""" + kwargs = {'filename': filename, + 'codec': openjp2.CODEC_J2K, + 'comp_prec': 8, + 'irreversible': 0, + 'num_comps': 1, + 'image_height': 256, + 'image_width': 256, + 'tile_height': 128, + 'tile_width': 128} + tile_encoder(**kwargs) + +def xtx5_setup(filename): + """Runs tests rta5, tte5.""" + kwargs = {'filename': filename, + 'codec': openjp2.CODEC_J2K, + 'comp_prec': 8, + 'irreversible': 0, + 'num_comps': 1, + 'image_height': 512, + 'image_width': 512, + 'tile_height': 256, + 'tile_width': 256} + tile_encoder(**kwargs) + +if __name__ == "__main__": + unittest.main() diff --git a/glymur/lib/test/test_openjpeg.py b/glymur/lib/test/test_openjpeg.py index 449083f..91e6d84 100644 --- a/glymur/lib/test/test_openjpeg.py +++ b/glymur/lib/test/test_openjpeg.py @@ -1,14 +1,22 @@ """ Tests for OpenJPEG module. """ +# unittest2 is python2.6 only (pylint/python-2.7) +# pylint: disable=F0401 + +# pylint: disable=E1101,R0904 + import ctypes import re import sys -import unittest + +if sys.hexversion < 0x02070000: + import unittest2 as unittest +else: + import unittest import glymur - @unittest.skipIf(glymur.lib.openjpeg.OPENJPEG is None, "Missing openjpeg library.") class TestOpenJPEG(unittest.TestCase): diff --git a/glymur/lib/test/test_printing.py b/glymur/lib/test/test_printing.py deleted file mode 100644 index c7be21c..0000000 --- a/glymur/lib/test/test_printing.py +++ /dev/null @@ -1,75 +0,0 @@ -# -*- coding: utf-8 -*- -"""Test suite for printing. -""" - -import re -import sys -import unittest - -if sys.hexversion < 0x03000000: - from mock import patch - from StringIO import StringIO -else: - from unittest.mock import patch - from io import StringIO - -import glymur -from . import fixtures - - -@unittest.skipIf(sys.hexversion < 0x03000000, "do not care about 2.7 here") -@unittest.skipIf(re.match('0|1|2.0', glymur.version.openjpeg_version), - "Requires openjpeg 2.1.0 or higher") -class TestPrintingOpenjp2(unittest.TestCase): - """Tests for verifying how printing works on openjp2 library structures.""" - def setUp(self): - self.jp2file = glymur.data.nemo() - - def tearDown(self): - pass - - def test_decompression_parameters(self): - """printing DecompressionParametersType""" - dparams = glymur.lib.openjp2.set_default_decoder_parameters() - with patch('sys.stdout', new=StringIO()) as fake_out: - print(dparams) - actual = fake_out.getvalue().strip() - expected = fixtures.decompression_parameters_type - self.assertEqual(actual, expected) - - def test_progression_order_changes(self): - """printing PocType""" - ptype = glymur.lib.openjp2.PocType() - with patch('sys.stdout', new=StringIO()) as fake_out: - print(ptype) - actual = fake_out.getvalue().strip() - expected = fixtures.default_progression_order_changes_type - self.assertEqual(actual, expected) - - def test_default_compression_parameters(self): - """printing default compression parameters""" - cparams = glymur.lib.openjp2.set_default_encoder_parameters() - with patch('sys.stdout', new=StringIO()) as fake_out: - print(cparams) - actual = fake_out.getvalue().strip() - expected = fixtures.default_compression_parameters_type - self.assertEqual(actual, expected) - - def test_default_component_parameters(self): - """printing default image component parameters""" - icpt = glymur.lib.openjp2.ImageComptParmType() - with patch('sys.stdout', new=StringIO()) as fake_out: - print(icpt) - actual = fake_out.getvalue().strip() - expected = fixtures.default_image_component_parameters - self.assertEqual(actual, expected) - - def test_default_image_type(self): - """printing default image type""" - it = glymur.lib.openjp2.ImageType() - with patch('sys.stdout', new=StringIO()) as fake_out: - print(it) - actual = fake_out.getvalue().strip() - - expected = fixtures.default_image_type - self.assertRegex(actual, expected) diff --git a/glymur/test/fixtures.py b/glymur/test/fixtures.py index 34327fc..e1df82b 100644 --- a/glymur/test/fixtures.py +++ b/glymur/test/fixtures.py @@ -4,171 +4,20 @@ Test fixtures common to more than one test point. import os import re import sys -import textwrap -import unittest import warnings import numpy as np -import six import glymur -# If openjpeg is not installed, many tests cannot be run. -if glymur.version.openjpeg_version == '0.0.0': - OPENJPEG_NOT_AVAILABLE = True - OPENJPEG_NOT_AVAILABLE_MSG = 'OpenJPEG library not installed' -else: - OPENJPEG_NOT_AVAILABLE = False - OPENJPEG_NOT_AVAILABLE_MSG = None -# Some versions of "six" on python3 cause problems when verifying warnings. -# Only use when the version is 1.7 or higher. -# And moreover, we only test using the 3.x infrastructure, never on 2.x. -WARNING_INFRASTRUCTURE_ISSUE = False -WARNING_INFRASTRUCTURE_MSG = "" -if sys.hexversion < 0x03000000: - WARNING_INFRASTRUCTURE_ISSUE = True - WARNING_INFRASTRUCTURE_MSG = "3.x warning infrastructure only" -elif re.match('1.[0-6]', six.__version__) is not None: - WARNING_INFRASTRUCTURE_ISSUE = True - msg = "Cannot run test with version {0} of python-six" - WARNING_INFRASTRUCTURE_MSG = msg.format(six.__version__) - -# Cannot reopen a named temporary file in windows. -WINDOWS_TMP_FILE_MSG = "cannot use NamedTemporaryFile like this in windows" - - -class MetadataBase(unittest.TestCase): - """ - Base class for testing metadata. - - This class has helper routines defined for testing metadata so that it can - be subclassed and used easily. - """ - - def setUp(self): - pass - - def tearDown(self): - pass - - def verify_codeblock_style(self, actual, style): - """ - Verify the code-block style for SPcod and SPcoc parameters. - - This information is stored in a single byte. Please reference - Table A-17 in FCD15444-1 - """ - expected = 0 - if style[0]: - # Selective arithmetic coding bypass - expected |= 0x01 - if style[1]: - # Reset context probabilities - expected |= 0x02 - if style[2]: - # Termination on each coding pass - expected |= 0x04 - if style[3]: - # Vertically causal context - expected |= 0x08 - if style[4]: - # Predictable termination - expected |= 0x10 - if style[5]: - # Segmentation symbols - expected |= 0x20 - self.assertEqual(actual, expected) - - def verifySignatureBox(self, box): - """ - The signature box is a constant. - """ - self.assertEqual(box.signature, (13, 10, 135, 10)) - - def verify_filetype_box(self, actual, expected): - """ - All JP2 files should have a brand reading 'jp2 ' and just a single - entry in the compatibility list, also 'jp2 '. JPX files can have more - compatibility items. - """ - self.assertEqual(actual.brand, expected.brand) - self.assertEqual(actual.minor_version, expected.minor_version) - self.assertEqual(actual.minor_version, 0) - for cl in expected.compatibility_list: - self.assertIn(cl, actual.compatibility_list) - - def verifyRGNsegment(self, actual, expected): - """ - verify the fields of a RGN segment - """ - self.assertEqual(actual.crgn, expected.crgn) # 0 = component - self.assertEqual(actual.srgn, expected.srgn) # 0 = implicit - self.assertEqual(actual.sprgn, expected.sprgn) - - def verifySOTsegment(self, actual, expected): - """ - verify the fields of a SOT (start of tile) segment - """ - self.assertEqual(actual.isot, expected.isot) - self.assertEqual(actual.psot, expected.psot) - self.assertEqual(actual.tpsot, expected.tpsot) - self.assertEqual(actual.tnsot, expected.tnsot) - - def verifyCMEsegment(self, actual, expected): - """ - verify the fields of a CME (comment) segment - """ - self.assertEqual(actual.rcme, expected.rcme) - self.assertEqual(actual.ccme, expected.ccme) - - def verifySizSegment(self, actual, expected): - """ - Verify the fields of the SIZ segment. - """ - for field in ['rsiz', 'xsiz', 'ysiz', 'xosiz', 'yosiz', 'xtsiz', - 'ytsiz', 'xtosiz', 'ytosiz', 'bitdepth', - 'xrsiz', 'yrsiz']: - self.assertEqual(getattr(actual, field), getattr(expected, field)) - - def verifyImageHeaderBox(self, box1, box2): - self.assertEqual(box1.height, box2.height) - self.assertEqual(box1.width, box2.width) - self.assertEqual(box1.num_components, box2.num_components) - self.assertEqual(box1.bits_per_component, box2.bits_per_component) - self.assertEqual(box1.signed, box2.signed) - self.assertEqual(box1.compression, box2.compression) - self.assertEqual(box1.colorspace_unknown, box2.colorspace_unknown) - self.assertEqual(box1.ip_provided, box2.ip_provided) - - def verifyColourSpecificationBox(self, actual, expected): - """ - Does not currently check icc profiles. - """ - self.assertEqual(actual.method, expected.method) - self.assertEqual(actual.precedence, expected.precedence) - self.assertEqual(actual.approximation, expected.approximation) - - if expected.colorspace is None: - self.assertIsNone(actual.colorspace) - self.assertIsNotNone(actual.icc_profile) - else: - self.assertEqual(actual.colorspace, expected.colorspace) - self.assertIsNone(actual.icc_profile) - - -# The Python XMP Toolkit may be used for XMP UUIDs, but only if available and -# if the version is at least 2.0.0. -try: - import libxmp - if hasattr(libxmp, 'version') and re.match(r'''[2-9].\d*.\d*''', - libxmp.version.VERSION): - from libxmp import XMPMeta - HAS_PYTHON_XMP_TOOLKIT = True - else: - HAS_PYTHON_XMP_TOOLKIT = False -except: - HAS_PYTHON_XMP_TOOLKIT = False +# Need to know of the libopenjp2 version is the official 2.0.0 release and NOT +# the 2.0+ development version. +OPENJP2_IS_V2_OFFICIAL = False +if glymur.lib.openjp2.OPENJP2 is not None: + if not hasattr(glymur.lib.openjp2.OPENJP2, + 'opj_stream_create_default_file_stream_v3'): + OPENJP2_IS_V2_OFFICIAL = True NO_READ_BACKEND_MSG = "Matplotlib with the PIL backend must be available in " @@ -182,50 +31,6 @@ except: raise -# The Cinema2K/4K tests seem to need the freeimage backend to skimage.io -# in order to work. Unfortunately, scikit-image/freeimage is about as wonky as -# it gets. Anaconda can get totally weirded out on versions up through 3.6.4 -# on Python3 with scikit-image up through version 0.10.0. -NO_SKIMAGE_FREEIMAGE_SUPPORT = False -try: - import skimage - import skimage.io - if (((sys.hexversion >= 0x03000000) and - ('Anaconda' in sys.version) and - (re.match('0.10', skimage.__version__)))): - NO_SKIMAGE_FREEIMAGE_SUPPORT = True - else: - skimage.io.use_plugin('freeimage', 'imread') -except ((ImportError, RuntimeError)): - NO_SKIMAGE_FREEIMAGE_SUPPORT = True - - -def _indent(textstr): - """ - Indent a string. - - Textwrap's indent method only exists for 3.3 or above. In 2.7 we have - to fake it. - - Parameters - ---------- - textstring : str - String to be indented. - indent_level : str - Number of spaces of indentation to add. - - Returns - ------- - indented_string : str - Possibly multi-line string indented a certain bit. - """ - if sys.hexversion >= 0x03030000: - return textwrap.indent(textstr, ' ') - else: - lst = [(' ' + x) for x in textstr.split('\n')] - return '\n'.join(lst) - - def opj_data_file(relative_file_name): """Compact way of forming a full filename from OpenJPEG's test suite.""" jfile = os.path.join(OPJ_DATA_ROOT, relative_file_name) @@ -236,6 +41,7 @@ try: # The whole point of trying to import PIL is to determine if it's there # or not. We won't use it directly. + # pylint: disable=F0401,W0611 import PIL NO_READ_BACKEND = False @@ -362,117 +168,6 @@ def read_pgx_header(pgx_file): header = header.rstrip() return header, pos -nemo_xmp = """ - - - - Google - 2013-02-09T14:47:53 - - - 1 - 72/1 - 72/1 - 2 - HTC - HTC Glacier - 2592 - 1456 - - - 8 - 8 - 8 - - - 2 - 3 - - - 1343036288/4294967295 - 1413044224/4294967295 - - - - - 2748779008/4294967295 - 1417339264/4294967295 - 1288490240/4294967295 - 2576980480/4294967295 - 644245120/4294967295 - 257698032/4294967295 - - - - - 1 - 2528 - 1424 - 353/100 - 0 - 0/1 - WGS-84 - 2013-02-09T14:47:53 - - - 76 - - - 0220 - 0100 - - - 1 - 2 - 3 - 0 - - - 42,20.56N - 71,5.29W - 2013-02-09T19:47:53Z - NETWORK - - - 2013-02-09T14:47:53 - - - - - Glymur - Python XMP Toolkit - - - - - -""" -nemo_xmp_box = """UUID Box (uuid) @ (77, 3146) - UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) - UUID Data: -{0}""".format(_indent(nemo_xmp)) - -nemo_xmp_box = """UUID Box (uuid) @ (77, 3146) - UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) - UUID Data: -{0}""".format(_indent(nemo_xmp)) - -SimpleRDF = """ - - - Simple value - - - - Suse - Fedora - - - - -""" - text_gbr_27 = """Colour Specification Box (colr) @ (179, 1339) Method: any ICC profile Precedence: 2 @@ -542,174 +237,7 @@ text_gbr_34 = """Colour Specification Box (colr) @ (179, 1339) # Metadata dump of nemo. -dump = r'''JPEG 2000 Signature Box (jP ) @ (0, 12) - Signature: 0d0a870a -File Type Box (ftyp) @ (12, 20) - Brand: jp2 - Compatibility: ['jp2 '] -JP2 Header Box (jp2h) @ (32, 45) - Image Header Box (ihdr) @ (40, 22) - Size: [1456 2592 3] - Bitdepth: 8 - Signed: False - Compression: wavelet - Colorspace Unknown: False - Colour Specification Box (colr) @ (62, 15) - Method: enumerated colorspace - Precedence: 0 - Colorspace: sRGB -UUID Box (uuid) @ (77, 3146) - UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) - UUID Data: -{0} -Contiguous Codestream Box (jp2c) @ (3223, 1132296) - Main header: - 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 resolutions: 2 - Code block height, width: (64 x 64) - Wavelet transform: 5-3 reversible - Precinct size: default, 2^15 x 2^15 - 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"''' - -nemo_with_codestream_header = dump.format(_indent(nemo_xmp)) - -nemo_dump_short = r"""JPEG 2000 Signature Box (jP ) @ (0, 12) -File Type Box (ftyp) @ (12, 20) -JP2 Header Box (jp2h) @ (32, 45) - Image Header Box (ihdr) @ (40, 22) - Colour Specification Box (colr) @ (62, 15) -UUID Box (uuid) @ (77, 3146) -Contiguous Codestream Box (jp2c) @ (3223, 1132296)""" - -nemo_dump_no_xml = '''JPEG 2000 Signature Box (jP ) @ (0, 12) - Signature: 0d0a870a -File Type Box (ftyp) @ (12, 20) - Brand: jp2 - Compatibility: ['jp2 '] -JP2 Header Box (jp2h) @ (32, 45) - Image Header Box (ihdr) @ (40, 22) - Size: [1456 2592 3] - Bitdepth: 8 - Signed: False - Compression: wavelet - Colorspace Unknown: False - Colour Specification Box (colr) @ (62, 15) - Method: enumerated colorspace - Precedence: 0 - Colorspace: sRGB -UUID Box (uuid) @ (77, 3146) - UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) -Contiguous Codestream Box (jp2c) @ (3223, 1132296) - Main header: - 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 resolutions: 2 - Code block height, width: (64 x 64) - Wavelet transform: 5-3 reversible - Precinct size: default, 2^15 x 2^15 - 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"''' - -dump = r"""JPEG 2000 Signature Box (jP ) @ (0, 12) - Signature: 0d0a870a -File Type Box (ftyp) @ (12, 20) - Brand: jp2 - Compatibility: ['jp2 '] -JP2 Header Box (jp2h) @ (32, 45) - Image Header Box (ihdr) @ (40, 22) - Size: [1456 2592 3] - Bitdepth: 8 - Signed: False - Compression: wavelet - Colorspace Unknown: False - Colour Specification Box (colr) @ (62, 15) - Method: enumerated colorspace - Precedence: 0 - Colorspace: sRGB -UUID Box (uuid) @ (77, 3146) - UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) - UUID Data: -{0} -Contiguous Codestream Box (jp2c) @ (3223, 1132296)""" -nemo_dump_no_codestream = dump.format(_indent(nemo_xmp)) - -nemo_dump_no_codestream_no_xml = r"""JPEG 2000 Signature Box (jP ) @ (0, 12) - Signature: 0d0a870a -File Type Box (ftyp) @ (12, 20) - Brand: jp2 - Compatibility: ['jp2 '] -JP2 Header Box (jp2h) @ (32, 45) - Image Header Box (ihdr) @ (40, 22) - Size: [1456 2592 3] - Bitdepth: 8 - Signed: False - Compression: wavelet - Colorspace Unknown: False - Colour Specification Box (colr) @ (62, 15) - Method: enumerated colorspace - Precedence: 0 - Colorspace: sRGB -UUID Box (uuid) @ (77, 3146) - UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) -Contiguous Codestream Box (jp2c) @ (3223, 1132296)""" - -nemo = """JPEG 2000 Signature Box (jP ) @ (0, 12) +nemo_dump_full_opj2 = r'''JPEG 2000 Signature Box (jP ) @ (0, 12) Signature: 0d0a870a File Type Box (ftyp) @ (12, 20) Brand: jp2 @@ -725,370 +253,166 @@ JP2 Header Box (jp2h) @ (32, 45) Method: enumerated colorspace Precedence: 0 Colorspace: sRGB -UUID Box (uuid) @ (77, 3146) +UUID Box (uuid) @ (77, 638) + UUID: 4a706754-6966-6645-7869-662d3e4a5032 (Exif) + UUID Data: +{'Image': {'Make': 'HTC', + 'Model': 'HTC Glacier', + 'XResolution': 72.0, + 'YResolution': 72.0, + 'ResolutionUnit': 2, + 'YCbCrPositioning': 1, + 'ExifTag': 138, + 'GPSTag': 354}, + 'Photo': {'ISOSpeedRatings': 76, + 'ExifVersion': (48, 50, 50, 48), + 'DateTimeOriginal': '2013:02:09 14:47:53', + 'DateTimeDigitized': '2013:02:09 14:47:53', + 'ComponentsConfiguration': (1, 2, 3, 0), + 'FocalLength': 3.53, + 'FlashpixVersion': (48, 49, 48, 48), + 'ColorSpace': 1, + 'PixelXDimension': 2528, + 'PixelYDimension': 1424, + 'InteroperabilityTag': 324}, + 'GPSInfo': {'GPSVersionID': (2, 2, 0), + 'GPSLatitudeRef': 'N', + 'GPSLatitude': [42.0, 20.0, 33.61], + 'GPSLongitudeRef': 'W', + 'GPSLongitude': [71.0, 5.0, 17.32], + 'GPSAltitudeRef': 0, + 'GPSAltitude': 0.0, + 'GPSTimeStamp': [19.0, 47.0, 53.0], + 'GPSMapDatum': 'WGS-84', + 'GPSProcessingMethod': (65, + 83, + 67, + 73, + 73, + 0, + 0, + 0, + 78, + 69, + 84, + 87, + 79, + 82, + 75), + 'GPSDateStamp': '2013:02:09'}, + 'Iop': None} +UUID Box (uuid) @ (715, 2412) UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) - UUID Data: - - - - - Google - 2013-02-09T14:47:53 - - - 1 - 72/1 - 72/1 - 2 - HTC - HTC Glacier - 2592 - 1456 - - - 8 - 8 - 8 - - - 2 - 3 - - - 1343036288/4294967295 - 1413044224/4294967295 - - - - - 2748779008/4294967295 - 1417339264/4294967295 - 1288490240/4294967295 - 2576980480/4294967295 - 644245120/4294967295 - 257698032/4294967295 - - - - - 1 - 2528 - 1424 - 353/100 - 0 - 0/1 - WGS-84 - 2013-02-09T14:47:53 - - - 76 - - - 0220 - 0100 - - - 1 - 2 - 3 - 0 - - - 42,20.56N - 71,5.29W - 2013-02-09T19:47:53Z - NETWORK - - - 2013-02-09T14:47:53 - - - - - Glymur - Python XMP Toolkit - - - - + UUID Data: + + + + - -Contiguous Codestream Box (jp2c) @ (3223, 1132296) - 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 resolutions: 2 - Code block height, width: (64 x 64) - Wavelet transform: 5-3 reversible - Precinct size: default, 2^15 x 2^15 - 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 resolutions: 2 - Code block height, width: (64 x 64) - Wavelet transform: 5-3 reversible - 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 resolutions: 2 - Code block height, width: (64 x 64) - Wavelet transform: 5-3 reversible - 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)""" - -# Output of reader requirements printing for text_GBR.jp2 -text_GBR_rreq = r"""Reader Requirements Box (rreq) @ (40, 109) - Fully Understands Aspect Mask: 0xffff - Display Completely Mask: 0xf8f0 - Standard Features and Masks: - Feature 001: 0x8000 Deprecated - contains no extensions - Feature 005: 0x4080 Unrestricted JPEG 2000 Part 1 codestream, ITU-T Rec. T.800 | ISO/IEC 15444-1 - Feature 012: 0x2040 Deprecated - codestream is contiguous - Feature 018: 0x1020 Deprecated - support for compositing is not required - Feature 044: 0x810 Compositing layer uses Any ICC profile - Vendor Features: - UUID 3a0d0218-0ae9-4115-b376-4bca41ce0e71 - UUID 47c92ccc-d1a1-4581-b904-38bb5467713b - UUID bc45a774-dd50-4ec6-a9f6-f3a137f47e90 - UUID d7c8c5ef-951f-43b2-8757-042500f538e8""" - -file1_xml = """XML Box (xml ) @ (36, 439) - - \t - \t\t2001-11-01T13:45:00.000-06:00 - \t\tProfessional 120 Image - \t - """ - -issue_182_cmap = """Component Mapping Box (cmap) @ (130, 24) - Component 0 ==> palette column 0 - Component 0 ==> palette column 1 - Component 0 ==> palette column 2 - Component 0 ==> palette column 3""" - -issue_183_colr = """Colour Specification Box (colr) @ (62, 12) - Method: restricted ICC profile - Precedence: 0 - ICC Profile: None""" - - -# Progression order is invalid. -issue_186_progression_order = """COD marker segment @ (174, 12) - Coding style: - Entropy coder, without partitions - SOP marker segments: False - EPH marker segments: False - Coding style parameters: - Progression order: 33 (invalid) - Number of layers: 1 - Multiple component transformation usage: reversible - Number of resolutions: 6 - Code block height, width: (32 x 32) - Wavelet transform: 9-7 irreversible - Precinct size: default, 2^15 x 2^15 - 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""" - -# Cinema 2K profile -cinema2k_profile = """SIZ marker segment @ (2, 47) - Profile: Cinema 2K - Reference Grid Height, Width: (1080 x 1920) - Vertical, Horizontal Reference Grid Offset: (0 x 0) - Reference Tile Height, Width: (1080 x 1920) - Vertical, Horizontal Reference Tile Offset: (0 x 0) - Bitdepth: (12, 12, 12) - Signed: (False, False, False) - Vertical, Horizontal Subsampling: ((1, 1), (1, 1), (1, 1))""" - -jplh_color_group_box = r"""Compositing Layer Header Box (jplh) @ (314227, 31) - Colour Group Box (cgrp) @ (314235, 23) - Colour Specification Box (colr) @ (314243, 15) - Method: enumerated colorspace - Precedence: 0 - Colorspace: sRGB""" - -fragment_list_box = r"""Fragment List Box (flst) @ (-1, 0) - Offset 0: 89 - Fragment Length 0: 1132288 - Data Reference 0: 0""" - -number_list_box = r"""Number List Box (nlst) @ (-1, 0) - Association[0]: the rendered result - Association[1]: codestream 0 - Association[2]: compositing layer 0""" - - -goodstuff_codestream_header = r"""Codestream: - SOC marker segment @ (0, 0) - SIZ marker segment @ (2, 47) - Profile: no profile - Reference Grid Height, Width: (800 x 480) - Vertical, Horizontal Reference Grid Offset: (0 x 0) - Reference Tile Height, Width: (800 x 480) - 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 @ (51, 12) - Coding style: - Entropy coder, without partitions - SOP marker segments: False - EPH marker segments: False - Coding style parameters: - Progression order: LRCP - Number of layers: 1 - Multiple component transformation usage: reversible - Number of resolutions: 6 - Code block height, width: (64 x 64) - Wavelet transform: 5-3 reversible - Precinct size: default, 2^15 x 2^15 - 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 @ (65, 19) - Quantization style: no quantization, 2 guard bits - Step size: [(0, 8), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10)]""" - -goodstuff_with_full_header = r"""Codestream: - SOC marker segment @ (0, 0) - SIZ marker segment @ (2, 47) - Profile: no profile - Reference Grid Height, Width: (800 x 480) - Vertical, Horizontal Reference Grid Offset: (0 x 0) - Reference Tile Height, Width: (800 x 480) - 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 @ (51, 12) - Coding style: - Entropy coder, without partitions - SOP marker segments: False - EPH marker segments: False - Coding style parameters: - Progression order: LRCP - Number of layers: 1 - Multiple component transformation usage: reversible - Number of resolutions: 6 - Code block height, width: (64 x 64) - Wavelet transform: 5-3 reversible - Precinct size: default, 2^15 x 2^15 - 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 @ (65, 19) - Quantization style: no quantization, 2 guard bits - Step size: [(0, 8), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10)] - SOT marker segment @ (86, 10) - Tile part index: 0 - Tile part length: 115132 - Tile part instance: 0 - Number of tile parts: 1 - COC marker segment @ (98, 9) - Associated component: 1 - Coding style for this component: Entropy coder, PARTITION = 0 - Coding style parameters: - Number of resolutions: 6 - Code block height, width: (64 x 64) - Wavelet transform: 5-3 reversible - 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 @ (109, 20) - Associated Component: 1 - Quantization style: no quantization, 2 guard bits - Step size: [(0, 8), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10)] - COC marker segment @ (131, 9) - Associated component: 2 - Coding style for this component: Entropy coder, PARTITION = 0 - Coding style parameters: - Number of resolutions: 6 - Code block height, width: (64 x 64) - Wavelet transform: 5-3 reversible - 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 @ (142, 20) - Associated Component: 2 - Quantization style: no quantization, 2 guard bits - Step size: [(0, 8), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), (0, 10)] - SOD marker segment @ (164, 0) - EOC marker segment @ (115218, 0)""" + +Contiguous Codestream Box (jp2c) @ (3127, 1132296) + Main header: + SOC marker segment @ (3135, 0) + SIZ marker segment @ (3137, 47) + Profile: 2 + 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 @ (3186, 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 resolutions: 2 + Code block height, width: (64 x 64) + Wavelet transform: 5-3 reversible + Precinct size: default, 2^15 x 2^15 + 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 @ (3200, 7) + Quantization style: no quantization, 2 guard bits + Step size: [(0, 8), (0, 9), (0, 9), (0, 10)] + CME marker segment @ (3209, 37) + "Created by OpenJPEG version 2.0.0"''' +nemo_dump_full_p27 = r'''JPEG 2000 Signature Box (jP ) @ (0, 12) + Signature: 0d0a870a +File Type Box (ftyp) @ (12, 20) + Brand: jp2 + Compatibility: ['jp2 '] +JP2 Header Box (jp2h) @ (32, 45) + Image Header Box (ihdr) @ (40, 22) + Size: [1456 2592 3] + Bitdepth: 8 + Signed: False + Compression: wavelet + Colorspace Unknown: False + Colour Specification Box (colr) @ (62, 15) + Method: enumerated colorspace + Precedence: 0 + Colorspace: sRGB +UUID Box (uuid) @ (77, 638) + UUID: 4a706754-6966-6645-7869-662d3e4a5032 (Exif) + UUID Data: +{'GPSInfo': OrderedDict([('GPSVersionID', (2, 2, 0)), ('GPSLatitudeRef', 'N'), ('GPSLatitude', [42.0, 20.0, 33.61]), ('GPSLongitudeRef', 'W'), ('GPSLongitude', [71.0, 5.0, 17.32]), ('GPSAltitudeRef', 0), ('GPSAltitude', 0.0), ('GPSTimeStamp', [19.0, 47.0, 53.0]), ('GPSMapDatum', 'WGS-84'), ('GPSProcessingMethod', (65, 83, 67, 73, 73, 0, 0, 0, 78, 69, 84, 87, 79, 82, 75)), ('GPSDateStamp', '2013:02:09')]), + 'Image': OrderedDict([('Make', 'HTC'), ('Model', 'HTC Glacier'), ('XResolution', 72.0), ('YResolution', 72.0), ('ResolutionUnit', 2), ('YCbCrPositioning', 1), ('ExifTag', 138), ('GPSTag', 354)]), + 'Iop': None, + 'Photo': OrderedDict([('ISOSpeedRatings', 76), ('ExifVersion', (48, 50, 50, 48)), ('DateTimeOriginal', '2013:02:09 14:47:53'), ('DateTimeDigitized', '2013:02:09 14:47:53'), ('ComponentsConfiguration', (1, 2, 3, 0)), ('FocalLength', 3.53), ('FlashpixVersion', (48, 49, 48, 48)), ('ColorSpace', 1), ('PixelXDimension', 2528), ('PixelYDimension', 1424), ('InteroperabilityTag', 324)])} +UUID Box (uuid) @ (715, 2412) + UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP) + UUID Data: + + + + + + +Contiguous Codestream Box (jp2c) @ (3127, 1132296) + Main header: + SOC marker segment @ (3135, 0) + SIZ marker segment @ (3137, 47) + Profile: 2 + 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 @ (3186, 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 resolutions: 2 + Code block height, width: (64 x 64) + Wavelet transform: 5-3 reversible + Precinct size: default, 2^15 x 2^15 + 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 @ (3200, 7) + Quantization style: no quantization, 2 guard bits + Step size: [(0, 8), (0, 9), (0, 9), (0, 10)] + CME marker segment @ (3209, 37) + "Created by OpenJPEG version 2.0.0"''' diff --git a/glymur/test/test_callbacks.py b/glymur/test/test_callbacks.py index cc30de8..722c5da 100644 --- a/glymur/test/test_callbacks.py +++ b/glymur/test/test_callbacks.py @@ -1,12 +1,22 @@ """ Test suite for openjpeg's callback functions. """ +# R0904: Seems like pylint is fooled in this situation +# pylint: disable=R0904 + +# 'mock' most certainly is in unittest (Python 3.3) +# pylint: disable=E0611,F0401 + import os import re import sys import tempfile +import warnings -import unittest +if sys.hexversion < 0x02070000: + import unittest2 as unittest +else: + import unittest if sys.hexversion <= 0x03030000: from mock import patch @@ -17,9 +27,9 @@ else: import glymur -from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG - +@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None, + "Missing openjp2 library.") class TestCallbacks(unittest.TestCase): """Test suite for callbacks.""" @@ -30,60 +40,68 @@ class TestCallbacks(unittest.TestCase): def tearDown(self): pass - @unittest.skipIf(glymur.version.openjpeg_version[0] != '2', - "Missing openjp2 library.") - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) - @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") - def test_info_callback_on_write_backwards_compatibility(self): - """Verify messages printed when writing an image in verbose mode.""" - j = glymur.Jp2k(self.jp2file) - with self.assertWarns(UserWarning): - tiledata = j.read(tile=0) - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - with patch('sys.stdout', new=StringIO()) as fake_out: - glymur.Jp2k(tfile.name, data=tiledata, verbose=True) - actual = fake_out.getvalue().strip() - expected = '[INFO] tile number 1 / 1' - self.assertEqual(actual, expected) - - @unittest.skipIf(glymur.version.openjpeg_version[0] != '2', - "Missing openjp2 library.") @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_info_callback_on_write(self): """Verify messages printed when writing an image in verbose mode.""" j = glymur.Jp2k(self.jp2file) - tiledata = j[:] + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + tiledata = j.read(tile=0) with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + j = glymur.Jp2k(tfile.name, 'wb') with patch('sys.stdout', new=StringIO()) as fake_out: - glymur.Jp2k(tfile.name, data=tiledata, verbose=True) + j.write(tiledata, verbose=True) actual = fake_out.getvalue().strip() expected = '[INFO] tile number 1 / 1' self.assertEqual(actual, expected) - @unittest.skipIf(glymur.version.openjpeg_version[0] == '0', - "Missing openjpeg/openjp2 library.") def test_info_callbacks_on_read(self): """stdio output when info callback handler is enabled""" # Verify that we get the expected stdio output when our internal info # callback handler is enabled. - jp2 = glymur.Jp2k(self.j2kfile) + j = glymur.Jp2k(self.j2kfile) with patch('sys.stdout', new=StringIO()) as fake_out: - jp2.verbose = True - jp2[::2, ::2] + j.read(rlevel=1, verbose=True, area=(0, 0, 200, 150)) actual = fake_out.getvalue().strip() - if glymur.version.openjpeg_version[0] == '2': - lines = ['[INFO] Start to read j2k main header (0).', - '[INFO] Main header has been correctly decoded.', - '[INFO] Setting decoding area to 0,0,480,800', - '[INFO] Header of tile 0 / 0 has been read.', - '[INFO] Tile 1/1 has been decoded.', - '[INFO] Image data has been updated with tile 1.'] + lines = ['[INFO] Start to read j2k main header (0).', + '[INFO] Main header has been correctly decoded.', + '[INFO] Setting decoding area to 0,0,150,200', + '[INFO] Header of tile 0 / 0 has been read.', + '[INFO] Tile 1/1 has been decoded.', + '[INFO] Image data has been updated with tile 1.'] + + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + +@unittest.skipIf(glymur.lib.openjpeg.OPENJPEG is None, + "Missing openjpeg library.") +class TestCallbacks15(unittest.TestCase): + """This test suite is for OpenJPEG 1.5.1 properties. + """ + + def setUp(self): + self.jp2file = glymur.data.nemo() + self.j2kfile = glymur.data.goodstuff() + + def tearDown(self): + pass + + def test_info_callbacks_on_read(self): + """Verify stdout when reading. + + Verify that we get the expected stdio output when our internal info + callback handler is enabled. + """ + with patch('glymur.lib.openjp2.OPENJP2', new=None): + # Force to use OPENJPEG instead of OPENJP2. + j = glymur.Jp2k(self.j2kfile) + with patch('sys.stdout', new=StringIO()) as fake_out: + j.read(rlevel=1, verbose=True) + actual = fake_out.getvalue().strip() - expected = '\n'.join(lines) - self.assertEqual(actual, expected) - else: regex = re.compile(r"""\[INFO\]\stile\s1\sof\s1\s+ \[INFO\]\s-\stiers-1\stook\s [0-9]+\.[0-9]+\ss\s+ @@ -93,7 +111,13 @@ class TestCallbacks(unittest.TestCase): [0-9]+\.[0-9]+\ss""", re.VERBOSE) + # assertRegex in Python 3.3 (python2.7/pylint issue) + # pylint: disable=E1101 if sys.hexversion <= 0x03020000: self.assertRegexpMatches(actual, regex) else: self.assertRegex(actual, regex) + + +if __name__ == "__main__": + unittest.main() diff --git a/glymur/test/test_codestream.py b/glymur/test/test_codestream.py new file mode 100644 index 0000000..1141460 --- /dev/null +++ b/glymur/test/test_codestream.py @@ -0,0 +1,123 @@ +""" +Test suite for codestream parsing. +""" + +# unittest doesn't work well with R0904. +# pylint: disable=R0904 + +# tempfile.TemporaryDirectory, unittest.assertWarns introduced in 3.2 +# pylint: disable=E1101 + +# unittest2 is python2.6 only (pylint/python-2.7) +# pylint: disable=F0401 + +import os +import struct +import sys +import tempfile +import warnings + +if sys.hexversion < 0x02070000: + import unittest2 as unittest +else: + import unittest + +from glymur import Jp2k +import glymur + +try: + DATA_ROOT = os.environ['OPJ_DATA_ROOT'] +except KeyError: + DATA_ROOT = None +except: + raise + + +@unittest.skipIf(DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") +class TestCodestream(unittest.TestCase): + """Test suite for unusual codestream cases.""" + + def setUp(self): + self.jp2file = glymur.data.nemo() + + def tearDown(self): + pass + + @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") + def test_reserved_marker_segment(self): + """Reserved marker segments are ok.""" + + # Some marker segments were reserved in FCD15444-1. Since that + # standard is old, some of them may have come into use. + # + # Let's inject a reserved marker segment into a file that + # we know something about to make sure we can still parse it. + filename = os.path.join(DATA_ROOT, 'input/conformance/p0_01.j2k') + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + with open(filename, 'rb') as ifile: + # Everything up until the first QCD marker. + read_buffer = ifile.read(45) + 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[2].marker_id, '0xff6f') + self.assertEqual(codestream.segment[2].length, 3) + self.assertEqual(codestream.segment[2].data, b'\x00') + + @unittest.skipIf(sys.hexversion < 0x03020000, + "Uses features introduced in 3.2.") + @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") + def test_unknown_marker_segment(self): + """Should warn for an unknown marker.""" + # Let's inject a marker segment whose marker does not appear to + # be valid. We still parse the file, but warn about the offending + # marker. + filename = os.path.join(DATA_ROOT, 'input/conformance/p0_01.j2k') + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + with open(filename, 'rb') as ifile: + # Everything up until the first QCD marker. + read_buffer = ifile.read(45) + tfile.write(read_buffer) + + # Write the new marker segment, 0xff79 = 65401 + read_buffer = struct.pack('>HHB', int(65401), 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() + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + codestream = Jp2k(tfile.name).get_codestream() + self.assertEqual(len(w), 1) + + self.assertEqual(codestream.segment[2].marker_id, '0xff79') + self.assertEqual(codestream.segment[2].length, 3) + self.assertEqual(codestream.segment[2].data, b'\x00') + + def test_psot_is_zero(self): + """Psot=0 in SOT is perfectly legal. Issue #78.""" + filename = os.path.join(DATA_ROOT, + 'input/nonregression/123.j2c') + j = Jp2k(filename) + codestream = j.get_codestream(header_only=False) + + # The codestream is valid, so we should be able to get the entire + # codestream, so the last one is EOC. + self.assertEqual(codestream.segment[-1].marker_id, 'EOC') + +if __name__ == "__main__": + unittest.main() diff --git a/glymur/test/test_config.py b/glymur/test/test_config.py index 59a8ef3..2efd7b1 100644 --- a/glymur/test/test_config.py +++ b/glymur/test/test_config.py @@ -1,13 +1,26 @@ """These tests are for edge cases where OPENJPEG does not exist, but OPENJP2 may be present in some form or other. """ -import contextlib -import ctypes +# unittest doesn't work well with R0904. +# pylint: disable=R0904 + +# tempfile.TemporaryDirectory, unittest.assertWarns introduced in 3.2 +# pylint: disable=E1101 + +# unittest.mock only in Python 3.3 (python2.7/pylint import issue) +# pylint: disable=E0611,F0401 + + import imp import os import sys import tempfile -import unittest +import warnings + +if sys.hexversion < 0x02070000: + import unittest2 as unittest +else: + import unittest if sys.hexversion <= 0x03030000: from mock import patch @@ -17,43 +30,6 @@ else: import glymur from glymur import Jp2k -from .fixtures import (WARNING_INFRASTRUCTURE_ISSUE, - WARNING_INFRASTRUCTURE_MSG, - WINDOWS_TMP_FILE_MSG) - - -def openjpeg_not_found_by_ctypes(): - """ - Need to know if openjpeg library can be picked right up by ctypes for one - of the tests. - """ - with patch.dict('os.environ', - {'DYLD_FALLBACK_LIBRARY_PATH': '/opt/local/lib'}): - if ctypes.util.find_library('openjpeg') is None: - return True - else: - return False - - -@contextlib.contextmanager -def chdir(dirname=None): - """ - This context manager restores the value of the current working directory - (cwd) after the enclosed code block completes or raises an exception. If a - directory name is supplied to the context manager then the cwd is changed - prior to running the code block. - - Shamelessly lifted from - http://www.astropython.org/snippet/2009/10/chdir-context-manager - """ - curdir = os.getcwd() - try: - if dirname is not None: - os.chdir(dirname) - yield - finally: - os.chdir(curdir) - @unittest.skipIf(sys.hexversion < 0x03020000, "TemporaryDirectory introduced in 3.2.") @@ -89,6 +65,7 @@ class TestSuite(unittest.TestCase): # Need to reliably recover the location of the openjp2 library, # so using '_name' appears to be the only way to do it. + # pylint: disable=W0212 libloc = glymur.lib.openjp2.OPENJP2._name line = 'openjp2: {0}\n'.format(libloc) tfile.write(line) @@ -97,8 +74,6 @@ class TestSuite(unittest.TestCase): imp.reload(glymur.lib.openjp2) Jp2k(self.jp2file) - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) - @unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) def test_xdg_env_config_file_is_bad(self): """A non-existant library location should be rejected.""" with tempfile.TemporaryDirectory() as tdir: @@ -113,60 +88,55 @@ class TestSuite(unittest.TestCase): with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}): # Misconfigured new configuration file should # be rejected. - regex = 'could not be loaded' - with self.assertWarnsRegex(UserWarning, regex): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") imp.reload(glymur.lib.openjp2) + self.assertEqual(len(w), 1) - @unittest.skipIf(glymur.lib.openjp2.OPENJPEG is None, - "Needs openjp2 and openjpeg before this test make sense.") - @unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) - def test_library_specified_as_None(self): - """Verify that we can stop library from being loaded by using None.""" - with tempfile.TemporaryDirectory() as tdir: - configdir = os.path.join(tdir, 'glymur') - os.mkdir(configdir) - fname = os.path.join(configdir, 'glymurrc') - with open(fname, 'w') as fptr: - # Essentially comment out openjp2 and preferentially load - # openjpeg instead. - fptr.write('[library]\n') - fptr.write('openjp2: None\n') - msg = 'openjpeg: {0}\n' - msg = msg.format(glymur.lib.openjp2.OPENJPEG._name) - fptr.write(msg) - fptr.flush() - with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}): - imp.reload(glymur.lib.openjp2) - self.assertIsNone(glymur.lib.openjp2.OPENJP2) - self.assertIsNotNone(glymur.lib.openjp2.OPENJPEG) - @unittest.skipIf(glymur.lib.openjp2.OPENJPEG is None, - "Needs openjpeg before this test make sense.") - @unittest.skipIf(openjpeg_not_found_by_ctypes(), - "OpenJPEG must be found before this test can work.") - @unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) - def test_config_dir_but_no_config_file(self): +@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None and + glymur.lib.openjpeg.OPENJPEG is None, + "Missing openjp2 library.") +class TestConfig(unittest.TestCase): + """Test suite for reading without proper library in place.""" - with tempfile.TemporaryDirectory() as tdir: - configdir = os.path.join(tdir, 'glymur') - os.mkdir(configdir) - with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}): - # Should still be able to load openjpeg, despite the - # configuration file not being there - imp.reload(glymur.lib.openjpeg) - self.assertIsNotNone(glymur.lib.openjp2.OPENJPEG) + def setUp(self): + self.jp2file = glymur.data.nemo() + self.j2kfile = glymur.data.goodstuff() - @unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) - def test_config_file_in_current_directory(self): - """A configuration file in the current directory should be honored.""" - libloc = glymur.lib.openjp2.OPENJP2._name - with tempfile.TemporaryDirectory() as tdir1: - fname = os.path.join(tdir1, 'glymurrc') - with open(fname, 'w') as fptr: - fptr.write('[library]\n') - fptr.write('openjp2: {0}\n'.format(libloc)) - fptr.flush() - with chdir(tdir1): - # Should be able to load openjp2 as before. - imp.reload(glymur.lib.openjp2) - self.assertEqual(glymur.lib.openjp2.OPENJP2._name, libloc) + def tearDown(self): + pass + + def test_read_without_library(self): + """Don't have either openjp2 or openjpeg libraries? Must error out. + """ + with patch('glymur.lib.openjp2.OPENJP2', new=None): + with patch('glymur.lib.openjpeg.OPENJPEG', new=None): + with self.assertRaises(glymur.jp2k.LibraryNotFoundError): + glymur.Jp2k(self.jp2file).read() + + def test_read_bands_without_library(self): + """Don't have openjp2 library? Must error out. + """ + with patch('glymur.lib.openjp2.OPENJP2', new=None): + with patch('glymur.lib.openjpeg.OPENJPEG', new=None): + with patch('glymur.version.openjpeg_version_tuple', + new=(0, 0, 0)): + with self.assertRaises(glymur.jp2k.LibraryNotFoundError): + glymur.Jp2k(self.jp2file).read_bands() + + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + def test_write_without_library(self): + """Don't have openjpeg libraries? Must error out. + """ + data = glymur.Jp2k(self.j2kfile).read() + with patch('glymur.lib.openjp2.OPENJP2', new=None): + with patch('glymur.lib.openjpeg.OPENJPEG', new=None): + with self.assertRaises(glymur.jp2k.LibraryNotFoundError): + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + ofile = Jp2k(tfile.name, 'wb') + ofile.write(data) + + +if __name__ == "__main__": + unittest.main() diff --git a/glymur/test/test_conformance.py b/glymur/test/test_conformance.py new file mode 100644 index 0000000..ccdaffa --- /dev/null +++ b/glymur/test/test_conformance.py @@ -0,0 +1,126 @@ +""" +These tests deal with JPX/JP2/J2K images in the format-corpus repository. +""" +# R0904: Not too many methods in unittest. +# pylint: disable=R0904 + +# E1101: assertWarns introduced in python 3.2 +# pylint: disable=E1101 + +# unittest2 is python2.6 only (pylint/python-2.7) +# pylint: disable=F0401 + +import os +from os.path import join +import re +import sys +import warnings + +if sys.hexversion < 0x02070000: + import unittest2 as unittest +else: + import unittest + +import glymur +from glymur import Jp2k + +try: + FORMAT_CORPUS_DATA_ROOT = os.environ['FORMAT_CORPUS_DATA_ROOT'] +except KeyError: + FORMAT_CORPUS_DATA_ROOT = None + +try: + OPJ_DATA_ROOT = os.environ['OPJ_DATA_ROOT'] +except KeyError: + OPJ_DATA_ROOT = None + + +@unittest.skipIf(FORMAT_CORPUS_DATA_ROOT is None, + "FORMAT_CORPUS_DATA_ROOT environment variable not set") +class TestSuiteFormatCorpus(unittest.TestCase): + """Test suite for files in format corpus repository.""" + + @unittest.skipIf(re.match(r"""1\.[0123]""", + glymur.version.openjpeg_version) is not None, + "Needs 1.3+ to catch this.") + def test_balloon_trunc1(self): + """Has one byte shaved off of EOC marker.""" + jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT, + 'jp2k-test/byteCorruption/balloon_trunc1.jp2') + j2k = Jp2k(jfile) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + codestream = j2k.get_codestream(header_only=False) + self.assertEqual(len(w), 1) + + # The last segment is truncated, so there should not be an EOC marker. + self.assertNotEqual(codestream.segment[-1].marker_id, 'EOC') + + # The codestream is not as long as claimed. + with self.assertRaises((OSError, IOError)): + j2k.read(rlevel=-1) + + def test_balloon_trunc3(self): + """Most of last tile is missing.""" + jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT, + 'jp2k-test/byteCorruption/balloon_trunc3.jp2') + j2k = Jp2k(jfile) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + codestream = j2k.get_codestream(header_only=False) + self.assertEqual(len(w), 1) + + # The last segment is truncated, so there should not be an EOC marker. + self.assertNotEqual(codestream.segment[-1].marker_id, 'EOC') + + # Should error out, it does not. + #with self.assertRaises(OSError): + # j2k.read(rlevel=-1) + + def test_jp2_brand_any_icc_profile(self): + """If 'jp2 ', then the method cannot be any icc profile.""" + jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT, + 'jp2k-test', 'icc', + 'balloon_eciRGBv2_ps_adobeplugin.jpf') + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + Jp2k(jfile) + self.assertEqual(len(w), 1) + + def test_jp2_brand_iccpr_mult_colr(self): + """Has colr box, one that conforms, one that does not.""" + + # Wrong 'brand' field; contains two versions of ICC profile: one + # embedded using "Any ICC" method; other embedded using "Restricted + # ICC" method, with description ("Modified eciRGB v2") and profileClass + # ("Input Device") changed relative to original profile. + jfile = join(FORMAT_CORPUS_DATA_ROOT, 'jp2k-test', 'icc', + 'balloon_eciRGBv2_ps_adobeplugin_jp2compatible.jpf') + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + Jp2k(jfile) + self.assertEqual(len(w), 1) + + +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") +class TestSuiteOpj(unittest.TestCase): + """Test suite for files in openjpeg repository.""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def test_jp2_brand_any_icc_profile(self): + """If 'jp2 ', then the method cannot be any icc profile.""" + filename = os.path.join(OPJ_DATA_ROOT, + 'input/nonregression/text_GBR.jp2') + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + Jp2k(filename) + self.assertEqual(len(w), 1) + +if __name__ == "__main__": + unittest.main() diff --git a/glymur/test/test_glymur_warnings.py b/glymur/test/test_glymur_warnings.py deleted file mode 100644 index 8086004..0000000 --- a/glymur/test/test_glymur_warnings.py +++ /dev/null @@ -1,164 +0,0 @@ -""" -Test suite for warnings issued by glymur. -""" -import os -import re -import struct -import tempfile -import unittest - -from glymur import Jp2k -import glymur - -from .fixtures import opj_data_file, OPJ_DATA_ROOT -from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG - - -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") -@unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) -class TestWarnings(unittest.TestCase): - """Test suite for warnings issued by glymur.""" - - def test_invalid_compatibility_list_entry(self): - """should not error out with invalid compatibility list entry""" - filename = opj_data_file('input/nonregression/issue397.jp2') - with self.assertWarns(UserWarning): - Jp2k(filename) - self.assertTrue(True) - - def test_exceeded_box_length(self): - """ - should warn if reading past end of a box - - Verify that a warning is issued if we read past the end of a box - This file has a palette (pclr) box whose length is impossibly - short. - """ - infile = os.path.join(OPJ_DATA_ROOT, - 'input/nonregression/mem-b2ace68c-1381.jp2') - regex = re.compile(r'''Encountered\san\sunrecoverable\sValueError\s - while\sparsing\sa\sPalette\sbox\sat\sbyte\s - offset\s\d+\.\s+The\soriginal\serror\smessage\s - was\s"total\ssize\sof\snew\sarray\smust\sbe\s - unchanged"''', - re.VERBOSE) - with self.assertWarnsRegex(UserWarning, regex): - Jp2k(infile) - - def test_NR_DEC_issue188_beach_64bitsbox_jp2_41_decode(self): - """ - Has an 'XML ' box instead of 'xml '. Yes that is pedantic, but it - really does deserve a warning. - """ - relpath = 'input/nonregression/issue188_beach_64bitsbox.jp2' - jfile = opj_data_file(relpath) - pattern = r"""Unrecognized\sbox\s\(b'XML\s'\)\sencountered.""" - regex = re.compile(pattern, re.VERBOSE) - with self.assertWarnsRegex(UserWarning, regex): - Jp2k(jfile) - - def test_NR_gdal_fuzzer_unchecked_numresolutions_dump(self): - """ - Has an invalid number of resolutions. - """ - lst = ['input', 'nonregression', - 'gdal_fuzzer_unchecked_numresolutions.jp2'] - jfile = opj_data_file('/'.join(lst)) - regex = re.compile(r"""Invalid\snumber\sof\sresolutions\s - \(\d+\)\.""", - re.VERBOSE) - with self.assertWarnsRegex(UserWarning, regex): - Jp2k(jfile).get_codestream() - - @unittest.skipIf(re.match("1.5|2.0.0", glymur.version.openjpeg_version), - "Test not passing on 1.5.x, not introduced until 2.x") - def test_NR_gdal_fuzzer_check_number_of_tiles(self): - """ - Has an impossible tiling setup. - """ - lst = ['input', 'nonregression', - 'gdal_fuzzer_check_number_of_tiles.jp2'] - jfile = opj_data_file('/'.join(lst)) - regex = re.compile(r"""Invalid\snumber\sof\stiles\s - \(\d+\)\.""", - re.VERBOSE) - with self.assertWarnsRegex(UserWarning, regex): - Jp2k(jfile).get_codestream() - - def test_NR_gdal_fuzzer_check_comp_dx_dy_jp2_dump(self): - """ - Invalid subsampling value. - """ - lst = ['input', 'nonregression', 'gdal_fuzzer_check_comp_dx_dy.jp2'] - jfile = opj_data_file('/'.join(lst)) - regex = re.compile(r"""Invalid\ssubsampling\svalue\sfor\scomponent\s - \d+:\s+ - dx=\d+,\s*dy=\d+""", - re.VERBOSE) - with self.assertWarnsRegex(UserWarning, regex): - Jp2k(jfile).get_codestream() - - def test_NR_gdal_fuzzer_assert_in_opj_j2k_read_SQcd_SQcc_patch_jp2(self): - lst = ['input', 'nonregression', - 'gdal_fuzzer_assert_in_opj_j2k_read_SQcd_SQcc.patch.jp2'] - jfile = opj_data_file('/'.join(lst)) - regex = re.compile(r"""Invalid\scomponent\snumber\s\(\d+\),\s - number\sof\scomponents\sis\sonly\s\d+""", - re.VERBOSE) - with self.assertWarnsRegex(UserWarning, regex): - Jp2k(jfile).get_codestream() - - def test_bad_rsiz(self): - """Should warn if RSIZ is bad. Issue196""" - filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2') - with self.assertWarnsRegex(UserWarning, 'Invalid profile'): - Jp2k(filename).get_codestream() - - def test_bad_wavelet_transform(self): - """Should warn if wavelet transform is bad. Issue195""" - filename = opj_data_file('input/nonregression/edf_c2_10025.jp2') - with self.assertWarnsRegex(UserWarning, 'Invalid wavelet transform'): - Jp2k(filename).get_codestream() - - def test_invalid_progression_order(self): - """Should still be able to parse even if prog order is invalid.""" - jfile = opj_data_file('input/nonregression/2977.pdf.asan.67.2198.jp2') - with self.assertWarnsRegex(UserWarning, 'Invalid progression order'): - Jp2k(jfile).get_codestream() - - def test_tile_height_is_zero(self): - """Zero tile height should not cause an exception.""" - filename = 'input/nonregression/2539.pdf.SIGFPE.706.1712.jp2' - filename = opj_data_file(filename) - with self.assertWarnsRegex(UserWarning, 'Invalid tile dimensions'): - Jp2k(filename).get_codestream() - - @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") - def test_unknown_marker_segment(self): - """Should warn for an unknown marker.""" - # Let's inject a marker segment whose marker does not appear to - # be valid. We still parse the file, but warn about the offending - # marker. - filename = os.path.join(OPJ_DATA_ROOT, 'input/conformance/p0_01.j2k') - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - with open(filename, 'rb') as ifile: - # Everything up until the first QCD marker. - read_buffer = ifile.read(45) - tfile.write(read_buffer) - - # Write the new marker segment, 0xff79 = 65401 - read_buffer = struct.pack('>HHB', int(65401), 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() - - with self.assertWarnsRegex(UserWarning, 'Unrecognized marker'): - Jp2k(tfile.name).get_codestream() - - -if __name__ == "__main__": - unittest.main() diff --git a/glymur/test/test_icc.py b/glymur/test/test_icc.py index e18775c..b998d7c 100644 --- a/glymur/test/test_icc.py +++ b/glymur/test/test_icc.py @@ -1,17 +1,29 @@ """ ICC profile tests. """ + +# unittest doesn't work well with R0904. +# pylint: disable=R0904 + +# unittest2 is python2.6 only (pylint/python-2.7) +# pylint: disable=F0401 + import datetime -import unittest +import os +import sys +import warnings + +if sys.hexversion < 0x02070000: + import unittest2 as unittest +else: + import unittest import numpy as np from glymur import Jp2k from .fixtures import OPJ_DATA_ROOT, opj_data_file -from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG -@unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") class TestICC(unittest.TestCase): @@ -26,9 +38,7 @@ class TestICC(unittest.TestCase): def test_file5(self): """basic ICC profile""" filename = opj_data_file('input/conformance/file5.jp2') - with self.assertWarns(UserWarning): - # The file has a bad compatibility list entry. Not important here. - j = Jp2k(filename) + j = Jp2k(filename) profile = j.box[3].box[1].icc_profile self.assertEqual(profile['Size'], 546) self.assertEqual(profile['Preferred CMM Type'], 0) @@ -60,6 +70,12 @@ class TestICC(unittest.TestCase): """invalid ICC header data should cause UserWarning""" jfile = opj_data_file('input/nonregression/orb-blue10-lin-jp2.jp2') - regex = 'ICC profile header is corrupt' - with self.assertWarnsRegex(UserWarning, regex): + # assertWarns in Python 3.3 (python2.7/pylint issue) + # pylint: disable=E1101 + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") Jp2k(jfile) + self.assertEqual(len(w), 1) + +if __name__ == "__main__": + unittest.main() diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index f9e7c5c..7d63233 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -1,6 +1,21 @@ """ Test suite specifically targeting JP2 box layout. """ +# E1103: return value from read may be list or np array +# pylint: disable=E1103 + +# F0401: unittest2 is needed on python-2.6 (pylint on 2.7) +# pylint: disable=F0401 + +# R0902: More than 7 instance attributes are just fine for testing. +# pylint: disable=R0902 + +# R0904: Seems like pylint is fooled in this situation +# pylint: disable=R0904 + +# W0613: load_tests doesn't need to use ignore or loader arguments. +# pylint: disable=W0613 + import doctest import os import re @@ -8,10 +23,14 @@ import shutil import struct import sys import tempfile -from uuid import UUID -import unittest +import uuid +import xml.etree.cElementTree as ET + +if sys.hexversion < 0x02070000: + import unittest2 as unittest +else: + import unittest -import lxml.etree as ET import numpy as np import glymur @@ -22,13 +41,12 @@ from glymur.jp2box import JPEG2000SignatureBox from glymur.core import COLOR, OPACITY from glymur.core import RED, GREEN, BLUE, GREY, WHOLE_IMAGE -from .fixtures import (WARNING_INFRASTRUCTURE_ISSUE, - WARNING_INFRASTRUCTURE_MSG, - WINDOWS_TMP_FILE_MSG, MetadataBase) +from .fixtures import OPENJP2_IS_V2_OFFICIAL - -def docTearDown(doctest_obj): - glymur.set_parseoptions(full_codestream=False) +try: + FORMAT_CORPUS_DATA_ROOT = os.environ['FORMAT_CORPUS_DATA_ROOT'] +except KeyError: + FORMAT_CORPUS_DATA_ROOT = None def load_tests(loader, tests, ignore): @@ -36,85 +54,13 @@ def load_tests(loader, tests, ignore): if os.name == "nt": # Can't do it on windows, temporary file issue. return tests - tests.addTests(doctest.DocTestSuite('glymur.jp2box', - tearDown=docTearDown)) + tests.addTests(doctest.DocTestSuite('glymur.jp2box')) return tests - -@unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) -class TestDataEntryURL(unittest.TestCase): - """Test suite for DataEntryURL boxes.""" - def setUp(self): - self.jp2file = glymur.data.nemo() - - @unittest.skipIf(re.match("1.5|2", - glymur.version.openjpeg_version) is None, - "Must have openjpeg 1.5 or higher to run") - def test_wrap_greyscale(self): - """A single component should be wrapped as GREYSCALE.""" - j = Jp2k(self.jp2file) - data = j[:] - red = data[:, :, 0] - - # Write it back out as a raw codestream. - with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile1: - j2k = glymur.Jp2k(tfile1.name, data=red) - - # Ok, now rewrap it as JP2. The colorspace should be GREYSCALE. - with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile2: - jp2 = j2k.wrap(tfile2.name) - self.assertEqual(jp2.box[2].box[1].colorspace, - glymur.core.GREYSCALE) - - def test_basic_url(self): - """Just your most basic URL box.""" - # Wrap our j2k file in a JP2 box along with an interior url box. - jp2 = Jp2k(self.jp2file) - - url = 'http://glymur.readthedocs.org' - deurl = glymur.jp2box.DataEntryURLBox(0, (0, 0, 0), url) - boxes = [box for box in jp2.box if box.box_id != 'uuid'] - boxes.append(deurl) - with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: - jp22 = jp2.wrap(tfile.name, boxes=boxes) - - actdata = [box.box_id for box in jp22.box] - expdata = ['jP ', 'ftyp', 'jp2h', 'jp2c', 'url '] - self.assertEqual(actdata, expdata) - self.assertEqual(jp22.box[4].version, 0) - self.assertEqual(jp22.box[4].flag, (0, 0, 0)) - self.assertEqual(jp22.box[4].url, url) - - def test_null_termination(self): - """I.9.3.2 specifies that location field must be null terminated.""" - jp2 = Jp2k(self.jp2file) - - url = 'http://glymur.readthedocs.org' - deurl = glymur.jp2box.DataEntryURLBox(0, (0, 0, 0), url) - boxes = [box for box in jp2.box if box.box_id != 'uuid'] - boxes.append(deurl) - with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: - jp22 = jp2.wrap(tfile.name, boxes=boxes) - - self.assertEqual(jp22.box[-1].length, 42) - - # Go to the last box. Seek past the L, T, version, - # and flag fields. - with open(tfile.name, 'rb') as fptr: - fptr.seek(jp22.box[-1].offset + 4 + 4 + 1 + 3) - - nbytes = (jp22.box[-1].offset + - jp22.box[-1].length - - fptr.tell()) - read_buffer = fptr.read(nbytes) - read_url = read_buffer.decode('utf-8') - self.assertEqual(url + chr(0), read_url) - - -@unittest.skipIf(re.match(r'''0|1|2.0.0''', - glymur.version.openjpeg_version) is not None, - "Not supported until 2.1") -@unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) +@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2 or + re.match(r'''2.0.0''', glymur.version.openjpeg_version), + "Not supported until 2.0+.") +@unittest.skipIf(os.name == "nt", "Temporary file issue on window.") class TestChannelDefinition(unittest.TestCase): """Test suite for channel definition boxes.""" @@ -122,21 +68,24 @@ class TestChannelDefinition(unittest.TestCase): def setUpClass(cls): """Need a one_plane plane image for greyscale testing.""" j2k = Jp2k(glymur.data.goodstuff()) - data = j2k[:] + data = j2k.read() # Write the first component back out to file. with tempfile.NamedTemporaryFile(suffix=".j2k", delete=False) as tfile: - Jp2k(tfile.name, data=data[:, :, 0]) + grey_j2k = Jp2k(tfile.name, 'wb') + grey_j2k.write(data[:, :, 0]) cls.one_plane = tfile.name # Write the first two components back out to file. with tempfile.NamedTemporaryFile(suffix=".j2k", delete=False) as tfile: - Jp2k(tfile.name, data=data[:, :, 0:1]) + grey_j2k = Jp2k(tfile.name, 'wb') + grey_j2k.write(data[:, :, 0:1]) cls.two_planes = tfile.name # Write four components back out to file. with tempfile.NamedTemporaryFile(suffix=".j2k", delete=False) as tfile: + rgba_jp2 = Jp2k(tfile.name, 'wb') shape = (data.shape[0], data.shape[1], 1) alpha = np.zeros((shape), dtype=data.dtype) data4 = np.concatenate((data, alpha), axis=2) - Jp2k(tfile.name, data=data4) + rgba_jp2.write(data4) cls.four_planes = tfile.name @classmethod @@ -169,7 +118,7 @@ class TestChannelDefinition(unittest.TestCase): def test_cdef_no_inputs(self): """channel_type and association are required inputs.""" - with self.assertRaises(TypeError): + with self.assertRaises(IOError): glymur.jp2box.ChannelDefinitionBox() def test_rgb_with_index(self): @@ -345,10 +294,9 @@ class TestChannelDefinition(unittest.TestCase): boxes = [self.jp2b, self.ftyp, self.jp2h, cdef, self.jp2c] with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: - with self.assertRaises((IOError, OSError)): + with self.assertRaises(IOError): j2k.wrap(tfile.name, boxes=boxes) - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_bad_type(self): """Channel types are limited to 0, 1, 2, 65535 Should reject if not all of index, channel_type, association the @@ -356,52 +304,21 @@ class TestChannelDefinition(unittest.TestCase): """ channel_type = (COLOR, COLOR, 3) association = (RED, GREEN, BLUE) - - with self.assertWarns(UserWarning): + with self.assertRaises(IOError): glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type, association=association) - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_wrong_lengths(self): """Should reject if not all of index, channel_type, association the same length. """ channel_type = (COLOR, COLOR) association = (RED, GREEN, BLUE) - with self.assertWarns(UserWarning): + with self.assertRaises(IOError): glymur.jp2box.ChannelDefinitionBox(channel_type=channel_type, association=association) -class TestFileTypeBox(unittest.TestCase): - """Test suite for ftyp box issues.""" - - def setUp(self): - pass - - def tearDown(self): - pass - - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) - def test_brand_unknown(self): - """A ftyp box brand must be 'jp2 ' or 'jpx '.""" - with self.assertWarns(UserWarning): - ftyp = glymur.jp2box.FileTypeBox(brand='jp3') - with self.assertRaises(IOError): - with tempfile.TemporaryFile() as tfile: - ftyp.write(tfile) - - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) - def test_cl_entry_unknown(self): - """A ftyp box cl list can only contain 'jp2 ', 'jpx ', or 'jpxb'.""" - with self.assertWarns(UserWarning): - # Bad compatibility list item. - ftyp = glymur.jp2box.FileTypeBox(compatibility_list=['jp3']) - with self.assertRaises(IOError): - with tempfile.TemporaryFile() as tfile: - ftyp.write(tfile) - - class TestColourSpecificationBox(unittest.TestCase): """Test suite for colr box instantiation.""" @@ -424,7 +341,8 @@ class TestColourSpecificationBox(unittest.TestCase): def tearDown(self): pass - @unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) + @unittest.skipIf(os.name == "nt", + "Problems using NamedTemporaryFile on windows.") def test_colr_with_out_enum_cspace(self): """must supply an enumerated colorspace when writing""" j2k = Jp2k(self.j2kfile) @@ -432,10 +350,10 @@ class TestColourSpecificationBox(unittest.TestCase): boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c] boxes[2].box = [self.ihdr, ColourSpecificationBox(colorspace=None)] with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: - with self.assertRaises(IOError): + with self.assertRaises(NotImplementedError): j2k.wrap(tfile.name, boxes=boxes) - @unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) + @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_missing_colr_box(self): """jp2h must have a colr box""" j2k = Jp2k(self.j2kfile) @@ -445,18 +363,6 @@ class TestColourSpecificationBox(unittest.TestCase): with self.assertRaises(IOError): j2k.wrap(tfile.name, boxes=boxes) - @unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) - def test_bad_approx_jp2_field(self): - """JP2 has requirements for approx field""" - j2k = Jp2k(self.j2kfile) - boxes = [self.jp2b, self.ftyp, self.jp2h, self.jp2c] - colr = ColourSpecificationBox(colorspace=glymur.core.SRGB, - approximation=1) - boxes[2].box = [self.ihdr, colr] - with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: - with self.assertRaises(IOError): - j2k.wrap(tfile.name, boxes=boxes) - def test_default_colr(self): """basic colr instantiation""" colr = ColourSpecificationBox(colorspace=glymur.core.SRGB) @@ -466,89 +372,31 @@ class TestColourSpecificationBox(unittest.TestCase): self.assertEqual(colr.colorspace, glymur.core.SRGB) self.assertIsNone(colr.icc_profile) - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_colr_with_cspace_and_icc(self): """Colour specification boxes can't have both.""" - regex = 'Colorspace and icc_profile cannot both be set' - with self.assertWarnsRegex(UserWarning, regex): + with self.assertRaises((OSError, IOError)): colorspace = glymur.core.SRGB rawb = b'\x01\x02\x03\x04' glymur.jp2box.ColourSpecificationBox(colorspace=colorspace, icc_profile=rawb) - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_colr_with_bad_method(self): """colr must have a valid method field""" colorspace = glymur.core.SRGB method = -1 - regex = 'Invalid method' - with self.assertWarnsRegex(UserWarning, regex): + with self.assertRaises(IOError): glymur.jp2box.ColourSpecificationBox(colorspace=colorspace, method=method) - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_colr_with_bad_approx(self): - """colr should have a valid approximation field""" + """colr must have a valid approximation field""" colorspace = glymur.core.SRGB approx = -1 - with self.assertWarnsRegex(UserWarning, 'Invalid approximation'): + with self.assertRaises(IOError): glymur.jp2box.ColourSpecificationBox(colorspace=colorspace, approximation=approx) - def test_colr_with_bad_color(self): - """colr must have a valid color, strange as though that may sound.""" - colorspace = -1 - approx = 0 - colr = glymur.jp2box.ColourSpecificationBox(colorspace=colorspace, - approximation=approx) - with tempfile.TemporaryFile() as tfile: - with self.assertRaises(IOError): - colr.write(tfile) - -@unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) -class TestPaletteBox(unittest.TestCase): - """Test suite for pclr box instantiation.""" - - def setUp(self): - pass - - def tearDown(self): - pass - - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) - def test_mismatched_bitdepth_signed(self): - """bitdepth and signed arguments must have equal length""" - palette = np.array([[255, 0, 255], [0, 255, 0]], dtype=np.uint8) - bps = (8, 8, 8) - signed = (False, False) - with self.assertWarns(UserWarning): - glymur.jp2box.PaletteBox(palette, bits_per_component=bps, - signed=signed) - - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) - def test_mismatched_signed_palette(self): - """bitdepth and signed arguments must have equal length""" - palette = np.array([[255, 0, 255], [0, 255, 0]], dtype=np.uint8) - bps = (8, 8, 8, 8) - signed = (False, False, False, False) - with self.assertWarns(UserWarning): - glymur.jp2box.PaletteBox(palette, bits_per_component=bps, - signed=signed) - - def test_writing_with_different_bitdepths(self): - """Bitdepths must be the same when writing.""" - palette = np.array([[255, 0, 255], [0, 255, 0]], dtype=np.uint16) - bps = (8, 16, 8) - signed = (False, False, False) - pclr = glymur.jp2box.PaletteBox(palette, bits_per_component=bps, - signed=signed) - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - with self.assertRaises(IOError): - pclr.write(tfile) - - -@unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) class TestAppend(unittest.TestCase): """Tests for append method.""" @@ -566,13 +414,13 @@ class TestAppend(unittest.TestCase): jp2 = Jp2k(tfile.name) the_xml = ET.fromstring('0') - xmlbox = glymur.jp2box.XMLBox(xml=ET.ElementTree(the_xml)) + xmlbox = glymur.jp2box.XMLBox(xml=the_xml) jp2.append(xmlbox) # The sequence of box IDs should be the same as before, but with an # xml box at the end. box_ids = [box.box_id for box in jp2.box] - expected = ['jP ', 'ftyp', 'jp2h', 'uuid', 'jp2c', 'xml '] + expected = ['jP ', 'ftyp', 'jp2h', 'uuid', 'uuid', 'jp2c', 'xml '] self.assertEqual(box_ids, expected) self.assertEqual(ET.tostring(jp2.box[-1].xml.getroot()), b'0') @@ -582,14 +430,14 @@ class TestAppend(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: shutil.copyfile(self.j2kfile, tfile.name) - j2k = Jp2k(tfile.name) + jp2 = Jp2k(tfile.name) - # Make an XML box. XML boxes should always be appendable to jp2 - # files. - the_xml = ET.fromstring('0') - xmlbox = glymur.jp2box.XMLBox(xml=the_xml) + # Make a UUID box. + uuid_instance = uuid.UUID('00000000-0000-0000-0000-000000000000') + data = b'0123456789' + uuidbox = glymur.jp2box.UUIDBox(uuid_instance, data) with self.assertRaises(IOError): - j2k.append(xmlbox) + jp2.append(uuidbox) def test_length_field_is_zero(self): """L=0 (length field in box header) is handled. @@ -621,34 +469,32 @@ class TestAppend(unittest.TestCase): # The sequence of box IDs should be the same as before, but with an # xml box at the end. box_ids = [box.box_id for box in jp2.box] - expected = ['jP ', 'ftyp', 'jp2h', 'uuid', 'jp2c', 'xml '] + expected = ['jP ', 'ftyp', 'jp2h', 'uuid', 'uuid', 'jp2c', 'xml '] self.assertEqual(box_ids, expected) self.assertEqual(ET.tostring(jp2.box[-1].xml.getroot()), b'0') - def test_append_allowable_boxes(self): + def test_only_xml_allowed_to_append(self): """Only XML boxes are allowed to be appended.""" with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: shutil.copyfile(self.jp2file, tfile.name) jp2 = Jp2k(tfile.name) - # Make a UUID box. Only XMP UUID boxes can currently be appended. - uuid_instance = UUID('00000000-0000-0000-0000-000000000000') + # Make a UUID box. + uuid_instance = uuid.UUID('00000000-0000-0000-0000-000000000000') data = b'0123456789' uuidbox = glymur.jp2box.UUIDBox(uuid_instance, data) with self.assertRaises(IOError): jp2.append(uuidbox) -@unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) class TestWrap(unittest.TestCase): """Tests for wrap method.""" def setUp(self): self.j2kfile = glymur.data.goodstuff() self.jp2file = glymur.data.nemo() - self.jpxfile = glymur.data.jpxfile() def tearDown(self): pass @@ -702,6 +548,7 @@ class TestWrap(unittest.TestCase): self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.SRGB) self.assertIsNone(jp2.box[2].box[1].icc_profile) + @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_wrap(self): """basic test for rewrapping a j2c file, no specified boxes""" j2k = Jp2k(self.j2kfile) @@ -709,27 +556,7 @@ class TestWrap(unittest.TestCase): j2k.wrap(tfile.name) self.verify_wrapped_raw(tfile.name) - def test_jpx_to_jp2(self): - """basic test for rewrapping a jpx file""" - jpx = Jp2k(self.jpxfile) - # Use only the signature, file type, header, and 1st codestream. - lst = [0, 1, 2, 5] - boxes = [jpx.box[idx] for idx in lst] - with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: - jp2 = jpx.wrap(tfile.name, boxes=boxes) - - # Verify the outer boxes. - boxes = [box.box_id for box in jp2.box] - self.assertEqual(boxes, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - # Verify the inside boxes. - boxes = [box.box_id for box in jp2.box[2].box] - self.assertEqual(boxes, ['ihdr', 'colr', 'pclr', 'cmap']) - - expected_offsets = [0, 12, 40, 887] - for j, offset in enumerate(expected_offsets): - self.assertEqual(jp2.box[j].offset, offset) - + @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_wrap_jp2(self): """basic test for rewrapping a jp2 file, no specified boxes""" j2k = Jp2k(self.jp2file) @@ -738,60 +565,7 @@ class TestWrap(unittest.TestCase): boxes = [box.box_id for box in jp2.box] self.assertEqual(boxes, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - def test_wrap_jp2_Lzero(self): - """Wrap jp2 with jp2c box length is zero""" - with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: - with open(self.jp2file, 'rb') as ifile: - tfile.write(ifile.read()) - # Rewrite with codestream length as zero. - tfile.seek(3223) - tfile.write(struct.pack('>I', 0)) - tfile.flush() - jp2 = Jp2k(tfile.name) - - with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile2: - jp2 = jp2.wrap(tfile2.name) - boxes = [box for box in jp2.box] - self.assertEqual(boxes[3].length, 1132296) - - def test_wrap_jp2_Lone(self): - """Wrap jp2 with jp2c box length is 1, implies Q field""" - with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: - with open(self.jp2file, 'rb') as ifile: - tfile.write(ifile.read(3223)) - # Write new L, T, Q fields - tfile.write(struct.pack('>I4sQ', 1, b'jp2c', 1132296 + 8)) - # skip over the old L, T fields - ifile.seek(3231) - tfile.write(ifile.read()) - tfile.flush() - jp2 = Jp2k(tfile.name) - - with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile2: - jp2 = jp2.wrap(tfile2.name) - boxes = [box for box in jp2.box] - self.assertEqual(boxes[3].length, 1132296 + 8) - - def test_wrap_compatibility_not_jp2(self): - """File type compatibility must contain jp2""" - jp2 = Jp2k(self.jp2file) - boxes = [box for box in jp2.box] - boxes[1].compatibility_list = ['jpx '] - with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: - with self.assertRaises(IOError): - jp2.wrap(tfile.name, boxes=boxes) - - def test_empty_jp2h(self): - """JP2H box list cannot be empty.""" - jp2 = Jp2k(self.jp2file) - with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: - boxes = jp2.box - # Right here the jp2h superbox has two child boxes. Empty out that - # list to trigger the error. - boxes[2].box = [] - with self.assertRaises(IOError): - jp2.wrap(tfile.name, boxes=boxes) - + @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_default_layout_with_boxes(self): """basic test for rewrapping a jp2 file, boxes specified""" j2k = Jp2k(self.j2kfile) @@ -811,6 +585,7 @@ class TestWrap(unittest.TestCase): j2k.wrap(tfile.name, boxes=boxes) self.verify_wrapped_raw(tfile.name) + @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_ihdr_not_first_in_jp2h(self): """The specification says that ihdr must be the first box in jp2h.""" j2k = Jp2k(self.j2kfile) @@ -830,6 +605,7 @@ class TestWrap(unittest.TestCase): with self.assertRaises(IOError): j2k.wrap(tfile.name, boxes=boxes) + @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_first_boxes_jp_and_ftyp(self): """first two boxes must be jP followed by ftyp""" j2k = Jp2k(self.j2kfile) @@ -851,33 +627,7 @@ class TestWrap(unittest.TestCase): with self.assertRaises(IOError): j2k.wrap(tfile.name, boxes=boxes) - def test_pclr_not_in_jp2h(self): - """A palette box must reside in a JP2 header box.""" - palette = np.array([[255, 0, 255], [0, 255, 0]], dtype=np.int32) - bps = (8, 8, 8) - pclr = glymur.jp2box.PaletteBox(palette=palette, - bits_per_component=bps, - signed=(True, False, True)) - - j2k = Jp2k(self.j2kfile) - codestream = j2k.get_codestream() - height = codestream.segment[1].ysiz - width = codestream.segment[1].xsiz - num_components = len(codestream.segment[1].xrsiz) - - jp2b = JPEG2000SignatureBox() - ftyp = FileTypeBox() - jp2h = JP2HeaderBox() - jp2c = ContiguousCodestreamBox() - colr = ColourSpecificationBox(colorspace=glymur.core.SRGB) - ihdr = ImageHeaderBox(height=height, width=width, - num_components=num_components) - jp2h.box = [ihdr, colr] - boxes = [jp2b, ftyp, jp2h, jp2c, pclr] - with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: - with self.assertRaises(IOError): - j2k.wrap(tfile.name, boxes=boxes) - + @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_jp2h_not_preceeding_jp2c(self): """jp2h must precede jp2c""" j2k = Jp2k(self.j2kfile) @@ -899,6 +649,7 @@ class TestWrap(unittest.TestCase): with self.assertRaises(IOError): j2k.wrap(tfile.name, boxes=boxes) + @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_missing_codestream(self): """Need a codestream box in order to call wrap method.""" j2k = Jp2k(self.j2kfile) @@ -918,75 +669,10 @@ class TestWrap(unittest.TestCase): with self.assertRaises(IOError): j2k.wrap(tfile.name, boxes=boxes) - def test_wrap_jpx_to_jp2_with_unadorned_jpch(self): - """A JPX file rewrapped with plain jpch is not allowed.""" - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile1: - jpx = Jp2k(self.jpxfile) - boxes = [jpx.box[0], jpx.box[1], jpx.box[2], - glymur.jp2box.ContiguousCodestreamBox()] - with self.assertRaises(IOError): - jpx.wrap(tfile1.name, boxes=boxes) - - def test_wrap_jpx_to_jp2_with_incorrect_jp2c_offset(self): - """Reject A JPX file rewrapped with bad jp2c offset.""" - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile1: - jpx = Jp2k(self.jpxfile) - jpch = jpx.box[5] - - # The offset should be 902. - jpch.offset = 901 - jpch.length = 313274 - boxes = [jpx.box[0], jpx.box[1], jpx.box[2], jpch] - with self.assertRaises(IOError): - jpx.wrap(tfile1.name, boxes=boxes) - - def test_wrap_jpx_to_jp2_with_correctly_specified_jp2c(self): - """Accept A JPX file rewrapped with good jp2c.""" - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile1: - jpx = Jp2k(self.jpxfile) - jpch = jpx.box[5] - - # This time get it right. - jpch.offset = 903 - jpch.length = 313274 - boxes = [jpx.box[0], jpx.box[1], jpx.box[2], jpch] - jp2 = jpx.wrap(tfile1.name, boxes=boxes) - - act_ids = [box.box_id for box in jp2.box] - exp_ids = ['jP ', 'ftyp', 'jp2h', 'jp2c'] - self.assertEqual(act_ids, exp_ids) - - act_offsets = [box.offset for box in jp2.box] - exp_offsets = [0, 12, 40, 887] - self.assertEqual(act_offsets, exp_offsets) - - act_lengths = [box.length for box in jp2.box] - exp_lengths = [12, 28, 847, 313274] - self.assertEqual(act_lengths, exp_lengths) - - def test_full_blown_jpx(self): - """Rewrap a jpx file.""" - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile1: - jpx = Jp2k(self.jpxfile) - idx = (list(range(5)) + - list(range(9, 12)) + list(range(6, 9))) + [12] - boxes = [jpx.box[j] for j in idx] - jpx2 = jpx.wrap(tfile1.name, boxes=boxes) - exp_ids = [box.box_id for box in boxes] - lengths = [box.length for box in jpx.box] - exp_lengths = [lengths[j] for j in idx] - act_ids = [box.box_id for box in jpx2.box] - act_lengths = [box.length for box in jpx2.box] - self.assertEqual(exp_ids, act_ids) - self.assertEqual(exp_lengths, act_lengths) - class TestJp2Boxes(unittest.TestCase): """Tests for canonical JP2 boxes.""" - def setUp(self): - self.jpxfile = glymur.data.jpxfile() - def test_default_jp2k(self): """Should be able to instantiate a JPEG2000SignatureBox""" jp2k = glymur.jp2box.JPEG2000SignatureBox() @@ -1021,312 +707,42 @@ class TestJp2Boxes(unittest.TestCase): """Raw instantiation should not produce a main_header.""" box = ContiguousCodestreamBox() self.assertEqual(box.box_id, 'jp2c') - self.assertIsNone(box.codestream) - - def test_codestream_main_header_offset(self): - """main_header_offset is an attribute of the CCS box""" - j = Jp2k(self.jpxfile) - self.assertEqual(j.box[5].main_header_offset, - j.box[5].offset + 8) + self.assertIsNone(box.main_header) -class TestRepr(MetadataBase): - """Tests for __repr__ methods.""" - def test_default_jp2k(self): - """Should be able to eval a JPEG2000SignatureBox""" - jp2k = glymur.jp2box.JPEG2000SignatureBox() +class TestJpxBoxes(unittest.TestCase): + """Tests for JPX boxes.""" - # Test the representation instantiation. - newbox = eval(repr(jp2k)) - self.assertTrue(isinstance(newbox, glymur.jp2box.JPEG2000SignatureBox)) - self.assertEqual(newbox.signature, (13, 10, 135, 10)) + def setUp(self): + pass - def test_free(self): - """Should be able to instantiate a free box""" - free = glymur.jp2box.FreeBox() + def tearDown(self): + pass - # Test the representation instantiation. - newbox = eval(repr(free)) - self.assertTrue(isinstance(newbox, glymur.jp2box.FreeBox)) + @unittest.skipIf(FORMAT_CORPUS_DATA_ROOT is None, + "FORMAT_CORPUS_DATA_ROOT environment variable not set") + def test_codestream_header(self): + """Should recognize codestream header box.""" + jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT, + 'jp2k-formats/balloon.jpf') + jpx = Jp2k(jfile) - def test_nlst(self): - """Should be able to instantiate a number list box""" - assn = (0, 1, 2) - nlst = glymur.jp2box.NumberListBox(assn) + # This superbox just happens to be empty. + self.assertEqual(jpx.box[4].box_id, 'jpch') + self.assertEqual(len(jpx.box[4].box), 0) - # Test the representation instantiation. - newbox = eval(repr(nlst)) - self.assertTrue(isinstance(newbox, glymur.jp2box.NumberListBox)) - self.assertEqual(newbox.associations, (0, 1, 2)) + @unittest.skipIf(FORMAT_CORPUS_DATA_ROOT is None, + "FORMAT_CORPUS_DATA_ROOT environment variable not set") + def test_compositing_layer_header(self): + """Should recognize compositing layer header box.""" + jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT, + 'jp2k-formats/balloon.jpf') + jpx = Jp2k(jfile) - def test_ftbl(self): - """Should be able to instantiate a fragment table box""" - ftbl = glymur.jp2box.FragmentTableBox() + # This superbox just happens to be empty. + self.assertEqual(jpx.box[5].box_id, 'jplh') + self.assertEqual(len(jpx.box[5].box), 0) - # Test the representation instantiation. - newbox = eval(repr(ftbl)) - self.assertTrue(isinstance(newbox, glymur.jp2box.FragmentTableBox)) - def test_dref(self): - """Should be able to instantiate a data reference box""" - dref = glymur.jp2box.DataReferenceBox() - - # Test the representation instantiation. - newbox = eval(repr(dref)) - self.assertTrue(isinstance(newbox, glymur.jp2box.DataReferenceBox)) - - def test_flst(self): - """Should be able to instantiate a fragment list box""" - flst = glymur.jp2box.FragmentListBox([89], [1132288], [0]) - - # Test the representation instantiation. - newbox = eval(repr(flst)) - self.assertTrue(isinstance(newbox, glymur.jp2box.FragmentListBox)) - self.assertEqual(newbox.fragment_offset, [89]) - self.assertEqual(newbox.fragment_length, [1132288]) - self.assertEqual(newbox.data_reference, [0]) - - def test_default_cgrp(self): - """Should be able to instantiate a color group box""" - cgrp = glymur.jp2box.ColourGroupBox() - - # Test the representation instantiation. - newbox = eval(repr(cgrp)) - self.assertTrue(isinstance(newbox, glymur.jp2box.ColourGroupBox)) - - def test_default_ftyp(self): - """Should be able to instantiate a FileTypeBox""" - ftyp = glymur.jp2box.FileTypeBox() - - # Test the representation instantiation. - newbox = eval(repr(ftyp)) - self.verify_filetype_box(newbox, FileTypeBox()) - - def test_colourspecification_box(self): - """Verify __repr__ method on colr box.""" - # TODO: add icc_profile - box = ColourSpecificationBox(colorspace=glymur.core.SRGB) - - newbox = eval(repr(box)) - self.assertEqual(newbox.method, glymur.core.ENUMERATED_COLORSPACE) - self.assertEqual(newbox.precedence, 0) - self.assertEqual(newbox.approximation, 0) - self.assertEqual(newbox.colorspace, glymur.core.SRGB) - self.assertIsNone(newbox.icc_profile) - - def test_channeldefinition_box(self): - """Verify __repr__ method on cdef box.""" - channel_type = [COLOR, COLOR, COLOR] - association = [RED, GREEN, BLUE] - cdef = glymur.jp2box.ChannelDefinitionBox(index=[0, 1, 2], - channel_type=channel_type, - association=association) - newbox = eval(repr(cdef)) - self.assertEqual(newbox.index, (0, 1, 2)) - self.assertEqual(newbox.channel_type, (COLOR, COLOR, COLOR)) - self.assertEqual(newbox.association, (RED, GREEN, BLUE)) - - def test_jp2header_box(self): - """Verify __repr__ method on ihdr box.""" - ihdr = ImageHeaderBox(100, 200, num_components=3) - colr = ColourSpecificationBox(colorspace=glymur.core.SRGB) - jp2h = JP2HeaderBox(box=[ihdr, colr]) - newbox = eval(repr(jp2h)) - self.assertEqual(newbox.box_id, 'jp2h') - self.assertEqual(newbox.box[0].box_id, 'ihdr') - self.assertEqual(newbox.box[1].box_id, 'colr') - - def test_imageheader_box(self): - """Verify __repr__ method on jhdr box.""" - ihdr = ImageHeaderBox(100, 200, num_components=3) - - newbox = eval(repr(ihdr)) - self.assertEqual(newbox.height, 100) - self.assertEqual(newbox.width, 200) - self.assertEqual(newbox.num_components, 3) - self.assertFalse(newbox.signed) - self.assertEqual(newbox.bits_per_component, 8) - self.assertEqual(newbox.compression, 7) - self.assertFalse(newbox.colorspace_unknown) - self.assertFalse(newbox.ip_provided) - - def test_association_box(self): - """Verify __repr__ method on asoc box.""" - asoc = glymur.jp2box.AssociationBox() - newbox = eval(repr(asoc)) - self.assertEqual(newbox.box_id, 'asoc') - self.assertEqual(len(newbox.box), 0) - - def test_codestreamheader_box(self): - """Verify __repr__ method on jpch box.""" - jpch = glymur.jp2box.CodestreamHeaderBox() - newbox = eval(repr(jpch)) - self.assertEqual(newbox.box_id, 'jpch') - self.assertEqual(len(newbox.box), 0) - - def test_compositinglayerheader_box(self): - """Verify __repr__ method on jplh box.""" - jplh = glymur.jp2box.CompositingLayerHeaderBox() - newbox = eval(repr(jplh)) - self.assertEqual(newbox.box_id, 'jplh') - self.assertEqual(len(newbox.box), 0) - - def test_componentmapping_box(self): - """Verify __repr__ method on cmap box.""" - cmap = glymur.jp2box.ComponentMappingBox(component_index=(0, 0, 0), - mapping_type=(1, 1, 1), - palette_index=(0, 1, 2)) - newbox = eval(repr(cmap)) - self.assertEqual(newbox.box_id, 'cmap') - self.assertEqual(newbox.component_index, (0, 0, 0)) - self.assertEqual(newbox.mapping_type, (1, 1, 1)) - self.assertEqual(newbox.palette_index, (0, 1, 2)) - - def test_resolution_boxes(self): - """Verify __repr__ method on resolution boxes.""" - resc = glymur.jp2box.CaptureResolutionBox(0.5, 2.5) - resd = glymur.jp2box.DisplayResolutionBox(2.5, 0.5) - res_super_box = glymur.jp2box.ResolutionBox(box=[resc, resd]) - - newbox = eval(repr(res_super_box)) - - self.assertEqual(newbox.box_id, 'res ') - self.assertEqual(newbox.box[0].box_id, 'resc') - self.assertEqual(newbox.box[0].vertical_resolution, 0.5) - self.assertEqual(newbox.box[0].horizontal_resolution, 2.5) - self.assertEqual(newbox.box[1].box_id, 'resd') - self.assertEqual(newbox.box[1].vertical_resolution, 2.5) - self.assertEqual(newbox.box[1].horizontal_resolution, 0.5) - - def test_label_box(self): - """Verify __repr__ method on label box.""" - lbl = glymur.jp2box.LabelBox("this is a test") - newbox = eval(repr(lbl)) - self.assertEqual(newbox.box_id, 'lbl ') - self.assertEqual(newbox.label, "this is a test") - - def test_data_entry_url_box(self): - """Verify __repr__ method on data entry url box.""" - version = 0 - flag = (0, 0, 0) - url = "http://readthedocs.glymur.org" - box = glymur.jp2box.DataEntryURLBox(version, flag, url) - newbox = eval(repr(box)) - self.assertEqual(newbox.box_id, 'url ') - self.assertEqual(newbox.version, version) - self.assertEqual(newbox.flag, flag) - self.assertEqual(newbox.url, url) - - def test_uuidinfo_box(self): - """Verify __repr__ method on uinf box.""" - uinf = glymur.jp2box.UUIDInfoBox() - newbox = eval(repr(uinf)) - self.assertEqual(newbox.box_id, 'uinf') - self.assertEqual(len(newbox.box), 0) - - def test_uuidlist_box(self): - """Verify __repr__ method on ulst box.""" - uuid1 = UUID('00000000-0000-0000-0000-000000000001') - uuid2 = UUID('00000000-0000-0000-0000-000000000002') - uuids = [uuid1, uuid2] - ulst = glymur.jp2box.UUIDListBox(ulst=uuids) - newbox = eval(repr(ulst)) - self.assertEqual(newbox.box_id, 'ulst') - self.assertEqual(newbox.ulst[0], uuid1) - self.assertEqual(newbox.ulst[1], uuid2) - - def test_palette_box(self): - """Verify Palette box repr.""" - palette = np.array([[255, 0, 1000], [0, 255, 0]], dtype=np.int32) - bps = (8, 8, 16) - box = glymur.jp2box.PaletteBox(palette=palette, bits_per_component=bps, - signed=(True, False, True)) - - # Test will fail unless addition imports from numpy are done. - from numpy import array, int32 - newbox = eval(repr(box)) - np.testing.assert_array_equal(newbox.palette, palette) - self.assertEqual(newbox.bits_per_component, (8, 8, 16)) - self.assertEqual(newbox.signed, (True, False, True)) - - def test_xml_box(self): - """Verify xml box repr.""" - elt = ET.fromstring('0') - tree = ET.ElementTree(elt) - box = glymur.jp2box.XMLBox(xml=tree) - - regexp = r"""glymur.jp2box.XMLBox""" - regexp += r"""[(]xml=[)]""" - - if sys.hexversion < 0x03000000: - self.assertRegexpMatches(repr(box), regexp) - else: - self.assertRegex(repr(box), regexp) - - def test_readerrequirements_box(self): - """Verify rreq repr method.""" - box = glymur.jp2box.ReaderRequirementsBox(fuam=160, dcm=192, - standard_flag=(5, 61, 43), - standard_mask=(128, 96, 64), - vendor_feature=[], - vendor_mask=[]) - newbox = eval(repr(box)) - self.assertEqual(box.fuam, newbox.fuam) - self.assertEqual(box.dcm, newbox.dcm) - self.assertEqual(box.standard_flag, newbox.standard_flag) - self.assertEqual(box.standard_mask, newbox.standard_mask) - self.assertEqual(box.vendor_feature, newbox.vendor_feature) - self.assertEqual(box.vendor_mask, newbox.vendor_mask) - - def test_uuid_box_generic(self): - """Verify uuid repr method.""" - uuid_instance = UUID('00000000-0000-0000-0000-000000000000') - data = b'0123456789' - box = glymur.jp2box.UUIDBox(the_uuid=uuid_instance, raw_data=data) - - # Since the raw_data parameter is a sequence of bytes which could be - # quite long, don't bother trying to make it conform to eval(repr()). - regexp = r"""glymur.jp2box.UUIDBox\(""" - regexp += """the_uuid=""" - regexp += """UUID\('00000000-0000-0000-0000-000000000000'\),\s""" - regexp += """raw_data=\)""" - - if sys.hexversion < 0x03000000: - self.assertRegexpMatches(repr(box), regexp) - else: - self.assertRegex(repr(box), regexp) - - def test_uuid_box_xmp(self): - """Verify uuid repr method for XMP UUID box.""" - jp2file = glymur.data.nemo() - j = Jp2k(jp2file) - box = j.box[3] - - # Since the raw_data parameter is a sequence of bytes which could be - # quite long, don't bother trying to make it conform to eval(repr()). - regexp = r"""glymur.jp2box.UUIDBox\(""" - regexp += """the_uuid=""" - regexp += """UUID\('be7acfcb-97a9-42e8-9c71-999491e3afac'\),\s""" - regexp += """raw_data=\)""" - - if sys.hexversion < 0x03000000: - self.assertRegexpMatches(repr(box), regexp) - else: - self.assertRegex(repr(box), regexp) - - def test_contiguous_codestream_box(self): - """Verify contiguous codestream box repr method.""" - jp2file = glymur.data.nemo() - jp2 = Jp2k(jp2file) - box = jp2.box[-1] - - # Difficult to eval(repr()) this, so just match the general pattern. - regexp = "glymur.jp2box.ContiguousCodeStreamBox" - regexp += "[(]codestream= - - - 1 - 2008 - 141100 - - - - """ - with tempfile.NamedTemporaryFile(suffix=".xml", delete=False) as tfile: - tfile.write(raw_xml) - tfile.flush() - self.xmlfile = tfile.name - - def tearDown(self): - os.unlink(self.xmlfile) - - def test_jpx_ftbl_no_codestream(self): - """Can have a jpx with no codestream.""" - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile1: - with open(self.jp2file, 'rb') as fptr: - tfile1.write(fptr.read()) - tfile1.flush() - jp2_1 = Jp2k(tfile1.name) - jp2h = jp2_1.box[2] - - jp2c = [box for box in jp2_1.box if box.box_id == 'jp2c'][0] - - # coff and clen will be the offset and length input arguments - # to the fragment list box. dr_idx is the data reference index. - coff = [] - clen = [] - dr_idx = [] - - coff.append(jp2c.main_header_offset) - clen.append(jp2c.length - (coff[0] - jp2c.offset)) - dr_idx.append(1) - - # Make the url box for this codestream. - url1 = DataEntryURLBox(0, [0, 0, 0], 'file://' + tfile1.name) - url1_name_len = len(url1.url) + 1 - - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile2: - - j2k = Jp2k(self.j2kfile) - jp2_2 = j2k.wrap(tfile2.name) - - jp2c = [box for box in jp2_2.box if box.box_id == 'jp2c'][0] - coff.append(jp2c.main_header_offset) - clen.append(jp2c.length - (coff[0] - jp2c.offset)) - dr_idx.append(2) - - # Make the url box for this codestream. - url2 = DataEntryURLBox(0, [0, 0, 0], 'file://' + tfile2.name) - - boxes = [JPEG2000SignatureBox(), - FileTypeBox(brand='jpx ', - compatibility_list=['jpx ', - 'jp2 ', 'jpxb']), - jp2h] - with tempfile.NamedTemporaryFile(suffix='.jpx') as tjpx: - for box in boxes: - box.write(tjpx) - - flst = FragmentListBox(coff, clen, dr_idx) - ftbl = FragmentTableBox([flst]) - ftbl.write(tjpx) - - boxes = [url1, url2] - dtbl = DataReferenceBox(data_entry_url_boxes=boxes) - dtbl.write(tjpx) - tjpx.flush() - - jpx_no_jp2c = Jp2k(tjpx.name) - jpx_boxes = [box.box_id for box in jpx_no_jp2c.box] - self.assertEqual(jpx_boxes, ['jP ', 'ftyp', 'jp2h', - 'ftbl', 'dtbl']) - self.assertEqual(jpx_no_jp2c.box[4].DR[0].offset, 141) - - offset = 141 + 8 + 4 + url1_name_len - self.assertEqual(jpx_no_jp2c.box[4].DR[1].offset, offset) - - def test_jp2_with_jpx_box(self): - """If the brand is jp2, then no jpx boxes are allowed.""" - jp2 = Jp2k(self.jp2file) - boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] - boxes = jp2.box - - boxes.append(glymur.jp2box.AssociationBox()) - - with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: - with self.assertRaises(IOError): - jp2.wrap(tfile.name, boxes=boxes) - - def test_jpch_jplh(self): - """Write a codestream header, compositing layer header box.""" - jp2 = Jp2k(self.jp2file) - boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] - - # The ftyp box must be modified to jpx. - boxes[1].brand = 'jpx ' - boxes[1].compatibility_list = ['jp2 ', 'jpxb'] - - jpch = glymur.jp2box.CodestreamHeaderBox() - boxes.append(jpch) - jplh = glymur.jp2box.CompositingLayerHeaderBox() - boxes.append(jplh) - - with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: - jpx = jp2.wrap(tfile.name, boxes=boxes) - - self.assertEqual(jpx.box[-2].box_id, 'jpch') - self.assertEqual(jpx.box[-1].box_id, 'jplh') - - def test_cgrp(self): - """Write a color group box.""" - jp2 = Jp2k(self.jp2file) - boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] - - # The ftyp box must be modified to jpx. - boxes[1].brand = 'jpx ' - boxes[1].compatibility_list = ['jp2 ', 'jpxb'] - - colr_rgb = ColourSpecificationBox(colorspace=glymur.core.SRGB) - colr_gr = ColourSpecificationBox(colorspace=glymur.core.GREYSCALE) - box = [colr_rgb, colr_gr] - - cgrp = glymur.jp2box.ColourGroupBox(box=box) - boxes.append(cgrp) - - with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: - jpx = jp2.wrap(tfile.name, boxes=boxes) - - self.assertEqual(jpx.box[-1].box_id, 'cgrp') - self.assertEqual(jpx.box[-1].box[0].box_id, 'colr') - self.assertEqual(jpx.box[-1].box[1].box_id, 'colr') - - def test_label_neg(self): - """Can't write a label box embedded in any old box.""" - jp2 = Jp2k(self.jp2file) - boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] - - # The ftyp box must be modified to jpx. - boxes[1].brand = 'jpx ' - boxes[1].compatibility_list = ['jp2 ', 'jpxb'] - - lblb = glymur.jp2box.LabelBox("Just a test") - box = [lblb] - - cgrp = glymur.jp2box.ColourGroupBox(box=box) - boxes.append(cgrp) - - with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: - with self.assertRaises(IOError): - jp2.wrap(tfile.name, boxes=boxes) - - def test_cgrp_neg(self): - """Can't write a cgrp with anything but colr sub boxes""" - jp2 = Jp2k(self.jp2file) - boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] - - # The ftyp box must be modified to jpx. - boxes[1].brand = 'jpx ' - boxes[1].compatibility_list = ['jp2 ', 'jpxb'] - - the_xml = ET.fromstring('0') - xmlb = glymur.jp2box.XMLBox(xml=the_xml) - box = [xmlb] - - cgrp = glymur.jp2box.ColourGroupBox(box=box) - boxes.append(cgrp) - - with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: - with self.assertRaises(IOError): - jp2.wrap(tfile.name, boxes=boxes) - - def test_ftbl(self): - """Write a fragment table box.""" - # Add a negative test where offset < 0 - # Add a negative test where length < 0 - # Add a negative test where ref > 0 but no data reference box. - # Add a negative test where more than one flst - # Add negative test where ftbl contained in a superbox. - jp2 = Jp2k(self.jp2file) - boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] - - # The ftyp box must be modified to jpx. - boxes[1].brand = 'jpx ' - boxes[1].compatibility_list = ['jp2 ', 'jpxb'] - - offset = [89] - length = [1132288] - reference = [0] - flst = glymur.jp2box.FragmentListBox(offset, length, reference) - ftbl = glymur.jp2box.FragmentTableBox(box=[flst]) - boxes.append(ftbl) - - with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: - jpx = jp2.wrap(tfile.name, boxes=boxes) - - self.assertEqual(jpx.box[1].compatibility_list, ['jp2 ', 'jpxb']) - self.assertEqual(jpx.box[-1].box_id, 'ftbl') - self.assertEqual(jpx.box[-1].box[0].box_id, 'flst') - - def test_jpxb_compatibility(self): - """Wrap JP2 to JPX, state jpxb compatibility""" - jp2 = Jp2k(self.jp2file) - boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] - - # The ftyp box must be modified to jpx with jp2 compatibility. - boxes[1].brand = 'jpx ' - boxes[1].compatibility_list = ['jp2 ', 'jpxb'] - - numbers = (0, 1) - nlst = glymur.jp2box.NumberListBox(numbers) - the_xml = ET.fromstring('0') - xmlb = glymur.jp2box.XMLBox(xml=the_xml) - asoc = glymur.jp2box.AssociationBox([nlst, xmlb]) - boxes.append(asoc) - - with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: - jpx = jp2.wrap(tfile.name, boxes=boxes) - - self.assertEqual(jpx.box[1].compatibility_list, ['jp2 ', 'jpxb']) - self.assertEqual(jpx.box[-1].box_id, 'asoc') - self.assertEqual(jpx.box[-1].box[0].box_id, 'nlst') - self.assertEqual(jpx.box[-1].box[1].box_id, 'xml ') - self.assertEqual(jpx.box[-1].box[0].associations, numbers) - self.assertEqual(ET.tostring(jpx.box[-1].box[1].xml.getroot()), - b'0') - - def test_association_label_box(self): - """Wrap JP2 to JPX with asoc, label, and nlst boxes""" - jp2 = Jp2k(self.jp2file) - boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] - - # The ftyp box must be modified to jpx with jp2 compatibility. - boxes[1].brand = 'jpx ' - boxes[1].compatibility_list = ['jp2 ', 'jpx '] - - label = 'this is a test' - lblb = glymur.jp2box.LabelBox(label) - numbers = (0, 1) - nlst = glymur.jp2box.NumberListBox(numbers) - the_xml = ET.fromstring('0') - xmlb = glymur.jp2box.XMLBox(xml=the_xml) - asoc = glymur.jp2box.AssociationBox([nlst, xmlb, lblb]) - boxes.append(asoc) - - with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: - jpx = jp2.wrap(tfile.name, boxes=boxes) - - self.assertEqual(jpx.box[1].compatibility_list, ['jp2 ', 'jpx ']) - self.assertEqual(jpx.box[-1].box_id, 'asoc') - self.assertEqual(jpx.box[-1].box[0].box_id, 'nlst') - self.assertEqual(jpx.box[-1].box[0].associations, numbers) - self.assertEqual(jpx.box[-1].box[1].box_id, 'xml ') - self.assertEqual(ET.tostring(jpx.box[-1].box[1].xml.getroot()), - b'0') - self.assertEqual(jpx.box[-1].box[2].box_id, 'lbl ') - self.assertEqual(jpx.box[-1].box[2].label, label) - - def test_empty_data_reference(self): - """Empty data reference boxes can be created, but not written.""" - jp2 = Jp2k(self.jp2file) - boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] - - boxes[1].brand = 'jpx ' - - dref = glymur.jp2box.DataReferenceBox() - boxes.append(dref) - - with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: - with self.assertRaises(IOError): - jp2.wrap(tfile.name, boxes=boxes) - - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) - def test_deurl_child_of_dtbl(self): - """Data reference boxes can only contain data entry url boxes.""" - jp2 = Jp2k(self.jp2file) - boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] - - ftyp = glymur.jp2box.FileTypeBox() - with self.assertWarns(UserWarning): - dref = glymur.jp2box.DataReferenceBox([ftyp]) - - # Try to get around it by appending the ftyp box after creation. - dref = glymur.jp2box.DataReferenceBox() - dref.DR.append(ftyp) - - boxes.append(dref) - - with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: - with self.assertRaises(IOError): - jp2.wrap(tfile.name, boxes=boxes) - - def test_only_one_data_reference(self): - """Data reference boxes cannot be inside a superbox .""" - jp2 = Jp2k(self.jp2file) - boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] - - # Have to make the ftyp brand jpx. - boxes[1].brand = 'jpx ' - - flag = 0 - version = (0, 0, 0) - url = 'file:////usr/local/bin' - deurl = glymur.jp2box.DataEntryURLBox(flag, version, url) - dref = glymur.jp2box.DataReferenceBox([deurl]) - boxes.append(dref) - boxes.append(dref) - - with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: - with self.assertRaises(IOError): - jp2.wrap(tfile.name, boxes=boxes) - - def test_lbl_at_top_level(self): - """Label boxes can only be inside a asoc box .""" - jp2 = Jp2k(self.jp2file) - boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] - - # Have to make the ftyp brand jpx. - boxes[1].brand = 'jpx ' - - lblb = glymur.jp2box.LabelBox('hi there') - - # Put it inside the jp2 header box. - boxes[2].box.append(lblb) - - with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: - with self.assertRaises(IOError): - jp2.wrap(tfile.name, boxes=boxes) - - def test_data_reference_in_subbox(self): - """Data reference boxes cannot be inside a superbox .""" - jp2 = Jp2k(self.jp2file) - boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] - - # Have to make the ftyp brand jpx. - boxes[1].brand = 'jpx ' - - flag = 0 - version = (0, 0, 0) - url = 'file:////usr/local/bin' - deurl = glymur.jp2box.DataEntryURLBox(flag, version, url) - dref = glymur.jp2box.DataReferenceBox([deurl]) - - # Put it inside the jp2 header box. - boxes[2].box.append(dref) - - with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: - with self.assertRaises(IOError): - jp2.wrap(tfile.name, boxes=boxes) - - def test_jp2_to_jpx_sans_jp2_compatibility(self): - """jp2 wrapped to jpx not including jp2 compatibility is wrong.""" - jp2 = Jp2k(self.jp2file) - boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] - - # Have to make the ftyp brand jpx. - boxes[1].brand = 'jpx ' - boxes[1].compatibility_list.append('jp2 ') - - numbers = [0, 1] - nlst = glymur.jp2box.NumberListBox(numbers) - the_xml = ET.fromstring('0') - xmlb = glymur.jp2box.XMLBox(xml=the_xml) - asoc = glymur.jp2box.AssociationBox([nlst, xmlb]) - boxes.append(asoc) - - with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: - with self.assertRaises(RuntimeError): - jp2.wrap(tfile.name, boxes=boxes) - - def test_jp2_to_jpx_sans_jpx_brand(self): - """Verify error when jp2 wrapped to jpx does not include jpx brand.""" - jp2 = Jp2k(self.jp2file) - boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]] - boxes[1].brand = 'jpx ' - numbers = [0, 1] - nlst = glymur.jp2box.NumberListBox(numbers) - the_xml = ET.fromstring('0') - xmlb = glymur.jp2box.XMLBox(xml=the_xml) - asoc = glymur.jp2box.AssociationBox([nlst, xmlb]) - boxes.append(asoc) - - with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: - with self.assertRaises(RuntimeError): - jp2.wrap(tfile.name, boxes=boxes) - - -@unittest.skipIf(os.name == "nt", "Temporary file issue on window.") -class TestJPX(unittest.TestCase): - """Test suite for other JPX boxes.""" +class TestReaderRequirements(unittest.TestCase): + """Test suite for XML boxes.""" def setUp(self): self.jp2file = glymur.data.nemo() - self.jpxfile = glymur.data.jpxfile() + pass def tearDown(self): pass - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) - def test_flst_lens_not_the_same(self): - """A fragment list box items must be the same length.""" - offset = [89] - length = [1132288] - reference = [0, 0] - with self.assertWarns(UserWarning): - flst = glymur.jp2box.FragmentListBox(offset, length, reference) - with self.assertRaises(IOError): - with tempfile.TemporaryFile() as tfile: - flst.write(tfile) + def test_mask_length_is_3(self): + """The standard says that the mask length should be 1, 2, 4, or 8.""" + # Rewrite nemo to include this kind of rreq box. + with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile: + with open(self.jp2file, 'rb') as nemof: + # Read the jP and ftyp boxes as-is. + write_buffer = nemof.read(32) + tfile.write(write_buffer) - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) - def test_flst_offsets_not_positive(self): - """A fragment list box offsets must be positive.""" - offset = [0] - length = [1132288] - reference = [0] - with self.assertWarns(UserWarning): - flst = glymur.jp2box.FragmentListBox(offset, length, reference) - with self.assertRaises((IOError, OSError)): - with tempfile.TemporaryFile() as tfile: - flst.write(tfile) + # Fake a rreq box with ML = 3. + write_buffer = struct.pack('>I4sB', 74, b'rreq', 3) + tfile.write(write_buffer) - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) - def test_flst_lengths_not_positive(self): - """A fragment list box lengths must be positive.""" - offset = [89] - length = [0] - reference = [0] - with self.assertWarns(UserWarning): - flst = glymur.jp2box.FragmentListBox(offset, length, reference) - with self.assertRaises(IOError): - with tempfile.TemporaryFile() as tfile: - flst.write(tfile) + # pad the rest with zeros + write_buffer = struct.pack('>65s', b'\x00' * 65) + tfile.write(write_buffer) - def test_ftbl_boxes_empty(self): - """A fragment table box must have at least one child box.""" - ftbl = glymur.jp2box.FragmentTableBox() - with self.assertRaises(IOError): - with tempfile.TemporaryFile() as tfile: - ftbl.write(tfile) + # Write the rest of nemo. + tfile.write(nemof.read()) + tfile.flush() - def test_ftbl_child_not_flst(self): - """A fragment table box can only contain a fragment list.""" - free = glymur.jp2box.FreeBox() - ftbl = glymur.jp2box.FragmentTableBox(box=[free]) - with self.assertRaises(IOError): - with tempfile.TemporaryFile() as tfile: - ftbl.write(tfile) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + j = Jp2k(tfile.name) + self.assertEqual(len(w), 1) + self.assertEqual(j.box[2].box_id, 'rreq') + self.assertEqual(type(j.box[2]), + glymur.jp2box.ReaderRequirementsBox) - def test_data_reference_requires_dtbl(self): - """The existance of data reference box requires a ftbl box as well.""" - flag = 0 - version = (0, 0, 0) - url1 = 'file:////usr/local/bin' - url2 = 'http://glymur.readthedocs.org' - jpx1 = glymur.Jp2k(self.jp2file) - boxes = jpx1.box - boxes[1].brand = 'jpx ' - - deurl1 = glymur.jp2box.DataEntryURLBox(flag, version, url1) - deurl2 = glymur.jp2box.DataEntryURLBox(flag, version, url2) - dref = glymur.jp2box.DataReferenceBox([deurl1, deurl2]) - boxes.append(dref) - - with tempfile.NamedTemporaryFile(suffix='.jpx') as tfile: - with self.assertRaises(IOError): - jpx1.wrap(tfile.name, boxes=boxes) - - def test_dtbl(self): - """Verify that we can interpret Data Reference boxes.""" - # Copy the existing JPX file, add a data reference box onto the end. - flag = 0 - version = (0, 0, 0) - url1 = 'file:////usr/local/bin' - url2 = 'http://glymur.readthedocs.org' + chr(0) * 3 - with tempfile.NamedTemporaryFile(suffix='.jpx') as tfile: - with open(self.jpxfile, 'rb') as ifile: - tfile.write(ifile.read()) - - deurl1 = glymur.jp2box.DataEntryURLBox(flag, version, url1) - deurl2 = glymur.jp2box.DataEntryURLBox(flag, version, url2) - dref = glymur.jp2box.DataReferenceBox([deurl1, deurl2]) - dref.write(tfile) - - tfile.flush() - - jpx = Jp2k(tfile.name) - - self.assertEqual(jpx.box[-1].box_id, 'dtbl') - self.assertEqual(len(jpx.box[-1].DR), 2) - self.assertEqual(jpx.box[-1].DR[0].url, url1) - self.assertEqual(jpx.box[-1].DR[1].url, url2.rstrip('\0')) - - def test_ftbl(self): - """Verify that we can interpret Fragment Table boxes.""" - # Copy the existing JPX file, add a fragment table box onto the end. - with tempfile.NamedTemporaryFile(suffix='.jpx') as tfile: - with open(self.jpxfile, 'rb') as ifile: - tfile.write(ifile.read()) - write_buffer = struct.pack('>I4s', 32, b'ftbl') - tfile.write(write_buffer) - - # Just one fragment list box - write_buffer = struct.pack('>I4s', 24, b'flst') - tfile.write(write_buffer) - - # Simple offset, length, reference - write_buffer = struct.pack('>HQIH', 1, 4237, 170246, 3) - tfile.write(write_buffer) - - tfile.flush() - - jpx = Jp2k(tfile.name) - - self.assertEqual(jpx.box[-1].box_id, 'ftbl') - self.assertEqual(jpx.box[-1].box[0].box_id, 'flst') - self.assertEqual(jpx.box[-1].box[0].fragment_offset, (4237,)) - self.assertEqual(jpx.box[-1].box[0].fragment_length, (170246,)) - self.assertEqual(jpx.box[-1].box[0].data_reference, (3,)) - - def test_rreq3(self): - """Verify that we can read a rreq box with mask length 3 bytes""" - rreq_buffer = ctypes.create_string_buffer(74) - struct.pack_into('>I4s', rreq_buffer, 0, 74, b'rreq') - - # mask length - struct.pack_into('>B', rreq_buffer, 8, 3) - - # fuam, dcm. 6 bytes, two sets of 3. - lst = (255, 224, 0, 0, 31, 252) - struct.pack_into('>BBBBBB', rreq_buffer, 9, *lst) - - # number of standard features: 11 - struct.pack_into('>H', rreq_buffer, 15, 11) - - standard_flags = [5, 42, 45, 2, 18, 19, 1, 8, 12, 31, 20] - standard_masks = [8388608, 4194304, 2097152, 1048576, 524288, 262144, - 131072, 65536, 32768, 16384, 8192] - for j in range(len(standard_flags)): - mask = (standard_masks[j] >> 16, - standard_masks[j] & 0x0000ffff >> 8, - standard_masks[j] & 0x000000ff) - struct.pack_into('>HBBB', rreq_buffer, 17 + j * 5, - standard_flags[j], *mask) - - # num vendor features: 0 - struct.pack_into('>H', rreq_buffer, 72, 0) - - # Ok, done with the box, we can now insert it into the jpx file after - # the ftyp box. - with tempfile.NamedTemporaryFile(suffix=".jpx") as ofile: - with open(self.jpxfile, 'rb') as ifile: - ofile.write(ifile.read(40)) - ofile.write(rreq_buffer) - ofile.write(ifile.read()) - ofile.flush() - - jpx = Jp2k(ofile.name) - - self.assertEqual(jpx.box[2].box_id, 'rreq') - self.assertEqual(type(jpx.box[2]), - glymur.jp2box.ReaderRequirementsBox) - self.assertEqual(jpx.box[2].standard_flag, - (5, 42, 45, 2, 18, 19, 1, 8, 12, 31, 20)) - - def test_nlst(self): - """Verify that we can handle a number list box.""" - j = Jp2k(self.jpxfile) - nlst = j.box[12].box[0].box[0] - self.assertEqual(nlst.box_id, 'nlst') - self.assertEqual(type(nlst), glymur.jp2box.NumberListBox) - - # Two associations. - self.assertEqual(len(nlst.associations), 2) - - # Codestream 0 - self.assertEqual(nlst.associations[0], 1 << 24) - - # Compositing Layer 0 - self.assertEqual(nlst.associations[1], 2 << 24) diff --git a/glymur/test/test_jp2box_uuid.py b/glymur/test/test_jp2box_uuid.py deleted file mode 100644 index 6886c30..0000000 --- a/glymur/test/test_jp2box_uuid.py +++ /dev/null @@ -1,172 +0,0 @@ -# -*- coding: utf-8 -*- -"""Test suite for printing. -""" -import os -import shutil -import struct -import sys -import tempfile -import uuid - -if sys.hexversion < 0x02070000: - import unittest2 as unittest -else: - import unittest - -import lxml.etree - -from .fixtures import (WARNING_INFRASTRUCTURE_ISSUE, - WARNING_INFRASTRUCTURE_MSG, - WINDOWS_TMP_FILE_MSG) - -import glymur -from glymur import Jp2k -from .fixtures import SimpleRDF - - -@unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) -class TestSuite(unittest.TestCase): - """Tests for XMP, Exif UUIDs.""" - - def setUp(self): - self.jp2file = glymur.data.nemo() - - def tearDown(self): - pass - - def test_append_xmp_uuid(self): - """Should be able to append an XMP UUID box.""" - the_uuid = uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac') - raw_data = SimpleRDF.encode('utf-8') - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - shutil.copyfile(self.jp2file, tfile.name) - jp2 = Jp2k(tfile.name) - ubox = glymur.jp2box.UUIDBox(the_uuid=the_uuid, raw_data=raw_data) - jp2.append(ubox) - - # Should be two UUID boxes now. - expected_ids = ['jP ', 'ftyp', 'jp2h', 'uuid', 'jp2c', 'uuid'] - actual_ids = [b.box_id for b in jp2.box] - self.assertEqual(actual_ids, expected_ids) - - # The data should be an XMP packet, which gets interpreted as - # an ElementTree. - self.assertTrue(isinstance(jp2.box[-1].data, - lxml.etree._ElementTree)) - - def test_big_endian_exif(self): - """Verify read of Exif big-endian IFD.""" - with tempfile.NamedTemporaryFile(suffix='.jp2', mode='wb') as tfile: - - with open(self.jp2file, 'rb') as ifptr: - tfile.write(ifptr.read()) - - # Write L, T, UUID identifier. - tfile.write(struct.pack('>I4s', 52, b'uuid')) - tfile.write(b'JpgTiffExif->JP2') - - tfile.write(b'Exif\x00\x00') - xbuffer = struct.pack('>BBHI', 77, 77, 42, 8) - tfile.write(xbuffer) - - # We will write just a single tag. - tfile.write(struct.pack('>H', 1)) - - # The "Make" tag is tag no. 271. - tfile.write(struct.pack('>HHI4s', 271, 2, 3, b'HTC\x00')) - tfile.flush() - - jp2 = glymur.Jp2k(tfile.name) - self.assertEqual(jp2.box[-1].data['Make'], "HTC") - - -@unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) -@unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) -class TestSuiteWarns(unittest.TestCase): - """Tests for XMP, Exif UUIDs, issues warnings.""" - - def setUp(self): - self.jp2file = glymur.data.nemo() - - def tearDown(self): - pass - - def test_unrecognized_exif_tag(self): - """Verify warning in case of unrecognized tag.""" - with tempfile.NamedTemporaryFile(suffix='.jp2', mode='wb') as tfile: - - with open(self.jp2file, 'rb') as ifptr: - tfile.write(ifptr.read()) - - # Write L, T, UUID identifier. - tfile.write(struct.pack('>I4s', 52, b'uuid')) - tfile.write(b'JpgTiffExif->JP2') - - tfile.write(b'Exif\x00\x00') - xbuffer = struct.pack('I4s', 52, b'uuid')) - tfile.write(b'JpgTiffExif->JP2') - - tfile.write(b'Exif\x00\x00') - xbuffer = struct.pack('I4s', 52, b'uuid')) - tfile.write(b'JpgTiffExif->JP2') - - tfile.write(b'Exif\x00\x00') - xbuffer = struct.pack('Россия') + +@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") class TestJp2kBadXmlFile(unittest.TestCase): """Test suite for bad XML box situations""" @@ -185,11 +213,12 @@ class TestJp2kBadXmlFile(unittest.TestCase): def tearDown(self): pass - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_invalid_xml_box(self): """Should be able to recover info from xml box with bad xml.""" - with self.assertWarns(UserWarning): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") jp2k = Jp2k(self._bad_xml_file) + self.assertEqual(len(w), 1) self.assertEqual(jp2k.box[3].box_id, 'xml ') self.assertEqual(jp2k.box[3].offset, 77) @@ -197,7 +226,7 @@ class TestJp2kBadXmlFile(unittest.TestCase): self.assertIsNone(jp2k.box[3].xml) -@unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) +@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") class TestBadButRecoverableXmlFile(unittest.TestCase): """Test suite for XML box that is bad, but we can still recover the XML.""" @@ -238,17 +267,19 @@ class TestBadButRecoverableXmlFile(unittest.TestCase): def tearDownClass(cls): os.unlink(cls._bad_xml_file) - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) + @unittest.skipIf(sys.hexversion < 0x03020000, + "Uses features introduced in 3.2.") def test_bad_xml_box_warning(self): """Should warn in case of bad XML""" - regex = 'A UnicodeDecodeError was encountered parsing an XML box' - with self.assertWarnsRegex(UserWarning, regex): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") Jp2k(self._bad_xml_file) + self.assertEqual(len(w), 1) - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_recover_from_bad_xml(self): """Should be able to recover info from xml box with bad xml.""" - with self.assertWarns(UserWarning): + with warnings.catch_warnings(): + warnings.simplefilter("ignore") jp2 = Jp2k(self._bad_xml_file) self.assertEqual(jp2.box[3].box_id, 'xml ') @@ -258,31 +289,3 @@ class TestBadButRecoverableXmlFile(unittest.TestCase): b'this is a test') -@unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") -class TestXML_OpjDataRoot(unittest.TestCase): - """Test suite for XML boxes, requires OPJ_DATA_ROOT.""" - - def test_bom(self): - """Byte order markers are illegal in UTF-8. Issue 185""" - filename = opj_data_file(os.path.join('input', - 'nonregression', - 'issue171.jp2')) - msg = 'An illegal BOM \(byte order marker\) was detected and removed ' - msg += 'from the XML contents in the box starting at byte offset \d+' - with self.assertWarnsRegex(UserWarning, re.compile(msg)): - jp2 = Jp2k(filename) - - self.assertIsNotNone(jp2.box[3].xml) - - def test_invalid_utf8(self): - """Bad byte sequence that cannot be parsed.""" - relname = '26ccf3651020967f7778238ef5af08af.SIGFPE.d25.527.jp2' - filename = opj_data_file(os.path.join('input', - 'nonregression', - relname)) - with self.assertWarns((UserWarning, UserWarning)): - jp2 = Jp2k(filename) - - self.assertIsNone(jp2.box[3].box[1].box[1].xml) diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 52945f9..3130d0a 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -1,373 +1,82 @@ """ Tests for general glymur functionality. """ +# R0904: Not too many methods in unittest. +# pylint: disable=R0904 + +# E0611: unittest.mock is unknown to python2.7/pylint +# pylint: disable=E0611,F0401 + import doctest import os import re +import shutil import struct import sys import tempfile -import unittest import uuid -import warnings from xml.etree import cElementTree as ET -if sys.hexversion <= 0x03030000: - from mock import patch +if sys.hexversion < 0x02070000: + import unittest2 as unittest else: - from unittest.mock import patch + import unittest + +import warnings import numpy as np import pkg_resources import glymur from glymur import Jp2k -from glymur.version import openjpeg_version - -from .fixtures import HAS_PYTHON_XMP_TOOLKIT -from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG -from .fixtures import OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG - -if HAS_PYTHON_XMP_TOOLKIT: - import libxmp - from libxmp import XMPMeta +from .fixtures import OPENJP2_IS_V2_OFFICIAL from .fixtures import OPJ_DATA_ROOT, opj_data_file -from . import fixtures - - -def docTearDown(doctest_obj): - glymur.set_parseoptions(full_codestream=False) # Doc tests should be run as well. def load_tests(loader, tests, ignore): + # W0613: "loader" and "ignore" are necessary for the protocol + # They are unused here, however. + # pylint: disable=W0613 + """Should run doc tests as well""" if os.name == "nt": # Can't do it on windows, temporary file issue. return tests + if sys.hexversion < 0x02070000: + # Don't bother with doctests on 2.6 for the time being. + return tests if glymur.lib.openjp2.OPENJP2 is not None: - tests.addTests(doctest.DocTestSuite('glymur.jp2k', - tearDown=docTearDown)) + tests.addTests(doctest.DocTestSuite('glymur.jp2k')) return tests -class SliceProtocolBase(unittest.TestCase): - """ - Test slice protocol, i.e. when using [ ] to read image data. - """ - @classmethod - def setUpClass(self): - - self.jp2 = Jp2k(glymur.data.nemo()) - self.jp2_data = self.jp2[:] - self.jp2_data_r1 = self.jp2[::2, ::2] - - self.j2k = Jp2k(glymur.data.goodstuff()) - self.j2k_data = self.j2k[:] - - self.j2k_data_r1 = self.j2k[::2, ::2] - self.j2k_data_r5 = self.j2k[::32, ::32] - - -@unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) -@unittest.skipIf(re.match("1.5|2", glymur.version.openjpeg_version) is None, - "Must have openjpeg 1.5 or higher to run") -@unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) -class TestSliceProtocolBaseWrite(SliceProtocolBase): - - def test_write_ellipsis(self): - expected = self.j2k_data - - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, shape=expected.shape) - j[...] = expected - actual = j[:] - - np.testing.assert_array_equal(actual, expected) - - def test_basic_write(self): - expected = self.j2k_data - - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, data=self.j2k_data) - actual = j[:] - - np.testing.assert_array_equal(actual, expected) - - def test_cannot_write_with_non_default_single_slice(self): - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, shape=self.j2k_data.shape) - with self.assertRaises(TypeError): - j[slice(None, 0)] = self.j2k_data - with self.assertRaises(TypeError): - j[slice(0, None)] = self.j2k_data - with self.assertRaises(TypeError): - j[slice(0, 0, None)] = self.j2k_data - with self.assertRaises(TypeError): - j[slice(0, 640)] = self.j2k_data - - def test_cannot_write_a_row(self): - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, shape=self.j2k_data.shape) - with self.assertRaises(TypeError): - j[5] = self.j2k_data - - def test_cannot_write_a_pixel(self): - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, shape=self.j2k_data.shape) - with self.assertRaises(TypeError): - j[25, 35] = self.j2k_data[25, 35] - - def test_cannot_write_a_column(self): - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, shape=self.j2k_data.shape) - with self.assertRaises(TypeError): - j[:, 25, :] = self.j2k_data[:, :25, :] - - def test_cannot_write_a_band(self): - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, shape=self.j2k_data.shape) - with self.assertRaises(TypeError): - j[:, :, 0] = self.j2k_data[:, :, 0] - - def test_cannot_write_a_subarray(self): - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, shape=self.j2k_data.shape) - with self.assertRaises(TypeError): - j[:25, :45, :] = self.j2k_data[:25, :25, :] - - -@unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) -class TestSliceProtocolRead(SliceProtocolBase): - - def test_resolution_strides_cannot_differ(self): - with self.assertRaises(IndexError): - # Strides in x/y directions cannot differ. - self.j2k[::2, ::3] - - def test_resolution_strides_must_be_powers_of_two(self): - with self.assertRaises(IndexError): - self.j2k[::3, ::3] - - def test_integer_index_in_3d(self): - - for j in [0, 1, 2]: - band = self.j2k[:, :, j] - np.testing.assert_array_equal(self.j2k_data[:, :, j], band) - - def test_slice_in_third_dimension(self): - actual = self.j2k[:, :, 1:3] - expected = self.j2k_data[:, :, 1:3] - np.testing.assert_array_equal(actual, expected) - - def test_reduce_resolution_and_slice_in_third_dimension(self): - actual = self.j2k[::2, ::2, 1:3] - expected = self.j2k_data_r1[:, :, 1:3] - np.testing.assert_array_equal(actual, expected) - - def test_retrieve_single_row(self): - actual = self.jp2[0] - expected = self.jp2_data[0] - np.testing.assert_array_equal(actual, expected) - - def test_retrieve_single_pixel(self): - actual = self.jp2[0, 0] - expected = self.jp2_data[0, 0] - np.testing.assert_array_equal(actual, expected) - - def test_retrieve_single_component(self): - actual = self.jp2[20, 20, 2] - expected = self.jp2_data[20, 20, 2] - np.testing.assert_array_equal(actual, expected) - - def test_ellipsis_full_read(self): - actual = self.j2k[...] - expected = self.j2k_data - np.testing.assert_array_equal(actual, expected) - - def test_ellipsis_band_select(self): - actual = self.j2k[..., 0] - expected = self.j2k_data[..., 0] - np.testing.assert_array_equal(actual, expected) - - def test_ellipsis_row_select(self): - actual = self.j2k[0, ...] - expected = self.j2k_data[0, ...] - np.testing.assert_array_equal(actual, expected) - - def test_two_ellipsis_band_select(self): - actual = self.j2k[..., ..., 1] - expected = self.j2k_data[:, :, 1] - np.testing.assert_array_equal(actual, expected) - - def test_two_ellipsis_row_select(self): - actual = self.j2k[1, ..., ...] - expected = self.j2k_data[1, :, :] - np.testing.assert_array_equal(actual, expected) - - def test_two_ellipsis_and_full_slice(self): - actual = self.j2k[..., ..., :] - expected = self.j2k_data[:] - np.testing.assert_array_equal(actual, expected) - - def test_single_slice(self): - rows = slice(3, 8) - actual = self.j2k[rows] - expected = self.j2k_data[3:8, :, :] - np.testing.assert_array_equal(actual, expected) - - @unittest.skipIf(re.match("0|1", glymur.version.openjpeg_version), - "Must have openjpeg 2 or higher to run") - def test_region_rlevel5(self): - """ - maximim rlevel - - There seems to be a difference between version of openjpeg, as - openjp2 produces an image of size (16, 13, 3) and openjpeg produced - (17, 12, 3). - """ - actual = self.j2k[5:533:32, 27:423:32] - expected = self.j2k_data_r5[1:17, 1:14] - np.testing.assert_array_equal(actual, expected) - - class TestJp2k(unittest.TestCase): - """These tests should be run by just about all configuration.""" + """Test suite for openjpeg software starting at 1.3""" + + # These tests should be run by just about all configuration. def setUp(self): self.jp2file = glymur.data.nemo() self.j2kfile = glymur.data.goodstuff() - self.jpxfile = glymur.data.jpxfile() def tearDown(self): pass - @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) - def test_warn_if_using_read_method(self): - """Should warn if deprecated read method is called""" - with self.assertWarns(DeprecationWarning): - Jp2k(self.jp2file).read() - - def test_shape_jp2(self): - """verify shape attribute for JP2 file - """ - jp2 = Jp2k(self.jp2file) - self.assertEqual(jp2.shape, (1456, 2592, 3)) - - @unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") - def test_shape_greyscale_jp2(self): - """verify shape attribute for greyscale JP2 file - """ - with warnings.catch_warnings(): - # Suppress a warning due to bad compatibility list entry. - warnings.simplefilter("ignore") - jfile = opj_data_file('input/conformance/file4.jp2') - jp2 = Jp2k(jfile) - self.assertEqual(jp2.shape, (512, 768)) - - @unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") - def test_shape_single_channel_j2k(self): - """verify shape attribute for single channel J2K file - """ - jfile = opj_data_file('input/conformance/p0_01.j2k') - jp2 = Jp2k(jfile) - self.assertEqual(jp2.shape, (128, 128)) - - def test_shape_j2k(self): - """verify shape attribute for J2K file - """ - j2k = Jp2k(self.j2kfile) - self.assertEqual(j2k.shape, (800, 480, 3)) - - def test_shape_jpx_jp2(self): - """verify shape attribute for JPX file with JP2 compatibility - """ - jpx = Jp2k(self.jpxfile) - self.assertEqual(jpx.shape, (1024, 1024, 3)) - - @unittest.skipIf(re.match("0|1.[0-4]", glymur.version.openjpeg_version), - "Must have openjpeg 1.5 or higher to run") - @unittest.skipIf(os.name == "nt", "Unexplained failure on windows") - def test_irreversible(self): - """Irreversible""" - j = Jp2k(self.jp2file) - expdata = j[:] - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j2 = Jp2k(tfile.name, data=expdata, irreversible=True) - - codestream = j2.get_codestream() - self.assertEqual(codestream.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - - actdata = j2[:] - self.assertTrue(fixtures.mse(actdata[0], expdata[0]) < 0.38) - - @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) - @unittest.skipIf(re.match('1.[0-4]', openjpeg_version) is not None, - "Not supported on OpenJPEG {0}".format(openjpeg_version)) - @unittest.skipIf(re.match('1.5.(1|2)', openjpeg_version) is not None, - "Mysteriously fails in 1.5.1 and 1.5.2") - def test_no_cxform_pclr_jpx(self): - """Indices for pclr jpxfile if no color transform""" - j = Jp2k(self.jpxfile) - rgb = j[:] - j.ignore_pclr_cmap_cdef = True - idx = j[:] - nr, nc = 1024, 1024 - self.assertEqual(rgb.shape, (nr, nc, 3)) - self.assertEqual(idx.shape, (nr, nc)) - - # Should be able to manually reconstruct the RGB image from the palette - # and indices. - palette = j.box[2].box[2].palette - rgb_from_idx = np.zeros(rgb.shape, dtype=np.uint8) - for r in np.arange(nr): - for c in np.arange(nc): - rgb_from_idx[r, c] = palette[idx[r, c]] - np.testing.assert_array_equal(rgb, rgb_from_idx) - - @unittest.skipIf(os.name == "nt", "Unexplained failure on windows") - def test_repr(self): - """Verify that results of __repr__ are eval-able.""" + def test_rlevel_max(self): + """Verify that rlevel=-1 gets us the lowest resolution image""" j = Jp2k(self.j2kfile) - newjp2 = eval(repr(j)) - - self.assertEqual(newjp2.filename, self.j2kfile) - self.assertEqual(len(newjp2.box), 0) - - @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) - def test_rlevel_max_backwards_compatibility(self): - """ - Verify that rlevel=-1 gets us the lowest resolution image - - This is an old option only available via the read method, not via - array-style slicing. - """ - j = Jp2k(self.j2kfile) - if sys.hexversion < 0x03000000: - with warnings.catch_warnings(): - # Suppress a warning due to deprecated syntax - # Not as easy to verify the warning under python2. - warnings.simplefilter("ignore") - thumbnail1 = j.read(rlevel=-1) - else: - with self.assertWarns(DeprecationWarning): - thumbnail1 = j.read(rlevel=-1) - thumbnail2 = j[::32, ::32] + thumbnail1 = j.read(rlevel=-1) + thumbnail2 = j.read(rlevel=5) np.testing.assert_array_equal(thumbnail1, thumbnail2) self.assertEqual(thumbnail1.shape, (25, 15, 3)) - @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) def test_rlevel_too_high(self): """Should error out appropriately if reduce level too high""" j = Jp2k(self.jp2file) with self.assertRaises(IOError): - j[::64, ::64] + j.read(rlevel=6) def test_not_jpeg2000(self): """Should error out appropriately if not given a JPEG 2000 file.""" @@ -388,7 +97,7 @@ class TestJp2k(unittest.TestCase): jp2k = Jp2k(self.jp2file) # top-level boxes - self.assertEqual(len(jp2k.box), 5) + self.assertEqual(len(jp2k.box), 6) self.assertEqual(jp2k.box[0].box_id, 'jP ') self.assertEqual(jp2k.box[0].offset, 0) @@ -407,11 +116,15 @@ class TestJp2k(unittest.TestCase): self.assertEqual(jp2k.box[3].box_id, 'uuid') self.assertEqual(jp2k.box[3].offset, 77) - self.assertEqual(jp2k.box[3].length, 3146) + self.assertEqual(jp2k.box[3].length, 638) - self.assertEqual(jp2k.box[4].box_id, 'jp2c') - self.assertEqual(jp2k.box[4].offset, 3223) - self.assertEqual(jp2k.box[4].length, 1132296) + self.assertEqual(jp2k.box[4].box_id, 'uuid') + self.assertEqual(jp2k.box[4].offset, 715) + self.assertEqual(jp2k.box[4].length, 2412) + + self.assertEqual(jp2k.box[5].box_id, 'jp2c') + self.assertEqual(jp2k.box[5].offset, 3127) + self.assertEqual(jp2k.box[5].length, 1132296) # jp2h super box self.assertEqual(len(jp2k.box[2].box), 2) @@ -444,7 +157,7 @@ class TestJp2k(unittest.TestCase): jp2k = Jp2k(self.j2kfile) self.assertEqual(len(jp2k.box), 0) - @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") def test_64bit_xl_field(self): """XL field should be supported""" # Verify that boxes with the XL field are properly read. @@ -453,7 +166,7 @@ class TestJp2k(unittest.TestCase): with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: with open(self.jp2file, 'rb') as ifile: # Everything up until the jp2c box. - write_buffer = ifile.read(3223) + write_buffer = ifile.read(3127) tfile.write(write_buffer) # The L field must be 1 in order to signal the presence of the @@ -474,11 +187,11 @@ class TestJp2k(unittest.TestCase): jp2k = Jp2k(tfile.name) - self.assertEqual(jp2k.box[4].box_id, 'jp2c') - self.assertEqual(jp2k.box[4].offset, 3223) - self.assertEqual(jp2k.box[4].length, 1133427 + 8) + self.assertEqual(jp2k.box[5].box_id, 'jp2c') + self.assertEqual(jp2k.box[5].offset, 3127) + self.assertEqual(jp2k.box[5].length, 1133427 + 8) - @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") def test_length_field_is_zero(self): """L=0 (length field in box header) is allowed""" # Verify that boxes with the L field as zero are correctly read. @@ -514,21 +227,32 @@ class TestJp2k(unittest.TestCase): self.assertEqual(new_jp2.box[j].length, baseline_jp2.box[j].length) - @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) def test_basic_jp2(self): """Just a very basic test that reading a JP2 file does not error out. """ j2k = Jp2k(self.jp2file) - j2k[::2, ::2] + j2k.read(rlevel=1) - @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) def test_basic_j2k(self): """This test is only useful when openjp2 is not available and OPJ_DATA_ROOT is not set. We need at least one working J2K test. """ j2k = Jp2k(self.j2kfile) - j2k[:] + j2k.read() + + @unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") + def test_read_differing_subsamples(self): + """should error out with read used on differently subsampled images""" + # Verify that we error out appropriately if we use the read method + # on an image with differing subsamples + # + # Issue 86. + filename = opj_data_file('input/conformance/p0_05.j2k') + j = Jp2k(filename) + with self.assertRaises(RuntimeError): + j.read() def test_empty_box_with_j2k(self): """Verify that the list of boxes in a J2C/J2K file is present, but @@ -537,7 +261,7 @@ class TestJp2k(unittest.TestCase): j = Jp2k(self.j2kfile) self.assertEqual(j.box, []) - @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") def test_uinf_ulst_url_boxes(self): """Verify that we can read UINF, ULST, and URL boxes""" # Verify that we can read UINF, ULST, and URL boxes. I don't have @@ -596,7 +320,7 @@ class TestJp2k(unittest.TestCase): self.assertEqual(jp2k.box[3].box[1].flag, (0, 0, 0)) self.assertEqual(jp2k.box[3].box[1].url, 'abcd') - @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") def test_xml_with_trailing_nulls(self): """ElementTree doesn't like trailing null chars after valid XML text""" with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: @@ -627,48 +351,48 @@ class TestJp2k(unittest.TestCase): self.assertEqual(ET.tostring(jp2k.box[3].xml.getroot()), b'this is a test') - @unittest.skipIf(not HAS_PYTHON_XMP_TOOLKIT, - "Requires Python XMP Toolkit >= 2.0") def test_xmp_attribute(self): """Verify the XMP packet in the shipping example file can be read.""" j = Jp2k(self.jp2file) - - xmp = j.box[3].data + xmp = j.box[4].data ns0 = '{http://www.w3.org/1999/02/22-rdf-syntax-ns#}' ns1 = '{http://ns.adobe.com/xap/1.0/}' - name = '{0}RDF/{0}Description/{1}CreatorTool'.format(ns0, ns1) + name = '{0}RDF/{0}Description'.format(ns0) elt = xmp.find(name) - self.assertEqual(elt.text, 'Google') + attr_value = elt.attrib['{0}CreatorTool'.format(ns1)] + self.assertEqual(attr_value, 'glymur') - xmp = XMPMeta() - xmp.parse_from_str(j.box[3].raw_data.decode('utf-8'), - xmpmeta_wrap=False) - creator_tool = xmp.get_property(libxmp.consts.XMP_NS_XMP, - 'CreatorTool') - self.assertEqual(creator_tool, 'Google') + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") + def test_unrecognized_exif_tag(self): + """An unrecognized exif tag should be handled gracefully.""" + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + shutil.copyfile(self.jp2file, tfile.name) - @unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) - @unittest.skipIf(re.match(r'''(1|2.0.0)''', - glymur.version.openjpeg_version) is not None, - "Not supported until 2.0.1") - def test_jpx_mult_codestreams_jp2_brand(self): - """Read JPX codestream when jp2-compatible.""" - # The file in question has multiple codestreams. - jpx = Jp2k(self.jpxfile) - data = jpx[:] - self.assertEqual(data.shape, (1024, 1024, 3)) + # The Exif UUID starts at byte 77. There are 8 bytes for the L and + # T fields, then 16 bytes for the UUID identifier, then 6 exif + # header bytes, then 8 bytes for the TIFF header, then 2 bytes + # the the Image IFD number of tags, where we finally find the first + # tag, "Make" (271). We'll corrupt it by changing it into 171, + # which does not correspond to any known Exif Image tag. + with open(tfile.name, 'r+b') as fptr: + fptr.seek(117) + write_buffer = struct.pack('= 2x that of the code block size""" - data = np.zeros((640, 480), dtype=np.uint8) - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - with self.assertRaises(IOError): - Jp2k(tfile.name, data=data, - cbsize=(16, 16), psizes=[(16, 16)]) - - def test_precinct_size_not_power_of_two(self): - """must be power of two""" - data = np.zeros((640, 480), dtype=np.uint8) - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - with self.assertRaises(IOError): - Jp2k(tfile.name, data=data, - cbsize=(16, 16), psizes=[(48, 48)]) - - def test_unsupported_int32(self): - """Should raise a runtime error if trying to write int32""" - data = np.zeros((128, 128), dtype=np.int32) - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - with self.assertRaises(RuntimeError): - Jp2k(tfile.name, data=data) - - def test_unsupported_uint32(self): - """Should raise a runtime error if trying to write uint32""" - data = np.zeros((128, 128), dtype=np.uint32) - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - with self.assertRaises(RuntimeError): - Jp2k(tfile.name, data=data) - - def test_write_with_version_too_early(self): - """Should raise a runtime error if trying to write with version 1.3""" - data = np.zeros((128, 128), dtype=np.uint8) - versions = ["1.0.0", "1.1.0", "1.2.0", "1.3.0"] - for version in versions: - with patch('glymur.version.openjpeg_version', new=version): - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - with self.assertRaises(RuntimeError): - Jp2k(tfile.name, data=data) + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") def test_cblkh_different_than_width(self): """Verify that we can set a code block size where height does not equal width. """ data = np.zeros((128, 128), dtype=np.uint8) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + # The code block dimensions are given as rows x columns. - j = Jp2k(tfile.name, data=data, cbsize=(16, 32)) + j.write(data, cbsize=(16, 32)) + codestream = j.get_codestream() # Code block size is reported as XY in the codestream. self.assertEqual(tuple(codestream.segment[2].spcod[5:7]), (3, 2)) + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") def test_too_many_dimensions(self): """OpenJP2 only allows 2D or 3D images.""" with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') with self.assertRaises(IOError): - Jp2k(tfile.name, - data=np.zeros((128, 128, 2, 2), dtype=np.uint8)) + data = np.zeros((128, 128, 2, 2), dtype=np.uint8) + j.write(data) + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") def test_2d_rgb(self): """RGB must have at least 3 components.""" with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + j = Jp2k(tfile.name, 'wb') with self.assertRaises(IOError): - Jp2k(tfile.name, - data=np.zeros((128, 128, 2), dtype=np.uint8), - colorspace='rgb') + data = np.zeros((128, 128, 2), dtype=np.uint8) + j.write(data, colorspace='rgb') + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") def test_colorspace_with_j2k(self): """Specifying a colorspace with J2K does not make sense""" with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') with self.assertRaises(IOError): - Jp2k(tfile.name, - data=np.zeros((128, 128, 3), dtype=np.uint8), - colorspace='rgb') + data = np.zeros((128, 128, 3), dtype=np.uint8) + j.write(data, colorspace='rgb') + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") def test_specify_rgb(self): """specify RGB explicitly""" with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - j = Jp2k(tfile.name, - data=np.zeros((128, 128, 3), dtype=np.uint8), - colorspace='rgb') + j = Jp2k(tfile.name, 'wb') + data = np.zeros((128, 128, 3), dtype=np.uint8) + j.write(data, colorspace='rgb') self.assertEqual(j.box[2].box[1].colorspace, glymur.core.SRGB) + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") def test_specify_gray(self): """test gray explicitly specified (that's GRAY, not GREY)""" with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + j = Jp2k(tfile.name, 'wb') data = np.zeros((128, 128), dtype=np.uint8) - j = Jp2k(tfile.name, data=data, colorspace='gray') + j.write(data, colorspace='gray') self.assertEqual(j.box[2].box[1].colorspace, glymur.core.GREYSCALE) + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") def test_specify_grey(self): """test grey explicitly specified""" with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + j = Jp2k(tfile.name, 'wb') data = np.zeros((128, 128), dtype=np.uint8) - j = Jp2k(tfile.name, data=data, colorspace='grey') + j.write(data, colorspace='grey') self.assertEqual(j.box[2].box[1].colorspace, glymur.core.GREYSCALE) + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") def test_grey_with_two_extra_comps(self): """should be able to write gray + two extra components""" with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + j = Jp2k(tfile.name, 'wb') data = np.zeros((128, 128, 3), dtype=np.uint8) - j = Jp2k(tfile.name, data=data, colorspace='gray') + j.write(data, colorspace='gray') self.assertEqual(j.box[2].box[0].height, 128) self.assertEqual(j.box[2].box[0].width, 128) self.assertEqual(j.box[2].box[0].num_components, 3) @@ -793,53 +493,64 @@ class TestJp2k_write(unittest.TestCase): def test_specify_ycc(self): """Should reject YCC""" with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + j = Jp2k(tfile.name, 'wb') with self.assertRaises(IOError): data = np.zeros((128, 128, 3), dtype=np.uint8) - Jp2k(tfile.name, data=data, colorspace='ycc') + j.write(data, colorspace='ycc') + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") def test_write_with_jp2_in_caps(self): """should be able to write with JP2 suffix.""" j2k = Jp2k(self.j2kfile) - expdata = j2k[:] + expdata = j2k.read() with tempfile.NamedTemporaryFile(suffix='.JP2') as tfile: - ofile = Jp2k(tfile.name, data=expdata) - actdata = ofile[:] + ofile = Jp2k(tfile.name, 'wb') + ofile.write(expdata) + actdata = ofile.read() np.testing.assert_array_equal(actdata, expdata) + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") def test_write_srgb_without_mct(self): """should be able to write RGB without specifying mct""" j2k = Jp2k(self.j2kfile) - expdata = j2k[:] + expdata = j2k.read() with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - ofile = Jp2k(tfile.name, data=expdata, mct=False) - actdata = ofile[:] + ofile = Jp2k(tfile.name, 'wb') + ofile.write(expdata, mct=False) + actdata = ofile.read() np.testing.assert_array_equal(actdata, expdata) codestream = ofile.get_codestream() self.assertEqual(codestream.segment[2].spcod[3], 0) # no mct + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") def test_write_grayscale_with_mct(self): """MCT usage makes no sense for grayscale images.""" j2k = Jp2k(self.j2kfile) - expdata = j2k[:] + expdata = j2k.read() with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + ofile = Jp2k(tfile.name, 'wb') with self.assertRaises(IOError): - Jp2k(tfile.name, data=expdata[:, :, 0], mct=True) + ofile.write(expdata[:, :, 0], mct=True) + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") def test_write_cprl(self): """Must be able to write a CPRL progression order file""" # Issue 17 j = Jp2k(self.jp2file) - expdata = j[::2, ::2] + expdata = j.read(rlevel=1) with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - ofile = Jp2k(tfile.name, data=expdata, prog='CPRL') - actdata = ofile[:] + ofile = Jp2k(tfile.name, 'wb') + ofile.write(expdata, prog='CPRL') + actdata = ofile.read() np.testing.assert_array_equal(actdata, expdata) codestream = ofile.get_codestream() self.assertEqual(codestream.segment[2].spcod[0], glymur.core.CPRL) +@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] >= 2, + "Negative tests only for version 1.x") class TestJp2k_1_x(unittest.TestCase): """Test suite for openjpeg 1.x, not appropriate for 2.x""" @@ -850,38 +561,41 @@ class TestJp2k_1_x(unittest.TestCase): def tearDown(self): pass + def test_area(self): + """Area option not allowed for 1.x. + """ + j2k = Jp2k(self.j2kfile) + with self.assertRaises(TypeError): + j2k.read(area=(0, 0, 100, 100)) + def test_tile(self): """tile option not allowed for 1.x. """ - with patch('glymur.version.openjpeg_version_tuple', new=(1, 5, 0)): - j2k = Jp2k(self.j2kfile) - with warnings.catch_warnings(): - # The tile keyword is deprecated, so suppress the warning. - warnings.simplefilter('ignore') - with self.assertRaises(TypeError): - j2k.read(tile=0) + j2k = Jp2k(self.j2kfile) + with self.assertRaises(TypeError): + j2k.read(tile=0) def test_layer(self): """layer option not allowed for 1.x. """ - with patch('glymur.version.openjpeg_version_tuple', new=(1, 5, 0)): - j2k = Jp2k(self.j2kfile) - with self.assertRaises(RuntimeError): - j2k.layer = 1 + j2k = Jp2k(self.j2kfile) + with self.assertRaises(TypeError): + j2k.read(layer=1) -@unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) -@unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) -class Test_2p0_official(unittest.TestCase): - """Tests specific to v2.0.0""" +@unittest.skipIf(re.match("2.0.0", glymur.version.openjpeg_version) is None, + "Tests only to be run on 2.0.0.") +class TestJp2k_2_0_official(unittest.TestCase): + """Test suite to only be run on v2.0 official.""" + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") def test_extra_components_on_v2(self): """Can only write 4 components on 2.0+, should error out otherwise.""" - with patch('glymur.version.openjpeg_version', new="2.0.0"): - with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - data = np.zeros((128, 128, 4), dtype=np.uint8) - with self.assertRaises(IOError): - Jp2k(tfile.name, data=data) + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + j = Jp2k(tfile.name, 'wb') + data = np.zeros((128, 128, 4), dtype=np.uint8) + with self.assertRaises(IOError): + j.write(data) @unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2, @@ -901,30 +615,32 @@ class TestJp2k_2_0(unittest.TestCase): j = Jp2k(self.jp2file) with self.assertRaises(IOError): # Start corner must be >= 0 - j[-1:1, -1:1] + j.read(area=(-1, -1, 1, 1)) with self.assertRaises(IOError): # End corner must be > 0 - j[10:0, 10:0] + j.read(area=(10, 10, 0, 0)) with self.assertRaises(IOError): # End corner must be >= start corner - j[10:8, 10:8] + j.read(area=(10, 10, 8, 8)) - @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") def test_unrecognized_jp2_clrspace(self): """We only allow RGB and GRAYSCALE. Should error out with others""" with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - data = np.zeros((128, 128, 3), dtype=np.uint8) + j = Jp2k(tfile.name, 'wb') with self.assertRaises(IOError): - Jp2k(tfile.name, data=data, colorspace='cmyk') + data = np.zeros((128, 128, 3), dtype=np.uint8) + j.write(data, colorspace='cmyk') - @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") def test_asoc_label_box(self): """Test asoc and label box""" # Construct a fake file with an asoc and a label box, as # OpenJPEG doesn't have such a file. - data = Jp2k(self.jp2file)[::2, ::2] + data = Jp2k(self.jp2file).read(rlevel=1) with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - Jp2k(tfile.name, data=data) + j = Jp2k(tfile.name, 'wb') + j.write(data) with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile2: @@ -965,10 +681,9 @@ class TestJp2k_2_0(unittest.TestCase): self.assertEqual(jasoc.box[3].box[1].box_id, 'xml ') -@unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) -@unittest.skipIf(re.match(r'''(1|2.0.0)''', - glymur.version.openjpeg_version) is not None, - "Not to be run until unless 2.0.1 or higher is present") +@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2 or + OPENJP2_IS_V2_OFFICIAL, + "Missing openjp2 library version 2.0+.") class TestJp2k_2_1(unittest.TestCase): """Only to be run in 2.0+.""" @@ -979,355 +694,65 @@ class TestJp2k_2_1(unittest.TestCase): def tearDown(self): pass - @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") def test_grey_with_extra_component(self): """version 2.0 cannot write gray + extra""" with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + j = Jp2k(tfile.name, 'wb') data = np.zeros((128, 128, 2), dtype=np.uint8) - j = Jp2k(tfile.name, data=data) + j.write(data) self.assertEqual(j.box[2].box[0].height, 128) self.assertEqual(j.box[2].box[0].width, 128) self.assertEqual(j.box[2].box[0].num_components, 2) self.assertEqual(j.box[2].box[1].colorspace, glymur.core.GREYSCALE) - @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") def test_rgb_with_extra_component(self): """v2.0+ should be able to write extra components""" with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + j = Jp2k(tfile.name, 'wb') data = np.zeros((128, 128, 4), dtype=np.uint8) - j = Jp2k(tfile.name, data=data) + j.write(data) self.assertEqual(j.box[2].box[0].height, 128) self.assertEqual(j.box[2].box[0].width, 128) self.assertEqual(j.box[2].box[0].num_components, 4) self.assertEqual(j.box[2].box[1].colorspace, glymur.core.SRGB) - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) - @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) + @unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows") def test_openjpeg_library_message(self): """Verify the error message produced by the openjpeg library""" # This will confirm that the error callback mechanism is working. with open(self.jp2file, 'rb') as fptr: data = fptr.read() with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - # Codestream starts at byte 3323. SIZ marker at 3233. - # COD marker at 3282. Subsampling at 3276. - offset = 3223 - tfile.write(data[0:offset+52]) + # Codestream starts at byte 3127. SIZ marker at 3137. + # COD marker at 3186. Subsampling at 3180. + tfile.write(data[0:3179]) # Make the DY bytes of the SIZ segment zero. That means that # a subsampling factor is zero, which is illegal. tfile.write(b'\x00') - tfile.write(data[offset+53:offset+55]) + tfile.write(data[3180:3182]) tfile.write(b'\x00') - tfile.write(data[offset+57:offset+59]) + tfile.write(data[3184:3186]) tfile.write(b'\x00') - tfile.write(data[offset+59:]) + tfile.write(data[3186:]) tfile.flush() with warnings.catch_warnings(): - warnings.simplefilter('ignore') + warnings.simplefilter("ignore") j = Jp2k(tfile.name) - regexp = re.compile(r'''OpenJPEG\slibrary\serror:\s+ - Invalid\svalues\sfor\scomp\s=\s0\s+ - :\sdx=1\sdy=0''', re.VERBOSE) - if sys.hexversion < 0x03020000: - with self.assertRaisesRegexp((IOError, OSError), - regexp): - j[::2, ::2] - else: - with self.assertRaisesRegex((IOError, OSError), - regexp): - j[::2, ::2] + regexp = re.compile(r'''OpenJPEG\slibrary\serror:\s+ + Invalid\svalues\sfor\scomp\s=\s0\s+ + :\sdx=1\sdy=0''', re.VERBOSE) + if sys.hexversion < 0x03020000: + with self.assertRaisesRegexp((IOError, OSError), regexp): + j.read(rlevel=1) + else: + with self.assertRaisesRegex((IOError, OSError), regexp): + j.read(rlevel=1) -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") -class TestParsing(unittest.TestCase): - """Tests for verifying how parsing may be altered.""" - def setUp(self): - self.jp2file = glymur.data.nemo() - # Reset parseoptions for every test. - glymur.set_parseoptions(full_codestream=False) - - def tearDown(self): - glymur.set_parseoptions(full_codestream=False) - - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) - def test_bad_rsiz(self): - """Should not warn if RSIZ when parsing is turned off.""" - filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2') - glymur.set_parseoptions(full_codestream=False) - Jp2k(filename) - - glymur.set_parseoptions(full_codestream=True) - with self.assertWarnsRegex(UserWarning, 'Invalid profile'): - Jp2k(filename) - - def test_main_header(self): - """verify that the main header isn't loaded during normal parsing""" - # The hidden _main_header attribute should show up after accessing it. - jp2 = Jp2k(self.jp2file) - jp2c = jp2.box[4] - self.assertIsNone(jp2c._codestream) - jp2c.codestream - self.assertIsNotNone(jp2c._codestream) - - -@unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") -class TestJp2kOpjDataRootWarnings(unittest.TestCase): - """These tests should be run by just about all configuration.""" - - def test_undecodeable_box_id(self): - """Should warn in case of undecodeable box ID but not error out.""" - filename = opj_data_file('input/nonregression/edf_c2_1013627.jp2') - with self.assertWarnsRegex(UserWarning, 'Unrecognized box'): - jp2 = Jp2k(filename) - - # Now make sure we got all of the boxes. Ignore the last, which was - # bad. - box_ids = [box.box_id for box in jp2.box[:-1]] - self.assertEqual(box_ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - def test_bad_ftyp_brand(self): - """Should warn in case of bad ftyp brand.""" - filename = opj_data_file('input/nonregression/edf_c2_1000290.jp2') - with self.assertWarns(UserWarning): - Jp2k(filename) - - def test_invalid_approximation(self): - """Should warn in case of invalid approximation.""" - filename = opj_data_file('input/nonregression/edf_c2_1015644.jp2') - with self.assertWarnsRegex(UserWarning, 'Invalid approximation'): - Jp2k(filename) - - def test_invalid_colorspace(self): - """ - Should warn in case of invalid colorspace. - - There are multiple warnings, so there's no good way to regex them all. - """ - filename = opj_data_file('input/nonregression/edf_c2_1103421.jp2') - with self.assertWarns(UserWarning): - Jp2k(filename) - - def test_stupid_windows_eol_at_end(self): - """Garbage characters at the end of the file.""" - filename = opj_data_file('input/nonregression/issue211.jp2') - with self.assertWarns(UserWarning): - Jp2k(filename) - - -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") -class TestJp2kOpjDataRoot(unittest.TestCase): - """These tests should be run by just about all configurations.""" - - @unittest.skipIf(re.match("0|1.[0-4]", glymur.version.openjpeg_version), - "Must have openjpeg 1.5 or higher to run") - @unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) - def test_irreversible(self): - """Irreversible""" - filename = opj_data_file('input/nonregression/issue141.rawl') - expdata = np.fromfile(filename, dtype=np.uint16) - expdata.resize((32, 2048)) - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, data=expdata, irreversible=True, numres=5) - - codestream = j.get_codestream() - self.assertEqual(codestream.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - - actdata = j[:] - self.assertTrue(fixtures.mse(actdata, expdata) < 250) - - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) - def test_no_cxform_pclr_jp2(self): - """Indices for pclr jpxfile if no color transform""" - filename = opj_data_file('input/conformance/file9.jp2') - with self.assertWarns(UserWarning): - jp2 = Jp2k(filename) - rgb = jp2[:] - jp2.ignore_pclr_cmap_cdef = True - idx = jp2[:] - self.assertEqual(rgb.shape, (512, 768, 3)) - self.assertEqual(idx.shape, (512, 768)) - - # Should be able to manually reconstruct the RGB image from the palette - # and indices. - palette = jp2.box[2].box[1].palette - rgb_from_idx = np.zeros(rgb.shape, dtype=np.uint8) - for r in np.arange(rgb.shape[0]): - for c in np.arange(rgb.shape[1]): - rgb_from_idx[r, c] = palette[idx[r, c]] - np.testing.assert_array_equal(rgb, rgb_from_idx) - - def test_read_differing_subsamples(self): - """should error out with read used on differently subsampled images""" - # Verify that we error out appropriately if we use the read method - # on an image with differing subsamples - # - # Issue 86. - filename = opj_data_file('input/conformance/p0_05.j2k') - j = Jp2k(filename) - with self.assertRaises(RuntimeError): - j[:] - - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) - def test_no_cxform_cmap(self): - """Bands as physically ordered, not as physically intended""" - # This file has the components physically reversed. The cmap box - # tells the decoder how to order them, but this flag prevents that. - filename = opj_data_file('input/conformance/file2.jp2') - with self.assertWarns(UserWarning): - # The file has a bad compatibility list entry. Not important here. - j = Jp2k(filename) - ycbcr = j[:] - j.ignore_pclr_cmap_cdef = True - crcby = j[:] - - expected = np.zeros(ycbcr.shape, ycbcr.dtype) - for k in range(crcby.shape[2]): - expected[:, :, crcby.shape[2] - k - 1] = crcby[:, :, k] - - np.testing.assert_array_equal(ycbcr, expected) - - -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") -class TestCodestreamOpjData(unittest.TestCase): - """Test suite for unusual codestream cases. Uses OPJ_DATA_ROOT""" - - def setUp(self): - self.jp2file = glymur.data.nemo() - - def tearDown(self): - pass - - @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") - def test_reserved_marker_segment(self): - """Reserved marker segments are ok.""" - - # Some marker segments were reserved in FCD15444-1. Since that - # standard is old, some of them may have come into use. - # - # Let's inject a reserved marker segment into a file that - # we know something about to make sure we can still parse it. - filename = os.path.join(OPJ_DATA_ROOT, 'input/conformance/p0_01.j2k') - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - with open(filename, 'rb') as ifile: - # Everything up until the first QCD marker. - read_buffer = ifile.read(45) - 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[2].marker_id, '0xff6f') - self.assertEqual(codestream.segment[2].length, 3) - self.assertEqual(codestream.segment[2].data, b'\x00') - - def test_psot_is_zero(self): - """Psot=0 in SOT is perfectly legal. Issue #78.""" - filename = os.path.join(OPJ_DATA_ROOT, - 'input/nonregression/123.j2c') - j = Jp2k(filename) - codestream = j.get_codestream(header_only=False) - - # The codestream is valid, so we should be able to get the entire - # codestream, so the last one is EOC. - self.assertEqual(codestream.segment[-1].marker_id, 'EOC') - - def test_siz_segment_ssiz_signed(self): - """ssiz attribute to be removed in future release""" - filename = os.path.join(OPJ_DATA_ROOT, 'input/conformance/p0_03.j2k') - j = Jp2k(filename) - 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, (131,)) - - -class TestCodestreamRepr(unittest.TestCase): - - def setUp(self): - self.jp2file = glymur.data.nemo() - - def tearDown(self): - pass - - def test_soc(self): - """Test SOC segment repr""" - segment = glymur.codestream.SOCsegment() - newseg = eval(repr(segment)) - self.assertEqual(newseg.marker_id, 'SOC') - - def test_siz(self): - """Test SIZ segment repr""" - kwargs = {'rsiz': 0, - 'xysiz': (2592, 1456), - 'xyosiz': (0, 0), - 'xytsiz': (2592, 1456), - 'xytosiz': (0, 0), - 'Csiz': 3, - 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': ((1, 1, 1), (1, 1, 1))} - segment = glymur.codestream.SIZsegment(**kwargs) - newseg = eval(repr(segment)) - self.assertEqual(newseg.marker_id, 'SIZ') - self.assertEqual(newseg.xsiz, 2592) - self.assertEqual(newseg.ysiz, 1456) - self.assertEqual(newseg.xosiz, 0) - self.assertEqual(newseg.yosiz, 0) - self.assertEqual(newseg.xtsiz, 2592) - self.assertEqual(newseg.ytsiz, 1456) - self.assertEqual(newseg.xtosiz, 0) - self.assertEqual(newseg.ytosiz, 0) - - self.assertEqual(newseg.xrsiz, (1, 1, 1)) - self.assertEqual(newseg.yrsiz, (1, 1, 1)) - self.assertEqual(newseg.bitdepth, (8, 8, 8)) - self.assertEqual(newseg.signed, (False, False, False)) - - 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)) - - -class TestCodestream(unittest.TestCase): - """Test suite for unusual codestream cases.""" - - def setUp(self): - self.jp2file = glymur.data.nemo() - - def tearDown(self): - pass - - 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)) +if __name__ == "__main__": + unittest.main() diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py index 9e8a0eb..c956354 100644 --- a/glymur/test/test_opj_suite.py +++ b/glymur/test/test_opj_suite.py @@ -2,25 +2,56 @@ The tests defined here roughly correspond to what is in the OpenJPEG test suite. """ + +# Some test names correspond with openjpeg tests. Long names are ok in this +# case. +# pylint: disable=C0103 + +# All of these tests correspond to tests in openjpeg, so no docstring is really +# needed. +# pylint: disable=C0111 + +# This module is very long, cannot be helped. +# pylint: disable=C0302 + +# unittest fools pylint with "too many public methods" +# pylint: disable=R0904 + +# Some tests use numpy test infrastructure, which means the tests never +# reference "self", so pylint claims it should be a function. No, no, no. +# pylint: disable=R0201 + +# Many tests are pretty long and that can't be helped. +# pylint: disable=R0915 + +# asserWarns introduced in python 3.2 (python2.7/pylint issue) +# pylint: disable=E1101 + +# unittest2 is python2.6 only (pylint/python-2.7) +# pylint: disable=F0401 + import re import sys -import unittest + +if sys.hexversion < 0x02070000: + import unittest2 as unittest +else: + import unittest + import warnings import numpy as np -import glymur from glymur import Jp2k -from glymur.jp2box import FileTypeBox, ImageHeaderBox, ColourSpecificationBox +import glymur -from .fixtures import (OPJ_DATA_ROOT, MetadataBase, - WARNING_INFRASTRUCTURE_ISSUE, - WARNING_INFRASTRUCTURE_MSG, - mse, peak_tolerance, read_pgx, opj_data_file, - OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) +from .fixtures import OPENJP2_IS_V2_OFFICIAL, OPJ_DATA_ROOT +from .fixtures import mse, peak_tolerance, read_pgx, opj_data_file -@unittest.skipIf(OPENJPEG_NOT_AVAILABLE, OPENJPEG_NOT_AVAILABLE_MSG) +@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None and + glymur.lib.openjpeg.OPENJPEG is None, + "Missing openjpeg libraries.") @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") class TestSuite(unittest.TestCase): @@ -31,20 +62,111 @@ class TestSuite(unittest.TestCase): def tearDown(self): pass + def test_ETS_C0P0_p0_01_j2k(self): + jfile = opj_data_file('input/conformance/p0_01.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(rlevel=0) + + pgxfile = opj_data_file('baseline/conformance/c0p0_01.pgx') + pgxdata = read_pgx(pgxfile) + np.testing.assert_array_equal(jpdata, pgxdata) + + def test_ETS_C0P0_p0_02_j2k(self): + jfile = opj_data_file('input/conformance/p0_02.j2k') + jp2k = Jp2k(jfile) + with warnings.catch_warnings(): + # Invalid marker ID. + warnings.simplefilter("ignore") + jpdata = jp2k.read(rlevel=0) + + pgxfile = opj_data_file('baseline/conformance/c0p0_02.pgx') + pgxdata = read_pgx(pgxfile) + np.testing.assert_array_equal(jpdata, pgxdata) + + def test_ETS_C0P0_p0_03_j2k_r1(self): + jfile = opj_data_file('input/conformance/p0_03.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(rlevel=1) + + pgxfile = opj_data_file('baseline/conformance/c0p0_03r1.pgx') + pgxdata = read_pgx(pgxfile) + np.testing.assert_array_equal(jpdata, pgxdata) + + def test_ETS_C0P0_p0_09_j2k(self): + jfile = opj_data_file('input/conformance/p0_09.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(rlevel=2) + + pgxfile = opj_data_file('baseline/conformance/c0p0_09.pgx') + pgxdata = read_pgx(pgxfile) + + self.assertTrue(peak_tolerance(jpdata, pgxdata) < 4) + self.assertTrue(mse(jpdata, pgxdata) < 1.47) + + def test_ETS_C0P0_p0_11_j2k(self): + jfile = opj_data_file('input/conformance/p0_11.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(rlevel=0) + + pgxfile = opj_data_file('baseline/conformance/c0p0_11.pgx') + pgxdata = read_pgx(pgxfile) + + np.testing.assert_array_equal(jpdata, pgxdata) + + def test_ETS_C0P0_p0_15_j2k_r1(self): + jfile = opj_data_file('input/conformance/p0_15.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(rlevel=1) + + pgxfile = opj_data_file('baseline/conformance/c0p0_15r1.pgx') + pgxdata = read_pgx(pgxfile) + + np.testing.assert_array_equal(jpdata, pgxdata) + + def test_ETS_C0P0_p0_16_j2k(self): + jfile = opj_data_file('input/conformance/p0_16.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(rlevel=0) + + pgxfile = opj_data_file('baseline/conformance/c0p0_16.pgx') + pgxdata = read_pgx(pgxfile) + + np.testing.assert_array_equal(jpdata, pgxdata) + + def test_ETS_C0P1_p1_01_j2k(self): + jfile = opj_data_file('input/conformance/p1_01.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(rlevel=0) + + pgxfile = opj_data_file('baseline/conformance/c0p1_01.pgx') + pgxdata = read_pgx(pgxfile) + + np.testing.assert_array_equal(jpdata, pgxdata) + def test_ETS_C1P0_p0_01_j2k(self): jfile = opj_data_file('input/conformance/p0_01.j2k') jp2k = Jp2k(jfile) - jpdata = jp2k[:] + jpdata = jp2k.read(rlevel=0) pgxfile = opj_data_file('baseline/conformance/c1p0_01_0.pgx') pgxdata = read_pgx(pgxfile) np.testing.assert_array_equal(jpdata, pgxdata) + def test_ETS_C1P0_p0_02_j2k(self): + jfile = opj_data_file('input/conformance/p0_02.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(rlevel=0) + + pgxfile = opj_data_file('baseline/conformance/c1p0_02_0.pgx') + pgxdata = read_pgx(pgxfile) + + np.testing.assert_array_equal(jpdata, pgxdata) + def test_ETS_C1P0_p0_03_j2k(self): jfile = opj_data_file('input/conformance/p0_03.j2k') jp2k = Jp2k(jfile) - jpdata = jp2k[:] + jpdata = jp2k.read(rlevel=0) pgxfile = opj_data_file('baseline/conformance/c1p0_03_0.pgx') pgxdata = read_pgx(pgxfile) @@ -54,7 +176,7 @@ class TestSuite(unittest.TestCase): def test_ETS_C1P0_p0_04_j2k(self): jfile = opj_data_file('input/conformance/p0_04.j2k') jp2k = Jp2k(jfile) - jpdata = jp2k[:] + jpdata = jp2k.read(rlevel=0) pgxfile = opj_data_file('baseline/conformance/c1p0_04_0.pgx') pgxdata = read_pgx(pgxfile) @@ -74,7 +196,7 @@ class TestSuite(unittest.TestCase): def test_ETS_C1P0_p0_08_j2k(self): jfile = opj_data_file('input/conformance/p0_08.j2k') jp2k = Jp2k(jfile) - jpdata = jp2k[::2, ::2] + jpdata = jp2k.read(rlevel=1) pgxfile = opj_data_file('baseline/conformance/c1p0_08_0.pgx') pgxdata = read_pgx(pgxfile) @@ -91,7 +213,7 @@ class TestSuite(unittest.TestCase): def test_ETS_C1P0_p0_09_j2k(self): jfile = opj_data_file('input/conformance/p0_09.j2k') jp2k = Jp2k(jfile) - jpdata = jp2k[:] + jpdata = jp2k.read(rlevel=0) pgxfile = opj_data_file('baseline/conformance/c1p0_09_0.pgx') pgxdata = read_pgx(pgxfile) @@ -100,7 +222,7 @@ class TestSuite(unittest.TestCase): def test_ETS_C1P0_p0_11_j2k(self): jfile = opj_data_file('input/conformance/p0_11.j2k') jp2k = Jp2k(jfile) - jpdata = jp2k[:] + jpdata = jp2k.read(rlevel=0) pgxfile = opj_data_file('baseline/conformance/c1p0_11_0.pgx') pgxdata = read_pgx(pgxfile) @@ -109,7 +231,7 @@ class TestSuite(unittest.TestCase): def test_ETS_C1P0_p0_14_j2k(self): jfile = opj_data_file('input/conformance/p0_14.j2k') jp2k = Jp2k(jfile) - jpdata = jp2k[:] + jpdata = jp2k.read(rlevel=0) pgxfile = opj_data_file('baseline/conformance/c1p0_14_0.pgx') pgxdata = read_pgx(pgxfile) @@ -126,7 +248,7 @@ class TestSuite(unittest.TestCase): def test_ETS_C1P0_p0_15_j2k(self): jfile = opj_data_file('input/conformance/p0_15.j2k') jp2k = Jp2k(jfile) - jpdata = jp2k[:] + jpdata = jp2k.read(rlevel=0) pgxfile = opj_data_file('baseline/conformance/c1p0_15_0.pgx') pgxdata = read_pgx(pgxfile) @@ -135,7 +257,7 @@ class TestSuite(unittest.TestCase): def test_ETS_C1P0_p0_16_j2k(self): jfile = opj_data_file('input/conformance/p0_16.j2k') jp2k = Jp2k(jfile) - jpdata = jp2k[:] + jpdata = jp2k.read(rlevel=0) pgxfile = opj_data_file('baseline/conformance/c1p0_16_0.pgx') pgxdata = read_pgx(pgxfile) @@ -144,7 +266,7 @@ class TestSuite(unittest.TestCase): def test_ETS_C1P1_p1_01_j2k(self): jfile = opj_data_file('input/conformance/p1_01.j2k') jp2k = Jp2k(jfile) - jpdata = jp2k[:] + jpdata = jp2k.read(rlevel=0) pgxfile = opj_data_file('baseline/conformance/c1p1_01_0.pgx') pgxdata = read_pgx(pgxfile) @@ -153,7 +275,7 @@ class TestSuite(unittest.TestCase): def test_ETS_C1P1_p1_02_j2k(self): jfile = opj_data_file('input/conformance/p1_02.j2k') jp2k = Jp2k(jfile) - jpdata = jp2k[:] + jpdata = jp2k.read(rlevel=0) pgxfile = opj_data_file('baseline/conformance/c1p1_02_0.pgx') pgxdata = read_pgx(pgxfile) @@ -173,143 +295,30 @@ class TestSuite(unittest.TestCase): def test_ETS_C1P1_p1_04_j2k(self): jfile = opj_data_file('input/conformance/p1_04.j2k') jp2k = Jp2k(jfile) - jpdata = jp2k[:] + jpdata = jp2k.read() pgxfile = opj_data_file('baseline/conformance/c1p1_04_0.pgx') pgxdata = read_pgx(pgxfile) self.assertTrue(peak_tolerance(jpdata, pgxdata) < 624) self.assertTrue(mse(jpdata, pgxdata) < 3080) - def test_NR_DEC_Bretagne2_j2k_1_decode(self): - jfile = opj_data_file('input/nonregression/Bretagne2.j2k') - jp2 = Jp2k(jfile) - jp2[:] - self.assertTrue(True) - - def test_NR_DEC__00042_j2k_2_decode(self): - jfile = opj_data_file('input/nonregression/_00042.j2k') - jp2 = Jp2k(jfile) - jp2[:] - self.assertTrue(True) - - def test_NR_DEC_buxI_j2k_9_decode(self): - jfile = opj_data_file('input/nonregression/buxI.j2k') - Jp2k(jfile)[:] - self.assertTrue(True) - - def test_NR_DEC_buxR_j2k_10_decode(self): - jfile = opj_data_file('input/nonregression/buxR.j2k') - Jp2k(jfile)[:] - self.assertTrue(True) - - def test_NR_DEC_Cannotreaddatawithnosizeknown_j2k_11_decode(self): - relpath = 'input/nonregression/Cannotreaddatawithnosizeknown.j2k' - jfile = opj_data_file(relpath) - Jp2k(jfile)[:] - self.assertTrue(True) - - def test_NR_DEC_cthead1_j2k_12_decode(self): - jfile = opj_data_file('input/nonregression/cthead1.j2k') - Jp2k(jfile)[:] - self.assertTrue(True) - - def test_NR_DEC_CT_Phillips_JPEG2K_Decompr_Problem_j2k_13_decode(self): - relpath = 'input/nonregression/CT_Phillips_JPEG2K_Decompr_Problem.j2k' - jfile = opj_data_file(relpath) - Jp2k(jfile)[:] - self.assertTrue(True) - - def test_NR_DEC_j2k32_j2k_15_decode(self): - jfile = opj_data_file('input/nonregression/j2k32.j2k') - Jp2k(jfile)[:] - self.assertTrue(True) - - def test_NR_DEC_MarkerIsNotCompliant_j2k_17_decode(self): - jfile = opj_data_file('input/nonregression/MarkerIsNotCompliant.j2k') - Jp2k(jfile)[:] - self.assertTrue(True) - - def test_NR_DEC_Marrin_jp2_18_decode(self): - jfile = opj_data_file('input/nonregression/Marrin.jp2') - Jp2k(jfile)[:] - self.assertTrue(True) - - def test_NR_DEC_movie_00000_j2k_20_decode(self): - jfile = opj_data_file('input/nonregression/movie_00000.j2k') - Jp2k(jfile)[:] - self.assertTrue(True) - - def test_NR_DEC_movie_00001_j2k_21_decode(self): - jfile = opj_data_file('input/nonregression/movie_00001.j2k') - Jp2k(jfile)[:] - self.assertTrue(True) - - def test_NR_DEC_movie_00002_j2k_22_decode(self): - jfile = opj_data_file('input/nonregression/movie_00002.j2k') - Jp2k(jfile)[:] - self.assertTrue(True) - - def test_NR_DEC_orb_blue_lin_j2k_j2k_23_decode(self): - jfile = opj_data_file('input/nonregression/orb-blue10-lin-j2k.j2k') - Jp2k(jfile)[:] - self.assertTrue(True) - - def test_NR_DEC_orb_blue_win_j2k_j2k_24_decode(self): - jfile = opj_data_file('input/nonregression/orb-blue10-win-j2k.j2k') - Jp2k(jfile)[:] - self.assertTrue(True) - - def test_NR_DEC_relax_jp2_27_decode(self): - jfile = opj_data_file('input/nonregression/relax.jp2') - Jp2k(jfile)[:] - self.assertTrue(True) - - def test_NR_DEC_test_lossless_j2k_28_decode(self): - jfile = opj_data_file('input/nonregression/test_lossless.j2k') - Jp2k(jfile)[:] - self.assertTrue(True) - - def test_NR_DEC_pacs_ge_j2k_30_decode(self): - jfile = opj_data_file('input/nonregression/pacs.ge.j2k') - Jp2k(jfile)[:] - self.assertTrue(True) - - -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") -@unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) -class TestSuiteWarns(MetadataBase): - """ - Identical setup to above, but these tests issue warnings. - """ - - def setUp(self): - pass - - def tearDown(self): - pass - def test_ETS_JP2_file1(self): jfile = opj_data_file('input/conformance/file1.jp2') - with self.assertWarns(UserWarning): - # Bad compatibility list item. - jp2k = Jp2k(jfile) - jpdata = jp2k[:] + jp2k = Jp2k(jfile) + jpdata = jp2k.read() self.assertEqual(jpdata.shape, (512, 768, 3)) def test_ETS_JP2_file2(self): jfile = opj_data_file('input/conformance/file2.jp2') - with self.assertWarns(UserWarning): - jp2k = Jp2k(jfile) - jpdata = jp2k[:] + jp2k = Jp2k(jfile) + jpdata = jp2k.read() self.assertEqual(jpdata.shape, (640, 480, 3)) @unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2, "Functionality not implemented for 1.x") def test_ETS_JP2_file3(self): jfile = opj_data_file('input/conformance/file3.jp2') - with self.assertWarns(UserWarning): - jp2k = Jp2k(jfile) + jp2k = Jp2k(jfile) jpdata = jp2k.read_bands() self.assertEqual(jpdata[0].shape, (640, 480)) self.assertEqual(jpdata[1].shape, (320, 240)) @@ -317,156 +326,189 @@ class TestSuiteWarns(MetadataBase): def test_ETS_JP2_file4(self): jfile = opj_data_file('input/conformance/file4.jp2') - with self.assertWarns(UserWarning): - jp2k = Jp2k(jfile) - jpdata = jp2k[:] + jp2k = Jp2k(jfile) + jpdata = jp2k.read() self.assertEqual(jpdata.shape, (512, 768)) def test_ETS_JP2_file5(self): jfile = opj_data_file('input/conformance/file5.jp2') - with self.assertWarns(UserWarning): - # There's a warning for an unknown compatibility entry. - # Ignore it here. - jp2k = Jp2k(jfile) - jpdata = jp2k[:] + jp2k = Jp2k(jfile) + jpdata = jp2k.read() self.assertEqual(jpdata.shape, (512, 768, 3)) def test_ETS_JP2_file6(self): jfile = opj_data_file('input/conformance/file6.jp2') - with self.assertWarns(UserWarning): - jp2k = Jp2k(jfile) - jpdata = jp2k[:] + jp2k = Jp2k(jfile) + jpdata = jp2k.read() self.assertEqual(jpdata.shape, (512, 768)) def test_ETS_JP2_file7(self): jfile = opj_data_file('input/conformance/file7.jp2') - with self.assertWarns(UserWarning): - jp2k = Jp2k(jfile) - jpdata = jp2k[:] + jp2k = Jp2k(jfile) + jpdata = jp2k.read() self.assertEqual(jpdata.shape, (640, 480, 3)) def test_ETS_JP2_file8(self): jfile = opj_data_file('input/conformance/file8.jp2') - with self.assertWarns(UserWarning): - jp2k = Jp2k(jfile) - jpdata = jp2k[:] + jp2k = Jp2k(jfile) + jpdata = jp2k.read() self.assertEqual(jpdata.shape, (400, 700)) def test_ETS_JP2_file9(self): jfile = opj_data_file('input/conformance/file9.jp2') - with self.assertWarns(UserWarning): - jp2k = Jp2k(jfile) - jpdata = jp2k[:] - self.assertEqual(jpdata.shape, (512, 768, 3)) + jp2k = Jp2k(jfile) + jpdata = jp2k.read() + if re.match(r"""1\.3""", glymur.version.openjpeg_version): + # Version 1.3 reads the indexed image as indices, not as RGB. + self.assertEqual(jpdata.shape, (512, 768)) + else: + self.assertEqual(jpdata.shape, (512, 768, 3)) - def test_NR_broken1_jp2_dump(self): - jfile = opj_data_file('input/nonregression/broken1.jp2') + def test_NR_DEC_Bretagne2_j2k_1_decode(self): + jfile = opj_data_file('input/nonregression/Bretagne2.j2k') + jp2 = Jp2k(jfile) + jp2.read() + self.assertTrue(True) - # The colr box has a ridiculously incorrect box length. - regex = re.compile(r'''b'colr'\sbox\shas\sincorrect\sbox\slength\s - \(\d+\)''', - re.VERBOSE) - with self.assertWarnsRegex(UserWarning, regex): + def test_NR_DEC__00042_j2k_2_decode(self): + jfile = opj_data_file('input/nonregression/_00042.j2k') + jp2 = Jp2k(jfile) + jp2.read() + self.assertTrue(True) + + @unittest.skipIf(re.match(r"""1\.5\.2""", glymur.version.openjpeg_version), + "Test fails in 1.5.2") + @unittest.skipIf(sys.hexversion < 0x03020000, + "Uses features introduced in 3.2.") + def test_NR_DEC_broken_jp2_4_decode(self): + jfile = opj_data_file('input/nonregression/broken.jp2') + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + # colr box has bad length. jp2 = Jp2k(jfile) + self.assertEqual(len(w), 2) + with self.assertRaises(IOError): + jp2.read() + self.assertTrue(True) - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + @unittest.skipIf(re.match(r"""1\.5\.2""", glymur.version.openjpeg_version), + "Test fails in 1.5.2") + def test_NR_DEC_broken3_jp2_6_decode(self): + jfile = opj_data_file('input/nonregression/broken3.jp2') + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + # colr box has bad length. + j = Jp2k(jfile) + self.assertEqual(len(w), 2) - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) + with self.assertRaises(IOError): + j.read() - # Signature box. Check for corruption. - self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) - self.verify_filetype_box(jp2.box[1], FileTypeBox()) + def test_NR_DEC_buxI_j2k_9_decode(self): + jfile = opj_data_file('input/nonregression/buxI.j2k') + Jp2k(jfile).read() + self.assertTrue(True) - expected = ImageHeaderBox(152, 203, num_components=3) - self.verifyImageHeaderBox(jp2.box[2].box[0], expected) + def test_NR_DEC_buxR_j2k_10_decode(self): + jfile = opj_data_file('input/nonregression/buxR.j2k') + Jp2k(jfile).read() + self.assertTrue(True) - expected = ColourSpecificationBox(colorspace=glymur.core.SRGB) - self.verifyColourSpecificationBox(jp2.box[2].box[1], expected) + def test_NR_DEC_Cannotreaddatawithnosizeknown_j2k_11_decode(self): + relpath = 'input/nonregression/Cannotreaddatawithnosizeknown.j2k' + jfile = opj_data_file(relpath) + Jp2k(jfile).read() + self.assertTrue(True) - c = jp2.box[3].codestream + def test_NR_DEC_cthead1_j2k_12_decode(self): + jfile = opj_data_file('input/nonregression/cthead1.j2k') + Jp2k(jfile).read() + self.assertTrue(True) - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'CME', 'COD', 'QCD', 'QCC', 'QCC'] - self.assertEqual(ids, expected) + def test_NR_DEC_CT_Phillips_JPEG2K_Decompr_Problem_j2k_13_decode(self): + relpath = 'input/nonregression/CT_Phillips_JPEG2K_Decompr_Problem.j2k' + jfile = opj_data_file(relpath) + Jp2k(jfile).read() + self.assertTrue(True) - kwargs = {'rsiz': 0, 'xysiz': (203, 152), 'xyosiz': (0, 0), - 'xytsiz': (203, 152), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) + def test_NR_DEC_j2k32_j2k_15_decode(self): + jfile = opj_data_file('input/nonregression/j2k32.j2k') + Jp2k(jfile).read() + self.assertTrue(True) - pargs = (glymur.core.RCME_ISO_8859_1, - "Creator: JasPer Version 1.701.0".encode()) - self.verifyCMEsegment(c.segment[2], - glymur.codestream.CMEsegment(*pargs)) + def test_NR_DEC_MarkerIsNotCompliant_j2k_17_decode(self): + jfile = opj_data_file('input/nonregression/MarkerIsNotCompliant.j2k') + Jp2k(jfile).read() + self.assertTrue(True) - # COD: Coding style default - self.assertFalse(c.segment[3].scod & 2) # no sop - self.assertFalse(c.segment[3].scod & 4) # no eph - self.assertEqual(c.segment[3].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[3].layers, 1) # layers = 1 - self.assertEqual(c.segment[3].spcod[3], 1) # mct - self.assertEqual(c.segment[3].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[3].code_block_size), - (64, 64)) # cblk - self.verify_codeblock_style(c.segment[3].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[3].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[3].spcod), 9) + def test_NR_DEC_Marrin_jp2_18_decode(self): + jfile = opj_data_file('input/nonregression/Marrin.jp2') + Jp2k(jfile).read() + self.assertTrue(True) - # QCD: Quantization default - self.assertEqual(c.segment[4].sqcd & 0x1f, 0) - self.assertEqual(c.segment[4].guard_bits, 2) - self.assertEqual(c.segment[4].mantissa, [0] * 16) - self.assertEqual(c.segment[4].exponent, - [8] + [9, 9, 10] * 5) + def test_NR_DEC_movie_00000_j2k_20_decode(self): + jfile = opj_data_file('input/nonregression/movie_00000.j2k') + Jp2k(jfile).read() + self.assertTrue(True) - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[5].cqcc, 1) - self.assertEqual(c.segment[5].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[5].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[5].mantissa, [0] * 16) - self.assertEqual(c.segment[5].exponent, - [8] + [9, 9, 10] * 5) + def test_NR_DEC_movie_00001_j2k_21_decode(self): + jfile = opj_data_file('input/nonregression/movie_00001.j2k') + Jp2k(jfile).read() + self.assertTrue(True) - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[6].cqcc, 2) - self.assertEqual(c.segment[6].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[6].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[6].mantissa, [0] * 16) - self.assertEqual(c.segment[6].exponent, - [8] + [9, 9, 10] * 5) + def test_NR_DEC_movie_00002_j2k_22_decode(self): + jfile = opj_data_file('input/nonregression/movie_00002.j2k') + Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_orb_blue_lin_j2k_j2k_23_decode(self): + jfile = opj_data_file('input/nonregression/orb-blue10-lin-j2k.j2k') + Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_orb_blue_win_j2k_j2k_24_decode(self): + jfile = opj_data_file('input/nonregression/orb-blue10-win-j2k.j2k') + Jp2k(jfile).read() + self.assertTrue(True) def test_NR_DEC_orb_blue_lin_jp2_25_decode(self): jfile = opj_data_file('input/nonregression/orb-blue10-lin-jp2.jp2') - with self.assertWarns(UserWarning): + with warnings.catch_warnings(): # This file has an invalid ICC profile - Jp2k(jfile)[:] + warnings.simplefilter("ignore") + Jp2k(jfile).read() self.assertTrue(True) def test_NR_DEC_orb_blue_win_jp2_26_decode(self): jfile = opj_data_file('input/nonregression/orb-blue10-win-jp2.jp2') - with self.assertWarns(UserWarning): - Jp2k(jfile)[:] + Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_relax_jp2_27_decode(self): + jfile = opj_data_file('input/nonregression/relax.jp2') + Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_test_lossless_j2k_28_decode(self): + jfile = opj_data_file('input/nonregression/test_lossless.j2k') + Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_pacs_ge_j2k_30_decode(self): + jfile = opj_data_file('input/nonregression/pacs.ge.j2k') + Jp2k(jfile).read() self.assertTrue(True) @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") -@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] != 2, +@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] == 1, "Feature not supported in glymur until openjpeg 2.0") -class TestSuiteBands(unittest.TestCase): - """ - Test the read_bands method. +class TestSuite_bands(unittest.TestCase): + """Runs tests introduced in version 1.x but only pass in glymur with 2.0 + + The deal here is that the feature works with 1.x, but glymur only supports + it with version 2.0. """ def setUp(self): @@ -475,6 +517,27 @@ class TestSuiteBands(unittest.TestCase): def tearDown(self): pass + def test_ETS_C0P0_p0_05_j2k(self): + jfile = opj_data_file('input/conformance/p0_05.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read_bands(rlevel=3) + + pgxfile = opj_data_file('baseline/conformance/c0p0_05.pgx') + pgxdata = read_pgx(pgxfile) + self.assertTrue(peak_tolerance(jpdata[0], pgxdata) < 54) + self.assertTrue(mse(jpdata[0], pgxdata) < 68) + + def test_ETS_C0P1_p1_03_j2k(self): + jfile = opj_data_file('input/conformance/p1_03.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read_bands(rlevel=3) + + pgxfile = opj_data_file('baseline/conformance/c0p1_03.pgx') + pgxdata = read_pgx(pgxfile) + + self.assertTrue(peak_tolerance(jpdata[0], pgxdata) < 28) + self.assertTrue(mse(jpdata[0], pgxdata) < 18.8) + def test_ETS_C1P1_p1_03_j2k(self): jfile = opj_data_file('input/conformance/p1_03.j2k') jp2k = Jp2k(jfile) @@ -557,7 +620,7 @@ class TestSuiteBands(unittest.TestCase): @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") -@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] < 2, +@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] == 1, "Tests not passing until 2.0") class TestSuite2point0(unittest.TestCase): """Runs tests introduced in version 2.0 or that pass only in 2.0""" @@ -571,7 +634,7 @@ class TestSuite2point0(unittest.TestCase): def test_ETS_C1P0_p0_10_j2k(self): jfile = opj_data_file('input/conformance/p0_10.j2k') jp2k = Jp2k(jfile) - jpdata = jp2k[:] + jpdata = jp2k.read(rlevel=0) pgxfile = opj_data_file('baseline/conformance/c1p0_10_0.pgx') pgxdata = read_pgx(pgxfile) @@ -585,375 +648,39 @@ class TestSuite2point0(unittest.TestCase): pgxdata = read_pgx(pgxfile) np.testing.assert_array_equal(jpdata[:, :, 2], pgxdata) - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_NR_DEC_broken2_jp2_5_decode(self): - """ - Invalid marker ID on codestream, Null pointer access upon read. - """ + # Null pointer access jfile = opj_data_file('input/nonregression/broken2.jp2') - regex = re.compile(r'''Invalid\smarker\sid\sencountered\sat\sbyte\s - \d+\sin\scodestream:\s*"0x[a-fA-F0-9]{4}"''', - re.VERBOSE) with self.assertRaises(IOError): - with self.assertWarnsRegex(UserWarning, regex): - Jp2k(jfile)[:] + with warnings.catch_warnings(): + # Invalid marker ID. + warnings.simplefilter("ignore") + Jp2k(jfile).read() + self.assertTrue(True) - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_NR_DEC_broken4_jp2_7_decode(self): jfile = opj_data_file('input/nonregression/broken4.jp2') with self.assertRaises(IOError): - with self.assertWarns(UserWarning): + with warnings.catch_warnings(): # invalid number of subbands, bad marker ID - Jp2k(jfile)[:] + warnings.simplefilter("ignore") + Jp2k(jfile).read() self.assertTrue(True) - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_NR_DEC_kakadu_v4_4_openjpegv2_broken_j2k_16_decode(self): # This test actually passes in 1.5, but produces unpleasant warning # messages that cannot be turned off? relpath = 'input/nonregression/kakadu_v4-4_openjpegv2_broken.j2k' jfile = opj_data_file(relpath) if glymur.version.openjpeg_version_tuple[0] < 2: - with self.assertWarns(UserWarning): + with warnings.catch_warnings(): # Incorrect warning issued about tile parts. - Jp2k(jfile)[:] - else: - Jp2k(jfile)[:] - self.assertTrue(True) - - -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") -@unittest.skipIf(re.match(r'''0|1|2.0.0''', - glymur.version.openjpeg_version) is not None, - "Only supported in 2.0.1 or higher") -class TestSuite2point1(unittest.TestCase): - """Runs tests introduced in version 2.0+ or that pass only in 2.0+""" - - def setUp(self): - pass - - def tearDown(self): - pass - - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) - def test_NR_DEC_text_GBR_jp2_29_decode(self): - jfile = opj_data_file('input/nonregression/text_GBR.jp2') - with self.assertWarns(UserWarning): - # brand is 'jp2 ', but has any icc profile. - jp2 = Jp2k(jfile) - jp2[:] - self.assertTrue(True) - - def test_NR_DEC_kodak_2layers_lrcp_j2c_31_decode(self): - jfile = opj_data_file('input/nonregression/kodak_2layers_lrcp.j2c') - Jp2k(jfile)[:] - self.assertTrue(True) - - def test_NR_DEC_kodak_2layers_lrcp_j2c_32_decode(self): - jfile = opj_data_file('input/nonregression/kodak_2layers_lrcp.j2c') - Jp2k(jfile)[::4, ::4] - self.assertTrue(True) - - def test_NR_DEC_issue104_jpxstream_jp2_33_decode(self): - jfile = opj_data_file('input/nonregression/issue104_jpxstream.jp2') - Jp2k(jfile)[:] - self.assertTrue(True) - - def test_NR_DEC_mem_b2b86b74_2753_jp2_35_decode(self): - jfile = opj_data_file('input/nonregression/mem-b2b86b74-2753.jp2') - Jp2k(jfile)[:] - self.assertTrue(True) - - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) - def test_NR_DEC_gdal_fuzzer_unchecked_num_resolutions_jp2_36_decode(self): - f = 'input/nonregression/gdal_fuzzer_unchecked_numresolutions.jp2' - jfile = opj_data_file(f) - with self.assertWarns(UserWarning): - # Invalid number of resolutions. - j = Jp2k(jfile) - with self.assertRaises(IOError): - j[:] - - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) - def test_NR_DEC_gdal_fuzzer_check_number_of_tiles_jp2_38_decode(self): - relpath = 'input/nonregression/gdal_fuzzer_check_number_of_tiles.jp2' - jfile = opj_data_file(relpath) - with self.assertWarns(UserWarning): - # Invalid number of tiles. - j = Jp2k(jfile) - with self.assertRaises(IOError): - j[:] - - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) - def test_NR_DEC_gdal_fuzzer_check_comp_dx_dy_jp2_39_decode(self): - relpath = 'input/nonregression/gdal_fuzzer_check_comp_dx_dy.jp2' - jfile = opj_data_file(relpath) - with self.assertWarns(UserWarning): - # Invalid subsampling value - with self.assertRaises(IOError): - Jp2k(jfile)[:] - - def test_NR_DEC_file_409752_jp2_40_decode(self): - jfile = opj_data_file('input/nonregression/file409752.jp2') - with self.assertRaises(RuntimeError): - Jp2k(jfile)[:] - - def test_NR_DEC_issue206_image_000_jp2_42_decode(self): - jfile = opj_data_file('input/nonregression/issue206_image-000.jp2') - Jp2k(jfile)[:] - self.assertTrue(True) - - def test_NR_DEC_p1_04_j2k_57_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - tdata = jp2k[896:1024, 896:1024] # last tile - odata = jp2k[:] - np.testing.assert_array_equal(tdata, odata[896:1024, 896:1024]) - - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) - def test_NR_DEC_p1_04_j2k_57_decode_0p7_backwards_compatibility(self): - """ - 0.7.x usage deprecated - """ - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - if sys.hexversion < 0x03000000: - with warnings.catch_warnings(): - # Suppress a warning due to deprecated syntax warnings.simplefilter("ignore") - tdata = jp2k.read(tile=63) # last tile + Jp2k(jfile).read() else: - with self.assertWarns(DeprecationWarning): - tdata = jp2k.read(tile=63) # last tile - odata = jp2k[:] - np.testing.assert_array_equal(tdata, odata[896:1024, 896:1024]) - - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) - def test_NR_DEC_p1_04_j2k_58_decode_0p7_backwards_compatibility(self): - """ - 0.7.x usage deprecated - """ - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - if sys.hexversion < 0x03000000: - with warnings.catch_warnings(): - # Suppress a warning due to deprecated syntax - tdata = jp2k.read(tile=63, rlevel=2) # last tile - else: - with self.assertWarns(DeprecationWarning): - tdata = jp2k.read(tile=63, rlevel=2) # last tile - odata = jp2k[::4, ::4] - np.testing.assert_array_equal(tdata, odata[224:256, 224:256]) - - def test_NR_DEC_p1_04_j2k_58_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - tdata = jp2k[896:1024:4, 896:1024:4] # last tile - odata = jp2k[::4, ::4] - np.testing.assert_array_equal(tdata, odata[224:256, 224:256]) - - def test_NR_DEC_p1_04_j2k_59_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - tdata = jp2k[128:256, 512:640] # 2nd row, 5th column - odata = jp2k[:] - np.testing.assert_array_equal(tdata, odata[128:256, 512:640]) - - def test_NR_DEC_p1_04_j2k_60_decode(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - jp2k = Jp2k(jfile) - tdata = jp2k[128:256:2, 512:640:2] # 2nd row, 5th column - odata = jp2k[::2, ::2] - np.testing.assert_array_equal(tdata, odata[64:128, 256:320]) - - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) - def test_NR_DEC_jp2_36_decode(self): - lst = ('input', - 'nonregression', - 'gdal_fuzzer_assert_in_opj_j2k_read_SQcd_SQcc.patch.jp2') - jfile = opj_data_file('/'.join(lst)) - with self.assertWarns(UserWarning): - # Invalid component number. - j = Jp2k(jfile) - with self.assertRaises(IOError): - j[:] + Jp2k(jfile).read() + self.assertTrue(True) -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") -@unittest.skipIf(re.match(r'''0|1|2.0.0''', - glymur.version.openjpeg_version) is not None, - "Only supported in 2.0.1 or higher") -class TestReadArea(unittest.TestCase): - """ - Runs tests introduced in version 2.0+ or that pass only in 2.0+ - - Specifically for read method with area parameter. - """ - @classmethod - def setUpClass(self): - - jfile = opj_data_file('input/conformance/p1_04.j2k') - self.j2k = Jp2k(jfile) - self.j2k_data = self.j2k[:] - self.j2k_half_data = self.j2k[::2, ::2] - self.j2k_quarter_data = self.j2k[::4, ::4] - - jfile = opj_data_file('input/conformance/p1_06.j2k') - self.j2k_p1_06 = Jp2k(jfile) - - def test_NR_DEC_p1_04_j2k_43_decode(self): - actual = self.j2k[:1024, :1024] - expected = self.j2k_data - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_44_decode(self): - actual = self.j2k[640:768, 512:640] - expected = self.j2k_data[640:768, 512:640] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_45_decode(self): - actual = self.j2k[896:1024, 896:1024] - expected = self.j2k_data[896:1024, 896:1024] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_46_decode(self): - actual = self.j2k[500:800, 100:300] - expected = self.j2k_data[500:800, 100:300] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_47_decode(self): - actual = self.j2k[520:600, 260:360] - expected = self.j2k_data[520:600, 260:360] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_48_decode(self): - actual = self.j2k[520:660, 260:360] - expected = self.j2k_data[520:660, 260:360] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_49_decode(self): - actual = self.j2k[520:600, 360:400] - expected = self.j2k_data[520:600, 360:400] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_50_decode(self): - actual = self.j2k[:1024:4, :1024:4] - expected = self.j2k_quarter_data - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_51_decode(self): - actual = self.j2k[640:768:4, 512:640:4] - expected = self.j2k_quarter_data[160:192, 128:160] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_52_decode(self): - actual = self.j2k[896:1024:4, 896:1024:4] - expected = self.j2k_quarter_data[224:352, 224:352] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_53_decode(self): - actual = self.j2k[500:800:4, 100:300:4] - expected = self.j2k_quarter_data[125:200, 25:75] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_54_decode(self): - actual = self.j2k[520:600:4, 260:360:4] - expected = self.j2k_quarter_data[130:150, 65:90] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_55_decode(self): - actual = self.j2k[520:660:4, 260:360:4] - expected = self.j2k_quarter_data[130:165, 65:90] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_04_j2k_56_decode(self): - actual = self.j2k[520:600:4, 360:400:4] - expected = self.j2k_quarter_data[130:150, 90:100] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p1_06_j2k_70_decode(self): - actual = self.j2k_p1_06[9:12:2, 9:12:2] - self.assertEqual(actual.shape, (1, 1, 3)) - - def test_NR_DEC_p1_06_j2k_71_decode(self): - actual = self.j2k_p1_06[10:12:2, 4:10:2] - self.assertEqual(actual.shape, (1, 3, 3)) - - def test_NR_DEC_p1_06_j2k_72_decode(self): - ssdata = self.j2k_p1_06[3:9:2, 3:9:2] - self.assertEqual(ssdata.shape, (3, 3, 3)) - - def test_NR_DEC_p1_06_j2k_73_decode(self): - ssdata = self.j2k_p1_06[4:7:2, 4:7:2] - self.assertEqual(ssdata.shape, (2, 2, 3)) - - def test_NR_DEC_p1_06_j2k_74_decode(self): - ssdata = self.j2k_p1_06[4:5:2, 4:5:2] - self.assertEqual(ssdata.shape, (1, 1, 3)) - - def test_NR_DEC_p1_06_j2k_75_decode(self): - # Image size would be 0 x 0. - with self.assertRaises((IOError, OSError)): - self.j2k_p1_06[9:12:4, 9:12:4] - - def test_NR_DEC_p0_04_j2k_85_decode(self): - actual = self.j2k[:256, :256] - expected = self.j2k_data[:256, :256] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p0_04_j2k_86_decode(self): - actual = self.j2k[:128, 128:256] - expected = self.j2k_data[:128, 128:256] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p0_04_j2k_87_decode(self): - actual = self.j2k[10:200, 50:120] - expected = self.j2k_data[10:200, 50:120] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p0_04_j2k_88_decode(self): - actual = self.j2k[150:210, 10:190] - expected = self.j2k_data[150:210, 10:190] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p0_04_j2k_89_decode(self): - actual = self.j2k[80:150, 100:200] - expected = self.j2k_data[80:150, 100:200] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p0_04_j2k_90_decode(self): - actual = self.j2k[20:50, 150:200] - expected = self.j2k_data[20:50, 150:200] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p0_04_j2k_91_decode(self): - actual = self.j2k[:256:4, :256:4] - expected = self.j2k_quarter_data[0:64, 0:64] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p0_04_j2k_92_decode(self): - actual = self.j2k[:128:4, 128:256:4] - expected = self.j2k_quarter_data[:32, 32:64] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p0_04_j2k_93_decode(self): - actual = self.j2k[10:200:4, 50:120:4] - expected = self.j2k_quarter_data[3:50, 13:30] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p0_04_j2k_94_decode(self): - actual = self.j2k[150:210:4, 10:190:4] - expected = self.j2k_quarter_data[38:53, 3:48] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p0_04_j2k_95_decode(self): - actual = self.j2k[80:150:4, 100:200:4] - expected = self.j2k_quarter_data[20:38, 25:50] - np.testing.assert_array_equal(actual, expected) - - def test_NR_DEC_p0_04_j2k_96_decode(self): - actual = self.j2k[20:50:4, 150:200:4] - expected = self.j2k_quarter_data[5:13, 38:50] - np.testing.assert_array_equal(actual, expected) +if __name__ == "__main__": + unittest.main() diff --git a/glymur/test/test_opj_suite_2p1.py b/glymur/test/test_opj_suite_2p1.py new file mode 100644 index 0000000..12d19a0 --- /dev/null +++ b/glymur/test/test_opj_suite_2p1.py @@ -0,0 +1,407 @@ +""" +The tests defined here roughly correspond to what is in the OpenJPEG test +suite. +""" + +# Some test names correspond with openjpeg tests. Long names are ok in this +# case. +# pylint: disable=C0103 + +# All of these tests correspond to tests in openjpeg, so no docstring is really +# needed. +# pylint: disable=C0111 + +# This module is very long, cannot be helped. +# pylint: disable=C0302 + +# unittest fools pylint with "too many public methods" +# pylint: disable=R0904 + +# Some tests use numpy test infrastructure, which means the tests never +# reference "self", so pylint claims it should be a function. No, no, no. +# pylint: disable=R0201 + +# Many tests are pretty long and that can't be helped. +# pylint: disable=R0915 + +# asserWarns introduced in python 3.2 (python2.7/pylint issue) +# pylint: disable=E1101 + +# unittest2 is python2.6 only (pylint/python-2.7) +# pylint: disable=F0401 + +import re +import sys + +if sys.hexversion < 0x02070000: + import unittest2 as unittest +else: + import unittest + +import warnings + +import numpy as np + +from glymur import Jp2k +import glymur + +from .fixtures import OPENJP2_IS_V2_OFFICIAL, OPJ_DATA_ROOT +from .fixtures import mse, peak_tolerance, read_pgx, opj_data_file + + +@unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") +@unittest.skipIf(re.match(r'''2.0.0''', glymur.version.openjpeg_version), + "Tests not introduced until 2.0.1") +@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] == 1, + "Tests not introduced until 2.1") +class TestSuite2point1(unittest.TestCase): + """Runs tests introduced in version 2.0+ or that pass only in 2.0+""" + + def setUp(self): + pass + + def tearDown(self): + pass + + def test_NR_DEC_text_GBR_jp2_29_decode(self): + jfile = opj_data_file('input/nonregression/text_GBR.jp2') + with warnings.catch_warnings(): + # brand is 'jp2 ', but has any icc profile. + warnings.simplefilter("ignore") + jp2 = Jp2k(jfile) + jp2.read() + self.assertTrue(True) + + def test_NR_DEC_kodak_2layers_lrcp_j2c_31_decode(self): + jfile = opj_data_file('input/nonregression/kodak_2layers_lrcp.j2c') + Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_kodak_2layers_lrcp_j2c_32_decode(self): + jfile = opj_data_file('input/nonregression/kodak_2layers_lrcp.j2c') + Jp2k(jfile).read(layer=2) + self.assertTrue(True) + + def test_NR_DEC_issue104_jpxstream_jp2_33_decode(self): + jfile = opj_data_file('input/nonregression/issue104_jpxstream.jp2') + Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_mem_b2b86b74_2753_jp2_35_decode(self): + jfile = opj_data_file('input/nonregression/mem-b2b86b74-2753.jp2') + Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_gdal_fuzzer_unchecked_num_resolutions_jp2_36_decode(self): + f = 'input/nonregression/gdal_fuzzer_unchecked_numresolutions.jp2' + jfile = opj_data_file(f) + with warnings.catch_warnings(): + # Invalid number of resolutions. + warnings.simplefilter("ignore") + j = Jp2k(jfile) + with self.assertRaises(IOError): + j.read() + + def test_NR_DEC_gdal_fuzzer_check_number_of_tiles_jp2_38_decode(self): + relpath = 'input/nonregression/gdal_fuzzer_check_number_of_tiles.jp2' + jfile = opj_data_file(relpath) + with warnings.catch_warnings(): + # Invalid number of tiles. + warnings.simplefilter("ignore") + j = Jp2k(jfile) + with self.assertRaises(IOError): + j.read() + + def test_NR_DEC_gdal_fuzzer_check_comp_dx_dy_jp2_39_decode(self): + relpath = 'input/nonregression/gdal_fuzzer_check_comp_dx_dy.jp2' + jfile = opj_data_file(relpath) + with warnings.catch_warnings(): + # Invalid subsampling value + warnings.simplefilter("ignore") + with self.assertRaises(IOError): + Jp2k(jfile).read() + + def test_NR_DEC_file_409752_jp2_40_decode(self): + jfile = opj_data_file('input/nonregression/file409752.jp2') + with self.assertRaises(RuntimeError): + Jp2k(jfile).read() + + def test_NR_DEC_issue188_beach_64bitsbox_jp2_41_decode(self): + # Has an 'XML ' box instead of 'xml '. Yes that is pedantic, but it + # really does deserve a warning. + relpath = 'input/nonregression/issue188_beach_64bitsbox.jp2' + jfile = opj_data_file(relpath) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + Jp2k(jfile).read() + self.assertEqual(len(w), 1) + + def test_NR_DEC_issue206_image_000_jp2_42_decode(self): + jfile = opj_data_file('input/nonregression/issue206_image-000.jp2') + Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_p1_04_j2k_43_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(0, 0, 1024, 1024)) + odata = jp2k.read() + np.testing.assert_array_equal(ssdata, odata) + + def test_NR_DEC_p1_04_j2k_44_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(640, 512, 768, 640)) + odata = jp2k.read() + np.testing.assert_array_equal(ssdata, odata[640:768, 512:640]) + + def test_NR_DEC_p1_04_j2k_45_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(896, 896, 1024, 1024)) + odata = jp2k.read() + np.testing.assert_array_equal(ssdata, odata[896:1024, 896:1024]) + + def test_NR_DEC_p1_04_j2k_46_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(500, 100, 800, 300)) + odata = jp2k.read() + np.testing.assert_array_equal(ssdata, odata[500:800, 100:300]) + + def test_NR_DEC_p1_04_j2k_47_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(520, 260, 600, 360)) + odata = jp2k.read() + np.testing.assert_array_equal(ssdata, odata[520:600, 260:360]) + + def test_NR_DEC_p1_04_j2k_48_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(520, 260, 660, 360)) + odata = jp2k.read() + np.testing.assert_array_equal(ssdata, odata[520:660, 260:360]) + + def test_NR_DEC_p1_04_j2k_49_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(520, 360, 600, 400)) + odata = jp2k.read() + np.testing.assert_array_equal(ssdata, odata[520:600, 360:400]) + + def test_NR_DEC_p1_04_j2k_50_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(0, 0, 1024, 1024), rlevel=2) + odata = jp2k.read(rlevel=2) + + np.testing.assert_array_equal(ssdata, odata[0:256, 0:256]) + + def test_NR_DEC_p1_04_j2k_51_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(640, 512, 768, 640), rlevel=2) + odata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(ssdata, odata[160:192, 128:160]) + + def test_NR_DEC_p1_04_j2k_52_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(896, 896, 1024, 1024), rlevel=2) + odata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(ssdata, odata[224:352, 224:352]) + + def test_NR_DEC_p1_04_j2k_53_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(500, 100, 800, 300), rlevel=2) + odata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(ssdata, odata[125:200, 25:75]) + + def test_NR_DEC_p1_04_j2k_54_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(520, 260, 600, 360), rlevel=2) + odata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(ssdata, odata[130:150, 65:90]) + + def test_NR_DEC_p1_04_j2k_55_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(520, 260, 660, 360), rlevel=2) + odata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(ssdata, odata[130:165, 65:90]) + + def test_NR_DEC_p1_04_j2k_56_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(520, 360, 600, 400), rlevel=2) + odata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(ssdata, odata[130:150, 90:100]) + + def test_NR_DEC_p1_04_j2k_57_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + tdata = jp2k.read(tile=63) # last tile + odata = jp2k.read() + np.testing.assert_array_equal(tdata, odata[896:1024, 896:1024]) + + def test_NR_DEC_p1_04_j2k_58_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + tdata = jp2k.read(tile=63, rlevel=2) # last tile + odata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(tdata, odata[224:256, 224:256]) + + def test_NR_DEC_p1_04_j2k_59_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + tdata = jp2k.read(tile=12) # 2nd row, 5th column + odata = jp2k.read() + np.testing.assert_array_equal(tdata, odata[128:256, 512:640]) + + def test_NR_DEC_p1_04_j2k_60_decode(self): + jfile = opj_data_file('input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + tdata = jp2k.read(tile=12, rlevel=1) # 2nd row, 5th column + odata = jp2k.read(rlevel=1) + np.testing.assert_array_equal(tdata, odata[64:128, 256:320]) + + def test_NR_DEC_jp2_36_decode(self): + lst = ('input', + 'nonregression', + 'gdal_fuzzer_assert_in_opj_j2k_read_SQcd_SQcc.patch.jp2') + jfile = opj_data_file('/'.join(lst)) + with warnings.catch_warnings(): + # Invalid component number. + warnings.simplefilter("ignore") + j = Jp2k(jfile) + with self.assertRaises(IOError): + j.read() + + def test_NR_DEC_p1_06_j2k_70_decode(self): + jfile = opj_data_file('input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(9, 9, 12, 12), rlevel=1) + self.assertEqual(ssdata.shape, (1, 1, 3)) + + def test_NR_DEC_p1_06_j2k_71_decode(self): + jfile = opj_data_file('input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(10, 4, 12, 10), rlevel=1) + self.assertEqual(ssdata.shape, (1, 3, 3)) + + def test_NR_DEC_p1_06_j2k_72_decode(self): + jfile = opj_data_file('input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(3, 3, 9, 9), rlevel=1) + self.assertEqual(ssdata.shape, (3, 3, 3)) + + def test_NR_DEC_p1_06_j2k_73_decode(self): + jfile = opj_data_file('input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(4, 4, 7, 7), rlevel=1) + self.assertEqual(ssdata.shape, (2, 2, 3)) + + def test_NR_DEC_p1_06_j2k_74_decode(self): + jfile = opj_data_file('input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(4, 4, 5, 5), rlevel=1) + self.assertEqual(ssdata.shape, (1, 1, 3)) + + def test_NR_DEC_p1_06_j2k_75_decode(self): + # Image size would be 0 x 0. + jfile = opj_data_file('input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + with self.assertRaises((IOError, OSError)): + jp2k.read(area=(9, 9, 12, 12), rlevel=2) + + def test_NR_DEC_p0_04_j2k_85_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(0, 0, 256, 256)) + fulldata = jp2k.read() + np.testing.assert_array_equal(fulldata[0:256, 0:256], ssdata) + + def test_NR_DEC_p0_04_j2k_86_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(0, 128, 128, 256)) + fulldata = jp2k.read() + np.testing.assert_array_equal(fulldata[0:128, 128:256], ssdata) + + def test_NR_DEC_p0_04_j2k_87_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(10, 50, 200, 120)) + fulldata = jp2k.read() + np.testing.assert_array_equal(fulldata[10:200, 50:120], ssdata) + + def test_NR_DEC_p0_04_j2k_88_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(150, 10, 210, 190)) + fulldata = jp2k.read() + np.testing.assert_array_equal(fulldata[150:210, 10:190], ssdata) + + def test_NR_DEC_p0_04_j2k_89_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(80, 100, 150, 200)) + fulldata = jp2k.read() + np.testing.assert_array_equal(fulldata[80:150, 100:200], ssdata) + + def test_NR_DEC_p0_04_j2k_90_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(20, 150, 50, 200)) + fulldata = jp2k.read() + np.testing.assert_array_equal(fulldata[20:50, 150:200], ssdata) + + def test_NR_DEC_p0_04_j2k_91_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(0, 0, 256, 256), rlevel=2) + fulldata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(fulldata[0:64, 0:64], ssdata) + + def test_NR_DEC_p0_04_j2k_92_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(0, 128, 128, 256), rlevel=2) + fulldata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(fulldata[0:32, 32:64], ssdata) + + def test_NR_DEC_p0_04_j2k_93_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(10, 50, 200, 120), rlevel=2) + fulldata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(fulldata[3:50, 13:30], ssdata) + + def test_NR_DEC_p0_04_j2k_94_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(150, 10, 210, 190), rlevel=2) + fulldata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(fulldata[38:53, 3:48], ssdata) + + def test_NR_DEC_p0_04_j2k_95_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(80, 100, 150, 200), rlevel=2) + fulldata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(fulldata[20:38, 25:50], ssdata) + + def test_NR_DEC_p0_04_j2k_96_decode(self): + jfile = opj_data_file('input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(20, 150, 50, 200), rlevel=2) + fulldata = jp2k.read(rlevel=2) + np.testing.assert_array_equal(fulldata[5:13, 38:50], ssdata) + + +if __name__ == "__main__": + unittest.main() diff --git a/glymur/test/test_opj_suite_dump.py b/glymur/test/test_opj_suite_dump.py deleted file mode 100644 index 7ac513f..0000000 --- a/glymur/test/test_opj_suite_dump.py +++ /dev/null @@ -1,3416 +0,0 @@ -""" -The tests defined here roughly correspond to what is in the OpenJPEG test -suite. -""" -import re -import unittest -import warnings - -import numpy as np - -import glymur -from glymur import Jp2k -from glymur.codestream import CMEsegment, SOTsegment, RGNsegment -from glymur.core import (RCME_ISO_8859_1, RCME_BINARY, SRGB, - GREYSCALE, RESTRICTED_ICC_PROFILE, - ENUMERATED_COLORSPACE) -from glymur.jp2box import FileTypeBox - -from .fixtures import (MetadataBase, OPJ_DATA_ROOT, - WARNING_INFRASTRUCTURE_ISSUE, - WARNING_INFRASTRUCTURE_MSG, - opj_data_file) - -comment1 = "Creator: AV-J2K (c) 2000,2001 Algo Vision Technology" - - -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") -class TestSuite(MetadataBase): - - def setUp(self): - pass - - def tearDown(self): - pass - - def test_NR_file409752(self): - jfile = opj_data_file('input/nonregression/file409752.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - self.verifySignatureBox(jp2.box[0]) - self.verify_filetype_box(jp2.box[1], FileTypeBox()) - - ihdr = glymur.jp2box.ImageHeaderBox(243, 720, num_components=3) - self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - - colr = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.YCC) - self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) - - c = jp2.box[3].codestream - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - kwargs = {'rsiz': 0, 'xysiz': (720, 243), 'xyosiz': (0, 0), - 'xytsiz': (720, 243), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 2, 2), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (32, 128)) # cblk - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) - self.assertEqual(c.segment[3].guard_bits, 1) - self.assertEqual(c.segment[3].mantissa, - [1816, 1792, 1792, 1724, 1770, 1770, 1724, 1868, - 1868, 1892, 3, 3, 69, 2002, 2002, 1889]) - self.assertEqual(c.segment[3].exponent, - [13] * 4 + [12] * 3 + [11] * 3 + [9] * 6) - - def test_NR_p0_01_dump(self): - jfile = opj_data_file('input/conformance/p0_01.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - # Segment IDs. - actual = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'QCD', 'COD', 'SOT', 'SOD', 'EOC'] - self.assertEqual(actual, expected) - - kwargs = {'rsiz': 1, 'xysiz': (128, 128), 'xyosiz': (0, 0), - 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (8,), - 'signed': (False,), 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # QCD: Quantization default - self.assertEqual(c.segment[2].sqcd & 0x1f, 0) - self.assertEqual(c.segment[2].guard_bits, 2) - self.assertEqual(c.segment[2].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - self.assertEqual(c.segment[2].mantissa, - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - - # COD: Coding style default - self.assertFalse(c.segment[3].scod & 2) # no sop - self.assertFalse(c.segment[3].scod & 4) # no eph - self.assertEqual(c.segment[3].spcod[0], glymur.core.RLCP) - self.assertEqual(c.segment[3].layers, 1) # layers = 1 - self.assertEqual(c.segment[3].spcod[3], 0) # mct - self.assertEqual(c.segment[3].spcod[4], 3) # layers - self.assertEqual(tuple(c.segment[3].code_block_size), - (64, 64)) # cblk - self.verify_codeblock_style(c.segment[3].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[3].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - - self.verifySOTsegment(c.segment[4], SOTsegment(0, 7314, 0, 1)) - - def test_NR_p0_02_dump(self): - jfile = opj_data_file('input/conformance/p0_02.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - kwargs = {'rsiz': 1, 'xysiz': (127, 126), 'xyosiz': (0, 0), - 'xytsiz': (127, 126), 'xytosiz': (0, 0), 'bitdepth': (8,), - 'signed': (False,), 'xyrsiz': [(2,), (1,)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertTrue(c.segment[2].scod & 2) # sop - self.assertTrue(c.segment[2].scod & 4) # eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 6) # layers = 6 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 3) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, True, False, True, True]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - - # COC: Coding style component - self.assertEqual(c.segment[3].ccoc, 0) - self.assertEqual(c.segment[3].spcoc[0], 3) # levels - self.assertEqual(tuple(c.segment[3].code_block_size), - (32, 32)) # cblk - self.verify_codeblock_style(c.segment[3].spcoc[3], - [False, False, True, False, True, True]) - self.assertEqual(c.segment[3].spcoc[4], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[4].sqcd & 0x1f, 0) # none - self.assertEqual(c.segment[4].guard_bits, 3) - self.assertEqual(c.segment[4].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - self.assertEqual(c.segment[4].mantissa, - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) - - pargs = (RCME_ISO_8859_1, - "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) - self.verifyCMEsegment(c.segment[5], CMEsegment(*pargs)) - - # One unknown marker - self.assertEqual(c.segment[6].marker_id, '0xff30') - - self.verifySOTsegment(c.segment[7], SOTsegment(0, 6047, 0, 1)) - - # SOD: start of data - # Just one. - self.assertEqual(c.segment[8].marker_id, 'SOD') - - # SOP, EPH - sop = [x.marker_id for x in c.segment if x.marker_id == 'SOP'] - eph = [x.marker_id for x in c.segment if x.marker_id == 'EPH'] - self.assertEqual(len(sop), 24) - self.assertEqual(len(eph), 24) - - # EOC: end of codestream - self.assertEqual(c.segment[-1].marker_id, 'EOC') - - def test_NR_p0_03_dump(self): - jfile = opj_data_file('input/conformance/p0_03.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - kwargs = {'rsiz': 1, 'xysiz': (256, 256), 'xyosiz': (0, 0), - 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (4,), - 'signed': (True,), 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertTrue(c.segment[2].scod & 2) - self.assertFalse(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.PCRL) - self.assertEqual(c.segment[2].layers, 8) # 8 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 1) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 1) # scalar implicit - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].exponent, [0]) - self.assertEqual(c.segment[3].mantissa, [0]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[4].cqcc, 0) - self.assertEqual(c.segment[4].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[4].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[4].exponent, [4, 5, 5, 6]) - self.assertEqual(c.segment[4].mantissa, [0, 0, 0, 0]) - - # POD: progression order change - self.assertEqual(c.segment[5].rspod, (0,)) - self.assertEqual(c.segment[5].cspod, (0,)) - self.assertEqual(c.segment[5].lyepod, (8,)) - self.assertEqual(c.segment[5].repod, (33,)) - self.assertEqual(c.segment[5].cdpod, (255,)) - self.assertEqual(c.segment[5].ppod, (glymur.core.LRCP,)) - - # CRG: component registration - self.assertEqual(c.segment[6].xcrg, (65424,)) - self.assertEqual(c.segment[6].ycrg, (32558,)) - - pargs = (RCME_ISO_8859_1, - "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) - self.verifyCMEsegment(c.segment[7], CMEsegment(*pargs)) - - pargs = (RCME_ISO_8859_1, comment1.encode()) - self.verifyCMEsegment(c.segment[8], CMEsegment(*pargs)) - - pargs = (RCME_BINARY, c.segment[9].ccme) - self.verifyCMEsegment(c.segment[9], CMEsegment(*pargs)) - - # TLM (tile-part length) - self.assertEqual(c.segment[10].ztlm, 0) - self.assertEqual(c.segment[10].ttlm, (0, 1, 2, 3)) - self.assertEqual(c.segment[10].ptlm, (4267, 2117, 4080, 2081)) - - self.verifySOTsegment(c.segment[11], SOTsegment(0, 4267, 0, 1)) - self.verifyRGNsegment(c.segment[12], RGNsegment(0, 0, 7)) - - # SOD: start of data - # Just one. - self.assertEqual(c.segment[13].marker_id, 'SOD') - - def test_NR_p0_04_dump(self): - jfile = opj_data_file('input/conformance/p0_04.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - kwargs = {'rsiz': 1, 'xysiz': (640, 480), 'xyosiz': (0, 0), - 'xytsiz': (640, 480), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) - self.assertFalse(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) - self.assertEqual(c.segment[2].layers, 20) # 20 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 6) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, True, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(c.segment[2].precinct_size, - [(128, 128), (128, 128), (128, 128), (128, 128), - (128, 128), (128, 128), (128, 128)]) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) # scalar expounded - self.assertEqual(c.segment[3].guard_bits, 3) - self.assertEqual(c.segment[3].exponent, - [16, 16, 16, 16, 15, 15, 15, 14, 14, 14, 13, 13, 13, - 11, 11, 11, 11, 11, 11]) - self.assertEqual(c.segment[3].mantissa, - [1814, 1815, 1815, 1817, 1821, 1821, 1827, 1845, 1845, - 1868, 1925, 1925, 2007, 32, 32, 131, 2002, 2002, - 1888]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[4].cqcc, 1) - # quantization type - self.assertEqual(c.segment[4].sqcc & 0x1f, 2) # none - self.assertEqual(c.segment[4].guard_bits, 3) - self.assertEqual(c.segment[4].exponent, - [14, 14, 14, 14, 13, 13, 13, 12, 12, 12, 11, 11, 11, - 9, 9, 9, 9, 9, 9]) - self.assertEqual(c.segment[4].mantissa, - [1814, 1815, 1815, 1817, 1821, 1821, 1827, 1845, - 1845, 1868, 1925, 1925, 2007, 32, 32, 131, 2002, - 2002, 1888]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[5].cqcc, 2) - # quantization type - self.assertEqual(c.segment[5].sqcc & 0x1f, 2) # none - self.assertEqual(c.segment[5].guard_bits, 3) - self.assertEqual(c.segment[5].exponent, - [14, 14, 14, 14, 13, 13, 13, 12, 12, 12, 11, 11, 11, - 9, 9, 9, 9, 9, 9]) - self.assertEqual(c.segment[5].mantissa, - [1814, 1815, 1815, 1817, 1821, 1821, 1827, 1845, - 1845, 1868, 1925, 1925, 2007, 32, 32, 131, 2002, - 2002, 1888]) - - pargs = (RCME_ISO_8859_1, - "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) - self.verifyCMEsegment(c.segment[6], CMEsegment(*pargs)) - - self.verifySOTsegment(c.segment[7], SOTsegment(0, 264383, 0, 1)) - - # SOD: start of data - # Just one. - self.assertEqual(c.segment[8].marker_id, 'SOD') - - def test_NR_p0_05_dump(self): - jfile = opj_data_file('input/conformance/p0_05.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - kwargs = {'rsiz': 1, 'xysiz': (1024, 1024), 'xyosiz': (0, 0), - 'xytsiz': (1024, 1024), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8, 8), - 'signed': (False, False, False, False), - 'xyrsiz': [(1, 1, 2, 2), (1, 1, 2, 2)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) - self.assertFalse(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.PCRL) - self.assertEqual(c.segment[2].layers, 7) # 7 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 6) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (32, 32)) # cblk - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # COC: Coding style component - self.assertEqual(c.segment[3].ccoc, 1) - self.assertEqual(c.segment[3].spcoc[0], 3) # levels - self.assertEqual(tuple(c.segment[3].code_block_size), - (32, 32)) # cblk - self.verify_codeblock_style(c.segment[3].spcoc[3], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[3].spcoc[4], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - - # COC: Coding style component - self.assertEqual(c.segment[4].ccoc, 3) - self.assertEqual(c.segment[4].spcoc[0], 6) # levels - self.assertEqual(tuple(c.segment[4].code_block_size), - (32, 32)) # cblk - self.verify_codeblock_style(c.segment[4].spcoc[3], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[4].spcoc[4], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[5].sqcd & 0x1f, 2) # scalar expounded - self.assertEqual(c.segment[5].guard_bits, 3) - self.assertEqual(c.segment[5].exponent, - [16, 16, 16, 16, 15, 15, 15, 14, 14, 14, 13, 13, 13, - 11, 11, 11, 11, 11, 11]) - self.assertEqual(c.segment[5].mantissa, - [1814, 1815, 1815, 1817, 1821, 1821, 1827, 1845, - 1845, 1868, 1925, 1925, 2007, 32, 32, 131, 2002, - 2002, 1888]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[6].cqcc, 0) - # quantization type - self.assertEqual(c.segment[6].sqcc & 0x1f, 1) # scalar derived - self.assertEqual(c.segment[6].guard_bits, 3) - self.assertEqual(c.segment[6].exponent, [14]) - self.assertEqual(c.segment[6].mantissa, [0]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[7].cqcc, 3) - # quantization type - self.assertEqual(c.segment[7].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[7].guard_bits, 3) - self.assertEqual(c.segment[7].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, - 9, 9, 10]) - self.assertEqual(c.segment[7].mantissa, [0] * 19) - - pargs = (RCME_ISO_8859_1, - "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) - self.verifyCMEsegment(c.segment[8], CMEsegment(*pargs)) - - # TLM (tile-part length) - self.assertEqual(c.segment[9].ztlm, 0) - self.assertEqual(c.segment[9].ttlm, (0,)) - self.assertEqual(c.segment[9].ptlm, (1310540,)) - - self.verifySOTsegment(c.segment[10], SOTsegment(0, 1310540, 0, 1)) - - # SOD: start of data - # Just one. - self.assertEqual(c.segment[11].marker_id, 'SOD') - - def test_NR_p0_06_dump(self): - jfile = opj_data_file('input/conformance/p0_06.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - kwargs = {'rsiz': 2, 'xysiz': (513, 129), 'xyosiz': (0, 0), - 'xytsiz': (513, 129), 'xytosiz': (0, 0), - 'bitdepth': (12, 12, 12, 12), - 'signed': (False, False, False, False), - 'xyrsiz': [(1, 2, 1, 2), (1, 1, 2, 2)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) - self.assertFalse(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.RPCL) - self.assertEqual(c.segment[2].layers, 4) # 4 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 6) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) # scalar expounded - self.assertEqual(c.segment[3].guard_bits, 3) - self.assertEqual(c.segment[3].mantissa, - [512, 518, 522, 524, 516, 524, 522, 527, 523, 549, - 557, 561, 853, 852, 700, 163, 78, 1508, 1831]) - self.assertEqual(c.segment[3].exponent, - [7, 7, 7, 7, 6, 6, 6, 5, 5, 5, 4, 4, 4, 3, 3, 2, 1, 2, - 1]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[4].cqcc, 1) - # quantization type - self.assertEqual(c.segment[4].sqcc & 0x1f, 2) # scalar derived - self.assertEqual(c.segment[4].guard_bits, 4) - self.assertEqual(c.segment[4].mantissa, - [1527, 489, 665, 506, 487, 502, 493, 493, 500, 485, - 505, 491, 490, 491, 499, 509, 503, 496, 558]) - self.assertEqual(c.segment[4].exponent, - [10, 10, 10, 10, 9, 9, 9, 8, 8, 8, 7, 7, 7, 6, 6, 6, - 5, 5, 5]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[5].cqcc, 2) - # quantization type - self.assertEqual(c.segment[5].sqcc & 0x1f, 2) # scalar derived - self.assertEqual(c.segment[5].guard_bits, 5) - self.assertEqual(c.segment[5].mantissa, - [1337, 728, 890, 719, 716, 726, 700, 718, 704, 704, - 712, 712, 717, 719, 701, 749, 753, 718, 841]) - self.assertEqual(c.segment[5].exponent, - [10, 10, 10, 10, 9, 9, 9, 8, 8, 8, 7, 7, 7, 6, 6, 6, - 5, 5, 5]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[6].cqcc, 3) - # quantization type - self.assertEqual(c.segment[6].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[6].guard_bits, 6) - self.assertEqual(c.segment[6].mantissa, [0] * 19) - self.assertEqual(c.segment[6].exponent, - [12, 13, 13, 14, 13, 13, 14, 13, 13, 14, 13, 13, 14, - 13, 13, 14, 13, 13, 14]) - - # COC: Coding style component - self.assertEqual(c.segment[7].ccoc, 3) - self.assertEqual(c.segment[7].spcoc[0], 6) # levels - self.assertEqual(tuple(c.segment[7].code_block_size), - (64, 64)) # cblk - self.verify_codeblock_style(c.segment[7].spcoc[3], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[7].spcoc[4], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - - self.verifyRGNsegment(c.segment[8], RGNsegment(0, 0, 11)) - self.verifySOTsegment(c.segment[9], SOTsegment(0, 33582, 0, 1)) - self.verifyRGNsegment(c.segment[10], RGNsegment(0, 0, 9)) - - # SOD: start of data - # Just one. - self.assertEqual(c.segment[11].marker_id, 'SOD') - - def test_NR_p0_07_dump(self): - jfile = opj_data_file('input/conformance/p0_07.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - kwargs = {'rsiz': 1, 'xysiz': (2048, 2048), 'xyosiz': (0, 0), - 'xytsiz': (128, 128), 'xytosiz': (0, 0), - 'bitdepth': (12, 12, 12), - 'signed': (True, True, True), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertTrue(c.segment[2].scod & 2) - self.assertTrue(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) - self.assertEqual(c.segment[2].layers, 8) # 8 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 3) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) # none - self.assertEqual(c.segment[3].guard_bits, 1) - self.assertEqual(c.segment[3].mantissa, [0] * 10) - self.assertEqual(c.segment[3].exponent, - [14, 15, 15, 16, 15, 15, 16, 15, 15, 16]) - - pargs = (RCME_ISO_8859_1, "Kakadu-3.0.7".encode()) - self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) - - self.verifySOTsegment(c.segment[5], SOTsegment(0, 9951, 0, 0)) - - # POD: progression order change - self.assertEqual(c.segment[6].rspod, (0,)) - self.assertEqual(c.segment[6].cspod, (0,)) - self.assertEqual(c.segment[6].lyepod, (9,)) - self.assertEqual(c.segment[6].repod, (3,)) - self.assertEqual(c.segment[6].cdpod, (3,)) - self.assertEqual(c.segment[6].ppod, (glymur.core.LRCP,)) - - # PLT: packet length, tile part - self.assertEqual(c.segment[7].zplt, 0) - - # SOD: start of data - self.assertEqual(c.segment[8].marker_id, 'SOD') - - def test_NR_p0_08_dump(self): - jfile = opj_data_file('input/conformance/p0_08.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - kwargs = {'rsiz': 1, 'xysiz': (513, 3072), 'xyosiz': (0, 0), - 'xytsiz': (513, 3072), 'xytosiz': (0, 0), - 'bitdepth': (12, 12, 12), - 'signed': (True, True, True), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertTrue(c.segment[2].scod & 2) - self.assertTrue(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.CPRL) - self.assertEqual(c.segment[2].layers, 30) # 30 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 7) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # COC: Coding style component - self.assertEqual(c.segment[3].ccoc, 0) - self.assertEqual(c.segment[3].spcoc[0], 6) # levels - self.assertEqual(tuple(c.segment[3].code_block_size), - (64, 64)) # cblk - self.verify_codeblock_style(c.segment[3].spcoc[3], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[3].spcoc[4], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - - # COC: Coding style component - self.assertEqual(c.segment[4].ccoc, 1) - self.assertEqual(c.segment[4].spcoc[0], 7) # levels - self.assertEqual(tuple(c.segment[4].code_block_size), - (32, 32)) # cblk - self.verify_codeblock_style(c.segment[4].spcoc[3], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[4].spcoc[4], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - - # COC: Coding style component - self.assertEqual(c.segment[5].ccoc, 2) - self.assertEqual(c.segment[5].spcoc[0], 8) # levels - self.assertEqual(tuple(c.segment[5].code_block_size), - (64, 64)) # cblk - self.verify_codeblock_style(c.segment[5].spcoc[3], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[5].spcoc[4], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[6].sqcd & 0x1f, 0) # none - self.assertEqual(c.segment[6].guard_bits, 4) - self.assertEqual(c.segment[6].mantissa, [0] * 22) - self.assertEqual(c.segment[6].exponent, - [11, 12, 12, 13, 12, 12, 13, 12, 12, 13, 12, 12, 13, - 12, 12, 13, 12, 12, 13, 12, 12, 13]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[7].cqcc, 0) - # quantization type - self.assertEqual(c.segment[7].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[7].guard_bits, 4) - self.assertEqual(c.segment[7].mantissa, [0] * 19) - self.assertEqual(c.segment[7].exponent, - [11, 12, 12, 13, 12, 12, 13, 12, 12, 13, 12, 12, 13, - 12, 12, 13, 12, 12, 13]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[8].cqcc, 2) - # quantization type - self.assertEqual(c.segment[8].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[8].guard_bits, 4) - self.assertEqual(c.segment[8].mantissa, [0] * 25) - self.assertEqual(c.segment[8].exponent, - [11, 12, 12, 13, 12, 12, 13, 12, 12, 13, 12, 12, 13, - 12, 12, 13, 12, 12, 13, 12, 12, 13, 12, 12, 13]) - - pargs = (RCME_ISO_8859_1, "Kakadu-3.0.7".encode()) - self.verifyCMEsegment(c.segment[9], CMEsegment(*pargs)) - - self.verifySOTsegment(c.segment[10], SOTsegment(0, 3820593, 0, 1)) - - def test_NR_p0_09_dump(self): - jfile = opj_data_file('input/conformance/p0_09.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - kwargs = {'rsiz': 0, 'xysiz': (17, 37), 'xyosiz': (0, 0), - 'xytsiz': (17, 37), 'xytosiz': (0, 0), 'bitdepth': (8,), - 'signed': (False,), 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) - self.assertFalse(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) # scalar expounded - self.assertEqual(c.segment[3].guard_bits, 1) - self.assertEqual(c.segment[3].mantissa, - [1915, 1884, 1884, 1853, 1884, 1884, 1853, 1962, 1962, - 1986, 53, 53, 120, 26, 26, 1983]) - self.assertEqual(c.segment[3].exponent, - [16, 16, 16, 16, 15, 15, 15, 14, 14, 14, 12, 12, 12, - 11, 11, 12]) - - pargs = (RCME_ISO_8859_1, "Kakadu-3.0.7".encode()) - self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) - - self.verifySOTsegment(c.segment[5], SOTsegment(0, 478, 0, 1)) - - # SOD: start of data - # Just one. - self.assertEqual(c.segment[6].marker_id, 'SOD') - - # EOC: end of codestream - self.assertEqual(c.segment[7].marker_id, 'EOC') - - def test_NR_p0_10_dump(self): - jfile = opj_data_file('input/conformance/p0_10.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - kwargs = {'rsiz': 1, 'xysiz': (256, 256), 'xyosiz': (0, 0), - 'xytsiz': (128, 128), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(4, 4, 4), (4, 4, 4)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) - self.assertFalse(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 2) # 2 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 3) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) # none - self.assertEqual(c.segment[3].guard_bits, 0) - self.assertEqual(c.segment[3].mantissa, [0] * 10) - self.assertEqual(c.segment[3].exponent, - [11, 12, 12, 13, 12, 12, 13, 12, 12, 13]) - - self.verifySOTsegment(c.segment[4], SOTsegment(0, 2453, 0, 0)) - - self.assertEqual(c.segment[5].marker_id, 'SOD') - self.verifySOTsegment(c.segment[6], SOTsegment(1, 2403, 0, 0)) - - self.assertEqual(c.segment[7].marker_id, 'SOD') - self.verifySOTsegment(c.segment[8], SOTsegment(2, 2420, 0, 0)) - - self.assertEqual(c.segment[9].marker_id, 'SOD') - self.verifySOTsegment(c.segment[10], SOTsegment(3, 2472, 0, 0)) - - self.assertEqual(c.segment[11].marker_id, 'SOD') - self.verifySOTsegment(c.segment[12], SOTsegment(0, 1043, 1, 2)) - - self.assertEqual(c.segment[13].marker_id, 'SOD') - self.verifySOTsegment(c.segment[14], SOTsegment(1, 1101, 1, 2)) - - self.assertEqual(c.segment[15].marker_id, 'SOD') - self.verifySOTsegment(c.segment[16], SOTsegment(3, 1054, 1, 2)) - - self.assertEqual(c.segment[17].marker_id, 'SOD') - self.verifySOTsegment(c.segment[18], SOTsegment(2, 14, 1, 0)) - - self.assertEqual(c.segment[19].marker_id, 'SOD') - self.verifySOTsegment(c.segment[20], SOTsegment(2, 1089, 2, 0)) - - self.assertEqual(c.segment[21].marker_id, 'SOD') - self.assertEqual(c.segment[22].marker_id, 'EOC') - - def test_NR_p0_11_dump(self): - jfile = opj_data_file('input/conformance/p0_11.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - kwargs = {'rsiz': 1, 'xysiz': (128, 1), 'xyosiz': (0, 0), - 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (8,), - 'signed': (False,), 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) - self.assertTrue(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 0) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, True]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(c.segment[2].precinct_size, [(128, 2)]) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) # none - self.assertEqual(c.segment[3].guard_bits, 3) - self.assertEqual(c.segment[3].mantissa, [0]) - self.assertEqual(c.segment[3].exponent, [8]) - - pargs = (RCME_ISO_8859_1, - "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) - self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) - - self.verifySOTsegment(c.segment[5], SOTsegment(0, 118, 0, 1)) - - # SOD: start of data - self.assertEqual(c.segment[6].marker_id, 'SOD') - - # SOP, EPH - sop = [x.marker_id for x in c.segment if x.marker_id == 'SOP'] - eph = [x.marker_id for x in c.segment if x.marker_id == 'EPH'] - self.assertEqual(len(sop), 0) - self.assertEqual(len(eph), 1) - - # EOC: end of codestream - self.assertEqual(c.segment[-1].marker_id, 'EOC') - - def test_NR_p0_12_dump(self): - jfile = opj_data_file('input/conformance/p0_12.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - kwargs = {'rsiz': 1, 'xysiz': (3, 5), 'xyosiz': (0, 0), - 'xytsiz': (3, 5), 'xytosiz': (0, 0), 'bitdepth': (8,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertTrue(c.segment[2].scod & 2) - self.assertFalse(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 3) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (32, 32)) # cblk - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, True, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) # none - self.assertEqual(c.segment[3].guard_bits, 3) - self.assertEqual(c.segment[3].mantissa, [0] * 10) - self.assertEqual(c.segment[3].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - - pargs = (RCME_ISO_8859_1, - "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) - self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) - - self.verifySOTsegment(c.segment[5], SOTsegment(0, 162, 0, 1)) - - # SOD: start of data - self.assertEqual(c.segment[6].marker_id, 'SOD') - - # SOP, EPH - sop = [x.marker_id for x in c.segment if x.marker_id == 'SOP'] - eph = [x.marker_id for x in c.segment if x.marker_id == 'EPH'] - self.assertEqual(len(sop), 4) - self.assertEqual(len(eph), 0) - - # EOC: end of codestream - self.assertEqual(c.segment[-1].marker_id, 'EOC') - - def test_NR_p0_13_dump(self): - jfile = opj_data_file('input/conformance/p0_13.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - kwargs = {'rsiz': 1, 'xysiz': (1, 1), 'xyosiz': (0, 0), - 'xytsiz': (1, 1), 'xytosiz': (0, 0), - 'bitdepth': tuple([8] * 257), - 'signed': tuple([False] * 257), - 'xyrsiz': [tuple([1] * 257), tuple([1] * 257)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 1) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, True, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # COC: Coding style component - self.assertEqual(c.segment[3].ccoc, 2) - self.assertEqual(c.segment[3].spcoc[0], 1) # levels - self.assertEqual(tuple(c.segment[3].code_block_size), (64, 64)) - self.verify_codeblock_style(c.segment[3].spcoc[3], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[3].spcoc[4], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[4].sqcd & 0x1f, 0) # none - self.assertEqual(c.segment[4].guard_bits, 2) - self.assertEqual(c.segment[4].mantissa, [0] * 4) - self.assertEqual(c.segment[4].exponent, - [8, 9, 9, 10]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[5].cqcc, 1) - self.assertEqual(c.segment[5].guard_bits, 3) - # quantization type - self.assertEqual(c.segment[5].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[5].exponent, [9, 10, 10, 11]) - self.assertEqual(c.segment[5].mantissa, [0, 0, 0, 0]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[6].cqcc, 2) - self.assertEqual(c.segment[6].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[6].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[6].exponent, [9, 10, 10, 11]) - self.assertEqual(c.segment[6].mantissa, [0, 0, 0, 0]) - - self.verifyRGNsegment(c.segment[7], RGNsegment(3, 0, 11)) - - # POD: progression order change - self.assertEqual(c.segment[8].rspod, (0, 0)) - self.assertEqual(c.segment[8].cspod, (0, 128)) - self.assertEqual(c.segment[8].lyepod, (1, 1)) - self.assertEqual(c.segment[8].repod, (33, 33)) - self.assertEqual(c.segment[8].cdpod, (128, 257)) - self.assertEqual(c.segment[8].ppod, - (glymur.core.RLCP, glymur.core.CPRL)) - - pargs = (RCME_ISO_8859_1, - "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) - self.verifyCMEsegment(c.segment[9], CMEsegment(*pargs)) - - self.verifySOTsegment(c.segment[10], SOTsegment(0, 1537, 0, 1)) - - # SOD: start of data - self.assertEqual(c.segment[11].marker_id, 'SOD') - - # EOC: end of codestream - self.assertEqual(c.segment[12].marker_id, 'EOC') - - def test_NR_p0_14_dump(self): - jfile = opj_data_file('input/conformance/p0_14.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - kwargs = {'rsiz': 0, 'xysiz': (49, 49), 'xyosiz': (0, 0), - 'xytsiz': (49, 49), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) - self.assertFalse(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # 1 layer - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) # none - self.assertEqual(c.segment[3].guard_bits, 1) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [10, 11, 11, 12, 11, 11, 12, 11, 11, 12, 11, 11, 12, - 11, 11, 12]) - - pargs = (RCME_ISO_8859_1, "Kakadu-3.0.7".encode()) - self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) - - self.verifySOTsegment(c.segment[5], SOTsegment(0, 1528, 0, 1)) - self.assertEqual(c.segment[6].marker_id, 'SOD') - self.assertEqual(c.segment[7].marker_id, 'EOC') - - def test_NR_p0_15_dump(self): - jfile = opj_data_file('input/conformance/p0_15.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - kwargs = {'rsiz': 1, 'xysiz': (256, 256), 'xyosiz': (0, 0), - 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (4,), - 'signed': (True,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertTrue(c.segment[2].scod & 2) - self.assertFalse(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.PCRL) - self.assertEqual(c.segment[2].layers, 8) # layers = 8 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 1) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 1) # derived - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0]) - self.assertEqual(c.segment[3].exponent, [0]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[4].cqcc, 0) - self.assertEqual(c.segment[4].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[4].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[4].mantissa, [0] * 4) - self.assertEqual(c.segment[4].exponent, [4, 5, 5, 6]) - - # POD: progression order change - self.assertEqual(c.segment[5].rspod, (0,)) - self.assertEqual(c.segment[5].cspod, (0,)) - self.assertEqual(c.segment[5].lyepod, (8,)) - self.assertEqual(c.segment[5].repod, (33,)) - self.assertEqual(c.segment[5].cdpod, (255,)) - self.assertEqual(c.segment[5].ppod, (glymur.core.LRCP,)) - - # CRG: component registration - self.assertEqual(c.segment[6].xcrg, (65424,)) - self.assertEqual(c.segment[6].ycrg, (32558,)) - - pargs = (RCME_ISO_8859_1, - "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) - self.verifyCMEsegment(c.segment[7], CMEsegment(*pargs)) - - pargs = (RCME_ISO_8859_1, comment1.encode()) - self.verifyCMEsegment(c.segment[8], CMEsegment(*pargs)) - - pargs = (RCME_BINARY, c.segment[9].ccme) - self.verifyCMEsegment(c.segment[9], CMEsegment(*pargs)) - - # TLM: tile-part length - self.assertEqual(c.segment[10].ztlm, 0) - self.assertEqual(c.segment[10].ttlm, (0, 1, 2, 3)) - self.assertEqual(c.segment[10].ptlm, (4267, 2117, 4080, 2081)) - - self.verifySOTsegment(c.segment[11], SOTsegment(0, 4267, 0, 1)) - - self.verifyRGNsegment(c.segment[12], RGNsegment(0, 0, 7)) - - # SOD: start of data - self.assertEqual(c.segment[13].marker_id, 'SOD') - - # 16 SOP markers would be here if we were looking for them - - self.verifySOTsegment(c.segment[31], SOTsegment(1, 2117, 0, 1)) - - # SOD: start of data - self.assertEqual(c.segment[32].marker_id, 'SOD') - - # 16 SOP markers would be here if we were looking for them - - self.verifySOTsegment(c.segment[49], SOTsegment(2, 4080, 0, 1)) - - # SOD: start of data - self.assertEqual(c.segment[50].marker_id, 'SOD') - - # 16 SOP markers would be here if we were looking for them - - self.verifySOTsegment(c.segment[67], SOTsegment(3, 2081, 0, 1)) - - # SOD: start of data - self.assertEqual(c.segment[68].marker_id, 'SOD') - - # 16 SOP markers would be here if we were looking for them - - # EOC: end of codestream - self.assertEqual(c.segment[85].marker_id, 'EOC') - - def test_NR_p0_16_dump(self): - jfile = opj_data_file('input/conformance/p0_16.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - kwargs = {'rsiz': 0, 'xysiz': (128, 128), 'xyosiz': (0, 0), - 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (8,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) - self.assertFalse(c.segment[2].scod & 4) - self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) - self.assertEqual(c.segment[2].layers, 3) # layers = 3 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 3) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) # none - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 10) - self.assertEqual(c.segment[3].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - - self.verifySOTsegment(c.segment[4], SOTsegment(0, 7331, 0, 1)) - - # SOD: start of data - self.assertEqual(c.segment[5].marker_id, 'SOD') - - # EOC: end of codestream - self.assertEqual(c.segment[6].marker_id, 'EOC') - - def test_NR_p1_01_dump(self): - jfile = opj_data_file('input/conformance/p1_01.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - kwargs = {'rsiz': 2, 'xysiz': (127, 227), 'xyosiz': (5, 128), - 'xytsiz': (127, 126), 'xytosiz': (1, 101), 'bitdepth': (8,), - 'signed': (False,), - 'xyrsiz': [(2,), (1,)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertTrue(c.segment[2].scod & 2) # SOP - self.assertTrue(c.segment[2].scod & 4) # EPH - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 5) # layers = 5 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 3) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, True, False, True, True]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # COC: Coding style component - self.assertEqual(c.segment[3].ccoc, 0) - self.assertEqual(c.segment[3].spcoc[0], 3) # level - self.assertEqual(tuple(c.segment[3].code_block_size), (32, 32)) - self.verify_codeblock_style(c.segment[3].spcoc[3], - [False, False, True, False, True, True]) - self.assertEqual(c.segment[3].spcoc[4], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[4].sqcd & 0x1f, 0) # none - self.assertEqual(c.segment[4].guard_bits, 3) - self.assertEqual(c.segment[4].mantissa, [0] * 10) - self.assertEqual(c.segment[4].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - - pargs = (RCME_ISO_8859_1, - "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) - self.verifyCMEsegment(c.segment[5], CMEsegment(*pargs)) - - self.verifySOTsegment(c.segment[6], SOTsegment(0, 4627, 0, 1)) - - # SOD: start of data - self.assertEqual(c.segment[7].marker_id, 'SOD') - - # SOP, EPH - sop = [x.marker_id for x in c.segment if x.marker_id == 'SOP'] - eph = [x.marker_id for x in c.segment if x.marker_id == 'EPH'] - self.assertEqual(len(sop), 20) - self.assertEqual(len(eph), 20) - - # EOC: end of codestream - self.assertEqual(c.segment[-1].marker_id, 'EOC') - - def test_NR_p1_02_dump(self): - jfile = opj_data_file('input/conformance/p1_02.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - kwargs = {'rsiz': 2, 'xysiz': (640, 480), 'xyosiz': (0, 0), - 'xytsiz': (640, 480), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 19) # layers = 19 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 6) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, True, False, True, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(c.segment[2].precinct_size, - [(128, 128), (256, 256), (512, 512), (1024, 1024), - (2048, 2048), (4096, 4096), (8192, 8192)]) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) # expounded - self.assertEqual(c.segment[3].guard_bits, 3) - self.assertEqual(c.segment[3].mantissa, - [1814, 1815, 1815, 1817, 1821, 1821, 1827, 1845, 1845, - 1868, 1925, 1925, 2007, 32, 32, 131, 2002, 2002, - 1888]) - self.assertEqual(c.segment[3].exponent, - [16, 16, 16, 16, 15, 15, 15, 14, 14, 14, 13, 13, 13, - 11, 11, 11, 11, 11, 11]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[4].cqcc, 1) - self.assertEqual(c.segment[4].guard_bits, 3) - # quantization type - self.assertEqual(c.segment[4].sqcc & 0x1f, 2) # expounded - self.assertEqual(c.segment[4].mantissa, - [1814, 1815, 1815, 1817, 1821, 1821, 1827, 1845, 1845, - 1868, 1925, 1925, 2007, 32, 32, 131, 2002, 2002, - 1888]) - self.assertEqual(c.segment[4].exponent, - [14, 14, 14, 14, 13, 13, 13, 12, 12, 12, 11, 11, 11, - 9, 9, 9, 9, 9, 9]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[5].cqcc, 2) - self.assertEqual(c.segment[5].guard_bits, 3) - # quantization type - self.assertEqual(c.segment[5].sqcc & 0x1f, 2) # expounded - self.assertEqual(c.segment[5].mantissa, - [1814, 1815, 1815, 1817, 1821, 1821, 1827, 1845, 1845, - 1868, 1925, 1925, 2007, 32, 32, 131, 2002, 2002, - 1888]) - self.assertEqual(c.segment[5].exponent, - [14, 14, 14, 14, 13, 13, 13, 12, 12, 12, 11, 11, 11, - 9, 9, 9, 9, 9, 9]) - - pargs = (RCME_ISO_8859_1, - "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) - self.verifyCMEsegment(c.segment[6], CMEsegment(*pargs)) - - self.verifySOTsegment(c.segment[7], SOTsegment(0, 262838, 0, 1)) - - # PPT: packed packet headers, tile-part header - self.assertEqual(c.segment[8].marker_id, 'PPT') - self.assertEqual(c.segment[8].zppt, 0) - - # SOD: start of data - self.assertEqual(c.segment[9].marker_id, 'SOD') - - # EOC: end of codestream - self.assertEqual(c.segment[10].marker_id, 'EOC') - - def test_NR_p1_03_dump(self): - jfile = opj_data_file('input/conformance/p1_03.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - kwargs = {'rsiz': 2, 'xysiz': (1024, 1024), 'xyosiz': (0, 0), - 'xytsiz': (1024, 1024), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8, 8), - 'signed': (False, False, False, False), - 'xyrsiz': [(1, 1, 2, 2), (1, 1, 2, 2)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.PCRL) - self.assertEqual(c.segment[2].layers, 10) # layers = 10 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 6) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) - self.verify_codeblock_style(c.segment[2].spcod[7], - [True, False, True, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # COC: Coding style component - self.assertEqual(c.segment[3].ccoc, 1) - self.assertEqual(c.segment[3].spcoc[0], 3) # level - self.assertEqual(tuple(c.segment[3].code_block_size), (32, 32)) - self.verify_codeblock_style(c.segment[3].spcoc[3], - [True, False, True, False, False, False]) - self.assertEqual(c.segment[3].spcoc[4], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - - # COC: Coding style component - self.assertEqual(c.segment[4].ccoc, 3) - self.assertEqual(c.segment[4].spcoc[0], 6) # level - self.assertEqual(tuple(c.segment[4].code_block_size), (32, 32)) - self.verify_codeblock_style(c.segment[4].spcoc[3], - [True, False, True, False, False, False]) - self.assertEqual(c.segment[4].spcoc[4], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[5].sqcd & 0x1f, 2) # expounded - self.assertEqual(c.segment[5].guard_bits, 3) - self.assertEqual(c.segment[5].mantissa, - [1814, 1815, 1815, 1817, 1821, 1821, 1827, 1845, 1845, - 1868, 1925, 1925, 2007, 32, 32, 131, 2002, 2002, - 1888]) - self.assertEqual(c.segment[5].exponent, - [16, 16, 16, 16, 15, 15, 15, 14, 14, 14, 13, 13, 13, - 11, 11, 11, 11, 11, 11]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[6].cqcc, 0) - self.assertEqual(c.segment[6].guard_bits, 3) - # quantization type - self.assertEqual(c.segment[6].sqcc & 0x1f, 1) # derived - self.assertEqual(c.segment[6].mantissa, [0]) - self.assertEqual(c.segment[6].exponent, [14]) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[7].cqcc, 3) - self.assertEqual(c.segment[7].guard_bits, 3) - # quantization type - self.assertEqual(c.segment[7].sqcc & 0x1f, 0) # none - self.assertEqual(c.segment[7].mantissa, [0] * 19) - self.assertEqual(c.segment[7].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, - 9, 9, 10]) - - pargs = (RCME_ISO_8859_1, - "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) - self.verifyCMEsegment(c.segment[8], CMEsegment(*pargs)) - - # PPM: packed packet headers, main header - self.assertEqual(c.segment[9].marker_id, 'PPM') - self.assertEqual(c.segment[9].zppm, 0) - - # TLM (tile-part length) - self.assertEqual(c.segment[10].ztlm, 0) - self.assertEqual(c.segment[10].ttlm, (0,)) - self.assertEqual(c.segment[10].ptlm, (1366780,)) - - self.verifySOTsegment(c.segment[11], SOTsegment(0, 1366780, 0, 1)) - - # SOD: start of data - self.assertEqual(c.segment[12].marker_id, 'SOD') - - # EOC: end of codestream - self.assertEqual(c.segment[13].marker_id, 'EOC') - - def test_NR_p1_04_dump(self): - jfile = opj_data_file('input/conformance/p1_04.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - kwargs = {'rsiz': 2, 'xysiz': (1024, 1024), 'xyosiz': (0, 0), - 'xytsiz': (128, 128), 'xytosiz': (0, 0), 'bitdepth': (12,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 3) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) # expounded - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, - [84, 423, 408, 435, 450, 435, 470, 549, 520, 618]) - self.assertEqual(c.segment[3].exponent, - [8, 10, 10, 10, 9, 9, 9, 8, 8, 8]) - - # TLM (tile-part length) - self.assertEqual(c.segment[4].ztlm, 0) - self.assertIsNone(c.segment[4].ttlm) - self.assertEqual(c.segment[4].ptlm, - (350, 356, 402, 245, 402, 564, 675, 283, 317, 299, - 330, 333, 346, 403, 839, 667, 328, 349, 274, 325, - 501, 561, 756, 710, 779, 620, 628, 675, 600, 66195, - 721, 719, 565, 565, 546, 586, 574, 641, 713, 634, - 573, 528, 544, 597, 771, 665, 624, 706, 568, 537, - 554, 546, 542, 635, 826, 667, 617, 606, 813, 586, - 641, 654, 669, 623)) - - pargs = (RCME_ISO_8859_1, "Created by Aware, Inc.".encode()) - self.verifyCMEsegment(c.segment[5], CMEsegment(*pargs)) - - self.verifySOTsegment(c.segment[6], SOTsegment(0, 350, 0, 1)) - - # SOD: start of data - self.assertEqual(c.segment[7].marker_id, 'SOD') - - self.verifySOTsegment(c.segment[8], SOTsegment(1, 356, 0, 1)) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[9].sqcd & 0x1f, 2) # expounded - self.assertEqual(c.segment[9].guard_bits, 2) - self.assertEqual(c.segment[9].mantissa, - [75, 1093, 1098, 1115, 1157, 1134, 1186, 1217, 1245, - 1248]) - self.assertEqual(c.segment[9].exponent, - [8, 10, 10, 10, 9, 9, 9, 8, 8, 8]) - - # SOD: start of data - self.assertEqual(c.segment[10].marker_id, 'SOD') - - self.verifySOTsegment(c.segment[11], SOTsegment(2, 402, 0, 1)) - - # and so on - - # There should be 64 SOD, SOT, QCD segments. - ids = [x.marker_id for x in c.segment if x.marker_id == 'SOT'] - self.assertEqual(len(ids), 64) - ids = [x.marker_id for x in c.segment if x.marker_id == 'SOD'] - self.assertEqual(len(ids), 64) - ids = [x.marker_id for x in c.segment if x.marker_id == 'QCD'] - self.assertEqual(len(ids), 64) - - # Tiles should be in order, right? - tiles = [x.isot for x in c.segment if x.marker_id == 'SOT'] - self.assertEqual(tiles, list(range(64))) - - # EOC: end of codestream - self.assertEqual(c.segment[-1].marker_id, 'EOC') - - def test_NR_p1_05_dump(self): - jfile = opj_data_file('input/conformance/p1_05.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - kwargs = {'rsiz': 2, 'xysiz': (529, 524), 'xyosiz': (17, 12), - 'xytsiz': (37, 37), 'xytosiz': (8, 2), - 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertTrue(c.segment[2].scod & 2) # sop - self.assertTrue(c.segment[2].scod & 4) # eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.PCRL) - self.assertEqual(c.segment[2].layers, 2) # levels = 2 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 7) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 8)) # cblk - self.verify_codeblock_style(c.segment[2].spcod[7], - [True, False, False, True, True, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(c.segment[2].precinct_size, [(16, 16)] * 8) - - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) # expounded - self.assertEqual(c.segment[3].guard_bits, 3) - self.assertEqual(c.segment[3].mantissa, - [1813, 1814, 1814, 1814, 1815, 1815, 1817, 1821, - 1821, 1827, 1845, 1845, 1868, 1925, 1925, 2007, - 32, 32, 131, 2002, 2002, 1888]) - self.assertEqual(c.segment[3].exponent, - [17, 17, 17, 17, 16, 16, 16, 15, 15, 15, 14, 14, - 14, 13, 13, 13, 11, 11, 11, 11, 11, 11]) - - pargs = (RCME_ISO_8859_1, - "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) - self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) - - # 225 consecutive PPM segments. - zppm = [x.zppm for x in c.segment[5:230]] - self.assertEqual(zppm, list(range(225))) - - self.verifySOTsegment(c.segment[230], SOTsegment(0, 580, 0, 1)) - - # 225 total SOT segments - isot = [x.isot for x in c.segment if x.marker_id == 'SOT'] - self.assertEqual(isot, list(range(225))) - - # scads of SOP, EPH segments - sop = [x.marker_id for x in c.segment if x.marker_id == 'SOP'] - eph = [x.marker_id for x in c.segment if x.marker_id == 'EPH'] - self.assertEqual(len(sop), 26472) - self.assertEqual(len(eph), 0) - - # EOC: end of codestream - self.assertEqual(c.segment[-1].marker_id, 'EOC') - - def test_NR_p1_06_dump(self): - jfile = opj_data_file('input/conformance/p1_06.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - kwargs = {'rsiz': 2, 'xysiz': (12, 12), 'xyosiz': (0, 0), - 'xytsiz': (3, 3), 'xytosiz': (0, 0), 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertTrue(c.segment[2].scod & 2) # sop - self.assertTrue(c.segment[2].scod & 4) # eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.PCRL) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 4) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (32, 64)) - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, True, False, True]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) # expounded - self.assertEqual(c.segment[3].guard_bits, 3) - self.assertEqual(c.segment[3].mantissa, - [1821, 1845, 1845, 1868, 1925, 1925, 2007, 32, - 32, 131, 2002, 2002, 1888]) - self.assertEqual(c.segment[3].exponent, - [14, 14, 14, 14, 13, 13, 13, 11, 11, 11, - 11, 11, 11]) - - pargs = (RCME_ISO_8859_1, - "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) - self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) - - self.verifySOTsegment(c.segment[5], SOTsegment(0, 349, 0, 1)) - - # PPT: packed packet headers, tile-part header - self.assertEqual(c.segment[6].marker_id, 'PPT') - self.assertEqual(c.segment[6].zppt, 0) - - # scads of SOP, EPH segments - - # 16 SOD segments - sods = [x for x in c.segment if x.marker_id == 'SOD'] - self.assertEqual(len(sods), 16) - - # 16 PPT segments - ppts = [x for x in c.segment if x.marker_id == 'PPT'] - self.assertEqual(len(ppts), 16) - - # 16 SOT segments - isots = [x.isot for x in c.segment if x.marker_id == 'SOT'] - self.assertEqual(isots, list(range(16))) - - # EOC: end of codestream - self.assertEqual(c.segment[-1].marker_id, 'EOC') - - def test_NR_p1_07_dump(self): - jfile = opj_data_file('input/conformance/p1_07.j2k') - c = Jp2k(jfile).get_codestream(header_only=False) - - kwargs = {'rsiz': 2, 'xysiz': (12, 12), 'xyosiz': (4, 0), - 'xytsiz': (12, 12), 'xytosiz': (4, 0), 'bitdepth': (8, 8), - 'signed': (False, False), - 'xyrsiz': [(4, 1), (1, 1)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertTrue(c.segment[2].scod & 2) # sop - self.assertTrue(c.segment[2].scod & 4) # eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.RPCL) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 1) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(c.segment[2].precinct_size, [(1, 1), (2, 2)]) - - # COC: Coding style component - self.assertEqual(c.segment[3].ccoc, 1) - self.assertEqual(c.segment[3].spcoc[0], 1) # level - self.assertEqual(tuple(c.segment[3].code_block_size), (64, 64)) - self.verify_codeblock_style(c.segment[3].spcoc[3], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[3].spcoc[4], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(c.segment[3].precinct_size, [(2, 2), (4, 4)]) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[4].sqcd & 0x1f, 0) # none - self.assertEqual(c.segment[4].guard_bits, 2) - self.assertEqual(c.segment[4].mantissa, [0] * 4) - self.assertEqual(c.segment[4].exponent, [8, 9, 9, 10]) - - pargs = (RCME_ISO_8859_1, - "Creator: AV-J2K (c) 2000,2001 Algo Vision".encode()) - self.verifyCMEsegment(c.segment[5], CMEsegment(*pargs)) - - self.verifySOTsegment(c.segment[6], SOTsegment(0, 434, 0, 1)) - - # scads of SOP, EPH segments - - # EOC: end of codestream - self.assertEqual(c.segment[-1].marker_id, 'EOC') - - def test_NR_00042_j2k_dump(self): - # Profile 3. - jfile = opj_data_file('input/nonregression/_00042.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream(header_only=False) - - kwargs = {'rsiz': 3, 'xysiz': (1920, 1080), 'xyosiz': (0, 0), - 'xytsiz': (1920, 1080), 'xytosiz': (0, 0), - 'bitdepth': (12, 12, 12), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.CPRL) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(c.segment[2].precinct_size[0], (128, 128)) - self.assertEqual(c.segment[2].precinct_size[1:], [(256, 256)] * 5) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, - [1824, 1776, 1776, 1728, 1792, 1792, 1760, 1872, - 1872, 1896, 5, 5, 71, 2003, 2003, 1890]) - self.assertEqual(c.segment[3].exponent, - [18, 18, 18, 18, 17, 17, 17, 16, - 16, 16, 14, 14, 14, 14, 14, 14]) - - # COC: Coding style component - self.assertEqual(c.segment[4].ccoc, 1) - self.assertEqual(c.segment[4].spcoc[0], 5) # level - self.assertEqual(tuple(c.segment[4].code_block_size), (32, 32)) - self.verify_codeblock_style(c.segment[4].spcoc[3], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[4].spcoc[4], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[5].cqcc, 1) - self.assertEqual(c.segment[5].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[5].sqcc & 0x1f, 2) - self.assertEqual(c.segment[5].mantissa, - [1824, 1776, 1776, 1728, 1792, 1792, 1760, 1872, - 1872, 1896, 5, 5, 71, 2003, 2003, 1890]) - self.assertEqual(c.segment[5].exponent, - [18, 18, 18, 18, 17, 17, 17, 16, 16, 16, 14, 14, 14, - 14, 14, 14]) - - # COC: Coding style component - self.assertEqual(c.segment[6].ccoc, 2) - self.assertEqual(c.segment[6].spcoc[0], 5) # level - self.assertEqual(tuple(c.segment[6].code_block_size), (32, 32)) - self.verify_codeblock_style(c.segment[6].spcoc[3], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[6].spcoc[4], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - - # QCC: Quantization component - # associated component - self.assertEqual(c.segment[7].cqcc, 2) - self.assertEqual(c.segment[7].guard_bits, 2) - # quantization type - self.assertEqual(c.segment[7].sqcc & 0x1f, 2) # none - self.assertEqual(c.segment[7].mantissa, - [1824, 1776, 1776, 1728, 1792, 1792, 1760, 1872, - 1872, 1896, 5, 5, 71, 2003, 2003, 1890]) - self.assertEqual(c.segment[7].exponent, - [18, 18, 18, 18, 17, 17, 17, 16, 16, 16, 14, 14, - 14, 14, 14, 14]) - - pargs = (RCME_ISO_8859_1, "Created by OpenJPEG version 1.3.0".encode()) - self.verifyCMEsegment(c.segment[8], CMEsegment(*pargs)) - - # TLM (tile-part length) - self.assertEqual(c.segment[9].ztlm, 0) - self.assertEqual(c.segment[9].ttlm, (0, 0, 0)) - self.assertEqual(c.segment[9].ptlm, (45274, 20838, 8909)) - - # 3 tiles, one for each component - idx = [x.isot for x in c.segment if x.marker_id == 'SOT'] - self.assertEqual(idx, [0, 0, 0]) - lens = [x.psot for x in c.segment if x.marker_id == 'SOT'] - self.assertEqual(lens, [45274, 20838, 8909]) - tpsot = [x.tpsot for x in c.segment if x.marker_id == 'SOT'] - self.assertEqual(tpsot, [0, 1, 2]) - - sods = [x for x in c.segment if x.marker_id == 'SOD'] - self.assertEqual(len(sods), 3) - - # EOC: end of codestream - self.assertEqual(c.segment[-1].marker_id, 'EOC') - - def test_Bretagne2_j2k_dump(self): - jfile = opj_data_file('input/nonregression/Bretagne2.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream(header_only=False) - - kwargs = {'rsiz': 0, 'xysiz': (2592, 1944), 'xyosiz': (0, 0), - 'xytsiz': (640, 480), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 3) # layers = 3 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(c.segment[2].precinct_size, - [(16, 16), (32, 32), (64, 64), (128, 128), - (128, 128), (128, 128)]) - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] - expected += ['SOT', 'COC', 'QCC', 'COC', 'QCC', 'SOD'] * 25 - expected += ['EOC'] - self.assertEqual(ids, expected) - - def test_NR_buxI_j2k_dump(self): - jfile = opj_data_file('input/nonregression/buxI.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream(header_only=False) - - kwargs = {'rsiz': 0, 'xysiz': (512, 512), 'xyosiz': (0, 0), - 'xytsiz': (512, 512), 'xytosiz': (0, 0), 'bitdepth': (16,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 2) # layers = 2 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME', 'SOT', 'SOD', 'EOC'] - self.assertEqual(ids, expected) - - def test_NR_buxR_j2k_dump(self): - jfile = opj_data_file('input/nonregression/buxR.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream(header_only=False) - - kwargs = {'rsiz': 0, 'xysiz': (512, 512), 'xyosiz': (0, 0), - 'xytsiz': (512, 512), 'xytosiz': (0, 0), 'bitdepth': (16,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 2) # layers = 2 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME', 'SOT', 'SOD', 'EOC'] - self.assertEqual(ids, expected) - - def test_NR_Cannotreaddatawithnosizeknown_j2k(self): - lst = ['input', 'nonregression', - 'Cannotreaddatawithnosizeknown.j2k'] - path = '/'.join(lst) - - jfile = opj_data_file(path) - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - kwargs = {'rsiz': 0, 'xysiz': (1420, 1416), 'xyosiz': (0, 0), - 'xytsiz': (1420, 1416), 'xytosiz': (0, 0), 'bitdepth': (16,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 11) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 4) - self.assertEqual(c.segment[3].mantissa, [0] * 34) - self.assertEqual(c.segment[3].exponent, [16] + [17, 17, 18] * 11) - - def test_NR_CT_Phillips_JPEG2K_Decompr_Problem_dump(self): - jfile = opj_data_file('input/nonregression/' - + 'CT_Phillips_JPEG2K_Decompr_Problem.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] - self.assertEqual(ids, expected) - - kwargs = {'rsiz': 0, 'xysiz': (512, 614), 'xyosiz': (0, 0), - 'xytsiz': (512, 614), 'xytosiz': (0, 0), 'bitdepth': (12,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) - self.assertEqual(c.segment[3].guard_bits, 1) - self.assertEqual(c.segment[3].mantissa, - [442, 422, 422, 403, 422, 422, 403, 472, 472, 487, - 591, 591, 676, 558, 558, 485]) - self.assertEqual(c.segment[3].exponent, - [22, 22, 22, 22, 21, 21, 21, 20, 20, 20, 19, 19, 19, - 18, 18, 18]) - - pargs = (RCME_ISO_8859_1, "Kakadu-3.2".encode()) - self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) - - def test_NR_cthead1_dump(self): - jfile = opj_data_file('input/nonregression/cthead1.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME', 'CME'] - self.assertEqual(ids, expected) - - kwargs = {'rsiz': 0, 'xysiz': (256, 256), 'xyosiz': (0, 0), - 'xytsiz': (256, 256), 'xytosiz': (0, 0), 'bitdepth': (8,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 1) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [9, 10, 10, 11, 10, 10, 11, 10, 10, 11, 10, 10, 10, - 9, 9, 10]) - - pargs = (RCME_ISO_8859_1, "Kakadu-v6.3.1".encode()) - self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) - - def test_NR_illegalcolortransform_dump(self): - jfile = opj_data_file('input/nonregression/illegalcolortransform.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - kwargs = {'rsiz': 0, 'xysiz': (1420, 1416), 'xyosiz': (0, 0), - 'xytsiz': (1420, 1416), 'xytosiz': (0, 0), 'bitdepth': (16,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 11) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 4) - self.assertEqual(c.segment[3].mantissa, [0] * 34) - self.assertEqual(c.segment[3].exponent, [16] + [17, 17, 18] * 11) - - def test_NR_j2k32_dump(self): - jfile = opj_data_file('input/nonregression/j2k32.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] - self.assertEqual(ids, expected) - - kwargs = {'rsiz': 0, 'xysiz': (256, 256), 'xyosiz': (0, 0), - 'xytsiz': (256, 256), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), - 'signed': (True, True, True), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - # quantization type - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, [8, 9, 9, 10, 9, 9, 10, 9, 9, - 10, 9, 9, 10, 9, 9, 10]) - - pargs = (RCME_BINARY, c.segment[4].ccme) - self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) - - def test_NR_kakadu_v4_4_openjpegv2_broken_dump(self): - jfile = opj_data_file('input/nonregression/' - + 'kakadu_v4-4_openjpegv2_broken.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - kwargs = {'rsiz': 0, 'xysiz': (2048, 2500), 'xyosiz': (0, 0), - 'xytsiz': (2048, 2500), 'xytosiz': (0, 0), 'bitdepth': (16,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 12) # layers = 12 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 8) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 1) - self.assertEqual(c.segment[3].mantissa, [0] * 25) - self.assertEqual(c.segment[3].exponent, - [17, 18, 18, 19, 18, 18, 19, 18, 18, 19, 18, 18, 19, - 18, 18, 19, 18, 18, 19, 18, 18, 19, 18, 18, 19]) - - pargs = (RCME_ISO_8859_1, "Kakadu-v4.4".encode()) - self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) - - ccme = "Kdu-Layer-Info: log_2{Delta-D(MSE)/[2^16*Delta-L(bytes)]}," - ccme += " L(bytes)\n" - ccme += " -65.4, 6.8e+004\n" - ccme += " -66.3, 1.0e+005\n" - ccme += " -67.3, 2.0e+005\n" - ccme += " -68.5, 4.1e+005\n" - ccme += " -69.0, 5.1e+005\n" - ccme += " -69.5, 5.9e+005\n" - ccme += " -69.7, 6.8e+005\n" - ccme += " -70.3, 8.2e+005\n" - ccme += " -70.8, 1.0e+006\n" - ccme += " -71.9, 1.4e+006\n" - ccme += " -73.8, 2.0e+006\n" - ccme += "-256.0, 3.7e+006\n" - pargs = (RCME_ISO_8859_1, ccme.encode()) - self.verifyCMEsegment(c.segment[5], CMEsegment(*pargs)) - - def test_NR_MarkerIsNotCompliant_j2k_dump(self): - jfile = opj_data_file('input/nonregression/MarkerIsNotCompliant.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - kwargs = {'rsiz': 0, 'xysiz': (1420, 1416), 'xyosiz': (0, 0), - 'xytsiz': (1420, 1416), 'xytosiz': (0, 0), 'bitdepth': (16,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 11) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 4) - self.assertEqual(c.segment[3].mantissa, [0] * 34) - self.assertEqual(c.segment[3].exponent, - [16, 17, 17, 18, 17, 17, 18, 17, 17, 18, 17, 17, 18, - 17, 17, 18, 17, 17, 18, 17, 17, 18, 17, 17, 18, 17, - 17, 18, 17, 17, 18, 17, 17, 18]) - - def test_NR_movie_00000(self): - jfile = opj_data_file('input/nonregression/movie_00000.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - kwargs = {'rsiz': 0, 'xysiz': (1920, 1080), 'xyosiz': (0, 0), - 'xytsiz': (1920, 1080), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - - def test_NR_movie_00001(self): - jfile = opj_data_file('input/nonregression/movie_00001.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - kwargs = {'rsiz': 0, 'xysiz': (1920, 1080), 'xyosiz': (0, 0), - 'xytsiz': (1920, 1080), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - - def test_NR_movie_00002(self): - jfile = opj_data_file('input/nonregression/movie_00002.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - kwargs = {'rsiz': 0, 'xysiz': (1920, 1080), 'xyosiz': (0, 0), - 'xytsiz': (1920, 1080), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - - def test_NR_orb_blue10_lin_j2k_dump(self): - jfile = opj_data_file('input/nonregression/orb-blue10-lin-j2k.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - kwargs = {'rsiz': 0, 'xysiz': (117, 117), 'xyosiz': (0, 0), - 'xytsiz': (117, 117), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8, 8), - 'signed': (False, False, False, False), - 'xyrsiz': [(1, 1, 1, 1), (1, 1, 1, 1)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - - def test_NR_orb_blue10_win_j2k_dump(self): - jfile = opj_data_file('input/nonregression/orb-blue10-win-j2k.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - kwargs = {'rsiz': 0, 'xysiz': (117, 117), 'xyosiz': (0, 0), - 'xytsiz': (117, 117), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8, 8), - 'signed': (False, False, False, False), - 'xyrsiz': [(1, 1, 1, 1), (1, 1, 1, 1)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - - def test_NR_pacs_ge_j2k_dump(self): - jfile = opj_data_file('input/nonregression/pacs.ge.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] - self.assertEqual(ids, expected) - - kwargs = {'rsiz': 0, 'xysiz': (512, 512), 'xyosiz': (0, 0), - 'xytsiz': (512, 512), 'xytosiz': (0, 0), - 'bitdepth': (16,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 16) # layers = 16 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 1) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [18, 19, 19, 20, 19, 19, 20, 19, 19, 20, 19, 19, 20, - 19, 19, 20]) - - pargs = (RCME_ISO_8859_1, "Kakadu-2.0.2".encode()) - self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) - - def test_NR_test_lossless_j2k_dump(self): - jfile = opj_data_file('input/nonregression/test_lossless.j2k') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] - self.assertEqual(ids, expected) - - kwargs = {'rsiz': 0, 'xysiz': (1024, 1024), 'xyosiz': (0, 0), - 'xytsiz': (1024, 1024), 'xytosiz': (0, 0), - 'bitdepth': (12,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [12, 13, 13, 14, 13, 13, 14, 13, 13, 14, 13, 13, 14, - 13, 13, 14]) - - pargs = (RCME_ISO_8859_1, "ClearCanvas DICOM OpenJPEG".encode()) - self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) - - def test_NR_123_j2c_dump(self): - jfile = opj_data_file('input/nonregression/123.j2c') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - kwargs = {'rsiz': 0, 'xysiz': (1800, 1800), 'xyosiz': (0, 0), - 'xytsiz': (1800, 1800), 'xytosiz': (0, 0), - 'bitdepth': (16,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 11) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 4) - self.assertEqual(c.segment[3].mantissa, [0] * 34) - self.assertEqual(c.segment[3].exponent, - [16] + [17, 17, 18] * 11) - - def test_NR_bug_j2c_dump(self): - jfile = opj_data_file('input/nonregression/bug.j2c') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - kwargs = {'rsiz': 0, 'xysiz': (1800, 1800), 'xyosiz': (0, 0), - 'xytsiz': (1800, 1800), 'xytosiz': (0, 0), - 'bitdepth': (16,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 11) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (64, 64)) - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 4) - self.assertEqual(c.segment[3].mantissa, [0] * 34) - self.assertEqual(c.segment[3].exponent, - [16] + [17, 17, 18] * 11) - - def test_NR_kodak_2layers_lrcp_j2c_dump(self): - jfile = opj_data_file('input/nonregression/kodak_2layers_lrcp.j2c') - jp2k = Jp2k(jfile) - c = jp2k.get_codestream() - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] - self.assertEqual(ids, expected) - - kwargs = {'rsiz': 0, 'xysiz': (2048, 1556), 'xyosiz': (0, 0), - 'xytsiz': (2048, 1556), 'xytosiz': (0, 0), - 'bitdepth': (12, 12, 12), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 2) # layers = 2 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(c.segment[2].precinct_size, - [(128, 128)] + [(256, 256)] * 5) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [13, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, - 13, 13, 13]) - - pargs = (RCME_ISO_8859_1, "DCP-Werkstatt".encode()) - self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) - - def test_NR_issue104_jpxstream_dump(self): - jfile = opj_data_file('input/nonregression/issue104_jpxstream.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[3].box] - self.assertEqual(ids, ['ihdr', 'colr', 'pclr', 'cmap']) - - self.verifySignatureBox(jp2.box[0]) - self.verify_filetype_box(jp2.box[1], - FileTypeBox(compatibility_list=['jp2 ', - 'jpxb', - 'jpx '])) - - # Reader requirements talk. - # unrestricted jpeg 2000 part 1 - self.assertTrue(5 in jp2.box[2].standard_flag) - - ihdr = glymur.jp2box.ImageHeaderBox(203, 479, colorspace_unknown=True) - self.verifyImageHeaderBox(jp2.box[3].box[0], ihdr) - - colr = glymur.jp2box.ColourSpecificationBox(colorspace=SRGB, - approximation=1, - precedence=2) - self.verifyColourSpecificationBox(jp2.box[3].box[1], colr) - - # Jp2 Header - # Palette box. - self.assertEqual(jp2.box[3].box[2].palette.shape, (256, 3)) - - # Jp2 Header - # Component mapping box - self.assertEqual(jp2.box[3].box[3].component_index, (0, 0, 0)) - self.assertEqual(jp2.box[3].box[3].mapping_type, (1, 1, 1)) - self.assertEqual(jp2.box[3].box[3].palette_index, (0, 1, 2)) - - c = jp2.box[4].codestream - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - kwargs = {'rsiz': 0, 'xysiz': (479, 203), 'xyosiz': (0, 0), - 'xytsiz': (256, 203), 'xytosiz': (0, 0), - 'bitdepth': (8,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, [8] + [9, 9, 10] * 5) - - def test_NR_issue206_image_000_dump(self): - jfile = opj_data_file('input/nonregression/issue206_image-000.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[3].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - self.verifySignatureBox(jp2.box[0]) - self.verify_filetype_box(jp2.box[1], - FileTypeBox(compatibility_list=['jp2 ', - 'jpxb', - 'jpx '])) - - # Reader requirements talk. - # unrestricted jpeg 2000 part 1 - self.assertTrue(5 in jp2.box[2].standard_flag) - - ihdr = glymur.jp2box.ImageHeaderBox(326, 431, - num_components=3, - colorspace_unknown=True) - self.verifyImageHeaderBox(jp2.box[3].box[0], ihdr) - - colr = glymur.jp2box.ColourSpecificationBox(colorspace=SRGB, - approximation=1, - precedence=2) - self.verifyColourSpecificationBox(jp2.box[3].box[1], colr) - - c = jp2.box[4].codestream - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - kwargs = {'rsiz': 0, 'xysiz': (431, 326), 'xyosiz': (0, 0), - 'xytsiz': (256, 256), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), (32, 32)) - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, [8] + [9, 9, 10] * 5) - - def test_NR_Marrin_jp2_dump(self): - jfile = opj_data_file('input/nonregression/Marrin.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr', 'cdef', 'res ']) - - ids = [box.box_id for box in jp2.box[2].box[3].box] - self.assertEqual(ids, ['resd']) - - self.verifySignatureBox(jp2.box[0]) - self.verify_filetype_box(jp2.box[1], FileTypeBox()) - - ihdr = glymur.jp2box.ImageHeaderBox(135, 135, num_components=2, - colorspace_unknown=True) - self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - - colr = glymur.jp2box.ColourSpecificationBox(colorspace=GREYSCALE) - self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) - - # Jp2 Header - # Channel Definition - self.assertEqual(jp2.box[2].box[2].index, (0, 1)) - self.assertEqual(jp2.box[2].box[2].channel_type, (0, 1)) # opacity - self.assertEqual(jp2.box[2].box[2].association, (0, 0)) # both main - - c = jp2.box[3].codestream - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] - self.assertEqual(ids, expected) - - kwargs = {'rsiz': 0, 'xysiz': (135, 135), 'xyosiz': (0, 0), - 'xytsiz': (135, 135), 'xytosiz': (0, 0), - 'bitdepth': (8, 8), - 'signed': (False, False), - 'xyrsiz': [(1, 1), (1, 1)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 2) # layers = 2 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) - self.assertEqual(c.segment[3].guard_bits, 1) - self.assertEqual(c.segment[3].mantissa, - [1822, 1770, 1770, 1724, 1792, 1792, 1762, 1868, 1868, - 1892, 3, 3, 69, 2002, 2002, 1889]) - self.assertEqual(c.segment[3].exponent, - [14] * 4 + [13] * 3 + [12] * 3 + [10] * 6) - - pargs = (RCME_ISO_8859_1, "Kakadu-v5.2.1".encode()) - self.verifyCMEsegment(c.segment[4], CMEsegment(*pargs)) - - def test_NR_mem_b2b86b74_2753_dump(self): - jfile = opj_data_file('input/nonregression/mem-b2b86b74-2753.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[3].box] - self.assertEqual(ids, ['ihdr', 'colr', 'pclr', 'cmap']) - - self.verifySignatureBox(jp2.box[0]) - self.verify_filetype_box(jp2.box[1], - FileTypeBox(compatibility_list=['jp2 ', - 'jpxb', - 'jpx '])) - - # Reader requirements talk. - # unrestricted jpeg 2000 part 1 - self.assertTrue(5 in jp2.box[2].standard_flag) - - ihdr = glymur.jp2box.ImageHeaderBox(46, 124, bits_per_component=4, - colorspace_unknown=True) - self.verifyImageHeaderBox(jp2.box[3].box[0], ihdr) - - method = ENUMERATED_COLORSPACE - colr = glymur.jp2box.ColourSpecificationBox(colorspace=SRGB, - method=method, - approximation=1, - precedence=2) - self.verifyColourSpecificationBox(jp2.box[3].box[1], colr) - - # Jp2 Header - # Palette box. - # 3 columns with 16 entries. - self.assertEqual(jp2.box[3].box[2].palette.shape, (16, 3)) - - # Jp2 Header - # Component mapping box - self.assertEqual(jp2.box[3].box[3].component_index, (0, 0, 0)) - self.assertEqual(jp2.box[3].box[3].mapping_type, (1, 1, 1)) - self.assertEqual(jp2.box[3].box[3].palette_index, (0, 1, 2)) - - c = jp2.box[4].codestream - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - kwargs = {'rsiz': 0, 'xysiz': (124, 46), 'xyosiz': (0, 0), - 'xytsiz': (124, 46), 'xytosiz': (0, 0), - 'bitdepth': (4,), - 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.RLCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (32, 32)) # cblk - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, [4] + [5, 5, 6] * 5) - - def test_NR_merged_dump(self): - jfile = opj_data_file('input/nonregression/merged.jp2') - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - self.verifySignatureBox(jp2.box[0]) - self.verify_filetype_box(jp2.box[1], FileTypeBox()) - - ihdr = glymur.jp2box.ImageHeaderBox(576, 766, num_components=3) - self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - - colr = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.YCC) - self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) - - c = jp2.box[3].codestream - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'POD'] - self.assertEqual(ids, expected) - - kwargs = {'rsiz': 0, 'xysiz': (766, 576), 'xyosiz': (0, 0), - 'xytsiz': (766, 576), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 2, 2), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (32, 128)) # cblk - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 1) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, [8] + [9, 9, 10] * 5) - - # POD: progression order change - self.assertEqual(c.segment[4].rspod, (0, 0)) - self.assertEqual(c.segment[4].cspod, (0, 1)) - self.assertEqual(c.segment[4].lyepod, (1, 1)) - self.assertEqual(c.segment[4].repod, (6, 6)) - self.assertEqual(c.segment[4].cdpod, (1, 3)) - - podvals = (glymur.core.LRCP, glymur.core.LRCP) - self.assertEqual(c.segment[4].ppod, podvals) - - -@unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") -class TestSuiteWarns(MetadataBase): - - @unittest.skipIf(re.match("1.5|2.0.0", glymur.version.openjpeg_version), - "Test not passing on 1.5, 2.0: not introduced until 2.x") - def test_NR_DEC_issue188_beach_64bitsbox_jp2_41_decode(self): - """ - Has an 'XML ' box instead of 'xml '. Just verify we can read it. - """ - relpath = 'input/nonregression/issue188_beach_64bitsbox.jp2' - jfile = opj_data_file(relpath) - with self.assertWarns(UserWarning): - Jp2k(jfile)[:] - self.assertTrue(True) - - def test_NR_broken4_jp2_dump(self): - jfile = opj_data_file('input/nonregression/broken4.jp2') - with warnings.catch_warnings(): - # Suppress a warning, all we really care is parsing the entire - # file. - warnings.simplefilter("ignore") - with self.assertWarns(UserWarning): - jp2 = Jp2k(jfile) - self.assertEqual(jp2.box[-1].codestream.segment[-1].marker_id, - 'QCC') - - def test_NR_broken2_jp2_dump(self): - """ - Invalid marker ID in the codestream. - """ - jfile = opj_data_file('input/nonregression/broken2.jp2') - with warnings.catch_warnings(): - # Suppress a warning, all we really care is parsing the entire - # file. - warnings.simplefilter("ignore") - with self.assertWarns(UserWarning): - # Invalid marker ID on codestream. - jp2 = Jp2k(jfile) - self.assertEqual(jp2.box[-1].codestream.segment[-1].marker_id, - 'QCC') - - def test_NR_file1_dump(self): - jfile = opj_data_file('input/conformance/file1.jp2') - with self.assertWarns(UserWarning): - # Bad compatibility list item. - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'xml ', 'jp2h', 'xml ', - 'jp2c']) - - ids = [box.box_id for box in jp2.box[3].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - self.verifySignatureBox(jp2.box[0]) - self.verify_filetype_box(jp2.box[1], FileTypeBox()) - - # XML box - tags = [x.tag for x in jp2.box[2].xml.getroot()] - self.assertEqual(tags, - ['{http://www.jpeg.org/jpx/1.0/xml}' - + 'GENERAL_CREATION_INFO']) - - ihdr = glymur.jp2box.ImageHeaderBox(512, 768, num_components=3) - self.verifyImageHeaderBox(jp2.box[3].box[0], ihdr) - - colr = glymur.jp2box.ColourSpecificationBox(colorspace=SRGB, - approximation=1) - self.verifyColourSpecificationBox(jp2.box[3].box[1], colr) - - # XML box - tags = [x.tag for x in jp2.box[4].xml.getroot()] - self.assertEqual(tags, ['{http://www.jpeg.org/jpx/1.0/xml}CAPTION', - '{http://www.jpeg.org/jpx/1.0/xml}LOCATION', - '{http://www.jpeg.org/jpx/1.0/xml}EVENT']) - - def test_NR_file2_dump(self): - jfile = opj_data_file('input/conformance/file2.jp2') - with self.assertWarns(UserWarning): - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr', 'cdef']) - - self.verifySignatureBox(jp2.box[0]) - self.verify_filetype_box(jp2.box[1], FileTypeBox()) - - ihdr = glymur.jp2box.ImageHeaderBox(640, 480, num_components=3) - self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - - colr = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.YCC, - approximation=1) - self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) - - # Jp2 Header - # Channel Definition - self.assertEqual(jp2.box[2].box[2].index, (0, 1, 2)) - self.assertEqual(jp2.box[2].box[2].channel_type, (0, 0, 0)) # color - self.assertEqual(jp2.box[2].box[2].association, (3, 2, 1)) # reverse - - def test_NR_file3_dump(self): - # Three 8-bit components in the sRGB-YCC colourspace, with the Cb and - # Cr components being subsampled 2x in both the horizontal and - # vertical directions. The components are stored in the standard - # order. - jfile = opj_data_file('input/conformance/file3.jp2') - with self.assertWarns(UserWarning): - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - self.verifySignatureBox(jp2.box[0]) - self.verify_filetype_box(jp2.box[1], FileTypeBox()) - - ihdr = glymur.jp2box.ImageHeaderBox(640, 480, num_components=3) - self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - - colr = glymur.jp2box.ColourSpecificationBox(colorspace=glymur.core.YCC, - approximation=1) - self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) - - # sub-sampling - codestream = jp2.get_codestream() - self.assertEqual(codestream.segment[1].xrsiz[0], 1) - self.assertEqual(codestream.segment[1].yrsiz[0], 1) - self.assertEqual(codestream.segment[1].xrsiz[1], 2) - self.assertEqual(codestream.segment[1].yrsiz[1], 2) - self.assertEqual(codestream.segment[1].xrsiz[2], 2) - self.assertEqual(codestream.segment[1].yrsiz[2], 2) - - def test_NR_file4_dump(self): - # One 8-bit component in the grey colourspace. - jfile = opj_data_file('input/conformance/file4.jp2') - with self.assertWarns(UserWarning): - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - self.verifySignatureBox(jp2.box[0]) - self.verify_filetype_box(jp2.box[1], FileTypeBox()) - - ihdr = glymur.jp2box.ImageHeaderBox(512, 768) - self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - - colr = glymur.jp2box.ColourSpecificationBox(colorspace=GREYSCALE, - approximation=1) - self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) - - def test_NR_file5_dump(self): - # Three 8-bit components in the ROMM-RGB colourspace, encapsulated in a - # JPX file. The components have been transformed using - # the RCT. The colourspace is specified using both a Restricted ICC - # profile and using the JPX-defined enumerated code for the ROMM-RGB - # colourspace. - jfile = opj_data_file('input/conformance/file5.jp2') - with self.assertWarns(UserWarning): - # There's a warning for an unknown compatibility entry. - # Ignore it here. - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[3].box] - self.assertEqual(ids, ['ihdr', 'colr', 'colr']) - - self.verifySignatureBox(jp2.box[0]) - expected = FileTypeBox(brand='jpx ', - compatibility_list=['jp2 ', 'jpx ', 'jpxb']) - self.verify_filetype_box(jp2.box[1], expected) - - ihdr = glymur.jp2box.ImageHeaderBox(512, 768, num_components=3) - self.verifyImageHeaderBox(jp2.box[3].box[0], ihdr) - - method = RESTRICTED_ICC_PROFILE - icc_profile = bytes([0] * 546) - colr = glymur.jp2box.ColourSpecificationBox(method=method, - approximation=1, - icc_profile=icc_profile) - self.verifyColourSpecificationBox(jp2.box[3].box[1], colr) - self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 546) - - def test_NR_file6_dump(self): - jfile = opj_data_file('input/conformance/file6.jp2') - with self.assertWarns(UserWarning): - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - self.verifySignatureBox(jp2.box[0]) - self.verify_filetype_box(jp2.box[1], FileTypeBox()) - - ihdr = glymur.jp2box.ImageHeaderBox(512, 768, bits_per_component=12) - self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - - method = ENUMERATED_COLORSPACE - colr = glymur.jp2box.ColourSpecificationBox(colorspace=GREYSCALE, - method=method, - approximation=1) - self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) - - def test_NR_file7_dump(self): - # Three 16-bit components in the e-sRGB colourspace, encapsulated in a - # JP2 compatible JPX file. The components have been transformed using - # the RCT. The colourspace is specified using both a Restricted ICC - # profile and using the JPX-defined enumerated code for the e-sRGB - # colourspace. - jfile = opj_data_file('input/conformance/file7.jp2') - with self.assertWarns(UserWarning): - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[3].box] - self.assertEqual(ids, ['ihdr', 'colr', 'colr']) - - self.verifySignatureBox(jp2.box[0]) - - # File type box. - self.assertEqual(jp2.box[1].brand, 'jpx ') - self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') - - ihdr = glymur.jp2box.ImageHeaderBox(640, 480, - num_components=3, - bits_per_component=16) - self.verifyImageHeaderBox(jp2.box[3].box[0], ihdr) - - method = RESTRICTED_ICC_PROFILE - colr = glymur.jp2box.ColourSpecificationBox(method=method, - approximation=1) - self.verifyColourSpecificationBox(jp2.box[3].box[1], colr) - self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 13332) - - def test_NR_file8_dump(self): - # One 8-bit component in a gamma 1.8 space. The colourspace is - # specified using a Restricted ICC profile. - jfile = opj_data_file('input/conformance/file8.jp2') - with self.assertWarns(UserWarning): - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'xml ', 'jp2c', - 'xml ']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - self.verifySignatureBox(jp2.box[0]) - self.verify_filetype_box(jp2.box[1], FileTypeBox()) - - ihdr = glymur.jp2box.ImageHeaderBox(400, 700) - self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - - method = RESTRICTED_ICC_PROFILE - colr = glymur.jp2box.ColourSpecificationBox(method=method, - approximation=1) - self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) - self.assertEqual(jp2.box[2].box[1].icc_profile['Size'], 414) - - # XML box - tags = [x.tag for x in jp2.box[3].xml.getroot()] - self.assertEqual(tags, - ['{http://www.jpeg.org/jpx/1.0/xml}' - + 'GENERAL_CREATION_INFO']) - - # XML box - tags = [x.tag for x in jp2.box[5].xml.getroot()] - self.assertEqual(tags, - ['{http://www.jpeg.org/jpx/1.0/xml}CAPTION', - '{http://www.jpeg.org/jpx/1.0/xml}LOCATION', - '{http://www.jpeg.org/jpx/1.0/xml}THING', - '{http://www.jpeg.org/jpx/1.0/xml}EVENT']) - - def test_NR_file9_dump(self): - # Colormap - jfile = opj_data_file('input/conformance/file9.jp2') - with self.assertWarns(UserWarning): - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'pclr', 'cmap', 'colr']) - - self.verifySignatureBox(jp2.box[0]) - self.verify_filetype_box(jp2.box[1], FileTypeBox()) - - ihdr = glymur.jp2box.ImageHeaderBox(512, 768) - self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - - # Palette box. - self.assertEqual(jp2.box[2].box[1].palette.shape, (256, 3)) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[0, 0], 0) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[0, 1], 0) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[0, 2], 0) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[128, 0], 73) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[128, 1], 92) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[128, 2], 53) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[255, 0], 245) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[255, 1], 245) - np.testing.assert_array_equal(jp2.box[2].box[1].palette[255, 2], 245) - - # Component mapping box - self.assertEqual(jp2.box[2].box[2].component_index, (0, 0, 0)) - self.assertEqual(jp2.box[2].box[2].mapping_type, (1, 1, 1)) - self.assertEqual(jp2.box[2].box[2].palette_index, (0, 1, 2)) - - colr = glymur.jp2box.ColourSpecificationBox(colorspace=SRGB, - approximation=1) - self.verifyColourSpecificationBox(jp2.box[2].box[3], colr) - - def test_NR_issue188_beach_64bitsbox(self): - lst = ['input', 'nonregression', 'issue188_beach_64bitsbox.jp2'] - jfile = opj_data_file('/'.join(lst)) - with self.assertWarns(UserWarning): - # There's a warning for an unknown box. - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', b'XML ', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - self.verifySignatureBox(jp2.box[0]) - self.verify_filetype_box(jp2.box[1], FileTypeBox()) - - ihdr = glymur.jp2box.ImageHeaderBox(200, 200, - num_components=3, - colorspace_unknown=True) - self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - - cspace = glymur.core.SRGB - colr = glymur.jp2box.ColourSpecificationBox(colorspace=cspace) - self.verifyColourSpecificationBox(jp2.box[2].box[1], colr) - - # Skip the 4th box, it is uknown. - - c = jp2.box[4].codestream - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME', 'CME'] - self.assertEqual(ids, expected) - - kwargs = {'rsiz': 0, 'xysiz': (200, 200), 'xyosiz': (0, 0), - 'xytsiz': (200, 200), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), - 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 2) - self.assertEqual(c.segment[3].guard_bits, 1) - - def test_NR_orb_blue10_lin_jp2_dump(self): - jfile = opj_data_file('input/nonregression/orb-blue10-lin-jp2.jp2') - with self.assertWarns(UserWarning): - # This file has an invalid ICC profile - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - self.verifySignatureBox(jp2.box[0]) - self.verify_filetype_box(jp2.box[1], FileTypeBox()) - - ihdr = glymur.jp2box.ImageHeaderBox(117, 117, num_components=4) - self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.RESTRICTED_ICC_PROFILE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 - self.assertIsNone(jp2.box[2].box[1].icc_profile) - self.assertIsNone(jp2.box[2].box[1].colorspace) - - c = jp2.box[3].codestream - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - kwargs = {'rsiz': 0, 'xysiz': (117, 117), 'xyosiz': (0, 0), - 'xytsiz': (117, 117), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8, 8), - 'signed': (False, False, False, False), - 'xyrsiz': [(1, 1, 1, 1), (1, 1, 1, 1)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - - def test_NR_orb_blue10_win_jp2_dump(self): - jfile = opj_data_file('input/nonregression/orb-blue10-win-jp2.jp2') - with self.assertWarns(UserWarning): - # This file has an invalid ICC profile - jp2 = Jp2k(jfile) - - ids = [box.box_id for box in jp2.box] - self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) - - ids = [box.box_id for box in jp2.box[2].box] - self.assertEqual(ids, ['ihdr', 'colr']) - - self.verifySignatureBox(jp2.box[0]) - self.verify_filetype_box(jp2.box[1], FileTypeBox()) - - ihdr = glymur.jp2box.ImageHeaderBox(117, 117, num_components=4) - self.verifyImageHeaderBox(jp2.box[2].box[0], ihdr) - - # Jp2 Header - # Colour specification - self.assertEqual(jp2.box[2].box[1].method, - glymur.core.RESTRICTED_ICC_PROFILE) - self.assertEqual(jp2.box[2].box[1].precedence, 0) - self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 - self.assertIsNone(jp2.box[2].box[1].icc_profile) - self.assertIsNone(jp2.box[2].box[1].colorspace) - - c = jp2.box[3].codestream - - ids = [x.marker_id for x in c.segment] - expected = ['SOC', 'SIZ', 'COD', 'QCD'] - self.assertEqual(ids, expected) - - kwargs = {'rsiz': 0, 'xysiz': (117, 117), 'xyosiz': (0, 0), - 'xytsiz': (117, 117), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8, 8), - 'signed': (False, False, False, False), - 'xyrsiz': [(1, 1, 1, 1), (1, 1, 1, 1)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) - - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 1) # layers = 1 - self.assertEqual(c.segment[2].spcod[3], 0) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # level - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblk - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) - - # QCD: Quantization default - self.assertEqual(c.segment[3].sqcd & 0x1f, 0) - self.assertEqual(c.segment[3].guard_bits, 2) - self.assertEqual(c.segment[3].mantissa, [0] * 16) - self.assertEqual(c.segment[3].exponent, - [8, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10, 9, 9, 10]) - - -if __name__ == "__main__": - unittest.main() diff --git a/glymur/test/test_opj_suite_neg.py b/glymur/test/test_opj_suite_neg.py index d2351a4..3e60cea 100644 --- a/glymur/test/test_opj_suite_neg.py +++ b/glymur/test/test_opj_suite_neg.py @@ -2,30 +2,37 @@ The tests here do not correspond directly to the OpenJPEG test suite, but seem like logical negative tests to add. """ +# R0904: Not too many methods in unittest. +# pylint: disable=R0904 + +# unittest2 is python2.6 only (pylint/python-2.7) +# pylint: disable=F0401 + import os import re +import sys import tempfile -import unittest +import warnings + +if sys.hexversion < 0x02070000: + import unittest2 as unittest +else: + import unittest import numpy as np -try: - import skimage.io -except ImportError: - pass from .fixtures import OPJ_DATA_ROOT, opj_data_file, read_image from .fixtures import NO_READ_BACKEND, NO_READ_BACKEND_MSG -from .fixtures import NO_SKIMAGE_FREEIMAGE_SUPPORT -from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG -from . import fixtures from glymur import Jp2k import glymur +@unittest.skipIf(re.match(r"""1\.[01234]""", glymur.version.openjpeg_version), + "Functionality not implemented for 1.3, 1.4") @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_OPJ_DATA_ROOT environment variable not set") -class TestSuiteNegativeRead(unittest.TestCase): +class TestSuiteNegative(unittest.TestCase): """Test suite for certain negative tests from openjpeg suite.""" def setUp(self): @@ -35,6 +42,18 @@ class TestSuiteNegativeRead(unittest.TestCase): def tearDown(self): pass + @unittest.skipIf(NO_READ_BACKEND, NO_READ_BACKEND_MSG) + @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") + def test_psnr_with_cratios(self): + """Using psnr with cratios options is not allowed.""" + # Not an OpenJPEG test, but close. + infile = opj_data_file('input/nonregression/Bretagne1.ppm') + data = read_image(infile) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + with self.assertRaises(IOError): + j.write(data, psnr=[30, 35, 40], cratios=[2, 3, 4]) + def test_nr_marker_not_compliant(self): """non-compliant marker, should still be able to read""" relpath = 'input/nonregression/MarkerIsNotCompliant.j2k' @@ -43,14 +62,15 @@ class TestSuiteNegativeRead(unittest.TestCase): jp2k.get_codestream(header_only=False) self.assertTrue(True) - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) def test_nr_illegalclrtransform(self): """EOC marker is bad""" relpath = 'input/nonregression/illegalcolortransform.j2k' jfile = opj_data_file(relpath) jp2k = Jp2k(jfile) - with self.assertWarns(UserWarning): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") codestream = jp2k.get_codestream(header_only=False) + self.assertEqual(len(w), 1) # Verify that the last segment returned in the codestream is SOD, # not EOC. Codestream parsing should stop when we try to jump to @@ -65,80 +85,67 @@ class TestSuiteNegativeRead(unittest.TestCase): jp2k.get_codestream(header_only=False) self.assertTrue(True) - -@unittest.skipIf(re.match("1.5|2", glymur.version.openjpeg_version) is None, - "Must have openjpeg 1.5 or higher to run") -@unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_OPJ_DATA_ROOT environment variable not set") -class TestSuiteNegativeWrite(unittest.TestCase): - """Test suite for certain negative tests from openjpeg suite.""" - - def setUp(self): - self.jp2file = glymur.data.nemo() - self.j2kfile = glymur.data.goodstuff() - - def tearDown(self): - pass - - @unittest.skipIf(NO_SKIMAGE_FREEIMAGE_SUPPORT, - "Cannot read input image without scikit-image/freeimage") - def test_cinema2K_bad_frame_rate(self): - """Cinema2k frame rate must be either 24 or 48.""" - relfile = 'input/nonregression/X_5_2K_24_235_CBR_STEM24_000.tif' - infile = opj_data_file(relfile) - data = skimage.io.imread(infile) - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - with self.assertRaises(IOError): - Jp2k(tfile.name, data=data, cinema2k=36) - - @unittest.skipIf(NO_READ_BACKEND, NO_READ_BACKEND_MSG) - def test_psnr_with_cratios(self): - """Using psnr with cratios options is not allowed.""" - # Not an OpenJPEG test, but close. - infile = opj_data_file('input/nonregression/Bretagne1.ppm') - data = read_image(infile) - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - with self.assertRaises(IOError): - Jp2k(tfile.name, - data=data, psnr=[30, 35, 40], cratios=[2, 3, 4]) - + @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_code_block_dimensions(self): """don't allow extreme codeblock sizes""" # opj_compress doesn't allow the dimensions of a codeblock # to be too small or too big, so neither will we. data = np.zeros((256, 256), dtype=np.uint8) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + # opj_compress doesn't allow code block area to exceed 4096. with self.assertRaises(IOError): - Jp2k(tfile.name, data=data, cbsize=(256, 256)) + j.write(data, cbsize=(256, 256)) # opj_compress doesn't allow either dimension to be less than 4. with self.assertRaises(IOError): - Jp2k(tfile.name, data=data, cbsize=(2048, 2)) + j.write(data, cbsize=(2048, 2)) with self.assertRaises(IOError): - Jp2k(tfile.name, data=data, cbsize=(2, 2048)) + j.write(data, cbsize=(2, 2048)) + def test_exceeded_box(self): + """should warn if reading past end of a box""" + # Verify that a warning is issued if we read past the end of a box + # This file has a palette (pclr) box whose length is impossibly + # short. + infile = os.path.join(OPJ_DATA_ROOT, + 'input/nonregression/mem-b2ace68c-1381.jp2') + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("ignore") + Jp2k(infile) + + @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_precinct_size_not_p2(self): """precinct sizes should be powers of two.""" ifile = Jp2k(self.j2kfile) - data = ifile[::4, ::4] + data = ifile.read(rlevel=2) with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + ofile = Jp2k(tfile.name, 'wb') with self.assertRaises(IOError): - Jp2k(tfile.name, data=data, psizes=[(13, 13)]) + ofile.write(data, psizes=[(13, 13)]) + @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_cblk_size_not_power_of_two(self): """code block sizes should be powers of two.""" ifile = Jp2k(self.j2kfile) - data = ifile[::4, ::4] + data = ifile.read(rlevel=2) with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + ofile = Jp2k(tfile.name, 'wb') with self.assertRaises(IOError): - Jp2k(tfile.name, data=data, cbsize=(13, 12)) + ofile.write(data, cbsize=(13, 12)) + @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_cblk_size_precinct_size(self): """code block sizes should never exceed half that of precinct size.""" ifile = Jp2k(self.j2kfile) - data = ifile[::4, ::4] + data = ifile.read(rlevel=2) with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + ofile = Jp2k(tfile.name, 'wb') with self.assertRaises(IOError): - Jp2k(tfile.name, data=data, cbsize=(64, 64), psizes=[(64, 64)]) + ofile.write(data, + cbsize=(64, 64), + psizes=[(64, 64)]) + +if __name__ == "__main__": + unittest.main() diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py index f44c7b4..2b1271b 100644 --- a/glymur/test/test_opj_suite_write.py +++ b/glymur/test/test_opj_suite_write.py @@ -2,235 +2,42 @@ The tests defined here roughly correspond to what is in the OpenJPEG test suite. """ +# C0103: method names longer that 30 chars are ok in tests, IMHO +# R0904: Seems like pylint is fooled in this situation +# pylint: disable=R0904,C0103 + +# unittest2 is python2.6 only (pylint/python-2.7) +# pylint: disable=F0401 + import os import re import sys import tempfile -import unittest -if sys.hexversion <= 0x03030000: - from mock import patch +if sys.hexversion < 0x02070000: + import unittest2 as unittest else: - from unittest.mock import patch - -import numpy as np -try: - import skimage.io -except ImportError: - pass + import unittest from .fixtures import read_image, NO_READ_BACKEND, NO_READ_BACKEND_MSG -from .fixtures import OPJ_DATA_ROOT, NO_SKIMAGE_FREEIMAGE_SUPPORT -from .fixtures import opj_data_file -from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG -from . import fixtures +from .fixtures import OPJ_DATA_ROOT, opj_data_file -import glymur from glymur import Jp2k -from glymur.codestream import SIZsegment -from glymur.version import openjpeg_version +import glymur -class CinemaBase(fixtures.MetadataBase): - - def verify_cinema_cod(self, cod_segment): - - self.assertFalse(cod_segment.scod & 2) # no sop - self.assertFalse(cod_segment.scod & 4) # no eph - self.assertEqual(cod_segment.spcod[0], glymur.core.CPRL) - self.assertEqual(cod_segment.layers, 1) - self.assertEqual(cod_segment.spcod[3], 1) # mct - self.assertEqual(cod_segment.spcod[4], 5) # levels - self.assertEqual(tuple(cod_segment.code_block_size), (32, 32)) - - def check_cinema4k_codestream(self, codestream, image_size): - - kwargs = {'rsiz': 4, 'xysiz': image_size, 'xyosiz': (0, 0), - 'xytsiz': image_size, 'xytosiz': (0, 0), - 'bitdepth': (12, 12, 12), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(codestream.segment[1], SIZsegment(**kwargs)) - - self.verify_cinema_cod(codestream.segment[2]) - - def check_cinema2k_codestream(self, codestream, image_size): - - kwargs = {'rsiz': 3, 'xysiz': image_size, 'xyosiz': (0, 0), - 'xytsiz': image_size, 'xytosiz': (0, 0), - 'bitdepth': (12, 12, 12), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(codestream.segment[1], SIZsegment(**kwargs)) - - self.verify_cinema_cod(codestream.segment[2]) - - -@unittest.skipIf(NO_SKIMAGE_FREEIMAGE_SUPPORT, - "Cannot read input image without scikit-image/freeimage") -@unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) -@unittest.skipIf(re.match(r'''(1|2.0.0)''', +@unittest.skipIf(os.name == "nt", "no write support on windows, period") +@unittest.skipIf(re.match(r"""1\.[01234]\.\d""", glymur.version.openjpeg_version) is not None, - "Uses features not supported until 2.0.1") -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") -class WriteCinema(CinemaBase): - """Tests for writing with openjp2 backend. - - These tests either roughly correspond with those tests with similar names - in the OpenJPEG test suite or are closely associated. - """ - def test_cinema2K_with_others(self): - """Can't specify cinema2k with any other options.""" - relfile = 'input/nonregression/X_5_2K_24_235_CBR_STEM24_000.tif' - infile = opj_data_file(relfile) - data = skimage.io.imread(infile) - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - with self.assertRaises(IOError): - Jp2k(tfile.name, data=data, - cinema2k=48, cratios=[200, 100, 50]) - - def test_cinema4K_with_others(self): - """Can't specify cinema4k with any other options.""" - relfile = 'input/nonregression/ElephantDream_4K.tif' - infile = opj_data_file(relfile) - data = skimage.io.imread(infile) - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - with self.assertRaises(IOError): - Jp2k(tfile.name, data=data, - cinema4k=True, cratios=[200, 100, 50]) - - -@unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) -@unittest.skipIf(NO_SKIMAGE_FREEIMAGE_SUPPORT, - "Cannot read input image without scikit-image/freeimage") -@unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) -@unittest.skipIf(re.match(r'''(1|2.0.0)''', - glymur.version.openjpeg_version) is not None, - "Uses features not supported until 2.0.1") -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_DATA_ROOT environment variable not set") -class WriteCinemaWarns(CinemaBase): - """Tests for writing with openjp2 backend. - - These tests either roughly correspond with those tests with similar names - in the OpenJPEG test suite or are closely associated. These tests issue - warnings. - """ - def test_NR_ENC_ElephantDream_4K_tif_21_encode(self): - relfile = 'input/nonregression/ElephantDream_4K.tif' - infile = opj_data_file(relfile) - data = skimage.io.imread(infile) - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - regex = 'OpenJPEG library warning:.*' - with self.assertWarnsRegex(UserWarning, re.compile(regex)): - j = Jp2k(tfile.name, data=data, cinema4k=True) - - codestream = j.get_codestream() - self.check_cinema4k_codestream(codestream, (4096, 2160)) - - def test_NR_ENC_X_5_2K_24_235_CBR_STEM24_000_tif_19_encode(self): - relfile = 'input/nonregression/X_5_2K_24_235_CBR_STEM24_000.tif' - infile = opj_data_file(relfile) - data = skimage.io.imread(infile) - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - with self.assertWarnsRegex(UserWarning, - 'OpenJPEG library warning'): - j = Jp2k(tfile.name, data=data, cinema2k=48) - - codestream = j.get_codestream() - self.check_cinema2k_codestream(codestream, (2048, 857)) - - def test_NR_ENC_X_6_2K_24_FULL_CBR_CIRCLE_000_tif_20_encode(self): - relfile = 'input/nonregression/X_6_2K_24_FULL_CBR_CIRCLE_000.tif' - infile = opj_data_file(relfile) - data = skimage.io.imread(infile) - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - with self.assertWarnsRegex(UserWarning, - 'OpenJPEG library warning'): - j = Jp2k(tfile.name, data=data, cinema2k=48) - - codestream = j.get_codestream() - self.check_cinema2k_codestream(codestream, (2048, 1080)) - - def test_NR_ENC_X_6_2K_24_FULL_CBR_CIRCLE_000_tif_17_encode(self): - relfile = 'input/nonregression/X_6_2K_24_FULL_CBR_CIRCLE_000.tif' - infile = opj_data_file(relfile) - data = skimage.io.imread(infile) - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - with self.assertWarnsRegex(UserWarning, - 'OpenJPEG library warning'): - j = Jp2k(tfile.name, data=data, cinema2k=24) - - codestream = j.get_codestream() - self.check_cinema2k_codestream(codestream, (2048, 1080)) - - def test_NR_ENC_X_5_2K_24_235_CBR_STEM24_000_tif_16_encode(self): - relfile = 'input/nonregression/X_5_2K_24_235_CBR_STEM24_000.tif' - infile = opj_data_file(relfile) - data = skimage.io.imread(infile) - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - with self.assertWarnsRegex(UserWarning, - 'OpenJPEG library warning'): - # OpenJPEG library warning: The desired maximum codestream - # size has limited at least one of the desired quality layers - j = Jp2k(tfile.name, data=data, cinema2k=24) - - codestream = j.get_codestream() - self.check_cinema2k_codestream(codestream, (2048, 857)) - - def test_NR_ENC_X_4_2K_24_185_CBR_WB_000_tif_18_encode(self): - relfile = 'input/nonregression/X_4_2K_24_185_CBR_WB_000.tif' - infile = opj_data_file(relfile) - data = skimage.io.imread(infile) - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - regex = 'OpenJPEG library warning' - with self.assertWarnsRegex(UserWarning, regex): - # OpenJPEG library warning: The desired maximum codestream - # size has limited at least one of the desired quality layers - j = Jp2k(tfile.name, data=data, cinema2k=48) - - codestream = j.get_codestream() - self.check_cinema2k_codestream(codestream, (1998, 1080)) - - -@unittest.skipIf(NO_SKIMAGE_FREEIMAGE_SUPPORT, - "Cannot read input image without scikit-image/freeimage") -@unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) -@unittest.skipIf(OPJ_DATA_ROOT is None, - "OPJ_OPJ_DATA_ROOT environment variable not set") -class TestNegative2pointzero(unittest.TestCase): - """Feature set not supported for versions less than 2.0.1""" - - def setUp(self): - self.jp2file = glymur.data.nemo() - self.j2kfile = glymur.data.goodstuff() - - def tearDown(self): - pass - - def test_cinema_mode(self): - """Cinema mode not allowed for anything less than 2.0.1""" - relfile = 'input/nonregression/X_4_2K_24_185_CBR_WB_000.tif' - infile = opj_data_file(relfile) - data = skimage.io.imread(infile) - versions = ["1.5.0", "2.0.0"] - for version in versions: - with patch('glymur.version.openjpeg_version', new=version): - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - with self.assertRaises(IOError): - Jp2k(tfile.name, data=data, cinema2k=48) - - -@unittest.skipIf(re.match(r'''1.[0-4]''', openjpeg_version) is not None, - "Writing not supported until OpenJPEG 1.5") -@unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG) + "Writing only supported with openjpeg version 1.5+.") @unittest.skipIf(NO_READ_BACKEND, NO_READ_BACKEND_MSG) @unittest.skipIf(OPJ_DATA_ROOT is None, "OPJ_DATA_ROOT environment variable not set") -class TestSuiteWrite(fixtures.MetadataBase): +class TestSuiteWrite(unittest.TestCase): """Tests for writing with openjp2 backend. - These tests either roughly correspond with those tests with similar names - in the OpenJPEG test suite or are closely associated. + These tests roughly correspond with those tests with similar names in the + OpenJPEG test suite. """ def setUp(self): pass @@ -238,65 +45,109 @@ class TestSuiteWrite(fixtures.MetadataBase): def tearDown(self): pass - def test_NR_ENC_issue141_rawl_23_encode(self): - filename = opj_data_file('input/nonregression/issue141.rawl') - expdata = np.fromfile(filename, dtype=np.uint16) - expdata.resize((2816, 2048)) - with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, data=expdata, irreversible=True) - - codestream = j.get_codestream() - self.assertEqual(codestream.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_9X7_IRREVERSIBLE) - def test_NR_ENC_Bretagne1_ppm_1_encode(self): """NR-ENC-Bretagne1.ppm-1-encode""" infile = opj_data_file('input/nonregression/Bretagne1.ppm') data = read_image(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, data=data, cratios=[200, 100, 50]) + j = Jp2k(tfile.name, 'wb') + j.write(data, cratios=[200, 100, 50]) # Should be three layers. - c = j.get_codestream() + codestream = j.get_codestream() - kwargs = {'rsiz': 0, 'xysiz': (640, 480), 'xyosiz': (0, 0), - 'xytsiz': (640, 480), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(c.segment[1], - glymur.codestream.SIZsegment(**kwargs)) + # SIZ: Image and tile size + # Profile: "0" means profile 2 + self.assertEqual(codestream.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual((codestream.segment[1].xsiz, + codestream.segment[1].ysiz), + (640, 480)) + # Reference grid offset + self.assertEqual((codestream.segment[1].xosiz, + codestream.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((codestream.segment[1].xtsiz, + codestream.segment[1].ytsiz), + (640, 480)) + # Tile offset + self.assertEqual((codestream.segment[1].xtosiz, + codestream.segment[1].ytosiz), + (0, 0)) + # bitdepth + self.assertEqual(codestream.segment[1].bitdepth, (8, 8, 8)) + # signed + self.assertEqual(codestream.segment[1].signed, + (False, False, False)) + # subsampling + self.assertEqual(list(zip(codestream.segment[1].xrsiz, + codestream.segment[1].yrsiz)), + [(1, 1)] * 3) - # COD: Coding style default - self.assertFalse(c.segment[2].scod & 2) # no sop - self.assertFalse(c.segment[2].scod & 4) # no eph - self.assertEqual(c.segment[2].spcod[0], glymur.core.LRCP) - self.assertEqual(c.segment[2].layers, 3) # layers = 3 - self.assertEqual(c.segment[2].spcod[3], 1) # mct - self.assertEqual(c.segment[2].spcod[4], 5) # levels - self.assertEqual(tuple(c.segment[2].code_block_size), - (64, 64)) # cblksz - self.verify_codeblock_style(c.segment[2].spcod[7], - [False, False, False, False, False, False]) - self.assertEqual(c.segment[2].spcod[8], - glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) - self.assertEqual(len(c.segment[2].spcod), 9) + # COD: Coding style default + self.assertFalse(codestream.segment[2].scod & 2) # no sop + self.assertFalse(codestream.segment[2].scod & 4) # no eph + self.assertEqual(codestream.segment[2].spcod[0], glymur.core.LRCP) + self.assertEqual(codestream.segment[2].layers, 3) # layers = 3 + self.assertEqual(codestream.segment[2].spcod[3], 1) # mct + self.assertEqual(codestream.segment[2].spcod[4], 5) # levels + self.assertEqual(tuple(codestream.segment[2].code_block_size), + (64, 64)) # cblksz + # Selective arithmetic coding bypass + self.assertFalse(codestream.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(codestream.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(codestream.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(codestream.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(codestream.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(codestream.segment[2].spcod[7] & 0x0020) + self.assertEqual(codestream.segment[2].spcod[8], + glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) + self.assertEqual(len(codestream.segment[2].spcod), 9) def test_NR_ENC_Bretagne1_ppm_2_encode(self): """NR-ENC-Bretagne1.ppm-2-encode""" infile = opj_data_file('input/nonregression/Bretagne1.ppm') data = read_image(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, data=data, psnr=[30, 35, 40], numres=2) + j = Jp2k(tfile.name, 'wb') + j.write(data, psnr=[30, 35, 40], numres=2) # Should be three layers. codestream = j.get_codestream() - kwargs = {'rsiz': 0, 'xysiz': (640, 480), 'xyosiz': (0, 0), - 'xytsiz': (640, 480), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(codestream.segment[1], - glymur.codestream.SIZsegment(**kwargs)) + # SIZ: Image and tile size + # Profile: "0" means profile 2 + self.assertEqual(codestream.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual((codestream.segment[1].xsiz, + codestream.segment[1].ysiz), + (640, 480)) + # Reference grid offset + self.assertEqual((codestream.segment[1].xosiz, + codestream.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((codestream.segment[1].xtsiz, + codestream.segment[1].ytsiz), + (640, 480)) + # Tile offset + self.assertEqual((codestream.segment[1].xtosiz, + codestream.segment[1].ytosiz), + (0, 0)) + # bitdepth + self.assertEqual(codestream.segment[1].bitdepth, + (8, 8, 8)) + # signed + self.assertEqual(codestream.segment[1].signed, + (False, False, False)) + # subsampling + self.assertEqual(list(zip(codestream.segment[1].xrsiz, + codestream.segment[1].yrsiz)), + [(1, 1)] * 3) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -307,9 +158,18 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(codestream.segment[2].spcod[4], 1) # levels self.assertEqual(tuple(codestream.segment[2].code_block_size), (64, 64)) # cblksz - self.verify_codeblock_style(codestream.segment[2].spcod[7], - [False, False, - False, False, False, False]) + # Selective arithmetic coding bypass + self.assertFalse(codestream.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(codestream.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(codestream.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(codestream.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(codestream.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(codestream.segment[2].spcod[7] & 0x0020) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) @@ -319,19 +179,40 @@ class TestSuiteWrite(fixtures.MetadataBase): infile = opj_data_file('input/nonregression/Bretagne1.ppm') data = read_image(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, - data=data, - psnr=[30, 35, 40], cbsize=(16, 16), psizes=[(64, 64)]) + j = Jp2k(tfile.name, 'wb') + j.write(data, psnr=[30, 35, 40], cbsize=(16, 16), + psizes=[(64, 64)]) # Should be three layers. codestream = j.get_codestream() - kwargs = {'rsiz': 0, 'xysiz': (640, 480), 'xyosiz': (0, 0), - 'xytsiz': (640, 480), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(codestream.segment[1], - glymur.codestream.SIZsegment(**kwargs)) + # SIZ: Image and tile size + # Profile: "0" means profile 2 + self.assertEqual(codestream.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual((codestream.segment[1].xsiz, + codestream.segment[1].ysiz), + (640, 480)) + # Reference grid offset + self.assertEqual((codestream.segment[1].xosiz, + codestream.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((codestream.segment[1].xtsiz, + codestream.segment[1].ytsiz), + (640, 480)) + # Tile offset + self.assertEqual((codestream.segment[1].xtosiz, + codestream.segment[1].ytosiz), + (0, 0)) + # bitdepth + self.assertEqual(codestream.segment[1].bitdepth, (8, 8, 8)) + # signed + self.assertEqual(codestream.segment[1].signed, + (False, False, False)) + # subsampling + self.assertEqual(list(zip(codestream.segment[1].xrsiz, + codestream.segment[1].yrsiz)), + [(1, 1)] * 3) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -342,9 +223,18 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(codestream.segment[2].spcod[4], 5) # levels self.assertEqual(tuple(codestream.segment[2].code_block_size), (16, 16)) # cblksz - self.verify_codeblock_style(codestream.segment[2].spcod[7], - [False, False, - False, False, False, False]) + # Selective arithmetic coding bypass + self.assertFalse(codestream.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(codestream.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(codestream.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(codestream.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(codestream.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(codestream.segment[2].spcod[7] & 0x0020) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(codestream.segment[2].precinct_size, @@ -356,22 +246,43 @@ class TestSuiteWrite(fixtures.MetadataBase): infile = opj_data_file('input/nonregression/Bretagne2.ppm') data = read_image(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, - data=data, - psizes=[(128, 128)] * 3, - cratios=[100, 20, 2], - tilesize=(480, 640), - cbsize=(32, 32)) + j = Jp2k(tfile.name, 'wb') + j.write(data, + psizes=[(128, 128)] * 3, + cratios=[100, 20, 2], + tilesize=(480, 640), + cbsize=(32, 32)) # Should be three layers. codestream = j.get_codestream() - kwargs = {'rsiz': 0, 'xysiz': (2592, 1944), 'xyosiz': (0, 0), - 'xytsiz': (640, 480), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(codestream.segment[1], - glymur.codestream.SIZsegment(**kwargs)) + # SIZ: Image and tile size + # Profile: "0" means profile 2 + self.assertEqual(codestream.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual((codestream.segment[1].xsiz, + codestream.segment[1].ysiz), + (data.shape[1], data.shape[0])) + # Reference grid offset + self.assertEqual((codestream.segment[1].xosiz, + codestream.segment[1].yosiz), (0, 0)) + # Tile size. Reported as XY, not RC. + self.assertEqual((codestream.segment[1].xtsiz, + codestream.segment[1].ytsiz), + (640, 480)) + # Tile offset + self.assertEqual((codestream.segment[1].xtosiz, + codestream.segment[1].ytosiz), + (0, 0)) + # bitdepth + self.assertEqual(codestream.segment[1].bitdepth, (8, 8, 8)) + # signed + self.assertEqual(codestream.segment[1].signed, + (False, False, False)) + # subsampling + self.assertEqual(list(zip(codestream.segment[1].xrsiz, + codestream.segment[1].yrsiz)), + [(1, 1)] * 3) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -382,9 +293,18 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(codestream.segment[2].spcod[4], 5) # levels self.assertEqual(tuple(codestream.segment[2].code_block_size), (32, 32)) # cblksz - self.verify_codeblock_style(codestream.segment[2].spcod[7], - [False, False, - False, False, False, False]) + # Selective arithmetic coding bypass + self.assertFalse(codestream.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(codestream.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(codestream.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(codestream.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(codestream.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(codestream.segment[2].spcod[7] & 0x0020) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(codestream.segment[2].precinct_size, @@ -395,16 +315,38 @@ class TestSuiteWrite(fixtures.MetadataBase): infile = opj_data_file('input/nonregression/Bretagne2.ppm') data = read_image(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, data=data, tilesize=(127, 127), prog="PCRL") + j = Jp2k(tfile.name, 'wb') + j.write(data, tilesize=(127, 127), prog="PCRL") codestream = j.get_codestream() - kwargs = {'rsiz': 0, 'xysiz': (2592, 1944), 'xyosiz': (0, 0), - 'xytsiz': (127, 127), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(codestream.segment[1], - glymur.codestream.SIZsegment(**kwargs)) + # SIZ: Image and tile size + # Profile: "0" means profile 2 + self.assertEqual(codestream.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual((codestream.segment[1].xsiz, + codestream.segment[1].ysiz), + (data.shape[1], data.shape[0])) + # Reference grid offset + self.assertEqual((codestream.segment[1].xosiz, + codestream.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((codestream.segment[1].xtsiz, + codestream.segment[1].ytsiz), + (127, 127)) + # Tile offset + self.assertEqual((codestream.segment[1].xtosiz, + codestream.segment[1].ytosiz), + (0, 0)) + # bitdepth + self.assertEqual(codestream.segment[1].bitdepth, (8, 8, 8)) + # signed + self.assertEqual(codestream.segment[1].signed, + (False, False, False)) + # subsampling + self.assertEqual(list(zip(codestream.segment[1].xrsiz, + codestream.segment[1].yrsiz)), + [(1, 1)] * 3) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -415,9 +357,18 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(codestream.segment[2].spcod[4], 5) # levels self.assertEqual(tuple(codestream.segment[2].code_block_size), (64, 64)) # cblksz - self.verify_codeblock_style(codestream.segment[2].spcod[7], - [False, False, - False, False, False, False]) + # Selective arithmetic coding bypass + self.assertFalse(codestream.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(codestream.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(codestream.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(codestream.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(codestream.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(codestream.segment[2].spcod[7] & 0x0020) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) @@ -427,16 +378,38 @@ class TestSuiteWrite(fixtures.MetadataBase): infile = opj_data_file('input/nonregression/Bretagne2.ppm') data = read_image(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, data=data, subsam=(2, 2), sop=True) + j = Jp2k(tfile.name, 'wb') + j.write(data, subsam=(2, 2), sop=True) codestream = j.get_codestream(header_only=False) - kwargs = {'rsiz': 0, 'xysiz': (5183, 3887), 'xyosiz': (0, 0), - 'xytsiz': (5183, 3887), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), 'signed': (False, False, False), - 'xyrsiz': [(2, 2, 2), (2, 2, 2)]} - self.verifySizSegment(codestream.segment[1], - glymur.codestream.SIZsegment(**kwargs)) + # SIZ: Image and tile size + # Profile: "0" means profile 2 + self.assertEqual(codestream.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual((codestream.segment[1].xsiz, + codestream.segment[1].ysiz), + (5183, 3887)) + # Reference grid offset + self.assertEqual((codestream.segment[1].xosiz, + codestream.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((codestream.segment[1].xtsiz, + codestream.segment[1].ytsiz), + (5183, 3887)) + # Tile offset + self.assertEqual((codestream.segment[1].xtosiz, + codestream.segment[1].ytosiz), + (0, 0)) + # bitdepth + self.assertEqual(codestream.segment[1].bitdepth, (8, 8, 8)) + # signed + self.assertEqual(codestream.segment[1].signed, + (False, False, False)) + # subsampling + self.assertEqual(list(zip(codestream.segment[1].xrsiz, + codestream.segment[1].yrsiz)), + [(2, 2)] * 3) # COD: Coding style default self.assertTrue(codestream.segment[2].scod & 2) # sop @@ -447,9 +420,18 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(codestream.segment[2].spcod[4], 5) # levels self.assertEqual(tuple(codestream.segment[2].code_block_size), (64, 64)) # cblksz - self.verify_codeblock_style(codestream.segment[2].spcod[7], - [False, False, False, - False, False, False]) + # Selective arithmetic coding bypass + self.assertFalse(codestream.segment[2].spcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(codestream.segment[2].spcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(codestream.segment[2].spcod[7] & 0x04) + # Vertically causal context + self.assertFalse(codestream.segment[2].spcod[7] & 0x08) + # Predictable termination + self.assertFalse(codestream.segment[2].spcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(codestream.segment[2].spcod[7] & 0x0020) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) @@ -464,16 +446,38 @@ class TestSuiteWrite(fixtures.MetadataBase): infile = opj_data_file('input/nonregression/Bretagne2.ppm') data = read_image(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, data=data, modesw=38, eph=True) + j = Jp2k(tfile.name, 'wb') + j.write(data, modesw=38, eph=True) codestream = j.get_codestream(header_only=False) - kwargs = {'rsiz': 0, 'xysiz': (2592, 1944), 'xyosiz': (0, 0), - 'xytsiz': (2592, 1944), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(codestream.segment[1], - glymur.codestream.SIZsegment(**kwargs)) + # SIZ: Image and tile size + # Profile: "0" means profile 2 + self.assertEqual(codestream.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual((codestream.segment[1].xsiz, + codestream.segment[1].ysiz), + (2592, 1944)) + # Reference grid offset + self.assertEqual((codestream.segment[1].xosiz, + codestream.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((codestream.segment[1].xtsiz, + codestream.segment[1].ytsiz), + (2592, 1944)) + # Tile offset + self.assertEqual((codestream.segment[1].xtosiz, + codestream.segment[1].ytosiz), + (0, 0)) + # bitdepth + self.assertEqual(codestream.segment[1].bitdepth, (8, 8, 8)) + # signed + self.assertEqual(codestream.segment[1].signed, + (False, False, False)) + # subsampling + self.assertEqual(list(zip(codestream.segment[1].xrsiz, + codestream.segment[1].yrsiz)), + [(1, 1)] * 3) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -483,10 +487,19 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(codestream.segment[2].spcod[3], 1) # mct self.assertEqual(codestream.segment[2].spcod[4], 5) # levels self.assertEqual(tuple(codestream.segment[2].code_block_size), - (64, 64)) # cblksz - self.verify_codeblock_style(codestream.segment[2].spcod[7], - [False, True, True, - False, False, True]) + (64, 64)) # cblksz + # Selective arithmetic coding BYPASS + self.assertFalse(codestream.segment[2].spcod[7] & 0x01) + # RESET context probabilities (RESET) + self.assertTrue(codestream.segment[2].spcod[7] & 0x02) + # Termination on each coding pass, RESTART(TERMALL) + self.assertTrue(codestream.segment[2].spcod[7] & 0x04) + # Vertically causal context (VSC) + self.assertFalse(codestream.segment[2].spcod[7] & 0x08) + # Predictable termination, ERTERM(SEGTERM) + self.assertFalse(codestream.segment[2].spcod[7] & 0x0010) + # Segmentation symbols, SEGMARK(SEGSYSM) + self.assertTrue(codestream.segment[2].spcod[7] & 0x0020) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) @@ -500,17 +513,39 @@ class TestSuiteWrite(fixtures.MetadataBase): infile = opj_data_file('input/nonregression/Bretagne2.ppm') data = read_image(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, - data=data, grid_offset=[300, 150], cratios=[800]) + j = Jp2k(tfile.name, 'wb') + j.write(data, grid_offset=[300, 150], cratios=[800]) codestream = j.get_codestream(header_only=False) - kwargs = {'rsiz': 0, 'xysiz': (2742, 2244), 'xyosiz': (150, 300), - 'xytsiz': (2742, 2244), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(codestream.segment[1], - glymur.codestream.SIZsegment(**kwargs)) + # SIZ: Image and tile size + # Profile: "0" means profile 2 + self.assertEqual(codestream.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual((codestream.segment[1].xsiz, + codestream.segment[1].ysiz), + (2742, 2244)) + # Reference grid offset + self.assertEqual((codestream.segment[1].xosiz, + codestream.segment[1].yosiz), + (150, 300)) + # Tile size + self.assertEqual((codestream.segment[1].xtsiz, + codestream.segment[1].ytsiz), + (2742, 2244)) + # Tile offset + self.assertEqual((codestream.segment[1].xtosiz, + codestream.segment[1].ytosiz), + (0, 0)) + # bitdepth + self.assertEqual(codestream.segment[1].bitdepth, (8, 8, 8)) + # signed + self.assertEqual(codestream.segment[1].signed, + (False, False, False)) + # subsampling + self.assertEqual(list(zip(codestream.segment[1].xrsiz, + codestream.segment[1].yrsiz)), + [(1, 1)] * 3) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -521,9 +556,18 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(codestream.segment[2].spcod[4], 5) # levels self.assertEqual(tuple(codestream.segment[2].code_block_size), (64, 64)) # cblksz - self.verify_codeblock_style(codestream.segment[2].spcod[7], - [False, False, False, - False, False, False]) + # Selective arithmetic coding BYPASS + self.assertFalse(codestream.segment[2].spcod[7] & 0x01) + # RESET context probabilities (RESET) + self.assertFalse(codestream.segment[2].spcod[7] & 0x02) + # Termination on each coding pass, RESTART(TERMALL) + self.assertFalse(codestream.segment[2].spcod[7] & 0x04) + # Vertically causal context (VSC) + self.assertFalse(codestream.segment[2].spcod[7] & 0x08) + # Predictable termination, ERTERM(SEGTERM) + self.assertFalse(codestream.segment[2].spcod[7] & 0x0010) + # Segmentation symbols, SEGMARK(SEGSYSM) + self.assertFalse(codestream.segment[2].spcod[7] & 0x0020) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) @@ -533,16 +577,38 @@ class TestSuiteWrite(fixtures.MetadataBase): infile = opj_data_file('input/nonregression/Cevennes1.bmp') data = read_image(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, data=data, cratios=[800]) + j = Jp2k(tfile.name, 'wb') + j.write(data, cratios=[800]) codestream = j.get_codestream(header_only=False) - kwargs = {'rsiz': 0, 'xysiz': (2592, 1944), 'xyosiz': (0, 0), - 'xytsiz': (2592, 1944), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(codestream.segment[1], - glymur.codestream.SIZsegment(**kwargs)) + # SIZ: Image and tile size + # Profile: "0" means profile 2 + self.assertEqual(codestream.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual((codestream.segment[1].xsiz, + codestream.segment[1].ysiz), + (2592, 1944)) + # Reference grid offset + self.assertEqual((codestream.segment[1].xosiz, + codestream.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((codestream.segment[1].xtsiz, + codestream.segment[1].ytsiz), + (2592, 1944)) + # Tile offset + self.assertEqual((codestream.segment[1].xtosiz, + codestream.segment[1].ytosiz), + (0, 0)) + # bitdepth + self.assertEqual(codestream.segment[1].bitdepth, (8, 8, 8)) + # signed + self.assertEqual(codestream.segment[1].signed, + (False, False, False)) + # subsampling + self.assertEqual(list(zip(codestream.segment[1].xrsiz, + codestream.segment[1].yrsiz)), + [(1, 1)] * 3) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -553,9 +619,18 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(codestream.segment[2].spcod[4], 5) # levels self.assertEqual(tuple(codestream.segment[2].code_block_size), (64, 64)) # cblksz - self.verify_codeblock_style(codestream.segment[2].spcod[7], - [False, False, False, - False, False, False]) + # Selective arithmetic coding BYPASS + self.assertFalse(codestream.segment[2].spcod[7] & 0x01) + # RESET context probabilities (RESET) + self.assertFalse(codestream.segment[2].spcod[7] & 0x02) + # Termination on each coding pass, RESTART(TERMALL) + self.assertFalse(codestream.segment[2].spcod[7] & 0x04) + # Vertically causal context (VSC) + self.assertFalse(codestream.segment[2].spcod[7] & 0x08) + # Predictable termination, ERTERM(SEGTERM) + self.assertFalse(codestream.segment[2].spcod[7] & 0x0010) + # Segmentation symbols, SEGMARK(SEGSYSM) + self.assertFalse(codestream.segment[2].spcod[7] & 0x0020) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) @@ -565,16 +640,38 @@ class TestSuiteWrite(fixtures.MetadataBase): infile = opj_data_file('input/nonregression/Cevennes2.ppm') data = read_image(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, data=data, cratios=[50]) + j = Jp2k(tfile.name, 'wb') + j.write(data, cratios=[50]) codestream = j.get_codestream(header_only=False) - kwargs = {'rsiz': 0, 'xysiz': (640, 480), 'xyosiz': (0, 0), - 'xytsiz': (640, 480), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(codestream.segment[1], - glymur.codestream.SIZsegment(**kwargs)) + # SIZ: Image and tile size + # Profile: "0" means profile 2 + self.assertEqual(codestream.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual((codestream.segment[1].xsiz, + codestream.segment[1].ysiz), + (640, 480)) + # Reference grid offset + self.assertEqual((codestream.segment[1].xosiz, + codestream.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((codestream.segment[1].xtsiz, + codestream.segment[1].ytsiz), + (640, 480)) + # Tile offset + self.assertEqual((codestream.segment[1].xtosiz, + codestream.segment[1].ytosiz), + (0, 0)) + # bitdepth + self.assertEqual(codestream.segment[1].bitdepth, (8, 8, 8)) + # signed + self.assertEqual(codestream.segment[1].signed, + (False, False, False)) + # subsampling + self.assertEqual(list(zip(codestream.segment[1].xrsiz, + codestream.segment[1].yrsiz)), + [(1, 1)] * 3) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -585,9 +682,18 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(codestream.segment[2].spcod[4], 5) # levels self.assertEqual(tuple(codestream.segment[2].code_block_size), (64, 64)) # cblksz - self.verify_codeblock_style(codestream.segment[2].spcod[7], - [False, False, False, - False, False, False]) + # Selective arithmetic coding BYPASS + self.assertFalse(codestream.segment[2].spcod[7] & 0x01) + # RESET context probabilities (RESET) + self.assertFalse(codestream.segment[2].spcod[7] & 0x02) + # Termination on each coding pass, RESTART(TERMALL) + self.assertFalse(codestream.segment[2].spcod[7] & 0x04) + # Vertically causal context (VSC) + self.assertFalse(codestream.segment[2].spcod[7] & 0x08) + # Predictable termination, ERTERM(SEGTERM) + self.assertFalse(codestream.segment[2].spcod[7] & 0x0010) + # Segmentation symbols, SEGMARK(SEGSYSM) + self.assertFalse(codestream.segment[2].spcod[7] & 0x0020) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) @@ -596,8 +702,8 @@ class TestSuiteWrite(fixtures.MetadataBase): """NR-ENC-Rome.bmp-11-encode""" data = read_image(opj_data_file('input/nonregression/Rome.bmp')) with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: - jp2 = Jp2k(tfile.name, - data=data, psnr=[30, 35, 50], prog='LRCP', numres=3) + jp2 = Jp2k(tfile.name, 'wb') + jp2.write(data, psnr=[30, 35, 50], prog='LRCP', numres=3) ids = [box.box_id for box in jp2.box] self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) @@ -632,14 +738,36 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertIsNone(jp2.box[2].box[1].icc_profile) self.assertEqual(jp2.box[2].box[1].colorspace, glymur.core.SRGB) - codestream = jp2.box[3].codestream + codestream = jp2.box[3].main_header - kwargs = {'rsiz': 0, 'xysiz': (640, 480), 'xyosiz': (0, 0), - 'xytsiz': (640, 480), 'xytosiz': (0, 0), - 'bitdepth': (8, 8, 8), 'signed': (False, False, False), - 'xyrsiz': [(1, 1, 1), (1, 1, 1)]} - self.verifySizSegment(codestream.segment[1], - glymur.codestream.SIZsegment(**kwargs)) + # SIZ: Image and tile size + # Profile: "0" means profile 2 + self.assertEqual(codestream.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual((codestream.segment[1].xsiz, + codestream.segment[1].ysiz), + (640, 480)) + # Reference grid offset + self.assertEqual((codestream.segment[1].xosiz, + codestream.segment[1].yosiz), + (0, 0)) + # Tile size + self.assertEqual((codestream.segment[1].xtsiz, + codestream.segment[1].ytsiz), + (640, 480)) + # Tile offset + self.assertEqual((codestream.segment[1].xtosiz, + codestream.segment[1].ytosiz), + (0, 0)) + # bitdepth + self.assertEqual(codestream.segment[1].bitdepth, (8, 8, 8)) + # signed + self.assertEqual(codestream.segment[1].signed, + (False, False, False)) + # subsampling + self.assertEqual(list(zip(codestream.segment[1].xrsiz, + codestream.segment[1].yrsiz)), + [(1, 1)] * 3) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -650,9 +778,18 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(codestream.segment[2].spcod[4], 2) # levels self.assertEqual(tuple(codestream.segment[2].code_block_size), (64, 64)) # cblksz - self.verify_codeblock_style(codestream.segment[2].spcod[7], - [False, False, False, - False, False, False]) + # Selective arithmetic coding BYPASS + self.assertFalse(codestream.segment[2].spcod[7] & 0x01) + # RESET context probabilities (RESET) + self.assertFalse(codestream.segment[2].spcod[7] & 0x02) + # Termination on each coding pass, RESTART(TERMALL) + self.assertFalse(codestream.segment[2].spcod[7] & 0x04) + # Vertically causal context (VSC) + self.assertFalse(codestream.segment[2].spcod[7] & 0x08) + # Predictable termination, ERTERM(SEGTERM) + self.assertFalse(codestream.segment[2].spcod[7] & 0x0010) + # Segmentation symbols, SEGMARK(SEGSYSM) + self.assertFalse(codestream.segment[2].spcod[7] & 0x0020) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) @@ -664,16 +801,37 @@ class TestSuiteWrite(fixtures.MetadataBase): infile = opj_data_file('input/nonregression/random-issue-0005.tif') data = read_image(infile) with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: - j = Jp2k(tfile.name, data=data) + j = Jp2k(tfile.name, 'wb') + j.write(data) codestream = j.get_codestream(header_only=False) - kwargs = {'rsiz': 0, 'xysiz': (1024, 1024), 'xyosiz': (0, 0), - 'xytsiz': (1024, 1024), 'xytosiz': (0, 0), - 'bitdepth': (16,), 'signed': (False,), - 'xyrsiz': [(1,), (1,)]} - self.verifySizSegment(codestream.segment[1], - glymur.codestream.SIZsegment(**kwargs)) + # SIZ: Image and tile size + # Profile: "0" means profile 2 + self.assertEqual(codestream.segment[1].rsiz, 0) + # Reference grid size + self.assertEqual((codestream.segment[1].xsiz, + codestream.segment[1].ysiz), + (1024, 1024)) + # Reference grid offset + self.assertEqual((codestream.segment[1].xosiz, + codestream.segment[1].yosiz), (0, 0)) + # Tile size + self.assertEqual((codestream.segment[1].xtsiz, + codestream.segment[1].ytsiz), + (1024, 1024)) + # Tile offset + self.assertEqual((codestream.segment[1].xtosiz, + codestream.segment[1].ytosiz), + (0, 0)) + # bitdepth + self.assertEqual(codestream.segment[1].bitdepth, (16,)) + # signed + self.assertEqual(codestream.segment[1].signed, (False,)) + # subsampling + self.assertEqual(list(zip(codestream.segment[1].xrsiz, + codestream.segment[1].yrsiz)), + [(1, 1)]) # COD: Coding style default self.assertFalse(codestream.segment[2].scod & 2) # no sop @@ -684,13 +842,21 @@ class TestSuiteWrite(fixtures.MetadataBase): self.assertEqual(codestream.segment[2].spcod[4], 5) # levels self.assertEqual(tuple(codestream.segment[2].code_block_size), (64, 64)) # cblksz - self.verify_codeblock_style(codestream.segment[2].spcod[7], - [False, False, False, - False, False, False]) + # Selective arithmetic coding BYPASS + self.assertFalse(codestream.segment[2].spcod[7] & 0x01) + # RESET context probabilities (RESET) + self.assertFalse(codestream.segment[2].spcod[7] & 0x02) + # Termination on each coding pass, RESTART(TERMALL) + self.assertFalse(codestream.segment[2].spcod[7] & 0x04) + # Vertically causal context (VSC) + self.assertFalse(codestream.segment[2].spcod[7] & 0x08) + # Predictable termination, ERTERM(SEGTERM) + self.assertFalse(codestream.segment[2].spcod[7] & 0x0010) + # Segmentation symbols, SEGMARK(SEGSYSM) + self.assertFalse(codestream.segment[2].spcod[7] & 0x0020) self.assertEqual(codestream.segment[2].spcod[8], glymur.core.WAVELET_XFORM_5X3_REVERSIBLE) self.assertEqual(len(codestream.segment[2].spcod), 9) - if __name__ == "__main__": unittest.main() diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py index bccaa00..3c325f5 100644 --- a/glymur/test/test_printing.py +++ b/glymur/test/test_printing.py @@ -1,12 +1,27 @@ # -*- coding: utf-8 -*- """Test suite for printing. """ +# C0302: don't care too much about having too many lines in a test module +# pylint: disable=C0302 + +# E061: unittest.mock introduced in 3.3 (python-2.7/pylint issue) +# pylint: disable=E0611,F0401 + +# R0904: Not too many methods in unittest. +# pylint: disable=R0904 + import os import re import struct import sys import tempfile -import unittest +import warnings +from xml.etree import cElementTree as ET + +if sys.hexversion < 0x02070000: + import unittest2 as unittest +else: + import unittest if sys.hexversion < 0x03000000: from StringIO import StringIO @@ -18,86 +33,117 @@ if sys.hexversion <= 0x03030000: else: from unittest.mock import patch -import lxml.etree as ET - import glymur -from glymur import Jp2k, command_line +from glymur import Jp2k from . import fixtures -from .fixtures import (OPJ_DATA_ROOT, opj_data_file, - WARNING_INFRASTRUCTURE_ISSUE, - WARNING_INFRASTRUCTURE_MSG, - WINDOWS_TMP_FILE_MSG, - text_gbr_27, text_gbr_33, text_gbr_34) +from .fixtures import OPJ_DATA_ROOT, opj_data_file +from .fixtures import text_gbr_27, text_gbr_33, text_gbr_34 -@unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG) -class TestPrinting(unittest.TestCase): - """Tests for verifying how printing works.""" +@unittest.skipIf(os.name == "nt", "Temporary file issue on window.") +@unittest.skipIf(re.match(r"""1\.[01234]""", glymur.version.openjpeg_version), + "Need at least 1.5 in order to write jp2 files.") +class TestPrintingNeedsLib(unittest.TestCase): + """These tests require the library, mostly in order to just setup the test. + """ + + @classmethod + def setUpClass(cls): + # Setup a plain JP2 file without the two UUID boxes. + jp2file = glymur.data.nemo() + with tempfile.NamedTemporaryFile(suffix='.jp2', delete=False) as tfile: + cls._plain_nemo_file = tfile.name + ijfile = Jp2k(jp2file) + data = ijfile.read(rlevel=1) + ojfile = Jp2k(cls._plain_nemo_file, 'wb') + ojfile.write(data) + + @classmethod + def tearDownClass(cls): + os.unlink(cls._plain_nemo_file) + def setUp(self): - self.jpxfile = glymur.data.jpxfile() self.jp2file = glymur.data.nemo() self.j2kfile = glymur.data.goodstuff() - # Reset printoptions for every test. - glymur.set_printoptions(short=False, xml=True, codestream=True) + # Save the output of dumping nemo.jp2 for more than one test. + lines = ['JPEG 2000 Signature Box (jP ) @ (0, 12)', + ' Signature: 0d0a870a', + 'File Type Box (ftyp) @ (12, 20)', + ' Brand: jp2 ', + " Compatibility: ['jp2 ']", + 'JP2 Header Box (jp2h) @ (32, 45)', + ' Image Header Box (ihdr) @ (40, 22)', + ' Size: [728 1296 3]', + ' Bitdepth: 8', + ' Signed: False', + ' Compression: wavelet', + ' Colorspace Unknown: False', + ' Colour Specification Box (colr) @ (62, 15)', + ' Method: enumerated colorspace', + ' Precedence: 0', + ' Colorspace: sRGB', + 'Contiguous Codestream Box (jp2c) @ (77, 1632355)', + ' Main header:', + ' SOC marker segment @ (85, 0)', + ' SIZ marker segment @ (87, 47)', + ' Profile: 2', + ' Reference Grid Height, Width: (728 x 1296)', + ' Vertical, Horizontal Reference Grid Offset: ' + + '(0 x 0)', + ' Reference Tile Height, Width: (728 x 1296)', + ' 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 @ (136, 12)', + ' Coding style:', + ' Entropy coder, without partitions', + ' SOP marker segments: False', + ' EPH marker segments: False', + ' Coding style parameters:', + ' Progression order: LRCP', + ' Number of layers: 1', + ' Multiple component transformation usage: ' + + 'reversible', + ' Number of resolutions: 6', + ' Code block height, width: (64 x 64)', + ' Wavelet transform: 5-3 reversible', + ' Precinct size: default, 2^15 x 2^15', + ' 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 @ (150, 19)', + ' Quantization style: no quantization, ' + + '2 guard bits', + ' Step size: [(0, 8), (0, 9), (0, 9), ' + + '(0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), ' + + '(0, 10), (0, 9), (0, 9), (0, 10), (0, 9), (0, 9), ' + + '(0, 10)]'] + self.expected_plain = '\n'.join(lines) def tearDown(self): - glymur.set_parseoptions(full_codestream=False) + pass - def test_version_info(self): - """Should be able to print(glymur.version.info)""" - with patch('sys.stdout', new=StringIO()) as fake_out: - print(glymur.version.info) - fake_out.getvalue().strip() - - self.assertTrue(True) - - @unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG) - def test_unknown_superbox(self): - """Verify that we can handle an unknown superbox.""" - with tempfile.NamedTemporaryFile(suffix='.jpx') as tfile: - with open(self.jpxfile, 'rb') as ifile: - tfile.write(ifile.read()) - - # Add the header for an unknown superbox. - write_buffer = struct.pack('>I4s', 20, 'grp '.encode()) - tfile.write(write_buffer) - - # Add a free box inside of it. We won't be able to identify it, - # but it's there. - write_buffer = struct.pack('>I4sI', 12, 'free'.encode(), 0) - tfile.write(write_buffer) - tfile.flush() - - with self.assertWarns(UserWarning): - jpx = Jp2k(tfile.name) - - glymur.set_printoptions(short=True) - with patch('sys.stdout', new=StringIO()) as fake_out: - print(jpx.box[-1]) - actual = fake_out.getvalue().strip() - if sys.hexversion < 0x03000000: - expected = "Unknown Box (grp ) @ (1399071, 20)" - else: - expected = "Unknown Box (b'grp ') @ (1399071, 20)" - self.assertEqual(actual, expected) - - def test_printoptions_bad_argument(self): - """Verify error when bad parameter to set_printoptions""" - with self.assertRaises(TypeError): - glymur.set_printoptions(hi='low') - - @unittest.skipIf(re.match("1.5|2", - glymur.version.openjpeg_version) is None, - "Must have openjpeg 1.5 or higher to run") def test_asoc_label_box(self): """verify printing of asoc, label boxes""" # Construct a fake file with an asoc and a label box, as # OpenJPEG doesn't have such a file. - data = glymur.Jp2k(self.jp2file)[::2, ::2] + data = glymur.Jp2k(self.jp2file).read(rlevel=1) with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + j = glymur.Jp2k(tfile.name, 'wb') + j.write(data) + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile2: - glymur.Jp2k(tfile.name, data=data) # Offset of the codestream is where we start. wbuffer = tfile.read(77) @@ -141,6 +187,35 @@ class TestPrinting(unittest.TestCase): expected = '\n'.join(lines) self.assertEqual(actual, expected) + @unittest.skipIf(sys.hexversion < 0x02070000, "Do not bother with 2.6") + def test_jp2dump(self): + """basic jp2dump test""" + with patch('sys.stdout', new=StringIO()) as fake_out: + glymur.jp2dump(glymur.data.nemo()) + actual = fake_out.getvalue().strip() + + # Get rid of the filename line, as it is not set in stone. + lst = actual.split('\n') + lst = lst[1:] + actual = '\n'.join(lst) + self.maxDiff = None + if sys.hexversion < 0x02080000: + # Ordered dicts are different in 2.7 + self.assertEqual(actual, fixtures.nemo_dump_full_p27) + else: + self.assertEqual(actual, fixtures.nemo_dump_full_opj2) + + +class TestPrinting(unittest.TestCase): + """Test suite for printing where the libraries are not needed""" + + def setUp(self): + # Save sys.stdout. + self.jp2file = glymur.data.nemo() + + def tearDown(self): + pass + def test_coc_segment(self): """verify printing of COC segment""" j = glymur.Jp2k(self.jp2file) @@ -149,7 +224,7 @@ class TestPrinting(unittest.TestCase): print(codestream.segment[6]) actual = fake_out.getvalue().strip() - lines = ['COC marker segment @ (3356, 9)', + lines = ['COC marker segment @ (3260, 9)', ' Associated component: 1', ' Coding style for this component: ' + 'Entropy coder, PARTITION = 0', @@ -177,7 +252,7 @@ class TestPrinting(unittest.TestCase): print(codestream.segment[2]) actual = fake_out.getvalue().strip() - lines = ['COD marker segment @ (3282, 12)', + lines = ['COD marker segment @ (3186, 12)', ' Coding style:', ' Entropy coder, without partitions', ' SOP marker segments: False', @@ -203,6 +278,93 @@ class TestPrinting(unittest.TestCase): expected = '\n'.join(lines) self.assertEqual(actual, expected) + @unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") + def test_icc_profile(self): + """verify icc profile printing with a jpx""" + # ICC profiles may be used in JP2, but the approximation field should + # be zero unless we have jpx. This file does both. + filename = opj_data_file('input/nonregression/text_GBR.jp2') + with warnings.catch_warnings(): + # brand is 'jp2 ', but has any icc profile. + warnings.simplefilter("ignore") + jp2 = Jp2k(filename) + + with patch('sys.stdout', new=StringIO()) as fake_out: + print(jp2.box[3].box[1]) + actual = fake_out.getvalue().strip() + if sys.hexversion < 0x03000000: + expected = text_gbr_27 + elif sys.hexversion < 0x03040000: + expected = text_gbr_33 + else: + expected = text_gbr_34 + + self.assertEqual(actual, expected) + + @unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") + def test_crg(self): + """verify printing of CRG segment""" + filename = opj_data_file('input/conformance/p0_03.j2k') + j = glymur.Jp2k(filename) + codestream = j.get_codestream() + with patch('sys.stdout', new=StringIO()) as fake_out: + print(codestream.segment[-5]) + actual = fake_out.getvalue().strip() + lines = ['CRG marker segment @ (87, 6)', + ' Vertical, Horizontal offset: (0.50, 1.00)'] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + @unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") + def test_rgn(self): + """verify printing of RGN segment""" + filename = opj_data_file('input/conformance/p0_03.j2k') + j = glymur.Jp2k(filename) + codestream = j.get_codestream(header_only=False) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(codestream.segment[12]) + actual = fake_out.getvalue().strip() + lines = ['RGN marker segment @ (310, 5)', + ' Associated component: 0', + ' ROI style: 0', + ' Parameter: 7'] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + @unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") + def test_sop(self): + """verify printing of SOP segment""" + filename = opj_data_file('input/conformance/p0_03.j2k') + j = glymur.Jp2k(filename) + codestream = j.get_codestream(header_only=False) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(codestream.segment[-2]) + actual = fake_out.getvalue().strip() + lines = ['SOP marker segment @ (12836, 4)', + ' Nsop: 15'] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + @unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") + def test_cme(self): + """Test printing a CME or comment marker segment.""" + filename = opj_data_file('input/conformance/p0_02.j2k') + j = glymur.Jp2k(filename) + codestream = j.get_codestream() + # 2nd to last segment in the main header + with patch('sys.stdout', new=StringIO()) as fake_out: + print(codestream.segment[-2]) + actual = fake_out.getvalue().strip() + lines = ['CME marker segment @ (85, 45)', + ' "Creator: AV-J2K (c) 2000,2001 Algo Vision"'] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + def test_eoc_segment(self): """verify printing of eoc segment""" j = glymur.Jp2k(self.jp2file) @@ -211,7 +373,92 @@ class TestPrinting(unittest.TestCase): print(codestream.segment[-1]) actual = fake_out.getvalue().strip() - lines = ['EOC marker segment @ (1135517, 0)'] + lines = ['EOC marker segment @ (1135421, 0)'] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + @unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") + def test_plt_segment(self): + """verify printing of PLT segment""" + filename = opj_data_file('input/conformance/p0_07.j2k') + j = glymur.Jp2k(filename) + codestream = j.get_codestream(header_only=False) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(codestream.segment[49935]) + actual = fake_out.getvalue().strip() + + lines = ['PLT marker segment @ (7871146, 38)', + ' Index: 0', + ' Iplt: [9, 122, 19, 30, 27, 9, 41, 62, 18, 29, 261,' + + ' 55, 82, 299, 93, 941, 951, 687, 1729, 1443, 1008, 2168,' + + ' 2188, 2223]'] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + @unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") + def test_pod_segment(self): + """verify printing of POD segment""" + filename = opj_data_file('input/conformance/p0_13.j2k') + j = glymur.Jp2k(filename) + codestream = j.get_codestream() + with patch('sys.stdout', new=StringIO()) as fake_out: + print(codestream.segment[8]) + actual = fake_out.getvalue().strip() + + lines = ['POD marker segment @ (878, 20)', + ' Progression change 0:', + ' Resolution index start: 0', + ' Component index start: 0', + ' Layer index end: 1', + ' Resolution index end: 33', + ' Component index end: 128', + ' Progression order: RLCP', + ' Progression change 1:', + ' Resolution index start: 0', + ' Component index start: 128', + ' Layer index end: 1', + ' Resolution index end: 33', + ' Component index end: 257', + ' Progression order: CPRL'] + + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + @unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") + def test_ppm_segment(self): + """verify printing of PPM segment""" + filename = opj_data_file('input/conformance/p1_03.j2k') + j = glymur.Jp2k(filename) + codestream = j.get_codestream() + with patch('sys.stdout', new=StringIO()) as fake_out: + print(codestream.segment[9]) + actual = fake_out.getvalue().strip() + + lines = ['PPM marker segment @ (213, 43712)', + ' Index: 0', + ' Data: 43709 uninterpreted bytes'] + + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + @unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") + def test_ppt_segment(self): + """verify printing of ppt segment""" + filename = opj_data_file('input/conformance/p1_06.j2k') + j = glymur.Jp2k(filename) + codestream = j.get_codestream(header_only=False) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(codestream.segment[6]) + actual = fake_out.getvalue().strip() + + lines = ['PPT marker segment @ (155, 109)', + ' Index: 0', + ' Packet headers: 106 uninterpreted bytes'] + expected = '\n'.join(lines) self.assertEqual(actual, expected) @@ -223,7 +470,7 @@ class TestPrinting(unittest.TestCase): print(codestream.segment[7]) actual = fake_out.getvalue().strip() - lines = ['QCC marker segment @ (3367, 8)', + lines = ['QCC marker segment @ (3271, 8)', ' Associated Component: 1', ' Quantization style: no quantization, 2 guard bits', ' Step size: [(0, 8), (0, 9), (0, 9), (0, 10)]'] @@ -239,7 +486,7 @@ class TestPrinting(unittest.TestCase): print(codestream.segment[3]) actual = fake_out.getvalue().strip() - lines = ['QCD marker segment @ (3296, 7)', + lines = ['QCD marker segment @ (3200, 7)', ' Quantization style: no quantization, 2 guard bits', ' Step size: [(0, 8), (0, 9), (0, 9), (0, 10)]'] @@ -254,8 +501,8 @@ class TestPrinting(unittest.TestCase): print(codestream.segment[1]) actual = fake_out.getvalue().strip() - lines = ['SIZ marker segment @ (3233, 47)', - ' Profile: no profile', + lines = ['SIZ marker segment @ (3137, 47)', + ' Profile: 2', ' Reference Grid Height, Width: (1456 x 2592)', ' Vertical, Horizontal Reference Grid Offset: (0 x 0)', ' Reference Tile Height, Width: (1456 x 2592)', @@ -276,7 +523,7 @@ class TestPrinting(unittest.TestCase): print(codestream.segment[0]) actual = fake_out.getvalue().strip() - lines = ['SOC marker segment @ (3231, 0)'] + lines = ['SOC marker segment @ (3135, 0)'] expected = '\n'.join(lines) self.assertEqual(actual, expected) @@ -288,7 +535,7 @@ class TestPrinting(unittest.TestCase): print(codestream.segment[10]) actual = fake_out.getvalue().strip() - lines = ['SOD marker segment @ (3398, 0)'] + lines = ['SOD marker segment @ (3302, 0)'] expected = '\n'.join(lines) self.assertEqual(actual, expected) @@ -300,7 +547,7 @@ class TestPrinting(unittest.TestCase): print(codestream.segment[5]) actual = fake_out.getvalue().strip() - lines = ['SOT marker segment @ (3344, 10)', + lines = ['SOT marker segment @ (3248, 10)', ' Tile part index: 0', ' Tile part length: 1132173', ' Tile part instance: 0', @@ -309,14 +556,47 @@ class TestPrinting(unittest.TestCase): expected = '\n'.join(lines) self.assertEqual(actual, expected) + @unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") + def test_tlm_segment(self): + """verify printing of TLM segment""" + filename = opj_data_file('input/conformance/p0_15.j2k') + j = glymur.Jp2k(filename) + codestream = j.get_codestream() + with patch('sys.stdout', new=StringIO()) as fake_out: + print(codestream.segment[10]) + actual = fake_out.getvalue().strip() + + lines = ['TLM marker segment @ (268, 28)', + ' Index: 0', + ' Tile number: (0, 1, 2, 3)', + ' Length: (4267, 2117, 4080, 2081)'] + + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + @unittest.skipIf(sys.hexversion < 0x02070000, + "Differences in XML printing between 2.6 and 2.7") def test_xmp(self): """Verify the printing of a UUID/XMP box.""" j = glymur.Jp2k(self.jp2file) with patch('sys.stdout', new=StringIO()) as fake_out: - print(j.box[3]) + print(j.box[4]) actual = fake_out.getvalue().strip() - expected = fixtures.nemo_xmp_box + lst = ['UUID Box (uuid) @ (715, 2412)', + ' UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP)', + ' UUID Data: ', + ' ', + ' ', + ' ', + ' ', + ' '] + expected = '\n'.join(lst) self.assertEqual(actual, expected) def test_codestream(self): @@ -326,9 +606,9 @@ class TestPrinting(unittest.TestCase): print(j.get_codestream()) actual = fake_out.getvalue().strip() lst = ['Codestream:', - ' SOC marker segment @ (3231, 0)', - ' SIZ marker segment @ (3233, 47)', - ' Profile: no profile', + ' SOC marker segment @ (3135, 0)', + ' SIZ marker segment @ (3137, 47)', + ' Profile: 2', ' Reference Grid Height, Width: (1456 x 2592)', ' Vertical, Horizontal Reference Grid Offset: (0 x 0)', ' Reference Tile Height, Width: (1456 x 2592)', @@ -337,7 +617,7 @@ class TestPrinting(unittest.TestCase): ' Signed: (False, False, False)', ' Vertical, Horizontal Subsampling: ' + '((1, 1), (1, 1), (1, 1))', - ' COD marker segment @ (3282, 12)', + ' COD marker segment @ (3186, 12)', ' Coding style:', ' Entropy coder, without partitions', ' SOP marker segments: False', @@ -359,25 +639,59 @@ class TestPrinting(unittest.TestCase): ' Vertically stripe causal context: False', ' Predictable termination: False', ' Segmentation symbols: False', - ' QCD marker segment @ (3296, 7)', + ' QCD marker segment @ (3200, 7)', ' Quantization style: no quantization, ' + '2 guard bits', ' Step size: [(0, 8), (0, 9), (0, 9), (0, 10)]', - ' CME marker segment @ (3305, 37)', + ' CME marker segment @ (3209, 37)', ' "Created by OpenJPEG version 2.0.0"'] expected = '\n'.join(lst) self.assertEqual(actual, expected) + @unittest.skipIf(sys.hexversion < 0x02070000, + "Differences in XML printing between 2.6 and 2.7") + @unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") + def test_xml(self): + """verify printing of XML box""" + filename = opj_data_file('input/conformance/file1.jp2') + j = glymur.Jp2k(filename) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(j.box[2]) + actual = fake_out.getvalue().strip() + + lines = ['XML Box (xml ) @ (36, 439)', + ' ', + + ' ', + ' ' + + '2001-11-01T13:45:00.000-06:00' + + '', + + ' ' + + 'Professional 120 Image' + + '', + + ' ', + ' '] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + @unittest.skipIf(sys.hexversion < 0x03000000, "Only trusting python3 for printing non-ascii chars") def test_xml_latin1(self): """Should be able to print an XMLBox with utf-8 encoding (latin1).""" # Seems to be inconsistencies between different versions of python2.x - # as to what gets printed. + # as to what gets printed. # # 2.7.5 (fedora 19) prints xml entities. # 2.7.3 seems to want to print hex escapes. - text = u"""Strömung""" + text = u""" + Strömung""" if sys.hexversion < 0x03000000: xml = ET.parse(StringIO(text.encode('utf-8'))) else: @@ -399,13 +713,14 @@ class TestPrinting(unittest.TestCase): @unittest.skipIf(sys.hexversion < 0x03000000, "Only trusting python3 for printing non-ascii chars") def test_xml_cyrrilic(self): - """Should be able to print XMLBox with utf-8 encoding (cyrrillic).""" + """Should be able to print an XMLBox with utf-8 encoding (cyrrillic).""" # Seems to be inconsistencies between different versions of python2.x - # as to what gets printed. + # as to what gets printed. # # 2.7.5 (fedora 19) prints xml entities. # 2.7.3 seems to want to print hex escapes. - text = u"""Россия""" + text = u""" + Россия""" if sys.hexversion < 0x03000000: xml = ET.parse(StringIO(text.encode('utf-8'))) else: @@ -417,8 +732,7 @@ class TestPrinting(unittest.TestCase): actual = fake_out.getvalue().strip() if sys.hexversion < 0x03000000: lines = ["XML Box (xml ) @ (-1, 0)", - (" Росс", - "ия")] + " Россия"] else: lines = ["XML Box (xml ) @ (-1, 0)", " Россия"] @@ -426,6 +740,113 @@ class TestPrinting(unittest.TestCase): expected = '\n'.join(lines) self.assertEqual(actual, expected) + @unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") + def test_channel_definition(self): + """verify printing of cdef box""" + filename = opj_data_file('input/conformance/file2.jp2') + j = glymur.Jp2k(filename) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(j.box[2].box[2]) + actual = fake_out.getvalue().strip() + lines = ['Channel Definition Box (cdef) @ (81, 28)', + ' Channel 0 (color) ==> (3)', + ' Channel 1 (color) ==> (2)', + ' Channel 2 (color) ==> (1)'] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + @unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") + def test_component_mapping(self): + """verify printing of cmap box""" + filename = opj_data_file('input/conformance/file9.jp2') + j = glymur.Jp2k(filename) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(j.box[2].box[2]) + actual = fake_out.getvalue().strip() + lines = ['Component Mapping Box (cmap) @ (848, 20)', + ' Component 0 ==> palette column 0', + ' Component 0 ==> palette column 1', + ' Component 0 ==> palette column 2'] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + @unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") + def test_palette7(self): + """verify printing of pclr box""" + filename = opj_data_file('input/conformance/file9.jp2') + j = glymur.Jp2k(filename) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(j.box[2].box[1]) + actual = fake_out.getvalue().strip() + lines = ['Palette Box (pclr) @ (66, 782)', + ' Size: (256 x 3)'] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + @unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") + def test_rreq(self): + """verify printing of reader requirements box""" + filename = opj_data_file('input/conformance/file7.jp2') + j = glymur.Jp2k(filename) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(j.box[2]) + actual = fake_out.getvalue().strip() + lines = ['Reader Requirements Box (rreq) @ (44, 24)', + ' Standard Features:', + ' Feature 005: ' + + 'Unrestricted JPEG 2000 Part 1 codestream, ' + + 'ITU-T Rec. T.800 | ISO/IEC 15444-1', + ' Feature 060: e-sRGB enumerated colorspace', + + ' Feature 043: ' + + '(Deprecated) ' + + 'compositing layer uses restricted ICC profile', + + ' Vendor Features:'] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + @unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") + def test_differing_subsamples(self): + """verify printing of SIZ with different subsampling... Issue 86.""" + filename = opj_data_file('input/conformance/p0_05.j2k') + j = glymur.Jp2k(filename) + codestream = j.get_codestream() + with patch('sys.stdout', new=StringIO()) as fake_out: + print(codestream.segment[1]) + actual = fake_out.getvalue().strip() + lines = ['SIZ marker segment @ (2, 50)', + ' Profile: 0', + ' Reference Grid Height, Width: (1024 x 1024)', + ' Vertical, Horizontal Reference Grid Offset: (0 x 0)', + ' Reference Tile Height, Width: (1024 x 1024)', + ' Vertical, Horizontal Reference Tile Offset: (0 x 0)', + ' Bitdepth: (8, 8, 8, 8)', + ' Signed: (False, False, False, False)', + ' Vertical, Horizontal Subsampling: ' + + '((1, 1), (1, 1), (2, 2), (2, 2))'] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + @unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") + def test_palette_box(self): + """Verify that palette (pclr) boxes are printed without error.""" + filename = opj_data_file('input/conformance/file9.jp2') + j = glymur.Jp2k(filename) + with patch('sys.stdout', new=StringIO()) as fake_out: + print(j.box[2].box[1]) + actual = fake_out.getvalue().strip() + lines = ['Palette Box (pclr) @ (66, 782)', + ' Size: (256 x 3)'] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + @unittest.skipIf(os.name == "nt", "Temporary file issue on window.") def test_less_common_boxes(self): """verify uinf, ulst, url, res, resd, resc box printing""" @@ -503,465 +924,18 @@ class TestPrinting(unittest.TestCase): expected = '\n'.join(lines) self.assertEqual(actual, expected) - def test_flst(self): - """Verify printing of fragment list box.""" - flst = glymur.jp2box.FragmentListBox([89], [1132288], [0]) - with patch('sys.stdout', new=StringIO()) as fake_out: - print(flst) - actual = fake_out.getvalue().strip() - self.assertEqual(actual, fixtures.fragment_list_box) - - def test_dref(self): - """Verify printing of data reference box.""" - dref = glymur.jp2box.DataReferenceBox() - with patch('sys.stdout', new=StringIO()) as fake_out: - print(dref) - actual = fake_out.getvalue().strip() - self.assertEqual(actual, 'Data Reference Box (dtbl) @ (-1, 0)') - - def test_jplh_cgrp(self): - """Verify printing of compositing layer header box, color group box.""" - jpx = glymur.Jp2k(self.jpxfile) - with patch('sys.stdout', new=StringIO()) as fake_out: - print(jpx.box[7]) - actual = fake_out.getvalue().strip() - self.assertEqual(actual, fixtures.jplh_color_group_box) - - def test_free(self): - """Verify printing of Free box.""" - free = glymur.jp2box.FreeBox() - with patch('sys.stdout', new=StringIO()) as fake_out: - print(free) - actual = fake_out.getvalue().strip() - self.assertEqual(actual, 'Free Box (free) @ (-1, 0)') - - def test_nlst(self): - """Verify printing of number list box.""" - assn = (0, 16777216, 33554432) - nlst = glymur.jp2box.NumberListBox(assn) - with patch('sys.stdout', new=StringIO()) as fake_out: - print(nlst) - actual = fake_out.getvalue().strip() - self.assertEqual(actual, fixtures.number_list_box) - - def test_ftbl(self): - """Verify printing of fragment table box.""" - ftbl = glymur.jp2box.FragmentTableBox() - with patch('sys.stdout', new=StringIO()) as fake_out: - print(ftbl) - actual = fake_out.getvalue().strip() - self.assertEqual(actual, 'Fragment Table Box (ftbl) @ (-1, 0)') - - def test_jpch(self): - """Verify printing of JPCH box.""" - jpx = glymur.Jp2k(self.jpxfile) - with patch('sys.stdout', new=StringIO()) as fake_out: - print(jpx.box[3]) - actual = fake_out.getvalue().strip() - self.assertEqual(actual, 'Codestream Header Box (jpch) @ (887, 8)') - @unittest.skipIf(sys.hexversion < 0x03000000, "Ordered dicts not printing well in 2.7") - def test_exif_uuid(self): - """Verify printing of exif information""" - with tempfile.NamedTemporaryFile(suffix='.jp2', mode='wb') as tfile: - - with open(self.jp2file, 'rb') as ifptr: - tfile.write(ifptr.read()) - - # Write L, T, UUID identifier. - tfile.write(struct.pack('>I4s', 76, b'uuid')) - tfile.write(b'JpgTiffExif->JP2') - - tfile.write(b'Exif\x00\x00') - xbuffer = struct.pack(' (3)', - ' Channel 1 (color) ==> (2)', - ' Channel 2 (color) ==> (1)'] - expected = '\n'.join(lines) - self.assertEqual(actual, expected) - - def test_component_mapping(self): - """verify printing of cmap box""" - filename = opj_data_file('input/conformance/file9.jp2') - with self.assertWarns(UserWarning): - j = glymur.Jp2k(filename) - with patch('sys.stdout', new=StringIO()) as fake_out: - print(j.box[2].box[2]) - actual = fake_out.getvalue().strip() - lines = ['Component Mapping Box (cmap) @ (848, 20)', - ' Component 0 ==> palette column 0', - ' Component 0 ==> palette column 1', - ' Component 0 ==> palette column 2'] - expected = '\n'.join(lines) - self.assertEqual(actual, expected) - - def test_palette7(self): - """verify printing of pclr box""" - filename = opj_data_file('input/conformance/file9.jp2') - with self.assertWarns(UserWarning): - j = glymur.Jp2k(filename) - with patch('sys.stdout', new=StringIO()) as fake_out: - print(j.box[2].box[1]) - actual = fake_out.getvalue().strip() - lines = ['Palette Box (pclr) @ (66, 782)', - ' Size: (256 x 3)'] - expected = '\n'.join(lines) - self.assertEqual(actual, expected) - - def test_rreq(self): - """verify printing of reader requirements box""" - filename = opj_data_file('input/nonregression/text_GBR.jp2') - with self.assertWarns(UserWarning): - j = glymur.Jp2k(filename) - with patch('sys.stdout', new=StringIO()) as fake_out: - print(j.box[2]) - actual = fake_out.getvalue().strip() - self.assertEqual(actual, fixtures.text_GBR_rreq) - - def test_palette_box(self): - """Verify that palette (pclr) boxes are printed without error.""" - filename = opj_data_file('input/conformance/file9.jp2') - with self.assertWarns(UserWarning): - j = glymur.Jp2k(filename) - with patch('sys.stdout', new=StringIO()) as fake_out: - print(j.box[2].box[1]) - actual = fake_out.getvalue().strip() - lines = ['Palette Box (pclr) @ (66, 782)', - ' Size: (256 x 3)'] - expected = '\n'.join(lines) - self.assertEqual(actual, expected) - - def test_icc_profile(self): + @unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") + def test_jpx_approx_icc_profile(self): """verify icc profile printing with a jpx""" # ICC profiles may be used in JP2, but the approximation field should # be zero unless we have jpx. This file does both. filename = opj_data_file('input/nonregression/text_GBR.jp2') - with self.assertWarns(UserWarning): + with warnings.catch_warnings(): # brand is 'jp2 ', but has any icc profile. + warnings.simplefilter("ignore") jp2 = Jp2k(filename) with patch('sys.stdout', new=StringIO()) as fake_out: @@ -976,160 +950,89 @@ class TestPrintingOpjDataRootWarns(unittest.TestCase): self.assertEqual(actual, expected) + @unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") def test_uuid(self): """verify printing of UUID box""" filename = opj_data_file('input/nonregression/text_GBR.jp2') - with self.assertWarns(UserWarning): + with warnings.catch_warnings(): + # brand is 'jp2 ', but has any icc profile. + warnings.simplefilter("ignore") jp2 = Jp2k(filename) with patch('sys.stdout', new=StringIO()) as fake_out: print(jp2.box[4]) actual = fake_out.getvalue().strip() lines = ['UUID Box (uuid) @ (1544, 25)', - ' UUID: 3a0d0218-0ae9-4115-b376-4bca41ce0e71 (unknown)', + ' UUID: 3a0d0218-0ae9-4115-b376-4bca41ce0e71', ' UUID Data: 1 bytes'] expected = '\n'.join(lines) self.assertEqual(actual, expected) - def test_issue182(self): - """Should not show the format string in output.""" - # The cmap box is wildly broken, but printing was still wrong. - # Format strings like %d were showing up in the output. - filename = opj_data_file('input/nonregression/mem-b2ace68c-1381.jp2') + @unittest.skipIf(sys.hexversion < 0x03000000, + "Ordered dicts not printing well in 2.7") + def test_exif_uuid(self): + """Verify printing of exif information""" + j = glymur.Jp2k(self.jp2file) - with self.assertWarns(UserWarning): - # Ignore warning about bad pclr box. - jp2 = Jp2k(filename) with patch('sys.stdout', new=StringIO()) as fake_out: - print(jp2.box[3].box[3]) - actual = fake_out.getvalue().strip() - self.assertEqual(actual, fixtures.issue_182_cmap) - - def test_issue183(self): - filename = opj_data_file('input/nonregression/orb-blue10-lin-jp2.jp2') - - with self.assertWarns(UserWarning): - # Ignore warning about bad pclr box. - jp2 = Jp2k(filename) - with patch('sys.stdout', new=StringIO()) as fake_out: - print(jp2.box[2].box[1]) - actual = fake_out.getvalue().strip() - self.assertEqual(actual, fixtures.issue_183_colr) - - def test_bom(self): - """Byte order markers are illegal in UTF-8. Issue 185""" - filename = opj_data_file(os.path.join('input', - 'nonregression', - 'issue171.jp2')) - with self.assertWarns(UserWarning): - jp2 = Jp2k(filename) - with patch('sys.stdout', new=StringIO()): - # No need to verify, it's enough that we don't error out. - print(jp2) - - self.assertTrue(True) - - -class TestJp2dump(unittest.TestCase): - """Tests for verifying how jp2dump console script works.""" - def setUp(self): - self.jpxfile = glymur.data.jpxfile() - self.jp2file = glymur.data.nemo() - self.j2kfile = glymur.data.goodstuff() - - # Reset printoptions for every test. - glymur.set_printoptions(short=False, xml=True, codestream=True) - glymur.set_parseoptions(full_codestream=False) - - def tearDown(self): - glymur.set_parseoptions(full_codestream=False) - - def run_jp2dump(self, args): - sys.argv = args - with patch('sys.stdout', new=StringIO()) as fake_out: - command_line.main() - actual = fake_out.getvalue().strip() - # Remove the file line, as that is filesystem-dependent. - lines = actual.split('\n') - actual = '\n'.join(lines[1:]) - return actual - - def test_default_nemo(self): - """by default one should get the main header""" - actual = self.run_jp2dump(['', self.jp2file]) - - # shave off the non-main-header segments - lines = fixtures.nemo.split('\n') - expected = lines[0:140] - expected = '\n'.join(expected) - self.assertEqual(actual, expected) - - def test_jp2_codestream_0(self): - """Verify dumping with -c 0, supressing all codestream details.""" - actual = self.run_jp2dump(['', '-c', '0', self.jp2file]) - - # shave off the codestream details - lines = fixtures.nemo.split('\n') - expected = lines[0:105] - expected = '\n'.join(expected) - self.assertEqual(actual, expected) - - def test_jp2_codestream_1(self): - """Verify dumping with -c 1, print just the header.""" - actual = self.run_jp2dump(['', '-c', '1', self.jp2file]) - - # shave off the non-main-header segments - lines = fixtures.nemo.split('\n') - expected = lines[0:140] - expected = '\n'.join(expected) - self.assertEqual(actual, expected) - - def test_jp2_codestream_2(self): - """Verify dumping with -c 2, print entire jp2 jacket, codestream.""" - actual = self.run_jp2dump(['', '-c', '2', self.jp2file]) - - # shave off the non-main-header segments - expected = fixtures.nemo - self.assertEqual(actual, expected) - - @unittest.skipIf(sys.hexversion < 0x03000000, "assertRegex not in 2.7") - def test_j2k_codestream_0(self): - """-c 0 should print just a single line when used on a codestream.""" - sys.argv = ['', '-c', '0', self.j2kfile] - with patch('sys.stdout', new=StringIO()) as fake_out: - command_line.main() - actual = fake_out.getvalue().strip() - self.assertRegex(actual, "File: .*") - - def test_j2k_codestream_2(self): - """Verify dumping with -c 2, full details.""" - with patch('sys.stdout', new=StringIO()) as fake_out: - sys.argv = ['', '-c', '2', self.j2kfile] - command_line.main() + print(j.box[3]) actual = fake_out.getvalue().strip() - self.assertIn(fixtures.goodstuff_with_full_header, actual) + lines = ["UUID Box (uuid) @ (77, 638)", + " UUID: 4a706754-6966-6645-7869-662d3e4a5032 (Exif)", + " UUID Data: ", + "{'Image': {'Make': 'HTC',", + " 'Model': 'HTC Glacier',", + " 'XResolution': 72.0,", + " 'YResolution': 72.0,", + " 'ResolutionUnit': 2,", + " 'YCbCrPositioning': 1,", + " 'ExifTag': 138,", + " 'GPSTag': 354},", + " 'Photo': {'ISOSpeedRatings': 76,", + " 'ExifVersion': (48, 50, 50, 48),", + " 'DateTimeOriginal': '2013:02:09 14:47:53',", + " 'DateTimeDigitized': '2013:02:09 14:47:53',", + " 'ComponentsConfiguration': (1, 2, 3, 0),", + " 'FocalLength': 3.53,", + " 'FlashpixVersion': (48, 49, 48, 48),", + " 'ColorSpace': 1,", + " 'PixelXDimension': 2528,", + " 'PixelYDimension': 1424,", + " 'InteroperabilityTag': 324},", + " 'GPSInfo': {'GPSVersionID': (2, 2, 0),", + " 'GPSLatitudeRef': 'N',", + " 'GPSLatitude': [42.0, 20.0, 33.61],", + " 'GPSLongitudeRef': 'W',", + " 'GPSLongitude': [71.0, 5.0, 17.32],", + " 'GPSAltitudeRef': 0,", + " 'GPSAltitude': 0.0,", + " 'GPSTimeStamp': [19.0, 47.0, 53.0],", + " 'GPSMapDatum': 'WGS-84',", + " 'GPSProcessingMethod': (65,", + " 83,", + " 67,", + " 73,", + " 73,", + " 0,", + " 0,", + " 0,", + " 78,", + " 69,", + " 84,", + " 87,", + " 79,", + " 82,", + " 75),", + " 'GPSDateStamp': '2013:02:09'},", + " 'Iop': None}"] - def test_codestream_invalid(self): - """Verify dumping with -c 3, not allowd.""" - with self.assertRaises(ValueError): - sys.argv = ['', '-c', '3', self.jp2file] - command_line.main() + expected = '\n'.join(lines) - def test_short(self): - """Verify dumping with -s, short option.""" - actual = self.run_jp2dump(['', '-s', self.jp2file]) - - self.assertEqual(actual, fixtures.nemo_dump_short) - - def test_suppress_xml(self): - """Verify dumping with -x, suppress XML.""" - actual = self.run_jp2dump(['', '-x', self.jp2file]) - - # shave off the XML and non-main-header segments - lines = fixtures.nemo.split('\n') - expected = lines[0:18] - expected.extend(lines[104:140]) - expected = '\n'.join(expected) self.assertEqual(actual, expected) + + +if __name__ == "__main__": + unittest.main() diff --git a/glymur/version.py b/glymur/version.py index 4096ee5..4a9e4e5 100644 --- a/glymur/version.py +++ b/glymur/version.py @@ -1,24 +1,21 @@ -""" -This file is part of glymur, a Python interface for accessing JPEG 2000. - -http://glymur.readthedocs.org - -Copyright 2013 John Evans - -License: MIT -""" +# This file is part of glymur, a Python interface for accessing JPEG 2000. +# +# http://glymur.readthedocs.org +# +# Copyright 2013 John Evans +# +# License: MIT import sys +import numpy as np from distutils.version import LooseVersion -import lxml.etree -import numpy as np - -from .lib import openjpeg as opj, openjp2 as opj2 +from .lib import openjpeg as opj +from .lib import openjp2 as opj2 # Do not change the format of this next line! Doing so risks breaking # setup.py -version = "0.8.0" +version = "0.5.12" _sv = LooseVersion(version) version_tuple = _sv.version @@ -49,12 +46,10 @@ OPENJPEG {openjpeg} Python {python} sys.platform {platform} sys.maxsize {maxsize} -lxml {elxml} numpy {numpy} """.format(glymur=version, openjpeg=openjpeg_version, python=sys.version, platform=sys.platform, maxsize=sys.maxsize, - elxml=lxml.etree.__version__, numpy=np.__version__) diff --git a/setup.py b/setup.py index b49ed69..e2f72f0 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -from setuptools import setup +from setuptools import setup, find_packages import os import re import sys @@ -11,25 +11,24 @@ kwargs = {'name': 'Glymur', 'url': 'https://github.com/quintusdias/glymur', 'packages': ['glymur', 'glymur.data', 'glymur.test', 'glymur.lib', 'glymur.lib.test'], - 'package_data': {'glymur': ['data/*.jp2', - 'data/*.j2k', - 'data/*.jpx']}, - 'entry_points': { - 'console_scripts': ['jp2dump=glymur.command_line:main'], - }, + 'package_data': {'glymur': ['data/*.jp2', 'data/*.j2k']}, + 'scripts': ['bin/jp2dump'], 'license': 'MIT', 'test_suite': 'glymur.test'} -install_requires = ['numpy>=1.7.0', 'lxml>=3.0.0'] +instllrqrs = ['numpy>=1.4.1'] if sys.hexversion < 0x03030000: - install_requires.append('contextlib2>=0.4') - install_requires.append('mock>=1.0.1') -kwargs['install_requires'] = install_requires + instllrqrs.append('contextlib2>=0.4') + instllrqrs.append('mock>=1.0.1') +if sys.hexversion < 0x02070000: + instllrqrs.append('ordereddict>=1.1') + instllrqrs.append('unittest2>=0.5.1') +kwargs['install_requires'] = instllrqrs clssfrs = ["Programming Language :: Python", + "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.3", - "Programming Language :: Python :: 3.4", "Programming Language :: Python :: Implementation :: CPython", "License :: OSI Approved :: MIT License", "Development Status :: 5 - Production/Stable", @@ -43,7 +42,7 @@ kwargs['classifiers'] = clssfrs # Get the version string. Cannot do this by importing glymur! version_file = os.path.join('glymur', 'version.py') -with open(version_file, 'rt') as fptr: +with open('glymur/version.py', 'rt') as fptr: contents = fptr.read() match = re.search('version\s*=\s*"(?P\d*.\d*.\d*.*)"\n', contents) kwargs['version'] = match.group('version')