Creating and Displaying an Image
Before you dive into ImgLib2 for real, you should know how to create and display an image, so that you can visually enjoy the fruits of your labor.
The following piece of code creates and displays an 400x320 8bit gray-level image:
import net.imglib2.img.Img;
import net.imglib2.img.array.ArrayImgFactory;
import net.imglib2.img.display.imagej.ImageJFunctions;
import net.imglib2.type.numeric.integer.UnsignedByteType;
public class CreateAndDisplay
{
public static void main( final String[] args )
{
final Img< UnsignedByteType > img = new ArrayImgFactory< UnsignedByteType >()
.create( new long[] { 400, 320 }, new UnsignedByteType() );
ImageJFunctions.show( img );
}
}
When you run this example, you should get a window showing a black 400x320 image. In lines 10-11, the image is created. In line 12 it is displayed. Now, that is one awfully long line just to create a black image. Let’s break it down into smaller parts.
final ImgFactory< UnsignedByteType > factory = new ArrayImgFactory< UnsignedByteType >();
final long[] dimensions = new long[] { 400, 320 };
final UnsignedByteType type = new UnsignedByteType();
final Img< UnsignedByteType > img = factory.create( dimensions, type );
Pixel images in ImgLib2 are created using an ImgFactory
. There are different ImgFactories, that create pixel containers with different memory layouts. Here, we create an ArrayImgFactory
. This factory creates in containers that map to a single flat Java array:
final ImgFactory< UnsignedByteType > factory
= new ArrayImgFactory< UnsignedByteType >();
The type parameter of the factory that 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
.
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:
final long[] dimensions = new long[] { 400, 320 };
Finally, we need to provide a type variable, that is a variable having the type that is to be stored in the image. This must match the generic type parameter of the ImgFactory
. Thus we create an UnsignedByteType
:
final UnsignedByteType type = new UnsignedByteType();
Then we can create the image, using the factory, dimensions, and type variable:
final Img< UnsignedByteType > img = factory.create( dimensions, type );
We store the result of the create()
method in an Img
variable. Img
is a convenience interface that gathers properties of pixel image containers such as having a number of dimensions, being able to iterate it’s pixels, etc.
This image is then displayed using:
ImageJFunctions.show( img );
ImageJFunctions
provides convenience methods to wrap ImgLib2 constructs into ImageJ containers and display them. It works with 2D and 3D images and can handle most of the pixel types supported by ImgLib2. ImgLib2 provides more sophisticated ways of getting image data to your screen, but we will not go into that now. As a rule of thumb, if you have something remotely resembling a pixel grid, usually you can ImageJFunctions.show()
it.
Opening And Displaying Image Files
You can open image files with ImgOpener
which is using LOCI Bio-Formats. The following opens and displays an image file:
import net.imglib2.img.Img;
import net.imglib2.img.array.ArrayImgFactory;
import net.imglib2.img.display.imagej.ImageJFunctions;
import io.scif.img.ImgIOException;
import io.scif.img.ImgOpener;
import net.imglib2.type.numeric.integer.UnsignedByteType;
public class OpenAndDisplay
{
public static void main( final String[] args )
{
try
{
final Img< UnsignedByteType > img = new ImgOpener().openImg( "graffiti.tif",
new ArrayImgFactory< UnsignedByteType >(), new UnsignedByteType() );
ImageJFunctions.show( img );
}
catch ( final ImgIOException e )
{
e.printStackTrace();
}
}
}
The image is loaded in lines 14-15. Lets look the steps in more detail. We create an ImgOpener
:
final ImgOpener opener = new ImgOpener();
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
layout again, and we want to have UnsignedByteType
values again.
Similar to the above example we need an ImgFactory
and an instance of the value type:
final ImgFactory< UnsignedByteType > factory = new ArrayImgFactory< UnsignedByteType >();
final UnsignedByteType type = new UnsignedByteType();
Then we can use the openImg()
method, giving a filename, ImgFactory
, and type instance:
final Img< UnsignedByteType > img = opener.openImg( "graffiti.tif", factory, type );
If there is a problem reading the image, openImg()
throws an ImgIOException
. If all goes well, we store the result in an Img
variable for convenience. (Actually the result is an ImgPlus
wrapping an ArrayImg
.)
Notes
- Note that
Img
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 theArrayImgFactory< UnsignedByteType >.create()
is actually anArrayImg< 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 ofImg
that you really need. For instance, if you need something which has boundaries and can be iterated you would useIterableInterval
. - There are more ImgLib2 Examples on Opening, creating and displaying images.