%classpath config resolver scijava.public https://maven.scijava.org/content/groups/public
%classpath add mvn net.imagej imagej 2.0.0-rc-71
ij = new net.imagej.ImageJ()
"ImageJ v${ij.getVersion()} is ready to go."
Added new repo: scijava.public
ImageJ v2.0.0-rc-71 is ready to go.
Images in ImageJ are backed by data structures from the ImgLib2 library. There are several important interfaces, but the two most crucial to understand initially are IterableInterval
and RandomAccessibleInterval
.
As you can infer from their names, both of them are Interval
s, which means finite, discrete point samplings in
$\mathbb{Z}^{n}$
bounded in each dimension. For example, a uint8
image with dimensions $1024 \times 768$ is an interval in $\mathbb{Z}^{2}$ bounded in X (the first dimension) by $[0, 1023]$ and bounded in Y (the second dimension) by $[0, 767]$, with each integer coordinate inside the interval possessing some value $v \in \mathbb{Z} : 0 \leq v \leq 255$.
An IterableInterval
is an image which can be iterated in some order. That is, you can loop over its samples, although the iteration order may vary depending on the type of image—that is, you cannot rely on a particular sample order a priori. The good news is that an IterableInterval
does know its dimensional position at each iteration. An IterableInterval
is essentially a stream.
A RandomAccessibleInterval
is an image which can be inspected at will at arbitrary positions within the interval. In other words, it provides random access to the image samples.
If you are familiar with the java.io
package of the Java standard library, IterableInterval
is to java.io.InputStream
as RandomAccessibleInterval
is to java.io.RandomAccessFile
.
Let's dive into the API for each of these types of images!
Here is a demonstration of image iteration:
import net.imglib2.IterableInterval
// Create a tiny image.
image = ij.op().run("create.img", [5, 3])
println("The image is a " + image.getClass().getName())
println("image instanceof IterableInterval? " + (image instanceof IterableInterval))
println("Each sample is a " + image.firstElement().getClass().getName())
// Populate it with a diagonal gradient.
ij.op().image().equation(image, "p[0]+p[1]")
// Iterate over the image samples!
print("Sample values =")
// A foreach loop in Groovy uses "in" to seperate arguments whereas a Java foreach loop uses ":" to seperate arguments
for (v in image)
print(" " + v)
println()
The image is a net.imglib2.img.array.ArrayImg image instanceof IterableInterval? true Each sample is a net.imglib2.type.numeric.real.DoubleType Sample values = 0.0 1.0 2.0 3.0 4.0 1.0 2.0 3.0 4.0 5.0 2.0 3.0 4.0 5.0 6.0
null
The actual type of the created image object is ArrayImg
, which is an image container backed by one big array—in this case, a double[]
because the samples are of type DoubleType
.
More generally: an Img
is an object which is both an IterableInterval
and a RandomAccessibleInterval
. Other kinds of image containers include PlanarImg
(one array per 2D slice) and CellImg
(one array per N-dimensional block).
You might be wondering: what if I need to know the dimensional position during iteration? The solution is to use a cursor:
print("Samples by position:")
c = image.localizingCursor()
while (c.hasNext()) {
v = c.next()
xPos = c.getLongPosition(0)
yPos = c.getLongPosition(1)
if (xPos == 0) println()
print("\t(" + xPos + ", " + yPos + ") = " + v)
}
Samples by position: (0, 0) = 0.0 (1, 0) = 1.0 (2, 0) = 2.0 (3, 0) = 3.0 (4, 0) = 4.0 (0, 1) = 1.0 (1, 1) = 2.0 (2, 1) = 3.0 (3, 1) = 4.0 (4, 1) = 5.0 (0, 2) = 2.0 (1, 2) = 3.0 (2, 2) = 4.0 (3, 2) = 5.0 (4, 2) = 6.0
null
Each cursor is a pointer into the image somewhere, which knows its position. It is possible to run multiple cursors over an image simultaneously.
Note that for performance, we use a localizing cursor above by calling localizingCursor()
, because we knew we would query the position every time. If we were going to query the position only rarely, cursor()
would be better—a vanilla cursor still knows its position, but does less bookkeeping and hence is faster to iterate in cases where you don't query very often.
From the positions above, we see that the iteration order here happens to be nice and organized, with X moving fastest, followed by Y. This so-called flat iteration order is what you get with ArrayImg
, but may differ with other image containers. Here is an example of how a cell image with $2 \times 2 \times 2$ blocks differs from an array image of the same size:
// Create an array image and a cell (2x2x2) image.
import net.imglib2.img.array.ArrayImgs
import net.imglib2.img.cell.CellImgFactory
long[] dims = [4, 2, 2]
arrayImg = ArrayImgs.unsignedBytes(dims)
cellImg = new CellImgFactory(2).create(dims, arrayImg.firstElement())
def printPositions(ii, width) {
c = ii.localizingCursor()
col = 0
while (c.hasNext()) {
v = c.next()
xPos = c.getLongPosition(0)
yPos = c.getLongPosition(1)
zPos = c.getLongPosition(2)
print("\t(" + xPos + ", " + yPos + ", " + zPos + ")")
if (++col == width) { col = 0; println() }
}
println()
}
println()
println("Array image iteration order =")
printPositions(arrayImg, 4)
println("Cell image iteration order =")
printPositions(cellImg, 4)
Array image iteration order = (0, 0, 0) (1, 0, 0) (2, 0, 0) (3, 0, 0) (0, 1, 0) (1, 1, 0) (2, 1, 0) (3, 1, 0) (0, 0, 1) (1, 0, 1) (2, 0, 1) (3, 0, 1) (0, 1, 1) (1, 1, 1) (2, 1, 1) (3, 1, 1) Cell image iteration order = (0, 0, 0) (1, 0, 0) (0, 1, 0) (1, 1, 0) (0, 0, 1) (1, 0, 1) (0, 1, 1) (1, 1, 1) (2, 0, 0) (3, 0, 0) (2, 1, 0) (3, 1, 0) (2, 0, 1) (3, 0, 1) (2, 1, 1) (3, 1, 1)
null
Sometimes you need to access samples in an order other than the natural one. This is possible as long as the image implements the RandomAccessible
interface.
By using a RandomAccess
accessor, we can get access to any pixel of the image in any order:
import net.imglib2.RandomAccessibleInterval
import net.imglib2.img.array.ArrayImgs
import net.imglib2.img.cell.CellImgFactory
import net.imglib2.type.numeric.integer.UnsignedByteType;
import net.imglib2.type.numeric.real.DoubleType;
// create array image and cell image
long[] dims = [128,96]
arrayImg = ArrayImgs.unsignedBytes(dims);
cellImg = new CellImgFactory(1).create(dims, arrayImg.firstElement())
// is the image RandomAccessible
println("Array image instanceof RandomAccessible? " + (arrayImg instanceof RandomAccessibleInterval))
println("Cell image instanceof RandomAccessible? " + (cellImg instanceof RandomAccessibleInterval))
// start drawing
def drawimage(image){
ra = image.randomAccess()
ra.setPosition(15,0)
for (y in 15..45) {
ra.setPosition(y, 1)
UnsignedByteType t = ra.get()
t.set(255)
}
ra.setPosition(15,1)
for (x in 0..cellImg.dimension(0) - 1){
ra.setPosition(x, 0)
UnsignedByteType t = ra.get()
t.set(255)
}
ra.setPosition(112, 0)
for (y in 15..45) {
ra.setPosition(y, 1)
UnsignedByteType t = ra.get()
t.set(255)
}
ra.setPosition(80, 1)
for (x in 57..71){
ra.setPosition(x, 0)
UnsignedByteType t = ra.get()
t.set(255)
}
}
drawimage(arrayImg)
drawimage(cellImg)
ij.notebook().display(['ArrayImg': arrayImg,'CellImg': cellImg])
Array image instanceof RandomAccessible? true Cell image instanceof RandomAccessible? true
ArrayImg | |
CellImg |
TODO: