Merge branch 'issue46' into devel
This commit is contained in:
commit
eb46457e15
5 changed files with 108 additions and 139 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
Jun 15, 2013 - v0.1.9 Reading ICC profile headers. Exif dictionaries changed
|
Jun 15, 2013 - v0.1.9 Reading ICC profile headers as ordered dicts. Exif
|
||||||
to ordered dicts.
|
dictionaries changed to ordered dicts.
|
||||||
|
|
||||||
Jun 14, 2013 - v0.1.8 Added reduce=-1 option to get lowest resolution
|
Jun 14, 2013 - v0.1.8 Added reduce=-1 option to get lowest resolution
|
||||||
thumbnail.
|
thumbnail.
|
||||||
|
|
|
||||||
145
glymur/jp2box.py
145
glymur/jp2box.py
|
|
@ -155,9 +155,9 @@ class ColourSpecificationBox(Jp2kBox):
|
||||||
colorspace : int or None
|
colorspace : int or None
|
||||||
Enumerated colorspace, corresponds to one of 'sRGB', 'greyscale', or
|
Enumerated colorspace, corresponds to one of 'sRGB', 'greyscale', or
|
||||||
'YCC'. If not None, then icc_profile must be None.
|
'YCC'. If not None, then icc_profile must be None.
|
||||||
icc_profile : _ICCProfile or None
|
icc_profile : dict
|
||||||
ICC profile header according to ICC profile specification. If not
|
ICC profile header according to ICC profile specification. If
|
||||||
None, then color_space must be None.
|
colorspace is not None, then icc_profile must be empty.
|
||||||
"""
|
"""
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
Jp2kBox.__init__(self, id='', longname='Colour Specification')
|
Jp2kBox.__init__(self, id='', longname='Colour Specification')
|
||||||
|
|
@ -176,7 +176,9 @@ class ColourSpecificationBox(Jp2kBox):
|
||||||
x = _colorspace_map_display[self.colorspace]
|
x = _colorspace_map_display[self.colorspace]
|
||||||
msg += '\n Colorspace: {0}'.format(x)
|
msg += '\n Colorspace: {0}'.format(x)
|
||||||
else:
|
else:
|
||||||
msg += '\n ICC Profile: {0}'.format(self.icc_profile.__str__())
|
x = pprint.pformat(self.icc_profile)
|
||||||
|
lines = [' ' * 8 + y for y in x.split('\n')]
|
||||||
|
msg += '\n ICC Profile:\n{0}'.format('\n'.join(lines))
|
||||||
|
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
|
@ -229,7 +231,7 @@ class ColourSpecificationBox(Jp2kBox):
|
||||||
kwargs['icc_profile'] = None
|
kwargs['icc_profile'] = None
|
||||||
else:
|
else:
|
||||||
icc_profile = _ICCProfile(f.read(n))
|
icc_profile = _ICCProfile(f.read(n))
|
||||||
kwargs['icc_profile'] = icc_profile
|
kwargs['icc_profile'] = icc_profile.header
|
||||||
|
|
||||||
box = ColourSpecificationBox(**kwargs)
|
box = ColourSpecificationBox(**kwargs)
|
||||||
return box
|
return box
|
||||||
|
|
@ -279,110 +281,71 @@ class _ICCProfile:
|
||||||
|
|
||||||
def __init__(self, buffer):
|
def __init__(self, buffer):
|
||||||
self._raw_buffer = buffer
|
self._raw_buffer = buffer
|
||||||
|
header = collections.OrderedDict()
|
||||||
|
|
||||||
self.size, = struct.unpack('>I', self._raw_buffer[0:4])
|
data = struct.unpack('>IIBB', self._raw_buffer[0:10])
|
||||||
self.preferred_cmm_type, = struct.unpack('>I', self._raw_buffer[4:8])
|
header['Size'] = data[0]
|
||||||
|
header['Preferred CMM Type'] = data[1]
|
||||||
|
major = data[2]
|
||||||
|
minor = (data[3] & 0xf0) >> 4
|
||||||
|
bugfix = (data[3] & 0x0f)
|
||||||
|
header['Version'] = '{0}.{1}.{2}'.format(major, minor, bugfix)
|
||||||
|
|
||||||
data = struct.unpack('>BB', self._raw_buffer[8:10])
|
header['Device Class'] = self.profile_class[self._raw_buffer[12:16]]
|
||||||
major = data[0]
|
header['Color Space'] = self.colour_space_dict[self._raw_buffer[16:20]]
|
||||||
minor = (data[1] & 0xf0) >> 4
|
data = self.colour_space_dict[self._raw_buffer[20:24]]
|
||||||
bugfix = (data[1] & 0x0f)
|
header['Connection Space'] = data
|
||||||
self.version = '{0}.{1}.{2}'.format(major, minor, bugfix)
|
|
||||||
|
|
||||||
self.device_class = self.profile_class[self._raw_buffer[12:16]]
|
|
||||||
self.colour_space = self.colour_space_dict[self._raw_buffer[16:20]]
|
|
||||||
self.connection_space = self.colour_space_dict[self._raw_buffer[20:24]]
|
|
||||||
|
|
||||||
data = struct.unpack('>HHHHHH', self._raw_buffer[24:36])
|
data = struct.unpack('>HHHHHH', self._raw_buffer[24:36])
|
||||||
self.datetime = datetime.datetime(*data)
|
header['Datetime'] = datetime.datetime(*data)
|
||||||
self.file_signature = buffer[36:40].decode('utf-8')
|
header['File Signature'] = buffer[36:40].decode('utf-8')
|
||||||
if buffer[40:44] == b'\x00\x00\x00\x00':
|
if buffer[40:44] == b'\x00\x00\x00\x00':
|
||||||
self.platform = 'unrecognized'
|
header['Platform'] = 'unrecognized'
|
||||||
else:
|
else:
|
||||||
self.platform = buffer[40:44].decode('utf-8')
|
header['Platform'] = buffer[40:44].decode('utf-8')
|
||||||
|
|
||||||
self.flags, = struct.unpack('>I', buffer[44:48])
|
x, = struct.unpack('>I', buffer[44:48])
|
||||||
|
y = 'embedded, ' if x & 0x01 else 'not embedded, '
|
||||||
|
y += 'cannot ' if x & 0x02 else 'can '
|
||||||
|
y += 'be used independently'
|
||||||
|
header['Flags'] = y
|
||||||
|
|
||||||
self.device_manufacturer = buffer[48:52].decode('utf-8')
|
header['Device Manufacturer'] = buffer[48:52].decode('utf-8')
|
||||||
if buffer[52:56] == b'\x00\x00\x00\x00':
|
if buffer[52:56] == b'\x00\x00\x00\x00':
|
||||||
self.device_model = ''
|
device_model = ''
|
||||||
else:
|
else:
|
||||||
self.device_model = buffer[52:56].decode('utf-8')
|
device_model = buffer[52:56].decode('utf-8')
|
||||||
self.device_attributes, = struct.unpack('>Q', buffer[56:64])
|
header['Device Model'] = device_model
|
||||||
self.rendering_intent, = struct.unpack('>I', buffer[64:68])
|
|
||||||
|
x, = struct.unpack('>Q', buffer[56:64])
|
||||||
|
y = 'transparency, ' if x & 0x01 else 'reflective, '
|
||||||
|
y += 'matte, ' if x & 0x02 else 'glossy, '
|
||||||
|
y += 'negative ' if x & 0x04 else 'positive '
|
||||||
|
y += 'media polarity, '
|
||||||
|
y += 'black and white media' if x & 0x08 else 'color media'
|
||||||
|
header['Device Attributes'] = y
|
||||||
|
|
||||||
|
x, = struct.unpack('>I', buffer[64:68])
|
||||||
|
try:
|
||||||
|
header['Rendering Intent'] = self.rendering_intent_dict[x]
|
||||||
|
except KeyError:
|
||||||
|
header['Rendering Intent'] = 'unknown'
|
||||||
|
|
||||||
data = struct.unpack('>iii', buffer[68:80])
|
data = struct.unpack('>iii', buffer[68:80])
|
||||||
self.illuminant = np.array(data, dtype=np.float64) / 65536
|
header['Illuminant'] = np.array(data, dtype=np.float64) / 65536
|
||||||
|
|
||||||
if buffer[80:84] == b'\x00\x00\x00\x00':
|
if buffer[80:84] == b'\x00\x00\x00\x00':
|
||||||
self.creator = 'unrecognized'
|
creator = 'unrecognized'
|
||||||
else:
|
else:
|
||||||
self.creator = buffer[80:84].decode('utf-8')
|
creator = buffer[80:84].decode('utf-8')
|
||||||
|
header['Creator'] = creator
|
||||||
|
|
||||||
self.profile_id = buffer[84:100]
|
if header['Version'][0] == '4':
|
||||||
self.reserved = buffer[100:127]
|
header['Profile Id'] = buffer[84:100]
|
||||||
|
|
||||||
def __str__(self):
|
# Final 27 bytes are reserved.
|
||||||
msg = "\n Size: {0}"
|
|
||||||
msg += "\n Preferred CMM type: {1:x}"
|
|
||||||
msg += "\n Version: {2}"
|
|
||||||
msg += "\n Device class signature: {3}"
|
|
||||||
msg += "\n Color space: {4}"
|
|
||||||
msg += "\n Connection space: {5}"
|
|
||||||
msg += "\n Creation time: {6}"
|
|
||||||
msg += "\n File signature: {7}"
|
|
||||||
msg += "\n Platform: {8}"
|
|
||||||
msg += "\n Flags: {9}"
|
|
||||||
msg += "\n Device manufacturer: {10}"
|
|
||||||
msg += "\n Device model: {11}"
|
|
||||||
msg += "\n Device attributes: {12}"
|
|
||||||
msg += "\n Rendering intent: {13}"
|
|
||||||
msg += "\n Illuminant: {14}"
|
|
||||||
msg += "\n Creator signature: {15}"
|
|
||||||
|
|
||||||
if self.flags & 0x01:
|
self.header = header
|
||||||
flag_string = 'embedded, '
|
|
||||||
else:
|
|
||||||
flag_string = 'not embedded, '
|
|
||||||
if self.flags & 0x02:
|
|
||||||
flag_string += 'cannot be used independently'
|
|
||||||
else:
|
|
||||||
flag_string += 'can be used independently'
|
|
||||||
|
|
||||||
if self.device_attributes & 0x01:
|
|
||||||
attr_string = 'transparency, '
|
|
||||||
else:
|
|
||||||
attr_string = 'reflective, '
|
|
||||||
if self.device_attributes & 0x02:
|
|
||||||
attr_string += 'matte, '
|
|
||||||
else:
|
|
||||||
attr_string += 'glossy, '
|
|
||||||
if self.device_attributes & 0x04:
|
|
||||||
attr_string += 'negative media polarity, '
|
|
||||||
else:
|
|
||||||
attr_string += 'positive media polarity, '
|
|
||||||
if self.device_attributes & 0x08:
|
|
||||||
attr_string += 'black and white media'
|
|
||||||
else:
|
|
||||||
attr_string += 'color media'
|
|
||||||
|
|
||||||
msg = msg.format(self.size,
|
|
||||||
self.preferred_cmm_type,
|
|
||||||
self.version,
|
|
||||||
self.device_class,
|
|
||||||
self.colour_space,
|
|
||||||
self.connection_space,
|
|
||||||
self.datetime,
|
|
||||||
self.file_signature,
|
|
||||||
self.platform,
|
|
||||||
flag_string,
|
|
||||||
self.device_manufacturer,
|
|
||||||
self.device_model,
|
|
||||||
attr_string,
|
|
||||||
self.rendering_intent_dict[self.rendering_intent],
|
|
||||||
self.illuminant,
|
|
||||||
self.creator)
|
|
||||||
return(msg)
|
|
||||||
|
|
||||||
|
|
||||||
class ComponentDefinitionBox(Jp2kBox):
|
class ComponentDefinitionBox(Jp2kBox):
|
||||||
|
|
|
||||||
|
|
@ -35,32 +35,31 @@ class TestICC(unittest.TestCase):
|
||||||
filename = os.path.join(data_root, 'input/conformance/file5.jp2')
|
filename = os.path.join(data_root, 'input/conformance/file5.jp2')
|
||||||
j = Jp2k(filename)
|
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)
|
||||||
self.assertEqual(profile.version, '2.2.0')
|
self.assertEqual(profile['Version'], '2.2.0')
|
||||||
self.assertEqual(profile.device_class, 'input device profile')
|
self.assertEqual(profile['Device Class'], 'input device profile')
|
||||||
self.assertEqual(profile.colour_space, 'RGB')
|
self.assertEqual(profile['Color Space'], 'RGB')
|
||||||
self.assertEqual(profile.datetime,
|
self.assertEqual(profile['Datetime'],
|
||||||
datetime.datetime(2001, 8, 30, 13, 32, 37))
|
datetime.datetime(2001, 8, 30, 13, 32, 37))
|
||||||
self.assertEqual(profile.file_signature, 'acsp')
|
self.assertEqual(profile['File Signature'], 'acsp')
|
||||||
self.assertEqual(profile.platform, 'unrecognized')
|
self.assertEqual(profile['Platform'], 'unrecognized')
|
||||||
self.assertTrue(profile.flags & 0x01) # embedded
|
self.assertEqual(profile['Flags'],
|
||||||
self.assertFalse(profile.flags & 0x02) # use anywhere
|
'embedded, can be used independently')
|
||||||
|
|
||||||
self.assertEqual(profile.device_manufacturer, 'KODA')
|
self.assertEqual(profile['Device Manufacturer'], 'KODA')
|
||||||
self.assertEqual(profile.device_model, 'ROMM')
|
self.assertEqual(profile['Device Model'], 'ROMM')
|
||||||
|
|
||||||
self.assertFalse(profile.device_attributes & 0x01) # reflective
|
self.assertEqual(profile['Device Attributes'],
|
||||||
self.assertFalse(profile.device_attributes & 0x02) # glossy
|
'reflective, glossy, positive media polarity, '
|
||||||
self.assertFalse(profile.device_attributes & 0x04) # positive
|
+ 'color media')
|
||||||
self.assertFalse(profile.device_attributes & 0x08) # colour
|
self.assertEqual(profile['Rendering Intent'], 'perceptual')
|
||||||
self.assertEqual(profile.rendering_intent & 0x00ff, 0) # perceptual
|
|
||||||
|
|
||||||
np.testing.assert_almost_equal(profile.illuminant,
|
np.testing.assert_almost_equal(profile['Illuminant'],
|
||||||
(0.964203, 1.000000, 0.824905),
|
(0.964203, 1.000000, 0.824905),
|
||||||
decimal=6)
|
decimal=6)
|
||||||
|
|
||||||
self.assertEqual(profile.creator, 'JPEG')
|
self.assertEqual(profile['Creator'], 'JPEG')
|
||||||
|
|
||||||
@unittest.skipIf(sys.hexversion < 0x03020000,
|
@unittest.skipIf(sys.hexversion < 0x03020000,
|
||||||
"Uses features introduced in 3.2.")
|
"Uses features introduced in 3.2.")
|
||||||
|
|
|
||||||
|
|
@ -3579,7 +3579,7 @@ class TestSuite(unittest.TestCase):
|
||||||
self.assertEqual(jp2.box[3].box[1].method, 2) # enumerated
|
self.assertEqual(jp2.box[3].box[1].method, 2) # enumerated
|
||||||
self.assertEqual(jp2.box[3].box[1].precedence, 0)
|
self.assertEqual(jp2.box[3].box[1].precedence, 0)
|
||||||
self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact
|
self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact
|
||||||
self.assertEqual(jp2.box[3].box[1].icc_profile.size, 546)
|
self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 546)
|
||||||
self.assertIsNone(jp2.box[3].box[1].colorspace)
|
self.assertIsNone(jp2.box[3].box[1].colorspace)
|
||||||
|
|
||||||
# Jp2 Header
|
# Jp2 Header
|
||||||
|
|
@ -3674,7 +3674,7 @@ class TestSuite(unittest.TestCase):
|
||||||
self.assertEqual(jp2.box[3].box[1].method, 2)
|
self.assertEqual(jp2.box[3].box[1].method, 2)
|
||||||
self.assertEqual(jp2.box[3].box[1].precedence, 0)
|
self.assertEqual(jp2.box[3].box[1].precedence, 0)
|
||||||
self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact
|
self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact
|
||||||
self.assertEqual(jp2.box[3].box[1].icc_profile.size, 13332)
|
self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 13332)
|
||||||
self.assertIsNone(jp2.box[3].box[1].colorspace)
|
self.assertIsNone(jp2.box[3].box[1].colorspace)
|
||||||
|
|
||||||
# Jp2 Header
|
# Jp2 Header
|
||||||
|
|
@ -3723,7 +3723,7 @@ class TestSuite(unittest.TestCase):
|
||||||
self.assertEqual(jp2.box[2].box[1].method, 2) # enumerated
|
self.assertEqual(jp2.box[2].box[1].method, 2) # enumerated
|
||||||
self.assertEqual(jp2.box[2].box[1].precedence, 0)
|
self.assertEqual(jp2.box[2].box[1].precedence, 0)
|
||||||
self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact
|
self.assertEqual(jp2.box[2].box[1].approximation, 1) # JPX exact
|
||||||
self.assertEqual(jp2.box[2].box[1].icc_profile.size, 414)
|
self.assertEqual(jp2.box[2].box[1].icc_profile['Size'], 414)
|
||||||
self.assertIsNone(jp2.box[2].box[1].colorspace)
|
self.assertIsNone(jp2.box[2].box[1].colorspace)
|
||||||
|
|
||||||
# XML box
|
# XML box
|
||||||
|
|
@ -6630,7 +6630,7 @@ class TestSuite(unittest.TestCase):
|
||||||
self.assertEqual(jp2.box[3].box[1].method, 3) # any icc
|
self.assertEqual(jp2.box[3].box[1].method, 3) # any icc
|
||||||
self.assertEqual(jp2.box[3].box[1].precedence, 2)
|
self.assertEqual(jp2.box[3].box[1].precedence, 2)
|
||||||
self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact
|
self.assertEqual(jp2.box[3].box[1].approximation, 1) # JPX exact
|
||||||
self.assertEqual(jp2.box[3].box[1].icc_profile.size, 1328)
|
self.assertEqual(jp2.box[3].box[1].icc_profile['Size'], 1328)
|
||||||
self.assertIsNone(jp2.box[3].box[1].colorspace)
|
self.assertIsNone(jp2.box[3].box[1].colorspace)
|
||||||
|
|
||||||
# UUID boxes. All mentioned in the RREQ box.
|
# UUID boxes. All mentioned in the RREQ box.
|
||||||
|
|
|
||||||
|
|
@ -775,6 +775,8 @@ class TestPrinting(unittest.TestCase):
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
|
@unittest.skipIf(sys.hexversion < 0x03000000,
|
||||||
|
"Ordered dicts not printing well in 2.7")
|
||||||
@unittest.skipIf(data_root is None,
|
@unittest.skipIf(data_root is None,
|
||||||
"OPJ_DATA_ROOT environment variable not set")
|
"OPJ_DATA_ROOT environment variable not set")
|
||||||
def test_jpx_approximation_with_icc_profile(self):
|
def test_jpx_approximation_with_icc_profile(self):
|
||||||
|
|
@ -785,29 +787,32 @@ class TestPrinting(unittest.TestCase):
|
||||||
|
|
||||||
print(j.box[3].box[1])
|
print(j.box[3].box[1])
|
||||||
actual = sys.stdout.getvalue().strip()
|
actual = sys.stdout.getvalue().strip()
|
||||||
lines = ['Colour Specification Box (colr) @ (179, 1339)',
|
lines = ["Colour Specification Box (colr) @ (179, 1339)",
|
||||||
' Method: any ICC profile',
|
" Method: any ICC profile",
|
||||||
' Precedence: 2',
|
" Precedence: 2",
|
||||||
' Approximation: accurately represents '
|
" Approximation: accurately represents "
|
||||||
+ 'correct colorspace definition',
|
+ "correct colorspace definition",
|
||||||
' ICC Profile: ',
|
" ICC Profile:",
|
||||||
' Size: 1328',
|
" {'Size': 1328,",
|
||||||
' Preferred CMM type: 6170706c',
|
" 'Preferred CMM Type': 1634758764,",
|
||||||
' Version: 2.2.0',
|
" 'Version': '2.2.0',",
|
||||||
' Device class signature: display device profile',
|
" 'Device Class': 'display device profile',",
|
||||||
' Color space: RGB',
|
" 'Color Space': 'RGB',",
|
||||||
' Connection space: XYZ',
|
" 'Connection Space': 'XYZ',",
|
||||||
' Creation time: 2009-02-25 11:26:11',
|
" 'Datetime': "
|
||||||
' File signature: acsp',
|
+ "datetime.datetime(2009, 2, 25, 11, 26, 11),",
|
||||||
' Platform: APPL',
|
" 'File Signature': 'acsp',",
|
||||||
' Flags: not embedded, can be used independently',
|
" 'Platform': 'APPL',",
|
||||||
' Device manufacturer: appl',
|
" 'Flags': 'not embedded, "
|
||||||
' Device model: ',
|
+ "can be used independently',",
|
||||||
' Device attributes: '
|
" 'Device Manufacturer': 'appl',",
|
||||||
+ 'reflective, glossy, positive media polarity, color media',
|
" 'Device Model': '',",
|
||||||
' Rendering intent: perceptual',
|
" 'Device Attributes': 'reflective, glossy, "
|
||||||
' Illuminant: [ 0.96420288 1. 0.8249054 ]',
|
+ "positive media polarity, color media',",
|
||||||
' Creator signature: appl']
|
" 'Rendering Intent': 'perceptual',",
|
||||||
|
" 'Illuminant': array([ 0.96420288, 1. ,"
|
||||||
|
+ " 0.8249054 ]),",
|
||||||
|
" 'Creator': 'appl'}"]
|
||||||
|
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
@ -828,6 +833,8 @@ class TestPrinting(unittest.TestCase):
|
||||||
expected = '\n'.join(lines)
|
expected = '\n'.join(lines)
|
||||||
self.assertEqual(actual, expected)
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
|
@unittest.skipIf(sys.hexversion < 0x03000000,
|
||||||
|
"Ordered dicts not printing well in 2.7")
|
||||||
def test_exif_uuid(self):
|
def test_exif_uuid(self):
|
||||||
j = glymur.Jp2k(self.jp2file)
|
j = glymur.Jp2k(self.jp2file)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue