Skip to content

Commit

Permalink
chore: cleanup tiling strategy comments for classes and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
drduhe committed Oct 31, 2024
1 parent 2b2e2b2 commit 5629bdc
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,19 @@ def _group_features_by_overlap(
def _calculate_overlap_for_full_tiles(
full_image_size: ImageDimensions, fixed_tile_size: ImageDimensions, minimum_overlap: ImageDimensions
) -> ImageDimensions:
"""
Calculate the adjusted overlap for generating full tiles.
This method adjusts the minimum overlap to ensure that tiles generated from the
full image will align properly, minimizing any remaining space at the edges.
:param full_image_size: The dimensions of the full image (width, height) in pixels.
:param fixed_tile_size: The fixed dimensions of the tiles (width, height) in pixels.
:param minimum_overlap: The minimum overlap (width, height) between tiles in pixels.
:return: The adjusted overlap (width, height) that should be applied to the tiles.
"""

def expand_overlap(image_dimension: int, tile_dimension: int, minimum_overlap: int) -> int:
stride = tile_dimension - minimum_overlap
num_tiles = ceildiv(image_dimension - minimum_overlap, stride)
Expand All @@ -242,6 +255,21 @@ def expand_overlap(image_dimension: int, tile_dimension: int, minimum_overlap: i
def _calculate_region_size_for_full_tiles(
nominal_region_size: ImageDimensions, fixed_tile_size: ImageDimensions, minimum_overlap: ImageDimensions
) -> ImageDimensions:
"""
Calculate the adjusted region size to accommodate full tiles.
This method adjusts the region size to ensure that tiles can be generated without
leaving any partial tiles. It considers the fixed tile size and the minimum overlap
between tiles.
:param nominal_region_size: The nominal region size (width, height) in pixels.
:param fixed_tile_size: The fixed dimensions of the tiles (width, height) in pixels.
:param minimum_overlap: The minimum overlap (width, height) between tiles in pixels.
:raises ValueError: If the requested overlap is greater than or equal to the tile size.
:return: The adjusted region size (width, height) that accommodates full tiles.
"""
if minimum_overlap[0] >= fixed_tile_size[0] or minimum_overlap[1] >= fixed_tile_size[1]:
raise ValueError(f"Requested overlap {minimum_overlap} is invalid for tile size {fixed_tile_size}")

Expand Down
35 changes: 32 additions & 3 deletions test/aws/osml/model_runner/tile_worker/test_tiling_strategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,41 @@

import pytest

from aws.osml.model_runner.tile_worker.tiling_strategy import generate_crops


class TestTilingStrategy(TestCase):
def test_chip_generator(self):
from aws.osml.model_runner.tile_worker.tiling_strategy import generate_crops
def test_chip_generator_partial_overlap(self):
"""
Test that the chip generator correctly produces crops based on the given image size,
crop size, and overlap for a partial overlap scenario.
"""

# Set up chips with partial overlap
chip_list = []
for chip in generate_crops(((5, 10), (1024, 1024)), (300, 300), (44, 44)):
chip_list.append(chip)

# Verify the total number of chips and their positions/sizes
assert len(chip_list) == 16
assert chip_list[0] == ((5, 10), (300, 300))
assert chip_list[1] == ((5, 266), (300, 300))
assert chip_list[3] == ((5, 778), (256, 300))
assert chip_list[12] == ((773, 10), (300, 256))
assert chip_list[15] == ((773, 778), (256, 256))

def test_chip_generator_no_overlap(self):
"""
Test that the chip generator correctly produces crops based on the given image size,
crop size, and overlap for a scenario with no overlap.
"""

# Set up chips with no overlap, producing complete tiles
chip_list = []
for chip in generate_crops(((0, 0), (5000, 2500)), (2048, 2048), (0, 0)):
chip_list.append(chip)

# Verify the total number of chips and their positions/sizes
assert len(chip_list) == 6
assert chip_list[0] == ((0, 0), (2048, 2048))
assert chip_list[1] == ((0, 2048), (2048, 2048))
Expand All @@ -32,13 +47,27 @@ def test_chip_generator(self):
assert chip_list[4] == ((2048, 2048), (2048, 452))
assert chip_list[5] == ((2048, 4096), (904, 452))

def test_chip_generator_full_overlap(self):
"""
Test that the chip generator correctly produces crops based on the given image size,
crop size, and overlap for a full overlap scenario.
"""

# Set up chips with full overlap, large crop sizes
chip_list = []
for chip in generate_crops(((150, 150), (5000, 5000)), (2048, 2048), (1024, 1024)):
chip_list.append(chip)

# Verify the output is logically handled even without assert checks (e.g., no exceptions)
assert len(chip_list) == 16

def test_invalid_chip_generator(self):
from aws.osml.model_runner.tile_worker.tiling_strategy import generate_crops
"""
Test that the chip generator raises an error for invalid overlap configurations.
Verifies scenarios where the overlap exceeds crop size, which is not allowed.
"""

# Overlap values larger than crop dimensions should raise a ValueError
with pytest.raises(ValueError):
chip_list = []
for chip in generate_crops(((5, 10), (1024, 1024)), (300, 300), (301, 0)):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,27 @@

class TestVariableOverlapTilingStrategy(TestCase):
def test_compute_regions_full_image(self):
"""
Test that regions are correctly computed for a full-sized image
based on the specified nominal region size, tile size, and overlap.
"""
from aws.osml.model_runner.tile_worker import VariableOverlapTilingStrategy

tiling_strategy = VariableOverlapTilingStrategy()

# Define image and tiling parameters
full_image_size = (25000, 12000)
nominal_region_size = (10000, 10000)
overlap = (100, 100)
tile_size = (4096, 4096)

# Check a full image
# Compute regions for the full image
regions = tiling_strategy.compute_regions(((0, 0), full_image_size), nominal_region_size, tile_size, overlap)

# Verify the number of computed regions
assert len(regions) == 8

# Verify that all computed regions match expected results
for r in regions:
assert r in [
((0, 0), (7580, 8048)),
Expand All @@ -31,17 +40,26 @@ def test_compute_regions_full_image(self):
]

def test_compute_regions_roi(self):
"""
Test that regions are correctly computed within a specific region of interest (ROI)
based on the specified nominal region size, tile size, and overlap.
"""
from aws.osml.model_runner.tile_worker import VariableOverlapTilingStrategy

tiling_strategy = VariableOverlapTilingStrategy()

# Define tiling parameters
nominal_region_size = (10000, 10000)
overlap = (100, 100)
tile_size = (4096, 4096)

# Check regions generated from a subset of an image
# Compute regions within the ROI
regions = tiling_strategy.compute_regions(((200, 8000), (17000, 11800)), nominal_region_size, tile_size, overlap)

# Verify the number of computed regions
assert len(regions) == 6

# Verify that all computed regions match expected results
for r in regions:
assert r in [
((200, 8000), (7322, 7948)),
Expand All @@ -53,28 +71,40 @@ def test_compute_regions_roi(self):
]

def test_compute_regions_tiny_image(self):
"""
Test that regions are correctly computed for a tiny image where the
nominal region size is larger than the image itself.
"""
from aws.osml.model_runner.tile_worker import VariableOverlapTilingStrategy

tiling_strategy = VariableOverlapTilingStrategy()

# Define tiling parameters
nominal_region_size = (10000, 10000)
overlap = (100, 100)
tile_size = (4096, 4096)

# Check regions generated from an image that has a dimension smaller than the fixed tile size
# Compute regions for a tiny image
regions = tiling_strategy.compute_regions(((0, 0), (12000, 2000)), nominal_region_size, tile_size, overlap)

# Verify the number of computed regions
assert len(regions) == 2

# Verify that all computed regions match expected results
for r in regions:
assert r in [((0, 0), (8048, 2000)), ((0, 7904), (4096, 2000))]

def test_compute_tiles(self):
"""
Test that tiles are correctly computed within specified regions based on tile size and overlap.
"""
from aws.osml.model_runner.tile_worker import VariableOverlapTilingStrategy

tiling_strategy = VariableOverlapTilingStrategy()
overlap = (100, 100)
tile_size = (4096, 4096)

# First full region
# Test full region tiling
tiles = tiling_strategy.compute_tiles(((0, 0), (7580, 8048)), tile_size, overlap)
assert len(tiles) == 4
for t in tiles:
Expand All @@ -85,52 +115,59 @@ def test_compute_tiles(self):
((3952, 3484), (4096, 4096)),
]

# A region on the right edge of the image
# Test region on the right edge of the image
tiles = tiling_strategy.compute_tiles(((0, 20904), (4096, 8048)), tile_size, overlap)
assert len(tiles) == 2
for t in tiles:
assert t in [((0, 20904), (4096, 4096)), ((3952, 20904), (4096, 4096))]

# A region on the bottom edge of the image
# Test region on the bottom edge of the image
tiles = tiling_strategy.compute_tiles(((7904, 13936), (7580, 4096)), tile_size, overlap)
assert len(tiles) == 2
for t in tiles:
assert t in [((7904, 13936), (4096, 4096)), ((7904, 17420), (4096, 4096))]

# The bottom right corner region
# Test bottom right corner region
tiles = tiling_strategy.compute_tiles(((7904, 20904), (4096, 4096)), tile_size, overlap)
assert len(tiles) == 1
assert tiles[0] == ((7904, 20904), (4096, 4096))

def test_compute_tiles_tiny_region(self):
"""
Test that tiles are correctly computed for a small region that is smaller than the tile size.
"""
from aws.osml.model_runner.tile_worker import VariableOverlapTilingStrategy

tiling_strategy = VariableOverlapTilingStrategy()
overlap = (100, 100)
tile_size = (4096, 4096)

# If the requested region is smaller than the actual tile size then the full region
# will be turned into a single tile. This is an odd edge case that we don't expect
# to see much but we don't want to error out and will instead just fall back to
# a partial tile strategy.
# Compute tiles for a small region
tiles = tiling_strategy.compute_tiles(((10, 50), (1024, 2048)), tile_size, overlap)

# Verify that only a single tile is produced for the tiny region
assert len(tiles) == 1
assert tiles[0] == ((10, 50), (1024, 2048))

def test_deconflict_features(self):
"""
Test that duplicate features are properly deconflicted based on specified rules.
"""
from geojson import Feature

from aws.osml.model_runner.inference import FeatureSelector
from aws.osml.model_runner.tile_worker import VariableOverlapTilingStrategy

tiling_strategy = VariableOverlapTilingStrategy()

# Define image and tiling parameters
full_image_size = (25000, 12000)
full_image_region = ((0, 0), full_image_size)
nominal_region_size = (10000, 10000)
overlap = (100, 100)
tile_size = (4096, 4096)

# Sample feature inputs, including duplicates
features = [
# Duplicate features in overlap of two regions, keep 1
Feature(properties={"imageBBox": [20904, 7904, 20924, 7924]}),
Expand All @@ -149,20 +186,24 @@ def test_deconflict_features(self):
Feature(properties={"imageBBox": [10, 10, 10, 10]}),
]

# Mock feature selector to deconflict overlapping features
class DummyFeatureSelector(FeatureSelector):
def select_features(self, features):
if len(features) > 0:
return [features[0]]
return []

mock_feature_selector = Mock(wraps=DummyFeatureSelector())

# Deconflict features using the tiling strategy
deduped_features = tiling_strategy.cleanup_duplicate_features(
full_image_region, nominal_region_size, tile_size, overlap, features, mock_feature_selector
)

# Check to ensure we have the correct number of features returned and that the feature selector
# was called once for each overlapping group
# Verify the correct number of deconflicted features
assert len(deduped_features) == 6

# Verify that the feature selector was called for each overlapping group
assert len(mock_feature_selector.method_calls) == 4


Expand Down
Loading

0 comments on commit 5629bdc

Please sign in to comment.