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
Merge remote-tracking branch 'origin/master' into renerichter-rl
  • Loading branch information
the-lay committed Nov 8, 2021
commit 6db742e504b0f5b4e5fb95567212da37b0f5df09
34 changes: 20 additions & 14 deletions tiler/merger.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,8 @@ class Merger:
(Ronneberger et al. 2015, U-Net paper)
"""

def __init__(self,
tiler: Tiler,
window: Union[None, str, np.ndarray] = None,
logits: int = 0,
atol: float = 1e-10):
def __init__(
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 @@ -173,7 +170,8 @@ def set_window(self, window: Union[None, str, np.ndarray] = None) -> None:
self.window = window
else:
raise ValueError(
f'Unsupported type for window function ({type(window)}), expected str or np.ndarray.')
f"Unsupported type for window function ({type(window)}), expected str or np.ndarray."
)

def reset(self) -> None:
"""Reset data and normalization buffers.
Expand Down Expand Up @@ -215,8 +213,11 @@ def add(self, tile_id: int, data: np.ndarray) -> None:
)

data_shape = np.array(data.shape)
expected_tile_shape = ((self.logits, ) + tuple(self.tiler.tile_shape)
) if self.logits > 0 else tuple(self.tiler.tile_shape)
expected_tile_shape = (
((self.logits,) + tuple(self.tiler.tile_shape))
if self.logits > 0
else tuple(self.tiler.tile_shape)
)

if self.tiler.mode != "irregular":
if not np.all(np.equal(data_shape, expected_tile_shape)):
Expand All @@ -243,8 +244,9 @@ def add(self, tile_id: int, data: np.ndarray) -> None:

# TODO check for self.data and data dtypes mismatch?
if self.logits > 0:
self.data[tuple([slice(None, None, None)] + sl)
] += (data * self.window[tuple(win_sl[1:])])
self.data[tuple([slice(None, None, None)] + sl)] += (
data * self.window[tuple(win_sl[1:])]
)
self.weights_sum[tuple(sl)] += self.window[tuple(win_sl[1:])]
else:
self.data[tuple(sl)] += data * self.window[tuple(win_sl)]
Expand All @@ -269,15 +271,19 @@ def add_batch(self, batch_id: int, batch_size: int, data: np.ndarray) -> None:

if batch_id < 0 or batch_id >= n_batches:
raise IndexError(
f'Out of bounds. There are {n_batches} batches of {batch_size}, starting from index 0.')
f"Out of bounds. There are {n_batches} batches of {batch_size}, starting from index 0."
)

# add each tile in a batch with computed tile_id
for data_i, tile_i in enumerate(range(batch_id * batch_size,
min((batch_id + 1) * batch_size, len(self.tiler)))):
for data_i, tile_i in enumerate(
range(
batch_id * batch_size, min((batch_id + 1) * batch_size, len(self.tiler))
)
):
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.
"""Normalised applied weights such that sum guarantees approx. 1.

Parameters
----------
Expand Down
79 changes: 49 additions & 30 deletions tiler/tiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,16 @@ class Tiler:
The first values are used to pad the end and the end values are used to pad the beginning.
"""

def __init__(self,
data_shape: Union[Tuple, List],
tile_shape: Union[Tuple, List],
overlap: Union[int, float, Tuple, List] = 0,
channel_dimension: Optional[int] = None,
mode: str = 'constant',
constant_value: float = 0.0,
get_padding: bool = False):
def __init__(
self,
data_shape: Union[Tuple, List],
tile_shape: Union[Tuple, List],
overlap: Union[int, float, Tuple, List] = 0,
channel_dimension: Optional[int] = None,
mode: str = "constant",
constant_value: float = 0.0,
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,
with `Tiler.iterate()` (or the alias `Tiler.__call__()`).
Expand Down Expand Up @@ -126,11 +128,13 @@ def __init__(self,
self._tile_overlap[self.channel_dimension] = 0

elif isinstance(self.overlap, int):
tile_shape_without_channel = self.tile_shape[np.arange(
self._n_dim) != self.channel_dimension]
tile_shape_without_channel = self.tile_shape[
np.arange(self._n_dim) != self.channel_dimension
]
if self.overlap < 0 or np.any(self.overlap >= tile_shape_without_channel):
raise ValueError(
f'Integer overlap must be in range of 0 to {np.max(tile_shape_without_channel)}')
f"Integer overlap must be in range of 0 to {np.max(tile_shape_without_channel)}"
)

self._tile_overlap: np.ndarray = np.array(
[self.overlap for _ in self.tile_shape]
Expand All @@ -151,13 +155,16 @@ def __init__(self,
"Unsupported overlap mode (not float, int, list or tuple)."
)

self._tile_step: np.ndarray = (
self.tile_shape - self._tile_overlap).astype(int) # tile step
self._tile_step: np.ndarray = (self.tile_shape - self._tile_overlap).astype(
int
) # tile step

# Calculate mosaic (collection of tiles) shape
div, mod = np.divmod([self.data_shape[d] - self._tile_overlap[d]
for d in range(self._n_dim)], self._tile_step)
if self.mode == 'drop':
div, mod = np.divmod(
[self.data_shape[d] - self._tile_overlap[d] for d in range(self._n_dim)],
self._tile_step,
)
if self.mode == "drop":
self._indexing_shape = div
else:
self._indexing_shape = div + (mod != 0)
Expand All @@ -180,8 +187,9 @@ def __init__(self,
self._tile_step[self.channel_dimension] = 0

# Tile indexing
self._tile_index = np.vstack(np.meshgrid(
*[np.arange(0, x) for x in self._indexing_shape], indexing='ij'))
self._tile_index = np.vstack(
np.meshgrid(*[np.arange(0, x) for x in self._indexing_shape], indexing="ij")
)
self._tile_index = self._tile_index.reshape(self._n_dim, -1).T
self.n_tiles = len(self._tile_index)

Expand Down Expand Up @@ -356,8 +364,13 @@ def get_tile(
# get tile data
tile_corner = self._tile_index[tile_id] * self._tile_step
# take the lesser of the tile shape and the distance to the edge
sampling = [slice(tile_corner[d], np.min(
[self.data_shape[d], tile_corner[d] + self.tile_shape[d]])) for d in range(self._n_dim)]
sampling = [
slice(
tile_corner[d],
np.min([self.data_shape[d], tile_corner[d] + self.tile_shape[d]]),
)
for d in range(self._n_dim)
]

if callable(data):
sampling = [x.stop - x.start for x in sampling]
Expand All @@ -369,13 +382,18 @@ def get_tile(
tile_data = tile_data.copy()

shape_diff = self.tile_shape - tile_data.shape
if (self.mode != 'irregular') and np.any(shape_diff > 0):
if self.mode == 'constant':
tile_data = np.pad(tile_data, list((0, diff) for diff in shape_diff), mode=self.mode,
constant_values=self.constant_value)
elif self.mode == 'reflect' or self.mode == 'edge' or self.mode == 'wrap':
tile_data = np.pad(tile_data, list((0, diff)
for diff in shape_diff), mode=self.mode)
if (self.mode != "irregular") and np.any(shape_diff > 0):
if self.mode == "constant":
tile_data = np.pad(
tile_data,
list((0, diff) for diff in shape_diff),
mode=self.mode,
constant_values=self.constant_value,
)
elif self.mode == "reflect" or self.mode == "edge" or self.mode == "wrap":
tile_data = np.pad(
tile_data, list((0, diff) for diff in shape_diff), mode=self.mode
)

return tile_data

Expand Down Expand Up @@ -404,8 +422,9 @@ def get_tile_bbox_position(
starting_corner = self._tile_step * self.get_tile_mosaic_position(tile_id, True)
finish_corner = starting_corner + self.tile_shape
if self.channel_dimension is not None and not with_channel_dim:
dim_indices = list(range(self.channel_dimension)) + \
list(range(self.channel_dimension + 1, len(self._tile_step)))
dim_indices = list(range(self.channel_dimension)) + list(
range(self.channel_dimension + 1, len(self._tile_step))
)
starting_corner = starting_corner[dim_indices]
finish_corner = finish_corner[dim_indices]
return starting_corner, finish_corner
Expand Down Expand Up @@ -457,7 +476,7 @@ def calculate_padding(self,
tile_shape: np.ndarray,
overlap: np.ndarray,
pprint: Optional[bool] = False) -> np.ndarray:
"""Calculates the Padding from a given input.
"""Calculates the Padding from a given input.


Parameters
Expand Down
You are viewing a condensed version of this merge commit. You can view the full changes here.