Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/mr_ci.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
---
name: Units Tests

on: [push]
on:
pull_request:
branches:
- main

jobs:

Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/mr_ci_text_spotting.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
---
name: Units Tests - Text Spotting

on: [push]
on:
pull_request:
branches:
- main

# Run linter with github actions for quick feedbacks.
jobs:
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/publish-to-conda-forge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ name: Publish to Conda Forge
on:
workflow_dispatch:
push:
branches:
- main
tags:
- v*

Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/publish-to-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ name: Publish Tagged Python 🐍 distributions 📦 to PyPI
on:
workflow_dispatch:
push:
branches:
- main
tags:
- v*

Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ The following table shows which versions of MapReader are compatible with which

_Add new changes here_

### Added

- Added `skip_blank_patches` argument to `MapImages.patchify_all()` ([#540](https://github.com/maps-as-data/MapReader/pull/540))

## [v1.6.1](https://github.com/Living-with-machines/MapReader/releases/tag/v1.6.1) (2024-11-18)

### Added
Expand Down
1 change: 1 addition & 0 deletions docs/source/using-mapreader/step-by-step-guide/2-load.rst
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ This will create 1024 x 1024 pixel patches with 10% overlap between each patch.
- ``square_cuts`` - This is a deprecated method and no longer recommended for use. By default, this is set to ``False`` and padding is added to patches at the edges of the parent image to ensure square patches. If you set ``square_cuts=True``, instead of padding, there will be some overlap between edge patches.
- ``add_to_parent`` - By default, this is set to ``True`` so that each time you run ``patchify_all`` your patches are added to your ``MapImages`` object. Setting it to ``False`` (by specifying ``add_to_parent=False``) will mean your patches are created, but not added to your ``MapImages`` object. This can be useful for testing out different patch sizes.
- ``rewrite`` - By default, this is set to ``False`` so that if your patches already exist they are not overwritten. Setting it to ``True`` (by specifying ``rewrite=True``) will mean already existing patches are recreated and overwritten.
- ``skip_blank_patches`` - By default, this is set to ``False``. Setting to ``True`` will omit any patches that only contain ``0`` values, which can speed up processing on irregularly shaped map images that have empty regions. The `Image.getbbox() <https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.getbbox>`_ method is used to determine if a patch is blank.

If you would like to save your patches as geo-referenced tiffs (i.e. geotiffs), use:

Expand Down
18 changes: 17 additions & 1 deletion mapreader/load/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -1208,6 +1208,7 @@ def patchify_all(
patch_size: int | None = 100,
tree_level: str | None = "parent",
path_save: str | None = None,
skip_blank_patches: bool = False,
add_to_parents: bool | None = True,
square_cuts: bool | None = False,
resize_factor: bool | None = False,
Expand All @@ -1234,6 +1235,8 @@ def patchify_all(
Directory to save the patches.
If None, will be set as f"patches_{patch_size}_{method}" (e.g. "patches_100_pixel").
By default None.
skip_blank_patches : bool
If True, any patch that only contains 0 values will be skipped, by default ``False``. Uses PIL.Image().get_bbox().
add_to_parents : bool, optional
If True, patches will be added to the MapImages instance's
``images`` dictionary, by default ``True``.
Expand Down Expand Up @@ -1313,6 +1316,7 @@ def patchify_all(
add_to_parents=add_to_parents,
resize_factor=resize_factor,
output_format=output_format,
skip_blank_patches=skip_blank_patches,
rewrite=rewrite,
verbose=verbose,
overlap=overlap,
Expand All @@ -1326,6 +1330,7 @@ def _patchify_by_pixel(
add_to_parents: bool | None = True,
resize_factor: bool | None = False,
output_format: str | None = "png",
skip_blank_patches: bool = False,
rewrite: bool | None = False,
verbose: bool | None = False,
overlap: int | None = 0,
Expand All @@ -1347,6 +1352,8 @@ def _patchify_by_pixel(
If True, resize the images before patchifying, by default ``False``.
output_format : str, optional
Format to use when writing image files, by default ``"png"``.
skip_blank_patches : bool
If True, any patch that only contains 0 values will be skipped, by default ``False``. Uses PIL.Image().get_bbox().
rewrite : bool, optional
If True, existing patches will be rewritten, by default ``False``.
verbose : bool, optional
Expand All @@ -1370,6 +1377,7 @@ def _patchify_by_pixel(
)

height, width = img.height, img.width
overlap_pixels = int(patch_size * overlap)

x = 0
while x < width:
Expand All @@ -1389,6 +1397,15 @@ def _patchify_by_pixel(

else:
patch = img.crop((x, y, max_x, max_y))

# skip if blank and don't add to parents
if skip_blank_patches and patch.getbbox() is None:
self._print_if_verbose(
f"[INFO] Skipping empty patch: {patch_id}.", verbose
)
y = y + patch_size - overlap_pixels
continue

if max_x == width:
patch = ImageOps.pad(
patch, (patch_size, patch.height), centering=(0, 0)
Expand Down Expand Up @@ -1416,7 +1433,6 @@ def _patchify_by_pixel(
self._add_patch_coords_id(patch_id)
self._add_patch_polygons_id(patch_id)

overlap_pixels = int(patch_size * overlap)
y = y + patch_size - overlap_pixels
x = x + patch_size - overlap_pixels

Expand Down
Binary file added tests/sample_files/cropped_blank_corners_rgb.tif
Binary file not shown.
Binary file added tests/sample_files/cropped_blank_corners_rgba.tif
Binary file not shown.
20 changes: 20 additions & 0 deletions tests/test_load/test_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,26 @@ def test_patchify_pixels(sample_dir, image_id, tmp_path):
assert os.path.isfile(f"{tmp_path}/patch-0-0-3-3-#{image_id}#.png")


def test_patchify_pixels_skip_blank_rgb(sample_dir, tmp_path):
maps = MapImages(f"{sample_dir}/cropped_blank_corners_rgb.tif")
maps.patchify_all(patch_size=3, path_save=tmp_path, skip_blank_patches=True)
parent_list = maps.list_parents()
patch_list = maps.list_patches()
assert len(parent_list) == 1
assert len(patch_list) == 5
assert os.path.isfile(f"{tmp_path}/patch-0-3-3-6-#cropped_blank_corners_rgb.tif#.png")


def test_patchify_pixels_skip_blank_rgba(sample_dir, tmp_path):
maps = MapImages(f"{sample_dir}/cropped_blank_corners_rgba.tif")
maps.patchify_all(patch_size=3, path_save=tmp_path, skip_blank_patches=True)
parent_list = maps.list_parents()
patch_list = maps.list_patches()
assert len(parent_list) == 1
assert len(patch_list) == 5
assert os.path.isfile(f"{tmp_path}/patch-0-3-3-6-#cropped_blank_corners_rgba.tif#.png")


def test_patchify_pixels_square(sample_dir, image_id, tmp_path):
maps = MapImages(f"{sample_dir}/{image_id}")
maps.patchify_all(patch_size=5, path_save=f"{tmp_path}_square", square_cuts=True)
Expand Down