Write support for Palette, Component Mapping box. Closes #149
This commit is contained in:
parent
60656be667
commit
a3fb3ec037
3 changed files with 95 additions and 9 deletions
11
CHANGES.txt
11
CHANGES.txt
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 = '>'
|
||||
|
|
|
|||
|
|
@ -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"""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue