diff --git a/invokeai/backend/tiles/tiles.py b/invokeai/backend/tiles/tiles.py index 2ae62e241d..9715fc4950 100644 --- a/invokeai/backend/tiles/tiles.py +++ b/invokeai/backend/tiles/tiles.py @@ -159,7 +159,7 @@ def calc_tiles_even_split( def calc_tiles_min_overlap( - image_height: int, image_width: int, tile_height: int, tile_width: int, min_overlap: int, round_to_8: bool + image_height: int, image_width: int, tile_height: int, tile_width: int, min_overlap: int = 0, round_to_8: bool = False ) -> list[Tile]: """Calculate the tile coordinates for a given image shape under a simple tiling scheme with overlaps. diff --git a/tests/backend/tiles/test_tiles.py b/tests/backend/tiles/test_tiles.py index 353e65d336..a930f2f829 100644 --- a/tests/backend/tiles/test_tiles.py +++ b/tests/backend/tiles/test_tiles.py @@ -1,7 +1,12 @@ import numpy as np import pytest -from invokeai.backend.tiles.tiles import calc_tiles_with_overlap, merge_tiles_with_linear_blending +from invokeai.backend.tiles.tiles import ( + calc_tiles_even_split, + calc_tiles_min_overlap, + calc_tiles_with_overlap, + merge_tiles_with_linear_blending, +) from invokeai.backend.tiles.utils import TBLR, Tile #################################### @@ -85,6 +90,237 @@ def test_calc_tiles_with_overlap_input_validation( calc_tiles_with_overlap(image_height, image_width, tile_height, tile_width, overlap) +#################################### +# Test calc_tiles_min_overlap(...) +#################################### + + +def test_calc_tiles_min_overlap_single_tile(): + """Test calc_tiles_min_overlap() behavior when a single tile covers the image.""" + tiles = calc_tiles_min_overlap( + image_height=512, image_width=1024, tile_height=512, tile_width=1024, min_overlap=64, round_to_8=False + ) + + expected_tiles = [ + Tile(coords=TBLR(top=0, bottom=512, left=0, right=1024), overlap=TBLR(top=0, bottom=0, left=0, right=0)) + ] + + assert tiles == expected_tiles + + +def test_calc_tiles_min_overlap_evenly_divisible(): + """Test calc_tiles_min_overlap() behavior when the image is evenly covered by multiple tiles.""" + # Parameters mimic roughly the same output as the original tile generations of the same test name + tiles = calc_tiles_min_overlap( + image_height=576, image_width=1600, tile_height=320, tile_width=576, min_overlap=64, round_to_8=False + ) + + expected_tiles = [ + # Row 0 + Tile(coords=TBLR(top=0, bottom=320, left=0, right=576), overlap=TBLR(top=0, bottom=64, left=0, right=64)), + Tile(coords=TBLR(top=0, bottom=320, left=512, right=1088), overlap=TBLR(top=0, bottom=64, left=64, right=64)), + Tile(coords=TBLR(top=0, bottom=320, left=1024, right=1600), overlap=TBLR(top=0, bottom=64, left=64, right=0)), + # Row 1 + Tile(coords=TBLR(top=256, bottom=576, left=0, right=576), overlap=TBLR(top=64, bottom=0, left=0, right=64)), + Tile(coords=TBLR(top=256, bottom=576, left=512, right=1088), overlap=TBLR(top=64, bottom=0, left=64, right=64)), + Tile(coords=TBLR(top=256, bottom=576, left=1024, right=1600), overlap=TBLR(top=64, bottom=0, left=64, right=0)), + ] + + assert tiles == expected_tiles + + +def test_calc_tiles_min_overlap_not_evenly_divisible(): + """Test calc_tiles_min_overlap() behavior when the image requires 'uneven' overlaps to achieve proper coverage.""" + # Parameters mimic roughly the same output as the original tile generations of the same test name + tiles = calc_tiles_min_overlap( + image_height=400, image_width=1200, tile_height=256, tile_width=512, min_overlap=64, round_to_8=False + ) + + expected_tiles = [ + # Row 0 + Tile(coords=TBLR(top=0, bottom=256, left=0, right=512), overlap=TBLR(top=0, bottom=112, left=0, right=168)), + Tile(coords=TBLR(top=0, bottom=256, left=344, right=856), overlap=TBLR(top=0, bottom=112, left=168, right=168)), + Tile(coords=TBLR(top=0, bottom=256, left=688, right=1200), overlap=TBLR(top=0, bottom=112, left=168, right=0)), + # Row 1 + Tile(coords=TBLR(top=144, bottom=400, left=0, right=512), overlap=TBLR(top=112, bottom=0, left=0, right=168)), + Tile( + coords=TBLR(top=144, bottom=400, left=448, right=960), overlap=TBLR(top=112, bottom=0, left=168, right=168) + ), + Tile( + coords=TBLR(top=144, bottom=400, left=688, right=1200), overlap=TBLR(top=112, bottom=0, left=168, right=0) + ), + ] + + assert tiles == expected_tiles + + +def test_calc_tiles_min_overlap_difficult_size(): + """Test calc_tiles_min_overlap() behavior when the image is a difficult size to spilt evenly and keep div8.""" + # Parameters are a difficult size for other tile gen routines to calculate + tiles = calc_tiles_min_overlap( + image_height=1000, image_width=1000, tile_height=256, tile_width=512, min_overlap=64, round_to_8=False + ) + + expected_tiles = [ + # Row 0 + Tile(coords=TBLR(top=0, bottom=512, left=0, right=512), overlap=TBLR(top=0, bottom=268, left=0, right=268)), + Tile(coords=TBLR(top=0, bottom=512, left=244, right=756), overlap=TBLR(top=0, bottom=268, left=268, right=268)), + Tile(coords=TBLR(top=0, bottom=512, left=488, right=1000), overlap=TBLR(top=0, bottom=268, left=268, right=0)), + # Row 1 + Tile(coords=TBLR(top=244, bottom=756, left=0, right=512), overlap=TBLR(top=268, bottom=268, left=0, right=0)), + Tile(coords=TBLR(top=244, bottom=756, left=244, right=756),overlap=TBLR(top=268, bottom=268, left=268, right=268)), + Tile(coords=TBLR(top=244, bottom=756, left=488, right=1000), overlap=TBLR(top=268, bottom=268, left=268, right=0)), + # Row 2 + Tile(coords=TBLR(top=488, bottom=1000, left=0, right=512), overlap=TBLR(top=268, bottom=0, left=0, right=268)), + Tile(coords=TBLR(top=488, bottom=1000, left=244, right=756),overlap=TBLR(top=268, bottom=0, left=268, right=268)), + Tile(coords=TBLR(top=488, bottom=1000, left=488, right=1000), overlap=TBLR(top=268, bottom=0, left=268, right=0)), + ] + + assert tiles == expected_tiles + + +def test_calc_tiles_min_overlap_difficult_size_div8(): + """Test calc_tiles_min_overlap() behavior when the image is a difficult size to spilt evenly and keep div8.""" + # Parameters are a difficult size for other tile gen routines to calculate + tiles = calc_tiles_min_overlap( + image_height=1000, image_width=1000, tile_height=256, tile_width=512, min_overlap=64, round_to_8=True + ) + + expected_tiles = [ + # Row 0 + Tile(coords=TBLR(top=0, bottom=512, left=0, right=560), overlap=TBLR(top=0, bottom=272, left=0, right=272)), + Tile(coords=TBLR(top=0, bottom=512, left=240, right=752), overlap=TBLR(top=0, bottom=272, left=272, right=264)), + Tile(coords=TBLR(top=0, bottom=512, left=488, right=1000), overlap=TBLR(top=0, bottom=272, left=264, right=0)), + # Row 1 + Tile(coords=TBLR(top=240, bottom=752, left=0, right=512), overlap=TBLR(top=272, bottom=264, left=0, right=272)), + Tile(coords=TBLR(top=240, bottom=752, left=240, right=752),overlap=TBLR(top=272, bottom=264, left=272, right=264)), + Tile(coords=TBLR(top=240, bottom=752, left=488, right=1000), overlap=TBLR(top=272, bottom=264, left=264, right=0)), + # Row 2 + Tile(coords=TBLR(top=488, bottom=1000, left=0, right=512), overlap=TBLR(top=264, bottom=0, left=0, right=272)), + Tile(coords=TBLR(top=488, bottom=1000, left=240, right=752),overlap=TBLR(top=264, bottom=0, left=272, right=264)), + Tile(coords=TBLR(top=488, bottom=1000, left=488, right=1000), overlap=TBLR(top=264, bottom=0, left=264, right=0)), + ] + + assert tiles == expected_tiles + + +@pytest.mark.parametrize( + ["image_height", "image_width", "tile_height", "tile_width", "overlap", "raises"], + [ + (128, 128, 128, 128, 127, False), # OK + (128, 128, 128, 128, 0, False), # OK + (128, 128, 64, 64, 0, False), # OK + (128, 128, 129, 128, 0, True), # tile_height exceeds image_height. + (128, 128, 128, 129, 0, True), # tile_width exceeds image_width. + (128, 128, 64, 128, 64, True), # overlap equals tile_height. + (128, 128, 128, 64, 64, True), # overlap equals tile_width. + ], +) +def test_calc_tiles_min_overlap_input_validation( + image_height: int, image_width: int, tile_height: int, tile_width: int, min_overlap: int, round_to_8: bool , raises: bool +): + """Test that calc_tiles_with_overlap() raises an exception if the inputs are invalid.""" + if raises: + with pytest.raises(AssertionError): + calc_tiles_min_overlap(image_height, image_width, tile_height, tile_width, min_overlap, round_to_8) + else: + calc_tiles_min_overlap(image_height, image_width, tile_height, tile_width, min_overlap, round_to_8) + +#################################### +# Test calc_tiles_even_split(...) +#################################### + + +def test_calc_tiles_even_split_single_tile(): + """Test calc_tiles_even_split() behavior when a single tile covers the image.""" + tiles = calc_tiles_even_split(image_height=512, image_width=1024, num_tiles_x=1, num_tiles_y=1, overlap=0.25) + + expected_tiles = [ + Tile(coords=TBLR(top=0, bottom=512, left=0, right=1024), overlap=TBLR(top=0, bottom=0, left=0, right=0)) + ] + + assert tiles == expected_tiles + + +def test_calc_tiles_even_split_evenly_divisible(): + """Test calc_tiles_even_split() behavior when the image is evenly covered by multiple tiles.""" + # Parameters mimic roughly the same output as the original tile generations of the same test name + tiles = calc_tiles_even_split(image_height=576, image_width=1600, num_tiles_x=3, num_tiles_y=2, overlap=0.25) + + expected_tiles = [ + # Row 0 + Tile(coords=TBLR(top=0, bottom=320, left=0, right=624), overlap=TBLR(top=0, bottom=72, left=0, right=136)), + Tile(coords=TBLR(top=0, bottom=320, left=488, right=1112), overlap=TBLR(top=0, bottom=72, left=136, right=136)), + Tile(coords=TBLR(top=0, bottom=320, left=976, right=1600), overlap=TBLR(top=0, bottom=72, left=136, right=0)), + # Row 1 + Tile(coords=TBLR(top=248, bottom=576, left=0, right=624), overlap=TBLR(top=72, bottom=0, left=0, right=136)), + Tile( + coords=TBLR(top=248, bottom=576, left=488, right=1112), overlap=TBLR(top=72, bottom=0, left=136, right=136) + ), + Tile(coords=TBLR(top=248, bottom=576, left=976, right=1600), overlap=TBLR(top=72, bottom=0, left=136, right=0)), + ] + assert tiles == expected_tiles + + +def test_calc_tiles_even_split_not_evenly_divisible(): + """Test calc_tiles_even_split() behavior when the image requires 'uneven' overlaps to achieve proper coverage.""" + # Parameters mimic roughly the same output as the original tile generations of the same test name + tiles = calc_tiles_even_split(image_height=400, image_width=1200, num_tiles_x=3, num_tiles_y=2, overlap=0.25) + + expected_tiles = [ + # Row 0 + Tile(coords=TBLR(top=0, bottom=224, left=0, right=464), overlap=TBLR(top=0, bottom=56, left=0, right=104)), + Tile(coords=TBLR(top=0, bottom=224, left=360, right=824), overlap=TBLR(top=0, bottom=56, left=104, right=104)), + Tile(coords=TBLR(top=0, bottom=224, left=720, right=1200), overlap=TBLR(top=0, bottom=56, left=104, right=0)), + # Row 1 + Tile(coords=TBLR(top=168, bottom=400, left=0, right=464), overlap=TBLR(top=56, bottom=0, left=0, right=104)), + Tile( + coords=TBLR(top=168, bottom=400, left=360, right=824), overlap=TBLR(top=56, bottom=0, left=104, right=104) + ), + Tile(coords=TBLR(top=168, bottom=400, left=720, right=1200), overlap=TBLR(top=56, bottom=0, left=104, right=0)), + ] + + assert tiles == expected_tiles + + +def test_calc_tiles_even_split_difficult_size(): + """Test calc_tiles_even_split() behavior when the image is a difficult size to spilt evenly and keep div8.""" + # Parameters are a difficult size for other tile gen routines to calculate + tiles = calc_tiles_even_split(image_height=1000, image_width=1000, num_tiles_x=2, num_tiles_y=2, overlap=0.25) + + expected_tiles = [ + # Row 0 + Tile(coords=TBLR(top=0, bottom=560, left=0, right=560), overlap=TBLR(top=0, bottom=128, left=0, right=128)), + Tile(coords=TBLR(top=0, bottom=560, left=432, right=1000), overlap=TBLR(top=0, bottom=128, left=128, right=0)), + # Row 1 + Tile(coords=TBLR(top=432, bottom=1000, left=0, right=560), overlap=TBLR(top=128, bottom=0, left=0, right=128)), + Tile( + coords=TBLR(top=432, bottom=1000, left=432, right=1000), overlap=TBLR(top=128, bottom=0, left=128, right=0) + ), + ] + + assert tiles == expected_tiles + +@pytest.mark.parametrize( + ["image_height", "image_width", "num_tiles_x", "num_tiles_y", "overlap", "raises"], + [ + (128, 128, 1, 1, 0.25, False), # OK + (128, 128, 1, 1, 0, False), # OK + (128, 128, 2, 1, 0, False), # OK + (127, 127, 1, 1, 0, True), # image size must be drivable by 8 + ], +) +def test_calc_tiles_even_split_input_validation( + image_height: int, image_width: int, num_tiles_x: int, num_tiles_y: int, overlap: float, raises: bool +): + """Test that calc_tiles_with_overlap() raises an exception if the inputs are invalid.""" + if raises: + with pytest.raises(AssertionError): + calc_tiles_even_split(image_height, image_width, num_tiles_x, num_tiles_y, overlap) + else: + calc_tiles_even_split(image_height, image_width, num_tiles_x, num_tiles_y, overlap) + + ############################################# # Test merge_tiles_with_linear_blending(...) #############################################