def test_init(self): tiler = Tiler(data_shape=self.data.shape, tile_shape=(10, )) # logits test with self.assertRaises(ValueError): Merger(tiler=tiler, logits=-1) with self.assertRaises(ValueError): Merger(tiler=tiler, logits='unsupported_type') merger = Merger(tiler=tiler) np.testing.assert_equal(merger.data.shape, self.data.shape) merger2 = Merger(tiler=tiler, logits=99) np.testing.assert_equal(merger2.data.shape, (99, ) + self.data.shape)
def test_overlap_tile_window(self): # no overlap is given - the resulting window should be just zeros tiler = Tiler(data_shape=(100, ), tile_shape=(10, ), overlap=0) merger = Merger(tiler=tiler, window='overlap-tile') np.testing.assert_equal(merger.window, np.zeros((10, ))) # odd overlap - discarding overlap // 2 elements from both sides tiler = Tiler(data_shape=(100, ), tile_shape=(10, ), overlap=5) merger = Merger(tiler=tiler, window='overlap-tile') np.testing.assert_equal(merger.window, [0, 0, 1, 1, 1, 1, 1, 1, 0, 0]) # even overlap - discarding overlap // 2 elements from both sides tiler = Tiler(data_shape=(100, ), tile_shape=(10, ), overlap=(6, )) merger = Merger(tiler=tiler, window='overlap-tile') np.testing.assert_equal(merger.window, [0, 0, 0, 1, 1, 1, 1, 0, 0, 0]) # channel dimension case tiler = Tiler(data_shape=(3, 100), tile_shape=(3, 4), channel_dimension=0, overlap=2) merger = Merger(tiler=tiler, window='overlap-tile') np.testing.assert_equal(merger.window, [[0, 1, 1, 0], [0, 1, 1, 0], [0, 1, 1, 0]]) # 2D even case tiler = Tiler(data_shape=(100, 100), tile_shape=(4, 4), overlap=(2, 2)) merger = Merger(tiler=tiler, window='overlap-tile') np.testing.assert_equal( merger.window, [[0, 0, 0, 0], [0, 1, 1, 0], [0, 1, 1, 0], [0, 0, 0, 0]]) # 2D odd case tiler = Tiler(data_shape=(100, 100), tile_shape=(4, 4), overlap=3) merger = Merger(tiler=tiler, window='overlap-tile') np.testing.assert_equal( merger.window, [[0, 0, 0, 0], [0, 1, 1, 0], [0, 1, 1, 0], [0, 0, 0, 0]]) # Channel + 2D even case tiler = Tiler(data_shape=(3, 100, 100), tile_shape=(3, 4, 4), channel_dimension=0, overlap=2) merger = Merger(tiler=tiler, window='overlap-tile') np.testing.assert_equal( merger.window, [[[0, 0, 0, 0], [0, 1, 1, 0], [0, 1, 1, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 1, 1, 0], [0, 1, 1, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 1, 1, 0], [0, 1, 1, 0], [0, 0, 0, 0]]])
def test_generate_window(self): tiler = Tiler(data_shape=self.data.shape, tile_shape=(10, )) with self.assertRaises(ValueError): Merger(tiler=tiler, window='unsupported_window') with self.assertRaises(ValueError): Merger(tiler=tiler, window=np.zeros((10, 10))) with self.assertRaises(ValueError): Merger(tiler=tiler, window=10) window = np.zeros((10, )) window[1:10] = 1 merger = Merger(tiler=tiler, window=window) for t_id, t in tiler(self.data): merger.add(t_id, t) np.testing.assert_equal(merger.merge(), [i if i % 10 else 0 for i in range(100)])
padded_image = np.pad(image, ((32, 32), (32, 32), (0, 0)), mode='reflect') # Specifying tiling parameters # The overlap should be 0.5, 64 or explicitly (64, 64, 0) tiler = Tiler(data_shape=padded_image.shape, tile_shape=(128, 128, 3), overlap=(64, 64, 0), channel_dimension=2) # Specifying merging parameters # You can define overlap-tile window explicitly, i.e. # window = np.zeros((128, 128, 3)) # window[32:-32, 32:-32, :] = 1 # merger = Merger(tiler=tiler, window=window) # or you can use overlap-tile window which will do that automatically based on tiler.overlap merger = Merger(tiler=tiler, window='overlap-tile') # Let's define a function that will be applied to each tile def process(patch: np.ndarray, sanity_check: bool = True) -> np.ndarray: # One example can be a sanity check # Make the parts that should be removed black # There should not appear any black spots in the final merged image if sanity_check: patch[:32, :, :] = 0 patch[-32:, :, :] = 0 patch[:, :32, :] = 0 patch[:, -32:, :] = 0 return patch
# That means we need to pad the image by 14 from each side # To extrapolate missing context let's use reflect mode padded_volume = np.pad(volume, 14, mode='reflect') # Specifying tiling # The overlap should be 28 voxels tiler = Tiler(data_shape=padded_volume.shape, tile_shape=(48, 48, 48), overlap=(28, 28, 28)) # Window function for merging window = np.zeros((48, 48, 48)) window[14:-14, 14:-14, 14:-14] = 1 # Specifying merging merger = Merger(tiler=tiler, window=window) # Let's define a function that will be applied to each tile # For this example, let's black out the sides that should be "cropped" by window function # as a way to confirm that only the middle parts are being merged def process(patch: np.ndarray) -> np.ndarray: patch[:14, :, :] = 0 patch[-14:, :, :] = 0 patch[:, :14, :] = 0 patch[:, -14:, :] = 0 patch[:, :, :14] = 0 patch[:, :, -14:] = 0 return patch # Iterate through all the tiles and apply the processing function and merge everything back for tile_id, tile in tiler(padded_volume, progress_bar=True):
import numpy as np from PIL import Image from tiler import Tiler, Merger # Loading image # Photo by Christian Holzinger on Unsplash: https://unsplash.com/photos/CUY_YHhCFl4 image = np.array(Image.open('example_image.jpg')) # 1280x1920x3 # Setup Tiler and Merger tiler = Tiler(data_shape=image.shape, tile_shape=(200, 200, 3), channel_dimension=2) merger = Merger(tiler) # Example 1: process all tiles one by one, i.e. batch_size=0 for tile_i, tile in tiler(image, batch_size=0): merger.add(tile_i, tile) result_bs0 = merger.merge().astype(np.uint8) # Example 2: process all tiles in batches of 1, i.e. batch_size=1 merger.reset() for batch_i, batch in tiler(image, batch_size=1): merger.add_batch(batch_i, 1, batch) result_bs1 = merger.merge().astype(np.uint8) # Example 3: process all tiles in batches of 10, i.e. batch_size=10 merger.reset() for batch_i, batch in tiler(image, batch_size=10): merger.add_batch(batch_i, 10, batch) result_bs10 = merger.merge().astype(np.uint8)
def test_batch_add(self): tiler = Tiler(data_shape=self.data.shape, tile_shape=(10, )) merger = Merger(tiler) batch1 = [x for _, x in tiler(self.data, False, batch_size=1)] np.testing.assert_equal(len(batch1), 10) np.testing.assert_equal(batch1[0].shape, ( 1, 10, )) for i, b in enumerate(batch1): merger.add_batch(i, 1, b) np.testing.assert_equal(merger.merge(), self.data) merger.reset() batch10 = [x for _, x in tiler(self.data, False, batch_size=10)] for i, b in enumerate(batch10): merger.add_batch(i, 10, b) np.testing.assert_equal(merger.merge(), self.data) merger.reset() batch8 = [x for _, x in tiler(self.data, False, batch_size=8)] np.testing.assert_equal(len(batch8), 2) np.testing.assert_equal(batch8[0].shape, ( 8, 10, )) np.testing.assert_equal(batch8[1].shape, ( 2, 10, )) for i, b in enumerate(batch8): merger.add_batch(i, 8, b) np.testing.assert_equal(merger.merge(), self.data) merger.reset() batch8_drop = [ x for _, x in tiler(self.data, False, batch_size=8, drop_last=True) ] np.testing.assert_equal(len(batch8_drop), 1) np.testing.assert_equal(batch8_drop[0].shape, ( 8, 10, )) for i, b in enumerate(batch8_drop): merger.add_batch(i, 8, b) np.testing.assert_equal(merger.merge()[:80], self.data[:80]) np.testing.assert_equal(merger.merge()[80:], np.zeros((20, ))) with self.assertRaises(IndexError): merger.add_batch(-1, 10, batch10[0]) with self.assertRaises(IndexError): merger.add_batch(10, 10, batch10[9])
def test_add(self): tiler = Tiler(data_shape=self.data.shape, tile_shape=(10, )) tiler2 = Tiler(data_shape=self.data.shape, tile_shape=(12, ), mode='irregular') tiler3 = Tiler(data_shape=(3, ) + self.data.shape, tile_shape=( 3, 10, ), channel_dimension=0) merger = Merger(tiler) merger_logits = Merger(tiler, logits=3) merger_irregular = Merger(tiler2) merger_channel_dim = Merger(tiler3) tile = tiler.get_tile(self.data, 0) tile_logits = np.vstack((tile, tile, tile)) tile_irregular = tiler2.get_tile(self.data, len(tiler2) - 1) # Wrong tile id cases with self.assertRaises(IndexError): merger.add(-1, np.ones((10, ))) with self.assertRaises(IndexError): merger.add(len(tiler), np.ones((10, ))) # Usual mergers expect tile_shape == data_shape with self.assertRaises(ValueError): merger.add(0, np.ones(( 3, 10, ))) merger.add(0, tile) np.testing.assert_equal(merger.merge()[:10], tile) # Logits merger expects an extra dimension in front for logits with self.assertRaises(ValueError): merger_logits.add(0, np.ones((10, ))) merger_logits.add(0, tile_logits) np.testing.assert_equal(merger_logits.merge()[:, :10], tile_logits) np.testing.assert_equal( merger_logits.merge(argmax=True)[:10], np.zeros((10, ))) # Irregular merger expects all(data_shape <= tile_shape) with self.assertRaises(ValueError): merger_irregular.add(0, np.ones((13, ))) merger_irregular.add(len(tiler2) - 1, tile_irregular) np.testing.assert_equal( merger_irregular.merge()[-len(tile_irregular):], tile_irregular) # Channel dimension merger with self.assertRaises(ValueError): merger_channel_dim.add(0, np.ones((10, ))) merger_channel_dim.add(0, tile_logits) np.testing.assert_equal(merger_channel_dim.merge()[:, :10], tile_logits) # gotta get that 100% coverage # this should just print a warning # let's suppress it to avoid confusion with open(os.devnull, "w") as null: with redirect_stderr(null): merger.set_window('boxcar')
def test_merge(self): # Test padding tiler = Tiler(data_shape=self.data.shape, tile_shape=(12, )) merger = Merger(tiler) for t_id, t in tiler(self.data): merger.add(t_id, t) np.testing.assert_equal(merger.merge(unpad=True), self.data) np.testing.assert_equal( merger.merge(unpad=False), np.hstack((self.data, [0, 0, 0, 0, 0, 0, 0, 0]))) # Test argmax merger = Merger(tiler, logits=3) for t_id, t in tiler(self.data): merger.add(t_id, np.vstack((t, t / 2, t / 3))) np.testing.assert_equal(merger.merge(unpad=True, argmax=True), np.zeros((100, ))) np.testing.assert_equal( merger.merge(unpad=True, argmax=False), np.vstack((self.data, self.data / 2, self.data / 3))) np.testing.assert_equal(merger.merge(unpad=False, argmax=True), np.zeros((108, ))) np.testing.assert_equal( merger.merge(unpad=False, argmax=False), np.vstack((np.hstack((self.data, [0, 0, 0, 0, 0, 0, 0, 0])), np.hstack((self.data, [0, 0, 0, 0, 0, 0, 0, 0])) / 2, np.hstack((self.data, [0, 0, 0, 0, 0, 0, 0, 0])) / 3)))