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 Nov 9, 2021
commit c10c04cd69dfee5a3a2d147924b67129ebf1d074
39 changes: 29 additions & 10 deletions tiler/merger.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,12 @@ class Merger:
"""

def __init__(
self, tiler: Tiler, window: Union[None, str, np.ndarray] = None, logits: int = 0, atol: float = 1e-10):
self,
tiler: Tiler,
window: Union[None, str, np.ndarray] = None,
logits: int = 0,
atol: float = 1e-10,
):
"""Merger precomputes everything for merging together tiles created by given Tiler.

TODO:
Expand Down Expand Up @@ -101,7 +106,6 @@ def __init__(
def _generate_window(self, window: str, shape: Union[Tuple, List]) -> np.ndarray:
"""Generate n-dimensional window according to the given shape.
Adapted from: https://stackoverflow.com/a/53588640/1668421
We use scipy to generate windows (scipy.signal.get_window()).

Args:
window (str): Specifies window function. Must be one of `Merger.SUPPORTED_WINDOWS`.
Expand Down Expand Up @@ -282,7 +286,13 @@ 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:
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
Expand All @@ -307,7 +317,7 @@ def norm_by_weights(self, data: np.ndarray, weights: np.ndarray, atol: float = 1
weights[weights < atol] = 1

# reweight
data_re = data/weights
data_re = data / weights

return data_re

Expand All @@ -326,10 +336,15 @@ def do_unpad(self, data: np.ndarray, pads: np.ndarray) -> np.ndarray:
[type]
[description]
"""
slices = [slice(pad[0], data.shape[m]-pad[1]) for m, pad in enumerate(pads)]
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:
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.

Args:
Expand All @@ -345,15 +360,19 @@ def merge(self, unpad: bool = True, argmax: bool = False, data_orig_shape: np.nd
data = self.norm_by_weights(data, self.weights_sum, self.atol)

if unpad:
if not hasattr(self.tiler, 'pads'):
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.')
"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))
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))]
#
Expand Down
81 changes: 47 additions & 34 deletions tiler/tiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def __init__(
channel_dimension: Optional[int] = None,
mode: str = "constant",
constant_value: float = 0.0,
get_padding: bool = False
get_padding: bool = False,
):
"""Tiler class precomputes everything for tiling with specified parameters, without actually slicing data.
You can access tiles individually with `Tiler.get_tile()` or with an iterator, both individually and in batches,
Expand Down Expand Up @@ -88,14 +88,16 @@ def __init__(
# need to caclulate get correct padding?
if get_padding:
self.pads = self.calculate_padding(
self.data_shape, self.tile_shape, np.asarray(self.overlap))
self.data_shape, self.tile_shape, np.asarray(self.overlap)
)
self.data_shape = self.fix_data_shape(self.data_shape, self.pads)

# Tiling mode
self.mode = mode
if self.mode not in self.TILING_MODES:
raise ValueError(
f'{self.mode} is an unsupported tiling mode, please check the documentation.')
f"{self.mode} is an unsupported tiling mode, please check the documentation."
)

# Constant value used for constant tiling mode
self.constant_value = constant_value
Expand Down Expand Up @@ -471,11 +473,13 @@ def get_mosaic_shape(self, with_channel_dim: bool = False) -> np.ndarray:
]
return self._indexing_shape

def calculate_padding(self,
data_shape_nonpad: np.ndarray,
tile_shape: np.ndarray,
overlap: np.ndarray,
pprint: Optional[bool] = False) -> np.ndarray:
def calculate_padding(
self,
data_shape_nonpad: np.ndarray,
tile_shape: np.ndarray,
overlap: np.ndarray,
pprint: Optional[bool] = False,
) -> np.ndarray:
"""Calculates the Padding from a given input.


Expand Down Expand Up @@ -506,26 +510,31 @@ def calculate_padding(self,
overlap = np.mod(overlap, tile_shape)

# get padding -> note: at max adding 1 more tile should be nessary as negative overlap is not allowed
step_size = tile_shape-overlap
dis = (data_shape_nonpad-tile_shape)/step_size
step_size = tile_shape - overlap
dis = (data_shape_nonpad - tile_shape) / step_size

# assuming even tileshapes
last_pos = tile_shape+np.ceil(dis)*step_size
pad_add = last_pos-data_shape_nonpad
last_pos = tile_shape + np.ceil(dis) * step_size
pad_add = last_pos - data_shape_nonpad

# calculate pads and (if uneven padding necessary) pad more to the right
pads = np.transpose([pad_add//2, pad_add//2+np.mod(pad_add, 2)]).astype('int')
pads = np.transpose([pad_add // 2, pad_add // 2 + np.mod(pad_add, 2)]).astype(
"int"
)

# pretty print-out results if wanted
if pprint:
print(
f"Input: data_shape_nonpad={data_shape_nonpad},\t tile_shape={tile_shape},\t overlap=\t{overlap}\npads=\t{list(pads)}.")
f"Input: data_shape_nonpad={data_shape_nonpad},\t tile_shape={tile_shape},\t overlap=\t{overlap}\npads=\t{list(pads)}."
)

return pads

def pad_outer(self,
data: Union[np.ndarray, Callable[..., np.ndarray]],
pads: Union[np.ndarray, Tuple, List]) -> np.ndarray:
def pad_outer(
self,
data: Union[np.ndarray, Callable[..., np.ndarray]],
pads: Union[np.ndarray, Tuple, List],
) -> np.ndarray:
"""Simple padding wrapper to be part of the routine.

Parameters
Expand All @@ -540,11 +549,11 @@ def pad_outer(self,
[type]
[description]
"""
return np.pad(data, pads, mode='reflect')
return np.pad(data, pads, mode="reflect")

def fix_data_shape(self,
data_shape: np.ndarray,
pads: Union[np.ndarray, Tuple, List]):
def fix_data_shape(
self, data_shape: np.ndarray, pads: Union[np.ndarray, Tuple, List]
):
"""Calculate correct padded data-shape.

Parameters
Expand All @@ -561,32 +570,36 @@ def fix_data_shape(self,
"""
data_shape_new = np.array(data_shape)
for m, pad in enumerate(pads):
data_shape_new[m] += (pad[0]+pad[1])
data_shape_new[m] += pad[0] + pad[1]

return data_shape_new

def calculate_minimal_overlap(self,
data_shape: np.ndarray,
tile_shape: np.ndarray,
pprint: Optional[bool] = False) -> tuple:
def calculate_minimal_overlap(
self,
data_shape: np.ndarray,
tile_shape: np.ndarray,
pprint: Optional[bool] = False,
) -> tuple:

# get padding
rmod = np.mod(data_shape, tile_shape)
pad_add = np.mod(tile_shape-rmod, tile_shape)
data_shape_new = data_shape+pad_add
pads = np.transpose([np.ceil(pad_add/2), np.ceil(pad_add/2) +
np.mod(pad_add, 2)]).astype('int')
pad_add = np.mod(tile_shape - rmod, tile_shape)
data_shape_new = data_shape + pad_add
pads = np.transpose(
[np.ceil(pad_add / 2), np.ceil(pad_add / 2) + np.mod(pad_add, 2)]
).astype("int")

# get minimal overlap
divs = (data_shape_new//tile_shape)-1
divs = (data_shape_new // tile_shape) - 1
divs[divs < 1] = 1
overlap = np.floor(pad_add/divs).astype('int')
overlap_perc = overlap/tile_shape
overlap = np.floor(pad_add / divs).astype("int")
overlap_perc = overlap / tile_shape

# pretty print
if pprint:
print(
f"Input: data_shape=\t{data_shape},\t tile_shape={tile_shape}\noverlap=\t{overlap}\noverlap_perc=\t{overlap_perc}\npads=\t\t{list(pads)}\n~~~~~~~~~~~~~~~~~~~~")
f"Input: data_shape=\t{data_shape},\t tile_shape={tile_shape}\noverlap=\t{overlap}\noverlap_perc=\t{overlap_perc}\npads=\t\t{list(pads)}\n~~~~~~~~~~~~~~~~~~~~"
)

# done?
return overlap, overlap_perc, pads