IOLink  IOL_v1.6.1_release
Fundamentals

IOLink is quite rich in functionalities to convert/transform/create views, and it should increase in further versions.

This chapters describes a variety of specific cases. It cannot be complete, of course, but should be upgraded case by case.

For more readability, cases are sorted by topic.

View type

View is the IOLink main interface and can represent any data. But in order to access to specific API according to the type of data, you must transform a View instance into the appropriate derived object:

  • ImageView
  • MultiImageView
  • LodImageView

IOLink provides helpers to do this operation. These helpers are called providers. Here is an example of use of ImageViewProvider:

if(ImageViewProvider::isImage(view))
{
std::shared_ptr<ImageView> image = ImageViewProvider::toImage(view);
// your code here
}
else
{
// your code here
}

View capabilities

Each kind of View defines a variety of capabilities. They give access to various sets of methods. If you call a method of a non supported capability, a NotImplementedError will be raised.

For example, ImageView instances can be readable, writable, reshapable, or have their CPU memory buffer accessible. Each of these are listed in the ImageCapability enumeration.

There is a method to check if an ImageView supports a specific capability:

if(image->support(ImageCapability::READ))
{
// your reading code here
}
if(image->support(ImageCapability::WRITE))
{
// your writing code here
}
if(image->support(ImageCapability::RESHAPE))
{
// your reshape code here
}
if(image->support(ImageCapability::MEMORY_ACCESS))
{
// your low level buffer operations here
}

You also have the possibility to restrict view capabilities by using following helpers:

std::shared_ptr<ImageView> image = ImageViewProvider::toImage(view);
std::shared_ptr<ImageView> r_img = ImageViewProvider::toReadOnly(image);
// r_img has only Read capability now
std::shared_ptr<ImageView> w_img = ImageViewProvider::toWriteOnly(image);
// w_img has only Write capability now

Region

Region types describes an axis aligned area, defined by its origin and size. For images, origin and size units are the pixel.

Read a region from a view

A helper is available to create a region with (0, 0) as its origin and a vector as its size. It is useful when you want to read all the image content in one call.

RegionXu64 fullRegion = RegionXu64::createFullRegion(image->shape());
std::cout << "region dimension = " << fullRegion.dimensionCount() << std::endl;
std::cout << "region min = " << fullRegion.min().toString() << std::endl;
std::cout << "region max = " << fullRegion.max().toString() << std::endl;
std::cout << "pixel count = " << fullRegion.elementCount()<< std::endl;

You will get following result with an image of shape (100, 200):

region dimension = 2
region min = (0, 0)
region max = (99, 199)
Pixel counts = 20000

But if you want to access a smaller region, you can define your own region:

RegionXu64 region(VectorXu64{5, 6}, VectorXu64{10, 15});
std::cout << "region dimension = " << region.dimensionCount() << std::endl;
std::cout << "region min = " << region.min().toString() << std::endl;
std::cout << "region max = " << region.max().toString() << std::endl;
std::cout << "pixel count = " << region.elementCount()<< std::endl;

This time, you will get following result:

region dimension = 2
region min = (5, 6)
region max = (14, 20)
Pixel counts = 150

When your region is defined, you can access to data through a method from an ImageView instance that supports the READ capability.

// allocate memory to store pixels of given region
size_t bufferSize = region.elementCount() * image->dataType().byteCount();
std::vector<uint16_t> buffer(bufferSize);
image->readRegion(region, buffer.data());

Create a view onto a specific region

You may want to create an ImageView that references a sub region of another ImageView, without copy. It can be useful, for example to split a big image into tiles to speed-up its processing.

As explained above to read a specific region, you must first define a region. Then you can use the appropriate method from the ImageViewFactory to create a view onto this region.

RegionXu64 smallRegion(VectorXu64{10, 20}, VectorXu64{100, 150});
std::shared_ptr<ImageView> viewSmallRegion = ImageViewFactory::extractRegion(view, smallRegion);
std::cout << "Shape of new view: " << viewSmallRegion->shape() << std::endl;

You will get:

Shape of new view: (100, 150)

Change ImageView dimension with region view

You may want to extract only a part of a 3D image. To do this you can use a region with a size of 1 on the third dimension:

RegionXu64 regionSlice(VectorXu64{0, 0, 11}, VectorXu64{100, 200, 1});
std::shared_ptr<ImageView> viewSlice = ImageViewFactory::extractRegion(image, regionSlice);

The view on this slice is still three-dimensional, but the user may want a two-dimensional image as a result. There is a variant of the extract region method to do that:

RegionXu64 regionSlice(VectorXu64{0, 0, 11}, VectorXu64{100, 200, 1});
std::shared_ptr<ImageView> viewSlice = ImageViewFactory::extractAdjustedRegion(image, regionSlice);

When using this variant, all the shape dimensions of size one will be removed.

For example:

  • view on region of size {100, 200, 1} will result in a shape of {100, 200)}.
  • view on region of size {100, 1, 20} will result in a shape of {100, 20)}.
  • view on region of size {1, 200, 20} will result in a shape of {200, 20)}.

Data type

Introspection

IOLink uses its own definition of basic types, called DataType. Theses types are created to make introspection possible. From any type, you can access at runtime to informations like bit depth, element count, primitive type, and interpretation.

Here are the basic types managed by IOLink:

Name C type Primitive type Size Bits Interpretation
UINT8 uint8_t UNSIGNED_INTEGER 1 8 RAW
UINT16 uint16_t UNSIGNED_INTEGER 1 16 RAW
UINT32 uint32_t UNSIGNED_INTEGER 1 32 RAW
UINT64 uint64_t UNSIGNED_INTEGER 1 64 RAW
INT8 int8_t SIGNED_INTEGER 1 8 RAW
INT16 int16_t SIGNED_INTEGER 1 16 RAW
INT32 int32_t SIGNED_INTEGER 1 32 RAW
INT64 int64_t SIGNED_INTEGER 1 64 RAW
FLOAT float FLOATING_POINT 1 32 RAW
DOUBLE double FLOATING_POINT 1 64 RAW
UTF8_STRING const char* UNICODE_STRING 1 8 RAW
UTF16_STRING const char16_t* UNICODE_STRING 1 16 RAW
UTF32_STRING const char32_t* UNICODE_STRING 1 32 RAW
VEC2_UINT8 uint8_t[2] UNSIGNED_INTEGER 2 8 RAW
VEC2_UINT16 uint16_t[2] UNSIGNED_INTEGER 2 16 RAW
VEC2_UINT32 uint32_t[2] UNSIGNED_INTEGER 2 32 RAW
VEC2_UINT64 uint64_t[2] UNSIGNED_INTEGER 2 64 RAW
VEC2_INT8 int8_t[2] SIGNED_INTEGER 2 8 RAW
VEC2_INT16 int16_t[2] SIGNED_INTEGER 2 16 RAW
VEC2_INT32 int32_t[2] SIGNED_INTEGER 2 32 RAW
VEC2_INT64 int64_t[2] SIGNED_INTEGER 2 64 RAW
VEC2_FLOAT float[2] FLOATING_POINT 2 32 RAW
VEC2_DOUBLE double[2] FLOATING_POINT 2 64 RAW
VEC3_UINT8 uint8_t[3] UNSIGNED_INTEGER 3 8 RAW
VEC3_UINT16 uint16_t[3] UNSIGNED_INTEGER 3 16 RAW
VEC3_UINT32 uint32_t[3] UNSIGNED_INTEGER 3 32 RAW
VEC3_UINT64 uint64_t[3] UNSIGNED_INTEGER 3 64 RAW
VEC3_INT8 int8_t[3] SIGNED_INTEGER 3 8 RAW
VEC3_INT16 int16_t[3] SIGNED_INTEGER 3 16 RAW
VEC3_INT32 int32_t[3] SIGNED_INTEGER 3 32 RAW
VEC3_INT64 int64_t[3] SIGNED_INTEGER 3 64 RAW
VEC3_FLOAT float[3] FLOATING_POINT 3 32 RAW
VEC3_DOUBLE double[3] FLOATING_POINT 3 64 RAW
VEC4_UINT8 uint8_t[4] UNSIGNED_INTEGER 4 8 RAW
VEC4_UINT16 uint16_t[4] UNSIGNED_INTEGER 4 16 RAW
VEC4_UINT32 uint32_t[4] UNSIGNED_INTEGER 4 32 RAW
VEC4_UINT64 uint64_t[4] UNSIGNED_INTEGER 4 64 RAW
VEC4_INT8 int8_t[4] SIGNED_INTEGER 4 8 RAW
VEC4_INT16 int16_t[4] SIGNED_INTEGER 4 16 RAW
VEC4_INT32 int32_t[4] SIGNED_INTEGER 4 32 RAW
VEC4_INT64 int64_t[4] SIGNED_INTEGER 4 64 RAW
VEC4_FLOAT float[4] FLOATING_POINT 4 32 RAW
VEC4_DOUBLE double[4] FLOATING_POINT 4 64 RAW
COMPLEX_FLOAT float[2] FLOATING_POINT 2 32 COMPLEX
COMPLEX_DOUBLE double[2] FLOATING_POINT 2 64 COMPLEX
MATRIX2_FLOAT float[4] FLOATING_POINT 4 32 SQUARE_MATRIX
MATRIX2_DOUBLE double[4] FLOATING_POINT 4 64 SQUARE_MATRIX
MATRIX3_FLOAT float[9] FLOATING_POINT 9 32 SQUARE_MATRIX
MATRIX3_DOUBLE double[9] FLOATING_POINT 9 64 SQUARE_MATRIX
MATRIX4_FLOAT float[16] FLOATING_POINT 16 32 SQUARE_MATRIX
MATRIX4_DOUBLE double[16] FLOATING_POINT 16 64 SQUARE_MATRIX

UINT8 is the typical type used for grayscale images which pixels are stored on 8 bits. For example, VEC4_UINT8 allows to store RGBA8888 pixel values.

A user is able to create new types using the following mechanism:

// creation of a 5-dimensional unsigned data type coded on 128 bits
DataType CUSTOM_TYPE = DataType(PrimitiveTypeId::UNSIGNED_INTEGER, 5, 128);

At runtime, this mechanism allows users to know in detail the pixel definition of images which they handle.

void displayPixelInfo( std::shared_ptr<ImageView> view)
{
DataType type = view->dataType();
std::cout << "Scalar bit depth: " << type.bitDepth() << std::endl;
std::cout << "Number of components: " << type.dimension() << std::endl;
std::cout << "Size of one pixel in bytes:" << type.byteCount() << std::endl;
std::cout << "Size of one pixel in bits: " << type.bitCount() << std::endl;
std::cout << "Scalar type of pixel: " << DataType::extractScalarType(type) << std::endl;
//Each type can provide its data range (i.e. unsigned 8 bits => min= 0 , max= 256 )
const Vector2d range = DataType::standardRange(type);
std::cout << "Type: Range (" << range[0] << ", " << range[1] << ")" << std::endl;
}

This can be very useful to allocate a memory buffer before reading a region of an imported ImageView. Here is a simple example:

DataType dtype = view->dataType();
RegionXu64 smallPart(VectorXu64{10, 10}, VectorXu64{5, 3});
size_t buffserSize = dtype->byteCount() * smallPart.elementCount()
std::vector<uint8_t> buffer(bufferSize);
// whatever given ImageView, now my buffer is correctly sized to read the region
...

Data Type Switch

You can need to handle views of any dataType, and you want to discriminate each one for specific treatments. Using switch statement is not possible to process each DataType values separately. You must use if statement as follow:

An user may need to branch the execution flow for different data types. As types are DataType instances you cannot use the switch statement and must use the if statement instead:

DataType dtype = view->dataType();
if(dtype == DataTypeId::INT64)
{
// do stuff
}
else if(dtype == DataTypeId::FLOAT)
{
// do stuff
}
else if(dtype == DataTypeId::VEC3_UINT32)
{
// do stuff
}
else
{
// do stuff
}

You can also build a decision tree, based on the different fields of the type. And you can use the switch statement. For example the following double switch is commonly used in IOLink's code:

switch (type.primitiveType()):
{
case PrimitiveTypeId::UNSIGNED_INTEGER:
switch(type.bitDepth())
{
case 8:
// do stuff
break;
case 16:
// do stuff
break;
default:
// do stuff
break;
}
break;
case PrimitiveTypeId::SIGNED_INTEGER:
switch(type.bitDepth())
{
case 8:
// do stuff
break;
case 16:
// do stuff
break;
default:
// do stuff
break;
}
break;
case PrimitiveTypeId::FLOATING_POINT:
switch(type.bitDepth())
{
case 32:
// do stuff
break;
case 64:
// do stuff
break;
default:
// do stuff
break;
}
break;
default:
// do stuff
break;
}

MultiImage

MultiImageView is another type of view, that stores an array of ImageView instances.

Capabilities

MultiImageView has two capabilities which you can check before accessing to its content:

  • READ to access to any ImageViews which it contains
  • WRITE to add or remove ImageViews in it
// check if multiImageView has READ capability
if(multiImgView->support(MultiImageCapability::READ)
{
// do stuff
}
// check if multiImageView has WRITE capability
if(multiImgView->support(MultiImageCapability::WRITE)
{
// do stuff
}

Create an empty multi image in memory

MultiImageViewFactory provides a method to create a memory managed MultiImageView. This instance will support all the MultiImageView capabilities.

std::shared_ptr<MultiImageView> multiImgView = MultiImageViewFactory::create();

Create a MultiImageView from a list of ImageView instances

Following code allows to directly create a MultiImageView filled with a list of ImageViews:

std::shared_ptr<ImageView> image1 = ...;
std::shared_ptr<ImageView> image2 = ...;
std::shared_ptr<ImageView> image3 = ...;
std::shared_ptr<MultiImageView> multiImageView = MultiImageViewFactory::create({image1, image2, image3});

Access MultiImage content

Internal frames are indexed from 0 to (frameCount - 1) like a C array.

// to retrieve count of internal frames (ImageViews)
uint64_t frameCount = multiImgView->frameCount();
// parse all frames
for(uint64_t idx = 0; idx < framecount ; idx++)
{
std::shared_ptr<ImageView> frame = multiImgView->frame(idx);
// do stuff
}

Modify MultiImageView content

multiImgView->addFrame(ImageViewFactory::allocate(VectorXu64{100, 200}, DataTypeId::UINT8));
multiImgView->addFrame(ImageViewFactory::allocate(VectorXu64{50, 500, 300}, DataTypeId::UINT32));
//remove second frame
multiImgView->removeFrame(1);

A MultiImageView can contain many ImageView instances of any shape, type, or dimensions. And each frame can have heterogeneous capabilities.

Stack MultiImageView content

There is a specific method in ImageViewFactory to stack all frames of a MultiImageView instance and transform it into an ImageView. The frames must fulfill some conditions, all their data type and shape must be the same for the stacking process to work.

The method is used as follows:

// stack the images to add a new dimension (added in last position)
std::shared_ptr<ImageView> stackedImg = ImageViewFactory::stack(multiImg);
// stack the images to add SLICE dimension
std::shared_ptr<ImageView> volume = ImageViewFactory::stack(multiImg, ImageDimension::SLICE);

The difference between stackedImg and volume in the example above is that the stackedImg dimension will lose its interpretation, as the interpretation of the new dimension is not given. In the volume case, the dimension SLICE is added, so as it is assumed that the MultiImageView stores a list of 2D images, the stacked result being a volume.

Channels

A channel is a part of your data. DataType element count field defines the number of channels of a RGB image for example. In this first example we say that channels are interlaced. An alternative representation can be an image whose data type has an element count of one, and a CHANNEL additionnal dimension. This second kind of image is said to have planar channels.

Extract a channel

You can extract a channel from an image using a method from ImageViewFactory:

// we have a view with a datatype whose channel count is 3
std::shared_ptr<ImageView> imgView3U16 = ImageViewFactory::allocate(VectorXu64{10, 15}, DataTypeId::VEC3_UINT16);
// extract first channel
std::shared_ptr<ImageView> chan0_ImgView = ImageViewFactory::extractChannel(imgView3U16, 0);
// extract second channel
std::shared_ptr<ImageView> chan1_ImgView = ImageViewFactory::extractChannel(imgView3U16, 1);
// extract third channel
std::shared_ptr<ImageView> chan2_ImgView = ImageViewFactory::extractChannel(imgView3U16, 2);

Deinterlace an ImageView

You can create a MultiImageView from an ImageView, with each frame being a channel from the original image. Due to this change of interface, some properties are lost though.

// we have a view with a datatype whose channel count is 3
std::shared_ptr<ImageView> imgView3U16 = ImageViewFactory::allocate(VectorXu64{10, 15}, DataTypeId::VEC3_UINT16);
std::shared_ptr<MultiImageView> deinterlaced_img = MultiImageViewFactory::deinterlace(imgView3U16);
// this will print '3' as frame count
std::cout << "Nb frames : "<< deinterlaced_img->frameCount() << std::endl;

Interlace a MultiImageView

Another factory allows you to create an interlaced ImageView from your MultiImageView if contained frames have the same shape, datatype, and are mono-channel:

// we have a MultiImageView with 3 frames whose datatype is scalar
DataType type = DataTypeId::INT16;
VectorXu64 shape{10, 15};
// create views with same shape and datatype
std::shared_ptr<ImageView> imgView = ImageViewFactory::allocate(shape, type);
std::shared_ptr<ImageView> imgView2 = ImageViewFactory::allocate(shape, type);
std::shared_ptr<ImageView> imgView3 = ImageViewFactory::allocate(shape, type);
std::shared_ptr<MultiImageView> multiImgView = MultiImageViewFactory::create();
multiImgView->addFrame(imgView1);
multiImgView->addFrame(imgView2);
multiImgView->addFrame(imgView3);
// interlace
std::shared_ptr<ImageView> interlaced_view = ImageViewFactory::interlace(multiImgView);
// this will print 'new datatype: VEC3_INT16'
std::cout << "new datatype: " << interlaced_view->dataType().toString() << std::endl;

Create a view from a user buffer

It is possible to create an ImageView that will use a user allocated memory buffer for its pixel data. This method is highly unsafe, and the memory allocation and deallocation is the user's responsability.

std::vector<uint8_t> buff(64);
// create an ImageView from a pointer toward raw image data (whose size is 64 bytes)
std::shared_ptr<ImageView> image = ImageViewFactory::fromBuffer(VectorXu64{8, 8}, DataTypeId.UINT8, buff, buff.size());

Re-order dimensions

In ImageView instances, IOLink uses a canonical order of dimensions for reading and writing.

Dimensions are always ordered as following:

  • COLUMN
  • ROW
  • SLICE
  • CHANNEL
  • SEQUENCE

Of course, some dimensions can be missing in your ImageView, depending on your data. But the order is always the same. For example a sequence of images will contain COLUMN, ROW, and SEQUENCE dimensions, in this order.

Data which are not initially created with IOLink, as shown in the previous chapter, may need to be re-ordered to follow this convention.

You can use a method from ImageViewFactory to change the dimension order of your data. This will indicate to IOLink how to access them properly.

std::shared_ptr<Imageview> viewRaw; // a view onto raw data. Dimensions are SLICE, COLUMN, ROW
// stuff here to fill viewRaw
auto iolink_imageView = ImageViewFactory::reinterpretAxes(viewRaw, {ImageDimension::SLICE, ImageDimension::COLUMN, ImageDimension::ROW});

In previous example, original view contains three dimensions: SLICE, COLUMN, ROW. This view does not follow IOLink order convention. After a call to appropriate method from ImageViewFactory, you get an ImageView which follows IOLink order: COLUMN, ROW, SLICE.

And now, as dimensions are identified and ordered with the IOLink convention, your ImageView axes interpretation field will return VOLUME.

Image Properties

In IOLink, for images, we distinguish two kinds of additional informations: properties and metadata.

Properties are specific to image views, and are considered as mandatory to correctly interpret the image content. These properties are either provided in the original image file, or set by the user, or to default values at initialization.

SpatialCalibrationProperty describes the position of the image in 3D space, giving all informations linked to the image's framework. It has the following fields:

fields type
origin() Vector3d
spacing() Vector3d
directions() SpacialDirections
unit() string

ImageInfoProperty is used to know what the image represents (a volume, a sequence, etc.) and how its elements should be interpreted.

fields type
interpretation() ImageInterpretation
hasAlpha() bool
axesRepresentation() ImageType
bitDepth() uint8_t
valueRange() Vector2d

Read Properties

Properties are readable by default.

Properties can be accessed by requesting the ImageProperties instance from an ImageView instance, and navigating until needed information:

auto imgView = ImageViewFactory::allocate(VectorXu64{100, 200}, DataTypeId::UINT16);
SpatialCalibrationProperty calib = imgView->properties()->calibration();
ImageInfoProperty imageInfo = imgView->properties()->imageInfo();

Or you can use shortcuts for a direct access to the needed information:

auto imgView = ImageViewFactory::allocate(VectorXu64{100, 200}, DataTypeId::UINT16);
// access to ImageInterpretation using shortcut
imgView.interpretation();

Modify Properties

You must have WRITE access to modify properties of an ImageView. In an ImageView, the modification of properties is designed to be transactional, which means whole properties shall normally be cloned so as to be modified and then re-assigned to the ImageView. The following code explains how to use this mechanism to modify an ImageView properties.

auto imgView = ImageViewFactory::allocate(VectorXu64{100, 200}, DataTypeId::UINT16);
auto properties = imgView->properties();
// Properties can be set if the image view supports write access
if (imgView->support(ImageCapability::WRITE))
{
// replace whole calibration object at once
SpatialCalibrationProperty newCalib(Vector3d{1.0, 3.0, 5.0}, Vector3d{2.0, 4.0, 6.0}, SpatialDirections(), "cm");
auto newProperties = ImageProperties::fromProperties(properties, newCalib);
// or change one field of calibration directly
// original properties are cloned and modified one by one
newProperties = properties->clone();
newProperties->calibration().setOrigin(Vector3d{ 1.0, 3.0, 5.0 });
newProperties->calibration().setSpacing(Vector3d{ 2.0, 4.0, 6.0 });
newProperties->calibration().setDirections(SpatialDirections());
newProperties->calibration().setUnit("cm");
imgView->setProperties(newProperties);
}

This syntax can seem complicated and restrictive, thus some shortcuts are present to help. But keep in mind that shortcuts for set operations trigger a transaction (see above code) with each call. Concerning in-memory ImageViews, this has no impact, but it can be an issue for some other implementations.

auto imgView = ImageViewFactory::allocate(VectorXu64{100, 200}, DataTypeId::UINT16);
// Properties can be set if the image view supports write access
if (imgView->support(ImageCapability::WRITE))
{
imgView.setSpatialOrigin(Vector3d{1.0, 3.0, 5.0});
imgView.setSpatialSpacing(Vector3d{2.0, 4.0, 6.0});
imgView.setSpatialDirections(SpatialDirections()); // default value for spatial directions
imgView.setSpatialUnit("cm");
}
std::shared_ptr<ImageView> image = ImageViewFactory::allocate(VectorXu64{12, 12}, DataTypeId::UINT8);
std::shared_ptr<MetadataNode> root = MetadataNodeFactory::create("root", nullptr);
root->addChild(MetadataNodeFactory::create("name", VariantDataValueFactory::create("John Doe")));
root->addChild(MetadataNodeFactory::create("age", VariantDataValueFactory::create(42)));
image->setMetadata(root);

Access to Metadata (from key)

auto root = image->metadata();
std::cout << "root children:" << root->childCount() << std::endl;
auto child0 = root->child("name");
auto child1 = root->child("age");
auto valraw_child0 = child0->value()->raw();
auto valraw_child1 = child1->value()->raw();
const std::string str_val_child0 = reinterpret_cast<const char* const*>(valraw_child0[0]);
const std::string str_val_child1 = reinterpret_cast<const char* const*>(valraw_child1[0]);
std::cout << "Name:" << str_val_child0 << std::endl;
std::cout << "Age:" << str_val_child1 << std::endl;

And you get:

root children: 2
Name: John Doe
Age: 42

As you can see, the conversion of raw metadata value can be tricky and involve some explicit reinterpretations. See next chapter to see how to simplify this task.

Interpret Metadata

VariantDataValueConverter is a helper available useful for the conversion of metadata values. If the metadata value is a number, you can directly cast it into any number type (if conversion is possible) or transform it into a string.

auto childAge = root->child("age");
// use converter to automatically cast the (number) value to desired type
uint32_t age = VariantDataValueConverter::toUint32(childAge->value());
float age_float = VariantDataValueConverter::toFloat(childAge->value());
double age_double = VariantDataValueConverter::toDouble(childAge->value());
std::string age_string = VariantDataValueConverter::toString(childAge->value());

Parse Metadata

You can also parse all metadata without knowing its content. The recursive way is the easiest one, but you can also parse metadata using the iterative way.

Here is an example of recursive method:

void parseNode(std::shared_ptr<MetadataNode> node)
{
// process current node here
for (auto child : *node)
{
parseNode(*child);
}
}

DataAccess conversion

As already explained, RandomAccess and StreamAccess are two different types of accessors, because they don't provide the same access method to data. The first one needs to have complete access to the whole accessor to jump to desired part at any moment. The second one allow sequential access to data, and is close in functionnalities to the classic streams, for example files of pipes.

There is a method from RandomAccessFactory to transform a StreamAccess into a RandomAccess, and also the opposite way.

Create a RandomAccess from a StreamAccess

Here is an example of conversion from StreamAccess into RandomAccess:

// you create a RandomAccess from your stream
std::shared_ptr<RandomAccess> RandomAccess = RandomAccessFactory::fromStream(stream);

Remark: given stream must have seek capability, otherwise the bridge would not be able to work.

Create a StreamAccess from a RandomAccess

Here is an example of conversion from RandomAccess into StreamAccess:

// you create a StreamAccess from you accessor
std::shared_ptr<StreamAccess> stream = StreamAccessFactory::fromRandomAccess(random);