From 5520ccdef38cb0b3d6250fbf795c454b50e5ce19 Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Fri, 12 Dec 2014 13:24:10 +1100 Subject: [PATCH 01/20] Initial work on array dimension naming guide --- doc/source/user_guide/numpy_images.txt | 129 +++++++++++++++++++++---- 1 file changed, 109 insertions(+), 20 deletions(-) diff --git a/doc/source/user_guide/numpy_images.txt b/doc/source/user_guide/numpy_images.txt index 60358be5..c0e1bd58 100644 --- a/doc/source/user_guide/numpy_images.txt +++ b/doc/source/user_guide/numpy_images.txt @@ -1,11 +1,13 @@ A crash course on Numpy for images ---------------------------------- -Images manipulated by ``scikit-image`` are simply NumPy arrays. Hence, a -large fraction of operations on images will just consist in using NumPy:: +Images manipulated by ``scikit-image`` are simply Numpy arrays. Hence, a +large fraction of operations on images will just consist in using Numpy:: >>> from skimage import data >>> camera = data.camera() + >>> type(camera) + Retrieving the geometry of the image and the number of pixels: :: @@ -22,25 +24,32 @@ Retrieving statistical information about gray values: :: 118.31400299072266 Numpy arrays representing images can be of different integer of float -numerical types. See :ref:`data_types` for more information about data -types. +numerical types. See :ref:`data_types` for more information about these +types and how scikit-image treats them. + + +Numpy indexing +-------------- Numpy indexing can be used both for looking at pixel values, and to modify pixel values: :: - >>> # Value of pixel on 10th line and 20th column + >>> # Get the value of the pixel on the 10th row and 20th column >>> camera[10, 20] 153 - >>> # Turn to black pixel on 3rd line and 10th column + >>> # Set to black the pixel on the 3rd row and 10th column >>> camera[3, 10] = 0 -Be careful that the first dimension (``camera.shape[0]``) corresponds to -lines, while the second dimension (``camera.shape[1]``) stands for -columns. +Be careful: in Numpy indexing, the first dimension (``camera.shape[0]``) +corresponds to rows, while the second (``camera.shape[1]``) corresponds +to columns, with the origin (``camera[0, 0]``) on the top-left corner. +This matches matrix/linear algebra notation, but is in contrast to +Cartesian (x, y) coordinates. See `Coordinate conventions`_ below for +more details. Beyond individual pixels, it is possible to access / modify values of whole sets of pixels, using the different indexing possibilities of -NumPy. +Numpy. Slicing:: @@ -55,28 +64,108 @@ Masking (indexing with masks of booleans):: Fancy indexing (indexing with sets of indices) :: - >>> inds_x = np.arange(len(camera)) - >>> inds_y = 4 * inds_x % len(camera) - >>> camera[inds_x, inds_y] = 0 + >>> inds_r = np.arange(len(camera)) + >>> inds_c = 4 * inds_r % len(camera) + >>> camera[inds_r, inds_c] = 0 Using masks, especially, is very useful to select a set of pixels on which to perform further manipulations. The mask can be any boolean array -of same shape as the image (or at least a shape broadcastable to the -image shape). This can be useful to define a region of interest, as a +of same shape as the image (or a shape broadcastable to the image shape). +This can be useful to define a region of interest, such as a disk: :: - >>> l_x, l_y = camera.shape[0], camera.shape[1] - >>> X, Y = np.ogrid[:l_x, :l_y] - >>> outer_disk_mask = (X - l_x / 2)**2 + (Y - l_y / 2)**2 < (l_x / 2)**2 + >>> l_r, l_c = camera.shape + >>> R, C = np.ogrid[:l_r, :l_c] + >>> outer_disk_mask = (R - l_r / 2)**2 + (C - l_c / 2)**2 < (l_r / 2)**2 >>> camera[outer_disk_mask] = 0 .. image:: ../../_images/plot_camera_numpy_1.png :width: 45% :target: ../auto_examples/plot_camera_numpy.html -Boolean arithmetics can be used to define more complex masks: :: +Boolean arithmetic can be used to define more complex masks: :: - >>> lower_half = X > l_x / 2 + >>> lower_half = R > l_r / 2 >>> lower_half_disk = np.logical_and(lower_half, outer_disk_mask) >>> camera = data.camera() >>> camera[lower_half_disk] = 0 + + +Color images +------------ + +All of the above is true of color images, too: a color image is a +Numpy array, with an additional trailing dimension for the channels: + + >>> cat = data.chelsea() + >>> type(cat) + + >>> cat.shape + (300, 451, 3) + +That's a 300-by-451 pixel image with red, green, and blue channels. +As before, we can get and set pixel values: + + >>> cat[10, 20] + array([151, 129, 115], dtype=uint8) + >>> # set the pixel at row 50, column 60 to black + >>> cat[50, 60] = 0 + >>> # set the pixel at row 50, column 61 to green + >>> cat[50, 61] = [0, 255, 0] # [red, green, blue] + +We can also use masks where they match the 2 spatial dimensions of the +image: + +.. plot:: + Using a 2D mask on a 2D color image + >>> reddish = cat[:, :, 0] > 160 + >>> cat[reddish] = [0, 255, 0] + >>> plt.imshow(cat) + + +Coordinate conventions +---------------------- + +Because we represent images with numpy arrays, our coordinates must +match accordingly. Two-dimensional (2D) grayscale images (such as +`camera` above) are indexed by row and columns (abbreviated to either +``row, col`` or ``r, c``), with the lowest element (0, 0) at the top- +-left corner. In various parts of the library, you will +also see ``rr`` and ``cc`` refer to lists of row and column +coordinates. We distinguish this from (x, y), which commonly denote +Cartesian coordinates, where x is the horizontal coordinate, y the +vertical, and the origin is on the bottom right. (Matplotlib, for +example, uses this convention.) + +In the case of color (or multichannel) images, the last dimension +contains the color information and is denoted ``channel`` or ``ch``. + +Finally, for 3D images, we refer to the leading dimension as +``level``, abbreviated as ``lev`` or (rarely) ``l``. In many cases, +the third imaging dimension has lower resolution than the other two, +and processing must be done level-wise. When levels are the leading +dimension, we can use the following syntax: + + >>> for image in image3d: # iterate over first dimension + ... do_something_to(image) + + +Notes on array order +-------------------- + +Although the labeling of the axes seems arbitrary, it can have a +significant effect on speed of operations. This is because modern +processors never retrieve just one item from memory, but rather a +whole chunk of adjacent items. Therefore, processing elements that are +adjacent in memory one after the other is faster than processing them +in a different order, even if the number of operations is the same: + + >>> def in_order_multiply(arr, scalar): + ... for plane in list(range(arr.shape[0])): + ... arr[plane, :, :] *= scalar + ... + >>> def out_of_order_multiply(arr, scalar): + ... for plane in list(range(arr.shape[2])): + ... arr[:, :, plane] *= scalar + ... + >>> import time From 0b779588b002ccab043696001d5b2401d782519b Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Fri, 19 Dec 2014 11:39:48 +1100 Subject: [PATCH 02/20] Restore NumPy from Numpy, grudgingly --- doc/source/user_guide/numpy_images.txt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/doc/source/user_guide/numpy_images.txt b/doc/source/user_guide/numpy_images.txt index c0e1bd58..9917d556 100644 --- a/doc/source/user_guide/numpy_images.txt +++ b/doc/source/user_guide/numpy_images.txt @@ -1,8 +1,8 @@ -A crash course on Numpy for images +A crash course on NumPy for images ---------------------------------- -Images manipulated by ``scikit-image`` are simply Numpy arrays. Hence, a -large fraction of operations on images will just consist in using Numpy:: +Images manipulated by ``scikit-image`` are simply NumPy arrays. Hence, a +large fraction of operations on images will just consist in using NumPy:: >>> from skimage import data >>> camera = data.camera() @@ -23,15 +23,15 @@ Retrieving statistical information about gray values: :: >>> camera.mean() 118.31400299072266 -Numpy arrays representing images can be of different integer of float +NumPy arrays representing images can be of different integer of float numerical types. See :ref:`data_types` for more information about these types and how scikit-image treats them. -Numpy indexing +NumPy indexing -------------- -Numpy indexing can be used both for looking at pixel values, and to +NumPy indexing can be used both for looking at pixel values, and to modify pixel values: :: >>> # Get the value of the pixel on the 10th row and 20th column @@ -40,7 +40,7 @@ modify pixel values: :: >>> # Set to black the pixel on the 3rd row and 10th column >>> camera[3, 10] = 0 -Be careful: in Numpy indexing, the first dimension (``camera.shape[0]``) +Be careful: in NumPy indexing, the first dimension (``camera.shape[0]``) corresponds to rows, while the second (``camera.shape[1]``) corresponds to columns, with the origin (``camera[0, 0]``) on the top-left corner. This matches matrix/linear algebra notation, but is in contrast to @@ -49,7 +49,7 @@ more details. Beyond individual pixels, it is possible to access / modify values of whole sets of pixels, using the different indexing possibilities of -Numpy. +NumPy. Slicing:: @@ -95,7 +95,7 @@ Color images ------------ All of the above is true of color images, too: a color image is a -Numpy array, with an additional trailing dimension for the channels: +NumPy array, with an additional trailing dimension for the channels: >>> cat = data.chelsea() >>> type(cat) From b998405f4873bb345e3e84d32cc82e182f4fae1e Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Fri, 19 Dec 2014 11:54:35 +1100 Subject: [PATCH 03/20] Replace 'level' and 'plane' with 'frame' --- doc/source/user_guide/numpy_images.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/source/user_guide/numpy_images.txt b/doc/source/user_guide/numpy_images.txt index 9917d556..188218c9 100644 --- a/doc/source/user_guide/numpy_images.txt +++ b/doc/source/user_guide/numpy_images.txt @@ -141,7 +141,7 @@ In the case of color (or multichannel) images, the last dimension contains the color information and is denoted ``channel`` or ``ch``. Finally, for 3D images, we refer to the leading dimension as -``level``, abbreviated as ``lev`` or (rarely) ``l``. In many cases, +``frame``, abbreviated as ``frm`` or ``f``. In many cases, the third imaging dimension has lower resolution than the other two, and processing must be done level-wise. When levels are the leading dimension, we can use the following syntax: @@ -161,11 +161,11 @@ adjacent in memory one after the other is faster than processing them in a different order, even if the number of operations is the same: >>> def in_order_multiply(arr, scalar): - ... for plane in list(range(arr.shape[0])): - ... arr[plane, :, :] *= scalar + ... for frame in list(range(arr.shape[0])): + ... arr[frame, :, :] *= scalar ... >>> def out_of_order_multiply(arr, scalar): - ... for plane in list(range(arr.shape[2])): - ... arr[:, :, plane] *= scalar + ... for frame in list(range(arr.shape[2])): + ... arr[:, :, frame] *= scalar ... >>> import time From 76103d4b057a0787d4f310303896fd832b6438ee Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Fri, 19 Dec 2014 11:55:00 +1100 Subject: [PATCH 04/20] Define 'prefetching' for users --- doc/source/user_guide/numpy_images.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/source/user_guide/numpy_images.txt b/doc/source/user_guide/numpy_images.txt index 188218c9..c4f18450 100644 --- a/doc/source/user_guide/numpy_images.txt +++ b/doc/source/user_guide/numpy_images.txt @@ -156,8 +156,9 @@ Notes on array order Although the labeling of the axes seems arbitrary, it can have a significant effect on speed of operations. This is because modern processors never retrieve just one item from memory, but rather a -whole chunk of adjacent items. Therefore, processing elements that are -adjacent in memory one after the other is faster than processing them +whole chunk of adjacent items. (This is called prefetching.) +Therefore, processing elements that are +next to each other in memory is faster than processing them in a different order, even if the number of operations is the same: >>> def in_order_multiply(arr, scalar): From 53585641d2c30d8942198c22cd5f416324a930d1 Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Fri, 19 Dec 2014 11:55:16 +1100 Subject: [PATCH 05/20] Finish code snippet for timings --- doc/source/user_guide/numpy_images.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/source/user_guide/numpy_images.txt b/doc/source/user_guide/numpy_images.txt index c4f18450..df87a482 100644 --- a/doc/source/user_guide/numpy_images.txt +++ b/doc/source/user_guide/numpy_images.txt @@ -170,3 +170,12 @@ in a different order, even if the number of operations is the same: ... arr[:, :, frame] *= scalar ... >>> import time + >>> im3d = np.random.rand(100, 1024, 1024) + >>> t0 = time.time(); x = in_order_multiply(im3d, 5); t1 = time.time() + >>> print(t1 - t0, "seconds") + >>> im3d_t = np.transpose(im3d).copy() + >>> im3d_t.shape + (1024, 1024, 100) + >>> s0 = time.time(); x = out_of_order_multiply(im3d, 5); s1 = time.time() + >>> print(s1 - s0, "seconds") + >>> print("Speedup: ", (s1 - s0) / (t1 - t0)) From 4a04a6c90c5938db4df7e4f3065dd8e9f03c9d49 Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Sat, 20 Dec 2014 00:18:15 +1100 Subject: [PATCH 06/20] Fix and add output for timing calls --- doc/source/user_guide/numpy_images.txt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/doc/source/user_guide/numpy_images.txt b/doc/source/user_guide/numpy_images.txt index df87a482..b2cdf3b4 100644 --- a/doc/source/user_guide/numpy_images.txt +++ b/doc/source/user_guide/numpy_images.txt @@ -172,10 +172,13 @@ in a different order, even if the number of operations is the same: >>> import time >>> im3d = np.random.rand(100, 1024, 1024) >>> t0 = time.time(); x = in_order_multiply(im3d, 5); t1 = time.time() - >>> print(t1 - t0, "seconds") + >>> print("%.2f seconds" % (t1 - t0)) + 0.14 seconds >>> im3d_t = np.transpose(im3d).copy() >>> im3d_t.shape (1024, 1024, 100) >>> s0 = time.time(); x = out_of_order_multiply(im3d, 5); s1 = time.time() - >>> print(s1 - s0, "seconds") - >>> print("Speedup: ", (s1 - s0) / (t1 - t0)) + >>> print("%.2f seconds" % (s1 - s0)) + 1.18 seconds + >>> print("Speedup: %.1fx" % ((s1 - s0) / (t1 - t0))) + Speedup: 8.6x From 0dd81de56726f3250dc05d9c0ec8de8a942c5d53 Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Wed, 31 Dec 2014 14:36:01 +1100 Subject: [PATCH 07/20] Add paragraph about data locality --- doc/source/user_guide/numpy_images.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/source/user_guide/numpy_images.txt b/doc/source/user_guide/numpy_images.txt index b2cdf3b4..a3a73bef 100644 --- a/doc/source/user_guide/numpy_images.txt +++ b/doc/source/user_guide/numpy_images.txt @@ -182,3 +182,11 @@ in a different order, even if the number of operations is the same: 1.18 seconds >>> print("Speedup: %.1fx" % ((s1 - s0) / (t1 - t0))) Speedup: 8.6x + + +When the dimension you are iterating over is even larger, the +speedup is even more dramatic. It is worth thinking about this +*data locality* when writing algorithms. In particular, know that +scikit-image uses C-contiguous arrays unless otherwise specified, so +one should iterate along the last/rightmost dimension in the +innermost loop of the computation. From ad2b02b86581b11bffccea660cee026a9da859b2 Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Wed, 31 Dec 2014 14:41:27 +1100 Subject: [PATCH 08/20] Label transpose operation for clarity --- doc/source/user_guide/numpy_images.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/user_guide/numpy_images.txt b/doc/source/user_guide/numpy_images.txt index a3a73bef..e11616d8 100644 --- a/doc/source/user_guide/numpy_images.txt +++ b/doc/source/user_guide/numpy_images.txt @@ -174,7 +174,7 @@ in a different order, even if the number of operations is the same: >>> t0 = time.time(); x = in_order_multiply(im3d, 5); t1 = time.time() >>> print("%.2f seconds" % (t1 - t0)) 0.14 seconds - >>> im3d_t = np.transpose(im3d).copy() + >>> im3d_t = np.transpose(im3d).copy() # place "frames" dimension at end >>> im3d_t.shape (1024, 1024, 100) >>> s0 = time.time(); x = out_of_order_multiply(im3d, 5); s1 = time.time() From 9c770dbdf2fe55ac2c06f37611468b81632f0b1a Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Wed, 31 Dec 2014 15:16:00 +1100 Subject: [PATCH 09/20] Prefer row/col to r/c when talking about circles --- doc/source/user_guide/numpy_images.txt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/doc/source/user_guide/numpy_images.txt b/doc/source/user_guide/numpy_images.txt index e11616d8..d494ccdc 100644 --- a/doc/source/user_guide/numpy_images.txt +++ b/doc/source/user_guide/numpy_images.txt @@ -74,9 +74,11 @@ of same shape as the image (or a shape broadcastable to the image shape). This can be useful to define a region of interest, such as a disk: :: - >>> l_r, l_c = camera.shape - >>> R, C = np.ogrid[:l_r, :l_c] - >>> outer_disk_mask = (R - l_r / 2)**2 + (C - l_c / 2)**2 < (l_r / 2)**2 + >>> nrows, ncols = camera.shape + >>> row, col = np.ogrid[:nrows, :ncols] + >>> cnt_row, cnt_col = nrows / 2, ncols / 2 + >>> outer_disk_mask = ((row - cnt_row)**2 + (col - cnt_col)**2 < + ... (nrows / 2)**2) >>> camera[outer_disk_mask] = 0 .. image:: ../../_images/plot_camera_numpy_1.png @@ -85,7 +87,7 @@ disk: :: Boolean arithmetic can be used to define more complex masks: :: - >>> lower_half = R > l_r / 2 + >>> lower_half = row > cnt_row >>> lower_half_disk = np.logical_and(lower_half, outer_disk_mask) >>> camera = data.camera() >>> camera[lower_half_disk] = 0 From f9b5f44faa0f91409252299350edfcb161e675cd Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Wed, 31 Dec 2014 15:16:39 +1100 Subject: [PATCH 10/20] Clarify boolean masks on color images --- doc/source/user_guide/numpy_images.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/user_guide/numpy_images.txt b/doc/source/user_guide/numpy_images.txt index d494ccdc..1c1ee2d8 100644 --- a/doc/source/user_guide/numpy_images.txt +++ b/doc/source/user_guide/numpy_images.txt @@ -115,8 +115,8 @@ As before, we can get and set pixel values: >>> # set the pixel at row 50, column 61 to green >>> cat[50, 61] = [0, 255, 0] # [red, green, blue] -We can also use masks where they match the 2 spatial dimensions of the -image: +We can also use 2D boolean masks for a 2D color image, as we did with +the grayscale image above: .. plot:: Using a 2D mask on a 2D color image From e4d91d1c9f35246aa0fa3b2703340972e5ddf134 Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Wed, 31 Dec 2014 17:00:24 +1100 Subject: [PATCH 11/20] Give 3D image examples --- doc/source/user_guide/numpy_images.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/source/user_guide/numpy_images.txt b/doc/source/user_guide/numpy_images.txt index 1c1ee2d8..d85cecfe 100644 --- a/doc/source/user_guide/numpy_images.txt +++ b/doc/source/user_guide/numpy_images.txt @@ -142,10 +142,11 @@ example, uses this convention.) In the case of color (or multichannel) images, the last dimension contains the color information and is denoted ``channel`` or ``ch``. -Finally, for 3D images, we refer to the leading dimension as -``frame``, abbreviated as ``frm`` or ``f``. In many cases, +Finally, for 3D images, such as videos, magnetic resonance imaging +(MRI) scans, or confocal microscopy, we refer to the leading dimension +as ``frame``, abbreviated as ``frm`` or ``f``. In many cases, the third imaging dimension has lower resolution than the other two, -and processing must be done level-wise. When levels are the leading +and processing must be done frame-wise. When frames are the leading dimension, we can use the following syntax: >>> for image in image3d: # iterate over first dimension From 097f8eaaa0b33b5c691cad4079c375a3630949a8 Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Wed, 31 Dec 2014 17:09:36 +1100 Subject: [PATCH 12/20] Space comment and code in plot directive call --- doc/source/user_guide/numpy_images.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/source/user_guide/numpy_images.txt b/doc/source/user_guide/numpy_images.txt index d85cecfe..6a324d90 100644 --- a/doc/source/user_guide/numpy_images.txt +++ b/doc/source/user_guide/numpy_images.txt @@ -119,7 +119,9 @@ We can also use 2D boolean masks for a 2D color image, as we did with the grayscale image above: .. plot:: + Using a 2D mask on a 2D color image + >>> reddish = cat[:, :, 0] > 160 >>> cat[reddish] = [0, 255, 0] >>> plt.imshow(cat) From 110ac37dc4fa0826a89af3eeb9c3f032e71a5480 Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Thu, 22 Jan 2015 11:57:49 +1100 Subject: [PATCH 13/20] Add doctest skip directives to timing calls --- doc/source/user_guide/numpy_images.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/source/user_guide/numpy_images.txt b/doc/source/user_guide/numpy_images.txt index 6a324d90..1d8f9ae0 100644 --- a/doc/source/user_guide/numpy_images.txt +++ b/doc/source/user_guide/numpy_images.txt @@ -151,7 +151,8 @@ the third imaging dimension has lower resolution than the other two, and processing must be done frame-wise. When frames are the leading dimension, we can use the following syntax: - >>> for image in image3d: # iterate over first dimension + >>> for image in image3d: # doctest: +SKIP + ... # iterate over the leading dimension (frames) ... do_something_to(image) @@ -177,15 +178,15 @@ in a different order, even if the number of operations is the same: >>> import time >>> im3d = np.random.rand(100, 1024, 1024) >>> t0 = time.time(); x = in_order_multiply(im3d, 5); t1 = time.time() - >>> print("%.2f seconds" % (t1 - t0)) + >>> print("%.2f seconds" % (t1 - t0)) # doctest: +SKIP 0.14 seconds >>> im3d_t = np.transpose(im3d).copy() # place "frames" dimension at end >>> im3d_t.shape (1024, 1024, 100) >>> s0 = time.time(); x = out_of_order_multiply(im3d, 5); s1 = time.time() - >>> print("%.2f seconds" % (s1 - s0)) + >>> print("%.2f seconds" % (s1 - s0)) doctest: +SKIP 1.18 seconds - >>> print("Speedup: %.1fx" % ((s1 - s0) / (t1 - t0))) + >>> print("Speedup: %.1fx" % ((s1 - s0) / (t1 - t0))) doctest: +SKIP Speedup: 8.6x From 6eb8b0d07929396d4fa834ebc4d9a1307bb91dfb Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Thu, 22 Jan 2015 12:47:44 +1100 Subject: [PATCH 14/20] Add table summarising image types --- doc/source/user_guide/numpy_images.txt | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/doc/source/user_guide/numpy_images.txt b/doc/source/user_guide/numpy_images.txt index 1d8f9ae0..f5289e45 100644 --- a/doc/source/user_guide/numpy_images.txt +++ b/doc/source/user_guide/numpy_images.txt @@ -146,7 +146,23 @@ contains the color information and is denoted ``channel`` or ``ch``. Finally, for 3D images, such as videos, magnetic resonance imaging (MRI) scans, or confocal microscopy, we refer to the leading dimension -as ``frame``, abbreviated as ``frm`` or ``f``. In many cases, +as ``frame``, abbreviated as ``frm`` or ``f``. + +These conventions are summarized below: + +.. table:: Dimension name and order conventions in scikit-image + +======================== ======================================== +Image type coordinates +======================== ======================================== +2D grayscale (row, col) +2D multichannel (eg. RGB) (row, col, ch) +3D grayscale (frm, row, col) +3D multichannel (frm, row, col, ch) +======================== ======================================== + + +In many cases, the third imaging dimension has lower resolution than the other two, and processing must be done frame-wise. When frames are the leading dimension, we can use the following syntax: From 64f00bb86070cbebd513ad47c5dcd3a4aeb86709 Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Thu, 22 Jan 2015 12:49:28 +1100 Subject: [PATCH 15/20] Add note on 5D images --- doc/source/user_guide/numpy_images.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/doc/source/user_guide/numpy_images.txt b/doc/source/user_guide/numpy_images.txt index f5289e45..89ee5e41 100644 --- a/doc/source/user_guide/numpy_images.txt +++ b/doc/source/user_guide/numpy_images.txt @@ -212,3 +212,15 @@ speedup is even more dramatic. It is worth thinking about this scikit-image uses C-contiguous arrays unless otherwise specified, so one should iterate along the last/rightmost dimension in the innermost loop of the computation. + +A note on time +-------------- + +Although scikit-image does not currently (0.11) provide functions to +work specifically with time-varying 3D data, our compatibility with +numpy arrays allows us to work quite naturally with a 5D array of the +shape (t, frm, row, col, ch): + + >>> for timepoint in image5d: # doctest: +SKIP + ... # each timepoint is a 3D multichannel image + ... do_something_with(timepoint) From 2f05992148a1f9245e9f31b60f84a32bb83d1e97 Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Thu, 22 Jan 2015 13:01:04 +1100 Subject: [PATCH 16/20] Expand on skimage support for 3D processing --- doc/source/user_guide/numpy_images.txt | 29 ++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/doc/source/user_guide/numpy_images.txt b/doc/source/user_guide/numpy_images.txt index 89ee5e41..802cdf6e 100644 --- a/doc/source/user_guide/numpy_images.txt +++ b/doc/source/user_guide/numpy_images.txt @@ -162,14 +162,31 @@ Image type coordinates ======================== ======================================== -In many cases, -the third imaging dimension has lower resolution than the other two, -and processing must be done frame-wise. When frames are the leading -dimension, we can use the following syntax: +Many functions in scikit-image operate on 3D images directly: - >>> for image in image3d: # doctest: +SKIP + >>> im3d = np.random.rand(100, 1000, 1000) + >>> from skimage import morphology + >>> from scipy import ndimage as nd + >>> seeds = nd.label(im3d < 0.1)[0] + >>> ws = morphology.watershed(im3d, seeds) + +In many cases, +the third imaging dimension has lower resolution than the other two. +Some scikit-image functions provide a ``spacing`` keyword argument +to process these images: + + >>> from skimage import segmentation + >>> slics = segmentation.slic(im3d, spacing=[5, 1, 1], multichannel=False) + + +Other times, processing must be done frame-wise. When frames are the +leading dimension, we can use the following syntax: + + >>> from skimage import filters + >>> edges = np.zeros_like(im3d) + >>> for frm, image in enumerate(im3d): ... # iterate over the leading dimension (frames) - ... do_something_to(image) + ... edges[frm] = filters.sobel(image) Notes on array order From 3667b4d81c9b0e8824005c649b899532ba42d1c4 Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Thu, 22 Jan 2015 13:04:29 +1100 Subject: [PATCH 17/20] Be more specific about cat image dimensions --- doc/source/user_guide/numpy_images.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/source/user_guide/numpy_images.txt b/doc/source/user_guide/numpy_images.txt index 802cdf6e..8704b377 100644 --- a/doc/source/user_guide/numpy_images.txt +++ b/doc/source/user_guide/numpy_images.txt @@ -105,7 +105,8 @@ NumPy array, with an additional trailing dimension for the channels: >>> cat.shape (300, 451, 3) -That's a 300-by-451 pixel image with red, green, and blue channels. +This shows that ``cat`` is a 300-by-451 pixel image with three +channels (red, green, and blue). As before, we can get and set pixel values: >>> cat[10, 20] From f1b309ecfb1e69ac506f647446b0ec733995862d Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Thu, 22 Jan 2015 13:05:19 +1100 Subject: [PATCH 18/20] Specify standard Cartesian coordinates --- doc/source/user_guide/numpy_images.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/user_guide/numpy_images.txt b/doc/source/user_guide/numpy_images.txt index 8704b377..d2c25ef2 100644 --- a/doc/source/user_guide/numpy_images.txt +++ b/doc/source/user_guide/numpy_images.txt @@ -138,8 +138,8 @@ match accordingly. Two-dimensional (2D) grayscale images (such as -left corner. In various parts of the library, you will also see ``rr`` and ``cc`` refer to lists of row and column coordinates. We distinguish this from (x, y), which commonly denote -Cartesian coordinates, where x is the horizontal coordinate, y the -vertical, and the origin is on the bottom right. (Matplotlib, for +standard Cartesian coordinates, where x is the horizontal coordinate, +y the vertical, and the origin is on the bottom right. (Matplotlib, for example, uses this convention.) In the case of color (or multichannel) images, the last dimension From fd6c7a26d8b9c6e60f7a2bda854c76ff2efe9d04 Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Tue, 3 Feb 2015 14:08:24 +1100 Subject: [PATCH 19/20] Replace frame with plane --- doc/source/user_guide/numpy_images.txt | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/doc/source/user_guide/numpy_images.txt b/doc/source/user_guide/numpy_images.txt index d2c25ef2..c1664259 100644 --- a/doc/source/user_guide/numpy_images.txt +++ b/doc/source/user_guide/numpy_images.txt @@ -147,7 +147,7 @@ contains the color information and is denoted ``channel`` or ``ch``. Finally, for 3D images, such as videos, magnetic resonance imaging (MRI) scans, or confocal microscopy, we refer to the leading dimension -as ``frame``, abbreviated as ``frm`` or ``f``. +as ``plane``, abbreviated as ``pln`` or ``p``. These conventions are summarized below: @@ -158,8 +158,8 @@ Image type coordinates ======================== ======================================== 2D grayscale (row, col) 2D multichannel (eg. RGB) (row, col, ch) -3D grayscale (frm, row, col) -3D multichannel (frm, row, col, ch) +3D grayscale (pln, row, col) +3D multichannel (pln, row, col, ch) ======================== ======================================== @@ -180,14 +180,14 @@ to process these images: >>> slics = segmentation.slic(im3d, spacing=[5, 1, 1], multichannel=False) -Other times, processing must be done frame-wise. When frames are the +Other times, processing must be done plane-wise. When planes are the leading dimension, we can use the following syntax: >>> from skimage import filters >>> edges = np.zeros_like(im3d) - >>> for frm, image in enumerate(im3d): - ... # iterate over the leading dimension (frames) - ... edges[frm] = filters.sobel(image) + >>> for pln, image in enumerate(im3d): + ... # iterate over the leading dimension (planes) + ... edges[pln] = filters.sobel(image) Notes on array order @@ -202,19 +202,19 @@ next to each other in memory is faster than processing them in a different order, even if the number of operations is the same: >>> def in_order_multiply(arr, scalar): - ... for frame in list(range(arr.shape[0])): - ... arr[frame, :, :] *= scalar + ... for plane in list(range(arr.shape[0])): + ... arr[plane, :, :] *= scalar ... >>> def out_of_order_multiply(arr, scalar): - ... for frame in list(range(arr.shape[2])): - ... arr[:, :, frame] *= scalar + ... for plane in list(range(arr.shape[2])): + ... arr[:, :, plane] *= scalar ... >>> import time >>> im3d = np.random.rand(100, 1024, 1024) >>> t0 = time.time(); x = in_order_multiply(im3d, 5); t1 = time.time() >>> print("%.2f seconds" % (t1 - t0)) # doctest: +SKIP 0.14 seconds - >>> im3d_t = np.transpose(im3d).copy() # place "frames" dimension at end + >>> im3d_t = np.transpose(im3d).copy() # place "planes" dimension at end >>> im3d_t.shape (1024, 1024, 100) >>> s0 = time.time(); x = out_of_order_multiply(im3d, 5); s1 = time.time() @@ -237,7 +237,7 @@ A note on time Although scikit-image does not currently (0.11) provide functions to work specifically with time-varying 3D data, our compatibility with numpy arrays allows us to work quite naturally with a 5D array of the -shape (t, frm, row, col, ch): +shape (t, pln, row, col, ch): >>> for timepoint in image5d: # doctest: +SKIP ... # each timepoint is a 3D multichannel image From ba2134a2b1681d6f783af08e46836aa05620d981 Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Tue, 3 Feb 2015 14:13:40 +1100 Subject: [PATCH 20/20] Update discussion on time-varying images --- doc/source/user_guide/numpy_images.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/doc/source/user_guide/numpy_images.txt b/doc/source/user_guide/numpy_images.txt index c1664259..916c5285 100644 --- a/doc/source/user_guide/numpy_images.txt +++ b/doc/source/user_guide/numpy_images.txt @@ -242,3 +242,15 @@ shape (t, pln, row, col, ch): >>> for timepoint in image5d: # doctest: +SKIP ... # each timepoint is a 3D multichannel image ... do_something_with(timepoint) + + +We can then supplement the above table as follows: + +.. table:: Addendum to dimension names and orders in scikit-image + +======================== ======================================== +Image type coordinates +======================== ======================================== +2D color video (t, row, col, ch) +3D multichannel video (t, pln, row, col, ch) +======================== ========================================