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
Refactored normalization by weights in merging
  • Loading branch information
the-lay committed Dec 11, 2021
commit aa8642067b44d3053874d5f7437db63be2e52a52
25 changes: 23 additions & 2 deletions tests/test_merger.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

class TestMergingCommon(unittest.TestCase):
def setUp(self) -> None:
self.data = np.arange(0, 100)
self.data = np.arange(0, 100, dtype=np.float64)

def test_init(self):
tiler = Tiler(data_shape=self.data.shape, tile_shape=(10,))
Expand Down Expand Up @@ -240,7 +240,7 @@ def test_overlap_tile_window(self):

def test_merge(self):

# Test padding
# Test unpadding
tiler = Tiler(data_shape=self.data.shape, tile_shape=(12,))
merger = Merger(tiler)
for t_id, t in tiler(self.data):
Expand All @@ -251,6 +251,18 @@ def test_merge(self):
merger.merge(unpad=False), np.hstack((self.data, [0, 0, 0, 0, 0, 0, 0, 0]))
)

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

# Test without normalization by weights
window = np.ones((12, )) * 2
merger = Merger(tiler, window=window)
for t_id, t in tiler(self.data):
merger.add(t_id, t)
np.testing.assert_equal(merger.merge(normalize_by_weights=False), self.data * 2)
np.testing.assert_equal(merger.merge(normalize_by_weights=True), self.data)

# Test argmax
merger = Merger(tiler, logits=3)
for t_id, t in tiler(self.data):
Expand All @@ -275,3 +287,12 @@ def test_merge(self):
)
),
)

# Test explicit unpadding
tiler = Tiler(data_shape=self.data.shape, tile_shape=(12,))
merger = Merger(tiler)
padded_data = tiler.apply_padding(self.data)
np.testing.assert_equal(tiler._padding, [(4, 4)])
for t_id, t in tiler(padded_data):
merger.add(t_id, t)
np.testing.assert_equal(merger.merge(unpad=True), self.data)
110 changes: 26 additions & 84 deletions tiler/merger.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ def __init__(
logits: int = 0,
save_visits: bool = True,
dtype: np.dtype = np.float64,
atol: float = 1e-10,
):
"""Merger precomputes everything for merging together tiles created by given Tiler.

Expand All @@ -84,7 +83,6 @@ def __init__(
"""

self.tiler = tiler
self.atol = atol

# Logits support
if not isinstance(logits, int) or logits < 0:
Expand Down Expand Up @@ -115,6 +113,7 @@ def _generate_window(self, window: str, shape: Union[Tuple, List]) -> np.ndarray

Args:
window (str): Specifies window function. Must be one of `Merger.SUPPORTED_WINDOWS`.

shape (tuple or list): Shape of the requested window.

Returns:
Expand Down Expand Up @@ -216,6 +215,7 @@ def add(self, tile_id: int, data: np.ndarray) -> None:

Args:
tile_id (int): Specifies which tile it is.

data (np.ndarray): Specifies tile data.

Returns:
Expand Down Expand Up @@ -278,7 +278,9 @@ def add_batch(self, batch_id: int, batch_size: int, data: np.ndarray) -> None:

Args:
batch_id (int): Specifies batch number, must be >= 0.

batch_size (int): Specifies batch size, must be >= 0.

data (np.ndarray): Tile data array, must have shape `[batch, *tile_shape]

Returns:
Expand All @@ -302,101 +304,41 @@ def add_batch(self, batch_id: int, batch_size: int, data: np.ndarray) -> None:
):
self.add(tile_i, data[data_i])

def norm_by_weights(
self,
data: np.ndarray,
weights: np.ndarray,
atol: float = 1e-10,
in_place: bool = True,
) -> np.ndarray:
"""Normalised applied weights such that sum guarantees approx. 1.

Parameters
----------
data : np.ndarray
padded image data
weights : np.ndarray
weights that were generated by merging-process
atol : float, optional
absolute tolarenced weights size assuming weights in [0,1], by default 1e-10

Returns
-------
data_re: np.ndarray
reweighted data
"""
# do operation in-place?
if not in_place:
weights = np.array(weights)

# avoid division by values close to 0
weights[weights < atol] = 1

# reweight
data_re = data / weights

return data_re

def do_unpad(self, data: np.ndarray, pads: np.ndarray) -> np.ndarray:
"""Simple unpadding using the data-set and applied pads.

Parameters
----------
data : [type]
[description]
pads : [type]
[description]

Returns
-------
[type]
[description]
"""
slices = [slice(pad[0], data.shape[m] - pad[1]) for m, pad in enumerate(pads)]
return data[slices]

def merge(
self,
unpad: bool = True,
argmax: bool = False,
data_orig_shape: np.ndarray = None,
) -> np.ndarray:
"""Returns final merged data array obtained from added tiles.
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:
unpad (bool): If unpad is True, removes padded array elements. Default is True.

argmax (bool): If argmax is True, the first dimension will be argmaxed. Default is False.

normalize_by_weights (bool): If normalize is True, the accumulated data will be divided by weights. Default is True.

Returns:
np.ndarray: Final merged data array obtained from added tiles.
"""

data = self.data

# normalize weights to have final sum_up to 1
data = self.norm_by_weights(data, self.weights_sum, self.atol)
if normalize_by_weights:
data = np.nan_to_num(data / self.weights_sum)

if unpad:
if not hasattr(self.tiler, "pads"):
if data_orig_shape is None:
if self.data_orig_shape is None:
raise ValueError(
"data_orig_shape needs to be given if data_shape was aautomatically calculated."
)
else:
self.data_orig_shape = data_orig_shape
self.pads = self.tiler.calculate_padding(
data_shape_nonpad=data_orig_shape,
tile_shape=self.tiler.tile_shape,
overlap=np.array(self.tiler.overlap),
)
data = self.do_unpad(data, self.tiler.pads)
# sl = [slice(None, self.tiler.data_shape[i]) for i in range(len(self.tiler.data_shape))]
#
# if self.logits:
# sl = [slice(None, None, None)] + sl
#
# data = data[tuple(sl)]
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:
sl = [slice(None, None, None)] + sl

data = data[tuple(sl)]

if argmax:
data = np.argmax(data, 0)

return data