From 9681548d9c375f8c71a39e1ef48551a153ab19af Mon Sep 17 00:00:00 2001 From: Benjamin Gilbert Date: Tue, 24 May 2016 19:34:08 -0500 Subject: [PATCH] Add tests --- .gitignore | 1 + MANIFEST.in | 1 + setup.py | 1 + tests/__init__.py | 23 +++++++++ tests/boxes.png | Bin 0 -> 204 bytes tests/boxes.tiff | Bin 0 -> 3580 bytes tests/test_base.py | 39 +++++++++++++++ tests/test_deepzoom.py | 76 +++++++++++++++++++++++++++++ tests/test_imageslide.py | 102 +++++++++++++++++++++++++++++++++++++++ tests/test_openslide.py | 121 +++++++++++++++++++++++++++++++++++++++++++++++ tests/unopenable.tiff | Bin 0 -> 1020 bytes 11 files changed, 364 insertions(+) create mode 100644 tests/__init__.py create mode 100644 tests/boxes.png create mode 100644 tests/boxes.tiff create mode 100644 tests/test_base.py create mode 100644 tests/test_deepzoom.py create mode 100644 tests/test_imageslide.py create mode 100644 tests/test_openslide.py create mode 100644 tests/unopenable.tiff diff --git a/.gitignore b/.gitignore index 87d0e17..78dc741 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ /dist /MANIFEST /*.egg-info +/openslide/_convert*.so *.pyc diff --git a/MANIFEST.in b/MANIFEST.in index 61194a1..253894b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,4 @@ include *.txt recursive-include doc *.py *.rst recursive-include examples *.html *.js *.png *.py +recursive-include tests *.png *.py *.tiff diff --git a/setup.py b/setup.py index 55dbbff..7c9c9ba 100644 --- a/setup.py +++ b/setup.py @@ -21,6 +21,7 @@ ], ), }, + test_suite='tests', maintainer='OpenSlide project', maintainer_email='openslide-users@lists.andrew.cmu.edu', description='Python interface to OpenSlide', diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..d160b84 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,23 @@ +# +# openslide-python - Python bindings for the OpenSlide library +# +# Copyright (c) 2016 Benjamin Gilbert +# +# This library is free software; you can redistribute it and/or modify it +# under the terms of version 2.1 of the GNU Lesser General Public License +# as published by the Free Software Foundation. +# +# This library is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +# License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +import os + +def file_path(name): + return os.path.join(os.path.dirname(__file__), name) diff --git a/tests/boxes.png b/tests/boxes.png new file mode 100644 index 0000000000000000000000000000000000000000..f71c27275f141d69e31bdeaf5fa39556302c470c GIT binary patch literal 204 zcmeAS@N?(olHy`uVBq!ia0y~yVAKJ!e=#uw$u#Ck2|$V`z$e6&f#E+GGW`GlUu=H% zE}(d|r;B4q#jUqz4)QuU2rwMT{Zu9L>a*~>=ZzaUo%D=ZeRR%qPCxy$XlKki`MBcw zhC5>PAY8rce^+;VgE_U8Q+1(&K*}p-^+}L=m^!db+z!6pr&B=YNbM|wNWBeJ&y^5c TzfAih&>0M#u6{1-oD!M<@_SY5 literal 0 HcmV?d00001 diff --git a/tests/boxes.tiff b/tests/boxes.tiff new file mode 100644 index 0000000000000000000000000000000000000000..acf31dd0f1dc9f0acdc1e8f4526433e06dafc801 GIT binary patch literal 3580 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 Z0DLMwN0m-n>T|@EPy-_Kwd_v&{J&sqwA%mx literal 0 HcmV?d00001 diff --git a/tests/test_base.py b/tests/test_base.py new file mode 100644 index 0000000..4ea0418 --- /dev/null +++ b/tests/test_base.py @@ -0,0 +1,39 @@ +# +# openslide-python - Python bindings for the OpenSlide library +# +# Copyright (c) 2016 Benjamin Gilbert +# +# This library is free software; you can redistribute it and/or modify it +# under the terms of version 2.1 of the GNU Lesser General Public License +# as published by the Free Software Foundation. +# +# This library is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +# License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +import openslide +from openslide import open_slide, OpenSlide, ImageSlide +import sys +import unittest + +from . import file_path + +# Tests should be written to be compatible with Python 2.6 unittest. + +class TestLibrary(unittest.TestCase): + def test_version(self): + string = unicode if sys.version[0] == '2' else str + self.assertTrue(isinstance(openslide.__version__, string)) + self.assertTrue(isinstance(openslide.__library_version__, string)) + + def test_open_slide(self): + self.assertTrue(isinstance(open_slide(file_path('boxes.tiff')), + OpenSlide)) + self.assertTrue(isinstance(open_slide(file_path('boxes.png')), + ImageSlide)) diff --git a/tests/test_deepzoom.py b/tests/test_deepzoom.py new file mode 100644 index 0000000..b505764 --- /dev/null +++ b/tests/test_deepzoom.py @@ -0,0 +1,76 @@ +# +# openslide-python - Python bindings for the OpenSlide library +# +# Copyright (c) 2016 Benjamin Gilbert +# +# This library is free software; you can redistribute it and/or modify it +# under the terms of version 2.1 of the GNU Lesser General Public License +# as published by the Free Software Foundation. +# +# This library is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +# License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +from openslide import OpenSlide, ImageSlide +from openslide.deepzoom import DeepZoomGenerator +import unittest + +from . import file_path + +# Tests should be written to be compatible with Python 2.6 unittest. + +class _BoxesDeepZoomTest(object): + def setUp(self): + self.osr = self.CLASS(file_path(self.FILENAME)) + self.dz = DeepZoomGenerator(self.osr, 254, 1) + + def tearDown(self): + self.osr.close() + + def test_metadata(self): + self.assertEqual(self.dz.level_count, 10) + self.assertEqual(self.dz.tile_count, 11) + self.assertEqual(self.dz.level_tiles, + ((1, 1), (1, 1), (1, 1), (1, 1), (1, 1), + (1, 1), (1, 1), (1, 1), (1, 1), (2, 1))) + self.assertEqual(self.dz.level_dimensions, + ((1, 1), (2, 1), (3, 2), (5, 4), (10, 8), + (19, 16), (38, 32), (75, 63), (150, 125), (300, 250))) + + def test_get_tile(self): + self.assertEqual(self.dz.get_tile(9, (1, 0)).size, (47, 250)) + + def test_get_tile_bad_level(self): + self.assertRaises(ValueError, lambda: self.dz.get_tile(-1, (0, 0))) + self.assertRaises(ValueError, lambda: self.dz.get_tile(10, (0, 0))) + + def test_get_tile_bad_address(self): + self.assertRaises(ValueError, lambda: self.dz.get_tile(0, (-1, 0))) + self.assertRaises(ValueError, lambda: self.dz.get_tile(0, (1, 0))) + + def test_get_tile_coordinates(self): + self.assertEqual(self.dz.get_tile_coordinates(9, (1, 0)), + ((253, 0), 0, (47, 250))) + + def test_get_tile_dimensions(self): + self.assertEqual(self.dz.get_tile_dimensions(9, (1, 0)), (47, 250)) + + def test_get_dzi(self): + self.assertTrue('http://schemas.microsoft.com/deepzoom/2008' in + self.dz.get_dzi('jpeg')) + + +class TestSlideDeepZoom(_BoxesDeepZoomTest, unittest.TestCase): + CLASS = OpenSlide + FILENAME = 'boxes.tiff' + + +class TestImageDeepZoom(_BoxesDeepZoomTest, unittest.TestCase): + CLASS = ImageSlide + FILENAME = 'boxes.png' diff --git a/tests/test_imageslide.py b/tests/test_imageslide.py new file mode 100644 index 0000000..510534c --- /dev/null +++ b/tests/test_imageslide.py @@ -0,0 +1,102 @@ +# +# openslide-python - Python bindings for the OpenSlide library +# +# Copyright (c) 2016 Benjamin Gilbert +# +# This library is free software; you can redistribute it and/or modify it +# under the terms of version 2.1 of the GNU Lesser General Public License +# as published by the Free Software Foundation. +# +# This library is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +# License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +from openslide import ImageSlide, OpenSlideError +from PIL import Image +import unittest + +from . import file_path + +# Tests should be written to be compatible with Python 2.6 unittest. + +class TestImageWithoutOpening(unittest.TestCase): + def test_detect_format(self): + self.assertTrue( + ImageSlide.detect_format(file_path('__missing_file')) is None) + self.assertTrue( + ImageSlide.detect_format(file_path('../setup.py')) is None) + self.assertEqual( + ImageSlide.detect_format(file_path('boxes.png')), 'PNG') + + def test_open(self): + self.assertRaises(IOError, + lambda: ImageSlide(file_path('__does_not_exist'))) + self.assertRaises(IOError, + lambda: ImageSlide(file_path('../setup.py'))) + + # passing PIL.Image to ImageSlide + self.assertEqual( + ImageSlide(Image.open(file_path('boxes.png'))).dimensions, + (300, 250)) + + def test_operations_on_closed_handle(self): + img = Image.open(file_path('boxes.png')) + osr = ImageSlide(img) + osr.close() + self.assertRaises(AttributeError, + lambda: osr.read_region((0, 0), 0, (100, 100))) + # If an Image is passed to the constructor, ImageSlide.close() + # shouldn't close it + self.assertEqual(img.getpixel((0, 0)), 3) + + def test_context_manager(self): + osr = ImageSlide(file_path('boxes.png')) + with osr: + pass + self.assertRaises(AttributeError, + lambda: osr.read_region((0, 0), 0, (100, 100))) + + +class TestImage(unittest.TestCase): + def setUp(self): + self.osr = ImageSlide(file_path('boxes.png')) + + def tearDown(self): + self.osr.close() + + def test_metadata(self): + self.assertEqual(self.osr.level_count, 1) + self.assertEqual(self.osr.level_dimensions, ((300, 250),)) + self.assertEqual(self.osr.dimensions, (300, 250)) + self.assertEqual(self.osr.level_downsamples, (1.0,)) + + self.assertEqual(self.osr.get_best_level_for_downsample(0.5), 0) + self.assertEqual(self.osr.get_best_level_for_downsample(3), 0) + + self.assertEqual(self.osr.properties, {}) + self.assertEqual(self.osr.associated_images, {}) + + def test_read_region(self): + self.assertEqual(self.osr.read_region((-10, -10), 0, (400, 400)).size, + (400, 400)) + + def test_read_region_size_dimension_zero(self): + self.assertEqual(self.osr.read_region((0, 0), 0, (400, 0)).size, + (400, 0)) + + def test_read_region_bad_level(self): + self.assertRaises(OpenSlideError, + lambda: self.osr.read_region((0, 0), 1, (100, 100))) + + def test_read_region_bad_size(self): + self.assertRaises(OpenSlideError, + lambda: self.osr.read_region((0, 0), 0, (400, -5))) + + def test_thumbnail(self): + self.assertEqual(self.osr.get_thumbnail((100, 100)).size, (100, 83)) diff --git a/tests/test_openslide.py b/tests/test_openslide.py new file mode 100644 index 0000000..715c09a --- /dev/null +++ b/tests/test_openslide.py @@ -0,0 +1,121 @@ +# +# openslide-python - Python bindings for the OpenSlide library +# +# Copyright (c) 2016 Benjamin Gilbert +# +# This library is free software; you can redistribute it and/or modify it +# under the terms of version 2.1 of the GNU Lesser General Public License +# as published by the Free Software Foundation. +# +# This library is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +# License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +from ctypes import ArgumentError +from openslide import (OpenSlide, OpenSlideError, + OpenSlideUnsupportedFormatError) +import unittest + +from . import file_path + +# Tests should be written to be compatible with Python 2.6 unittest. + +class TestSlideWithoutOpening(unittest.TestCase): + def test_detect_format(self): + self.assertTrue( + OpenSlide.detect_format(file_path('__missing_file')) is None) + self.assertTrue( + OpenSlide.detect_format(file_path('../setup.py')) is None) + self.assertEqual( + OpenSlide.detect_format(file_path('boxes.tiff')), + 'generic-tiff') + + def test_open(self): + self.assertRaises(OpenSlideUnsupportedFormatError, + lambda: OpenSlide('__does_not_exist')) + self.assertRaises(OpenSlideUnsupportedFormatError, + lambda: OpenSlide('setup.py')) + self.assertRaises(OpenSlideError, + lambda: OpenSlide('unopenable.tiff')) + + def test_operations_on_closed_handle(self): + osr = OpenSlide(file_path('boxes.tiff')) + props = osr.properties + associated = osr.associated_images + osr.close() + self.assertRaises(ArgumentError, + lambda: osr.read_region((0, 0), 0, (100, 100))) + self.assertRaises(ArgumentError, lambda: osr.close()) + self.assertRaises(ArgumentError, lambda: props['openslide.vendor']) + self.assertRaises(ArgumentError, lambda: associated['label']) + + def test_context_manager(self): + osr = OpenSlide(file_path('boxes.tiff')) + with osr: + self.assertEqual(osr.level_count, 4) + self.assertRaises(ArgumentError, lambda: osr.level_count) + + +class TestSlide(unittest.TestCase): + def setUp(self): + self.osr = OpenSlide(file_path('boxes.tiff')) + + def tearDown(self): + self.osr.close() + + def test_basic_metadata(self): + self.assertEqual(self.osr.level_count, 4) + self.assertEqual(self.osr.level_dimensions, + ((300, 250), (150, 125), (75, 62), (37, 31))) + self.assertEqual(self.osr.dimensions, (300, 250)) + + self.assertEqual(len(self.osr.level_downsamples), self.osr.level_count) + self.assertEqual(self.osr.level_downsamples[0:2], (1, 2)) + self.assertAlmostEqual(self.osr.level_downsamples[2], 4, places=0) + self.assertAlmostEqual(self.osr.level_downsamples[3], 8, places=0) + + self.assertEqual(self.osr.get_best_level_for_downsample(0.5), 0) + self.assertEqual(self.osr.get_best_level_for_downsample(3), 1) + self.assertEqual(self.osr.get_best_level_for_downsample(37), 3) + + def test_properties(self): + self.assertEqual(self.osr.properties['openslide.vendor'], + 'generic-tiff') + self.assertRaises(KeyError, + lambda: self.osr.properties['__does_not_exist']) + # test __len__ and __iter__ + self.assertEqual(len([v for v in self.osr.properties]), + len(self.osr.properties)) + + def test_associated_images(self): + # XXX test an associated image + self.assertRaises(KeyError, + lambda: self.osr.associated_images['__missing']) + # test __len__ and __iter__ + self.assertEqual(len([v for v in self.osr.associated_images]), + len(self.osr.associated_images)) + + def test_read_region(self): + self.assertEqual(self.osr.read_region((-10, -10), 1, (400, 400)).size, + (400, 400)) + + def test_read_region_size_dimension_zero(self): + self.assertEqual(self.osr.read_region((0, 0), 1, (400, 0)).size, + (400, 0)) + + def test_read_region_bad_level(self): + self.assertEqual(self.osr.read_region((0, 0), 4, (100, 100)).size, + (100, 100)) + + def test_read_region_bad_size(self): + self.assertRaises(OpenSlideError, + lambda: self.osr.read_region((0, 0), 1, (400, -5))) + + def test_thumbnail(self): + self.assertEqual(self.osr.get_thumbnail((100, 100)).size, (100, 83)) diff --git a/tests/unopenable.tiff b/tests/unopenable.tiff new file mode 100644 index 0000000000000000000000000000000000000000..e3de7034255156506c2f98ea9bfc54d68dc1a94d GIT binary patch literal 1020 zcmebD)MAigW?-n8^Y)-2BLf4^;RQd<=Vrd(4`F8*1*0J_5<_4^h4@aUe*z2)jLZzc zECIntY$hNZm}M9~010NO`2RBu3~W#~6HrbFNxdkP4bmgU$O<;^0gxjNWxoKjWsuZs zAgOnPvKfGeIwP@N7+JvPZ~)D8g|dZ!<^%)Pfb97OB7opO5bUkx;Dpf}3?Rq|h5-FQ B9*+P3 literal 0 HcmV?d00001