Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add auto-tiling and fix weighting after merging #7

Merged
merged 23 commits into from
Dec 14, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
5351226
add auto-tiling and fix weighting after merging
renerichter Sep 27, 2021
fdc38c8
add minimal overlap
renerichter Sep 29, 2021
6db742e
Merge remote-tracking branch 'origin/master' into renerichter-rl
the-lay Nov 8, 2021
c10c04c
Code linting
the-lay Nov 9, 2021
a14beea
Added ndarray types; Throw an exception in case of non-matching data.…
the-lay Nov 18, 2021
ab58cd3
Removing flattop window since it has negative values
the-lay Nov 18, 2021
46e948b
Code linting
the-lay Nov 19, 2021
a6e56e4
Added dtype keyword to Merger that specifies dtype of data buffer
the-lay Dec 10, 2021
4474666
Added apply_padding method; overlap now can be given as a numpy array
the-lay Dec 10, 2021
3640e2d
Small documentation fixes
the-lay Dec 10, 2021
ae79a80
Saving Merger data_visits is now optional
the-lay Dec 10, 2021
ebac20a
Fixed data visits check
the-lay Dec 10, 2021
51cc98c
Added an uncovered edge case test
the-lay Dec 11, 2021
5eee24b
Added test for Merger with disabled save_visits
the-lay Dec 11, 2021
aa86420
Refactored normalization by weights in merging
the-lay Dec 11, 2021
69ab5c9
Fixed explicit padding for odd data shapes
the-lay Dec 11, 2021
d0a559e
Hiding division by zero warning when normalizing by weight
the-lay Dec 11, 2021
f0b1f9b
Code linting
the-lay Dec 11, 2021
bd1cd9e
Updated documentation
the-lay Dec 11, 2021
f032b09
Fixing trying to submit coveralls on pull requests
the-lay Dec 11, 2021
054b5d7
Teaser image generated script now actually tiles and merges the image :)
the-lay Dec 11, 2021
a02c0c6
Merger buffer dtypes are now hardcoded, optional casting to specified…
the-lay Dec 11, 2021
c230be4
Refactored extra padding system and updated examples
the-lay Dec 12, 2021
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
Prev Previous commit
Next Next commit
Code linting
  • Loading branch information
the-lay committed Dec 11, 2021
commit f0b1f9beb5dbde0005f81078bcf84ef2228ffec5
4 changes: 2 additions & 2 deletions tests/test_merger.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,10 +253,10 @@ def test_merge(self):

# Unmatching dtype should raise an exception
with self.assertRaises(ValueError):
merger.add(0, np.zeros((12, ), dtype=np.int64))
merger.add(0, np.zeros((12,), dtype=np.int64))

# Test without normalization by weights
window = np.ones((12, )) * 2
window = np.ones((12,)) * 2
merger = Merger(tiler, window=window)
for t_id, t in tiler(self.data):
merger.add(t_id, t)
Expand Down
15 changes: 9 additions & 6 deletions tests/test_tiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,8 +402,13 @@ def test_overlap(self):
# Float overlap + channel dimension
c_data = np.expand_dims(self.data, 0)
tiler = Tiler(
data_shape=c_data.shape, tile_shape=(1, tile_size,),
overlap=overlap, channel_dimension=0
data_shape=c_data.shape,
tile_shape=(
1,
tile_size,
),
overlap=overlap,
channel_dimension=0,
)
expected_split = np.expand_dims(expected_split, 0)
calculated_split = np.expand_dims(calculated_split, 0)
Expand Down Expand Up @@ -557,16 +562,14 @@ def test_tile_bbox_position(self):

def test_apply_padding(self):

t1 = Tiler(data_shape=self.data.shape,
tile_shape=(3,),
mode="reflect")
t1 = Tiler(data_shape=self.data.shape, tile_shape=(3,), mode="reflect")

# without apply padding the padding is done based on data in the tile
# last tile has only one 99, but when reflect padded becomes 99, 99, 99
np.testing.assert_equal(t1.get_tile(self.data, 0), [0, 1, 2])
np.testing.assert_equal(t1.get_tile(self.data, len(t1) - 1), [99, 99, 99])

# padding should now be correctly applied,
# with apply padding, padding should now correctly take into account data outside the tile
data = t1.apply_padding(self.data, mode="reflect")
np.testing.assert_equal(t1.get_tile(data, 0), [1, 0, 1])
np.testing.assert_equal(t1.get_tile(data, len(t1) - 1), [98, 99, 98])
Expand Down
26 changes: 16 additions & 10 deletions tiler/merger.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,9 @@ def add(self, tile_id: int, data: np.ndarray) -> None:

# Check for dtype mismatch
if self.dtype != data.dtype:
raise ValueError(f"Passed data dtype ({data.dtype}) must match Merger initialized dtype ({self.dtype}).")
raise ValueError(
f"Passed data dtype ({data.dtype}) must match Merger initialized dtype ({self.dtype})."
)

# Select coordinates for data
shape_diff = expected_tile_shape - data_shape
Expand Down Expand Up @@ -304,11 +306,12 @@ def add_batch(self, batch_id: int, batch_size: int, data: np.ndarray) -> None:
):
self.add(tile_i, data[data_i])

def merge(self,
unpad: bool = True,
argmax: bool = False,
normalize_by_weights: bool = True,
) -> np.ndarray:
def merge(
self,
unpad: bool = True,
argmax: bool = False,
normalize_by_weights: bool = True,
) -> np.ndarray:
"""Returns merged data array obtained from added tiles.

Args:
Expand All @@ -331,13 +334,16 @@ def merge(self,
# TODO check which way is better
# ignoring should be more precise without atol
# but can hide other errors
with np.errstate(divide='ignore', invalid='ignore'):
with np.errstate(divide="ignore", invalid="ignore"):
data = np.nan_to_num(data / self.weights_sum)

if unpad:
sl = [slice(pad_from, shape - pad_to)
for shape, (pad_from, pad_to)
in zip(self.tiler.data_shape, self.tiler._padding)]
sl = [
slice(pad_from, shape - pad_to)
for shape, (pad_from, pad_to) in zip(
self.tiler.data_shape, self.tiler._padding
)
]

# if merger has logits dimension, add another slicing in front
if self.logits:
Expand Down
63 changes: 37 additions & 26 deletions tiler/tiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,18 +71,19 @@ def __init__(
Default is `0.0`.
"""

self._recalculate(data_shape, tile_shape,
overlap, channel_dimension,
mode, constant_value)

def _recalculate(self,
data_shape: Union[Tuple, List, np.ndarray],
tile_shape: Union[Tuple, List, np.ndarray],
overlap: Union[int, float, Tuple, List, np.ndarray] = 0,
channel_dimension: Optional[int] = None,
mode: str = "constant",
constant_value: float = 0.0,
) -> None:
self._recalculate(
data_shape, tile_shape, overlap, channel_dimension, mode, constant_value
)

def _recalculate(
self,
data_shape: Union[Tuple, List, np.ndarray],
tile_shape: Union[Tuple, List, np.ndarray],
overlap: Union[int, float, Tuple, List, np.ndarray] = 0,
channel_dimension: Optional[int] = None,
mode: str = "constant",
constant_value: float = 0.0,
) -> None:

# Data and tile shapes
self.data_shape = np.asarray(data_shape).astype(int)
Expand All @@ -94,9 +95,11 @@ def _recalculate(self,
"Tile and data shapes must be tuple or lists of positive numbers."
)
if self.tile_shape.size != self.data_shape.size:
raise ValueError("Tile and data shapes must have the same length. "
"Hint: if you require tiles with less dimensions than data, put 1 in sliced dimensions, "
"e.g. to get 1d 64px lines of 2d 64x64px image would mean tile_shape of (64, 1).")
raise ValueError(
"Tile and data shapes must have the same length. "
"Hint: if you require tiles with less dimensions than data, put 1 in sliced dimensions, "
"e.g. to get 1d 64px lines of 2d 64x64px image would mean tile_shape of (64, 1)."
)

# Tiling mode
self.mode = mode
Expand Down Expand Up @@ -151,7 +154,11 @@ def _recalculate(self,
if self.channel_dimension is not None:
self._tile_overlap[self.channel_dimension] = 0

elif isinstance(self.overlap, list) or isinstance(self.overlap, tuple) or isinstance(self.overlap, np.ndarray):
elif (
isinstance(self.overlap, list)
or isinstance(self.overlap, tuple)
or isinstance(self.overlap, np.ndarray)
):
if np.any(np.array(self.overlap) < 0) or np.any(
self.overlap >= self.tile_shape
):
Expand Down Expand Up @@ -489,10 +496,11 @@ def get_mosaic_shape(self, with_channel_dim: bool = False) -> np.ndarray:
]
return self._indexing_shape

def apply_padding(self,
data: np.ndarray,
mode: str = "reflect",
) -> np.ndarray:
def apply_padding(
self,
data: np.ndarray,
mode: str = "reflect",
) -> np.ndarray:
"""Applies difference between required data shape and original data shape as padding around data.
Automatically adjusts Tiler parameters and returns padded data.
Moreover, Merger (with `unpad=True`) will automatically remove padding created with this method.
Expand Down Expand Up @@ -530,18 +538,21 @@ def apply_padding(self,

# If user provided data that is different from initialized data shape, raise an error
if np.not_equal(self.data_shape, data.shape).any():
raise ValueError(f"Provided data has a different shape (data.shape={data.shape}) than what Tiler "
f"was initialized with (self.data_shape={self.data_shape}).")
raise ValueError(
f"Provided data has a different shape (data.shape={data.shape}) than what Tiler "
f"was initialized with (self.data_shape={self.data_shape})."
)

# Split shape diff in two and use those values as frame padding for data
pre_pad = self._shape_diff // 2
post_pad = (self._shape_diff // 2) + np.mod(self._shape_diff, 2)

# Adjust parameters for new data shape and
self._recalculate(self._new_shape,
self.tile_shape,
self.overlap,
)
self._recalculate(
self._new_shape,
self.tile_shape,
self.overlap,
)
self._padding = list(zip(pre_pad, post_pad))

# Return padded input data
Expand Down