From 19336fc15e884c080d19a0fd7de6a4a942cc4266 Mon Sep 17 00:00:00 2001 From: John Evans Date: Sun, 23 Jun 2013 07:41:31 -0400 Subject: [PATCH] Starting on the _write method. --- glymur/jp2box.py | 41 +++++++++++++++++++++++++++++++++++--- glymur/jp2k.py | 23 ++++++++++++++++++++- glymur/test/__init__.py | 1 + glymur/test/test_jp2box.py | 29 ++++++++++++++++++++++++++- 4 files changed, 89 insertions(+), 5 deletions(-) diff --git a/glymur/jp2box.py b/glymur/jp2box.py index 3dfa1b8..e6a7921 100644 --- a/glymur/jp2box.py +++ b/glymur/jp2box.py @@ -63,7 +63,8 @@ class Jp2kBox(object): def _write(self, f): """Must be implemented in a subclass. """ - raise NotImplementedError("Not supported for this box.") + msg = "Not supported for {0} box.".format(self.longname) + raise NotImplementedError(msg) def _parse_superbox(self, f): """Parse a superbox (box consisting of nothing but other boxes. @@ -617,8 +618,8 @@ class FileTypeBox(Jp2kBox): self.brand = 'jp2 ' if 'minor_version' not in kwargs.keys(): self.minor_version = 0 - if 'compatibility_box' not in kwargs.keys(): - self.compatibility_box = ['jp2 '] + if 'compatibility_list' not in kwargs.keys(): + self.compatibility_list = ['jp2 '] def __str__(self): lst = [Jp2kBox.__str__(self), @@ -629,6 +630,18 @@ class FileTypeBox(Jp2kBox): return msg + def _write(self, f): + """Write a File Type box to file. + """ + length = 16 + 4*len(self.compatibility_list) + f.write(struct.pack('>I', length)) + f.write('ftyp'.encode()) + f.write(self.brand.encode()) + f.write(struct.pack('>I', self.minor_version)) + + for item in self.compatibility_list: + f.write(item.encode()) + @staticmethod def _parse(f, id, offset, length): """Parse JPEG 2000 file type box. @@ -876,6 +889,21 @@ class JP2HeaderBox(Jp2kBox): msg += ''.join(strs) return msg + def _write(self, f): + """Write a JP2 Header box to file. + """ + # Write the contained boxes, then come back and write the length. + orig_pos = f.tell() + f.write(struct.pack('>I', 0)) + f.write('jp2h'.encode()) + for box in self.box: + box._write(f) + + end_pos = f.tell() + f.seek(orig_pos) + f.write(struct.pack('>I', end_pos - orig_pos)) + f.seek(end_pos) + @staticmethod def _parse(f, id, offset, length): """Parse JPEG 2000 header box. @@ -938,6 +966,13 @@ class JPEG2000SignatureBox(Jp2kBox): msg = msg.format(*self.signature) return msg + def _write(self, f): + """Write a JPEG 2000 Signature box to file. + """ + f.write(struct.pack('>I', 12)) + f.write(self.id.encode()) + f.write(struct.pack('>BBBB', *self.signature)) + @staticmethod def _parse(f, id, offset, length): """Parse JPEG 2000 signature box. diff --git a/glymur/jp2k.py b/glymur/jp2k.py index b09271e..fcd0893 100644 --- a/glymur/jp2k.py +++ b/glymur/jp2k.py @@ -17,7 +17,8 @@ import numpy as np from .codestream import Codestream from .core import progression_order -from .jp2box import Jp2kBox +from .core import SRGB +from .jp2box import * from .lib import openjp2 as opj2 _cspace_map = {'rgb': opj2._CLRSPC_SRGB, @@ -398,6 +399,26 @@ class Jp2k(Jp2kBox): self._parse() + def tojp2(self, filename): + """Write a codestream, wrapping it in the JP2 format. + + Parameters + ---------- + filename : str + JP2 file to be created from a raw codestream. + """ + boxes = [JPEG2000SignatureBox(), + FileTypeBox(), + JP2HeaderBox(), + ContiguousCodestreamBox()] + boxes[2].box = [ImageHeaderBox(height=1456, + width=2592, + num_components=3), + ColourSpecificationBox(colorspace=SRGB)] + with open(filename, 'wb') as fp: + for box in boxes: + box._write(fp) + def read(self, reduce=0, layer=0, area=None, tile=None, verbose=False): """Read a JPEG 2000 image. diff --git a/glymur/test/__init__.py b/glymur/test/__init__.py index 1e19d88..aec2d72 100644 --- a/glymur/test/__init__.py +++ b/glymur/test/__init__.py @@ -6,3 +6,4 @@ from .test_printing import TestPrinting as printing from .test_opj_suite import TestSuite as suite from .test_opj_suite_write import TestSuiteWrite as suitew from .test_opj_suite_neg import TestSuiteNegative as suiteneg +from .test_jp2box import TestJp2Boxes as box diff --git a/glymur/test/test_jp2box.py b/glymur/test/test_jp2box.py index 93b4be0..bb068d2 100644 --- a/glymur/test/test_jp2box.py +++ b/glymur/test/test_jp2box.py @@ -1,10 +1,12 @@ import doctest +import tempfile import unittest import numpy as np import pkg_resources import glymur +from glymur import Jp2k from glymur.jp2box import * @@ -18,6 +20,26 @@ def load_tests(loader, tests, ignore): "Missing openjp2 library.") class TestJp2Boxes(unittest.TestCase): + @classmethod + def setUpClass(cls): + # We need a raw codestream, so use the one in nemo.jp2. + jp2file = pkg_resources.resource_filename(glymur.__name__, + "data/nemo.jp2") + j = Jp2k(jp2file) + c = [box for box in j.box if box.id == 'jp2c'][0] + + with tempfile.NamedTemporaryFile(suffix='.jp2', delete=False) as ofile: + with open(jp2file, 'rb') as ifile: + # Everything up until the jp2c box. + ifile.seek(c.offset+8) + ofile.write(ifile.read(c.length)) + + cls.raw_codestream = ofile.name + + @classmethod + def tearDownClass(cls): + os.unlink(cls.raw_codestream) + def setUp(self): self.jp2file = pkg_resources.resource_filename(glymur.__name__, "data/nemo.jp2") @@ -35,7 +57,7 @@ class TestJp2Boxes(unittest.TestCase): b = glymur.jp2box.FileTypeBox() self.assertEqual(b.brand, 'jp2 ') self.assertEqual(b.minor_version, 0) - self.assertEqual(b.compatibility_box, ['jp2 ']) + self.assertEqual(b.compatibility_list, ['jp2 ']) def test_default_ImageHeaderBox(self): # Should be able to instantiate an image header box. @@ -85,5 +107,10 @@ class TestJp2Boxes(unittest.TestCase): self.assertEqual(b.id, 'jp2c') self.assertEqual(b.main_header, []) + def test_tojp2(self): + j = Jp2k(self.raw_codestream) + with tempfile.NamedTemporaryFile(suffix=".jp2") as tfile: + j.tojp2(tfile.name) + if __name__ == "__main__": unittest.main()