Compare commits
1 commit
master
...
release-0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5936706f0e |
56 changed files with 5578 additions and 13455 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,2 +0,0 @@
|
||||||
*.pyc
|
|
||||||
*.swp
|
|
||||||
13
.travis.yml
13
.travis.yml
|
|
@ -1,5 +1,6 @@
|
||||||
language: python
|
language: python
|
||||||
python:
|
python:
|
||||||
|
- "2.6"
|
||||||
- "2.7"
|
- "2.7"
|
||||||
- "3.3"
|
- "3.3"
|
||||||
- "3.4"
|
- "3.4"
|
||||||
|
|
@ -12,13 +13,17 @@ before_install:
|
||||||
|
|
||||||
# command to install dependencies
|
# command to install dependencies
|
||||||
install:
|
install:
|
||||||
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install lxml contextlib2 mock six; fi
|
- if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install --use-mirrors contextlib2 mock ordereddict unittest2; fi
|
||||||
- if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then pip install lxml numpy six; fi
|
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --use-mirrors contextlib2 mock; fi
|
||||||
- if [[ $TRAVIS_PYTHON_VERSION == '3.4' ]]; then pip install lxml numpy six; fi
|
- if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then pip install --use-mirrors numpy; fi
|
||||||
|
- if [[ $TRAVIS_PYTHON_VERSION == '3.4' ]]; then pip install --use-mirrors numpy; fi
|
||||||
|
|
||||||
# command to run tests
|
# command to run tests
|
||||||
script:
|
script:
|
||||||
- python -m unittest discover
|
- if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then unit2 discover; fi
|
||||||
|
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then python -m unittest discover; fi
|
||||||
|
- if [[ $TRAVIS_PYTHON_VERSION == '3.3' ]]; then python -m unittest discover; fi
|
||||||
|
- if [[ $TRAVIS_PYTHON_VERSION == '3.4' ]]; then python -m unittest discover; fi
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
email: "john.g.evans.ne@gmail.com"
|
email: "john.g.evans.ne@gmail.com"
|
||||||
|
|
|
||||||
25
CHANGES.txt
25
CHANGES.txt
|
|
@ -1,26 +1,5 @@
|
||||||
Jan 10, 2015 - v0.8.0 Reduced number of steps required for writing
|
May 09, 2014 - v0.5.11 Added support for Python 3.4, OpenJPEG 2.0.1, and
|
||||||
images. Deprecated old read and write methods in favor of
|
OpenJPEG 2.1.0.
|
||||||
array-style slicing. Added ignore_pclr_cmap_cdef, verbose,
|
|
||||||
shape, codestream, layer properties.
|
|
||||||
|
|
||||||
Oct 06, 2014 - v0.7.2 Added ellipsis support in array-style slicing.
|
|
||||||
|
|
||||||
Oct 02, 2014 - v0.7.1 Fixed README to mention Python 3.4
|
|
||||||
|
|
||||||
Oct 01, 2014 - v0.7.0 Added array-style slicing.
|
|
||||||
|
|
||||||
August 03, 2014 - v0.6.0 Added Cinema2K, Cinema4K write support.
|
|
||||||
Changed constructor for ChannelDefinition box. Removed support
|
|
||||||
for Python 2.6. Added write support for JP2 UUID, DataEntryURL,
|
|
||||||
Palette and Component Mapping boxes, JPX Association, NumberList
|
|
||||||
and DataReference boxes. Added read support for JPX free,
|
|
||||||
number list, data reference, fragment table, and fragment list
|
|
||||||
boxes. Improved JPX Reader Requirements box support. Added
|
|
||||||
get_printoptions, set_printoptions functions. Palette box now
|
|
||||||
a 2D numpy array instead of a list of 1D arrays. JP2 super box
|
|
||||||
constructors now take optional box list argument. Fixed bug
|
|
||||||
where JPX files with more than one codestream but advertising
|
|
||||||
jp2 compatibility were not being read.
|
|
||||||
|
|
||||||
Jan 28, 2014 - v0.5.10 Fixed bad warning when reader requirements box mask
|
Jan 28, 2014 - v0.5.10 Fixed bad warning when reader requirements box mask
|
||||||
length is unsupported.
|
length is unsupported.
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,6 @@ glymur: a Python interface for JPEG 2000
|
||||||
|
|
||||||
**glymur** contains a Python interface to the OpenJPEG library which
|
**glymur** contains a Python interface to the OpenJPEG library which
|
||||||
allows one to read and write JPEG 2000 files. **glymur** works on
|
allows one to read and write JPEG 2000 files. **glymur** works on
|
||||||
Python 2.7, 3.3, and 3.4.
|
Python 2.6, 2.7, 3.3, and 3.4.
|
||||||
|
|
||||||
Please read the docs, https://glymur.readthedocs.org/en/latest/
|
Please read the docs, https://glymur.readthedocs.org/en/latest/
|
||||||
|
|
|
||||||
15
bin/jp2dump
Executable file
15
bin/jp2dump
Executable 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)
|
||||||
116
docs/source/api.rst
Normal file
116
docs/source/api.rst
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
---
|
||||||
|
API
|
||||||
|
---
|
||||||
|
|
||||||
|
Jp2k
|
||||||
|
----
|
||||||
|
.. autoclass:: glymur.Jp2k
|
||||||
|
:members: read, write, wrap, read_bands, get_codestream
|
||||||
|
|
||||||
|
Individual Boxes
|
||||||
|
----------------
|
||||||
|
Jp2kbox
|
||||||
|
'''''''
|
||||||
|
.. autoclass:: glymur.jp2box.Jp2kBox
|
||||||
|
:members:
|
||||||
|
|
||||||
|
AssociationBox
|
||||||
|
''''''''''''''
|
||||||
|
.. autoclass:: glymur.jp2box.AssociationBox
|
||||||
|
:members:
|
||||||
|
|
||||||
|
ColourSpecificationBox
|
||||||
|
''''''''''''''''''''''
|
||||||
|
.. autoclass:: glymur.jp2box.ColourSpecificationBox
|
||||||
|
:members:
|
||||||
|
|
||||||
|
ChannelDefinitionBox
|
||||||
|
''''''''''''''''''''''
|
||||||
|
.. autoclass:: glymur.jp2box.ChannelDefinitionBox
|
||||||
|
:members:
|
||||||
|
|
||||||
|
ComponentMappingBox
|
||||||
|
'''''''''''''''''''
|
||||||
|
.. autoclass:: glymur.jp2box.ComponentMappingBox
|
||||||
|
:members:
|
||||||
|
|
||||||
|
ContiguousCodestreamBox
|
||||||
|
'''''''''''''''''''''''
|
||||||
|
.. autoclass:: glymur.jp2box.ContiguousCodestreamBox
|
||||||
|
:members:
|
||||||
|
|
||||||
|
DataEntryURLBox
|
||||||
|
'''''''''''''''
|
||||||
|
.. autoclass:: glymur.jp2box.DataEntryURLBox
|
||||||
|
:members:
|
||||||
|
|
||||||
|
FileTypeBox
|
||||||
|
'''''''''''
|
||||||
|
.. autoclass:: glymur.jp2box.FileTypeBox
|
||||||
|
:members:
|
||||||
|
|
||||||
|
ImageHeaderBox
|
||||||
|
''''''''''''''
|
||||||
|
.. autoclass:: glymur.jp2box.ImageHeaderBox
|
||||||
|
:members:
|
||||||
|
|
||||||
|
JP2HeaderBox
|
||||||
|
''''''''''''
|
||||||
|
.. autoclass:: glymur.jp2box.JP2HeaderBox
|
||||||
|
:members:
|
||||||
|
|
||||||
|
JPEG2000SignatureBox
|
||||||
|
''''''''''''''''''''
|
||||||
|
.. autoclass:: glymur.jp2box.JPEG2000SignatureBox
|
||||||
|
:members:
|
||||||
|
|
||||||
|
LabelBox
|
||||||
|
''''''''
|
||||||
|
.. autoclass:: glymur.jp2box.LabelBox
|
||||||
|
:members:
|
||||||
|
|
||||||
|
PaletteBox
|
||||||
|
''''''''''
|
||||||
|
.. autoclass:: glymur.jp2box.PaletteBox
|
||||||
|
:members:
|
||||||
|
|
||||||
|
ReaderRequirementsBox
|
||||||
|
'''''''''''''''''''''
|
||||||
|
.. autoclass:: glymur.jp2box.ReaderRequirementsBox
|
||||||
|
:members:
|
||||||
|
|
||||||
|
ResolutionBox
|
||||||
|
'''''''''''''
|
||||||
|
.. autoclass:: glymur.jp2box.ResolutionBox
|
||||||
|
:members:
|
||||||
|
|
||||||
|
CaptureResolutionBox
|
||||||
|
''''''''''''''''''''
|
||||||
|
.. autoclass:: glymur.jp2box.CaptureResolutionBox
|
||||||
|
:members:
|
||||||
|
|
||||||
|
DisplayResolutionBox
|
||||||
|
''''''''''''''''''''
|
||||||
|
.. autoclass:: glymur.jp2box.DisplayResolutionBox
|
||||||
|
:members:
|
||||||
|
|
||||||
|
UUIDBox
|
||||||
|
'''''''
|
||||||
|
.. autoclass:: glymur.jp2box.UUIDBox
|
||||||
|
:members:
|
||||||
|
|
||||||
|
UUIDInfoBox
|
||||||
|
'''''''''''
|
||||||
|
.. autoclass:: glymur.jp2box.UUIDInfoBox
|
||||||
|
:members:
|
||||||
|
|
||||||
|
UUIDListBox
|
||||||
|
'''''''''''
|
||||||
|
.. autoclass:: glymur.jp2box.UUIDListBox
|
||||||
|
:members:
|
||||||
|
|
||||||
|
XMLBox
|
||||||
|
''''''
|
||||||
|
.. autoclass:: glymur.jp2box.XMLBox
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
# serve to show the default.
|
# serve to show the default.
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
class Mock(object):
|
class Mock(object):
|
||||||
|
|
@ -41,12 +42,12 @@ for mod_name in MOCK_MODULES:
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
# 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
|
# 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.
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
# sys.path.insert(0, os.path.abspath('.'))
|
#sys.path.insert(0, os.path.abspath('.'))
|
||||||
|
|
||||||
# -- General configuration ----------------------------------------------------
|
# -- General configuration ----------------------------------------------------
|
||||||
|
|
||||||
# If your documentation needs a minimal Sphinx version, state it here.
|
# If your documentation needs a minimal Sphinx version, state it here.
|
||||||
# needs_sphinx = '1.0'
|
#needs_sphinx = '1.0'
|
||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||||
|
|
@ -61,7 +62,7 @@ templates_path = ['_templates']
|
||||||
source_suffix = '.rst'
|
source_suffix = '.rst'
|
||||||
|
|
||||||
# The encoding of source files.
|
# The encoding of source files.
|
||||||
# source_encoding = 'utf-8-sig'
|
#source_encoding = 'utf-8-sig'
|
||||||
|
|
||||||
# The master toctree document.
|
# The master toctree document.
|
||||||
master_doc = 'index'
|
master_doc = 'index'
|
||||||
|
|
@ -75,19 +76,19 @@ copyright = u'2013, John Evans'
|
||||||
# built documents.
|
# built documents.
|
||||||
#
|
#
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = '0.8'
|
version = '0.5'
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = '0.8.0'
|
release = '0.5.11'
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# for a list of supported languages.
|
# for a list of supported languages.
|
||||||
# language = None
|
#language = None
|
||||||
|
|
||||||
# There are two options for replacing |today|: either, you set today to some
|
# There are two options for replacing |today|: either, you set today to some
|
||||||
# non-false value, then it is used:
|
# non-false value, then it is used:
|
||||||
# today = ''
|
#today = ''
|
||||||
# Else, today_fmt is used as the format for a strftime call.
|
# Else, today_fmt is used as the format for a strftime call.
|
||||||
# today_fmt = '%B %d, %Y'
|
#today_fmt = '%B %d, %Y'
|
||||||
|
|
||||||
# List of patterns, relative to source directory, that match files and
|
# List of patterns, relative to source directory, that match files and
|
||||||
# directories to ignore when looking for source files.
|
# directories to ignore when looking for source files.
|
||||||
|
|
@ -95,24 +96,24 @@ exclude_patterns = []
|
||||||
|
|
||||||
# The reST default role (used for this markup: `text`) to use for all
|
# The reST default role (used for this markup: `text`) to use for all
|
||||||
# documents.
|
# documents.
|
||||||
# default_role = None
|
#default_role = None
|
||||||
|
|
||||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||||
# add_function_parentheses = True
|
#add_function_parentheses = True
|
||||||
|
|
||||||
# If true, the current module name will be prepended to all description
|
# If true, the current module name will be prepended to all description
|
||||||
# unit titles (such as .. function::).
|
# unit titles (such as .. function::).
|
||||||
# add_module_names = True
|
#add_module_names = True
|
||||||
|
|
||||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||||
# output. They are ignored by default.
|
# output. They are ignored by default.
|
||||||
# show_authors = False
|
#show_authors = False
|
||||||
|
|
||||||
# The name of the Pygments (syntax highlighting) style to use.
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
pygments_style = 'sphinx'
|
pygments_style = 'sphinx'
|
||||||
|
|
||||||
# A list of ignored prefixes for module index sorting.
|
# A list of ignored prefixes for module index sorting.
|
||||||
# modindex_common_prefix = []
|
#modindex_common_prefix = []
|
||||||
|
|
||||||
|
|
||||||
# -- Options for HTML output --------------------------------------------------
|
# -- Options for HTML output --------------------------------------------------
|
||||||
|
|
@ -124,26 +125,26 @@ html_theme = 'default'
|
||||||
# Theme options are theme-specific and customize the look and feel of a theme
|
# 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
|
# further. For a list of options available for each theme, see the
|
||||||
# documentation.
|
# documentation.
|
||||||
# html_theme_options = {}
|
#html_theme_options = {}
|
||||||
|
|
||||||
# Add any paths that contain custom themes here, relative to this directory.
|
# Add any paths that contain custom themes here, relative to this directory.
|
||||||
# html_theme_path = []
|
#html_theme_path = []
|
||||||
|
|
||||||
# The name for this set of Sphinx documents. If None, it defaults to
|
# The name for this set of Sphinx documents. If None, it defaults to
|
||||||
# "<project> v<release> documentation".
|
# "<project> v<release> documentation".
|
||||||
# html_title = None
|
#html_title = None
|
||||||
|
|
||||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||||
# html_short_title = None
|
#html_short_title = None
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top
|
# The name of an image file (relative to this directory) to place at the top
|
||||||
# of the sidebar.
|
# of the sidebar.
|
||||||
# html_logo = None
|
#html_logo = None
|
||||||
|
|
||||||
# The name of an image file (within the static path) to use as favicon of the
|
# 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
|
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||||
# pixels large.
|
# pixels large.
|
||||||
# html_favicon = None
|
#html_favicon = None
|
||||||
|
|
||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
# 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,
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
|
|
@ -152,44 +153,44 @@ html_static_path = ['_static']
|
||||||
|
|
||||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||||
# using the given strftime format.
|
# using the given strftime format.
|
||||||
# html_last_updated_fmt = '%b %d, %Y'
|
#html_last_updated_fmt = '%b %d, %Y'
|
||||||
|
|
||||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||||
# typographically correct entities.
|
# typographically correct entities.
|
||||||
# html_use_smartypants = True
|
#html_use_smartypants = True
|
||||||
|
|
||||||
# Custom sidebar templates, maps document names to template names.
|
# Custom sidebar templates, maps document names to template names.
|
||||||
# html_sidebars = {}
|
#html_sidebars = {}
|
||||||
|
|
||||||
# Additional templates that should be rendered to pages, maps page names to
|
# Additional templates that should be rendered to pages, maps page names to
|
||||||
# template names.
|
# template names.
|
||||||
# html_additional_pages = {}
|
#html_additional_pages = {}
|
||||||
|
|
||||||
# If false, no module index is generated.
|
# If false, no module index is generated.
|
||||||
# html_domain_indices = True
|
#html_domain_indices = True
|
||||||
|
|
||||||
# If false, no index is generated.
|
# If false, no index is generated.
|
||||||
# html_use_index = True
|
#html_use_index = True
|
||||||
|
|
||||||
# If true, the index is split into individual pages for each letter.
|
# If true, the index is split into individual pages for each letter.
|
||||||
# html_split_index = False
|
#html_split_index = False
|
||||||
|
|
||||||
# If true, links to the reST sources are added to the pages.
|
# If true, links to the reST sources are added to the pages.
|
||||||
html_show_sourcelink = True
|
html_show_sourcelink = True
|
||||||
|
|
||||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||||
# html_show_sphinx = True
|
#html_show_sphinx = True
|
||||||
|
|
||||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||||
# html_show_copyright = True
|
#html_show_copyright = True
|
||||||
|
|
||||||
# If true, an OpenSearch description file will be output, and all pages will
|
# 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
|
# contain a <link> tag referring to it. The value of this option must be the
|
||||||
# base URL from which the finished HTML is served.
|
# base URL from which the finished HTML is served.
|
||||||
# html_use_opensearch = ''
|
#html_use_opensearch = ''
|
||||||
|
|
||||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||||
# html_file_suffix = None
|
#html_file_suffix = None
|
||||||
|
|
||||||
# Output file base name for HTML help builder.
|
# Output file base name for HTML help builder.
|
||||||
htmlhelp_basename = 'glymurdoc'
|
htmlhelp_basename = 'glymurdoc'
|
||||||
|
|
@ -198,13 +199,13 @@ htmlhelp_basename = 'glymurdoc'
|
||||||
# -- Options for LaTeX output -------------------------------------------------
|
# -- Options for LaTeX output -------------------------------------------------
|
||||||
|
|
||||||
# The paper size ('letterpaper' or 'a4paper').
|
# The paper size ('letterpaper' or 'a4paper').
|
||||||
# 'papersize': 'letterpaper',
|
#'papersize': 'letterpaper',
|
||||||
|
|
||||||
# The font size ('10pt', '11pt' or '12pt').
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
# 'pointsize': '10pt',
|
#'pointsize': '10pt',
|
||||||
|
|
||||||
# Additional stuff for the LaTeX preamble.
|
# Additional stuff for the LaTeX preamble.
|
||||||
# 'preamble': '',
|
#'preamble': '',
|
||||||
latex_elements = {}
|
latex_elements = {}
|
||||||
|
|
||||||
# Grouping the document tree into LaTeX files. List of tuples
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
|
|
@ -215,23 +216,23 @@ latex_documents = [('index', 'glymur.tex', u'glymur Documentation',
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top of
|
# The name of an image file (relative to this directory) to place at the top of
|
||||||
# the title page.
|
# the title page.
|
||||||
# latex_logo = None
|
#latex_logo = None
|
||||||
|
|
||||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||||
# not chapters.
|
# not chapters.
|
||||||
# latex_use_parts = False
|
#latex_use_parts = False
|
||||||
|
|
||||||
# If true, show page references after internal links.
|
# If true, show page references after internal links.
|
||||||
# latex_show_pagerefs = False
|
#latex_show_pagerefs = False
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
# If true, show URL addresses after external links.
|
||||||
# latex_show_urls = False
|
#latex_show_urls = False
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
# Documents to append as an appendix to all manuals.
|
||||||
# latex_appendices = []
|
#latex_appendices = []
|
||||||
|
|
||||||
# If false, no module index is generated.
|
# If false, no module index is generated.
|
||||||
# latex_domain_indices = True
|
#latex_domain_indices = True
|
||||||
|
|
||||||
|
|
||||||
# -- Options for manual page output -------------------------------------------
|
# -- Options for manual page output -------------------------------------------
|
||||||
|
|
@ -244,7 +245,7 @@ man_pages = [
|
||||||
]
|
]
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
# If true, show URL addresses after external links.
|
||||||
# man_show_urls = False
|
#man_show_urls = False
|
||||||
|
|
||||||
|
|
||||||
# -- Options for Texinfo output -----------------------------------------------
|
# -- Options for Texinfo output -----------------------------------------------
|
||||||
|
|
@ -257,13 +258,13 @@ texinfo_documents = [('index', 'glymur', u'glymur Documentation',
|
||||||
'One line description of project.', 'Miscellaneous'), ]
|
'One line description of project.', 'Miscellaneous'), ]
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
# Documents to append as an appendix to all manuals.
|
||||||
# texinfo_appendices = []
|
#texinfo_appendices = []
|
||||||
|
|
||||||
# If false, no module index is generated.
|
# If false, no module index is generated.
|
||||||
# texinfo_domain_indices = True
|
#texinfo_domain_indices = True
|
||||||
|
|
||||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||||
# texinfo_show_urls = 'footnote'
|
#texinfo_show_urls = 'footnote'
|
||||||
|
|
||||||
|
|
||||||
# Example configuration for intersphinx: refer to the Python standard library.
|
# Example configuration for intersphinx: refer to the Python standard library.
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,31 @@
|
||||||
----------------------------------
|
----------------------------------
|
||||||
Advanced Installation Instructions
|
Advanced Installation Instructions
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
Most users won't need to read this! You've been warned...
|
||||||
|
|
||||||
''''''''''''''''''''''
|
''''''''''''''''''''''
|
||||||
Glymur Configuration
|
Glymur Configuration
|
||||||
''''''''''''''''''''''
|
''''''''''''''''''''''
|
||||||
|
|
||||||
The default glymur installation process relies upon OpenJPEG being
|
The default glymur installation process relies upon OpenJPEG being
|
||||||
properly installed on your system as a shared library. If you have
|
properly installed on your system as a shared library. You need
|
||||||
OpenJPEG installed through your system’s package manager on linux
|
at least version 1.5 in order to read and write JPEG 2000 files.
|
||||||
or if you use MacPorts on the mac, you are probably already set to
|
|
||||||
go. But if you have OpenJPEG installed into a non-standard place
|
|
||||||
or if you use windows, then read on.
|
|
||||||
|
|
||||||
Glymur uses ctypes to access the openjp2/openjpeg libraries, and
|
Glymur uses ctypes to access the openjp2/openjpeg libraries, and
|
||||||
because ctypes accesses libraries in a platform-dependent manner,
|
because ctypes accesses libraries in a platform-dependent manner,
|
||||||
it is recommended that **if** you compile and install OpenJPEG into a
|
it is recommended that if you compile and install OpenJPEG into a
|
||||||
non-standard location, you should then create a configuration file
|
non-standard location, you should create a configuration file to
|
||||||
to help Glymur properly find the openjpeg or openjp2 libraries
|
help Glymur properly find the openjpeg or openjp2 libraries (linux
|
||||||
(linux users or macports users don’t need to bother with this if
|
users or macports users don't need to bother with this if you are
|
||||||
you are using OpenJPEG as provided by your package manager). The
|
using OpenJPEG as provided by your package manager). The configuration
|
||||||
configuration format is the same as used by Python’s configparser
|
format is the same as used by Python's configparser module,
|
||||||
module, i.e. ::
|
i.e. ::
|
||||||
|
|
||||||
[library]
|
[library]
|
||||||
openjp2: /somewhere/lib/libopenjp2.so
|
openjp2: /opt/openjp2-svn/lib/libopenjp2.so
|
||||||
|
|
||||||
This assumes, of course, that you've installed OpenJPEG into
|
This assumes, of course, that you've installed OpenJPEG into
|
||||||
/opt/openjpeg on a linux system. The location of the configuration file
|
/opt/openjp2-svn on a linux system. The location of the configuration file
|
||||||
can vary as well. If you use either linux or mac, the path
|
can vary as well. If you use either linux or mac, the path
|
||||||
to the configuration file would normally be ::
|
to the configuration file would normally be ::
|
||||||
|
|
||||||
|
|
@ -38,25 +36,22 @@ the path will be ::
|
||||||
|
|
||||||
$XDG_CONFIG_HOME/glymur/glymurrc
|
$XDG_CONFIG_HOME/glymur/glymurrc
|
||||||
|
|
||||||
On windows, the path to the configuration file can be determined by starting
|
On windows, the path to the configuration file can be determined
|
||||||
up Python and typing ::
|
by starting up Python and typing ::
|
||||||
|
|
||||||
import os
|
import os
|
||||||
os.path.join(os.path.expanduser('~', 'glymur', 'glymurrc')
|
os.path.join(os.path.expanduser('~'), 'glymur', 'glymurrc')
|
||||||
|
|
||||||
|
|
||||||
You may also include a line for the version 1.x openjpeg library if you have it
|
You may also include a line for the version 1.x openjpeg library if you have it
|
||||||
installed in a non-standard place, i.e. ::
|
installed in a non-standard place, i.e. ::
|
||||||
|
|
||||||
[library]
|
[library]
|
||||||
openjpeg: /somewhere/lib/libopenjpeg.so
|
openjpeg: /not/the/usual/location/lib/libopenjpeg.so
|
||||||
|
|
||||||
Once again, you should not have to bother with a configuration file if you use
|
|
||||||
mac or linux and OpenJPEG is provided by your package manager.
|
|
||||||
|
|
||||||
'''''''
|
'''''''
|
||||||
Testing
|
Testing
|
||||||
'''''''
|
'''''''
|
||||||
|
|
||||||
It is not necessary, but you may wish to download OpenJPEG's test
|
It is not necessary, but you may wish to download OpenJPEG's test
|
||||||
data for the purpose of configuring and running OpenJPEG's test
|
data for the purpose of configuring and running OpenJPEG's test
|
||||||
suite. Check their instructions on how to do that. You can then
|
suite. Check their instructions on how to do that. You can then
|
||||||
|
|
|
||||||
|
|
@ -3,220 +3,36 @@ How do I...?
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
|
||||||
... read images?
|
... read the lowest resolution thumbnail?
|
||||||
================
|
=========================================
|
||||||
Jp2k implements slicing via the :py:meth:`__getitem__` method, meaning that
|
Printing the Jp2k object should reveal the number of resolutions (look in the
|
||||||
multiple resolution imagery in a JPEG 2000 file can
|
COD segment section), but you can take a shortcut by supplying -1 as the
|
||||||
easily be accessed via array-style slicing. For example here's how to
|
resolution level. ::
|
||||||
retrieve a full resolution and first lower-resolution image ::
|
|
||||||
|
|
||||||
>>> import glymur
|
>>> import glymur
|
||||||
>>> jp2file = glymur.data.nemo() # just a path to a JPEG2000 file
|
>>> file = glymur.data.nemo()
|
||||||
>>> jp2 = glymur.Jp2k(jp2file)
|
>>> j = glymur.Jp2k(file)
|
||||||
>>> fullres = jp2[:]
|
>>> thumbnail = j.read(rlevel=-1)
|
||||||
>>> fullres.shape
|
|
||||||
(1456, 2592, 3)
|
|
||||||
>>> thumbnail = jp2[::2, ::2]
|
|
||||||
>>> thumbnail.shape
|
|
||||||
(728, 1296, 3)
|
|
||||||
|
|
||||||
... write images?
|
|
||||||
=================
|
|
||||||
It's pretty simple, just supply the image data as the 2nd argument to the Jp2k
|
|
||||||
constructor.
|
|
||||||
|
|
||||||
>>> import glymur, numpy as np
|
|
||||||
>>> jp2 = glymur.Jp2k('zeros.jp2', data=np.zeros((640, 480), dtype=np.uint8)
|
|
||||||
|
|
||||||
You must have OpenJPEG version 1.5 or more recent in order to write JPEG 2000
|
|
||||||
images with glymur.
|
|
||||||
|
|
||||||
... display metadata?
|
... display metadata?
|
||||||
=====================
|
=====================
|
||||||
There are two ways. From the command line, the console script **jp2dump** is
|
There are two ways. From the unix command line, the script *jp2dump* is
|
||||||
available. ::
|
available. ::
|
||||||
|
|
||||||
$ jp2dump /path/to/glymur/installation/data/nemo.jp2
|
$ jp2dump /path/to/glymur/installation/data/nemo.jp2
|
||||||
|
|
||||||
From within Python, the same result is obtained simply by printing the Jp2k
|
From within Python, it is as simple as printing the Jp2k object, i.e. ::
|
||||||
object, i.e. ::
|
|
||||||
|
|
||||||
>>> import glymur
|
>>> from glymur import Jp2k
|
||||||
>>> jp2file = glymur.data.nemo() # just a path to a JP2 file
|
>>> file = glymur.data.nemo()
|
||||||
>>> jp2 = glymur.Jp2k(jp2file)
|
>>> j = Jp2k(file)
|
||||||
>>> print(jp2)
|
>>> print(j)
|
||||||
File: nemo.jp2
|
|
||||||
JPEG 2000 Signature Box (jP ) @ (0, 12)
|
|
||||||
Signature: 0d0a870a
|
|
||||||
File Type Box (ftyp) @ (12, 20)
|
|
||||||
Brand: jp2
|
|
||||||
Compatibility: ['jp2 ']
|
|
||||||
JP2 Header Box (jp2h) @ (32, 45)
|
|
||||||
Image Header Box (ihdr) @ (40, 22)
|
|
||||||
Size: [1456 2592 3]
|
|
||||||
Bitdepth: 8
|
|
||||||
Signed: False
|
|
||||||
Compression: wavelet
|
|
||||||
Colorspace Unknown: False
|
|
||||||
Colour Specification Box (colr) @ (62, 15)
|
|
||||||
Method: enumerated colorspace
|
|
||||||
Precedence: 0
|
|
||||||
Colorspace: sRGB
|
|
||||||
UUID Box (uuid) @ (77, 3146)
|
|
||||||
UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP)
|
|
||||||
UUID Data:
|
|
||||||
<ns0:xmpmeta xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:ns0="adobe:ns:meta/" xmlns:ns2="http://ns.adobe.com/xap/1.0/" xmlns:ns3="http://ns.adobe.com/tiff/1.0/" xmlns:ns4="http://ns.adobe.com/exif/1.0/" xmlns:ns5="http://ns.adobe.com/photoshop/1.0/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" ns0:xmptk="Exempi + XMP Core 5.1.2">
|
|
||||||
<rdf:RDF>
|
|
||||||
<rdf:Description rdf:about="">
|
|
||||||
<ns2:CreatorTool>Google</ns2:CreatorTool>
|
|
||||||
<ns2:CreateDate>2013-02-09T14:47:53</ns2:CreateDate>
|
|
||||||
</rdf:Description>
|
|
||||||
<rdf:Description rdf:about="">
|
|
||||||
<ns3:YCbCrPositioning>1</ns3:YCbCrPositioning>
|
|
||||||
<ns3:XResolution>72/1</ns3:XResolution>
|
|
||||||
<ns3:YResolution>72/1</ns3:YResolution>
|
|
||||||
<ns3:ResolutionUnit>2</ns3:ResolutionUnit>
|
|
||||||
<ns3:Make>HTC</ns3:Make>
|
|
||||||
<ns3:Model>HTC Glacier</ns3:Model>
|
|
||||||
<ns3:ImageWidth>2592</ns3:ImageWidth>
|
|
||||||
<ns3:ImageLength>1456</ns3:ImageLength>
|
|
||||||
<ns3:BitsPerSample>
|
|
||||||
<rdf:Seq>
|
|
||||||
<rdf:li>8</rdf:li>
|
|
||||||
<rdf:li>8</rdf:li>
|
|
||||||
<rdf:li>8</rdf:li>
|
|
||||||
</rdf:Seq>
|
|
||||||
</ns3:BitsPerSample>
|
|
||||||
<ns3:PhotometricInterpretation>2</ns3:PhotometricInterpretation>
|
|
||||||
<ns3:SamplesPerPixel>3</ns3:SamplesPerPixel>
|
|
||||||
<ns3:WhitePoint>
|
|
||||||
<rdf:Seq>
|
|
||||||
<rdf:li>1343036288/4294967295</rdf:li>
|
|
||||||
<rdf:li>1413044224/4294967295</rdf:li>
|
|
||||||
</rdf:Seq>
|
|
||||||
</ns3:WhitePoint>
|
|
||||||
<ns3:PrimaryChromaticities>
|
|
||||||
<rdf:Seq>
|
|
||||||
<rdf:li>2748779008/4294967295</rdf:li>
|
|
||||||
<rdf:li>1417339264/4294967295</rdf:li>
|
|
||||||
<rdf:li>1288490240/4294967295</rdf:li>
|
|
||||||
<rdf:li>2576980480/4294967295</rdf:li>
|
|
||||||
<rdf:li>644245120/4294967295</rdf:li>
|
|
||||||
<rdf:li>257698032/4294967295</rdf:li>
|
|
||||||
</rdf:Seq>
|
|
||||||
</ns3:PrimaryChromaticities>
|
|
||||||
</rdf:Description>
|
|
||||||
<rdf:Description rdf:about="">
|
|
||||||
<ns4:ColorSpace>1</ns4:ColorSpace>
|
|
||||||
<ns4:PixelXDimension>2528</ns4:PixelXDimension>
|
|
||||||
<ns4:PixelYDimension>1424</ns4:PixelYDimension>
|
|
||||||
<ns4:FocalLength>353/100</ns4:FocalLength>
|
|
||||||
<ns4:GPSAltitudeRef>0</ns4:GPSAltitudeRef>
|
|
||||||
<ns4:GPSAltitude>0/1</ns4:GPSAltitude>
|
|
||||||
<ns4:GPSMapDatum>WGS-84</ns4:GPSMapDatum>
|
|
||||||
<ns4:DateTimeOriginal>2013-02-09T14:47:53</ns4:DateTimeOriginal>
|
|
||||||
<ns4:ISOSpeedRatings>
|
|
||||||
<rdf:Seq>
|
|
||||||
<rdf:li>76</rdf:li>
|
|
||||||
</rdf:Seq>
|
|
||||||
</ns4:ISOSpeedRatings>
|
|
||||||
<ns4:ExifVersion>0220</ns4:ExifVersion>
|
|
||||||
<ns4:FlashpixVersion>0100</ns4:FlashpixVersion>
|
|
||||||
<ns4:ComponentsConfiguration>
|
|
||||||
<rdf:Seq>
|
|
||||||
<rdf:li>1</rdf:li>
|
|
||||||
<rdf:li>2</rdf:li>
|
|
||||||
<rdf:li>3</rdf:li>
|
|
||||||
<rdf:li>0</rdf:li>
|
|
||||||
</rdf:Seq>
|
|
||||||
</ns4:ComponentsConfiguration>
|
|
||||||
<ns4:GPSLatitude>42,20.56N</ns4:GPSLatitude>
|
|
||||||
<ns4:GPSLongitude>71,5.29W</ns4:GPSLongitude>
|
|
||||||
<ns4:GPSTimeStamp>2013-02-09T19:47:53Z</ns4:GPSTimeStamp>
|
|
||||||
<ns4:GPSProcessingMethod>NETWORK</ns4:GPSProcessingMethod>
|
|
||||||
</rdf:Description>
|
|
||||||
<rdf:Description rdf:about="">
|
|
||||||
<ns5:DateCreated>2013-02-09T14:47:53</ns5:DateCreated>
|
|
||||||
</rdf:Description>
|
|
||||||
<rdf:Description rdf:about="">
|
|
||||||
<dc:Creator>
|
|
||||||
<rdf:Seq>
|
|
||||||
<rdf:li>Glymur</rdf:li>
|
|
||||||
<rdf:li>Python XMP Toolkit</rdf:li>
|
|
||||||
</rdf:Seq>
|
|
||||||
</dc:Creator>
|
|
||||||
</rdf:Description>
|
|
||||||
</rdf:RDF>
|
|
||||||
</ns0:xmpmeta>
|
|
||||||
Contiguous Codestream Box (jp2c) @ (3223, 1132296)
|
|
||||||
Main header:
|
|
||||||
SOC marker segment @ (3231, 0)
|
|
||||||
SIZ marker segment @ (3233, 47)
|
|
||||||
Profile: 2
|
|
||||||
Reference Grid Height, Width: (1456 x 2592)
|
|
||||||
Vertical, Horizontal Reference Grid Offset: (0 x 0)
|
|
||||||
Reference Tile Height, Width: (1456 x 2592)
|
|
||||||
Vertical, Horizontal Reference Tile Offset: (0 x 0)
|
|
||||||
Bitdepth: (8, 8, 8)
|
|
||||||
Signed: (False, False, False)
|
|
||||||
Vertical, Horizontal Subsampling: ((1, 1), (1, 1), (1, 1))
|
|
||||||
COD marker segment @ (3282, 12)
|
|
||||||
Coding style:
|
|
||||||
Entropy coder, without partitions
|
|
||||||
SOP marker segments: False
|
|
||||||
EPH marker segments: False
|
|
||||||
Coding style parameters:
|
|
||||||
Progression order: LRCP
|
|
||||||
Number of layers: 2
|
|
||||||
Multiple component transformation usage: reversible
|
|
||||||
Number of resolutions: 2
|
|
||||||
Code block height, width: (64 x 64)
|
|
||||||
Wavelet transform: 5-3 reversible
|
|
||||||
Precinct size: default, 2^15 x 2^15
|
|
||||||
Code block context:
|
|
||||||
Selective arithmetic coding bypass: False
|
|
||||||
Reset context probabilities on coding pass boundaries: False
|
|
||||||
Termination on each coding pass: False
|
|
||||||
Vertically stripe causal context: False
|
|
||||||
Predictable termination: False
|
|
||||||
Segmentation symbols: False
|
|
||||||
QCD marker segment @ (3296, 7)
|
|
||||||
Quantization style: no quantization, 2 guard bits
|
|
||||||
Step size: [(0, 8), (0, 9), (0, 9), (0, 10)]
|
|
||||||
CME marker segment @ (3305, 37)
|
|
||||||
"Created by OpenJPEG version 2.0.0"
|
|
||||||
|
|
||||||
That's fairly overwhelming, and perhaps lost in the flood of information
|
This prints the metadata found in the JP2 boxes, but in the case of the
|
||||||
is the fact that the codestream metadata is limited to just what's in the
|
codestream box, only the main header is printed. It is possible to print
|
||||||
main codestream header. You can suppress the codestream and XML details by
|
**only** the codestream information as well, i.e. ::
|
||||||
making use of the :py:meth:`set_printoptions` function::
|
|
||||||
|
|
||||||
>>> glymur.set_printoptions(codestream=False, xml=False)
|
>>> print(j.get_codestream())
|
||||||
>>> print(jp2)
|
|
||||||
File: nemo.jp2
|
|
||||||
JPEG 2000 Signature Box (jP ) @ (0, 12)
|
|
||||||
Signature: 0d0a870a
|
|
||||||
File Type Box (ftyp) @ (12, 20)
|
|
||||||
Brand: jp2
|
|
||||||
Compatibility: ['jp2 ']
|
|
||||||
JP2 Header Box (jp2h) @ (32, 45)
|
|
||||||
Image Header Box (ihdr) @ (40, 22)
|
|
||||||
Size: [1456 2592 3]
|
|
||||||
Bitdepth: 8
|
|
||||||
Signed: False
|
|
||||||
Compression: wavelet
|
|
||||||
Colorspace Unknown: False
|
|
||||||
Colour Specification Box (colr) @ (62, 15)
|
|
||||||
Method: enumerated colorspace
|
|
||||||
Precedence: 0
|
|
||||||
Colorspace: sRGB
|
|
||||||
UUID Box (uuid) @ (77, 3146)
|
|
||||||
UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP)
|
|
||||||
Contiguous Codestream Box (jp2c) @ (3223, 1132296)
|
|
||||||
|
|
||||||
It is possible to easily print the codestream header details as well, i.e. ::
|
|
||||||
|
|
||||||
>>> print(j.codestream) # details not show
|
|
||||||
|
|
||||||
... add XML metadata?
|
... add XML metadata?
|
||||||
=====================
|
=====================
|
||||||
|
|
@ -239,11 +55,12 @@ Consider the following XML file `data.xml` : ::
|
||||||
</locality>
|
</locality>
|
||||||
</info>
|
</info>
|
||||||
|
|
||||||
The :py:meth:`append` method can add an XML box as shown below::
|
The **append** method can add an XML box as shown below::
|
||||||
|
|
||||||
>>> import shutil
|
>>> import shutil
|
||||||
>>> import glymur
|
>>> import glymur
|
||||||
>>> shutil.copyfile(glymur.data.nemo(), 'myfile.jp2')
|
>>> shutil.copyfile(glymur.data.nemo(), 'myfile.jp2')
|
||||||
|
>>> from xml.etree import cElementTree as ET
|
||||||
>>> jp2 = glymur.Jp2k('myfile.jp2')
|
>>> jp2 = glymur.Jp2k('myfile.jp2')
|
||||||
>>> xmlbox = glymur.jp2box.XMLBox(filename='data.xml')
|
>>> xmlbox = glymur.jp2box.XMLBox(filename='data.xml')
|
||||||
>>> jp2.append(xmlbox)
|
>>> jp2.append(xmlbox)
|
||||||
|
|
@ -254,12 +71,11 @@ The :py:meth:`append` method can add an XML box as shown below::
|
||||||
An existing raw codestream (or JP2 file) can be wrapped (re-wrapped) in a
|
An existing raw codestream (or JP2 file) can be wrapped (re-wrapped) in a
|
||||||
user-defined set of JP2 boxes. To get just a minimal JP2 jacket on the
|
user-defined set of JP2 boxes. To get just a minimal JP2 jacket on the
|
||||||
codestream provided by `goodstuff.j2k` (a file consisting of a raw codestream),
|
codestream provided by `goodstuff.j2k` (a file consisting of a raw codestream),
|
||||||
you can use the :py:meth:`wrap` method with no box argument: ::
|
you can use the **wrap** method with no box argument: ::
|
||||||
|
|
||||||
>>> import glymur
|
>>> import glymur
|
||||||
>>> glymur.set_printoptions(codestream=False)
|
>>> jfile = glymur.data.goodstuff()
|
||||||
>>> jp2file = glymur.data.goodstuff()
|
>>> j2k = glymur.Jp2k(jfile)
|
||||||
>>> j2k = glymur.Jp2k(jp2file)
|
|
||||||
>>> jp2 = j2k.wrap("newfile.jp2")
|
>>> jp2 = j2k.wrap("newfile.jp2")
|
||||||
>>> print(jp2)
|
>>> print(jp2)
|
||||||
File: newfile.jp2
|
File: newfile.jp2
|
||||||
|
|
@ -280,6 +96,10 @@ you can use the :py:meth:`wrap` method with no box argument: ::
|
||||||
Precedence: 0
|
Precedence: 0
|
||||||
Colorspace: sRGB
|
Colorspace: sRGB
|
||||||
Contiguous Codestream Box (jp2c) @ (77, 115228)
|
Contiguous Codestream Box (jp2c) @ (77, 115228)
|
||||||
|
Main header:
|
||||||
|
.
|
||||||
|
. (truncated)
|
||||||
|
.
|
||||||
|
|
||||||
The raw codestream was wrapped in a JP2 jacket with four boxes in the outer
|
The raw codestream was wrapped in a JP2 jacket with four boxes in the outer
|
||||||
layer (the signature, file type, JP2 header, and contiguous codestream), with
|
layer (the signature, file type, JP2 header, and contiguous codestream), with
|
||||||
|
|
@ -288,9 +108,8 @@ JP2 header superbox.
|
||||||
|
|
||||||
XML boxes are not in the minimal set of box requirements for the JP2 format, so
|
XML boxes are not in the minimal set of box requirements for the JP2 format, so
|
||||||
in order to add an XML box into the mix before the codestream box, we'll need to
|
in order to add an XML box into the mix before the codestream box, we'll need to
|
||||||
re-specify all of the boxes. If you already have a JP2 jacket in place,
|
re-specify all of the boxes. If you already have a JP2 jacket in place, you can just reuse that,
|
||||||
you can just reuse that, though. Take the following example content in
|
though. Take the following example content in an XML file `favorites.xml` : ::
|
||||||
an XML file `favorites.xml` : ::
|
|
||||||
|
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<favorite_things>
|
<favorite_things>
|
||||||
|
|
@ -326,20 +145,25 @@ the following will work. ::
|
||||||
<favorite_things>
|
<favorite_things>
|
||||||
<category>Light Ale</category>
|
<category>Light Ale</category>
|
||||||
</favorite_things>
|
</favorite_things>
|
||||||
Contiguous Codestream Box (jp2c) @ (153, 115236)
|
|
||||||
|
|
||||||
As to the question of which method you should use, :py:meth:`append` or
|
Contiguous Codestream Box (jp2c) @ (153, 115236)
|
||||||
:py:meth:`wrap`, to add metadata, you should keep in mind that :py:meth:`wrap`
|
Main header:
|
||||||
produces a new JP2 file, while :py:meth:`append` modifies an existing file and
|
.
|
||||||
is currently limited to XML and UUID boxes.
|
. (truncated)
|
||||||
|
.
|
||||||
|
|
||||||
|
As to the question of which method you should use, **append** or **wrap**,
|
||||||
|
to add metadata, you should keep in mind that **wrap** produces a new JP2 file,
|
||||||
|
while **append** modifies an existing file and is currently limited to XML
|
||||||
|
boxes.
|
||||||
|
|
||||||
... create an image with an alpha layer?
|
... create an image with an alpha layer?
|
||||||
========================================
|
========================================
|
||||||
|
|
||||||
OpenJPEG can create JP2 files with more than 3 components (use version 2.1.0+
|
OpenJPEG can create JP2 files with more than 3 components (requires version
|
||||||
for this), but by default, any extra components are not described
|
2.1), but by default any extra components are not described as such by the JP2
|
||||||
as such. In order to do so, we need to rewrap such an image in a
|
boxes created by OpenJPEG. In order to do so, we need to rewrap such
|
||||||
set of boxes that includes a channel definition box.
|
an image in a set of boxes that includes a channel definition box.
|
||||||
|
|
||||||
This example is based on SciPy example code found at
|
This example is based on SciPy example code found at
|
||||||
http://scipy-lectures.github.io/advanced/image_processing/#basic-manipulations .
|
http://scipy-lectures.github.io/advanced/image_processing/#basic-manipulations .
|
||||||
|
|
@ -349,14 +173,15 @@ image isn't square. ::
|
||||||
>>> import numpy as np
|
>>> import numpy as np
|
||||||
>>> import glymur
|
>>> import glymur
|
||||||
>>> from glymur import Jp2k
|
>>> from glymur import Jp2k
|
||||||
>>> rgb = Jp2k(glymur.data.goodstuff())[:]
|
>>> rgb = Jp2k(glymur.data.goodstuff()).read()
|
||||||
>>> lx, ly = rgb.shape[0:2]
|
>>> lx, ly = rgb.shape[0:2]
|
||||||
>>> X, Y = np.ogrid[0:lx, 0:ly]
|
>>> X, Y = np.ogrid[0:lx, 0:ly]
|
||||||
>>> mask = ly**2*(X - lx / 2) ** 2 + lx**2*(Y - ly / 2) ** 2 > (lx * ly / 2)**2
|
>>> mask = ly**2*(X - lx / 2) ** 2 + lx**2*(Y - ly / 2) ** 2 > (lx * ly / 2)**2
|
||||||
>>> alpha = 255 * np.ones((lx, ly, 1), dtype=np.uint8)
|
>>> alpha = 255 * np.ones((lx, ly, 1), dtype=np.uint8)
|
||||||
>>> alpha[mask] = 0
|
>>> alpha[mask] = 0
|
||||||
>>> rgba = np.concatenate((rgb, alpha), axis=2)
|
>>> rgba = np.concatenate((rgb, alpha), axis=2)
|
||||||
>>> jp2 = Jp2k('tmp.jp2', data=rgba)
|
>>> jp2 = Jp2k('tmp.jp2', 'wb')
|
||||||
|
>>> jp2.write(rgba)
|
||||||
|
|
||||||
Next we need to specify what types of channels we have.
|
Next we need to specify what types of channels we have.
|
||||||
The first three channels are color channels, but we identify the fourth as
|
The first three channels are color channels, but we identify the fourth as
|
||||||
|
|
@ -373,7 +198,7 @@ channel, but we aren't doing that). ::
|
||||||
|
|
||||||
>>> from glymur.core import RED, GREEN, BLUE, WHOLE_IMAGE
|
>>> from glymur.core import RED, GREEN, BLUE, WHOLE_IMAGE
|
||||||
>>> asoc = [RED, GREEN, BLUE, WHOLE_IMAGE]
|
>>> asoc = [RED, GREEN, BLUE, WHOLE_IMAGE]
|
||||||
>>> cdef = glymur.jp2box.ChannelDefinitionBox(ctype, asoc)
|
>>> cdef = glymur.jp2box.ChannelDefinitionBox(channel_type=ctype, association=asoc)
|
||||||
>>> print(cdef)
|
>>> print(cdef)
|
||||||
Channel Definition Box (cdef) @ (0, 0)
|
Channel Definition Box (cdef) @ (0, 0)
|
||||||
Channel 0 (color) ==> (1)
|
Channel 0 (color) ==> (1)
|
||||||
|
|
@ -394,213 +219,32 @@ Here's how the Preview application on the mac shows the RGBA image.
|
||||||
.. image:: goodstuff_alpha.png
|
.. image:: goodstuff_alpha.png
|
||||||
|
|
||||||
|
|
||||||
... work with XMP UUIDs?
|
work with XMP UUIDs?
|
||||||
========================
|
====================
|
||||||
`Wikipedia <http://en.wikipedia.org/wiki/Extensible_Metadata_Platform>`_ states
|
|
||||||
that "The Extensible Metadata Platform (XMP) is an ISO standard,
|
|
||||||
originally created by Adobe Systems Inc., for the creation, processing
|
|
||||||
and interchange of standardized and custom metadata for all kinds
|
|
||||||
of resources."
|
|
||||||
|
|
||||||
The example JP2 file shipped with glymur has an XMP UUID. ::
|
The example JP2 file shipped with glymur has an XMP UUID. ::
|
||||||
|
|
||||||
>>> import glymur
|
>>> import glymur
|
||||||
>>> j = glymur.Jp2k(glymur.data.nemo())
|
>>> j = glymur.Jp2k(glymur.data.nemo())
|
||||||
>>> print(j.box[3]) # formatting added to the XML below
|
>>> print(j.box[4])
|
||||||
<ns0:xmpmeta xmlns:dc="http://purl.org/dc/elements/1.1/"
|
UUID Box (uuid) @ (715, 2412)
|
||||||
xmlns:ns0="adobe:ns:meta/"
|
UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP)
|
||||||
xmlns:ns2="http://ns.adobe.com/xap/1.0/"
|
UUID Data:
|
||||||
xmlns:ns3="http://ns.adobe.com/tiff/1.0/"
|
<ns0:xmpmeta xmlns:ns0="adobe:ns:meta/" xmlns:ns2="http://ns.adobe.com/xap/1.0/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" ns0:xmptk="XMP Core 4.4.0-Exiv2">
|
||||||
xmlns:ns4="http://ns.adobe.com/exif/1.0/"
|
|
||||||
xmlns:ns5="http://ns.adobe.com/photoshop/1.0/"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
ns0:xmptk="Exempi + XMP Core 5.1.2">
|
|
||||||
<rdf:RDF>
|
<rdf:RDF>
|
||||||
<rdf:Description rdf:about="">
|
<rdf:Description ns2:CreatorTool="glymur" rdf:about="" />
|
||||||
<ns2:CreatorTool>Google</ns2:CreatorTool>
|
</rdf:RDF>
|
||||||
<ns2:CreateDate>2013-02-09T14:47:53</ns2:CreateDate>
|
</ns0:xmpmeta>
|
||||||
</rdf:Description>
|
|
||||||
|
|
||||||
.
|
Since the UUID data in this case is returned as an ElementTree instance, one can
|
||||||
.
|
use ElementTree to access the data. For example, to extract the
|
||||||
.
|
**CreatorTool** attribute value, the following would work::
|
||||||
</ns0:xmpmeta>
|
|
||||||
|
|
||||||
Since the UUID data in this case is returned as an lxml ElementTree
|
>>> xmp = j.box[4].data
|
||||||
instance, one can use lxml to access the data. For example, to
|
>>> ns0 = '{http://www.w3.org/1999/02/22-rdf-syntax-ns#}'
|
||||||
extract the **CreatorTool** attribute value, one could do the
|
>>> ns1 = '{http://ns.adobe.com/xap/1.0/}'
|
||||||
following
|
>>> name = '{0}RDF/{0}Description'.format(ns0)
|
||||||
|
|
||||||
>>> xmp = j.box[3].data
|
|
||||||
>>> rdf = '{http://www.w3.org/1999/02/22-rdf-syntax-ns#}'
|
|
||||||
>>> ns2 = '{http://ns.adobe.com/xap/1.0/}'
|
|
||||||
>>> name = '{0}RDF/{0}Description/{1}CreatorTool'.format(rdf, ns2)
|
|
||||||
>>> elt = xmp.find(name)
|
>>> elt = xmp.find(name)
|
||||||
>>> elt
|
>>> elt
|
||||||
<Element '{http://ns.adobe.com/xap/1.0/#}CreatorTool' at 0xb50684a4>
|
<Element '{http://www.w3.org/1999/02/22-rdf-syntax-ns#}Description' at 0xb4baa93c>
|
||||||
>>> elt.text
|
>>> elt.attrib['{0}CreatorTool'.format(ns1)]
|
||||||
'Google'
|
'glymur'
|
||||||
|
|
||||||
But that would be painful. A better solution is to install the Python XMP
|
|
||||||
Toolkit (make sure it is at least version 2.0)::
|
|
||||||
|
|
||||||
>>> from libxmp import XMPMeta
|
|
||||||
>>> from libxmp.consts import XMP_NS_XMP as NS_XAP
|
|
||||||
>>> meta = XMPMeta()
|
|
||||||
>>> meta.parse_from_str(j.box[3].raw_data.decode('utf-8'))
|
|
||||||
>>> meta.get_property(NS_XAP, 'CreatorTool')
|
|
||||||
'Google'
|
|
||||||
|
|
||||||
Where the Python XMP Toolkit can really shine, though, is when you are
|
|
||||||
converting an image from another format such as TIFF or JPEG into JPEG 2000.
|
|
||||||
For example, if you were to be converting the TIFF image found at
|
|
||||||
http://photojournal.jpl.nasa.gov/tiff/PIA17145.tif info JPEG 2000::
|
|
||||||
|
|
||||||
>>> import skimage.io
|
|
||||||
>>> image = skimage.io.imread('PIA17145.tif')
|
|
||||||
>>> from glymur import Jp2k
|
|
||||||
>>> jp2 = Jp2k('PIA17145.jp2', data=image)
|
|
||||||
|
|
||||||
Next you can extract the XMP metadata.
|
|
||||||
|
|
||||||
>>> from libxmp import XMPFiles
|
|
||||||
>>> xf = XMPFiles()
|
|
||||||
>>> xf.open_file('PIA17145.tif')
|
|
||||||
>>> xmp = xf.get_xmp()
|
|
||||||
>>> print(xmp)
|
|
||||||
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
|
|
||||||
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Exempi + XMP Core 5.1.2">
|
|
||||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
|
||||||
<rdf:Description rdf:about=""
|
|
||||||
xmlns:tiff="http://ns.adobe.com/tiff/1.0/">
|
|
||||||
<tiff:ImageWidth>1016</tiff:ImageWidth>
|
|
||||||
<tiff:ImageLength>1016</tiff:ImageLength>
|
|
||||||
<tiff:BitsPerSample>
|
|
||||||
<rdf:Seq>
|
|
||||||
<rdf:li>8</rdf:li>
|
|
||||||
</rdf:Seq>
|
|
||||||
</tiff:BitsPerSample>
|
|
||||||
<tiff:Compression>1</tiff:Compression>
|
|
||||||
<tiff:PhotometricInterpretation>1</tiff:PhotometricInterpretation>
|
|
||||||
<tiff:SamplesPerPixel>1</tiff:SamplesPerPixel>
|
|
||||||
<tiff:PlanarConfiguration>1</tiff:PlanarConfiguration>
|
|
||||||
<tiff:ResolutionUnit>2</tiff:ResolutionUnit>
|
|
||||||
</rdf:Description>
|
|
||||||
<rdf:Description rdf:about=""
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
|
||||||
<dc:description>
|
|
||||||
<rdf:Alt>
|
|
||||||
<rdf:li xml:lang="x-default">converted PNM file</rdf:li>
|
|
||||||
</rdf:Alt>
|
|
||||||
</dc:description>
|
|
||||||
</rdf:Description>
|
|
||||||
</rdf:RDF>
|
|
||||||
</x:xmpmeta>
|
|
||||||
<?xpacket end="w"?>
|
|
||||||
|
|
||||||
If you are familiar with TIFF, you can verify that there's no XMP tag in the
|
|
||||||
TIFF file, but the Python XMP Toolkit takes advantage of the TIFF header
|
|
||||||
structure to populate an XMP packet for you. If you were working with a JPEG
|
|
||||||
file with Exif metadata, that information would be included in the XMP packet
|
|
||||||
as well. Now you can append the XMP packet in a UUIDBox. In order to do this,
|
|
||||||
though, you have to know the UUID that signifies XMP data.::
|
|
||||||
|
|
||||||
>>> import uuid
|
|
||||||
>>> xmp_uuid = uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac')
|
|
||||||
>>> box = glymur.jp2box.UUIDBox(xmp_uuid, str(xmp).encode())
|
|
||||||
>>> jp2.append(box)
|
|
||||||
>>> print(jp2.box[-1])
|
|
||||||
UUID Box (uuid) @ (592316, 1053)
|
|
||||||
UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP)
|
|
||||||
UUID Data:
|
|
||||||
<ns0:xmpmeta xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:ns0="adobe:ns:meta/" xmlns:ns2="http://ns.adobe.com/tiff/1.0/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" ns0:xmptk="Exempi + XMP Core 5.1.2">
|
|
||||||
<rdf:RDF>
|
|
||||||
<rdf:Description rdf:about="">
|
|
||||||
<ns2:ImageWidth>1016</ns2:ImageWidth>
|
|
||||||
<ns2:ImageLength>1016</ns2:ImageLength>
|
|
||||||
<ns2:BitsPerSample>
|
|
||||||
<rdf:Seq>
|
|
||||||
<rdf:li>8</rdf:li>
|
|
||||||
</rdf:Seq>
|
|
||||||
</ns2:BitsPerSample>
|
|
||||||
<ns2:Compression>1</ns2:Compression>
|
|
||||||
<ns2:PhotometricInterpretation>1</ns2:PhotometricInterpretation>
|
|
||||||
<ns2:SamplesPerPixel>1</ns2:SamplesPerPixel>
|
|
||||||
<ns2:PlanarConfiguration>1</ns2:PlanarConfiguration>
|
|
||||||
<ns2:ResolutionUnit>2</ns2:ResolutionUnit>
|
|
||||||
</rdf:Description>
|
|
||||||
<rdf:Description rdf:about="">
|
|
||||||
<dc:description>
|
|
||||||
<rdf:Alt>
|
|
||||||
<rdf:li xml:lang="x-default">converted PNM file</rdf:li>
|
|
||||||
</rdf:Alt>
|
|
||||||
</dc:description>
|
|
||||||
</rdf:Description>
|
|
||||||
</rdf:RDF>
|
|
||||||
</ns0:xmpmeta>
|
|
||||||
|
|
||||||
You can also build up XMP metadata from scratch. For instance, if we try to
|
|
||||||
wrap `goodstuff.j2k` again::
|
|
||||||
|
|
||||||
>>> import glymur
|
|
||||||
>>> j2kfile = glymur.data.goodstuff()
|
|
||||||
>>> j2k = glymur.Jp2k(j2kfile)
|
|
||||||
>>> jp2 = j2k.wrap("goodstuff.jp2")
|
|
||||||
|
|
||||||
Now build up the metadata piece-by-piece. It would help to have the XMP
|
|
||||||
standard close at hand::
|
|
||||||
|
|
||||||
>>> from libxmp import XMPMeta
|
|
||||||
>>> from libxmp.consts import XMP_NS_TIFF as NS_TIFF
|
|
||||||
>>> from libxmp.consts import XMP_NS_DC as NS_DC
|
|
||||||
>>> xmp = XMPMeta()
|
|
||||||
>>> ihdr = jp2.box[2].box[0]
|
|
||||||
>>> xmp.set_property(NS_TIFF, "ImageWidth", str(ihdr.width))
|
|
||||||
>>> xmp.set_property(NS_TIFF, "ImageHeight", str(ihdr.height))
|
|
||||||
>>> xmp.set_property(NS_TIFF, "BitsPerSample", '3')
|
|
||||||
>>> xmp.set_property(NS_DC, "Title", u'Stürm und Drang')
|
|
||||||
>>> xmp.set_property(NS_DC, "Creator", 'Glymur')
|
|
||||||
|
|
||||||
We can then append the XMP in a UUID box just as before::
|
|
||||||
|
|
||||||
>>> import uuid
|
|
||||||
>>> xmp_uuid = uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac')
|
|
||||||
>>> box = glymur.jp2box.UUIDBox(xmp_uuid, str(xmp).encode())
|
|
||||||
>>> jp2.append(box)
|
|
||||||
>>> glymur.set_printoptions(codestream=False)
|
|
||||||
>>> print(jp2)
|
|
||||||
File: goodstuff.jp2
|
|
||||||
JPEG 2000 Signature Box (jP ) @ (0, 12)
|
|
||||||
Signature: 0d0a870a
|
|
||||||
File Type Box (ftyp) @ (12, 20)
|
|
||||||
Brand: jp2
|
|
||||||
Compatibility: ['jp2 ']
|
|
||||||
JP2 Header Box (jp2h) @ (32, 45)
|
|
||||||
Image Header Box (ihdr) @ (40, 22)
|
|
||||||
Size: [800 480 3]
|
|
||||||
Bitdepth: 8
|
|
||||||
Signed: False
|
|
||||||
Compression: wavelet
|
|
||||||
Colorspace Unknown: False
|
|
||||||
Colour Specification Box (colr) @ (62, 15)
|
|
||||||
Method: enumerated colorspace
|
|
||||||
Precedence: 0
|
|
||||||
Colorspace: sRGB
|
|
||||||
Contiguous Codestream Box (jp2c) @ (77, 115228)
|
|
||||||
UUID Box (uuid) @ (115305, 671)
|
|
||||||
UUID: be7acfcb-97a9-42e8-9c71-999491e3afac (XMP)
|
|
||||||
UUID Data:
|
|
||||||
<ns0:xmpmeta xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:ns0="adobe:ns:meta/" xmlns:ns2="http://ns.adobe.com/tiff/1.0/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" ns0:xmptk="Exempi + XMP Core 5.1.2">
|
|
||||||
<rdf:RDF>
|
|
||||||
<rdf:Description rdf:about="">
|
|
||||||
<ns2:ImageWidth>480</ns2:ImageWidth>
|
|
||||||
<ns2:ImageHeight>800</ns2:ImageHeight>
|
|
||||||
<ns2:BitsPerSample>3</ns2:BitsPerSample>
|
|
||||||
</rdf:Description>
|
|
||||||
<rdf:Description rdf:about="">
|
|
||||||
<dc:Title>Stürm und Drang</dc:Title>
|
|
||||||
<dc:Creator>Glymur</dc:Creator>
|
|
||||||
</rdf:Description>
|
|
||||||
</rdf:RDF>
|
|
||||||
</ns0:xmpmeta>
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,8 @@ Contents:
|
||||||
introduction
|
introduction
|
||||||
detailed_installation
|
detailed_installation
|
||||||
how_do_i
|
how_do_i
|
||||||
whatsnew/index
|
|
||||||
roadmap
|
roadmap
|
||||||
|
api
|
||||||
|
|
||||||
------------------
|
------------------
|
||||||
Indices and tables
|
Indices and tables
|
||||||
|
|
|
||||||
|
|
@ -3,33 +3,46 @@ Glymur: a Python interface for JPEG 2000
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
|
|
||||||
**Glymur** is an interface to the OpenJPEG library
|
**Glymur** is an interface to the OpenJPEG library
|
||||||
which allows one to read and write JPEG 2000 files from Python.
|
which allows one to read and write JPEG 2000 files from within Python.
|
||||||
Glymur supports both reading and writing of JPEG 2000 images, but writing
|
Glymur supports both reading and writing of JPEG 2000 images, but writing
|
||||||
JPEG 2000 images is currently limited to images that can fit in memory.
|
JPEG 2000 images is currently limited to images that can fit in memory
|
||||||
**Glymur** can read images using OpenJPEG library versions as far back as 1.3,
|
|
||||||
but it is strongly recommended to use version 2.1.0, which is the most recently
|
|
||||||
released version of OpenJPEG at this time.
|
|
||||||
|
|
||||||
In regards to metadata, most JP2 boxes are properly interpreted.
|
Of particular focus is retrieval of metadata. Reading Exif UUIDs is supported,
|
||||||
Certain optional JP2 boxes can also be written, including XML boxes and
|
as is reading XMP UUIDs as the XMP data packet is just XML. There is
|
||||||
XMP UUIDs. There is incomplete support for reading JPX metadata.
|
some very limited support for reading JPX metadata. For instance,
|
||||||
|
**asoc** and **labl** boxes are recognized, so GMLJP2 metadata can
|
||||||
|
be retrieved from such JPX files.
|
||||||
|
|
||||||
Glymur works on Python versions 2.7, 3.3 and 3.4. If you have Python 2.6,
|
Glymur works on Python 2.6, 2.7, 3.3, and 3.4.
|
||||||
you should use the 0.5 series of Glymur.
|
|
||||||
|
|
||||||
For more information about OpenJPEG, please consult http://www.openjpeg.org.
|
OpenJPEG Installation
|
||||||
|
=====================
|
||||||
|
Glymur will read JPEG 2000 images with versions 1.3, 1.4, 1.5, 2.0, and 2.1 of
|
||||||
|
OpenJPEG. Writing images is only supported with the 1.5 or better, however,
|
||||||
|
and version 2.1 is strongly recommended. For more information about OpenJPEG,
|
||||||
|
please consult http://www.openjpeg.org.
|
||||||
|
|
||||||
|
If you use MacPorts or if you have a sufficiently recent version of
|
||||||
|
Linux, your package manager should already provide you with a version of
|
||||||
|
OpenJPEG 1.X which glymur can already use. If your platform is windows,
|
||||||
|
I suggest using the windows installers provided to you by the OpenJPEG
|
||||||
|
folks at https://code.google.com/p/openjpeg/downloads/list .
|
||||||
|
|
||||||
Glymur Installation
|
Glymur Installation
|
||||||
===================
|
===================
|
||||||
You can retrieve the source for Glymur from either of
|
You can retrieve the source for Glymur from either of
|
||||||
|
|
||||||
* https://pypi.python.org/pypi/Glymur/ (stable releases)
|
* https://pypi.python.org/pypi/Glymur/ (stable releases)
|
||||||
* http://github.com/quintusdias/glymur (bleeding edge, use the devel branch)
|
* http://github.com/quintusdias/glymur (bleeding edge)
|
||||||
|
|
||||||
but you should also be able to install Glymur via pip ::
|
but you should also be able to install Glymur via pip ::
|
||||||
|
|
||||||
$ pip install glymur
|
$ pip install glymur
|
||||||
|
|
||||||
In addition to the package, this also gives you a command line script
|
This will install the **jp2dump** script that can be used from the unix command
|
||||||
**jp2dump** that can be used from the command line line to print JPEG 2000
|
line, so you should adjust your **$PATH**
|
||||||
metadata.
|
to take advantage of it. For example, if you install with pip's
|
||||||
|
`--user` option on linux ::
|
||||||
|
|
||||||
|
$ export PATH=$HOME/.local/bin:$PATH
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,3 @@
|
||||||
------------
|
|
||||||
Known Issues
|
|
||||||
------------
|
|
||||||
|
|
||||||
* Creating a Jp2 file with the irreversible option does not work
|
|
||||||
on windows.
|
|
||||||
* Eval-ing a :py:meth:`repr` string does not work on windows.
|
|
||||||
|
|
||||||
-------
|
-------
|
||||||
Roadmap
|
Roadmap
|
||||||
-------
|
-------
|
||||||
|
|
@ -13,4 +5,8 @@ Roadmap
|
||||||
Here's an incomplete list of what I'd like to focus on in the future.
|
Here's an incomplete list of what I'd like to focus on in the future.
|
||||||
|
|
||||||
* continue to monitor upstream changes in the openjp2 library
|
* continue to monitor upstream changes in the openjp2 library
|
||||||
* investigate JPIP (likely to be a big project)
|
* investigate using CFFI or cython instead of ctypes to wrap openjp2
|
||||||
|
* investigate adding write support for UUID/XMP boxes (potentially a big project)
|
||||||
|
* investigate JPIP (likely to be an even bigger project)
|
||||||
|
|
||||||
|
Support for Python 2.6 will be dropped with the 0.6.0 release.
|
||||||
|
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
=====================
|
|
||||||
Changes in glymur 0.5
|
|
||||||
=====================
|
|
||||||
|
|
||||||
Changes in 0.5.12
|
|
||||||
=================
|
|
||||||
|
|
||||||
* Minor documentation fixes for grammar and style.
|
|
||||||
* The functions removed in 0.5.11 due to API changes in OpenJPEG 2.1.0 were
|
|
||||||
restored for backwards compatibility. They are deprecated, though, and will
|
|
||||||
be removed in 0.6.0.
|
|
||||||
|
|
||||||
* ``glymur.lib.openjp2.stream_create_default_file_stream_v3``
|
|
||||||
* ``glymur.lib.openjp2.opj.stream_destroy_v3``
|
|
||||||
|
|
||||||
|
|
||||||
Changes in 0.5.11
|
|
||||||
=================
|
|
||||||
|
|
||||||
* Added support for Python 3.4.
|
|
||||||
* OpenJPEG 1.5.2 and 2.0.1 are officially supported.
|
|
||||||
* OpenJPEG 2.1.0 is officially supported, but the ABI changes introduced by
|
|
||||||
OpenJPEG 2.1.0 required corresponding changes to glymur's ctypes interface.
|
|
||||||
The functions
|
|
||||||
|
|
||||||
* ``glymur.lib.openjp2.stream_create_default_file_stream_v3``
|
|
||||||
* ``glymur.lib.openjp2.opj.stream_destroy_v3``
|
|
||||||
|
|
||||||
functions were renamed to
|
|
||||||
|
|
||||||
* ``glymur.lib.openjp2.stream_create_default_file_stream``
|
|
||||||
* ``glymur.lib.openjp2.opj.stream_destroy``
|
|
||||||
|
|
||||||
in order to follow OpenJPEG's upstream changes. Unless you were using the
|
|
||||||
svn version of OpenJPEG, you should not be affected by this.
|
|
||||||
|
|
||||||
|
|
||||||
Changes in 0.5.10
|
|
||||||
=================
|
|
||||||
|
|
||||||
* Fixed bad warning issued when an unsupported reader requirement box mask
|
|
||||||
length was encountered.
|
|
||||||
|
|
||||||
Changes in 0.5.9
|
|
||||||
================
|
|
||||||
|
|
||||||
* Fixed bad library load on linux as a result of botched 0.5.8 release.
|
|
||||||
This release was primarily aimed at supporting SunPy.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
=====================
|
|
||||||
Changes in glymur 0.6
|
|
||||||
=====================
|
|
||||||
|
|
||||||
Changes in 0.6.0
|
|
||||||
=================
|
|
||||||
|
|
||||||
* Added Cinema2K, Cinema4K write support.
|
|
||||||
* Added irreversible 9-7 transform write support.
|
|
||||||
* Added set_printoptions, get_printoptions functions.
|
|
||||||
* Added write support for JP2 UUID, data entry URL, palette, and component
|
|
||||||
mapping boxes.
|
|
||||||
* Added read/write support for JPX free, number list, and data reference boxes
|
|
||||||
* Added read support for JPX fragment list and fragment table boxes
|
|
||||||
* Incompatible change to channel definition box constructor, channel_type and
|
|
||||||
association are no longer keyword arguments
|
|
||||||
* Incompatible change to palette box constructor, it now takes a 2D numpy array
|
|
||||||
instead of a list of 1D arrays
|
|
||||||
* Dropped support for 1.3 and 1.4.
|
|
||||||
* Dropped support for Python 2.6.
|
|
||||||
* Dropped windows support. It might still work, I don't have access to a windows
|
|
||||||
box with which to test it.
|
|
||||||
* Added lxml as a package dependency, replacing ElementTree.
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
=====================
|
|
||||||
Changes in glymur 0.7
|
|
||||||
=====================
|
|
||||||
|
|
||||||
Changes in 0.7.3
|
|
||||||
=================
|
|
||||||
|
|
||||||
* added read support back for metadata only when the OpenJPEG library is
|
|
||||||
not installed
|
|
||||||
|
|
||||||
Changes in 0.7.2
|
|
||||||
=================
|
|
||||||
|
|
||||||
* added ellipsis support in array-style slicing
|
|
||||||
|
|
||||||
Changes in 0.7.1
|
|
||||||
=================
|
|
||||||
|
|
||||||
* fixed release notes regarding Python 3.4
|
|
||||||
|
|
||||||
Changes in 0.7.0
|
|
||||||
=================
|
|
||||||
|
|
||||||
* implemented :py:meth:`__getitem__`, :py:meth:`__setitem__` support
|
|
||||||
* added back windows support
|
|
||||||
* box_id and longname are class attributes now instead of instance
|
|
||||||
attributes
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
=====================
|
|
||||||
Changes in glymur 0.8
|
|
||||||
=====================
|
|
||||||
|
|
||||||
Changes in 0.8.0
|
|
||||||
=================
|
|
||||||
|
|
||||||
* Simplified writing images by moving data and options into the
|
|
||||||
constructor.
|
|
||||||
* Deprecated :py:meth:`read` method in favor of array-style slicing.
|
|
||||||
In order to retain certain functionality, the following parameters
|
|
||||||
to the :py:meth:`read` method have become top-level properties
|
|
||||||
|
|
||||||
* verbose
|
|
||||||
* layer
|
|
||||||
* ignore_pclr_cmap_cdef
|
|
||||||
|
|
||||||
* Two additional properties were introduced.
|
|
||||||
|
|
||||||
* codestream
|
|
||||||
* shape
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
.. _whatsnew:
|
|
||||||
|
|
||||||
**********************
|
|
||||||
"What's new" documents
|
|
||||||
**********************
|
|
||||||
|
|
||||||
These document the changes between minor (or major) versions of glymur.
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
|
|
||||||
0.8
|
|
||||||
0.7
|
|
||||||
0.6
|
|
||||||
0.5
|
|
||||||
|
|
@ -1,25 +1,24 @@
|
||||||
"""glymur - read, write, and interrogate JPEG 2000 files
|
"""glymur - read, write, and interrogate JPEG 2000 files
|
||||||
"""
|
"""
|
||||||
import unittest
|
import sys
|
||||||
|
|
||||||
from glymur import version
|
from glymur import version
|
||||||
__version__ = version.version
|
__version__ = version.version
|
||||||
|
|
||||||
from .jp2k import Jp2k
|
from .jp2k import Jp2k
|
||||||
from .jp2box import (get_printoptions,
|
from .jp2dump import jp2dump
|
||||||
set_printoptions,
|
|
||||||
get_parseoptions,
|
|
||||||
set_parseoptions)
|
|
||||||
|
|
||||||
from . import data
|
from . import data
|
||||||
|
|
||||||
|
|
||||||
|
# unittest2 only in python-2.6 (pylint/python2.7 issue)
|
||||||
|
# pylint: disable=F0401
|
||||||
def runtests():
|
def runtests():
|
||||||
"""Discover and run all tests for the glymur package.
|
"""Discover and run all tests for the glymur package.
|
||||||
"""
|
"""
|
||||||
|
if sys.hexversion <= 0x02070000:
|
||||||
|
import unittest2 as unittest
|
||||||
|
else:
|
||||||
|
import unittest
|
||||||
suite = unittest.defaultTestLoader.discover(__path__[0])
|
suite = unittest.defaultTestLoader.discover(__path__[0])
|
||||||
unittest.TextTestRunner(verbosity=2).run(suite)
|
unittest.TextTestRunner(verbosity=2).run(suite)
|
||||||
|
|
||||||
|
|
||||||
__all__ = [__version__, Jp2k, get_printoptions, set_printoptions,
|
|
||||||
get_parseoptions, set_parseoptions, data, runtests]
|
|
||||||
|
|
|
||||||
|
|
@ -1,505 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
Part of glymur.
|
|
||||||
"""
|
|
||||||
from collections import OrderedDict
|
|
||||||
import struct
|
|
||||||
import sys
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
import lxml.etree as ET
|
|
||||||
|
|
||||||
|
|
||||||
def xml(raw_data):
|
|
||||||
"""
|
|
||||||
XMP data to be parsed as XML.
|
|
||||||
"""
|
|
||||||
if sys.hexversion < 0x03000000:
|
|
||||||
elt = ET.fromstring(raw_data)
|
|
||||||
else:
|
|
||||||
text = raw_data.decode('utf-8')
|
|
||||||
elt = ET.fromstring(text)
|
|
||||||
|
|
||||||
return ET.ElementTree(elt)
|
|
||||||
|
|
||||||
|
|
||||||
def tiff_header(read_buffer):
|
|
||||||
"""
|
|
||||||
Interpret the uuid raw data as a tiff header.
|
|
||||||
"""
|
|
||||||
# Ignore the first six bytes.
|
|
||||||
# Next 8 should be (73, 73, 42, 8) or (77, 77, 42, 8)
|
|
||||||
data = struct.unpack('<BB', read_buffer[6:8])
|
|
||||||
if data[0] == 73 and data[1] == 73:
|
|
||||||
# little endian
|
|
||||||
endian = '<'
|
|
||||||
elif data[0] == 77 and data[1] == 77:
|
|
||||||
# big endian
|
|
||||||
endian = '>'
|
|
||||||
else:
|
|
||||||
msg = "The byte order indication in the TIFF header ({0}) is "
|
|
||||||
msg += "invalid. It should be either {1} or {2}."
|
|
||||||
msg = msg.format(read_buffer[6:8], bytes([73, 73]), bytes([77, 77]))
|
|
||||||
raise IOError(msg)
|
|
||||||
|
|
||||||
_, offset = struct.unpack(endian + 'HI', read_buffer[8:14])
|
|
||||||
|
|
||||||
# This is the 'Exif Image' portion.
|
|
||||||
exif = _ExifImageIfd(endian, read_buffer[6:], offset)
|
|
||||||
return exif.processed_ifd
|
|
||||||
|
|
||||||
|
|
||||||
class _Ifd(object):
|
|
||||||
"""
|
|
||||||
Attributes
|
|
||||||
----------
|
|
||||||
read_buffer : bytes
|
|
||||||
Raw byte stream consisting of the UUID data.
|
|
||||||
datatype2fmt : dictionary
|
|
||||||
Class attribute, maps the TIFF enumerated datatype to the python
|
|
||||||
datatype and data width.
|
|
||||||
endian : str
|
|
||||||
Either '<' for big-endian, or '>' for little-endian.
|
|
||||||
num_tags : int
|
|
||||||
Number of tags in the IFD.
|
|
||||||
raw_ifd : dictionary
|
|
||||||
Maps tag number to "mildly-interpreted" tag value.
|
|
||||||
processed_ifd : dictionary
|
|
||||||
Maps tag name to "mildly-interpreted" tag value.
|
|
||||||
"""
|
|
||||||
datatype2fmt = {1: ('B', 1),
|
|
||||||
2: ('B', 1),
|
|
||||||
3: ('H', 2),
|
|
||||||
4: ('I', 4),
|
|
||||||
5: ('II', 8),
|
|
||||||
7: ('B', 1),
|
|
||||||
9: ('i', 4),
|
|
||||||
10: ('ii', 8)}
|
|
||||||
|
|
||||||
def __init__(self, endian, read_buffer, offset):
|
|
||||||
self.endian = endian
|
|
||||||
self.read_buffer = read_buffer
|
|
||||||
self.processed_ifd = OrderedDict()
|
|
||||||
|
|
||||||
self.num_tags, = struct.unpack(endian + 'H',
|
|
||||||
read_buffer[offset:offset + 2])
|
|
||||||
|
|
||||||
fmt = self.endian + 'HHII' * self.num_tags
|
|
||||||
ifd_buffer = read_buffer[offset + 2:offset + 2 + self.num_tags * 12]
|
|
||||||
data = struct.unpack(fmt, ifd_buffer)
|
|
||||||
self.raw_ifd = OrderedDict()
|
|
||||||
for j, tag in enumerate(data[0::4]):
|
|
||||||
# The offset to the tag offset/payload is the offset to the IFD
|
|
||||||
# plus 2 bytes for the number of tags plus 12 bytes for each
|
|
||||||
# tag entry plus 8 bytes to the offset/payload itself.
|
|
||||||
toffp = read_buffer[offset + 10 + j * 12:offset + 10 + j * 12 + 4]
|
|
||||||
tag_data = self.parse_tag(data[j * 4 + 1],
|
|
||||||
data[j * 4 + 2],
|
|
||||||
toffp)
|
|
||||||
self.raw_ifd[tag] = tag_data
|
|
||||||
|
|
||||||
def parse_tag(self, dtype, count, offset_buf):
|
|
||||||
"""Interpret an Exif image tag data payload.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
fmt = self.datatype2fmt[dtype][0] * count
|
|
||||||
payload_size = self.datatype2fmt[dtype][1] * count
|
|
||||||
except KeyError:
|
|
||||||
msg = 'Invalid TIFF tag datatype ({0}).'.format(dtype)
|
|
||||||
raise IOError(msg)
|
|
||||||
|
|
||||||
if payload_size <= 4:
|
|
||||||
# Interpret the payload from the 4 bytes in the tag entry.
|
|
||||||
target_buffer = offset_buf[:payload_size]
|
|
||||||
else:
|
|
||||||
# Interpret the payload at the offset specified by the 4 bytes in
|
|
||||||
# the tag entry.
|
|
||||||
offset, = struct.unpack(self.endian + 'I', offset_buf)
|
|
||||||
target_buffer = self.read_buffer[offset:offset + payload_size]
|
|
||||||
|
|
||||||
if dtype == 2:
|
|
||||||
# ASCII
|
|
||||||
if sys.hexversion < 0x03000000:
|
|
||||||
payload = target_buffer.rstrip('\x00')
|
|
||||||
else:
|
|
||||||
payload = target_buffer.decode('utf-8').rstrip('\x00')
|
|
||||||
|
|
||||||
else:
|
|
||||||
payload = struct.unpack(self.endian + fmt, target_buffer)
|
|
||||||
if dtype == 5 or dtype == 10:
|
|
||||||
# Rational or Signed Rational. Construct the list of values.
|
|
||||||
rational_payload = []
|
|
||||||
for j in range(count):
|
|
||||||
value = float(payload[j * 2]) / float(payload[j * 2 + 1])
|
|
||||||
rational_payload.append(value)
|
|
||||||
payload = rational_payload
|
|
||||||
if count == 1:
|
|
||||||
# If just a single value, then return a scalar instead of a
|
|
||||||
# tuple.
|
|
||||||
payload = payload[0]
|
|
||||||
|
|
||||||
return payload
|
|
||||||
|
|
||||||
def post_process(self, tagnum2name):
|
|
||||||
"""Map the tag name instead of tag number to the tag value.
|
|
||||||
"""
|
|
||||||
for tag, value in self.raw_ifd.items():
|
|
||||||
try:
|
|
||||||
tag_name = tagnum2name[tag]
|
|
||||||
except KeyError:
|
|
||||||
# Ok, we don't recognize this tag. Just use the numeric id.
|
|
||||||
msg = 'Unrecognized Exif tag: {0}'.format(tag)
|
|
||||||
warnings.warn(msg, UserWarning)
|
|
||||||
tag_name = tag
|
|
||||||
self.processed_ifd[tag_name] = value
|
|
||||||
|
|
||||||
|
|
||||||
class _ExifImageIfd(_Ifd):
|
|
||||||
"""
|
|
||||||
Attributes
|
|
||||||
----------
|
|
||||||
tagnum2name : dict
|
|
||||||
Maps Exif image tag numbers to the tag names.
|
|
||||||
ifd : dict
|
|
||||||
Maps tag names to tag values.
|
|
||||||
"""
|
|
||||||
tagnum2name = {11: 'ProcessingSoftware',
|
|
||||||
254: 'NewSubfileType',
|
|
||||||
255: 'SubfileType',
|
|
||||||
256: 'ImageWidth',
|
|
||||||
257: 'ImageLength',
|
|
||||||
258: 'BitsPerSample',
|
|
||||||
259: 'Compression',
|
|
||||||
262: 'PhotometricInterpretation',
|
|
||||||
263: 'Threshholding',
|
|
||||||
264: 'CellWidth',
|
|
||||||
265: 'CellLength',
|
|
||||||
266: 'FillOrder',
|
|
||||||
269: 'DocumentName',
|
|
||||||
270: 'ImageDescription',
|
|
||||||
271: 'Make',
|
|
||||||
272: 'Model',
|
|
||||||
273: 'StripOffsets',
|
|
||||||
274: 'Orientation',
|
|
||||||
277: 'SamplesPerPixel',
|
|
||||||
278: 'RowsPerStrip',
|
|
||||||
279: 'StripByteCounts',
|
|
||||||
282: 'XResolution',
|
|
||||||
283: 'YResolution',
|
|
||||||
284: 'PlanarConfiguration',
|
|
||||||
290: 'GrayResponseUnit',
|
|
||||||
291: 'GrayResponseCurve',
|
|
||||||
292: 'T4Options',
|
|
||||||
293: 'T6Options',
|
|
||||||
296: 'ResolutionUnit',
|
|
||||||
301: 'TransferFunction',
|
|
||||||
305: 'Software',
|
|
||||||
306: 'DateTime',
|
|
||||||
315: 'Artist',
|
|
||||||
316: 'HostComputer',
|
|
||||||
317: 'Predictor',
|
|
||||||
318: 'WhitePoint',
|
|
||||||
319: 'PrimaryChromaticities',
|
|
||||||
320: 'ColorMap',
|
|
||||||
321: 'HalftoneHints',
|
|
||||||
322: 'TileWidth',
|
|
||||||
323: 'TileLength',
|
|
||||||
324: 'TileOffsets',
|
|
||||||
325: 'TileByteCounts',
|
|
||||||
330: 'SubIFDs',
|
|
||||||
332: 'InkSet',
|
|
||||||
333: 'InkNames',
|
|
||||||
334: 'NumberOfInks',
|
|
||||||
336: 'DotRange',
|
|
||||||
337: 'TargetPrinter',
|
|
||||||
338: 'ExtraSamples',
|
|
||||||
339: 'SampleFormat',
|
|
||||||
340: 'SMinSampleValue',
|
|
||||||
341: 'SMaxSampleValue',
|
|
||||||
342: 'TransferRange',
|
|
||||||
343: 'ClipPath',
|
|
||||||
344: 'XClipPathUnits',
|
|
||||||
345: 'YClipPathUnits',
|
|
||||||
346: 'Indexed',
|
|
||||||
347: 'JPEGTables',
|
|
||||||
351: 'OPIProxy',
|
|
||||||
512: 'JPEGProc',
|
|
||||||
513: 'JPEGInterchangeFormat',
|
|
||||||
514: 'JPEGInterchangeFormatLength',
|
|
||||||
515: 'JPEGRestartInterval',
|
|
||||||
517: 'JPEGLosslessPredictors',
|
|
||||||
518: 'JPEGPointTransforms',
|
|
||||||
519: 'JPEGQTables',
|
|
||||||
520: 'JPEGDCTables',
|
|
||||||
521: 'JPEGACTables',
|
|
||||||
529: 'YCbCrCoefficients',
|
|
||||||
530: 'YCbCrSubSampling',
|
|
||||||
531: 'YCbCrPositioning',
|
|
||||||
532: 'ReferenceBlackWhite',
|
|
||||||
700: 'XMLPacket',
|
|
||||||
18246: 'Rating',
|
|
||||||
18249: 'RatingPercent',
|
|
||||||
32781: 'ImageID',
|
|
||||||
33421: 'CFARepeatPatternDim',
|
|
||||||
33422: 'CFAPattern',
|
|
||||||
33423: 'BatteryLevel',
|
|
||||||
33432: 'Copyright',
|
|
||||||
33434: 'ExposureTime',
|
|
||||||
33437: 'FNumber',
|
|
||||||
33723: 'IPTCNAA',
|
|
||||||
34377: 'ImageResources',
|
|
||||||
34665: 'ExifTag',
|
|
||||||
34675: 'InterColorProfile',
|
|
||||||
34850: 'ExposureProgram',
|
|
||||||
34852: 'SpectralSensitivity',
|
|
||||||
34853: 'GPSTag',
|
|
||||||
34855: 'ISOSpeedRatings',
|
|
||||||
34856: 'OECF',
|
|
||||||
34857: 'Interlace',
|
|
||||||
34858: 'TimeZoneOffset',
|
|
||||||
34859: 'SelfTimerMode',
|
|
||||||
36867: 'DateTimeOriginal',
|
|
||||||
37122: 'CompressedBitsPerPixel',
|
|
||||||
37377: 'ShutterSpeedValue',
|
|
||||||
37378: 'ApertureValue',
|
|
||||||
37379: 'BrightnessValue',
|
|
||||||
37380: 'ExposureBiasValue',
|
|
||||||
37381: 'MaxApertureValue',
|
|
||||||
37382: 'SubjectDistance',
|
|
||||||
37383: 'MeteringMode',
|
|
||||||
37384: 'LightSource',
|
|
||||||
37385: 'Flash',
|
|
||||||
37386: 'FocalLength',
|
|
||||||
37387: 'FlashEnergy',
|
|
||||||
37388: 'SpatialFrequencyResponse',
|
|
||||||
37389: 'Noise',
|
|
||||||
37390: 'FocalPlaneXResolution',
|
|
||||||
37391: 'FocalPlaneYResolution',
|
|
||||||
37392: 'FocalPlaneResolutionUnit',
|
|
||||||
37393: 'ImageNumber',
|
|
||||||
37394: 'SecurityClassification',
|
|
||||||
37395: 'ImageHistory',
|
|
||||||
37396: 'SubjectLocation',
|
|
||||||
37397: 'ExposureIndex',
|
|
||||||
37398: 'TIFFEPStandardID',
|
|
||||||
37399: 'SensingMethod',
|
|
||||||
40091: 'XPTitle',
|
|
||||||
40092: 'XPComment',
|
|
||||||
40093: 'XPAuthor',
|
|
||||||
40094: 'XPKeywords',
|
|
||||||
40095: 'XPSubject',
|
|
||||||
50341: 'PrintImageMatching',
|
|
||||||
50706: 'DNGVersion',
|
|
||||||
50707: 'DNGBackwardVersion',
|
|
||||||
50708: 'UniqueCameraModel',
|
|
||||||
50709: 'LocalizedCameraModel',
|
|
||||||
50710: 'CFAPlaneColor',
|
|
||||||
50711: 'CFALayout',
|
|
||||||
50712: 'LinearizationTable',
|
|
||||||
50713: 'BlackLevelRepeatDim',
|
|
||||||
50714: 'BlackLevel',
|
|
||||||
50715: 'BlackLevelDeltaH',
|
|
||||||
50716: 'BlackLevelDeltaV',
|
|
||||||
50717: 'WhiteLevel',
|
|
||||||
50718: 'DefaultScale',
|
|
||||||
50719: 'DefaultCropOrigin',
|
|
||||||
50720: 'DefaultCropSize',
|
|
||||||
50721: 'ColorMatrix1',
|
|
||||||
50722: 'ColorMatrix2',
|
|
||||||
50723: 'CameraCalibration1',
|
|
||||||
50724: 'CameraCalibration2',
|
|
||||||
50725: 'ReductionMatrix1',
|
|
||||||
50726: 'ReductionMatrix2',
|
|
||||||
50727: 'AnalogBalance',
|
|
||||||
50728: 'AsShotNeutral',
|
|
||||||
50729: 'AsShotWhiteXY',
|
|
||||||
50730: 'BaselineExposure',
|
|
||||||
50731: 'BaselineNoise',
|
|
||||||
50732: 'BaselineSharpness',
|
|
||||||
50733: 'BayerGreenSplit',
|
|
||||||
50734: 'LinearResponseLimit',
|
|
||||||
50735: 'CameraSerialNumber',
|
|
||||||
50736: 'LensInfo',
|
|
||||||
50737: 'ChromaBlurRadius',
|
|
||||||
50738: 'AntiAliasStrength',
|
|
||||||
50739: 'ShadowScale',
|
|
||||||
50740: 'DNGPrivateData',
|
|
||||||
50741: 'MakerNoteSafety',
|
|
||||||
50778: 'CalibrationIlluminant1',
|
|
||||||
50779: 'CalibrationIlluminant2',
|
|
||||||
50780: 'BestQualityScale',
|
|
||||||
50781: 'RawDataUniqueID',
|
|
||||||
50827: 'OriginalRawFileName',
|
|
||||||
50828: 'OriginalRawFileData',
|
|
||||||
50829: 'ActiveArea',
|
|
||||||
50830: 'MaskedAreas',
|
|
||||||
50831: 'AsShotICCProfile',
|
|
||||||
50832: 'AsShotPreProfileMatrix',
|
|
||||||
50833: 'CurrentICCProfile',
|
|
||||||
50834: 'CurrentPreProfileMatrix',
|
|
||||||
50879: 'ColorimetricReference',
|
|
||||||
50931: 'CameraCalibrationSignature',
|
|
||||||
50932: 'ProfileCalibrationSignature',
|
|
||||||
50934: 'AsShotProfileName',
|
|
||||||
50935: 'NoiseReductionApplied',
|
|
||||||
50936: 'ProfileName',
|
|
||||||
50937: 'ProfileHueSatMapDims',
|
|
||||||
50938: 'ProfileHueSatMapData1',
|
|
||||||
50939: 'ProfileHueSatMapData2',
|
|
||||||
50940: 'ProfileToneCurve',
|
|
||||||
50941: 'ProfileEmbedPolicy',
|
|
||||||
50942: 'ProfileCopyright',
|
|
||||||
50964: 'ForwardMatrix1',
|
|
||||||
50965: 'ForwardMatrix2',
|
|
||||||
50966: 'PreviewApplicationName',
|
|
||||||
50967: 'PreviewApplicationVersion',
|
|
||||||
50968: 'PreviewSettingsName',
|
|
||||||
50969: 'PreviewSettingsDigest',
|
|
||||||
50970: 'PreviewColorSpace',
|
|
||||||
50971: 'PreviewDateTime',
|
|
||||||
50972: 'RawImageDigest',
|
|
||||||
50973: 'OriginalRawFileDigest',
|
|
||||||
50974: 'SubTileBlockSize',
|
|
||||||
50975: 'RowInterleaveFactor',
|
|
||||||
50981: 'ProfileLookTableDims',
|
|
||||||
50982: 'ProfileLookTableData',
|
|
||||||
51008: 'OpcodeList1',
|
|
||||||
51009: 'OpcodeList2',
|
|
||||||
51022: 'OpcodeList3',
|
|
||||||
51041: 'NoiseProfile'}
|
|
||||||
|
|
||||||
def __init__(self, endian, read_buffer, offset):
|
|
||||||
_Ifd.__init__(self, endian, read_buffer, offset)
|
|
||||||
self.post_process(self.tagnum2name)
|
|
||||||
|
|
||||||
|
|
||||||
class _ExifPhotoIfd(_Ifd):
|
|
||||||
"""Represents tags found in the Exif sub ifd.
|
|
||||||
"""
|
|
||||||
tagnum2name = {33434: 'ExposureTime',
|
|
||||||
33437: 'FNumber',
|
|
||||||
34850: 'ExposureProgram',
|
|
||||||
34852: 'SpectralSensitivity',
|
|
||||||
34855: 'ISOSpeedRatings',
|
|
||||||
34856: 'OECF',
|
|
||||||
34864: 'SensitivityType',
|
|
||||||
34865: 'StandardOutputSensitivity',
|
|
||||||
34866: 'RecommendedExposureIndex',
|
|
||||||
34867: 'ISOSpeed',
|
|
||||||
34868: 'ISOSpeedLatitudeyyy',
|
|
||||||
34869: 'ISOSpeedLatitudezzz',
|
|
||||||
36864: 'ExifVersion',
|
|
||||||
36867: 'DateTimeOriginal',
|
|
||||||
36868: 'DateTimeDigitized',
|
|
||||||
37121: 'ComponentsConfiguration',
|
|
||||||
37122: 'CompressedBitsPerPixel',
|
|
||||||
37377: 'ShutterSpeedValue',
|
|
||||||
37378: 'ApertureValue',
|
|
||||||
37379: 'BrightnessValue',
|
|
||||||
37380: 'ExposureBiasValue',
|
|
||||||
37381: 'MaxApertureValue',
|
|
||||||
37382: 'SubjectDistance',
|
|
||||||
37383: 'MeteringMode',
|
|
||||||
37384: 'LightSource',
|
|
||||||
37385: 'Flash',
|
|
||||||
37386: 'FocalLength',
|
|
||||||
37396: 'SubjectArea',
|
|
||||||
37500: 'MakerNote',
|
|
||||||
37510: 'UserComment',
|
|
||||||
37520: 'SubSecTime',
|
|
||||||
37521: 'SubSecTimeOriginal',
|
|
||||||
37522: 'SubSecTimeDigitized',
|
|
||||||
40960: 'FlashpixVersion',
|
|
||||||
40961: 'ColorSpace',
|
|
||||||
40962: 'PixelXDimension',
|
|
||||||
40963: 'PixelYDimension',
|
|
||||||
40964: 'RelatedSoundFile',
|
|
||||||
40965: 'InteroperabilityTag',
|
|
||||||
41483: 'FlashEnergy',
|
|
||||||
41484: 'SpatialFrequencyResponse',
|
|
||||||
41486: 'FocalPlaneXResolution',
|
|
||||||
41487: 'FocalPlaneYResolution',
|
|
||||||
41488: 'FocalPlaneResolutionUnit',
|
|
||||||
41492: 'SubjectLocation',
|
|
||||||
41493: 'ExposureIndex',
|
|
||||||
41495: 'SensingMethod',
|
|
||||||
41728: 'FileSource',
|
|
||||||
41729: 'SceneType',
|
|
||||||
41730: 'CFAPattern',
|
|
||||||
41985: 'CustomRendered',
|
|
||||||
41986: 'ExposureMode',
|
|
||||||
41987: 'WhiteBalance',
|
|
||||||
41988: 'DigitalZoomRatio',
|
|
||||||
41989: 'FocalLengthIn35mmFilm',
|
|
||||||
41990: 'SceneCaptureType',
|
|
||||||
41991: 'GainControl',
|
|
||||||
41992: 'Contrast',
|
|
||||||
41993: 'Saturation',
|
|
||||||
41994: 'Sharpness',
|
|
||||||
41995: 'DeviceSettingDescription',
|
|
||||||
41996: 'SubjectDistanceRange',
|
|
||||||
42016: 'ImageUniqueID',
|
|
||||||
42032: 'CameraOwnerName',
|
|
||||||
42033: 'BodySerialNumber',
|
|
||||||
42034: 'LensSpecification',
|
|
||||||
42035: 'LensMake',
|
|
||||||
42036: 'LensModel',
|
|
||||||
42037: 'LensSerialNumber'}
|
|
||||||
|
|
||||||
def __init__(self, endian, read_buffer, offset):
|
|
||||||
_Ifd.__init__(self, endian, read_buffer, offset)
|
|
||||||
self.post_process(self.tagnum2name)
|
|
||||||
|
|
||||||
|
|
||||||
class _ExifGPSInfoIfd(_Ifd):
|
|
||||||
"""Represents information found in the GPSInfo sub IFD.
|
|
||||||
"""
|
|
||||||
tagnum2name = {0: 'GPSVersionID',
|
|
||||||
1: 'GPSLatitudeRef',
|
|
||||||
2: 'GPSLatitude',
|
|
||||||
3: 'GPSLongitudeRef',
|
|
||||||
4: 'GPSLongitude',
|
|
||||||
5: 'GPSAltitudeRef',
|
|
||||||
6: 'GPSAltitude',
|
|
||||||
7: 'GPSTimeStamp',
|
|
||||||
8: 'GPSSatellites',
|
|
||||||
9: 'GPSStatus',
|
|
||||||
10: 'GPSMeasureMode',
|
|
||||||
11: 'GPSDOP',
|
|
||||||
12: 'GPSSpeedRef',
|
|
||||||
13: 'GPSSpeed',
|
|
||||||
14: 'GPSTrackRef',
|
|
||||||
15: 'GPSTrack',
|
|
||||||
16: 'GPSImgDirectionRef',
|
|
||||||
17: 'GPSImgDirection',
|
|
||||||
18: 'GPSMapDatum',
|
|
||||||
19: 'GPSDestLatitudeRef',
|
|
||||||
20: 'GPSDestLatitude',
|
|
||||||
21: 'GPSDestLongitudeRef',
|
|
||||||
22: 'GPSDestLongitude',
|
|
||||||
23: 'GPSDestBearingRef',
|
|
||||||
24: 'GPSDestBearing',
|
|
||||||
25: 'GPSDestDistanceRef',
|
|
||||||
26: 'GPSDestDistance',
|
|
||||||
27: 'GPSProcessingMethod',
|
|
||||||
28: 'GPSAreaInformation',
|
|
||||||
29: 'GPSDateStamp',
|
|
||||||
30: 'GPSDifferential'}
|
|
||||||
|
|
||||||
def __init__(self, endian, read_buffer, offset):
|
|
||||||
_Ifd.__init__(self, endian, read_buffer, offset)
|
|
||||||
self.post_process(self.tagnum2name)
|
|
||||||
|
|
||||||
|
|
||||||
class _ExifInteroperabilityIfd(_Ifd):
|
|
||||||
"""Represents tags found in the Interoperability sub IFD.
|
|
||||||
"""
|
|
||||||
tagnum2name = {1: 'InteroperabilityIndex',
|
|
||||||
2: 'InteroperabilityVersion',
|
|
||||||
4096: 'RelatedImageFileFormat',
|
|
||||||
4097: 'RelatedImageWidth',
|
|
||||||
4098: 'RelatedImageLength'}
|
|
||||||
|
|
||||||
def __init__(self, endian, read_buffer, offset):
|
|
||||||
_Ifd.__init__(self, endian, read_buffer, offset)
|
|
||||||
self.post_process(self.tagnum2name)
|
|
||||||
|
|
@ -6,13 +6,16 @@ codestreams.
|
||||||
|
|
||||||
# The number of lines in the module is long and that's ok. It would not help
|
# The number of lines in the module is long and that's ok. It would not help
|
||||||
# matters to move anything out to another file.
|
# matters to move anything out to another file.
|
||||||
|
# pylint: disable=C0302
|
||||||
|
|
||||||
# "Too many instance attributes", "Too many arguments"
|
# "Too many instance attributes", "Too many arguments"
|
||||||
# Some segments just have a lot of information.
|
# Some segments just have a lot of information.
|
||||||
# It doesn't make sense to subclass just for that.
|
# It doesn't make sense to subclass just for that.
|
||||||
|
# pylint: disable=R0902,R0913
|
||||||
|
|
||||||
# "Too few public methods" Some segments don't define any new methods from
|
# "Too few public methods" Some segments don't define any new methods from
|
||||||
# the base Segment class.
|
# the base Segment class.
|
||||||
|
# pylint: disable=R0903
|
||||||
|
|
||||||
import math
|
import math
|
||||||
import struct
|
import struct
|
||||||
|
|
@ -21,37 +24,22 @@ import warnings
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from .core import (LRCP, RLCP, RPCL, PCRL, CPRL,
|
from .core import LRCP, RLCP, RPCL, PCRL, CPRL
|
||||||
WAVELET_XFORM_9X7_IRREVERSIBLE,
|
from .core import WAVELET_XFORM_9X7_IRREVERSIBLE
|
||||||
WAVELET_XFORM_5X3_REVERSIBLE,
|
from .core import WAVELET_XFORM_5X3_REVERSIBLE
|
||||||
_Keydefaultdict)
|
from .core import _CAPABILITIES_DISPLAY
|
||||||
from .lib import openjp2 as opj2
|
from .lib import openjp2 as opj2
|
||||||
|
|
||||||
_factory = lambda x: '{0} (invalid)'.format(x)
|
_PROGRESSION_ORDER_DISPLAY = {
|
||||||
_PROGRESSION_ORDER_DISPLAY = _Keydefaultdict(_factory, {LRCP: 'LRCP',
|
LRCP: 'LRCP',
|
||||||
RLCP: 'RLCP',
|
RLCP: 'RLCP',
|
||||||
RPCL: 'RPCL',
|
RPCL: 'RPCL',
|
||||||
PCRL: 'PCRL',
|
PCRL: 'PCRL',
|
||||||
CPRL: 'CPRL'})
|
CPRL: 'CPRL'}
|
||||||
|
|
||||||
_keysvalues = {WAVELET_XFORM_9X7_IRREVERSIBLE: '9-7 irreversible',
|
_WAVELET_TRANSFORM_DISPLAY = {
|
||||||
WAVELET_XFORM_5X3_REVERSIBLE: '5-3 reversible'}
|
WAVELET_XFORM_9X7_IRREVERSIBLE: '9-7 irreversible',
|
||||||
_WAVELET_TRANSFORM_DISPLAY = _Keydefaultdict(_factory, _keysvalues)
|
WAVELET_XFORM_5X3_REVERSIBLE: '5-3 reversible'}
|
||||||
|
|
||||||
_NO_PROFILE = 0
|
|
||||||
_PROFILE_0 = 1
|
|
||||||
_PROFILE_1 = 2
|
|
||||||
_PROFILE_3 = 3
|
|
||||||
_PROFILE_4 = 4
|
|
||||||
|
|
||||||
_KNOWN_PROFILES = [_NO_PROFILE, _PROFILE_0, _PROFILE_1, _PROFILE_3, _PROFILE_4]
|
|
||||||
|
|
||||||
# How to display the codestream profile.
|
|
||||||
_CAPABILITIES_DISPLAY = _Keydefaultdict(_factory, {_NO_PROFILE: 'no profile',
|
|
||||||
_PROFILE_0: '0',
|
|
||||||
_PROFILE_1: '1',
|
|
||||||
_PROFILE_3: 'Cinema 2K',
|
|
||||||
_PROFILE_4: 'Cinema 4K'})
|
|
||||||
|
|
||||||
# Need a catch-all list of valid markers.
|
# Need a catch-all list of valid markers.
|
||||||
# See table A-1 in ISO/IEC FCD15444-1.
|
# See table A-1 in ISO/IEC FCD15444-1.
|
||||||
|
|
@ -183,7 +171,15 @@ class Codestream(object):
|
||||||
while True:
|
while True:
|
||||||
|
|
||||||
read_buffer = fptr.read(2)
|
read_buffer = fptr.read(2)
|
||||||
self._marker_id, = struct.unpack('>H', read_buffer)
|
try:
|
||||||
|
self._marker_id, = struct.unpack('>H', read_buffer)
|
||||||
|
except struct.error:
|
||||||
|
# Treat this as a warning.
|
||||||
|
msg = "Marker had length {0} instead of expected length of 2 "
|
||||||
|
msg += "bytes. Codestream parsing terminated."
|
||||||
|
warnings.warn(msg.format(len(read_buffer)))
|
||||||
|
break
|
||||||
|
|
||||||
self._offset = fptr.tell() - 2
|
self._offset = fptr.tell() - 2
|
||||||
|
|
||||||
if self._marker_id == 0xff90 and header_only:
|
if self._marker_id == 0xff90 and header_only:
|
||||||
|
|
@ -197,7 +193,7 @@ class Codestream(object):
|
||||||
msg = 'Invalid marker id encountered at byte {0:d} '
|
msg = 'Invalid marker id encountered at byte {0:d} '
|
||||||
msg += 'in codestream: "0x{1:x}"'
|
msg += 'in codestream: "0x{1:x}"'
|
||||||
msg = msg.format(self._offset, self._marker_id)
|
msg = msg.format(self._offset, self._marker_id)
|
||||||
warnings.warn(msg, UserWarning)
|
warnings.warn(msg)
|
||||||
break
|
break
|
||||||
|
|
||||||
self.segment.append(segment)
|
self.segment.append(segment)
|
||||||
|
|
@ -293,6 +289,7 @@ class Codestream(object):
|
||||||
msg += ''.join(strs)
|
msg += ''.join(strs)
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
# pylint: disable=R0201
|
||||||
def _parse_cme_segment(self, fptr):
|
def _parse_cme_segment(self, fptr):
|
||||||
"""Parse the CME marker segment.
|
"""Parse the CME marker segment.
|
||||||
|
|
||||||
|
|
@ -366,22 +363,14 @@ class Codestream(object):
|
||||||
COD segment instance.
|
COD segment instance.
|
||||||
"""
|
"""
|
||||||
offset = fptr.tell() - 2
|
offset = fptr.tell() - 2
|
||||||
|
offset = fptr.tell() - 2
|
||||||
|
|
||||||
read_buffer = fptr.read(2)
|
read_buffer = fptr.read(3)
|
||||||
length, = struct.unpack('>H', read_buffer)
|
length, scod = struct.unpack('>HB', read_buffer)
|
||||||
|
|
||||||
read_buffer = fptr.read(length - 2)
|
numbytes = offset + 2 + length - fptr.tell()
|
||||||
scod, = struct.unpack_from('>B', read_buffer, offset=0)
|
spcod = fptr.read(numbytes)
|
||||||
spcod = read_buffer[1:]
|
|
||||||
spcod = np.frombuffer(spcod, dtype=np.uint8)
|
spcod = np.frombuffer(spcod, dtype=np.uint8)
|
||||||
if spcod[0] not in [LRCP, RLCP, RPCL, PCRL, CPRL]:
|
|
||||||
msg = "Invalid progression order in COD segment: {0}."
|
|
||||||
warnings.warn(msg.format(spcod[0]))
|
|
||||||
|
|
||||||
if spcod[8] not in [WAVELET_XFORM_9X7_IRREVERSIBLE,
|
|
||||||
WAVELET_XFORM_5X3_REVERSIBLE]:
|
|
||||||
msg = "Invalid wavelet transform in COD segment: {0}."
|
|
||||||
warnings.warn(msg.format(spcod[8]))
|
|
||||||
|
|
||||||
sop = (scod & 2) > 0
|
sop = (scod & 2) > 0
|
||||||
eph = (scod & 4) > 0
|
eph = (scod & 4) > 0
|
||||||
|
|
@ -569,21 +558,22 @@ class Codestream(object):
|
||||||
read_buffer = fptr.read(2)
|
read_buffer = fptr.read(2)
|
||||||
length, = struct.unpack('>H', read_buffer)
|
length, = struct.unpack('>H', read_buffer)
|
||||||
|
|
||||||
read_buffer = fptr.read(length - 2)
|
|
||||||
if self._csiz > 256:
|
if self._csiz > 256:
|
||||||
|
read_buffer = fptr.read(3)
|
||||||
fmt = '>HB'
|
fmt = '>HB'
|
||||||
mantissa_exponent_offset = 3
|
mantissa_exponent_buffer_length = length - 5
|
||||||
else:
|
else:
|
||||||
|
read_buffer = fptr.read(2)
|
||||||
fmt = '>BB'
|
fmt = '>BB'
|
||||||
mantissa_exponent_offset = 2
|
mantissa_exponent_buffer_length = length - 4
|
||||||
cqcc, sqcc = struct.unpack_from(fmt, read_buffer)
|
cqcc, sqcc = struct.unpack(fmt, read_buffer)
|
||||||
if cqcc >= self._csiz:
|
if cqcc >= self._csiz:
|
||||||
msg = "Invalid component number ({0}), "
|
msg = "Invalid component number ({0}), "
|
||||||
msg += "number of components is only {1}."
|
msg += "number of components is only {1}."
|
||||||
msg = msg.format(cqcc, self._csiz)
|
msg = msg.format(cqcc, self._csiz)
|
||||||
warnings.warn(msg)
|
warnings.warn(msg)
|
||||||
|
|
||||||
spqcc = read_buffer[mantissa_exponent_offset:]
|
spqcc = fptr.read(mantissa_exponent_buffer_length)
|
||||||
|
|
||||||
return QCCsegment(cqcc, sqcc, spqcc, length, offset)
|
return QCCsegment(cqcc, sqcc, spqcc, length, offset)
|
||||||
|
|
||||||
|
|
@ -636,7 +626,7 @@ class Codestream(object):
|
||||||
srgn = data[1]
|
srgn = data[1]
|
||||||
sprgn = data[2]
|
sprgn = data[2]
|
||||||
|
|
||||||
return RGNsegment(crgn, srgn, sprgn, length, offset)
|
return RGNsegment(length, offset, crgn, srgn, sprgn)
|
||||||
|
|
||||||
def _parse_siz_segment(self, fptr):
|
def _parse_siz_segment(self, fptr):
|
||||||
"""Parse the SIZ segment.
|
"""Parse the SIZ segment.
|
||||||
|
|
@ -655,63 +645,17 @@ class Codestream(object):
|
||||||
read_buffer = fptr.read(2)
|
read_buffer = fptr.read(2)
|
||||||
length, = struct.unpack('>H', read_buffer)
|
length, = struct.unpack('>H', read_buffer)
|
||||||
|
|
||||||
read_buffer = fptr.read(length - 2)
|
xy_buffer = fptr.read(36)
|
||||||
data = struct.unpack_from('>HIIIIIIIIH', read_buffer)
|
|
||||||
|
|
||||||
rsiz = data[0]
|
num_components, = struct.unpack('>H', xy_buffer[-2:])
|
||||||
if rsiz not in _KNOWN_PROFILES:
|
|
||||||
warnings.warn("Invalid profile: (Rsiz={0}).".format(rsiz))
|
|
||||||
|
|
||||||
xysiz = (data[1], data[2])
|
component_buffer = fptr.read(num_components * 3)
|
||||||
xyosiz = (data[3], data[4])
|
|
||||||
xytsiz = (data[5], data[6])
|
|
||||||
xytosiz = (data[7], data[8])
|
|
||||||
|
|
||||||
# Csiz is the number of components
|
segment = SIZsegment(xy_buffer, component_buffer, length, offset)
|
||||||
Csiz = data[9]
|
|
||||||
|
|
||||||
data = struct.unpack_from('>' + 'B' * (length - 36 - 2),
|
|
||||||
read_buffer, offset=36)
|
|
||||||
|
|
||||||
bitdepth = tuple(((x & 0x7f) + 1) for x in data[0::3])
|
|
||||||
signed = tuple(((x & 0x80) > 0) for x in data[0::3])
|
|
||||||
xrsiz = data[1::3]
|
|
||||||
yrsiz = data[2::3]
|
|
||||||
|
|
||||||
for j, subsampling in enumerate(zip(xrsiz, yrsiz)):
|
|
||||||
if 0 in subsampling:
|
|
||||||
msg = "Invalid subsampling value for component {0}: "
|
|
||||||
msg += "dx={1}, dy={2}."
|
|
||||||
msg = msg.format(j, subsampling[0], subsampling[1])
|
|
||||||
warnings.warn(msg)
|
|
||||||
|
|
||||||
try:
|
|
||||||
num_tiles_x = (xysiz[0] - xyosiz[0]) / (xytsiz[0] - xytosiz[0])
|
|
||||||
num_tiles_y = (xysiz[1] - xyosiz[1]) / (xytsiz[1] - xytosiz[1])
|
|
||||||
except ZeroDivisionError:
|
|
||||||
warnings.warn("Invalid tile dimensions.")
|
|
||||||
else:
|
|
||||||
numtiles = math.ceil(num_tiles_x) * math.ceil(num_tiles_y)
|
|
||||||
if numtiles > 65535:
|
|
||||||
msg = "Invalid number of tiles ({0}).".format(numtiles)
|
|
||||||
warnings.warn(msg)
|
|
||||||
|
|
||||||
kwargs = {'rsiz': rsiz,
|
|
||||||
'xysiz': xysiz,
|
|
||||||
'xyosiz': xyosiz,
|
|
||||||
'xytsiz': xytsiz,
|
|
||||||
'xytosiz': xytosiz,
|
|
||||||
'Csiz': Csiz,
|
|
||||||
'bitdepth': bitdepth,
|
|
||||||
'signed': signed,
|
|
||||||
'xyrsiz': (xrsiz, yrsiz),
|
|
||||||
'length': length,
|
|
||||||
'offset': offset}
|
|
||||||
segment = SIZsegment(**kwargs)
|
|
||||||
|
|
||||||
# Need to keep track of the number of components from SIZ for
|
# Need to keep track of the number of components from SIZ for
|
||||||
# other markers.
|
# other markers
|
||||||
self._csiz = Csiz
|
self._csiz = len(segment.ssiz)
|
||||||
|
|
||||||
return segment
|
return segment
|
||||||
|
|
||||||
|
|
@ -787,8 +731,8 @@ class Codestream(object):
|
||||||
read_buffer = fptr.read(2)
|
read_buffer = fptr.read(2)
|
||||||
length, = struct.unpack('>H', read_buffer)
|
length, = struct.unpack('>H', read_buffer)
|
||||||
|
|
||||||
read_buffer = fptr.read(length - 2)
|
read_buffer = fptr.read(2)
|
||||||
ztlm, stlm = struct.unpack_from('>BB', read_buffer)
|
ztlm, stlm = struct.unpack('>BB', read_buffer)
|
||||||
ttlm_st = (stlm >> 4) & 0x3
|
ttlm_st = (stlm >> 4) & 0x3
|
||||||
ptlm_sp = (stlm >> 6) & 0x1
|
ptlm_sp = (stlm >> 6) & 0x1
|
||||||
|
|
||||||
|
|
@ -798,6 +742,7 @@ class Codestream(object):
|
||||||
else:
|
else:
|
||||||
ntiles = nbytes / (ttlm_st + (ptlm_sp + 1) * 2)
|
ntiles = nbytes / (ttlm_st + (ptlm_sp + 1) * 2)
|
||||||
|
|
||||||
|
read_buffer = fptr.read(nbytes)
|
||||||
if ttlm_st == 0:
|
if ttlm_st == 0:
|
||||||
ttlm = None
|
ttlm = None
|
||||||
fmt = ''
|
fmt = ''
|
||||||
|
|
@ -811,8 +756,7 @@ class Codestream(object):
|
||||||
else:
|
else:
|
||||||
fmt += 'I'
|
fmt += 'I'
|
||||||
|
|
||||||
data = struct.unpack_from('>' + fmt * int(ntiles), read_buffer,
|
data = struct.unpack('>' + fmt * int(ntiles), read_buffer)
|
||||||
offset=2)
|
|
||||||
if ttlm_st == 0:
|
if ttlm_st == 0:
|
||||||
ttlm = None
|
ttlm = None
|
||||||
ptlm = data
|
ptlm = data
|
||||||
|
|
@ -822,6 +766,7 @@ class Codestream(object):
|
||||||
|
|
||||||
return TLMsegment(length, offset, ztlm, ttlm, ptlm)
|
return TLMsegment(length, offset, ztlm, ttlm, ptlm)
|
||||||
|
|
||||||
|
# pylint: disable=W0613
|
||||||
def _parse_reserved_marker(self, fptr):
|
def _parse_reserved_marker(self, fptr):
|
||||||
"""Marker range between 0xff30 and 0xff39.
|
"""Marker range between 0xff30 and 0xff39.
|
||||||
"""
|
"""
|
||||||
|
|
@ -1066,7 +1011,7 @@ class CMEsegment(Segment):
|
||||||
15444-1:2004 - Information technology -- JPEG 2000 image coding system:
|
15444-1:2004 - Information technology -- JPEG 2000 image coding system:
|
||||||
Core coding system
|
Core coding system
|
||||||
"""
|
"""
|
||||||
def __init__(self, rcme, ccme, length=-1, offset=-1):
|
def __init__(self, rcme, ccme, length, offset):
|
||||||
Segment.__init__(self, marker_id='CME')
|
Segment.__init__(self, marker_id='CME')
|
||||||
self.rcme = rcme
|
self.rcme = rcme
|
||||||
self.ccme = ccme
|
self.ccme = ccme
|
||||||
|
|
@ -1455,7 +1400,7 @@ class RGNsegment(Segment):
|
||||||
15444-1:2004 - Information technology -- JPEG 2000 image coding system:
|
15444-1:2004 - Information technology -- JPEG 2000 image coding system:
|
||||||
Core coding system
|
Core coding system
|
||||||
"""
|
"""
|
||||||
def __init__(self, crgn, srgn, sprgn, length=-1, offset=-1):
|
def __init__(self, length, offset, crgn, srgn, sprgn):
|
||||||
Segment.__init__(self, marker_id='RGN')
|
Segment.__init__(self, marker_id='RGN')
|
||||||
self.length = length
|
self.length = length
|
||||||
self.offset = offset
|
self.offset = offset
|
||||||
|
|
@ -1496,8 +1441,8 @@ class SIZsegment(Segment):
|
||||||
Width and height of reference tile with respect to the reference grid.
|
Width and height of reference tile with respect to the reference grid.
|
||||||
xtosiz, ytosiz : int
|
xtosiz, ytosiz : int
|
||||||
Horizontal and vertical offsets of tile from origin of reference grid.
|
Horizontal and vertical offsets of tile from origin of reference grid.
|
||||||
Csiz : int
|
ssiz : iterable bytes
|
||||||
Number of components in image.
|
Encoded precision (depth) in bits and sign of each component.
|
||||||
bitdepth : iterable bytes
|
bitdepth : iterable bytes
|
||||||
Precision (depth) in bits of each component.
|
Precision (depth) in bits of each component.
|
||||||
signed : iterable bool
|
signed : iterable bool
|
||||||
|
|
@ -1512,45 +1457,48 @@ class SIZsegment(Segment):
|
||||||
15444-1:2004 - Information technology -- JPEG 2000 image coding system:
|
15444-1:2004 - Information technology -- JPEG 2000 image coding system:
|
||||||
Core coding system
|
Core coding system
|
||||||
"""
|
"""
|
||||||
def __init__(self, rsiz=-1, xysiz=None, xyosiz=-1, xytsiz=-1, xytosiz=-1,
|
def __init__(self, xy_buffer, component_buffer, length, offset):
|
||||||
Csiz=-1, bitdepth=None, signed=None, xyrsiz=-1, length=-1,
|
Segment.__init__(self, marker_id='SIZ')
|
||||||
offset=-1):
|
|
||||||
Segment.__init__(self, marker_id='SIZ', length=length, offset=offset)
|
|
||||||
|
|
||||||
self.rsiz = rsiz
|
data = struct.unpack('>HIIIIIIIIH', xy_buffer)
|
||||||
self.xsiz, self.ysiz = xysiz
|
|
||||||
self.xosiz, self.yosiz = xyosiz
|
|
||||||
self.xtsiz, self.ytsiz = xytsiz
|
|
||||||
self.xtosiz, self.ytosiz = xytosiz
|
|
||||||
self.Csiz = Csiz
|
|
||||||
self.bitdepth = bitdepth
|
|
||||||
self.signed = signed
|
|
||||||
self.xrsiz, self.yrsiz = xyrsiz
|
|
||||||
|
|
||||||
# ssiz attribute to be removed in 1.0.0
|
self.rsiz = data[0]
|
||||||
lst = []
|
self.xsiz = data[1]
|
||||||
for bitdepth, signed in zip(self.bitdepth, self.signed):
|
self.ysiz = data[2]
|
||||||
if signed:
|
self.xosiz = data[3]
|
||||||
lst.append((bitdepth - 1) | 0x80)
|
self.yosiz = data[4]
|
||||||
else:
|
self.xtsiz = data[5]
|
||||||
lst.append(bitdepth - 1)
|
self.ytsiz = data[6]
|
||||||
self.ssiz = tuple(lst)
|
self.xtosiz = data[7]
|
||||||
|
self.ytosiz = data[8]
|
||||||
|
# disregarding the last element in data
|
||||||
|
|
||||||
def __repr__(self):
|
num_tiles_x = (self.xsiz - self.xosiz) / (self.xtsiz - self.xtosiz)
|
||||||
msg = "glymur.codestream.SIZsegment(rsiz={rsiz}, xysiz={xysiz}, "
|
num_tiles_y = (self.ysiz - self.yosiz) / (self.ytsiz - self.ytosiz)
|
||||||
msg += "xyosiz={xyosiz}, xytsiz={xytsiz}, xytosiz={xytosiz}, "
|
numtiles = math.ceil(num_tiles_x) * math.ceil(num_tiles_y)
|
||||||
msg += "Csiz={Csiz}, bitdepth={bitdepth}, signed={signed}, "
|
if numtiles > 65535:
|
||||||
msg += "xyrsiz={xyrsiz})"
|
msg = "Invalid number of tiles ({0}).".format(numtiles)
|
||||||
msg = msg.format(rsiz=self.rsiz,
|
warnings.warn(msg)
|
||||||
xysiz=(self.xsiz, self.ysiz),
|
|
||||||
xyosiz=(self.xosiz, self.yosiz),
|
data = struct.unpack('>' + 'B' * len(component_buffer),
|
||||||
xytsiz=(self.xtsiz, self.ytsiz),
|
component_buffer)
|
||||||
xytosiz=(self.xtosiz, self.ytosiz),
|
|
||||||
Csiz=self.Csiz,
|
self.ssiz = data[0::3]
|
||||||
bitdepth=self.bitdepth,
|
|
||||||
signed=self.signed,
|
for j, subsampling in enumerate(list(zip(data[1::3], data[2::3]))):
|
||||||
xyrsiz=(self.xrsiz, self.yrsiz))
|
if 0 in subsampling:
|
||||||
return msg
|
msg = "Invalid subsampling value for component {0}: "
|
||||||
|
msg += "dx={1}, dy={2}."
|
||||||
|
msg = msg.format(j, subsampling[0], subsampling[1])
|
||||||
|
warnings.warn(msg)
|
||||||
|
self.xrsiz = data[1::3]
|
||||||
|
self.yrsiz = data[2::3]
|
||||||
|
|
||||||
|
self.bitdepth = tuple(((x & 0x7f) + 1) for x in self.ssiz)
|
||||||
|
self.signed = tuple(((x & 0xb0) > 0) for x in self.ssiz)
|
||||||
|
|
||||||
|
self.length = length
|
||||||
|
self.offset = offset
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
msg = Segment.__str__(self)
|
msg = Segment.__str__(self)
|
||||||
|
|
@ -1601,10 +1549,6 @@ class SOCsegment(Segment):
|
||||||
Segment.__init__(self, marker_id='SOC')
|
Segment.__init__(self, marker_id='SOC')
|
||||||
self.__dict__.update(**kwargs)
|
self.__dict__.update(**kwargs)
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
msg = "glymur.codestream.SOCsegment()"
|
|
||||||
return msg
|
|
||||||
|
|
||||||
|
|
||||||
class SODsegment(Segment):
|
class SODsegment(Segment):
|
||||||
"""Container for Start of Data (SOD) segment information.
|
"""Container for Start of Data (SOD) segment information.
|
||||||
|
|
@ -1719,7 +1663,7 @@ class SOTsegment(Segment):
|
||||||
15444-1:2004 - Information technology -- JPEG 2000 image coding system:
|
15444-1:2004 - Information technology -- JPEG 2000 image coding system:
|
||||||
Core coding system
|
Core coding system
|
||||||
"""
|
"""
|
||||||
def __init__(self, isot, psot, tpsot, tnsot, length=-1, offset=-1):
|
def __init__(self, isot, psot, tpsot, tnsot, length, offset):
|
||||||
Segment.__init__(self, marker_id='SOT')
|
Segment.__init__(self, marker_id='SOT')
|
||||||
self.isot = isot
|
self.isot = isot
|
||||||
self.psot = psot
|
self.psot = psot
|
||||||
|
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
"""
|
|
||||||
Entry point for console script jp2dump.
|
|
||||||
"""
|
|
||||||
import argparse
|
|
||||||
import os
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
from . import Jp2k, set_printoptions, set_parseoptions, lib
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""
|
|
||||||
Entry point for console script jp2dump.
|
|
||||||
"""
|
|
||||||
|
|
||||||
kwargs = {'description': 'Print JPEG2000 metadata.',
|
|
||||||
'formatter_class': argparse.ArgumentDefaultsHelpFormatter}
|
|
||||||
parser = argparse.ArgumentParser(**kwargs)
|
|
||||||
|
|
||||||
parser.add_argument('-x', '--noxml',
|
|
||||||
help='suppress XML',
|
|
||||||
action='store_true')
|
|
||||||
parser.add_argument('-s', '--short',
|
|
||||||
help='only print box id, offset, and length',
|
|
||||||
action='store_true')
|
|
||||||
|
|
||||||
chelp = 'Level of codestream information. 0 suppresses all details, '
|
|
||||||
chelp += '1 prints the main header, 2 prints the full codestream.'
|
|
||||||
parser.add_argument('-c', '--codestream',
|
|
||||||
help=chelp,
|
|
||||||
metavar='LEVEL',
|
|
||||||
nargs=1,
|
|
||||||
type=int,
|
|
||||||
default=[1])
|
|
||||||
|
|
||||||
parser.add_argument('filename')
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
if args.noxml:
|
|
||||||
set_printoptions(xml=False)
|
|
||||||
if args.short:
|
|
||||||
set_printoptions(short=True)
|
|
||||||
|
|
||||||
codestream_level = args.codestream[0]
|
|
||||||
if codestream_level not in [0, 1, 2]:
|
|
||||||
raise ValueError("Invalid level of codestream information specified.")
|
|
||||||
|
|
||||||
if codestream_level == 0:
|
|
||||||
set_printoptions(codestream=False)
|
|
||||||
elif codestream_level == 2:
|
|
||||||
set_parseoptions(full_codestream=True)
|
|
||||||
|
|
||||||
filename = args.filename
|
|
||||||
|
|
||||||
with warnings.catch_warnings(record=True) as wctx:
|
|
||||||
|
|
||||||
# JP2 metadata can be extensive, so don't print any warnings until we
|
|
||||||
# are done with the metadata.
|
|
||||||
jp2 = Jp2k(filename)
|
|
||||||
if jp2._codec_format == lib.openjp2.CODEC_J2K:
|
|
||||||
if codestream_level == 0:
|
|
||||||
print('File: {0}'.format(os.path.basename(filename)))
|
|
||||||
elif codestream_level == 1:
|
|
||||||
print(jp2)
|
|
||||||
elif codestream_level == 2:
|
|
||||||
print('File: {0}'.format(os.path.basename(filename)))
|
|
||||||
print(jp2.get_codestream(header_only=False))
|
|
||||||
else:
|
|
||||||
print(jp2)
|
|
||||||
|
|
||||||
# Re-emit any warnings that may have been suppressed.
|
|
||||||
if len(wctx) > 0:
|
|
||||||
print("\n")
|
|
||||||
for warning in wctx:
|
|
||||||
print("{0}:{1}: {2}: {3}".format(warning.filename,
|
|
||||||
warning.lineno,
|
|
||||||
warning.category.__name__,
|
|
||||||
warning.message))
|
|
||||||
117
glymur/core.py
117
glymur/core.py
|
|
@ -1,21 +1,5 @@
|
||||||
"""Core definitions to be shared amongst the modules.
|
"""Core definitions to be shared amongst the modules.
|
||||||
"""
|
"""
|
||||||
import collections
|
|
||||||
|
|
||||||
|
|
||||||
class _Keydefaultdict(collections.defaultdict):
|
|
||||||
"""Unlisted keys help form their own error message.
|
|
||||||
|
|
||||||
Normally defaultdict uses a factory function with no input arguments, but
|
|
||||||
that's not quite the behavior we want.
|
|
||||||
"""
|
|
||||||
def __missing__(self, key):
|
|
||||||
if self.default_factory is None:
|
|
||||||
raise KeyError(key)
|
|
||||||
else:
|
|
||||||
ret = self[key] = self.default_factory(key)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
# Progression order
|
# Progression order
|
||||||
LRCP = 0
|
LRCP = 0
|
||||||
RLCP = 1
|
RLCP = 1
|
||||||
|
|
@ -23,74 +7,6 @@ RPCL = 2
|
||||||
PCRL = 3
|
PCRL = 3
|
||||||
CPRL = 4
|
CPRL = 4
|
||||||
|
|
||||||
STD = 0
|
|
||||||
CINEMA2K = 3
|
|
||||||
CINEMA4K = 4
|
|
||||||
|
|
||||||
RSIZ = {
|
|
||||||
'STD': STD,
|
|
||||||
'CINEMA2K': CINEMA2K,
|
|
||||||
'CINEMA4K': CINEMA4K}
|
|
||||||
|
|
||||||
OFF = 0
|
|
||||||
CINEMA2K_24 = 1
|
|
||||||
CINEMA2K_48 = 2
|
|
||||||
CINEMA4K_24 = 3
|
|
||||||
|
|
||||||
OPJ_OFF = 0 # Not Digital Cinema
|
|
||||||
OPJ_CINEMA2K_24 = 1 # 2K Digital Cinema at 24 fps
|
|
||||||
OPJ_CINEMA2K_48 = 2 # 2K Digital Cinema at 48 fps
|
|
||||||
OPJ_CINEMA4K_24 = 3 # 4K Digital Cinema at 24 fps
|
|
||||||
|
|
||||||
# no profile, conform to 15444-1
|
|
||||||
OPJ_PROFILE_NONE = 0x0000
|
|
||||||
# Profile 0 as described in 15444-1,Table A.45
|
|
||||||
OPJ_PROFILE_0 = 0x0001
|
|
||||||
# Profile 1 as described in 15444-1,Table A.45
|
|
||||||
OPJ_PROFILE_1 = 0x0002
|
|
||||||
# At least 1 extension defined in 15444-2 (Part-2)
|
|
||||||
OPJ_PROFILE_PART2 = 0x8000
|
|
||||||
# 2K cinema profile defined in 15444-1 AMD1
|
|
||||||
OPJ_PROFILE_CINEMA_2K = 0x0003
|
|
||||||
# 4K cinema profile defined in 15444-1 AMD1
|
|
||||||
OPJ_PROFILE_CINEMA_4K = 0x0004
|
|
||||||
# Scalable 2K cinema profile defined in 15444-1 AMD2
|
|
||||||
OPJ_PROFILE_CINEMA_S2K = 0x0005
|
|
||||||
# Scalable 4K cinema profile defined in 15444-1 AMD2
|
|
||||||
OPJ_PROFILE_CINEMA_S4K = 0x0006
|
|
||||||
# Long term storage cinema profile defined in 15444-1 AMD2
|
|
||||||
OPJ_PROFILE_CINEMA_LTS = 0x0007
|
|
||||||
# Single Tile Broadcast profile defined in 15444-1 AMD3
|
|
||||||
OPJ_PROFILE_BC_SINGLE = 0x0100
|
|
||||||
# Multi Tile Broadcast profile defined in 15444-1 AMD3
|
|
||||||
OPJ_PROFILE_BC_MULTI = 0x0200
|
|
||||||
# Multi Tile Reversible Broadcast profile defined in 15444-1 AMD3
|
|
||||||
OPJ_PROFILE_BC_MULTI_R = 0x0300
|
|
||||||
# 2K Single Tile Lossy IMF profile defined in 15444-1 AMD 8
|
|
||||||
OPJ_PROFILE_IMF_2K = 0x0400
|
|
||||||
# 4K Single Tile Lossy IMF profile defined in 15444-1 AMD 8
|
|
||||||
OPJ_PROFILE_IMF_4K = 0x0401
|
|
||||||
# 8K Single Tile Lossy IMF profile defined in 15444-1 AMD 8
|
|
||||||
OPJ_PROFILE_IMF_8K = 0x0402
|
|
||||||
# 2K Single/Multi Tile Reversible IMF profile defined in 15444-1 AMD 8
|
|
||||||
OPJ_PROFILE_IMF_2K_R = 0x0403
|
|
||||||
# 4K Single/Multi Tile Reversible IMF profile defined in 15444-1 AMD 8
|
|
||||||
OPJ_PROFILE_IMF_4K_R = 0x0800
|
|
||||||
# 8K Single/Multi Tile Reversible IMF profile defined in 15444-1 AMD 8
|
|
||||||
OPJ_PROFILE_IMF_8K_R = 0x0801
|
|
||||||
|
|
||||||
# JPEG 2000 codestream and component size limits in cinema profiles
|
|
||||||
#
|
|
||||||
# Maximum codestream length for 24fps
|
|
||||||
OPJ_CINEMA_24_CS = 1302083
|
|
||||||
# Maximum codestream length for 48fps
|
|
||||||
OPJ_CINEMA_48_CS = 651041
|
|
||||||
# Maximum size per color component for 2K & 4K @ 24fps
|
|
||||||
OPJ_CINEMA_24_COMP = 1041666
|
|
||||||
# Maximum size per color component for 2K @ 48fps
|
|
||||||
OPJ_CINEMA_48_COMP = 520833
|
|
||||||
|
|
||||||
|
|
||||||
PROGRESSION_ORDER = {
|
PROGRESSION_ORDER = {
|
||||||
'LRCP': LRCP,
|
'LRCP': LRCP,
|
||||||
'RLCP': RLCP,
|
'RLCP': RLCP,
|
||||||
|
|
@ -118,26 +34,24 @@ YCC = 18
|
||||||
E_SRGB = 20
|
E_SRGB = 20
|
||||||
ROMM_RGB = 21
|
ROMM_RGB = 21
|
||||||
|
|
||||||
_factory = lambda x: '{0} (unrecognized)'.format(x)
|
_COLORSPACE_MAP_DISPLAY = {
|
||||||
_COLORSPACE_MAP_DISPLAY = _Keydefaultdict(_factory,
|
CMYK: 'CMYK',
|
||||||
{CMYK: 'CMYK',
|
SRGB: 'sRGB',
|
||||||
SRGB: 'sRGB',
|
GREYSCALE: 'greyscale',
|
||||||
GREYSCALE: 'greyscale',
|
YCC: 'YCC',
|
||||||
YCC: 'YCC',
|
E_SRGB: 'e-sRGB',
|
||||||
E_SRGB: 'e-sRGB',
|
ROMM_RGB: 'ROMM-RGB'}
|
||||||
ROMM_RGB: 'ROMM-RGB'})
|
|
||||||
|
|
||||||
# enumerated color channel types
|
# enumerated color channel types
|
||||||
COLOR = 0
|
COLOR = 0
|
||||||
OPACITY = 1
|
OPACITY = 1
|
||||||
PRE_MULTIPLIED_OPACITY = 2
|
PRE_MULTIPLIED_OPACITY = 2
|
||||||
_UNSPECIFIED = 65535
|
_UNSPECIFIED = 65535
|
||||||
_factory = lambda x: '{0} (invalid)'.format(x)
|
_COLOR_TYPE_MAP_DISPLAY = {
|
||||||
_dict = {COLOR: 'color',
|
COLOR: 'color',
|
||||||
OPACITY: 'opacity',
|
OPACITY: 'opacity',
|
||||||
PRE_MULTIPLIED_OPACITY: 'pre-multiplied opacity',
|
PRE_MULTIPLIED_OPACITY: 'pre-multiplied opacity',
|
||||||
_UNSPECIFIED: 'unspecified'}
|
_UNSPECIFIED: 'unspecified'}
|
||||||
_COLOR_TYPE_MAP_DISPLAY = _Keydefaultdict(_factory, _dict)
|
|
||||||
|
|
||||||
# color channel definitions.
|
# color channel definitions.
|
||||||
RED = 1
|
RED = 1
|
||||||
|
|
@ -152,3 +66,10 @@ _COLORSPACE = {SRGB: {"R": 1, "G": 2, "B": 3},
|
||||||
YCC: {"Y": 1, "Cb": 2, "Cr": 3},
|
YCC: {"Y": 1, "Cb": 2, "Cr": 3},
|
||||||
E_SRGB: {"R": 1, "G": 2, "B": 3},
|
E_SRGB: {"R": 1, "G": 2, "B": 3},
|
||||||
ROMM_RGB: {"R": 1, "G": 2, "B": 3}}
|
ROMM_RGB: {"R": 1, "G": 2, "B": 3}}
|
||||||
|
|
||||||
|
# How to display the codestream profile.
|
||||||
|
_CAPABILITIES_DISPLAY = {
|
||||||
|
0: '2',
|
||||||
|
1: '0',
|
||||||
|
2: '1',
|
||||||
|
3: '3'}
|
||||||
|
|
|
||||||
|
|
@ -31,15 +31,3 @@ def goodstuff():
|
||||||
"""
|
"""
|
||||||
filename = pkg_resources.resource_filename(__name__, "goodstuff.j2k")
|
filename = pkg_resources.resource_filename(__name__, "goodstuff.j2k")
|
||||||
return filename
|
return filename
|
||||||
|
|
||||||
|
|
||||||
def jpxfile():
|
|
||||||
"""Shortcut for specifying path to heliov.jpx.
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
file : str
|
|
||||||
Platform-independent path to 12-v6.4.jpx
|
|
||||||
"""
|
|
||||||
filename = pkg_resources.resource_filename(__name__, "heliov.jpx")
|
|
||||||
return filename
|
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
2931
glymur/jp2box.py
2931
glymur/jp2box.py
File diff suppressed because it is too large
Load diff
36
glymur/jp2dump.py
Normal file
36
glymur/jp2dump.py
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
"""
|
||||||
|
Entry point for jp2dump script.
|
||||||
|
"""
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
from .jp2k import Jp2k
|
||||||
|
|
||||||
|
|
||||||
|
def jp2dump(filename, codestream=False):
|
||||||
|
"""Prints JPEG2000 metadata.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
filename : string
|
||||||
|
The input JPEG2000 file.
|
||||||
|
codestream : optional, logical scalar
|
||||||
|
Whether or not to dump codestream contents.
|
||||||
|
"""
|
||||||
|
with warnings.catch_warnings(record=True) as wctx:
|
||||||
|
|
||||||
|
# JP2 metadata can be extensive, so don't print any warnings until we
|
||||||
|
# are done with the metadata.
|
||||||
|
j = Jp2k(filename)
|
||||||
|
if codestream:
|
||||||
|
print(j.get_codestream(header_only=False))
|
||||||
|
else:
|
||||||
|
print(j)
|
||||||
|
|
||||||
|
# Re-emit any warnings that may have been suppressed.
|
||||||
|
if len(wctx) > 0:
|
||||||
|
print("\n")
|
||||||
|
for warning in wctx:
|
||||||
|
print("{0}:{1}: {2}: {3}".format(warning.filename,
|
||||||
|
warning.lineno,
|
||||||
|
warning.category.__name__,
|
||||||
|
warning.message))
|
||||||
1648
glymur/jp2k.py
1648
glymur/jp2k.py
File diff suppressed because it is too large
Load diff
|
|
@ -2,5 +2,3 @@
|
||||||
from . import openjp2 as openjp2
|
from . import openjp2 as openjp2
|
||||||
from . import openjpeg as openjpeg
|
from . import openjpeg as openjpeg
|
||||||
from . import c
|
from . import c
|
||||||
|
|
||||||
__all__ = [openjp2, openjpeg, c]
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
"""
|
"""
|
||||||
Configure glymur to use installed libraries if possible.
|
Configure glymur to use installed libraries if possible.
|
||||||
"""
|
"""
|
||||||
|
# configparser is new in python3 (pylint/python-2.7)
|
||||||
|
# pylint: disable=F0401
|
||||||
|
|
||||||
import ctypes
|
import ctypes
|
||||||
from ctypes.util import find_library
|
from ctypes.util import find_library
|
||||||
import os
|
import os
|
||||||
|
|
@ -15,22 +18,6 @@ else:
|
||||||
from configparser import ConfigParser
|
from configparser import ConfigParser
|
||||||
from configparser import NoOptionError
|
from configparser import NoOptionError
|
||||||
|
|
||||||
# default library locations for MacPorts
|
|
||||||
_macports_default_location = {'openjp2': '/opt/local/lib/libopenjp2.dylib',
|
|
||||||
'openjpeg': '/opt/local/lib/libopenjpeg.dylib'}
|
|
||||||
|
|
||||||
# default library locations on Windows
|
|
||||||
_windows_default_location = {'openjp2': os.path.join('C:\\',
|
|
||||||
'Program files',
|
|
||||||
'OpenJPEG 2.0',
|
|
||||||
'bin',
|
|
||||||
'openjp2.dll'),
|
|
||||||
'openjpeg': os.path.join('C:\\',
|
|
||||||
'Program files',
|
|
||||||
'OpenJPEG 1.5',
|
|
||||||
'bin',
|
|
||||||
'openjpeg.dll')}
|
|
||||||
|
|
||||||
|
|
||||||
def glymurrc_fname():
|
def glymurrc_fname():
|
||||||
"""Return the path to the configuration file.
|
"""Return the path to the configuration file.
|
||||||
|
|
@ -56,23 +43,52 @@ def glymurrc_fname():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def load_openjpeg_library(libname):
|
def load_openjpeg(path):
|
||||||
|
"""Load the openjpeg library, falling back on defaults if necessary.
|
||||||
|
|
||||||
path = read_config_file(libname)
|
Parameters
|
||||||
if path is not None:
|
----------
|
||||||
return load_library_handle(path)
|
path : str
|
||||||
|
Path to openjpeg 1.5 library as specified by configuration file. Will
|
||||||
|
be None if no configuration file specified.
|
||||||
|
"""
|
||||||
|
if path is None:
|
||||||
|
# Let ctypes try to find it.
|
||||||
|
path = find_library('openjpeg')
|
||||||
|
|
||||||
# No location specified by the configuration file, must look for it
|
# If we could not find it, then look in some likely locations on mac
|
||||||
# elsewhere.
|
# and win.
|
||||||
path = find_library(libname)
|
if path is None:
|
||||||
|
# Could not find a library via ctypes
|
||||||
|
if platform.system() == 'Darwin':
|
||||||
|
# MacPorts
|
||||||
|
path = '/opt/local/lib/libopenjpeg.dylib'
|
||||||
|
elif os.name == 'nt':
|
||||||
|
path = os.path.join('C:\\', 'Program files', 'OpenJPEG 1.5',
|
||||||
|
'bin', 'openjpeg.dll')
|
||||||
|
|
||||||
|
if path is not None and not os.path.exists(path):
|
||||||
|
# the mac/win default location does not exist.
|
||||||
|
return None
|
||||||
|
|
||||||
|
return load_library_handle(path)
|
||||||
|
|
||||||
|
|
||||||
|
def load_openjp2(path):
|
||||||
|
"""Load the openjp2 library, falling back on defaults if necessary.
|
||||||
|
"""
|
||||||
|
if path is None:
|
||||||
|
# No help from the config file, try to find it via ctypes.
|
||||||
|
path = find_library('openjp2')
|
||||||
|
|
||||||
if path is None:
|
if path is None:
|
||||||
# Could not find a library via ctypes
|
# Could not find a library via ctypes
|
||||||
if platform.system() == 'Darwin':
|
if platform.system() == 'Darwin':
|
||||||
# MacPorts
|
# MacPorts
|
||||||
path = _macports_default_location[libname]
|
path = '/opt/local/lib/libopenjp2.dylib'
|
||||||
elif os.name == 'nt':
|
elif os.name == 'nt':
|
||||||
path = _windows_default_location[libname]
|
path = os.path.join('C:\\', 'Program files', 'OpenJPEG 2.0',
|
||||||
|
'bin', 'openjp2.dll')
|
||||||
|
|
||||||
if path is not None and not os.path.exists(path):
|
if path is not None and not os.path.exists(path):
|
||||||
# the mac/win default location does not exist.
|
# the mac/win default location does not exist.
|
||||||
|
|
@ -84,11 +100,10 @@ def load_openjpeg_library(libname):
|
||||||
def load_library_handle(path):
|
def load_library_handle(path):
|
||||||
"""Load the library, return the ctypes handle."""
|
"""Load the library, return the ctypes handle."""
|
||||||
|
|
||||||
if path is None or path in ['None', 'none']:
|
if path is None:
|
||||||
# Either could not find a library via ctypes or
|
# Either could not find a library via ctypes or user-configuration-file,
|
||||||
# user-configuration-file, or we could not find it in any of the
|
# or we could not find it in any of the default locations.
|
||||||
# default locations, or possibly the user intentionally does not want
|
# This is probably a very old linux.
|
||||||
# one of the libraries to load.
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -97,59 +112,47 @@ def load_library_handle(path):
|
||||||
else:
|
else:
|
||||||
opj_lib = ctypes.CDLL(path)
|
opj_lib = ctypes.CDLL(path)
|
||||||
except (TypeError, OSError):
|
except (TypeError, OSError):
|
||||||
msg = 'The library specified by configuration file at {0} could not '
|
msg = '"Library {0}" could not be loaded. Operating in degraded mode.'
|
||||||
msg += 'be loaded.'
|
msg = msg.format(path)
|
||||||
warnings.warn(msg.format(path), UserWarning)
|
warnings.warn(msg, UserWarning)
|
||||||
opj_lib = None
|
opj_lib = None
|
||||||
|
|
||||||
return opj_lib
|
return opj_lib
|
||||||
|
|
||||||
|
|
||||||
def read_config_file(libname):
|
def read_config_file():
|
||||||
"""
|
"""
|
||||||
Extract library locations from a configuration file.
|
We must use a configuration file that the user must write.
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
libname : str
|
|
||||||
One of either 'openjp2' or 'openjpeg'
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
path : None or str
|
|
||||||
None if no location is specified, otherwise a path to the library
|
|
||||||
"""
|
"""
|
||||||
|
lib = {'openjp2': None, 'openjpeg': None}
|
||||||
filename = glymurrc_fname()
|
filename = glymurrc_fname()
|
||||||
if filename is None:
|
if filename is not None:
|
||||||
# There's no library file path to return in this case.
|
# Read the configuration file for the library location.
|
||||||
return None
|
parser = ConfigParser()
|
||||||
|
parser.read(filename)
|
||||||
|
try:
|
||||||
|
lib['openjp2'] = parser.get('library', 'openjp2')
|
||||||
|
except NoOptionError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
lib['openjpeg'] = parser.get('library', 'openjpeg')
|
||||||
|
except NoOptionError:
|
||||||
|
pass
|
||||||
|
|
||||||
# Read the configuration file for the library location.
|
return lib
|
||||||
parser = ConfigParser()
|
|
||||||
parser.read(filename)
|
|
||||||
try:
|
|
||||||
path = parser.get('library', libname)
|
|
||||||
except NoOptionError:
|
|
||||||
path = None
|
|
||||||
return path
|
|
||||||
|
|
||||||
|
|
||||||
def glymur_config():
|
def glymur_config():
|
||||||
|
"""Try to ascertain locations of openjp2, openjpeg libraries.
|
||||||
"""
|
"""
|
||||||
Try to ascertain locations of openjp2, openjpeg libraries.
|
libs = read_config_file()
|
||||||
|
libopenjp2_handle = load_openjp2(libs['openjp2'])
|
||||||
Returns
|
libopenjpeg_handle = load_openjpeg(libs['openjpeg'])
|
||||||
-------
|
if libopenjp2_handle is None and libopenjpeg_handle is None:
|
||||||
tpl : tuple
|
|
||||||
tuple of library handles
|
|
||||||
"""
|
|
||||||
lst = []
|
|
||||||
for libname in ['openjp2', 'openjpeg']:
|
|
||||||
lst.append(load_openjpeg_library(libname))
|
|
||||||
if all(handle is None for handle in lst):
|
|
||||||
msg = "Neither the openjp2 nor the openjpeg library could be loaded. "
|
msg = "Neither the openjp2 nor the openjpeg library could be loaded. "
|
||||||
warnings.warn(msg)
|
msg += "Operating in severely degraded mode."
|
||||||
return tuple(lst)
|
warnings.warn(msg, UserWarning)
|
||||||
|
return libopenjp2_handle, libopenjpeg_handle
|
||||||
|
|
||||||
|
|
||||||
def get_configdir():
|
def get_configdir():
|
||||||
|
|
|
||||||
|
|
@ -2,23 +2,22 @@
|
||||||
Wraps individual functions in openjp2 library.
|
Wraps individual functions in openjp2 library.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# pylint: disable=C0302,R0903,W0201
|
||||||
|
|
||||||
import ctypes
|
import ctypes
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import textwrap
|
|
||||||
|
|
||||||
from .config import glymur_config
|
from .config import glymur_config
|
||||||
|
|
||||||
OPENJP2, OPENJPEG = glymur_config()
|
OPENJP2, OPENJPEG = glymur_config()
|
||||||
|
|
||||||
|
|
||||||
def version():
|
def version():
|
||||||
"""Wrapper for opj_version library routine."""
|
"""Wrapper for opj_version library routine."""
|
||||||
try:
|
try:
|
||||||
OPENJP2.opj_version.restype = ctypes.c_char_p
|
OPENJP2.opj_version.restype = ctypes.c_char_p
|
||||||
except:
|
except AttributeError:
|
||||||
|
# No library, so the version is zero.
|
||||||
return "0.0.0"
|
return "0.0.0"
|
||||||
|
|
||||||
library_version = OPENJP2.opj_version()
|
library_version = OPENJP2.opj_version()
|
||||||
if sys.hexversion >= 0x03000000:
|
if sys.hexversion >= 0x03000000:
|
||||||
return library_version.decode('utf-8')
|
return library_version.decode('utf-8')
|
||||||
|
|
@ -26,10 +25,9 @@ def version():
|
||||||
return library_version
|
return library_version
|
||||||
|
|
||||||
if OPENJP2 is not None:
|
if OPENJP2 is not None:
|
||||||
_MAJOR, _MINOR, _PATCH = version().split('.')
|
_MAJOR, _MINOR, _PATH = version().split('.')
|
||||||
else:
|
else:
|
||||||
_MINOR = 0
|
_MINOR = 0
|
||||||
|
|
||||||
ERROR_MSG_LST = []
|
ERROR_MSG_LST = []
|
||||||
|
|
||||||
# Map certain atomic OpenJPEG datatypes to the ctypes equivalents.
|
# Map certain atomic OpenJPEG datatypes to the ctypes equivalents.
|
||||||
|
|
@ -55,7 +53,6 @@ CLRSPC_UNSPECIFIED = 0
|
||||||
CLRSPC_SRGB = 1
|
CLRSPC_SRGB = 1
|
||||||
CLRSPC_GRAY = 2
|
CLRSPC_GRAY = 2
|
||||||
CLRSPC_YCC = 3
|
CLRSPC_YCC = 3
|
||||||
CLRSPC_EYCC = 4
|
|
||||||
COLOR_SPACE_TYPE = ctypes.c_int
|
COLOR_SPACE_TYPE = ctypes.c_int
|
||||||
|
|
||||||
# supported codec
|
# supported codec
|
||||||
|
|
@ -131,13 +128,6 @@ class PocType(ctypes.Structure):
|
||||||
("tx0_t", ctypes.c_uint32),
|
("tx0_t", ctypes.c_uint32),
|
||||||
("ty0_t", ctypes.c_uint32)]
|
("ty0_t", ctypes.c_uint32)]
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
msg = "{0}:\n".format(self.__class__)
|
|
||||||
for field_name, _ in self._fields_:
|
|
||||||
msg += " {0}: {1}\n".format(
|
|
||||||
field_name, getattr(self, field_name))
|
|
||||||
return msg
|
|
||||||
|
|
||||||
|
|
||||||
class DecompressionParametersType(ctypes.Structure):
|
class DecompressionParametersType(ctypes.Structure):
|
||||||
"""Decompression parameters.
|
"""Decompression parameters.
|
||||||
|
|
@ -201,13 +191,6 @@ class DecompressionParametersType(ctypes.Structure):
|
||||||
# maximum number of tiles
|
# maximum number of tiles
|
||||||
("flags", ctypes.c_uint32)]
|
("flags", ctypes.c_uint32)]
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
msg = "{0}:\n".format(self.__class__)
|
|
||||||
for field_name, _ in self._fields_:
|
|
||||||
msg += " {0}: {1}\n".format(
|
|
||||||
field_name, getattr(self, field_name))
|
|
||||||
return msg
|
|
||||||
|
|
||||||
|
|
||||||
class CompressionParametersType(ctypes.Structure):
|
class CompressionParametersType(ctypes.Structure):
|
||||||
"""Compression parameters.
|
"""Compression parameters.
|
||||||
|
|
@ -399,46 +382,6 @@ class CompressionParametersType(ctypes.Structure):
|
||||||
# values.
|
# values.
|
||||||
_fields_.append(("rsiz", ctypes.c_uint16))
|
_fields_.append(("rsiz", ctypes.c_uint16))
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
msg = "{0}:\n".format(self.__class__)
|
|
||||||
for field_name, _ in self._fields_:
|
|
||||||
|
|
||||||
if field_name == 'poc':
|
|
||||||
msg += " numpocs: {0}\n".format(self.numpocs)
|
|
||||||
for j in range(self.numpocs):
|
|
||||||
msg += " [#{0}]:".format(j)
|
|
||||||
msg += " {0}".format(str(self.poc[j]))
|
|
||||||
|
|
||||||
elif field_name in ['tcp_rates', 'tcp_distoratio']:
|
|
||||||
lst = []
|
|
||||||
arr = getattr(self, field_name)
|
|
||||||
lst = [arr[j] for j in range(self.tcp_numlayers)]
|
|
||||||
msg += " {0}: {1}\n".format(field_name, lst)
|
|
||||||
|
|
||||||
elif field_name in ['prcw_init', 'prch_init']:
|
|
||||||
pass
|
|
||||||
|
|
||||||
elif field_name == 'res_spec':
|
|
||||||
prcw_init = [self.prcw_init[j] for j in range(self.res_spec)]
|
|
||||||
prch_init = [self.prch_init[j] for j in range(self.res_spec)]
|
|
||||||
msg += " res_spec: {0}\n".format(self.res_spec)
|
|
||||||
msg += " prch_init: {0}\n".format(prch_init)
|
|
||||||
msg += " prcw_init: {0}\n".format(prcw_init)
|
|
||||||
|
|
||||||
elif field_name in [
|
|
||||||
'jpwl_hprot_tph_tileno', 'jpwl_hprot_tph',
|
|
||||||
'jpwl_pprot_tileno', 'jpwl_pprot_packno', 'jpwl_pprot',
|
|
||||||
'jpwl_sens_tph_tileno', 'jpwl_sens_tph']:
|
|
||||||
arr = getattr(self, field_name)
|
|
||||||
lst = [arr[j] for j in range(JPWL_MAX_NO_TILESPECS)]
|
|
||||||
msg += " {0}: {1}\n".format(field_name, lst)
|
|
||||||
|
|
||||||
else:
|
|
||||||
msg += " {0}: {1}\n".format(
|
|
||||||
field_name, getattr(self, field_name))
|
|
||||||
return msg
|
|
||||||
|
|
||||||
|
|
||||||
class ImageCompType(ctypes.Structure):
|
class ImageCompType(ctypes.Structure):
|
||||||
"""Defines a single image component.
|
"""Defines a single image component.
|
||||||
|
|
||||||
|
|
@ -478,14 +421,7 @@ class ImageCompType(ctypes.Structure):
|
||||||
("data", ctypes.POINTER(ctypes.c_int32))]
|
("data", ctypes.POINTER(ctypes.c_int32))]
|
||||||
|
|
||||||
if _MINOR == '1':
|
if _MINOR == '1':
|
||||||
_fields_.append(("alpha", ctypes.c_uint16))
|
_fields_.append(("alpha", ctypes.c_uint16))
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
msg = "{0}:\n".format(self.__class__)
|
|
||||||
for field_name, _ in self._fields_:
|
|
||||||
msg += " {0}: {1}\n".format(
|
|
||||||
field_name, getattr(self, field_name))
|
|
||||||
return msg
|
|
||||||
|
|
||||||
|
|
||||||
class ImageType(ctypes.Structure):
|
class ImageType(ctypes.Structure):
|
||||||
|
|
@ -518,26 +454,6 @@ class ImageType(ctypes.Structure):
|
||||||
# restricted ICC profile buffer length
|
# restricted ICC profile buffer length
|
||||||
("icc_profile_len", ctypes.c_uint32)]
|
("icc_profile_len", ctypes.c_uint32)]
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
msg = "{0}:\n".format(self.__class__)
|
|
||||||
for field_name, _ in self._fields_:
|
|
||||||
|
|
||||||
if field_name == "numcomps":
|
|
||||||
msg += " numcomps: {0}\n".format(self.numcomps)
|
|
||||||
for j in range(self.numcomps):
|
|
||||||
msg += " comps[#{0}]:\n".format(j)
|
|
||||||
msg += textwrap.indent(str(self.comps[j]), ' ' * 12)
|
|
||||||
|
|
||||||
elif field_name == "comps":
|
|
||||||
# handled above
|
|
||||||
pass
|
|
||||||
|
|
||||||
else:
|
|
||||||
msg += " {0}: {1}\n".format(
|
|
||||||
field_name, getattr(self, field_name))
|
|
||||||
|
|
||||||
return msg
|
|
||||||
|
|
||||||
|
|
||||||
class ImageComptParmType(ctypes.Structure):
|
class ImageComptParmType(ctypes.Structure):
|
||||||
"""Component parameters structure used by image_create function.
|
"""Component parameters structure used by image_create function.
|
||||||
|
|
@ -567,12 +483,106 @@ class ImageComptParmType(ctypes.Structure):
|
||||||
# signed (1) / unsigned (0)
|
# signed (1) / unsigned (0)
|
||||||
("sgnd", ctypes.c_uint32)]
|
("sgnd", ctypes.c_uint32)]
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
msg = "{0}:\n".format(self.__class__)
|
class TccpInfo(ctypes.Structure):
|
||||||
for field_name, _ in self._fields_:
|
"""Tile-component coding parameters information.
|
||||||
msg += " {0}: {1}\n".format(
|
|
||||||
field_name, getattr(self, field_name))
|
Corresponds to tccp_info_t type in openjp2 header file.
|
||||||
return msg
|
"""
|
||||||
|
_fields_ = [
|
||||||
|
# component index
|
||||||
|
("compno", ctypes.c_uint32),
|
||||||
|
|
||||||
|
# coding style
|
||||||
|
("csty", ctypes.c_uint32),
|
||||||
|
|
||||||
|
# number of resolutions
|
||||||
|
("numresolutions", ctypes.c_uint32),
|
||||||
|
|
||||||
|
# code-blocks width
|
||||||
|
("cblkw", ctypes.c_uint32),
|
||||||
|
|
||||||
|
# code-blocks height
|
||||||
|
("cblkh", ctypes.c_uint32),
|
||||||
|
|
||||||
|
# code-block coding style
|
||||||
|
("cblksty", ctypes.c_uint32),
|
||||||
|
|
||||||
|
# discrete wavelet transform identifier
|
||||||
|
("qmfbid", ctypes.c_uint32),
|
||||||
|
|
||||||
|
# quantization style
|
||||||
|
("qntsty", ctypes.c_uint32),
|
||||||
|
|
||||||
|
# stepsizes used for quantization
|
||||||
|
("stepsizes_mant", ctypes.c_uint32 * J2K_MAXBANDS),
|
||||||
|
("stepsizes_expn", ctypes.c_uint32 * J2K_MAXBANDS),
|
||||||
|
|
||||||
|
# stepsizes used for quantization
|
||||||
|
("numgbits", ctypes.c_uint32),
|
||||||
|
|
||||||
|
# region of interest shift
|
||||||
|
("roishift", ctypes.c_int32),
|
||||||
|
|
||||||
|
# precinct width
|
||||||
|
("prcw", ctypes.c_uint32 * J2K_MAXRLVLS),
|
||||||
|
|
||||||
|
# precinct width
|
||||||
|
("prch", ctypes.c_uint32 * J2K_MAXRLVLS)]
|
||||||
|
|
||||||
|
|
||||||
|
class TileInfoV2(ctypes.Structure):
|
||||||
|
"""Tile coding parameters information
|
||||||
|
|
||||||
|
Corresponds to tile_info_v2_t type in openjp2 headers.
|
||||||
|
"""
|
||||||
|
_fields_ = [
|
||||||
|
# number (index) of tile
|
||||||
|
("tileno", ctypes.c_int32),
|
||||||
|
|
||||||
|
# coding style
|
||||||
|
("csty", ctypes.c_uint32),
|
||||||
|
|
||||||
|
# progression order
|
||||||
|
("prg", PROG_ORDER_TYPE),
|
||||||
|
|
||||||
|
# number of layers
|
||||||
|
("numlayers", ctypes.c_uint32),
|
||||||
|
|
||||||
|
# multi-component transform identifier
|
||||||
|
("mct", ctypes.c_uint32),
|
||||||
|
|
||||||
|
# information concerning tile component parameters
|
||||||
|
("tccp_info", ctypes.POINTER(TccpInfo))]
|
||||||
|
|
||||||
|
|
||||||
|
class CodestreamInfoV2(ctypes.Structure):
|
||||||
|
"""information about the codestream.
|
||||||
|
|
||||||
|
Corresponds to codestream_info_v2_t type in openjp2 header files.
|
||||||
|
"""
|
||||||
|
_fields_ = [
|
||||||
|
# tile info
|
||||||
|
# tile origin in x, y (XTOsiz, YTOsiz)
|
||||||
|
("tx0", ctypes.c_uint32),
|
||||||
|
("ty0", ctypes.c_uint32),
|
||||||
|
|
||||||
|
# tile size in x, y = XTsiz, YTsiz
|
||||||
|
("tdx", ctypes.c_uint32),
|
||||||
|
("tdy", ctypes.c_uint32),
|
||||||
|
|
||||||
|
# number of tiles in X, Y
|
||||||
|
("tw", ctypes.c_uint32),
|
||||||
|
("th", ctypes.c_uint32),
|
||||||
|
|
||||||
|
# number of components
|
||||||
|
("nbcomps", ctypes.c_uint32),
|
||||||
|
|
||||||
|
# default information regarding tiles inside of image
|
||||||
|
("m_default_tile_info", TileInfoV2),
|
||||||
|
|
||||||
|
# information regarding tiles inside of image
|
||||||
|
("tile_info", ctypes.POINTER(TileInfoV2))]
|
||||||
|
|
||||||
|
|
||||||
def check_error(status):
|
def check_error(status):
|
||||||
|
|
@ -737,6 +747,28 @@ def encode(codec, stream):
|
||||||
OPENJP2.opj_encode(codec, stream)
|
OPENJP2.opj_encode(codec, stream)
|
||||||
|
|
||||||
|
|
||||||
|
def get_cstr_info(codec):
|
||||||
|
"""get the codestream information from the codec
|
||||||
|
|
||||||
|
Wraps the openjp2 library function opj_get_cstr_info.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
codec : CODEC_TYPE
|
||||||
|
The jpeg2000 codec.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
cstr_info_p : CodestreamInfoV2
|
||||||
|
Reference to codestream information.
|
||||||
|
"""
|
||||||
|
OPENJP2.opj_get_cstr_info.argtypes = [CODEC_TYPE]
|
||||||
|
OPENJP2.opj_get_cstr_info.restype = ctypes.POINTER(CodestreamInfoV2)
|
||||||
|
|
||||||
|
cstr_info_p = OPENJP2.opj_get_cstr_info(codec)
|
||||||
|
return cstr_info_p
|
||||||
|
|
||||||
|
|
||||||
def get_decoded_tile(codec, stream, imagep, tile_index):
|
def get_decoded_tile(codec, stream, imagep, tile_index):
|
||||||
"""get the decoded tile from the codec
|
"""get the decoded tile from the codec
|
||||||
|
|
||||||
|
|
@ -767,6 +799,23 @@ def get_decoded_tile(codec, stream, imagep, tile_index):
|
||||||
OPENJP2.opj_get_decoded_tile(codec, stream, imagep, tile_index)
|
OPENJP2.opj_get_decoded_tile(codec, stream, imagep, tile_index)
|
||||||
|
|
||||||
|
|
||||||
|
def destroy_cstr_info(cstr_info_p):
|
||||||
|
"""destroy codestream information after compression or decompression
|
||||||
|
|
||||||
|
Wraps the openjp2 library function opj_destroy_cstr_info.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
cstr_info_p : CodestreamInfoV2 pointer
|
||||||
|
Pointer to codestream info structure.
|
||||||
|
"""
|
||||||
|
ARGTYPES = [ctypes.POINTER(ctypes.POINTER(CodestreamInfoV2))]
|
||||||
|
OPENJP2.opj_destroy_cstr_info.argtypes = ARGTYPES
|
||||||
|
OPENJP2.opj_destroy_cstr_info.restype = ctypes.c_void_p
|
||||||
|
|
||||||
|
OPENJP2.opj_destroy_cstr_info(ctypes.byref(cstr_info_p))
|
||||||
|
|
||||||
|
|
||||||
def end_compress(codec, stream):
|
def end_compress(codec, stream):
|
||||||
"""End of compressing the current image.
|
"""End of compressing the current image.
|
||||||
|
|
||||||
|
|
@ -1246,8 +1295,7 @@ def start_compress(codec, image, stream):
|
||||||
def _stream_create_default_file_stream_2p0(fptr, isa_read_stream):
|
def _stream_create_default_file_stream_2p0(fptr, isa_read_stream):
|
||||||
"""Wraps openjp2 library function opj_stream_create_default_vile_stream.
|
"""Wraps openjp2 library function opj_stream_create_default_vile_stream.
|
||||||
|
|
||||||
Sets the stream to be a file stream. This is valid only for version 2.0.0
|
Sets the stream to be a file stream.
|
||||||
of OpenJPEG.
|
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
|
|
@ -1272,8 +1320,7 @@ def _stream_create_default_file_stream_2p0(fptr, isa_read_stream):
|
||||||
def _stream_create_default_file_stream_2p1(fname, isa_read_stream):
|
def _stream_create_default_file_stream_2p1(fname, isa_read_stream):
|
||||||
"""Wraps openjp2 library function opj_stream_create_default_vile_stream.
|
"""Wraps openjp2 library function opj_stream_create_default_vile_stream.
|
||||||
|
|
||||||
Sets the stream to be a file stream. This function is only valid for the
|
Sets the stream to be a file stream.
|
||||||
2.1 version of the openjp2 library.
|
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
|
|
@ -1301,7 +1348,6 @@ if re.match(r'''2.0''', version()):
|
||||||
else:
|
else:
|
||||||
stream_create_default_file_stream = _stream_create_default_file_stream_2p1
|
stream_create_default_file_stream = _stream_create_default_file_stream_2p1
|
||||||
|
|
||||||
|
|
||||||
def stream_destroy(stream):
|
def stream_destroy(stream):
|
||||||
"""Wraps openjp2 library function opj_stream_destroy.
|
"""Wraps openjp2 library function opj_stream_destroy.
|
||||||
|
|
||||||
|
|
@ -1358,3 +1404,5 @@ def write_tile(codec, tile_index, data, data_size, stream):
|
||||||
def set_error_message(msg):
|
def set_error_message(msg):
|
||||||
"""The openjpeg error handler has recorded an error message."""
|
"""The openjpeg error handler has recorded an error message."""
|
||||||
ERROR_MSG_LST.append(msg)
|
ERROR_MSG_LST.append(msg)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
"""Wraps library calls to openjpeg.
|
"""Wraps library calls to openjpeg.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# pylint: disable=R0903
|
||||||
|
|
||||||
import ctypes
|
import ctypes
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from .config import glymur_config
|
import numpy as np
|
||||||
|
|
||||||
|
from .config import glymur_config
|
||||||
_, OPENJPEG = glymur_config()
|
_, OPENJPEG = glymur_config()
|
||||||
|
|
||||||
# Maximum number of tile parts expected by JPWL: increase at your will
|
# Maximum number of tile parts expected by JPWL: increase at your will
|
||||||
|
|
@ -55,10 +58,8 @@ class CommonStructType(ctypes.Structure):
|
||||||
("mj2_handle", ctypes.c_void_p)]
|
("mj2_handle", ctypes.c_void_p)]
|
||||||
|
|
||||||
|
|
||||||
STREAM_READ = 0x0001 # The stream was opened for reading.
|
STREAM_READ = 0x0001 # The stream was opened for reading.
|
||||||
STREAM_WRITE = 0x0002 # The stream was opened for writing.
|
STREAM_WRITE = 0x0002 # The stream was opened for writing.
|
||||||
|
|
||||||
|
|
||||||
class CioType(ctypes.Structure):
|
class CioType(ctypes.Structure):
|
||||||
"""Byte input-output stream (CIO)
|
"""Byte input-output stream (CIO)
|
||||||
|
|
||||||
|
|
@ -89,57 +90,70 @@ class CompressionInfoType(CommonStructType):
|
||||||
class PocType(ctypes.Structure):
|
class PocType(ctypes.Structure):
|
||||||
"""Progression order changes."""
|
"""Progression order changes."""
|
||||||
_fields_ = [("resno", ctypes.c_int),
|
_fields_ = [("resno", ctypes.c_int),
|
||||||
# Resolution num start, Component num start, given by POC
|
# Resolution num start, Component num start, given by POC
|
||||||
("compno0", ctypes.c_int),
|
("compno0", ctypes.c_int),
|
||||||
|
|
||||||
# Layer num end,Resolution num end, Component num end, given
|
# Layer num end,Resolution num end, Component num end, given by POC
|
||||||
# by POC
|
("layno1", ctypes.c_int),
|
||||||
("layno1", ctypes.c_int),
|
("resno1", ctypes.c_int),
|
||||||
("resno1", ctypes.c_int),
|
("compno1", ctypes.c_int),
|
||||||
("compno1", ctypes.c_int),
|
|
||||||
|
|
||||||
# Layer num start,Precinct num start, Precinct num end
|
# Layer num start,Precinct num start, Precinct num end
|
||||||
("layno0", ctypes.c_int),
|
("layno0", ctypes.c_int),
|
||||||
("precno0", ctypes.c_int),
|
("precno0", ctypes.c_int),
|
||||||
("precno1", ctypes.c_int),
|
("precno1", ctypes.c_int),
|
||||||
|
|
||||||
# Progression order enum
|
# Progression order enum
|
||||||
# OPJ_PROG_ORDER prg1,prg;
|
# OPJ_PROG_ORDER prg1,prg;
|
||||||
("prg1", ctypes.c_int),
|
("prg1", ctypes.c_int),
|
||||||
("prg", ctypes.c_int),
|
("prg", ctypes.c_int),
|
||||||
|
|
||||||
# Progression order string
|
# Progression order string
|
||||||
# char progorder[5];
|
# char progorder[5];
|
||||||
("progorder", ctypes.c_char * 5),
|
("progorder", ctypes.c_char * 5),
|
||||||
|
|
||||||
# Tile number
|
# Tile number
|
||||||
# int tile;
|
# int tile;
|
||||||
("tile", ctypes.c_int),
|
("tile", ctypes.c_int),
|
||||||
|
|
||||||
("tx0", ctypes.c_int),
|
# /** Start and end values for Tile width and height*/
|
||||||
("tx1", ctypes.c_int),
|
# int tx0,tx1,ty0,ty1;
|
||||||
("ty0", ctypes.c_int),
|
("tx0", ctypes.c_int),
|
||||||
("ty1", ctypes.c_int),
|
("tx1", ctypes.c_int),
|
||||||
("layS", ctypes.c_int),
|
("ty0", ctypes.c_int),
|
||||||
("resS", ctypes.c_int),
|
("ty1", ctypes.c_int),
|
||||||
("compS", ctypes.c_int),
|
|
||||||
("prcS", ctypes.c_int),
|
# /** Start value, initialised in pi_initialise_encode*/
|
||||||
("layE", ctypes.c_int),
|
# int layS, resS, compS, prcS;
|
||||||
("resE", ctypes.c_int),
|
("layS", ctypes.c_int),
|
||||||
("compE", ctypes.c_int),
|
("resS", ctypes.c_int),
|
||||||
("prcE", ctypes.c_int),
|
("compS", ctypes.c_int),
|
||||||
("txS", ctypes.c_int),
|
("prcS", ctypes.c_int),
|
||||||
("txE", ctypes.c_int),
|
|
||||||
("tyS", ctypes.c_int),
|
# /** End value, initialised in pi_initialise_encode */
|
||||||
("tyE", ctypes.c_int),
|
# int layE, resE, compE, prcE;
|
||||||
("dx", ctypes.c_int),
|
("layE", ctypes.c_int),
|
||||||
("dy", ctypes.c_int),
|
("resE", ctypes.c_int),
|
||||||
("lay_t", ctypes.c_int),
|
("compE", ctypes.c_int),
|
||||||
("res_t", ctypes.c_int),
|
("prcE", ctypes.c_int),
|
||||||
("comp_t", ctypes.c_int),
|
|
||||||
("prc_t", ctypes.c_int),
|
# Start and end values of Tile width and height, initialised in
|
||||||
("tx0_t", ctypes.c_int),
|
# pi_initialise_encode int txS,txE,tyS,tyE,dx,dy;
|
||||||
("ty0_t", ctypes.c_int)]
|
("txS", ctypes.c_int),
|
||||||
|
("txE", ctypes.c_int),
|
||||||
|
("tyS", ctypes.c_int),
|
||||||
|
("tyE", ctypes.c_int),
|
||||||
|
("dx", ctypes.c_int),
|
||||||
|
("dy", ctypes.c_int),
|
||||||
|
|
||||||
|
# Temporary values for Tile parts, initialised in pi_create_encode
|
||||||
|
# int lay_t, res_t, comp_t, prc_t,tx0_t,ty0_t;
|
||||||
|
("lay_t", ctypes.c_int),
|
||||||
|
("res_t", ctypes.c_int),
|
||||||
|
("comp_t", ctypes.c_int),
|
||||||
|
("prc_t", ctypes.c_int),
|
||||||
|
("tx0_t", ctypes.c_int),
|
||||||
|
("ty0_t", ctypes.c_int)]
|
||||||
|
|
||||||
|
|
||||||
class CompressionParametersType(ctypes.Structure):
|
class CompressionParametersType(ctypes.Structure):
|
||||||
|
|
@ -360,47 +374,48 @@ class DecompressionParametersType(ctypes.Structure):
|
||||||
class ImageComptParmType(ctypes.Structure):
|
class ImageComptParmType(ctypes.Structure):
|
||||||
"""Component parameters structure used by the opj_image_create function.
|
"""Component parameters structure used by the opj_image_create function.
|
||||||
"""
|
"""
|
||||||
_fields_ = [("dx", ctypes.c_int),
|
_fields_ = [
|
||||||
# XRsiz: horizontal separation of a sample of ith component
|
# XRsiz: horizontal separation of a sample of ith component with
|
||||||
# with respect to the reference grid
|
# respect to the reference grid
|
||||||
|
("dx", ctypes.c_int),
|
||||||
|
|
||||||
# YRsiz: vertical separation of a sample of ith component with
|
# YRsiz: vertical separation of a sample of ith component with
|
||||||
# respect to the reference grid */
|
# respect to the reference grid */
|
||||||
("dy", ctypes.c_int),
|
("dy", ctypes.c_int),
|
||||||
|
|
||||||
# data width, height
|
# data width, height
|
||||||
("w", ctypes.c_int),
|
("w", ctypes.c_int),
|
||||||
("h", ctypes.c_int),
|
("h", ctypes.c_int),
|
||||||
|
|
||||||
# x component offset compared to the whole image
|
# x component offset compared to the whole image
|
||||||
# y component offset compared to the whole image
|
# y component offset compared to the whole image
|
||||||
("x0", ctypes.c_int),
|
("x0", ctypes.c_int),
|
||||||
("y0", ctypes.c_int),
|
("y0", ctypes.c_int),
|
||||||
|
|
||||||
# precision
|
# precision
|
||||||
('prec', ctypes.c_int),
|
('prec', ctypes.c_int),
|
||||||
|
|
||||||
# image depth in bits
|
# image depth in bits
|
||||||
('bpp', ctypes.c_int),
|
('bpp', ctypes.c_int),
|
||||||
|
|
||||||
# signed (1) / unsigned (0)
|
# signed (1) / unsigned (0)
|
||||||
('sgnd', ctypes.c_int)]
|
('sgnd', ctypes.c_int)]
|
||||||
|
|
||||||
|
|
||||||
class ImageCompType(ctypes.Structure):
|
class ImageCompType(ctypes.Structure):
|
||||||
"""Defines a single image component. """
|
"""Defines a single image component. """
|
||||||
_fields_ = [("dx", ctypes.c_int),
|
_fields_ = [("dx", ctypes.c_int),
|
||||||
("dy", ctypes.c_int),
|
("dy", ctypes.c_int),
|
||||||
("w", ctypes.c_int),
|
("w", ctypes.c_int),
|
||||||
("h", ctypes.c_int),
|
("h", ctypes.c_int),
|
||||||
("x0", ctypes.c_int),
|
("x0", ctypes.c_int),
|
||||||
("y0", ctypes.c_int),
|
("y0", ctypes.c_int),
|
||||||
("prec", ctypes.c_int),
|
("prec", ctypes.c_int),
|
||||||
("bpp", ctypes.c_int),
|
("bpp", ctypes.c_int),
|
||||||
("sgnd", ctypes.c_int),
|
("sgnd", ctypes.c_int),
|
||||||
("resno_decoded", ctypes.c_int),
|
("resno_decoded", ctypes.c_int),
|
||||||
("factor", ctypes.c_int),
|
("factor", ctypes.c_int),
|
||||||
("data", ctypes.POINTER(ctypes.c_int))]
|
("data", ctypes.POINTER(ctypes.c_int))]
|
||||||
|
|
||||||
|
|
||||||
class ImageType(ctypes.Structure):
|
class ImageType(ctypes.Structure):
|
||||||
|
|
@ -452,7 +467,6 @@ def cio_tell(cio):
|
||||||
pos = OPENJPEG.cio_tell(cio)
|
pos = OPENJPEG.cio_tell(cio)
|
||||||
return pos
|
return pos
|
||||||
|
|
||||||
|
|
||||||
def create_compress(fmt):
|
def create_compress(fmt):
|
||||||
"""Wrapper for openjpeg library function opj_create_compress.
|
"""Wrapper for openjpeg library function opj_create_compress.
|
||||||
|
|
||||||
|
|
@ -523,11 +537,56 @@ def destroy_decompress(dinfo):
|
||||||
OPENJPEG.opj_destroy_decompress(dinfo)
|
OPENJPEG.opj_destroy_decompress(dinfo)
|
||||||
|
|
||||||
|
|
||||||
|
def image_cmptparm_t_from_np(np_image):
|
||||||
|
"""Return appropriate image_cmptparm_t based on given numpy array.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
num_comps = np_image.shape[2]
|
||||||
|
except IndexError:
|
||||||
|
num_comps = 1
|
||||||
|
|
||||||
|
cmpt_parm_array_t = ImageCmptparmType * num_comps
|
||||||
|
tarr = cmpt_parm_array_t()
|
||||||
|
|
||||||
|
if np_image.dtype == np.uint8:
|
||||||
|
prec = 8
|
||||||
|
bpp = 8
|
||||||
|
sgnd = 0
|
||||||
|
elif np_image.dtype == np.int8:
|
||||||
|
prec = 8
|
||||||
|
bpp = 8
|
||||||
|
sgnd = 1
|
||||||
|
elif np_image.dtype == np.uint16:
|
||||||
|
prec = 16
|
||||||
|
bpp = 16
|
||||||
|
sgnd = 0
|
||||||
|
elif np_image.dtype == np.int16:
|
||||||
|
prec = 16
|
||||||
|
bpp = 16
|
||||||
|
sgnd = 1
|
||||||
|
else:
|
||||||
|
raise(TypeError("unhandled"))
|
||||||
|
|
||||||
|
for j in range(0, num_comps):
|
||||||
|
tarr[j].dx = 1
|
||||||
|
tarr[j].dy = 1
|
||||||
|
tarr[j].w = np_image.shape[1]
|
||||||
|
tarr[j].h = np_image.shape[0]
|
||||||
|
tarr[j].x0 = 0
|
||||||
|
tarr[j].y0 = 0
|
||||||
|
tarr[j].prec = prec
|
||||||
|
tarr[j].bpp = bpp
|
||||||
|
tarr[j].sgnd = sgnd
|
||||||
|
|
||||||
|
return(tarr)
|
||||||
|
|
||||||
|
|
||||||
def image_create(cmptparms, cspace):
|
def image_create(cmptparms, cspace):
|
||||||
"""Wrapper for openjpeg library function opj_image_create.
|
"""Wrapper for openjpeg library function opj_image_create.
|
||||||
"""
|
"""
|
||||||
lst = [ctypes.c_int, ctypes.POINTER(ImageComptParmType), ctypes.c_int]
|
OPENJPEG.opj_image_create.argtypes = [ctypes.c_int,
|
||||||
OPENJPEG.opj_image_create.argtypes = lst
|
ctypes.POINTER(ImageComptParmType),
|
||||||
|
ctypes.c_int]
|
||||||
OPENJPEG.opj_image_create.restype = ctypes.POINTER(ImageType)
|
OPENJPEG.opj_image_create.restype = ctypes.POINTER(ImageType)
|
||||||
|
|
||||||
image = OPENJPEG.opj_image_create(len(cmptparms), cmptparms, cspace)
|
image = OPENJPEG.opj_image_create(len(cmptparms), cmptparms, cspace)
|
||||||
|
|
|
||||||
|
|
@ -1,144 +0,0 @@
|
||||||
decompression_parameters_type = """<class 'glymur.lib.openjp2.DecompressionParametersType'>:
|
|
||||||
cp_reduce: 0
|
|
||||||
cp_layer: 0
|
|
||||||
infile: b''
|
|
||||||
outfile: b''
|
|
||||||
decod_format: -1
|
|
||||||
cod_format: -1
|
|
||||||
DA_x0: 0
|
|
||||||
DA_x1: 0
|
|
||||||
DA_y0: 0
|
|
||||||
DA_y1: 0
|
|
||||||
m_verbose: 0
|
|
||||||
tile_index: 0
|
|
||||||
nb_tile_to_decode: 0
|
|
||||||
jpwl_correct: 0
|
|
||||||
jpwl_exp_comps: 0
|
|
||||||
jpwl_max_tiles: 0
|
|
||||||
flags: 0"""
|
|
||||||
|
|
||||||
default_progression_order_changes_type = """<class 'glymur.lib.openjp2.PocType'>:
|
|
||||||
resno0: 0
|
|
||||||
compno0: 0
|
|
||||||
layno1: 0
|
|
||||||
resno1: 0
|
|
||||||
compno1: 0
|
|
||||||
layno0: 0
|
|
||||||
precno0: 0
|
|
||||||
precno1: 0
|
|
||||||
prg1: 0
|
|
||||||
prg: 0
|
|
||||||
progorder: b''
|
|
||||||
tile: 0
|
|
||||||
tx0: 0
|
|
||||||
tx1: 0
|
|
||||||
ty0: 0
|
|
||||||
ty1: 0
|
|
||||||
layS: 0
|
|
||||||
resS: 0
|
|
||||||
compS: 0
|
|
||||||
prcS: 0
|
|
||||||
layE: 0
|
|
||||||
resE: 0
|
|
||||||
compE: 0
|
|
||||||
prcE: 0
|
|
||||||
txS: 0
|
|
||||||
txE: 0
|
|
||||||
tyS: 0
|
|
||||||
tyE: 0
|
|
||||||
dx: 0
|
|
||||||
dy: 0
|
|
||||||
lay_t: 0
|
|
||||||
res_t: 0
|
|
||||||
comp_t: 0
|
|
||||||
prec_t: 0
|
|
||||||
tx0_t: 0
|
|
||||||
ty0_t: 0"""
|
|
||||||
|
|
||||||
default_compression_parameters_type = """<class 'glymur.lib.openjp2.CompressionParametersType'>:
|
|
||||||
tile_size_on: 0
|
|
||||||
cp_tx0: 0
|
|
||||||
cp_ty0: 0
|
|
||||||
cp_tdx: 0
|
|
||||||
cp_tdy: 0
|
|
||||||
cp_disto_alloc: 0
|
|
||||||
cp_fixed_alloc: 0
|
|
||||||
cp_fixed_quality: 0
|
|
||||||
cp_matrice: None
|
|
||||||
cp_comment: None
|
|
||||||
csty: 0
|
|
||||||
prog_order: 0
|
|
||||||
numpocs: 0
|
|
||||||
numpocs: 0
|
|
||||||
tcp_numlayers: 0
|
|
||||||
tcp_rates: []
|
|
||||||
tcp_distoratio: []
|
|
||||||
numresolution: 6
|
|
||||||
cblockw_init: 64
|
|
||||||
cblockh_init: 64
|
|
||||||
mode: 0
|
|
||||||
irreversible: 0
|
|
||||||
roi_compno: -1
|
|
||||||
roi_shift: 0
|
|
||||||
res_spec: 0
|
|
||||||
prch_init: []
|
|
||||||
prcw_init: []
|
|
||||||
infile: b''
|
|
||||||
outfile: b''
|
|
||||||
index_on: 0
|
|
||||||
index: b''
|
|
||||||
image_offset_x0: 0
|
|
||||||
image_offset_y0: 0
|
|
||||||
subsampling_dx: 1
|
|
||||||
subsampling_dy: 1
|
|
||||||
decod_format: -1
|
|
||||||
cod_format: -1
|
|
||||||
jpwl_epc_on: 0
|
|
||||||
jpwl_hprot_mh: 0
|
|
||||||
jpwl_hprot_tph_tileno: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
||||||
jpwl_hprot_tph: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
||||||
jpwl_pprot_tileno: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
||||||
jpwl_pprot_packno: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
||||||
jpwl_pprot: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
||||||
jpwl_sens_size: 0
|
|
||||||
jpwl_sens_addr: 0
|
|
||||||
jpwl_sens_range: 0
|
|
||||||
jpwl_sens_mh: 0
|
|
||||||
jpwl_sens_tph_tileno: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
||||||
jpwl_sens_tph: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
||||||
cp_cinema: 0
|
|
||||||
max_comp_size: 0
|
|
||||||
cp_rsiz: 0
|
|
||||||
tp_on: 0
|
|
||||||
tp_flag: 0
|
|
||||||
tcp_mct: 0
|
|
||||||
jpip_on: 0
|
|
||||||
mct_data: None
|
|
||||||
max_cs_size: 0
|
|
||||||
rsiz: 0"""
|
|
||||||
|
|
||||||
default_image_component_parameters = """<class 'glymur.lib.openjp2.ImageComptParmType'>:
|
|
||||||
dx: 0
|
|
||||||
dy: 0
|
|
||||||
w: 0
|
|
||||||
h: 0
|
|
||||||
x0: 0
|
|
||||||
y0: 0
|
|
||||||
prec: 0
|
|
||||||
bpp: 0
|
|
||||||
sgnd: 0"""
|
|
||||||
|
|
||||||
# The "icc_profile_buf" field is problematic as it is a pointer value, i.e.
|
|
||||||
#
|
|
||||||
# icc_profile_buf: <glymur.lib.openjp2.LP_c_ubyte object at 0x7f28cd5d5d90>
|
|
||||||
#
|
|
||||||
# Have to treat it as a regular expression.
|
|
||||||
default_image_type = """<class 'glymur.lib.openjp2.ImageType'>:
|
|
||||||
x0: 0
|
|
||||||
y0: 0
|
|
||||||
x1: 0
|
|
||||||
y1: 0
|
|
||||||
numcomps: 0
|
|
||||||
color_space: 0
|
|
||||||
icc_profile_buf: <glymur.lib.openjp2.LP_c_ubyte object at 0x[0-9A-Fa-f]*>
|
|
||||||
icc_profile_len: 0"""
|
|
||||||
|
|
@ -1,23 +1,37 @@
|
||||||
"""
|
"""
|
||||||
Tests for libopenjp2 wrapping functions.
|
Tests for libopenjp2 wrapping functions.
|
||||||
"""
|
"""
|
||||||
|
# R0904: Seems like pylint is fooled in this situation
|
||||||
|
# W0142: using kwargs is ok in this context
|
||||||
|
# pylint: disable=R0904,W0142
|
||||||
|
|
||||||
|
# unittest2 is python-2.6 only (pylint/python-2.7)
|
||||||
|
# pylint: disable=F0401
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
|
||||||
|
if sys.hexversion < 0x02070000:
|
||||||
|
import unittest2 as unittest
|
||||||
|
else:
|
||||||
|
import unittest
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
import glymur
|
import glymur
|
||||||
from glymur.lib import openjp2
|
from glymur.lib import openjp2
|
||||||
|
|
||||||
|
if re.match("2.0", glymur.version.openjpeg_version):
|
||||||
|
OPENJP2_IS_V2_OFFICIAL = True
|
||||||
|
else:
|
||||||
|
OPENJP2_IS_V2_OFFICIAL = False
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
@unittest.skipIf(openjp2.OPENJP2 is None,
|
@unittest.skipIf(openjp2.OPENJP2 is None,
|
||||||
"Missing openjp2 library.")
|
"Missing openjp2 library.")
|
||||||
@unittest.skipIf(re.match(r'''(1|2.0)''',
|
@unittest.skipIf(OPENJP2_IS_V2_OFFICIAL, "API followed here specific to V2.1")
|
||||||
glymur.version.openjpeg_version) is not None,
|
|
||||||
"Not to be run until 2.1.0")
|
|
||||||
class TestOpenJP2(unittest.TestCase):
|
class TestOpenJP2(unittest.TestCase):
|
||||||
"""Test openjp2 library functionality.
|
"""Test openjp2 library functionality.
|
||||||
|
|
||||||
|
|
@ -51,6 +65,53 @@ class TestOpenJP2(unittest.TestCase):
|
||||||
self.assertEqual(dparams.DA_x1, 0)
|
self.assertEqual(dparams.DA_x1, 0)
|
||||||
self.assertEqual(dparams.DA_y1, 0)
|
self.assertEqual(dparams.DA_y1, 0)
|
||||||
|
|
||||||
|
def tile_macro(self, codec, stream, imagep, tidx):
|
||||||
|
"""called only by j2k_random_tile_access"""
|
||||||
|
openjp2.get_decoded_tile(codec, stream, imagep, tidx)
|
||||||
|
for j in range(imagep.contents.numcomps):
|
||||||
|
self.assertIsNotNone(imagep.contents.comps[j].data)
|
||||||
|
|
||||||
|
def j2k_random_tile_access(self, filename, codec_format=None):
|
||||||
|
"""fixture called by the test_rtaX methods"""
|
||||||
|
dparam = openjp2.set_default_decoder_parameters()
|
||||||
|
|
||||||
|
infile = filename.encode()
|
||||||
|
nelts = openjp2.PATH_LEN - len(infile)
|
||||||
|
infile += b'0' * nelts
|
||||||
|
dparam.infile = infile
|
||||||
|
|
||||||
|
dparam.decod_format = codec_format
|
||||||
|
|
||||||
|
codec = openjp2.create_decompress(codec_format)
|
||||||
|
|
||||||
|
openjp2.set_info_handler(codec, None)
|
||||||
|
openjp2.set_warning_handler(codec, None)
|
||||||
|
openjp2.set_error_handler(codec, None)
|
||||||
|
|
||||||
|
stream = openjp2.stream_create_default_file_stream(filename, True)
|
||||||
|
|
||||||
|
openjp2.setup_decoder(codec, dparam)
|
||||||
|
image = openjp2.read_header(stream, codec)
|
||||||
|
|
||||||
|
cstr_info = openjp2.get_cstr_info(codec)
|
||||||
|
|
||||||
|
tile_ul = 0
|
||||||
|
tile_ur = cstr_info.contents.tw - 1
|
||||||
|
tile_lr = cstr_info.contents.tw * cstr_info.contents.th - 1
|
||||||
|
tile_ll = tile_lr - cstr_info.contents.tw
|
||||||
|
|
||||||
|
self.tile_macro(codec, stream, image, tile_ul)
|
||||||
|
self.tile_macro(codec, stream, image, tile_ur)
|
||||||
|
self.tile_macro(codec, stream, image, tile_lr)
|
||||||
|
self.tile_macro(codec, stream, image, tile_ll)
|
||||||
|
|
||||||
|
openjp2.destroy_cstr_info(cstr_info)
|
||||||
|
|
||||||
|
openjp2.end_decompress(codec, stream)
|
||||||
|
openjp2.destroy_codec(codec)
|
||||||
|
openjp2.stream_destroy(stream)
|
||||||
|
openjp2.image_destroy(image)
|
||||||
|
|
||||||
def test_tte0(self):
|
def test_tte0(self):
|
||||||
"""Runs test designated tte0 in OpenJPEG test suite."""
|
"""Runs test designated tte0 in OpenJPEG test suite."""
|
||||||
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
|
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
|
||||||
|
|
@ -108,6 +169,15 @@ class TestOpenJP2(unittest.TestCase):
|
||||||
tile_decoder(**kwargs)
|
tile_decoder(**kwargs)
|
||||||
self.assertTrue(True)
|
self.assertTrue(True)
|
||||||
|
|
||||||
|
def test_rta1(self):
|
||||||
|
"""Runs test designated rta1 in OpenJPEG test suite."""
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
|
||||||
|
self.xtx1_setup(tfile.name)
|
||||||
|
|
||||||
|
codec_format = openjp2.CODEC_J2K
|
||||||
|
self.j2k_random_tile_access(tfile.name, codec_format)
|
||||||
|
self.assertTrue(True)
|
||||||
|
|
||||||
def test_tte2(self):
|
def test_tte2(self):
|
||||||
"""Runs test designated tte2 in OpenJPEG test suite."""
|
"""Runs test designated tte2 in OpenJPEG test suite."""
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
|
|
@ -129,25 +199,62 @@ class TestOpenJP2(unittest.TestCase):
|
||||||
tile_decoder(**kwargs)
|
tile_decoder(**kwargs)
|
||||||
self.assertTrue(True)
|
self.assertTrue(True)
|
||||||
|
|
||||||
|
def test_rta2(self):
|
||||||
|
"""Runs test designated rta2 in OpenJPEG test suite."""
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
|
||||||
|
xtx2_setup(tfile.name)
|
||||||
|
|
||||||
|
codec_format = openjp2.CODEC_JP2
|
||||||
|
self.j2k_random_tile_access(tfile.name, codec_format)
|
||||||
|
|
||||||
def test_tte3(self):
|
def test_tte3(self):
|
||||||
"""Runs test designated tte3 in OpenJPEG test suite."""
|
"""Runs test designated tte3 in OpenJPEG test suite."""
|
||||||
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
|
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
|
||||||
xtx3_setup(tfile.name)
|
xtx3_setup(tfile.name)
|
||||||
self.assertTrue(True)
|
self.assertTrue(True)
|
||||||
|
|
||||||
|
def test_rta3(self):
|
||||||
|
"""Runs test designated rta3 in OpenJPEG test suite."""
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
|
||||||
|
xtx3_setup(tfile.name)
|
||||||
|
|
||||||
|
codec_format = openjp2.CODEC_J2K
|
||||||
|
self.j2k_random_tile_access(tfile.name, codec_format)
|
||||||
|
self.assertTrue(True)
|
||||||
|
|
||||||
def test_tte4(self):
|
def test_tte4(self):
|
||||||
"""Runs test designated tte4 in OpenJPEG test suite."""
|
"""Runs test designated tte4 in OpenJPEG test suite."""
|
||||||
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
|
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
|
||||||
xtx4_setup(tfile.name)
|
xtx4_setup(tfile.name)
|
||||||
self.assertTrue(True)
|
self.assertTrue(True)
|
||||||
|
|
||||||
|
def test_rta4(self):
|
||||||
|
"""Runs test designated rta4 in OpenJPEG test suite."""
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
|
||||||
|
xtx4_setup(tfile.name)
|
||||||
|
|
||||||
|
codec_format = openjp2.CODEC_J2K
|
||||||
|
self.j2k_random_tile_access(tfile.name, codec_format)
|
||||||
|
|
||||||
def test_tte5(self):
|
def test_tte5(self):
|
||||||
"""Runs test designated tte5 in OpenJPEG test suite."""
|
"""Runs test designated tte5 in OpenJPEG test suite."""
|
||||||
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
|
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
|
||||||
xtx5_setup(tfile.name)
|
xtx5_setup(tfile.name)
|
||||||
self.assertTrue(True)
|
self.assertTrue(True)
|
||||||
|
|
||||||
|
def test_rta5(self):
|
||||||
|
"""Runs test designated rta5 in OpenJPEG test suite."""
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".j2k") as tfile:
|
||||||
|
xtx5_setup(tfile.name)
|
||||||
|
|
||||||
|
codec_format = openjp2.CODEC_J2K
|
||||||
|
self.j2k_random_tile_access(tfile.name, codec_format)
|
||||||
|
|
||||||
|
|
||||||
|
#def tile_encoder(num_comps=None, tile_width=None, tile_height=None,
|
||||||
|
# filename=None, codec=None, comp_prec=None,
|
||||||
|
# image_width=None, image_height=None,
|
||||||
|
# irreversible=None):
|
||||||
def tile_encoder(**kwargs):
|
def tile_encoder(**kwargs):
|
||||||
"""Fixture used by many tests."""
|
"""Fixture used by many tests."""
|
||||||
num_tiles = ((kwargs['image_width'] / kwargs['tile_width']) *
|
num_tiles = ((kwargs['image_width'] / kwargs['tile_width']) *
|
||||||
|
|
@ -222,7 +329,6 @@ def tile_encoder(**kwargs):
|
||||||
openjp2.destroy_codec(codec)
|
openjp2.destroy_codec(codec)
|
||||||
openjp2.image_destroy(l_image)
|
openjp2.image_destroy(l_image)
|
||||||
|
|
||||||
|
|
||||||
def tile_decoder(**kwargs):
|
def tile_decoder(**kwargs):
|
||||||
"""Fixture called with various configurations by many tests.
|
"""Fixture called with various configurations by many tests.
|
||||||
|
|
||||||
|
|
@ -267,7 +373,6 @@ def tile_decoder(**kwargs):
|
||||||
openjp2.stream_destroy(stream)
|
openjp2.stream_destroy(stream)
|
||||||
openjp2.image_destroy(image)
|
openjp2.image_destroy(image)
|
||||||
|
|
||||||
|
|
||||||
def ttx0_setup(filename):
|
def ttx0_setup(filename):
|
||||||
"""Runs tests tte0, tte0."""
|
"""Runs tests tte0, tte0."""
|
||||||
kwargs = {'filename': filename,
|
kwargs = {'filename': filename,
|
||||||
|
|
@ -281,7 +386,6 @@ def ttx0_setup(filename):
|
||||||
'tile_width': 100}
|
'tile_width': 100}
|
||||||
tile_encoder(**kwargs)
|
tile_encoder(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
def xtx2_setup(filename):
|
def xtx2_setup(filename):
|
||||||
"""Runs tests rta2, tte2, ttd2."""
|
"""Runs tests rta2, tte2, ttd2."""
|
||||||
kwargs = {'filename': filename,
|
kwargs = {'filename': filename,
|
||||||
|
|
@ -295,7 +399,6 @@ def xtx2_setup(filename):
|
||||||
'tile_width': 128}
|
'tile_width': 128}
|
||||||
tile_encoder(**kwargs)
|
tile_encoder(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
def xtx3_setup(filename):
|
def xtx3_setup(filename):
|
||||||
"""Runs tests tte3, rta3."""
|
"""Runs tests tte3, rta3."""
|
||||||
kwargs = {'filename': filename,
|
kwargs = {'filename': filename,
|
||||||
|
|
@ -309,7 +412,6 @@ def xtx3_setup(filename):
|
||||||
'tile_width': 128}
|
'tile_width': 128}
|
||||||
tile_encoder(**kwargs)
|
tile_encoder(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
def xtx4_setup(filename):
|
def xtx4_setup(filename):
|
||||||
"""Runs tests rta4, tte4."""
|
"""Runs tests rta4, tte4."""
|
||||||
kwargs = {'filename': filename,
|
kwargs = {'filename': filename,
|
||||||
|
|
@ -323,7 +425,6 @@ def xtx4_setup(filename):
|
||||||
'tile_width': 128}
|
'tile_width': 128}
|
||||||
tile_encoder(**kwargs)
|
tile_encoder(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
def xtx5_setup(filename):
|
def xtx5_setup(filename):
|
||||||
"""Runs tests rta5, tte5."""
|
"""Runs tests rta5, tte5."""
|
||||||
kwargs = {'filename': filename,
|
kwargs = {'filename': filename,
|
||||||
|
|
@ -336,3 +437,6 @@ def xtx5_setup(filename):
|
||||||
'tile_height': 256,
|
'tile_height': 256,
|
||||||
'tile_width': 256}
|
'tile_width': 256}
|
||||||
tile_encoder(**kwargs)
|
tile_encoder(**kwargs)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,22 @@
|
||||||
"""
|
"""
|
||||||
Tests for OpenJPEG module.
|
Tests for OpenJPEG module.
|
||||||
"""
|
"""
|
||||||
|
# unittest2 is python2.6 only (pylint/python-2.7)
|
||||||
|
# pylint: disable=F0401
|
||||||
|
|
||||||
|
# pylint: disable=E1101,R0904
|
||||||
|
|
||||||
import ctypes
|
import ctypes
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
|
||||||
|
if sys.hexversion < 0x02070000:
|
||||||
|
import unittest2 as unittest
|
||||||
|
else:
|
||||||
|
import unittest
|
||||||
|
|
||||||
import glymur
|
import glymur
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(glymur.lib.openjpeg.OPENJPEG is None,
|
@unittest.skipIf(glymur.lib.openjpeg.OPENJPEG is None,
|
||||||
"Missing openjpeg library.")
|
"Missing openjpeg library.")
|
||||||
class TestOpenJPEG(unittest.TestCase):
|
class TestOpenJPEG(unittest.TestCase):
|
||||||
|
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""Test suite for printing.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
if sys.hexversion < 0x03000000:
|
|
||||||
from mock import patch
|
|
||||||
from StringIO import StringIO
|
|
||||||
else:
|
|
||||||
from unittest.mock import patch
|
|
||||||
from io import StringIO
|
|
||||||
|
|
||||||
import glymur
|
|
||||||
from . import fixtures
|
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(sys.hexversion < 0x03000000, "do not care about 2.7 here")
|
|
||||||
@unittest.skipIf(re.match('0|1|2.0', glymur.version.openjpeg_version),
|
|
||||||
"Requires openjpeg 2.1.0 or higher")
|
|
||||||
class TestPrintingOpenjp2(unittest.TestCase):
|
|
||||||
"""Tests for verifying how printing works on openjp2 library structures."""
|
|
||||||
def setUp(self):
|
|
||||||
self.jp2file = glymur.data.nemo()
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_decompression_parameters(self):
|
|
||||||
"""printing DecompressionParametersType"""
|
|
||||||
dparams = glymur.lib.openjp2.set_default_decoder_parameters()
|
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
|
||||||
print(dparams)
|
|
||||||
actual = fake_out.getvalue().strip()
|
|
||||||
expected = fixtures.decompression_parameters_type
|
|
||||||
self.assertEqual(actual, expected)
|
|
||||||
|
|
||||||
def test_progression_order_changes(self):
|
|
||||||
"""printing PocType"""
|
|
||||||
ptype = glymur.lib.openjp2.PocType()
|
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
|
||||||
print(ptype)
|
|
||||||
actual = fake_out.getvalue().strip()
|
|
||||||
expected = fixtures.default_progression_order_changes_type
|
|
||||||
self.assertEqual(actual, expected)
|
|
||||||
|
|
||||||
def test_default_compression_parameters(self):
|
|
||||||
"""printing default compression parameters"""
|
|
||||||
cparams = glymur.lib.openjp2.set_default_encoder_parameters()
|
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
|
||||||
print(cparams)
|
|
||||||
actual = fake_out.getvalue().strip()
|
|
||||||
expected = fixtures.default_compression_parameters_type
|
|
||||||
self.assertEqual(actual, expected)
|
|
||||||
|
|
||||||
def test_default_component_parameters(self):
|
|
||||||
"""printing default image component parameters"""
|
|
||||||
icpt = glymur.lib.openjp2.ImageComptParmType()
|
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
|
||||||
print(icpt)
|
|
||||||
actual = fake_out.getvalue().strip()
|
|
||||||
expected = fixtures.default_image_component_parameters
|
|
||||||
self.assertEqual(actual, expected)
|
|
||||||
|
|
||||||
def test_default_image_type(self):
|
|
||||||
"""printing default image type"""
|
|
||||||
it = glymur.lib.openjp2.ImageType()
|
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
|
||||||
print(it)
|
|
||||||
actual = fake_out.getvalue().strip()
|
|
||||||
|
|
||||||
expected = fixtures.default_image_type
|
|
||||||
self.assertRegex(actual, expected)
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,12 +1,22 @@
|
||||||
"""
|
"""
|
||||||
Test suite for openjpeg's callback functions.
|
Test suite for openjpeg's callback functions.
|
||||||
"""
|
"""
|
||||||
|
# R0904: Seems like pylint is fooled in this situation
|
||||||
|
# pylint: disable=R0904
|
||||||
|
|
||||||
|
# 'mock' most certainly is in unittest (Python 3.3)
|
||||||
|
# pylint: disable=E0611,F0401
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import warnings
|
||||||
|
|
||||||
import unittest
|
if sys.hexversion < 0x02070000:
|
||||||
|
import unittest2 as unittest
|
||||||
|
else:
|
||||||
|
import unittest
|
||||||
|
|
||||||
if sys.hexversion <= 0x03030000:
|
if sys.hexversion <= 0x03030000:
|
||||||
from mock import patch
|
from mock import patch
|
||||||
|
|
@ -17,9 +27,9 @@ else:
|
||||||
|
|
||||||
import glymur
|
import glymur
|
||||||
|
|
||||||
from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG
|
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None,
|
||||||
|
"Missing openjp2 library.")
|
||||||
class TestCallbacks(unittest.TestCase):
|
class TestCallbacks(unittest.TestCase):
|
||||||
"""Test suite for callbacks."""
|
"""Test suite for callbacks."""
|
||||||
|
|
||||||
|
|
@ -30,60 +40,68 @@ class TestCallbacks(unittest.TestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@unittest.skipIf(glymur.version.openjpeg_version[0] != '2',
|
|
||||||
"Missing openjp2 library.")
|
|
||||||
@unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG)
|
|
||||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
|
||||||
def test_info_callback_on_write_backwards_compatibility(self):
|
|
||||||
"""Verify messages printed when writing an image in verbose mode."""
|
|
||||||
j = glymur.Jp2k(self.jp2file)
|
|
||||||
with self.assertWarns(UserWarning):
|
|
||||||
tiledata = j.read(tile=0)
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
|
||||||
glymur.Jp2k(tfile.name, data=tiledata, verbose=True)
|
|
||||||
actual = fake_out.getvalue().strip()
|
|
||||||
expected = '[INFO] tile number 1 / 1'
|
|
||||||
self.assertEqual(actual, expected)
|
|
||||||
|
|
||||||
@unittest.skipIf(glymur.version.openjpeg_version[0] != '2',
|
|
||||||
"Missing openjp2 library.")
|
|
||||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
def test_info_callback_on_write(self):
|
def test_info_callback_on_write(self):
|
||||||
"""Verify messages printed when writing an image in verbose mode."""
|
"""Verify messages printed when writing an image in verbose mode."""
|
||||||
j = glymur.Jp2k(self.jp2file)
|
j = glymur.Jp2k(self.jp2file)
|
||||||
tiledata = j[:]
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter("ignore")
|
||||||
|
tiledata = j.read(tile=0)
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||||
|
j = glymur.Jp2k(tfile.name, 'wb')
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
glymur.Jp2k(tfile.name, data=tiledata, verbose=True)
|
j.write(tiledata, verbose=True)
|
||||||
actual = fake_out.getvalue().strip()
|
actual = fake_out.getvalue().strip()
|
||||||
expected = '[INFO] tile number 1 / 1'
|
expected = '[INFO] tile number 1 / 1'
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
@unittest.skipIf(glymur.version.openjpeg_version[0] == '0',
|
|
||||||
"Missing openjpeg/openjp2 library.")
|
|
||||||
def test_info_callbacks_on_read(self):
|
def test_info_callbacks_on_read(self):
|
||||||
"""stdio output when info callback handler is enabled"""
|
"""stdio output when info callback handler is enabled"""
|
||||||
|
|
||||||
# Verify that we get the expected stdio output when our internal info
|
# Verify that we get the expected stdio output when our internal info
|
||||||
# callback handler is enabled.
|
# callback handler is enabled.
|
||||||
jp2 = glymur.Jp2k(self.j2kfile)
|
j = glymur.Jp2k(self.j2kfile)
|
||||||
with patch('sys.stdout', new=StringIO()) as fake_out:
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
jp2.verbose = True
|
j.read(rlevel=1, verbose=True, area=(0, 0, 200, 150))
|
||||||
jp2[::2, ::2]
|
|
||||||
actual = fake_out.getvalue().strip()
|
actual = fake_out.getvalue().strip()
|
||||||
|
|
||||||
if glymur.version.openjpeg_version[0] == '2':
|
lines = ['[INFO] Start to read j2k main header (0).',
|
||||||
lines = ['[INFO] Start to read j2k main header (0).',
|
'[INFO] Main header has been correctly decoded.',
|
||||||
'[INFO] Main header has been correctly decoded.',
|
'[INFO] Setting decoding area to 0,0,150,200',
|
||||||
'[INFO] Setting decoding area to 0,0,480,800',
|
'[INFO] Header of tile 0 / 0 has been read.',
|
||||||
'[INFO] Header of tile 0 / 0 has been read.',
|
'[INFO] Tile 1/1 has been decoded.',
|
||||||
'[INFO] Tile 1/1 has been decoded.',
|
'[INFO] Image data has been updated with tile 1.']
|
||||||
'[INFO] Image data has been updated with tile 1.']
|
|
||||||
|
expected = '\n'.join(lines)
|
||||||
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(glymur.lib.openjpeg.OPENJPEG is None,
|
||||||
|
"Missing openjpeg library.")
|
||||||
|
class TestCallbacks15(unittest.TestCase):
|
||||||
|
"""This test suite is for OpenJPEG 1.5.1 properties.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.jp2file = glymur.data.nemo()
|
||||||
|
self.j2kfile = glymur.data.goodstuff()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_info_callbacks_on_read(self):
|
||||||
|
"""Verify stdout when reading.
|
||||||
|
|
||||||
|
Verify that we get the expected stdio output when our internal info
|
||||||
|
callback handler is enabled.
|
||||||
|
"""
|
||||||
|
with patch('glymur.lib.openjp2.OPENJP2', new=None):
|
||||||
|
# Force to use OPENJPEG instead of OPENJP2.
|
||||||
|
j = glymur.Jp2k(self.j2kfile)
|
||||||
|
with patch('sys.stdout', new=StringIO()) as fake_out:
|
||||||
|
j.read(rlevel=1, verbose=True)
|
||||||
|
actual = fake_out.getvalue().strip()
|
||||||
|
|
||||||
expected = '\n'.join(lines)
|
|
||||||
self.assertEqual(actual, expected)
|
|
||||||
else:
|
|
||||||
regex = re.compile(r"""\[INFO\]\stile\s1\sof\s1\s+
|
regex = re.compile(r"""\[INFO\]\stile\s1\sof\s1\s+
|
||||||
\[INFO\]\s-\stiers-1\stook\s
|
\[INFO\]\s-\stiers-1\stook\s
|
||||||
[0-9]+\.[0-9]+\ss\s+
|
[0-9]+\.[0-9]+\ss\s+
|
||||||
|
|
@ -93,7 +111,13 @@ class TestCallbacks(unittest.TestCase):
|
||||||
[0-9]+\.[0-9]+\ss""",
|
[0-9]+\.[0-9]+\ss""",
|
||||||
re.VERBOSE)
|
re.VERBOSE)
|
||||||
|
|
||||||
|
# assertRegex in Python 3.3 (python2.7/pylint issue)
|
||||||
|
# pylint: disable=E1101
|
||||||
if sys.hexversion <= 0x03020000:
|
if sys.hexversion <= 0x03020000:
|
||||||
self.assertRegexpMatches(actual, regex)
|
self.assertRegexpMatches(actual, regex)
|
||||||
else:
|
else:
|
||||||
self.assertRegex(actual, regex)
|
self.assertRegex(actual, regex)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
|
|
|
||||||
123
glymur/test/test_codestream.py
Normal file
123
glymur/test/test_codestream.py
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
"""
|
||||||
|
Test suite for codestream parsing.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# unittest doesn't work well with R0904.
|
||||||
|
# pylint: disable=R0904
|
||||||
|
|
||||||
|
# tempfile.TemporaryDirectory, unittest.assertWarns introduced in 3.2
|
||||||
|
# pylint: disable=E1101
|
||||||
|
|
||||||
|
# unittest2 is python2.6 only (pylint/python-2.7)
|
||||||
|
# pylint: disable=F0401
|
||||||
|
|
||||||
|
import os
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
if sys.hexversion < 0x02070000:
|
||||||
|
import unittest2 as unittest
|
||||||
|
else:
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from glymur import Jp2k
|
||||||
|
import glymur
|
||||||
|
|
||||||
|
try:
|
||||||
|
DATA_ROOT = os.environ['OPJ_DATA_ROOT']
|
||||||
|
except KeyError:
|
||||||
|
DATA_ROOT = None
|
||||||
|
except:
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(DATA_ROOT is None,
|
||||||
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
|
class TestCodestream(unittest.TestCase):
|
||||||
|
"""Test suite for unusual codestream cases."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.jp2file = glymur.data.nemo()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
|
def test_reserved_marker_segment(self):
|
||||||
|
"""Reserved marker segments are ok."""
|
||||||
|
|
||||||
|
# Some marker segments were reserved in FCD15444-1. Since that
|
||||||
|
# standard is old, some of them may have come into use.
|
||||||
|
#
|
||||||
|
# Let's inject a reserved marker segment into a file that
|
||||||
|
# we know something about to make sure we can still parse it.
|
||||||
|
filename = os.path.join(DATA_ROOT, 'input/conformance/p0_01.j2k')
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
|
||||||
|
with open(filename, 'rb') as ifile:
|
||||||
|
# Everything up until the first QCD marker.
|
||||||
|
read_buffer = ifile.read(45)
|
||||||
|
tfile.write(read_buffer)
|
||||||
|
|
||||||
|
# Write the new marker segment, 0xff6f = 65391
|
||||||
|
read_buffer = struct.pack('>HHB', int(65391), int(3), int(0))
|
||||||
|
tfile.write(read_buffer)
|
||||||
|
|
||||||
|
# Get the rest of the input file.
|
||||||
|
read_buffer = ifile.read()
|
||||||
|
tfile.write(read_buffer)
|
||||||
|
tfile.flush()
|
||||||
|
|
||||||
|
codestream = Jp2k(tfile.name).get_codestream()
|
||||||
|
|
||||||
|
self.assertEqual(codestream.segment[2].marker_id, '0xff6f')
|
||||||
|
self.assertEqual(codestream.segment[2].length, 3)
|
||||||
|
self.assertEqual(codestream.segment[2].data, b'\x00')
|
||||||
|
|
||||||
|
@unittest.skipIf(sys.hexversion < 0x03020000,
|
||||||
|
"Uses features introduced in 3.2.")
|
||||||
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
|
def test_unknown_marker_segment(self):
|
||||||
|
"""Should warn for an unknown marker."""
|
||||||
|
# Let's inject a marker segment whose marker does not appear to
|
||||||
|
# be valid. We still parse the file, but warn about the offending
|
||||||
|
# marker.
|
||||||
|
filename = os.path.join(DATA_ROOT, 'input/conformance/p0_01.j2k')
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
|
||||||
|
with open(filename, 'rb') as ifile:
|
||||||
|
# Everything up until the first QCD marker.
|
||||||
|
read_buffer = ifile.read(45)
|
||||||
|
tfile.write(read_buffer)
|
||||||
|
|
||||||
|
# Write the new marker segment, 0xff79 = 65401
|
||||||
|
read_buffer = struct.pack('>HHB', int(65401), int(3), int(0))
|
||||||
|
tfile.write(read_buffer)
|
||||||
|
|
||||||
|
# Get the rest of the input file.
|
||||||
|
read_buffer = ifile.read()
|
||||||
|
tfile.write(read_buffer)
|
||||||
|
tfile.flush()
|
||||||
|
|
||||||
|
with warnings.catch_warnings(record=True) as w:
|
||||||
|
warnings.simplefilter("always")
|
||||||
|
codestream = Jp2k(tfile.name).get_codestream()
|
||||||
|
self.assertEqual(len(w), 1)
|
||||||
|
|
||||||
|
self.assertEqual(codestream.segment[2].marker_id, '0xff79')
|
||||||
|
self.assertEqual(codestream.segment[2].length, 3)
|
||||||
|
self.assertEqual(codestream.segment[2].data, b'\x00')
|
||||||
|
|
||||||
|
def test_psot_is_zero(self):
|
||||||
|
"""Psot=0 in SOT is perfectly legal. Issue #78."""
|
||||||
|
filename = os.path.join(DATA_ROOT,
|
||||||
|
'input/nonregression/123.j2c')
|
||||||
|
j = Jp2k(filename)
|
||||||
|
codestream = j.get_codestream(header_only=False)
|
||||||
|
|
||||||
|
# The codestream is valid, so we should be able to get the entire
|
||||||
|
# codestream, so the last one is EOC.
|
||||||
|
self.assertEqual(codestream.segment[-1].marker_id, 'EOC')
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
|
|
@ -1,13 +1,26 @@
|
||||||
"""These tests are for edge cases where OPENJPEG does not exist, but
|
"""These tests are for edge cases where OPENJPEG does not exist, but
|
||||||
OPENJP2 may be present in some form or other.
|
OPENJP2 may be present in some form or other.
|
||||||
"""
|
"""
|
||||||
import contextlib
|
# unittest doesn't work well with R0904.
|
||||||
import ctypes
|
# pylint: disable=R0904
|
||||||
|
|
||||||
|
# tempfile.TemporaryDirectory, unittest.assertWarns introduced in 3.2
|
||||||
|
# pylint: disable=E1101
|
||||||
|
|
||||||
|
# unittest.mock only in Python 3.3 (python2.7/pylint import issue)
|
||||||
|
# pylint: disable=E0611,F0401
|
||||||
|
|
||||||
|
|
||||||
import imp
|
import imp
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import warnings
|
||||||
|
|
||||||
|
if sys.hexversion < 0x02070000:
|
||||||
|
import unittest2 as unittest
|
||||||
|
else:
|
||||||
|
import unittest
|
||||||
|
|
||||||
if sys.hexversion <= 0x03030000:
|
if sys.hexversion <= 0x03030000:
|
||||||
from mock import patch
|
from mock import patch
|
||||||
|
|
@ -17,43 +30,6 @@ else:
|
||||||
import glymur
|
import glymur
|
||||||
from glymur import Jp2k
|
from glymur import Jp2k
|
||||||
|
|
||||||
from .fixtures import (WARNING_INFRASTRUCTURE_ISSUE,
|
|
||||||
WARNING_INFRASTRUCTURE_MSG,
|
|
||||||
WINDOWS_TMP_FILE_MSG)
|
|
||||||
|
|
||||||
|
|
||||||
def openjpeg_not_found_by_ctypes():
|
|
||||||
"""
|
|
||||||
Need to know if openjpeg library can be picked right up by ctypes for one
|
|
||||||
of the tests.
|
|
||||||
"""
|
|
||||||
with patch.dict('os.environ',
|
|
||||||
{'DYLD_FALLBACK_LIBRARY_PATH': '/opt/local/lib'}):
|
|
||||||
if ctypes.util.find_library('openjpeg') is None:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def chdir(dirname=None):
|
|
||||||
"""
|
|
||||||
This context manager restores the value of the current working directory
|
|
||||||
(cwd) after the enclosed code block completes or raises an exception. If a
|
|
||||||
directory name is supplied to the context manager then the cwd is changed
|
|
||||||
prior to running the code block.
|
|
||||||
|
|
||||||
Shamelessly lifted from
|
|
||||||
http://www.astropython.org/snippet/2009/10/chdir-context-manager
|
|
||||||
"""
|
|
||||||
curdir = os.getcwd()
|
|
||||||
try:
|
|
||||||
if dirname is not None:
|
|
||||||
os.chdir(dirname)
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
os.chdir(curdir)
|
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(sys.hexversion < 0x03020000,
|
@unittest.skipIf(sys.hexversion < 0x03020000,
|
||||||
"TemporaryDirectory introduced in 3.2.")
|
"TemporaryDirectory introduced in 3.2.")
|
||||||
|
|
@ -89,6 +65,7 @@ class TestSuite(unittest.TestCase):
|
||||||
|
|
||||||
# Need to reliably recover the location of the openjp2 library,
|
# Need to reliably recover the location of the openjp2 library,
|
||||||
# so using '_name' appears to be the only way to do it.
|
# so using '_name' appears to be the only way to do it.
|
||||||
|
# pylint: disable=W0212
|
||||||
libloc = glymur.lib.openjp2.OPENJP2._name
|
libloc = glymur.lib.openjp2.OPENJP2._name
|
||||||
line = 'openjp2: {0}\n'.format(libloc)
|
line = 'openjp2: {0}\n'.format(libloc)
|
||||||
tfile.write(line)
|
tfile.write(line)
|
||||||
|
|
@ -97,8 +74,6 @@ class TestSuite(unittest.TestCase):
|
||||||
imp.reload(glymur.lib.openjp2)
|
imp.reload(glymur.lib.openjp2)
|
||||||
Jp2k(self.jp2file)
|
Jp2k(self.jp2file)
|
||||||
|
|
||||||
@unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG)
|
|
||||||
@unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG)
|
|
||||||
def test_xdg_env_config_file_is_bad(self):
|
def test_xdg_env_config_file_is_bad(self):
|
||||||
"""A non-existant library location should be rejected."""
|
"""A non-existant library location should be rejected."""
|
||||||
with tempfile.TemporaryDirectory() as tdir:
|
with tempfile.TemporaryDirectory() as tdir:
|
||||||
|
|
@ -113,60 +88,55 @@ class TestSuite(unittest.TestCase):
|
||||||
with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}):
|
with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}):
|
||||||
# Misconfigured new configuration file should
|
# Misconfigured new configuration file should
|
||||||
# be rejected.
|
# be rejected.
|
||||||
regex = 'could not be loaded'
|
with warnings.catch_warnings(record=True) as w:
|
||||||
with self.assertWarnsRegex(UserWarning, regex):
|
warnings.simplefilter("always")
|
||||||
imp.reload(glymur.lib.openjp2)
|
imp.reload(glymur.lib.openjp2)
|
||||||
|
self.assertEqual(len(w), 1)
|
||||||
|
|
||||||
@unittest.skipIf(glymur.lib.openjp2.OPENJPEG is None,
|
|
||||||
"Needs openjp2 and openjpeg before this test make sense.")
|
|
||||||
@unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG)
|
|
||||||
def test_library_specified_as_None(self):
|
|
||||||
"""Verify that we can stop library from being loaded by using None."""
|
|
||||||
with tempfile.TemporaryDirectory() as tdir:
|
|
||||||
configdir = os.path.join(tdir, 'glymur')
|
|
||||||
os.mkdir(configdir)
|
|
||||||
fname = os.path.join(configdir, 'glymurrc')
|
|
||||||
with open(fname, 'w') as fptr:
|
|
||||||
# Essentially comment out openjp2 and preferentially load
|
|
||||||
# openjpeg instead.
|
|
||||||
fptr.write('[library]\n')
|
|
||||||
fptr.write('openjp2: None\n')
|
|
||||||
msg = 'openjpeg: {0}\n'
|
|
||||||
msg = msg.format(glymur.lib.openjp2.OPENJPEG._name)
|
|
||||||
fptr.write(msg)
|
|
||||||
fptr.flush()
|
|
||||||
with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}):
|
|
||||||
imp.reload(glymur.lib.openjp2)
|
|
||||||
self.assertIsNone(glymur.lib.openjp2.OPENJP2)
|
|
||||||
self.assertIsNotNone(glymur.lib.openjp2.OPENJPEG)
|
|
||||||
|
|
||||||
@unittest.skipIf(glymur.lib.openjp2.OPENJPEG is None,
|
@unittest.skipIf(glymur.lib.openjp2.OPENJP2 is None and
|
||||||
"Needs openjpeg before this test make sense.")
|
glymur.lib.openjpeg.OPENJPEG is None,
|
||||||
@unittest.skipIf(openjpeg_not_found_by_ctypes(),
|
"Missing openjp2 library.")
|
||||||
"OpenJPEG must be found before this test can work.")
|
class TestConfig(unittest.TestCase):
|
||||||
@unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG)
|
"""Test suite for reading without proper library in place."""
|
||||||
def test_config_dir_but_no_config_file(self):
|
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory() as tdir:
|
def setUp(self):
|
||||||
configdir = os.path.join(tdir, 'glymur')
|
self.jp2file = glymur.data.nemo()
|
||||||
os.mkdir(configdir)
|
self.j2kfile = glymur.data.goodstuff()
|
||||||
with patch.dict('os.environ', {'XDG_CONFIG_HOME': tdir}):
|
|
||||||
# Should still be able to load openjpeg, despite the
|
|
||||||
# configuration file not being there
|
|
||||||
imp.reload(glymur.lib.openjpeg)
|
|
||||||
self.assertIsNotNone(glymur.lib.openjp2.OPENJPEG)
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG)
|
def tearDown(self):
|
||||||
def test_config_file_in_current_directory(self):
|
pass
|
||||||
"""A configuration file in the current directory should be honored."""
|
|
||||||
libloc = glymur.lib.openjp2.OPENJP2._name
|
def test_read_without_library(self):
|
||||||
with tempfile.TemporaryDirectory() as tdir1:
|
"""Don't have either openjp2 or openjpeg libraries? Must error out.
|
||||||
fname = os.path.join(tdir1, 'glymurrc')
|
"""
|
||||||
with open(fname, 'w') as fptr:
|
with patch('glymur.lib.openjp2.OPENJP2', new=None):
|
||||||
fptr.write('[library]\n')
|
with patch('glymur.lib.openjpeg.OPENJPEG', new=None):
|
||||||
fptr.write('openjp2: {0}\n'.format(libloc))
|
with self.assertRaises(glymur.jp2k.LibraryNotFoundError):
|
||||||
fptr.flush()
|
glymur.Jp2k(self.jp2file).read()
|
||||||
with chdir(tdir1):
|
|
||||||
# Should be able to load openjp2 as before.
|
def test_read_bands_without_library(self):
|
||||||
imp.reload(glymur.lib.openjp2)
|
"""Don't have openjp2 library? Must error out.
|
||||||
self.assertEqual(glymur.lib.openjp2.OPENJP2._name, libloc)
|
"""
|
||||||
|
with patch('glymur.lib.openjp2.OPENJP2', new=None):
|
||||||
|
with patch('glymur.lib.openjpeg.OPENJPEG', new=None):
|
||||||
|
with patch('glymur.version.openjpeg_version_tuple',
|
||||||
|
new=(0, 0, 0)):
|
||||||
|
with self.assertRaises(glymur.jp2k.LibraryNotFoundError):
|
||||||
|
glymur.Jp2k(self.jp2file).read_bands()
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||||
|
def test_write_without_library(self):
|
||||||
|
"""Don't have openjpeg libraries? Must error out.
|
||||||
|
"""
|
||||||
|
data = glymur.Jp2k(self.j2kfile).read()
|
||||||
|
with patch('glymur.lib.openjp2.OPENJP2', new=None):
|
||||||
|
with patch('glymur.lib.openjpeg.OPENJPEG', new=None):
|
||||||
|
with self.assertRaises(glymur.jp2k.LibraryNotFoundError):
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||||
|
ofile = Jp2k(tfile.name, 'wb')
|
||||||
|
ofile.write(data)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
|
|
|
||||||
126
glymur/test/test_conformance.py
Normal file
126
glymur/test/test_conformance.py
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
"""
|
||||||
|
These tests deal with JPX/JP2/J2K images in the format-corpus repository.
|
||||||
|
"""
|
||||||
|
# R0904: Not too many methods in unittest.
|
||||||
|
# pylint: disable=R0904
|
||||||
|
|
||||||
|
# E1101: assertWarns introduced in python 3.2
|
||||||
|
# pylint: disable=E1101
|
||||||
|
|
||||||
|
# unittest2 is python2.6 only (pylint/python-2.7)
|
||||||
|
# pylint: disable=F0401
|
||||||
|
|
||||||
|
import os
|
||||||
|
from os.path import join
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
if sys.hexversion < 0x02070000:
|
||||||
|
import unittest2 as unittest
|
||||||
|
else:
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import glymur
|
||||||
|
from glymur import Jp2k
|
||||||
|
|
||||||
|
try:
|
||||||
|
FORMAT_CORPUS_DATA_ROOT = os.environ['FORMAT_CORPUS_DATA_ROOT']
|
||||||
|
except KeyError:
|
||||||
|
FORMAT_CORPUS_DATA_ROOT = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
OPJ_DATA_ROOT = os.environ['OPJ_DATA_ROOT']
|
||||||
|
except KeyError:
|
||||||
|
OPJ_DATA_ROOT = None
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(FORMAT_CORPUS_DATA_ROOT is None,
|
||||||
|
"FORMAT_CORPUS_DATA_ROOT environment variable not set")
|
||||||
|
class TestSuiteFormatCorpus(unittest.TestCase):
|
||||||
|
"""Test suite for files in format corpus repository."""
|
||||||
|
|
||||||
|
@unittest.skipIf(re.match(r"""1\.[0123]""",
|
||||||
|
glymur.version.openjpeg_version) is not None,
|
||||||
|
"Needs 1.3+ to catch this.")
|
||||||
|
def test_balloon_trunc1(self):
|
||||||
|
"""Has one byte shaved off of EOC marker."""
|
||||||
|
jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT,
|
||||||
|
'jp2k-test/byteCorruption/balloon_trunc1.jp2')
|
||||||
|
j2k = Jp2k(jfile)
|
||||||
|
with warnings.catch_warnings(record=True) as w:
|
||||||
|
warnings.simplefilter("always")
|
||||||
|
codestream = j2k.get_codestream(header_only=False)
|
||||||
|
self.assertEqual(len(w), 1)
|
||||||
|
|
||||||
|
# The last segment is truncated, so there should not be an EOC marker.
|
||||||
|
self.assertNotEqual(codestream.segment[-1].marker_id, 'EOC')
|
||||||
|
|
||||||
|
# The codestream is not as long as claimed.
|
||||||
|
with self.assertRaises((OSError, IOError)):
|
||||||
|
j2k.read(rlevel=-1)
|
||||||
|
|
||||||
|
def test_balloon_trunc3(self):
|
||||||
|
"""Most of last tile is missing."""
|
||||||
|
jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT,
|
||||||
|
'jp2k-test/byteCorruption/balloon_trunc3.jp2')
|
||||||
|
j2k = Jp2k(jfile)
|
||||||
|
with warnings.catch_warnings(record=True) as w:
|
||||||
|
warnings.simplefilter("always")
|
||||||
|
codestream = j2k.get_codestream(header_only=False)
|
||||||
|
self.assertEqual(len(w), 1)
|
||||||
|
|
||||||
|
# The last segment is truncated, so there should not be an EOC marker.
|
||||||
|
self.assertNotEqual(codestream.segment[-1].marker_id, 'EOC')
|
||||||
|
|
||||||
|
# Should error out, it does not.
|
||||||
|
#with self.assertRaises(OSError):
|
||||||
|
# j2k.read(rlevel=-1)
|
||||||
|
|
||||||
|
def test_jp2_brand_any_icc_profile(self):
|
||||||
|
"""If 'jp2 ', then the method cannot be any icc profile."""
|
||||||
|
jfile = os.path.join(FORMAT_CORPUS_DATA_ROOT,
|
||||||
|
'jp2k-test', 'icc',
|
||||||
|
'balloon_eciRGBv2_ps_adobeplugin.jpf')
|
||||||
|
with warnings.catch_warnings(record=True) as w:
|
||||||
|
warnings.simplefilter("always")
|
||||||
|
Jp2k(jfile)
|
||||||
|
self.assertEqual(len(w), 1)
|
||||||
|
|
||||||
|
def test_jp2_brand_iccpr_mult_colr(self):
|
||||||
|
"""Has colr box, one that conforms, one that does not."""
|
||||||
|
|
||||||
|
# Wrong 'brand' field; contains two versions of ICC profile: one
|
||||||
|
# embedded using "Any ICC" method; other embedded using "Restricted
|
||||||
|
# ICC" method, with description ("Modified eciRGB v2") and profileClass
|
||||||
|
# ("Input Device") changed relative to original profile.
|
||||||
|
jfile = join(FORMAT_CORPUS_DATA_ROOT, 'jp2k-test', 'icc',
|
||||||
|
'balloon_eciRGBv2_ps_adobeplugin_jp2compatible.jpf')
|
||||||
|
with warnings.catch_warnings(record=True) as w:
|
||||||
|
warnings.simplefilter("always")
|
||||||
|
Jp2k(jfile)
|
||||||
|
self.assertEqual(len(w), 1)
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(OPJ_DATA_ROOT is None,
|
||||||
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
|
class TestSuiteOpj(unittest.TestCase):
|
||||||
|
"""Test suite for files in openjpeg repository."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_jp2_brand_any_icc_profile(self):
|
||||||
|
"""If 'jp2 ', then the method cannot be any icc profile."""
|
||||||
|
filename = os.path.join(OPJ_DATA_ROOT,
|
||||||
|
'input/nonregression/text_GBR.jp2')
|
||||||
|
with warnings.catch_warnings(record=True) as w:
|
||||||
|
warnings.simplefilter("always")
|
||||||
|
Jp2k(filename)
|
||||||
|
self.assertEqual(len(w), 1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
|
|
@ -1,164 +0,0 @@
|
||||||
"""
|
|
||||||
Test suite for warnings issued by glymur.
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import struct
|
|
||||||
import tempfile
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
from glymur import Jp2k
|
|
||||||
import glymur
|
|
||||||
|
|
||||||
from .fixtures import opj_data_file, OPJ_DATA_ROOT
|
|
||||||
from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG
|
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(OPJ_DATA_ROOT is None,
|
|
||||||
"OPJ_DATA_ROOT environment variable not set")
|
|
||||||
@unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG)
|
|
||||||
class TestWarnings(unittest.TestCase):
|
|
||||||
"""Test suite for warnings issued by glymur."""
|
|
||||||
|
|
||||||
def test_invalid_compatibility_list_entry(self):
|
|
||||||
"""should not error out with invalid compatibility list entry"""
|
|
||||||
filename = opj_data_file('input/nonregression/issue397.jp2')
|
|
||||||
with self.assertWarns(UserWarning):
|
|
||||||
Jp2k(filename)
|
|
||||||
self.assertTrue(True)
|
|
||||||
|
|
||||||
def test_exceeded_box_length(self):
|
|
||||||
"""
|
|
||||||
should warn if reading past end of a box
|
|
||||||
|
|
||||||
Verify that a warning is issued if we read past the end of a box
|
|
||||||
This file has a palette (pclr) box whose length is impossibly
|
|
||||||
short.
|
|
||||||
"""
|
|
||||||
infile = os.path.join(OPJ_DATA_ROOT,
|
|
||||||
'input/nonregression/mem-b2ace68c-1381.jp2')
|
|
||||||
regex = re.compile(r'''Encountered\san\sunrecoverable\sValueError\s
|
|
||||||
while\sparsing\sa\sPalette\sbox\sat\sbyte\s
|
|
||||||
offset\s\d+\.\s+The\soriginal\serror\smessage\s
|
|
||||||
was\s"total\ssize\sof\snew\sarray\smust\sbe\s
|
|
||||||
unchanged"''',
|
|
||||||
re.VERBOSE)
|
|
||||||
with self.assertWarnsRegex(UserWarning, regex):
|
|
||||||
Jp2k(infile)
|
|
||||||
|
|
||||||
def test_NR_DEC_issue188_beach_64bitsbox_jp2_41_decode(self):
|
|
||||||
"""
|
|
||||||
Has an 'XML ' box instead of 'xml '. Yes that is pedantic, but it
|
|
||||||
really does deserve a warning.
|
|
||||||
"""
|
|
||||||
relpath = 'input/nonregression/issue188_beach_64bitsbox.jp2'
|
|
||||||
jfile = opj_data_file(relpath)
|
|
||||||
pattern = r"""Unrecognized\sbox\s\(b'XML\s'\)\sencountered."""
|
|
||||||
regex = re.compile(pattern, re.VERBOSE)
|
|
||||||
with self.assertWarnsRegex(UserWarning, regex):
|
|
||||||
Jp2k(jfile)
|
|
||||||
|
|
||||||
def test_NR_gdal_fuzzer_unchecked_numresolutions_dump(self):
|
|
||||||
"""
|
|
||||||
Has an invalid number of resolutions.
|
|
||||||
"""
|
|
||||||
lst = ['input', 'nonregression',
|
|
||||||
'gdal_fuzzer_unchecked_numresolutions.jp2']
|
|
||||||
jfile = opj_data_file('/'.join(lst))
|
|
||||||
regex = re.compile(r"""Invalid\snumber\sof\sresolutions\s
|
|
||||||
\(\d+\)\.""",
|
|
||||||
re.VERBOSE)
|
|
||||||
with self.assertWarnsRegex(UserWarning, regex):
|
|
||||||
Jp2k(jfile).get_codestream()
|
|
||||||
|
|
||||||
@unittest.skipIf(re.match("1.5|2.0.0", glymur.version.openjpeg_version),
|
|
||||||
"Test not passing on 1.5.x, not introduced until 2.x")
|
|
||||||
def test_NR_gdal_fuzzer_check_number_of_tiles(self):
|
|
||||||
"""
|
|
||||||
Has an impossible tiling setup.
|
|
||||||
"""
|
|
||||||
lst = ['input', 'nonregression',
|
|
||||||
'gdal_fuzzer_check_number_of_tiles.jp2']
|
|
||||||
jfile = opj_data_file('/'.join(lst))
|
|
||||||
regex = re.compile(r"""Invalid\snumber\sof\stiles\s
|
|
||||||
\(\d+\)\.""",
|
|
||||||
re.VERBOSE)
|
|
||||||
with self.assertWarnsRegex(UserWarning, regex):
|
|
||||||
Jp2k(jfile).get_codestream()
|
|
||||||
|
|
||||||
def test_NR_gdal_fuzzer_check_comp_dx_dy_jp2_dump(self):
|
|
||||||
"""
|
|
||||||
Invalid subsampling value.
|
|
||||||
"""
|
|
||||||
lst = ['input', 'nonregression', 'gdal_fuzzer_check_comp_dx_dy.jp2']
|
|
||||||
jfile = opj_data_file('/'.join(lst))
|
|
||||||
regex = re.compile(r"""Invalid\ssubsampling\svalue\sfor\scomponent\s
|
|
||||||
\d+:\s+
|
|
||||||
dx=\d+,\s*dy=\d+""",
|
|
||||||
re.VERBOSE)
|
|
||||||
with self.assertWarnsRegex(UserWarning, regex):
|
|
||||||
Jp2k(jfile).get_codestream()
|
|
||||||
|
|
||||||
def test_NR_gdal_fuzzer_assert_in_opj_j2k_read_SQcd_SQcc_patch_jp2(self):
|
|
||||||
lst = ['input', 'nonregression',
|
|
||||||
'gdal_fuzzer_assert_in_opj_j2k_read_SQcd_SQcc.patch.jp2']
|
|
||||||
jfile = opj_data_file('/'.join(lst))
|
|
||||||
regex = re.compile(r"""Invalid\scomponent\snumber\s\(\d+\),\s
|
|
||||||
number\sof\scomponents\sis\sonly\s\d+""",
|
|
||||||
re.VERBOSE)
|
|
||||||
with self.assertWarnsRegex(UserWarning, regex):
|
|
||||||
Jp2k(jfile).get_codestream()
|
|
||||||
|
|
||||||
def test_bad_rsiz(self):
|
|
||||||
"""Should warn if RSIZ is bad. Issue196"""
|
|
||||||
filename = opj_data_file('input/nonregression/edf_c2_1002767.jp2')
|
|
||||||
with self.assertWarnsRegex(UserWarning, 'Invalid profile'):
|
|
||||||
Jp2k(filename).get_codestream()
|
|
||||||
|
|
||||||
def test_bad_wavelet_transform(self):
|
|
||||||
"""Should warn if wavelet transform is bad. Issue195"""
|
|
||||||
filename = opj_data_file('input/nonregression/edf_c2_10025.jp2')
|
|
||||||
with self.assertWarnsRegex(UserWarning, 'Invalid wavelet transform'):
|
|
||||||
Jp2k(filename).get_codestream()
|
|
||||||
|
|
||||||
def test_invalid_progression_order(self):
|
|
||||||
"""Should still be able to parse even if prog order is invalid."""
|
|
||||||
jfile = opj_data_file('input/nonregression/2977.pdf.asan.67.2198.jp2')
|
|
||||||
with self.assertWarnsRegex(UserWarning, 'Invalid progression order'):
|
|
||||||
Jp2k(jfile).get_codestream()
|
|
||||||
|
|
||||||
def test_tile_height_is_zero(self):
|
|
||||||
"""Zero tile height should not cause an exception."""
|
|
||||||
filename = 'input/nonregression/2539.pdf.SIGFPE.706.1712.jp2'
|
|
||||||
filename = opj_data_file(filename)
|
|
||||||
with self.assertWarnsRegex(UserWarning, 'Invalid tile dimensions'):
|
|
||||||
Jp2k(filename).get_codestream()
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
|
||||||
def test_unknown_marker_segment(self):
|
|
||||||
"""Should warn for an unknown marker."""
|
|
||||||
# Let's inject a marker segment whose marker does not appear to
|
|
||||||
# be valid. We still parse the file, but warn about the offending
|
|
||||||
# marker.
|
|
||||||
filename = os.path.join(OPJ_DATA_ROOT, 'input/conformance/p0_01.j2k')
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
|
|
||||||
with open(filename, 'rb') as ifile:
|
|
||||||
# Everything up until the first QCD marker.
|
|
||||||
read_buffer = ifile.read(45)
|
|
||||||
tfile.write(read_buffer)
|
|
||||||
|
|
||||||
# Write the new marker segment, 0xff79 = 65401
|
|
||||||
read_buffer = struct.pack('>HHB', int(65401), int(3), int(0))
|
|
||||||
tfile.write(read_buffer)
|
|
||||||
|
|
||||||
# Get the rest of the input file.
|
|
||||||
read_buffer = ifile.read()
|
|
||||||
tfile.write(read_buffer)
|
|
||||||
tfile.flush()
|
|
||||||
|
|
||||||
with self.assertWarnsRegex(UserWarning, 'Unrecognized marker'):
|
|
||||||
Jp2k(tfile.name).get_codestream()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
unittest.main()
|
|
||||||
|
|
@ -1,17 +1,29 @@
|
||||||
"""
|
"""
|
||||||
ICC profile tests.
|
ICC profile tests.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# unittest doesn't work well with R0904.
|
||||||
|
# pylint: disable=R0904
|
||||||
|
|
||||||
|
# unittest2 is python2.6 only (pylint/python-2.7)
|
||||||
|
# pylint: disable=F0401
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import unittest
|
import os
|
||||||
|
import sys
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
if sys.hexversion < 0x02070000:
|
||||||
|
import unittest2 as unittest
|
||||||
|
else:
|
||||||
|
import unittest
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from glymur import Jp2k
|
from glymur import Jp2k
|
||||||
from .fixtures import OPJ_DATA_ROOT, opj_data_file
|
from .fixtures import OPJ_DATA_ROOT, opj_data_file
|
||||||
from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG
|
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG)
|
|
||||||
@unittest.skipIf(OPJ_DATA_ROOT is None,
|
@unittest.skipIf(OPJ_DATA_ROOT is None,
|
||||||
"OPJ_DATA_ROOT environment variable not set")
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
class TestICC(unittest.TestCase):
|
class TestICC(unittest.TestCase):
|
||||||
|
|
@ -26,9 +38,7 @@ class TestICC(unittest.TestCase):
|
||||||
def test_file5(self):
|
def test_file5(self):
|
||||||
"""basic ICC profile"""
|
"""basic ICC profile"""
|
||||||
filename = opj_data_file('input/conformance/file5.jp2')
|
filename = opj_data_file('input/conformance/file5.jp2')
|
||||||
with self.assertWarns(UserWarning):
|
j = Jp2k(filename)
|
||||||
# The file has a bad compatibility list entry. Not important here.
|
|
||||||
j = Jp2k(filename)
|
|
||||||
profile = j.box[3].box[1].icc_profile
|
profile = j.box[3].box[1].icc_profile
|
||||||
self.assertEqual(profile['Size'], 546)
|
self.assertEqual(profile['Size'], 546)
|
||||||
self.assertEqual(profile['Preferred CMM Type'], 0)
|
self.assertEqual(profile['Preferred CMM Type'], 0)
|
||||||
|
|
@ -60,6 +70,12 @@ class TestICC(unittest.TestCase):
|
||||||
"""invalid ICC header data should cause UserWarning"""
|
"""invalid ICC header data should cause UserWarning"""
|
||||||
jfile = opj_data_file('input/nonregression/orb-blue10-lin-jp2.jp2')
|
jfile = opj_data_file('input/nonregression/orb-blue10-lin-jp2.jp2')
|
||||||
|
|
||||||
regex = 'ICC profile header is corrupt'
|
# assertWarns in Python 3.3 (python2.7/pylint issue)
|
||||||
with self.assertWarnsRegex(UserWarning, regex):
|
# pylint: disable=E1101
|
||||||
|
with warnings.catch_warnings(record=True) as w:
|
||||||
|
warnings.simplefilter("always")
|
||||||
Jp2k(jfile)
|
Jp2k(jfile)
|
||||||
|
self.assertEqual(len(w), 1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -3,614 +3,61 @@
|
||||||
Test suite specifically targeting JPX box layout.
|
Test suite specifically targeting JPX box layout.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import ctypes
|
|
||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import warnings
|
||||||
|
import xml.etree.cElementTree as ET
|
||||||
|
|
||||||
import lxml.etree as ET
|
if sys.hexversion < 0x02070000:
|
||||||
|
import unittest2 as unittest
|
||||||
|
else:
|
||||||
|
import unittest
|
||||||
|
|
||||||
import glymur
|
import glymur
|
||||||
from glymur import Jp2k
|
from glymur import Jp2k
|
||||||
from glymur.jp2box import DataEntryURLBox, FileTypeBox, JPEG2000SignatureBox
|
from glymur.jp2box import ReaderRequirementsBox
|
||||||
from glymur.jp2box import DataReferenceBox, FragmentListBox, FragmentTableBox
|
|
||||||
from glymur.jp2box import ColourSpecificationBox
|
|
||||||
|
|
||||||
from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG
|
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(sys.hexversion < 0x03000000, "Warning assert on 2.x.")
|
||||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
class TestJPXWrap(unittest.TestCase):
|
class TestReaderRequirements(unittest.TestCase):
|
||||||
"""Test suite for wrapping JPX files."""
|
"""Test suite for XML boxes."""
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.jpxfile = glymur.data.jpxfile()
|
|
||||||
self.jp2file = glymur.data.nemo()
|
|
||||||
self.j2kfile = glymur.data.goodstuff()
|
|
||||||
|
|
||||||
raw_xml = b"""<?xml version="1.0"?>
|
|
||||||
<data>
|
|
||||||
<country name="Liechtenstein">
|
|
||||||
<rank>1</rank>
|
|
||||||
<year>2008</year>
|
|
||||||
<gdppc>141100</gdppc>
|
|
||||||
<neighbor name="Austria" direction="E"/>
|
|
||||||
<neighbor name="Switzerland" direction="W"/>
|
|
||||||
</country>
|
|
||||||
</data>"""
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=".xml", delete=False) as tfile:
|
|
||||||
tfile.write(raw_xml)
|
|
||||||
tfile.flush()
|
|
||||||
self.xmlfile = tfile.name
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
os.unlink(self.xmlfile)
|
|
||||||
|
|
||||||
def test_jpx_ftbl_no_codestream(self):
|
|
||||||
"""Can have a jpx with no codestream."""
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile1:
|
|
||||||
with open(self.jp2file, 'rb') as fptr:
|
|
||||||
tfile1.write(fptr.read())
|
|
||||||
tfile1.flush()
|
|
||||||
jp2_1 = Jp2k(tfile1.name)
|
|
||||||
jp2h = jp2_1.box[2]
|
|
||||||
|
|
||||||
jp2c = [box for box in jp2_1.box if box.box_id == 'jp2c'][0]
|
|
||||||
|
|
||||||
# coff and clen will be the offset and length input arguments
|
|
||||||
# to the fragment list box. dr_idx is the data reference index.
|
|
||||||
coff = []
|
|
||||||
clen = []
|
|
||||||
dr_idx = []
|
|
||||||
|
|
||||||
coff.append(jp2c.main_header_offset)
|
|
||||||
clen.append(jp2c.length - (coff[0] - jp2c.offset))
|
|
||||||
dr_idx.append(1)
|
|
||||||
|
|
||||||
# Make the url box for this codestream.
|
|
||||||
url1 = DataEntryURLBox(0, [0, 0, 0], 'file://' + tfile1.name)
|
|
||||||
url1_name_len = len(url1.url) + 1
|
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile2:
|
|
||||||
|
|
||||||
j2k = Jp2k(self.j2kfile)
|
|
||||||
jp2_2 = j2k.wrap(tfile2.name)
|
|
||||||
|
|
||||||
jp2c = [box for box in jp2_2.box if box.box_id == 'jp2c'][0]
|
|
||||||
coff.append(jp2c.main_header_offset)
|
|
||||||
clen.append(jp2c.length - (coff[0] - jp2c.offset))
|
|
||||||
dr_idx.append(2)
|
|
||||||
|
|
||||||
# Make the url box for this codestream.
|
|
||||||
url2 = DataEntryURLBox(0, [0, 0, 0], 'file://' + tfile2.name)
|
|
||||||
|
|
||||||
boxes = [JPEG2000SignatureBox(),
|
|
||||||
FileTypeBox(brand='jpx ',
|
|
||||||
compatibility_list=['jpx ',
|
|
||||||
'jp2 ', 'jpxb']),
|
|
||||||
jp2h]
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jpx') as tjpx:
|
|
||||||
for box in boxes:
|
|
||||||
box.write(tjpx)
|
|
||||||
|
|
||||||
flst = FragmentListBox(coff, clen, dr_idx)
|
|
||||||
ftbl = FragmentTableBox([flst])
|
|
||||||
ftbl.write(tjpx)
|
|
||||||
|
|
||||||
boxes = [url1, url2]
|
|
||||||
dtbl = DataReferenceBox(data_entry_url_boxes=boxes)
|
|
||||||
dtbl.write(tjpx)
|
|
||||||
tjpx.flush()
|
|
||||||
|
|
||||||
jpx_no_jp2c = Jp2k(tjpx.name)
|
|
||||||
jpx_boxes = [box.box_id for box in jpx_no_jp2c.box]
|
|
||||||
self.assertEqual(jpx_boxes, ['jP ', 'ftyp', 'jp2h',
|
|
||||||
'ftbl', 'dtbl'])
|
|
||||||
self.assertEqual(jpx_no_jp2c.box[4].DR[0].offset, 141)
|
|
||||||
|
|
||||||
offset = 141 + 8 + 4 + url1_name_len
|
|
||||||
self.assertEqual(jpx_no_jp2c.box[4].DR[1].offset, offset)
|
|
||||||
|
|
||||||
def test_jp2_with_jpx_box(self):
|
|
||||||
"""If the brand is jp2, then no jpx boxes are allowed."""
|
|
||||||
jp2 = Jp2k(self.jp2file)
|
|
||||||
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
|
|
||||||
boxes = jp2.box
|
|
||||||
|
|
||||||
boxes.append(glymur.jp2box.AssociationBox())
|
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
jp2.wrap(tfile.name, boxes=boxes)
|
|
||||||
|
|
||||||
def test_jpch_jplh(self):
|
|
||||||
"""Write a codestream header, compositing layer header box."""
|
|
||||||
jp2 = Jp2k(self.jp2file)
|
|
||||||
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
|
|
||||||
|
|
||||||
# The ftyp box must be modified to jpx.
|
|
||||||
boxes[1].brand = 'jpx '
|
|
||||||
boxes[1].compatibility_list = ['jp2 ', 'jpxb']
|
|
||||||
|
|
||||||
jpch = glymur.jp2box.CodestreamHeaderBox()
|
|
||||||
boxes.append(jpch)
|
|
||||||
jplh = glymur.jp2box.CompositingLayerHeaderBox()
|
|
||||||
boxes.append(jplh)
|
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
|
|
||||||
jpx = jp2.wrap(tfile.name, boxes=boxes)
|
|
||||||
|
|
||||||
self.assertEqual(jpx.box[-2].box_id, 'jpch')
|
|
||||||
self.assertEqual(jpx.box[-1].box_id, 'jplh')
|
|
||||||
|
|
||||||
def test_cgrp(self):
|
|
||||||
"""Write a color group box."""
|
|
||||||
jp2 = Jp2k(self.jp2file)
|
|
||||||
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
|
|
||||||
|
|
||||||
# The ftyp box must be modified to jpx.
|
|
||||||
boxes[1].brand = 'jpx '
|
|
||||||
boxes[1].compatibility_list = ['jp2 ', 'jpxb']
|
|
||||||
|
|
||||||
colr_rgb = ColourSpecificationBox(colorspace=glymur.core.SRGB)
|
|
||||||
colr_gr = ColourSpecificationBox(colorspace=glymur.core.GREYSCALE)
|
|
||||||
box = [colr_rgb, colr_gr]
|
|
||||||
|
|
||||||
cgrp = glymur.jp2box.ColourGroupBox(box=box)
|
|
||||||
boxes.append(cgrp)
|
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
|
|
||||||
jpx = jp2.wrap(tfile.name, boxes=boxes)
|
|
||||||
|
|
||||||
self.assertEqual(jpx.box[-1].box_id, 'cgrp')
|
|
||||||
self.assertEqual(jpx.box[-1].box[0].box_id, 'colr')
|
|
||||||
self.assertEqual(jpx.box[-1].box[1].box_id, 'colr')
|
|
||||||
|
|
||||||
def test_label_neg(self):
|
|
||||||
"""Can't write a label box embedded in any old box."""
|
|
||||||
jp2 = Jp2k(self.jp2file)
|
|
||||||
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
|
|
||||||
|
|
||||||
# The ftyp box must be modified to jpx.
|
|
||||||
boxes[1].brand = 'jpx '
|
|
||||||
boxes[1].compatibility_list = ['jp2 ', 'jpxb']
|
|
||||||
|
|
||||||
lblb = glymur.jp2box.LabelBox("Just a test")
|
|
||||||
box = [lblb]
|
|
||||||
|
|
||||||
cgrp = glymur.jp2box.ColourGroupBox(box=box)
|
|
||||||
boxes.append(cgrp)
|
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
jp2.wrap(tfile.name, boxes=boxes)
|
|
||||||
|
|
||||||
def test_cgrp_neg(self):
|
|
||||||
"""Can't write a cgrp with anything but colr sub boxes"""
|
|
||||||
jp2 = Jp2k(self.jp2file)
|
|
||||||
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
|
|
||||||
|
|
||||||
# The ftyp box must be modified to jpx.
|
|
||||||
boxes[1].brand = 'jpx '
|
|
||||||
boxes[1].compatibility_list = ['jp2 ', 'jpxb']
|
|
||||||
|
|
||||||
the_xml = ET.fromstring('<?xml version="1.0"?><data>0</data>')
|
|
||||||
xmlb = glymur.jp2box.XMLBox(xml=the_xml)
|
|
||||||
box = [xmlb]
|
|
||||||
|
|
||||||
cgrp = glymur.jp2box.ColourGroupBox(box=box)
|
|
||||||
boxes.append(cgrp)
|
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
jp2.wrap(tfile.name, boxes=boxes)
|
|
||||||
|
|
||||||
def test_ftbl(self):
|
|
||||||
"""Write a fragment table box."""
|
|
||||||
# Add a negative test where offset < 0
|
|
||||||
# Add a negative test where length < 0
|
|
||||||
# Add a negative test where ref > 0 but no data reference box.
|
|
||||||
# Add a negative test where more than one flst
|
|
||||||
# Add negative test where ftbl contained in a superbox.
|
|
||||||
jp2 = Jp2k(self.jp2file)
|
|
||||||
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
|
|
||||||
|
|
||||||
# The ftyp box must be modified to jpx.
|
|
||||||
boxes[1].brand = 'jpx '
|
|
||||||
boxes[1].compatibility_list = ['jp2 ', 'jpxb']
|
|
||||||
|
|
||||||
offset = [89]
|
|
||||||
length = [1132288]
|
|
||||||
reference = [0]
|
|
||||||
flst = glymur.jp2box.FragmentListBox(offset, length, reference)
|
|
||||||
ftbl = glymur.jp2box.FragmentTableBox(box=[flst])
|
|
||||||
boxes.append(ftbl)
|
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
|
|
||||||
jpx = jp2.wrap(tfile.name, boxes=boxes)
|
|
||||||
|
|
||||||
self.assertEqual(jpx.box[1].compatibility_list, ['jp2 ', 'jpxb'])
|
|
||||||
self.assertEqual(jpx.box[-1].box_id, 'ftbl')
|
|
||||||
self.assertEqual(jpx.box[-1].box[0].box_id, 'flst')
|
|
||||||
|
|
||||||
def test_jpxb_compatibility(self):
|
|
||||||
"""Wrap JP2 to JPX, state jpxb compatibility"""
|
|
||||||
jp2 = Jp2k(self.jp2file)
|
|
||||||
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
|
|
||||||
|
|
||||||
# The ftyp box must be modified to jpx with jp2 compatibility.
|
|
||||||
boxes[1].brand = 'jpx '
|
|
||||||
boxes[1].compatibility_list = ['jp2 ', 'jpxb']
|
|
||||||
|
|
||||||
numbers = (0, 1)
|
|
||||||
nlst = glymur.jp2box.NumberListBox(numbers)
|
|
||||||
the_xml = ET.fromstring('<?xml version="1.0"?><data>0</data>')
|
|
||||||
xmlb = glymur.jp2box.XMLBox(xml=the_xml)
|
|
||||||
asoc = glymur.jp2box.AssociationBox([nlst, xmlb])
|
|
||||||
boxes.append(asoc)
|
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
|
|
||||||
jpx = jp2.wrap(tfile.name, boxes=boxes)
|
|
||||||
|
|
||||||
self.assertEqual(jpx.box[1].compatibility_list, ['jp2 ', 'jpxb'])
|
|
||||||
self.assertEqual(jpx.box[-1].box_id, 'asoc')
|
|
||||||
self.assertEqual(jpx.box[-1].box[0].box_id, 'nlst')
|
|
||||||
self.assertEqual(jpx.box[-1].box[1].box_id, 'xml ')
|
|
||||||
self.assertEqual(jpx.box[-1].box[0].associations, numbers)
|
|
||||||
self.assertEqual(ET.tostring(jpx.box[-1].box[1].xml.getroot()),
|
|
||||||
b'<data>0</data>')
|
|
||||||
|
|
||||||
def test_association_label_box(self):
|
|
||||||
"""Wrap JP2 to JPX with asoc, label, and nlst boxes"""
|
|
||||||
jp2 = Jp2k(self.jp2file)
|
|
||||||
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
|
|
||||||
|
|
||||||
# The ftyp box must be modified to jpx with jp2 compatibility.
|
|
||||||
boxes[1].brand = 'jpx '
|
|
||||||
boxes[1].compatibility_list = ['jp2 ', 'jpx ']
|
|
||||||
|
|
||||||
label = 'this is a test'
|
|
||||||
lblb = glymur.jp2box.LabelBox(label)
|
|
||||||
numbers = (0, 1)
|
|
||||||
nlst = glymur.jp2box.NumberListBox(numbers)
|
|
||||||
the_xml = ET.fromstring('<?xml version="1.0"?><data>0</data>')
|
|
||||||
xmlb = glymur.jp2box.XMLBox(xml=the_xml)
|
|
||||||
asoc = glymur.jp2box.AssociationBox([nlst, xmlb, lblb])
|
|
||||||
boxes.append(asoc)
|
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
|
|
||||||
jpx = jp2.wrap(tfile.name, boxes=boxes)
|
|
||||||
|
|
||||||
self.assertEqual(jpx.box[1].compatibility_list, ['jp2 ', 'jpx '])
|
|
||||||
self.assertEqual(jpx.box[-1].box_id, 'asoc')
|
|
||||||
self.assertEqual(jpx.box[-1].box[0].box_id, 'nlst')
|
|
||||||
self.assertEqual(jpx.box[-1].box[0].associations, numbers)
|
|
||||||
self.assertEqual(jpx.box[-1].box[1].box_id, 'xml ')
|
|
||||||
self.assertEqual(ET.tostring(jpx.box[-1].box[1].xml.getroot()),
|
|
||||||
b'<data>0</data>')
|
|
||||||
self.assertEqual(jpx.box[-1].box[2].box_id, 'lbl ')
|
|
||||||
self.assertEqual(jpx.box[-1].box[2].label, label)
|
|
||||||
|
|
||||||
def test_empty_data_reference(self):
|
|
||||||
"""Empty data reference boxes can be created, but not written."""
|
|
||||||
jp2 = Jp2k(self.jp2file)
|
|
||||||
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
|
|
||||||
|
|
||||||
boxes[1].brand = 'jpx '
|
|
||||||
|
|
||||||
dref = glymur.jp2box.DataReferenceBox()
|
|
||||||
boxes.append(dref)
|
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
jp2.wrap(tfile.name, boxes=boxes)
|
|
||||||
|
|
||||||
@unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG)
|
|
||||||
def test_deurl_child_of_dtbl(self):
|
|
||||||
"""Data reference boxes can only contain data entry url boxes."""
|
|
||||||
jp2 = Jp2k(self.jp2file)
|
|
||||||
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
|
|
||||||
|
|
||||||
ftyp = glymur.jp2box.FileTypeBox()
|
|
||||||
with self.assertWarns(UserWarning):
|
|
||||||
dref = glymur.jp2box.DataReferenceBox([ftyp])
|
|
||||||
|
|
||||||
# Try to get around it by appending the ftyp box after creation.
|
|
||||||
dref = glymur.jp2box.DataReferenceBox()
|
|
||||||
dref.DR.append(ftyp)
|
|
||||||
|
|
||||||
boxes.append(dref)
|
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
jp2.wrap(tfile.name, boxes=boxes)
|
|
||||||
|
|
||||||
def test_only_one_data_reference(self):
|
|
||||||
"""Data reference boxes cannot be inside a superbox ."""
|
|
||||||
jp2 = Jp2k(self.jp2file)
|
|
||||||
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
|
|
||||||
|
|
||||||
# Have to make the ftyp brand jpx.
|
|
||||||
boxes[1].brand = 'jpx '
|
|
||||||
|
|
||||||
flag = 0
|
|
||||||
version = (0, 0, 0)
|
|
||||||
url = 'file:////usr/local/bin'
|
|
||||||
deurl = glymur.jp2box.DataEntryURLBox(flag, version, url)
|
|
||||||
dref = glymur.jp2box.DataReferenceBox([deurl])
|
|
||||||
boxes.append(dref)
|
|
||||||
boxes.append(dref)
|
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
jp2.wrap(tfile.name, boxes=boxes)
|
|
||||||
|
|
||||||
def test_lbl_at_top_level(self):
|
|
||||||
"""Label boxes can only be inside a asoc box ."""
|
|
||||||
jp2 = Jp2k(self.jp2file)
|
|
||||||
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
|
|
||||||
|
|
||||||
# Have to make the ftyp brand jpx.
|
|
||||||
boxes[1].brand = 'jpx '
|
|
||||||
|
|
||||||
lblb = glymur.jp2box.LabelBox('hi there')
|
|
||||||
|
|
||||||
# Put it inside the jp2 header box.
|
|
||||||
boxes[2].box.append(lblb)
|
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
jp2.wrap(tfile.name, boxes=boxes)
|
|
||||||
|
|
||||||
def test_data_reference_in_subbox(self):
|
|
||||||
"""Data reference boxes cannot be inside a superbox ."""
|
|
||||||
jp2 = Jp2k(self.jp2file)
|
|
||||||
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
|
|
||||||
|
|
||||||
# Have to make the ftyp brand jpx.
|
|
||||||
boxes[1].brand = 'jpx '
|
|
||||||
|
|
||||||
flag = 0
|
|
||||||
version = (0, 0, 0)
|
|
||||||
url = 'file:////usr/local/bin'
|
|
||||||
deurl = glymur.jp2box.DataEntryURLBox(flag, version, url)
|
|
||||||
dref = glymur.jp2box.DataReferenceBox([deurl])
|
|
||||||
|
|
||||||
# Put it inside the jp2 header box.
|
|
||||||
boxes[2].box.append(dref)
|
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
jp2.wrap(tfile.name, boxes=boxes)
|
|
||||||
|
|
||||||
def test_jp2_to_jpx_sans_jp2_compatibility(self):
|
|
||||||
"""jp2 wrapped to jpx not including jp2 compatibility is wrong."""
|
|
||||||
jp2 = Jp2k(self.jp2file)
|
|
||||||
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
|
|
||||||
|
|
||||||
# Have to make the ftyp brand jpx.
|
|
||||||
boxes[1].brand = 'jpx '
|
|
||||||
boxes[1].compatibility_list.append('jp2 ')
|
|
||||||
|
|
||||||
numbers = [0, 1]
|
|
||||||
nlst = glymur.jp2box.NumberListBox(numbers)
|
|
||||||
the_xml = ET.fromstring('<?xml version="1.0"?><data>0</data>')
|
|
||||||
xmlb = glymur.jp2box.XMLBox(xml=the_xml)
|
|
||||||
asoc = glymur.jp2box.AssociationBox([nlst, xmlb])
|
|
||||||
boxes.append(asoc)
|
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
|
|
||||||
with self.assertRaises(RuntimeError):
|
|
||||||
jp2.wrap(tfile.name, boxes=boxes)
|
|
||||||
|
|
||||||
def test_jp2_to_jpx_sans_jpx_brand(self):
|
|
||||||
"""Verify error when jp2 wrapped to jpx does not include jpx brand."""
|
|
||||||
jp2 = Jp2k(self.jp2file)
|
|
||||||
boxes = [jp2.box[idx] for idx in [0, 1, 2, 4]]
|
|
||||||
boxes[1].brand = 'jpx '
|
|
||||||
numbers = [0, 1]
|
|
||||||
nlst = glymur.jp2box.NumberListBox(numbers)
|
|
||||||
the_xml = ET.fromstring('<?xml version="1.0"?><data>0</data>')
|
|
||||||
xmlb = glymur.jp2box.XMLBox(xml=the_xml)
|
|
||||||
asoc = glymur.jp2box.AssociationBox([nlst, xmlb])
|
|
||||||
boxes.append(asoc)
|
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
|
|
||||||
with self.assertRaises(RuntimeError):
|
|
||||||
jp2.wrap(tfile.name, boxes=boxes)
|
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
|
||||||
class TestJPX(unittest.TestCase):
|
|
||||||
"""Test suite for other JPX boxes."""
|
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.jp2file = glymur.data.nemo()
|
self.jp2file = glymur.data.nemo()
|
||||||
self.jpxfile = glymur.data.jpxfile()
|
pass
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG)
|
def test_mask_length_is_3(self):
|
||||||
def test_flst_lens_not_the_same(self):
|
"""The standard says that the mask length should be 1, 2, 4, or 8."""
|
||||||
"""A fragment list box items must be the same length."""
|
# Rewrite nemo to include this kind of rreq box.
|
||||||
offset = [89]
|
with tempfile.NamedTemporaryFile(suffix=".jpx") as tfile:
|
||||||
length = [1132288]
|
with open(self.jp2file, 'rb') as nemof:
|
||||||
reference = [0, 0]
|
# Read the jP and ftyp boxes as-is.
|
||||||
with self.assertWarns(UserWarning):
|
write_buffer = nemof.read(32)
|
||||||
flst = glymur.jp2box.FragmentListBox(offset, length, reference)
|
tfile.write(write_buffer)
|
||||||
with self.assertRaises(IOError):
|
|
||||||
with tempfile.TemporaryFile() as tfile:
|
|
||||||
flst.write(tfile)
|
|
||||||
|
|
||||||
@unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG)
|
# Fake a rreq box with ML = 3.
|
||||||
def test_flst_offsets_not_positive(self):
|
write_buffer = struct.pack('>I4sB', 74, b'rreq', 3)
|
||||||
"""A fragment list box offsets must be positive."""
|
tfile.write(write_buffer)
|
||||||
offset = [0]
|
|
||||||
length = [1132288]
|
|
||||||
reference = [0]
|
|
||||||
with self.assertWarns(UserWarning):
|
|
||||||
flst = glymur.jp2box.FragmentListBox(offset, length, reference)
|
|
||||||
with self.assertRaises((IOError, OSError)):
|
|
||||||
with tempfile.TemporaryFile() as tfile:
|
|
||||||
flst.write(tfile)
|
|
||||||
|
|
||||||
@unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG)
|
# pad the rest with zeros
|
||||||
def test_flst_lengths_not_positive(self):
|
write_buffer = struct.pack('>65s', b'\x00' * 65)
|
||||||
"""A fragment list box lengths must be positive."""
|
tfile.write(write_buffer)
|
||||||
offset = [89]
|
|
||||||
length = [0]
|
|
||||||
reference = [0]
|
|
||||||
with self.assertWarns(UserWarning):
|
|
||||||
flst = glymur.jp2box.FragmentListBox(offset, length, reference)
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
with tempfile.TemporaryFile() as tfile:
|
|
||||||
flst.write(tfile)
|
|
||||||
|
|
||||||
def test_ftbl_boxes_empty(self):
|
# Write the rest of nemo.
|
||||||
"""A fragment table box must have at least one child box."""
|
tfile.write(nemof.read())
|
||||||
ftbl = glymur.jp2box.FragmentTableBox()
|
tfile.flush()
|
||||||
with self.assertRaises(IOError):
|
|
||||||
with tempfile.TemporaryFile() as tfile:
|
|
||||||
ftbl.write(tfile)
|
|
||||||
|
|
||||||
def test_ftbl_child_not_flst(self):
|
with warnings.catch_warnings(record=True) as w:
|
||||||
"""A fragment table box can only contain a fragment list."""
|
warnings.simplefilter("always")
|
||||||
free = glymur.jp2box.FreeBox()
|
j = Jp2k(tfile.name)
|
||||||
ftbl = glymur.jp2box.FragmentTableBox(box=[free])
|
self.assertEqual(len(w), 1)
|
||||||
with self.assertRaises(IOError):
|
self.assertEqual(j.box[2].box_id, 'rreq')
|
||||||
with tempfile.TemporaryFile() as tfile:
|
self.assertEqual(type(j.box[2]),
|
||||||
ftbl.write(tfile)
|
glymur.jp2box.ReaderRequirementsBox)
|
||||||
|
|
||||||
def test_data_reference_requires_dtbl(self):
|
|
||||||
"""The existance of data reference box requires a ftbl box as well."""
|
|
||||||
flag = 0
|
|
||||||
version = (0, 0, 0)
|
|
||||||
url1 = 'file:////usr/local/bin'
|
|
||||||
url2 = 'http://glymur.readthedocs.org'
|
|
||||||
jpx1 = glymur.Jp2k(self.jp2file)
|
|
||||||
boxes = jpx1.box
|
|
||||||
boxes[1].brand = 'jpx '
|
|
||||||
|
|
||||||
deurl1 = glymur.jp2box.DataEntryURLBox(flag, version, url1)
|
|
||||||
deurl2 = glymur.jp2box.DataEntryURLBox(flag, version, url2)
|
|
||||||
dref = glymur.jp2box.DataReferenceBox([deurl1, deurl2])
|
|
||||||
boxes.append(dref)
|
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jpx') as tfile:
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
jpx1.wrap(tfile.name, boxes=boxes)
|
|
||||||
|
|
||||||
def test_dtbl(self):
|
|
||||||
"""Verify that we can interpret Data Reference boxes."""
|
|
||||||
# Copy the existing JPX file, add a data reference box onto the end.
|
|
||||||
flag = 0
|
|
||||||
version = (0, 0, 0)
|
|
||||||
url1 = 'file:////usr/local/bin'
|
|
||||||
url2 = 'http://glymur.readthedocs.org' + chr(0) * 3
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jpx') as tfile:
|
|
||||||
with open(self.jpxfile, 'rb') as ifile:
|
|
||||||
tfile.write(ifile.read())
|
|
||||||
|
|
||||||
deurl1 = glymur.jp2box.DataEntryURLBox(flag, version, url1)
|
|
||||||
deurl2 = glymur.jp2box.DataEntryURLBox(flag, version, url2)
|
|
||||||
dref = glymur.jp2box.DataReferenceBox([deurl1, deurl2])
|
|
||||||
dref.write(tfile)
|
|
||||||
|
|
||||||
tfile.flush()
|
|
||||||
|
|
||||||
jpx = Jp2k(tfile.name)
|
|
||||||
|
|
||||||
self.assertEqual(jpx.box[-1].box_id, 'dtbl')
|
|
||||||
self.assertEqual(len(jpx.box[-1].DR), 2)
|
|
||||||
self.assertEqual(jpx.box[-1].DR[0].url, url1)
|
|
||||||
self.assertEqual(jpx.box[-1].DR[1].url, url2.rstrip('\0'))
|
|
||||||
|
|
||||||
def test_ftbl(self):
|
|
||||||
"""Verify that we can interpret Fragment Table boxes."""
|
|
||||||
# Copy the existing JPX file, add a fragment table box onto the end.
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jpx') as tfile:
|
|
||||||
with open(self.jpxfile, 'rb') as ifile:
|
|
||||||
tfile.write(ifile.read())
|
|
||||||
write_buffer = struct.pack('>I4s', 32, b'ftbl')
|
|
||||||
tfile.write(write_buffer)
|
|
||||||
|
|
||||||
# Just one fragment list box
|
|
||||||
write_buffer = struct.pack('>I4s', 24, b'flst')
|
|
||||||
tfile.write(write_buffer)
|
|
||||||
|
|
||||||
# Simple offset, length, reference
|
|
||||||
write_buffer = struct.pack('>HQIH', 1, 4237, 170246, 3)
|
|
||||||
tfile.write(write_buffer)
|
|
||||||
|
|
||||||
tfile.flush()
|
|
||||||
|
|
||||||
jpx = Jp2k(tfile.name)
|
|
||||||
|
|
||||||
self.assertEqual(jpx.box[-1].box_id, 'ftbl')
|
|
||||||
self.assertEqual(jpx.box[-1].box[0].box_id, 'flst')
|
|
||||||
self.assertEqual(jpx.box[-1].box[0].fragment_offset, (4237,))
|
|
||||||
self.assertEqual(jpx.box[-1].box[0].fragment_length, (170246,))
|
|
||||||
self.assertEqual(jpx.box[-1].box[0].data_reference, (3,))
|
|
||||||
|
|
||||||
def test_rreq3(self):
|
|
||||||
"""Verify that we can read a rreq box with mask length 3 bytes"""
|
|
||||||
rreq_buffer = ctypes.create_string_buffer(74)
|
|
||||||
struct.pack_into('>I4s', rreq_buffer, 0, 74, b'rreq')
|
|
||||||
|
|
||||||
# mask length
|
|
||||||
struct.pack_into('>B', rreq_buffer, 8, 3)
|
|
||||||
|
|
||||||
# fuam, dcm. 6 bytes, two sets of 3.
|
|
||||||
lst = (255, 224, 0, 0, 31, 252)
|
|
||||||
struct.pack_into('>BBBBBB', rreq_buffer, 9, *lst)
|
|
||||||
|
|
||||||
# number of standard features: 11
|
|
||||||
struct.pack_into('>H', rreq_buffer, 15, 11)
|
|
||||||
|
|
||||||
standard_flags = [5, 42, 45, 2, 18, 19, 1, 8, 12, 31, 20]
|
|
||||||
standard_masks = [8388608, 4194304, 2097152, 1048576, 524288, 262144,
|
|
||||||
131072, 65536, 32768, 16384, 8192]
|
|
||||||
for j in range(len(standard_flags)):
|
|
||||||
mask = (standard_masks[j] >> 16,
|
|
||||||
standard_masks[j] & 0x0000ffff >> 8,
|
|
||||||
standard_masks[j] & 0x000000ff)
|
|
||||||
struct.pack_into('>HBBB', rreq_buffer, 17 + j * 5,
|
|
||||||
standard_flags[j], *mask)
|
|
||||||
|
|
||||||
# num vendor features: 0
|
|
||||||
struct.pack_into('>H', rreq_buffer, 72, 0)
|
|
||||||
|
|
||||||
# Ok, done with the box, we can now insert it into the jpx file after
|
|
||||||
# the ftyp box.
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=".jpx") as ofile:
|
|
||||||
with open(self.jpxfile, 'rb') as ifile:
|
|
||||||
ofile.write(ifile.read(40))
|
|
||||||
ofile.write(rreq_buffer)
|
|
||||||
ofile.write(ifile.read())
|
|
||||||
ofile.flush()
|
|
||||||
|
|
||||||
jpx = Jp2k(ofile.name)
|
|
||||||
|
|
||||||
self.assertEqual(jpx.box[2].box_id, 'rreq')
|
|
||||||
self.assertEqual(type(jpx.box[2]),
|
|
||||||
glymur.jp2box.ReaderRequirementsBox)
|
|
||||||
self.assertEqual(jpx.box[2].standard_flag,
|
|
||||||
(5, 42, 45, 2, 18, 19, 1, 8, 12, 31, 20))
|
|
||||||
|
|
||||||
def test_nlst(self):
|
|
||||||
"""Verify that we can handle a number list box."""
|
|
||||||
j = Jp2k(self.jpxfile)
|
|
||||||
nlst = j.box[12].box[0].box[0]
|
|
||||||
self.assertEqual(nlst.box_id, 'nlst')
|
|
||||||
self.assertEqual(type(nlst), glymur.jp2box.NumberListBox)
|
|
||||||
|
|
||||||
# Two associations.
|
|
||||||
self.assertEqual(len(nlst.associations), 2)
|
|
||||||
|
|
||||||
# Codestream 0
|
|
||||||
self.assertEqual(nlst.associations[0], 1 << 24)
|
|
||||||
|
|
||||||
# Compositing Layer 0
|
|
||||||
self.assertEqual(nlst.associations[1], 2 << 24)
|
|
||||||
|
|
|
||||||
|
|
@ -1,172 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""Test suite for printing.
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import struct
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
if sys.hexversion < 0x02070000:
|
|
||||||
import unittest2 as unittest
|
|
||||||
else:
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import lxml.etree
|
|
||||||
|
|
||||||
from .fixtures import (WARNING_INFRASTRUCTURE_ISSUE,
|
|
||||||
WARNING_INFRASTRUCTURE_MSG,
|
|
||||||
WINDOWS_TMP_FILE_MSG)
|
|
||||||
|
|
||||||
import glymur
|
|
||||||
from glymur import Jp2k
|
|
||||||
from .fixtures import SimpleRDF
|
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG)
|
|
||||||
class TestSuite(unittest.TestCase):
|
|
||||||
"""Tests for XMP, Exif UUIDs."""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.jp2file = glymur.data.nemo()
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_append_xmp_uuid(self):
|
|
||||||
"""Should be able to append an XMP UUID box."""
|
|
||||||
the_uuid = uuid.UUID('be7acfcb-97a9-42e8-9c71-999491e3afac')
|
|
||||||
raw_data = SimpleRDF.encode('utf-8')
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
|
||||||
shutil.copyfile(self.jp2file, tfile.name)
|
|
||||||
jp2 = Jp2k(tfile.name)
|
|
||||||
ubox = glymur.jp2box.UUIDBox(the_uuid=the_uuid, raw_data=raw_data)
|
|
||||||
jp2.append(ubox)
|
|
||||||
|
|
||||||
# Should be two UUID boxes now.
|
|
||||||
expected_ids = ['jP ', 'ftyp', 'jp2h', 'uuid', 'jp2c', 'uuid']
|
|
||||||
actual_ids = [b.box_id for b in jp2.box]
|
|
||||||
self.assertEqual(actual_ids, expected_ids)
|
|
||||||
|
|
||||||
# The data should be an XMP packet, which gets interpreted as
|
|
||||||
# an ElementTree.
|
|
||||||
self.assertTrue(isinstance(jp2.box[-1].data,
|
|
||||||
lxml.etree._ElementTree))
|
|
||||||
|
|
||||||
def test_big_endian_exif(self):
|
|
||||||
"""Verify read of Exif big-endian IFD."""
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2', mode='wb') as tfile:
|
|
||||||
|
|
||||||
with open(self.jp2file, 'rb') as ifptr:
|
|
||||||
tfile.write(ifptr.read())
|
|
||||||
|
|
||||||
# Write L, T, UUID identifier.
|
|
||||||
tfile.write(struct.pack('>I4s', 52, b'uuid'))
|
|
||||||
tfile.write(b'JpgTiffExif->JP2')
|
|
||||||
|
|
||||||
tfile.write(b'Exif\x00\x00')
|
|
||||||
xbuffer = struct.pack('>BBHI', 77, 77, 42, 8)
|
|
||||||
tfile.write(xbuffer)
|
|
||||||
|
|
||||||
# We will write just a single tag.
|
|
||||||
tfile.write(struct.pack('>H', 1))
|
|
||||||
|
|
||||||
# The "Make" tag is tag no. 271.
|
|
||||||
tfile.write(struct.pack('>HHI4s', 271, 2, 3, b'HTC\x00'))
|
|
||||||
tfile.flush()
|
|
||||||
|
|
||||||
jp2 = glymur.Jp2k(tfile.name)
|
|
||||||
self.assertEqual(jp2.box[-1].data['Make'], "HTC")
|
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG)
|
|
||||||
@unittest.skipIf(os.name == "nt", WINDOWS_TMP_FILE_MSG)
|
|
||||||
class TestSuiteWarns(unittest.TestCase):
|
|
||||||
"""Tests for XMP, Exif UUIDs, issues warnings."""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.jp2file = glymur.data.nemo()
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_unrecognized_exif_tag(self):
|
|
||||||
"""Verify warning in case of unrecognized tag."""
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2', mode='wb') as tfile:
|
|
||||||
|
|
||||||
with open(self.jp2file, 'rb') as ifptr:
|
|
||||||
tfile.write(ifptr.read())
|
|
||||||
|
|
||||||
# Write L, T, UUID identifier.
|
|
||||||
tfile.write(struct.pack('>I4s', 52, b'uuid'))
|
|
||||||
tfile.write(b'JpgTiffExif->JP2')
|
|
||||||
|
|
||||||
tfile.write(b'Exif\x00\x00')
|
|
||||||
xbuffer = struct.pack('<BBHI', 73, 73, 42, 8)
|
|
||||||
tfile.write(xbuffer)
|
|
||||||
|
|
||||||
# We will write just a single tag.
|
|
||||||
tfile.write(struct.pack('<H', 1))
|
|
||||||
|
|
||||||
# The "Make" tag is tag no. 271. Corrupt it to 171.
|
|
||||||
tfile.write(struct.pack('<HHI4s', 171, 2, 3, b'HTC\x00'))
|
|
||||||
tfile.flush()
|
|
||||||
|
|
||||||
with self.assertWarnsRegex(UserWarning, 'Unrecognized Exif tag'):
|
|
||||||
glymur.Jp2k(tfile.name)
|
|
||||||
|
|
||||||
def test_bad_tag_datatype(self):
|
|
||||||
"""Only certain datatypes are allowable"""
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2', mode='wb') as tfile:
|
|
||||||
|
|
||||||
with open(self.jp2file, 'rb') as ifptr:
|
|
||||||
tfile.write(ifptr.read())
|
|
||||||
|
|
||||||
# Write L, T, UUID identifier.
|
|
||||||
tfile.write(struct.pack('>I4s', 52, b'uuid'))
|
|
||||||
tfile.write(b'JpgTiffExif->JP2')
|
|
||||||
|
|
||||||
tfile.write(b'Exif\x00\x00')
|
|
||||||
xbuffer = struct.pack('<BBHI', 73, 73, 42, 8)
|
|
||||||
tfile.write(xbuffer)
|
|
||||||
|
|
||||||
# We will write just a single tag.
|
|
||||||
tfile.write(struct.pack('<H', 1))
|
|
||||||
|
|
||||||
# 2000 is not an allowable TIFF datatype.
|
|
||||||
tfile.write(struct.pack('<HHI4s', 271, 2000, 3, b'HTC\x00'))
|
|
||||||
tfile.flush()
|
|
||||||
|
|
||||||
with self.assertWarnsRegex(UserWarning, 'Invalid TIFF tag'):
|
|
||||||
j = glymur.Jp2k(tfile.name)
|
|
||||||
|
|
||||||
self.assertEqual(j.box[-1].box_id, 'uuid')
|
|
||||||
|
|
||||||
def test_bad_tiff_header_byte_order_indication(self):
|
|
||||||
"""Only b'II' and b'MM' are allowed."""
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2', mode='wb') as tfile:
|
|
||||||
|
|
||||||
with open(self.jp2file, 'rb') as ifptr:
|
|
||||||
tfile.write(ifptr.read())
|
|
||||||
|
|
||||||
# Write L, T, UUID identifier.
|
|
||||||
tfile.write(struct.pack('>I4s', 52, b'uuid'))
|
|
||||||
tfile.write(b'JpgTiffExif->JP2')
|
|
||||||
|
|
||||||
tfile.write(b'Exif\x00\x00')
|
|
||||||
xbuffer = struct.pack('<BBHI', 74, 73, 42, 8)
|
|
||||||
tfile.write(xbuffer)
|
|
||||||
|
|
||||||
# We will write just a single tag.
|
|
||||||
tfile.write(struct.pack('<H', 1))
|
|
||||||
|
|
||||||
# 271 is the Make.
|
|
||||||
tfile.write(struct.pack('<HHI4s', 271, 2, 3, b'HTC\x00'))
|
|
||||||
tfile.flush()
|
|
||||||
|
|
||||||
regex = 'The byte order indication in the TIFF header '
|
|
||||||
with self.assertWarnsRegex(UserWarning, regex):
|
|
||||||
jp2 = glymur.Jp2k(tfile.name)
|
|
||||||
|
|
||||||
self.assertEqual(jp2.box[-1].box_id, 'uuid')
|
|
||||||
|
|
@ -2,13 +2,43 @@
|
||||||
"""
|
"""
|
||||||
Test suite specifically targeting JP2 box layout.
|
Test suite specifically targeting JP2 box layout.
|
||||||
"""
|
"""
|
||||||
import os
|
# E1103: return value from read may be list or np array
|
||||||
import re
|
# pylint: disable=E1103
|
||||||
import struct
|
|
||||||
import tempfile
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import lxml.etree as ET
|
# F0401: unittest2 is needed on python-2.6 (pylint on 2.7)
|
||||||
|
# pylint: disable=F0401
|
||||||
|
|
||||||
|
# R0902: More than 7 instance attributes are just fine for testing.
|
||||||
|
# pylint: disable=R0902
|
||||||
|
|
||||||
|
# R0904: Seems like pylint is fooled in this situation
|
||||||
|
# pylint: disable=R0904
|
||||||
|
|
||||||
|
# W0613: load_tests doesn't need to use ignore or loader arguments.
|
||||||
|
# pylint: disable=W0613
|
||||||
|
|
||||||
|
import os
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import warnings
|
||||||
|
import xml.etree.cElementTree as ET
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
if sys.hexversion < 0x03000000:
|
||||||
|
from StringIO import StringIO
|
||||||
|
else:
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
|
if sys.hexversion <= 0x03030000:
|
||||||
|
from mock import patch
|
||||||
|
else:
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
if sys.hexversion < 0x02070000:
|
||||||
|
import unittest2 as unittest
|
||||||
|
else:
|
||||||
|
import unittest
|
||||||
|
|
||||||
import glymur
|
import glymur
|
||||||
from glymur import Jp2k
|
from glymur import Jp2k
|
||||||
|
|
@ -16,12 +46,8 @@ from glymur.jp2box import ColourSpecificationBox, ContiguousCodestreamBox
|
||||||
from glymur.jp2box import FileTypeBox, ImageHeaderBox, JP2HeaderBox
|
from glymur.jp2box import FileTypeBox, ImageHeaderBox, JP2HeaderBox
|
||||||
from glymur.jp2box import JPEG2000SignatureBox
|
from glymur.jp2box import JPEG2000SignatureBox
|
||||||
|
|
||||||
from .fixtures import OPJ_DATA_ROOT, opj_data_file
|
|
||||||
from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG
|
|
||||||
from . import fixtures
|
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
@unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG)
|
|
||||||
class TestXML(unittest.TestCase):
|
class TestXML(unittest.TestCase):
|
||||||
"""Test suite for XML boxes."""
|
"""Test suite for XML boxes."""
|
||||||
|
|
||||||
|
|
@ -145,6 +171,8 @@ class TestXML(unittest.TestCase):
|
||||||
u'<country>Россия</country>')
|
u'<country>Россия</country>')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||||
class TestJp2kBadXmlFile(unittest.TestCase):
|
class TestJp2kBadXmlFile(unittest.TestCase):
|
||||||
"""Test suite for bad XML box situations"""
|
"""Test suite for bad XML box situations"""
|
||||||
|
|
||||||
|
|
@ -185,11 +213,12 @@ class TestJp2kBadXmlFile(unittest.TestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG)
|
|
||||||
def test_invalid_xml_box(self):
|
def test_invalid_xml_box(self):
|
||||||
"""Should be able to recover info from xml box with bad xml."""
|
"""Should be able to recover info from xml box with bad xml."""
|
||||||
with self.assertWarns(UserWarning):
|
with warnings.catch_warnings(record=True) as w:
|
||||||
|
warnings.simplefilter("always")
|
||||||
jp2k = Jp2k(self._bad_xml_file)
|
jp2k = Jp2k(self._bad_xml_file)
|
||||||
|
self.assertEqual(len(w), 1)
|
||||||
|
|
||||||
self.assertEqual(jp2k.box[3].box_id, 'xml ')
|
self.assertEqual(jp2k.box[3].box_id, 'xml ')
|
||||||
self.assertEqual(jp2k.box[3].offset, 77)
|
self.assertEqual(jp2k.box[3].offset, 77)
|
||||||
|
|
@ -197,7 +226,7 @@ class TestJp2kBadXmlFile(unittest.TestCase):
|
||||||
self.assertIsNone(jp2k.box[3].xml)
|
self.assertIsNone(jp2k.box[3].xml)
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG)
|
@unittest.skipIf(os.name == "nt", "NamedTemporaryFile issue on windows")
|
||||||
class TestBadButRecoverableXmlFile(unittest.TestCase):
|
class TestBadButRecoverableXmlFile(unittest.TestCase):
|
||||||
"""Test suite for XML box that is bad, but we can still recover the XML."""
|
"""Test suite for XML box that is bad, but we can still recover the XML."""
|
||||||
|
|
||||||
|
|
@ -238,17 +267,19 @@ class TestBadButRecoverableXmlFile(unittest.TestCase):
|
||||||
def tearDownClass(cls):
|
def tearDownClass(cls):
|
||||||
os.unlink(cls._bad_xml_file)
|
os.unlink(cls._bad_xml_file)
|
||||||
|
|
||||||
@unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG)
|
@unittest.skipIf(sys.hexversion < 0x03020000,
|
||||||
|
"Uses features introduced in 3.2.")
|
||||||
def test_bad_xml_box_warning(self):
|
def test_bad_xml_box_warning(self):
|
||||||
"""Should warn in case of bad XML"""
|
"""Should warn in case of bad XML"""
|
||||||
regex = 'A UnicodeDecodeError was encountered parsing an XML box'
|
with warnings.catch_warnings(record=True) as w:
|
||||||
with self.assertWarnsRegex(UserWarning, regex):
|
warnings.simplefilter("always")
|
||||||
Jp2k(self._bad_xml_file)
|
Jp2k(self._bad_xml_file)
|
||||||
|
self.assertEqual(len(w), 1)
|
||||||
|
|
||||||
@unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG)
|
|
||||||
def test_recover_from_bad_xml(self):
|
def test_recover_from_bad_xml(self):
|
||||||
"""Should be able to recover info from xml box with bad xml."""
|
"""Should be able to recover info from xml box with bad xml."""
|
||||||
with self.assertWarns(UserWarning):
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter("ignore")
|
||||||
jp2 = Jp2k(self._bad_xml_file)
|
jp2 = Jp2k(self._bad_xml_file)
|
||||||
|
|
||||||
self.assertEqual(jp2.box[3].box_id, 'xml ')
|
self.assertEqual(jp2.box[3].box_id, 'xml ')
|
||||||
|
|
@ -258,31 +289,3 @@ class TestBadButRecoverableXmlFile(unittest.TestCase):
|
||||||
b'<test>this is a test</test>')
|
b'<test>this is a test</test>')
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG)
|
|
||||||
@unittest.skipIf(OPJ_DATA_ROOT is None,
|
|
||||||
"OPJ_DATA_ROOT environment variable not set")
|
|
||||||
class TestXML_OpjDataRoot(unittest.TestCase):
|
|
||||||
"""Test suite for XML boxes, requires OPJ_DATA_ROOT."""
|
|
||||||
|
|
||||||
def test_bom(self):
|
|
||||||
"""Byte order markers are illegal in UTF-8. Issue 185"""
|
|
||||||
filename = opj_data_file(os.path.join('input',
|
|
||||||
'nonregression',
|
|
||||||
'issue171.jp2'))
|
|
||||||
msg = 'An illegal BOM \(byte order marker\) was detected and removed '
|
|
||||||
msg += 'from the XML contents in the box starting at byte offset \d+'
|
|
||||||
with self.assertWarnsRegex(UserWarning, re.compile(msg)):
|
|
||||||
jp2 = Jp2k(filename)
|
|
||||||
|
|
||||||
self.assertIsNotNone(jp2.box[3].xml)
|
|
||||||
|
|
||||||
def test_invalid_utf8(self):
|
|
||||||
"""Bad byte sequence that cannot be parsed."""
|
|
||||||
relname = '26ccf3651020967f7778238ef5af08af.SIGFPE.d25.527.jp2'
|
|
||||||
filename = opj_data_file(os.path.join('input',
|
|
||||||
'nonregression',
|
|
||||||
relname))
|
|
||||||
with self.assertWarns((UserWarning, UserWarning)):
|
|
||||||
jp2 = Jp2k(filename)
|
|
||||||
|
|
||||||
self.assertIsNone(jp2.box[3].box[1].box[1].xml)
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
407
glymur/test/test_opj_suite_2p1.py
Normal file
407
glymur/test/test_opj_suite_2p1.py
Normal file
|
|
@ -0,0 +1,407 @@
|
||||||
|
"""
|
||||||
|
The tests defined here roughly correspond to what is in the OpenJPEG test
|
||||||
|
suite.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Some test names correspond with openjpeg tests. Long names are ok in this
|
||||||
|
# case.
|
||||||
|
# pylint: disable=C0103
|
||||||
|
|
||||||
|
# All of these tests correspond to tests in openjpeg, so no docstring is really
|
||||||
|
# needed.
|
||||||
|
# pylint: disable=C0111
|
||||||
|
|
||||||
|
# This module is very long, cannot be helped.
|
||||||
|
# pylint: disable=C0302
|
||||||
|
|
||||||
|
# unittest fools pylint with "too many public methods"
|
||||||
|
# pylint: disable=R0904
|
||||||
|
|
||||||
|
# Some tests use numpy test infrastructure, which means the tests never
|
||||||
|
# reference "self", so pylint claims it should be a function. No, no, no.
|
||||||
|
# pylint: disable=R0201
|
||||||
|
|
||||||
|
# Many tests are pretty long and that can't be helped.
|
||||||
|
# pylint: disable=R0915
|
||||||
|
|
||||||
|
# asserWarns introduced in python 3.2 (python2.7/pylint issue)
|
||||||
|
# pylint: disable=E1101
|
||||||
|
|
||||||
|
# unittest2 is python2.6 only (pylint/python-2.7)
|
||||||
|
# pylint: disable=F0401
|
||||||
|
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if sys.hexversion < 0x02070000:
|
||||||
|
import unittest2 as unittest
|
||||||
|
else:
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from glymur import Jp2k
|
||||||
|
import glymur
|
||||||
|
|
||||||
|
from .fixtures import OPENJP2_IS_V2_OFFICIAL, OPJ_DATA_ROOT
|
||||||
|
from .fixtures import mse, peak_tolerance, read_pgx, opj_data_file
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(OPJ_DATA_ROOT is None,
|
||||||
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
|
@unittest.skipIf(re.match(r'''2.0.0''', glymur.version.openjpeg_version),
|
||||||
|
"Tests not introduced until 2.0.1")
|
||||||
|
@unittest.skipIf(glymur.version.openjpeg_version_tuple[0] == 1,
|
||||||
|
"Tests not introduced until 2.1")
|
||||||
|
class TestSuite2point1(unittest.TestCase):
|
||||||
|
"""Runs tests introduced in version 2.0+ or that pass only in 2.0+"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_NR_DEC_text_GBR_jp2_29_decode(self):
|
||||||
|
jfile = opj_data_file('input/nonregression/text_GBR.jp2')
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
# brand is 'jp2 ', but has any icc profile.
|
||||||
|
warnings.simplefilter("ignore")
|
||||||
|
jp2 = Jp2k(jfile)
|
||||||
|
jp2.read()
|
||||||
|
self.assertTrue(True)
|
||||||
|
|
||||||
|
def test_NR_DEC_kodak_2layers_lrcp_j2c_31_decode(self):
|
||||||
|
jfile = opj_data_file('input/nonregression/kodak_2layers_lrcp.j2c')
|
||||||
|
Jp2k(jfile).read()
|
||||||
|
self.assertTrue(True)
|
||||||
|
|
||||||
|
def test_NR_DEC_kodak_2layers_lrcp_j2c_32_decode(self):
|
||||||
|
jfile = opj_data_file('input/nonregression/kodak_2layers_lrcp.j2c')
|
||||||
|
Jp2k(jfile).read(layer=2)
|
||||||
|
self.assertTrue(True)
|
||||||
|
|
||||||
|
def test_NR_DEC_issue104_jpxstream_jp2_33_decode(self):
|
||||||
|
jfile = opj_data_file('input/nonregression/issue104_jpxstream.jp2')
|
||||||
|
Jp2k(jfile).read()
|
||||||
|
self.assertTrue(True)
|
||||||
|
|
||||||
|
def test_NR_DEC_mem_b2b86b74_2753_jp2_35_decode(self):
|
||||||
|
jfile = opj_data_file('input/nonregression/mem-b2b86b74-2753.jp2')
|
||||||
|
Jp2k(jfile).read()
|
||||||
|
self.assertTrue(True)
|
||||||
|
|
||||||
|
def test_NR_DEC_gdal_fuzzer_unchecked_num_resolutions_jp2_36_decode(self):
|
||||||
|
f = 'input/nonregression/gdal_fuzzer_unchecked_numresolutions.jp2'
|
||||||
|
jfile = opj_data_file(f)
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
# Invalid number of resolutions.
|
||||||
|
warnings.simplefilter("ignore")
|
||||||
|
j = Jp2k(jfile)
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
j.read()
|
||||||
|
|
||||||
|
def test_NR_DEC_gdal_fuzzer_check_number_of_tiles_jp2_38_decode(self):
|
||||||
|
relpath = 'input/nonregression/gdal_fuzzer_check_number_of_tiles.jp2'
|
||||||
|
jfile = opj_data_file(relpath)
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
# Invalid number of tiles.
|
||||||
|
warnings.simplefilter("ignore")
|
||||||
|
j = Jp2k(jfile)
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
j.read()
|
||||||
|
|
||||||
|
def test_NR_DEC_gdal_fuzzer_check_comp_dx_dy_jp2_39_decode(self):
|
||||||
|
relpath = 'input/nonregression/gdal_fuzzer_check_comp_dx_dy.jp2'
|
||||||
|
jfile = opj_data_file(relpath)
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
# Invalid subsampling value
|
||||||
|
warnings.simplefilter("ignore")
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
Jp2k(jfile).read()
|
||||||
|
|
||||||
|
def test_NR_DEC_file_409752_jp2_40_decode(self):
|
||||||
|
jfile = opj_data_file('input/nonregression/file409752.jp2')
|
||||||
|
with self.assertRaises(RuntimeError):
|
||||||
|
Jp2k(jfile).read()
|
||||||
|
|
||||||
|
def test_NR_DEC_issue188_beach_64bitsbox_jp2_41_decode(self):
|
||||||
|
# Has an 'XML ' box instead of 'xml '. Yes that is pedantic, but it
|
||||||
|
# really does deserve a warning.
|
||||||
|
relpath = 'input/nonregression/issue188_beach_64bitsbox.jp2'
|
||||||
|
jfile = opj_data_file(relpath)
|
||||||
|
with warnings.catch_warnings(record=True) as w:
|
||||||
|
warnings.simplefilter("always")
|
||||||
|
Jp2k(jfile).read()
|
||||||
|
self.assertEqual(len(w), 1)
|
||||||
|
|
||||||
|
def test_NR_DEC_issue206_image_000_jp2_42_decode(self):
|
||||||
|
jfile = opj_data_file('input/nonregression/issue206_image-000.jp2')
|
||||||
|
Jp2k(jfile).read()
|
||||||
|
self.assertTrue(True)
|
||||||
|
|
||||||
|
def test_NR_DEC_p1_04_j2k_43_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p1_04.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
ssdata = jp2k.read(area=(0, 0, 1024, 1024))
|
||||||
|
odata = jp2k.read()
|
||||||
|
np.testing.assert_array_equal(ssdata, odata)
|
||||||
|
|
||||||
|
def test_NR_DEC_p1_04_j2k_44_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p1_04.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
ssdata = jp2k.read(area=(640, 512, 768, 640))
|
||||||
|
odata = jp2k.read()
|
||||||
|
np.testing.assert_array_equal(ssdata, odata[640:768, 512:640])
|
||||||
|
|
||||||
|
def test_NR_DEC_p1_04_j2k_45_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p1_04.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
ssdata = jp2k.read(area=(896, 896, 1024, 1024))
|
||||||
|
odata = jp2k.read()
|
||||||
|
np.testing.assert_array_equal(ssdata, odata[896:1024, 896:1024])
|
||||||
|
|
||||||
|
def test_NR_DEC_p1_04_j2k_46_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p1_04.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
ssdata = jp2k.read(area=(500, 100, 800, 300))
|
||||||
|
odata = jp2k.read()
|
||||||
|
np.testing.assert_array_equal(ssdata, odata[500:800, 100:300])
|
||||||
|
|
||||||
|
def test_NR_DEC_p1_04_j2k_47_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p1_04.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
ssdata = jp2k.read(area=(520, 260, 600, 360))
|
||||||
|
odata = jp2k.read()
|
||||||
|
np.testing.assert_array_equal(ssdata, odata[520:600, 260:360])
|
||||||
|
|
||||||
|
def test_NR_DEC_p1_04_j2k_48_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p1_04.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
ssdata = jp2k.read(area=(520, 260, 660, 360))
|
||||||
|
odata = jp2k.read()
|
||||||
|
np.testing.assert_array_equal(ssdata, odata[520:660, 260:360])
|
||||||
|
|
||||||
|
def test_NR_DEC_p1_04_j2k_49_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p1_04.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
ssdata = jp2k.read(area=(520, 360, 600, 400))
|
||||||
|
odata = jp2k.read()
|
||||||
|
np.testing.assert_array_equal(ssdata, odata[520:600, 360:400])
|
||||||
|
|
||||||
|
def test_NR_DEC_p1_04_j2k_50_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p1_04.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
ssdata = jp2k.read(area=(0, 0, 1024, 1024), rlevel=2)
|
||||||
|
odata = jp2k.read(rlevel=2)
|
||||||
|
|
||||||
|
np.testing.assert_array_equal(ssdata, odata[0:256, 0:256])
|
||||||
|
|
||||||
|
def test_NR_DEC_p1_04_j2k_51_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p1_04.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
ssdata = jp2k.read(area=(640, 512, 768, 640), rlevel=2)
|
||||||
|
odata = jp2k.read(rlevel=2)
|
||||||
|
np.testing.assert_array_equal(ssdata, odata[160:192, 128:160])
|
||||||
|
|
||||||
|
def test_NR_DEC_p1_04_j2k_52_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p1_04.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
ssdata = jp2k.read(area=(896, 896, 1024, 1024), rlevel=2)
|
||||||
|
odata = jp2k.read(rlevel=2)
|
||||||
|
np.testing.assert_array_equal(ssdata, odata[224:352, 224:352])
|
||||||
|
|
||||||
|
def test_NR_DEC_p1_04_j2k_53_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p1_04.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
ssdata = jp2k.read(area=(500, 100, 800, 300), rlevel=2)
|
||||||
|
odata = jp2k.read(rlevel=2)
|
||||||
|
np.testing.assert_array_equal(ssdata, odata[125:200, 25:75])
|
||||||
|
|
||||||
|
def test_NR_DEC_p1_04_j2k_54_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p1_04.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
ssdata = jp2k.read(area=(520, 260, 600, 360), rlevel=2)
|
||||||
|
odata = jp2k.read(rlevel=2)
|
||||||
|
np.testing.assert_array_equal(ssdata, odata[130:150, 65:90])
|
||||||
|
|
||||||
|
def test_NR_DEC_p1_04_j2k_55_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p1_04.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
ssdata = jp2k.read(area=(520, 260, 660, 360), rlevel=2)
|
||||||
|
odata = jp2k.read(rlevel=2)
|
||||||
|
np.testing.assert_array_equal(ssdata, odata[130:165, 65:90])
|
||||||
|
|
||||||
|
def test_NR_DEC_p1_04_j2k_56_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p1_04.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
ssdata = jp2k.read(area=(520, 360, 600, 400), rlevel=2)
|
||||||
|
odata = jp2k.read(rlevel=2)
|
||||||
|
np.testing.assert_array_equal(ssdata, odata[130:150, 90:100])
|
||||||
|
|
||||||
|
def test_NR_DEC_p1_04_j2k_57_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p1_04.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
tdata = jp2k.read(tile=63) # last tile
|
||||||
|
odata = jp2k.read()
|
||||||
|
np.testing.assert_array_equal(tdata, odata[896:1024, 896:1024])
|
||||||
|
|
||||||
|
def test_NR_DEC_p1_04_j2k_58_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p1_04.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
tdata = jp2k.read(tile=63, rlevel=2) # last tile
|
||||||
|
odata = jp2k.read(rlevel=2)
|
||||||
|
np.testing.assert_array_equal(tdata, odata[224:256, 224:256])
|
||||||
|
|
||||||
|
def test_NR_DEC_p1_04_j2k_59_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p1_04.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
tdata = jp2k.read(tile=12) # 2nd row, 5th column
|
||||||
|
odata = jp2k.read()
|
||||||
|
np.testing.assert_array_equal(tdata, odata[128:256, 512:640])
|
||||||
|
|
||||||
|
def test_NR_DEC_p1_04_j2k_60_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p1_04.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
tdata = jp2k.read(tile=12, rlevel=1) # 2nd row, 5th column
|
||||||
|
odata = jp2k.read(rlevel=1)
|
||||||
|
np.testing.assert_array_equal(tdata, odata[64:128, 256:320])
|
||||||
|
|
||||||
|
def test_NR_DEC_jp2_36_decode(self):
|
||||||
|
lst = ('input',
|
||||||
|
'nonregression',
|
||||||
|
'gdal_fuzzer_assert_in_opj_j2k_read_SQcd_SQcc.patch.jp2')
|
||||||
|
jfile = opj_data_file('/'.join(lst))
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
# Invalid component number.
|
||||||
|
warnings.simplefilter("ignore")
|
||||||
|
j = Jp2k(jfile)
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
j.read()
|
||||||
|
|
||||||
|
def test_NR_DEC_p1_06_j2k_70_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p1_06.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
ssdata = jp2k.read(area=(9, 9, 12, 12), rlevel=1)
|
||||||
|
self.assertEqual(ssdata.shape, (1, 1, 3))
|
||||||
|
|
||||||
|
def test_NR_DEC_p1_06_j2k_71_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p1_06.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
ssdata = jp2k.read(area=(10, 4, 12, 10), rlevel=1)
|
||||||
|
self.assertEqual(ssdata.shape, (1, 3, 3))
|
||||||
|
|
||||||
|
def test_NR_DEC_p1_06_j2k_72_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p1_06.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
ssdata = jp2k.read(area=(3, 3, 9, 9), rlevel=1)
|
||||||
|
self.assertEqual(ssdata.shape, (3, 3, 3))
|
||||||
|
|
||||||
|
def test_NR_DEC_p1_06_j2k_73_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p1_06.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
ssdata = jp2k.read(area=(4, 4, 7, 7), rlevel=1)
|
||||||
|
self.assertEqual(ssdata.shape, (2, 2, 3))
|
||||||
|
|
||||||
|
def test_NR_DEC_p1_06_j2k_74_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p1_06.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
ssdata = jp2k.read(area=(4, 4, 5, 5), rlevel=1)
|
||||||
|
self.assertEqual(ssdata.shape, (1, 1, 3))
|
||||||
|
|
||||||
|
def test_NR_DEC_p1_06_j2k_75_decode(self):
|
||||||
|
# Image size would be 0 x 0.
|
||||||
|
jfile = opj_data_file('input/conformance/p1_06.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
with self.assertRaises((IOError, OSError)):
|
||||||
|
jp2k.read(area=(9, 9, 12, 12), rlevel=2)
|
||||||
|
|
||||||
|
def test_NR_DEC_p0_04_j2k_85_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p0_04.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
ssdata = jp2k.read(area=(0, 0, 256, 256))
|
||||||
|
fulldata = jp2k.read()
|
||||||
|
np.testing.assert_array_equal(fulldata[0:256, 0:256], ssdata)
|
||||||
|
|
||||||
|
def test_NR_DEC_p0_04_j2k_86_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p0_04.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
ssdata = jp2k.read(area=(0, 128, 128, 256))
|
||||||
|
fulldata = jp2k.read()
|
||||||
|
np.testing.assert_array_equal(fulldata[0:128, 128:256], ssdata)
|
||||||
|
|
||||||
|
def test_NR_DEC_p0_04_j2k_87_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p0_04.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
ssdata = jp2k.read(area=(10, 50, 200, 120))
|
||||||
|
fulldata = jp2k.read()
|
||||||
|
np.testing.assert_array_equal(fulldata[10:200, 50:120], ssdata)
|
||||||
|
|
||||||
|
def test_NR_DEC_p0_04_j2k_88_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p0_04.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
ssdata = jp2k.read(area=(150, 10, 210, 190))
|
||||||
|
fulldata = jp2k.read()
|
||||||
|
np.testing.assert_array_equal(fulldata[150:210, 10:190], ssdata)
|
||||||
|
|
||||||
|
def test_NR_DEC_p0_04_j2k_89_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p0_04.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
ssdata = jp2k.read(area=(80, 100, 150, 200))
|
||||||
|
fulldata = jp2k.read()
|
||||||
|
np.testing.assert_array_equal(fulldata[80:150, 100:200], ssdata)
|
||||||
|
|
||||||
|
def test_NR_DEC_p0_04_j2k_90_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p0_04.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
ssdata = jp2k.read(area=(20, 150, 50, 200))
|
||||||
|
fulldata = jp2k.read()
|
||||||
|
np.testing.assert_array_equal(fulldata[20:50, 150:200], ssdata)
|
||||||
|
|
||||||
|
def test_NR_DEC_p0_04_j2k_91_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p0_04.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
ssdata = jp2k.read(area=(0, 0, 256, 256), rlevel=2)
|
||||||
|
fulldata = jp2k.read(rlevel=2)
|
||||||
|
np.testing.assert_array_equal(fulldata[0:64, 0:64], ssdata)
|
||||||
|
|
||||||
|
def test_NR_DEC_p0_04_j2k_92_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p0_04.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
ssdata = jp2k.read(area=(0, 128, 128, 256), rlevel=2)
|
||||||
|
fulldata = jp2k.read(rlevel=2)
|
||||||
|
np.testing.assert_array_equal(fulldata[0:32, 32:64], ssdata)
|
||||||
|
|
||||||
|
def test_NR_DEC_p0_04_j2k_93_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p0_04.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
ssdata = jp2k.read(area=(10, 50, 200, 120), rlevel=2)
|
||||||
|
fulldata = jp2k.read(rlevel=2)
|
||||||
|
np.testing.assert_array_equal(fulldata[3:50, 13:30], ssdata)
|
||||||
|
|
||||||
|
def test_NR_DEC_p0_04_j2k_94_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p0_04.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
ssdata = jp2k.read(area=(150, 10, 210, 190), rlevel=2)
|
||||||
|
fulldata = jp2k.read(rlevel=2)
|
||||||
|
np.testing.assert_array_equal(fulldata[38:53, 3:48], ssdata)
|
||||||
|
|
||||||
|
def test_NR_DEC_p0_04_j2k_95_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p0_04.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
ssdata = jp2k.read(area=(80, 100, 150, 200), rlevel=2)
|
||||||
|
fulldata = jp2k.read(rlevel=2)
|
||||||
|
np.testing.assert_array_equal(fulldata[20:38, 25:50], ssdata)
|
||||||
|
|
||||||
|
def test_NR_DEC_p0_04_j2k_96_decode(self):
|
||||||
|
jfile = opj_data_file('input/conformance/p0_04.j2k')
|
||||||
|
jp2k = Jp2k(jfile)
|
||||||
|
ssdata = jp2k.read(area=(20, 150, 50, 200), rlevel=2)
|
||||||
|
fulldata = jp2k.read(rlevel=2)
|
||||||
|
np.testing.assert_array_equal(fulldata[5:13, 38:50], ssdata)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -2,30 +2,37 @@
|
||||||
The tests here do not correspond directly to the OpenJPEG test suite, but
|
The tests here do not correspond directly to the OpenJPEG test suite, but
|
||||||
seem like logical negative tests to add.
|
seem like logical negative tests to add.
|
||||||
"""
|
"""
|
||||||
|
# R0904: Not too many methods in unittest.
|
||||||
|
# pylint: disable=R0904
|
||||||
|
|
||||||
|
# unittest2 is python2.6 only (pylint/python-2.7)
|
||||||
|
# pylint: disable=F0401
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import warnings
|
||||||
|
|
||||||
|
if sys.hexversion < 0x02070000:
|
||||||
|
import unittest2 as unittest
|
||||||
|
else:
|
||||||
|
import unittest
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
try:
|
|
||||||
import skimage.io
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
from .fixtures import OPJ_DATA_ROOT, opj_data_file, read_image
|
from .fixtures import OPJ_DATA_ROOT, opj_data_file, read_image
|
||||||
from .fixtures import NO_READ_BACKEND, NO_READ_BACKEND_MSG
|
from .fixtures import NO_READ_BACKEND, NO_READ_BACKEND_MSG
|
||||||
from .fixtures import NO_SKIMAGE_FREEIMAGE_SUPPORT
|
|
||||||
from .fixtures import WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG
|
|
||||||
from . import fixtures
|
|
||||||
|
|
||||||
from glymur import Jp2k
|
from glymur import Jp2k
|
||||||
import glymur
|
import glymur
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipIf(re.match(r"""1\.[01234]""", glymur.version.openjpeg_version),
|
||||||
|
"Functionality not implemented for 1.3, 1.4")
|
||||||
@unittest.skipIf(OPJ_DATA_ROOT is None,
|
@unittest.skipIf(OPJ_DATA_ROOT is None,
|
||||||
"OPJ_OPJ_DATA_ROOT environment variable not set")
|
"OPJ_OPJ_DATA_ROOT environment variable not set")
|
||||||
class TestSuiteNegativeRead(unittest.TestCase):
|
class TestSuiteNegative(unittest.TestCase):
|
||||||
"""Test suite for certain negative tests from openjpeg suite."""
|
"""Test suite for certain negative tests from openjpeg suite."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
@ -35,6 +42,18 @@ class TestSuiteNegativeRead(unittest.TestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@unittest.skipIf(NO_READ_BACKEND, NO_READ_BACKEND_MSG)
|
||||||
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
|
def test_psnr_with_cratios(self):
|
||||||
|
"""Using psnr with cratios options is not allowed."""
|
||||||
|
# Not an OpenJPEG test, but close.
|
||||||
|
infile = opj_data_file('input/nonregression/Bretagne1.ppm')
|
||||||
|
data = read_image(infile)
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
|
||||||
|
j = Jp2k(tfile.name, 'wb')
|
||||||
|
with self.assertRaises(IOError):
|
||||||
|
j.write(data, psnr=[30, 35, 40], cratios=[2, 3, 4])
|
||||||
|
|
||||||
def test_nr_marker_not_compliant(self):
|
def test_nr_marker_not_compliant(self):
|
||||||
"""non-compliant marker, should still be able to read"""
|
"""non-compliant marker, should still be able to read"""
|
||||||
relpath = 'input/nonregression/MarkerIsNotCompliant.j2k'
|
relpath = 'input/nonregression/MarkerIsNotCompliant.j2k'
|
||||||
|
|
@ -43,14 +62,15 @@ class TestSuiteNegativeRead(unittest.TestCase):
|
||||||
jp2k.get_codestream(header_only=False)
|
jp2k.get_codestream(header_only=False)
|
||||||
self.assertTrue(True)
|
self.assertTrue(True)
|
||||||
|
|
||||||
@unittest.skipIf(WARNING_INFRASTRUCTURE_ISSUE, WARNING_INFRASTRUCTURE_MSG)
|
|
||||||
def test_nr_illegalclrtransform(self):
|
def test_nr_illegalclrtransform(self):
|
||||||
"""EOC marker is bad"""
|
"""EOC marker is bad"""
|
||||||
relpath = 'input/nonregression/illegalcolortransform.j2k'
|
relpath = 'input/nonregression/illegalcolortransform.j2k'
|
||||||
jfile = opj_data_file(relpath)
|
jfile = opj_data_file(relpath)
|
||||||
jp2k = Jp2k(jfile)
|
jp2k = Jp2k(jfile)
|
||||||
with self.assertWarns(UserWarning):
|
with warnings.catch_warnings(record=True) as w:
|
||||||
|
warnings.simplefilter("always")
|
||||||
codestream = jp2k.get_codestream(header_only=False)
|
codestream = jp2k.get_codestream(header_only=False)
|
||||||
|
self.assertEqual(len(w), 1)
|
||||||
|
|
||||||
# Verify that the last segment returned in the codestream is SOD,
|
# Verify that the last segment returned in the codestream is SOD,
|
||||||
# not EOC. Codestream parsing should stop when we try to jump to
|
# not EOC. Codestream parsing should stop when we try to jump to
|
||||||
|
|
@ -65,80 +85,67 @@ class TestSuiteNegativeRead(unittest.TestCase):
|
||||||
jp2k.get_codestream(header_only=False)
|
jp2k.get_codestream(header_only=False)
|
||||||
self.assertTrue(True)
|
self.assertTrue(True)
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
@unittest.skipIf(re.match("1.5|2", glymur.version.openjpeg_version) is None,
|
|
||||||
"Must have openjpeg 1.5 or higher to run")
|
|
||||||
@unittest.skipIf(os.name == "nt", fixtures.WINDOWS_TMP_FILE_MSG)
|
|
||||||
@unittest.skipIf(OPJ_DATA_ROOT is None,
|
|
||||||
"OPJ_OPJ_DATA_ROOT environment variable not set")
|
|
||||||
class TestSuiteNegativeWrite(unittest.TestCase):
|
|
||||||
"""Test suite for certain negative tests from openjpeg suite."""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.jp2file = glymur.data.nemo()
|
|
||||||
self.j2kfile = glymur.data.goodstuff()
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@unittest.skipIf(NO_SKIMAGE_FREEIMAGE_SUPPORT,
|
|
||||||
"Cannot read input image without scikit-image/freeimage")
|
|
||||||
def test_cinema2K_bad_frame_rate(self):
|
|
||||||
"""Cinema2k frame rate must be either 24 or 48."""
|
|
||||||
relfile = 'input/nonregression/X_5_2K_24_235_CBR_STEM24_000.tif'
|
|
||||||
infile = opj_data_file(relfile)
|
|
||||||
data = skimage.io.imread(infile)
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
Jp2k(tfile.name, data=data, cinema2k=36)
|
|
||||||
|
|
||||||
@unittest.skipIf(NO_READ_BACKEND, NO_READ_BACKEND_MSG)
|
|
||||||
def test_psnr_with_cratios(self):
|
|
||||||
"""Using psnr with cratios options is not allowed."""
|
|
||||||
# Not an OpenJPEG test, but close.
|
|
||||||
infile = opj_data_file('input/nonregression/Bretagne1.ppm')
|
|
||||||
data = read_image(infile)
|
|
||||||
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
|
|
||||||
with self.assertRaises(IOError):
|
|
||||||
Jp2k(tfile.name,
|
|
||||||
data=data, psnr=[30, 35, 40], cratios=[2, 3, 4])
|
|
||||||
|
|
||||||
def test_code_block_dimensions(self):
|
def test_code_block_dimensions(self):
|
||||||
"""don't allow extreme codeblock sizes"""
|
"""don't allow extreme codeblock sizes"""
|
||||||
# opj_compress doesn't allow the dimensions of a codeblock
|
# opj_compress doesn't allow the dimensions of a codeblock
|
||||||
# to be too small or too big, so neither will we.
|
# to be too small or too big, so neither will we.
|
||||||
data = np.zeros((256, 256), dtype=np.uint8)
|
data = np.zeros((256, 256), dtype=np.uint8)
|
||||||
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
|
with tempfile.NamedTemporaryFile(suffix='.j2k') as tfile:
|
||||||
|
j = Jp2k(tfile.name, 'wb')
|
||||||
|
|
||||||
# opj_compress doesn't allow code block area to exceed 4096.
|
# opj_compress doesn't allow code block area to exceed 4096.
|
||||||
with self.assertRaises(IOError):
|
with self.assertRaises(IOError):
|
||||||
Jp2k(tfile.name, data=data, cbsize=(256, 256))
|
j.write(data, cbsize=(256, 256))
|
||||||
|
|
||||||
# opj_compress doesn't allow either dimension to be less than 4.
|
# opj_compress doesn't allow either dimension to be less than 4.
|
||||||
with self.assertRaises(IOError):
|
with self.assertRaises(IOError):
|
||||||
Jp2k(tfile.name, data=data, cbsize=(2048, 2))
|
j.write(data, cbsize=(2048, 2))
|
||||||
with self.assertRaises(IOError):
|
with self.assertRaises(IOError):
|
||||||
Jp2k(tfile.name, data=data, cbsize=(2, 2048))
|
j.write(data, cbsize=(2, 2048))
|
||||||
|
|
||||||
|
def test_exceeded_box(self):
|
||||||
|
"""should warn if reading past end of a box"""
|
||||||
|
# Verify that a warning is issued if we read past the end of a box
|
||||||
|
# This file has a palette (pclr) box whose length is impossibly
|
||||||
|
# short.
|
||||||
|
infile = os.path.join(OPJ_DATA_ROOT,
|
||||||
|
'input/nonregression/mem-b2ace68c-1381.jp2')
|
||||||
|
with warnings.catch_warnings(record=True) as w:
|
||||||
|
warnings.simplefilter("ignore")
|
||||||
|
Jp2k(infile)
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
def test_precinct_size_not_p2(self):
|
def test_precinct_size_not_p2(self):
|
||||||
"""precinct sizes should be powers of two."""
|
"""precinct sizes should be powers of two."""
|
||||||
ifile = Jp2k(self.j2kfile)
|
ifile = Jp2k(self.j2kfile)
|
||||||
data = ifile[::4, ::4]
|
data = ifile.read(rlevel=2)
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||||
|
ofile = Jp2k(tfile.name, 'wb')
|
||||||
with self.assertRaises(IOError):
|
with self.assertRaises(IOError):
|
||||||
Jp2k(tfile.name, data=data, psizes=[(13, 13)])
|
ofile.write(data, psizes=[(13, 13)])
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
def test_cblk_size_not_power_of_two(self):
|
def test_cblk_size_not_power_of_two(self):
|
||||||
"""code block sizes should be powers of two."""
|
"""code block sizes should be powers of two."""
|
||||||
ifile = Jp2k(self.j2kfile)
|
ifile = Jp2k(self.j2kfile)
|
||||||
data = ifile[::4, ::4]
|
data = ifile.read(rlevel=2)
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||||
|
ofile = Jp2k(tfile.name, 'wb')
|
||||||
with self.assertRaises(IOError):
|
with self.assertRaises(IOError):
|
||||||
Jp2k(tfile.name, data=data, cbsize=(13, 12))
|
ofile.write(data, cbsize=(13, 12))
|
||||||
|
|
||||||
|
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
|
||||||
def test_cblk_size_precinct_size(self):
|
def test_cblk_size_precinct_size(self):
|
||||||
"""code block sizes should never exceed half that of precinct size."""
|
"""code block sizes should never exceed half that of precinct size."""
|
||||||
ifile = Jp2k(self.j2kfile)
|
ifile = Jp2k(self.j2kfile)
|
||||||
data = ifile[::4, ::4]
|
data = ifile.read(rlevel=2)
|
||||||
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
with tempfile.NamedTemporaryFile(suffix='.jp2') as tfile:
|
||||||
|
ofile = Jp2k(tfile.name, 'wb')
|
||||||
with self.assertRaises(IOError):
|
with self.assertRaises(IOError):
|
||||||
Jp2k(tfile.name, data=data, cbsize=(64, 64), psizes=[(64, 64)])
|
ofile.write(data,
|
||||||
|
cbsize=(64, 64),
|
||||||
|
psizes=[(64, 64)])
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,24 +1,21 @@
|
||||||
"""
|
# This file is part of glymur, a Python interface for accessing JPEG 2000.
|
||||||
This file is part of glymur, a Python interface for accessing JPEG 2000.
|
#
|
||||||
|
# http://glymur.readthedocs.org
|
||||||
http://glymur.readthedocs.org
|
#
|
||||||
|
# Copyright 2013 John Evans
|
||||||
Copyright 2013 John Evans
|
#
|
||||||
|
# License: MIT
|
||||||
License: MIT
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
import numpy as np
|
||||||
from distutils.version import LooseVersion
|
from distutils.version import LooseVersion
|
||||||
|
|
||||||
import lxml.etree
|
from .lib import openjpeg as opj
|
||||||
import numpy as np
|
from .lib import openjp2 as opj2
|
||||||
|
|
||||||
from .lib import openjpeg as opj, openjp2 as opj2
|
|
||||||
|
|
||||||
# Do not change the format of this next line! Doing so risks breaking
|
# Do not change the format of this next line! Doing so risks breaking
|
||||||
# setup.py
|
# setup.py
|
||||||
version = "0.8.0"
|
version = "0.5.11"
|
||||||
_sv = LooseVersion(version)
|
_sv = LooseVersion(version)
|
||||||
version_tuple = _sv.version
|
version_tuple = _sv.version
|
||||||
|
|
||||||
|
|
@ -49,12 +46,10 @@ OPENJPEG {openjpeg}
|
||||||
Python {python}
|
Python {python}
|
||||||
sys.platform {platform}
|
sys.platform {platform}
|
||||||
sys.maxsize {maxsize}
|
sys.maxsize {maxsize}
|
||||||
lxml {elxml}
|
|
||||||
numpy {numpy}
|
numpy {numpy}
|
||||||
""".format(glymur=version,
|
""".format(glymur=version,
|
||||||
openjpeg=openjpeg_version,
|
openjpeg=openjpeg_version,
|
||||||
python=sys.version,
|
python=sys.version,
|
||||||
platform=sys.platform,
|
platform=sys.platform,
|
||||||
maxsize=sys.maxsize,
|
maxsize=sys.maxsize,
|
||||||
elxml=lxml.etree.__version__,
|
|
||||||
numpy=np.__version__)
|
numpy=np.__version__)
|
||||||
|
|
|
||||||
25
setup.py
25
setup.py
|
|
@ -1,4 +1,4 @@
|
||||||
from setuptools import setup
|
from setuptools import setup, find_packages
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
@ -11,25 +11,24 @@ kwargs = {'name': 'Glymur',
|
||||||
'url': 'https://github.com/quintusdias/glymur',
|
'url': 'https://github.com/quintusdias/glymur',
|
||||||
'packages': ['glymur', 'glymur.data', 'glymur.test', 'glymur.lib',
|
'packages': ['glymur', 'glymur.data', 'glymur.test', 'glymur.lib',
|
||||||
'glymur.lib.test'],
|
'glymur.lib.test'],
|
||||||
'package_data': {'glymur': ['data/*.jp2',
|
'package_data': {'glymur': ['data/*.jp2', 'data/*.j2k']},
|
||||||
'data/*.j2k',
|
'scripts': ['bin/jp2dump'],
|
||||||
'data/*.jpx']},
|
|
||||||
'entry_points': {
|
|
||||||
'console_scripts': ['jp2dump=glymur.command_line:main'],
|
|
||||||
},
|
|
||||||
'license': 'MIT',
|
'license': 'MIT',
|
||||||
'test_suite': 'glymur.test'}
|
'test_suite': 'glymur.test'}
|
||||||
|
|
||||||
install_requires = ['numpy>=1.7.0', 'lxml>=3.0.0']
|
instllrqrs = ['numpy>=1.4.1']
|
||||||
if sys.hexversion < 0x03030000:
|
if sys.hexversion < 0x03030000:
|
||||||
install_requires.append('contextlib2>=0.4')
|
instllrqrs.append('contextlib2>=0.4')
|
||||||
install_requires.append('mock>=1.0.1')
|
instllrqrs.append('mock>=1.0.1')
|
||||||
kwargs['install_requires'] = install_requires
|
if sys.hexversion < 0x02070000:
|
||||||
|
instllrqrs.append('ordereddict>=1.1')
|
||||||
|
instllrqrs.append('unittest2>=0.5.1')
|
||||||
|
kwargs['install_requires'] = instllrqrs
|
||||||
|
|
||||||
clssfrs = ["Programming Language :: Python",
|
clssfrs = ["Programming Language :: Python",
|
||||||
|
"Programming Language :: Python :: 2.6",
|
||||||
"Programming Language :: Python :: 2.7",
|
"Programming Language :: Python :: 2.7",
|
||||||
"Programming Language :: Python :: 3.3",
|
"Programming Language :: Python :: 3.3",
|
||||||
"Programming Language :: Python :: 3.4",
|
|
||||||
"Programming Language :: Python :: Implementation :: CPython",
|
"Programming Language :: Python :: Implementation :: CPython",
|
||||||
"License :: OSI Approved :: MIT License",
|
"License :: OSI Approved :: MIT License",
|
||||||
"Development Status :: 5 - Production/Stable",
|
"Development Status :: 5 - Production/Stable",
|
||||||
|
|
@ -43,7 +42,7 @@ kwargs['classifiers'] = clssfrs
|
||||||
|
|
||||||
# Get the version string. Cannot do this by importing glymur!
|
# Get the version string. Cannot do this by importing glymur!
|
||||||
version_file = os.path.join('glymur', 'version.py')
|
version_file = os.path.join('glymur', 'version.py')
|
||||||
with open(version_file, 'rt') as fptr:
|
with open('glymur/version.py', 'rt') as fptr:
|
||||||
contents = fptr.read()
|
contents = fptr.read()
|
||||||
match = re.search('version\s*=\s*"(?P<version>\d*.\d*.\d*.*)"\n', contents)
|
match = re.search('version\s*=\s*"(?P<version>\d*.\d*.\d*.*)"\n', contents)
|
||||||
kwargs['version'] = match.group('version')
|
kwargs['version'] = match.group('version')
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue