Skip to content

Commit aefa828

Browse files
Tiled upscaling - EvenSplit to use overlap in pixels instead tile fraction (#5309)
## What type of PR is this? (check all applicable) - [ ] Refactor - [x] Feature - [ ] Bug Fix - [ ] Optimization - [ ] Documentation Update - [ ] Community Node Submission ## Have you discussed this change with the InvokeAI team? - [x] Yes - [ ] No, because: ## Have you updated all relevant documentation? - [ ] Yes - [x] No ## Description Change CalculateImageTilesEvenSplitInvocation to have an overlap in pixels rather than as a percentage of the tile. This makes it easier to have predictable blending of the seams as you have a known overlap size. ## Related Tickets & Documents <!-- For pull requests that relate or close an issue, please include them below. For example having the text: "closes #1234" would connect the current pull request to issue 1234. And when we merge the pull request, Github will automatically close the issue. --> - Related Issue # - Closes # ## QA Instructions, Screenshots, Recordings <!-- Please provide steps on how to test changes, any hardware or software specifications as well as any other pertinent information. --> ## Merge Plan <!-- A merge plan describes how this PR should be handled after it is approved. Example merge plans: - "This PR can be merged when approved" - "This must be squash-merged when approved" - "DO NOT MERGE - I will rebase and tidy commits before merging" - "#dev-chat on discord needs to be advised of this change when it is merged" A merge plan is particularly important for large PRs or PRs that touch the database in any way. --> ## Added/updated tests? - [x] Yes - [ ] No : _please replace this line with details on why tests have not been included_ ## [optional] Are there any post deployment tasks we need to perform?
2 parents 77b7426 + 457b0df commit aefa828

File tree

3 files changed

+75
-77
lines changed

3 files changed

+75
-77
lines changed

invokeai/app/invocations/tiles.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def invoke(self, context: InvocationContext) -> CalculateImageTilesOutput:
7777
title="Calculate Image Tiles Even Split",
7878
tags=["tiles"],
7979
category="tiles",
80-
version="1.0.0",
80+
version="1.1.0",
8181
classification=Classification.Beta,
8282
)
8383
class CalculateImageTilesEvenSplitInvocation(BaseInvocation):
@@ -97,11 +97,11 @@ class CalculateImageTilesEvenSplitInvocation(BaseInvocation):
9797
ge=1,
9898
description="Number of tiles to divide image into on the y axis",
9999
)
100-
overlap_fraction: float = InputField(
101-
default=0.25,
100+
overlap: int = InputField(
101+
default=128,
102102
ge=0,
103-
lt=1,
104-
description="Overlap between adjacent tiles as a fraction of the tile's dimensions (0-1)",
103+
multiple_of=8,
104+
description="The overlap, in pixels, between adjacent tiles.",
105105
)
106106

107107
def invoke(self, context: InvocationContext) -> CalculateImageTilesOutput:
@@ -110,7 +110,7 @@ def invoke(self, context: InvocationContext) -> CalculateImageTilesOutput:
110110
image_width=self.image_width,
111111
num_tiles_x=self.num_tiles_x,
112112
num_tiles_y=self.num_tiles_y,
113-
overlap_fraction=self.overlap_fraction,
113+
overlap=self.overlap,
114114
)
115115
return CalculateImageTilesOutput(tiles=tiles)
116116

invokeai/backend/tiles/tiles.py

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ def calc_tiles_with_overlap(
102102

103103

104104
def calc_tiles_even_split(
105-
image_height: int, image_width: int, num_tiles_x: int, num_tiles_y: int, overlap_fraction: float = 0
105+
image_height: int, image_width: int, num_tiles_x: int, num_tiles_y: int, overlap: int = 0
106106
) -> list[Tile]:
107107
"""Calculate the tile coordinates for a given image shape with the number of tiles requested.
108108
@@ -111,47 +111,51 @@ def calc_tiles_even_split(
111111
image_width (int): The image width in px.
112112
num_x_tiles (int): The number of tile to split the image into on the X-axis.
113113
num_y_tiles (int): The number of tile to split the image into on the Y-axis.
114-
overlap_fraction (float, optional): The target overlap as fraction of the tiles size. Defaults to 0.
114+
overlap (int, optional): The overlap between adjacent tiles in pixels. Defaults to 0.
115115
116116
Returns:
117117
list[Tile]: A list of tiles that cover the image shape. Ordered from left-to-right, top-to-bottom.
118118
"""
119-
120-
# Ensure tile size is divisible by 8
119+
# Ensure the image is divisible by LATENT_SCALE_FACTOR
121120
if image_width % LATENT_SCALE_FACTOR != 0 or image_height % LATENT_SCALE_FACTOR != 0:
122121
raise ValueError(f"image size (({image_width}, {image_height})) must be divisible by {LATENT_SCALE_FACTOR}")
123122

124-
# Calculate the overlap size based on the percentage and adjust it to be divisible by 8 (rounding up)
125-
overlap_x = LATENT_SCALE_FACTOR * math.ceil(
126-
int((image_width / num_tiles_x) * overlap_fraction) / LATENT_SCALE_FACTOR
127-
)
128-
overlap_y = LATENT_SCALE_FACTOR * math.ceil(
129-
int((image_height / num_tiles_y) * overlap_fraction) / LATENT_SCALE_FACTOR
130-
)
131-
132123
# Calculate the tile size based on the number of tiles and overlap, and ensure it's divisible by 8 (rounding down)
133-
tile_size_x = LATENT_SCALE_FACTOR * math.floor(
134-
((image_width + overlap_x * (num_tiles_x - 1)) // num_tiles_x) / LATENT_SCALE_FACTOR
135-
)
136-
tile_size_y = LATENT_SCALE_FACTOR * math.floor(
137-
((image_height + overlap_y * (num_tiles_y - 1)) // num_tiles_y) / LATENT_SCALE_FACTOR
138-
)
124+
if num_tiles_x > 1:
125+
# ensure the overlap is not more than the maximum overlap if we only have 1 tile then we dont care about overlap
126+
assert overlap <= image_width - (LATENT_SCALE_FACTOR * (num_tiles_x - 1))
127+
tile_size_x = LATENT_SCALE_FACTOR * math.floor(
128+
((image_width + overlap * (num_tiles_x - 1)) // num_tiles_x) / LATENT_SCALE_FACTOR
129+
)
130+
assert overlap < tile_size_x
131+
else:
132+
tile_size_x = image_width
133+
134+
if num_tiles_y > 1:
135+
# ensure the overlap is not more than the maximum overlap if we only have 1 tile then we dont care about overlap
136+
assert overlap <= image_height - (LATENT_SCALE_FACTOR * (num_tiles_y - 1))
137+
tile_size_y = LATENT_SCALE_FACTOR * math.floor(
138+
((image_height + overlap * (num_tiles_y - 1)) // num_tiles_y) / LATENT_SCALE_FACTOR
139+
)
140+
assert overlap < tile_size_y
141+
else:
142+
tile_size_y = image_height
139143

140144
# tiles[y * num_tiles_x + x] is the tile for the y'th row, x'th column.
141145
tiles: list[Tile] = []
142146

143147
# Calculate tile coordinates. (Ignore overlap values for now.)
144148
for tile_idx_y in range(num_tiles_y):
145149
# Calculate the top and bottom of the row
146-
top = tile_idx_y * (tile_size_y - overlap_y)
150+
top = tile_idx_y * (tile_size_y - overlap)
147151
bottom = min(top + tile_size_y, image_height)
148152
# For the last row adjust bottom to be the height of the image
149153
if tile_idx_y == num_tiles_y - 1:
150154
bottom = image_height
151155

152156
for tile_idx_x in range(num_tiles_x):
153157
# Calculate the left & right coordinate of each tile
154-
left = tile_idx_x * (tile_size_x - overlap_x)
158+
left = tile_idx_x * (tile_size_x - overlap)
155159
right = min(left + tile_size_x, image_width)
156160
# For the last tile in the row adjust right to be the width of the image
157161
if tile_idx_x == num_tiles_x - 1:

tests/backend/tiles/test_tiles.py

Lines changed: 45 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -305,9 +305,7 @@ def test_calc_tiles_min_overlap_input_validation(
305305

306306
def test_calc_tiles_even_split_single_tile():
307307
"""Test calc_tiles_even_split() behavior when a single tile covers the image."""
308-
tiles = calc_tiles_even_split(
309-
image_height=512, image_width=1024, num_tiles_x=1, num_tiles_y=1, overlap_fraction=0.25
310-
)
308+
tiles = calc_tiles_even_split(image_height=512, image_width=1024, num_tiles_x=1, num_tiles_y=1, overlap=64)
311309

312310
expected_tiles = [
313311
Tile(
@@ -322,36 +320,34 @@ def test_calc_tiles_even_split_single_tile():
322320
def test_calc_tiles_even_split_evenly_divisible():
323321
"""Test calc_tiles_even_split() behavior when the image is evenly covered by multiple tiles."""
324322
# Parameters mimic roughly the same output as the original tile generations of the same test name
325-
tiles = calc_tiles_even_split(
326-
image_height=576, image_width=1600, num_tiles_x=3, num_tiles_y=2, overlap_fraction=0.25
327-
)
323+
tiles = calc_tiles_even_split(image_height=576, image_width=1600, num_tiles_x=3, num_tiles_y=2, overlap=64)
328324

329325
expected_tiles = [
330326
# Row 0
331327
Tile(
332-
coords=TBLR(top=0, bottom=320, left=0, right=624),
333-
overlap=TBLR(top=0, bottom=72, left=0, right=136),
328+
coords=TBLR(top=0, bottom=320, left=0, right=576),
329+
overlap=TBLR(top=0, bottom=64, left=0, right=64),
334330
),
335331
Tile(
336-
coords=TBLR(top=0, bottom=320, left=488, right=1112),
337-
overlap=TBLR(top=0, bottom=72, left=136, right=136),
332+
coords=TBLR(top=0, bottom=320, left=512, right=1088),
333+
overlap=TBLR(top=0, bottom=64, left=64, right=64),
338334
),
339335
Tile(
340-
coords=TBLR(top=0, bottom=320, left=976, right=1600),
341-
overlap=TBLR(top=0, bottom=72, left=136, right=0),
336+
coords=TBLR(top=0, bottom=320, left=1024, right=1600),
337+
overlap=TBLR(top=0, bottom=64, left=64, right=0),
342338
),
343339
# Row 1
344340
Tile(
345-
coords=TBLR(top=248, bottom=576, left=0, right=624),
346-
overlap=TBLR(top=72, bottom=0, left=0, right=136),
341+
coords=TBLR(top=256, bottom=576, left=0, right=576),
342+
overlap=TBLR(top=64, bottom=0, left=0, right=64),
347343
),
348344
Tile(
349-
coords=TBLR(top=248, bottom=576, left=488, right=1112),
350-
overlap=TBLR(top=72, bottom=0, left=136, right=136),
345+
coords=TBLR(top=256, bottom=576, left=512, right=1088),
346+
overlap=TBLR(top=64, bottom=0, left=64, right=64),
351347
),
352348
Tile(
353-
coords=TBLR(top=248, bottom=576, left=976, right=1600),
354-
overlap=TBLR(top=72, bottom=0, left=136, right=0),
349+
coords=TBLR(top=256, bottom=576, left=1024, right=1600),
350+
overlap=TBLR(top=64, bottom=0, left=64, right=0),
355351
),
356352
]
357353
assert tiles == expected_tiles
@@ -360,36 +356,34 @@ def test_calc_tiles_even_split_evenly_divisible():
360356
def test_calc_tiles_even_split_not_evenly_divisible():
361357
"""Test calc_tiles_even_split() behavior when the image requires 'uneven' overlaps to achieve proper coverage."""
362358
# Parameters mimic roughly the same output as the original tile generations of the same test name
363-
tiles = calc_tiles_even_split(
364-
image_height=400, image_width=1200, num_tiles_x=3, num_tiles_y=2, overlap_fraction=0.25
365-
)
359+
tiles = calc_tiles_even_split(image_height=400, image_width=1200, num_tiles_x=3, num_tiles_y=2, overlap=64)
366360

367361
expected_tiles = [
368362
# Row 0
369363
Tile(
370-
coords=TBLR(top=0, bottom=224, left=0, right=464),
371-
overlap=TBLR(top=0, bottom=56, left=0, right=104),
364+
coords=TBLR(top=0, bottom=232, left=0, right=440),
365+
overlap=TBLR(top=0, bottom=64, left=0, right=64),
372366
),
373367
Tile(
374-
coords=TBLR(top=0, bottom=224, left=360, right=824),
375-
overlap=TBLR(top=0, bottom=56, left=104, right=104),
368+
coords=TBLR(top=0, bottom=232, left=376, right=816),
369+
overlap=TBLR(top=0, bottom=64, left=64, right=64),
376370
),
377371
Tile(
378-
coords=TBLR(top=0, bottom=224, left=720, right=1200),
379-
overlap=TBLR(top=0, bottom=56, left=104, right=0),
372+
coords=TBLR(top=0, bottom=232, left=752, right=1200),
373+
overlap=TBLR(top=0, bottom=64, left=64, right=0),
380374
),
381375
# Row 1
382376
Tile(
383-
coords=TBLR(top=168, bottom=400, left=0, right=464),
384-
overlap=TBLR(top=56, bottom=0, left=0, right=104),
377+
coords=TBLR(top=168, bottom=400, left=0, right=440),
378+
overlap=TBLR(top=64, bottom=0, left=0, right=64),
385379
),
386380
Tile(
387-
coords=TBLR(top=168, bottom=400, left=360, right=824),
388-
overlap=TBLR(top=56, bottom=0, left=104, right=104),
381+
coords=TBLR(top=168, bottom=400, left=376, right=816),
382+
overlap=TBLR(top=64, bottom=0, left=64, right=64),
389383
),
390384
Tile(
391-
coords=TBLR(top=168, bottom=400, left=720, right=1200),
392-
overlap=TBLR(top=56, bottom=0, left=104, right=0),
385+
coords=TBLR(top=168, bottom=400, left=752, right=1200),
386+
overlap=TBLR(top=64, bottom=0, left=64, right=0),
393387
),
394388
]
395389

@@ -399,40 +393,40 @@ def test_calc_tiles_even_split_not_evenly_divisible():
399393
def test_calc_tiles_even_split_difficult_size():
400394
"""Test calc_tiles_even_split() behavior when the image is a difficult size to spilt evenly and keep div8."""
401395
# Parameters are a difficult size for other tile gen routines to calculate
402-
tiles = calc_tiles_even_split(
403-
image_height=1000, image_width=1000, num_tiles_x=2, num_tiles_y=2, overlap_fraction=0.25
404-
)
396+
tiles = calc_tiles_even_split(image_height=1000, image_width=1000, num_tiles_x=2, num_tiles_y=2, overlap=64)
405397

406398
expected_tiles = [
407399
# Row 0
408400
Tile(
409-
coords=TBLR(top=0, bottom=560, left=0, right=560),
410-
overlap=TBLR(top=0, bottom=128, left=0, right=128),
401+
coords=TBLR(top=0, bottom=528, left=0, right=528),
402+
overlap=TBLR(top=0, bottom=64, left=0, right=64),
411403
),
412404
Tile(
413-
coords=TBLR(top=0, bottom=560, left=432, right=1000),
414-
overlap=TBLR(top=0, bottom=128, left=128, right=0),
405+
coords=TBLR(top=0, bottom=528, left=464, right=1000),
406+
overlap=TBLR(top=0, bottom=64, left=64, right=0),
415407
),
416408
# Row 1
417409
Tile(
418-
coords=TBLR(top=432, bottom=1000, left=0, right=560),
419-
overlap=TBLR(top=128, bottom=0, left=0, right=128),
410+
coords=TBLR(top=464, bottom=1000, left=0, right=528),
411+
overlap=TBLR(top=64, bottom=0, left=0, right=64),
420412
),
421413
Tile(
422-
coords=TBLR(top=432, bottom=1000, left=432, right=1000),
423-
overlap=TBLR(top=128, bottom=0, left=128, right=0),
414+
coords=TBLR(top=464, bottom=1000, left=464, right=1000),
415+
overlap=TBLR(top=64, bottom=0, left=64, right=0),
424416
),
425417
]
426418

427419
assert tiles == expected_tiles
428420

429421

430422
@pytest.mark.parametrize(
431-
["image_height", "image_width", "num_tiles_x", "num_tiles_y", "overlap_fraction", "raises"],
423+
["image_height", "image_width", "num_tiles_x", "num_tiles_y", "overlap", "raises"],
432424
[
433-
(128, 128, 1, 1, 0.25, False), # OK
425+
(128, 128, 1, 1, 127, False), # OK
434426
(128, 128, 1, 1, 0, False), # OK
435-
(128, 128, 2, 1, 0, False), # OK
427+
(128, 128, 2, 2, 0, False), # OK
428+
(128, 128, 2, 1, 120, True), # overlap equals tile_height.
429+
(128, 128, 1, 2, 120, True), # overlap equals tile_width.
436430
(127, 127, 1, 1, 0, True), # image size must be dividable by 8
437431
],
438432
)
@@ -441,15 +435,15 @@ def test_calc_tiles_even_split_input_validation(
441435
image_width: int,
442436
num_tiles_x: int,
443437
num_tiles_y: int,
444-
overlap_fraction: float,
438+
overlap: int,
445439
raises: bool,
446440
):
447441
"""Test that calc_tiles_even_split() raises an exception if the inputs are invalid."""
448442
if raises:
449-
with pytest.raises(ValueError):
450-
calc_tiles_even_split(image_height, image_width, num_tiles_x, num_tiles_y, overlap_fraction)
443+
with pytest.raises((AssertionError, ValueError)):
444+
calc_tiles_even_split(image_height, image_width, num_tiles_x, num_tiles_y, overlap)
451445
else:
452-
calc_tiles_even_split(image_height, image_width, num_tiles_x, num_tiles_y, overlap_fraction)
446+
calc_tiles_even_split(image_height, image_width, num_tiles_x, num_tiles_y, overlap)
453447

454448

455449
#############################################

0 commit comments

Comments
 (0)