From 2034fc9c142978b02165b56f87e3afc5ff6902dc Mon Sep 17 00:00:00 2001 From: alisterburt Date: Thu, 13 Jan 2022 15:42:47 +0000 Subject: [PATCH 1/5] prepend extra dims following numpy broadcasting rules --- tiler/tiler.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tiler/tiler.py b/tiler/tiler.py index 3b1b55a..871c291 100644 --- a/tiler/tiler.py +++ b/tiler/tiler.py @@ -101,11 +101,20 @@ def recalculate( raise ValueError( "Tile and data shapes must be tuple or lists of positive numbers." ) + if self.tile_shape.size <= self.data_shape.size: + size_difference = self.data_shape.size - self.tile_shape.size + self.tile_shape = np.insert( + arr=self.tile_shape, + obj=0, + values=np.ones(size_difference), + axis=0 + ) 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)." + "Tile shape must have less elements than the data shape." + "Hint: your tile shape will be prepended with ones to match the data shape," + "following numpy broadcasting semantics." + "e.g. data_shape=(28, 28), tile_shape=(28) -> tile_shape=(1, 28)" ) # Tiling mode From ab50246bb6c6bfa2f669d6f9dcb80f8979007bc0 Mon Sep 17 00:00:00 2001 From: alisterburt Date: Thu, 13 Jan 2022 15:49:45 +0000 Subject: [PATCH 2/5] add test --- tests/test_tiler.py | 4 ++++ tiler/tiler.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_tiler.py b/tests/test_tiler.py index d9b34f3..8d5b009 100644 --- a/tests/test_tiler.py +++ b/tests/test_tiler.py @@ -35,6 +35,10 @@ def test_init(self): overlap="unsupported_overlap", ) + # test tile shape broadcasting + tiler = Tiler(data_shape=(300, 300), tile_shape=(10)) + assert np.allclose(tiler.tile_shape, (1, 10)) + def test_repr(self): # gotta get that coverage tiler = Tiler( diff --git a/tiler/tiler.py b/tiler/tiler.py index 871c291..32f0a68 100644 --- a/tiler/tiler.py +++ b/tiler/tiler.py @@ -95,7 +95,7 @@ def recalculate( if data_shape is not None: self.data_shape = np.asarray(data_shape).astype(int) if tile_shape is not None: - self.tile_shape = np.asarray(tile_shape).astype(int) + self.tile_shape = np.atleast_1d(np.asarray(tile_shape).astype(int)) self._n_dim: int = len(self.data_shape) if (self.tile_shape <= 0).any() or (self.data_shape <= 0).any(): raise ValueError( From 2d16b8d1b57533b5681f049f7a8f00b38d2d283e Mon Sep 17 00:00:00 2001 From: alisterburt Date: Thu, 13 Jan 2022 15:52:48 +0000 Subject: [PATCH 3/5] update README.md to reflect changes --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 851ce88..80cc88a 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,11 @@ This section is a work in progress. **How do I create tiles with less dimensions than the data array?** -Tiler expects `tile_shape` to have the same length as `data_shape`. -However you can specify the unwanted dimension as just 1. -For example, if you want to get 2d tiles out from 3d array you can specify depth dimension of 1: +Tiler expects `tile_shape` to have less than or the same number of elements as `data_shape`. +If `tile_shape` has less elements than `data_shape`, `tile_shape` will be prepended with +ones to match the size of `data_shape`. +For example, if you want to get 2d tiles out from 3d array: +`Tiler(data_shape=(128,128,128), tile_shape=(128, 128))`. + +This is equivalent to: `Tiler(data_shape=(128,128,128), tile_shape=(1, 128, 128))`. From aa291e2aadcf3f1c5632840467232b11f10a43d7 Mon Sep 17 00:00:00 2001 From: the-lay Date: Wed, 19 Jan 2022 20:43:27 +0200 Subject: [PATCH 4/5] Updated tile_shape matching; data_shape and tile_shape are now int64 --- README.md | 7 ++----- tests/test_tiler.py | 2 +- tiler/tiler.py | 34 ++++++++++++++++++---------------- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 80cc88a..53a5f15 100644 --- a/README.md +++ b/README.md @@ -138,9 +138,6 @@ This section is a work in progress. Tiler expects `tile_shape` to have less than or the same number of elements as `data_shape`. If `tile_shape` has less elements than `data_shape`, `tile_shape` will be prepended with -ones to match the size of `data_shape`. -For example, if you want to get 2d tiles out from 3d array: -`Tiler(data_shape=(128,128,128), tile_shape=(128, 128))`. - -This is equivalent to: +ones to match the size of `data_shape`. +For example, if you want to get 2d tiles out from 3d array you can initialize Tiler like this: `Tiler(data_shape=(128,128,128), tile_shape=(128, 128))` and it will be equivalent to `Tiler(data_shape=(128,128,128), tile_shape=(1, 128, 128))`. diff --git a/tests/test_tiler.py b/tests/test_tiler.py index 8d5b009..dfa54ea 100644 --- a/tests/test_tiler.py +++ b/tests/test_tiler.py @@ -36,7 +36,7 @@ def test_init(self): ) # test tile shape broadcasting - tiler = Tiler(data_shape=(300, 300), tile_shape=(10)) + tiler = Tiler(data_shape=(300, 300), tile_shape=(10,)) assert np.allclose(tiler.tile_shape, (1, 10)) def test_repr(self): diff --git a/tiler/tiler.py b/tiler/tiler.py index 32f0a68..f51cbbe 100644 --- a/tiler/tiler.py +++ b/tiler/tiler.py @@ -47,7 +47,8 @@ def __init__( If there is a channel dimension, it should be included in the shape. tile_shape (tuple, list or np.ndarray): Shape of a tile, e.g. (256, 256, 3), [64, 64, 64] or np.ndarray([3, 128, 128]). - Tile must have the same number of dimensions as data. + Tile must have the same number of dimensions as data or less. + If less, the shape will be automatically prepended with ones to match data_shape size. overlap (int, float, tuple, list or np.ndarray): Specifies overlap between tiles. If integer, the same overlap of overlap pixels applied in each dimension, except channel_dimension. @@ -93,28 +94,29 @@ def recalculate( # Data and tile shapes if data_shape is not None: - self.data_shape = np.asarray(data_shape).astype(int) + self.data_shape = np.asarray(data_shape, dtype=np.int64) if tile_shape is not None: - self.tile_shape = np.atleast_1d(np.asarray(tile_shape).astype(int)) + self.tile_shape = np.atleast_1d(np.asarray(tile_shape, dtype=np.int64)) + + # Append ones to match data_shape size + if self.tile_shape.size <= self.data_shape.size: + size_difference = self.data_shape.size - self.tile_shape.size + self.tile_shape = np.insert( + arr=self.tile_shape, obj=0, values=np.ones(size_difference), axis=0 + ) + warnings.warn( + f"Tiler automatically adjusted tile_shape from {tuple(tile_shape)} to {tuple(self.tile_shape)}." + ) self._n_dim: int = len(self.data_shape) if (self.tile_shape <= 0).any() or (self.data_shape <= 0).any(): raise ValueError( - "Tile and data shapes must be tuple or lists of positive numbers." - ) - if self.tile_shape.size <= self.data_shape.size: - size_difference = self.data_shape.size - self.tile_shape.size - self.tile_shape = np.insert( - arr=self.tile_shape, - obj=0, - values=np.ones(size_difference), - axis=0 + "Tile and data shapes must be tuple, list or ndarray of positive integers." ) if self.tile_shape.size != self.data_shape.size: raise ValueError( - "Tile shape must have less elements than the data shape." - "Hint: your tile shape will be prepended with ones to match the data shape," - "following numpy broadcasting semantics." - "e.g. data_shape=(28, 28), tile_shape=(28) -> tile_shape=(1, 28)" + "Tile shape must have less or equal number of elements compared to the data shape. " + "If less, your tile shape will be prepended with ones to match the data shape, " + "e.g. data_shape=(28, 28), tile_shape=(28) -> tile_shape=(1, 28)." ) # Tiling mode From c0b5a5baff74bc7bca92bb4887355ee6bb1eafcc Mon Sep 17 00:00:00 2001 From: the-lay Date: Wed, 19 Jan 2022 20:44:45 +0200 Subject: [PATCH 5/5] Updated documentation to most recent version; removed baby logo from the docs website --- README.md | 2 +- docs/index.html | 77 +++++++++++++++++++++++++++++++-------------- misc/baby_logo.png | Bin 0 -> 1969 bytes misc/docs.sh | 2 +- tiler/__init__.py | 11 ++++--- 5 files changed, 62 insertions(+), 30 deletions(-) create mode 100644 misc/baby_logo.png diff --git a/README.md b/README.md index 53a5f15..dd63456 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ![tiler_logo](https://user-images.githubusercontent.com/1889128/148645934-2b1545bc-e7ea-4f02-88fb-10100b6aa9be.png) tiler +# ![tiler_baby_logo](misc/baby_logo.png) tiler ![Tiler teaser image](misc/teaser/tiler_teaser.png) diff --git a/docs/index.html b/docs/index.html index 6867bad..5f7bf29 100644 --- a/docs/index.html +++ b/docs/index.html @@ -135,6 +135,9 @@

such as semantic segmentation in deep learning, especially in domains where images do not fit into GPU memory (e.g., hyperspectral satellite images, whole slide images, videos, tomography data).

+

Please see Quick start section.
+If you want to use tiler interactively, I highly recommend napari and napari-tiler plugin.

+

Features

    @@ -149,8 +152,7 @@

    Features

    Quick start

    -

    This is an example of basic functionality.
    -You can find more examples in examples.
    +

    You can find more examples in examples.
    For more Tiler and Merger functionality, please check documentation.

    import numpy as np
    @@ -263,9 +265,10 @@ 

    Frequently asked questions

    How do I create tiles with less dimensions than the data array?

    -

    Tiler expects tile_shape to have the same length as data_shape. -However you can specify the unwanted dimension as just 1. -For example, if you want to get 2d tiles out from 3d array you can specify depth dimension of 1: +

    Tiler expects tile_shape to have less than or the same number of elements as data_shape. +If tile_shape has less elements than data_shape, tile_shape will be prepended with +ones to match the size of data_shape.
    +For example, if you want to get 2d tiles out from 3d array you can initialize Tiler like this: Tiler(data_shape=(128,128,128), tile_shape=(128, 128)) and it will be equivalent to Tiler(data_shape=(128,128,128), tile_shape=(1, 128, 128)).

    @@ -298,10 +301,13 @@

    Frequently asked questions

    if "pdoc" in sys.modules: # pragma: no cover with open("README.md", "r") as f: _readme = f.read() - _readme = _readme.split("# tiler", 1)[1] # remove header - _readme = _readme.replace( - "misc/teaser/tiler_teaser.png", "tiler_teaser.png" - ) # replace image path + + # remove baby logo and header + _readme = _readme.split("\n", 2)[2] + + # replace teaser image path + _readme = _readme.replace("misc/teaser/tiler_teaser.png", "tiler_teaser.png") + _readme = _readme.replace("misc/baby_logo.png", "baby_logo.png") __doc__ = _readme @@ -360,7 +366,8 @@

    Frequently asked questions

    If there is a channel dimension, it should be included in the shape. tile_shape (tuple, list or np.ndarray): Shape of a tile, e.g. (256, 256, 3), [64, 64, 64] or np.ndarray([3, 128, 128]). - Tile must have the same number of dimensions as data. + Tile must have the same number of dimensions as data or less. + If less, the shape will be automatically prepended with ones to match data_shape size. overlap (int, float, tuple, list or np.ndarray): Specifies overlap between tiles. If integer, the same overlap of overlap pixels applied in each dimension, except channel_dimension. @@ -406,19 +413,29 @@

    Frequently asked questions

    # Data and tile shapes if data_shape is not None: - self.data_shape = np.asarray(data_shape).astype(int) + self.data_shape = np.asarray(data_shape, dtype=np.int64) if tile_shape is not None: - self.tile_shape = np.asarray(tile_shape).astype(int) + self.tile_shape = np.atleast_1d(np.asarray(tile_shape, dtype=np.int64)) + + # Append ones to match data_shape size + if self.tile_shape.size <= self.data_shape.size: + size_difference = self.data_shape.size - self.tile_shape.size + self.tile_shape = np.insert( + arr=self.tile_shape, obj=0, values=np.ones(size_difference), axis=0 + ) + warnings.warn( + f"Tiler automatically adjusted tile_shape from {tuple(tile_shape)} to {tuple(self.tile_shape)}." + ) self._n_dim: int = len(self.data_shape) if (self.tile_shape <= 0).any() or (self.data_shape <= 0).any(): raise ValueError( - "Tile and data shapes must be tuple or lists of positive numbers." + "Tile and data shapes must be tuple, list or ndarray of positive integers." ) 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)." + "Tile shape must have less or equal number of elements compared to the data shape. " + "If less, your tile shape will be prepended with ones to match the data shape, " + "e.g. data_shape=(28, 28), tile_shape=(28) -> tile_shape=(1, 28)." ) # Tiling mode @@ -960,7 +977,8 @@

    Frequently asked questions

    If there is a channel dimension, it should be included in the shape. tile_shape (tuple, list or np.ndarray): Shape of a tile, e.g. (256, 256, 3), [64, 64, 64] or np.ndarray([3, 128, 128]). - Tile must have the same number of dimensions as data. + Tile must have the same number of dimensions as data or less. + If less, the shape will be automatically prepended with ones to match data_shape size. overlap (int, float, tuple, list or np.ndarray): Specifies overlap between tiles. If integer, the same overlap of overlap pixels applied in each dimension, except channel_dimension. @@ -1002,7 +1020,8 @@
    Args
  • data_shape (tuple, list or np.ndarray): Input data shape, e.g. (1920, 1080, 3), [512, 512, 512] or np.ndarray([3, 1024, 768]). If there is a channel dimension, it should be included in the shape.
  • tile_shape (tuple, list or np.ndarray): Shape of a tile, e.g. (256, 256, 3), [64, 64, 64] or np.ndarray([3, 128, 128]). -Tile must have the same number of dimensions as data.
  • +Tile must have the same number of dimensions as data or less. +If less, the shape will be automatically prepended with ones to match data_shape size.
  • overlap (int, float, tuple, list or np.ndarray): Specifies overlap between tiles. If integer, the same overlap of overlap pixels applied in each dimension, except channel_dimension. If float, percentage of a tile_shape to overlap (from 0.0 to 1.0), except channel_dimension. @@ -1087,19 +1106,29 @@
    Args
    # Data and tile shapes if data_shape is not None: - self.data_shape = np.asarray(data_shape).astype(int) + self.data_shape = np.asarray(data_shape, dtype=np.int64) if tile_shape is not None: - self.tile_shape = np.asarray(tile_shape).astype(int) + self.tile_shape = np.atleast_1d(np.asarray(tile_shape, dtype=np.int64)) + + # Append ones to match data_shape size + if self.tile_shape.size <= self.data_shape.size: + size_difference = self.data_shape.size - self.tile_shape.size + self.tile_shape = np.insert( + arr=self.tile_shape, obj=0, values=np.ones(size_difference), axis=0 + ) + warnings.warn( + f"Tiler automatically adjusted tile_shape from {tuple(tile_shape)} to {tuple(self.tile_shape)}." + ) self._n_dim: int = len(self.data_shape) if (self.tile_shape <= 0).any() or (self.data_shape <= 0).any(): raise ValueError( - "Tile and data shapes must be tuple or lists of positive numbers." + "Tile and data shapes must be tuple, list or ndarray of positive integers." ) 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)." + "Tile shape must have less or equal number of elements compared to the data shape. " + "If less, your tile shape will be prepended with ones to match the data shape, " + "e.g. data_shape=(28, 28), tile_shape=(28) -> tile_shape=(1, 28)." ) # Tiling mode diff --git a/misc/baby_logo.png b/misc/baby_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..bee964aae8fee00e989b92591e0d09d3803c92ce GIT binary patch literal 1969 zcmV;i2Tu5jP)9BB6~KS@e%owbVvl!hV-sU=FiU6?5n8H10U@DQN>GtS zL|dukrTbH#`q=hAC`#LxRxOH9TBn;DsZ!G&sDoG%QfL&u7uY>JbzxDQE#*W9H zG26Jxkw&BMF6aKfd(S!Fxd$pM5{Oh%p;iec64Bdj#=o~Oy!q_@aQ9DRGV~=IKb8sX z2_X3>y2T?*Y^&3vCw5wGr~0g>CqvR)^Rfv)?Oe-EzB}g44~}~ag9GXH=7ScrD-Mu8->C^BO*-zQh|+6fR08~ng}7t297^uPhbATo&No8cmCA?H3Xi4D^}J$K!e|T53?r&oSqx5eVX1jEH8IRpb|kWmh9;3I_~DlN1Ua{+RJ zOD1sCgtvHO)J^>HhBH0r5xfHgB>^?$?jEyY;Bm9zWRKa{A%zfu3P=VvN+}GHKgOJL z$ps!#2x991tb74OYFI)KvdRtS{fwLT9akbfU20xH;Q1=>JmtIB-I?J#?!sVBdH+zT zj|+ez)Lh^DiakGe1^jD)QeJ@a0`L^dQ%ZRX2nd>WG|N_A64Zo(y9zu|UjBdi=TiI} z09`=5(DxxFyjvhTS{nOLZ*JRndTQqC;LWkOexw5kf&TRFGsjFla%_0~!sXkO@4q}X z`|){Dqd+4NEiCk!gTG`eai9wDfr&!@YH0xK3S+JVS68&h3+?k&(Wc{FoA!TwOUHp{ z!&Z$UgkXB^8UXrAZKfHPy3JSs}`Rv{4}5i zFn|n@E&u>b12ZMSXEn|2>T28nt=7i=f!4+yQB5;T`h#AoQ3!!$R_R+h4(!?4``n&P zcJ3#)#xDMIWc=-Oi>W(*0TL^Q`@kg7TG*{N;BEmvU8EP$+8Ez5kT?`wZQZ z+1xyovEj0St6`&&xYfV)D@S+q96z#{x|10md;7JK@k{5^+1Y=Q4-YOd#tQEP>dD)k zjD+iUb+>=v8{O?+JRA$hLqgnt_`N4!F{~6dRc+DT+fO{dqv!Z@Q*+lBCucwU^T_z6 zmvi>Q3}7z})ugehqxGq8wl(cK+0xM0WE!#ZRH}TuejvbAw4$!2v!<^xQOmOF0NpI*qI_;9BtmkLsw+ zw$3OW%@N|YF;;r95RxG9(8^?{6+Ys;y*X|M$4M>k<>&kA+1k-iX3qZ+5Cl1A5nZ>4 z7VOFM9dfo)euj|vfrkMEflnrz$lLA40D@)2u*_HyfaBU_$J>rgDm{zqfe;$eNIgQx zjd2no#F`9KAaOl^`B^E&RAP>iiA6@I91@E)-@&q%rF>RhsuHstW_;fc*7QDUxP6T69|9GkGBbmGdcVG}0;Xn}RR?>!4*j5~^U%?dRi&*5n99r| zge>fsT!yx+Dm3}2Pb#IDN!*ybIr`?gk%>z$1%Y>!67lANO2C>l`nxtg^WEO=BLlHW zJXUfCbMu%+1bsz?HJh8qG@@vlf#YU_k@1V~-57cO7xT%n^FV67H6N;oE`)4ts^4~| zr}Nn}?Jb{cTNysG_5y|;E&{Zj6v@=&!mY6juMCe}JnQ-PWs2)Kl?7ZOk=p9^fo)xf zzq5JMzFnF$M9yAB*DQ41!uM@8GXCzB8~=U%S99~j=RhUyt+KL!OQMIZ+QU7Y4}QPB zW$!`XbDWWhOMkj``>nH%o4H87ME6+{(F3BXQri9p0(&mMRfJ>~00000NkvXXu0mjf D{>jL> literal 0 HcmV?d00001 diff --git a/misc/docs.sh b/misc/docs.sh index 3b4e6f7..e6df956 100755 --- a/misc/docs.sh +++ b/misc/docs.sh @@ -1,4 +1,4 @@ -cd $(dirname "$file_name")/.. +cd "$(dirname "$file_name")"/.. || exit # build docs pdoc -o docs -d google tiler diff --git a/tiler/__init__.py b/tiler/__init__.py index a719054..51bcf9e 100644 --- a/tiler/__init__.py +++ b/tiler/__init__.py @@ -25,8 +25,11 @@ if "pdoc" in sys.modules: # pragma: no cover with open("README.md", "r") as f: _readme = f.read() - _readme = _readme.split("# tiler", 1)[1] # remove header - _readme = _readme.replace( - "misc/teaser/tiler_teaser.png", "tiler_teaser.png" - ) # replace image path + + # remove baby logo and header + _readme = _readme.split("\n", 2)[2] + + # replace teaser image path + _readme = _readme.replace("misc/teaser/tiler_teaser.png", "tiler_teaser.png") + _readme = _readme.replace("misc/baby_logo.png", "baby_logo.png") __doc__ = _readme