This page is a tutorial for saving TIFFs using TiffImages.jl and covers some common use cases
You might want to write TIFFs to disk too. Now this can be done quite simply with TiffImages.jl. Say you have some AbstractArray type that you want to save, here we'll call it data
:
using Random
+using Images # for nice inline images
+
+Random.seed!(123)
+data = rand(RGB{N0f8}, 10, 10)
TiffImages.jl only works with AbstractArrays with eltype
s of <:Colorant
because the writer needs to know how to represent the image data on disk. Make sure to convert your AbstractArrays
using before passing them. See the common strategies section below for tips.
In most simple cases, all you need to do is use the save
function
using TiffImages
+TiffImages.save("test.tif", data)
That's it! TiffImages will convert your data into its own internal file type and then rapidly write it to disk. See the "Incremental writing" section of Lazy TIFFs for building a TIFF piece by piece.
If you need more fine-grained control over what tags are included when the image is written, this section is for you!
Next lets convert data
to a TIFF type
using TiffImages
+img = TiffImages.DenseTaggedImage(data)
Wait nothing happened! Hang with me, lets take a closer look at our new object using the dump
command. We can see that there's now new information associated with our data! TiffImages.jl usually represents TIFF images as simply the data and associated tags that describe the data
dump(img; maxdepth=1)
TiffImages.DenseTaggedImage{RGB{N0f8}, 2, UInt32, Matrix{RGB{N0f8}}}
+ data: Array{RGB{N0f8}}((10, 10))
+ ifds: Array{TiffImages.IFD{UInt32}}((1,))
The tags are organized as a vector of what are called Image File Directories (IFDs). For a simple 2D image like what we have, the IFDs will be stored a vector of length=1. For 3D images, the length of the IFDs vector will equal the length of the image in the third dimension.
Lets take a look at what tags there are:
ifd = ifds(img) # returns a single IFD since our data is 2D
IFD, with tags:
+ Tag(IMAGEWIDTH, 10)
+ Tag(IMAGELENGTH, 10)
+ Tag(BITSPERSAMPLE, UInt16[8, 8, 8])
+ Tag(PHOTOMETRIC, 2)
+ Tag(SAMPLESPERPIXEL, 3)
+ Tag(SAMPLEFORMAT, UInt16[1, 1, 1])
These are some of the most basic tags that are required by the TIFF spec. We can even update it to add our own custom tags
ifd[TiffImages.IMAGEDESCRIPTION] = "This is very important data"
+ifd
IFD, with tags:
+ Tag(IMAGEWIDTH, 10)
+ Tag(IMAGELENGTH, 10)
+ Tag(BITSPERSAMPLE, UInt16[8, 8, 8])
+ Tag(PHOTOMETRIC, 2)
+ Tag(IMAGEDESCRIPTION, "This is very importa...")
+ Tag(SAMPLESPERPIXEL, 3)
+ Tag(SAMPLEFORMAT, UInt16[1, 1, 1])
We can even add tags that aren't in the standard set in TiffImages.TiffTag
as long as they are a UInt16
ifd[UInt16(34735)] = UInt16[1, 2, 3]
+ifd
IFD, with tags:
+ Tag(IMAGEWIDTH, 10)
+ Tag(IMAGELENGTH, 10)
+ Tag(BITSPERSAMPLE, UInt16[8, 8, 8])
+ Tag(PHOTOMETRIC, 2)
+ Tag(IMAGEDESCRIPTION, "This is very importa...")
+ Tag(SAMPLESPERPIXEL, 3)
+ Tag(SAMPLEFORMAT, UInt16[1, 1, 1])
+ Tag(UNKNOWN(34735), UInt16[1, 2, 3])
We can also delete tags if we decide we don't want them:
delete!(ifd, TiffImages.IMAGEDESCRIPTION)
+ifd
IFD, with tags:
+ Tag(IMAGEWIDTH, 10)
+ Tag(IMAGELENGTH, 10)
+ Tag(BITSPERSAMPLE, UInt16[8, 8, 8])
+ Tag(PHOTOMETRIC, 2)
+ Tag(SAMPLESPERPIXEL, 3)
+ Tag(SAMPLEFORMAT, UInt16[1, 1, 1])
+ Tag(UNKNOWN(34735), UInt16[1, 2, 3])
Careful with delete!
, if any of core tags are deleted, TiffImages.jl and other readers might fail to read the file
Once you're happy with your TIFF object, you can write it to disk as follows:
TiffImages.save("test.tif", img)
And to just double check, we can load it right back in
TiffImages.load("test.tif")
The general strategy for saving arrays will differ a bit depending on the type. The key step is the convert or reinterpret the arrays so that the elements are subtypes of Colors.Colorant
Say you want to save a 3D array of small integers as grayscale values.
data2 = rand(UInt8.(1:255), 5, 10)
+eltype(data2)
UInt8
You can't directly save the data2
since TiffImages.jl needs some color information to properly save the file. You can use reinterpret
to accomplish this:
grays = reinterpret(Gray{N0f8}, data2)
+img2 = TiffImages.DenseTaggedImage(grays)
Here the data are first reinterpreted as N0f8
s, which is a FixedPointNumber
then wrapped with a Gray type that marks this as a grayscale image. TiffImages.jl uses this information to update the TIFF tags
With RGB we can reinterpret the first dimension of a 3D array as the 3 different color components (red, green, and blue):
data = rand(Float64, 3, 5, 10);
+colors = dropdims(reinterpret(RGB{eltype(data)}, data), dims=1) # drop first dimension
+img3 = TiffImages.DenseTaggedImage(colors)
Here we dropped the first dimension since it was collapsed into the RGB type when we ran the reinterpret
command.
Say you want to save data that has negative integer values. In that case, you can't use N0f8
, etc because those only worked for unsigned integers. You have to instead use Q0f63
, etc, which is a different kind of fixed point number that uses one bit for the sign info (that's why it's Q0f63
, not Q0f64
!)
data = rand(-100:100, 5, 5)
5×5 Matrix{Int64}:
+ 25 18 84 -67 -74
+ 87 34 -23 22 100
+ -93 63 -2 -16 63
+ -96 -65 -80 -99 98
+ 11 -59 -57 17 -6
img4 = TiffImages.DenseTaggedImage(reinterpret(Gray{Q0f63}, data))
+println(ifds(img4))
IFD, with tags:
+ Tag(IMAGEWIDTH, 5)
+ Tag(IMAGELENGTH, 5)
+ Tag(BITSPERSAMPLE, 64)
+ Tag(PHOTOMETRIC, 1)
+ Tag(SAMPLESPERPIXEL, 1)
+ Tag(SAMPLEFORMAT, 2)
As you can see the SAMPLEFORMATS
and BITSPERSAMPLE
tags correctly updated to show that this TIFF contains signed integers and 64-bit data, respectively.
Currently, several of the display libraries struggle with showing Colorant
s backed by a signed type so you might run into errors, but the data will still save properly
This page was generated using Literate.jl.