DGtal  1.5.beta
Handle large images in DGtal
Authors
Martial Tola, David Coeurjolly
Date
2013/03/12

Part of the Image package

Overview

Large images like tiled images, HDF5 images, ... can be handled in DGtal.

For this, we provide two important mechanisms: an image factory mechanism and an image cache mechanism with read and write policies.

The image factory is necessary to keep control on the created images. As its responsibility is to create images according to a given domain. The factory can update (flush) the image on disk and also can free the image from memory. Image factory models can associated with specific image formats such as HDF5 which can be activated using a WITH_HDF5 flag during DGtal build (please refer to Building DGtal). This mechanism is a DGtal concept called CImageFactory which is detailed below.

As we want to take also into account large images effectively, we also need a cache mechanism to access to subsets, so called tiles, of such images.

The cache has the responsibility to store tiles and giving us read and write access to them. The reading, writing and updating mechanisms of the cache are controlled with two policies: the read and the write policies. These policies are DGtal concepts called respectively CImageCacheReadPolicy and CImageCacheWritePolicy which are detailed just below.

The cache process is rather simple. It consists of three important methods that are detailed here:

  • the read function returns true and get the value of an image from cache at a given position given by a point only if that point belongs to an image from cache, else returns false.
  • the write function returns true and set the value of an image from cache at a given position given by a point only if that point belongs to an image from cache, else returns false.
  • the update function update the cache according to the read policy.

Concepts

CImageFactory

Any model of the concept CImageFactory must have this nested type:

  • OutputImage, which specifies the type of the images created by the factory.

Moreover, it must have the following methods:

  • requestImage, which takes a domain as input parameter and returns an OutputImage pointer on the created image.
  • flushImage, which takes an OutputImage pointer as input parameter in order to flush/synchronize it on disk.
  • detachImage, which takes an OutputImage pointer as input parameter in order to delete/release it.

Cache policies concepts

Cache policies concepts are divided in two concepts: read and write policies.

CImageCacheReadPolicy

Any model of the concept CImageCacheReadPolicy must have this nested type:

  • ImageContainer, which specifies the type of the images stored in the cache.

Moreover, it must have the following methods:

  • getPage, which takes a point as input parameter and returns an ImageContainer pointer on the image that contains the point or NULL if no image in the cache contains that point.
  • getPage, which takes a domain as input parameter and returns an ImageContainer pointer on the image that contains the domain or NULL if no image in the cache contains that domain.
  • getPageToDetach, which returns an ImageContainer pointer on the image that we have to detach or NULL if no image have to be detached.
  • updateCache, which takes a domain as input parameter in order to update the cache according to the defined cache policy.
  • clearCache, which clears the cache.

CImageCacheWritePolicy

Any model of the concept CImageCacheWritePolicy must have this nested type:

  • ImageContainer, which specifies the type of the images stored in the cache.

Moreover, it must have the following methods:

  • writeInPage, which takes an ImageContainer pointer, a point and a value for that point as input parameters in order to set the value on the image at the position given by the point.
  • flushPage, which takes an ImageContainer pointer as input parameter in order to flush the image on disk according to the defined cache policy.

Models

Image factory models

  • ImageFactoryFromImage model is a rather simple one. It implements a factory which produces images from a bigger original one. The bigger one is still in memory. This model is for debugging purposes.
  • ImageFactoryFromHDF5 (with WITH_HDF5 build flag) model is similar to ImageFactoryFromImage: it implements a factory which produces images from an HDF5 "dataset/file" according to a given domain. When requesting a "block" of an HDF5 image, the factory will perform disk I/O access to load the appropriate chunk.

Cache policies models

Cache policies models are split into read and write policies.

  • ImageCacheReadPolicyLAST model is a rather simple one. It implements a 'LAST' read policy cache. The cache keeps only one page in memory, the last one. When the page needs to be replaced, the new page replaces the old one.
  • ImageCacheReadPolicyFIFO model is a rather simple one. It implements a 'FIFO' read policy cache. The cache keeps track of all the pages in memory in a queue, with the most recent arrival at the back, and the earliest arrival in front. When a page needs to be replaced, the page at the front of the queue (the oldest page) is selected.
  • ImageCacheWritePolicyWT model is a rather simple one. It implements a 'WT (Write-through)' write policy cache. Write is done synchronously both to the cache and to the disk.
  • ImageCacheWritePolicyWB model is a rather simple one. It implements a 'WB (Write-back or Write-behind)' write policy cache. Initially, writing is done only to the cache. The write to the disk is postponed until the cache blocks containing the data are about to be modified/replaced by new content.

The TiledImage class

The TiledImage is a simple class that implements a tiled image from a "bigger/original" one from an ImageFactory.

The tiled image is created from an existing image and with four parameters:

  • An alias to the image factory (see ImageFactoryFromImage or ImageFactoryFromHDF5).
  • An alias to a read policy.
  • An alias to a write policy.
  • and a parameter to describe the number of tiles we want for each dimension.
Note
It is important to take into account that read and write policies are passed as aliases in the TiledImage constructor, so for example, if two TiledImage instances are successively created with the same read policy instance, the state of the cache for a given time is therefore the same for the two TiledImage instances.

Concerning the cache mechanism explained at the top of this document, the accessor operator() (i.e. the getter) and the setter setValue are therefore really simple to write for the TiledImage class:

  • the getter just returns the value of an image (from cache) at a given position given by a point if the image is in the cache. If not, the cache is first update with the image that contains that point.
  • in the same manner, the setter just write the value of an image (in cache) at a given position given by a point if the image is in the cache. If not, the cache is first update with the image that contains that point.

In order to illustrate the next TiledImage usage sample, we are going a) to use these includes:

#include "DGtal/io/colormaps/HueShadeColorMap.h"
#include "DGtal/images/ImageContainerBySTLVector.h"
#include "DGtal/images/ImageFactoryFromImage.h"
#include "DGtal/images/TiledImage.h"

b) then define these type and variable:

typedef HueShadeColorMap<int> HueShade; // a simple HueShadeColorMap varying on 'int' values

c) then define a simple 16x16 (1,1) to (16,16) image (of 'int' type):

typedef ImageContainerBySTLVector<Z2i::Domain, int> VImage;
VImage image(Z2i::Domain(Z2i::Point(1,1), Z2i::Point(16,16)));
Space::Point Point
Definition: StdDefs.h:95
HyperRectDomain< Space > Domain
Definition: StdDefs.h:99
Image image(domain)

filled with 1 to 256 values like that:

int i = 1;
for (VImage::Iterator it = image.begin(); it != image.end(); ++it)
*it = i++;

which looks like that with a simple HueShadeColorMap varying from 1 to 256 and with (1,1) the first bottom-left point:

(1) simple 16x16 source image: (1,1) to (16,16) drawn with a simple HueShadeColorMap varying from 1 to 256.

Here is now the construction of a simple 4x4 tiled image with therefore 16 subdomains of the initial image domain. The tiled image is created from an existing image and with four parameters. The first parameter is an alias on the image factory (see ImageFactoryFromImage). The second parameter is an alias on a read policy. The third parameter is an alias on a write policy. The fourth parameter is to set how many tiles we want for each dimension:

// here we create an image factory
typedef ImageFactoryFromImage<VImage> MyImageFactoryFromImage;
typedef MyImageFactoryFromImage::OutputImage OutputImage;
MyImageFactoryFromImage imageFactoryFromImage(image);
// here we create read and write policies
typedef ImageCacheReadPolicyFIFO<OutputImage, MyImageFactoryFromImage> MyImageCacheReadPolicyFIFO;
typedef ImageCacheWritePolicyWT<OutputImage, MyImageFactoryFromImage> MyImageCacheWritePolicyWT;
MyImageCacheReadPolicyFIFO imageCacheReadPolicyFIFO(imageFactoryFromImage, 2);
MyImageCacheWritePolicyWT imageCacheWritePolicyWT(imageFactoryFromImage);
// here we create the TiledImage
typedef TiledImage<VImage, MyImageFactoryFromImage, MyImageCacheReadPolicyFIFO, MyImageCacheWritePolicyWT> MyTiledImage;
BOOST_CONCEPT_ASSERT(( concepts::CImage< MyTiledImage > ));
MyTiledImage tiledImage(imageFactoryFromImage, imageCacheReadPolicyFIFO, imageCacheWritePolicyWT, 4);

In this sample, we are going to work only with the 8 bottom subdomains of the original image.

At the beginning the cache is empty.

First we want to read the point 4,2 (which is in domain 1),

trace.info() << "Read value for Point 4,2: " << tiledImage(Z2i::Point(4,2)) << endl;
std::ostream & info()
Trace trace
Definition: Common.h:153

so after that the cache is (domain1,empty). Here is the domain1:

(3) read: not in cache, so, update. Cache: (domain1,empty). Read: domain1.

Then we want to read the point 10,6 (which is in domain 7),

trace.info() << "Read value for Point 10,6: " << tiledImage(Z2i::Point(10,6)) << endl;

so after that the cache is (domain1,domain7). Here is the domain7:

(4) read: not in cache, so, update. Cache: (domain1,domain7). Read: domain7.

Then we want to set the value of the point 11,7 to 1 (which is in domain 7),

aValue = 1; tiledImage.setValue(Z2i::Point(11,7), aValue);
trace.info() << "Write value for Point 11,7: " << aValue << endl;

so after that the cache is always (domain1,domain7). Here is the new domain7 after writing:

(5) write: in cache. Cache: (domain1,domain7). Write: domain7.

Then we want to read the point 2,3 (which is in domain 1),

trace.info() << "Read value for Point 2,3: " << tiledImage(Z2i::Point(2,3)) << endl;

so after that the cache is always (domain1,domain7). Here is the domain1:

(6) read: in cache. Cache: (domain1,domain7). Read: domain1.

Then we want to read the point 16,1 (which is in domain 4),

trace.info() << "Read value for Point 16,1: " << tiledImage(Z2i::Point(16,1)) << endl;

so after that the cache is (domain7,domain4). Here is the domain4:

(7) read: not in cache, so, update. Cache: (domain7,domain4). Read: domain4.

Then we want to set the value of the point 16,1 to 128 (which is in domain 4),

aValue = 128; tiledImage.setValue(Z2i::Point(16,1), aValue);
trace.info() << "Write value for Point 16,1: " << aValue << endl;

so after that the cache is always (domain7,domain4). Here is the new domain4 after writing:

(8) write: in cache. Cache: (domain7,domain4). Write: domain4

And finally, here is the modified original image after the two writings.

(9) result image.