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,
fragment table, and fragment list boxes. 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.
Feb 03, 2014 - Added write support for Palette and Component Mapping boxes.
Added read support for JPX free, number list, data reference, fragment
table, and fragment list boxes. 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
length is unsupported.

View file

@ -15,6 +15,7 @@ References
import copy
import datetime
import io
import math
import os
import pprint
@ -23,6 +24,7 @@ import sys
import uuid
import warnings
import xml.etree.cElementTree as ET
if sys.hexversion < 0x02070000:
# pylint: disable=F0401,E0611
from ordereddict import OrderedDict
@ -719,6 +721,20 @@ class ComponentMappingBox(Jp2kBox):
msg = msg.format(self.component_index[k], k)
return msg
def write(self, fptr):
"""Write a Component Mapping box to file.
"""
length = 8 + 4 * len(self.component_index)
write_buffer = struct.pack('>I4s', length, 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
def parse(fptr, offset, length):
"""Parse component mapping box.
@ -1527,9 +1543,9 @@ class PaletteBox(Jp2kBox):
self.offset = offset
def __repr__(self):
msg = "glymur.jp2box.PaletteBox({0}, bits_per_component={1}, "
msg += "signed={2})"
msg = msg.format(self.palette, self.bits_per_component, self.signed)
msg = "glymur.jp2box.PaletteBox(ndarray, bits_per_component={0}, "
msg += "signed={1})"
msg = msg.format(self.bits_per_component, self.signed)
return msg
def __str__(self):
@ -1537,6 +1553,49 @@ class PaletteBox(Jp2kBox):
msg += '\n Size: ({0} x {1})'.format(*self.palette.shape)
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
def parse(fptr, offset, length):
"""Parse palette box.
@ -1561,7 +1620,7 @@ class PaletteBox(Jp2kBox):
# Need to determine bps and signed or not
read_buffer = fptr.read(num_columns)
data = struct.unpack('>' + 'B' * num_columns, read_buffer)
bps = [((x & 0x07f) + 1) for x in data]
bps = [((x & 0x7f) + 1) for x in data]
signed = [((x & 0x80) > 1) for x in data]
fmt = '>'

View file

@ -26,6 +26,7 @@ import tempfile
import uuid
from uuid import UUID
import xml.etree.cElementTree as ET
import warnings
if sys.hexversion < 0x02070000:
import unittest2 as unittest
@ -496,6 +497,7 @@ class TestWrap(unittest.TestCase):
def setUp(self):
self.j2kfile = glymur.data.goodstuff()
self.jp2file = glymur.data.nemo()
self.jpxfile = glymur.data.jpxfile()
def tearDown(self):
pass
@ -557,6 +559,30 @@ class TestWrap(unittest.TestCase):
j2k.wrap(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.")
def test_wrap_jp2(self):
"""basic test for rewrapping a jp2 file, no specified boxes"""