diff --git a/glymur/jp2k.py b/glymur/jp2k.py index 264bd43..b7c82ee 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -77,11 +77,48 @@ class Jp2k(Jp2kBox): self.box = [] self._codec_format = None self._colorspace = None + self._shape = None # Parse the file for JP2/JPX contents only if we are reading it. if mode == 'rb': self.parse() + @property + def shape(self): + if self._shape is not None: + return self._shape + + cstr = self.get_codestream(header_only=True) + height = cstr.segment[1].ysiz + width = cstr.segment[1].xsiz + num_components = len(cstr.segment[1].xrsiz) + + # If JP2 and a palette box is present, then determine the shape from + # that. + if num_components == 1: + if self._codec_format == opj2.CODEC_J2K: + # There's no palette box or component mapping in a J2K file. + # The 3rd component in the shape would then be 1, but we'll + # ignore that. + self.shape = (height, width) + else: + jp2h = [box for box in self.box if box.box_id == 'jp2h'][0] + pclr = [box for box in jp2h.box if box.box_id == 'pclr'] + if len(pclr) == 0: + # No palette box, so just one component, which we will + # ignore. + self.shape = (height, width) + else: + self.shape = (height, width, len(pclr[0].signed)) + else: + self.shape = (height, width, num_components) + + return self._shape + + @shape.setter + def shape(self, shape): + self._shape = shape + def __repr__(self): msg = "glymur.Jp2k('{0}')".format(self.filename) return msg diff --git a/glymur/test/test_jp2k.py b/glymur/test/test_jp2k.py index 7c4e2a3..9a9fb72 100644 --- a/glymur/test/test_jp2k.py +++ b/glymur/test/test_jp2k.py @@ -476,6 +476,44 @@ class TestJp2k(unittest.TestCase): def tearDown(self): pass + def test_shape_jp2(self): + """verify shape attribute for JP2 file + """ + jp2 = Jp2k(self.jp2file) + self.assertEqual(jp2.shape, (1456, 2592, 3)) + + @unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") + def test_shape_greyscale_jp2(self): + """verify shape attribute for greyscale JP2 file + """ + with warnings.catch_warnings(): + # Suppress a warning due to bad compatibility list entry. + warnings.simplefilter("ignore") + jfile = opj_data_file('input/conformance/file4.jp2') + jp2 = Jp2k(jfile) + self.assertEqual(jp2.shape, (512, 768)) + + @unittest.skipIf(OPJ_DATA_ROOT is None, + "OPJ_DATA_ROOT environment variable not set") + def test_shape_single_channel_j2k(self): + """verify shape attribute for single channel J2K file + """ + jfile = opj_data_file('input/conformance/p0_01.j2k') + jp2 = Jp2k(jfile) + self.assertEqual(jp2.shape, (128, 128)) + + def test_shape_j2k(self): + """verify shape attribute for J2K file + """ + j2k = Jp2k(self.j2kfile) + self.assertEqual(j2k.shape, (800, 480, 3)) + + def test_shape_jpx_jp2(self): + """verify shape attribute for JPX file with JP2 compatibility + """ + jpx = Jp2k(self.jpxfile) + self.assertEqual(jpx.shape, (1024, 1024, 3)) @unittest.skipIf(os.name == "nt", "Unexplained failure on windows") def test_irreversible(self): diff --git a/glymur/test/test_opj_suite_dump.py b/glymur/test/test_opj_suite_dump.py index 76dc444..8201c9f 100644 --- a/glymur/test/test_opj_suite_dump.py +++ b/glymur/test/test_opj_suite_dump.py @@ -3052,7 +3052,7 @@ class TestSuiteWarns(MetadataBase): self.assertEqual(codestream.segment[1].yrsiz[2], 2) def test_NR_file4_dump(self): - # One 8-bit component in the sRGB-grey colourspace. + # One 8-bit component in the grey colourspace. jfile = opj_data_file('input/conformance/file4.jp2') with self.assertWarns(UserWarning): jp2 = Jp2k(jfile)