{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# ImgLib2 in Detail" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[ImgLib2](https://imagej.net/ImgLib2) is a general-purpose, multidimensional image processing library. Writing code using ImgLib2 is independent of image dimensionality, data type, and data storage strategy. It's designed to be reusable, to decouple algorithm development and data management, and to be extensible and adaptable through the addition of new algorithms, pixel types, and storage strategies.\n", "\n", "Find javadocs for ImgLib2 [here](https://javadoc.scijava.org/ImgLib2/)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Quick Start" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Added new repo: scijava.public\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "method": "display_data" }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "780848a5-00d5-4f5c-84d1-55ac5d486442", "version_major": 2, "version_minor": 0 }, "method": "display_data" }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "net.imagej.ImageJ@6939e9c4" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "%classpath config resolver scijava.public https://maven.scijava.org/content/groups/public\n", "%classpath add mvn net.imagej imagej 2.0.0-rc-71\n", "ij = new net.imagej.ImageJ()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Creating and Displaying an Image" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The following piece of code creates and displays an 400x320 8-bit gray-level image:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import net.imglib2.img.Img\n", "import net.imglib2.img.array.ArrayImgFactory\n", "import net.imglib2.type.numeric.integer.UnsignedByteType\n", " \n", "// will create a window showing a black 400x320 image\n", "long[] dimensions = [400, 320]\n", "final Img< UnsignedByteType > img = new ArrayImgFactory<>( new UnsignedByteType() ).create( dimensions )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Breaking _lines 6-7_ into individual steps...\n", "\n", "1. `final ImgFactory< UnsignedByteType > factory = new ArrayImgFactory<>( new UnsignedByteType() );`\n", "\n", "2. `final long[] dimensions = new long[] { 400, 320 };`\n", "\n", "3. `final Img< UnsignedByteType > img = factory.create( dimensions );`\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "_Line 1:_ \n", "Pixel images in ImgLib2 are created using an [ImgFactory](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/ImgFactory.html). There are different ImgFactories, that create pixel containers with different memory layouts. Here, we create an [ArrayImgFactory](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/array/ArrayImgFactory.html). This factory creates containers that map to a single flat Java array.\n", "\n", "The type parameter of the factory specifies the value type of the image we want to create. We want to create a 8-bit gray-level image, thus we use [UnsignedByteType](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/numeric/integer/UnsignedByteType.html).\n", "\n", "_Line 2:_\n", "Next we create a **`long[]`** array that specifies the image size in every dimension. The length of the array specifies the number of dimensions. Here, we state that we want to create 400x320 2D image.\n", "\n", "_Line 3:_\n", "We create the image, using the factory and dimensions. We store the result of the **`create()`** method in an [Img](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/Img.html) variable. [Img](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/Img.html) is a convenience interface that gathers properties of pixel image containers such as having a number of dimensions, being able to iterate its pixels, etc." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Opening and Displaying Image Files" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can open image files with the [IO](https://javadoc.scijava.org/SCIFIO/io/scif/img/IO.html) utility class of [SCIFIO](https://scif.io/) which calls [Bio-Formats](https://www.openmicroscopy.org/info/bio-formats) as needed. The following opens and displays an image file." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[INFO] Populating metadata\n", "[INFO] Populating metadata\n" ] }, { "data": { "text/html": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import net.imglib2.img.Img\n", "import net.imglib2.img.array.ArrayImgFactory\n", "import net.imglib2.type.numeric.integer.UnsignedByteType\n", "import io.scif.img.IO\n", "\n", "// save path for an image of Lena\n", "path = \"https://samples.fiji.sc/new-lenna.jpg\"\n", "\n", "// load image\n", "final Img< UnsignedByteType > img = IO.openImg( path,\n", " new ArrayImgFactory<>( new UnsignedByteType() ) );" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Again, breaking _lines 10-11_ into individual steps...\n", "\n", "1. `final ImgFactory< UnsignedByteType > factory = new ArrayImgFactory<>( new UnsignedByteType() );`\n", "\n", "2. `final Img< UnsignedByteType > img = IO.openImg( path, factory );`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "_Line 1:_\n", "When opening an image, we can specify which memory layout to use and as which value type we want to load the image. We want to use the [ArrayImg](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/array/ArrayImg.html) layout again, and we want to have [UnsignedByteType](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/numeric/integer/UnsignedByteType.html) values again. We need an [ImgFactory](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/ImgFactory.html) and an instance of the value type.\n", "\n", "_Line 2:_\n", "We can use the **`IO.openImg`** method, giving a filename and [ImgFactory](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/ImgFactory.html)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> Note that [Img](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/Img.html) is just convenience interface. When you get more proficient with ImgLib2 you will find yourself using it less and less. You will either be more concrete or more general than that. In the above example, we could be more concrete—the result of the **`ArrayImgFactory< UnsignedByteType >.create()`** is actually an **`ArrayImg< UnsignedByteType, ByteArray >`**. In algorithm implementations, you want to be as generic as possible to not constrain yourself to specific image types. You will specify only the super-interfaces of [Img](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/Img.html) that you really need. For instance, if you need something which has boundaries and can be iterated you would use [IterableInterval](https://javadoc.scijava.org/ImgLib2/net/imglib2/IterableInterval.html).\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Core Concepts" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Accessibles" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In ImgLib2, images are represented by [_Accessibles_](https://imagej.net/ImgLib2_-_Accessibles). Image here refers to any (partial) function from coordinates to values.\n", "\n", "In the following sections we will see how pixel values can be manipulated using [_Accessors_](https://imagej.net/ImgLib2_-_Accessors). _Accessors_ are obtained from _Accessibles_. \n", "\n", "_Accessibles_ represent the data itself. Pixel images, procedurally generated images, views into images (for instance sub-images), interpolated images, sparse collections of samples, the list of local intensity maxima of an image, list of nearest neighbors, etc., are all examples of _Accessibles_." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Accessors" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In ImgLib2, images are manipulated using [_Accessors_](https://imagej.net/ImgLib2_-_Accessors). For pixel images, you can think of an accessor as a movable reference to a pixel. It can be moved around the image, even to a pixel at specific coordinates. It can be de-referenced to get the pixel value, and it can retrieve the current position. The accessors provided by ImgLib2 typically implement [RandomAccess](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccess.html) of [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html).\n", "\n", "\n", ">_NOTE:_ \n", ">Both [RandomAccess](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccess.html) and [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html) implement the [Sampler](https://javadoc.scijava.org/ImgLib2/net/imglib2/Sampler.html) interface which allows to access pixel values. Both implement the [Localizable](https://javadoc.scijava.org/ImgLib2/net/imglib2/Localizable.html) interface which allows to retrieve the accessors current >pixel coordinates. Both inherit (through Localizable) the [EuclideanSpace](https://javadoc.scijava.org/ImgLib2/net/imglib2/EuclideanSpace.html) interval which allows to get the number of dimensions of the image.\n", "\n", ">Furthermore, [Sampler](https://javadoc.scijava.org/ImgLib2/net/imglib2/Sampler.html), [RandomAccess](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccess.html), and [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html) have a type parameter **< T >** that refers to the value type of the underlying image." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### RandomAccess" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[RandomAccess](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccess.html) provides n-dimensional random access through the [Positionable](https://javadoc.scijava.org/ImgLib2/net/imglib2/Positionable.html) interface. It can be used to access pixels at arbitrary integer coordinates. \n", "\n", "The following code uses a RandomAccess to draw some white pixels into an image.\n", "\n", ">_NOTE:_\n", "> * **`get()`** is defined in the [Sampler](https://javadoc.scijava.org/ImgLib2/net/imglib2/Sampler.html) interface, so you can obtain pixel references from a Cursor in exactly the same way.\n", "> * Often, the **`T`** obtained from **`Sampler < T >.get()`** is a proxy object that is re-used internally. You should assume that moving the accessor invalidates the proxy. If you want to keep a permanent reference to a pixel, use the **`Sampler < T >.copy()`** method. In the above example, this would return a copy of the RandomAccess refering to the same pixel.\n", "> * The [Positionable](https://javadoc.scijava.org/ImgLib2/net/imglib2/Positionable.html), [Localizable](https://javadoc.scijava.org/ImgLib2/net/imglib2/Localizable.html), … interfaces are not restricted to accessors. In fact, many ImgLib2 entities are [Localizable](https://javadoc.scijava.org/ImgLib2/net/imglib2/Localizable.html). For instance, the [Point](https://javadoc.scijava.org/ImgLib2/net/imglib2/Point.html) class implements [Positionable](https://javadoc.scijava.org/ImgLib2/net/imglib2/Positionable.html) and [Localizable](https://javadoc.scijava.org/ImgLib2/net/imglib2/Localizable.html), and simply represents a n-dimensional coordinate. In your own code, whenever you have something that can provide coordinates, you should consider implementing [Localizable](https://javadoc.scijava.org/ImgLib2/net/imglib2/Localizable.html)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First, we will create a 400x320 8-bit gray-level image." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import net.imglib2.img.array.ArrayImgs\n", "\n", "long[] dimensions = [400, 320]\n", "img = ArrayImgs.unsignedBytes(dimensions)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ ">_NOTE:_ \n", "> Up to now we used an **`ArrayImgFactory`** to create a new image. Here we use the utility class **`ArrayImgs`** to create ArrayImgs of different types taking the desired dimensionality as an argument. This provides a more convenient way to create images." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we will create a [RandomAccess](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccess.html) to the image. Img implements the [RandomAccessible](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccessible.html) interface, thus we can use randomAccess() to obtain one. The [RandomAccess](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccess.html) has the same generic type, [UnsignedByteType](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/numeric/integer/UnsignedByteType.html), as the image." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "java.util.Random@140f4365" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "r = img.randomAccess()\n", "random = new Random()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Using a Groovy style **for** loop, we will set 1000 random pixels to the value of 255 (ie. _white_). \n", "\n", "First, we obtain the _x_, _y_ coordiantes of a random pixel within the image boundaries (_lines 2-3_). \n", "\n", "Then we position the [RandomAccess](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccess.html) at those coordinates using **setPosition()** from the [Positionable](https://javadoc.scijava.org/ImgLib2/net/imglib2/Positionable.html) interface, which takes two parameters, the coordinate and the dimension (_lines 4-5_), so we set the coordinate in dimension 0 to the value _x_, and we set the coordinate in dimension 1 to the value _y_. \n", "\n", "In _line 6_, using **get()**, we retrieve the pixel value at that coordinate; this method retunrs an instance of the pixel value type [UnsignedByteType](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/numeric/integer/UnsignedByteType.html) that acts as a reference to the pixel. \n", "\n", "Finally, we set the pixel value to 255 (ie. _white_) via this reference (_line 7_)." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "1000.times {\n", " x = ( int ) ( random.nextFloat() * img.max( 0 ) )\n", " y = ( int ) ( random.nextFloat() * img.max( 1 ) )\n", " r.setPosition( x, 0 )\n", " r.setPosition( y, 1 )\n", " t = r.get()\n", " t.set( 255 )\n", "}\n", "img" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Before we move on to the \"other\" accessor, [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html), let's consider a generalization of the previous example. Let's say we like setting random pixels, and because we plan to do this a lot in the future, we extract this functionality into a method.\n", "\n", "Easy enough. But what if we want to apply the method to images of another value type, e.g. **`DoubleType`** or **`ARGBType`**? What if we want to apply it to a 3D image? ImgLib2 allows you to write code that handles of this transparently. \n", "\n", "In the following example, we write a function that sets 1000 random pixels to \"white\" no matter what." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We are going to write a **`draw()`** method that can be applied to many ImgLib2 constructs that are not pixel [Img](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/Img.html)s. For instance there are Views that employ on-the-fly coordinate transforms, sampled and interpolated data, etc.\n", "\n", "There is no need to restrict ourselves to [Img](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/Img.html). We need be able to get a [RandomAccess](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccess.html), so we need **`RandomAccessible< T >`**, and to get the number of dimensions and the extent in every dimension, so we need [Interval](https://javadoc.scijava.org/ImgLib2/net/imglib2/Interval.html) and therefore use **`RandomAccessibleInterval< T >`**.\n", "\n", "However, with great power comes great responsibility… Taking a general [Interval](https://javadoc.scijava.org/ImgLib2/net/imglib2/Interval.html) means that we no longer can assume that the interval starts at coordinates (0,0,…,0). Thus we need to make a final modification to correctly draw between min and max of the interval." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "script1546098252890$1@10f7a9a5" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import java.util.Random\n", "import net.imglib2.*\n", " \n", "construct01 = new Object() {\n", " def void draw( final RandomAccessibleInterval< T > img, final T value ) {\n", " // get the number of dimensions\n", " final int n = img.numDimensions()\n", " // Taking a general Interval means that we no longer can assume \n", " // that the interval starts at coordinates (0,0,…,0). Thus we need \n", " // to make a final modification to correctly draw between min and max of the interval.\n", " final long[] min = new long[ n ]\n", " img.min( min )\n", " final long[] scale = new long[ n ]\n", " for ( int d = 0; d < n; ++d ) {\n", " scale[ d ] = img.max( d ) - min[ d ]\n", " }\n", " final long[] pos = new long[ n ]\n", "\n", " // create a RandomAccess to the image (now the generic type, T)\n", " // Img implements the RandomAccessible interface, \n", " // thus we can use randomAccess() to obtain one\n", " final RandomAccess< T > r = img.randomAccess()\n", " final Random random = new Random()\n", " for ( int i = 0; i < 1000; ++i ) {\n", " // loop over all dimensions when setting the position of the RandomAccess\n", " for ( int d = 0; d < n; ++d ) {\n", " pos[ d ] = min[ d ] + ( long ) ( random.nextFloat() * scale[ d ] )\n", " }\n", " // set the position\n", " r.setPosition( pos )\n", "\n", " // get() the pixel at that coordinate and \n", " // set the pixel value via this reference\n", " r.get().set( value )\n", " }\n", " } \n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this example, we use a 3D FloatType image. We will call the **`draw()`** method and display the resulting image. " ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import net.imglib2.type.numeric.real.FloatType\n", "import net.imglib2.img.array.ArrayImgs\n", "\n", "long[] dimensions = [400, 320, 5]\n", "img = ArrayImgs.floats(dimensions)\n", "construct01.draw( img, new FloatType(255f)) // call draw() method\n", "img" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ ">_NOTE:_\n", "> * Besides **`setPosition()`** in a single or all dimensions, you can also relatively move a [RandomAccess](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccess.html) in one or all dimensions, or move the [RandomAccess](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccess.html) pixel-wise in one dimension (Have a look at the [Positionable](https://javadoc.scijava.org/ImgLib2/net/imglib2/Positionable.html) API doc!) Which of those is most efficient depends on the situation.\n", "> **`T`** here is a **`setPosition()`** version which takes a [Localizable](https://javadoc.scijava.org/ImgLib2/net/imglib2/Localizable.html). Often a situation occurs where you want to position accessor _a_ to the same location as accessor _b._ In this situation you can avoid localizing _b_ into an array and using that array to set the position of _a._ You can simply **`a.setPosition( b )`** because _b_ is [Localizable](https://javadoc.scijava.org/ImgLib2/net/imglib2/Localizable.html).\n", "> * By _image_, we do not necessarily mean _pixel image._\n", "> * We use the generic parameter **`< T extends Type< T > >`** instead of **`< Type >`** throughout ImgLib2 since that allows us to be more type-safe. Imagine implementing an **`add(a, b)`** method for a certain type: using the simpler generic parameter would not allow us to enforce both parameters to have the same subclass of **`Type`**!\n", "> * We create as many variables as possible outside of the loop (in particular, objects) since creating objects costs a bit of execution time and can easily dominate the performance of the algorithm if one is not careful about it." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Cursor" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html) provides iteration through the [Iterator](https://javadoc.scijava.org/ImgLib2/net/imglib2/Iterator.html) interface. It can be moved forward to visit all pixels of the image once. The [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html) concept is not limited to pixel images. A [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html) can be used to iterate every collection of [Localizable](https://javadoc.scijava.org/ImgLib2/net/imglib2/Localizable.html) samples.\n", "\n", "[Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html) provides iteration through the [net.imglib2.Iterator](https://javadoc.scijava.org/ImgLib2/net/imglib2/Iterator.html) interface. **`Iterator.fwd()`** advances the cursor. **`Iterator.hasNext()`** returns true if the cursor can be advanced further. Initially, a [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html) points before the first element. You have to call **`fwd()`** once to move to the first element.\n", "\n", "> _NOTE:_ \n", "> The ImgLib2 [net.imglib2.Iterator](https://javadoc.scijava.org/ImgLib2/net/imglib2/Iterator.html) interface is different from Java's [java.util.Iterator](https://javadoc.scijava.org/Java/java/util/Iterator.html). However, for convenience, [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html) implements [java.util.Iterator](https://javadoc.scijava.org/Java/java/util/Iterator.html) as well.\n", "\n", "Just like [RandomAccess](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccess.html), [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html) extends [Sampler](https://javadoc.scijava.org/ImgLib2/net/imglib2/Sampler.html), so you can **`get()`** the value of the current pixel (respectively sample). It also implements [Localizable](https://javadoc.scijava.org/ImgLib2/net/imglib2/Localizable.html), so you can query the coordinates of the current sample.\n", "\n", "Let's look at an example: " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We load and display an image of diatoms." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[INFO] Populating metadata\n", "[INFO] Populating metadata\n" ] }, { "data": { "text/html": [ "" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import io.scif.img.IO\n", "\n", "// load an image of diatoms\n", "path = \"https://wsr.imagej.net/images/Diatoms.jpg\"\n", "diatoms = IO.open(path)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then, we will find the maximum value (the intensity of the brightest pixel) within this image." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "maximum = 255" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import net.imglib2.Cursor\n", "import net.imglib2.img.Img\n", "\n", "// we get a Cursor from the image\n", "cursor = diatoms.cursor()\n", "int max = 0\n", "\n", "// we iterate over the image \n", "while ( cursor.hasNext() ) {\n", " // get the value at the current position\n", " t = cursor.next()\n", " // check that if greater than max; if so, set to max\n", " max = Math.max( t.get(), max )\n", "}\n", "\"maximum = \" + max" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's extend the previous example. Now we are also interested in the coordinates of the maximum. [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html) extends the [Localizable](https://javadoc.scijava.org/ImgLib2/net/imglib2/Localizable.html) interface which provides methods to get the current location either dimension-by-dimension or all at once. And let's do this via a generic version; for example, we will not take an **`Img< T >`** as the parameter, because that would be too restrictive. All we need is something that is iterable. Because we can easily put interval bounds on every iterable set of Localizables, ImgLib2 does not define an Iterable super-interface for IterableInterval. So [IterableInterval< T >](https://javadoc.scijava.org/ImgLib2/net/imglib2/IterableInterval.html) is the most general we can go here.\n", "\n", "So here is our more general method for finding the maximum pixel value in the image:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "script1546098255859$1@7abd1a16" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import net.imglib2.*\n", " \n", "construct02 = new Object() {\n", " // IterableInterval is the most general type to use, but must be Comparable\n", " // the return value of findmax is a Cursor, holds value and coordinates\n", " public static < T extends Comparable> Cursor< T > findmax( final IterableInterval< T > iterable ) {\n", " final Cursor< T > cursor = iterable.cursor()\n", " cursor.fwd()\n", " Cursor< T > max = cursor.copyCursor()\n", " while ( cursor.hasNext() ) {\n", " // to remember the maximum, we simply take a new copy \n", " // of the iterating cursor whenever a better max value is found\n", " if ( cursor.next().compareTo( max.get() ) > 0 ) {\n", " max = cursor.copyCursor()\n", " }\n", " }\n", " return max\n", " }\n", " }" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For finding the maximum, the only restriction we have to put on type T is that it is comparable.\n", "\n", "The return value of **`findmax()`** is a **`Cursor`**. Instead of creating a new class that represents a tuple of maximum value and coordinates, we simply return a **`Cursor`** positioned at the maximum.\n", "\n", "We will now call this method on the diatoms image, and will use the dimension-by-dimension **`getLongPosition()`** of the [Localizable](https://javadoc.scijava.org/ImgLib2/net/imglib2/Localizable.html) interface." ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "max = 255 found at (50, 0)" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// use findmax and get the maximum value and coordinates from the resulting Cursor,\n", "// using the dimension-by-dimension getLongPosition() of the Localizable interface\n", "max = construct02.findmax( diatoms )\n", "\"max = \" + max.get().get() + \" found at (\" + max.getLongPosition( 0 ) + \", \" + max.getLongPosition( 1 ) + \")\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> _NOTE:_\n", "> * The iteration order is subject to implementation, specialized for each memory layout to minimize access time. For example, an [ArrayImg](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/array/ArrayImg.html) has a different iteration order from a [CellImg](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/cell/CellImg.html). This is nicely illustrated in [ImgLib2 Example 2b - Duplicating an Img using a different ImgFactory](https://imagej.net/ImgLib2_Examples#Example_2b_-_Duplicating_an_Img_using_a_different_ImgFactory).\n", "> * Typically, there are two variants of [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html) available. One that calculates its location per each iteration and one that calculates it only per localization request. The former is more efficient when localization occurs frequently, the latter otherwise. In the maximum-finding example, we use the latter because localization is only required once after the maximum has been found. The former one could be obtained using **`localizingCursor()`** instead of **`cursor()`** (see [IterableInterval](https://javadoc.scijava.org/ImgLib2/net/imglib2/IterableInterval.html) API doc)\n", "> * **`copyCursor()`** is a work-around to circumvent a javac bug with covariant return type overriding. In the future (with JDK7) every [Sampler](https://javadoc.scijava.org/ImgLib2/net/imglib2/Sampler.html) can be copied using **`copy()`** instead of having specialised **`copyCursor()`**, **`copyRandomAccess()`**, … methods." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "ImgLib2 is not restricted to rasterized images and integer coordinates. It also supports continuous images and real-valued coordinates. Examples where this is appropriate are\n", "\n", "* an interpolated image, where an interpolated value can be obtained at any real coordinate. Note that this is a bounded, but continuous image. Thus it is not iterable.\n", "* a procedurally generated image, where a value can be computed at any real coordinate (continuous, unbounded, non-iterable).\n", "* collections of samples taken at arbitrary real coordinates (discrete, bounded, iterable).\n", "\n", "Real equivalents of the [Positionable](https://javadoc.scijava.org/ImgLib2/net/imglib2/Positionable.html) and [Localizable](https://javadoc.scijava.org/ImgLib2/net/imglib2/Localizable.html) interfaces have been added by which real-valued coordinates can be accessed, [RealPositionable](https://javadoc.scijava.org/ImgLib2/net/imglib2/RealPositionable.html) and [RealLocalizable](https://javadoc.scijava.org/ImgLib2/net/imglib2/RealLocalizable.html), respectively. [RealPositionable](https://javadoc.scijava.org/ImgLib2/net/imglib2/RealPositionable.html) extends [Positionable](https://javadoc.scijava.org/ImgLib2/net/imglib2/Positionable.html), because whenever something can be positioned at arbitrary real coordinates, of course it can be positioned to integer coordinates as well. [Localizable](https://javadoc.scijava.org/ImgLib2/net/imglib2/Localizable.html) extends [RealLocalizable](https://javadoc.scijava.org/ImgLib2/net/imglib2/RealLocalizable.html), as something that is able to provide its integer coordinates is always able to provide them as real coordinates too.\n", "\n", "Both, [RealRandomAccess](https://javadoc.scijava.org/ImgLib2/net/imglib2/RealRandomAccess.html) and [RealCursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/RealCursor.html) are [RealLocalizable](https://javadoc.scijava.org/ImgLib2/net/imglib2/RealLocalizable.html). Note that the inheritance relationship of [Localizable](https://javadoc.scijava.org/ImgLib2/net/imglib2/Localizable.html) and [RealLocalizable](https://javadoc.scijava.org/ImgLib2/net/imglib2/RealLocalizable.html) propagates to the cursors. Every [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html) is also a [RealCursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/RealCursor.html) (because it can provide its current integer coordinates as real coordinates as well). There is no such relationship between [RandomAccess](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccess.html) and [RealRandomAccess](https://javadoc.scijava.org/ImgLib2/net/imglib2/RealRandomAccess.html).\n", "\n", "You can read more about this final topic and see example code [here](https://imagej.net/ImgLib2_-_Accessors#Accessors_for_Continuous_Coordinates)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Example 01: Opening, creating and displaying images" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's get started with ImgLib2 by opening, creating, and displaying images. You can see these same tutorials on the ImageJ Wiki [here](https://imagej.net/ImgLib2_Examples)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Opening an ImgLib2 image" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The typical way to open an image in ImgLib2 is to make use of the [SCIFIO](https://scif.io/) importer. Below you see two examples of how to open an image as (_a_) its own type (e.g. **`UnsignedByteType`**) and (_b_) as float (**`FloatType`**). For (_a_) we assume, however, that the file contains some real valued numbers as defined by the interface **`RealType`**. Color images are opened as well and color is represented as its own dimension (like in the ImageJ Hyperstacks).\n", "\n", "Important: it does not matter which type of Img you use to hold the data as we will use [Iterator](https://javadoc.scijava.org/ImgLib2/net/imglib2/Iterator.html)s and [RandomAccess](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccess.html)es to access the image content. It might be, however, important if you work on two Img at the same time using [Iterator](https://javadoc.scijava.org/ImgLib2/net/imglib2/Iterator.html)s, see Example2." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the following example, (_a_), we use an [ArrayImg](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/array/ArrayImg.html) to hold the data. This means the data is held in one single java basic type array which results in optimal performance. The absolute size of image is, however, limited to 2^31-1 (~2 billion) pixels. The type of [Img](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/Img.html) to use is set by passing an **ImgOptions** configuration when calling the **ImgOpener**." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import io.scif.img.ImgOpener\n", "\n", "// define the file to open\n", "path = \"https://samples.fiji.sc/tutorials/DrosophilaWing.tif\"\n", " \n", "// create the ImgOpener. The type (e.g. ArrayImg, PlanarImg, CellImg) is\n", "// automatically determined. For a small image that fits in memory, this\n", "// should open as an ArrayImg.\n", "imgOpener = new ImgOpener()\n", " \n", "// open with ImgOpener. \n", "image = imgOpener.openImgs(path).get(0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In (_b_) just below, we use a [CellImg](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/cell/CellImg.html) instead. It partitions the image data into n-dimensional cells each holding only a part of the data. Further, [SCIFIO](https://scif.io/) takes care of caching cells in and out of memory as needed, greatly reducing the memory requirement to work with very large images.\n", "\n", "The SCIFIO importer also requires [Type](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/Type.html)s that implement [NativeType](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/NativeType.html), which means it is able to map the data into a Java basic type array. All available [Type](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/Type.html)s until now are implementing [NativeType](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/NativeType.html), if you want to work with some self-developed [Type](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/Type.html) it would be easiest to copy the opened [Img](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/Img.html) afterwards. Please also note that until now, the only [Img](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/Img.html) that supports non-native types is the [ListImg](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/list/ListImg.html) which stores every pixel as an individual object!" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import io.scif.config.SCIFIOConfig\n", "import io.scif.config.SCIFIOConfig.ImgMode\n", "\n", "// create the SCIFIOConfig. This gives us configuration control over how\n", "// the ImgOpener will open its datasets.\n", "config = new SCIFIOConfig()\n", " \n", "// If we know what type of Img we want, we can encourage their use through\n", "// an SCIFIOConfig instance. CellImgs dynamically load image regions and are\n", "// useful when an image won't fit in memory\n", "config.imgOpenerSetImgModes( ImgMode.CELL )\n", " \n", "// open with ImgOpener as a CellImg\n", "imageCell = imgOpener.openImg( path, config )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Creating a new ImgLib2 image" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Another important way to instantiate a new ImgLib2 [Img](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/Img.html) is to create a new one from scratch. This requires you to define its [Type](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/Type.html) as well as the [ImgFactory](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/ImgFactory.html) to use. It does additionally need one instance of the [Type](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/Type.html) that it is supposed to hold." ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import net.imglib2.img.cell.CellImgFactory\n", "import net.imglib2.type.numeric.real.FloatType\n", "\n", "// create the ImgFactory based on cells (cellsize = 5x5x5...x5) that will\n", "// instantiate the Img\n", "imgFactory = new CellImgFactory<>( new FloatType(), 5 )\n", " \n", "// create an 3d-Img with dimensions 20x30x40 (here cellsize is 5x5x5)Ø\n", "long[] dimensions = [20, 30, 40]\n", "img1 = imgFactory.create( dimensions )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Once you have one instance of an [Img](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/Img.html), it is very easy to create another one using the same [Type](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/Type.html) and [ImgFactory](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/ImgFactory.html), even if it has a different size. Note that the call [img.firstElement()](https://javadoc.scijava.org/ImgLib2/net/imglib2/IterableRealInterval.html#firstElement--) returns the first pixel of any [Iterable](https://javadoc.scijava.org/Java/java/lang/Iterable.html?is-external=true), e.g. an [Img](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/Img.html)." ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import net.imglib2.img.Img;\n", "import net.imglib2.img.ImgFactory;\n", "\n", "// create another image with the same size\n", "// note that the input provides the size for the new image as it implements\n", "// the Interval interface\n", "img2 = imgFactory.create( img1 )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Displaying images partly using Views" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "By using the concept of [Views](https://javadoc.scijava.org/ImgLib2/net/imglib2/view/Views.html) it is possible to display only parts of the image, display a rotated view, and many more cool things. Note that you can also concatenate them. [Views](https://javadoc.scijava.org/ImgLib2/net/imglib2/view/Views.html) are much more powerful than shown in this example, they will be increasingly used throughout the examples.\n", "\n", "A [View](https://javadoc.scijava.org/ImgLib2/net/imglib2/View.html) almost behaves similar to an [Img](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/Img.html), and in fact they share important concepts. Both are [RandomAccessible](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccessible.html), and [Views](https://javadoc.scijava.org/ImgLib2/net/imglib2/view/Views.html) that are not infinite are also an [Interval](https://javadoc.scijava.org/ImgLib2/net/imglib2/Interval.html) (i.e. those [Views](https://javadoc.scijava.org/ImgLib2/net/imglib2/view/Views.html) have a defined size) and can therefore be made [Iterable](https://javadoc.scijava.org/Java/java/lang/Iterable.html?is-external=true) (see example 2c). In ImgLib2, all algorithms are implemented for abstract concepts like [RandomAccessible](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccessible.html), [Iterable](https://javadoc.scijava.org/Java/java/lang/Iterable.html?is-external=true) or [Interval](https://javadoc.scijava.org/ImgLib2/net/imglib2/Interval.html). This enables us, as can be seen below, to display a [View](https://javadoc.scijava.org/ImgLib2/net/imglib2/View.html) the exact same way we would also display an [Img](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/Img.html)." ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import io.scif.img.ImgOpener\n", "import net.imglib2.type.numeric.real.FloatType\n", "import net.imglib2.view.Views\n", "\n", "// open file as float with ImgOpener\n", "img = new ImgOpener().openImg( \"https://samples.fiji.sc/tutorials/DrosophilaWing.tif\", new FloatType() )\n", " \n", "// use a View to define an interval (min and max coordinate, inclusive) to display\n", "long[] min = [200, 200]\n", "long[] max = [500, 350]\n", "view = Views.interval( img, min, max)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, we will display the partial image at a 90° rotation." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import net.imglib2.view.Views\n", "\n", "// display the same area rotated by 90 degrees (x-axis (0) and y-axis (1) switched)\n", "viewRotated = Views.rotate(view, 0, 1) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Example 02: How to use Cursor, RandomAccess, and Type" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that we have a few basics under our belts, let's take a closer look at using [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html), [RandomAccess](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccess.html), and [Type](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/Type.html). You can see these same tutorials on the ImageJ Wiki [here](https://imagej.net/ImgLib2_Examples#Example_2_-_How_to_use_Cursor.2C_RandomAccess_and_Type)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The following examples illustrate how to access pixels using [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html) and [RandomAccess](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccess.html), their basic properties, and how to modify pixel values using [Type](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/Type.html).\n", "\n", "Accessing pixels using a [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html) means to iterate all pixels in a way similar to iterating Java collections. However, a [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html) only ensures to visit each pixel exactly once, the order of iteration is not fixed in order to optimize the speed of iteration. This implies that the order of iteration on two different [Img](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/Img.html) is not necessarily the same, see the example for \"Duplicating an Img using a different ImgFactory\". [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html)s can be created by any object that implements [IterableInterval](https://javadoc.scijava.org/ImgLib2/net/imglib2/IterableInterval.html), such as an [Img](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/Img.html). [Views](https://javadoc.scijava.org/ImgLib2/net/imglib2/view/Views.html) that are not infinite can be made iterable (see the example for \"Generic copying of image data\"). Note that in general a [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html) has significantly higher performance than a [RandomAccess](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccess.html) and should therefore be given preference if possible.\n", "\n", "In contrast to iterating image data, a [RandomAccess](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccess.html) can be placed at arbitrary locations. It is possible to set them to a specific n-dimensional coordinate or move them relative to their current position. Note that relative movements are usually more performant. A [RandomAccess](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccess.html) can be created by any object that implements [RandomAccessible](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccessible.html), like an [Img](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/Img.html) or a [View](https://javadoc.scijava.org/ImgLib2/net/imglib2/View.html).\n", "\n", "[Localizable](https://javadoc.scijava.org/ImgLib2/net/imglib2/Localizable.html) is implemented by [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html) as well as [RandomAccess](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccess.html), which means they are able to report their current location. However, for [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html) we differentiate between a **`LocalizingCursor`** and a normal [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html). A **`LocalizingCursor`** updates his position on every move, no matter if it is queried or not whereas a normal [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html) computes its location on demand. Using a **`LocalizingCursor`** is more efficient if the location is queried for every pixel, a [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html) will be faster when localizing only occasionally.\n", "\n", "The [Sampler](https://javadoc.scijava.org/ImgLib2/net/imglib2/Sampler.html) interface implemented by [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html) and [RandomAccess](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccess.html) provides access to the [Type](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/Type.html) instance of the current pixel. Using the [Type](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/Type.html) instance it is possible to read and write its current value. Depending on the capabilities of the [Type](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/Type.html) more operations are available, e.g. +, -, *, / if it is a [NumericType](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/numeric/NumericType.html)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Duplicating an Img using a generic method" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The goal of this example is to make a copy of an existing [Img](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/Img.html). For this task it is sufficient to employ [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html)s. The order of iteration for both [Img](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/Img.html)'s will be the same as they are instantiated using the same [ImgFactory](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/ImgFactory.html). It is possible to test if two [IterableInterval](https://javadoc.scijava.org/ImgLib2/net/imglib2/IterableInterval.html)s have the same iteration order:\n", "\n", "boolean sameIterationOrder = interval1.iterationOrder().equals( interval2.iterationOrder() );\n", "\n", "The copy method itself is a generic method, it will work on any kind of [Type](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/Type.html). In this particular case it works on a [FloatType](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/numeric/real/FloatType.html), but would also work on anything else like for example a [ComplexDoubleType](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/numeric/complex/ComplexDoubleType.html). The declaration of the generic type is done in the method declaration:\n", "\n", " public < T extends Type < T > > Img< T > copyImage( ... )\n", "\n", "**`< T extends Type< T > >`** basically means that **`T`** can be anything that extends [Type](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/Type.html). These can be final implementations such as [FloatType](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/numeric/real/FloatType.html) or also intermediate interfaces such as [RealType](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/numeric/RealType.html). This, however, also means that in the method body only operations supported by [Type](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/Type.html) will be available. Note that the method returns a **`T`**, which also means that it will return an **`Img< FloatType >`** as we provide it with one in the constructor (see the example usage of the **`copyImage(...)`** method below)." ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "script1546098258750$1@437ed216" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import io.scif.img.ImgIOException\n", "import net.imglib2.Cursor\n", "import net.imglib2.img.Img\n", "import net.imglib2.type.Type\n", " \n", "/**\n", "* Generic, type-agnostic method to create an identical copy of an Img\n", "*\n", "* @param input - the Img to copy\n", "* @return - the copy of the Img\n", "*/\n", "construct03 = new Object() {\n", " public < T extends Type< T > > Img< T > copyImage( final Img< T > input ) {\n", " // create a new Image with the same properties\n", " // note that the input provides the size for the new image as it implements\n", " // the Interval interface\n", " output = input.factory().create( input )\n", " \n", " // create a cursor for both images\n", " cursorInput = input.cursor()\n", " cursorOutput = output.cursor()\n", " \n", " // iterate over the input\n", " while ( cursorInput.hasNext())\n", " {\n", " // move both cursors forward by one pixel\n", " cursorInput.fwd()\n", " cursorOutput.fwd()\n", " \n", " // set the value of this pixel of the output image to the same as the input,\n", " // every Type supports T.set( T type )\n", " cursorOutput.get().set( cursorInput.get() )\n", " }\n", " \n", " // return the copy\n", " return output\n", " }\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will now make a copy of an Image into another one using the generic method above." ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import io.scif.img.ImgOpener\n", "import net.imglib2.type.numeric.real.FloatType\n", "\n", "// open with ImgOpener as a FloatType\n", "img = new ImgOpener().openImg( \"https://samples.fiji.sc/tutorials/DrosophilaWing.tif\", new FloatType() )\n", " \n", "// copy the image, as it is a generic method it also works with FloatType\n", "duplicate = construct03.copyImage( img )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Duplicating an Img using a different ImgFactory" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "WARNING: The **copyImageWrong** method in this example makes a mistake on purpose! It intends to show that the iteration order of [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html)s is important to consider. The goal is to copy the content of an [ArrayImg](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/array/ArrayImg.html) (i.e. an [Img](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/Img.html) that was created using an [ArrayImgFactory](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/array/ArrayImgFactory.html)) into a [CellImg](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/cell/CellImg.html). Using only [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html)s for both images will have a wrong result as an [ArrayImg](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/array/ArrayImg.html) and a [CellImg](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/cell/CellImg.html) have different iteration orders. An [ArrayImg](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/array/ArrayImg.html) is iterated linearly, while a [CellImg](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/cell/CellImg.html) is iterated cell-by-cell, but linearly within each cell." ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "script1546098260306$1@6d0da016" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import net.imglib2.Cursor\n", "import net.imglib2.img.Img\n", "import net.imglib2.img.ImgFactory\n", "import net.imglib2.type.Type\n", "\n", "/**\n", "* WARNING: This method makes a mistake on purpose!\n", "*/\n", "construct04 = new Object() {\n", " public < T extends Type< T >> Img< T > copyImageWrong( final Img< T > input, final ImgFactory< T > imgFactory ) {\n", " // create a new Image with the same dimensions but the other imgFactory\n", " // note that the input provides the size for the new image as it\n", " // implements the Interval interface\n", " output = imgFactory.create( input )\n", " \n", " // create a cursor for both images\n", " cursorInput = input.cursor()\n", " cursorOutput = output.cursor()\n", " \n", " // iterate over the input cursor\n", " while ( cursorInput.hasNext())\n", " {\n", " // move both forward\n", " cursorInput.fwd()\n", " cursorOutput.fwd()\n", " \n", " // set the value of this pixel of the output image, every Type supports T.set( T type )\n", " cursorOutput.get().set( cursorInput.get() )\n", " }\n", " \n", " // return the copy\n", " return output\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import io.scif.img.ImgOpener\n", "import net.imglib2.img.Img\n", "import net.imglib2.img.array.ArrayImgFactory\n", "import net.imglib2.img.cell.CellImgFactory\n", "import net.imglib2.type.numeric.real.FloatType\n", "\n", "// open with ImgOpener. In addition to using ImgOptions, we can directly\n", "// pass an ImgFactory to the ImgOpener. This bypasses the Img selection\n", "// heuristic and allows custom ImgFactory implementations to be used\n", "img = (Img< FloatType >) new ImgOpener().openImg( \"https://samples.fiji.sc/tutorials/DrosophilaWing.tif\",\n", " new ArrayImgFactory<>(new FloatType()) )\n", " \n", "// copy the image into a CellImg with a cellsize of 20x20\n", "duplicate = construct04.copyImageWrong( img, new CellImgFactory<>( new FloatType(), 20 ) )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The correct code for the copy-method (in **copyImageCorrect**) requires the use of a [RandomAccess](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccess.html). We use a [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html) to iterate over all pixels of the input and a [RandomAccess](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccess.html) which we set to the same location as the output. Note that the **setPosition()** call of the [RandomAccess](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccess.html) directly takes the [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html) as input, which is possible because [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html) implements [Localizable](https://javadoc.scijava.org/ImgLib2/net/imglib2/Localizable.html). Please also note that we use a **LocalizingCursor** instead of a normal [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html) because we need the location of the [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html) at every pixel." ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "script1546098261911$1@3fae7f2b" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import net.imglib2.Cursor\n", "import net.imglib2.RandomAccess\n", "import net.imglib2.img.Img\n", "import net.imglib2.img.ImgFactory\n", "import net.imglib2.type.Type\n", "\n", "/**\n", "* This method copies the image correctly, using a RandomAccess.\n", "*/\n", "construct05 = new Object() {\n", " public < T extends Type< T >> Img< T > copyImageCorrect( final Img< T > input, final ImgFactory< T > imgFactory )\n", " {\n", " // create a new Image with the same dimensions but the other imgFactory\n", " // note that the input provides the size for the new image by implementing the Interval interface\n", " Img< T > output = imgFactory.create( input )\n", " \n", " // create a cursor that automatically localizes itself on every move\n", " Cursor< T > cursorInput = input.localizingCursor()\n", " RandomAccess< T > randomAccess = output.randomAccess()\n", " \n", " // iterate over the input cursor\n", " while ( cursorInput.hasNext())\n", " {\n", " // move input cursor forward\n", " cursorInput.fwd()\n", " \n", " // set the output cursor to the position of the input cursor\n", " randomAccess.setPosition( cursorInput )\n", " \n", " // set the value of this pixel of the output image, every Type supports T.set( T type )\n", " randomAccess.get().set( cursorInput.get() )\n", " }\n", " // return the copy\n", " return output\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import io.scif.img.ImgOpener\n", "import net.imglib2.img.Img\n", "import net.imglib2.img.array.ArrayImgFactory\n", "import net.imglib2.img.cell.CellImgFactory\n", "import net.imglib2.type.numeric.real.FloatType\n", "\n", "// open with ImgOpener. In addition to using ImgOptions, we can directly\n", "// pass an ImgFactory to the ImgOpener. This bypasses the Img selection\n", "// heuristic and allows custom ImgFactory implementations to be used\n", "img = (Img< FloatType >) new ImgOpener().openImg( \"https://samples.fiji.sc/tutorials/DrosophilaWing.tif\",\n", " new ArrayImgFactory<>( new FloatType() ) )\n", " \n", "// copy the image into a CellImg with a cellsize of 20x20\n", "duplicate = construct05.copyImageCorrect( img, new CellImgFactory<>( new FloatType(), 20 ) )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Generic copying of image data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In order to write a method that generically copies data requires an implementation for the underlying concepts of [RandomAccessible](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccessible.html), [Iterable](https://javadoc.scijava.org/Java/java/lang/Iterable.html?is-external=true) and [Interval](https://javadoc.scijava.org/ImgLib2/net/imglib2/Interval.html). In that way, it will run on [Img](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/Img.html), [View](https://javadoc.scijava.org/ImgLib2/net/imglib2/View.html) and any other class implemented for these interfaces (even if they do not exist yet).\n", "\n", "Therefore we design the copy method in a way that the target is an [IterableInterval](https://javadoc.scijava.org/ImgLib2/net/imglib2/IterableInterval.html) and the source is [RandomAccessible](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccessible.html). In this way, we simply iterate over the target and copy the corresponding pixels from the source.\n", "\n", "As the source only needs to be [RandomAccessible](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccessible.html), it can be basically anything that can return a value at a certain location. This can be as simple as an [Img](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/Img.html), but also interpolated sparse data, a function, a ray-tracer, a [View](https://javadoc.scijava.org/ImgLib2/net/imglib2/View.html), ....\n", "\n", "As the target needs to be an [IterableInterval](https://javadoc.scijava.org/ImgLib2/net/imglib2/IterableInterval.html), it is more confined. This, however does not necessarily mean that it can only be an [Img](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/Img.html) or a [View](https://javadoc.scijava.org/ImgLib2/net/imglib2/View.html) that is not infinite. It simply means it has to be something that is iterable and not infinite, which for example also applies to sparse data (e.g. a list of locations and their values)." ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "script1546098263241$1@393a2a65" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import net.imglib2.IterableInterval\n", "import net.imglib2.RandomAccessible\n", "import net.imglib2.type.Type\n", "\n", "/**\n", " * Copy from a source that is just RandomAccessible to an IterableInterval. Latter one defines\n", " * size and location of the copy operation. It will query the same pixel locations of the\n", " * IterableInterval in the RandomAccessible. It is up to the developer to ensure that these\n", " * coordinates match.\n", " *\n", " * Note that both, input and output could be Views, Img or anything that implements\n", " * those interfaces.\n", " *\n", " * @param source - a RandomAccess as source that can be infinite\n", " * @param target - an IterableInterval as target\n", " */\n", "construct06 = new Object() {\n", " public < T extends Type< T > > void copy( final RandomAccessible< T > source, final IterableInterval< T >\n", " target )\n", " {\n", " // create a cursor that automatically localizes itself on every move\n", " targetCursor = target.localizingCursor()\n", " sourceRandomAccess = source.randomAccess()\n", " \n", " // iterate over the input cursor\n", " while ( targetCursor.hasNext())\n", " {\n", " // move input cursor forward\n", " targetCursor.fwd()\n", " \n", " // set the output cursor to the position of the input cursor\n", " sourceRandomAccess.setPosition( targetCursor )\n", " \n", " // set the value of this pixel of the output image, every Type supports T.set( T type )\n", " targetCursor.get().set( sourceRandomAccess.get() )\n", " }\n", " }\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First, we will copy an Image into another with a LocalizingCursor." ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import io.scif.img.ImgOpener\n", "import net.imglib2.type.numeric.real.FloatType\n", " \n", "// open with ImgOpener as a float\n", "img = new ImgOpener().openImg(\"https://samples.fiji.sc/tutorials/DrosophilaWing.tif\", new FloatType())\n", " \n", "// copy & display an image\n", "duplicate = img.factory().create( img )\n", "construct06.copy( img, duplicate )\n", "duplicate" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then, we will copy an Image into another with a RandomAccess." ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import io.scif.img.ImgOpener\n", "import net.imglib2.type.numeric.real.FloatType\n", "import net.imglib2.view.Views\n", " \n", "// open with ImgOpener as a float\n", "img = new ImgOpener().openImg(\"https://samples.fiji.sc/tutorials/DrosophilaWing.tif\", new FloatType())\n", " \n", "// use a View to define an interval as source for copying\n", "// Views.offsetInterval() does not only define where it is, but also adds a translation\n", "// so that the minimal coordinate (upper left) of the view maps to (0,0)\n", "long[] offset = [100, 100]\n", "long[] dimension = [250, 150]\n", "viewSource = Views.offsetInterval( img, offset, dimension)\n", " \n", "// and as target\n", "long[] targetOffset = [500, 200]\n", "long[] targetDimension = [250, 150]\n", "viewTarget = Views.offsetInterval( img, targetOffset, targetDimension)\n", " \n", "// now we make the target iterable\n", "// (which is possible because it is a RandomAccessibleInterval)\n", "iterableTarget = Views.iterable( viewTarget )\n", " \n", "// copy it into the original image (overwriting part of img)\n", "construct06.copy( viewSource, iterableTarget )\n", " \n", "// show the original image\n", "img" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Example 03: Writing generic algorithms" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Imglib2 Examples 01 and 02** tried to introduce important tools you need in order to implement algorithms with ImgLib2. This example will show three generic implementations of algorithms computing the min/max, average, as well as the center of mass.\n", "\n", "The core idea is to implement algorithms as generic as possible in order to maximize code-*reusability.* In general, a good way to start is to think: What are the minimal requirements in order to implement algorithm X? This applies to all of the following three concepts:\n", "\n", "* **Type**: You should always use the most abstract [Type](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/Type.html) possible, i.e. the one that just offers enough operations to perform your goal. In this way, the algorithm will be able to run on [Type](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/Type.html)s you might not even have thought about when implementing it. A good example is the min&max search in example 3a. Instead of implementing it for [FloatType](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/numeric/real/FloatType.html) or the more abstract [RealType](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/numeric/RealType.html), we implement it for the even more abstract [Comparable](https://docs.oracle.com/javase/7/docs/api/java/lang/Comparable.html) & [Type](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/Type.html).\n", "* **Image data**: Every algorithm should only demand those interfaces that it requires, not specific implementations of it like [Img](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/Img.html). You might require [RandomAccessible](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccessible.html) (infinite), [RandomAccessibleInterval](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccessibleInterval.html) (finite), [Iterable](https://javadoc.scijava.org/Java/java/lang/Iterable.html?is-external=true) (values without location), [IterableInterval](https://javadoc.scijava.org/ImgLib2/net/imglib2/IterableInterval.html) (values and their location) or their corresponding interfaces for real-valued locations [RealRandomAccessible](https://javadoc.scijava.org/ImgLib2/net/imglib2/RealRandomAccessible.html), [RealRandomAccessibleRealInterval](https://javadoc.scijava.org/ImgLib2/net/imglib2/RealRandomAccessibleRealInterval.html) and [IterableRealInterval](https://javadoc.scijava.org/ImgLib2/net/imglib2/IterableRealInterval.html). Note that you can concatenate them if you need more than one property.\n", "* **Dimensionality**: Usually there is no reason to restrict an algorithm to a certain dimensionality (like only for two-dimensional images), at least we could not really come up with a convincing example. *If the application or plugin you are developing addresses a certain dimensionality (e.g. stitching of panorama photos) it is understandable that you do not want to implement everything n-dimensionally. But try to implement as many as possible of the smaller algorithm you are using as generic, n-dimensional methods. For example, everything that requires only to **iterate** the data is usually inherently n-dimensional.*\n", "\n", "Following those ideas, your newly-implemented algorithm will be applicable to any kind of data and dimensionality it is defined for, not only a very small domain you are currently working with. Also note that quite often this actually makes the implementation simpler." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Min/Max search" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Searching for the minimal and maximal value in a dataset is a very nice example to illustrate generic algorithms. In order to find min/max values, [Type](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/Type.html)s only need to be able to compare themselves. Therefore we do not need any numeric values, we only require them to implement the (Java) interface [Comparable](https://docs.oracle.com/javase/7/docs/api/java/lang/Comparable.html). Additionally, no random access to the data is required, we simply need to iterate all pixels, also their location is irrelevant. The image data we need only needs to be [Iterable](https://javadoc.scijava.org/Java/java/lang/Iterable.html?is-external=true).\n", "\n", "Below we show **three** small variations of the min/max search. \n", "* First, in Example 3a, we show the implementation as described above. \n", "* Second, in Example 3b, we illustrate that this also works on a standard Java ArrayList. \n", "* Third, in Example 3c, we show how the implementation changes if we do not only want the min/max value, but also their location. This requires to use [IterableInterval](https://javadoc.scijava.org/ImgLib2/net/imglib2/IterableInterval.html) instead, as [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html) can return their location." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Example 3a" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "script1546098270281$1@450b1afa" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import net.imglib2.type.Type\n", "\n", "construct01 = new Object() { \n", " /**\n", " * Compute the min and max for any {@link Iterable}, like an {@link Img}.\n", " *\n", " * The only functionality we need for that is to iterate. Therefore we need no {@link Cursor}\n", " * that can localize itself, neither do we need a {@link RandomAccess}. So we simply use the\n", " * most simple interface in the hierarchy.\n", " *\n", " * @param input - the input that has to just be {@link Iterable}\n", " * @param min - the type that will have min\n", " * @param max - the type that will have max\n", " */\n", " public & Type> void computeMinMax(Iterable input, T min, T max) {\n", " // create a cursor for the image (the order does not matter)\n", " Iterator iterator = input.iterator()\n", " \n", " // initialize min and max with the first image value\n", " T type = iterator.next()\n", " \n", " min.set(type)\n", " max.set(type)\n", " \n", " // loop over the rest of the data and determine min and max value\n", " while (iterator.hasNext()) {\n", " // we need this type more than once\n", " type = iterator.next()\n", " \n", " if (type.compareTo(min) < 0) {\n", " min.set( type )\n", " }\n", " \n", " if (type.compareTo(max) > 0) {\n", " max.set(type)\n", " }\n", " }\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "scrolled": true }, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "ccc6e420-c241-431b-94d2-c9a0b4252669", "version_major": 2, "version_minor": 0 }, "method": "display_data" }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import io.scif.img.ImgOpener\n", "import net.imglib2.type.numeric.real.FloatType\n", "\n", "/**\n", " * Perform a generic min & max search\n", " *\n", " * @author Stephan Preibisch & Stephan Saalfeld\n", " *\n", " */\n", "\n", "// open with ImgOpener\n", "img = new ImgOpener().openImgs(\"https://samples.fiji.sc/tutorials/DrosophilaWing.tif\", new FloatType()).get(0)\n", "\n", "// create two empty variables\n", "min = img.firstElement().createVariable()\n", "max = img.firstElement().createVariable()\n", "\n", "// compute min and max of the Image\n", "construct01.computeMinMax(img, min, max)\n", "\n", "[\"minimum Value (img)\": min.getRealDouble(),\n", "\"maximum Value (img)\": max.getRealDouble()]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Example 3b" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that this example works just the same way if the input is not an [Img](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/Img.html), but for example just a standard Java [ArrayList](https://docs.oracle.com/javase/7/docs/api/java/util/ArrayList.html)." ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "6c5dc193-0b2c-462b-aae6-c5c4840063bb", "version_major": 2, "version_minor": 0 }, "method": "display_data" }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import java.util.ArrayList\n", "import net.imglib2.type.numeric.real.FloatType\n", " \n", "/**\n", " * Perform a generic min & max search\n", " *\n", " * @author Stephan Preibisch & Stephan Saalfeld\n", " *\n", " */\n", "\n", "// it will work as well on a normal ArrayList\n", "ArrayList list = new ArrayList()\n", "\n", "// put values 0 to 10 into the ArrayList\n", "for (int i = 0; i <= 10; ++i) {\n", " list.add(new FloatType(i))\n", "}\n", "\n", "// create two empty variables\n", "min = new FloatType()\n", "max = new FloatType()\n", " \n", "// compute min and max of the ArrayList\n", "construct01.computeMinMax(list, min, max)\n", "\n", "[\"minimum Value (arraylist)\": min.getRealDouble(),\n", "\"maximum Value (arraylist)\": max.getRealDouble()]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Example 3c" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we want to compute the location of the minimal and maximal pixel value, an [Iterator](https://javadoc.scijava.org/ImgLib2/net/imglib2/Iterator.html) will not be sufficient as we need location information. Instead, the location search will demand an [IterableInterval](https://javadoc.scijava.org/ImgLib2/net/imglib2/IterableInterval.html) as input data which can create [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html)s. Apart from that, the algorithm looks quite similar. Note that we do not use a **LocalizingCursor** but only a [Cursor](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html); the location happens only when a new maximal or minimal value has been found while iterating the data." ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "script1546098271180$1@604f584e" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import net.imglib2.type.Type\n", "import net.imglib2.IterableInterval\n", "import net.imglib2.Point\n", "import net.imglib2.Cursor\n", " \n", "/**\n", " * Perform a generic min/max search.\n", " *\n", " * @author Stephan Preibisch & Stephan Saalfeld\n", " *\n", " */\n", "construct02 = new Object() {\n", " /**\n", " * Compute the location of the minimal and maximal intensity for any IterableInterval,\n", " * like an {@link Img}.\n", " *\n", " * The functionality we need is to iterate and retrieve the location. Therefore we need a\n", " * Cursor that can localize itself.\n", " * Note that we do not use a LocalizingCursor as localization just happens from time to time.\n", " *\n", " * @param input - the input that has to just be {@link IterableInterval}\n", " * @param minLocation - the location for the minimal value\n", " * @param maxLocation - the location of the maximal value\n", " */\n", " public & Type> void computeMinMaxLocation(IterableInterval input,\n", " Point minLocation, Point maxLocation)\n", " {\n", " // create a cursor for the image (the order does not matter)\n", " final Cursor cursor = input.cursor()\n", " \n", " // initialize min and max with the first image value\n", " T type = cursor.next()\n", " T min = type.copy()\n", " T max = type.copy()\n", " \n", " // loop over the rest of the data and determine min and max value\n", " while (cursor.hasNext()) {\n", " // we need this type more than once\n", " type = cursor.next()\n", " \n", " if (type.compareTo(min) < 0) {\n", " min.set(type)\n", " minLocation.setPosition(cursor)\n", " }\n", " \n", " if (type.compareTo(max) > 0) {\n", " max.set(type)\n", " maxLocation.setPosition(cursor)\n", " }\n", " }\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "2e6f0079-3e9f-448e-adfd-244173fd31d5", "version_major": 2, "version_minor": 0 }, "method": "display_data" }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import io.scif.img.ImgOpener\n", "import net.imglib2.type.numeric.real.FloatType\n", "import net.imglib2.Point\n", "\n", "// open with ImgOpener\n", "img = new ImgOpener().openImgs(\"https://samples.fiji.sc/tutorials/DrosophilaWing.tif\", new FloatType()).get(0)\n", " \n", " // create two location objects\n", " locationMin = new Point(img.numDimensions())\n", " locationMax = new Point(img.numDimensions())\n", " \n", " // compute location of min and max\n", " construct02.computeMinMaxLocation(img, locationMin, locationMax)\n", " \n", "[\"location of minimum Value (img)\": locationMin.toString(),\n", "\"location of maximum Value (img)\": locationMax.toString()]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Computing average" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In a very similar way, one can compute the average intensity for image data. Note that we restrict the [Type](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/Type.html) of data to [RealType](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/numeric/RealType.html). In theory, we could use [NumericType](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/numeric/NumericType.html) as it offers the possibility to add up values. However, we cannot ensure that [NumericType](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/numeric/NumericType.html) provided is capable of adding up millions of pixels without overflow. And even if we would ask for a second [NumericType](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/numeric/NumericType.html) that is capable of adding values up, it might still have numerical instabilities. Note that actually every Java native type has those instabilities. Therefore we use the [RealSum](https://javadoc.scijava.org/ImgLib2/net/imglib2/util/RealSum.html) class that offers correct addition of even very large amounts of pixels. As this implementation is only available for double values, we restrict the method here to [RealType](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/numeric/RealType.html)." ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "script1546098272287$1@7cd5faa0" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import net.imglib2.type.numeric.RealType\n", "import net.imglib2.util.RealSum\n", " \n", "/**\n", " * Perform a generic computation of average intensity\n", " *\n", " * @author Stephan Preibisch & Stephan Saalfeld\n", " *\n", " */\n", "construct03 = new Object() {\n", " /**\n", " * Compute the average intensity for an {@link Iterable}.\n", " *\n", " * @param input - the input data\n", " * @return - the average as double\n", " */\n", " public > double computeAverage(Iterable input) {\n", " // Count all values using the RealSum class.\n", " // It prevents numerical instabilities when adding up millions of pixels\n", " realSum = new RealSum()\n", " count = 0\n", " \n", " for (T type : input) {\n", " realSum.add(type.getRealDouble())\n", " ++count\n", " }\n", " \n", " return realSum.getSum() / count\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "average Value: 233.45769361413045" ] }, "execution_count": 34, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import io.scif.img.ImgOpener\n", "import net.imglib2.type.numeric.real.FloatType\n", "\n", "// open with ImgOpener\n", "img = new ImgOpener().openImgs(\"https://samples.fiji.sc/tutorials/DrosophilaWing.tif\", new FloatType()).get(0)\n", " \n", "// compute average of the image\n", "avg = construct03.computeAverage(img)\n", "\"average Value: \" + avg" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Example 04: Specialized iterables" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Example 4 will focus on how to work with specialized **iterables**. They are especially useful when performing operations in the local neighborhood of many pixels - like finding local minima/maxima, texture analysis, convolution with non-separable, non-linear filters and many more. One elegant solution is to write a specialized [**Iterable**](https://javadoc.scijava.org/Java/java/lang/Iterable.html) that will iterate all pixels in the local neighborhood. We implemented two examples:\n", "\n", "* A [**HyperSphere**](https://javadoc.scijava.org/ImgLib2/net/imglib2/algorithm/region/hypersphere/HyperSphere.html) that will iterate an n-dimensional sphere with a given radius at a defined location..\n", "* A [**LocalNeighborhood**](https://javadoc.scijava.org/ImgLib2/net/imglib2/algorithm/neighborhood/Neighborhood.html) that will iterate n-dimensionally all pixels adjacent to a certain location, but skip the central pixel (this corresponds to neighbors in 1d, an 8-neighborhood in 2d, a 26-neighborhood in 3d, and so on ...)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Example 4a - Drawing a sphere full of spheres" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the first sample we simply draw a sphere full of little spheres. We therefore create a large [**HyperSphere**](https://javadoc.scijava.org/ImgLib2/net/imglib2/algorithm/region/hypersphere/HyperSphere.html) in the center of a [**RandomAccessibleInterval**](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccessibleInterval.html). Note that the **HyperSphere** only needs a [**RandomAccessible**](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccessible.html), we need the additional [**Interval**](https://javadoc.scijava.org/ImgLib2/net/imglib2/util/Intervals.html) simply to compute the center and the radius of the large sphere. When iterating over all pixels of this large sphere, we create a small [**HyperSphere**](https://javadoc.scijava.org/ImgLib2/net/imglib2/algorithm/region/hypersphere/HyperSphere.html) at every n'th pixel and fill them with a random intensity.\n", "\n", "This example illustrates the use of specialized **Iterables**, and emphasizes the fact that they can be stacked on the underlying [**RandomAccessible**](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccessible.html) using the location of one as the center of a new one. Note that we always create new instances of [**HyperSphere**](https://javadoc.scijava.org/ImgLib2/net/imglib2/algorithm/region/hypersphere/HyperSphere.html) . The code reads very nicely but might not offer the best performance. We therefore added *update* methods to the [**HyperSphere**](https://javadoc.scijava.org/ImgLib2/net/imglib2/algorithm/region/hypersphere/HyperSphere.html) and its **Cursor** that could be used instead.\n", "\n", "Another interesting aspect of this example is the use of the [**ImagePlusImgFactory**](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/imageplus/ImagePlusImgFactory.html), which is the compatibility container for ImageJ. If the required dimensionality and [**Type**](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/Type.html) is available in ImageJ, it will internally create an ImagePlus and work on it directly. In this case, one can request the ImagePlus and show it directly. It will, however, fail if [**Type**](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/Type.html) and dimensionality are not supported by ImageJ and throw a [**ImgLibException**](https://javadoc.scijava.org/ImgLib2/net/imglib2/exception/ImgLibException.html)." ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "script1546098273677$1@44265606" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import java.util.Random\n", "import net.imglib2.Point\n", "import net.imglib2.RandomAccessibleInterval\n", "import net.imglib2.algorithm.region.hypersphere.HyperSphere\n", "import net.imglib2.algorithm.region.hypersphere.HyperSphereCursor\n", "import net.imglib2.exception.ImgLibException\n", "import net.imglib2.type.numeric.RealType\n", "import net.imglib2.util.Util\n", " \n", "/**\n", " * Example 4a - Drawing a sphere full of spheres\n", " *\n", " * @author Stephan Preibisch & Stephan Saalfeld\n", " *\n", " */\n", "construct01 = new Object() {\n", " \n", " /**\n", " * Draws a sphere that contains lots of small spheres into the center of the\n", " * interval.\n", " *\n", " * @param value type\n", " * @param randomAccessible - the image data to write to\n", " * @param minValue - the minimal intensity of one of the small spheres\n", " * @param maxValue - the maximal intensity of one of the small spheres\n", " */\n", " public > void drawSpheres(final RandomAccessibleInterval randomAccessible, final double\n", " minValue, final double maxValue)\n", " {\n", " // the number of dimensions\n", " int numDimensions = randomAccessible.numDimensions()\n", " \n", " // define the center and radius\n", " Point center = new Point(randomAccessible.numDimensions())\n", " int minSize = randomAccessible.dimension(0)\n", " \n", " for (int d = 0; d < numDimensions; ++d) {\n", " int size = randomAccessible.dimension(d)\n", " int half = size / 2\n", " center.setPosition(half, d)\n", " minSize = Math.min(minSize, size)\n", " }\n", " \n", " // define the maximal radius of the small spheres\n", " final int maxRadius = 5\n", " \n", " // compute the radius of the large sphere so that we do not draw\n", " // outside of the defined interval\n", " final long radiusLargeSphere = minSize / 2 - maxRadius - 1\n", " \n", " // instantiate a random number generator\n", " final Random rnd = new Random()\n", " \n", " // define a hypersphere (n-dimensional sphere)\n", " HyperSphere hyperSphere = new HyperSphere(randomAccessible, center, radiusLargeSphere)\n", " \n", " // create a cursor on the hypersphere\n", " HyperSphereCursor cursor = hyperSphere.cursor()\n", " while (cursor.hasNext()) {\n", " cursor.fwd()\n", " \n", " // the random radius of the current small hypersphere\n", " final int radius = rnd.nextInt(maxRadius) + 1\n", " \n", " // instantiate a small hypersphere at the location of the current pixel in the large hypersphere\n", " HyperSphere smallSphere = new HyperSphere(randomAccessible, cursor, radius)\n", " \n", " // define the random intensity for this small sphere\n", " final double randomValue = rnd.nextDouble()\n", " \n", " // take only every 4^dimension'th pixel by chance so that it is not too crowded\n", " if (Math.round(randomValue * 100) % Util.pow(4, numDimensions) == 0) {\n", " // scale to right range\n", " randomValue = rnd.nextDouble() * (maxValue - minValue) + minValue\n", " // set the value to all pixels in the small sphere if the intensity is brighter than the existing one\n", " for (final T value : smallSphere) {\n", " value.setReal(Math.max(randomValue, value.getRealDouble()))\n", " }\n", " }\n", " }\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import net.imglib2.type.numeric.integer.UnsignedByteType\n", "import net.imglib2.img.Img\n", "import net.imglib2.img.ImgFactory\n", "import net.imglib2.img.array.ArrayImgFactory\n", "import net.imglib2.view.Views\n", "\n", "long[] dims = [256, 256, 256]\n", "ImgFactory imgFactory = new ArrayImgFactory<>( new UnsignedByteType() )\n", "Img img = imgFactory.create( dims )\n", "\n", "// draw a small sphere for every pixel of a larger sphere\n", "int minv = 0\n", "int maxv = 255\n", "construct01.drawSpheres(img, minv, maxv)\n", "\n", "// Display the middle slice\n", "Views.hyperSlice(img, 2, 128)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Example 4b - Finding and displaying local minima" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this example we want to find all local minima in an image and display them as small spheres. To not capture too much of the noise in the image data, we first perform an in-place Gaussian smoothing with a sigma of 1, i.e. the data will be overwritten with the result. A complete documentation of the gauss package for ImgLib2 can be found [here](https://imagej.net/Gauss_Package_ImgLib2).\n", "\n", "We display the results using a binary image. *Note that the [**BitType**](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/logic/BitType.html) only requires one bit per pixel and therefore is very memory efficient.*\n", "\n", "The generic method for minima detection has some more interesting properties. The type of the source image data actually does not require to be of [**Type**](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/Type.html), it simply needs something that is comparable. The [**LocalNeighborhood**](https://javadoc.scijava.org/ImgLib2/net/imglib2/algorithm/neighborhood/Neighborhood.html) will iterate n-dimensionally all pixels adjacent to a certain location, but skip the central pixel (this corresponds to neighbors in 1d, an 8-neighborhood in 2d, a 26-neighborhood in 3d, and so on ...). This allows to efficiently detect if a pixel is a local minima or maxima. Note that the [**Cursor**](https://javadoc.scijava.org/ImgLib2/net/imglib2/Cursor.html) that performs the iteration can have special implementations for specific dimensionalities to speed up the iteration. See below the example for a specialized three-dimensional iteration:\n", "\n", "Please note as well that if one would increase the radius of the [**RectangleShape**](https://javadoc.scijava.org/ImgLib2/net/imglib2/algorithm/neighborhood/RectangleShape.html) to more than 1 (without at the same time changing the [**View**](https://javadoc.scijava.org/ImgLib2/net/imglib2/View.html) on source that creates an inset border of exactly this one pixel), this example would fail as we would try to write image data outside of the defined boundary. [**OutOfBoundsStrategies**](https://javadoc.scijava.org/ImgLib2/net/imglib2/view/Views.html) which define how to handle such cases is discussed in example 5." ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "script1546098277418$1@7e453c12" ] }, "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import net.imglib2.Cursor\n", "import net.imglib2.Interval\n", "import net.imglib2.RandomAccessibleInterval\n", "import net.imglib2.algorithm.gauss.Gauss\n", "import net.imglib2.algorithm.neighborhood.Neighborhood\n", "import net.imglib2.algorithm.neighborhood.RectangleShape\n", "import net.imglib2.algorithm.region.hypersphere.HyperSphere\n", "import net.imglib2.img.Img\n", "import net.imglib2.img.ImgFactory\n", "import net.imglib2.img.array.ArrayImgFactory\n", "import net.imglib2.type.NativeType\n", "import net.imglib2.type.logic.BitType\n", "import net.imglib2.type.numeric.RealType\n", "import net.imglib2.util.Intervals\n", "import net.imglib2.view.Views\n", " \n", "construct02 = new Object() {\n", " /**\n", " * Checks all pixels in the image if they are a local minima and draws a circle into the output if they are\n", " *\n", " * @param value type\n", " * @param \n", " * @param source - the image data to work on\n", " * @param imageFactory - the factory for the output img\n", " * @param outputType - the output type\n", " * @return - an Img with circles on locations of a local minimum\n", " */\n", " public static , U extends RealType> Img findAndDisplayLocalMinima(\n", " RandomAccessibleInterval source, ImgFactory imageFactory) {\n", " // Create a new image for the output\n", " Img output = imageFactory.create(source)\n", " \n", " // define an interval that is one pixel smaller on each side in each dimension,\n", " // so that the search in the 8-neighborhood (3x3x3...x3) never goes outside\n", " // of the defined interval\n", " Interval interval = Intervals.expand(source, -1)\n", " \n", " // create a view on the source with this interval\n", " source = Views.interval(source, interval)\n", " \n", " // create a Cursor that iterates over the source and checks in an 8-neighborhood if it is a minima\n", " final Cursor center = Views.iterable(source).cursor()\n", " \n", " // instantiate a RectangleShape to access rectangular local neighborhoods\n", " // of radius 1 (that is 3x3x...x3 neighborhoods), skipping the center pixel\n", " // (this corresponds to an 8-neighborhood in 2d or 26-neighborhood in 3d, ...)\n", " final RectangleShape shape = new RectangleShape(1, true)\n", " \n", " // iterate over the set of neighborhoods in the image\n", " for (final Neighborhood localNeighborhood : shape.neighborhoods(source)) {\n", " // what is the value that we investigate?\n", " // (the center cursor runs over the image in the same iteration order as neighborhood)\n", " final T centerValue = center.next()\n", " \n", " // keep this boolean true as long as no other value in the local neighborhood\n", " // is larger or equal\n", " boolean isMinimum = true\n", " \n", " // check if all pixels in the local neighborhood that are smaller\n", " for (final T value : localNeighborhood) {\n", " // test if the center is smaller than the current pixel value\n", " if (centerValue.compareTo(value) >= 0) {\n", " isMinimum = false\n", " break\n", " }\n", " }\n", " \n", " if (isMinimum) {\n", " // draw a sphere of radius one in the new image\n", " HyperSphere hyperSphere = new HyperSphere(output, center, 1)\n", " \n", " // set every value inside the sphere to 1\n", " for (U value : hyperSphere) {\n", " value.setOne()\n", " }\n", " }\n", " }\n", " \n", " return output\n", " }\n", "}" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ] }, "execution_count": 38, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import io.scif.img.ImgOpener\n", "import net.imglib2.img.Img\n", "import net.imglib2.algorithm.gauss.Gauss\n", "import net.imglib2.type.logic.BitType\n", "import net.imglib2.img.array.ArrayImgFactory\n", "import net.imglib2.type.numeric.real.FloatType\n", "\n", "img = new ImgOpener().openImgs(\"https://samples.fiji.sc/tutorials/DrosophilaWing.tif\").get(0)\n", "\n", "// first we do a small in-place gaussian smoothing with a sigma of 1.0\n", "double[] sigmas = [1.0, 1.0]\n", "Gauss.inDoubleInPlace(sigmas, img)\n", "\n", "// find local minima and paint them into another image as spheres\n", "featureMap = construct02.findAndDisplayLocalMinima(img, new ArrayImgFactory<>( new BitType() ) )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Example 05: Out of bounds" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Many algorithms, like convolutions, require to access pixels outside of an [**`Interval`**](https://javadoc.scijava.org/ImgLib2/net/imglib2/Interval.html), i.e. also pixels outside of an image. In ImgLib2 this is handled using [**`Views`**](https://javadoc.scijava.org/ImgLib2/net/imglib2/view/Views.html), which convert a [**`RandomAccessibleInterval`**](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccessibleInterval.html) into an infinite [**`RandomAccessible`**](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccessible.html) using an [**`OutOfBoundsStrategy`**](https://javadoc.scijava.org/ImgLib2/net/imglib2/view/Views.html). Those infinite [**`RandomAccessibles`**](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccessible.html) are able to return pixel values at any arbitrary location.\n", "\n", "*Important: One should never access pixels outside of the defined [**`Interval`**](https://javadoc.scijava.org/ImgLib2/net/imglib2/Interval.html) as it will in most cases result in unexpected behavior, depending on the kind of underlying [**`RandomAccessible`**]((https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccessible.html). If it is for example an [**`Img`**](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/Img.html), it will return wrong values or throw an exception.*\n", "\n", "Which [**`OutOfBoundsStrategies`**](https://javadoc.scijava.org/ImgLib2/net/imglib2/view/Views.html) to use depends on the task you want to perform. For convolutions, we suggest using the mirror strategy as it introduces the least artifacts. When working on Fourier images, the periodic strategy applies best as it correctly mimics its spatial properties. Random Value strategies might be useful to avoid accidental correlations, and constant value strategies are the most performant and might work well for simple operations or to avoid exceptions when accidental writing or reading outside of the Interval occurs." ] }, { "cell_type": "code", "execution_count": 39, "metadata": { "scrolled": false }, "outputs": [ { "data": { "text/html": [ "
EXTEND_VALUE_0EXTEND_VALUE_128EXTEND_RANDOM_256EXTEND_MIRROR_SINGLEEXTEND_MIRROR_DOUBLEEXTEND_PERIODICEXTEND_RANDOM_CUSTOM
" ] }, "execution_count": 39, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import io.scif.img.ImgOpener\n", "import net.imglib2.RandomAccessible\n", "import net.imglib2.img.Img\n", "import net.imglib2.type.numeric.real.FloatType\n", "import net.imglib2.view.Views\n", "import net.imglib2.FinalInterval\n", "import net.imglib2.outofbounds.OutOfBoundsConstantValueFactory\n", "import net.imglib2.view.ExtendedRandomAccessibleInterval\n", "\n", "enum ExtendType {\n", " EXTEND_VALUE_0,\n", " EXTEND_VALUE_128,\n", " EXTEND_RANDOM_256,\n", " EXTEND_MIRROR_SINGLE,\n", " EXTEND_MIRROR_DOUBLE,\n", " EXTEND_PERIODIC,\n", " EXTEND_RANDOM_CUSTOM\n", "}\n", "\n", "// open with ImgOpener as a FloatType\n", "image = new ImgOpener().openImgs(\"https://samples.fiji.sc/tutorials/DrosophilaWingSmall.tif\", new FloatType()).get(0)\n", "\n", "// in order to visualize them, we have to define a new interval\n", "// on them which can be displayed\n", "min = new long[image.numDimensions()]\n", "max = new long[image.numDimensions()]\n", " \n", "for ( int d = 0; d < image.numDimensions(); ++d ) {\n", " // we add/subtract another 30 pixels here to illustrate\n", " // that it is really infinite and does not only work once\n", " min[d] = -image.dimension( d ) - 90\n", " max[d] = image.dimension( d ) * 2 - 1 + 90\n", "}\n", "\n", "// define the Interval on the infinite random accessibles\n", "interval = new FinalInterval(min, max)\n", "\n", "// change this variable to switch for different kind of out-of-bounds display\n", "imgs = [:]\n", "for (type in [ExtendType.EXTEND_VALUE_0,\n", " ExtendType.EXTEND_VALUE_128,\n", " ExtendType.EXTEND_RANDOM_256,\n", " ExtendType.EXTEND_MIRROR_SINGLE,\n", " ExtendType.EXTEND_MIRROR_DOUBLE,\n", " ExtendType.EXTEND_PERIODIC,\n", " ExtendType.EXTEND_RANDOM_CUSTOM])\n", "{\n", " switch (type) {\n", " case ExtendType.EXTEND_VALUE_0:\n", " // create an infinite view where all values outside of the Interval are 0\n", " infinite = Views.extendValue(image, new FloatType(0))\n", " s = Views.interval(infinite, interval)\n", " break\n", " case ExtendType.EXTEND_VALUE_128:\n", " // create an infinite view where all values outside of the Interval are 128\n", " infinite = Views.extendValue(image, new FloatType(128))\n", " s = Views.interval(infinite, interval)\n", " break\n", " case ExtendType.EXTEND_RANDOM_256:\n", " // create an infinite view where all outside values are random in a range of 0-255\n", " infinite = Views.extendRandom(image, 0, 255);\n", " s = Views.interval(infinite, interval)\n", " break\n", " case ExtendType.EXTEND_MIRROR_SINGLE:\n", " // create an infinite view where all values outside of the Interval are\n", " // the mirrored content, the mirror is the last pixel\n", " infinite = Views.extendMirrorSingle(image);\n", " s = Views.interval(infinite, interval)\n", " break\n", " case ExtendType.EXTEND_MIRROR_DOUBLE:\n", " // create an infinite view where all values outside of the Interval are\n", " // the mirrored content, the mirror is BEHIND the last pixel,\n", " // i.e. the first and last pixel are always duplicated\n", " infinite = Views.extendMirrorDouble(image);\n", " s = Views.interval(infinite, interval)\n", " break\n", " case ExtendType.EXTEND_PERIODIC:\n", " // all values outside of the Interval periodically repeat the image content\n", " // (like the Fourier space assumes)\n", " infinite = Views.extendPeriodic(image);\n", " s = Views.interval(infinite, interval)\n", " break\n", " case ExtendType.EXTEND_RANDOM_CUSTOM:\n", " // if you implemented your own strategy that you want to instantiate, it will look like this\n", " infinite = new ExtendedRandomAccessibleInterval>(\n", " image, new OutOfBoundsConstantValueFactory>(new FloatType(196)));\n", " s = Views.interval(infinite, interval)\n", " break\n", " default:\n", " infinite = Views.extendValue(image, new FloatType(0))\n", " s = Views.interval(infinite, interval)\n", " }\n", " imgs.put(type.toString(), s)\n", "}\n", "ij.notebook().display([imgs])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Example 06: Convolution" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Example 06a: Gaussian convolution" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Note:** ImgLib2 contains a growing number of built-in standard algorithms. In examples 06a, 06b, and 06c, we will show some of those, illustrate how to use them and give some examples of what they might be used for.\n", "\n", "Typically algorithms provide static methods for simple calling, but they also have classes which you can instantiate yourself to have more options.\n", "\n", "*Important: Algorithms do not allow to work on a different dimensionality than the input data. You can achieve that by selecting hyperslices using [Views](https://javadoc.scijava.org/ImgLib2/net/imglib2/view/Views.html) (see Example 6a - variation 4). In this way, you can for example apply two-dimensional gaussians to each frame of a movie independently.*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The Gaussian convolution has its own [wiki](https://imagej.net/Gauss_Package_ImgLib2) page. You can apply the Gaussian convolution with different sigmas in any dimension. It will work on any kind of [**`RandomAccessibleInterval`**](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccessibleInterval.html). Below we show examples of a simple gaussian convolution (variation 1), convolution using a different [**`OutOfBoundsStrategy`**](https://javadoc.scijava.org/ImgLib2/net/imglib2/view/Views.html) (variation 2), convolution of a part of an [**`Interval`**](https://javadoc.scijava.org/ImgLib2/net/imglib2/Interval.html) (variation 3), and convolution of in a lower dimensionality than the image data (variation 4)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Example 6a - Gaussian convolution (variation 1 - simple)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here, we simply apply a Gaussian convolution with a sigma of 8. Note that it could be applied in-place as well when calling *Gauss.inFloatInPlace(...)*. The Gaussian convolution uses by default the **`OutOfBoundsMirrorStrategy`**." ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ] }, "execution_count": 40, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import io.scif.img.ImgOpener\n", "import net.imglib2.type.numeric.real.FloatType\n", "import net.imglib2.algorithm.gauss.Gauss\n", "import net.imglib2.img.Img\n", "\n", "// open with ImgOpener as a FloatType\n", "Img image = new ImgOpener().openImgs(\"https://samples.fiji.sc/tutorials/DrosophilaWing.tif\", new\n", " FloatType()).get(0)\n", "\n", "// perform gaussian convolution with float precision\n", "sigma = new double[image.numDimensions()]\n", "\n", "for (int d = 0; d < image.numDimensions(); ++d) {\n", " sigma[d] = 8.0\n", "}\n", "\n", "output = Gauss.toFloat(sigma, image)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Example 6a - Gaussian convolution (variation 2 - different OutOfBoundsStrategy)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here we use an **OutOfBoundsStrategyConstantValue** instead. It results in continuously darker borders as the zero-values from outside of the image are used in the convolution. Note that the computation is done in-place here. However, we still need to provide an [ImgFactory](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/ImgFactory.html) as the Gaussian convolution needs to create temporary image(s) - except for the one-dimensional case." ] }, { "cell_type": "code", "execution_count": 41, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ] }, "execution_count": 41, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import io.scif.img.ImgOpener\n", "import net.imglib2.type.numeric.real.FloatType\n", "import net.imglib2.view.Views\n", "import net.imglib2.img.array.ArrayImgFactory\n", "import net.imglib2.algorithm.gauss3.Gauss3\n", "\n", "// open with ImgOpener as a FloatType\n", "image = new ImgOpener().openImgs(\"https://samples.fiji.sc/tutorials/DrosophilaWing.tif\", new FloatType()).get(0)\n", "\n", "// first extend the image to infinity, zeropad\n", "infiniteImg = Views.extendValue(image, new FloatType())\n", "\n", "// now we convolve the whole image manually in-place\n", "// note that it is basically the same as the call above, just called in a more generic way\n", "//\n", "// sigma .. the sigma\n", "// infiniteImg ... the RandomAccessible that is the source for the convolution\n", "// image ... defines the RandomAccessibleInterval that is the target of the convolution\n", "Gauss3.gauss(sigma, infiniteImg, image)\n", "\n", "image" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Example 6a - Gaussian convolution (variation 3 - only part of an Interval)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here we only convolve part of an [**Interval**](https://javadoc.scijava.org/ImgLib2/net/imglib2/Interval.html), or in this case part of the [**Img**](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/Img.html). Note that for convolution, it will actually use the real image values outside of the defined [**Interval**](https://javadoc.scijava.org/ImgLib2/net/imglib2/Interval.html). The **OutOfBoundsStrategy** is only necessary if the kernel is that large so that it will actually grep image values outside of the underlying [**Img**](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/Img.html).\n", "\n", "Note: if you wanted, you could force it to use an **OutOfBoundsStrategy** directly outside of the [**Interval**](https://javadoc.scijava.org/ImgLib2/net/imglib2/Interval.html). For that you would have to create an [**RandomAccessibleInterval**](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccessibleInterval.html) on the [**Img**](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/Img.html), extend it by an **OutOfBoundsStrategy** and give this as input to the Gaussian convolution." ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ] }, "execution_count": 42, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import io.scif.img.ImgOpener\n", "import net.imglib2.type.numeric.real.FloatType\n", "import net.imglib2.view.Views\n", "import net.imglib2.algorithm.gauss3.Gauss3\n", "import net.imglib2.util.Intervals\n", "\n", "// open with ImgOpener as a FloatType\n", "image = new ImgOpener().openImgs(\"https://samples.fiji.sc/tutorials/DrosophilaWing.tif\", new FloatType()).get(0)\n", "\n", "// we need to extend it nevertheless as the algorithm needs more pixels from around\n", "// the convolved area and we are not sure how much exactly (although we could compute\n", "// it with some effort from the sigma).\n", "// Here we let the Views framework take care of the details. The Gauss convolution\n", "// knows which area of the source image is required, and if the extension is not needed,\n", "// it will operate on the original image with no runtime overhead.\n", "infiniteImg = Views.extendMirrorSingle(image)\n", " \n", "// define the area of the image which we want to compute\n", "interval = Intervals.createMinMax(100, 30, 500, 250)\n", "region = Views.interval(image, interval)\n", "\n", " // call the gauss, we convolve only a region and write it back to the exact same coordinates\n", " Gauss3.gauss(sigma, infiniteImg, region)\n", "\n", "image" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Example 6a - Gaussian convolution (variation 4 - with a lower dimensionality)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This example shows how to apply an algorithm to a lower dimensionality as the image data you are working on. Therefore we use [View](https://javadoc.scijava.org/ImgLib2/net/imglib2/View.html)s to create HyperSlices which have n-1 dimensions. We simply apply the algorithm in-place on those [View](https://javadoc.scijava.org/ImgLib2/net/imglib2/View.html)s which will automatically update the image data in the higher-dimensional data.\n", "\n", "Specifically, we apply 1-dimensional Gaussian convolution in 30-pixel wide stripes using a sigma of 16. Note that whenever you request a HyperSlice for a certain dimension, you will get back a [View](https://javadoc.scijava.org/ImgLib2/net/imglib2/View.html) that contains all dimensions but this one." ] }, { "cell_type": "code", "execution_count": 43, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ] }, "execution_count": 43, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import io.scif.img.ImgOpener\n", "import net.imglib2.type.numeric.real.FloatType\n", "import net.imglib2.view.Views\n", "import net.imglib2.algorithm.gauss3.Gauss3\n", "import net.imglib2.util.Intervals\n", "\n", "// open with ImgOpener as a FloatType\n", "image = new ImgOpener().openImgs(\"https://samples.fiji.sc/tutorials/DrosophilaWing.tif\", new FloatType()).get(0)\n", "\n", "double[] sigma = new double[image.numDimensions() - 1]\n", "\n", "for (int d = 0; d < sigma.length; ++d) {\n", " sigma[d] = 16.0\n", "}\n", " \n", "// iterate over all dimensions, take always a hyperslice\n", "for (int dim = 0; dim < image.numDimensions(); ++dim) // iterate over all possible hyperslices\n", "{\n", " for (long pos = 0; pos < image.dimension(dim); ++pos) // convolve a subset of the 1-dimensional views\n", " {\n", " long s = pos / 30\n", " if (s % 2 == 1) {\n", " // get the n-1 dimensional \"slice\"\n", " view = Views.hyperSlice(image, dim, pos)\n", " \n", " // compute the gauss in-place on the view\n", " Gauss3.gauss(sigma, Views.extendMirrorSingle(view), view)\n", " }\n", " }\n", "}\n", "\n", "image" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Example 6b - Convolution in Fourier space" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In image processing, it is sometimes necessary to convolve images with non-separable kernels. This can be efficiently done in Fourier space exploiting the convolution theorem. It states that a convolution in real-space corresponds to a multiplication in Fourier-space, and vice versa. Note that the computation time for such a convolution is independent of the size and shape of the kernel.\n", "\n", "*Note: that it is useful to normalize the kernel prior to Fourier convolution so that the sum of all pixels is one. Otherwise, the resulting intensities will be increased.*\n", "\n", "Some utility methods can be found below." ] }, { "cell_type": "code", "execution_count": 44, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "script1546098285046$1@1dbc649f" ] }, "execution_count": 44, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import net.imglib2.type.numeric.RealType\n", "import net.imglib2.type.numeric.real.FloatType\n", "import net.imglib2.util.RealSum\n", "\n", "utility = new Object() {\n", " /**\n", " * Computes the sum of all pixels in an iterable using RealSum\n", " *\n", " * @param value type\n", " * @param iterable - the image data\n", " * @return - the sum of values\n", " */\n", " public > double sumImage(final Iterable iterable) {\n", " final RealSum sum = new RealSum()\n", " \n", " for (final T type : iterable) {\n", " sum.add(type.getRealDouble())\n", " }\n", " \n", " return sum.getSum()\n", " }\n", " \n", " /**\n", " * Norms all image values so that their sum is 1\n", " *\n", " * @param iterable - the image data\n", " */\n", " public void norm(final Iterable iterable) {\n", " final double sum = sumImage(iterable)\n", " \n", " for (final FloatType type : iterable) {\n", " type.setReal(type.get() / sum)\n", " }\n", " }\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The implementation is as follows..." ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "method": "display_data" }, "metadata": {}, "output_type": "display_data" }, { "ename": "java.lang.NullPointerException", "evalue": "java.lang.NullPointerException", "output_type": "error", "traceback": [ "\u001b[1;31mjava.lang.NullPointerException\u001b[0;0m" ] } ], "source": [ "// NB: The imglib2-algorithm-gpl component is licensed under the GPL;\n", "// therefore, this code snippet is also licensed under the GPL!\n", "%classpath add mvn net.imglib2 imglib2-algorithm-gpl 0.2.3\n", "// NB: Unfortunately, due to a bug in BeakerX, the above throws NullPointerException. :-(\n", "\n", "import io.scif.img.ImgOpener\n", "import net.imglib2.img.Img\n", "import net.imglib2.type.numeric.real.FloatType\n", "//import net.imglib2.algorithm.fft2.FFTConvolution\n", "import net.imglib2.view.Views\n", "import net.imglib2.RandomAccessibleInterval\n", "\n", "// open image and kernel with ImgOpener as a FloatType\n", "image = new ImgOpener().openImgs(\"https://samples.fiji.sc/tutorials/DrosophilaWing.tif\", new FloatType()).get(0)\n", "kernel = new ImgOpener().openImgs(\"https://samples.fiji.sc/tutorials/kernelRing.tif\",new FloatType()).get(0)\n", "\n", "// normalize the kernel, otherwise we add energy to the image\n", "utility.norm(kernel)\n", "\n", "// compute & show fourier convolution (in-place)\n", "//fft2 = new FFTConvolution(image, kernel)\n", "//fft2.convolve()\n", "\n", "//image" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Example 6c - Complex numbers and Fourier transforms" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this example we show how to work with complex numbers and Fourier transforms. We show how to determine the location of a template in an image exploiting the *Fourier Shift Theorem*. We therefore compute the Fast Fourier Transform of a template, invert it and convolve it in Fourier space with the original image.\n", "\n", "Computing an FFT is straight forward. It does not offer a static method because the instance of the FFT is required to perform an inverse FFT. This is necessary because the input image needs to be extended to a size supported by the 1-d FFT method (edu_mines_jtk.jar). In order to restore the complete input image, remembering those parameters is essential.\n", "\n", "Note that for inverting the kernel we use methods defined for [**`ComplexType`**](https://javadoc.scijava.org/ImgLib2/net/imglib2/type/numeric/ComplexType.html), also the basic math operations add, mul, sub and div are implemented in complex math. The inverse FFT finally takes the instance of the FFT as a parameter from which it takes all required parameters for a correct inversion.\n", "\n", "The final convolution of the inverse template with the image is performed using the FourierConvolution (see example 6b). Note that all possible locations of the template in the image have been tested. The peak in the result image clearly marks the location of the template, while the computation time for the whole operation takes less than a second." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Some utility methods can be found below." ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "script1546098287724$1@62fc326a" ] }, "execution_count": 46, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import net.imglib2.type.numeric.RealType\n", "import net.imglib2.type.numeric.real.FloatType\n", "import net.imglib2.util.RealSum\n", "\n", "utility = new Object() {\n", " /**\n", " * Computes the sum of all pixels in an iterable using RealSum\n", " *\n", " * @param value type\n", " * @param iterable - the image data\n", " * @return - the sum of values\n", " */\n", " public > double sumImage(final Iterable iterable) {\n", " final RealSum sum = new RealSum()\n", " \n", " for (final T type : iterable) {\n", " sum.add(type.getRealDouble())\n", " }\n", " \n", " return sum.getSum()\n", " }\n", " \n", " /**\n", " * Norms all image values so that their sum is 1\n", " *\n", " * @param iterable - the image data\n", " */\n", " public void norm(final Iterable iterable) {\n", " final double sum = sumImage(iterable)\n", " \n", " for (final FloatType type : iterable) {\n", " type.setReal(type.get() / sum)\n", " }\n", " }\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The implementation is as follows..." ] }, { "cell_type": "code", "execution_count": 47, "metadata": { "scrolled": true }, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "", "version_major": 2, "version_minor": 0 }, "method": "display_data" }, "metadata": {}, "output_type": "display_data" }, { "ename": "java.lang.NullPointerException", "evalue": "java.lang.NullPointerException", "output_type": "error", "traceback": [ "\u001b[1;31mjava.lang.NullPointerException\u001b[0;0m" ] } ], "source": [ "// NB: The imglib2-algorithm-gpl component is licensed under the GPL;\n", "// therefore, this code snippet is also licensed under the GPL!\n", "%classpath add mvn net.imglib2 imglib2-algorithm-gpl 0.2.3\n", "// NB: Unfortunately, due to a bug in BeakerX, the above throws NullPointerException. :-(\n", "\n", "import io.scif.img.ImgOpener\n", "import net.imglib2.type.numeric.complex.ComplexFloatType\n", "import net.imglib2.type.numeric.real.FloatType\n", "import net.imglib2.algorithm.fft.FourierConvolution\n", "import net.imglib2.algorithm.fft.FourierTransform\n", "import net.imglib2.algorithm.fft.InverseFourierTransform\n", "\n", "// open image and kernel with ImgOpener as a FloatType\n", "image = new ImgOpener().openImgs(\"https://samples.fiji.sc/tutorials/DrosophilaWing.tif\", new FloatType()).get(0)\n", "template = new ImgOpener().openImgs(\"https://samples.fiji.sc/tutorials/WingTemplate.tif\", new FloatType()).get(0)\n", "\n", "fft = new FourierTransform(template, new ComplexFloatType())\n", "fft.process()\n", "templateFFT = fft.getResult()\n", "\n", "// complex invert the kernel\n", "ComplexFloatType c = new ComplexFloatType()\n", "for (ComplexFloatType t : templateFFT) {\n", " c.set(t)\n", " t.complexConjugate()\n", " c.mul(t)\n", " t.div(c)\n", "}\n", "\n", "// compute inverse fourier transform of the template\n", "ifft = new InverseFourierTransform(templateFFT, fft);\n", "ifft.process()\n", "templateInverse = ifft.getResult()\n", "\n", "// normalize the inverse template\n", "utility.norm(templateInverse)\n", "\n", "// compute fourier convolution of the inverse template and the image\n", "output = FourierConvolution.convolve(image, templateInverse)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The position where the template was located in the image is significantly visible: the brightest spot in the image." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Example 07: Interpolation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Interpolation is a basic operation required in many image processing tasks. In the terminology of ImgLib2 it means to convert a [**`RandomAccessible`**](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccessible.html) into a [**`RealRandomAccessible`**](https://javadoc.scijava.org/ImgLib2/net/imglib2/RealRandomAccessible.html) which is able to create a [**`RealRandomAccess`**](https://javadoc.scijava.org/ImgLib2/net/imglib2/RealRandomAccess.html). It can be positioned at real coordinates instead of only integer coordinates and return a value for each real location. \n", "\n", "Currently, three interpolation schemes are available for ImgLib2:\n", " * Nearest neighbor interpolation (also available for any kind of data that can return a nearest neighbor like sparse datasets)\n", " * Linear interpolation\n", " * Lanczos interpolation\n", "\n", "In the example, we magnify a given real interval in the [**`RealRandomAccessible`**](https://javadoc.scijava.org/ImgLib2/net/imglib2/RealRandomAccessible.html) which is based on the interpolation on an [Img](https://javadoc.scijava.org/ImgLib2/net/imglib2/img/Img.html) and compare the results of all three interpolation methods." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Below is the utility method for a given interval magnification." ] }, { "cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "script1546098289873$1@ad337cd" ] }, "execution_count": 48, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import net.imglib2.type.Type\n", "import net.imglib2.RealRandomAccessible\n", "import net.imglib2.RealInterval\n", "import net.imglib2.img.ImgFactory\n", "import net.imglib2.img.Img\n", "\n", "mag = new Object() {\n", " /**\n", " * Compute a magnified version of a given real interval\n", " *\n", " * @param value type\n", " * @param source - the input data\n", " * @param interval - the real interval on the source that should be\n", " * magnified\n", " * @param factory - the image factory for the output image\n", " * @param magnification - the ratio of magnification\n", " * @return - an Img that contains the magnified image content\n", " */\n", " public > Img magnify(\n", " RealRandomAccessible source,\n", " RealInterval interval,\n", " ImgFactory factory,\n", " double magnification) {\n", " \n", " numDimensions = interval.numDimensions()\n", " \n", " // compute the number of pixels of the output and the size of the real interval\n", " pixelSize = new long[numDimensions]\n", " intervalSize = new double[numDimensions]\n", " \n", " for (int d = 0; d < numDimensions; ++d) {\n", " intervalSize[d] = interval.realMax(d) - interval.realMin(d)\n", " pixelSize[d] = Math.round(intervalSize[d] * magnification) + 1\n", " }\n", " \n", " // create the output image\n", " output = factory.create(pixelSize)\n", " \n", " // cursor to iterate over all pixels\n", " cursor = output.localizingCursor()\n", " \n", " // create a RealRandomAccess on the source (interpolator)\n", " realRandomAccess = source.realRandomAccess()\n", " \n", " // the temporary array to compute the position\n", " tmp = new double[numDimensions]\n", " \n", " // for all pixels of the output image\n", " while (cursor.hasNext()) {\n", " cursor.fwd()\n", " \n", " // compute the appropriate location of the interpolator\n", " for (int d = 0; d < numDimensions; ++d) {\n", " tmp[d] = cursor.getDoublePosition(d) / output.realMax(d) * intervalSize[d] + interval.realMin(d)\n", " }\n", " \n", " // set the position\n", " realRandomAccess.setPosition(tmp)\n", " \n", " // set the new value\n", " cursor.get().set(realRandomAccess.get())\n", " }\n", " \n", " return output\n", " }\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The below code uses three different interpolators to 10x magnify a small area." ] }, { "cell_type": "code", "execution_count": 49, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/html": [ "
NEAREST_NEIGHBOR_INTERPOLATIONNLINEAR_INTERPOLATIONLANCZOS_INTERPOLATION
" ] }, "execution_count": 49, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import io.scif.img.ImgOpener\n", "import net.imglib2.FinalRealInterval\n", "import net.imglib2.type.numeric.real.FloatType\n", "import net.imglib2.interpolation.randomaccess.LanczosInterpolatorFactory\n", "import net.imglib2.interpolation.randomaccess.NLinearInterpolatorFactory\n", "import net.imglib2.interpolation.randomaccess.NearestNeighborInterpolatorFactory\n", "import net.imglib2.img.array.ArrayImgFactory\n", "import net.imglib2.view.Views\n", "\n", "enum InterpolationType {\n", " NEAREST_NEIGHBOR_INTERPOLATION,\n", " NLINEAR_INTERPOLATION,\n", " LANCZOS_INTERPOLATION\n", "}\n", "\n", "// open with ImgOpener as a FloatType\n", "image = new ImgOpener().openImgs(\"https://samples.fiji.sc/tutorials/DrosophilaWing.tif\", new FloatType()).get(0)\n", "\n", "// define the area in the interpolated image\n", "double[] min = [105.12, 40.43]\n", "double[] max = [129.56, 74.933]\n", "\n", "interval = new FinalRealInterval(min, max)\n", "\n", "imgs = [:]\n", "for (interpolant_type in\n", " [InterpolationType.NEAREST_NEIGHBOR_INTERPOLATION,\n", " InterpolationType.NLINEAR_INTERPOLATION,\n", " InterpolationType.LANCZOS_INTERPOLATION])\n", "{\n", " // create a RandomAccessible using the factory and views method\n", " // it is important to extend the image first, the interpolation scheme might\n", " // grep pixels outside of the boundaries even when locations inside are queried\n", " // as they integrate pixel information in a local neighborhood - the size of\n", " // this neighborhood depends on which interpolator is used\n", " switch (interpolant_type) {\n", " case InterpolationType.NEAREST_NEIGHBOR_INTERPOLATION:\n", " // create an InterpolatorFactory RealRandomAccessible using nearst neighbor interpolation\n", " nn_inter_factory = new NearestNeighborInterpolatorFactory()\n", " nn_interpolant = Views.interpolate(Views.extendMirrorSingle(image), nn_inter_factory)\n", " img = mag.magnify(nn_interpolant, interval, new ArrayImgFactory<>( new FloatType() ), 10)\n", " break\n", " case InterpolationType.NLINEAR_INTERPOLATION:\n", " // create an InterpolatorFactory RealRandomAccessible using linear interpolation\n", " linear_inter_factory = new NLinearInterpolatorFactory()\n", " nlinear_interpolant = Views.interpolate(Views.extendMirrorSingle(image), linear_inter_factory)\n", " img = mag.magnify(nlinear_interpolant, interval, new ArrayImgFactory<>( new FloatType() ), 10)\n", " break\n", " case InterpolationType.LANCZOS_INTERPOLATION:\n", " // create an InterpolatorFactory RealRandomAccessible using lanczos interpolation\n", " lanczos_inter_factory = new LanczosInterpolatorFactory()\n", " lanczos_interpolant = Views.interpolate(Views.extendMirrorSingle(image), lanczos_inter_factory)\n", " img = mag.magnify(lanczos_interpolant, interval, new ArrayImgFactory<>( new FloatType() ), 10)\n", " break\n", " default:\n", " nn_inter_factory = new NearestNeighborInterpolatorFactory()\n", " nn_interpolant = Views.interpolate(Views.extendMirrorSingle(image), nn_inter_factory)\n", " img = mag.magnify(nn_interpolant, interval, new ArrayImgFactory<>( new FloatType() ), 10)\n", " }\n", " imgs.put(interpolant_type.toString(), img)\n", "}\n", "ij.notebook().display([imgs])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The nearest neighbor interpolation is the fastest to compute and is the most versatile as it requires no computation, but just a lookout. The result is, however, very pixelated. The linear interpolation produces reasonable results and computes quite fast. The Lanczos interpolation shows the most visually pleasing results, but also introduces slight artifacts in the background." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Example 08: Working with sparse data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "ImgLib2 supports sparsely sampled data, i.e. collections of locations together with their value. Such datasets typically implement the [IterableRealInterval](https://javadoc.scijava.org/ImgLib2/net/imglib2/IterableRealInterval.html) interface, which means they can be iterated and have real-valued locations in n-dimensional space. Currently ImgLib2 supports to store such collections either as list ([RealPointSampleList](https://javadoc.scijava.org/ImgLib2/net/imglib2/RealPointSampleList.html)) or [KDTree](https://javadoc.scijava.org/ImgLib2/net/imglib2/KDTree.html). The [RealPointSampleList](https://javadoc.scijava.org/ImgLib2/net/imglib2/RealPointSampleList.html) can be iterated, whereas the [KDTree](https://javadoc.scijava.org/ImgLib2/net/imglib2/KDTree.html) additionally supports three efficient ways of searching for nearest neighboring points in the n-dimensional space ([NearestNeighborSearch](https://javadoc.scijava.org/ImgLib2/net/imglib2/neighborsearch/NearestNeighborSearch.html), [KNearestNeighborSearch](https://javadoc.scijava.org/ImgLib2/net/imglib2/neighborsearch/KNearestNeighborSearch.html), and [RadiusNeighborSearch](https://javadoc.scijava.org/ImgLib2/net/imglib2/neighborsearch/RadiusNeighborSearch.html)).\n", "\n", "In order to display sparse data ImgLib2 currently supports two interpolation schemes, the [NearestNeighborInterpolation](https://javadoc.scijava.org/ImgLib2/net/imglib2/interpolation/randomaccess/NearestNeighborInterpolator.html) and the [InverseDistanceWeightingInterpolator](https://javadoc.scijava.org/ImgLib2/net/imglib2/interpolation/neighborsearch/InverseDistanceWeightingInterpolator.html). They can compute a value for every location in space by returning either the value of the closest sample or an interpolated, distance-weighted value of the k nearest neighbors to the sampled location. The interpolation scheme therefore converts any [IterableRealInterval](https://javadoc.scijava.org/ImgLib2/net/imglib2/IterableRealInterval.html) into a [RealRandomAccessible](https://javadoc.scijava.org/ImgLib2/net/imglib2/RealRandomAccessible.html) that can be displayed by wrapping it into a [RandomAccessible](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccessible.html) and defining [Interval](https://javadoc.scijava.org/ImgLib2/net/imglib2/Interval.html) using [Views](https://javadoc.scijava.org/ImgLib2/net/imglib2/view/Views.html).\n", "\n", "*This is, however, not only useful for display. Note that you execute on such data any algorithm or method that is implemented for [RealRandomAccessible](https://javadoc.scijava.org/ImgLib2/net/imglib2/RealRandomAccessible.html) or [RandomAccessible](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccessible.html), like Gaussian convolution in the first example. **Note that none of these ever exists in memory, it is done completely virtual on just the sparse samples.***" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Example 8a - Create random sparse data, display and convolve it" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this example we create a certain number of random samples with random intensities inside a certain [Interval](https://javadoc.scijava.org/ImgLib2/net/imglib2/Interval.html). Using nearest neighbor interpolation we wrap it into a [RealRandomAccessible](https://javadoc.scijava.org/ImgLib2/net/imglib2/RealRandomAccessible.html), wrap it again into a [RandomAccessible](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccessible.html), define an [Interval](https://javadoc.scijava.org/ImgLib2/net/imglib2/Interval.html) on it and display it. On the same virtual data we perform a Gaussian convolution and show it, too." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Below is the utility method for a given interval magnification." ] }, { "cell_type": "code", "execution_count": 50, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "script1546098295980$1@206e4653" ] }, "execution_count": 50, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import net.imglib2.RealInterval\n", "import net.imglib2.RealPointSampleList\n", "import net.imglib2.type.numeric.real.FloatType\n", "import net.imglib2.RealPoint\n", "\n", "obj = new Object() {\n", " /**\n", " * Create a number of n-dimensional random points in a certain interval\n", " * having a random intensity 0...1\n", " *\n", " * @param interval - the interval in which points are created\n", " * @param numPoints - the amount of points\n", " *\n", " * @return a RealPointSampleList (which is an IterableRealInterval)\n", " */\n", " public RealPointSampleList createRandomPoints(RealInterval interval, int numPoints) {\n", " // the number of dimensions\n", " numDimensions = interval.numDimensions()\n", " \n", " // a random number generator\n", " Random rnd = new Random(System.currentTimeMillis())\n", " \n", " // a list of Samples with coordinates\n", " RealPointSampleList elements = new RealPointSampleList(numDimensions)\n", " \n", " for (int i = 0; i < numPoints; ++i) {\n", " RealPoint point = new RealPoint(numDimensions)\n", " \n", " for (int d = 0; d < numDimensions; ++d) {\n", " point.setPosition( rnd.nextDouble() * (interval.realMax(d) - interval.realMin(d)) + interval.realMin(d), d )\n", " \n", " // add a new element with a random intensity in the range 0...1\n", " elements.add(point, new FloatType(rnd.nextFloat()))\n", " }\n", " }\n", " return elements\n", " }\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "At first it shows nearest-neighbor rendered random sparse data." ] }, { "cell_type": "code", "execution_count": 51, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ] }, "execution_count": 51, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import net.imglib2.FinalInterval\n", "import net.imglib2.IterableRealInterval\n", "import net.imglib2.type.numeric.real.FloatType\n", "import net.imglib2.neighborsearch.NearestNeighborSearch\n", "import net.imglib2.neighborsearch.NearestNeighborSearchOnKDTree\n", "import net.imglib2.KDTree\n", "import net.imglib2.interpolation.neighborsearch.NearestNeighborSearchInterpolatorFactory\n", "import net.imglib2.view.Views\n", "\n", "long[] range = [375, 200]\n", "// the interval in which to create random points\n", "interval = new FinalInterval(range)\n", "\n", "// create an IterableRealInterval\n", " IterableRealInterval realInterval = obj.createRandomPoints(interval, 250)\n", "\n", "// using nearest neighbor search we will be able to return a value an any position in space\n", "search = new NearestNeighborSearchOnKDTree(new KDTree(realInterval))\n", "\n", "// make it into RealRandomAccessible using nearest neighbor search\n", "realRandomAccessible = Views.interpolate(search, new NearestNeighborSearchInterpolatorFactory())\n", "\n", "// convert it into a RandomAccessible which can be displayed\n", "randomAccessible = Views.raster(realRandomAccessible)\n", "\n", "// set the initial interval as area to view\n", "view = Views.interval(randomAccessible, interval)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "At second it shows the result of a Gaussian convolution, run directly on the virtual [RandomAccessibleInterval](https://javadoc.scijava.org/ImgLib2/net/imglib2/RandomAccessibleInterval.html)." ] }, { "cell_type": "code", "execution_count": 52, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ] }, "execution_count": 52, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import net.imglib2.img.array.ArrayImgFactory\n", "import net.imglib2.type.numeric.real.FloatType\n", "import net.imglib2.algorithm.gauss.Gauss\n", "import net.imglib2.Point\n", "\n", "// compute a gauss on it\n", "convolved = new ArrayImgFactory<>(new FloatType()).create(interval)\n", "\n", "double[] size = [3, 3]\n", "\n", "Gauss.inFloat(size, view, interval, convolved, new Point(view.numDimensions() ), convolved.factory())\n", "\n", "// display the convolved view\n", "convolved" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Example 8b - Randomly sample an existing image and display it" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this example we sample an existing image at random locations and render the result using a nearest neighbor interpolation as well as a distance-weighted average of the k nearest neighbors." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Below is the utility method for a given interval magnification." ] }, { "cell_type": "code", "execution_count": 53, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "script1546098296214$1@11dd71f6" ] }, "execution_count": 53, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import net.imglib2.Interval\n", "import net.imglib2.RealInterval\n", "import net.imglib2.RealPointSampleList\n", "import net.imglib2.type.numeric.real.FloatType\n", "import net.imglib2.type.numeric.RealType\n", "import net.imglib2.RealPoint\n", "import net.imglib2.RealRandomAccessible\n", "import net.imglib2.type.Type\n", "import net.imglib2.KDTree\n", "import net.imglib2.RandomAccessibleInterval\n", "import net.imglib2.neighborsearch.KNearestNeighborSearch\n", "import net.imglib2.neighborsearch.KNearestNeighborSearchOnKDTree\n", "import net.imglib2.neighborsearch.NearestNeighborSearchOnKDTree\n", "import net.imglib2.interpolation.neighborsearch.InverseDistanceWeightingInterpolatorFactory\n", "import net.imglib2.interpolation.neighborsearch.NearestNeighborSearchInterpolatorFactory\n", "import net.imglib2.view.Views\n", "\n", "obj = new Object() {\n", " /**\n", " * Sample a number of n-dimensional random points in a certain interval having a * random intensity 0...1\n", " *\n", " * @param interval - the interval in which points are created\n", " * @param numPoints - the amount of points\n", " *\n", " * @return a RealPointSampleList (which is an IterableRealInterval)\n", " */\n", " public > RealPointSampleList sampleRandomPoints(RealRandomAccessible input, RealInterval interval, int numPoints) {\n", " // the number of dimensions\n", " numDimensions = interval.numDimensions()\n", " \n", " // a random number generator\n", " rnd = new Random(1332441549191l)\n", " \n", " // a list of Samples with coordinates\n", " elements = new RealPointSampleList(numDimensions)\n", " \n", " // a random accessible in the image data to grep the right value\n", " realRandomAccess = input.realRandomAccess()\n", " \n", " for (int i = 0; i < numPoints; ++i) {\n", " point = new RealPoint( numDimensions)\n", " \n", " for (int d = 0; d < numDimensions; ++d) {\n", " point.setPosition(rnd.nextDouble() * (interval.realMax(d) - interval.realMin(d)) + interval.realMin(d), d)\n", " }\n", " \n", " realRandomAccess.setPosition(point)\n", " \n", " // add a new element with a random intensity in the range 0...1\n", " elements.add(point, realRandomAccess.get().copy())\n", " }\n", " return elements\n", " }\n", " \n", " /**\n", " * Sample randomly n points from the input and display the interpolated result using nearest neighbors\n", " *\n", " * @param input - the input data\n", " * @param interval - the size of the input (where to collect random samples)\n", " * @param numPoints - how many points to sample\n", " *\n", " * @return - a RandomAccessibleInterval of the same size as the input, rendered from the sparse data\n", " */\n", " public > RandomAccessibleInterval randomSampling(RealRandomAccessible input, Interval interval, int numPoints) {\n", " // create an IterableRealInterval\n", " realInterval = sampleRandomPoints(input, interval, numPoints)\n", " \n", " // using nearest neighbor search we will be able to return a value an any position in space\n", " search = new NearestNeighborSearchOnKDTree(new KDTree(realInterval))\n", " \n", " // make it into RealRandomAccessible using nearest neighbor search\n", " realRandomAccessible = Views.interpolate(search, new NearestNeighborSearchInterpolatorFactory())\n", " \n", " // convert it into a RandomAccessible which can be displayed\n", " randomAccessible = Views.raster(realRandomAccessible)\n", " \n", " // set the initial interval as area to view\n", " return Views.interval(randomAccessible, interval)\n", " }\n", " \n", " /**\n", " * Sample randomly n points from the input and display the interpolated result using\n", " * distance-weighted interpolation of 20 nearest neighbors\n", " *\n", " * @param input - the input data\n", " * @param interval - the size of the input (where to collect random samples)\n", " * @param numPoints - how many points to sample\n", " *\n", " * @return - a RandomAccessibleInterval of the same size as the input, rendered from the sparse data\n", " */\n", " public > RandomAccessibleInterval randomSamplingKNearest(RealRandomAccessible input, Interval interval, int numPoints) {\n", " // create an IterableRealInterval\n", " realInterval = sampleRandomPoints(input, interval, numPoints)\n", " \n", " // using nearest neighbor search we will be able to return a value an any position in space\n", " search = new KNearestNeighborSearchOnKDTree(new KDTree (realInterval), Math.min(20, (int)realInterval.size()))\n", " \n", " // make it into RealRandomAccessible using nearest neighbor search\n", " realRandomAccessible= Views.interpolate(search, new InverseDistanceWeightingInterpolatorFactory())\n", " \n", " // convert it into a RandomAccessible which can be displayed\n", " randomAccessible = Views.raster(realRandomAccessible)\n", " \n", " // set the initial interval as area to view\n", " return Views.interval(randomAccessible, interval)\n", " }\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Working with sparse data, sample an existing image at random locations and render it again using an increasing number of samples." ] }, { "cell_type": "code", "execution_count": 54, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ] }, "execution_count": 54, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import net.imglib2.type.numeric.real.FloatType\n", "import io.scif.img.ImgOpener\n", "\n", "// open sample image with ImgOpener as FloatType\n", "img = new ImgOpener().openImgs(\"https://samples.fiji.sc/tutorials/DrosophilaWing.tif\", new FloatType()).get(0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First we need to use linear interpolation to convert the input into a RealRandomAccessible." ] }, { "cell_type": "code", "execution_count": 55, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "net.imglib2.interpolation.Interpolant@18f13c43" ] }, "execution_count": 55, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import net.imglib2.type.numeric.real.FloatType\n", "import net.imglib2.view.Views\n", "import net.imglib2.interpolation.randomaccess.NLinearInterpolatorFactory\n", "\n", "// use linear interpolation to convert the input into a RealRandomAccessible\n", "realRandomAccessible = Views.interpolate(Views.extendMirrorSingle(img), new NLinearInterpolatorFactory())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Then it shows the rendering using nearest neighbor interpolation." ] }, { "cell_type": "code", "execution_count": 56, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ] }, "execution_count": 56, "metadata": {}, "output_type": "execute_result" } ], "source": [ "// You can change this variable to other number\n", "numPoints = 4096\n", "randomNNSample = obj.randomSampling(realRandomAccessible, img, numPoints)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "At last it shows the result when use a distance weighted interpolation of the numPoints nearest neighbors." ] }, { "cell_type": "code", "execution_count": 57, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ] }, "execution_count": 57, "metadata": {}, "output_type": "execute_result" } ], "source": [ "randomKNNSample = obj.randomSamplingKNearest(realRandomAccessible, img, numPoints)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You should notice the display and running time difference between nearest neighbor interpolation and distance weighted interpolation of k nearest neighbors." ] } ], "metadata": { "kernelspec": { "display_name": "Groovy", "language": "groovy", "name": "groovy" }, "language_info": { "codemirror_mode": "groovy", "file_extension": ".groovy", "mimetype": "", "name": "Groovy", "nbconverter_exporter": "", "version": "2.4.3" }, "toc": { "base_numbering": "0", "nav_menu": {}, "number_sections": true, "sideBar": true, "skip_h1_title": true, "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": false, "toc_position": { "height": "calc(100% - 180px)", "left": "10px", "top": "150px", "width": "307px" }, "toc_section_display": true, "toc_window_display": true } }, "nbformat": 4, "nbformat_minor": 2 }