From c617decf25917e900879a3b2beb89ea651a7303f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Piel?= Date: Tue, 16 Jan 2024 23:38:33 +0100 Subject: [PATCH] ctypes: move inline test cases to a dedicated testing module Instead of keeping the test cases at the end of libtiff_ctypes.py, place them in test_libtiff_ctypes.py. The main advantage is that they should be called automatically during CI. It also reduces the actual library code, which avoids unnecessarily loading this code every time the library is used. --- libtiff/libtiff_ctypes.py | 603 --------------------------- libtiff/tests/test_libtiff_ctypes.py | 563 ++++++++++++++++++++++++- 2 files changed, 562 insertions(+), 604 deletions(-) diff --git a/libtiff/libtiff_ctypes.py b/libtiff/libtiff_ctypes.py index 07edd15..06bfbb4 100644 --- a/libtiff/libtiff_ctypes.py +++ b/libtiff/libtiff_ctypes.py @@ -2012,606 +2012,3 @@ def suppress_warnings(): def suppress_errors(): libtiff.TIFFSetErrorHandler(_null_error_handler) - -def _test_custom_tags(): - def _tag_write(): - a = TIFF.open("/tmp/libtiff_test_custom_tags.tif", "w") - - a.SetField("ARTIST", b"MY NAME") - a.SetField("LibtiffTestByte", 42) - a.SetField("LibtiffTeststr", b"FAKE") - a.SetField("LibtiffTestuint16", 42) - a.SetField("LibtiffTestMultiuint32", (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) - a.SetField("LibtiffTestBytes", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) - a.SetField("XPOSITION", 42.0) - a.SetField("PRIMARYCHROMATICITIES", (1.0, 2, 3, 4, 5, 6)) - - arr = np.ones((512, 512), dtype=np.uint8) - arr[:, :] = 255 - a.write_image(arr) - - print("Tag Write: SUCCESS") - - def _tag_read(): - a = TIFF.open("/tmp/libtiff_test_custom_tags.tif", "r") - - tmp = a.read_image() - assert tmp.shape == ( - 512, - 512), "Image read was wrong shape (%r instead of (512,512))" % ( - tmp.shape,) - tmp = a.GetField("XPOSITION") - assert tmp == 42.0, "XPosition was not read as 42.0" - tmp = a.GetField("ARTIST") - assert tmp == b"MY NAME", "Artist was not read as 'MY NAME'" - tmp = a.GetField("LibtiffTestByte") - assert tmp == 42, "LibtiffTestbyte was not read as 42" - tmp = a.GetField("LibtiffTestuint16") - assert tmp == 42, "LibtiffTestuint16 was not read as 42" - tmp = a.GetField("LibtiffTestMultiuint32") - assert tmp == [1, 2, 3, 4, 5, 6, 7, 8, 9, - 10], "LibtiffTestMultiuint32 was not read as [1,2,3," \ - "4,5,6,7,8,9,10]" - tmp = a.GetField("LibtiffTeststr") - assert tmp == b"FAKE", "LibtiffTeststr was not read as 'FAKE'" - tmp = a.GetField("LibtiffTestBytes") - assert tmp == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - tmp = a.GetField("PRIMARYCHROMATICITIES") - assert tmp == [1.0, 2.0, 3.0, 4.0, 5.0, - 6.0], "PrimaryChromaticities was not read as [1.0," \ - "2.0,3.0,4.0,5.0,6.0]" - print("Tag Read: SUCCESS") - - # Define a C structure that says how each tag should be used - test_tags = [ - TIFFFieldInfo(40100, 1, 1, TIFFDataType.TIFF_BYTE, FIELD_CUSTOM, True, - False, b"LibtiffTestByte"), - TIFFFieldInfo(40103, 10, 10, TIFFDataType.TIFF_LONG, FIELD_CUSTOM, - True, False, b"LibtiffTestMultiuint32"), - TIFFFieldInfo(40102, 1, 1, TIFFDataType.TIFF_SHORT, FIELD_CUSTOM, True, - False, b"LibtiffTestuint16"), - TIFFFieldInfo(40101, -1, -1, TIFFDataType.TIFF_ASCII, FIELD_CUSTOM, - True, False, b"LibtiffTeststr"), - TIFFFieldInfo(40104, TIFF_VARIABLE2, TIFF_VARIABLE2, TIFFDataType.TIFF_BYTE, FIELD_CUSTOM, - True, True, b"LibtiffTestBytes"), - ] - - # Add tags to the libtiff library - # Keep pointer to extender object, no gc: - test_extender = add_tags(test_tags) # noqa: F841 - _tag_write() - _tag_read() - - -def _test_tile_write(): - a = TIFF.open("/tmp/libtiff_test_tile_write.tiff", "w") - - data_array = np.tile(list(range(500)), (1, 6)).astype(np.uint8) - a.SetField("TileWidth", 512) - a.SetField("TileLength", 528) - # tile_width and tile_height is not set, write_tiles get these values from - # TileWidth and TileLength tags - assert a.write_tiles(data_array) == (512 * 528) * 6,\ - "could not write tile images" # 1D - print("Tile Write: Wrote array of shape %r" % (data_array.shape,)) - - # 2D Arrays - data_array = np.tile(list(range(500)), (2500, 6)).astype(np.uint8) - assert a.write_tiles(data_array, 512, 528) == (512 * 528) * 5 * 6,\ - "could not write tile images" # 2D - print("Tile Write: Wrote array of shape %r" % (data_array.shape,)) - - # 3D Arrays, 3rd dimension as last dimension - data_array = np.array(range(2500 * 3000 * 3)).reshape( - 2500, 3000, 3).astype(np.uint8) - assert a.write_tiles(data_array, 512, 528, None, True) \ - == (512 * 528) * 5 * 6 * 3,\ - "could not write tile images" # 3D - print("Tile Write: Wrote array of shape %r" % (data_array.shape,)) - - # 3D Arrays, 3rd dimension as first dimension - data_array = np.array(range(2500 * 3000 * 3)).reshape( - 3, 2500, 3000).astype(np.uint8) - assert a.write_tiles(data_array, 512, 528, None, True)\ - == (512 * 528) * 5 * 6 * 3,\ - "could not write tile images" # 3D - print("Tile Write: Wrote array of shape %r" % (data_array.shape,)) - - # Grayscale image with 3 depths - data_array = np.array(range(2500 * 3000 * 3)).reshape( - 3, 2500, 3000).astype(np.uint8) - written_bytes = a.write_tiles(data_array, 512, 528) - assert written_bytes == 512 * 528 * 5 * 6 * 3,\ - "could not write tile images, written_bytes: %s" % (written_bytes,) - print("Tile Write: Wrote array of shape %r" % (data_array.shape,)) - - print("Tile Write: SUCCESS") - - -def _test_tile_read(filename="/tmp/libtiff_test_tile_write.tiff"): - import sys - if filename is None: - if len(sys.argv) != 2: - print("Run `libtiff.py ` for testing.") - return - filename = sys.argv[1] - - a = TIFF.open(filename, "r") - - # 1D Arrays (doesn't make much sense to tile) - a.SetDirectory(0) - # expected tag values for the first image - tags = [ - {"tag": "ImageWidth", "exp_value": 3000}, - {"tag": "ImageLength", "exp_value": 1}, - {"tag": "TileWidth", "exp_value": 512}, - {"tag": "TileLength", "exp_value": 528}, - {"tag": "BitsPerSample", "exp_value": 8}, - {"tag": "Compression", "exp_value": 1}, - ] - - # assert tag values - for tag in tags: - field_value = a.GetField(tag['tag']) - assert field_value == tag['exp_value'],\ - repr((tag['tag'], tag['exp_value'], field_value)) - - data_array = a.read_tiles() - print("Tile Read: Read array of shape %r" % (data_array.shape,)) - assert data_array.shape == (1, 3000), "tile data read was the wrong shape" - test_array = np.array(list(range(500)) * 6).astype(np.uint8).flatten() - assert np.nonzero(data_array.flatten() != test_array)[0].shape[0] == 0,\ - "tile data read was not the same as the expected data" - print("Tile Read: Data is the same as expected from tile write test") - - # 2D Arrays (doesn't make much sense to tile) - a.SetDirectory(1) - # expected tag values for the second image - tags = [ - {"tag": "ImageWidth", "exp_value": 3000}, - {"tag": "ImageLength", "exp_value": 2500}, - {"tag": "TileWidth", "exp_value": 512}, - {"tag": "TileLength", "exp_value": 528}, - {"tag": "BitsPerSample", "exp_value": 8}, - {"tag": "Compression", "exp_value": 1}, - ] - - # assert tag values - for tag in tags: - field_value = a.GetField(tag['tag']) - assert field_value == tag['exp_value'],\ - repr((tag['tag'], tag['exp_value'], field_value)) - - data_array = a.read_tiles() - print("Tile Read: Read array of shape %r" % (data_array.shape,)) - assert data_array.shape == (2500, 3000),\ - "tile data read was the wrong shape" - test_array = np.tile(list(range(500)), - (2500, 6)).astype(np.uint8).flatten() - assert np.nonzero(data_array.flatten() != test_array)[0].shape[0] == 0,\ - "tile data read was not the same as the expected data" - print("Tile Read: Data is the same as expected from tile write test") - - # 3D Arrays, 3rd dimension as last dimension - a.SetDirectory(2) - # expected tag values for the third image - tags = [ - {"tag": "ImageWidth", "exp_value": 3000}, - {"tag": "ImageLength", "exp_value": 2500}, - {"tag": "TileWidth", "exp_value": 512}, - {"tag": "TileLength", "exp_value": 528}, - {"tag": "BitsPerSample", "exp_value": 8}, - {"tag": "Compression", "exp_value": 1}, - ] - - # assert tag values - for tag in tags: - field_value = a.GetField(tag['tag']) - assert field_value == tag['exp_value'],\ - repr(tag['tag'], tag['exp_value'], field_value) - - data_array = a.read_tiles() - print("Tile Read: Read array of shape %r" % (data_array.shape,)) - assert data_array.shape == (2500, 3000, 3),\ - "tile data read was the wrong shape" - test_array = np.array(range(2500 * 3000 * 3)).reshape( - 2500, 3000, 3).astype(np.uint8).flatten() - assert np.nonzero(data_array.flatten() != test_array)[0].shape[0] == 0,\ - "tile data read was not the same as the expected data" - print("Tile Read: Data is the same as expected from tile write test") - - # 3D Arrays, 3rd dimension as first dimension - a.SetDirectory(3) - # expected tag values for the third image - tags = [ - {"tag": "ImageWidth", "exp_value": 3000}, - {"tag": "ImageLength", "exp_value": 2500}, - {"tag": "TileWidth", "exp_value": 512}, - {"tag": "TileLength", "exp_value": 528}, - {"tag": "BitsPerSample", "exp_value": 8}, - {"tag": "Compression", "exp_value": 1}, - ] - - # assert tag values - for tag in tags: - field_value = a.GetField(tag['tag']) - assert field_value == tag['exp_value'],\ - repr(tag['tag'], tag['exp_value'], field_value) - - data_array = a.read_tiles() - print("Tile Read: Read array of shape %r" % (data_array.shape,)) - assert data_array.shape == (3, 2500, 3000),\ - "tile data read was the wrong shape" - test_array = np.array(range(2500 * 3000 * 3)).reshape( - 3, 2500, 3000).astype(np.uint8).flatten() - assert np.nonzero(data_array.flatten() != test_array)[0].shape[0] == 0,\ - "tile data read was not the same as the expected data" - print("Tile Read: Data is the same as expected from tile write test") - - # Grayscale image with 3 depths - a.SetDirectory(4) - - # expected tag values for the third image - tags = [ - {"tag": "ImageWidth", "exp_value": 3000}, - {"tag": "ImageLength", "exp_value": 2500}, - {"tag": "TileWidth", "exp_value": 512}, - {"tag": "TileLength", "exp_value": 528}, - {"tag": "BitsPerSample", "exp_value": 8}, - {"tag": "Compression", "exp_value": 1}, - {"tag": "ImageDepth", "exp_value": 3} - ] - - # assert tag values - for tag in tags: - field_value = a.GetField(tag['tag']) - assert field_value == tag['exp_value'],\ - repr([tag['tag'], tag['exp_value'], field_value]) - - data_array = a.read_tiles() - print("Tile Read: Read array of shape %r" % (data_array.shape,)) - assert data_array.shape == (3, 2500, 3000),\ - "tile data read was the wrong shape" - test_array = np.array(range(2500 * 3000 * 3)).reshape( - 3, 2500, 3000).astype(np.uint8).flatten() - assert np.nonzero(data_array.flatten() != test_array)[0].shape[0] == 0,\ - "tile data read was not the same as the expected data" - print("Tile Read: Data is the same as expected from tile write test") - - print("Tile Read: SUCCESS") - - -def _test_read_one_tile(): - filename = "/tmp/libtiff_test_tile_write.tiff" - tiff = TIFF.open(filename, "r") - - # the first image is 1 pixel high - tile = tiff.read_one_tile(0, 0) - assert tile.shape == (1, 512), repr(tile.shape) - - # second image, 3000 x 2500 - tiff.SetDirectory(1) - tile = tiff.read_one_tile(0, 0) - assert tile.shape == (528, 512), repr(tile.shape) - - tile = tiff.read_one_tile(512, 528) - assert tile.shape == (528, 512), repr(tile.shape) - - # test tile on the right border - tile = tiff.read_one_tile(2560, 528) - assert tile.shape == (528, 440), repr(tile.shape) - - # test tile on the bottom border - tile = tiff.read_one_tile(512, 2112) - assert tile.shape == (388, 512), repr(tile.shape) - - # test tile on the right and bottom borders - tile = tiff.read_one_tile(2560, 2112) - assert tile.shape == (388, 440), repr(tile.shape) - - # test x and y values not multiples of the tile width and height - tile = tiff.read_one_tile(530, 600) - assert tile[0][0] == 12, tile[0][0] - - # test negative x - try: - tiff.read_one_tile(-5, 0) - raise AssertionError( - "An exception must be raised with invalid (x, y) values") - except ValueError as inst: - assert str(inst) == "Invalid x value", inst - - # test y greater than the image height - try: - tiff.read_one_tile(0, 5000) - raise AssertionError( - "An exception must be raised with invalid (x, y) values") - except ValueError as inst: - assert str(inst) == "Invalid y value", inst - - # RGB image sized 3000 x 2500, PLANARCONFIG_SEPARATE - tiff.SetDirectory(3) - tile = tiff.read_one_tile(0, 0) - assert tile.shape == (3, 528, 512), repr(tile.shape) - # get the tile on the lower bottom corner - tile = tiff.read_one_tile(2999, 2499) - assert tile.shape == (3, 388, 440), repr(tile.shape) - - # Grayscale image sized 3000 x 2500, 3 depths - tiff.SetDirectory(4) - tile = tiff.read_one_tile(0, 0) - assert tile.shape == (3, 528, 512), repr(tile.shape) - # get the tile on the lower bottom corner - tile = tiff.read_one_tile(2999, 2499) - assert tile.shape == (3, 388, 440), repr(tile.shape) - - -def _test_tiled_image_read(filename="/tmp/libtiff_test_tile_write.tiff"): - """ - Tests opening a tiled image - """ - - def assert_image_tag(tiff, tag_name, expected_value): - value = tiff.GetField(tag_name) - assert value == expected_value,\ - ('%s expected to be %d, but it\'s %d' - % (tag_name, expected_value, value)) - - # _test_tile_write is called here just to make sure that the image - # is saved, even if the order of the tests changed - _test_tile_write() - tiff = TIFF.open(filename, "r") - - # sets the current image to the second image - tiff.SetDirectory(1) - # test tag values - assert_image_tag(tiff, 'ImageWidth', 3000) - assert_image_tag(tiff, 'ImageLength', 2500) - assert_image_tag(tiff, 'TileWidth', 512) - assert_image_tag(tiff, 'TileLength', 528) - assert_image_tag(tiff, 'BitsPerSample', 8) - assert_image_tag(tiff, 'Compression', COMPRESSION_NONE) # noqa: F821 - - # read the image to a NumPy array - arr = tiff.read_image() - # test image NumPy array dimensions - assert arr.shape[0] == 2500, \ - 'Image width expected to be 2500, but it\'s %d' % (arr.shape[0]) - assert arr.shape[1] == 3000, \ - 'Image height expected to be 3000, but it\'s %d' % (arr.shape[1]) - - # generates the same array that was generated for the image - data_array = np.array(list(range(500)) * 6).astype(np.uint8) - # tests if the array from the read image is the same of the original image - assert (data_array == arr).all(), \ - 'The read tiled image is different from the generated image' - - -def _test_tags_write(): - tiff = TIFF.open('/tmp/libtiff_tags_write.tiff', mode='w') - tmp = tiff.SetField("Artist", b"A Name") - assert tmp == 1, "Tag 'Artist' was not written properly" - tmp = tiff.SetField("DocumentName", b"") - assert tmp == 1, "Tag 'DocumentName' with empty string was not written " \ - "properly" - tmp = tiff.SetField("PrimaryChromaticities", [1, 2, 3, 4, 5, 6]) - assert tmp == 1, "Tag 'PrimaryChromaticities' was not written properly" - tmp = tiff.SetField("BitsPerSample", 8) - assert tmp == 1, "Tag 'BitsPerSample' was not written properly" - tmp = tiff.SetField("ColorMap", [[x * 256 for x in range(256)]] * 3) - assert tmp == 1, "Tag 'ColorMap' was not written properly" - - arr = np.zeros((100, 100), np.uint8) - tiff.write_image(arr) - - print("Tag Write: SUCCESS") - - -def _test_tags_read(filename=None): - import sys - if filename is None: - if len(sys.argv) != 2: - filename = '/tmp/libtiff_tags_write.tiff' - if not os.path.isfile(filename): - print('Run `%s ` for testing.' % (__file__)) - return - else: - filename = sys.argv[1] - tiff = TIFF.open(filename) - tmp = tiff.GetField("Artist") - assert tmp == b"A Name", "Tag 'Artist' did not read the correct value (" \ - "Got '%s'; Expected 'A Name')" % (tmp,) - tmp = tiff.GetField("DocumentName") - assert tmp == b"", "Tag 'DocumentName' did not read the correct value (" \ - "Got '%s'; Expected empty string)" % (tmp,) - tmp = tiff.GetField("PrimaryChromaticities") - assert tmp == [1, 2, 3, 4, 5, - 6], "Tag 'PrimaryChromaticities' did not read the " \ - "correct value (Got '%r'; Expected '[1,2,3,4,5,6]'" % ( - tmp,) - tmp = tiff.GetField("BitsPerSample") - assert tmp == 8, "Tag 'BitsPerSample' did not read the correct value (" \ - "Got %s; Expected 8)" % (str(tmp),) - tmp = tiff.GetField("ColorMap") - try: - assert len( - tmp) == 3, "Tag 'ColorMap' should be three arrays, found %d" % \ - len(tmp) - assert len(tmp[ - 0]) == 256, "Tag 'ColorMap' should be three arrays " \ - "of 256 elements, found %d elements" % \ - len(tmp[0]) - assert len(tmp[ - 1]) == 256, "Tag 'ColorMap' should be three arrays " \ - "of 256 elements, found %d elements" % \ - len(tmp[1]) - assert len(tmp[ - 2]) == 256, "Tag 'ColorMap' should be three arrays " \ - "of 256 elements, found %d elements" % \ - len(tmp[2]) - except TypeError: - print( - "Tag 'ColorMap' has the wrong shape of 3 arrays of 256 elements " - "each") - return - - print("Tag Read: SUCCESS") - - -def _test_read(filename=None): - import sys - import time - if filename is None: - if len(sys.argv) != 2: - filename = '/tmp/libtiff_test_write.tiff' - if not os.path.isfile(filename): - print('Run `libtiff.py ` for testing.') - return - else: - filename = sys.argv[1] - print('Trying to open', filename, '...', end=' ') - tiff = TIFF.open(filename) - print('ok') - print('Trying to show info ...\n', '-' * 10) - print(tiff.info()) - print('-' * 10, 'ok') - print('Trying show images ...') - t = time.time() - _i = 0 - for image in tiff.iter_images(verbose=True): - # print image.min(), image.max(), image.mean () - _i += 1 - print('\tok', (time.time() - t) * 1e3, 'ms', _i, 'images') - - -def _test_write(): - tiff = TIFF.open('/tmp/libtiff_test_write.tiff', mode='w') - arr = np.zeros((5, 6), np.uint32) - for _i in range(arr.shape[0]): - for j in range(arr.shape[1]): - arr[_i, j] = _i + 10 * j - print(arr) - tiff.write_image(arr) - del tiff - - -def _test_write_float(): - tiff = TIFF.open('/tmp/libtiff_test_write.tiff', mode='w') - arr = np.zeros((5, 6), np.float64) - for _i in range(arr.shape[0]): - for j in range(arr.shape[1]): - arr[_i, j] = _i + 10 * j - print(arr) - tiff.write_image(arr) - del tiff - - tiff = TIFF.open('/tmp/libtiff_test_write.tiff', mode='r') - print(tiff.info()) - arr2 = tiff.read_image() - print(arr2) - - -def _test_write_rgba(): - tiff = TIFF.open('/tmp/libtiff_test_write.tiff', mode='w') - arr = np.zeros((5, 6, 4), np.uint8) - for i in np.ndindex(*arr.shape): - arr[i] = 20 * i[0] + 10 * i[1] + i[2] - print(arr) - tiff.write_image(arr, write_rgb=True) - del tiff - - tiff = TIFF.open('/tmp/libtiff_test_write.tiff', mode='r') - print(tiff.info()) - arr2 = tiff.read_image() - print(arr2) - - np.testing.assert_array_equal(arr, arr2) - - -def _test_tree(): - # Write a TIFF image with the following tree structure: - # Im0 --SubIFD--> Im0,1 ---> Im0,2 ---> Im0,3 - # | - # V - # Im1 - tiff = TIFF.open('/tmp/libtiff_test_write.tiff', mode='w') - arr = np.zeros((5, 6), np.uint32) - for i in np.ndindex(*arr.shape): - arr[i] = i[0] + 20 * i[1] - print(arr) - n = 3 - tiff.SetField("SubIFD", [0] * n) - tiff.write_image(arr) - for i in range(n): - arr[0, 0] = i - tiff.write_image(arr) - - arr[0, 0] = 255 - tiff.write_image(arr) - del tiff - - tiff = TIFF.open('/tmp/libtiff_test_write.tiff', mode='r') - print(tiff.info()) - n = 0 - for im in tiff.iter_images(verbose=True): - print(im) - n += 1 - - assert n == 2 - - -def _test_copy(): - tiff = TIFF.open('/tmp/libtiff_test_compression.tiff', mode='w') - arr = np.zeros((5, 6), np.uint32) - for _i in range(arr.shape[0]): - for j in range(arr.shape[1]): - arr[_i, j] = 1 + _i + 10 * j - # from scipy.stats import poisson - # arr = poisson.rvs (arr) - tiff.SetField('ImageDescription', b'Hey\nyou') - tiff.write_image(arr, compression='lzw') - del tiff - - tiff = TIFF.open('/tmp/libtiff_test_compression.tiff', mode='r') - print(tiff.info()) - arr2 = tiff.read_image() - - assert (arr == arr2).all(), 'arrays not equal' - - for compression in ['none', 'lzw', 'deflate']: - for sampleformat in ['int', 'uint', 'float']: - for bitspersample in [128, 64, 32, 16, 8]: - if sampleformat == 'float' and bitspersample < 32: - continue - if sampleformat in ['int', 'uint'] and bitspersample > 64: - continue - # With compression, less data types supported - if compression != 'none' and bitspersample > 32: - continue - # print compression, sampleformat, bitspersample - tiff.copy('/tmp/libtiff_test_copy2.tiff', - compression=compression, - imagedescription=b'hoo', - sampleformat=sampleformat, - bitspersample=bitspersample) - tiff2 = TIFF.open('/tmp/libtiff_test_copy2.tiff', mode='r') - arr3 = tiff2.read_image() - assert (arr == arr3).all(), 'arrays not equal %r' % ( - (compression, sampleformat, bitspersample),) - print('test copy ok') - - -if __name__ == '__main__': - _test_custom_tags() - _test_tile_write() - _test_tile_read() - _test_read_one_tile() - _test_tiled_image_read() - _test_tags_write() - _test_tags_read() - _test_write_float() - _test_write_rgba() - _test_tree() - _test_write() - _test_read() - _test_copy() diff --git a/libtiff/tests/test_libtiff_ctypes.py b/libtiff/tests/test_libtiff_ctypes.py index 5e6a5b5..90b0d31 100644 --- a/libtiff/tests/test_libtiff_ctypes.py +++ b/libtiff/tests/test_libtiff_ctypes.py @@ -1,7 +1,9 @@ -import pytest import numpy as np +import pytest +import time from libtiff import TIFFimage + lt = pytest.importorskip('libtiff.libtiff_ctypes') @@ -14,3 +16,562 @@ def test_issue69(tmp_path): del tif tif = lt.TIFF3D.open(fn) tif.close() + + +# Hold the extenders created, as dereferencing any of them could cause a crash +extenders = [] + + +def test_custom_tags(): + def _tag_write(): + a = lt.TIFF.open("/tmp/libtiff_test_custom_tags.tif", "w") + + a.SetField("ARTIST", b"MY NAME") + a.SetField("LibtiffTestByte", 42) + a.SetField("LibtiffTeststr", b"FAKE") + a.SetField("LibtiffTestuint16", 42) + a.SetField("LibtiffTestMultiuint32", (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) + a.SetField("LibtiffTestBytes", [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + a.SetField("XPOSITION", 42.0) + a.SetField("PRIMARYCHROMATICITIES", (1.0, 2, 3, 4, 5, 6)) + + arr = np.ones((512, 512), dtype=np.uint8) + arr[:, :] = 255 + a.write_image(arr) + + print("Tag Write: SUCCESS") + + def _tag_read(): + a = lt.TIFF.open("/tmp/libtiff_test_custom_tags.tif", "r") + + tmp = a.read_image() + assert tmp.shape == ( + 512, + 512), "Image read was wrong shape (%r instead of (512,512))" % ( + tmp.shape,) + tmp = a.GetField("XPOSITION") + assert tmp == 42.0, "XPosition was not read as 42.0" + tmp = a.GetField("ARTIST") + assert tmp == b"MY NAME", "Artist was not read as 'MY NAME'" + tmp = a.GetField("LibtiffTestByte") + assert tmp == 42, "LibtiffTestbyte was not read as 42" + tmp = a.GetField("LibtiffTestuint16") + assert tmp == 42, "LibtiffTestuint16 was not read as 42" + tmp = a.GetField("LibtiffTestMultiuint32") + assert tmp == [1, 2, 3, 4, 5, 6, 7, 8, 9, + 10], "LibtiffTestMultiuint32 was not read as [1,2,3," \ + "4,5,6,7,8,9,10]" + tmp = a.GetField("LibtiffTeststr") + assert tmp == b"FAKE", "LibtiffTeststr was not read as 'FAKE'" + tmp = a.GetField("LibtiffTestBytes") + assert tmp == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + tmp = a.GetField("PRIMARYCHROMATICITIES") + assert tmp == [1.0, 2.0, 3.0, 4.0, 5.0, + 6.0], "PrimaryChromaticities was not read as [1.0," \ + "2.0,3.0,4.0,5.0,6.0]" + print("Tag Read: SUCCESS") + + # Define a C structure that says how each tag should be used + test_tags = [ + lt.TIFFFieldInfo(40100, 1, 1, lt.TIFFDataType.TIFF_BYTE, lt.FIELD_CUSTOM, + True, False, b"LibtiffTestByte"), + lt.TIFFFieldInfo(40103, 10, 10, lt.TIFFDataType.TIFF_LONG, lt.FIELD_CUSTOM, + True, False, b"LibtiffTestMultiuint32"), + lt.TIFFFieldInfo(40102, 1, 1, lt.TIFFDataType.TIFF_SHORT, lt.FIELD_CUSTOM, + True, False, b"LibtiffTestuint16"), + lt.TIFFFieldInfo(40101, -1, -1, lt.TIFFDataType.TIFF_ASCII, lt.FIELD_CUSTOM, + True, False, b"LibtiffTeststr"), + lt.TIFFFieldInfo(40104, lt.TIFF_VARIABLE2, lt.TIFF_VARIABLE2, lt.TIFFDataType.TIFF_BYTE, + lt.FIELD_CUSTOM, True, True, b"LibtiffTestBytes"), + ] + + # Add tags to the libtiff library + # Keep pointer to extender object, no gc: + test_extender = lt.add_tags(test_tags) # noqa: F841 + extenders.append(test_extender) + _tag_write() + _tag_read() + + +def test_tile_write(): + a = lt.TIFF.open("/tmp/libtiff_test_tile_write.tiff", "w") + + data_array = np.tile(list(range(500)), (1, 6)).astype(np.uint8) + a.SetField("TileWidth", 512) + a.SetField("TileLength", 528) + # tile_width and tile_height is not set, write_tiles get these values from + # TileWidth and TileLength tags + assert a.write_tiles(data_array) == (512 * 528) * 6, "could not write tile images" # 1D + print("Tile Write: Wrote array of shape %r" % (data_array.shape,)) + + # 2D Arrays + data_array = np.tile(list(range(500)), (2500, 6)).astype(np.uint8) + assert a.write_tiles(data_array, 512, 528) == (512 * 528) * 5 * 6, \ + "could not write tile images" # 2D + print("Tile Write: Wrote array of shape %r" % (data_array.shape,)) + + # 3D Arrays, 3rd dimension as last dimension + data_array = np.array(range(2500 * 3000 * 3)) + data_array = data_array.reshape(2500, 3000, 3).astype(np.uint8) + assert a.write_tiles(data_array, 512, 528, None, True) == (512 * 528) * 5 * 6 * 3, \ + "could not write tile images" # 3D + print("Tile Write: Wrote array of shape %r" % (data_array.shape,)) + + # 3D Arrays, 3rd dimension as first dimension + data_array = np.array(range(2500 * 3000 * 3)).reshape( + 3, 2500, 3000).astype(np.uint8) + assert a.write_tiles(data_array, 512, 528, None, True) == (512 * 528) * 5 * 6 * 3, \ + "could not write tile images" # 3D + print("Tile Write: Wrote array of shape %r" % (data_array.shape,)) + + # Grayscale image with 3 depths + data_array = np.array(range(2500 * 3000 * 3)).reshape( + 3, 2500, 3000).astype(np.uint8) + written_bytes = a.write_tiles(data_array, 512, 528) + assert written_bytes == 512 * 528 * 5 * 6 * 3, \ + "could not write tile images, written_bytes: %s" % (written_bytes,) + print("Tile Write: Wrote array of shape %r" % (data_array.shape,)) + + print("Tile Write: SUCCESS") + + +def test_tile_read(): + filename = "/tmp/libtiff_test_tile_write.tiff" + + a = lt.TIFF.open(filename, "r") + + # 1D Arrays (doesn't make much sense to tile) + a.SetDirectory(0) + # expected tag values for the first image + tags = [ + {"tag": "ImageWidth", "exp_value": 3000}, + {"tag": "ImageLength", "exp_value": 1}, + {"tag": "TileWidth", "exp_value": 512}, + {"tag": "TileLength", "exp_value": 528}, + {"tag": "BitsPerSample", "exp_value": 8}, + {"tag": "Compression", "exp_value": 1}, + ] + + # assert tag values + for tag in tags: + field_value = a.GetField(tag['tag']) + assert field_value == tag['exp_value'], \ + repr((tag['tag'], tag['exp_value'], field_value)) + + data_array = a.read_tiles() + print("Tile Read: Read array of shape %r" % (data_array.shape,)) + assert data_array.shape == (1, 3000), "tile data read was the wrong shape" + test_array = np.array(list(range(500)) * 6).astype(np.uint8).flatten() + assert np.nonzero(data_array.flatten() != test_array)[0].shape[0] == 0, \ + "tile data read was not the same as the expected data" + print("Tile Read: Data is the same as expected from tile write test") + + # 2D Arrays (doesn't make much sense to tile) + a.SetDirectory(1) + # expected tag values for the second image + tags = [ + {"tag": "ImageWidth", "exp_value": 3000}, + {"tag": "ImageLength", "exp_value": 2500}, + {"tag": "TileWidth", "exp_value": 512}, + {"tag": "TileLength", "exp_value": 528}, + {"tag": "BitsPerSample", "exp_value": 8}, + {"tag": "Compression", "exp_value": 1}, + ] + + # assert tag values + for tag in tags: + field_value = a.GetField(tag['tag']) + assert field_value == tag['exp_value'], \ + repr((tag['tag'], tag['exp_value'], field_value)) + + data_array = a.read_tiles() + print("Tile Read: Read array of shape %r" % (data_array.shape,)) + assert data_array.shape == (2500, 3000), \ + "tile data read was the wrong shape" + test_array = np.tile(list(range(500)), + (2500, 6)).astype(np.uint8).flatten() + assert np.nonzero(data_array.flatten() != test_array)[0].shape[0] == 0, \ + "tile data read was not the same as the expected data" + print("Tile Read: Data is the same as expected from tile write test") + + # 3D Arrays, 3rd dimension as last dimension + a.SetDirectory(2) + # expected tag values for the third image + tags = [ + {"tag": "ImageWidth", "exp_value": 3000}, + {"tag": "ImageLength", "exp_value": 2500}, + {"tag": "TileWidth", "exp_value": 512}, + {"tag": "TileLength", "exp_value": 528}, + {"tag": "BitsPerSample", "exp_value": 8}, + {"tag": "Compression", "exp_value": 1}, + ] + + # assert tag values + for tag in tags: + field_value = a.GetField(tag['tag']) + assert field_value == tag['exp_value'], \ + repr(tag['tag'], tag['exp_value'], field_value) + + data_array = a.read_tiles() + print("Tile Read: Read array of shape %r" % (data_array.shape,)) + assert data_array.shape == (2500, 3000, 3), \ + "tile data read was the wrong shape" + test_array = np.array(range(2500 * 3000 * 3)).reshape( + 2500, 3000, 3).astype(np.uint8).flatten() + assert np.nonzero(data_array.flatten() != test_array)[0].shape[0] == 0, \ + "tile data read was not the same as the expected data" + print("Tile Read: Data is the same as expected from tile write test") + + # 3D Arrays, 3rd dimension as first dimension + a.SetDirectory(3) + # expected tag values for the third image + tags = [ + {"tag": "ImageWidth", "exp_value": 3000}, + {"tag": "ImageLength", "exp_value": 2500}, + {"tag": "TileWidth", "exp_value": 512}, + {"tag": "TileLength", "exp_value": 528}, + {"tag": "BitsPerSample", "exp_value": 8}, + {"tag": "Compression", "exp_value": 1}, + ] + + # assert tag values + for tag in tags: + field_value = a.GetField(tag['tag']) + assert field_value == tag['exp_value'], \ + repr(tag['tag'], tag['exp_value'], field_value) + + data_array = a.read_tiles() + print("Tile Read: Read array of shape %r" % (data_array.shape,)) + assert data_array.shape == (3, 2500, 3000), \ + "tile data read was the wrong shape" + test_array = np.array(range(2500 * 3000 * 3)).reshape( + 3, 2500, 3000).astype(np.uint8).flatten() + assert np.nonzero(data_array.flatten() != test_array)[0].shape[0] == 0, \ + "tile data read was not the same as the expected data" + print("Tile Read: Data is the same as expected from tile write test") + + # Grayscale image with 3 depths + a.SetDirectory(4) + + # expected tag values for the third image + tags = [ + {"tag": "ImageWidth", "exp_value": 3000}, + {"tag": "ImageLength", "exp_value": 2500}, + {"tag": "TileWidth", "exp_value": 512}, + {"tag": "TileLength", "exp_value": 528}, + {"tag": "BitsPerSample", "exp_value": 8}, + {"tag": "Compression", "exp_value": 1}, + {"tag": "ImageDepth", "exp_value": 3} + ] + + # assert tag values + for tag in tags: + field_value = a.GetField(tag['tag']) + assert field_value == tag['exp_value'], \ + repr([tag['tag'], tag['exp_value'], field_value]) + + data_array = a.read_tiles() + print("Tile Read: Read array of shape %r" % (data_array.shape,)) + assert data_array.shape == (3, 2500, 3000), \ + "tile data read was the wrong shape" + test_array = np.array(range(2500 * 3000 * 3)).reshape( + 3, 2500, 3000).astype(np.uint8).flatten() + assert np.nonzero(data_array.flatten() != test_array)[0].shape[0] == 0, \ + "tile data read was not the same as the expected data" + print("Tile Read: Data is the same as expected from tile write test") + + print("Tile Read: SUCCESS") + + +def test_read_one_tile(): + filename = "/tmp/libtiff_test_tile_write.tiff" + tiff = lt.TIFF.open(filename, "r") + + # the first image is 1 pixel high + tile = tiff.read_one_tile(0, 0) + assert tile.shape == (1, 512), repr(tile.shape) + + # second image, 3000 x 2500 + tiff.SetDirectory(1) + tile = tiff.read_one_tile(0, 0) + assert tile.shape == (528, 512), repr(tile.shape) + + tile = tiff.read_one_tile(512, 528) + assert tile.shape == (528, 512), repr(tile.shape) + + # test tile on the right border + tile = tiff.read_one_tile(2560, 528) + assert tile.shape == (528, 440), repr(tile.shape) + + # test tile on the bottom border + tile = tiff.read_one_tile(512, 2112) + assert tile.shape == (388, 512), repr(tile.shape) + + # test tile on the right and bottom borders + tile = tiff.read_one_tile(2560, 2112) + assert tile.shape == (388, 440), repr(tile.shape) + + # test x and y values not multiples of the tile width and height + tile = tiff.read_one_tile(530, 600) + assert tile[0][0] == 12, tile[0][0] + + # test negative x + try: + tiff.read_one_tile(-5, 0) + raise AssertionError( + "An exception must be raised with invalid (x, y) values") + except ValueError as inst: + assert str(inst) == "Invalid x value", inst + + # test y greater than the image height + try: + tiff.read_one_tile(0, 5000) + raise AssertionError( + "An exception must be raised with invalid (x, y) values") + except ValueError as inst: + assert str(inst) == "Invalid y value", inst + + # RGB image sized 3000 x 2500, PLANARCONFIG_SEPARATE + tiff.SetDirectory(3) + tile = tiff.read_one_tile(0, 0) + assert tile.shape == (3, 528, 512), repr(tile.shape) + # get the tile on the lower bottom corner + tile = tiff.read_one_tile(2999, 2499) + assert tile.shape == (3, 388, 440), repr(tile.shape) + + # Grayscale image sized 3000 x 2500, 3 depths + tiff.SetDirectory(4) + tile = tiff.read_one_tile(0, 0) + assert tile.shape == (3, 528, 512), repr(tile.shape) + # get the tile on the lower bottom corner + tile = tiff.read_one_tile(2999, 2499) + assert tile.shape == (3, 388, 440), repr(tile.shape) + + +def test_tiled_image_read(): + """ + Tests opening a tiled image + """ + filename = "/tmp/libtiff_test_tile_write.tiff" + + def assert_image_tag(tiff, tag_name, expected_value): + value = tiff.GetField(tag_name) + assert value == expected_value, \ + ('%s expected to be %d, but it\'s %d' + % (tag_name, expected_value, value)) + + # test_tile_write is called here just to make sure that the image + # is saved, even if the order of the tests changed + test_tile_write() + tiff = lt.TIFF.open(filename, "r") + + # sets the current image to the second image + tiff.SetDirectory(1) + # test tag values + assert_image_tag(tiff, 'ImageWidth', 3000) + assert_image_tag(tiff, 'ImageLength', 2500) + assert_image_tag(tiff, 'TileWidth', 512) + assert_image_tag(tiff, 'TileLength', 528) + assert_image_tag(tiff, 'BitsPerSample', 8) + assert_image_tag(tiff, 'Compression', lt.COMPRESSION_NONE) # noqa: F821 + + # read the image to a NumPy array + arr = tiff.read_image() + # test image NumPy array dimensions + assert arr.shape[0] == 2500, \ + 'Image width expected to be 2500, but it\'s %d' % (arr.shape[0]) + assert arr.shape[1] == 3000, \ + 'Image height expected to be 3000, but it\'s %d' % (arr.shape[1]) + + # generates the same array that was generated for the image + data_array = np.array(list(range(500)) * 6).astype(np.uint8) + # tests if the array from the read image is the same of the original image + assert (data_array == arr).all(), \ + 'The read tiled image is different from the generated image' + + +def test_tags_write(): + tiff = lt.TIFF.open('/tmp/libtiff_tags_write.tiff', mode='w') + tmp = tiff.SetField("Artist", b"A Name") + assert tmp == 1, "Tag 'Artist' was not written properly" + tmp = tiff.SetField("DocumentName", b"") + assert tmp == 1, "Tag 'DocumentName' with empty string was not written properly" + tmp = tiff.SetField("PrimaryChromaticities", [1, 2, 3, 4, 5, 6]) + assert tmp == 1, "Tag 'PrimaryChromaticities' was not written properly" + tmp = tiff.SetField("BitsPerSample", 8) + assert tmp == 1, "Tag 'BitsPerSample' was not written properly" + tmp = tiff.SetField("ColorMap", [[x * 256 for x in range(256)]] * 3) + assert tmp == 1, "Tag 'ColorMap' was not written properly" + + arr = np.zeros((100, 100), np.uint8) + tiff.write_image(arr) + + print("Tag Write: SUCCESS") + + +def test_tags_read(): + filename = '/tmp/libtiff_tags_write.tiff' + + tiff = lt.TIFF.open(filename) + tmp = tiff.GetField("Artist") + assert tmp == b"A Name", "Tag 'Artist' did not read the correct value (" \ + "Got '%s'; Expected 'A Name')" % (tmp,) + tmp = tiff.GetField("DocumentName") + assert tmp == b"", "Tag 'DocumentName' did not read the correct value (" \ + "Got '%s'; Expected empty string)" % (tmp,) + tmp = tiff.GetField("PrimaryChromaticities") + assert tmp == [1, 2, 3, 4, 5, 6], \ + "Tag 'PrimaryChromaticities' did not read the " \ + "correct value (Got '%r'; Expected '[1,2,3,4,5,6]'" % (tmp,) + tmp = tiff.GetField("BitsPerSample") + assert tmp == 8, "Tag 'BitsPerSample' did not read the correct value (" \ + "Got %s; Expected 8)" % (str(tmp),) + tmp = tiff.GetField("ColorMap") + try: + assert len(tmp) == 3, \ + f"Tag 'ColorMap' should be three arrays, found {len(tmp)}" + assert len(tmp[0]) == 256, \ + f"Tag 'ColorMap' should be three arrays of 256 elements, found {len(tmp[0])} elements" + assert len(tmp[1]) == 256, \ + f"Tag 'ColorMap' should be three arrays of 256 elements, found {len(tmp[1])} elements" + assert len(tmp[2]) == 256, \ + f"Tag 'ColorMap' should be three arrays of 256 elements, found {len(tmp[2])} elements" + except TypeError: + print("Tag 'ColorMap' has the wrong shape of 3 arrays of 256 elements each") + return + + print("Tag Read: SUCCESS") + + +def test_write(): + tiff = lt.TIFF.open('/tmp/libtiff_test_write.tiff', mode='w') + arr = np.zeros((5, 6), np.uint32) + for _i in range(arr.shape[0]): + for j in range(arr.shape[1]): + arr[_i, j] = _i + 10 * j + print(arr) + tiff.write_image(arr) + del tiff + + +def test_read(filename=None): + filename = '/tmp/libtiff_test_write.tiff' + + print('Trying to open', filename, '...', end=' ') + tiff = lt.TIFF.open(filename) + print('Trying to show info ...\n', '-' * 10) + print(tiff.info()) + print('-' * 10, 'ok') + print('Trying show images ...') + t = time.time() + i = 0 + for image in tiff.iter_images(verbose=True): + print(image.min(), image.max(), image.mean()) + i += 1 + print('\tok', (time.time() - t) * 1e3, 'ms', i, 'images') + + +def test_write_float(): + tiff = lt.TIFF.open('/tmp/libtiff_test_write.tiff', mode='w') + arr = np.zeros((5, 6), np.float64) + for _i in range(arr.shape[0]): + for j in range(arr.shape[1]): + arr[_i, j] = _i + 10 * j + print(arr) + tiff.write_image(arr) + del tiff + + tiff = lt.TIFF.open('/tmp/libtiff_test_write.tiff', mode='r') + print(tiff.info()) + arr2 = tiff.read_image() + print(arr2) + + +def test_write_rgba(): + tiff = lt.TIFF.open('/tmp/libtiff_test_write.tiff', mode='w') + arr = np.zeros((5, 6, 4), np.uint8) + for i in np.ndindex(*arr.shape): + arr[i] = 20 * i[0] + 10 * i[1] + i[2] + print(arr) + tiff.write_image(arr, write_rgb=True) + del tiff + + tiff = lt.TIFF.open('/tmp/libtiff_test_write.tiff', mode='r') + print(tiff.info()) + arr2 = tiff.read_image() + print(arr2) + + np.testing.assert_array_equal(arr, arr2) + + +def test_tree(): + # Write a TIFF image with the following tree structure: + # Im0 --SubIFD--> Im0,1 ---> Im0,2 ---> Im0,3 + # | + # V + # Im1 + tiff = lt.TIFF.open('/tmp/libtiff_test_write.tiff', mode='w') + arr = np.zeros((5, 6), np.uint32) + for i in np.ndindex(*arr.shape): + arr[i] = i[0] + 20 * i[1] + print(arr) + n = 3 + tiff.SetField("SubIFD", [0] * n) + tiff.write_image(arr) + for i in range(n): + arr[0, 0] = i + tiff.write_image(arr) + + arr[0, 0] = 255 + tiff.write_image(arr) + del tiff + + tiff = lt.TIFF.open('/tmp/libtiff_test_write.tiff', mode='r') + print(tiff.info()) + n = 0 + for im in tiff.iter_images(verbose=True): + print(im) + n += 1 + + assert n == 2 + + +def test_copy(): + tiff = lt.TIFF.open('/tmp/libtiff_test_compression.tiff', mode='w') + arr = np.zeros((5, 6), np.uint32) + for _i in range(arr.shape[0]): + for j in range(arr.shape[1]): + arr[_i, j] = 1 + _i + 10 * j + # from scipy.stats import poisson + # arr = poisson.rvs (arr) + tiff.SetField('ImageDescription', b'Hey\nyou') + tiff.write_image(arr, compression='lzw') + del tiff + + tiff = lt.TIFF.open('/tmp/libtiff_test_compression.tiff', mode='r') + print(tiff.info()) + arr2 = tiff.read_image() + + assert (arr == arr2).all(), 'arrays not equal' + + for compression in ['none', 'lzw', 'deflate']: + for sampleformat in ['int', 'uint', 'float']: + for bitspersample in [128, 64, 32, 16, 8]: + if sampleformat == 'float' and bitspersample < 32: + continue + if sampleformat in ['int', 'uint'] and bitspersample > 64: + continue + # With compression, less data types supported + if compression != 'none' and bitspersample > 32: + continue + # print compression, sampleformat, bitspersample + tiff.copy('/tmp/libtiff_test_copy2.tiff', + compression=compression, + imagedescription=b'hoo', + sampleformat=sampleformat, + bitspersample=bitspersample) + tiff2 = lt.TIFF.open('/tmp/libtiff_test_copy2.tiff', mode='r') + arr3 = tiff2.read_image() + assert (arr == arr3).all(), 'arrays not equal %r' % ( + (compression, sampleformat, bitspersample),) + print('test copy ok')