title: Extending IO author: Tim Holy order: 70 ...
Images
supports a number of image file formats. Popular formats such as PNG,
JPEG, and TIFF are currently loaded and saved via
ImageMagick.
Images
also supports a number of formats used more typically for scientific
work:
- NRRD, "nearly raw raster data" which despite its name allows for considerable metadata. Currently, this is perhaps the best default choice of formats for storing image data.
- SIF, a file format used by Andor Technology for their scientific CCD cameras
- B16, a format used by PCO/Cooke cameras
- Imagine, a format used for 4D (3 spatial dimensions + time) imaging
Finally, HDF5 and JLD can also be used to store images. There is an official HDF5 image standard, which we could fairly easily support, but the author's searching has yet to turn up an example file that complies with this standard. Many HDF5 images seem to be stored with custom attributes, which fortunately are quite easy to parse.
To read an image, one typically says
img = imread("filename.fmt")
and to write,
imwrite(img, "filename.fmt")
You can read from streams and control format this way:
img = imread(stream, Images.NRRDFile)
would load an image from a stream, interpreting it as having NRRD format. The stream should already be advanced past the format's magic bytes.
It is not difficult to extend the Images
library to handle custom image files.
For an example, here are the essential steps to set up support for a .sif
file.
All of the following code is found in one file, SIF.jl
.
First, we create a new type for the image, whose parent is
Images.ImageFileType
. Next, we register the .sif
extension and the "magic
bytes" to the new type. The magic bytes are found at the start of the file and
are used to uniquely identify the format.
# SIF.jl, adds an imread function for Andor .sif images
using Images
type AndorSIF <: Images.ImageFileType end
add_image_file_format(".sif", b"Andor Technology Multi-Channel File", AndorSIF)
Next, we create an imread function to handle the new image type. But first, we have to explicitly import the imread function so that we can extend it:
import Images.imread
function imread{S<:IO}(stream::S, ::Type{AndorSIF})
seek(stream, 0)
l = strip(readline(stream))
l == "Andor Technology Multi-Channel File" ||
error("Not an Andor file: " * l)
When this imread is called, the magic bytes have already been read from the
stream. In this case, it's easier to parse the header if we start over from the
beginning of the file with the seek(stream, 0)
statement.
Next, we parse the header information. Many of the fields in this file are
space delimited within lines. If something unexpected is read, we spit out an
error (very important when the file format changes, pulling out the rug from
underneath you). This particular file type has a large number of fields. For
completeness, we will store all of these fields in a Dict
named ixon
:
# ...skipped a few uninteresting fields before here
l = strip(readline(stream))
fields = split(l)
fields[1] == "65547" || fields[1] == "65558" ||
error("Unknown TInstaImage version number at line 3: " * fields[1])
ixon = {"data_type" => convert(Int,fields[2])}
ixon["active"] = convert(Int,fields[3])
ixon["structure_vers"] = convert(Int,fields[4]) # (== 1)
# ...and skip a whole bunch of uninteresting fields after here
We keep reading and parsing the image header until we reach the first byte of
the actual image data. Along the way, we collect variables width
, height
,
and frames
(this image file is actually a 3D array, an image sequence over
time). Now we are ready to read the actual pixel data:
pixels = read(stream, Float32, width, height, frames)
Finally, we wrap up by setting the properties, including the new ixon
collection with suppressed printing, and return the Image
:
prop = {"colorspace" => "Gray",
"spatialorder" => ["y", "x", "t"],
"ixon" => ixon,
"suppress" => Set({"ixon"})}
Image(pixels, prop)
end # imread()
To make your file format available to others, only a few changes are needed.
First, move your "registration" code from SIF.jl
to Images' io.jl
:
type AndorSIF <: ImageFileType end
add_image_file_format(".sif", b"Andor Technology Multi-Channel File", AndorSIF,
"SIF.jl")
Note that we've added one more argument to this function, the
"SIF.jl"
string. By supplying a filename, you're setting it up
so that the code to handle SIF images is loaded automatically the
first time you try to read or write a SIF file.
Second, the AndorSIF
type is now found in the Images
module, so
tell the imread
call to look there in SIF.jl
:
function imread{S<:IO}(stream::S, ::Type{Images.AndorSIF})
The final step is to take SIF.jl
and add it to the ioformats
directory (inside src/
). Submit a pull request, and once merged your
format will be usable by anyone.