From 87ff380fe482cd2426776bb9642f821500f5a05b Mon Sep 17 00:00:00 2001 From: skunkworxdark Date: Tue, 12 Dec 2023 13:40:28 +0000 Subject: [PATCH 1/5] fix for calc_tiles_min_overlap when tile size is bigger than image size --- invokeai/backend/tiles/tiles.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/invokeai/backend/tiles/tiles.py b/invokeai/backend/tiles/tiles.py index 1948f6624e..cdc94bf760 100644 --- a/invokeai/backend/tiles/tiles.py +++ b/invokeai/backend/tiles/tiles.py @@ -191,7 +191,14 @@ def calc_tiles_min_overlap( assert min_overlap < tile_height assert min_overlap < tile_width - # The If Else catches the case when the tile size is larger than the images size and just clips the number of tiles to 1 + # catches the cases when the tile size is larger than the images size and just clips the number of tiles to 1 + + if image_width < tile_width: + tile_width = image_width + + if image_height < tile_height: + tile_height = image_height + num_tiles_x = math.ceil((image_width - min_overlap) / (tile_width - min_overlap)) if tile_width < image_width else 1 num_tiles_y = ( math.ceil((image_height - min_overlap) / (tile_height - min_overlap)) if tile_height < image_height else 1 From 0b860582f0add6fc69374142f23d3744d1a8a8e6 Mon Sep 17 00:00:00 2001 From: skunkworxdark Date: Tue, 12 Dec 2023 14:00:06 +0000 Subject: [PATCH 2/5] remove unneeded if else --- invokeai/backend/tiles/tiles.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/invokeai/backend/tiles/tiles.py b/invokeai/backend/tiles/tiles.py index cdc94bf760..9f5817a4ca 100644 --- a/invokeai/backend/tiles/tiles.py +++ b/invokeai/backend/tiles/tiles.py @@ -199,10 +199,8 @@ def calc_tiles_min_overlap( if image_height < tile_height: tile_height = image_height - num_tiles_x = math.ceil((image_width - min_overlap) / (tile_width - min_overlap)) if tile_width < image_width else 1 - num_tiles_y = ( - math.ceil((image_height - min_overlap) / (tile_height - min_overlap)) if tile_height < image_height else 1 - ) + num_tiles_x = math.ceil((image_width - min_overlap) / (tile_width - min_overlap)) + num_tiles_y = math.ceil((image_height - min_overlap) / (tile_height - min_overlap)) # tiles[y * num_tiles_x + x] is the tile for the y'th row, x'th column. tiles: list[Tile] = [] From bca237228016db3ddb9674da07f63262a1abd218 Mon Sep 17 00:00:00 2001 From: skunkworxdark Date: Tue, 12 Dec 2023 14:02:28 +0000 Subject: [PATCH 3/5] updated comment --- invokeai/backend/tiles/tiles.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/invokeai/backend/tiles/tiles.py b/invokeai/backend/tiles/tiles.py index 9f5817a4ca..11d0c86c5c 100644 --- a/invokeai/backend/tiles/tiles.py +++ b/invokeai/backend/tiles/tiles.py @@ -191,8 +191,7 @@ def calc_tiles_min_overlap( assert min_overlap < tile_height assert min_overlap < tile_width - # catches the cases when the tile size is larger than the images size and just clips the number of tiles to 1 - + # catches the cases when the tile size is larger than the images size and adjusts the tile size if image_width < tile_width: tile_width = image_width From 612912a6c980d13e905a4a6086a722342fba735f Mon Sep 17 00:00:00 2001 From: skunkworxdark Date: Tue, 12 Dec 2023 14:12:22 +0000 Subject: [PATCH 4/5] updated tests with a test for tile > image for calc_tiles_min_overlap() --- tests/backend/tiles/test_tiles.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/backend/tiles/test_tiles.py b/tests/backend/tiles/test_tiles.py index 0b18f9ed54..114ff4a5e0 100644 --- a/tests/backend/tiles/test_tiles.py +++ b/tests/backend/tiles/test_tiles.py @@ -241,6 +241,28 @@ def test_calc_tiles_min_overlap_not_evenly_divisible(): assert tiles == expected_tiles +def test_calc_tiles_min_overlap_tile_bigger_than_image(): + """Test calc_tiles_min_overlap() behavior when the tile is nigger than the image""" + # Parameters mimic roughly the same output as the original tile generations of the same test name + tiles = calc_tiles_min_overlap( + image_height=1024, + image_width=1024, + tile_height=1408, + tile_width=1408, + min_overlap=128, + ) + + expected_tiles = [ + # single tile + Tile( + coords=TBLR(top=0, bottom=1024, left=0, right=1024), + overlap=TBLR(top=0, bottom=0, left=0, right=0), + ), + ] + + assert tiles == expected_tiles + + @pytest.mark.parametrize( [ "image_height", From 96a717c4ba4e88fc9fb25adfb1ae0ccd31dbf5ef Mon Sep 17 00:00:00 2001 From: skunkworxdark Date: Sun, 17 Dec 2023 15:10:50 +0000 Subject: [PATCH 5/5] In CalculateImageTilesEvenSplitInvocation to have overlap_fraction becomes just overlap. This is now in pixels rather than as a fraction of the tile size. Update calc_tiles_even_split() with the same change. Ensuring Overlap is within allowed size Update even_split tests --- invokeai/app/invocations/tiles.py | 12 ++-- invokeai/backend/tiles/tiles.py | 44 +++++++------- tests/backend/tiles/test_tiles.py | 96 +++++++++++++++---------------- 3 files changed, 75 insertions(+), 77 deletions(-) diff --git a/invokeai/app/invocations/tiles.py b/invokeai/app/invocations/tiles.py index cbf63ab169..e51f891a8d 100644 --- a/invokeai/app/invocations/tiles.py +++ b/invokeai/app/invocations/tiles.py @@ -77,7 +77,7 @@ class CalculateImageTilesInvocation(BaseInvocation): title="Calculate Image Tiles Even Split", tags=["tiles"], category="tiles", - version="1.0.0", + version="1.1.0", classification=Classification.Beta, ) class CalculateImageTilesEvenSplitInvocation(BaseInvocation): @@ -97,11 +97,11 @@ class CalculateImageTilesEvenSplitInvocation(BaseInvocation): ge=1, description="Number of tiles to divide image into on the y axis", ) - overlap_fraction: float = InputField( - default=0.25, + overlap: int = InputField( + default=128, ge=0, - lt=1, - description="Overlap between adjacent tiles as a fraction of the tile's dimensions (0-1)", + multiple_of=8, + description="The overlap, in pixels, between adjacent tiles.", ) def invoke(self, context: InvocationContext) -> CalculateImageTilesOutput: @@ -110,7 +110,7 @@ class CalculateImageTilesEvenSplitInvocation(BaseInvocation): image_width=self.image_width, num_tiles_x=self.num_tiles_x, num_tiles_y=self.num_tiles_y, - overlap_fraction=self.overlap_fraction, + overlap=self.overlap, ) return CalculateImageTilesOutput(tiles=tiles) diff --git a/invokeai/backend/tiles/tiles.py b/invokeai/backend/tiles/tiles.py index 11d0c86c5c..3c400fc87c 100644 --- a/invokeai/backend/tiles/tiles.py +++ b/invokeai/backend/tiles/tiles.py @@ -102,7 +102,7 @@ def calc_tiles_with_overlap( def calc_tiles_even_split( - image_height: int, image_width: int, num_tiles_x: int, num_tiles_y: int, overlap_fraction: float = 0 + image_height: int, image_width: int, num_tiles_x: int, num_tiles_y: int, overlap: int = 0 ) -> list[Tile]: """Calculate the tile coordinates for a given image shape with the number of tiles requested. @@ -111,31 +111,35 @@ def calc_tiles_even_split( image_width (int): The image width in px. num_x_tiles (int): The number of tile to split the image into on the X-axis. num_y_tiles (int): The number of tile to split the image into on the Y-axis. - overlap_fraction (float, optional): The target overlap as fraction of the tiles size. Defaults to 0. + overlap (int, optional): The overlap between adjacent tiles in pixels. Defaults to 0. Returns: list[Tile]: A list of tiles that cover the image shape. Ordered from left-to-right, top-to-bottom. """ - - # Ensure tile size is divisible by 8 + # Ensure the image is divisible by LATENT_SCALE_FACTOR if image_width % LATENT_SCALE_FACTOR != 0 or image_height % LATENT_SCALE_FACTOR != 0: raise ValueError(f"image size (({image_width}, {image_height})) must be divisible by {LATENT_SCALE_FACTOR}") - # Calculate the overlap size based on the percentage and adjust it to be divisible by 8 (rounding up) - overlap_x = LATENT_SCALE_FACTOR * math.ceil( - int((image_width / num_tiles_x) * overlap_fraction) / LATENT_SCALE_FACTOR - ) - overlap_y = LATENT_SCALE_FACTOR * math.ceil( - int((image_height / num_tiles_y) * overlap_fraction) / LATENT_SCALE_FACTOR - ) - # Calculate the tile size based on the number of tiles and overlap, and ensure it's divisible by 8 (rounding down) - tile_size_x = LATENT_SCALE_FACTOR * math.floor( - ((image_width + overlap_x * (num_tiles_x - 1)) // num_tiles_x) / LATENT_SCALE_FACTOR - ) - tile_size_y = LATENT_SCALE_FACTOR * math.floor( - ((image_height + overlap_y * (num_tiles_y - 1)) // num_tiles_y) / LATENT_SCALE_FACTOR - ) + if num_tiles_x > 1: + # ensure the overlap is not more than the maximum overlap if we only have 1 tile then we dont care about overlap + assert overlap <= image_width - (LATENT_SCALE_FACTOR * (num_tiles_x - 1)) + tile_size_x = LATENT_SCALE_FACTOR * math.floor( + ((image_width + overlap * (num_tiles_x - 1)) // num_tiles_x) / LATENT_SCALE_FACTOR + ) + assert overlap < tile_size_x + else: + tile_size_x = image_width + + if num_tiles_y > 1: + # ensure the overlap is not more than the maximum overlap if we only have 1 tile then we dont care about overlap + assert overlap <= image_height - (LATENT_SCALE_FACTOR * (num_tiles_y - 1)) + tile_size_y = LATENT_SCALE_FACTOR * math.floor( + ((image_height + overlap * (num_tiles_y - 1)) // num_tiles_y) / LATENT_SCALE_FACTOR + ) + assert overlap < tile_size_y + else: + tile_size_y = image_height # tiles[y * num_tiles_x + x] is the tile for the y'th row, x'th column. tiles: list[Tile] = [] @@ -143,7 +147,7 @@ def calc_tiles_even_split( # Calculate tile coordinates. (Ignore overlap values for now.) for tile_idx_y in range(num_tiles_y): # Calculate the top and bottom of the row - top = tile_idx_y * (tile_size_y - overlap_y) + top = tile_idx_y * (tile_size_y - overlap) bottom = min(top + tile_size_y, image_height) # For the last row adjust bottom to be the height of the image if tile_idx_y == num_tiles_y - 1: @@ -151,7 +155,7 @@ def calc_tiles_even_split( for tile_idx_x in range(num_tiles_x): # Calculate the left & right coordinate of each tile - left = tile_idx_x * (tile_size_x - overlap_x) + left = tile_idx_x * (tile_size_x - overlap) right = min(left + tile_size_x, image_width) # For the last tile in the row adjust right to be the width of the image if tile_idx_x == num_tiles_x - 1: diff --git a/tests/backend/tiles/test_tiles.py b/tests/backend/tiles/test_tiles.py index 114ff4a5e0..32c4bd34c8 100644 --- a/tests/backend/tiles/test_tiles.py +++ b/tests/backend/tiles/test_tiles.py @@ -305,9 +305,7 @@ def test_calc_tiles_min_overlap_input_validation( 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_fraction=0.25 - ) + tiles = calc_tiles_even_split(image_height=512, image_width=1024, num_tiles_x=1, num_tiles_y=1, overlap=64) expected_tiles = [ Tile( @@ -322,36 +320,34 @@ def test_calc_tiles_even_split_single_tile(): 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_fraction=0.25 - ) + tiles = calc_tiles_even_split(image_height=576, image_width=1600, num_tiles_x=3, num_tiles_y=2, overlap=64) 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), + 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=488, right=1112), - overlap=TBLR(top=0, bottom=72, left=136, right=136), + 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=976, right=1600), - overlap=TBLR(top=0, bottom=72, left=136, right=0), + 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=248, bottom=576, left=0, right=624), - overlap=TBLR(top=72, bottom=0, left=0, right=136), + coords=TBLR(top=256, bottom=576, left=0, right=576), + overlap=TBLR(top=64, bottom=0, left=0, right=64), ), Tile( - coords=TBLR(top=248, bottom=576, left=488, right=1112), - overlap=TBLR(top=72, bottom=0, left=136, right=136), + coords=TBLR(top=256, bottom=576, left=512, right=1088), + overlap=TBLR(top=64, bottom=0, left=64, right=64), ), Tile( - coords=TBLR(top=248, bottom=576, left=976, right=1600), - overlap=TBLR(top=72, bottom=0, left=136, right=0), + coords=TBLR(top=256, bottom=576, left=1024, right=1600), + overlap=TBLR(top=64, bottom=0, left=64, right=0), ), ] assert tiles == expected_tiles @@ -360,36 +356,34 @@ def test_calc_tiles_even_split_evenly_divisible(): 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_fraction=0.25 - ) + tiles = calc_tiles_even_split(image_height=400, image_width=1200, num_tiles_x=3, num_tiles_y=2, overlap=64) 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), + coords=TBLR(top=0, bottom=232, left=0, right=440), + overlap=TBLR(top=0, bottom=64, left=0, right=64), ), Tile( - coords=TBLR(top=0, bottom=224, left=360, right=824), - overlap=TBLR(top=0, bottom=56, left=104, right=104), + coords=TBLR(top=0, bottom=232, left=376, right=816), + overlap=TBLR(top=0, bottom=64, left=64, right=64), ), Tile( - coords=TBLR(top=0, bottom=224, left=720, right=1200), - overlap=TBLR(top=0, bottom=56, left=104, right=0), + coords=TBLR(top=0, bottom=232, left=752, right=1200), + overlap=TBLR(top=0, bottom=64, left=64, 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), + coords=TBLR(top=168, bottom=400, left=0, right=440), + overlap=TBLR(top=64, bottom=0, left=0, right=64), ), Tile( - coords=TBLR(top=168, bottom=400, left=360, right=824), - overlap=TBLR(top=56, bottom=0, left=104, right=104), + coords=TBLR(top=168, bottom=400, left=376, right=816), + overlap=TBLR(top=64, bottom=0, left=64, right=64), ), Tile( - coords=TBLR(top=168, bottom=400, left=720, right=1200), - overlap=TBLR(top=56, bottom=0, left=104, right=0), + coords=TBLR(top=168, bottom=400, left=752, right=1200), + overlap=TBLR(top=64, bottom=0, left=64, right=0), ), ] @@ -399,28 +393,26 @@ def test_calc_tiles_even_split_not_evenly_divisible(): 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_fraction=0.25 - ) + tiles = calc_tiles_even_split(image_height=1000, image_width=1000, num_tiles_x=2, num_tiles_y=2, overlap=64) 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), + coords=TBLR(top=0, bottom=528, left=0, right=528), + overlap=TBLR(top=0, bottom=64, left=0, right=64), ), Tile( - coords=TBLR(top=0, bottom=560, left=432, right=1000), - overlap=TBLR(top=0, bottom=128, left=128, right=0), + coords=TBLR(top=0, bottom=528, left=464, right=1000), + overlap=TBLR(top=0, bottom=64, left=64, 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), + coords=TBLR(top=464, bottom=1000, left=0, right=528), + overlap=TBLR(top=64, bottom=0, left=0, right=64), ), Tile( - coords=TBLR(top=432, bottom=1000, left=432, right=1000), - overlap=TBLR(top=128, bottom=0, left=128, right=0), + coords=TBLR(top=464, bottom=1000, left=464, right=1000), + overlap=TBLR(top=64, bottom=0, left=64, right=0), ), ] @@ -428,11 +420,13 @@ def test_calc_tiles_even_split_difficult_size(): @pytest.mark.parametrize( - ["image_height", "image_width", "num_tiles_x", "num_tiles_y", "overlap_fraction", "raises"], + ["image_height", "image_width", "num_tiles_x", "num_tiles_y", "overlap", "raises"], [ - (128, 128, 1, 1, 0.25, False), # OK + (128, 128, 1, 1, 127, False), # OK (128, 128, 1, 1, 0, False), # OK - (128, 128, 2, 1, 0, False), # OK + (128, 128, 2, 2, 0, False), # OK + (128, 128, 2, 1, 120, True), # overlap equals tile_height. + (128, 128, 1, 2, 120, True), # overlap equals tile_width. (127, 127, 1, 1, 0, True), # image size must be dividable by 8 ], ) @@ -441,15 +435,15 @@ def test_calc_tiles_even_split_input_validation( image_width: int, num_tiles_x: int, num_tiles_y: int, - overlap_fraction: float, + overlap: int, raises: bool, ): """Test that calc_tiles_even_split() raises an exception if the inputs are invalid.""" if raises: - with pytest.raises(ValueError): - calc_tiles_even_split(image_height, image_width, num_tiles_x, num_tiles_y, overlap_fraction) + with pytest.raises((AssertionError, ValueError)): + 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_fraction) + calc_tiles_even_split(image_height, image_width, num_tiles_x, num_tiles_y, overlap) #############################################