From 1eacf541abd55f5a2177e0c8cba2ba1646bd990c Mon Sep 17 00:00:00 2001 From: Loic Coyle Date: Sat, 6 Jul 2024 00:07:40 +0200 Subject: [PATCH] tests: redo tests with fixtures --- tests/unit/conftest.py | 37 +++++++++++ tests/unit/test_grid.py | 118 ++++++++++++++++++---------------- tests/unit/test_master.py | 91 +++++++++++++------------- tests/unit/test_metrics.py | 55 ++++++++-------- tests/unit/test_mosaic.py | 77 +++++++--------------- tests/unit/test_pool.py | 82 +++++++++--------------- tests/unit/test_utils.py | 128 +++++++++++++++++-------------------- 7 files changed, 285 insertions(+), 303 deletions(-) create mode 100644 tests/unit/conftest.py diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py new file mode 100644 index 0000000..ce4ee9f --- /dev/null +++ b/tests/unit/conftest.py @@ -0,0 +1,37 @@ +from pathlib import Path +import shutil +import pytest + +from phomo import Pool, utils + + +TEST_PATH = Path("test_tiles/") + + +@pytest.fixture(scope="session") +def tile_dir(): + tile_dir = TEST_PATH / "rainbow" + if not tile_dir.is_dir(): + tile_dir.mkdir(parents=True) + channel_range = range(0, 255, 60) + utils.rainbow_of_squares( + tile_dir, + size=(50, 50), + r_range=channel_range, + g_range=channel_range, + b_range=channel_range, + ) + yield tile_dir + shutil.rmtree(TEST_PATH) + + +@pytest.fixture(scope="session") +def pool(tile_dir: Path): + # create test pool + return Pool.from_dir(tile_dir, tile_size=(10, 10)) + + +@pytest.fixture(scope="session") +def pool_big_tiles(tile_dir: Path): + # create test pool + return Pool.from_dir(tile_dir, tile_size=(50, 50)) diff --git a/tests/unit/test_grid.py b/tests/unit/test_grid.py index c4b2462..4f5d86c 100644 --- a/tests/unit/test_grid.py +++ b/tests/unit/test_grid.py @@ -1,61 +1,71 @@ -from unittest import TestCase - import numpy as np +import pytest from PIL import Image from phomo import Master from phomo.grid import Grid -class TestMaster(TestCase): - def setUp(self): - # create a test image object - self.master_array = np.hstack( - [ - np.ones((64, 72, 3), dtype="uint8") * 255, - np.zeros((64, 56, 3), dtype="uint8"), - ] - ) - self.master_img = Image.fromarray(self.master_array) - # create test master - self.master = Master.from_image(self.master_img) - self.grid = Grid(self.master, mosaic_shape=(64, 128), tile_shape=(16, 16)) - - def test_slices(self): - assert len(self.grid.slices) == (self.master_array.shape[0] // 16) * ( - self.master_array.shape[1] // 16 - ) - - def test_arrays(self): - assert len(self.grid.arrays) == (self.master_array.shape[0] // 16) * ( - self.master_array.shape[1] // 16 - ) - - def test_subdivide(self): - prev_len = len(self.grid.slices) - self.grid.subdivide(0.1) - new_len = len(self.grid.slices) - # 4 tiles get divided into 4 which adds 4*3 tiles - assert new_len == prev_len + 4 * 3 - - def test_origin(self): - assert self.grid.origin == (0, 0) - grid = Grid(self.master, mosaic_shape=(64, 128), tile_shape=(12, 12)) - assert grid.origin == (2, 4) - - def test_remove_origin(self): - grid = Grid(self.master, mosaic_shape=(64, 128), tile_shape=(12, 12)) - # has an starting offset - assert grid.slices[0][0].start == 2 - assert grid.slices[0][0].stop == 14 - assert grid.slices[0][1].start == 4 - assert grid.slices[0][1].stop == 16 - new_slices = grid.remove_origin(grid.slices[0]) - # no starting offset - assert new_slices[0].start == 0 - assert new_slices[0].stop == 12 - assert new_slices[1].start == 0 - assert new_slices[1].stop == 12 - - def test_plot(self): - assert isinstance(self.grid.plot(), Image.Image) +@pytest.fixture +def master_array(): + return np.hstack( + [ + np.ones((64, 72, 3), dtype="uint8") * 255, + np.zeros((64, 56, 3), dtype="uint8"), + ] + ) + + +@pytest.fixture +def master(master_array): + return Master.from_image(Image.fromarray(master_array)) + + +@pytest.fixture +def grid(master): + return Grid(master, mosaic_shape=(64, 128), tile_shape=(16, 16)) + + +def test_slices(grid: Grid, master_array): + assert len(grid.slices) == (master_array.shape[0] // 16) * ( + master_array.shape[1] // 16 + ) + + +def test_arrays(grid: Grid, master_array): + assert len(grid.arrays) == (master_array.shape[0] // 16) * ( + master_array.shape[1] // 16 + ) + + +def test_subdivide(grid: Grid, master_array): + prev_len = len(grid.slices) + grid.subdivide(0.1) + new_len = len(grid.slices) + # 4 tiles get divided into 4 which adds 4*3 tiles + assert new_len == prev_len + 4 * 3 + + +def test_origin(grid: Grid, master: Master): + assert grid.origin == (0, 0) + grid = Grid(master, mosaic_shape=(64, 128), tile_shape=(12, 12)) + assert grid.origin == (2, 4) + + +def test_remove_origin(master: Master): + grid = Grid(master, mosaic_shape=(64, 128), tile_shape=(12, 12)) + # has an starting offset + assert grid.slices[0][0].start == 2 + assert grid.slices[0][0].stop == 14 + assert grid.slices[0][1].start == 4 + assert grid.slices[0][1].stop == 16 + new_slices = grid.remove_origin(grid.slices[0]) + # no starting offset + assert new_slices[0].start == 0 + assert new_slices[0].stop == 12 + assert new_slices[1].start == 0 + assert new_slices[1].stop == 12 + + +def test_plot(grid: Grid): + assert isinstance(grid.plot(), Image.Image) diff --git a/tests/unit/test_master.py b/tests/unit/test_master.py index 4a05541..c7411bb 100644 --- a/tests/unit/test_master.py +++ b/tests/unit/test_master.py @@ -1,54 +1,49 @@ -from pathlib import Path -from shutil import rmtree -from unittest import TestCase - import numpy as np +import pytest from PIL import Image from phomo import Master -class TestMaster(TestCase): - @classmethod - def setUpClass(cls): - cls.test_dir = Path("test_master") - if not cls.test_dir.is_dir(): - cls.test_dir.mkdir() - cls.master_path = cls.test_dir / "master.png" - # create a test image object - cls.master_array = np.ones((64, 64, 3), dtype="uint8") * 255 - cls.master_img = Image.fromarray(cls.master_array) - # create a test image file - cls.master_img.save(cls.master_path) - # create test master - cls.master = Master.from_file(cls.master_path) - - def test_constructors(self): - master_img = Master.from_image(self.master_img) - master_file = Master.from_file(self.master_path) - assert (master_img.array == master_file.array).all() - # make sure it works for single channel modes - master = Master.from_image(self.master_img.convert("L")) - assert master.array.shape[-1] == 3 - - def test_img(self): - assert isinstance(self.master.img, Image.Image) - - def test_pixels(self): - assert self.master.pixels.shape[-1] == 3 - assert ( - self.master.pixels.shape[0] - == self.master_array.shape[0] * self.master_array.shape[1] - ) - - # plot methods - def test_palette(self): - self.master.plot() - - def test_plot(self): - self.master.plot() - - @classmethod - def tearDownClass(cls): - if cls.test_dir.is_dir(): - rmtree(cls.test_dir) +@pytest.fixture +def master_array(): + return np.ones((64, 64, 3), dtype="uint8") * 255 + + +@pytest.fixture +def master_img(master_array): + return Image.fromarray(master_array) + + +@pytest.fixture +def master_path(tmp_path, master_img): + path = tmp_path / "master.png" + master_img.save(path) + return path + + +@pytest.fixture +def master(master_path): + return Master.from_file(master_path) + + +def test_constructors(master_img: Image.Image, master_path): + master_from_img = Master.from_image(master_img) + master_from_file = Master.from_file(master_path) + assert (master_from_img.array == master_from_file.array).all() + # make sure it works for single channel modes + master = Master.from_image(master_img.convert("L")) + assert master.array.shape[-1] == 3 + + +def test_img(master: Master): + assert isinstance(master.img, Image.Image) + + +def test_pixels(master: Master, master_array): + assert master.pixels.shape[-1] == 3 + assert master.pixels.shape[0] == master_array.shape[0] * master_array.shape[1] + + +def test_plot(master: Master): + master.plot() diff --git a/tests/unit/test_metrics.py b/tests/unit/test_metrics.py index dddc73c..75f7e45 100644 --- a/tests/unit/test_metrics.py +++ b/tests/unit/test_metrics.py @@ -1,39 +1,38 @@ -from unittest import TestCase - import numpy as np from phomo import metrics -class TestMetrics(TestCase): - # TODO: these tests are not very good... - def test_norm(self): - a = np.ones((5, 4, 3)) - b = np.ones((1, 5, 4, 3)) - assert metrics.norm(a, b) == 0 +def test_norm(): + a = np.ones((5, 4, 3)) + b = np.ones((1, 5, 4, 3)) + assert metrics.norm(a, b) == 0 + + a = np.ones((5, 4, 3)) + b = np.zeros((1, 5, 4, 3)) + assert metrics.norm(a, b) > 0 + + +def test_greyscale(): + a = np.ones((5, 4, 3)) + b = np.ones((1, 5, 4, 3)) + assert metrics.greyscale(a, b) == 0 - a = np.ones((5, 4, 3)) - b = np.zeros((1, 5, 4, 3)) - assert metrics.norm(a, b) > 0 + a = np.ones((5, 4, 3)) + b = np.zeros((1, 5, 4, 3)) + assert metrics.greyscale(a, b) > 0 - def test_greyscale(self): - a = np.ones((5, 4, 3)) - b = np.ones((1, 5, 4, 3)) - assert metrics.greyscale(a, b) == 0 - a = np.ones((5, 4, 3)) - b = np.zeros((1, 5, 4, 3)) - assert metrics.greyscale(a, b) > 0 +def test_luv_approx(): + a = np.ones((5, 4, 3)) + b = np.ones((1, 5, 4, 3)) + assert metrics.luv_approx(a, b) == 0 - def test_luv_approx(self): - a = np.ones((5, 4, 3)) - b = np.ones((1, 5, 4, 3)) - assert metrics.luv_approx(a, b) == 0 + a = np.ones((5, 4, 3)) + b = np.zeros((1, 5, 4, 3)) + assert metrics.luv_approx(a, b) > 0 - a = np.ones((5, 4, 3)) - b = np.zeros((1, 5, 4, 3)) - assert metrics.luv_approx(a, b) > 0 - def test_metrics(self): - # make sure this dictionary gets populated - assert len(metrics.METRICS) > 0 +def test_metrics(): + # make sure this dictionary gets populated + assert len(metrics.METRICS) > 0 diff --git a/tests/unit/test_mosaic.py b/tests/unit/test_mosaic.py index 96ed224..8595127 100644 --- a/tests/unit/test_mosaic.py +++ b/tests/unit/test_mosaic.py @@ -1,68 +1,39 @@ -from pathlib import Path -from shutil import rmtree -from unittest import TestCase - import numpy as np +import pytest from PIL import Image -from phomo import Master, Mosaic, Pool, utils +from phomo import Master, Mosaic, Pool + +@pytest.fixture +def mosaic(pool_big_tiles): + master_shape = (550, 512) + # create a test image object + master_array = np.ones((*master_shape, 3), dtype="uint8") * 255 -class TestMosaic(TestCase): - @classmethod - def setUpClass(cls): - cls.test_dir = Path("test_mosaic") - if not cls.test_dir.is_dir(): - cls.test_dir.mkdir() + # create test master + master = Master.from_image(Image.fromarray(master_array)) + return Mosaic(master, pool_big_tiles) - cls.master_shape = (550, 512) - cls.master_path = cls.test_dir / "master.png" - # create a test image object - cls.master_array = np.ones((*cls.master_shape, 3), dtype="uint8") * 255 - cls.master_img = Image.fromarray(cls.master_array) - # create a test image file - cls.master_img.save(cls.master_path) - # create test master - cls.master = Master.from_file(cls.master_path) - # rainbow tile directory - cls.tile_dir = cls.test_dir / "rainbow" - if not cls.tile_dir.is_dir(): - cls.tile_dir.mkdir() - channel_range = range(0, 255, 60) - utils.rainbow_of_squares( - cls.tile_dir, - size=(50, 50), - r_range=channel_range, - g_range=channel_range, - b_range=channel_range, - ) - cls.tile_paths = list(cls.tile_dir.glob("*")) - # create test pool - cls.pool = Pool.from_dir(cls.tile_dir) +def test_tile_shape(mosaic: Mosaic, pool_big_tiles: Pool): + assert mosaic.tile_shape == pool_big_tiles.array[0].shape[:-1] - cls.mosaic = Mosaic(cls.master, cls.pool) - def test_tile_shape(self): - assert self.mosaic.tile_shape == self.pool.array[0].shape[:-1] +def test_size(mosaic: Mosaic): + assert mosaic.size == (500, 550) - def test_size(self): - assert self.mosaic.size == (500, 550) - def test_n_leftover(self): - assert self.mosaic.n_leftover == 15 +def test_n_leftover(mosaic: Mosaic): + assert mosaic.n_leftover == 15 - def test_build(self): - mosaic_img = self.mosaic.build(self.mosaic.d_matrix(workers=1)) - assert mosaic_img.size == self.mosaic.size - mosaic_img = self.mosaic.build(self.mosaic.d_matrix(workers=2)) - assert mosaic_img.size == self.mosaic.size +def test_build(mosaic: Mosaic): + mosaic_img = mosaic.build(mosaic.d_matrix(workers=1)) + assert mosaic_img.size == mosaic.size - with self.assertRaises(ValueError): - mosaic_img = self.mosaic.build(self.mosaic.d_matrix(workers=0)) + mosaic_img = mosaic.build(mosaic.d_matrix(workers=2)) + assert mosaic_img.size == mosaic.size - @classmethod - def tearDownClass(cls): - if cls.test_dir.is_dir(): - rmtree(cls.test_dir) + with pytest.raises(ValueError): + mosaic_img = mosaic.build(mosaic.d_matrix(workers=0)) diff --git a/tests/unit/test_pool.py b/tests/unit/test_pool.py index 46d8ca1..ca9e0f9 100644 --- a/tests/unit/test_pool.py +++ b/tests/unit/test_pool.py @@ -1,66 +1,44 @@ -from pathlib import Path -from shutil import rmtree -from unittest import TestCase - +import pytest from PIL import Image -from phomo import Pool, utils +from phomo import Pool from phomo.pool import PoolTiles -class TestPool(TestCase): - @classmethod - def setUpClass(cls): - cls.test_dir = Path("test_pool") - if not cls.test_dir.is_dir(): - cls.test_dir.mkdir() - # rainbow tile directory - cls.tile_dir = cls.test_dir / "rainbow" - if not cls.tile_dir.is_dir(): - cls.tile_dir.mkdir() - channel_range = range(0, 255, 60) - utils.rainbow_of_squares( - cls.tile_dir, - size=(10, 10), - r_range=channel_range, - g_range=channel_range, - b_range=channel_range, - ) - cls.tile_paths = list(cls.tile_dir.glob("*")) +@pytest.fixture +def tile_paths(tile_dir): + return list(tile_dir.glob("*")) + + +def test_from_dir(tile_dir, tile_paths): + pool = Pool.from_dir(tile_dir) + assert len(pool) == len(tile_paths) + - # create test pool - cls.pool = Pool.from_dir(cls.tile_dir) +def test_form_files(tile_paths): + pool = Pool.from_files(tile_paths) + assert len(pool) == len(tile_paths) - def test_from_dir(self): - pool = Pool.from_dir(self.tile_dir) - assert len(pool) == len(self.tile_paths) - def test_form_files(self): - pool = Pool.from_files(self.tile_paths) - assert len(pool) == len(self.tile_paths) +def test_tiles(pool: Pool): + tiles = pool.tiles + assert isinstance(tiles, PoolTiles) + assert isinstance(tiles[0], Image.Image) - def test_tiles(self): - tiles = self.pool.tiles - assert isinstance(tiles, PoolTiles) - assert isinstance(tiles[0], Image.Image) - def test_pixels(self): - pixels = self.pool.pixels - assert pixels.ndim == 2 - assert pixels.shape[-1] == 3 - assert pixels.shape[0] == len(self.tile_paths) * 10 * 10 +def test_pixels(pool: Pool, tile_paths): + pixels = pool.pixels + assert pixels.ndim == 2 + assert pixels.shape[-1] == 3 + assert ( + pixels.shape[0] + == len(tile_paths) * pool.tiles[0].size[0] * pool.tiles[0].size[0] + ) - def test_len(self): - assert len(self.tile_paths) == len(self.pool) - # plot methods - def test_palette(self): - self.pool.plot() +def test_len(pool: Pool, tile_paths): + assert len(tile_paths) == len(pool) - def test_plot(self): - self.pool.plot() - @classmethod - def tearDownClass(cls): - if cls.test_dir.is_dir(): - rmtree(cls.test_dir) +def test_plot(pool: Pool): + pool.plot() diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index cdbe112..0c4a31d 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -1,79 +1,71 @@ -from pathlib import Path -from shutil import rmtree -from unittest import TestCase - import numpy as np +import pytest from PIL import Image from phomo import utils -class TestUtils(TestCase): - @classmethod - def setUpClass(cls): - # create test directory - cls.test_dir = Path("test_utils") - if not cls.test_dir.is_dir(): - cls.test_dir.mkdir() - # rainbow tile directory - cls.rainbow_dir = cls.test_dir / "rainbow" - if not cls.rainbow_dir.is_dir(): - cls.rainbow_dir.mkdir() - # create a test image object - cls.test_array = np.ones((64, 64, 3), dtype="uint8") * 255 - cls.test_img = Image.fromarray(cls.test_array) - # create a test image file - cls.test_img_file = cls.test_dir / "test_img.png" - cls.test_img.save(cls.test_img_file) +@pytest.fixture +def img_array(): + return np.ones((64, 64, 3), dtype="uint8") * 255 + + +@pytest.fixture +def img(img_array): + return Image.fromarray(img_array) + + +@pytest.fixture +def img_file(tmp_path, img): + img_file = tmp_path / "test_img.png" + img.save(img_file) + return img_file + + +def test_rainbow_of_squares(tmp_path): + # create the squares + channel_range = range(0, 256, 15) + shape = (20, 10) + utils.rainbow_of_squares( + tmp_path, + size=shape, + r_range=channel_range, + g_range=channel_range, + b_range=channel_range, + ) + tiles = list(tmp_path.glob("*")) + # check the number of tiles created + assert len(tiles) == len(list(channel_range)) ** 3 + # check the size of the tiles + img = Image.open(tiles[0]) + assert img.size == shape + - def test_rainbow_of_squares(self): - # create the squares - channel_range = range(0, 256, 15) - shape = (20, 10) - utils.rainbow_of_squares( - self.rainbow_dir, - size=shape, - r_range=channel_range, - g_range=channel_range, - b_range=channel_range, - ) - tiles = list(self.rainbow_dir.glob("*")) - # check the number of tiles created - assert len(tiles) == len(list(channel_range)) ** 3 - # check the size of the tiles - img = Image.open(tiles[0]) - assert img.size == shape +def test_crop_to_ratio(img): + # create a test white image + img_cropped = utils.crop_to_ratio(img, ratio=2) + # check the aspect ration of the img + assert img_cropped.size[0] / img_cropped.size[1] == 2 + assert img_cropped.size == (64, 32) - def test_crop_to_ratio(self): - # create a test white image - img_cropped = utils.crop_to_ratio(self.test_img, ratio=2) - # check the aspect ration of the img - assert img_cropped.size[0] / img_cropped.size[1] == 2 - assert img_cropped.size == (64, 32) - def test_open_img_file(self): - # just open the file - img = utils.open_img_file(self.test_img_file) - assert isinstance(img, Image.Image) - # crop to ratio - img = utils.open_img_file(self.test_img_file, crop_ratio=2) - assert img.size[0] / img.size[1] == 2 - assert img.size == (64, 32) - # change image size - img = utils.open_img_file(self.test_img_file, crop_ratio=2, size=(32, 64)) - assert img.size == (32, 64) - # convert to mode - img = utils.open_img_file( - self.test_img_file, crop_ratio=2, size=(32, 64), mode="L" - ) - assert img.size == (32, 64) - assert img.mode == "L" +def test_resize_array(img_array): + resized = utils.resize_array(img_array, (32, 64)) + assert resized.shape == (64, 32, 3) - def test_resize_array(self): - resized = utils.resize_array(self.test_array, (32, 64)) - assert resized.shape == (64, 32, 3) - @classmethod - def tearDownClass(cls): - if cls.test_dir.is_dir(): - rmtree(cls.test_dir) +def test_open_img_file(img_file): + # just open the file + img = utils.open_img_file(img_file) + assert isinstance(img, Image.Image) + # crop to ratio + img = utils.open_img_file(img_file, crop_ratio=2) + assert img.size[0] / img.size[1] == 2 + assert img.size == (64, 32) + # change image size + img = utils.open_img_file(img_file, crop_ratio=2, size=(32, 64)) + assert img.size == (32, 64) + # convert to mode + img = utils.open_img_file(img_file, crop_ratio=2, size=(32, 64), mode="L") + assert img.size == (32, 64) + assert img.mode == "L"