Initial commit.

This commit is contained in:
John Evans 2013-05-27 00:10:10 -04:00
commit e056e898cc
32 changed files with 16874 additions and 0 deletions

0
CHANGES.txt Normal file
View file

14
MANIFEST Normal file
View file

@ -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

9
README Normal file
View file

@ -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.

4
TODO Normal file
View file

@ -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

15
bin/jp2dump Executable file
View file

@ -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)

153
docs/Makefile Normal file
View file

@ -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 <target>' where <target> 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."

190
docs/make.bat Normal file
View file

@ -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 ^<target^>` where ^<target^> 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

5
docs/source/api.rst Normal file
View file

@ -0,0 +1,5 @@
API
===
.. autoclass:: glymur.Jp2k
:members:

248
docs/source/conf.py Normal file
View file

@ -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
# "<project> v<release> 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 <link> 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}

24
docs/source/how_do_i.rst Normal file
View file

@ -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())

25
docs/source/index.rst Normal file
View file

@ -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`

View file

@ -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.

99
glymur/__init__.py Normal file
View file

@ -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

1714
glymur/codestream.py Normal file

File diff suppressed because it is too large Load diff

123
glymur/core.py Normal file
View file

@ -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'}

BIN
glymur/data/nemo.jp2 Normal file

Binary file not shown.

1763
glymur/jp2box.py Normal file

File diff suppressed because it is too large Load diff

22
glymur/jp2dump.py Normal file
View file

@ -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)

671
glymur/jp2k.py Normal file
View file

@ -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

3
glymur/lib/__init__.py Normal file
View file

@ -0,0 +1,3 @@
"""This package organizes individual libraries employed by glymur."""
from . import openjp2
from . import test

1309
glymur/lib/openjp2.py Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1 @@
from .test_openjp2 import TestOpenJP2 as openjp2

View file

@ -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()

7
glymur/test/__init__.py Normal file
View file

@ -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

View file

@ -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()

View file

@ -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()

602
glymur/test/test_jp2k.py Normal file
View file

@ -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 = '<test>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 = '<test>this is a test</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 = '<test>this is a test</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()

File diff suppressed because it is too large Load diff

View file

@ -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()

View file

@ -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()

View file

@ -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)',
' <ns0:IMAGE_CREATION '
+ 'xmlns:ns0="http://www.jpeg.org/jpx/1.0/xml" '
+ 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" '
+ 'xsi:schemaLocation="http://www.jpeg.org/jpx/1.0/xml '
+ 'http://www.jpeg.org/metadata/15444-2.xsd">',
' <ns0:GENERAL_CREATION_INFO>',
' <ns0:CREATION_TIME>'
+ '2001-11-01T13:45:00.000-06:00'
+ '</ns0:CREATION_TIME>',
' <ns0:IMAGE_SOURCE>'
+ 'Professional 120 Image'
+ '</ns0:IMAGE_SOURCE>',
' </ns0:GENERAL_CREATION_INFO>',
' </ns0:IMAGE_CREATION>']
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 = '<test>this is a test</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)',
' <test>this is a test</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()

28
setup.py Normal file
View file

@ -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)