diff --git a/CHANGES.txt b/CHANGES.txt new file mode 100644 index 0000000..e69de29 diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..49ff165 --- /dev/null +++ b/MANIFEST @@ -0,0 +1,14 @@ +# file GENERATED by distutils, do NOT edit +README +setup.py +jptools/__init__.py +jptools/codestream.py +jptools/core.py +jptools/jp2box.py +jptools/jp2k.py +jptools/data/emneck.jp2 +jptools/test/__init__.py +jptools/test/test_codestream.py +jptools/test/test_conformance.py +jptools/test/test_jp2k.py +jptools/test/test_printing.py diff --git a/README b/README new file mode 100644 index 0000000..b11718c --- /dev/null +++ b/README @@ -0,0 +1,9 @@ +JPTOOLS +======= + +JPTOOLS provides a suite of JPEG2000 tools, including a bridge from Python to +the OpenJPEG library. + +Please read the docs. +===================== +Yes. Please. diff --git a/TODO b/TODO new file mode 100644 index 0000000..48883ad --- /dev/null +++ b/TODO @@ -0,0 +1,4 @@ +Test -c options with multiple values. +Test -b options with h*w>4096 +Test -b options with h or w > 1024 +Test -b options with h or w < 4 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/Makefile b/docs/Makefile new file mode 100644 index 0000000..f12a571 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,153 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/glymur.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/glymur.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/glymur" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/glymur" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..17a5342 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,190 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source +set I18NSPHINXOPTS=%SPHINXOPTS% source +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\glymur.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\glymur.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +:end diff --git a/docs/source/api.rst b/docs/source/api.rst new file mode 100644 index 0000000..8c9284e --- /dev/null +++ b/docs/source/api.rst @@ -0,0 +1,5 @@ +API +=== + +.. autoclass:: glymur.Jp2k + :members: diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..baafdd9 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,248 @@ +# -*- coding: utf-8 -*- +# +# glymur documentation build configuration file, created by +# sphinx-quickstart on Sun May 26 11:29:54 2013. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# 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('.')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#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. +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', + 'sphinx.ext.autosummary', + 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'numpydoc'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'glymur' +copyright = u'2013, John Evans' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.1' +# The full version, including alpha/beta/rc tags. +release = '0.1.0.a1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#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 + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#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 = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +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 = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#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 + +# 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 + +# 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, +# so a file named "default.css" will overwrite the builtin "default.css". +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' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#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 + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is 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 = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'glymurdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'glymur.tex', u'glymur Documentation', + u'John Evans', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'glymur', u'glymur Documentation', + [u'John Evans'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'glymur', u'glymur Documentation', + u'John Evans', 'glymur', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'http://docs.python.org/': None} diff --git a/docs/source/how_do_i.rst b/docs/source/how_do_i.rst new file mode 100644 index 0000000..2938255 --- /dev/null +++ b/docs/source/how_do_i.rst @@ -0,0 +1,24 @@ +************ +How do I...? +************ + +Display metadata? +================= +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, it is as simple as printing the Jp2k object, i.e.:: + + >>> import pkg_resources + >>> from glymur import Jp2k + >>> file = pkg_resources.resource_filename(glymur.__name__, "data/nemo.jp2") + >>> j = Jp2k(file) + >>> print(j) + +The primary emphasis is on JP2 metadata, but it is possible to +display raw codestream as well:: + + >>> print(j.get_codestream()) + diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..43632b4 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,25 @@ +.. glymur documentation master file, created by + sphinx-quickstart on Thu Jan 24 16:44:44 2013. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to glymur's documentation! +=================================== + +Contents: + +.. toctree:: + :maxdepth: 2 + + introduction + api + how_do_i + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst new file mode 100644 index 0000000..1428afa --- /dev/null +++ b/docs/source/introduction.rst @@ -0,0 +1,187 @@ +========================================= +glymur: a Python interface for JPEG 2000 +========================================= + +**glymur** contains a Python interface to the OpenJPEG library +which allows linux and mac users to read and write JPEG 2000 files. For more +information about OpenJPEG, please consult http://www.openjpeg.org. **glymur** +should be considered to be alpha-quality software. + +**glymur** tries to support reading (including all metadata) and writing of +JP2 and J2C files. Writing J2C/JP2 files is currently limited to images that +can fit in memory, however. + +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. + +------------ +Requirements +------------ +**glymur** works on Python 2.7 and 3.3. Python 3.3 is strongly recommended. + +OpenJPEG +======== +OpenJPEG must be built as a shared library. In addition, you +currently must compile OpenJPEG from the developmental source that +you can retrieve via subversion. As of this time of writing, svn +revision 2345 works. In addition, you should also retrieve their test data, as +you will need it when running **glymur**'s test suite. + +Earlier versions of OpenJPEG through the 2.0 official release are not supported. + +Be sure to have the following ports/RPMs/debs installed. + + * gcc + * gcc-c++ + * cmake + +You should build OpenJPEG with testing turned on. Consult the OpenJPEG +documentation on how to do this. If you use linux, make sure that you +have the following development packages installed + + * zlib-devel + * png-devel + * libtiff-devel + * lcms2-devel + +OS +== + +Mac OS X +-------- +All the necessary packages are available to use **glymur** with Python 3.3 via +MacPorts. A minimal set of ports includes + + * python33 + * py33-numpy + * py33-distribute + +To run all the testing, one of the following combinations of ports must +additionally be installed: + + * py33-scikit-image and either py33-Pillow or freeimage + * py33-matplotlib and py33-Pillow + +Linux +----- + +Fedora 18 +''''''''' +Fedora 18 ships with Python 3.3, so all the necessary RPMs are available to +meet the minimal set of requirements. + + * python3 + * python3-numpy + * python3-setuptools + * python3-matplotlib (for running tests) + * python3-matplotlib-tk (or whichever matplotlib backend you prefer) + +A few tests still will not run, however, unless one of the following +combinations of RPMs / Python packages is installed. + + * scikit-image and either Pillow or freeimage + * matplotlib and Pillow + +The 2nd route is probably the easiest, so go ahead and install **Pillow** +via **pip** since **Pillow** is not yet available in Fedora 18 default +repositories:: + + $ yum install python3-devel # pip needs this in order to compile Pillow + $ yum install python3-pip # First one must install pip of course! + $ pip-python3 install Pillow --user + $ export PYTHONPATH=$HOME/.local/lib/python3.3/site-packages:$PYTHONPATH + +Fedora 17 +''''''''' +Fedora 17 ships with Python 3.2 and 2.7, so these steps detail working with +2.7. + +Required RPMs include:: + + * python + * python-mock + * python-pip + * python-setuptools + * numpy + +In addition, you must install **contextlib2** via pip. + +A few tests still will not run, however, unless one of the following +combinations of RPMs / Python packages is installed. + + * scikit-image and either Pillow or freeimage + * matplotlib and Pillow + +**scikit-image** is not available in the Fedora 17 default repositories, but +it may be installed via **pip**:: + + $ yum install Cython # pip needs this in order to compile scikit-image + $ yum install python-devel # pip needs this in order to compile scikit-image + $ yum install freeimage # scikit-image uses this as a backend + $ yum install scipy # needed by scikit-image + $ pip-python install scikit-image --user + $ pip-python install contextlib2 --user + $ export PYTHONPATH=$HOME/.local/lib/python2.7/site-packages:$PYTHONPATH + +Windows +------- +Not currently supported. + +------------------------------------ +Installation, Testing, Configuration +------------------------------------ + +From this point forward, python3 will be referred to as just "python". + +Installation +============ + +In addition to merely installing **glymur**, you should adjust your **$PATH** +environment variable in order to be able to use the **jp2dump** script from +the unix command line. + +:: + + $ python setup.py install --prefix=/install/path + $ export PYTHONPATH=/install/path/lib/python3.3/site-packages + $ export PATH=/install/path/bin:$PATH + + +Configuration +============= +glymur uses **ctypes** (for the moment) to access the openjp2 library, and +because **ctypes** access libraries in a platform-dependent manner, it is +recommended that you create a configuration file to help glymur properly find +the openjp2 library. You may create the configuration file as follows:: + + $ mkdir ~/.glymur + $ cd ~/.glymur + $ cat > glymurrc << EOF + > [library] + > openjp2: /opt/openjp2-svn/lib/libopenjp2.so + > EOF + +That assumes, of course, that you've installed OpenJPEG into /opt/openjp2-svn. + + +Testing +======= +In order to run all of the test suite, you will first need the OpenJPEG test +data that you previously retrieved. +Then you should set the **OPJ_DATA_ROOT** environment variable to +point to this directory, e.g.:: + + $ cd /somewhere/outside/the/glymur/unpacking/directory + $ svn co http://openjpeg.googlecode.com/svn/data + $ export OPJ_DATA_ROOT=`pwd`/data + +The test suite may then be run with:: + + $ cd /back/to/glymur/unpacking/directory + $ python -m unittest discover + +At the moment, the development version of the library prints quite a few +warnings to stderr, which you may ignore. There are also more skipped tests +on Python 2.7 than on Python 3.3. The important thing will be whether +or not any test errors are reported at the end. diff --git a/glymur/__init__.py b/glymur/__init__.py new file mode 100644 index 0000000..e5f0350 --- /dev/null +++ b/glymur/__init__.py @@ -0,0 +1,99 @@ +"""glymur - read, write, and interrogate JPEG 2000 files +""" + + +def _glymurrc_fname(): + """Return the path to the configuration file. + + Search order: + 1) current working directory + 2) environ var GLYMURCONFIGDIR + 3) HOME/.glymur/glymurrc + """ + + # Current directory. + fname = os.path.join(os.getcwd(), 'glymurrc') + if os.path.exists(fname): + return fname + + # environ var GLYMURCONFIGDIR + if 'GLYMURCONFIGDIR' in os.environ: + path = os.environ['GLYMURCONFIGDIR'] + if os.path.exists(path): + fname = os.path.join(path, 'glymurrc') + if os.path.exists(fname): + return fname + + # HOME/.glymur/jptoolrsc + confdir = _get_configdir() + if confdir is not None: + fname = os.path.join(_get_configdir(), 'glymurrc') + if os.path.exists(fname): + return fname + + # didn't find a configuration file. + return None + + +def _config(): + """Read configuration file. + + Based on matplotlib. + """ + filename = _glymurrc_fname() + if filename is not None: + # Read the configuration file for the library location. + parser = ConfigParser() + parser.read(filename) + try: + libopenjp2_path = parser.get('library', 'openjp2') + if not os.path.exists(libopenjp2_path): + msg = 'OpenJP2 library path specified by configuration file ' + msg += 'does not exist.' + raise ImportError(msg) + except NoOptionError as e: + msg = "Error parsing configuration file '{0}': {1}" + msg = msg.format(fname, e.message) + warnings.warn(msg, UserWarning) + except: + raise + else: + # No help from the config file, try to find it ourselves. + from ctypes.util import find_library + libopenjp2_path = find_library('openjp2') + if libopenjp2_path is None: + raise ImportError('OpenJP2 library not found.') + + _OPENJP2 = ctypes.CDLL(libopenjp2_path) + return _OPENJP2 + + +def _get_configdir(): + """Return string representing the configuration directory. + + Default is HOME/.glymur. You can override this with the GLYMURCONFIGDIR + environment variable. + """ + + if 'GLYMURCONFIGDIR' in os.environ: + return os.environ['GLYMURCONFIGDIR'] + + if 'HOME' in os.environ: + return os.path.join(os.environ['HOME'], '.glymur') + +import sys +if sys.hexversion <= 0x03000000: + from ConfigParser import SafeConfigParser as ConfigParser + from ConfigParser import NoOptionError +else: + from configparser import ConfigParser + from configparser import NoOptionError +import ctypes +import os + +_OPENJP2 = _config() + +from .jp2k import Jp2k +from .jp2dump import jp2dump + +from . import test diff --git a/glymur/codestream.py b/glymur/codestream.py new file mode 100644 index 0000000..506450d --- /dev/null +++ b/glymur/codestream.py @@ -0,0 +1,1714 @@ +"""Codestream information. + +The module contains classes used to store information parsed from JPEG 2000 +codestreams. +""" + +import math +import struct +import sys +import warnings + +import numpy as np + +from .core import _progression_order_display +from .core import _wavelet_transform_display +from .core import _capabilities_display +from .lib import openjp2 as opj2 + +# Need a catch-all list of valid markers. +# See table A-1 in ISO/IEC FCD15444-1. +_valid_markers = [0xff00, 0xff01, 0xfffe] +for _marker in range(0xffc0, 0xffe0): + _valid_markers.append(_marker) +for _marker in range(0xfff0, 0xfff9): + _valid_markers.append(_marker) +for _marker in range(0xff4f, 0xff70): + _valid_markers.append(_marker) +for _marker in range(0xff90, 0xff94): + _valid_markers.append(_marker) + + +class Codestream: + """Container for codestream information. + + Attributes + ---------- + segment : list of marker segments + + Raises + ------ + IOError + If the file does not parse properly. + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system + """ + def __init__(self, f, header_only=True): + """ + Parameters + ---------- + f : file + Open file object. + header_only : bool, optional + If True, only marker segments in the main header are parsed. + Supplying False may impose a large performance penalty. + """ + + self._parse_tile_part_bit_stream_flag = False + + self.segment = [] + + # First two bytes are the SOC marker + buffer = f.read(2) + marker_id, = struct.unpack('>H', buffer) + segment = SOCsegment(offset=f.tell() - 2, length=0) + self.segment.append(segment) + + tile_offset = [] + tile_length = [] + + while True: + offset = f.tell() + buffer = f.read(2) + marker_id, = struct.unpack('>H', buffer) + + if marker_id >= 0xff30 and marker_id <= 0xff3f: + the_id = '0x{0:x}'.format(marker_id) + segment = Segment(id=the_id, offset=offset, length=0) + + elif marker_id == 0xff51: + # Need to keep track of the number of components from SIZ for + # other markers + segment = self._parseSIZsegment(f) + self._Csiz = len(segment.Ssiz) + + elif marker_id == 0xff52: + segment = self._parseCODsegment(f) + + elif marker_id == 0xff53: + segment = self._parseCOCsegment(f) + + elif marker_id == 0xff55: + segment = self._parseTLMsegment(f) + + elif marker_id == 0xff58: + segment = self._parsePLTsegment(f) + + elif marker_id == 0xff5c: + segment = self._parseQCDsegment(f) + + elif marker_id == 0xff5d: + segment = self._parseQCCsegment(f) + + elif marker_id == 0xff5e: + segment = self._parseRGNsegment(f) + + elif marker_id == 0xff5f: + segment = self._parsePODsegment(f) + + elif marker_id == 0xff60: + segment = self._parsePPMsegment(f) + + elif marker_id == 0xff61: + segment = self._parsePPTsegment(f) + + elif marker_id == 0xff63: + segment = self._parseCRGsegment(f) + + elif marker_id == 0xff64: + segment = self._parseCMEsegment(f) + + elif marker_id == 0xff90: + # Need to keep easy access to tile offsets and lengths for when + # we encounter start-of-data marker segments. + if header_only: + # Stop parsing as soon as we hit the first Start Of Tile. + return + + segment = self._parseSOTsegment(f) + if segment.offset not in tile_offset: + tile_offset.append(segment.offset) + tile_length.append(segment.Psot) + else: + msg = "Inconsistent start-of-tile (SOT) marker segment " + msg += "encountered in tile with index {0}. " + msg += "Codestream parsing terminated." + msg = msg.format(segment.Isot) + warnings.warn(msg) + return + + elif marker_id == 0xff93: + # start of data. Need to seek past the current tile part. + # The last SOT marker segment has the info that we need. + segment = self._parseSODsegment(f) + + elif marker_id == 0xffd9: + # end of codestream + segment = self._parseEOCsegment(f) + self.segment.append(segment) + break + + elif marker_id in _valid_markers: + # It's a reserved marker that I don't know anything about. + # See table A-1 in ISO/IEC FCD15444-1. + segment = self._parseGenericSegment(f, marker_id) + + elif ((marker_id & 0xff00) >> 8) == 255: + # Peek ahead to see if the next two bytes are a marker or not. + # Then seek back. + msg = "Unrecognized marker id: 0x{0:x}".format(marker_id) + warnings.warn(msg) + cpos = f.tell() + buffer = f.read(2) + next_item, = struct.unpack('>H', buffer) + f.seek(cpos) + if ((next_item & 0xff00) >> 8) == 255: + # No segment associated with this marker, so reset + # to two bytes after it. + segment = Segment(id='0x{0:x}'.format(marker_id), + offset=offset, length=0) + else: + segment = self._parseGenericSegment(f, marker_id) + + else: + msg = 'Invalid marker id encountered at byte {0:d}' + msg += 'in codestream: "0x{1:x}"' + msg = msg.format(offset, marker_id) + raise IOError(msg) + + self.segment.append(segment) + + if marker_id == 0xff93: + # If SOD, then we need to seek past the tile part bit stream. + x = f.tell() + if self._parse_tile_part_bit_stream_flag: + # But first parse the tile part bit stream for SOP and + # EPH segments. + self._parse_tile_part_bit_stream(f, segment, + tile_length[-1]) + + f.seek(tile_offset[-1] + tile_length[-1]) + + def _parse_tile_part_bit_stream(self, f, sod_marker, tile_length): + """Parse the tile part bit stream for SOP, EPH marker segments.""" + buffer = f.read(tile_length) + # The tile length could possibly be too large and extend past + # the end of file. We need to be a bit resilient. + count = min(tile_length, len(buffer)) + packet = np.frombuffer(buffer, dtype=np.uint8, count=count) + + indices = np.where(packet == 0xff) + for idx in indices[0]: + try: + if packet[idx+1] == 0x91 and (idx < (len(packet) - 5)): + kwargs = {} + kwargs['offset'] = sod_marker.offset + 2 + idx + kwargs['length'] = 4 + nsop = packet[(idx + 4):(idx+6)].view('uint16')[0] + if sys.byteorder == 'little': + nsop = nsop.byteswap() + kwargs['Nsop'] = nsop + segment = SOPsegment(**kwargs) + self.segment.append(segment) + elif packet[idx + 1] == 0x92: + kwargs = {} + kwargs['offset'] = sod_marker.offset + 2 + idx + kwargs['length'] = 0 + segment = EPHsegment(**kwargs) + self.segment.append(segment) + except IndexError: + continue + + def __str__(self): + msg = 'Codestream:\n' + for segment in self.segment: + strs = segment.__str__() + + # Add indentation + strs = [(' ' + x + '\n') for x in strs.split('\n')] + msg += ''.join(strs) + return msg + + def _parseSIZsegment(self, f): + """Parse the SIZ segment. + + Parameters + ---------- + f : file + Open file object. + + Returns + ------- + SIZsegment instance. + """ + kwargs = {} + kwargs['offset'] = f.tell() - 2 + + buffer = f.read(38) + data = struct.unpack('>HHIIIIIIIIH', buffer) + + kwargs['length'] = data[0] + kwargs['Rsiz'] = data[1] + + Xsiz = data[2] + Ysiz = data[3] + XOsiz = data[4] + YOsiz = data[5] + XTsiz = data[6] + YTsiz = data[7] + XTOsiz = data[8] + YTOsiz = data[9] + + num_tiles_x = (Xsiz - XOsiz) / (XTsiz - XTOsiz) + num_tiles_y = (Ysiz - YOsiz) / (YTsiz - 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) + + kwargs['Xsiz'] = Xsiz + kwargs['Ysiz'] = Ysiz + kwargs['XOsiz'] = XOsiz + kwargs['YOsiz'] = YOsiz + kwargs['XTsiz'] = XTsiz + kwargs['YTsiz'] = YTsiz + kwargs['XTOsiz'] = XTOsiz + kwargs['YTOsiz'] = YTOsiz + + num_components = data[10] + buffer = f.read(num_components * 3) + data = struct.unpack('>' + 'B' * num_components * 3, buffer) + + Ssiz = data[0::3] + kwargs['Ssiz'] = Ssiz + kwargs['_bitdepth'] = tuple(((x & 0x7f) + 1) for x in Ssiz) + kwargs['_signed'] = tuple(((x & 0xb0) > 0) for x in Ssiz) + + ssf = [] + 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) + kwargs['XRsiz'] = data[1::3] + kwargs['YRsiz'] = data[2::3] + + return SIZsegment(**kwargs) + + def _parseGenericSegment(self, f, marker_id): + """Parse a generic marker segment. + + Parameters + ---------- + f : file + Open file object. + + Returns + ------- + Segment instance. + """ + kwargs = {} + offset = f.tell() - 2 + + buffer = f.read(2) + length, = struct.unpack('>H', buffer) + data = f.read(length-2) + + segment = Segment(id='0x{0:x}'.format(marker_id), + offset=offset, length=length) + segment.data = data + return segment + + def _parseCMEsegment(self, f): + """Parse the CME marker segment. + + Parameters + ---------- + f : file + Open file object. + + Returns + ------- + CME segment instance. + """ + kwargs = {} + kwargs['offset'] = f.tell() - 2 + + buffer = f.read(4) + data = struct.unpack('>HH', buffer) + kwargs['length'] = data[0] + kwargs['Rcme'] = data[1] + kwargs['Ccme'] = f.read(kwargs['length'] - 4) + + return CMEsegment(**kwargs) + + def _parseCRGsegment(self, f): + """Parse the CRG marker segment. + + Parameters + ---------- + f : file + Open file object. + + Returns + ------- + CRG segment instance. + """ + kwargs = {} + kwargs['offset'] = f.tell() - 2 + + buffer = f.read(2) + length, = struct.unpack('>H', buffer) + kwargs['length'] = length + + buffer = f.read(4 * self._Csiz) + data = struct.unpack('>' + 'HH' * self._Csiz, buffer) + kwargs['Xcrg'] = data[0::2] + kwargs['Ycrg'] = data[1::2] + + return CRGsegment(**kwargs) + + def _parseEOCsegment(self, f): + """Parse the EOC marker segment. + + Parameters + ---------- + f : file + Open file object. + + Returns + ------- + EOC Segment instance. + """ + kwargs = {} + kwargs['offset'] = f.tell() - 2 + kwargs['length'] = 0 + + return EOCsegment(**kwargs) + + def _parseCOCsegment(self, f): + """Parse the COC marker segment. + + Parameters + ---------- + f : file + Open file object. + + Returns + ------- + COC segment instance. + """ + kwargs = {} + offset = f.tell() - 2 + kwargs['offset'] = offset + + buffer = f.read(2) + length, = struct.unpack('>H', buffer) + kwargs['length'] = length + + if self._Csiz <= 255: + buffer = f.read(1) + component, = struct.unpack('>B', buffer) + else: + buffer = f.read(2) + component, = struct.unpack('>H', buffer) + kwargs['Ccoc'] = component + + buffer = f.read(1) + kwargs['Scoc'], = struct.unpack('>B', buffer) + + n = offset + 2 + length - f.tell() + buffer = f.read(n) + SPcoc = np.frombuffer(buffer, dtype=np.uint8) + kwargs['SPcoc'] = SPcoc + + e1 = SPcoc[1] + e2 = SPcoc[2] + _code_block_size = (4 * math.pow(2, e2), 4 * math.pow(2, e1)) + kwargs['_code_block_size'] = _code_block_size + + if len(SPcoc) > 5: + kwargs['_precinct_size'] = _parse_precinct_size(SPcoc[5:]) + else: + kwargs['_precinct_size'] = None + + return COCsegment(**kwargs) + + def _parseCODsegment(self, f): + """Parse the COD segment. + + Parameters + ---------- + f : file + Open file object. + + Returns + ------- + COD segment instance. + """ + kwargs = {} + offset = f.tell() - 2 + kwargs['offset'] = f.tell() - 2 + + buffer = f.read(3) + length, Scod = struct.unpack('>HB', buffer) + kwargs['length'] = length + kwargs['Scod'] = Scod + + sop = (Scod & 2) > 0 + eph = (Scod & 4) > 0 + + if sop or eph: + self._parse_tile_part_bit_stream_flag = True + else: + self._parse_tile_part_bit_stream_flag = False + + n = offset + 2 + length - f.tell() + SPcod = f.read(n) + kwargs['SPcod'] = np.frombuffer(SPcod, dtype=np.uint8) + + # 0: + # 1: layers + params = struct.unpack('>BHBBBBBB', SPcod[0:9]) + kwargs['_layers'] = params[1] + kwargs['_numresolutions'] = params[3] + + if params[3] > opj2._J2K_MAXRLVLS: + msg = "Invalid number of resolutions ({0})." + msg = msg.format(params[3] + 1) + warnings.warn(msg) + + cblk_width = 4 * math.pow(2, params[4]) + cblk_height = 4 * math.pow(2, params[5]) + code_block_size = (cblk_height, cblk_width) + kwargs['_code_block_size'] = code_block_size + + if len(SPcod) > 9: + kwargs['_precinct_size'] = _parse_precinct_size(SPcod[9:]) + else: + kwargs['_precinct_size'] = None + + return CODsegment(**kwargs) + + def _parsePODsegment(self, f): + """Parse the POD segment. + + Parameters + ---------- + f : file + Open file object. + + Returns + ------- + POD segment instance. + """ + kwargs = {} + kwargs['offset'] = f.tell() - 2 + + buffer = f.read(2) + length, = struct.unpack('>H', buffer) + + if self._Csiz < 257: + n = int((length - 2) / 7) + buffer = f.read(n * 7) + fmt = '>' + 'BBHBBB' * n + else: + n = int((length - 2) / 9) + buffer = f.read(n * 9) + fmt = '>' + 'BHHBHB' * n + + data = struct.unpack(fmt, buffer) + + kwargs['length'] = length + kwargs['RSpod'] = data[0::6] + kwargs['CSpod'] = data[1::6] + kwargs['LYEpod'] = data[2::6] + kwargs['REpod'] = data[3::6] + kwargs['CEpod'] = data[4::6] + kwargs['Ppod'] = data[5::6] + + return PODsegment(**kwargs) + + def _parsePPMsegment(self, f): + """Parse the PPM segment. + + Parameters + ---------- + f : file + Open file object. + + Returns + ------- + PPM segment instance. + """ + kwargs = {} + kwargs['offset'] = f.tell() - 2 + + buffer = f.read(3) + length, zppm = struct.unpack('>HB', buffer) + kwargs['length'] = length + kwargs['Zppm'] = zppm + + n = length - 3 + kwargs['data'] = f.read(n) + + return PPMsegment(**kwargs) + + def _parsePLTsegment(self, f): + """Parse the PLT segment. + + The packet headers are not parsed, i.e. they remain "uninterpreted" + raw data beffers. + + Parameters + ---------- + f : file + Open file object. + + Returns + ------- + PLT segment instance. + """ + kwargs = {} + kwargs['offset'] = f.tell() - 2 + + buffer = f.read(3) + length, zplt = struct.unpack('>HB', buffer) + kwargs['length'] = length + kwargs['Zplt'] = zplt + + n = length - 3 + buffer = f.read(n) + iplt = np.frombuffer(buffer, dtype=np.uint8) + + packet_len = [] + plen = 0 + for x in iplt: + plen |= (x & 0x7f) + if x & 0x80: + # Continue by or-ing in the next byte. + plen <<= 7 + else: + packet_len.append(plen) + plen = 0 + + kwargs['Iplt'] = packet_len + + return PLTsegment(**kwargs) + + def _parsePPTsegment(self, f): + """Parse the PPT segment. + + The packet headers are not parsed, i.e. they remain "uninterpreted" + raw data beffers. + + Parameters + ---------- + f : file + Open file object. + + Returns + ------- + PPT segment instance. + """ + kwargs = {} + kwargs['offset'] = f.tell() - 2 + + buffer = f.read(3) + length, zppt = struct.unpack('>HB', buffer) + kwargs['length'] = length + kwargs['Zppt'] = zppt + + n = length - 3 + kwargs['Ippt'] = f.read(n) + + return PPTsegment(**kwargs) + + def _parseQuantization(self, buffer, sqcd): + """Tease out the quantization values. + + Args: + buffer: sequence of bytes from the QCC and QCD segments. + """ + n = len(buffer) + + exponent = [] + mantissa = [] + + if sqcd & 0x1f == 0: # no quantization + data = struct.unpack('>' + 'B' * n, buffer) + for j in range(len(data)): + exponent.append(data[j] >> 3) + mantissa.append(0) + else: + fmt = '>' + 'H' * int(n / 2) + data = struct.unpack(fmt, buffer) + for j in range(len(data)): + exponent.append(data[j] >> 11) + mantissa.append(data[j] & 0x07ff) + + return mantissa, exponent + + def _parseQCCsegment(self, f): + """Parse the QCC segment. + + Parameters + ---------- + f : file + Open file object. + + Returns + ------- + QCC Segment instance. + """ + kwargs = {} + offset = f.tell() - 2 + kwargs['offset'] = offset + + buffer = f.read(2) + length, = struct.unpack('>H', buffer) + kwargs['length'] = length + + if self._Csiz > 256: + buffer = f.read(3) + fmt = '>HB' + n = length - 5 + else: + buffer = f.read(2) + fmt = '>BB' + n = length - 4 + Cqcc, Sqcc = struct.unpack(fmt, buffer) + if Cqcc >= self._Csiz: + msg = "Invalid component number (%d), " + msg += "number of components is only %d." + msg = msg.format(Cqcc, self._Csiz) + warnings.warn(msg) + kwargs['Cqcc'] = Cqcc + + kwargs['Sqcc'] = Sqcc + kwargs['_guardBits'] = (Sqcc & 0xe0) >> 5 + + buffer = f.read(n) + + mantissa, exponent = self._parseQuantization(buffer, Sqcc) + + kwargs['SPqcc'] = buffer + kwargs['_exponent'] = exponent + kwargs['_mantissa'] = mantissa + + return QCCsegment(**kwargs) + + def _parseQCDsegment(self, f): + """Parse the QCD segment. + + Parameters + ---------- + f : file + Open file object. + + Returns + ------- + QCD Segment instance. + """ + kwargs = {} + kwargs['offset'] = f.tell() - 2 + + buffer = f.read(3) + length, sqcd = struct.unpack('>HB', buffer) + kwargs['length'] = length + kwargs['Sqcd'] = sqcd + + kwargs['_guardBits'] = (sqcd & 0xe0) >> 5 + + buffer = f.read(length - 3) + + mantissa, exponent = self._parseQuantization(buffer, sqcd) + + kwargs['SPqcd'] = buffer + kwargs['_exponent'] = exponent + kwargs['_mantissa'] = mantissa + + return QCDsegment(**kwargs) + + def _parseRGNsegment(self, f): + """Parse the RGN segment. + + Parameters + ---------- + f : file + Open file object. + + Returns + ------- + RGN segment instance. + """ + kwargs = {} + kwargs['offset'] = f.tell() - 2 + + buffer = f.read(2) + length, = struct.unpack('>H', buffer) + + if self._Csiz < 257: + buffer = f.read(3) + data = struct.unpack('>BBB', buffer) + else: + buffer = f.read(4) + data = struct.unpack('>HBB', buffer) + + kwargs['length'] = length + kwargs['Crgn'] = data[0] + kwargs['Srgn'] = data[1] + kwargs['SPrgn'] = data[2] + + return RGNsegment(**kwargs) + + def _parseSODsegment(self, f): + """Parse the SOD segment. + + Parameters + ---------- + f : file + Open file object. + + Returns + ------- + SOD segment instance. + """ + kwargs = {} + kwargs['offset'] = f.tell() - 2 + kwargs['length'] = 0 + + return SODsegment(**kwargs) + + def _parseSOTsegment(self, f): + """Parse the SOT segment. + + Parameters + ---------- + f : file + Open file object. + + Returns + ------- + SOT segment instance. + """ + kwargs = {} + kwargs['offset'] = f.tell() - 2 + + buffer = f.read(10) + data = struct.unpack('>HHIBB', buffer) + + kwargs['length'] = data[0] + kwargs['Isot'] = data[1] + kwargs['Psot'] = data[2] + kwargs['TPsot'] = data[3] + kwargs['TNsot'] = data[4] + + return SOTsegment(**kwargs) + + def _parseTLMsegment(self, f): + """Parse the TLM segment. + + Parameters + ---------- + f : file + Open file object. + + Returns + ------- + TLM segment instance. + """ + kwargs = {} + offset = f.tell() - 2 + kwargs['offset'] = offset + + buffer = f.read(2) + length, = struct.unpack('>H', buffer) + kwargs['length'] = length + + buffer = f.read(2) + Ztlm, Stlm = struct.unpack('>BB', buffer) + st = (Stlm >> 4) & 0x3 + sp = (Stlm >> 6) & 0x1 + + nbytes = length - 4 + if st == 0: + ntiles = nbytes / ((sp + 1) * 2) + else: + ntiles = nbytes / (st + (sp + 1) * 2) + + buffer = f.read(nbytes) + if st == 0: + Ttlm = None + fmt = '' + elif st == 1: + fmt = 'B' + elif st == 2: + fmt = 'H' + + if sp == 0: + fmt += 'H' + else: + fmt += 'I' + + data = struct.unpack('>' + fmt * int(ntiles), buffer) + if st == 0: + Ttlm = None + Ptlm = data + else: + Ttlm = data[0::2] + Ptlm = data[1::2] + + kwargs['Ztlm'] = Ztlm + kwargs['Ttlm'] = Ttlm + kwargs['Ptlm'] = Ptlm + + return TLMsegment(**kwargs) + + +class Segment: + """Segment information. + + Attributes + ---------- + id : str + Identifier for the segment. + offset : int + Offset of marker segment in bytes from beginning of file. + length : int + Length of marker segment in bytes. This number does not include the + two bytes constituting the marker. + """ + def __init__(self, id='', offset=-1, length=-1): + self.id = id + self.offset = offset + self.length = length + + def __str__(self): + msg = '{0} marker segment @ ({1}, {2})'.format(self.id, + self.offset, + self.length) + return msg + + def _print_quantization_style(self, sqcc): + """Only to be used with QCC and QCD segments.""" + + msg = '\n Quantization style: ' + if sqcc & 0x1f == 0: + msg += 'no quantization, ' + elif sqcc & 0x1f == 1: + msg += 'scalar implicit, ' + elif sqcc & 0x1f == 2: + msg += 'scalar explicit, ' + return msg + + +class COCsegment(Segment): + """COC (Coding style Component) segment information. + + Attributes + ---------- + id : str + Identifier for the segment. + offset : int + Offset of marker segment in bytes from beginning of file. + length : int + Length of marker segment in bytes. This number does not include the + two bytes constituting the marker. + Ccoc : byte + Index of associated component. + Scoc : byte + Coding style for this component. + SPcoc : byte array + Coding style parameters for this component. + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system + """ + def __init__(self, **kwargs): + Segment.__init__(self, id='COC') + self.__dict__.update(**kwargs) + + def __str__(self): + msg = Segment.__str__(self) + + msg += '\n Associated component: {0}'.format(self.Ccoc) + + msg += '\n Coding style for this component: ' + if self.Scoc == 0: + msg += 'Entropy coder, PARTITION = 0' + elif self.Scoc & 0x01: + msg += 'Entropy coder, PARTITION = 1' + + msg += '\n Coding style parameters:' + msg += '\n Number of resolutions: {0}' + msg += '\n Code block height, width: ({1} x {2})' + msg += '\n Wavelet transform: {3}' + msg = msg.format(self.SPcoc[0] + 1, + int(self._code_block_size[0]), + int(self._code_block_size[1]), + _wavelet_transform_display[self.SPcoc[4]]) + + msg += '\n ' + msg += _context_string(self.SPcoc[3]) + + if self._precinct_size is not None: + msg += '\n Precinct size: ' + for pps in self._precinct_size: + msg += '(%d, %d)'.format(pps) + + return msg + + +class CODsegment(Segment): + """COD segment information. + + Attributes + ---------- + id : str + Identifier for the segment. + offset : int + Offset of marker segment in bytes from beginning of file. + length : int + Length of marker segment in bytes. This number does not include the + two bytes constituting the marker. + Scod : int + Default coding style. + SPcod : bytes + Coding style parameters, including quality layers, multicomponent + transform usage, decomposition levels, code block size, style of code- + block passes, and which wavelet transform is used. + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system + """ + def __init__(self, **kwargs): + Segment.__init__(self, id='COD') + self.__dict__.update(**kwargs) + + def __str__(self): + msg = Segment.__str__(self) + + msg += '\n Coding style:' + msg += '\n Entropy coder, {0} partitions' + msg += '\n SOP marker segments: {1}' + msg += '\n EPH marker segments: {2}' + msg = msg.format('with' if (self.Scod & 1) else 'without', + ((self.Scod & 2) > 0), + ((self.Scod & 4) > 0)) + + if self.SPcod[3] == 0: + mct = 'no transform specified' + elif self.SPcod[3] & 0x01: + mct = 'reversible' + elif self.SPcod[3] & 0x02: + mct = 'irreversible' + else: + mct = 'unknown' + + msg += '\n ' + lines = ['Coding style parameters:', + ' Progression order: {0}', + ' Number of layers: {1}', + ' Multiple component transformation usage: {2}', + ' Number of resolutions: {3}', + ' Code block height, width: ({4} x {5})', + ' Wavelet transform: {6}'] + msg += '\n '.join(lines) + + msg = msg.format(_progression_order_display[self.SPcod[0]], + self._layers, + mct, + self.SPcod[4] + 1, + int(self._code_block_size[0]), + int(self._code_block_size[1]), + _wavelet_transform_display[self.SPcod[8]]) + + msg += '\n Precinct size: ' + if self._precinct_size is None: + msg += 'default, 2^15 x 2^15' + else: + for pps in self._precinct_size: + msg += '({0}, {1})'.format(pps[0], pps[1]) + + msg += '\n ' + msg += _context_string(self.SPcod[7]) + + return msg + + +class CMEsegment(Segment): + """CME (comment and extention) segment information. + + Attributes + ---------- + id : str + Identifier for the segment. + offset : int + Offset of marker segment in bytes from beginning of file. + length : int + Length of marker segment in bytes. This number does not include the + two bytes constituting the marker. + Rcme : int + Registration value of the marker segment. Zero means general binary + values, otherwise probably a string encoded in latin-1. + Ccme: bytes + Raw bytes representing the comment data. + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system + """ + def __init__(self, **kwargs): + Segment.__init__(self, id='CME') + self.__dict__.update(**kwargs) + + def __str__(self): + msg = Segment.__str__(self) + '\n' + if self.Rcme == 1: + # latin-1 string + msg += ' "{0}"' + msg = msg.format(self.Ccme.decode('latin-1')) + else: + msg += " binary data (Rcme = {0}): {1} bytes" + msg = msg.format(self.Rcme, len(self.Ccme)) + return msg + + +class CRGsegment(Segment): + """CRG (component registration) segment information. + + Attributes + ---------- + id : str + Identifier for the segment. + offset : int + Offset of marker segment in bytes from beginning of file. + length : int + Length of marker segment in bytes. This number does not include the + two bytes constituting the marker. + Xcrg, Ycrg : int + Horizontal, vertical offset for each component + """ + def __init__(self, **kwargs): + Segment.__init__(self, id='CRG') + self.__dict__.update(**kwargs) + + def __str__(self): + msg = Segment.__str__(self) + msg += '\n Vertical, Horizontal offset: ' + for j in range(len(self.Xcrg)): + msg += ' ({0:.2f}, {1:.2f})'.format(self.Ycrg[j]/65535.0, + self.Xcrg[j]/65535.0) + return msg + + +class EOCsegment(Segment): + """EOC segment information. + + Attributes + ---------- + id : str + Identifier for the segment. + offset : int + Offset of marker segment in bytes from beginning of file. + length : int + Length of marker segment in bytes. This number does not include the + two bytes constituting the marker, making the length for this marker + segment to be zero. + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system + """ + def __init__(self, **kwargs): + Segment.__init__(self, id='EOC') + self.__dict__.update(**kwargs) + + +class PODsegment(Segment): + """Progression Order Default/Change (POD) segment information. + + Attributes + ---------- + id : str + Identifier for the segment. + offset : int + Offset of marker segment in bytes from beginning of file. + length : int + Length of marker segment in bytes. This number does not include the + two bytes constituting the marker. + RSpod : tuple + resolution indices for start of a progression + CSpod : tuple + component indices for start of a progression + LYEpod : tuple + layer indices for end of a progression + REpod : tuple + resolution indices for end of a progression + CEpod : tuple + component indices for end of a progression + Ppod : tuple + progression order for each change + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system + """ + def __init__(self, **kwargs): + Segment.__init__(self, id='POD') + self.__dict__.update(**kwargs) + + def __str__(self): + msg = Segment.__str__(self) + for j in range(len(self.RSpod)): + + msg += '\n ' + lines = ['Progression change {0}:', + ' Resolution index start: {1}', + ' Component index start: {2}', + ' Layer index end: {3}', + ' Resolution index end: {4}', + ' Component index end: {5}', + ' Progression order: {6}'] + submsg = '\n '.join(lines) + msg += submsg.format(j, + self.RSpod[j], + self.CSpod[j], + self.LYEpod[j], + self.REpod[j], + self.CEpod[j], + _progression_order_display[self.Ppod[j]]) + + return msg + + +class PLTsegment(Segment): + """PLT segment information. + + Attributes + ---------- + id : str + Identifier for the segment. + offset : int + Offset of marker segment in bytes from beginning of file. + length : int + Length of marker segment in bytes. This number does not include the + two bytes constituting the marker. + Zplt : int + Index of this segment relative to other PLT segments. + Iplt : list + Packet lengths. + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system + """ + def __init__(self, **kwargs): + Segment.__init__(self, id='PLT') + self.__dict__.update(**kwargs) + + def __str__(self): + msg = Segment.__str__(self) + msg += "\n Index: {0}" + msg += "\n Iplt: {1}" + msg = msg.format(self.Zplt, self.Iplt) + + return msg + + +class PPMsegment(Segment): + """PPM segment information. + + Attributes + ---------- + id : str + Identifier for the segment. + offset : int + Offset of marker segment in bytes from beginning of file. + length : int + Length of marker segment in bytes. This number does not include the + two bytes constituting the marker. + Zppm : int + Index of this segment relative to other PPM segments. + data: byte array + Raw data buffer, constitutes both Nppm and Ippm fields. + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system + """ + def __init__(self, **kwargs): + Segment.__init__(self, id='PPM') + self.__dict__.update(**kwargs) + + def __str__(self): + msg = Segment.__str__(self) + msg += '\n Index: {0}' + msg += '\n Data: {1} uninterpreted bytes' + msg = msg.format(self.Zppm, len(self.data)) + return msg + + +class PPTsegment(Segment): + """PPT segment information. + + Attributes + ---------- + id : str + Identifier for the segment. + offset : int + Offset of marker segment in bytes from beginning of file. + length : int + Length of marker segment in bytes. This number does not include the + two bytes constituting the marker. + Zppt : int + Index of this segment relative to other PPT segments + Ippt : list + Uninterpreted packet headers. + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system + """ + def __init__(self, **kwargs): + Segment.__init__(self, id='PPT') + self.__dict__.update(**kwargs) + + def __str__(self): + msg = Segment.__str__(self) + msg += '\n Index: {0}' + msg += '\n Packet headers: {1} uninterpreted bytes' + msg = msg.format(self.Zppt, len(self.Ippt)) + return msg + + +class QCCsegment(Segment): + """QCC segment information. + + Attributes + ---------- + id : str + Identifier for the segment. + offset : int + Offset of marker segment in bytes from beginning of file. + length : int + Length of marker segment in bytes. This number does not include the + two bytes constituting the marker. + Cqcc : int + Index of associated component. + Sqcc : int + Quantization style for this component. + SPqcc : iterable bytes + Quantization value for each sub-band. + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system + """ + def __init__(self, **kwargs): + Segment.__init__(self, id='QCC') + self.__dict__.update(**kwargs) + + def __str__(self): + msg = Segment.__str__(self) + + msg += '\n Associated Component: {0}'.format(self.Cqcc) + msg += self._print_quantization_style(self.Sqcc) + msg += '{0} guard bits'.format(self._guardBits) + + step_size = zip(self._mantissa, self._exponent) + msg += '\n Step size: ' + str(list(step_size)) + return msg + + +class QCDsegment(Segment): + """QCD segment information. + + Attributes + ---------- + id : str + Identifier for the segment. + offset : int + Offset of marker segment in bytes from beginning of file. + length : int + Length of marker segment in bytes. This number does not include the + two bytes constituting the marker. + Sqcd : int + Quantization style for all components. + SPqcd : iterable bytes + Quantization step size values (uninterpreted). + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system + """ + def __init__(self, **kwargs): + Segment.__init__(self, id='QCD') + self.__dict__.update(**kwargs) + + def __str__(self): + msg = Segment.__str__(self) + + msg += self._print_quantization_style(self.Sqcd) + + msg += '{0} guard bits'.format(self._guardBits) + + step_size = zip(self._mantissa, self._exponent) + msg += '\n Step size: ' + str(list(step_size)) + return msg + + +class RGNsegment(Segment): + """RGN segment information. + + Attributes + ---------- + id : str + Identifier for the segment. + offset : int + Offset of marker segment in bytes from beginning of file. + length : int + Length of marker segment in bytes. This number does not include the + two bytes constituting the marker. + Crgn : int + Associated component. + Srgn : int + ROI style. + SPrgn : int + Parameter for ROI style. + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system + """ + def __init__(self, **kwargs): + Segment.__init__(self, id='RGN') + self.__dict__.update(**kwargs) + + def __str__(self): + msg = Segment.__str__(self) + + msg += '\n Associated component: {0}' + msg += '\n ROI style: {1}' + msg += '\n Parameter: {2}' + msg = msg.format(self.Crgn, self.Srgn, self.SPrgn) + + return msg + + +class SIZsegment(Segment): + """Container for SIZ segment information. + + Attributes + ---------- + id : str + Identifier for the segment. + offset : int + Offset of marker segment in bytes from beginning of file. + length : int + Length of marker segment in bytes. This number does not include the + two bytes constituting the marker. + Rsiz : int + Capabilities (profile) of codestream. + Xsiz, Ysiz : int + Width, height of reference grid. + XOsiz, YOsiz : int + Horizontal, vertical offset of reference grid. + XTsiz, YTsiz : int + 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. + Ssiz : iterable bytes + Precision (depth) in bits and sign of each component. + XRsiz, YRsiz : int + Horizontal and vertical sample separations with respect to reference + grid. + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system + """ + def __init__(self, **kwargs): + Segment.__init__(self, id='SIZ') + self.__dict__.update(**kwargs) + + def __str__(self): + msg = Segment.__str__(self) + msg += '\n ' + + lines = ['Profile: {0}', + 'Reference Grid Height, Width: ({1} x {2})', + 'Vertical, Horizontal Reference Grid Offset: ({3} x {4})', + 'Reference Tile Height, Width: ({5} x {6})', + 'Vertical, Horizontal Reference Tile Offset: ({7} x {8})', + 'Bitdepth: {9}', + 'Signed: {10}', + 'Vertical, Horizontal Subsampling: {11}'] + msg += '\n '.join(lines) + msg = msg.format(_capabilities_display[self.Rsiz], + self.Ysiz, self.Xsiz, + self.YOsiz, self.XOsiz, + self.YTsiz, self.XTsiz, + self.YTOsiz, self.XTOsiz, + self._bitdepth, + self._signed, + tuple(zip(self.YRsiz, self.XRsiz))) + + return msg + + +class SOCsegment(Segment): + """SOC segment information. + + Attributes + ---------- + id : str + Identifier for the segment. + offset : int + Offset of marker segment in bytes from beginning of file. + length : int + Length of marker segment in bytes. This number does not include the + two bytes constituting the marker, making the length for this marker + segment to be zero. + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system + """ + def __init__(self, **kwargs): + Segment.__init__(self, id='SOC') + self.__dict__.update(**kwargs) + + +class SODsegment(Segment): + """Container for Start of Data (SOD) segment information. + + Attributes + ---------- + id : str + Identifier for the segment. + offset : int + Offset of marker segment in bytes from beginning of file. + length : int + Length of marker segment in bytes. This number does not include the + two bytes constituting the marker, making the length for this marker + segment to be zero. + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system + """ + def __init__(self, **kwargs): + Segment.__init__(self, id='SOD') + self.__dict__.update(**kwargs) + + +class EPHsegment(Segment): + """Container for End of Packet (EPH) header information. + + Attributes + ---------- + id : str + Identifier for the segment. + offset : int + Offset of marker segment in bytes from beginning of file. + length : int + Length of marker segment in bytes. This number does not include the + two bytes constituting the marker, making the length for this marker + segment to be zero. + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system + """ + def __init__(self, **kwargs): + Segment.__init__(self, id='EPH') + self.__dict__.update(**kwargs) + + +class SOPsegment(Segment): + """Container for Start of Pata (SOP) segment information. + + Attributes + ---------- + id : str + Identifier for the segment. + offset : int + Offset of marker segment in bytes from beginning of file. + length : int + Length of marker segment in bytes. This number does not include the + two bytes constituting the marker. + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system + """ + def __init__(self, **kwargs): + Segment.__init__(self, id='SOP') + self.__dict__.update(**kwargs) + + def __str__(self): + msg = Segment.__str__(self) + msg += '\n Nsop: {0}'.format(self.Nsop) + return msg + + +class SOTsegment(Segment): + """Container for Start of Tile (SOT) segment information. + + Attributes + ---------- + id : str + Identifier for the segment. + offset : int + Offset of marker segment in bytes from beginning of file. + length : int + Length of marker segment in bytes. This number does not include the + two bytes constituting the marker. + Isot : int + Index of this particular tile. + Psot : int + Length, in bytes, from first byte of this SOT marker segment to the + end of the data of that tile part. + TPsot : int + Tile part instance. + TNsot : int + Number of tile-parts of a tile in codestream. + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system + """ + def __init__(self, **kwargs): + Segment.__init__(self, id='SOT') + self.__dict__.update(**kwargs) + + def __str__(self): + msg = Segment.__str__(self) + msg += '\n ' + lines = ['Tile part index: {0}', + 'Tile part length: {1}', + 'Tile part instance: {2}', + 'Number of tile parts: {3}'] + msg += '\n '.join(lines) + msg = msg.format(self.Isot, + self.Psot, + self.TPsot, + self.TNsot) + return msg + + +class TLMsegment(Segment): + """Container for TLM segment information. + + Attributes + ---------- + id : str + Identifier for the segment. + offset : int + Offset of marker segment in bytes from beginning of file. + length : int + Length of marker segment in bytes. This number does not include the + two bytes constituting the marker. + Ztlm : int + index relative to other TML marksers + Ttlm : int + number of the ith tile-part + Ptlm : int + length in bytes from beginning of the SOT marker of the ith + tile-part to the end of the data for that tile part + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system + """ + def __init__(self, **kwargs): + Segment.__init__(self, id='TLM') + self.__dict__.update(**kwargs) + + def __str__(self): + msg = Segment.__str__(self) + msg += '\n ' + lines = ['Index: {0}', + 'Tile number: {1}', + 'Length: {2}'] + msg += '\n '.join(lines) + msg = msg.format(self.Ztlm, + self.Ttlm, + self.Ptlm) + + return msg + + +def _parse_precinct_size(buffer): + """Compute precinct size from SPcod or SPcoc.""" + SPcocd = np.frombuffer(buffer, dtype=np.uint8) + precinct_size = [] + for x in SPcocd: + ep2 = (x & 0xF0) >> 4 + ep1 = x & 0x0F + precinct_size.append((2 ** ep1, 2 ** ep2)) + return precinct_size + + +def _context_string(context): + """Produce a string to represent the code block context""" + msg = 'Code block context:\n ' + lines = ['Selective arithmetic coding bypass: {0}', + 'Reset context probabilities on coding pass boundaries: {1}', + 'Termination on each coding pass: {2}', + 'Vertically stripe causal context: {3}', + 'Predictable termination: {4}', + 'Segmentation symbols: {5}'] + msg += '\n '.join(lines) + msg = msg.format(((context & 0x01) > 0), + ((context & 0x02) > 0), + ((context & 0x04) > 0), + ((context & 0x08) > 0), + ((context & 0x10) > 0), + ((context & 0x20) > 0)) + return msg diff --git a/glymur/core.py b/glymur/core.py new file mode 100644 index 0000000..73a286e --- /dev/null +++ b/glymur/core.py @@ -0,0 +1,123 @@ +# Progression order +LRCP = 0 +RLCP = 1 +RPCL = 2 +PCRL = 3 +CPRL = 4 + +_progression_order_display = { + LRCP: 'LRCP', + RLCP: 'RLCP', + RPCL: 'RPCL', + PCRL: 'PCRL', + CPRL: 'CPRL'} + +progression_order = { + 'LRCP': LRCP, + 'RLCP': RLCP, + 'RPCL': RPCL, + 'PCRL': PCRL, + 'CPRL': CPRL} + +WAVELET_TRANSFORM_9x7_IRREVERSIBLE = 0 +WAVELET_TRANSFORM_5x3_REVERSIBLE = 1 + +_wavelet_transform_display = { + WAVELET_TRANSFORM_9x7_IRREVERSIBLE: '9-7 irreversible', + WAVELET_TRANSFORM_5x3_REVERSIBLE: '5-3 reversible'} + +ENUMERATED_COLORSPACE = 1 +RESTRICTED_ICC_PROFILE = 2 +ANY_ICC_PROFILE = 3 +VENDOR_COLOR_METHOD = 4 + +_method_display = { + ENUMERATED_COLORSPACE: 'enumerated colorspace', + RESTRICTED_ICC_PROFILE: 'restricted ICC profile', + ANY_ICC_PROFILE: 'any ICC profile', + VENDOR_COLOR_METHOD: 'vendor color method'} + +_ = {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'} +_approximation_display = _ + +# Registration values for comment markers. +RCME_BINARY = 0 # binary value comments +RCME_ISO_8859_1 = 1 # comments in latin-1 codec + +# enumerated colorspaces +CMYK = 12 +SRGB = 16 +GREYSCALE = 17 +YCC = 18 +E_SRGB = 20 +ROMM_RGB = 21 + +_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 +_color_type_map_display = { + _COLOR: 'color', + _OPACITY: 'opacity', + _PRE_MULTIPLIED_OPACITY: 'pre-multiplied opacity', + _UNSPECIFIED: 'unspecified'} + +# enumerated color channel associations +_rgb_colorspace = {"R": 1, "G": 2, "B": 3} +_greyscale_colorspace = {"Y": 1} +_ycbcr_colorspace = {"Y": 1, "Cb": 2, "Cr": 3} +_colorspace = {SRGB: _rgb_colorspace, + GREYSCALE: _greyscale_colorspace, + YCC: _ycbcr_colorspace, + E_SRGB: _rgb_colorspace, + ROMM_RGB: _rgb_colorspace} + +# How to display the codestream profile. +_capabilities_display = { + 0: '2', + 1: '0', + 2: '1', + 3: '3'} + +# Reader requirements +RREQ_UNRESTRICTED_JPEG2000_PART_1 = 5 +RREQ_UNRESTRICTED_JPEG2000_PART_2 = 6 +RREQ_CMYK_ENUMERATED_COLORSPACE = 55 +RREQ_CMYK_ENUMERATED_COLORSPACE = 55 +RREQ_E_SRGB_ENUMERATED_COLORSPACE = 60 +RREQ_ROMM_RGB_ENUMERATED_COLORSPACE = 61 +_reader_requirements_display = { + 0: 'File not completely understood', + 1: 'Deprecated', + 2: 'Contains multiple composition layers', + 3: 'Deprecated', + 4: 'JPEG 2000 Part 1 Profile 1 codestream', + RREQ_UNRESTRICTED_JPEG2000_PART_1: + 'Unrestricted JPEG 2000 Part 1 codestream, ITU-T Rec. T.800 ' + + '| ISO/IEC 15444-1', + RREQ_UNRESTRICTED_JPEG2000_PART_2: + 'Unrestricted JPEG 2000 Part 2 codestream', + 7: 'JPEG codestream as defined in ISO/IEC 10918-1', + 8: 'Deprecated', + 9: 'Non-premultiplied opacity channel', + 10: 'Premultiplied opacity channel', + 12: 'Deprecated', + 18: 'Deprecated', + 43: '(Deprecated) compositing layer uses restricted ICC profile', + 44: 'Compositing layer uses Any ICC profile', + 45: 'Deprecated', + RREQ_CMYK_ENUMERATED_COLORSPACE: 'CMYK enumerated colorspace', + RREQ_E_SRGB_ENUMERATED_COLORSPACE: 'e-sRGB enumerated colorspace', + RREQ_ROMM_RGB_ENUMERATED_COLORSPACE: 'ROMM_RGB enumerated colorspace'} diff --git a/glymur/data/nemo.jp2 b/glymur/data/nemo.jp2 new file mode 100644 index 0000000..1b97d0d Binary files /dev/null and b/glymur/data/nemo.jp2 differ diff --git a/glymur/jp2box.py b/glymur/jp2box.py new file mode 100644 index 0000000..b2ca564 --- /dev/null +++ b/glymur/jp2box.py @@ -0,0 +1,1763 @@ +"""Classes for individual JPEG 2000 boxes. + +Contains classes for each individual JPEG2000 boxes. +""" + +import copy +import math +import os +import struct +import sys +import uuid +import warnings +import xml.etree.cElementTree as ET + +import numpy as np + +from .codestream import Codestream +from .core import _approximation_display +from .core import _colorspace_map_display +from .core import _color_type_map_display +from .core import _method_display +from .core import _reader_requirements_display + + +class Jp2kBox: + """Superclass for JPEG 2000 boxes. + + Attributes + ---------- + id : str + 4-byte 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, id='', offset=0, length=0, longname=''): + self.id = id + self.length = length + self.offset = offset + self.longname = longname + + def __str__(self): + msg = "{0} Box ({1})".format(self.longname, self.id) + msg += " @ ({0}, {1})".format(self.offset, self.length) + return msg + + def _parse_superbox(self, f): + """Parse a superbox (box consisting of nothing but other boxes. + + Parameters + ---------- + f : file + Open file object. + + Returns + ------- + List of top-level boxes in the JPEG 2000 file. + """ + + superbox = [] + + start = f.tell() + + while True: + + # Are we at the end of the superbox? + if start >= self.offset + self.length: + break + + buffer = f.read(8) + (L, T) = struct.unpack('>I4s', buffer) + if sys.hexversion >= 0x03000000: + T = T.decode('utf-8') + + 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. + num_bytes = self._file_size - f.tell() + 8 + + elif L == 1: + # The length of the box is in the XL field, a 64-bit value. + buffer = f.read(8) + num_bytes, = struct.unpack('>Q', buffer) + + else: + num_bytes = L + + # Call the proper parser for the given box with ID "T". + try: + box = _box_with_id[T]._parse(f, T, start, num_bytes) + except KeyError: + msg = 'Unrecognized box ({0}) encountered.'.format(T) + warnings.warn(msg) + box = Jp2kBox(T, offset=start, length=num_bytes, + longname='Unknown box') + + superbox.append(box) + + # Position to the start of the next box. + if num_bytes > self.length: + # Length of the current box goes past the end of the + # enclosing superbox. + msg = '{0} box has incorrect box length ({1})' + msg = msg.format(T, num_bytes) + warnings.warn(msg) + elif f.tell() > start + num_bytes: + # The box must be invalid somehow, as the file pointer is + # positioned past the end of the box. + msg = '{0} box may be invalid, the file pointer is positioned ' + msg += '{1} bytes past the end of the box.' + msg = msg.format(T, f.tell() - (start + num_bytes)) + warnings.warn(msg) + f.seek(start + num_bytes) + + start += num_bytes + + return superbox + + +class ColourSpecificationBox(Jp2kBox): + """Container for JPEG 2000 color specification box information. + + Attributes + ---------- + id : str + 4-byte 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. + method : int + Method for defining the colorspace. + precedence : int + How this box ranks in priority compared to other color specification + boxes. + approximation : int + Measure of colorspace accuracy. + color_space : int or None + Enumerated colorspace, corresponds to one of 'sRGB', 'greyscale', or + 'YCC'. If not None, then icc_profile must be None. + icc_profile : byte array or None + ICC profile according to ICC profile specification. If not None, then + color_space must be None. + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system" + """ + def __init__(self, **kwargs): + Jp2kBox.__init__(self, id='', longname='Colour Specification') + self.__dict__.update(**kwargs) + + def __str__(self): + msg = Jp2kBox.__str__(self) + + msg += '\n Method: {0}'.format(_method_display[self.method]) + msg += '\n Precedence: {0}'.format(self.precedence) + + if self.approximation is not 0: + x = _approximation_display[self.approximation] + msg += '\n Approximation: {0}'.format(x) + if self.color_space is not None: + x = _colorspace_map_display[self.color_space] + msg += '\n Colorspace: {0}'.format(x) + else: + x = len(self.icc_profile) + msg += '\n ICC Profile: {0} bytes'.format(x) + + return msg + + @staticmethod + def _parse(f, id, offset, length): + """Parse JPEG 2000 color specification box. + + Parameters + ---------- + f : file + Open file object. + id : byte + 4-byte unique identifier for this box. + offset : int + Start position of box in bytes. + length : int + Length of the box in bytes. + + Returns + ------- + kwargs : dictionary + dictionary of parameter values + """ + kwargs = {} + kwargs['id'] = id + kwargs['length'] = length + kwargs['offset'] = offset + + # Read the brand, minor version. + buffer = f.read(3) + (method, precedence, approximation) = struct.unpack('>BBB', buffer) + kwargs['method'] = method + kwargs['precedence'] = precedence + kwargs['approximation'] = approximation + + if method == 1: + # enumerated colour space + buffer = f.read(4) + kwargs['color_space'], = struct.unpack('>I', buffer) + kwargs['icc_profile'] = None + + else: + # ICC profile + kwargs['color_space'] = None + n = offset + length - f.tell() + kwargs['icc_profile'] = f.read(n) + + box = ColourSpecificationBox(**kwargs) + return box + + +class ComponentDefinitionBox(Jp2kBox): + """Container for component definition box information. + + Attributes + ---------- + id : str + 4-byte identifier for the box. + length : numeric scalar + 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. + component_number : int + number of the component + component_type : int + type of the component + association : int + number of the associated color + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system" + """ + def __init__(self, **kwargs): + Jp2kBox.__init__(self, id='', longname='Component Definition') + self.__dict__.update(**kwargs) + + def __str__(self): + msg = Jp2kBox.__str__(self) + for j in range(len(self.association)): + color_type_string = _color_type_map_display[self.component_type[j]] + if self.association[j] == 0: + assn = 'whole image' + else: + assn = str(self.association[j]) + msg += '\n Component {0} ({1}) ==> ({2})' + msg = msg.format(self.component_number[j], color_type_string, assn) + return msg + + @staticmethod + def _parse(f, id, offset, length): + """Parse component definition box. + + Parameters + ---------- + f : file + Open file object. + id : byte + 4-byte unique identifier for this box. + offset : int + Start position of box in bytes. + length : int + Length of the box in bytes. + + Returns + ------- + kwargs : dictionary of parameter values + """ + kwargs = {} + kwargs['id'] = id + kwargs['length'] = length + kwargs['offset'] = offset + + # Read the number of components. + buffer = f.read(2) + N, = struct.unpack('>H', buffer) + + component_number = [] + component_type = [] + association = [] + + buffer = f.read(N * 6) + data = struct.unpack('>' + 'HHH' * N, buffer) + kwargs['component_number'] = data[0:N * 6:3] + kwargs['component_type'] = data[1:N * 6:3] + kwargs['association'] = data[2:N * 6:3] + + box = ComponentDefinitionBox(**kwargs) + return box + + +class ComponentMappingBox(Jp2kBox): + """Container for channel identification information. + + Attributes + ---------- + id : str + 4-byte identifier for the box. + length : numeric scalar + Length of the box in bytes. + offset : int + Offset of the box from the start of the file. + longname : str + Verbose description of the box. + component_index : int + Index of component in codestream that is mapped to this channel. + mapping_type : int + mapping type, either direct use (0) or palette (1) + palette_index : int + Index component from palette + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system" + """ + def __init__(self, **kwargs): + Jp2kBox.__init__(self, id='', longname='Component Mapping') + self.__dict__.update(**kwargs) + + def __str__(self): + msg = Jp2kBox.__str__(self) + + for k in range(len(self.component_index)): + if self.mapping_type[k] == 1: + msg += '\n Component {0} ==> palette column {1}' + msg = msg.format(self.component_index[k], + self.palette_index[k]) + else: + msg += '\n Component %d ==> %d' + msg = msg.format(self.component_index[k], k) + return msg + + @staticmethod + def _parse(f, id, offset, length): + """Parse component mapping box. + + Parameters + ---------- + f : file + Open file object. + id : byte + 4-byte unique identifier for this box. + offset : int + Start position of box in bytes. + length : int + Length of the box in bytes. + + Returns + ------- + kwargs : dictionary of parameter values + """ + kwargs = {} + kwargs['id'] = id + kwargs['length'] = length + kwargs['offset'] = offset + + N = offset + length - f.tell() + num_components = int(N/4) + + buffer = f.read(N) + data = struct.unpack('>' + 'HBB' * num_components, buffer) + + kwargs['component_index'] = data[0:N:num_components] + kwargs['mapping_type'] = data[1:N:num_components] + kwargs['palette_index'] = data[2:N:num_components] + + box = ComponentMappingBox(**kwargs) + return box + + +class ContiguousCodestreamBox(Jp2kBox): + """Container for JPEG2000 codestream information. + + Attributes + ---------- + id : str + 4-byte 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. + main_header : list + List of segments in the codestream header. + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system" + """ + + def __init__(self, **kwargs): + Jp2kBox.__init__(self, id='jp2c', longname='Contiguous Codestream') + self.__dict__.update(**kwargs) + + def __str__(self): + msg = Jp2kBox.__str__(self) + msg += '\n Main header:' + for segment in self.main_header.segment: + segstr = segment.__str__() + + # Add indentation. + strs = [('\n ' + x) for x in segstr.split('\n')] + msg += ''.join(strs) + return msg + + @staticmethod + def _parse(f, id='jp2c', offset=0, length=0): + """Parse a codestream box. + + Parameters + ---------- + f : file + Open file object. + id : byte + 4-byte unique identifier for this box. + offset : int + Start position of box in bytes. + length : int + Length of the box in bytes. + + Returns + ------- + kwargs : dictionary of parameter values + """ + kwargs = {} + kwargs['id'] = id + kwargs['length'] = length + kwargs['offset'] = offset + + kwargs['main_header'] = Codestream(f, header_only=True) + + box = ContiguousCodestreamBox(**kwargs) + return box + + +class FileTypeBox(Jp2kBox): + """Container for JPEG 2000 file type box information. + + Attributes + ---------- + id : str + 4-byte 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. + brand: str + Specifies the governing recommendation or standard upon which this + file is based. + minor_version: int + Minor version number identifying the JP2 specification used. + compatibility_list: list + List of file conformance profiles. + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system" + """ + def __init__(self, **kwargs): + Jp2kBox.__init__(self, id='', longname='File Type') + self.__dict__.update(**kwargs) + + def __str__(self): + lst = [Jp2kBox.__str__(self), + ' Brand: {0}', + ' Compatibility: {1}'] + msg = '\n'.join(lst) + msg = msg.format(self.brand, self.compatibility_list) + + return msg + + @staticmethod + def _parse(f, id, offset, length): + """Parse JPEG 2000 file type box. + + Parameters + ---------- + f : file + Open file object. + id : byte + 4-byte unique identifier for this box. + offset : int + Start position of box in bytes. + length : int + Length of the box in bytes. + + Returns: + kwargs: dictionary of parameter values + """ + kwargs = {} + kwargs['id'] = id + kwargs['length'] = length + kwargs['offset'] = offset + + # Read the brand, minor version. + buffer = f.read(8) + (brand, minor_version) = struct.unpack('>4sI', buffer) + if sys.hexversion < 0x030000: + kwargs['brand'] = brand + else: + kwargs['brand'] = brand.decode('utf-8') + kwargs['minor_version'] = minor_version + + # Read the compatibility list. Each entry has 4 bytes. + current_pos = f.tell() + n = (offset + length - current_pos) / 4 + buffer = f.read(int(n) * 4) + compatibility_list = [] + for j in range(int(n)): + CL, = struct.unpack('>4s', buffer[4*j:4*(j+1)]) + if sys.hexversion >= 0x03000000: + CL = CL.decode('utf-8') + compatibility_list.append(CL) + + kwargs['compatibility_list'] = compatibility_list + + box = FileTypeBox(**kwargs) + return box + + +class ImageHeaderBox(Jp2kBox): + """Container for JPEG 2000 image header box information. + + Attributes + ---------- + id : str + 4-byte 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. + height, width : int + Height and width of image. + num_components : int + Number of image channels. + bits_per_component : int + Bits per component. + signed : bool + False if the image components are unsigned. + compression : nt + The compression type, should be 7 if JP2. + cspace_unknown : int + 0 if the color space is known and correctly specified. + ip_provided : int + 0 if the file does not contain intellectual propery rights information. + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system" + """ + def __init__(self, **kwargs): + Jp2kBox.__init__(self, id='', longname='Image Header') + self.__dict__.update(**kwargs) + + def __str__(self): + lst = [Jp2kBox.__str__(self)] + lst.append('Size: [{0} {1} {2}]'.format(self.height, self.width, + self.num_components)) + lst.append('Bitdepth: {0}'.format(self.bits_per_component)) + lst.append('Signed: {0}'.format(self.signed)) + if self.compression == 7: + lst.append('Compression: wavelet') + if self.cspace_unknown: + lst.append('Colorspace Unknown: True') + else: + lst.append('Colorspace Unknown: False') + return '\n '.join(lst) + + @staticmethod + def _parse(f, id, offset, length): + """Parse JPEG 2000 image header box. + + Parameters + ---------- + f : file + Open file object. + id : byte + 4-byte unique identifier for this box. + offset : int + Start position of box in bytes. + length : int + Length of the box in bytes. + + Returns: + kwargs: dictionary of parameter values + """ + kwargs = {} + kwargs['id'] = id + kwargs['length'] = length + kwargs['offset'] = offset + + # Read the box information + buffer = f.read(14) + params = struct.unpack('>IIHBBBB', buffer) + kwargs['height'] = params[0] + kwargs['width'] = params[1] + kwargs['num_components'] = params[2] + kwargs['bits_per_component'] = (params[3] & 0x7f) + 1 + kwargs['signed'] = (params[3] & 0x80) > 1 + kwargs['compression'] = params[4] + kwargs['cspace_unknown'] = params[5] + kwargs['ip_provided'] = params[6] + + box = ImageHeaderBox(**kwargs) + return box + + +class AssociationBox(Jp2kBox): + """Container for Association box information. + + Attributes + ---------- + id : str + 4-byte 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. + + References + ---------- + .. [JP2K15444-2m] International Organization for Standardication. ISO/IEC + 15444-2:2004 - Information technology -- JPEG 2000 image coding system: + Extensions + """ + def __init__(self, **kwargs): + Jp2kBox.__init__(self, id='', longname='Association') + self.__dict__.update(**kwargs) + + def __str__(self): + msg = Jp2kBox.__str__(self) + for box in self.box: + boxstr = box.__str__() + + # Add indentation. + strs = [('\n ' + x) for x in boxstr.split('\n')] + msg += ''.join(strs) + return msg + + @staticmethod + def _parse(f, id, offset, length): + """Parse association box. + + Parameters + ---------- + f : file + Open file object. + id : byte + 4-byte unique identifier for this box. + offset : int + Start position of box in bytes. + length : int + Length of the box in bytes. + + Returns + ------- + kwargs : dictionary of parameter values, for now just a single + 4-byte tuple. + """ + kwargs = {} + kwargs['id'] = id + kwargs['length'] = length + kwargs['offset'] = offset + + box = AssociationBox(**kwargs) + + # The JP2 header box is a superbox, so go ahead and parse its child + # boxes. + box.box = box._parse_superbox(f) + + return box + + +class JP2HeaderBox(Jp2kBox): + """Container for JP2 header box information. + + Attributes + ---------- + id : str + 4-byte 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. + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system" + """ + def __init__(self, **kwargs): + Jp2kBox.__init__(self, id='', longname='JP2 Header') + self.__dict__.update(**kwargs) + + def __str__(self): + msg = Jp2kBox.__str__(self) + for box in self.box: + boxstr = box.__str__() + + # Add indentation. + strs = [('\n ' + x) for x in boxstr.split('\n')] + msg += ''.join(strs) + return msg + + @staticmethod + def _parse(f, id, offset, length): + """Parse JPEG 2000 header box. + + Parameters + ---------- + f : file + Open file object. + id : byte + 4-byte unique identifier for this box. + offset : int + Start position of box in bytes. + length : int + Length of the box in bytes. + + Returns + ------- + kwargs : dictionary of parameter values, for now just a single + 4-byte tuple. + """ + kwargs = {} + kwargs['id'] = id + kwargs['length'] = length + kwargs['offset'] = offset + + box = JP2HeaderBox(**kwargs) + + # The JP2 header box is a superbox, so go ahead and parse its child + # boxes. + box.box = box._parse_superbox(f) + + return box + + +class JPEG2000SignatureBox(Jp2kBox): + """Container for JPEG 2000 signature box information. + + Attributes + ---------- + id : str + 4-byte 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. + signature : byte + Four-byte tuple identifying the file as JPEG 2000. + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system" + """ + def __init__(self, **kwargs): + Jp2kBox.__init__(self, id='', longname='JPEG 2000 Signature') + self.__dict__.update(**kwargs) + + def __str__(self): + msg = Jp2kBox.__str__(self) + msg += '\n Signature: {:02x}{:02x}{:02x}{:02x}' + msg = msg.format(*self.signature) + return msg + + @staticmethod + def _parse(f, id, offset, length): + """Parse JPEG 2000 signature box. + + Parameters + ---------- + f : file + Open file object. + id : byte + 4-byte unique identifier for this box. + offset : int + Start position of box in bytes. + length : int + Length of the box in bytes. + + Returns: + kwargs: dictionary of parameter values, for now just a single + 4-byte tuple. + """ + kwargs = {} + kwargs['id'] = id + kwargs['length'] = length + kwargs['offset'] = offset + + buffer = f.read(4) + kwargs['signature'] = struct.unpack('>BBBB', buffer) + + box = JPEG2000SignatureBox(**kwargs) + return box + + +class PaletteBox(Jp2kBox): + """Container for palette box information. + + Attributes + ---------- + id : str + 4-byte 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. + palette : list + Colormap represented as list of 1D arrays, one per color component. + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system" + """ + def __init__(self, **kwargs): + Jp2kBox.__init__(self, id='', longname='Palette') + self.__dict__.update(**kwargs) + + def __str__(self): + msg = Jp2kBox.__str__(self) + msg += '\n Size: ({0} x {1})'.format(len(self.palette[0]), + len(self.palette)) + return msg + + @staticmethod + def _parse(f, id, offset, length): + """Parse palette box. + + Parameters + ---------- + f : file + Open file object. + id : byte + 4-byte unique identifier for this box. + offset : int + Start position of box in bytes. + length : int + Length of the box in bytes. + + Returns + ------- + kwargs: dictionary of parameter values + """ + kwargs = {} + kwargs['id'] = id + kwargs['length'] = length + kwargs['offset'] = offset + + # Get the size of the palette. + buffer = f.read(3) + (NE, NC) = struct.unpack('>HB', buffer) + + # Need to determine bps and signed or not + buffer = f.read(NC) + data = struct.unpack('>' + 'B' * NC, buffer) + bps = [((x & 0x07f) + 1) for x in data] + signed = [((x & 0x80) > 1) for x in data] + kwargs['bits_per_component'] = bps + kwargs['signed'] = signed + + # 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. + palette = [] + fmt = '>' + bytes_per_row = 0 + for j in range(NC): + if bps[j] <= 8: + fmt += 'B' + bytes_per_row += 1 + palette.append(np.zeros(NE, dtype=np.uint8)) + elif bps[j] <= 16: + fmt += 'H' + bytes_per_row += 2 + palette.append(np.zeros(NE, dtype=np.uint16)) + elif bps[j] <= 32: + fmt += 'I' + bytes_per_row += 4 + palette.append(np.zeros(NE, dtype=np.uint32)) + else: + msg = 'Unsupported palette bitdepth (%d).' + raise IOError(msg) + n = NE * bytes_per_row + buffer = f.read(n) + + for j in range(NE): + row_buffer = buffer[(bytes_per_row * j):(bytes_per_row * (j + 1))] + row = struct.unpack(fmt, row_buffer) + for k in range(NC): + palette[k][j] = row[k] + + kwargs['palette'] = palette + box = PaletteBox(**kwargs) + return box + + +class ReaderRequirementsBox(Jp2kBox): + """Container for reader requirements box information. + + Attributes + ---------- + id : str + 4-byte 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. + FUAM : int + Fully Understand Aspects mask. + DCM : int + Decode completely mask. + standard_flag : list + Integers specifying standard features. + standard_mask : list + Specifies the compatibility mask for each corresponding standard + flag. + vendor_feature : list + Each item is a UUID corresponding to a vendor defined feature. + vendor_mask : list + Specifies the compatibility mask for each corresponding vendor + feature. + + References + ---------- + .. [JP2K15444-2m] International Organization for Standardication. ISO/IEC + 15444-2:2004 - Information technology -- JPEG 2000 image coding system: + Extensions + """ + def __init__(self, **kwargs): + Jp2kBox.__init__(self, id='', longname='Reader Requirements') + self.__dict__.update(**kwargs) + + def __str__(self): + msg = Jp2kBox.__str__(self) + + # TODO: include FUAM, DCM + + msg += '\n Standard Features:' + # TODO: include each standard mask + for j in range(len(self.standard_flag)): + sfl = self.standard_flag[j] + rrdisp = _reader_requirements_display[self.standard_flag[j]] + msg += '\n Feature {0:03d}: {1}'.format(sfl, rrdisp) + + # TODO: include the vendor mask + msg += '\n Vendor Features:' + for j in range(len(self.vendor_feature)): + msg += '\n UUID {0}'.format(self.vendor_feature[j]) + + return msg + + @staticmethod + def _parse(f, id, offset, length): + """Parse reader requirements box. + + Parameters + ---------- + f : file + Open file object. + id : byte + 4-byte unique identifier for this box. + offset : int + Start position of box in bytes. + length : int + Length of the box in bytes. + + Returns + ------- + ReaderRequirementsBox. + """ + kwargs = {} + kwargs['id'] = id + kwargs['length'] = length + kwargs['offset'] = offset + + buffer = f.read(1) + mask_length, = struct.unpack('>B', buffer) + if mask_length == 1: + mask_format = 'B' + elif mask_length == 2: + mask_format = 'H' + elif mask_length == 4: + mask_format = 'I' + else: + msg = 'Unhandled reader requirements box mask length (%d).' + msg %= mask_length + raise RuntimeError(msg) + + # Fully Understands Aspect Mask + # Decodes Completely Mask + buffer = f.read(2 * mask_length) + data = struct.unpack('>' + mask_format * 2, buffer) + kwargs['FUAM'] = data[0] + kwargs['DCM'] = data[1] + + buffer = f.read(2) + num_standard_flags, = struct.unpack('>H', 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. + buffer = f.read(num_standard_flags * (2 + mask_length)) + data = struct.unpack('>' + ('H' + mask_format) * num_standard_flags, + buffer) + kwargs['standard_flag'] = data[0:num_standard_flags * 2:2] + kwargs['standard_mask'] = data[1:num_standard_flags * 2:2] + + # Vendor features + buffer = f.read(2) + num_vendor_features, = struct.unpack('>H', 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 + buffer = f.read(num_vendor_features * entry_length) + vendor_feature = [] + vendor_mask = [] + for j in range(num_vendor_features): + ubuffer = buffer[j * entry_length:(j + 1) * entry_length] + vendor_feature.append(uuid.UUID(bytes=ubuffer[0:16])) + + vm = struct.unpack('>' + mask_format, ubuffer[16:]) + vendor_mask.append(vm) + + kwargs['vendor_feature'] = vendor_feature + kwargs['vendor_mask'] = vendor_mask + + box = ReaderRequirementsBox(**kwargs) + return box + + +class ResolutionBox(Jp2kBox): + """Container for Resolution superbox information. + + Attributes + ---------- + id : str + 4-byte 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. + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system" + """ + def __init__(self, **kwargs): + Jp2kBox.__init__(self, id='', longname='Resolution') + self.__dict__.update(**kwargs) + + def __str__(self): + msg = Jp2kBox.__str__(self) + for box in self.box: + boxstr = box.__str__() + + # Add indentation. + strs = [('\n ' + x) for x in boxstr.split('\n')] + msg += ''.join(strs) + return msg + + @staticmethod + def _parse(f, id, offset, length): + """Parse Resolution box. + + Parameters + ---------- + f : file + Open file object. + id : byte + 4-byte unique identifier for this box. + offset : int + Start position of box in bytes. + length : int + Length of the box in bytes. + + Returns + ------- + kwargs : dictionary of parameter values, for now just a single + 4-byte tuple. + """ + kwargs = {} + kwargs['id'] = id + kwargs['length'] = length + kwargs['offset'] = offset + + box = ResolutionBox(**kwargs) + + # The JP2 header box is a superbox, so go ahead and parse its child + # boxes. + box.box = box._parse_superbox(f) + + return box + + +class CaptureResolutionBox(ResolutionBox): + """Container for Capture resolution box information. + + Attributes + ---------- + id : str + 4-byte 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. + VR, HR : float + Vertical, horizontal resolution. + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system" + """ + def __init__(self, **kwargs): + ResolutionBox.__init__(self, id='', longname='Capture Resolution') + self.__dict__.update(**kwargs) + + def __str__(self): + msg = Jp2kBox.__str__(self) + msg += '\n VCR: {0}'.format(self.VR) + msg += '\n HCR: {0}'.format(self.HR) + return msg + + @staticmethod + def _parse(f, id, offset, length): + """Parse Resolution box. + + Parameters + ---------- + f : file + Open file object. + id : byte + 4-byte unique identifier for this box. + offset : int + Start position of box in bytes. + length : int + Length of the box in bytes. + + Returns + ------- + kwargs : dictionary of parameter values, for now just a single + 4-byte tuple. + """ + kwargs = {} + kwargs['id'] = id + kwargs['length'] = length + kwargs['offset'] = offset + + buffer = f.read(10) + (RN1, RD1, RN2, RD2, RE1, RE2) = struct.unpack('>HHHHBB', buffer) + kwargs['VR'] = RN1 / RD1 * math.pow(10, RE1) + kwargs['HR'] = RN2 / RD2 * math.pow(10, RE2) + + box = CaptureResolutionBox(**kwargs) + + return box + + +class DisplayResolutionBox(ResolutionBox): + """Container for Display resolution box information. + + Attributes + ---------- + id : str + 4-byte 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. + VR, HR : float + Vertical, horizontal resolution. + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system" + """ + def __init__(self, **kwargs): + ResolutionBox.__init__(self, id='', longname='Display Resolution') + self.__dict__.update(**kwargs) + + def __str__(self): + msg = Jp2kBox.__str__(self) + msg += '\n VDR: {0}'.format(self.VR) + msg += '\n HDR: {0}'.format(self.HR) + return msg + + @staticmethod + def _parse(f, id, offset, length): + """Parse Resolution box. + + Parameters + ---------- + f : file + Open file object. + id : byte + 4-byte unique identifier for this box. + offset : int + Start position of box in bytes. + length : int + Length of the box in bytes. + + Returns + ------- + kwargs : dictionary of parameter values, for now just a single + 4-byte tuple. + """ + kwargs = {} + kwargs['id'] = id + kwargs['length'] = length + kwargs['offset'] = offset + + buffer = f.read(10) + (RN1, RD1, RN2, RD2, RE1, RE2) = struct.unpack('>HHHHBB', buffer) + kwargs['VR'] = RN1 / RD1 * math.pow(10, RE1) + kwargs['HR'] = RN2 / RD2 * math.pow(10, RE2) + + box = DisplayResolutionBox(**kwargs) + + return box + + +class LabelBox(Jp2kBox): + """Container for Label box information. + + Attributes + ---------- + id : str + 4-byte 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. + label : str + Label + + References + ---------- + .. [JP2K15444-2m] International Organization for Standardication. ISO/IEC + 15444-2:2004 - Information technology -- JPEG 2000 image coding system: + Extensions + """ + def __init__(self, **kwargs): + Jp2kBox.__init__(self, id='', longname='Label') + self.__dict__.update(**kwargs) + + def __str__(self): + msg = Jp2kBox.__str__(self) + msg += '\n Label: {0}'.format(self.label) + return msg + + @staticmethod + def _parse(f, id, offset, length): + """Parse Label box. + + Parameters + ---------- + f : file + Open file object. + id : byte + 4-byte unique identifier for this box. + offset : int + Start position of box in bytes. + length : int + Length of the box in bytes. + + Returns + ------- + kwargs : dictionary of parameter values + """ + kwargs = {} + kwargs['id'] = id + kwargs['length'] = length + kwargs['offset'] = offset + + num_bytes = offset + length - f.tell() + buffer = f.read(num_bytes) + kwargs['label'] = buffer.decode('utf-8') + box = LabelBox(**kwargs) + return box + + +class XMLBox(Jp2kBox): + """Container for XML box information. + + Attributes + ---------- + id : str + 4-byte 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. + xml : ElementTree.Element + XML section. + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system" + """ + def __init__(self, **kwargs): + Jp2kBox.__init__(self, id='', longname='XML') + self.__dict__.update(**kwargs) + + def indent(self, elem, level=0): + """recipe for pretty printing XML. Please see + + http://effbot.org/zone/element-lib.htm#prettyprint + """ + 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: + self.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 __str__(self): + msg = Jp2kBox.__str__(self) + xml = self.xml + if self.xml is not None: + xml = copy.deepcopy(self.xml) + self.indent(xml) + xmltext = ET.tostring(xml).decode('utf-8') + + # Indent it a bit. + lst = [(' ' + x) for x in xmltext.split('\n')] + xml = '\n'.join(lst) + msg += '\n{0}'.format(xml) + else: + msg += '\n {0}'.format(xml) + return msg + + @staticmethod + def _parse(f, id, offset, length): + """Parse XML box. + + Parameters + ---------- + f : file + Open file object. + id : byte + 4-byte unique identifier for this box. + offset : int + Start position of box in bytes. + length : int + Length of the box in bytes. + + Returns + ------- + kwargs : dictionary of parameter values + """ + kwargs = {} + kwargs['id'] = id + kwargs['length'] = length + kwargs['offset'] = offset + + num_bytes = offset + length - f.tell() + buffer = f.read(num_bytes) + text = buffer.decode('utf-8') + + # Strip out any trailing nulls. + text = text.rstrip('\0') + + try: + kwargs['xml'] = ET.fromstring(text) + except ET.ParseError as e: + msg = 'A problem was encountered while parsing an XML box: "{0}"' + msg = msg.format(str(e)) + warnings.warn(msg, UserWarning) + kwargs['xml'] = None + + box = XMLBox(**kwargs) + return box + + +class UUIDListBox(Jp2kBox): + """Container for JPEG 2000 UUID list box. + + Attributes + ---------- + id : str + 4-byte 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. + ulst : list + List of UUIDs. + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system" + """ + def __init__(self, **kwargs): + Jp2kBox.__init__(self, id='', longname='UUID List') + self.__dict__.update(**kwargs) + + def __str__(self): + msg = Jp2kBox.__str__(self) + for enumerated_item in enumerate(self.ulst): + msg += '\n UUID[{0}]: {1}'.format(*enumerated_item) + return(msg) + + @staticmethod + def _parse(f, id, offset, length): + """Parse UUIDList box. + + Parameters + ---------- + f : file + Open file object. + id : byte + 4-byte unique identifier for this box. + offset : int + Start position of box in bytes. + length : int + Length of the box in bytes. + + Returns + ------- + kwargs : dictionary of parameter values + """ + buffer = f.read(2) + N, = struct.unpack('>H', buffer) + + ulst = [] + for j in range(N): + buffer = f.read(16) + ulst.append(uuid.UUID(bytes=buffer)) + + kwargs = {} + kwargs['id'] = id + kwargs['offset'] = offset + kwargs['length'] = length + kwargs['ulst'] = ulst + box = UUIDListBox(**kwargs) + return(box) + + +class UUIDInfoBox(Jp2kBox): + """Container for JPEG 2000 UUID Info superbox. + + Attributes + ---------- + id : str + 4-byte 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. + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system" + """ + def __init__(self, **kwargs): + Jp2kBox.__init__(self, id='', longname='UUIDInfo') + self.__dict__.update(**kwargs) + + def __str__(self): + msg = Jp2kBox.__str__(self) + + for box in self.box: + box_str = box.__str__() + + # Add indentation. + lst = [('\n ' + x) for x in box_str.split('\n')] + msg += ''.join(lst) + + return(msg) + + @staticmethod + def _parse(f, id, offset, length): + """Parse UUIDInfo super box. + + Parameters + ---------- + f : file + Open file object. + id : byte + 4-byte unique identifier for this box. + offset : int + Start position of box in bytes. + length : int + Length of the box in bytes. + + Returns + ------- + kwargs : dictionary of parameter values + """ + kwargs = {} + kwargs['id'] = id + kwargs['length'] = length + kwargs['offset'] = offset + + box = UUIDInfoBox(**kwargs) + + # The UUIDInfo box is a superbox, so go ahead and parse its child + # boxes. + box.box = box._parse_superbox(f) + + return box + + +class DataEntryURLBox(Jp2kBox): + """Container for data entry URL box information. + + Attributes + ---------- + id : str + 4-byte 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. + version : byte + Must be 0 for JP2. + flag : bytes + Particular attributes of this box, consists of three bytes. + URL : str + Associated URL. + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system" + """ + def __init__(self, **kwargs): + Jp2kBox.__init__(self, id='', longname='Data Entry URL') + self.__dict__.update(**kwargs) + + def __str__(self): + msg = Jp2kBox.__str__(self) + msg += '\n ' + + lines = ['Version: {0}', + 'Flag: {1} {2} {3}', + 'URL: "{4}"'] + msg += '\n '.join(lines) + msg = msg.format(self.version, + self.flag[0], self.flag[1], self.flag[2], + self.URL) + return msg + + @staticmethod + def _parse(f, id, offset, length): + """Parse Data Entry URL box. + + Parameters + ---------- + f : file + Open file object. + id : byte + 4-byte unique identifier for this box. + offset : int + Start position of box in bytes. + length : int + Length of the box in bytes. + + Returns + ------- + kwargs : dictionary of parameter values + """ + kwargs = {} + kwargs['id'] = id + kwargs['length'] = length + kwargs['offset'] = offset + + buffer = f.read(4) + data = struct.unpack('>BBBB', buffer) + kwargs['version'] = data[0] + kwargs['flag'] = data[1:4] + + n = offset + length - f.tell() + buffer = f.read(n) + kwargs['URL'] = buffer.decode('utf-8') + box = DataEntryURLBox(**kwargs) + return box + + +class UUIDBox(Jp2kBox): + """Container for UUID box information. + + Attributes + ---------- + id : str + 4-byte 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. + uuid : uuid.UUID + 16-byte UUID + data : bytes + Vendor-specific UUID data. + + References + ---------- + .. [JP2K15444-1i] International Organization for Standardication. ISO/IEC + 15444-1:2004 - Information technology -- JPEG 2000 image coding system: + Core coding system" + """ + def __init__(self, **kwargs): + Jp2kBox.__init__(self, id='', longname='UUID') + self.__dict__.update(**kwargs) + + def __str__(self): + msg = Jp2kBox.__str__(self) + msg += '\n UUID: {0}'.format(self.uuid) + msg += '\n UUID Data: {0} bytes'.format(len(self.data)) + return msg + + @staticmethod + def _parse(f, id, offset, length): + """Parse JPEG 2000 signature box. + + Parameters + ---------- + f : file + Open file object. + id : str + 4-byte unique identifier for this box. + offset : int + Start position of box in bytes. + length : int + Length of the box in bytes. + + Returns: + kwargs: dictionary of parameter values + """ + kwargs = {} + kwargs['id'] = id + kwargs['length'] = length + kwargs['offset'] = offset + + buffer = f.read(16) + kwargs['uuid'] = uuid.UUID(bytes=buffer) + + n = offset + length - f.tell() + buffer = f.read(n) + kwargs['data'] = buffer + box = UUIDBox(**kwargs) + return box + + +# Map each box ID to the corresponding class. +_box_with_id = { + 'asoc': AssociationBox, + 'cdef': ComponentDefinitionBox, + 'cmap': ComponentMappingBox, + 'colr': ColourSpecificationBox, + 'jP ': JPEG2000SignatureBox, + 'ftyp': FileTypeBox, + 'ihdr': ImageHeaderBox, + 'jp2h': JP2HeaderBox, + 'jp2c': ContiguousCodestreamBox, + 'lbl ': LabelBox, + 'pclr': PaletteBox, + 'res ': ResolutionBox, + 'resc': CaptureResolutionBox, + 'resd': DisplayResolutionBox, + 'rreq': ReaderRequirementsBox, + 'uinf': UUIDInfoBox, + 'ulst': UUIDListBox, + 'url ': DataEntryURLBox, + 'uuid': UUIDBox, + 'xml ': XMLBox} diff --git a/glymur/jp2dump.py b/glymur/jp2dump.py new file mode 100644 index 0000000..0e751e2 --- /dev/null +++ b/glymur/jp2dump.py @@ -0,0 +1,22 @@ +""" +Entry point for jp2dump script. +""" + +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. + """ + j = Jp2k(filename) + if codestream: + print(j.get_codestream(header_only=False)) + else: + print(j) diff --git a/glymur/jp2k.py b/glymur/jp2k.py new file mode 100644 index 0000000..cbd8be5 --- /dev/null +++ b/glymur/jp2k.py @@ -0,0 +1,671 @@ +"""Access to JPEG2000 files. + +License: MIT +""" +import sys +if sys.hexversion >= 0x03030000: + from contextlib import ExitStack +else: + from contextlib2 import ExitStack +import ctypes +import os +import struct +import warnings + +import numpy as np + +from .codestream import Codestream +from .core import progression_order +from .jp2box import Jp2kBox +from .lib import openjp2 as opj2 + +_cspace_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. +_CMPFUNC = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_char_p, ctypes.c_void_p) + + +def _default_error_handler(msg, client_data): + msg = "OpenJPEG library error: {0}".format(msg.decode('utf-8').rstrip()) + opj2._set_error_message(msg) + + +def _default_info_handler(msg, client_data): + print("[INFO] {0}".format(msg.decode('utf-8').rstrip())) + + +def _default_warning_handler(library_msg, client_data): + library_msg = library_msg.decode('utf-8').rstrip() + msg = "OpenJPEG library warning: {0}".format(library_msg) + warnings.warn(msg) + +_error_callback = _CMPFUNC(_default_error_handler) +_info_callback = _CMPFUNC(_default_info_handler) +_warning_callback = _CMPFUNC(_default_warning_handler) + + +class Jp2k(Jp2kBox): + """JPEG 2000 file. + + Attributes + ---------- + 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. + """ + + def __init__(self, filename, mode='rb'): + """ + Parameters + ---------- + filename : str or file + The path to JPEG 2000 file. + mode : str, optional + The mode used to open the file. + """ + self.filename = filename + self.mode = mode + self.box = [] + self.offset = 0 + + # Parse the file for JP2/JPX contents only if we are reading it. + if mode == 'rb': + self._parse() + + def __str__(self): + metadata = ['File: ' + os.path.basename(self.filename)] + if len(self.box) > 0: + for box in self.box: + metadata.append(box.__str__()) + else: + c = self.get_codestream() + metadata.append(c.__str__()) + return '\n'.join(metadata) + + def _parse(self): + """Parses the JPEG 2000 file. + + Raises + ------ + IOError + The file was not JPEG 2000. + """ + stat = os.stat(self.filename) + self.length = stat.st_size + self._file_size = stat.st_size + + with open(self.filename, 'rb') as f: + + # Make sure we have a JPEG2000 file. It could be either JP2 or + # J2C. Check for J2C first, single box in that case. + buffer = f.read(2) + signature, = struct.unpack('>H', buffer) + if signature == 0xff4f: + self._codec_format = opj2._CODEC_J2K + # That's it, we're done. The codestream object is only + # produced upon explicit request. + return + + self._codec_format = opj2._CODEC_JP2 + + # Should be JP2. + # First 4 bytes should be 12, the length of the 'jP ' box. + # 2nd 4 bytes should be the box ID ('jP '). + # 3rd 4 bytes should be the box signature (13, 10, 135, 10). + f.seek(0) + buffer = f.read(12) + values = struct.unpack('>I4s4B', buffer) + L = values[0] + T = values[1] + signature = values[2:] + if L != 12 or T != b'jP ' or signature != (13, 10, 135, 10): + msg = '{0} is not a JPEG 2000 file.'.format(self.filename) + raise IOError(msg) + + # Back up and start again, we know we have a superbox (box of + # boxes) here. + f.seek(0) + self.box = self._parse_superbox(f) + + def write(self, data, cratios=None, eph=False, psnr=None, numres=None, + cbsize=None, psizes=None, grid_offset=None, sop=False, + subsam=None, tilesize=None, prog=None, modesw=None, + colorspace=None, verbose=False): + """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 + ---------- + data : array + Image data to be written to file. + callbacks : bool, optional + If true, enable default info handler such that INFO messages + produced by the OpenJPEG library are output to the console. By + default, OpenJPEG warning and error messages are captured by + Python's own warning and error mechanisms. + cbsize : tuple, optional + Code block size (DY, DX). + colorspace : str, optional + Either 'rgb' or 'gray'. + cratios : sequence, optional + 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. + 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 : list, optional + Different PSNR for successive layers. + psizes : list, optional + List of precinct sizes. + 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 numpy as np + >>> import matplotlib as mpl + >>> import matplotlib.pyplot as plt + >>> import os + >>> file = os.path.join(mpl.get_data_path(), 'sample_data', 'lena.png') + >>> data = plt.imread(file) + >>> data = data * 255 + >>> from tempfile import NamedTemporaryFile + >>> tfile = NamedTemporaryFile(suffix='.jp2', delete=False) + >>> from glymur import Jp2k + >>> j = Jp2k(tfile.name, mode='wb') + >>> j.write(data.astype(np.uint8)) + """ + + cparams = opj2._set_default_encoder_parameters() + + outfile = self.filename.encode() + n = opj2._PATH_LEN - len(outfile) + outfile += b'0' * n + cparams.outfile = outfile + + if self.filename[-4:].lower() == '.jp2': + codec_fmt = opj2._CODEC_JP2 + else: + codec_fmt = opj2._CODEC_J2K + + cparams.cod_format = codec_fmt + + # Set defaults to lossless to begin. + cparams.tcp_rates[0] = 0 + cparams.tcp_numlayers = 1 + cparams.cp_disto_alloc = 1 + + if cbsize is not None: + w = cbsize[1] + h = cbsize[0] + if h * w > 4096 or h < 4 or w < 4: + msg = "Code block area cannot exceed 4096. " + msg += "Code block height and width must be larger than 4." + raise RuntimeError(msg) + cparams.cblockw_init = w + cparams.cblockh_init = h + + if cratios is not None: + cparams.tcp_numlayers = len(cratios) + for j, cratio in enumerate(cratios): + cparams.tcp_rates[j] = cratio + cparams.cp_disto_alloc = 1 + + if eph: + cparams.csty |= 0x04 + + if grid_offset is not None: + cparams.image_offset_x0 = grid_offset[1] + cparams.image_offset_y0 = grid_offset[0] + + if modesw is not None: + for x in range(6): + if modesw & (1 << x): + cparams.mode |= (1 << x) + + if numres is not None: + cparams.numresolution = numres + + if prog is not None: + prog = prog.upper() + cparams.prog_order = progression_order[prog] + + if psnr is not None: + cparams.tcp_numlayers = len(psnr) + for j, snr_layer in enumerate(psnr): + cparams.tcp_distoratio[j] = snr_layer + cparams.cp_fixed_quality = 1 + + if psizes is not None: + for j, precinct in enumerate(psizes): + cparams.prcw_init[j] = precinct[0] + cparams.prch_init[j] = precinct[1] + cparams.csty |= 0x01 + cparams.res_spec = len(psizes) + + if sop: + cparams.csty |= 0x02 + + if subsam is not None: + cparams.subsampling_dy = subsam[0] + cparams.subsampling_dx = subsam[1] + + if tilesize is not None: + cparams.cp_tdx = tilesize[1] + cparams.cp_tdy = tilesize[0] + cparams.tile_size_on = opj2._TRUE + + if cratios is not None and psnr is not None: + msg = "Cannot specify cratios and psnr together." + raise RuntimeError(msg) + + if data.ndim == 2: + numrows, numcols = data.shape + data = data.reshape(numrows, numcols, 1) + elif data.ndim == 3: + pass + else: + msg = "{0}D imagery is not allowed.".format(data.ndim) + raise IOError(msg) + + numrows, numcols, num_comps = data.shape + + if colorspace is None: + if data.shape[2] == 1 or data.shape[2] == 2: + colorspace = opj2._CLRSPC_GRAY + else: + # No YCC unless specifically told to do so. + colorspace = opj2._CLRSPC_SRGB + else: + if codec_fmt == opj2._CODEC_J2K: + raise IOError('Do not specify a colorspace with J2K.') + colorspace = colorspace.lower() + if colorspace not in ('rgb', 'grey', 'gray'): + msg = 'Invalid colorspace "{0}"'.format(colorspace) + raise IOError(msg) + elif colorspace == 'rgb' and data.shape[2] < 3: + msg = 'RGB colorspace requires at least 3 components.' + raise IOError(msg) + else: + colorspace = _cspace_map[colorspace] + + if data.dtype == np.uint8: + comp_prec = 8 + elif data.dtype == np.uint16: + comp_prec = 16 + else: + raise RuntimeError("unhandled datatype") + + comptparms = (opj2._image_comptparm_t * 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 + + image = opj2._image_create(comptparms, colorspace) + + # 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(data[:, :, k], dtype=np.int32) + dest = image.contents.comps[k].data + src = layer.ctypes.data + ctypes.memmove(dest, src, layer.nbytes) + + # set multi-component transform? + if image.contents.numcomps == 3: + cparams.tcp_mct = 1 + else: + cparams.tcp_mct = 0 + + codec = opj2._create_compress(codec_fmt) + + if verbose: + opj2._set_info_handler(codec, _info_callback) + else: + opj2._set_info_handler(codec, None) + + opj2._set_warning_handler(codec, _warning_callback) + opj2._set_error_handler(codec, _error_callback) + opj2._setup_encoder(codec, cparams, image) + strm = opj2._stream_create_default_file_stream_v3(self.filename, False) + opj2._start_compress(codec, image, strm) + opj2._encode(codec, strm) + opj2._end_compress(codec, strm) + opj2._stream_destroy_v3(strm) + opj2._destroy_codec(codec) + opj2._image_destroy(image) + + self._parse() + + def read(self, reduce=0, layer=0, area=None, tile=None, verbose=False): + """Read a JPEG 2000 image. + + Parameters + ---------- + layer : int, optional + Number of quality layer to decode. + reduce : int, optional + Factor by which to reduce output resolution. + 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 + ------- + result : array + The image data. + + Raises + ------ + IOError + If the image has differing subsample factors. + + Examples + -------- + >>> import glymur + >>> import pkg_resources as pkg + >>> jfile = pkg.resource_filename(glymur.__name__, "data/nemo.jp2") + >>> jp = glymur.Jp2k(jfile) + >>> data = jp.read(reduce=1) + """ + # Check for differing subsample factors. + 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." + raise IOError(msg) + + data = self._read_common(reduce=reduce, + layer=layer, + area=area, + tile=tile, + verbose=verbose, + as_bands=False) + + if data.shape[2] == 1: + data = data.view() + data.shape = data.shape[0:2] + + return data + + def _read_common(self, reduce=0, layer=0, area=None, tile=None, + verbose=False, as_bands=False): + """Read a JPEG 2000 image. + + Parameters + ---------- + layer : int, optional + Number of quality layer to decode. + reduce : int, optional + Factor by which to reduce output resolution. + 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. + as_bands : bool, optional + If true, return the individual 2D components in a list. + + Returns + ------- + data : list or array + The individual image components or a single array. + """ + dparam = opj2._set_default_decoder_parameters() + + infile = self.filename.encode() + nelts = opj2._PATH_LEN - len(infile) + infile += b'0' * nelts + dparam.infile = infile + + dparam.decod_format = self._codec_format + + dparam.cp_layer = layer + dparam.cp_reduce = reduce + if area is not None: + if area[0] < 0 or area[1] < 0: + msg = "Upper left corner coordinates must be nonnegative: {0}" + msg = msg.format(area) + raise IOError(msg) + if area[2] <= 0 or area[3] <= 0: + msg = "Lower right corner coordinates must be positive: {0}" + msg = msg.format(area) + raise IOError(msg) + dparam.DA_y0 = area[0] + dparam.DA_x0 = area[1] + dparam.DA_y1 = area[2] + dparam.DA_x1 = area[3] + + if tile is not None: + dparam.tile_index = tile + dparam.nb_tile_to_decode = 1 + + with ExitStack() as stack: + stream = opj2._stream_create_default_file_stream_v3(self.filename, + True) + stack.callback(opj2._stream_destroy_v3, 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 verbose: + opj2._set_info_handler(codec, _info_callback) + else: + opj2._set_info_handler(codec, None) + + opj2._setup_decoder(codec, dparam) + image = opj2._read_header(stream, codec) + stack.callback(opj2._image_destroy, image) + + if dparam.nb_tile_to_decode: + opj2._get_decoded_tile(codec, stream, image, dparam.tile_index) + else: + opj2._set_decode_area(codec, image, + dparam.DA_x0, dparam.DA_y0, + dparam.DA_x1, dparam.DA_y1) + opj2._decode(codec, stream, image) + opj2._end_decompress(codec, stream) + + component = image.contents.comps[0] + if component.sgnd: + if component.prec <= 8: + dtype = np.int8 + elif component.prec <= 16: + dtype = np.int16 + else: + raise RuntimeError("Unhandled precision, datatype") + else: + if component.prec <= 8: + dtype = np.uint8 + elif component.prec <= 16: + dtype = np.uint16 + else: + raise RuntimeError("Unhandled precision, datatype") + + if as_bands: + data = [] + else: + nrows = image.contents.comps[0].h + ncols = image.contents.comps[0].w + ncomps = image.contents.numcomps + data = np.zeros((nrows, ncols, ncomps), dtype) + + for k in range(image.contents.numcomps): + component = image.contents.comps[k] + nrows = component.h + ncols = component.w + + if nrows == 0 or ncols == 0: + # Letting this situation continue would segfault + # Python. + msg = "Component {0} has dimensions {1} x {2}" + msg = msg.format(k, nrows, ncols) + raise IOError(msg) + + addr = ctypes.addressof(component.data.contents) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + x = np.ctypeslib.as_array( + (ctypes.c_int32 * nrows * ncols).from_address(addr)) + if as_bands: + data.append(np.reshape(x.astype(dtype), (nrows, ncols))) + else: + data[:, :, k] = np.reshape(x.astype(dtype), (nrows, ncols)) + + return data + + def read_bands(self, reduce=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 + different subsampling factors across components. Otherwise you should + use the read method. + + Parameters + ---------- + layer : int, optional + Number of quality layer to decode. + reduce : int, optional + Factor by which to reduce output resolution. + 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 + ------- + lst : list + The individual image components. + + See also + -------- + read : read JPEG 2000 image + + Examples + -------- + >>> import glymur + >>> import pkg_resources as pkg + >>> jfile = pkg.resource_filename(glymur.__name__, "data/nemo.jp2") + >>> jp = glymur.Jp2k(jfile) + >>> components_lst = jp.read_bands(reduce=1) + """ + lst = self._read_common(reduce=reduce, + layer=layer, + area=area, + tile=tile, + verbose=verbose, + as_bands=True) + + return lst + + def get_codestream(self, header_only=True): + """Returns a codestream object. + + Parameters + ---------- + header_only : bool, optional + If True, only marker segments in the main header are parsed. + Supplying False may impose a large performance penalty. + + Returns + ------- + Object describing the codestream syntax. + + Examples + -------- + >>> import glymur + >>> import pkg_resources as pkg + >>> jfile = pkg.resource_filename(glymur.__name__, "data/nemo.jp2") + >>> jp = glymur.Jp2k(jfile) + >>> codestream = jp.get_codestream() + >>> print(codestream.segment[1]) + SIZ marker segment @ (87, 47) + Profile: 2 + Reference Grid Height, Width: (1456 x 2592) + Vertical, Horizontal Reference Grid Offset: (0 x 0) + Reference Tile Height, Width: (512 x 512) + 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)) + + Raises + ------ + IOError + If the file is JPX with more than one codestream. + """ + with open(self.filename, 'rb') as fp: + if self._codec_format == opj2._CODEC_J2K: + codestream = Codestream(fp, header_only=header_only) + else: + box = [x for x in self.box if x.id == 'jp2c'] + if len(box) != 1: + msg = "JP2 files must have a single codestream." + raise RuntimeError(msg) + fp.seek(box[0].offset) + buffer = fp.read(8) + (L, T) = struct.unpack('>I4s', buffer) + if L == 1: + # Seek past the XL field. + buffer = fp.read(8) + codestream = Codestream(fp, header_only=header_only) + + return codestream diff --git a/glymur/lib/__init__.py b/glymur/lib/__init__.py new file mode 100644 index 0000000..ca7460c --- /dev/null +++ b/glymur/lib/__init__.py @@ -0,0 +1,3 @@ +"""This package organizes individual libraries employed by glymur.""" +from . import openjp2 +from . import test diff --git a/glymur/lib/openjp2.py b/glymur/lib/openjp2.py new file mode 100644 index 0000000..5ba07e6 --- /dev/null +++ b/glymur/lib/openjp2.py @@ -0,0 +1,1309 @@ +""" +Wraps individual functions in openjp2 library. +""" + +import ctypes + +import numpy as np + +from .. import _OPENJP2 +from ..core import LRCP, RLCP, RPCL, PCRL, CPRL + + +_ERROR_MSG_LST = [] + +# Map certain atomic OpenJPEG datatypes to the ctypes equivalents. +_bool_t = ctypes.c_int32 +_codec_t_p = ctypes.c_void_p +_prog_order_t_p = ctypes.c_int32 +_cinema_mode_t = ctypes.c_int32 +_rsiz_capabilities_t = ctypes.c_int32 +_stream_t_p = ctypes.c_void_p + +_PATH_LEN = 4096 +_J2K_MAXRLVLS = 33 +_J2K_MAXBANDS = (3 * _J2K_MAXRLVLS - 2) + +_JPWL_MAX_NO_TILESPECS = 16 + +_TRUE = 1 +_FALSE = 0 + +# supported color spaces +_CLRSPC_UNKNOWN = -1 +_CLRSPC_UNSPECIFIED = 0 +_CLRSPC_SRGB = 1 +_CLRSPC_GRAY = 2 +_CLRSPC_YCC = 3 +color_space_t = ctypes.c_int + +# supported codec +_codec_format_t = ctypes.c_int +_CODEC_UNKNOWN = -1 +_CODEC_J2K = 0 +_CODEC_JPT = 1 +_CODEC_JP2 = 2 + + +class _poc_t(ctypes.Structure): + """Progression order changes.""" + # Resolution num start, Component num start, given by POC + _fields_ = [ + ("resno0", ctypes.c_uint32), + ("compno0", ctypes.c_uint32), + + # Layer num end,Resolution num end, Component num end, given by POC + ("layno1", ctypes.c_uint32), + ("resno1", ctypes.c_uint32), + ("compno1", ctypes.c_uint32), + + # Layer num start,Precinct num start, Precinct num end + ("layno0", ctypes.c_uint32), + ("precno0", ctypes.c_uint32), + ("precno1", ctypes.c_uint32), + + # Progression order enum + ("prg1", _prog_order_t_p), + ("prg", _prog_order_t_p), + + # Progression order string + ("progorder", ctypes.c_char * 5), + + # Tile number + ("tile", ctypes.c_uint32), + + # Start and end values for Tile width and height* + ("tx0", ctypes.c_int32), + ("tx1", ctypes.c_int32), + ("ty0", ctypes.c_int32), + ("ty1", ctypes.c_int32), + + # Start value, initialised in pi_initialise_encode + ("layS", ctypes.c_uint32), + ("resS", ctypes.c_uint32), + ("compS", ctypes.c_uint32), + ("prcS", ctypes.c_uint32), + + # End value, initialised in pi_initialise_encode + ("layE", ctypes.c_uint32), + ("resE", ctypes.c_uint32), + ("compE", ctypes.c_uint32), + ("prcE", ctypes.c_uint32), + + # Start and end values of Tile width and height, initialised in + # pi_initialise_encode + ("txS", ctypes.c_uint32), + ("txE", ctypes.c_uint32), + ("tyS", ctypes.c_uint32), + ("tyE", ctypes.c_uint32), + ("dx", ctypes.c_uint32), + ("dy", ctypes.c_uint32), + + # Temporary values for Tile parts, initialised in pi_create_encode + ("lay_t", ctypes.c_uint32), + ("res_t", ctypes.c_uint32), + ("comp_t", ctypes.c_uint32), + ("prec_t", ctypes.c_uint32), + ("tx0_t", ctypes.c_uint32), + ("ty0_t", ctypes.c_uint32)] + + +class _dparameters_t(ctypes.Structure): + """Decompression parameters""" + _fields_ = [ + # Set the number of highest resolutio levels to be discarded. The + # image resolution is effectively divided by 2 to the power of + # discarded levels. The reduce factor is limited by the smallest + # total number of decomposition levels among tiles. If not equal to + # zero, then the original dimension is divided by 2^(reduce). If + # equal to zero or not used, the image is decoded to the full + # resolution. + ("cp_reduce", ctypes.c_uint32), + + # Set the maximum number of quality layers to decode. If there are + # fewer quality layers than the specified number, all the quality + # layers are decoded. + # + # If != 0, then only the first cp_layer layers are decoded. + # If == 0 or not used, all the quality layers are decoded. + ("cp_layer", ctypes.c_uint32), + + # input file name + ("infile", ctypes.c_char * _PATH_LEN), + + # output file name + ("outfile", ctypes.c_char * _PATH_LEN), + + # input file format 0: PGX, 1: PxM, 2: BMP 3:TIF + # output file format 0: J2K, 1: JP2, 2: JPT + ("decod_format", ctypes.c_int), + ("cod_format", ctypes.c_int), + + # Decoding area left and right boundary. + # Decoding area upper and lower boundary. + ("DA_x0", ctypes.c_uint32), + ("DA_x1", ctypes.c_uint32), + ("DA_y0", ctypes.c_uint32), + ("DA_y1", ctypes.c_uint32), + + # verbose mode + ("m_verbose", _bool_t), + + # tile number of the decoded tile + ("tile_index", ctypes.c_uint32), + + # number of tiles to decode + ("nb_tile_to_decode", ctypes.c_uint32), + + # activates the JPWL correction capabilities + ("jpwl_correct", _bool_t), + + # activates the JPWL correction capabilities + ("jpwl_exp_comps", ctypes.c_int32), + + # maximum number of tiles + ("jpwl_max_tiles", ctypes.c_int32), + + # maximum number of tiles + ("flags", ctypes.c_uint32)] + + +class _cparameters_t(ctypes.Structure): + """Compression parameters""" + _fields_ = [ + # size of tile: + # tile_size_on = false (not in argument) or + # = true (in argument) + ("tile_size_on", _bool_t), + + # XTOsiz, YTOsiz + ("cp_tx0", ctypes.c_int), + ("cp_ty0", ctypes.c_int), + + # XTsiz, YTsiz + ("cp_tdx", ctypes.c_int), + ("cp_tdy", ctypes.c_int), + + # allocation by rate/distortion + ("cp_disto_alloc", ctypes.c_int), + + # allocation by fixed layer + ("cp_fixed_alloc", ctypes.c_int), + + # add fixed_quality + ("cp_fixed_quality", ctypes.c_int), + + # fixed layer + ("cp_matrice", ctypes.c_void_p), + + # comment for coding + ("cp_comment", ctypes.c_char_p), + + # csty : coding style + ("csty", ctypes.c_int), + + # progression order (default OPJ_LRCP) + ("prog_order", ctypes.c_int), + + # progression order changes + ("poc", _poc_t * 32), + + # number of progression order changes (POC), default to 0 + ("numpocs", ctypes.c_uint), + + # number of layers + ("tcp_numlayers", ctypes.c_int), + + # rates of layers + ("tcp_rates", ctypes.c_float * 100), + + # different psnr for successive layers + ("tcp_distoratio", ctypes.c_float * 100), + + # number of resolutions + ("numresolution", ctypes.c_int), + + # initial code block width, default to 64 + ("cblockw_init", ctypes.c_int), + + # initial code block height, default to 64 + ("cblockh_init", ctypes.c_int), + + # mode switch (cblk_style) + ("mode", ctypes.c_int), + + # 1 : use the irreversible DWT 9-7 + # 0 : use lossless compression (default) + ("irreversible", ctypes.c_int), + + # region of interest: affected component in [0..3], -1 means no ROI + ("roi_compno", ctypes.c_int), + + # region of interest: upshift value + ("roi_shift", ctypes.c_int), + + # number of precinct size specifications + ("res_spec", ctypes.c_int), + + # initial precinct width + ("prcw_init", ctypes.c_int * _J2K_MAXRLVLS), + + # initial precinct height + ("prch_init", ctypes.c_int * _J2K_MAXRLVLS), + + # input file name + ("infile", ctypes.c_char * _PATH_LEN), + + # output file name + ("outfile", ctypes.c_char * _PATH_LEN), + + # DEPRECATED. + ("index_on", ctypes.c_int), + + # DEPRECATED. + ("index", ctypes.c_char * _PATH_LEN), + + # subimage encoding: origin image offset in x direction + # subimage encoding: origin image offset in y direction + ("image_offset_x0", ctypes.c_int), + ("image_offset_y0", ctypes.c_int), + + # subsampling value for dx + # subsampling value for dy + ("subsampling_dx", ctypes.c_int), + ("subsampling_dy", ctypes.c_int), + + # input file format 0: PGX, 1: PxM, 2: BMP 3:TIF + # output file format 0: J2K, 1: JP2, 2: JPT + ("decod_format", ctypes.c_int), + ("cod_format", ctypes.c_int), + + # JPWL encoding parameters + # enables writing of EPC in MH, thus activating JPWL + ("jpwl_epc_on", _bool_t), + + # error protection method for MH (0,1,16,32,37-128) + ("jpwl_hprot_mh", ctypes.c_int), + + # tile number of header protection specification (>=0) + ("jpwl_hprot_tph_tileno", ctypes.c_int * _JPWL_MAX_NO_TILESPECS), + + # error protection methods for TPHs (0,1,16,32,37-128) + ("jpwl_hprot_tph", ctypes.c_int * _JPWL_MAX_NO_TILESPECS), + + # tile number of packet protection specification (>=0) + ("jpwl_pprot_tileno", ctypes.c_int * _JPWL_MAX_NO_TILESPECS), + + # packet number of packet protection specification (>=0) + ("jpwl_pprot_packno", ctypes.c_int * _JPWL_MAX_NO_TILESPECS), + + # error protection methods for packets (0,1,16,32,37-128) + ("jpwl_pprot", ctypes.c_int * _JPWL_MAX_NO_TILESPECS), + + # enables writing of ESD, (0=no/1/2 bytes) + ("jpwl_sens_size", ctypes.c_int), + + # sensitivity addressing size (0=auto/2/4 bytes) + ("jpwl_sens_addr", ctypes.c_int), + + # sensitivity range (0-3) + ("jpwl_sens_range", ctypes.c_int), + + # sensitivity method for MH (-1=no,0-7) + ("jpwl_sens_mh", ctypes.c_int), + + # tile number of sensitivity specification (>=0) + ("jpwl_sens_tph_tileno", ctypes.c_int * _JPWL_MAX_NO_TILESPECS), + + # sensitivity methods for TPHs (-1=no,0-7) + ("jpwl_sens_tph", ctypes.c_int * _JPWL_MAX_NO_TILESPECS), + + # Digital Cinema compliance 0-not compliant, 1-compliant + ("cp_cinema", _cinema_mode_t), + + # Maximum rate for each component. + # If == 0, component size limitation is not considered + ("max_comp_size", ctypes.c_int), + + # Profile name + ("cp_rsiz", _rsiz_capabilities_t), + + # Tile part generation + ("tp_on", ctypes.c_uint8), + + # Flag for Tile part generation + ("tp_flag", ctypes.c_uint8), + + # MCT (multiple component transform) + ("tcp_mct", ctypes.c_uint8), + + # Enable JPIP indexing + ("jpip_on", _bool_t), + + # Naive implementation of MCT restricted to a single reversible array + # based encoding without offset concerning all the components. + ("mct_data", ctypes.c_void_p)] + + +class _image_comp_t(ctypes.Structure): + """defines a single image component""" + _fields_ = [ + # XRsiz, YRsiz: horizontal, vertical separation of ith component with + # respect to the reference grid + ("dx", ctypes.c_uint32), + ("dy", ctypes.c_uint32), + + # data width and height + ("w", ctypes.c_uint32), + ("h", ctypes.c_uint32), + + # x, y component offset compared to the whole image + ("x0", ctypes.c_uint32), + ("y0", ctypes.c_uint32), + + # component depth in bits + ("prec", ctypes.c_uint32), + + # component depth in bits + ("bpp", ctypes.c_uint32), + + # signed (1) or unsigned (0) + ("sgnd", ctypes.c_uint32), + + # number of decoded resolution + ("resno_decoded", ctypes.c_uint32), + + # number of division by 2 of the out image component as compared to the + # original size of the image + ("factor", ctypes.c_uint32), + + # image component data + ("data", ctypes.POINTER(ctypes.c_int32))] + + +class _image_t(ctypes.Structure): + """defines image data and characteristics""" + _fields_ = [ + # XOsiz, YOsiz: horizontal and vertical offset from the origin of the + # reference grid to the left side of the image area + ("x0", ctypes.c_uint32), + ("y0", ctypes.c_uint32), + + # Xsiz, Ysiz: width and height of the reference grid. + ("x1", ctypes.c_uint32), + ("y1", ctypes.c_uint32), + + # number of components in the image + ("numcomps", ctypes.c_uint32), + + # color space: should be sRGB, greyscale, or YUV + ("color_space", color_space_t), + + # image components + ("comps", ctypes.POINTER(_image_comp_t)), + + # restricted ICC profile buffer + ("icc_profile_buf", ctypes.POINTER(ctypes.c_uint8)), + + # restricted ICC profile buffer length + ("icc_profile_len", ctypes.c_uint32)] + + +class _image_comptparm_t(ctypes.Structure): + """component parameters structure used by image_create function""" + _fields_ = [ + # XRsiz, YRsiz: horizontal, vertical separation of a sample of ith + # component with respect to the reference grid + ("dx", ctypes.c_uint32), + ("dy", ctypes.c_uint32), + + # data width, height + ("w", ctypes.c_uint32), + ("h", ctypes.c_uint32), + + # x, y component offset compared to the whole image + ("x0", ctypes.c_uint32), + ("y0", ctypes.c_uint32), + + # precision + ("prec", ctypes.c_uint32), + + # image depth in bits + ("bpp", ctypes.c_uint32), + + # signed (1) / unsigned (0) + ("sgnd", ctypes.c_uint32)] + + +class _tccp_info_t(ctypes.Structure): + """tile-component coding parameters information""" + _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 _tile_info_v2_t(ctypes.Structure): + """tile coding parameters information""" + _fields_ = [ + # number (index) of tile + ("tileno", ctypes.c_int32), + + # coding style + ("csty", ctypes.c_uint32), + + # progression order + ("prg", _prog_order_t_p), + + # number of layers + ("numlayers", ctypes.c_uint32), + + # multi-component transform identifier + ("mct", ctypes.c_uint32), + + # information concerning tile component parameters + ("tccp_info", ctypes.POINTER(_tccp_info_t))] + + +class _codestream_info_v2_t(ctypes.Structure): + """information about the codestream""" + _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", _tile_info_v2_t), + + # information regarding tiles inside of image + ("tile_info", ctypes.POINTER(_tile_info_v2_t))] + +# Restrict the input and output argument types for each function used in the +# API. +_OPENJP2.opj_create_compress.argtypes = [_codec_format_t] +_OPENJP2.opj_create_compress.restype = _codec_t_p + +_OPENJP2.opj_create_decompress.argtypes = [_codec_format_t] +_OPENJP2.opj_create_decompress.restype = _codec_t_p + +_argtypes = [_codec_t_p, _stream_t_p, ctypes.POINTER(_image_t)] +_OPENJP2.opj_decode.argtypes = _argtypes + +_argtypes = [_codec_t_p, ctypes.c_uint32, + ctypes.POINTER(ctypes.c_uint8), + ctypes.c_uint32, + _stream_t_p] +_OPENJP2.opj_decode_tile_data.argtypes = _argtypes + +_argtypes = [ctypes.POINTER(ctypes.POINTER(_codestream_info_v2_t))] +_OPENJP2.opj_destroy_cstr_info.argtypes = _argtypes +_OPENJP2.opj_destroy_cstr_info.restype = ctypes.c_void_p + +_argtypes = [_codec_t_p, _stream_t_p] +_OPENJP2.opj_encode.argtypes = _argtypes + +_OPENJP2.opj_get_cstr_info.argtypes = [_codec_t_p] +_OPENJP2.opj_get_cstr_info.restype = ctypes.POINTER(_codestream_info_v2_t) + +_argtypes = [_codec_t_p, + _stream_t_p, + ctypes.POINTER(_image_t), + ctypes.c_uint32] +_OPENJP2.opj_get_decoded_tile.argtypes = _argtypes + +_argtypes = [ctypes.c_uint32, + ctypes.POINTER(_image_comptparm_t), + color_space_t] +_OPENJP2.opj_image_create.argtypes = _argtypes +_OPENJP2.opj_image_create.restype = ctypes.POINTER(_image_t) + +_argtypes = [ctypes.c_uint32, + ctypes.POINTER(_image_comptparm_t), + color_space_t] +_OPENJP2.opj_image_tile_create.argtypes = _argtypes +_OPENJP2.opj_image_tile_create.restype = ctypes.POINTER(_image_t) + +_OPENJP2.opj_image_destroy.argtypes = [ctypes.POINTER(_image_t)] + +_argtypes = [_stream_t_p, _codec_t_p, ctypes.POINTER(ctypes.POINTER(_image_t))] +_OPENJP2.opj_read_header.argtypes = _argtypes + +_argtypes = [_codec_t_p, + _stream_t_p, + ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(ctypes.c_uint32), + ctypes.POINTER(ctypes.c_int32), ctypes.POINTER(ctypes.c_int32), + ctypes.POINTER(ctypes.c_int32), ctypes.POINTER(ctypes.c_int32), + ctypes.POINTER(ctypes.c_uint32), + ctypes.POINTER(_bool_t)] +_OPENJP2.opj_read_tile_header.argtypes = _argtypes + +_argtypes = [_codec_t_p, ctypes.POINTER(_image_t), ctypes.c_int32, + ctypes.c_int32, ctypes.c_int32, ctypes.c_int32] +_OPENJP2.opj_set_decode_area.argtypes = _argtypes + +_argtypes = [ctypes.POINTER(_cparameters_t)] +_OPENJP2.opj_set_default_encoder_parameters.argtypes = _argtypes + +_argtypes = [ctypes.POINTER(_dparameters_t)] +_OPENJP2.opj_set_default_decoder_parameters.argtypes = _argtypes + +_argtypes = [_codec_t_p, ctypes.c_void_p, ctypes.c_void_p] +_OPENJP2.opj_set_error_handler.argtypes = _argtypes +_OPENJP2.opj_set_info_handler.argtypes = _argtypes +_OPENJP2.opj_set_warning_handler.argtypes = _argtypes + +_argtypes = [_codec_t_p, ctypes.POINTER(_dparameters_t)] +_OPENJP2.opj_setup_decoder.argtypes = _argtypes + +_argtypes = [_codec_t_p, + ctypes.POINTER(_cparameters_t), + ctypes.POINTER(_image_t)] +_OPENJP2.opj_setup_encoder.argtypes = _argtypes + +_argtypes = [ctypes.c_char_p, ctypes.c_int32] +_OPENJP2.opj_stream_create_default_file_stream_v3.argtypes = _argtypes +_OPENJP2.opj_stream_create_default_file_stream_v3.restype = _stream_t_p + +_argtypes = [_codec_t_p, ctypes.POINTER(_image_t), _stream_t_p] +_OPENJP2.opj_start_compress.argtypes = _argtypes + +_OPENJP2.opj_end_compress.argtypes = [_codec_t_p, _stream_t_p] +_OPENJP2.opj_end_decompress.argtypes = [_codec_t_p, _stream_t_p] + +_OPENJP2.opj_stream_destroy_v3.argtypes = [_stream_t_p] +_OPENJP2.opj_destroy_codec.argtypes = [_codec_t_p] + +_argtypes = [_codec_t_p, + ctypes.c_uint32, + ctypes.POINTER(ctypes.c_uint8), + ctypes.c_uint32, + _stream_t_p] +_OPENJP2.opj_write_tile.argtypes = _argtypes + + +def _check_error(status): + """Set a generic function as the restype attribute of all OpenJPEG + functions that return a _bool_t value. This way we do not have to check + for error status in each wrapping function and an exception will always be + appropriately raised. + """ + global _ERROR_MSG_LST + if status != 1: + if len(_ERROR_MSG_LST) > 0: + # clear out the existing error message so that we don't pick up + # a bad one next time around. + msg = '\n'.join(_ERROR_MSG_LST) + _ERROR_MSG_LST = [] + raise IOError(msg) + else: + raise IOError("OpenJPEG function failure.") + +# These library functions all return an error status. Circumvent that and +# force # them to raise an exception. +_fcns = ['opj_decode', 'opj_decode_tile_data', 'opj_end_compress', + 'opj_encode', 'opj_end_decompress', 'opj_get_decoded_tile', + 'opj_read_header', 'opj_read_tile_header', 'opj_set_decode_area', + 'opj_set_error_handler', 'opj_set_info_handler', + 'opj_set_warning_handler', + 'opj_setup_decoder', 'opj_setup_encoder', 'opj_start_compress', + 'opj_write_tile'] +for _fcn in _fcns: + _attr = getattr(_OPENJP2, _fcn) + setattr(_attr, 'restype', _check_error) + + +def _create_compress(codec_format): + """Creates a J2K/JP2 compress structure. + + Wraps the openjp2 library function opj_create_compress. + + Parameters + ---------- + codec_format : int + Specifies codec to select. Should be one of _CODEC_J2K or _CODEC_JP2. + + Returns + ------- + codec : Reference to _codec_t_p instance. + """ + codec = _OPENJP2.opj_create_compress(codec_format) + return codec + + +def _decode(codec, stream, image): + """Reads an entire image. + + Wraps the openjp2 library function opj_decode. + + Parameters + ---------- + codec : _codec_t_p + The JPEG2000 codec + stream : _stream_t_p + The stream to decode. + image : _image_t + Output image structure. + + Raises + ------ + RuntimeError + If the OpenJPEG library routine opj_decode fails. + """ + _OPENJP2.opj_decode(codec, stream, image) + + +def _decode_tile_data(codec, tidx, data, data_size, stream): + """Reads tile data. + + Wraps the openjp2 library function opj_decode_tile_data. + + Parameters + ---------- + codec : _codec_t_p + The JPEG2000 codec + tile_index : int + The index of the tile being decoded + data : array + Holds a memory block into which data will be decoded. + data_size : int + The size of data in bytes + stream : _stream_t_p + The stream to decode. + + Raises + ------ + RuntimeError + If the OpenJPEG library routine opj_decode fails. + """ + datap = data.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8)) + _OPENJP2.opj_decode_tile_data(codec, + ctypes.c_uint32(tidx), + datap, + ctypes.c_uint32(data_size), + stream) + return codec + + +def _create_decompress(codec_format): + """Creates a J2K/JP2 decompress structure. + + Wraps the openjp2 library function opj_create_decompress. + + Parameters + ---------- + codec_format : int + Specifies codec to select. Should be one of _CODEC_J2K or _CODEC_JP2. + + Returns + ------- + codec : Reference to _codec_t_p instance. + """ + codec = _OPENJP2.opj_create_decompress(codec_format) + return codec + + +def _destroy_codec(codec): + """Destroy a decompressor handle. + + Wraps the openjp2 library function opj_destroy_codec. + + Parameters + ---------- + codec : _codec_t_p + Decompressor handle to destroy. + """ + _OPENJP2.opj_destroy_codec(codec) + + +def _encode(codec, stream): + """Wraps openjp2 library function opj_encode. + + Encode an image into a JPEG 2000 codestream. + + Parameters + ---------- + codec : _codec_t_p + The jpeg2000 codec. + stream : _stream_t_p + The stream to which data is written. + + Raises + ------ + RuntimeError + If the OpenJPEG library routine opj_encode fails. + """ + _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_t_p + The jpeg2000 codec. + + Returns + ------- + cstr_info_p : _codestream_info_v2_t + Reference to codestream information. + """ + 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 + + Wraps the openjp2 library function opj_get_decoded_tile. + + Parameters + ---------- + codec : _codec_t_p + The jpeg2000 codec. + stream : _stream_t_p + The input stream. + image : _image_t + Output image structure. + tiler_index : int + Index of the tile which will be decoded. + + Raises + ------ + RuntimeError + If the OpenJPEG library routine opj_get_decoded_tile fails. + """ + _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 : _codestream_info_v2_t pointer + Pointer to codestream info structure. + """ + _OPENJP2.opj_destroy_cstr_info(ctypes.byref(cstr_info_p)) + + +def _end_compress(codec, stream): + """End of compressing the current image. + + Wraps the openjp2 library function opj_end_compress. + + Parameters + ---------- + codec : _codec_t_p + Compressor handle. + stream : _stream_t_p + Output stream buffer. + + Raises + ------ + RuntimeError + If the OpenJPEG library routine opj_end_compress fails. + """ + _OPENJP2.opj_end_compress(codec, stream) + + +def _end_decompress(codec, stream): + """End of decompressing the current image. + + Wraps the openjp2 library function opj_end_decompress. + + Parameters + ---------- + codec : _codec_t_p + Compressor handle. + stream : _stream_t_p + Output stream buffer. + + Raises + ------ + RuntimeError + If the OpenJPEG library routine opj_end_decompress fails. + """ + _OPENJP2.opj_end_decompress(codec, stream) + + +def _image_destroy(image): + """Deallocate any resources associated with an image. + + Wraps the openjp2 library function opj_image_destroy. + + Parameters + ---------- + image : _image_t pointer + Image resource to be disposed. + """ + _OPENJP2.opj_image_destroy(image) + + +def _image_create(comptparms, clrspc): + """Creates a new image structure. + + Wraps the openjp2 library function opj_image_create. + + Parameters + ---------- + cmptparms : comptparms_t + The component parameters. + clrspc : int + Specifies the color space. + + Returns + ------- + image : _image_t + Reference to _image_t instance. + """ + image = _OPENJP2.opj_image_create(len(comptparms), + comptparms, + clrspc) + return image + + +def _image_tile_create(comptparms, clrspc): + """Creates a new image structure. + + Wraps the openjp2 library function opj_image_tile_create. + + Parameters + ---------- + cmptparms : comptparms_t + The component parameters. + clrspc : int + Specifies the color space. + + Returns + ------- + image : _image_t + Reference to _image_t instance. + """ + image = _OPENJP2.opj_image_tile_create(len(comptparms), + comptparms, + clrspc) + return image + + +def _read_header(stream, codec): + """Decodes an image header. + + Wraps the openjp2 library function opj_read_header. + + Parameters + ---------- + stream: _stream_t_p + The JPEG2000 stream. + codec: codec_t + The JPEG2000 codec to read. + + Returns + ------- + imagep : reference to _image_t instance + The image structure initialized with image characteristics. + + Raises + ------ + RuntimeError + If the OpenJPEG library routine opj_read_header fails. + """ + imagep = ctypes.POINTER(_image_t)() + _OPENJP2.opj_read_header(stream, codec, ctypes.byref(imagep)) + return imagep + + +def _read_tile_header(codec, stream): + """Reads a tile header. + + Wraps the openjp2 library function opj_read_tile_header. + + Parameters + ---------- + codec : codec_t + The JPEG2000 codec to read. + stream : _stream_t_p + The JPEG2000 stream. + + Returns + ------- + tile_index : int + index of the tile being decoded + data_size : int + number of bytes for the decoded area + x0, y0 : int + upper left-most coordinate of tile + x1, y1 : int + lower right-most coordinate of tile + ncomps : int + number of components in the tile + go_on : bool + indicates that decoding should continue + + Raises + ------ + RuntimeError + If the OpenJPEG library routine opj_read_tile_header fails. + """ + tile_index = ctypes.c_uint32() + data_size = ctypes.c_uint32() + x0 = ctypes.c_int32() + y0 = ctypes.c_int32() + x1 = ctypes.c_int32() + y1 = ctypes.c_int32() + ncomps = ctypes.c_uint32() + go_on = _bool_t() + _OPENJP2.opj_read_tile_header(codec, + stream, + ctypes.byref(tile_index), + ctypes.byref(data_size), + ctypes.byref(x0), + ctypes.byref(y0), + ctypes.byref(x1), + ctypes.byref(y1), + ctypes.byref(ncomps), + ctypes.byref(go_on)) + go_on = bool(go_on.value) + return (tile_index.value, + data_size.value, + x0.value, + y0.value, + x1.value, + y1.value, + ncomps.value, + go_on) + + +def _set_decode_area(codec, image, start_x=0, start_y=0, end_x=0, end_y=0): + """Wraps openjp2 library function opj_set_decode area. + + Sets the given area to be decoded. This function should be called right + after read_header and before any tile header reading. + + Parameters + ---------- + codec : _codec_t_p + Codec initialized by create_decompress function. + image : _image_t pointer + The decoded image previously set by read_header. + start_x, start_y : optional, int + The left and upper position of the rectangle to decode. + end_x, end_y : optional, int + The right and lower position of the rectangle to decode. + + Raises + ------ + RuntimeError + If the OpenJPEG library routine opj_set_decode_area fails. + """ + _OPENJP2.opj_set_decode_area(codec, image, + ctypes.c_int32(start_x), + ctypes.c_int32(start_y), + ctypes.c_int32(end_x), + ctypes.c_int32(end_y)) + + +def _set_default_decoder_parameters(): + """Wraps openjp2 library function opj_set_default_decoder_parameters. + + Sets decoding parameters to default values. + + Returns + ------- + dparam : _dparameters_t + Decompression parameters. + """ + dparams = _dparameters_t() + _OPENJP2.opj_set_default_decoder_parameters(ctypes.byref(dparams)) + return dparams + + +def _set_default_encoder_parameters(): + """Wraps openjp2 library function opj_set_default_encoder_parameters. + + Sets encoding parameters to default values. That means + + lossless + 1 tile + size of precinct : 2^15 x 2^15 (means 1 precinct) + size of code-block : 64 x 64 + number of resolutions: 6 + no SOP marker in the codestream + no EPH marker in the codestream + no sub-sampling in x or y direction + no mode switch activated + progression order: LRCP + no index file + no ROI upshifted + no offset of the origin of the image + no offset of the origin of the tiles + reversible DWT 5-3 + + The signature for this function differs from its C library counterpart, as + the the C function pass-by-reference parameter becomes the Python return + value. + + Returns + ------- + cparameters : _cparameters_t + Compression parameters. + """ + cparams = _cparameters_t() + _OPENJP2.opj_set_default_encoder_parameters(ctypes.byref(cparams)) + return cparams + + +def _set_error_handler(codec, handler, data=None): + """Wraps openjp2 library function opj_set_error_handler. + + Set the error handler use by openjpeg. + + Parameters + ---------- + codec : _codec_t_p + Codec initialized by create_compress function. + handler : python function + The callback function to be used. + user_data : anything + User/client data. + + Raises + ------ + RuntimeError + If the OpenJPEG library routine opj_set_error_handler fails. + """ + _OPENJP2.opj_set_error_handler(codec, handler, data) + + +def _set_info_handler(codec, handler, data=None): + """Wraps openjp2 library function opj_set_info_handler. + + Set the info handler use by openjpeg. + + Parameters + ---------- + codec : _codec_t_p + Codec initialized by create_compress function. + handler : python function + The callback function to be used. + user_data : anything + User/client data. + + Raises + ------ + RuntimeError + If the OpenJPEG library routine opj_set_info_handler fails. + """ + _OPENJP2.opj_set_info_handler(codec, handler, data) + + +def _set_warning_handler(codec, handler, data=None): + """Wraps openjp2 library function opj_set_warning_handler. + + Set the warning handler use by openjpeg. + + Parameters + ---------- + codec : _codec_t_p + Codec initialized by create_compress function. + handler : python function + The callback function to be used. + user_data : anything + User/client data. + + Raises + ------ + RuntimeError + If the OpenJPEG library routine opj_set_warning_handler fails. + """ + _OPENJP2.opj_set_warning_handler(codec, handler, data) + + +def _setup_decoder(codec, dparams): + """Wraps openjp2 library function opj_setup_decoder. + + Setup the decoder with decompression parameters. + + Parameters + ---------- + codec: _codec_t_p + Codec initialized by create_compress function. + dparams: _dparameters_t + Decompression parameters. + + Raises + ------ + RuntimeError + If the OpenJPEG library routine opj_setup_decoder fails. + """ + _OPENJP2.opj_setup_decoder(codec, ctypes.byref(dparams)) + + +def _setup_encoder(codec, cparams, image): + """Wraps openjp2 library function opj_setup_encoder. + + Setup the encoder parameters using the current image and using user + parameters. + + Parameters + ---------- + codec : _codec_t_p + codec initialized by create_compress function + cparams : _cparameters_t + compression parameters + image : _image_t + input-filled image + + Raises + ------ + RuntimeError + If the OpenJPEG library routine opj_setup_encoder fails. + """ + _OPENJP2.opj_setup_encoder(codec, ctypes.byref(cparams), image) + + +def _start_compress(codec, image, stream): + """Wraps openjp2 library function opj_start_compress. + + Start to compress the current image. + + Parameters + ---------- + codec : _codec_t_p + Compressor handle. + image : pointer to _image_t + Input filled image. + stream : _stream_t_p + Input stream. + + Raises + ------ + RuntimeError + If the OpenJPEG library routine opj_start_compress fails. + """ + _OPENJP2.opj_start_compress(codec, image, stream) + + +def _stream_create_default_file_stream_v3(fname, a_read_stream): + """Wraps openjp2 library function opj_stream_create_default_vile_stream_v3. + + Sets the stream to be a file stream. + + Parameters + ---------- + fname : str + Specifies a file. + a_read_stream: bool + True (read) or False (write) + + Returns + ------- + stream : stream_t + An OpenJPEG file stream. + """ + tf = 1 if a_read_stream else 0 + fn = ctypes.c_char_p(fname.encode()) + stream = _OPENJP2.opj_stream_create_default_file_stream_v3(fn, tf) + return stream + + +def _stream_destroy_v3(stream): + """Wraps openjp2 library function opj_stream_destroy. + + Destroys the stream created by create_stream_v3. + + Parameters + ---------- + stream : _stream_t_p + The file stream. + """ + _OPENJP2.opj_stream_destroy_v3(stream) + + +def _write_tile(codec, tile_index, data, data_size, stream): + """Wraps openjp2 library function opj_write_tile. + + Write a tile into an image. + + Parameters + ---------- + codec : _codec_t_p + The jpeg2000 codec + tile_index : int + The index of the tile to write, zero-indexing assumed + data : array + Image data arranged in usual C-order + data_size : int + Size of a tile in bytes + stream : _stream_t_p + The stream to write data to + + Raises + ------ + RuntimeError + If the OpenJPEG library routine opj_write_tile fails. + """ + datap = data.ctypes.data_as(ctypes.POINTER(ctypes.c_uint8)) + _OPENJP2.opj_write_tile(codec, + ctypes.c_uint32(int(tile_index)), + datap, + ctypes.c_uint32(int(data_size)), + stream) + + +def _set_error_message(msg): + """The openjpeg error handler has recorded an error message.""" + global _ERROR_MSG_LST + _ERROR_MSG_LST.append(msg) diff --git a/glymur/lib/test/__init__.py b/glymur/lib/test/__init__.py new file mode 100644 index 0000000..9f68aec --- /dev/null +++ b/glymur/lib/test/__init__.py @@ -0,0 +1 @@ +from .test_openjp2 import TestOpenJP2 as openjp2 diff --git a/glymur/lib/test/test_openjp2.py b/glymur/lib/test/test_openjp2.py new file mode 100644 index 0000000..0bee92f --- /dev/null +++ b/glymur/lib/test/test_openjp2.py @@ -0,0 +1,400 @@ +import doctest +import os +import pkg_resources +import shutil +import struct +import sys +import tempfile +import unittest + +import numpy as np + +import glymur + + +# Doc tests should be run as well. +def load_tests(loader, tests, ignore): + tests.addTests(doctest.DocTestSuite('glymur.lib.openjp2')) + return tests + + +class TestOpenJP2(unittest.TestCase): + + def setUp(self): + pass + + def tearDown(self): + pass + + def test_set_default_encoder_parameters(self): + cparams = glymur.lib.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.lib.openjp2.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_set_default_decoder_parameters(self): + dparams = glymur.lib.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 + glymur.lib.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): + # called by the test_rtaX methods + dparam = glymur.lib.openjp2._set_default_decoder_parameters() + + infile = filename.encode() + nelts = glymur.lib.openjp2._PATH_LEN - len(infile) + infile += b'0' * nelts + dparam.infile = infile + + dparam.decod_format = codec_format + + codec = glymur.lib.openjp2._create_decompress(codec_format) + + glymur.lib.openjp2._set_info_handler(codec, None) + glymur.lib.openjp2._set_warning_handler(codec, None) + glymur.lib.openjp2._set_error_handler(codec, None) + + x = (filename, True) + stream = glymur.lib.openjp2._stream_create_default_file_stream_v3(*x) + + glymur.lib.openjp2._setup_decoder(codec, dparam) + image = glymur.lib.openjp2._read_header(stream, codec) + + cstr_info = glymur.lib.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) + + glymur.lib.openjp2._destroy_cstr_info(cstr_info) + + glymur.lib.openjp2._end_decompress(codec, stream) + glymur.lib.openjp2._destroy_codec(codec) + glymur.lib.openjp2._stream_destroy_v3(stream) + glymur.lib.openjp2._image_destroy(image) + + def tile_decoder(self, x0=None, y0=None, x1=None, y1=None, filename=None, + codec_format=None): + x = (filename, True) + stream = glymur.lib.openjp2._stream_create_default_file_stream_v3(*x) + dparam = glymur.lib.openjp2._set_default_decoder_parameters() + + dparam.decod_format = codec_format + + # Do not use layer decoding limitation. + dparam.cp_layer = 0 + + # do not use resolution reductions. + dparam.cp_reduce = 0 + + codec = glymur.lib.openjp2._create_decompress(codec_format) + + glymur.lib.openjp2._set_info_handler(codec, None) + glymur.lib.openjp2._set_warning_handler(codec, None) + glymur.lib.openjp2._set_error_handler(codec, None) + + glymur.lib.openjp2._setup_decoder(codec, dparam) + image = glymur.lib.openjp2._read_header(stream, codec) + glymur.lib.openjp2._set_decode_area(codec, image, x0, y0, x1, y1) + + data = np.zeros((1150, 2048, 3), dtype=np.uint8) + while True: + rargs = glymur.lib.openjp2._read_tile_header(codec, stream) + tidx = rargs[0] + sz = rargs[1] + go_on = rargs[-1] + if not go_on: + break + glymur.lib.openjp2._decode_tile_data(codec, tidx, data, sz, stream) + + glymur.lib.openjp2._end_decompress(codec, stream) + glymur.lib.openjp2._destroy_codec(codec) + glymur.lib.openjp2._stream_destroy_v3(stream) + glymur.lib.openjp2._image_destroy(image) + + def tile_encoder(self, num_comps=None, tile_width=None, tile_height=None, + filename=None, codec=None, comp_prec=None, + image_width=None, image_height=None, + irreversible=None): + num_tiles = (image_width / tile_width) * (image_height / tile_height) + tile_size = tile_width * tile_height * num_comps * comp_prec / 8 + + data = np.random.random((tile_height, tile_width, num_comps)) + data = (data * 255).astype(np.uint8) + + l_param = glymur.lib.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 = tile_width + l_param.cp_tdy = tile_height + + # use irreversible encoding + l_param.irreversible = irreversible + + l_param.numresolution = 6 + + l_param.prog_order = glymur.lib.openjp2.LRCP + + l_params = (glymur.lib.openjp2._image_comptparm_t * num_comps)() + for j in range(num_comps): + l_params[j].dx = 1 + l_params[j].dy = 1 + l_params[j].h = image_height + l_params[j].w = image_width + l_params[j].sgnd = 0 + l_params[j].prec = comp_prec + l_params[j].x0 = 0 + l_params[j].y0 = 0 + + codec = glymur.lib.openjp2._create_compress(codec) + + glymur.lib.openjp2._set_info_handler(codec, None) + glymur.lib.openjp2._set_warning_handler(codec, None) + glymur.lib.openjp2._set_error_handler(codec, None) + + cspace = glymur.lib.openjp2._CLRSPC_SRGB + l_image = glymur.lib.openjp2._image_tile_create(l_params, cspace) + + l_image.contents.x0 = 0 + l_image.contents.y0 = 0 + l_image.contents.x1 = image_width + l_image.contents.y1 = image_height + l_image.contents.color_space = glymur.lib.openjp2._CLRSPC_SRGB + + glymur.lib.openjp2._setup_encoder(codec, l_param, l_image) + + x = (filename, False) + stream = glymur.lib.openjp2._stream_create_default_file_stream_v3(*x) + glymur.lib.openjp2._start_compress(codec, l_image, stream) + + for j in np.arange(num_tiles): + glymur.lib.openjp2._write_tile(codec, j, data, tile_size, stream) + + glymur.lib.openjp2._end_compress(codec, stream) + glymur.lib.openjp2._stream_destroy_v3(stream) + glymur.lib.openjp2._destroy_codec(codec) + glymur.lib.openjp2._image_destroy(l_image) + + def tte0_setup(self, filename): + kwargs = {'filename': filename, + 'codec': glymur.lib.openjp2._CODEC_J2K, + 'comp_prec': 8, + 'irreversible': 1, + 'num_comps': 3, + 'image_height': 200, + 'image_width': 200, + 'tile_height': 100, + 'tile_width': 100} + self.tile_encoder(**kwargs) + + def test_tte0(self): + # Runs test designated tte0 in OpenJPEG test suite. + with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: + self.tte0_setup(tfile.name) + + 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. + self.tte0_setup(tfile.name) + + kwargs = {'x0': 0, + 'y0': 0, + 'x1': 1000, + 'y1': 1000, + 'filename': tfile.name, + 'codec_format': glymur.lib.openjp2._CODEC_J2K} + self.tile_decoder(**kwargs) + + def tte1_setup(self, filename): + kwargs = {'filename': filename, + 'codec': glymur.lib.openjp2._CODEC_J2K, + 'comp_prec': 8, + 'irreversible': 1, + 'num_comps': 3, + 'image_height': 256, + 'image_width': 256, + 'tile_height': 128, + 'tile_width': 128} + self.tile_encoder(**kwargs) + + def test_tte1(self): + with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: + # Runs test designated tte1 in OpenJPEG test suite. + self.tte1_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.tte1_setup(tfile.name) + + kwargs = {'x0': 0, + 'y0': 0, + 'x1': 128, + 'y1': 128, + 'filename': tfile.name, + 'codec_format': glymur.lib.openjp2._CODEC_J2K} + self.tile_decoder(**kwargs) + + def test_rta1(self): + with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: + # Runs test designated rta1 in OpenJPEG test suite. + self.tte1_setup(tfile.name) + + kwargs = {'codec_format': glymur.lib.openjp2._CODEC_J2K} + self.j2k_random_tile_access(tfile.name, **kwargs) + + def tte2_setup(self, filename): + kwargs = {'filename': filename, + 'codec': glymur.lib.openjp2._CODEC_JP2, + 'comp_prec': 8, + 'irreversible': 1, + 'num_comps': 3, + 'image_height': 256, + 'image_width': 256, + 'tile_height': 128, + 'tile_width': 128} + self.tile_encoder(**kwargs) + + def test_tte2(self): + # Runs test designated tte2 in OpenJPEG test suite. + with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: + self.tte2_setup(tfile.name) + + 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. + self.tte2_setup(tfile.name) + + kwargs = {'x0': 0, + 'y0': 0, + 'x1': 128, + 'y1': 128, + 'filename': tfile.name, + 'codec_format': glymur.lib.openjp2._CODEC_JP2} + self.tile_decoder(**kwargs) + + def test_rta2(self): + with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: + # Runs test designated rta2 in OpenJPEG test suite. + self.tte2_setup(tfile.name) + + kwargs = {'codec_format': glymur.lib.openjp2._CODEC_JP2} + self.j2k_random_tile_access(tfile.name, **kwargs) + + def tte3_setup(self, filename): + kwargs = {'filename': filename, + 'codec': glymur.lib.openjp2._CODEC_J2K, + 'comp_prec': 8, + 'irreversible': 1, + 'num_comps': 1, + 'image_height': 256, + 'image_width': 256, + 'tile_height': 128, + 'tile_width': 128} + self.tile_encoder(**kwargs) + + def test_tte3(self): + with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: + # Runs test designated tte3 in OpenJPEG test suite. + self.tte3_setup(tfile.name) + + def test_rta3(self): + # Runs test designated rta3 in OpenJPEG test suite. + with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: + self.tte3_setup(tfile.name) + + kwargs = {'codec_format': glymur.lib.openjp2._CODEC_J2K} + self.j2k_random_tile_access(tfile.name, **kwargs) + + def tte4_setup(self, filename): + kwargs = {'filename': filename, + 'codec': glymur.lib.openjp2._CODEC_J2K, + 'comp_prec': 8, + 'irreversible': 0, + 'num_comps': 1, + 'image_height': 256, + 'image_width': 256, + 'tile_height': 128, + 'tile_width': 128} + self.tile_encoder(**kwargs) + + def test_tte4(self): + # Runs test designated tte4 in OpenJPEG test suite. + with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: + self.tte4_setup(tfile.name) + + def test_rta4(self): + # Runs test designated rta4 in OpenJPEG test suite. + with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: + self.tte4_setup(tfile.name) + + kwargs = {'codec_format': glymur.lib.openjp2._CODEC_J2K} + self.j2k_random_tile_access(tfile.name, **kwargs) + + def tte5_setup(self, filename): + kwargs = {'filename': filename, + 'codec': glymur.lib.openjp2._CODEC_J2K, + 'comp_prec': 8, + 'irreversible': 0, + 'num_comps': 1, + 'image_height': 512, + 'image_width': 512, + 'tile_height': 256, + 'tile_width': 256} + self.tile_encoder(**kwargs) + + def test_tte5(self): + # Runs test designated tte5 in OpenJPEG test suite. + with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: + self.tte5_setup(tfile.name) + + def test_rta5(self): + # Runs test designated rta5 in OpenJPEG test suite. + with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile: + self.tte5_setup(tfile.name) + + kwargs = {'codec_format': glymur.lib.openjp2._CODEC_J2K} + self.j2k_random_tile_access(tfile.name, **kwargs) + +if __name__ == "__main__": + unittest.main() diff --git a/glymur/test/__init__.py b/glymur/test/__init__.py new file mode 100644 index 0000000..9677af5 --- /dev/null +++ b/glymur/test/__init__.py @@ -0,0 +1,7 @@ +from .test_callbacks import TestCallbacks as callbacks +from .test_codestream import TestCodestream as codestream +from .test_jp2k import TestJp2k as jp2k +from .test_printing import TestPrinting as printing +from .test_opj_suite import TestSuite as suite +from .test_opj_suite_write import TestSuiteWrite as suitew +from .test_opj_suite_neg import TestSuiteNegative as suiteneg diff --git a/glymur/test/test_callbacks.py b/glymur/test/test_callbacks.py new file mode 100644 index 0000000..1c70743 --- /dev/null +++ b/glymur/test/test_callbacks.py @@ -0,0 +1,72 @@ +import os +import pkg_resources +import sys +import tempfile +import unittest +import warnings + +if sys.hexversion < 0x03000000: + from StringIO import StringIO +else: + from io import StringIO + +import glymur + +try: + data_root = os.environ['OPJ_DATA_ROOT'] +except KeyError: + data_root = None +except: + raise + + +class TestCallbacks(unittest.TestCase): + + def setUp(self): + # Save sys.stdout. + self.stdout = sys.stdout + sys.stdout = StringIO() + self.jp2file = pkg_resources.resource_filename(glymur.__name__, + "data/nemo.jp2") + + def tearDown(self): + # Restore stdout. + sys.stdout = self.stdout + + def test_info_callback_on_write(self): + # Verify the messages printed when writing an image in verbose mode. + j = glymur.Jp2k(self.jp2file) + 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') + j.write(tiledata, verbose=True) + actual = sys.stdout.getvalue().strip() + expected = '[INFO] tile number 1 / 1' + self.assertEqual(actual, expected) + + def test_info_warning_callbacks_on_read(self): + # Verify that we get the expected stdio output when our internal info + # callback handler is enabled. + j = glymur.Jp2k(self.jp2file) + d = j.read(reduce=3, verbose=True, area=(0, 0, 512, 1024)) + actual = sys.stdout.getvalue().strip() + + lines = ['[INFO] Start to read j2k main header (85).', + '[INFO] Main header has been correctly decoded.', + '[INFO] Setting decoding area to 0,0,1024,512', + '[INFO] Header of tile 0 / 17 has been read.', + '[INFO] Tile 1/18 has been decoded.', + '[INFO] Image data has been updated with tile 1.', + '[INFO] Header of tile 1 / 17 has been read.', + '[INFO] Tile 2/18 has been decoded.', + '[INFO] Image data has been updated with tile 2.', + '[INFO] Stream reached its end !'] + + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + +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..4acfb79 --- /dev/null +++ b/glymur/test/test_codestream.py @@ -0,0 +1,92 @@ +import os +import struct +import sys +import tempfile +import unittest + +import numpy as np +import pkg_resources + +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): + + def setUp(self): + self.jp2file = pkg_resources.resource_filename(glymur.__name__, + "data/nemo.jp2") + + def tearDown(self): + pass + + def test_reserved_marker_segment(self): + # 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. + buffer = ifile.read(45) + tfile.write(buffer) + + # Write the new marker segment, 0xff6f = 65391 + buffer = struct.pack('>HHB', int(65391), int(3), int(0)) + tfile.write(buffer) + + # Get the rest of the input file. + buffer = ifile.read() + tfile.write(buffer) + tfile.flush() + + j = Jp2k(tfile.name) + c = j.get_codestream() + + self.assertEqual(c.segment[2].id, '0xff6f') + self.assertEqual(c.segment[2].length, 3) + self.assertEqual(c.segment[2].data, b'\x00') + + @unittest.skipIf(sys.hexversion < 0x03020000, + "Uses features introduced in 3.2.") + def test_unknown_marker_segment(self): + # 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. + buffer = ifile.read(45) + tfile.write(buffer) + + # Write the new marker segment, 0xff79 = 65401 + buffer = struct.pack('>HHB', int(65401), int(3), int(0)) + tfile.write(buffer) + + # Get the rest of the input file. + buffer = ifile.read() + tfile.write(buffer) + tfile.flush() + + with self.assertWarns(UserWarning) as cw: + j = Jp2k(tfile.name) + c = j.get_codestream() + + self.assertEqual(c.segment[2].id, '0xff79') + self.assertEqual(c.segment[2].length, 3) + self.assertEqual(c.segment[2].data, b'\x00') + +if __name__ == "__main__": + unittest.main() diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py new file mode 100644 index 0000000..256cade --- /dev/null +++ b/glymur/test/test_jp2k.py @@ -0,0 +1,602 @@ +import contextlib +import ctypes +import doctest +import imp +import os +import re +import struct +import sys +import tempfile +import uuid +import unittest +if sys.hexversion <= 0x03030000: + from mock import patch +else: + from unittest.mock import patch +import warnings +from xml.etree import cElementTree as ET + +import numpy as np +import pkg_resources + +from glymur import Jp2k +from glymur.lib import openjp2 as opj2 +import glymur + +try: + data_root = os.environ['OPJ_DATA_ROOT'] +except KeyError: + data_root = None +except: + raise + + +# Doc tests should be run as well. +def load_tests(loader, tests, ignore): + tests.addTests(doctest.DocTestSuite('glymur.jp2k')) + return tests + + +@contextlib.contextmanager +def chdir(dirname=None): + curdir = os.getcwd() + try: + if dirname is not None: + os.chdir(dirname) + yield + finally: + os.chdir(curdir) + + +class TestJp2k(unittest.TestCase): + + def setUp(self): + self.jp2file = pkg_resources.resource_filename(glymur.__name__, + "data/nemo.jp2") + + def tearDown(self): + pass + + @unittest.skipIf(sys.hexversion < 0x03020000, + "Uses features introduced in 3.2.") + def test_invalid_xml_box(self): + # Should be able to recover from xml box with bad xml. + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + with open(self.jp2file, 'rb') as ifile: + # Everything up until the jp2c box. + buffer = ifile.read(77) + tfile.write(buffer) + + # Write the xml box with bad xml + # Length = 28, id is 'xml '. + buffer = struct.pack('>I4s', int(28), b'xml ') + tfile.write(buffer) + + buffer = 'this is a test' + buffer = buffer.encode() + tfile.write(buffer) + + # Get the rest of the input file. + buffer = ifile.read() + tfile.write(buffer) + tfile.flush() + + with self.assertWarns(UserWarning) as cw: + jp2k = Jp2k(tfile.name) + + self.assertEqual(jp2k.box[3].id, 'xml ') + self.assertEqual(jp2k.box[3].offset, 77) + self.assertEqual(jp2k.box[3].length, 28) + self.assertIsNone(jp2k.box[3].xml) + + def test_bad_area_parameter(self): + # Verify that we error out appropriately if given a bad area parameter. + j = Jp2k(self.jp2file) + with self.assertRaises(IOError): + # Start corner must be >= 0 + d = j.read(area=(-1, -1, 1, 1)) + with self.assertRaises(IOError): + # End corner must be > 0 + d = j.read(area=(10, 10, 0, 0)) + with self.assertRaises(IOError): + # End corner must be >= start corner + d = j.read(area=(10, 10, 8, 8)) + + def test_reduce_too_high(self): + # Verify that we error out appropriately if not given a JPEG 2000 file. + j = Jp2k(self.jp2file) + with self.assertRaises(IOError): + d = j.read(reduce=6) + + def test_not_JPEG2000(self): + # Verify that we error out appropriately if not given a JPEG 2000 file. + filename = pkg_resources.resource_filename(glymur.__name__, "jp2k.py") + with self.assertRaises(IOError): + jp2k = Jp2k(filename) + + def test_file_not_present(self): + # Verify that we error out appropriately if not given an existing file + # at all. + if sys.hexversion < 0x03030000: + error = OSError + else: + error = IOError + with self.assertRaises(error): + filename = 'this file does not actually exist on the file system.' + jp2k = Jp2k(filename) + + def test_nemo_tile(self): + # Issue 134, trouble reading first nemo tile. + j = Jp2k(self.jp2file) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + tiledata = j.read(tile=0) + subsetdata = j.read(area=(0, 0, 512, 512)) + np.testing.assert_array_equal(tiledata, subsetdata) + + def test_jp2_boxes(self): + # Verify the boxes of a JP2 file. + jp2k = Jp2k(self.jp2file) + + # top-level boxes + self.assertEqual(len(jp2k.box), 4) + + self.assertEqual(jp2k.box[0].id, 'jP ') + self.assertEqual(jp2k.box[0].offset, 0) + self.assertEqual(jp2k.box[0].length, 12) + self.assertEqual(jp2k.box[0].longname, 'JPEG 2000 Signature') + + self.assertEqual(jp2k.box[1].id, 'ftyp') + self.assertEqual(jp2k.box[1].offset, 12) + self.assertEqual(jp2k.box[1].length, 20) + self.assertEqual(jp2k.box[1].longname, 'File Type') + + self.assertEqual(jp2k.box[2].id, 'jp2h') + self.assertEqual(jp2k.box[2].offset, 32) + self.assertEqual(jp2k.box[2].length, 45) + self.assertEqual(jp2k.box[2].longname, 'JP2 Header') + + self.assertEqual(jp2k.box[3].id, 'jp2c') + self.assertEqual(jp2k.box[3].offset, 77) + self.assertEqual(jp2k.box[3].length, 1133427) + + # jp2h super box + self.assertEqual(len(jp2k.box[2].box), 2) + + self.assertEqual(jp2k.box[2].box[0].id, 'ihdr') + self.assertEqual(jp2k.box[2].box[0].offset, 40) + self.assertEqual(jp2k.box[2].box[0].length, 22) + self.assertEqual(jp2k.box[2].box[0].longname, 'Image Header') + self.assertEqual(jp2k.box[2].box[0].height, 1456) + self.assertEqual(jp2k.box[2].box[0].width, 2592) + self.assertEqual(jp2k.box[2].box[0].num_components, 3) + self.assertEqual(jp2k.box[2].box[0].bits_per_component, 8) + self.assertEqual(jp2k.box[2].box[0].signed, False) + self.assertEqual(jp2k.box[2].box[0].compression, 7) + self.assertEqual(jp2k.box[2].box[0].cspace_unknown, False) + self.assertEqual(jp2k.box[2].box[0].ip_provided, False) + + self.assertEqual(jp2k.box[2].box[1].id, 'colr') + self.assertEqual(jp2k.box[2].box[1].offset, 62) + self.assertEqual(jp2k.box[2].box[1].length, 15) + self.assertEqual(jp2k.box[2].box[1].longname, 'Colour Specification') + self.assertEqual(jp2k.box[2].box[1].precedence, 0) + self.assertEqual(jp2k.box[2].box[1].approximation, 0) + self.assertEqual(jp2k.box[2].box[1].color_space, glymur.core.SRGB) + self.assertIsNone(jp2k.box[2].box[1].icc_profile) + + @unittest.skipIf(data_root is None, + "OPJ_DATA_ROOT environment variable not set") + def test_j2k_box(self): + # Verify that a J2K file has no boxes. + filename = os.path.join(data_root, 'input/conformance/p0_01.j2k') + jp2k = Jp2k(filename) + self.assertEqual(len(jp2k.box), 0) + + def test_64bit_XL_field(self): + # Verify that boxes with the XL field are properly read. + # Don't have such a file on hand, so we create one. Copy our example + # file, but making the codestream have a 64-bit XL field. + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + with open(self.jp2file, 'rb') as ifile: + # Everything up until the jp2c box. + buffer = ifile.read(77) + tfile.write(buffer) + + # The L field must be 1 in order to signal the presence of the + # XL field. The actual length of the jp2c box increased by 8 + # (8 bytes for the XL field). + L = 1 + T = b'jp2c' + XL = 1133427 + 8 + buffer = struct.pack('>I4sQ', int(L), T, XL) + tfile.write(buffer) + + # Get the rest of the input file (minus the 8 bytes for L and + # T. + ifile.seek(8, 1) + buffer = ifile.read() + tfile.write(buffer) + tfile.flush() + + jp2k = Jp2k(tfile.name) + + self.assertEqual(jp2k.box[3].id, 'jp2c') + self.assertEqual(jp2k.box[3].offset, 77) + self.assertEqual(jp2k.box[3].length, 1133427 + 8) + + def test_L_is_zero(self): + # Verify that boxes with the L field as zero are correctly read. + # This should only happen in the last box of a JPEG 2000 file. + # Our example image has its last box at byte 588458. + baseline_jp2 = Jp2k(self.jp2file) + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + with open(self.jp2file, 'rb') as ifile: + # Everything up until the jp2c box. + buffer = ifile.read(588458) + tfile.write(buffer) + + L = 0 + T = b'uuid' + buffer = struct.pack('>I4s', int(L), T) + tfile.write(buffer) + + # Get the rest of the input file (minus the 8 bytes for L and + # T. + ifile.seek(8, 1) + buffer = ifile.read() + tfile.write(buffer) + tfile.flush() + + new_jp2 = Jp2k(tfile.name) + + # The top level boxes in each file should match. + for j in range(len(baseline_jp2.box)): + self.assertEqual(new_jp2.box[j].id, + baseline_jp2.box[j].id) + self.assertEqual(new_jp2.box[j].offset, + baseline_jp2.box[j].offset) + self.assertEqual(new_jp2.box[j].length, + baseline_jp2.box[j].length) + + @unittest.skipIf(data_root is None, + "OPJ_DATA_ROOT environment variable not set") + def test_read_differing_subsamples(self): + # Verify that we error out appropriately if we use the read method + # on an image with differing subsamples + # + # Issue 86. + filename = os.path.join(data_root, 'input/conformance/p0_05.j2k') + j = Jp2k(filename) + with self.assertRaises(IOError): + j.read() + + @unittest.skipIf(data_root is None, + "OPJ_DATA_ROOT environment variable not set") + def test_empty_box_with_j2k(self): + # Verify that the list of boxes in a J2C/J2K file is present, but + # empty. + filename = os.path.join(data_root, 'input/conformance/p0_05.j2k') + j = Jp2k(filename) + self.assertEqual(j.box, []) + + def test_code_block_height_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.write(data, cbsize=(16, 32)) + + c = j.get_codestream() + + # Code block size is reported as XY in the codestream. + self.assertEqual(tuple(c.segment[2].SPcod[5:7]), (3, 2)) + + def test_negative_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) as ce: + data = np.zeros((128, 128, 2, 2), dtype=np.uint8) + j.write(data) + + def test_unrecognized_jp2_colorspace(self): + # We only allow RGB and GRAYSCALE. + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + j = Jp2k(tfile.name, 'wb') + with self.assertRaises(IOError) as ce: + data = np.zeros((128, 128, 3), dtype=np.uint8) + j.write(data, colorspace='cmyk') + + 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) as ce: + data = np.zeros((128, 128, 2), dtype=np.uint8) + j.write(data, colorspace='rgb') + + 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) as ce: + data = np.zeros((128, 128, 3), dtype=np.uint8) + j.write(data, colorspace='rgb') + + def test_specify_rgb(self): + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + 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].color_space, glymur.core.SRGB) + + def test_specify_gray(self): + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + j = Jp2k(tfile.name, 'wb') + data = np.zeros((128, 128), dtype=np.uint8) + j.write(data, colorspace='gray') + self.assertEqual(j.box[2].box[1].color_space, + glymur.core.GREYSCALE) + + def test_specify_grey(self): + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + j = Jp2k(tfile.name, 'wb') + data = np.zeros((128, 128), dtype=np.uint8) + j.write(data, colorspace='grey') + self.assertEqual(j.box[2].box[1].color_space, + glymur.core.GREYSCALE) + + def test_grey_with_extra_component(self): + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + j = Jp2k(tfile.name, 'wb') + data = np.zeros((128, 128, 2), dtype=np.uint8) + 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].color_space, + glymur.core.GREYSCALE) + + def test_grey_with_two_extra_components(self): + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + j = Jp2k(tfile.name, 'wb') + data = np.zeros((128, 128, 3), dtype=np.uint8) + 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) + self.assertEqual(j.box[2].box[1].color_space, + glymur.core.GREYSCALE) + + def test_rgb_with_extra_component(self): + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + j = Jp2k(tfile.name, 'wb') + data = np.zeros((128, 128, 4), dtype=np.uint8) + 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].color_space, glymur.core.SRGB) + + def test_specify_ycc(self): + # We don't support writing YCC at the moment. + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + j = Jp2k(tfile.name, 'wb') + with self.assertRaises(IOError) as ce: + data = np.zeros((128, 128, 3), dtype=np.uint8) + j.write(data, colorspace='ycc') + + def test_uinf_ulst_url_boxes(self): + # Verify that we can read UINF, ULST, and URL boxes. I don't have + # easy access to such a file, and there's no such file in the + # openjpeg repository, so I'll fake one. + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + with open(self.jp2file, 'rb') as ifile: + # Everything up until the jp2c box. + buffer = ifile.read(77) + tfile.write(buffer) + + # Write the UINF superbox + # Length = 50, id is uinf. + buffer = struct.pack('>I4s', int(50), b'uinf') + tfile.write(buffer) + + # Write the ULST box. + # Length is 26, 1 UUID, hard code that UUID as zeros. + buffer = struct.pack('>I4sHIIII', int(26), b'ulst', int(1), + int(0), int(0), int(0), int(0)) + tfile.write(buffer) + + # Write the URL box. + # Length is 16, version is one byte, flag is 3 bytes, url + # is the rest. + buffer = struct.pack('>I4sBBBB', + int(16), b'url ', + int(0), int(0), int(0), int(0)) + tfile.write(buffer) + buffer = struct.pack('>ssss', b'a', b'b', b'c', b'd') + tfile.write(buffer) + + # Get the rest of the input file. + buffer = ifile.read() + tfile.write(buffer) + tfile.flush() + + jp2k = Jp2k(tfile.name) + + self.assertEqual(jp2k.box[3].id, 'uinf') + self.assertEqual(jp2k.box[3].offset, 77) + self.assertEqual(jp2k.box[3].length, 50) + + self.assertEqual(jp2k.box[3].box[0].id, 'ulst') + self.assertEqual(jp2k.box[3].box[0].offset, 85) + self.assertEqual(jp2k.box[3].box[0].length, 26) + ulst = [] + ulst.append(uuid.UUID('00000000-0000-0000-0000-000000000000')) + self.assertEqual(jp2k.box[3].box[0].ulst, ulst) + + self.assertEqual(jp2k.box[3].box[1].id, 'url ') + self.assertEqual(jp2k.box[3].box[1].offset, 111) + self.assertEqual(jp2k.box[3].box[1].length, 16) + self.assertEqual(jp2k.box[3].box[1].version, 0) + self.assertEqual(jp2k.box[3].box[1].flag, (0, 0, 0)) + self.assertEqual(jp2k.box[3].box[1].URL, 'abcd') + + def test_xml_box_with_trailing_nulls(self): + # ElementTree does not like trailing null chars after valid XML + # text. + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + with open(self.jp2file, 'rb') as ifile: + # Everything up until the jp2c box. + buffer = ifile.read(77) + tfile.write(buffer) + + # Write the xml box + # Length = 36, id is 'xml '. + buffer = struct.pack('>I4s', int(36), b'xml ') + tfile.write(buffer) + + buffer = 'this is a test' + chr(0) + buffer = buffer.encode() + tfile.write(buffer) + + # Get the rest of the input file. + buffer = ifile.read() + tfile.write(buffer) + tfile.flush() + + jp2k = Jp2k(tfile.name) + + self.assertEqual(jp2k.box[3].id, 'xml ') + self.assertEqual(jp2k.box[3].offset, 77) + self.assertEqual(jp2k.box[3].length, 36) + + def test_asoc_label_box(self): + # Construct a fake file with an asoc and a label box, as + # OpenJPEG doesn't have such a file. + data = Jp2k(self.jp2file).read(reduce=3) + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + j = Jp2k(tfile.name, 'wb') + j.write(data) + + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile2: + + # Offset of the codestream is where we start. + buffer = tfile.read(77) + tfile2.write(buffer) + + # read the rest of the file, it's the codestream. + codestream = tfile.read() + + # Write the asoc superbox. + # Length = 36, id is 'asoc'. + buffer = struct.pack('>I4s', int(56), b'asoc') + tfile2.write(buffer) + + # Write the contained label box + buffer = struct.pack('>I4s', int(13), b'lbl ') + tfile2.write(buffer) + tfile2.write('label'.encode()) + + # Write the xml box + # Length = 36, id is 'xml '. + buffer = struct.pack('>I4s', int(35), b'xml ') + tfile2.write(buffer) + + buffer = 'this is a test' + buffer = buffer.encode() + tfile2.write(buffer) + + # Now append the codestream. + tfile2.write(codestream) + + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + jasoc = Jp2k(tfile2.name) + self.assertEqual(jasoc.box[3].id, 'asoc') + self.assertEqual(jasoc.box[3].box[0].id, 'lbl ') + self.assertEqual(jasoc.box[3].box[0].label, 'label') + self.assertEqual(jasoc.box[3].box[1].id, 'xml ') + + 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 fp: + data = fp.read() + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + tfile.write(data[0:129]) + + # 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[130:132]) + tfile.write(b'\x00') + tfile.write(data[134:136]) + tfile.write(b'\x00') + + tfile.write(data[136:]) + tfile.flush() + with warnings.catch_warnings(): + 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, regexp) as ce: + d = j.read(reduce=3) + else: + with self.assertRaisesRegex(IOError, regexp) as ce: + d = j.read(reduce=3) + + @unittest.skipIf(sys.hexversion < 0x03020000, + "Uses features introduced in 3.2.") + def test_config_file_via_environ(self): + """Verify that we can read a configuration file set via environ var.""" + with tempfile.TemporaryDirectory() as tdir: + filename = os.path.join(tdir, 'glymurrc') + with open(filename, 'wb') as tfile: + tfile.write('[library]\n'.encode()) + line = 'openjp2: {0}\n'.format(glymur._OPENJP2._name) + tfile.write(line.encode()) + tfile.flush() + with patch.dict('os.environ', {'GLYMURCONFIGDIR': tdir}): + imp.reload(glymur) + j = Jp2k(self.jp2file) + + @unittest.skipIf(sys.hexversion < 0x03020000, + "Uses features introduced in 3.2.") + def test_config_file_via_environ_is_wrong(self): + # A non-existant library location should be rejected. + with tempfile.TemporaryDirectory() as tdir: + fname = os.path.join(tdir, 'glymurrc') + with open(fname, 'w') as fp: + with tempfile.NamedTemporaryFile(suffix='.dylib') as tfile: + fp.write('[library]\n') + fp.write('openjp2: {0}.not.there\n'.format(tfile.name)) + fp.flush() + with patch.dict('os.environ', {'GLYMURCONFIGDIR': tdir}): + # Misconfigured new configuration file should + # be rejected. + with self.assertRaises(ImportError) as ce: + imp.reload(glymur) + + @unittest.skipIf(sys.hexversion < 0x03020000, + "Uses features introduced in 3.2.") + def test_missing_config_file_via_environ(self): + # Verify that we error out properly if the configuration file + # specified via environment variable is not found. + with tempfile.TemporaryDirectory() as tdir: + with patch.dict('os.environ', {'GLYMURCONFIGDIR': tdir}): + # Misconfigured new configuration file should + # be rejected. + with self.assertRaises(ImportError) as ce: + imp.reload(glymur) + + +if __name__ == "__main__": + unittest.main() diff --git a/glymur/test/test_opj_suite.py b/glymur/test/test_opj_suite.py new file mode 100644 index 0000000..112fbaf --- /dev/null +++ b/glymur/test/test_opj_suite.py @@ -0,0 +1,7364 @@ +""" +The tests defined here roughly correspond to what is in the OpenJPEG test +suite. +""" + +import os +import platform +import re +import sys +from xml.etree import cElementTree as ET +import unittest +import warnings + +import numpy as np + +from glymur import Jp2k +import glymur + +try: + data_root = os.environ['OPJ_DATA_ROOT'] +except KeyError: + data_root = None +except: + raise + + +def mse(A, B): + """Mean Square Error""" + diff = A.astype(np.double) - B.astype(np.double) + #e = np.sqrt(np.mean(diff**2)) + e = np.mean(diff**2) + return e + + +def peak_tolerance(A, B): + """Peak Tolerance""" + diff = np.abs(A.astype(np.double) - B.astype(np.double)) + p = diff.max() + return p + + +def read_pgx(pgx_file): + """Helper function for reading the PGX comparison files. + + Open the file in ascii mode and read the header line. + Will look something like + + PG ML + 8 128 128 + PG%[ \t]%c%c%[ \t+-]%d%[ \t]%d%[ \t]%d" + """ + header = '' + with open(pgx_file, 'rb') as fp: + while True: + x = fp.read(1) + if x[0] == 10 or x == '\n': + pos = fp.tell() + break + else: + if sys.hexversion < 0x03000000: + header += x + else: + header += chr(x[0]) + + header = header.rstrip() + n = re.split('\s', header) + + if (n[1][0] == 'M') and (sys.byteorder == 'little'): + swapbytes = True + elif (n[1][0] == 'L') and (sys.byteorder == 'big'): + swapbytes = True + else: + swapbytes = False + + if (len(n) == 6): + bitdepth = int(n[3]) + signed = bitdepth < 0 + if signed: + bitdepth = -1 * bitdepth + nrows = int(n[5]) + ncols = int(n[4]) + else: + bitdepth = int(n[2]) + signed = bitdepth < 0 + if signed: + bitdepth = -1 * bitdepth + nrows = int(n[4]) + ncols = int(n[3]) + + if signed: + if bitdepth <= 8: + dtype = np.int8 + elif bitdepth <= 16: + dtype = np.int16 + else: + raise RuntimeError("unhandled bitdepth") + else: + if bitdepth <= 8: + dtype = np.uint8 + elif bitdepth <= 16: + dtype = np.uint16 + else: + raise RuntimeError("unhandled bitdepth") + + shape = [nrows, ncols] + + # Reopen the file in binary mode and seek to the start of the binary + # data + with open(pgx_file, 'rb') as fp: + fp.seek(pos) + data = np.fromfile(file=fp, dtype=dtype).reshape(shape) + + return(data.byteswap(swapbytes)) + + +@unittest.skipIf(data_root is None, + "OPJ_DATA_ROOT environment variable not set") +class TestSuite(unittest.TestCase): + + def setUp(self): + pass + + def tearDown(self): + pass + + def test_ETS_C0P0_p0_01_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p0_01.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=0) + + pgxfile = os.path.join(data_root, '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 = os.path.join(data_root, 'input/conformance/p0_02.j2k') + with warnings.catch_warnings(): + # There's a 0xff30 marker segment. Not illegal, but we don't + # really know what to do with it. Just ignore. + warnings.simplefilter("ignore") + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=0) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c0p0_02.pgx') + pgxdata = read_pgx(pgxfile) + np.testing.assert_array_equal(jpdata, pgxdata) + + @unittest.skip("Known failure in OPENJPEG test suite.") + def test_ETS_C0P0_p0_03_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p0_03.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=0) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c0p0_03r0.pgx') + pgxdata = read_pgx(pgxfile) + np.testing.assert_array_equal(jpdata, pgxdata) + + def test_ETS_C0P0_p0_03_j2k_r1(self): + jfile = os.path.join(data_root, 'input/conformance/p0_03.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=1) + + pgxfile = os.path.join(data_root, + 'baseline/conformance/c0p0_03r1.pgx') + pgxdata = read_pgx(pgxfile) + np.testing.assert_array_equal(jpdata, pgxdata) + + @unittest.skip("Known failure in OPENJPEG test suite.") + def test_ETS_C0P0_p0_04_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=3) + + pgxfile = os.path.join(data_root, + 'baseline/conformance/c0p0_04.pgx') + pgxdata = read_pgx(pgxfile) + + self.assertTrue(peak_tolerance(jpdata[:, :, 2], pgxdata) < 33) + self.assertTrue(mse(jpdata[:, :, 2], pgxdata) < 55.8) + + @unittest.skip("Known failure in OPENJPEG test suite.") + def test_ETS_C0P0_p0_05_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p0_05.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read_bands(reduce=3) + + pgxfile = os.path.join(data_root, + 'baseline/conformance/c0p0_05.pgx') + pgxdata = read_pgx(pgxfile) + self.assertTrue(peak_tolerance(jpdata[0], pgxdata) < 54) + self.assertTrue(mse(jpdata[0], pgxdata) < 68) + + @unittest.skip("Known failure in OPENJPEG test suite.") + def test_ETS_C0P0_p0_06_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p0_06.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=3) + + pgxfile = os.path.join(data_root, + 'baseline/conformance/c0p0_06.pgx') + pgxdata = read_pgx(pgxfile) + self.assertTrue(peak_tolerance(jpdata[:, :, 0], pgxdata) < 109) + self.assertTrue(mse(jpdata[:, :, 0], pgxdata) < 743) + + @unittest.skip("Known failure in OPENJPEG test suite.") + def test_ETS_C0P0_p0_07_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p0_07.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read() + + pgxfile = os.path.join(data_root, + 'baseline/conformance/c0p0_07.pgx') + pgxdata = read_pgx(pgxfile) + + self.assertTrue(peak_tolerance(jpdata[:, :, 0], pgxdata) < 10) + self.assertTrue(mse(jpdata[:, :, 0], pgxdata) < 0.34) + + @unittest.skip("Known failure in OPENJPEG test suite.") + def test_ETS_C0P0_p0_08_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p0_08.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=5) + + pgxfile = os.path.join(data_root, + 'baseline/conformance/c0p0_08.pgx') + pgxdata = read_pgx(pgxfile) + + self.assertTrue(peak_tolerance(jpdata[:, :, 0], pgxdata) < 7) + self.assertTrue(mse(jpdata[:, :, 0], pgxdata) < 6.72) + + def test_ETS_C0P0_p0_09_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p0_09.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=2) + + pgxfile = os.path.join(data_root, + 'baseline/conformance/c0p0_09.pgx') + pgxdata = read_pgx(pgxfile) + + self.assertTrue(peak_tolerance(jpdata, pgxdata) < 4) + self.assertTrue(mse(jpdata, pgxdata) < 1.47) + + @unittest.skip("Known failure in OPENJPEG test suite.") + def test_ETS_C0P0_p0_10_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p0_10.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=0) + + pgxfile = os.path.join(data_root, + 'baseline/conformance/c0p0_10.pgx') + pgxdata = read_pgx(pgxfile) + + self.assertTrue(peak_tolerance(jpdata[:, :, 0], pgxdata) < 10) + self.assertTrue(mse(jpdata[:, :, 0], pgxdata) < 2.84) + + def test_ETS_C0P0_p0_11_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p0_11.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=0) + + pgxfile = os.path.join(data_root, + 'baseline/conformance/c0p0_11.pgx') + pgxdata = read_pgx(pgxfile) + + np.testing.assert_array_equal(jpdata, pgxdata) + + def test_ETS_C0P0_p0_12_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p0_12.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=0) + + pgxfile = os.path.join(data_root, + 'baseline/conformance/c0p0_12.pgx') + pgxdata = read_pgx(pgxfile) + + np.testing.assert_array_equal(jpdata, pgxdata) + + @unittest.skip("Known failure in OPENJPEG test suite.") + def test_ETS_C0P0_p0_13_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p0_13.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=0) + + pgxfile = os.path.join(data_root, + 'baseline/conformance/c0p0_13.pgx') + pgxdata = read_pgx(pgxfile) + + np.testing.assert_array_equal(jpdata, pgxdata) + + @unittest.skip("Known failure in OPENJPEG test suite.") + def test_ETS_C0P0_p0_14_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p0_14.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=2) + + pgxfile = os.path.join(data_root, + 'baseline/conformance/c0p0_14.pgx') + pgxdata = read_pgx(pgxfile) + + np.testing.assert_array_equal(jpdata[:, :, 0], pgxdata) + + @unittest.skip("Known failure in OPENJPEG test suite.") + def test_ETS_C0P0_p0_15_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p0_15.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=0) + + pgxfile = os.path.join(data_root, + 'baseline/conformance/c0p0_15r0.pgx') + pgxdata = read_pgx(pgxfile) + + np.testing.assert_array_equal(jpdata, pgxdata) + + def test_ETS_C0P0_p0_15_j2k_r1(self): + jfile = os.path.join(data_root, 'input/conformance/p0_15.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=1) + + pgxfile = os.path.join(data_root, + '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 = os.path.join(data_root, 'input/conformance/p0_16.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=0) + + pgxfile = os.path.join(data_root, + '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 = os.path.join(data_root, 'input/conformance/p1_01.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=0) + + pgxfile = os.path.join(data_root, + 'baseline/conformance/c0p1_01.pgx') + pgxdata = read_pgx(pgxfile) + + np.testing.assert_array_equal(jpdata, pgxdata) + + @unittest.skip("Known failure in OPENJPEG test suite operation.") + def test_ETS_C0P1_p1_02_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p1_02.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=3) + + pgxfile = os.path.join(data_root, + 'baseline/conformance/c0p1_02.pgx') + pgxdata = read_pgx(pgxfile) + + print(peak_tolerance(jpdata[:, :, 0], pgxdata)) + print(peak_tolerance(jpdata[:, :, 1], pgxdata)) + print(peak_tolerance(jpdata[:, :, 2], pgxdata)) + self.assertTrue(peak_tolerance(jpdata[:, :, 0], pgxdata) < 35) + self.assertTrue(mse(jpdata[:, :, 0], pgxdata) < 74) + + @unittest.skip("Known failure in OPENJPEG test suite operation.") + def test_ETS_C0P1_p1_03_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p1_03.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=3) + + pgxfile = os.path.join(data_root, + 'baseline/conformance/c0p1_03.pgx') + pgxdata = read_pgx(pgxfile) + + print(peak_tolerance(jpdata[:, :, 0], pgxdata)) + print(peak_tolerance(jpdata[:, :, 1], pgxdata)) + print(peak_tolerance(jpdata[:, :, 2], pgxdata)) + self.assertTrue(peak_tolerance(jpdata[:, :, 0], pgxdata) < 28) + self.assertTrue(mse(jpdata[:, :, 0], pgxdata) < 18.8) + + @unittest.skip("Known failure in OPENJPEG test suite operation.") + def test_ETS_C0P1_p1_04_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=0) + + pgxfile = os.path.join(data_root, + 'baseline/conformance/c0p1_04r0.pgx') + pgxdata = read_pgx(pgxfile) + + print(peak_tolerance(jpdata, pgxdata)) + self.assertTrue(peak_tolerance(jpdata, pgxdata) < 2) + self.assertTrue(mse(jpdata, pgxdata) < 0.55) + + @unittest.skip("Known failure in OPENJPEG test suite, precision issue.") + def test_ETS_C0P1_p1_04_j2k_r3(self): + jfile = os.path.join(data_root, 'input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=3) + + pgxfile = os.path.join(data_root, + 'baseline/conformance/c0p1_04r3.pgx') + pgxdata = read_pgx(pgxfile) + + print(peak_tolerance(jpdata, pgxdata)) + self.assertTrue(peak_tolerance(jpdata, pgxdata) < 2) + self.assertTrue(mse(jpdata, pgxdata) < 0.55) + + @unittest.skip("Known failure in OPENJPEG test suite operation.") + def test_ETS_C0P1_p1_05_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p1_05.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=4) + + pgxfile = os.path.join(data_root, + 'baseline/conformance/c0p1_05.pgx') + pgxdata = read_pgx(pgxfile) + + print(peak_tolerance(jpdata[:, :, 0], pgxdata)) + print(peak_tolerance(jpdata[:, :, 1], pgxdata)) + self.assertTrue(peak_tolerance(jpdata[:, :, 0], pgxdata) < 128) + self.assertTrue(mse(jpdata[:, :, 0], pgxdata) < 16384) + + @unittest.skip("Known failure in OPENJPEG test suite operation.") + def test_ETS_C0P1_p1_06_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=1) + + pgxfile = os.path.join(data_root, + 'baseline/conformance/c0p1_06.pgx') + pgxdata = read_pgx(pgxfile) + + print(peak_tolerance(jpdata[:, :, 0], pgxdata)) + print(peak_tolerance(jpdata[:, :, 1], pgxdata)) + self.assertTrue(peak_tolerance(jpdata[:, :, 0], pgxdata) < 128) + self.assertTrue(mse(jpdata[:, :, 0], pgxdata) < 16384) + + @unittest.skip("Known failure in OPENJPEG test suite operation.") + def test_ETS_C0P1_p1_07_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p1_07.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=0) + + pgxfile = os.path.join(data_root, + 'baseline/conformance/c0p1_07.pgx') + pgxdata = read_pgx(pgxfile) + + # This one works. + np.testing.assert_array_equal(jpdata[:, :, 0], pgxdata) + # This one does not. + np.testing.assert_array_equal(jpdata[:, :, 1], pgxdata) + + def test_ETS_C1P0_p0_01_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p0_01.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=0) + + pgxfile = os.path.join(data_root, '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 = os.path.join(data_root, 'input/conformance/p0_02.j2k') + with warnings.catch_warnings(): + # There's a 0xff30 marker segment. Not illegal, but we don't + # really know what to do with it. Just ignore. + warnings.simplefilter("ignore") + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=0) + + pgxfile = os.path.join(data_root, '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 = os.path.join(data_root, 'input/conformance/p0_03.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=0) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p0_03_0.pgx') + pgxdata = read_pgx(pgxfile) + + np.testing.assert_array_equal(jpdata, pgxdata) + + def test_ETS_C1P0_p0_04_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=0) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p0_04_0.pgx') + pgxdata = read_pgx(pgxfile) + self.assertTrue(peak_tolerance(jpdata[:, :, 0], pgxdata) < 5) + self.assertTrue(mse(jpdata[:, :, 0], pgxdata) < 0.776) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p0_04_1.pgx') + pgxdata = read_pgx(pgxfile) + self.assertTrue(peak_tolerance(jpdata[:, :, 1], pgxdata) < 4) + self.assertTrue(mse(jpdata[:, :, 1], pgxdata) < 0.626) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p0_04_2.pgx') + pgxdata = read_pgx(pgxfile) + self.assertTrue(peak_tolerance(jpdata[:, :, 2], pgxdata) < 6) + self.assertTrue(mse(jpdata[:, :, 2], pgxdata) < 1.07) + + def test_ETS_C1P0_p0_05_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p0_05.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read_bands() + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p0_05_0.pgx') + pgxdata = read_pgx(pgxfile) + self.assertTrue(peak_tolerance(jpdata[0], pgxdata) < 2) + self.assertTrue(mse(jpdata[0], pgxdata) < 0.302) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p0_05_1.pgx') + pgxdata = read_pgx(pgxfile) + self.assertTrue(peak_tolerance(jpdata[1], pgxdata) < 2) + self.assertTrue(mse(jpdata[1], pgxdata) < 0.307) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p0_05_2.pgx') + pgxdata = read_pgx(pgxfile) + self.assertTrue(peak_tolerance(jpdata[2], pgxdata) < 2) + self.assertTrue(mse(jpdata[2], pgxdata) < 0.269) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p0_05_3.pgx') + pgxdata = read_pgx(pgxfile) + self.assertTrue(peak_tolerance(jpdata[3], pgxdata) == 0) + self.assertTrue(mse(jpdata[3], pgxdata) == 0) + + def test_ETS_C1P0_p0_06_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p0_06.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read_bands() + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p0_06_0.pgx') + pgxdata = read_pgx(pgxfile) + self.assertTrue(peak_tolerance(jpdata[0], pgxdata) < 635) + self.assertTrue(mse(jpdata[0], pgxdata) < 11287) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p0_06_1.pgx') + pgxdata = read_pgx(pgxfile) + self.assertTrue(peak_tolerance(jpdata[1], pgxdata) < 403) + self.assertTrue(mse(jpdata[1], pgxdata) < 6124) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p0_06_2.pgx') + pgxdata = read_pgx(pgxfile) + self.assertTrue(peak_tolerance(jpdata[2], pgxdata) < 378) + self.assertTrue(mse(jpdata[2], pgxdata) < 3968) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p0_06_3.pgx') + pgxdata = read_pgx(pgxfile) + self.assertTrue(peak_tolerance(jpdata[3], pgxdata) == 0) + self.assertTrue(mse(jpdata[3], pgxdata) == 0) + + @unittest.skip("Known failure in OPENJPEG test suite operation.") + def test_ETS_C1P0_p0_07_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p0_07.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read() + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p0_07_0.pgx') + pgxdata = read_pgx(pgxfile) + np.testing.assert_array_equal(jpdata[:, :, 0], pgxdata) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p0_07_1.pgx') + pgxdata = read_pgx(pgxfile) + np.testing.assert_array_equal(jpdata[:, : 1], pgxdata) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p0_07_2.pgx') + pgxdata = read_pgx(pgxfile) + np.testing.assert_array_equal(jpdata[:, : 2], pgxdata) + + def test_ETS_C1P0_p0_08_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p0_08.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=1) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p0_08_0.pgx') + pgxdata = read_pgx(pgxfile) + np.testing.assert_array_equal(jpdata[:, :, 0], pgxdata) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p0_08_1.pgx') + pgxdata = read_pgx(pgxfile) + np.testing.assert_array_equal(jpdata[:, :, 1], pgxdata) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p0_08_2.pgx') + pgxdata = read_pgx(pgxfile) + np.testing.assert_array_equal(jpdata[:, :, 2], pgxdata) + + def test_ETS_C1P0_p0_09_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p0_09.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=0) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p0_09_0.pgx') + pgxdata = read_pgx(pgxfile) + np.testing.assert_array_equal(jpdata, pgxdata) + + def test_ETS_C1P0_p0_10_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p0_10.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=0) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p0_10_0.pgx') + pgxdata = read_pgx(pgxfile) + np.testing.assert_array_equal(jpdata[:, :, 0], pgxdata) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p0_10_1.pgx') + pgxdata = read_pgx(pgxfile) + np.testing.assert_array_equal(jpdata[:, :, 1], pgxdata) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p0_10_2.pgx') + pgxdata = read_pgx(pgxfile) + np.testing.assert_array_equal(jpdata[:, :, 2], pgxdata) + + def test_ETS_C1P0_p0_11_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p0_11.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=0) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p0_11_0.pgx') + pgxdata = read_pgx(pgxfile) + np.testing.assert_array_equal(jpdata, pgxdata) + + def test_ETS_C1P0_p0_12_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p0_12.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=0) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p0_12_0.pgx') + pgxdata = read_pgx(pgxfile) + np.testing.assert_array_equal(jpdata, pgxdata) + + def test_ETS_C1P0_p0_13_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p0_13.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=0) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p0_13_0.pgx') + pgxdata = read_pgx(pgxfile) + np.testing.assert_array_equal(jpdata[:, :, 0], pgxdata) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p0_13_1.pgx') + pgxdata = read_pgx(pgxfile) + np.testing.assert_array_equal(jpdata[:, :, 1], pgxdata) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p0_13_2.pgx') + pgxdata = read_pgx(pgxfile) + np.testing.assert_array_equal(jpdata[:, :, 2], pgxdata) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p0_13_3.pgx') + pgxdata = read_pgx(pgxfile) + np.testing.assert_array_equal(jpdata[:, :, 3], pgxdata) + + def test_ETS_C1P0_p0_14_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p0_14.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=0) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p0_14_0.pgx') + pgxdata = read_pgx(pgxfile) + np.testing.assert_array_equal(jpdata[:, :, 0], pgxdata) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p0_14_1.pgx') + pgxdata = read_pgx(pgxfile) + np.testing.assert_array_equal(jpdata[:, :, 1], pgxdata) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p0_14_2.pgx') + pgxdata = read_pgx(pgxfile) + np.testing.assert_array_equal(jpdata[:, :, 2], pgxdata) + + def test_ETS_C1P0_p0_15_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p0_15.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=0) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p0_15_0.pgx') + pgxdata = read_pgx(pgxfile) + np.testing.assert_array_equal(jpdata, pgxdata) + + def test_ETS_C1P0_p0_16_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p0_16.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=0) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p0_16_0.pgx') + pgxdata = read_pgx(pgxfile) + np.testing.assert_array_equal(jpdata, pgxdata) + + def test_ETS_C1P1_p1_01_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p1_01.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=0) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p1_01_0.pgx') + pgxdata = read_pgx(pgxfile) + np.testing.assert_array_equal(jpdata, pgxdata) + + def test_ETS_C1P1_p1_02_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p1_02.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read(reduce=0) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p1_02_0.pgx') + pgxdata = read_pgx(pgxfile) + self.assertTrue(peak_tolerance(jpdata[:, :, 0], pgxdata) < 5) + self.assertTrue(mse(jpdata[:, :, 0], pgxdata) < 0.765) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p1_02_1.pgx') + pgxdata = read_pgx(pgxfile) + self.assertTrue(peak_tolerance(jpdata[:, :, 1], pgxdata) < 4) + self.assertTrue(mse(jpdata[:, :, 1], pgxdata) < 0.616) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p1_02_2.pgx') + pgxdata = read_pgx(pgxfile) + self.assertTrue(peak_tolerance(jpdata[:, :, 2], pgxdata) < 6) + self.assertTrue(mse(jpdata[:, :, 2], pgxdata) < 1.051) + + def test_ETS_C1P1_p1_03_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p1_03.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read_bands() + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p1_03_0.pgx') + pgxdata = read_pgx(pgxfile) + self.assertTrue(peak_tolerance(jpdata[0], pgxdata) < 2) + self.assertTrue(mse(jpdata[0], pgxdata) < 0.3) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p1_03_1.pgx') + pgxdata = read_pgx(pgxfile) + self.assertTrue(peak_tolerance(jpdata[1], pgxdata) < 2) + self.assertTrue(mse(jpdata[1], pgxdata) < 0.21) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p1_03_2.pgx') + pgxdata = read_pgx(pgxfile) + self.assertTrue(peak_tolerance(jpdata[2], pgxdata) <= 1) + self.assertTrue(mse(jpdata[2], pgxdata) < 0.2) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p1_03_3.pgx') + pgxdata = read_pgx(pgxfile) + np.testing.assert_array_equal(jpdata[3], pgxdata) + + def test_ETS_C1P1_p1_04_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read() + + pgxfile = os.path.join(data_root, '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_ETS_C1P1_p1_05_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p1_05.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read() + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p1_05_0.pgx') + pgxdata = read_pgx(pgxfile) + self.assertTrue(peak_tolerance(jpdata[:, :, 0], pgxdata) < 40) + self.assertTrue(mse(jpdata[:, :, 0], pgxdata) < 8.458) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p1_05_1.pgx') + pgxdata = read_pgx(pgxfile) + self.assertTrue(peak_tolerance(jpdata[:, :, 1], pgxdata) < 40) + self.assertTrue(mse(jpdata[:, :, 1], pgxdata) < 9.816) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p1_05_2.pgx') + pgxdata = read_pgx(pgxfile) + self.assertTrue(peak_tolerance(jpdata[:, :, 2], pgxdata) < 40) + self.assertTrue(mse(jpdata[:, :, 2], pgxdata) < 10.154) + + def test_ETS_C1P1_p1_06_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read() + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p1_06_0.pgx') + pgxdata = read_pgx(pgxfile) + self.assertTrue(peak_tolerance(jpdata[:, :, 0], pgxdata) < 2) + self.assertTrue(mse(jpdata[:, :, 0], pgxdata) < 0.6) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p1_06_1.pgx') + pgxdata = read_pgx(pgxfile) + self.assertTrue(peak_tolerance(jpdata[:, :, 1], pgxdata) < 2) + self.assertTrue(mse(jpdata[:, :, 1], pgxdata) < 0.6) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p1_06_2.pgx') + pgxdata = read_pgx(pgxfile) + self.assertTrue(peak_tolerance(jpdata[:, :, 2], pgxdata) < 2) + self.assertTrue(mse(jpdata[:, :, 2], pgxdata) < 0.6) + + def test_ETS_C1P1_p1_07_j2k(self): + jfile = os.path.join(data_root, 'input/conformance/p1_07.j2k') + jp2k = Jp2k(jfile) + jpdata = jp2k.read_bands() + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p1_07_0.pgx') + pgxdata = read_pgx(pgxfile) + self.assertTrue(peak_tolerance(jpdata[0], pgxdata) <= 0) + self.assertTrue(mse(jpdata[0], pgxdata) <= 0) + + pgxfile = os.path.join(data_root, 'baseline/conformance/c1p1_07_1.pgx') + pgxdata = read_pgx(pgxfile) + self.assertTrue(peak_tolerance(jpdata[1], pgxdata) <= 0) + self.assertTrue(mse(jpdata[1], pgxdata) <= 0) + + def test_ETS_JP2_file1(self): + jfile = os.path.join(data_root, 'input/conformance/file1.jp2') + jp2k = Jp2k(jfile) + jpdata = jp2k.read() + self.assertEqual(jpdata.shape, (512, 768, 3)) + + def test_ETS_JP2_file2(self): + jfile = os.path.join(data_root, 'input/conformance/file2.jp2') + jp2k = Jp2k(jfile) + jpdata = jp2k.read() + self.assertEqual(jpdata.shape, (640, 480, 3)) + + def test_ETS_JP2_file3(self): + jfile = os.path.join(data_root, 'input/conformance/file3.jp2') + jp2k = Jp2k(jfile) + jpdata = jp2k.read_bands() + self.assertEqual(jpdata[0].shape, (640, 480)) + self.assertEqual(jpdata[1].shape, (320, 240)) + self.assertEqual(jpdata[2].shape, (320, 240)) + + def test_ETS_JP2_file4(self): + jfile = os.path.join(data_root, 'input/conformance/file4.jp2') + jp2k = Jp2k(jfile) + jpdata = jp2k.read() + self.assertEqual(jpdata.shape, (512, 768)) + + def test_ETS_JP2_file5(self): + jfile = os.path.join(data_root, 'input/conformance/file5.jp2') + jp2k = Jp2k(jfile) + jpdata = jp2k.read() + self.assertEqual(jpdata.shape, (512, 768, 3)) + + def test_ETS_JP2_file6(self): + jfile = os.path.join(data_root, 'input/conformance/file6.jp2') + jp2k = Jp2k(jfile) + jpdata = jp2k.read() + self.assertEqual(jpdata.shape, (512, 768)) + + def test_ETS_JP2_file7(self): + jfile = os.path.join(data_root, 'input/conformance/file7.jp2') + jp2k = Jp2k(jfile) + jpdata = jp2k.read() + self.assertEqual(jpdata.shape, (640, 480, 3)) + + def test_ETS_JP2_file8(self): + jfile = os.path.join(data_root, 'input/conformance/file8.jp2') + jp2k = Jp2k(jfile) + jpdata = jp2k.read() + self.assertEqual(jpdata.shape, (400, 700)) + + def test_ETS_JP2_file9(self): + jfile = os.path.join(data_root, 'input/conformance/file9.jp2') + jp2k = Jp2k(jfile) + jpdata = jp2k.read() + self.assertEqual(jpdata.shape, (512, 768, 3)) + + def test_NR_p0_01_dump(self): + jfile = os.path.join(data_root, 'input/conformance/p0_01.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # Segment IDs. + actual = [x.id for x in c.segment] + expected = ['SOC', 'SIZ', 'QCD', 'COD', 'SOT', 'SOD', 'EOC'] + self.assertEqual(actual, expected) + + # SIZ: Image and tile size + # Profile: "1" means profile 0 + self.assertEqual(c.segment[1].Rsiz, 1) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 128) + self.assertEqual(c.segment[1].Ysiz, 128) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (128, 128)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8,)) + # signed + self.assertEqual(c.segment[1]._signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)]) + + # QCD: Quantization default + self.assertEqual(c.segment[2].Sqcd & 0x1f, 0) + self.assertEqual(c.segment[2]._guardBits, 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 + # Selective arithmetic coding bypass + self.assertFalse(c.segment[3].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[3].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[3].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[3].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[3].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[3].SPcod[7] & 0x0020) + self.assertEqual(c.segment[3].SPcod[8], + glymur.core.WAVELET_TRANSFORM_5x3_REVERSIBLE) + + # SOT: start of tile part + self.assertEqual(c.segment[4].Isot, 0) + self.assertEqual(c.segment[4].Psot, 7314) + self.assertEqual(c.segment[4].TPsot, 0) + self.assertEqual(c.segment[4].TNsot, 1) + + def test_NR_p0_02_dump(self): + jfile = os.path.join(data_root, 'input/conformance/p0_02.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 0 + self.assertEqual(c.segment[1].Rsiz, 1) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 127) + self.assertEqual(c.segment[1].Ysiz, 126) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (127, 126)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8,)) + # signed + self.assertEqual(c.segment[1]._signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(2, 1)]) + + # 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 + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertTrue(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertTrue(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertTrue(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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 + # Selective arithmetic coding bypass + self.assertFalse(c.segment[3].SPcoc[3] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[3].SPcoc[3] & 0x02) + # Termination on each coding pass + self.assertTrue(c.segment[3].SPcoc[3] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[3].SPcoc[3] & 0x08) + # Predictable termination + self.assertTrue(c.segment[3].SPcoc[3] & 0x0010) + # Segmentation symbols + self.assertTrue(c.segment[3].SPcoc[3] & 0x0020) + self.assertEqual(c.segment[3].SPcoc[4], + glymur.core.WAVELET_TRANSFORM_5x3_REVERSIBLE) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[4].Sqcd & 0x1f, 0) # none + self.assertEqual(c.segment[4]._guardBits, 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]) + + # COM: comment + # Registration + self.assertEqual(c.segment[5].Rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[5].Ccme.decode('latin-1'), + "Creator: AV-J2K (c) 2000,2001 Algo Vision") + + # One unknown marker + self.assertEqual(c.segment[6].id, '0xff30') + + # SOT: start of tile part + self.assertEqual(c.segment[7].Isot, 0) + self.assertEqual(c.segment[7].Psot, 6047) + self.assertEqual(c.segment[7].TPsot, 0) + self.assertEqual(c.segment[7].TNsot, 1) + + # SOD: start of data + # Just one. + self.assertEqual(c.segment[8].id, 'SOD') + + # SOP, EPH + sop = [x.id for x in c.segment if x.id == 'SOP'] + eph = [x.id for x in c.segment if x.id == 'EPH'] + self.assertEqual(len(sop), 24) + self.assertEqual(len(eph), 24) + + # EOC: end of codestream + self.assertEqual(c.segment[-1].id, 'EOC') + + def test_NR_p0_03_dump(self): + jfile = os.path.join(data_root, 'input/conformance/p0_03.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 0 + self.assertEqual(c.segment[1].Rsiz, 1) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 256) + self.assertEqual(c.segment[1].Ysiz, 256) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (128, 128)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (4,)) + # signed + self.assertEqual(c.segment[1]._signed, (True,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)]) + + # 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 + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_5x3_REVERSIBLE) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[3].Sqcd & 0x1f, 1) # scalar implicit + self.assertEqual(c.segment[3]._guardBits, 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]._guardBits, 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].CEpod, (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,)) + + # COM: comment + # Registration + self.assertEqual(c.segment[7].Rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[7].Ccme.decode('latin-1'), + "Creator: AV-J2K (c) 2000,2001 Algo Vision") + + # COM: comment + # Registration + self.assertEqual(c.segment[8].Rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[8].Ccme.decode('latin-1'), + "Creator: AV-J2K (c) 2000," + + "2001 Algo Vision Technology") + + # COM: comment + # Registration + self.assertEqual(c.segment[9].Rcme, glymur.core.RCME_BINARY) + # Comment value + self.assertEqual(len(c.segment[9].Ccme), 62) + + # 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)) + + # SOT: start of tile part + self.assertEqual(c.segment[11].Isot, 0) + self.assertEqual(c.segment[11].Psot, 4267) + self.assertEqual(c.segment[11].TPsot, 0) + self.assertEqual(c.segment[11].TNsot, 1) + + # RGN: region of interest + self.assertEqual(c.segment[12].Crgn, 0) + self.assertEqual(c.segment[12].Srgn, 0) + self.assertEqual(c.segment[12].SPrgn, 7) + + # SOD: start of data + # Just one. + self.assertEqual(c.segment[13].id, 'SOD') + + def test_NR_p0_04_dump(self): + jfile = os.path.join(data_root, 'input/conformance/p0_04.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 0 + self.assertEqual(c.segment[1].Rsiz, 1) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 640) + self.assertEqual(c.segment[1].Ysiz, 480) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (640, 480)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1), (1, 1), (1, 1)]) + + # 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 + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertTrue(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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]._guardBits, 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]._guardBits, 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]) + + # COM: comment + # Registration + self.assertEqual(c.segment[6].Rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[6].Ccme.decode('latin-1'), + "Creator: AV-J2K (c) 2000,2001 Algo Vision") + + # SOT: start of tile part + self.assertEqual(c.segment[7].Isot, 0) + self.assertEqual(c.segment[7].Psot, 264383) + self.assertEqual(c.segment[7].TPsot, 0) + self.assertEqual(c.segment[7].TNsot, 1) + + # SOD: start of data + # Just one. + self.assertEqual(c.segment[8].id, 'SOD') + + def test_NR_p0_05_dump(self): + jfile = os.path.join(data_root, 'input/conformance/p0_05.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 0 + self.assertEqual(c.segment[1].Rsiz, 1) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 1024) + self.assertEqual(c.segment[1].Ysiz, 1024) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), + (1024, 1024)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8, 8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1), (1, 1), (2, 2), (2, 2)]) + + # 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 + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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 + # Selective arithmetic coding bypass + self.assertFalse(c.segment[3].SPcoc[3] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[3].SPcoc[3] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[3].SPcoc[3] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[3].SPcoc[3] & 0x08) + # Predictable termination + self.assertFalse(c.segment[3].SPcoc[3] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[3].SPcoc[3] & 0x0020) + self.assertEqual(c.segment[3].SPcoc[4], + glymur.core.WAVELET_TRANSFORM_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 + # Selective arithmetic coding bypass + self.assertFalse(c.segment[4].SPcoc[3] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[4].SPcoc[3] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[4].SPcoc[3] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[4].SPcoc[3] & 0x08) + # Predictable termination + self.assertFalse(c.segment[4].SPcoc[3] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[4].SPcoc[3] & 0x0020) + self.assertEqual(c.segment[4].SPcoc[4], + glymur.core.WAVELET_TRANSFORM_5x3_REVERSIBLE) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[5].Sqcd & 0x1f, 2) # scalar expounded + self.assertEqual(c.segment[5]._guardBits, 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]._guardBits, 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]._guardBits, 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) + + # COM: comment + # Registration + self.assertEqual(c.segment[8].Rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[8].Ccme.decode('latin-1'), + "Creator: AV-J2K (c) 2000,2001 Algo Vision") + + # 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,)) + + # SOT: start of tile part + self.assertEqual(c.segment[10].Isot, 0) + self.assertEqual(c.segment[10].Psot, 1310540) + self.assertEqual(c.segment[10].TPsot, 0) + self.assertEqual(c.segment[10].TNsot, 1) + + # SOD: start of data + # Just one. + self.assertEqual(c.segment[11].id, 'SOD') + + def test_NR_p0_06_dump(self): + jfile = os.path.join(data_root, 'input/conformance/p0_06.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 0 + self.assertEqual(c.segment[1].Rsiz, 2) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 513) + self.assertEqual(c.segment[1].Ysiz, 129) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (513, 129)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (12, 12, 12, 12)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1), (2, 1), (1, 2), (2, 2)]) + + # 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 + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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]._guardBits, 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]._guardBits, 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]._guardBits, 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 + # Selective arithmetic coding bypass + self.assertFalse(c.segment[7].SPcoc[3] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[7].SPcoc[3] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[7].SPcoc[3] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[7].SPcoc[3] & 0x08) + # Predictable termination + self.assertFalse(c.segment[7].SPcoc[3] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[7].SPcoc[3] & 0x0020) + self.assertEqual(c.segment[7].SPcoc[4], + glymur.core.WAVELET_TRANSFORM_5x3_REVERSIBLE) + + # RGN: region of interest + self.assertEqual(c.segment[8].Crgn, 0) # component + self.assertEqual(c.segment[8].Srgn, 0) # implicit + self.assertEqual(c.segment[8].SPrgn, 11) + + # SOT: start of tile part + self.assertEqual(c.segment[9].Isot, 0) + self.assertEqual(c.segment[9].Psot, 33582) + self.assertEqual(c.segment[9].TPsot, 0) + self.assertEqual(c.segment[9].TNsot, 1) + + # RGN: region of interest + self.assertEqual(c.segment[10].Crgn, 0) # component + self.assertEqual(c.segment[10].Srgn, 0) # implicit + self.assertEqual(c.segment[10].SPrgn, 9) + + # SOD: start of data + # Just one. + self.assertEqual(c.segment[11].id, 'SOD') + + def test_NR_p0_07_dump(self): + jfile = os.path.join(data_root, 'input/conformance/p0_07.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 0 + self.assertEqual(c.segment[1].Rsiz, 1) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 2048) + self.assertEqual(c.segment[1].Ysiz, 2048) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (128, 128)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (12, 12, 12)) + # signed + self.assertEqual(c.segment[1]._signed, (True, True, True)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1), (1, 1), (1, 1)]) + + # 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 + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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]) + + # COM: comment + # Registration + self.assertEqual(c.segment[4].Rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[4].Ccme.decode('latin-1'), + "Kakadu-3.0.7") + + # SOT: start of tile part + self.assertEqual(c.segment[5].Isot, 0) + self.assertEqual(c.segment[5].Psot, 9951) + self.assertEqual(c.segment[5].TPsot, 0) + self.assertEqual(c.segment[5].TNsot, 0) # unknown + + # 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].CEpod, (3,)) + self.assertEqual(c.segment[6].Ppod, (glymur.core.LRCP,)) + + # PLT: packet length, tile part + self.assertEqual(c.segment[7].Zplt, 0) + #self.assertEqual(c.segment[7].iplt), 99) + + # SOD: start of data + self.assertEqual(c.segment[8].id, 'SOD') + + def test_NR_p0_08_dump(self): + jfile = os.path.join(data_root, 'input/conformance/p0_08.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 0 + self.assertEqual(c.segment[1].Rsiz, 1) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 513) + self.assertEqual(c.segment[1].Ysiz, 3072) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (513, 3072)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (12, 12, 12)) + # signed + self.assertEqual(c.segment[1]._signed, (True, True, True)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1), (1, 1), (1, 1)]) + + # 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 + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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 + # Selective arithmetic coding bypass + self.assertFalse(c.segment[3].SPcoc[3] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[3].SPcoc[3] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[3].SPcoc[3] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[3].SPcoc[3] & 0x08) + # Predictable termination + self.assertFalse(c.segment[3].SPcoc[3] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[3].SPcoc[3] & 0x0020) + self.assertEqual(c.segment[3].SPcoc[4], + glymur.core.WAVELET_TRANSFORM_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 + # Selective arithmetic coding bypass + self.assertFalse(c.segment[4].SPcoc[3] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[4].SPcoc[3] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[4].SPcoc[3] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[4].SPcoc[3] & 0x08) + # Predictable termination + self.assertFalse(c.segment[4].SPcoc[3] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[4].SPcoc[3] & 0x0020) + self.assertEqual(c.segment[4].SPcoc[4], + glymur.core.WAVELET_TRANSFORM_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 + # Selective arithmetic coding bypass + self.assertFalse(c.segment[5].SPcoc[3] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[5].SPcoc[3] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[5].SPcoc[3] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[5].SPcoc[3] & 0x08) + # Predictable termination + self.assertFalse(c.segment[5].SPcoc[3] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[5].SPcoc[3] & 0x0020) + self.assertEqual(c.segment[5].SPcoc[4], + glymur.core.WAVELET_TRANSFORM_5x3_REVERSIBLE) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[6].Sqcd & 0x1f, 0) # none + self.assertEqual(c.segment[6]._guardBits, 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]._guardBits, 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]._guardBits, 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]) + + # COM: comment + # Registration + self.assertEqual(c.segment[9].Rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[9].Ccme.decode('latin-1'), + "Kakadu-3.0.7") + + # SOT: start of tile part + self.assertEqual(c.segment[10].Isot, 0) + self.assertEqual(c.segment[10].Psot, 3820593) + self.assertEqual(c.segment[10].TPsot, 0) + self.assertEqual(c.segment[10].TNsot, 1) # unknown + + def test_NR_p0_09_dump(self): + jfile = os.path.join(data_root, 'input/conformance/p0_09.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "0" means profile 2, or full capabilities + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 17) + self.assertEqual(c.segment[1].Ysiz, 37) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (17, 37)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8,)) + # signed + self.assertEqual(c.segment[1]._signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)]) + + # 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 + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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]) + + # COM: comment + # Registration + self.assertEqual(c.segment[4].Rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[4].Ccme.decode('latin-1'), + "Kakadu-3.0.7") + + # SOT: start of tile part + self.assertEqual(c.segment[5].Isot, 0) + self.assertEqual(c.segment[5].Psot, 478) + self.assertEqual(c.segment[5].TPsot, 0) + self.assertEqual(c.segment[5].TNsot, 1) # unknown + + # SOD: start of data + # Just one. + self.assertEqual(c.segment[6].id, 'SOD') + + # EOC: end of codestream + self.assertEqual(c.segment[7].id, 'EOC') + + def test_NR_p0_10_dump(self): + jfile = os.path.join(data_root, 'input/conformance/p0_10.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 0 + self.assertEqual(c.segment[1].Rsiz, 1) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 256) + self.assertEqual(c.segment[1].Ysiz, 256) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (128, 128)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(4, 4), (4, 4), (4, 4)]) + + # 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 + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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]) + + # SOT: start of tile part + self.assertEqual(c.segment[4].Isot, 0) + self.assertEqual(c.segment[4].Psot, 2453) + self.assertEqual(c.segment[4].TPsot, 0) + self.assertEqual(c.segment[4].TNsot, 0) + + # SOD: start of data + self.assertEqual(c.segment[5].id, 'SOD') + + # SOT: start of tile part + self.assertEqual(c.segment[6].Isot, 1) + self.assertEqual(c.segment[6].Psot, 2403) + self.assertEqual(c.segment[6].TPsot, 0) + self.assertEqual(c.segment[6].TNsot, 0) + + # SOD: start of data + self.assertEqual(c.segment[7].id, 'SOD') + + # SOT: start of tile part + self.assertEqual(c.segment[8].Isot, 2) + self.assertEqual(c.segment[8].Psot, 2420) + self.assertEqual(c.segment[8].TPsot, 0) + self.assertEqual(c.segment[8].TNsot, 0) + + # SOD: start of data + self.assertEqual(c.segment[9].id, 'SOD') + + # SOT: start of tile part + self.assertEqual(c.segment[10].Isot, 3) + self.assertEqual(c.segment[10].Psot, 2472) + self.assertEqual(c.segment[10].TPsot, 0) + self.assertEqual(c.segment[10].TNsot, 0) + + # SOD: start of data + self.assertEqual(c.segment[11].id, 'SOD') + + # SOT: start of tile part + self.assertEqual(c.segment[12].Isot, 0) + self.assertEqual(c.segment[12].Psot, 1043) + self.assertEqual(c.segment[12].TPsot, 1) + self.assertEqual(c.segment[12].TNsot, 2) + + # SOD: start of data + self.assertEqual(c.segment[13].id, 'SOD') + + # SOT: start of tile part + self.assertEqual(c.segment[14].Isot, 1) + self.assertEqual(c.segment[14].Psot, 1101) + self.assertEqual(c.segment[14].TPsot, 1) + self.assertEqual(c.segment[14].TNsot, 2) + + # SOD: start of data + self.assertEqual(c.segment[15].id, 'SOD') + + # SOT: start of tile part + self.assertEqual(c.segment[16].Isot, 3) + self.assertEqual(c.segment[16].Psot, 1054) + self.assertEqual(c.segment[16].TPsot, 1) + self.assertEqual(c.segment[16].TNsot, 2) + + # SOD: start of data + self.assertEqual(c.segment[17].id, 'SOD') + + # SOT: start of tile part + self.assertEqual(c.segment[18].Isot, 2) + self.assertEqual(c.segment[18].Psot, 14) + self.assertEqual(c.segment[18].TPsot, 1) + self.assertEqual(c.segment[18].TNsot, 0) + + # SOD: start of data + self.assertEqual(c.segment[19].id, 'SOD') + + # SOT: start of tile part + self.assertEqual(c.segment[20].Isot, 2) + self.assertEqual(c.segment[20].Psot, 1089) + self.assertEqual(c.segment[20].TPsot, 2) + self.assertEqual(c.segment[20].TNsot, 0) + + # SOD: start of data + self.assertEqual(c.segment[21].id, 'SOD') + + # EOC: end of codestream + self.assertEqual(c.segment[22].id, 'EOC') + + def test_NR_p0_11_dump(self): + jfile = os.path.join(data_root, 'input/conformance/p0_11.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 0 + self.assertEqual(c.segment[1].Rsiz, 1) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 128) + self.assertEqual(c.segment[1].Ysiz, 1) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (128, 128)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8,)) + # signed + self.assertEqual(c.segment[1]._signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)]) + + # 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 + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertTrue(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 3) + self.assertEqual(c.segment[3]._mantissa, [0]) + self.assertEqual(c.segment[3]._exponent, [8]) + + # COM: comment + # Registration + self.assertEqual(c.segment[4].Rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[4].Ccme.decode('latin-1'), + "Creator: AV-J2K (c) 2000,2001 Algo Vision") + + # SOT: start of tile part + self.assertEqual(c.segment[5].Isot, 0) + self.assertEqual(c.segment[5].Psot, 118) + self.assertEqual(c.segment[5].TPsot, 0) + self.assertEqual(c.segment[5].TNsot, 1) + + # SOD: start of data + self.assertEqual(c.segment[6].id, 'SOD') + + # SOP, EPH + sop = [x.id for x in c.segment if x.id == 'SOP'] + eph = [x.id for x in c.segment if x.id == 'EPH'] + self.assertEqual(len(sop), 0) + self.assertEqual(len(eph), 1) + + # EOC: end of codestream + self.assertEqual(c.segment[-1].id, 'EOC') + + def test_NR_p0_12_dump(self): + jfile = os.path.join(data_root, 'input/conformance/p0_12.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 0 + self.assertEqual(c.segment[1].Rsiz, 1) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 3) + self.assertEqual(c.segment[1].Ysiz, 5) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (3, 5)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8,)) + # signed + self.assertEqual(c.segment[1]._signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)]) + + # 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 + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertTrue(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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]) + + # COM: comment + # Registration + self.assertEqual(c.segment[4].Rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[4].Ccme.decode('latin-1'), + "Creator: AV-J2K (c) 2000,2001 Algo Vision") + + # SOT: start of tile part + self.assertEqual(c.segment[5].Isot, 0) + self.assertEqual(c.segment[5].Psot, 162) + self.assertEqual(c.segment[5].TPsot, 0) + self.assertEqual(c.segment[5].TNsot, 1) + + # SOD: start of data + self.assertEqual(c.segment[6].id, 'SOD') + + # SOP, EPH + sop = [x.id for x in c.segment if x.id == 'SOP'] + eph = [x.id for x in c.segment if x.id == 'EPH'] + self.assertEqual(len(sop), 4) + self.assertEqual(len(eph), 0) + + # EOC: end of codestream + self.assertEqual(c.segment[-1].id, 'EOC') + + def test_NR_p0_13_dump(self): + jfile = os.path.join(data_root, 'input/conformance/p0_13.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 0 + self.assertEqual(c.segment[1].Rsiz, 1) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 1) + self.assertEqual(c.segment[1].Ysiz, 1) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (1, 1)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, tuple([8] * 257)) + # signed + self.assertEqual(c.segment[1]._signed, tuple([False] * 257)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)] * 257) + + # 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)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertTrue(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[3].SPcoc[3] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[3].SPcoc[3] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[3].SPcoc[3] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[3].SPcoc[3] & 0x08) + # Predictable termination + self.assertFalse(c.segment[3].SPcoc[3] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[3].SPcoc[3] & 0x0020) + self.assertEqual(c.segment[3].SPcoc[4], + glymur.core.WAVELET_TRANSFORM_5x3_REVERSIBLE) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[4].Sqcd & 0x1f, 0) # none + self.assertEqual(c.segment[4]._guardBits, 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]._guardBits, 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]._guardBits, 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]) + + # RGN: region of interest + self.assertEqual(c.segment[7].Crgn, 3) + self.assertEqual(c.segment[7].Srgn, 0) + self.assertEqual(c.segment[7].SPrgn, 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].CEpod, (128, 257)) + self.assertEqual(c.segment[8].Ppod, + (glymur.core.RLCP, glymur.core.CPRL)) + + # COM: comment + # Registration + self.assertEqual(c.segment[9].Rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[9].Ccme.decode('latin-1'), + "Creator: AV-J2K (c) 2000,2001 Algo Vision") + + # SOT: start of tile part + self.assertEqual(c.segment[10].Isot, 0) + self.assertEqual(c.segment[10].Psot, 1537) + self.assertEqual(c.segment[10].TPsot, 0) + self.assertEqual(c.segment[10].TNsot, 1) + + # SOD: start of data + self.assertEqual(c.segment[11].id, 'SOD') + + # EOC: end of codestream + self.assertEqual(c.segment[12].id, 'EOC') + + def test_NR_p0_14_dump(self): + jfile = os.path.join(data_root, 'input/conformance/p0_14.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "0" means profile 2 + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 49) + self.assertEqual(c.segment[1].Ysiz, 49) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (49, 49)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)] * 3) + + # 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)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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]) + + # COM: comment + # Registration + self.assertEqual(c.segment[4].Rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[4].Ccme.decode('latin-1'), + "Kakadu-3.0.7") + + # SOT: start of tile part + self.assertEqual(c.segment[5].Isot, 0) + self.assertEqual(c.segment[5].Psot, 1528) + self.assertEqual(c.segment[5].TPsot, 0) + self.assertEqual(c.segment[5].TNsot, 1) + + # SOD: start of data + self.assertEqual(c.segment[6].id, 'SOD') + + # EOC: end of codestream + self.assertEqual(c.segment[7].id, 'EOC') + + def test_NR_p0_15_dump(self): + jfile = os.path.join(data_root, 'input/conformance/p0_15.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 0 + self.assertEqual(c.segment[1].Rsiz, 1) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 256) + self.assertEqual(c.segment[1].Ysiz, 256) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (128, 128)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (4,)) + # signed + self.assertEqual(c.segment[1]._signed, (True,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)]) + + # 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)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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]._guardBits, 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].CEpod, (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,)) + + # COM: comment + # Registration + self.assertEqual(c.segment[7].Rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[7].Ccme.decode('latin-1'), + "Creator: AV-J2K (c) 2000,2001 Algo Vision") + + # COM: comment + # Registration + self.assertEqual(c.segment[8].Rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[8].Ccme.decode('latin-1'), + "Creator: AV-J2K (c) 2000," + + "2001 Algo Vision Technology") + + # COM: comment + # Registration + self.assertEqual(c.segment[9].Rcme, glymur.core.RCME_BINARY) + # Comment value + self.assertEqual(len(c.segment[9].Ccme), 62) + + # 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)) + + # SOT: start of tile part + self.assertEqual(c.segment[11].Isot, 0) + self.assertEqual(c.segment[11].Psot, 4267) + self.assertEqual(c.segment[11].TPsot, 0) + self.assertEqual(c.segment[11].TNsot, 1) + + # RGN: region of interest + self.assertEqual(c.segment[12].Crgn, 0) + self.assertEqual(c.segment[12].Srgn, 0) + self.assertEqual(c.segment[12].SPrgn, 7) + + # SOD: start of data + self.assertEqual(c.segment[13].id, 'SOD') + + # 16 SOP markers would be here if we were looking for them + + # SOT: start of tile part + self.assertEqual(c.segment[31].Isot, 1) + self.assertEqual(c.segment[31].Psot, 2117) + self.assertEqual(c.segment[31].TPsot, 0) + self.assertEqual(c.segment[31].TNsot, 1) + + # SOD: start of data + self.assertEqual(c.segment[32].id, 'SOD') + + # 16 SOP markers would be here if we were looking for them + + # SOT: start of tile part + self.assertEqual(c.segment[49].Isot, 2) + self.assertEqual(c.segment[49].Psot, 4080) + self.assertEqual(c.segment[49].TPsot, 0) + self.assertEqual(c.segment[49].TNsot, 1) + + # SOD: start of data + self.assertEqual(c.segment[50].id, 'SOD') + + # 16 SOP markers would be here if we were looking for them + + # SOT: start of tile part + self.assertEqual(c.segment[67].Isot, 3) + self.assertEqual(c.segment[67].Psot, 2081) + self.assertEqual(c.segment[67].TPsot, 0) + self.assertEqual(c.segment[67].TNsot, 1) + + # SOD: start of data + self.assertEqual(c.segment[68].id, 'SOD') + + # 16 SOP markers would be here if we were looking for them + + # EOC: end of codestream + self.assertEqual(c.segment[85].id, 'EOC') + + def test_NR_p0_16_dump(self): + jfile = os.path.join(data_root, 'input/conformance/p0_16.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "0" means profile 2 + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 128) + self.assertEqual(c.segment[1].Ysiz, 128) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (128, 128)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8,)) + # signed + self.assertEqual(c.segment[1]._signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)]) + + # 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)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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]) + + # SOT: start of tile part + self.assertEqual(c.segment[4].Isot, 0) + self.assertEqual(c.segment[4].Psot, 7331) + self.assertEqual(c.segment[4].TPsot, 0) + self.assertEqual(c.segment[4].TNsot, 1) + + # SOD: start of data + self.assertEqual(c.segment[5].id, 'SOD') + + # EOC: end of codestream + self.assertEqual(c.segment[6].id, 'EOC') + + def test_NR_p1_01_dump(self): + jfile = os.path.join(data_root, 'input/conformance/p1_01.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 1 + self.assertEqual(c.segment[1].Rsiz, 2) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 127) + self.assertEqual(c.segment[1].Ysiz, 227) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (5, 128)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (127, 126)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (1, 101)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8,)) + # signed + self.assertEqual(c.segment[1]._signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(2, 1)]) + + # 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)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertTrue(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertTrue(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertTrue(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[3].SPcoc[3] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[3].SPcoc[3] & 0x02) + # Termination on each coding pass + self.assertTrue(c.segment[3].SPcoc[3] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[3].SPcoc[3] & 0x08) + # Predictable termination + self.assertTrue(c.segment[3].SPcoc[3] & 0x0010) + # Segmentation symbols + self.assertTrue(c.segment[3].SPcoc[3] & 0x0020) + self.assertEqual(c.segment[3].SPcoc[4], + glymur.core.WAVELET_TRANSFORM_5x3_REVERSIBLE) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[4].Sqcd & 0x1f, 0) # none + self.assertEqual(c.segment[4]._guardBits, 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]) + + # COM: comment + # Registration + self.assertEqual(c.segment[5].Rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[5].Ccme.decode('latin-1'), + "Creator: AV-J2K (c) 2000,2001 Algo Vision") + + # SOT: start of tile part + self.assertEqual(c.segment[6].Isot, 0) + self.assertEqual(c.segment[6].Psot, 4627) + self.assertEqual(c.segment[6].TPsot, 0) + self.assertEqual(c.segment[6].TNsot, 1) + + # SOD: start of data + self.assertEqual(c.segment[7].id, 'SOD') + + # SOP, EPH + sop = [x.id for x in c.segment if x.id == 'SOP'] + eph = [x.id for x in c.segment if x.id == 'EPH'] + self.assertEqual(len(sop), 20) + self.assertEqual(len(eph), 20) + + # EOC: end of codestream + self.assertEqual(c.segment[-1].id, 'EOC') + + def test_NR_p1_02_dump(self): + jfile = os.path.join(data_root, 'input/conformance/p1_02.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 1 + self.assertEqual(c.segment[1].Rsiz, 2) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 640) + self.assertEqual(c.segment[1].Ysiz, 480) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (640, 480)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, tuple([8] * 3)) + # signed + self.assertEqual(c.segment[1]._signed, tuple([False] * 3)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.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, 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 + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertTrue(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertTrue(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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]._guardBits, 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]._guardBits, 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]) + + # COM: comment + # Registration + self.assertEqual(c.segment[6].Rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[6].Ccme.decode('latin-1'), + "Creator: AV-J2K (c) 2000,2001 Algo Vision") + + # SOT: start of tile part + self.assertEqual(c.segment[7].Isot, 0) + self.assertEqual(c.segment[7].Psot, 262838) + self.assertEqual(c.segment[7].TPsot, 0) + self.assertEqual(c.segment[7].TNsot, 1) + + # PPT: packed packet headers, tile-part header + self.assertEqual(c.segment[8].id, 'PPT') + self.assertEqual(c.segment[8].Zppt, 0) + + # SOD: start of data + self.assertEqual(c.segment[9].id, 'SOD') + + # EOC: end of codestream + self.assertEqual(c.segment[10].id, 'EOC') + + def test_NR_p1_03_dump(self): + jfile = os.path.join(data_root, 'input/conformance/p1_03.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 1 + self.assertEqual(c.segment[1].Rsiz, 2) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 1024) + self.assertEqual(c.segment[1].Ysiz, 1024) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), + (1024, 1024)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, tuple([8] * 4)) + # signed + self.assertEqual(c.segment[1]._signed, tuple([False] * 4)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1), (1, 1), (2, 2), (2, 2)]) + + # 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)) + # Selective arithmetic coding bypass + self.assertTrue(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertTrue(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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)) + # Selective arithmetic coding bypass + self.assertTrue(c.segment[3].SPcoc[3] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[3].SPcoc[3] & 0x02) + # Termination on each coding pass + self.assertTrue(c.segment[3].SPcoc[3] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[3].SPcoc[3] & 0x08) + # Predictable termination + self.assertFalse(c.segment[3].SPcoc[3] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[3].SPcoc[3] & 0x0020) + self.assertEqual(c.segment[3].SPcoc[4], + glymur.core.WAVELET_TRANSFORM_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)) + # Selective arithmetic coding bypass + self.assertTrue(c.segment[4].SPcoc[3] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[4].SPcoc[3] & 0x02) + # Termination on each coding pass + self.assertTrue(c.segment[4].SPcoc[3] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[4].SPcoc[3] & 0x08) + # Predictable termination + self.assertFalse(c.segment[4].SPcoc[3] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[4].SPcoc[3] & 0x0020) + self.assertEqual(c.segment[4].SPcoc[4], + glymur.core.WAVELET_TRANSFORM_5x3_REVERSIBLE) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[5].Sqcd & 0x1f, 2) # expounded + self.assertEqual(c.segment[5]._guardBits, 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]._guardBits, 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]._guardBits, 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]) + + # COM: comment + # Registration + self.assertEqual(c.segment[8].Rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[8].Ccme.decode('latin-1'), + "Creator: AV-J2K (c) 2000,2001 Algo Vision") + + # PPM: packed packet headers, main header + self.assertEqual(c.segment[9].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,)) + + # SOT: start of tile part + self.assertEqual(c.segment[11].Isot, 0) + self.assertEqual(c.segment[11].Psot, 1366780) + self.assertEqual(c.segment[11].TPsot, 0) + self.assertEqual(c.segment[11].TNsot, 1) + + # SOD: start of data + self.assertEqual(c.segment[12].id, 'SOD') + + # EOC: end of codestream + self.assertEqual(c.segment[13].id, 'EOC') + + def test_NR_p1_04_dump(self): + jfile = os.path.join(data_root, 'input/conformance/p1_04.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 1 + self.assertEqual(c.segment[1].Rsiz, 2) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 1024) + self.assertEqual(c.segment[1].Ysiz, 1024) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (128, 128)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (12,)) + # signed + self.assertEqual(c.segment[1]._signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)]) + + # 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)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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)) + + # COM: comment + # Registration + self.assertEqual(c.segment[5].Rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[5].Ccme.decode('latin-1'), + "Created by Aware, Inc.") + + # SOT: start of tile part + self.assertEqual(c.segment[6].Isot, 0) + self.assertEqual(c.segment[6].Psot, 350) + self.assertEqual(c.segment[6].TPsot, 0) + self.assertEqual(c.segment[6].TNsot, 1) + + # SOD: start of data + self.assertEqual(c.segment[7].id, 'SOD') + + # SOT: start of tile part + self.assertEqual(c.segment[8].Isot, 1) + self.assertEqual(c.segment[8].Psot, 356) + self.assertEqual(c.segment[8].TPsot, 0) + self.assertEqual(c.segment[8].TNsot, 1) + + # QCD: Quantization default + # quantization type + self.assertEqual(c.segment[9].Sqcd & 0x1f, 2) # expounded + self.assertEqual(c.segment[9]._guardBits, 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].id, 'SOD') + + # SOT: start of tile part + self.assertEqual(c.segment[11].Isot, 2) + self.assertEqual(c.segment[11].Psot, 402) + self.assertEqual(c.segment[11].TPsot, 0) + self.assertEqual(c.segment[11].TNsot, 1) + + # and so on + + # There should be 64 SOD, SOT, QCD segments. + ids = [x.id for x in c.segment if x.id == 'SOT'] + self.assertEqual(len(ids), 64) + ids = [x.id for x in c.segment if x.id == 'SOD'] + self.assertEqual(len(ids), 64) + ids = [x.id for x in c.segment if x.id == 'QCD'] + self.assertEqual(len(ids), 64) + + # Tiles should be in order, right? + tiles = [x.Isot for x in c.segment if x.id == 'SOT'] + self.assertEqual(tiles, list(range(64))) + + # EOC: end of codestream + self.assertEqual(c.segment[-1].id, 'EOC') + + def test_NR_p1_05_dump(self): + jfile = os.path.join(data_root, 'input/conformance/p1_05.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 1 + self.assertEqual(c.segment[1].Rsiz, 2) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 529) + self.assertEqual(c.segment[1].Ysiz, 524) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (17, 12)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (37, 37)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (8, 2)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)] * 3) + + # 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 + # Selective arithmetic coding bypass + self.assertTrue(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertTrue(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertTrue(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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]) + + # COM: comment + # Registration + self.assertEqual(c.segment[4].Rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[4].Ccme.decode('latin-1'), + "Creator: AV-J2K (c) 2000,2001 Algo Vision") + + # 225 consecutive PPM segments. + zppm = [x.Zppm for x in c.segment[5:230]] + self.assertEqual(zppm, list(range(225))) + + # SOT: start of tile part + self.assertEqual(c.segment[230].Isot, 0) + self.assertEqual(c.segment[230].Psot, 580) + self.assertEqual(c.segment[230].TPsot, 0) + self.assertEqual(c.segment[230].TNsot, 1) + + # 225 total SOT segments + Isot = [x.Isot for x in c.segment if x.id == 'SOT'] + self.assertEqual(Isot, list(range(225))) + + # scads of SOP, EPH segments + sop = [x.id for x in c.segment if x.id == 'SOP'] + eph = [x.id for x in c.segment if x.id == 'EPH'] + self.assertEqual(len(sop), 26472) + self.assertEqual(len(eph), 0) + + # EOC: end of codestream + self.assertEqual(c.segment[-1].id, 'EOC') + + def test_NR_p1_06_dump(self): + jfile = os.path.join(data_root, 'input/conformance/p1_06.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 1 + self.assertEqual(c.segment[1].Rsiz, 2) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 12) + self.assertEqual(c.segment[1].Ysiz, 12) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (3, 3)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)] * 3) + + # 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)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertTrue(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertTrue(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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]) + + # COM: comment + # Registration + self.assertEqual(c.segment[4].Rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[4].Ccme.decode('latin-1'), + "Creator: AV-J2K (c) 2000,2001 Algo Vision") + + # SOT: start of tile part + self.assertEqual(c.segment[5].Isot, 0) + self.assertEqual(c.segment[5].Psot, 349) + self.assertEqual(c.segment[5].TPsot, 0) + self.assertEqual(c.segment[5].TNsot, 1) + + # PPT: packed packet headers, tile-part header + self.assertEqual(c.segment[6].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.id == 'SOD'] + self.assertEqual(len(sods), 16) + + # 16 PPT segments + ppts = [x for x in c.segment if x.id == 'PPT'] + self.assertEqual(len(ppts), 16) + + # 16 SOT segments + Isots = [x.Isot for x in c.segment if x.id == 'SOT'] + self.assertEqual(Isots, list(range(16))) + + # EOC: end of codestream + self.assertEqual(c.segment[-1].id, 'EOC') + + def test_NR_p1_07_dump(self): + jfile = os.path.join(data_root, 'input/conformance/p1_07.j2k') + c = Jp2k(jfile).get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "1" means profile 1 + self.assertEqual(c.segment[1].Rsiz, 2) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 12) + self.assertEqual(c.segment[1].Ysiz, 12) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (4, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (12, 12)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (4, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(4, 1), (1, 1)]) + + # 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)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[3].SPcoc[3] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[3].SPcoc[3] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[3].SPcoc[3] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[3].SPcoc[3] & 0x08) + # Predictable termination + self.assertFalse(c.segment[3].SPcoc[3] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[3].SPcoc[3] & 0x0020) + self.assertEqual(c.segment[3].SPcoc[4], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 2) + self.assertEqual(c.segment[4]._mantissa, [0] * 4) + self.assertEqual(c.segment[4]._exponent, [8, 9, 9, 10]) + + # COM: comment + # Registration + self.assertEqual(c.segment[5].Rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[5].Ccme.decode('latin-1'), + "Creator: AV-J2K (c) 2000,2001 Algo Vision") + + # SOT: start of tile part + self.assertEqual(c.segment[6].Isot, 0) + self.assertEqual(c.segment[6].Psot, 434) + self.assertEqual(c.segment[6].TPsot, 0) + self.assertEqual(c.segment[6].TNsot, 1) + + # scads of SOP, EPH segments + + # EOC: end of codestream + self.assertEqual(c.segment[-1].id, 'EOC') + + def test_NR_file1_dump(self): + jfile = os.path.join(data_root, 'input/conformance/file1.jp2') + jp2 = Jp2k(jfile) + + ids = [box.id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'xml ', 'jp2h', 'xml ', + 'jp2c']) + + ids = [box.id for box in jp2.box[3].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') + + # XML box + tags = [x.tag for x in jp2.box[2].xml] + self.assertEqual(tags, + ['{http://www.jpeg.org/jpx/1.0/xml}' + + 'GENERAL_CREATION_INFO']) + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[3].box[0].height, 512) + self.assertEqual(jp2.box[3].box[0].width, 768) + self.assertEqual(jp2.box[3].box[0].num_components, 3) + self.assertEqual(jp2.box[3].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[3].box[0].signed, False) + self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[3].box[0].cspace_unknown, False) + self.assertEqual(jp2.box[3].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[3].box[1].method, 1) + self.assertEqual(jp2.box[3].box[1].precedence, 0) + self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact ?? + self.assertEqual(jp2.box[3].box[1].color_space, glymur.core.SRGB) + + # XML box + tags = [x.tag for x in jp2.box[4].xml] + 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 = os.path.join(data_root, 'input/conformance/file2.jp2') + jp2 = Jp2k(jfile) + + ids = [box.id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr', 'cdef']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 640) + self.assertEqual(jp2.box[2].box[0].width, 480) + self.assertEqual(jp2.box[2].box[0].num_components, 3) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].cspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[1].method, 1) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact?? + self.assertEqual(jp2.box[2].box[1].color_space, glymur.core.YCC) + + # Jp2 Header + # Channel Definition + self.assertEqual(jp2.box[2].box[2].component_number, (0, 1, 2)) + self.assertEqual(jp2.box[2].box[2].component_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 = os.path.join(data_root, 'input/conformance/file3.jp2') + jp2 = Jp2k(jfile) + + ids = [box.id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 640) + self.assertEqual(jp2.box[2].box[0].width, 480) + self.assertEqual(jp2.box[2].box[0].num_components, 3) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].cspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[1].method, 1) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact + self.assertEqual(jp2.box[2].box[1].color_space, glymur.core.YCC) + + # 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 sRGB-grey colourspace. + jfile = os.path.join(data_root, 'input/conformance/file4.jp2') + jp2 = Jp2k(jfile) + + ids = [box.id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 512) + self.assertEqual(jp2.box[2].box[0].width, 768) + self.assertEqual(jp2.box[2].box[0].num_components, 1) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].cspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[1].method, 1) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact? + self.assertEqual(jp2.box[2].box[1].color_space, glymur.core.GREYSCALE) + + def test_NR_file5_dump(self): + # Three 8-bit components in the ROMM-RGB 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 ROMM-RGB + # colourspace. + jfile = os.path.join(data_root, 'input/conformance/file5.jp2') + jp2 = Jp2k(jfile) + + ids = [box.id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) + + ids = [box.id for box in jp2.box[3].box] + self.assertEqual(ids, ['ihdr', 'colr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jpx ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') + self.assertEqual(jp2.box[1].compatibility_list[2], 'jpx ') + self.assertEqual(jp2.box[1].compatibility_list[3], 'jpxb') + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[3].box[0].height, 512) + self.assertEqual(jp2.box[3].box[0].width, 768) + self.assertEqual(jp2.box[3].box[0].num_components, 3) + self.assertEqual(jp2.box[3].box[0].signed, False) + self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[3].box[0].cspace_unknown, False) + self.assertEqual(jp2.box[3].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[3].box[1].method, 2) # enumerated + self.assertEqual(jp2.box[3].box[1].precedence, 0) + self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact + self.assertEqual(len(jp2.box[3].box[1].icc_profile), 546) + self.assertIsNone(jp2.box[3].box[1].color_space) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[3].box[2].method, 1) # enumerated + self.assertEqual(jp2.box[3].box[2].precedence, 1) + self.assertEqual(jp2.box[3].box[2].approximation, 1) # JPX exact + self.assertIsNone(jp2.box[3].box[2].icc_profile) + self.assertEqual(jp2.box[3].box[2].color_space, + glymur.core.ROMM_RGB) + + def test_NR_file6_dump(self): + jfile = os.path.join(data_root, 'input/conformance/file6.jp2') + jp2 = Jp2k(jfile) + + ids = [box.id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 512) + self.assertEqual(jp2.box[2].box[0].width, 768) + self.assertEqual(jp2.box[2].box[0].num_components, 1) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 12) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].cspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[1].method, 1) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact + self.assertIsNone(jp2.box[2].box[1].icc_profile) + self.assertEqual(jp2.box[2].box[1].color_space, + glymur.core.GREYSCALE) + + 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 = os.path.join(data_root, 'input/conformance/file7.jp2') + jp2 = Jp2k(jfile) + + ids = [box.id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) + + ids = [box.id for box in jp2.box[3].box] + self.assertEqual(ids, ['ihdr', 'colr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jpx ') + self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') + self.assertEqual(jp2.box[1].compatibility_list[2], 'jpx ') + self.assertEqual(jp2.box[1].compatibility_list[3], 'jpxb') + self.assertEqual(jp2.box[1].minor_version, 0) + + # Reader requirements talk. + self.assertTrue(glymur.core.RREQ_E_SRGB_ENUMERATED_COLORSPACE + in jp2.box[2].standard_flag) + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[3].box[0].height, 640) + self.assertEqual(jp2.box[3].box[0].width, 480) + self.assertEqual(jp2.box[3].box[0].num_components, 3) + self.assertEqual(jp2.box[3].box[0].bits_per_component, 16) + self.assertEqual(jp2.box[3].box[0].signed, False) + self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[3].box[0].cspace_unknown, False) + self.assertEqual(jp2.box[3].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[3].box[1].method, 2) + self.assertEqual(jp2.box[3].box[1].precedence, 0) + self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact + self.assertEqual(len(jp2.box[3].box[1].icc_profile), 13332) + self.assertIsNone(jp2.box[3].box[1].color_space) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[3].box[2].method, 1) # enumerated cspace + self.assertEqual(jp2.box[3].box[2].precedence, 1) + self.assertEqual(jp2.box[3].box[2].approximation, 1) # JPX exact + self.assertIsNone(jp2.box[3].box[2].icc_profile) + self.assertEqual(jp2.box[3].box[2].color_space, + glymur.core.E_SRGB) + + 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 = os.path.join(data_root, 'input/conformance/file8.jp2') + jp2 = Jp2k(jfile) + + ids = [box.id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'xml ', 'jp2c', + 'xml ']) + + ids = [box.id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 400) + self.assertEqual(jp2.box[2].box[0].width, 700) + self.assertEqual(jp2.box[2].box[0].num_components, 1) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].cspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[1].method, 2) # enumerated + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact + self.assertEqual(len(jp2.box[2].box[1].icc_profile), 414) + self.assertIsNone(jp2.box[2].box[1].color_space) + + # XML box + tags = [x.tag for x in jp2.box[3].xml] + 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] + 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 = os.path.join(data_root, 'input/conformance/file9.jp2') + jp2 = Jp2k(jfile) + + ids = [box.id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'pclr', 'cmap', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].compatibility_list[1], 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 512) + self.assertEqual(jp2.box[2].box[0].width, 768) + self.assertEqual(jp2.box[2].box[0].num_components, 1) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].cspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Palette box. + self.assertEqual(len(jp2.box[2].box[1].palette), 3) + self.assertEqual(len(jp2.box[2].box[1].palette[0]), 256) + self.assertEqual(len(jp2.box[2].box[1].palette[1]), 256) + self.assertEqual(len(jp2.box[2].box[1].palette[2]), 256) + 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[1][0], 0) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[2][0], 0) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[0][128], 73) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[1][128], 92) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[2][128], 53) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[0][-1], 245) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[1][-1], 245) + np.testing.assert_array_equal(jp2.box[2].box[1].palette[2][-1], 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)) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[3].method, 1) + self.assertEqual(jp2.box[2].box[3].precedence, 0) + self.assertEqual(jp2.box[2].box[3].approximation, 1) # JPX exact + self.assertIsNone(jp2.box[2].box[3].icc_profile) + self.assertEqual(jp2.box[2].box[3].color_space, glymur.core.SRGB) + + def test_NR_00042_j2k_dump(self): + # Profile 3. + jfile = os.path.join(data_root, 'input/nonregression/_00042.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "3" means profile 3 + self.assertEqual(c.segment[1].Rsiz, 3) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 1920) + self.assertEqual(c.segment[1].Ysiz, 1080) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), + (1920, 1080)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (12, 12, 12)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.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.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)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[4].SPcoc[3] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[4].SPcoc[3] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[4].SPcoc[3] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[4].SPcoc[3] & 0x08) + # Predictable termination + self.assertFalse(c.segment[4].SPcoc[3] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[4].SPcoc[3] & 0x0020) + self.assertEqual(c.segment[4].SPcoc[4], + glymur.core.WAVELET_TRANSFORM_9x7_IRREVERSIBLE) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[5].Cqcc, 1) + self.assertEqual(c.segment[5]._guardBits, 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)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[6].SPcoc[3] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[6].SPcoc[3] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[6].SPcoc[3] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[6].SPcoc[3] & 0x08) + # Predictable termination + self.assertFalse(c.segment[6].SPcoc[3] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[6].SPcoc[3] & 0x0020) + self.assertEqual(c.segment[6].SPcoc[4], + glymur.core.WAVELET_TRANSFORM_9x7_IRREVERSIBLE) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[7].Cqcc, 2) + self.assertEqual(c.segment[7]._guardBits, 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]) + + # COM: comment + # Registration + self.assertEqual(c.segment[8].Rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[8].Ccme.decode('latin-1'), + "Created by OpenJPEG version 1.3.0") + + # 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.id == 'SOT'] + self.assertEqual(idx, [0, 0, 0]) + lens = [x.Psot for x in c.segment if x.id == 'SOT'] + self.assertEqual(lens, [45274, 20838, 8909]) + TPsot = [x.TPsot for x in c.segment if x.id == 'SOT'] + self.assertEqual(TPsot, [0, 1, 2]) + + sods = [x for x in c.segment if x.id == 'SOD'] + self.assertEqual(len(sods), 3) + + # EOC: end of codestream + self.assertEqual(c.segment[-1].id, 'EOC') + + def test_Bretagne2_j2k_dump(self): + # Profile 3. + jfile = os.path.join(data_root, 'input/nonregression/Bretagne2.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "3" means profile 3 + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 2592) + self.assertEqual(c.segment[1].Ysiz, 1944) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (640, 480)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.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) # level + self.assertEqual(tuple(c.segment[2]._code_block_size), (32, 32)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_5x3_REVERSIBLE) + self.assertEqual(c.segment[2]._precinct_size, + [(16, 16), (32, 32), (64, 64), (128, 128), + (128, 128), (128, 128)]) + + ids = [x.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 = os.path.join(data_root, 'input/nonregression/buxI.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 512) + self.assertEqual(c.segment[1].Ysiz, 512) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (512, 512)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (16,)) + # signed + self.assertEqual(c.segment[1]._signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)] * 1) + + # 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)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_9x7_IRREVERSIBLE) + self.assertEqual(len(c.segment[2].SPcod), 9) + + ids = [x.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 = os.path.join(data_root, 'input/nonregression/buxR.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 512) + self.assertEqual(c.segment[1].Ysiz, 512) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (512, 512)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (16,)) + # signed + self.assertEqual(c.segment[1]._signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)] * 1) + + # 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)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_5x3_REVERSIBLE) + self.assertEqual(len(c.segment[2].SPcod), 9) + + ids = [x.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 = os.path.join(data_root, path) + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + ids = [x.id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 1420) + self.assertEqual(c.segment[1].Ysiz, 1416) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), + (1420, 1416)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (16,)) + # signed + self.assertEqual(c.segment[1]._signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)] * 1) + + # 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)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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 = os.path.join(data_root, + 'input/nonregression/' + + 'CT_Phillips_JPEG2K_Decompr_Problem.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + ids = [x.id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 512) + self.assertEqual(c.segment[1].Ysiz, 614) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (512, 614)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (12,)) + # signed + self.assertEqual(c.segment[1]._signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)] * 1) + + # 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)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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]) + + # COM: comment + # Registration + self.assertEqual(c.segment[4].Rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[4].Ccme.decode('latin-1'), "Kakadu-3.2") + + def test_NR_cthead1_dump(self): + jfile = os.path.join(data_root, + 'input/nonregression/cthead1.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + ids = [x.id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME', 'CME'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 256) + self.assertEqual(c.segment[1].Ysiz, 256) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (256, 256)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8,)) + # signed + self.assertEqual(c.segment[1]._signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)] * 1) + + # 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)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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]) + + # COM: comment + # Registration + self.assertEqual(c.segment[4].Rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[4].Ccme.decode('latin-1'), "Kakadu-v6.3.1") + + # COM: comment + # Registration + self.assertEqual(c.segment[4].Rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[4].Ccme.decode('latin-1'), "Kakadu-v6.3.1") + + def test_NR_illegalcolortransform_dump(self): + jfile = os.path.join(data_root, + 'input/nonregression/illegalcolortransform.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + ids = [x.id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 1420) + self.assertEqual(c.segment[1].Ysiz, 1416) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), + (1420, 1416)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (16,)) + # signed + self.assertEqual(c.segment[1]._signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)] * 1) + + # 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)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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 = os.path.join(data_root, 'input/nonregression/j2k32.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + ids = [x.id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 256) + self.assertEqual(c.segment[1].Ysiz, 256) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (256, 256)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (True, True, True)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.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, 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)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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]) + + # COM: comment + # Registration + self.assertEqual(c.segment[4].Rcme, glymur.core.RCME_BINARY) + # Comment value + self.assertEqual(len(c.segment[4].Ccme), 36) + + def test_NR_kakadu_v4_4_openjpegv2_broken_dump(self): + jfile = os.path.join(data_root, + 'input/nonregression/' + + 'kakadu_v4-4_openjpegv2_broken.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 2048) + self.assertEqual(c.segment[1].Ysiz, 2500) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), + (2048, 2500)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (16,)) + # signed + self.assertEqual(c.segment[1]._signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)] * 1) + + # 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)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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]) + + # COM: comment + # Registration + self.assertEqual(c.segment[4].Rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[4].Ccme.decode('latin-1'), "Kakadu-v4.4") + + # COM: comment + # Registration + self.assertEqual(c.segment[5].Rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + expected = "Kdu-Layer-Info: log_2{Delta-D(MSE)/[2^16*Delta-L(bytes)]}," + expected += " L(bytes)\n" + expected += " -65.4, 6.8e+004\n" + expected += " -66.3, 1.0e+005\n" + expected += " -67.3, 2.0e+005\n" + expected += " -68.5, 4.1e+005\n" + expected += " -69.0, 5.1e+005\n" + expected += " -69.5, 5.9e+005\n" + expected += " -69.7, 6.8e+005\n" + expected += " -70.3, 8.2e+005\n" + expected += " -70.8, 1.0e+006\n" + expected += " -71.9, 1.4e+006\n" + expected += " -73.8, 2.0e+006\n" + expected += "-256.0, 3.7e+006\n" + self.assertEqual(c.segment[5].Ccme.decode('latin-1'), expected) + + def test_NR_MarkerIsNotCompliant_j2k_dump(self): + jfile = os.path.join(data_root, + 'input/nonregression/MarkerIsNotCompliant.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 1420) + self.assertEqual(c.segment[1].Ysiz, 1416) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), + (1420, 1416)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (16,)) + # signed + self.assertEqual(c.segment[1]._signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)] * 1) + + # 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)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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 = os.path.join(data_root, + 'input/nonregression/movie_00000.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 1920) + self.assertEqual(c.segment[1].Ysiz, 1080) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), + (1920, 1080)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.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, 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)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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 = os.path.join(data_root, + 'input/nonregression/movie_00001.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 1920) + self.assertEqual(c.segment[1].Ysiz, 1080) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), + (1920, 1080)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.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, 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)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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 = os.path.join(data_root, + 'input/nonregression/movie_00002.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 1920) + self.assertEqual(c.segment[1].Ysiz, 1080) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), + (1920, 1080)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.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, 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)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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 = os.path.join(data_root, + 'input/nonregression/orb-blue10-lin-j2k.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + ids = [x.id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 117) + self.assertEqual(c.segment[1].Ysiz, 117) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (117, 117)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8, 8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)] * 4) + + # 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)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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 = os.path.join(data_root, + 'input/nonregression/orb-blue10-win-j2k.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + ids = [x.id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 117) + self.assertEqual(c.segment[1].Ysiz, 117) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (117, 117)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8, 8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)] * 4) + + # 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)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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 = os.path.join(data_root, 'input/nonregression/pacs.ge.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + ids = [x.id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 512) + self.assertEqual(c.segment[1].Ysiz, 512) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (512, 512)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (16,)) + # signed + self.assertEqual(c.segment[1]._signed, (True,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)] * 1) + + # 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)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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]) + + # COM: comment + # Registration + self.assertEqual(c.segment[4].Rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[4].Ccme.decode('latin-1'), + "Kakadu-2.0.2") + + def test_NR_test_lossless_j2k_dump(self): + jfile = os.path.join(data_root, + 'input/nonregression/test_lossless.j2k') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + ids = [x.id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 1024) + self.assertEqual(c.segment[1].Ysiz, 1024) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), + (1024, 1024)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (12,)) + # signed + self.assertEqual(c.segment[1]._signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)] * 1) + + # 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)) + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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]) + + # COM: comment + # Registration + self.assertEqual(c.segment[4].Rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[4].Ccme.decode('latin-1'), + "ClearCanvas DICOM OpenJPEG") + + def test_NR_123_j2c_dump(self): + jfile = os.path.join(data_root, 'input/nonregression/123.j2c') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + ids = [x.id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 1800) + self.assertEqual(c.segment[1].Ysiz, 1800) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), + (1800, 1800)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (16,)) + # signed + self.assertEqual(c.segment[1]._signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)] * 1) + + # 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)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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 = os.path.join(data_root, 'input/nonregression/bug.j2c') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + ids = [x.id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 1800) + self.assertEqual(c.segment[1].Ysiz, 1800) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), + (1800, 1800)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (16,)) + # signed + self.assertEqual(c.segment[1]._signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)] * 1) + + # 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)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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 = os.path.join(data_root, + 'input/nonregression/kodak_2layers_lrcp.j2c') + jp2k = Jp2k(jfile) + c = jp2k.get_codestream() + + ids = [x.id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 2048) + self.assertEqual(c.segment[1].Ysiz, 1556) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), + (2048, 1556)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (12, 12, 12)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.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, 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)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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]) + + # COM: comment + # Registration + self.assertEqual(c.segment[4].Rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[4].Ccme.decode('latin-1'), + "DCP-Werkstatt") + + @unittest.skipIf(sys.hexversion < 0x03020000, + "Uses features introduced in 3.2.") + def test_NR_broken_jp2_dump(self): + jfile = os.path.join(data_root, + 'input/nonregression/broken.jp2') + with self.assertWarns(UserWarning) as cw: + # colr box has bad length. + jp2 = Jp2k(jfile) + + ids = [box.id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 152) + self.assertEqual(jp2.box[2].box[0].width, 203) + self.assertEqual(jp2.box[2].box[0].num_components, 3) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].cspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[1].method, 1) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 0) # not allowed? + self.assertEqual(jp2.box[2].box[1].color_space, glymur.core.SRGB) + + c = jp2.box[3].main_header + + ids = [x.id for x in c.segment] + expected = ['SOC', 'SIZ', 'CME', 'COD', 'QCD', 'QCC', 'QCC'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 203) + self.assertEqual(c.segment[1].Ysiz, 152) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (203, 152)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)] * 3) + + # COM: comment + # Registration + self.assertEqual(c.segment[2].Rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[2].Ccme.decode('latin-1'), + "Creator: JasPer Version 1.701.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.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 + # Selective arithmetic coding bypass + self.assertFalse(c.segment[3].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[3].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[3].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[3].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[3].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[3].SPcod[7] & 0x0020) + self.assertEqual(c.segment[3].SPcod[8], + glymur.core.WAVELET_TRANSFORM_5x3_REVERSIBLE) + self.assertEqual(len(c.segment[3].SPcod), 9) + + # QCD: Quantization default + self.assertEqual(c.segment[4].Sqcd & 0x1f, 0) + self.assertEqual(c.segment[4]._guardBits, 2) + self.assertEqual(c.segment[4]._mantissa, [0] * 16) + self.assertEqual(c.segment[4]._exponent, + [8] + [9, 9, 10] * 5) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[5].Cqcc, 1) + self.assertEqual(c.segment[5]._guardBits, 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) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[6].Cqcc, 2) + self.assertEqual(c.segment[6]._guardBits, 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_broken2_jp2_dump(self): + # Invalid marker ID on codestream. + jfile = os.path.join(data_root, + 'input/nonregression/broken2.jp2') + with self.assertRaises(IOError): + jp2 = Jp2k(jfile) + + @unittest.skipIf(sys.hexversion < 0x03020000, + "Uses features introduced in 3.2.") + def test_NR_broken3_jp2_dump(self): + jfile = os.path.join(data_root, + 'input/nonregression/broken3.jp2') + with self.assertWarns(UserWarning) as cw: + # colr box has bad length. + jp2 = Jp2k(jfile) + + ids = [box.id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 152) + self.assertEqual(jp2.box[2].box[0].width, 203) + self.assertEqual(jp2.box[2].box[0].num_components, 3) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].cspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[1].method, 1) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 + self.assertEqual(jp2.box[2].box[1].color_space, glymur.core.SRGB) + + c = jp2.box[3].main_header + + ids = [x.id for x in c.segment] + expected = ['SOC', 'SIZ', 'CME', 'COD', 'QCD', 'QCC', 'QCC'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 203) + self.assertEqual(c.segment[1].Ysiz, 152) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (203, 152)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)] * 3) + + # COM: comment + # Registration + self.assertEqual(c.segment[2].Rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[2].Ccme.decode('latin-1'), + "Creator: JasPer Vers)on 1.701.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.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 + # Selective arithmetic coding bypass + self.assertFalse(c.segment[3].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[3].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[3].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[3].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[3].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[3].SPcod[7] & 0x0020) + self.assertEqual(c.segment[3].SPcod[8], + glymur.core.WAVELET_TRANSFORM_5x3_REVERSIBLE) + self.assertEqual(len(c.segment[3].SPcod), 9) + + # QCD: Quantization default + self.assertEqual(c.segment[4].Sqcd & 0x1f, 0) + self.assertEqual(c.segment[4]._guardBits, 2) + self.assertEqual(c.segment[4]._mantissa, [0] * 16) + self.assertEqual(c.segment[4]._exponent, + [8] + [9, 9, 10] * 5) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[5].Cqcc, 1) + self.assertEqual(c.segment[5]._guardBits, 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) + + # QCC: Quantization component + # associated component + self.assertEqual(c.segment[6].Cqcc, 2) + self.assertEqual(c.segment[6]._guardBits, 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_broken4_jp2_dump(self): + jfile = os.path.join(data_root, + 'input/nonregression/broken4.jp2') + with self.assertRaises(IOError): + jp2 = Jp2k(jfile) + + def test_NR_file409752(self): + jfile = os.path.join(data_root, + 'input/nonregression/file409752.jp2') + jp2 = Jp2k(jfile) + + ids = [box.id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 243) + self.assertEqual(jp2.box[2].box[0].width, 720) + self.assertEqual(jp2.box[2].box[0].num_components, 3) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].cspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[1].method, 1) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 + self.assertEqual(jp2.box[2].box[1].color_space, glymur.core.YCC) + + c = jp2.box[3].main_header + + ids = [x.id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 720) + self.assertEqual(c.segment[1].Ysiz, 243) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (720, 243)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1), (2, 1), (2, 1)]) + + # 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 + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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) + + @unittest.skipIf(sys.hexversion < 0x03020000, + "Uses features introduced in 3.2.") + 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 = os.path.join(data_root, '/'.join(lst)) + with self.assertWarns(UserWarning): + jp2 = Jp2k(jfile) + + @unittest.skipIf(sys.hexversion < 0x03020000, + "Uses features introduced in 3.2.") + def test_NR_gdal_fuzzer_check_comp_dx_dy_jp2_dump(self): + lst = ['input', 'nonregression', 'gdal_fuzzer_check_comp_dx_dy.jp2'] + jfile = os.path.join(data_root, '/'.join(lst)) + with self.assertWarns(UserWarning): + jp2 = Jp2k(jfile) + + @unittest.skipIf(sys.hexversion < 0x03020000, + "Uses features introduced in 3.2.") + 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 = os.path.join(data_root, '/'.join(lst)) + with self.assertWarns(UserWarning): + jp2 = Jp2k(jfile) + + @unittest.skipIf(sys.hexversion < 0x03020000, + "Uses features introduced in 3.2.") + def test_NR_gdal_fuzzer_unchecked_numresolutions_dump(self): + # Has an invalid number of resolutions. + lst = ['input', 'nonregression', + 'gdal_fuzzer_unchecked_numresolutions.jp2'] + jfile = os.path.join(data_root, '/'.join(lst)) + with self.assertWarns(UserWarning): + jp2 = Jp2k(jfile) + + def test_NR_issue104_jpxstream_dump(self): + jfile = os.path.join(data_root, + 'input/nonregression/issue104_jpxstream.jp2') + jp2 = Jp2k(jfile) + + ids = [box.id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) + + ids = [box.id for box in jp2.box[3].box] + self.assertEqual(ids, ['ihdr', 'colr', 'pclr', 'cmap']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + self.assertEqual(jp2.box[1].compatibility_list[1], 'jpxb') + self.assertEqual(jp2.box[1].compatibility_list[2], 'jpx ') + + # Reader requirements talk. + self.assertTrue(glymur.core.RREQ_UNRESTRICTED_JPEG2000_PART_1 + in jp2.box[2].standard_flag) + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[3].box[0].height, 203) + self.assertEqual(jp2.box[3].box[0].width, 479) + self.assertEqual(jp2.box[3].box[0].num_components, 1) + self.assertEqual(jp2.box[3].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[3].box[0].signed, False) + self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[3].box[0].cspace_unknown, True) + self.assertEqual(jp2.box[3].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[3].box[1].method, 1) # enumerated + self.assertEqual(jp2.box[3].box[1].precedence, 2) + self.assertEqual(jp2.box[3].box[1].approximation, 1) # exact + self.assertEqual(jp2.box[3].box[1].color_space, glymur.core.SRGB) + + # Jp2 Header + # Palette box. + self.assertEqual(len(jp2.box[3].box[2].palette), 3) + self.assertEqual(len(jp2.box[3].box[2].palette[0]), 256) + self.assertEqual(len(jp2.box[3].box[2].palette[1]), 256) + self.assertEqual(len(jp2.box[3].box[2].palette[2]), 256) + + # 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].main_header + + ids = [x.id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 479) + self.assertEqual(c.segment[1].Ysiz, 203) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (256, 203)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8,)) + # signed + self.assertEqual(c.segment[1]._signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)]) + + # 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 + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 2) + self.assertEqual(c.segment[3]._mantissa, [0] * 16) + self.assertEqual(c.segment[3]._exponent, [8] + [9, 9, 10] * 5) + + def test_NR_issue188_beach_64bitsbox(self): + lst = ['input', 'nonregression', 'issue188_beach_64bitsbox.jp2'] + jfile = os.path.join(data_root, '/'.join(lst)) + with warnings.catch_warnings(): + # There's a warning for an unknown box. We explicitly test for + # that down below. + warnings.simplefilter("ignore") + jp2 = Jp2k(jfile) + + ids = [box.id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'XML ', 'jp2c']) + + ids = [box.id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 200) + self.assertEqual(jp2.box[2].box[0].width, 200) + self.assertEqual(jp2.box[2].box[0].num_components, 3) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].cspace_unknown, True) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[1].method, 1) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 0) + self.assertEqual(jp2.box[2].box[1].color_space, glymur.core.SRGB) + + # Skip the 4th box, it is uknown. + + c = jp2.box[4].main_header + + ids = [x.id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME', 'CME'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 200) + self.assertEqual(c.segment[1].Ysiz, 200) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (200, 200)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.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, 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 + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 1) + + def test_NR_issue206_image_000_dump(self): + jfile = os.path.join(data_root, + 'input/nonregression/issue206_image-000.jp2') + jp2 = Jp2k(jfile) + + ids = [box.id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) + + ids = [box.id for box in jp2.box[3].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + self.assertEqual(jp2.box[1].compatibility_list[1], 'jpxb') + self.assertEqual(jp2.box[1].compatibility_list[2], 'jpx ') + + # Reader requirements talk. + self.assertTrue(glymur.core.RREQ_UNRESTRICTED_JPEG2000_PART_1 + in jp2.box[2].standard_flag) + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[3].box[0].height, 326) + self.assertEqual(jp2.box[3].box[0].width, 431) + self.assertEqual(jp2.box[3].box[0].num_components, 3) + self.assertEqual(jp2.box[3].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[3].box[0].signed, False) + self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[3].box[0].cspace_unknown, True) + self.assertEqual(jp2.box[3].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[3].box[1].method, 1) # ICC + self.assertEqual(jp2.box[3].box[1].precedence, 2) + self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact + self.assertEqual(jp2.box[3].box[1].color_space, glymur.core.SRGB) + + c = jp2.box[4].main_header + + ids = [x.id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 431) + self.assertEqual(c.segment[1].Ysiz, 326) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (256, 256)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.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.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)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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 = os.path.join(data_root, + 'input/nonregression/Marrin.jp2') + jp2 = Jp2k(jfile) + + ids = [box.id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr', 'cdef', 'res ']) + + ids = [box.id for box in jp2.box[2].box[3].box] + self.assertEqual(ids, ['resd']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 135) + self.assertEqual(jp2.box[2].box[0].width, 135) + self.assertEqual(jp2.box[2].box[0].num_components, 2) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].cspace_unknown, True) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[1].method, 1) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 + self.assertEqual(jp2.box[2].box[1].color_space, glymur.core.GREYSCALE) + + # Jp2 Header + # Channel Definition + self.assertEqual(jp2.box[2].box[2].component_number, (0, 1)) + self.assertEqual(jp2.box[2].box[2].component_type, (0, 1)) # opacity + self.assertEqual(jp2.box[2].box[2].association, (0, 0)) # both main + + c = jp2.box[3].main_header + + ids = [x.id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD', 'CME'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 135) + self.assertEqual(c.segment[1].Ysiz, 135) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (135, 135)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)] * 2) + + # 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 + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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) + + # COM: comment + # Registration + self.assertEqual(c.segment[4].Rcme, glymur.core.RCME_ISO_8859_1) + # Comment value + self.assertEqual(c.segment[4].Ccme.decode('latin-1'), + "Kakadu-v5.2.1") + + def test_NR_mem_b2ace68c_1381_dump(self): + jfile = os.path.join(data_root, + 'input/nonregression/mem-b2ace68c-1381.jp2') + with warnings.catch_warnings(): + # This file has a bad pclr box, we test for this elsewhere. + warnings.simplefilter("ignore") + jp2 = Jp2k(jfile) + + ids = [box.id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) + + ids = [box.id for box in jp2.box[3].box] + self.assertEqual(ids, ['ihdr', 'colr', 'pclr', 'cmap']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + self.assertEqual(jp2.box[1].compatibility_list[1], 'jpxb') + self.assertEqual(jp2.box[1].compatibility_list[2], 'jpx ') + + # Reader requirements talk. + self.assertTrue(glymur.core.RREQ_CMYK_ENUMERATED_COLORSPACE + in jp2.box[2].standard_flag) + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[3].box[0].height, 865) + self.assertEqual(jp2.box[3].box[0].width, 649) + self.assertEqual(jp2.box[3].box[0].num_components, 1) + self.assertEqual(jp2.box[3].box[0].bits_per_component, 1) + self.assertEqual(jp2.box[3].box[0].signed, False) + self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[3].box[0].cspace_unknown, True) + self.assertEqual(jp2.box[3].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[3].box[1].method, 1) # enumerated + self.assertEqual(jp2.box[3].box[1].precedence, 2) + self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact + self.assertEqual(jp2.box[3].box[1].color_space, glymur.core.CMYK) + + # Jp2 Header + # Palette box. + self.assertEqual(len(jp2.box[3].box[2].palette), 4) + self.assertEqual(len(jp2.box[3].box[2].palette[0]), 1) + self.assertEqual(len(jp2.box[3].box[2].palette[1]), 1) + self.assertEqual(len(jp2.box[3].box[2].palette[2]), 1) + self.assertEqual(len(jp2.box[3].box[2].palette[3]), 1) + + # Jp2 Header + # Component mapping box + self.assertEqual(jp2.box[3].box[3].component_index, (0, 1, 2)) + self.assertEqual(jp2.box[3].box[3].mapping_type, (1, 1, 0)) + self.assertEqual(jp2.box[3].box[3].palette_index, (0, 0, 1)) + + c = jp2.box[4].main_header + + ids = [x.id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 649) + self.assertEqual(c.segment[1].Ysiz, 865) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (256, 256)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (1,)) + # signed + self.assertEqual(c.segment[1]._signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)]) + + # 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 + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 3) + self.assertEqual(c.segment[3]._mantissa, [0] * 16) + self.assertEqual(c.segment[3]._exponent, [1] + [2, 2, 3] * 5) + + def test_NR_mem_b2b86b74_2753_dump(self): + jfile = os.path.join(data_root, + 'input/nonregression/mem-b2b86b74-2753.jp2') + jp2 = Jp2k(jfile) + + ids = [box.id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'rreq', 'jp2h', 'jp2c']) + + ids = [box.id for box in jp2.box[3].box] + self.assertEqual(ids, ['ihdr', 'colr', 'pclr', 'cmap']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + self.assertEqual(jp2.box[1].compatibility_list[1], 'jpxb') + self.assertEqual(jp2.box[1].compatibility_list[2], 'jpx ') + + # Reader requirements talk. + self.assertTrue(glymur.core.RREQ_UNRESTRICTED_JPEG2000_PART_1 + in jp2.box[2].standard_flag) + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[3].box[0].height, 46) + self.assertEqual(jp2.box[3].box[0].width, 124) + self.assertEqual(jp2.box[3].box[0].num_components, 1) + self.assertEqual(jp2.box[3].box[0].bits_per_component, 4) + self.assertEqual(jp2.box[3].box[0].signed, False) + self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[3].box[0].cspace_unknown, True) + self.assertEqual(jp2.box[3].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[3].box[1].method, 1) # enumerated + self.assertEqual(jp2.box[3].box[1].precedence, 2) + self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact + self.assertEqual(jp2.box[3].box[1].color_space, glymur.core.SRGB) + + # Jp2 Header + # Palette box. + # 3 columns with 16 entries. + self.assertEqual(len(jp2.box[3].box[2].palette), 3) + self.assertEqual(len(jp2.box[3].box[2].palette[0]), 16) + self.assertEqual(len(jp2.box[3].box[2].palette[1]), 16) + self.assertEqual(len(jp2.box[3].box[2].palette[2]), 16) + + # 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].main_header + + ids = [x.id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 124) + self.assertEqual(c.segment[1].Ysiz, 46) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (124, 46)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (4,)) + # signed + self.assertEqual(c.segment[1]._signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)]) + + # 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 + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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 = os.path.join(data_root, + 'input/nonregression/merged.jp2') + jp2 = Jp2k(jfile) + + ids = [box.id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 576) + self.assertEqual(jp2.box[2].box[0].width, 766) + self.assertEqual(jp2.box[2].box[0].num_components, 3) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].cspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[1].method, 1) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 + self.assertEqual(jp2.box[2].box[1].color_space, glymur.core.YCC) + + c = jp2.box[3].main_header + + ids = [x.id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD', 'POD'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 766) + self.assertEqual(c.segment[1].Ysiz, 576) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (766, 576)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1), (2, 1), (2, 1)]) + + # 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 + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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].CEpod, (1, 3)) + + podvals = (glymur.core.LRCP, glymur.core.LRCP) + self.assertEqual(c.segment[4].Ppod, podvals) + + def test_NR_orb_blue10_lin_jp2_dump(self): + jfile = os.path.join(data_root, + 'input/nonregression/orb-blue10-lin-jp2.jp2') + jp2 = Jp2k(jfile) + + ids = [box.id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 117) + self.assertEqual(jp2.box[2].box[0].width, 117) + self.assertEqual(jp2.box[2].box[0].num_components, 4) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].cspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[1].method, 2) # res icc + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 + self.assertEqual(len(jp2.box[2].box[1].icc_profile), 1) + self.assertIsNone(jp2.box[2].box[1].color_space) + + c = jp2.box[3].main_header + + ids = [x.id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 117) + self.assertEqual(c.segment[1].Ysiz, 117) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (117, 117)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8, 8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)] * 4) + + # 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 + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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 = os.path.join(data_root, + 'input/nonregression/orb-blue10-win-jp2.jp2') + jp2 = Jp2k(jfile) + + ids = [box.id for box in jp2.box] + self.assertEqual(ids, ['jP ', 'ftyp', 'jp2h', 'jp2c']) + + ids = [box.id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 117) + self.assertEqual(jp2.box[2].box[0].width, 117) + self.assertEqual(jp2.box[2].box[0].num_components, 4) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].cspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[1].method, 2) # restricted icc + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 0) # JP2 + self.assertEqual(len(jp2.box[2].box[1].icc_profile), 1) + self.assertIsNone(jp2.box[2].box[1].color_space) + + c = jp2.box[3].main_header + + ids = [x.id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 117) + self.assertEqual(c.segment[1].Ysiz, 117) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (117, 117)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8, 8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)] * 4) + + # 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 + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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_text_GBR_dump(self): + jfile = os.path.join(data_root, + 'input/nonregression/text_GBR.jp2') + jp2 = Jp2k(jfile) + + ids = [box.id for box in jp2.box] + lst = ['jP ', 'ftyp', 'rreq', 'jp2h', + 'uuid', 'uuid', 'uuid', 'uuid', 'jp2c'] + self.assertEqual(ids, lst) + + ids = [box.id for box in jp2.box[3].box] + self.assertEqual(ids, ['ihdr', 'colr', 'res ']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + + # Reader requirements. + # Compositing layer uses any icc profile + self.assertTrue(44 in jp2.box[2].standard_flag) + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[3].box[0].height, 400) + self.assertEqual(jp2.box[3].box[0].width, 400) + self.assertEqual(jp2.box[3].box[0].num_components, 3) + self.assertEqual(jp2.box[3].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[3].box[0].signed, False) + self.assertEqual(jp2.box[3].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[3].box[0].cspace_unknown, True) + self.assertEqual(jp2.box[3].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[3].box[1].method, 3) # any icc + self.assertEqual(jp2.box[3].box[1].precedence, 2) + self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact + self.assertEqual(len(jp2.box[3].box[1].icc_profile), 1328) + self.assertIsNone(jp2.box[3].box[1].color_space) + + # UUID boxes. All mentioned in the RREQ box. + self.assertEqual(jp2.box[2].vendor_feature[0], jp2.box[4].uuid) + self.assertEqual(jp2.box[2].vendor_feature[1], jp2.box[5].uuid) + self.assertEqual(jp2.box[2].vendor_feature[2], jp2.box[6].uuid) + self.assertEqual(jp2.box[2].vendor_feature[3], jp2.box[7].uuid) + + c = jp2.box[8].main_header + + ids = [x.id for x in c.segment] + expected = ['SOC', 'SIZ', 'COD', 'QCD'] + self.assertEqual(ids, expected) + + # SIZ: Image and tile size + # Profile: + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual(c.segment[1].Xsiz, 400) + self.assertEqual(c.segment[1].Ysiz, 400) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), (128, 128)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.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.RLCP) + self.assertEqual(c.segment[2]._layers, 6) # layers = 6 + 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)) # cblk + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_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]._guardBits, 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_DEC_Bretagne2_j2k_1_decode(self): + jfile = os.path.join(data_root, + 'input/nonregression/Bretagne2.j2k') + jp2 = Jp2k(jfile) + data = jp2.read() + self.assertTrue(True) + + def test_NR_DEC__00042_j2k_2_decode(self): + jfile = os.path.join(data_root, + 'input/nonregression/_00042.j2k') + jp2 = Jp2k(jfile) + data = jp2.read() + self.assertTrue(True) + + def test_NR_DEC_123_j2c_3_decode(self): + jfile = os.path.join(data_root, + 'input/nonregression/123.j2c') + jp2 = Jp2k(jfile) + data = jp2.read() + self.assertTrue(True) + + @unittest.skipIf(sys.hexversion < 0x03020000, + "Uses features introduced in 3.2.") + def test_NR_DEC_broken_jp2_4_decode(self): + jfile = os.path.join(data_root, + 'input/nonregression/broken.jp2') + with self.assertWarns(UserWarning) as cw: + # colr box has bad length. + jp2 = Jp2k(jfile) + with self.assertRaises(IOError): + data = jp2.read() + self.assertTrue(True) + + def test_NR_DEC_broken2_jp2_5_decode(self): + jfile = os.path.join(data_root, + 'input/nonregression/broken2.jp2') + with self.assertRaises(IOError): + data = Jp2k(jfile).read() + self.assertTrue(True) + + @unittest.skipIf(sys.hexversion < 0x03020000, + "Uses features introduced in 3.2.") + def test_NR_DEC_broken3_jp2_6_decode(self): + jfile = os.path.join(data_root, + 'input/nonregression/broken3.jp2') + with self.assertWarns(UserWarning) as cw: + # colr box has bad length. + j = Jp2k(jfile) + + with self.assertRaises(IOError) as ce: + d = j.read() + + def test_NR_DEC_broken4_jp2_7_decode(self): + jfile = os.path.join(data_root, + 'input/nonregression/broken4.jp2') + with self.assertRaises(IOError): + data = Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_bug_j2c_8_decode(self): + jfile = os.path.join(data_root, + 'input/nonregression/bug.j2c') + data = Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_buxI_j2k_9_decode(self): + jfile = os.path.join(data_root, + 'input/nonregression/buxI.j2k') + data = Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_buxR_j2k_10_decode(self): + jfile = os.path.join(data_root, + 'input/nonregression/buxR.j2k') + data = Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_Cannotreaddatawithnosizeknown_j2k_11_decode(self): + relpath = 'input/nonregression/Cannotreaddatawithnosizeknown.j2k' + jfile = os.path.join(data_root, relpath) + data = Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_cthead1_j2k_12_decode(self): + jfile = os.path.join(data_root, + 'input/nonregression/cthead1.j2k') + data = Jp2k(jfile).read() + 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 = os.path.join(data_root, relpath) + data = Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_illegalcolortransform_j2k_14_decode(self): + # Stream too short, expected SOT. + jfile = os.path.join(data_root, + 'input/nonregression/illegalcolortransform.j2k') + data = Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_j2k32_j2k_15_decode(self): + jfile = os.path.join(data_root, + 'input/nonregression/j2k32.j2k') + data = Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_kakadu_v4_4_openjpegv2_broken_j2k_16_decode(self): + relpath = 'input/nonregression/kakadu_v4-4_openjpegv2_broken.j2k' + jfile = os.path.join(data_root, relpath) + data = Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_MarkerIsNotCompliant_j2k_17_decode(self): + jfile = os.path.join(data_root, + 'input/nonregression/MarkerIsNotCompliant.j2k') + data = Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_Marrin_jp2_18_decode(self): + jfile = os.path.join(data_root, + 'input/nonregression/Marrin.jp2') + data = Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_merged_jp2_19_decode(self): + jfile = os.path.join(data_root, + 'input/nonregression/merged.jp2') + data = Jp2k(jfile).read_bands() + self.assertTrue(True) + + def test_NR_DEC_movie_00000_j2k_20_decode(self): + jfile = os.path.join(data_root, + 'input/nonregression/movie_00000.j2k') + data = Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_movie_00001_j2k_21_decode(self): + jfile = os.path.join(data_root, + 'input/nonregression/movie_00001.j2k') + data = Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_movie_00002_j2k_22_decode(self): + jfile = os.path.join(data_root, + 'input/nonregression/movie_00002.j2k') + data = Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_orb_blue_lin_j2k_j2k_23_decode(self): + jfile = os.path.join(data_root, + 'input/nonregression/orb-blue10-lin-j2k.j2k') + data = Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_orb_blue_win_j2k_j2k_24_decode(self): + jfile = os.path.join(data_root, + 'input/nonregression/orb-blue10-win-j2k.j2k') + data = Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_orb_blue_lin_jp2_25_decode(self): + jfile = os.path.join(data_root, + 'input/nonregression/orb-blue10-lin-jp2.jp2') + data = Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_orb_blue_win_jp2_26_decode(self): + jfile = os.path.join(data_root, + 'input/nonregression/orb-blue10-win-jp2.jp2') + data = Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_relax_jp2_27_decode(self): + jfile = os.path.join(data_root, + 'input/nonregression/relax.jp2') + data = Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_test_lossless_j2k_28_decode(self): + jfile = os.path.join(data_root, + 'input/nonregression/test_lossless.j2k') + data = Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_text_GBR_jp2_29_decode(self): + jfile = os.path.join(data_root, + 'input/nonregression/text_GBR.jp2') + data = Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_pacs_ge_j2k_30_decode(self): + jfile = os.path.join(data_root, + 'input/nonregression/pacs.ge.j2k') + data = Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_kodak_2layers_lrcp_j2c_31_decode(self): + jfile = os.path.join(data_root, + 'input/nonregression/kodak_2layers_lrcp.j2c') + data = Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_kodak_2layers_lrcp_j2c_32_decode(self): + jfile = os.path.join(data_root, + 'input/nonregression/kodak_2layers_lrcp.j2c') + data = Jp2k(jfile).read(layer=2) + self.assertTrue(True) + + def test_NR_DEC_issue104_jpxstream_jp2_33_decode(self): + jfile = os.path.join(data_root, + 'input/nonregression/issue104_jpxstream.jp2') + data = Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_mem_b2ace68c_1381_jp2_34_decode(self): + jfile = os.path.join(data_root, + 'input/nonregression/mem-b2ace68c-1381.jp2') + with warnings.catch_warnings(): + # This file has a bad pclr box, we test for this elsewhere. + warnings.simplefilter("ignore") + j = Jp2k(jfile) + data = j.read() + self.assertTrue(True) + + def test_NR_DEC_mem_b2b86b74_2753_jp2_35_decode(self): + jfile = os.path.join(data_root, + 'input/nonregression/mem-b2b86b74-2753.jp2') + data = 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 = os.path.join(data_root, f) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + j = Jp2k(jfile) + with self.assertRaises(IOError): + data = j.read() + + def test_NR_DEC_jp2_36_decode(self): + lst = ('input', + 'nonregression', + 'gdal_fuzzer_assert_in_opj_j2k_read_SQcd_SQcc.patch.jp2') + jfile = os.path.join(data_root, '/'.join(lst)) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + j = Jp2k(jfile) + with self.assertRaises(IOError): + data = 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 = os.path.join(data_root, relpath) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + j = Jp2k(jfile) + with self.assertRaises(IOError): + data = 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 = os.path.join(data_root, relpath) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + with self.assertRaises(IOError): + j = Jp2k(jfile).read() + + def test_NR_DEC_file_409752_jp2_40_decode(self): + jfile = os.path.join(data_root, + 'input/nonregression/file409752.jp2') + with self.assertRaises(IOError): + data = Jp2k(jfile).read() + + @unittest.skipIf(sys.hexversion < 0x03020000, + "Uses features introduced in 3.2.") + 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 = os.path.join(data_root, relpath) + with self.assertWarns(UserWarning) as cw: + data = Jp2k(jfile).read() + + def test_NR_DEC_issue206_image_000_jp2_42_decode(self): + jfile = os.path.join(data_root, + 'input/nonregression/issue206_image-000.jp2') + data = Jp2k(jfile).read() + self.assertTrue(True) + + def test_NR_DEC_p1_04_j2k_43_decode(self): + jfile = os.path.join(data_root, '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 = os.path.join(data_root, '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 = os.path.join(data_root, '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 = os.path.join(data_root, '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 = os.path.join(data_root, '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 = os.path.join(data_root, '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 = os.path.join(data_root, '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 = os.path.join(data_root, 'input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(0, 0, 1024, 1024), reduce=2) + odata = jp2k.read(reduce=2) + + np.testing.assert_array_equal(ssdata, odata[0:256, 0:256]) + + def test_NR_DEC_p1_04_j2k_51_decode(self): + jfile = os.path.join(data_root, 'input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(640, 512, 768, 640), reduce=2) + odata = jp2k.read(reduce=2) + np.testing.assert_array_equal(ssdata, odata[160:192, 128:160]) + + def test_NR_DEC_p1_04_j2k_52_decode(self): + jfile = os.path.join(data_root, 'input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(896, 896, 1024, 1024), reduce=2) + odata = jp2k.read(reduce=2) + np.testing.assert_array_equal(ssdata, odata[224:352, 224:352]) + + def test_NR_DEC_p1_04_j2k_53_decode(self): + jfile = os.path.join(data_root, 'input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(500, 100, 800, 300), reduce=2) + odata = jp2k.read(reduce=2) + np.testing.assert_array_equal(ssdata, odata[125:200, 25:75]) + + def test_NR_DEC_p1_04_j2k_54_decode(self): + jfile = os.path.join(data_root, 'input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(520, 260, 600, 360), reduce=2) + odata = jp2k.read(reduce=2) + np.testing.assert_array_equal(ssdata, odata[130:150, 65:90]) + + def test_NR_DEC_p1_04_j2k_55_decode(self): + jfile = os.path.join(data_root, 'input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(520, 260, 660, 360), reduce=2) + odata = jp2k.read(reduce=2) + np.testing.assert_array_equal(ssdata, odata[130:165, 65:90]) + + def test_NR_DEC_p1_04_j2k_56_decode(self): + jfile = os.path.join(data_root, 'input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(520, 360, 600, 400), reduce=2) + odata = jp2k.read(reduce=2) + np.testing.assert_array_equal(ssdata, odata[130:150, 90:100]) + + def test_NR_DEC_p1_04_j2k_57_decode(self): + jfile = os.path.join(data_root, 'input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + 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 = os.path.join(data_root, 'input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + tdata = jp2k.read(tile=63, reduce=2) # last tile + odata = jp2k.read(reduce=2) + np.testing.assert_array_equal(tdata, odata[224:256, 224:256]) + + def test_NR_DEC_p1_04_j2k_59_decode(self): + jfile = os.path.join(data_root, 'input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + 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 = os.path.join(data_root, 'input/conformance/p1_04.j2k') + jp2k = Jp2k(jfile) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + tdata = jp2k.read(tile=12, reduce=1) # 2nd row, 5th column + odata = jp2k.read(reduce=1) + np.testing.assert_array_equal(tdata, odata[64:128, 256:320]) + + def test_NR_DEC_p1_06_j2k_61_decode(self): + jfile = os.path.join(data_root, 'input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(0, 0, 12, 12)) + odata = jp2k.read() + np.testing.assert_array_equal(ssdata, odata[0:12, 0:12]) + + def test_NR_DEC_p1_06_j2k_62_decode(self): + jfile = os.path.join(data_root, 'input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(1, 8, 8, 11)) + odata = jp2k.read() + np.testing.assert_array_equal(ssdata, odata[1:8, 8:11]) + + def test_NR_DEC_p1_06_j2k_63_decode(self): + jfile = os.path.join(data_root, 'input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(9, 9, 12, 12)) + odata = jp2k.read() + np.testing.assert_array_equal(ssdata, odata[9:12, 9:12]) + + def test_NR_DEC_p1_06_j2k_64_decode(self): + jfile = os.path.join(data_root, 'input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(10, 4, 12, 10)) + odata = jp2k.read() + np.testing.assert_array_equal(ssdata, odata[10:12, 4:10]) + + def test_NR_DEC_p1_06_j2k_65_decode(self): + jfile = os.path.join(data_root, 'input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(3, 3, 9, 9)) + odata = jp2k.read() + np.testing.assert_array_equal(ssdata, odata[3:9, 3:9]) + + def test_NR_DEC_p1_06_j2k_66_decode(self): + jfile = os.path.join(data_root, 'input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(4, 4, 7, 7)) + odata = jp2k.read() + np.testing.assert_array_equal(ssdata, odata[4:7, 4:7]) + + def test_NR_DEC_p1_06_j2k_67_decode(self): + jfile = os.path.join(data_root, 'input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(4, 4, 5, 5)) + odata = jp2k.read() + np.testing.assert_array_equal(ssdata, odata[4:5, 4: 5]) + + def test_NR_DEC_p1_06_j2k_68_decode(self): + jfile = os.path.join(data_root, 'input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(0, 0, 12, 12), reduce=1) + odata = jp2k.read(reduce=1) + np.testing.assert_array_equal(ssdata, odata[0:6, 0:6]) + + def test_NR_DEC_p1_06_j2k_69_decode(self): + jfile = os.path.join(data_root, 'input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(1, 8, 8, 11), reduce=1) + self.assertEqual(ssdata.shape, (3, 2, 3)) + + def test_NR_DEC_p1_06_j2k_70_decode(self): + jfile = os.path.join(data_root, 'input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(9, 9, 12, 12), reduce=1) + self.assertEqual(ssdata.shape, (1, 1, 3)) + + def test_NR_DEC_p1_06_j2k_71_decode(self): + jfile = os.path.join(data_root, 'input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(10, 4, 12, 10), reduce=1) + self.assertEqual(ssdata.shape, (1, 3, 3)) + + def test_NR_DEC_p1_06_j2k_72_decode(self): + jfile = os.path.join(data_root, 'input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(3, 3, 9, 9), reduce=1) + self.assertEqual(ssdata.shape, (3, 3, 3)) + + def test_NR_DEC_p1_06_j2k_73_decode(self): + jfile = os.path.join(data_root, 'input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(4, 4, 7, 7), reduce=1) + self.assertEqual(ssdata.shape, (2, 2, 3)) + + def test_NR_DEC_p1_06_j2k_74_decode(self): + jfile = os.path.join(data_root, 'input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(4, 4, 5, 5), reduce=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 = os.path.join(data_root, 'input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + with self.assertRaises((IOError, OSError)) as ce: + ssdata = jp2k.read(area=(9, 9, 12, 12), reduce=2) + + def test_NR_DEC_p1_06_j2k_76_decode(self): + jfile = os.path.join(data_root, 'input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + fulldata = jp2k.read() + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + tiledata = jp2k.read(tile=0) + np.testing.assert_array_equal(tiledata, fulldata[0:3, 0:3]) + + def test_NR_DEC_p1_06_j2k_77_decode(self): + jfile = os.path.join(data_root, 'input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + fulldata = jp2k.read() + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + tiledata = jp2k.read(tile=5) + np.testing.assert_array_equal(tiledata, fulldata[3:6, 3:6]) + + def test_NR_DEC_p1_06_j2k_78_decode(self): + jfile = os.path.join(data_root, 'input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + fulldata = jp2k.read() + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + tiledata = jp2k.read(tile=9) + np.testing.assert_array_equal(tiledata, fulldata[6:9, 3:6]) + + def test_NR_DEC_p1_06_j2k_79_decode(self): + jfile = os.path.join(data_root, 'input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + fulldata = jp2k.read() + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + tiledata = jp2k.read(tile=15) + np.testing.assert_array_equal(tiledata, fulldata[9:12, 9:12]) + + def test_NR_DEC_p1_06_j2k_80_decode(self): + # Just read the data, don't bother verifying. + jfile = os.path.join(data_root, 'input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + tiledata = jp2k.read(tile=0, reduce=2) + + def test_NR_DEC_p1_06_j2k_81_decode(self): + # Just read the data, don't bother verifying. + jfile = os.path.join(data_root, 'input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + tiledata = jp2k.read(tile=5, reduce=2) + + def test_NR_DEC_p1_06_j2k_82_decode(self): + # Just read the data, don't bother verifying. + jfile = os.path.join(data_root, 'input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + tiledata = jp2k.read(tile=9, reduce=2) + + def test_NR_DEC_p1_06_j2k_83_decode(self): + # tile size is 3x3. Reducing two levels results in no data. + jfile = os.path.join(data_root, 'input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + with self.assertRaises((IOError, OSError)) as ce: + tiledata = jp2k.read(tile=15, reduce=2) + + def test_NR_DEC_p1_06_j2k_84_decode(self): + # Just read the data, don't bother verifying. + jfile = os.path.join(data_root, 'input/conformance/p1_06.j2k') + jp2k = Jp2k(jfile) + tiledata = jp2k.read(reduce=4) + + def test_NR_DEC_p0_04_j2k_85_decode(self): + jfile = os.path.join(data_root, '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 = os.path.join(data_root, '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 = os.path.join(data_root, '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 = os.path.join(data_root, '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 = os.path.join(data_root, '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 = os.path.join(data_root, '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 = os.path.join(data_root, 'input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(0, 0, 256, 256), reduce=2) + fulldata = jp2k.read(reduce=2) + np.testing.assert_array_equal(fulldata[0:64, 0:64], ssdata) + + def test_NR_DEC_p0_04_j2k_92_decode(self): + jfile = os.path.join(data_root, 'input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(0, 128, 128, 256), reduce=2) + fulldata = jp2k.read(reduce=2) + np.testing.assert_array_equal(fulldata[0:32, 32:64], ssdata) + + def test_NR_DEC_p0_04_j2k_93_decode(self): + jfile = os.path.join(data_root, 'input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(10, 50, 200, 120), reduce=2) + fulldata = jp2k.read(reduce=2) + np.testing.assert_array_equal(fulldata[3:50, 13:30], ssdata) + + def test_NR_DEC_p0_04_j2k_94_decode(self): + jfile = os.path.join(data_root, 'input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(150, 10, 210, 190), reduce=2) + fulldata = jp2k.read(reduce=2) + np.testing.assert_array_equal(fulldata[38:53, 3:48], ssdata) + + def test_NR_DEC_p0_04_j2k_95_decode(self): + jfile = os.path.join(data_root, 'input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(80, 100, 150, 200), reduce=2) + fulldata = jp2k.read(reduce=2) + np.testing.assert_array_equal(fulldata[20:38, 25:50], ssdata) + + def test_NR_DEC_p0_04_j2k_96_decode(self): + jfile = os.path.join(data_root, 'input/conformance/p0_04.j2k') + jp2k = Jp2k(jfile) + ssdata = jp2k.read(area=(20, 150, 50, 200), reduce=2) + fulldata = jp2k.read(reduce=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_neg.py b/glymur/test/test_opj_suite_neg.py new file mode 100644 index 0000000..8296861 --- /dev/null +++ b/glymur/test/test_opj_suite_neg.py @@ -0,0 +1,154 @@ +""" +The tests here do not correspond directly to the OpenJPEG test suite, but +seem like logical negative tests to add. +""" +import os +import sys +import tempfile +import unittest +import warnings + +import numpy as np + +from ..lib import openjp2 as opj2 + +# Need some combination of matplotlib, PIL, or scikits-image for reading +# other image formats. +no_read_backend = False +msg = "Either scikit-image with the freeimage backend or matplotlib " +msg += "with the PIL backend must be available in order to run the " +msg += "tests in this suite." +no_read_backend_msg = msg +try: + import skimage.io + try: + skimage.io.use_plugin('freeimage') + from skimage.io import imread + except ImportError: + try: + skimage.io.use_plugin('PIL') + from skimage.io import imread + except ImportError: + raise +except ImportError: + try: + from PIL import Image + from matplotlib.pyplot import imread + except ImportError: + no_read_backend = True + +from glymur import Jp2k +import glymur + +try: + data_root = os.environ['OPJ_DATA_ROOT'] +except KeyError: + data_root = None +except: + raise + + +def read_image(infile): + # PIL issues warnings which we do not care about, so suppress them. + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + data = imread(infile) + return data + + +@unittest.skipIf(no_read_backend, no_read_backend_msg) +@unittest.skipIf(data_root is None, + "OPJ_DATA_ROOT environment variable not set") +class TestSuiteNegative(unittest.TestCase): + + def setUp(self): + pass + + def tearDown(self): + pass + + def test_negative_psnr_with_cratios(self): + # Using psnr with cratios options is not allowed. + # Not an OpenJPEG test, but close. + infile = os.path.join(data_root, 'input/nonregression/Bretagne1.ppm') + data = read_image(infile) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + with self.assertRaises(RuntimeError): + j.write(data, psnr=[30, 35, 40], cratios=[2, 3, 4]) + + @unittest.skipIf(sys.hexversion < 0x03020000, + "Uses features introduced in 3.2.") + def test_NR_MarkerIsNotCompliant_j2k_dump(self): + # SOT marker gives bad offset. + relpath = 'input/nonregression/MarkerIsNotCompliant.j2k' + jfile = os.path.join(data_root, relpath) + jp2k = Jp2k(jfile) + with self.assertWarns(UserWarning) as cw: + c = jp2k.get_codestream(header_only=False) + + # Verify that the last segment returned in the codestream is SOD, + # not EOC. Codestream parsing should stop when we try to jump to + # the end of SOT. + self.assertEqual(c.segment[-1].id, 'SOD') + + @unittest.skipIf(sys.hexversion < 0x03020000, + "Uses features introduced in 3.2.") + def test_NR_illegalcolortransform_dump(self): + # SOT marker gives bad offset. + relpath = 'input/nonregression/illegalcolortransform.j2k' + jfile = os.path.join(data_root, relpath) + jp2k = Jp2k(jfile) + with self.assertWarns(UserWarning) as cw: + c = jp2k.get_codestream(header_only=False) + + # Verify that the last segment returned in the codestream is SOD, + # not EOC. Codestream parsing should stop when we try to jump to + # the end of SOT. + self.assertEqual(c.segment[-1].id, 'SOD') + + @unittest.skipIf(sys.hexversion < 0x03020000, + "Uses features introduced in 3.2.") + def test_NR_Cannotreaddatawithnosizeknown_j2k(self): + # SOT marker gives bad offset. + relpath = 'input/nonregression/Cannotreaddatawithnosizeknown.j2k' + jfile = os.path.join(data_root, relpath) + jp2k = Jp2k(jfile) + with self.assertWarns(UserWarning) as cw: + c = jp2k.get_codestream(header_only=False) + + # Verify that the last segment returned in the codestream is SOD, + # not EOC. Codestream parsing should stop when we try to jump to + # the end of SOT. + self.assertEqual(c.segment[-1].id, 'SOD') + + def test_code_block_dimensions(self): + # 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(RuntimeError) as cr: + j.write(data, cbsize=(256, 256)) + + # opj_compress doesn't allow either dimension to be less than 4. + with self.assertRaises(RuntimeError) as cr: + j.write(data, cbsize=(2048, 2)) + with self.assertRaises(RuntimeError) as cr: + j.write(data, cbsize=(2, 2048)) + + @unittest.skipIf(sys.hexversion < 0x03020000, + "Uses features introduced in 3.2.") + def test_exceeded_box(self): + # 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(data_root, + 'input/nonregression/mem-b2ace68c-1381.jp2') + with self.assertWarns(UserWarning) as cw: + j = Jp2k(infile) + +if __name__ == "__main__": + unittest.main() diff --git a/glymur/test/test_opj_suite_write.py b/glymur/test/test_opj_suite_write.py new file mode 100644 index 0000000..486521f --- /dev/null +++ b/glymur/test/test_opj_suite_write.py @@ -0,0 +1,809 @@ +""" +The tests defined here roughly correspond to what is in the OpenJPEG test +suite. +""" +import os +import platform +import sys +import tempfile +import unittest +import warnings + +import numpy as np + +from ..lib import openjp2 as opj2 + +# Need some combination of matplotlib, PIL, or scikits-image for reading +# other image formats. +no_read_backend = False +msg = "Either scikit-image with the freeimage backend or matplotlib " +msg += "with the PIL backend must be available in order to run the " +msg += "tests in this suite." +no_read_backend_msg = msg +try: + import skimage.io + try: + skimage.io.use_plugin('freeimage') + from skimage.io import imread + except ImportError: + try: + skimage.io.use_plugin('PIL') + from skimage.io import imread + except ImportError: + raise +except ImportError: + try: + from PIL import Image + from matplotlib.pyplot import imread + except ImportError: + no_read_backend = True + +from glymur import Jp2k +import glymur + +try: + data_root = os.environ['OPJ_DATA_ROOT'] +except KeyError: + data_root = None +except: + raise + + +def read_image(infile): + # PIL issues warnings which we do not care about, so suppress them. + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + data = imread(infile) + return data + + +@unittest.skipIf(no_read_backend, no_read_backend_msg) +@unittest.skipIf(data_root is None, + "OPJ_DATA_ROOT environment variable not set") +class TestSuiteWrite(unittest.TestCase): + + def setUp(self): + pass + + def tearDown(self): + pass + + def test_NR_ENC_Bretagne1_ppm_1_encode(self): + # NR-ENC-Bretagne1.ppm-1-encode + infile = os.path.join(data_root, 'input/nonregression/Bretagne1.ppm') + data = read_image(infile) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + j.write(data, cratios=[200, 100, 50]) + + # Should be three layers. + c = j.get_codestream() + + # SIZ: Image and tile size + # Profile: "0" means profile 2 + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual((c.segment[1].Xsiz, c.segment[1].Ysiz), + (640, 480)) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), + (640, 480)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), + (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.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 + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_5x3_REVERSIBLE) + self.assertEqual(len(c.segment[2].SPcod), 9) + + def test_NR_ENC_Bretagne1_ppm_2_encode(self): + # NR-ENC-Bretagne1.ppm-2-encode + infile = os.path.join(data_root, 'input/nonregression/Bretagne1.ppm') + data = read_image(infile) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + j.write(data, psnr=[30, 35, 40], numres=2) + + # Should be three layers. + c = j.get_codestream() + + # SIZ: Image and tile size + # Profile: "0" means profile 2 + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual((c.segment[1].Xsiz, c.segment[1].Ysiz), + (640, 480)) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), + (640, 480)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), + (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.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], 1) # levels + self.assertEqual(tuple(c.segment[2]._code_block_size), + (64, 64)) # cblksz + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_5x3_REVERSIBLE) + self.assertEqual(len(c.segment[2].SPcod), 9) + + def test_NR_ENC_Bretagne1_ppm_3_encode(self): + # NR-ENC-Bretagne1.ppm-3-encode + infile = os.path.join(data_root, 'input/nonregression/Bretagne1.ppm') + data = read_image(infile) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + j.write(data, psnr=[30, 35, 40], cbsize=(16, 16), + psizes=[(64, 64)]) + + # Should be three layers. + c = j.get_codestream() + + # SIZ: Image and tile size + # Profile: "0" means profile 2 + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual((c.segment[1].Xsiz, c.segment[1].Ysiz), + (640, 480)) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), + (640, 480)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), + (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.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), + (16, 16)) # cblksz + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_5x3_REVERSIBLE) + self.assertEqual(c.segment[2]._precinct_size, + [(2, 2), (4, 4), (8, 8), (16, 16), (32, 32), + (64, 64)]) + + def test_NR_ENC_Bretagne2_ppm_4_encode(self): + infile = os.path.join(data_root, 'input/nonregression/Bretagne2.ppm') + data = read_image(infile) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + 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. + c = j.get_codestream() + + # SIZ: Image and tile size + # Profile: "0" means profile 2 + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual((c.segment[1].Xsiz, c.segment[1].Ysiz), + (data.shape[1], data.shape[0])) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size. Reported as XY, not RC. + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), + (640, 480)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), + (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.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), + (32, 32)) # cblksz + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_5x3_REVERSIBLE) + self.assertEqual(c.segment[2]._precinct_size, + [(16, 16), (32, 32), (64, 64)] + [(128, 128)] * 3) + + def test_NR_ENC_Bretagne2_ppm_5_encode(self): + # NR-ENC-Bretagne2.ppm-4-encode + infile = os.path.join(data_root, 'input/nonregression/Bretagne2.ppm') + data = read_image(infile) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + j.write(data, tilesize=(127, 127), prog="PCRL") + + c = j.get_codestream() + + # SIZ: Image and tile size + # Profile: "0" means profile 2 + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual((c.segment[1].Xsiz, c.segment[1].Ysiz), + (data.shape[1], data.shape[0])) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), + (127, 127)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), + (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.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.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], 5) # levels + self.assertEqual(tuple(c.segment[2]._code_block_size), + (64, 64)) # cblksz + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_5x3_REVERSIBLE) + self.assertEqual(len(c.segment[2].SPcod), 9) + + def test_NR_ENC_Bretagne2_ppm_6_encode(self): + # NR-ENC-Bretagne2.ppm-6-encode + infile = os.path.join(data_root, 'input/nonregression/Bretagne2.ppm') + data = read_image(infile) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + j.write(data, subsam=(2, 2), sop=True) + + c = j.get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "0" means profile 2 + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual((c.segment[1].Xsiz, c.segment[1].Ysiz), + (5183, 3887)) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), + (5183, 3887)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), + (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(2, 2)] * 3) + + # COD: Coding style default + self.assertTrue(c.segment[2].Scod & 2) # 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) # levels + self.assertEqual(tuple(c.segment[2]._code_block_size), + (64, 64)) # cblksz + # Selective arithmetic coding bypass + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # Reset context probabilities + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_5x3_REVERSIBLE) + self.assertEqual(len(c.segment[2].SPcod), 9) + + # 18 SOP segments. + nsops = [x.Nsop for x in c.segment if x.id == 'SOP'] + self.assertEqual(nsops, list(range(18))) + + def test_NR_ENC_Bretagne2_ppm_7_encode(self): + infile = os.path.join(data_root, 'input/nonregression/Bretagne2.ppm') + data = read_image(infile) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + j.write(data, modesw=38, eph=True) + + c = j.get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "0" means profile 2 + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual((c.segment[1].Xsiz, c.segment[1].Ysiz), + (2592, 1944)) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), + (2592, 1944)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), + (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)] * 3) + + # COD: Coding style default + self.assertFalse(c.segment[2].Scod & 2) # no 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, 1) # layers = 1 + 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 + # Selective arithmetic coding BYPASS + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # RESET context probabilities (RESET) + self.assertTrue(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass, RESTART(TERMALL) + self.assertTrue(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context (VSC) + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination, ERTERM(SEGTERM) + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols, SEGMARK(SEGSYSM) + self.assertTrue(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_5x3_REVERSIBLE) + self.assertEqual(len(c.segment[2].SPcod), 9) + + # 18 EPH segments. + ephs = [x for x in c.segment if x.id == 'EPH'] + self.assertEqual(len(ephs), 18) + + def test_NR_ENC_Bretagne2_ppm_8_encode(self): + infile = os.path.join(data_root, 'input/nonregression/Bretagne2.ppm') + data = read_image(infile) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + j.write(data, grid_offset=[300, 150], cratios=[800]) + + c = j.get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "0" means profile 2 + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual((c.segment[1].Xsiz, c.segment[1].Ysiz), + (2742, 2244)) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), + (150, 300)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), + (2742, 2244)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), + (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.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, 1) # layers = 1 + 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 + # Selective arithmetic coding BYPASS + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # RESET context probabilities (RESET) + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass, RESTART(TERMALL) + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context (VSC) + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination, ERTERM(SEGTERM) + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols, SEGMARK(SEGSYSM) + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_5x3_REVERSIBLE) + self.assertEqual(len(c.segment[2].SPcod), 9) + + def test_NR_ENC_Cevennes1_bmp_9_encode(self): + infile = os.path.join(data_root, 'input/nonregression/Cevennes1.bmp') + data = read_image(infile) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + j.write(data, cratios=[800]) + + c = j.get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "0" means profile 2 + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual((c.segment[1].Xsiz, c.segment[1].Ysiz), + (2592, 1944)) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), + (2592, 1944)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), + (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.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, 1) # layers = 1 + 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 + # Selective arithmetic coding BYPASS + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # RESET context probabilities (RESET) + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass, RESTART(TERMALL) + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context (VSC) + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination, ERTERM(SEGTERM) + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols, SEGMARK(SEGSYSM) + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_5x3_REVERSIBLE) + self.assertEqual(len(c.segment[2].SPcod), 9) + + def test_NR_ENC_Cevennes2_ppm_10_encode(self): + infile = os.path.join(data_root, 'input/nonregression/Cevennes2.ppm') + data = read_image(infile) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + j.write(data, cratios=[50]) + + c = j.get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "0" means profile 2 + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual((c.segment[1].Xsiz, c.segment[1].Ysiz), + (640, 480)) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), + (640, 480)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), + (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.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, 1) # layers = 1 + 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 + # Selective arithmetic coding BYPASS + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # RESET context probabilities (RESET) + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass, RESTART(TERMALL) + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context (VSC) + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination, ERTERM(SEGTERM) + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols, SEGMARK(SEGSYSM) + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_5x3_REVERSIBLE) + self.assertEqual(len(c.segment[2].SPcod), 9) + + def test_NR_ENC_Rome_bmp_11_encode(self): + infile = os.path.join(data_root, 'input/nonregression/Rome.bmp') + data = read_image(infile) + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + jp2 = Jp2k(tfile.name, 'wb') + jp2.write(data, psnr=[30, 35, 50], prog='LRCP', numres=3) + + ids = [box.id for box in jp2.box] + lst = ['jP ', 'ftyp', 'jp2h', 'jp2c'] + self.assertEqual(ids, lst) + + ids = [box.id for box in jp2.box[2].box] + self.assertEqual(ids, ['ihdr', 'colr']) + + # Signature box. Check for corruption. + self.assertEqual(jp2.box[0].signature, (13, 10, 135, 10)) + + # File type box. + self.assertEqual(jp2.box[1].brand, 'jp2 ') + self.assertEqual(jp2.box[1].minor_version, 0) + self.assertEqual(jp2.box[1].compatibility_list[0], 'jp2 ') + + # Jp2 Header + # Image header + self.assertEqual(jp2.box[2].box[0].height, 480) + self.assertEqual(jp2.box[2].box[0].width, 640) + self.assertEqual(jp2.box[2].box[0].num_components, 3) + self.assertEqual(jp2.box[2].box[0].bits_per_component, 8) + self.assertEqual(jp2.box[2].box[0].signed, False) + self.assertEqual(jp2.box[2].box[0].compression, 7) # wavelet + self.assertEqual(jp2.box[2].box[0].cspace_unknown, False) + self.assertEqual(jp2.box[2].box[0].ip_provided, False) + + # Jp2 Header + # Colour specification + self.assertEqual(jp2.box[2].box[1].method, 1) + self.assertEqual(jp2.box[2].box[1].precedence, 0) + self.assertEqual(jp2.box[2].box[1].approximation, 0) + self.assertIsNone(jp2.box[2].box[1].icc_profile) + self.assertEqual(jp2.box[2].box[1].color_space, glymur.core.SRGB) + + c = jp2.box[3].main_header + + # SIZ: Image and tile size + # Profile: "0" means profile 2 + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual((c.segment[1].Xsiz, c.segment[1].Ysiz), + (640, 480)) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), + (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), + (640, 480)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), + (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (8, 8, 8)) + # signed + self.assertEqual(c.segment[1]._signed, (False, False, False)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.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], 2) # levels + self.assertEqual(tuple(c.segment[2]._code_block_size), + (64, 64)) # cblksz + # Selective arithmetic coding BYPASS + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # RESET context probabilities (RESET) + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass, RESTART(TERMALL) + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context (VSC) + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination, ERTERM(SEGTERM) + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols, SEGMARK(SEGSYSM) + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_5x3_REVERSIBLE) + self.assertEqual(len(c.segment[2].SPcod), 9) + + @unittest.skip("Known failure in openjpeg test suite.") + def test_NR_ENC_random_issue_0005_tif_12_encode(self): + # opj_decompress has trouble reading it, but that is not an issue here. + # The nature of the image itself seems to give the compressor trouble. + infile = os.path.join(data_root, + 'input/nonregression/random-issue-0005.tif') + data = read_image(infile) + with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile: + j = Jp2k(tfile.name, 'wb') + j.write(data) + + c = j.get_codestream(header_only=False) + + # SIZ: Image and tile size + # Profile: "0" means profile 2 + self.assertEqual(c.segment[1].Rsiz, 0) + # Reference grid size + self.assertEqual((c.segment[1].Xsiz, c.segment[1].Ysiz), + (1024, 1024)) + # Reference grid offset + self.assertEqual((c.segment[1].XOsiz, c.segment[1].YOsiz), (0, 0)) + # Tile size + self.assertEqual((c.segment[1].XTsiz, c.segment[1].YTsiz), + (1024, 1024)) + # Tile offset + self.assertEqual((c.segment[1].XTOsiz, c.segment[1].YTOsiz), + (0, 0)) + # bitdepth + self.assertEqual(c.segment[1]._bitdepth, (16,)) + # signed + self.assertEqual(c.segment[1]._signed, (False,)) + # subsampling + self.assertEqual(list(zip(c.segment[1].XRsiz, c.segment[1].YRsiz)), + [(1, 1)]) + + # 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) # levels + self.assertEqual(tuple(c.segment[2]._code_block_size), + (64, 64)) # cblksz + # Selective arithmetic coding BYPASS + self.assertFalse(c.segment[2].SPcod[7] & 0x01) + # RESET context probabilities (RESET) + self.assertFalse(c.segment[2].SPcod[7] & 0x02) + # Termination on each coding pass, RESTART(TERMALL) + self.assertFalse(c.segment[2].SPcod[7] & 0x04) + # Vertically causal context (VSC) + self.assertFalse(c.segment[2].SPcod[7] & 0x08) + # Predictable termination, ERTERM(SEGTERM) + self.assertFalse(c.segment[2].SPcod[7] & 0x0010) + # Segmentation symbols, SEGMARK(SEGSYSM) + self.assertFalse(c.segment[2].SPcod[7] & 0x0020) + self.assertEqual(c.segment[2].SPcod[8], + glymur.core.WAVELET_TRANSFORM_5x3_REVERSIBLE) + self.assertEqual(len(c.segment[2].SPcod), 9) + +if __name__ == "__main__": + unittest.main() diff --git a/glymur/test/test_printing.py b/glymur/test/test_printing.py new file mode 100644 index 0000000..299085d --- /dev/null +++ b/glymur/test/test_printing.py @@ -0,0 +1,767 @@ +import os +import pkg_resources +import struct +import sys +import tempfile +import unittest + +if sys.hexversion < 0x03000000: + from StringIO import StringIO +else: + from io import StringIO + +import glymur + +try: + data_root = os.environ['OPJ_DATA_ROOT'] +except KeyError: + data_root = None +except: + raise + + +class TestPrinting(unittest.TestCase): + + def setUp(self): + # Save sys.stdout. + self.stdout = sys.stdout + sys.stdout = StringIO() + self.jp2file = pkg_resources.resource_filename(glymur.__name__, + "data/nemo.jp2") + + # Save the output of dumping nemo.jp2 for more than one test. + lines = ['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', + 'Contiguous Codestream Box (jp2c) @ (77, 1133427)', + ' Main header:', + ' SOC marker segment @ (85, 0)', + ' SIZ marker segment @ (87, 47)', + ' Profile: 2', + ' Reference Grid Height, Width: (1456 x 2592)', + ' Vertical, Horizontal Reference Grid Offset: ' + + '(0 x 0)', + ' Reference Tile Height, Width: (512 x 512)', + ' 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: 3', + ' 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.expectedNemo = '\n'.join(lines) + + def tearDown(self): + # Restore stdout. + sys.stdout = self.stdout + + def test_jp2dump(self): + glymur.jp2dump(self.jp2file) + actual = sys.stdout.getvalue().strip() + self.assertEqual(actual, self.expectedNemo) + + def test_COC_segment(self): + j = glymur.Jp2k(self.jp2file) + codestream = j.get_codestream(header_only=False) + print(codestream.segment[5]) + actual = sys.stdout.getvalue().strip() + + lines = ['COC marker segment @ (183, 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'] + + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + def test_COD_segment(self): + j = glymur.Jp2k(self.jp2file) + codestream = j.get_codestream() + print(codestream.segment[2]) + actual = sys.stdout.getvalue().strip() + + lines = ['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: 3', + ' 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'] + + expected = '\n'.join(lines) + self.actual = actual + self.expected = expected + self.assertEqual(actual, expected) + + @unittest.skipIf(data_root is None, + "OPJ_DATA_ROOT environment variable not set") + def test_CRG(self): + filename = os.path.join(data_root, 'input/conformance/p0_03.j2k') + j = glymur.Jp2k(filename) + codestream = j.get_codestream() + print(codestream.segment[-5]) + actual = sys.stdout.getvalue().strip() + lines = ['CRG marker segment at (87, 6)', + ' Vertical, Horizontal offset: (0.50, 1.00)'] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + @unittest.skipIf(data_root is None, + "OPJ_DATA_ROOT environment variable not set") + def test_RGN(self): + filename = os.path.join(data_root, 'input/conformance/p0_03.j2k') + j = glymur.Jp2k(filename) + codestream = j.get_codestream(header_only=False) + print(codestream.segment[12]) + actual = sys.stdout.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(data_root is None, + "OPJ_DATA_ROOT environment variable not set") + def test_SOP(self): + filename = os.path.join(data_root, 'input/conformance/p0_03.j2k') + j = glymur.Jp2k(filename) + codestream = j.get_codestream(header_only=False) + print(codestream.segment[-2]) + actual = sys.stdout.getvalue().strip() + lines = ['SOP marker segment @ (12836, 4)', + ' Nsop: 15'] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + @unittest.skipIf(data_root is None, + "OPJ_DATA_ROOT environment variable not set") + def test_CME(self): + # Test printing a CME or comment marker segment. + filename = os.path.join(data_root, 'input/conformance/p0_02.j2k') + j = glymur.Jp2k(filename) + codestream = j.get_codestream() + # 2nd to last segment in the main header + print(codestream.segment[-2]) + actual = sys.stdout.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): + j = glymur.Jp2k(self.jp2file) + codestream = j.get_codestream(header_only=False) + print(codestream.segment[-1]) + actual = sys.stdout.getvalue().strip() + + lines = ['EOC marker segment @ (1133502, 0)'] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + @unittest.skipIf(data_root is None, + "OPJ_DATA_ROOT environment variable not set") + def test_PLT_segment(self): + filename = os.path.join(data_root, 'input/conformance/p0_07.j2k') + j = glymur.Jp2k(filename) + codestream = j.get_codestream(header_only=False) + print(codestream.segment[49935]) + actual = sys.stdout.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(data_root is None, + "OPJ_DATA_ROOT environment variable not set") + def test_POD_segment(self): + filename = os.path.join(data_root, 'input/conformance/p0_13.j2k') + j = glymur.Jp2k(filename) + codestream = j.get_codestream() + print(codestream.segment[8]) + actual = sys.stdout.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(data_root is None, + "OPJ_DATA_ROOT environment variable not set") + def test_PPM_segment(self): + filename = os.path.join(data_root, 'input/conformance/p1_03.j2k') + j = glymur.Jp2k(filename) + codestream = j.get_codestream() + print(codestream.segment[9]) + actual = sys.stdout.getvalue().strip() + + lines = ['PPM marker segment @ (213, 43712)', + ' Index: 0', + ' Data: 43709 uninterpreted bytes'] + + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + @unittest.skipIf(data_root is None, + "OPJ_DATA_ROOT environment variable not set") + def test_PPT_segment(self): + filename = os.path.join(data_root, 'input/conformance/p1_06.j2k') + j = glymur.Jp2k(filename) + codestream = j.get_codestream(header_only=False) + print(codestream.segment[6]) + actual = sys.stdout.getvalue().strip() + + lines = ['PPT marker segment @ (155, 109)', + ' Index: 0', + ' Packet headers: 106 uninterpreted bytes'] + + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + def test_QCC_segment(self): + j = glymur.Jp2k(self.jp2file) + codestream = j.get_codestream(header_only=False) + print(codestream.segment[6]) + actual = sys.stdout.getvalue().strip() + + lines = ['QCC marker segment @ (194, 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)]'] + + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + def test_QCD_segment_5x3_transform(self): + j = glymur.Jp2k(self.jp2file) + codestream = j.get_codestream() + print(codestream.segment[3]) + actual = sys.stdout.getvalue().strip() + + lines = ['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)]'] + + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + def test_SIZ_segment(self): + j = glymur.Jp2k(self.jp2file) + codestream = j.get_codestream() + print(codestream.segment[1]) + actual = sys.stdout.getvalue().strip() + + lines = ['SIZ marker segment @ (87, 47)', + ' Profile: 2', + ' Reference Grid Height, Width: (1456 x 2592)', + ' Vertical, Horizontal Reference Grid Offset: (0 x 0)', + ' Reference Tile Height, Width: (512 x 512)', + ' 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))'] + + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + def test_SOC_segment(self): + j = glymur.Jp2k(self.jp2file) + codestream = j.get_codestream() + print(codestream.segment[0]) + actual = sys.stdout.getvalue().strip() + + lines = ['SOC marker segment @ (85, 0)'] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + def test_SOD_segment(self): + j = glymur.Jp2k(self.jp2file) + codestream = j.get_codestream(header_only=False) + print(codestream.segment[9]) + actual = sys.stdout.getvalue().strip() + + lines = ['SOD marker segment @ (249, 0)'] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + def test_SOT_segment(self): + j = glymur.Jp2k(self.jp2file) + codestream = j.get_codestream(header_only=False) + print(codestream.segment[4]) + actual = sys.stdout.getvalue().strip() + + lines = ['SOT marker segment @ (171, 10)', + ' Tile part index: 0', + ' Tile part length: 78629', + ' Tile part instance: 0', + ' Number of tile parts: 1'] + + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + @unittest.skipIf(data_root is None, + "OPJ_DATA_ROOT environment variable not set") + def test_TLM_segment(self): + filename = os.path.join(data_root, 'input/conformance/p0_15.j2k') + j = glymur.Jp2k(filename) + codestream = j.get_codestream() + print(codestream.segment[10]) + actual = sys.stdout.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) + + def test_entire_file(self): + j = glymur.Jp2k(self.jp2file) + print(j) + actual = sys.stdout.getvalue().strip() + self.assertEqual(actual, self.expectedNemo) + + def test_codestream(self): + j = glymur.Jp2k(self.jp2file) + print(j.get_codestream()) + actual = sys.stdout.getvalue().strip() + + lst = ['Codestream:', + ' SOC marker segment @ (85, 0)', + ' SIZ marker segment @ (87, 47)', + ' Profile: 2', + ' Reference Grid Height, Width: (1456 x 2592)', + ' Vertical, Horizontal Reference Grid Offset: (0 x 0)', + ' Reference Tile Height, Width: (512 x 512)', + ' 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: 3', + ' 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)]'] + expected = '\n'.join(lst) + self.assertEqual(actual, expected) + + @unittest.skipIf(data_root is None, + "OPJ_DATA_ROOT environment variable not set") + def test_xml(self): + filename = os.path.join(data_root, 'input/conformance/file1.jp2') + j = glymur.Jp2k(filename) + print(j.box[2]) + actual = sys.stdout.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(data_root is None, + "OPJ_DATA_ROOT environment variable not set") + def test_component_definition(self): + filename = os.path.join(data_root, 'input/conformance/file2.jp2') + j = glymur.Jp2k(filename) + print(j.box[2].box[2]) + actual = sys.stdout.getvalue().strip() + lines = ['Component Definition Box (cdef) @ (81, 28)', + ' Component 0 (color) ==> (3)', + ' Component 1 (color) ==> (2)', + ' Component 2 (color) ==> (1)'] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + @unittest.skipIf(data_root is None, + "OPJ_DATA_ROOT environment variable not set") + def test_component_mapping(self): + filename = os.path.join(data_root, 'input/conformance/file9.jp2') + j = glymur.Jp2k(filename) + print(j.box[2].box[2]) + actual = sys.stdout.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(data_root is None, + "OPJ_DATA_ROOT environment variable not set") + def test_palette(self): + filename = os.path.join(data_root, 'input/conformance/file9.jp2') + j = glymur.Jp2k(filename) + print(j.box[2].box[1]) + actual = sys.stdout.getvalue().strip() + lines = ['Palette Box (pclr) @ (66, 782)', + ' Size: (256 x 3)'] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + @unittest.skipIf(data_root is None, + "OPJ_DATA_ROOT environment variable not set") + def test_palette(self): + filename = os.path.join(data_root, 'input/conformance/file7.jp2') + j = glymur.Jp2k(filename) + print(j.box[2]) + actual = sys.stdout.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(data_root is None, + "OPJ_DATA_ROOT environment variable not set") + def test_CRG(self): + filename = os.path.join(data_root, 'input/conformance/p0_03.j2k') + j = glymur.Jp2k(filename) + codestream = j.get_codestream() + print(codestream.segment[6]) + actual = sys.stdout.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(data_root is None, + "OPJ_DATA_ROOT environment variable not set") + def test_differing_subsamples(self): + # Issue 86. + filename = os.path.join(data_root, 'input/conformance/p0_05.j2k') + j = glymur.Jp2k(filename) + codestream = j.get_codestream() + print(codestream.segment[1]) + actual = sys.stdout.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(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 = os.path.join(data_root, 'input/conformance/file9.jp2') + j = glymur.Jp2k(filename) + print(j.box[2].box[1]) + actual = sys.stdout.getvalue().strip() + lines = ['Palette Box (pclr) @ (66, 782)', + ' Size: (256 x 3)'] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + def test_asoc_label_box(self): + # Construct a fake file with an asoc and a label box, as + # OpenJPEG doesn't have such a file. + data = glymur.Jp2k(self.jp2file).read(reduce=3) + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + j = glymur.Jp2k(tfile.name, 'wb') + j.write(data) + + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile2: + + # Offset of the codestream is where we start. + buffer = tfile.read(77) + tfile2.write(buffer) + + # read the rest of the file, it's the codestream. + codestream = tfile.read() + + # Write the asoc superbox. + # Length = 36, id is 'asoc'. + buffer = struct.pack('>I4s', int(56), b'asoc') + tfile2.write(buffer) + + # Write the contained label box + buffer = struct.pack('>I4s', int(13), b'lbl ') + tfile2.write(buffer) + tfile2.write('label'.encode()) + + # Write the xml box + # Length = 36, id is 'xml '. + buffer = struct.pack('>I4s', int(35), b'xml ') + tfile2.write(buffer) + + buffer = 'this is a test' + buffer = buffer.encode() + tfile2.write(buffer) + + # Now append the codestream. + tfile2.write(codestream) + + jasoc = glymur.Jp2k(tfile2.name) + print(jasoc.box[3]) + actual = sys.stdout.getvalue().strip() + lines = ['Association Box (asoc) @ (77, 56)', + ' Label Box (lbl ) @ (85, 13)', + ' Label: label', + ' XML Box (xml ) @ (98, 35)', + ' this is a test'] + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + def test_less_common_boxes(self): + with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile: + with open(self.jp2file, 'rb') as ifile: + # Everything up until the jp2c box. + buffer = ifile.read(77) + tfile.write(buffer) + + # Write the UINF superbox + # Length = 50, id is uinf. + buffer = struct.pack('>I4s', int(50), b'uinf') + tfile.write(buffer) + + # Write the ULST box. + # Length is 26, 1 UUID, hard code that UUID as zeros. + buffer = struct.pack('>I4sHIIII', int(26), b'ulst', int(1), + int(0), int(0), int(0), int(0)) + tfile.write(buffer) + + # Write the URL box. + # Length is 16, version is one byte, flag is 3 bytes, url + # is the rest. + buffer = struct.pack('>I4sBBBB', + int(16), b'url ', + int(0), int(0), int(0), int(0)) + tfile.write(buffer) + buffer = struct.pack('>ssss', b'a', b'b', b'c', b'd') + tfile.write(buffer) + + # Start the resolution superbox. + buffer = struct.pack('>I4s', int(44), b'res ') + tfile.write(buffer) + + # Write the capture resolution box. + buffer = struct.pack('>I4sHHHHBB', + int(18), b'resc', + int(1), int(1), int(1), int(1), + int(0), int(1)) + tfile.write(buffer) + + # Write the display resolution box. + buffer = struct.pack('>I4sHHHHBB', + int(18), b'resd', + int(1), int(1), int(1), int(1), + int(1), int(0)) + tfile.write(buffer) + + # Get the rest of the input file. + buffer = ifile.read() + tfile.write(buffer) + tfile.flush() + + jp2k = glymur.Jp2k(tfile.name) + print(jp2k.box[3]) + print(jp2k.box[4]) + actual = sys.stdout.getvalue().strip() + lines = ['UUIDInfo Box (uinf) @ (77, 50)', + ' UUID List Box (ulst) @ (85, 26)', + ' UUID[0]: 00000000-0000-0000-0000-000000000000', + ' Data Entry URL Box (url ) @ (111, 16)', + ' Version: 0', + ' Flag: 0 0 0', + ' URL: "abcd"', + 'Resolution Box (res ) @ (127, 44)', + ' Capture Resolution Box (resc) @ (135, 18)', + ' VCR: 1.0', + ' HCR: 10.0', + ' Display Resolution Box (resd) @ (153, 18)', + ' VDR: 10.0', + ' HDR: 1.0'] + + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + @unittest.skipIf(data_root is None, + "OPJ_DATA_ROOT environment variable not set") + def test_jpx_approximation_with_icc_profile(self): + # ICC profiles may be used in JP2, but the approximation field should + # be zero unless we have jpx. This file does both. + filename = os.path.join(data_root, 'input/nonregression/text_GBR.jp2') + j = glymur.Jp2k(filename) + + print(j.box[3].box[1]) + actual = sys.stdout.getvalue().strip() + lines = ['Colour Specification Box (colr) @ (179, 1339)', + ' Method: any ICC profile', + ' Precedence: 2', + ' Approximation: accurately represents ' + + 'correct colorspace definition', + ' ICC Profile: 1328 bytes'] + + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + + @unittest.skipIf(data_root is None, + "OPJ_DATA_ROOT environment variable not set") + def test_uuid(self): + # UUID box + filename = os.path.join(data_root, 'input/nonregression/text_GBR.jp2') + j = glymur.Jp2k(filename) + + print(j.box[4]) + actual = sys.stdout.getvalue().strip() + lines = ['UUID Box (uuid) @ (1544, 25)', + ' UUID: 3a0d0218-0ae9-4115-b376-4bca41ce0e71', + ' UUID Data: 1 bytes'] + + expected = '\n'.join(lines) + self.assertEqual(actual, expected) + +if __name__ == "__main__": + unittest.main() diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..b2b693f --- /dev/null +++ b/setup.py @@ -0,0 +1,28 @@ +from distutils.core import setup + +kwargs = {'name': 'glymur', + 'version': '0.1.0', + 'description': 'Tools for manipulating JPEG2000 files', + 'long_description': open('README').read(), + 'author': 'John Evans', + 'author_email': 'johnevans938 at gmail dot com', + 'url': 'https://bitbucket.org/johnevans007/jptools', + 'packages': ['glymur', 'glymur.test', 'glymur.lib', + 'glymur.lib.test'], + 'package_data': {'glymur': ['data/*.jp2']}, + 'scripts': ['bin/jp2dump'], + 'license': 'LICENSE.txt', + 'platforms': ['darwin']} +clssfrs = ["Programming Language :: Python", + "Programming Language :: Python :: 3.2", + "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: Implementation :: CPython", + "License :: OSI Approved :: MIT License", + "Development Status :: 1 - Alpha", + "Operating System :: MacOS", + "Operating System :: POSIX :: Linux", + "Intended Audience :: Scientific/Research", + "Intended Audience :: Information Technology", + "Topic :: Software Development :: Libraries :: Python Modules"] +kwargs['classifiers'] = clssfrs +setup(**kwargs)