Merge branch 'issue149' into devel

This commit is contained in:
jevans 2014-02-04 20:52:10 -05:00
commit c9a25e47c6
3 changed files with 95 additions and 9 deletions

View file

@ -1,8 +1,9 @@
Feb 03, 2014 - Added read support for JPX free, number list, data reference, Feb 03, 2014 - Added write support for Palette and Component Mapping boxes.
fragment table, and fragment list boxes. Palette box now a 2D numpy array Added read support for JPX free, number list, data reference, fragment
instead of a list of 1D arrays. JP2 super box constructors now take table, and fragment list boxes. Palette box now a 2D numpy array instead
optional box list argument. Fixed bug where JPX files with more than one of a list of 1D arrays. JP2 super box constructors now take optional box
codestream but advertising jp2 compatibility were not being read. 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.

View file

@ -15,6 +15,7 @@ References
import copy import copy
import datetime import datetime
import io
import math import math
import os import os
import pprint import pprint
@ -23,6 +24,7 @@ import sys
import uuid import uuid
import warnings import warnings
import xml.etree.cElementTree as ET import xml.etree.cElementTree as ET
if sys.hexversion < 0x02070000: if sys.hexversion < 0x02070000:
# pylint: disable=F0401,E0611 # pylint: disable=F0401,E0611
from ordereddict import OrderedDict from ordereddict import OrderedDict
@ -719,6 +721,20 @@ class ComponentMappingBox(Jp2kBox):
msg = msg.format(self.component_index[k], k) msg = msg.format(self.component_index[k], k)
return msg return msg
def write(self, fptr):
"""Write a Component Mapping box to file.
"""
length = 8 + 4 * len(self.component_index)
write_buffer = struct.pack('>I4s', length, self.box_id.encode())
fptr.write(write_buffer)
for j in range(len(self.component_index)):
write_buffer = struct.pack('>HBB',
self.component_index[j],
self.mapping_type[j],
self.palette_index[j])
fptr.write(write_buffer)
@staticmethod @staticmethod
def parse(fptr, offset, length): def parse(fptr, offset, length):
"""Parse component mapping box. """Parse component mapping box.
@ -1527,9 +1543,9 @@ class PaletteBox(Jp2kBox):
self.offset = offset self.offset = offset
def __repr__(self): def __repr__(self):
msg = "glymur.jp2box.PaletteBox({0}, bits_per_component={1}, " msg = "glymur.jp2box.PaletteBox(ndarray, bits_per_component={0}, "
msg += "signed={2})" msg += "signed={1})"
msg = msg.format(self.palette, self.bits_per_component, self.signed) msg = msg.format(self.bits_per_component, self.signed)
return msg return msg
def __str__(self): def __str__(self):
@ -1537,6 +1553,49 @@ class PaletteBox(Jp2kBox):
msg += '\n Size: ({0} x {1})'.format(*self.palette.shape) msg += '\n Size: ({0} x {1})'.format(*self.palette.shape)
return msg return msg
def write(self, fptr):
"""Write a Palette box to file.
"""
# Box length is usual header (8)
# + num entries NE (2) + num columns NC (1)
# + (bps/8, /signed) for each column (3) + bps * NC
# +
bytes_per_row = sum(self.bits_per_component) / 8
bytes_per_palette = bytes_per_row * self.palette.shape[0]
box_length = 8 + 3 + self.palette.shape[1] + bytes_per_palette
# Write the usual header.
write_buffer = struct.pack('>I4s',
int(box_length), self.box_id.encode())
fptr.write(write_buffer)
write_buffer = struct.pack('>HB', self.palette.shape[0],
self.palette.shape[1])
fptr.write(write_buffer)
bps_signed = [x - 1 for x in self.bits_per_component]
for j, item in enumerate(bps_signed):
if self.signed[j]:
bps_signed[j] |= 0x80
write_buffer = struct.pack('>' + 'B' * self.palette.shape[1],
*bps_signed)
fptr.write(write_buffer)
if self.bits_per_component[0] <= 8:
dtype = np.uint8
code = 'B'
elif self.bits_per_component[0] <= 16:
dtype = np.uint16
code = 'H'
elif self.bits_per_component[0] <= 32:
dtype = np.uint32
code = 'I'
fmt = '>' + code * self.palette.shape[1]
for row in self.palette:
write_buffer = struct.pack(fmt, *row)
fptr.write(write_buffer)
@staticmethod @staticmethod
def parse(fptr, offset, length): def parse(fptr, offset, length):
"""Parse palette box. """Parse palette box.
@ -1561,7 +1620,7 @@ class PaletteBox(Jp2kBox):
# Need to determine bps and signed or not # Need to determine bps and signed or not
read_buffer = fptr.read(num_columns) read_buffer = fptr.read(num_columns)
data = struct.unpack('>' + 'B' * num_columns, read_buffer) data = struct.unpack('>' + 'B' * num_columns, read_buffer)
bps = [((x & 0x07f) + 1) for x in data] bps = [((x & 0x7f) + 1) for x in data]
signed = [((x & 0x80) > 1) for x in data] signed = [((x & 0x80) > 1) for x in data]
fmt = '>' fmt = '>'

View file

@ -26,6 +26,7 @@ import tempfile
import uuid import uuid
from uuid import UUID from uuid import UUID
import xml.etree.cElementTree as ET import xml.etree.cElementTree as ET
import warnings
if sys.hexversion < 0x02070000: if sys.hexversion < 0x02070000:
import unittest2 as unittest import unittest2 as unittest
@ -496,6 +497,7 @@ class TestWrap(unittest.TestCase):
def setUp(self): def setUp(self):
self.j2kfile = glymur.data.goodstuff() self.j2kfile = glymur.data.goodstuff()
self.jp2file = glymur.data.nemo() self.jp2file = glymur.data.nemo()
self.jpxfile = glymur.data.jpxfile()
def tearDown(self): def tearDown(self):
pass pass
@ -557,6 +559,30 @@ class TestWrap(unittest.TestCase):
j2k.wrap(tfile.name) j2k.wrap(tfile.name)
self.verify_wrapped_raw(tfile.name) self.verify_wrapped_raw(tfile.name)
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
def test_palette(self):
"""basic test for rewrapping a jpx file"""
with warnings.catch_warnings():
# This file has a rreq mask length that we do not recognize.
warnings.simplefilter("ignore")
jpx = Jp2k(self.jpxfile)
idx = [0, 1, 3, 6]
boxes = [jpx.box[idx] for idx in [0, 1, 3, 6]]
with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile:
jp2 = jpx.wrap(tfile.name, boxes=boxes)
# Verify the outer boxes.
boxes = [box.box_id for box in jp2.box]
self.assertEqual(boxes, ['jP ', 'ftyp', 'jp2h', 'jp2c'])
# Verify the inside boxes.
boxes = [box.box_id for box in jp2.box[2].box]
self.assertEqual(boxes, ['ihdr', 'colr', 'pclr', 'cmap'])
expected_offsets = [0, 12, 40, 887]
for j, offset in enumerate(expected_offsets):
self.assertEqual(jp2.box[j].offset, offset)
@unittest.skipIf(os.name == "nt", "Temporary file issue on window.") @unittest.skipIf(os.name == "nt", "Temporary file issue on window.")
def test_wrap_jp2(self): def test_wrap_jp2(self):
"""basic test for rewrapping a jp2 file, no specified boxes""" """basic test for rewrapping a jp2 file, no specified boxes"""