IOLink
IOL_v1.1.0_release
|
IOLink is quite rich of functionalities to convert/transform/create views, and it should increase in further versions.
This chapter contains specific cases that we describe. It cannot be complete, of course, but should be upgraded case by case.
For more readability, cases are sorted by topic.
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 your View into the appropriate derived object:
In the case you receive a View object, this view can point toward any kind of data. You must downcast this object before being able to access to its content.
IOLink provides helpers to do this operation. These helpers are called Providers. Here is an example of use of ImageViewProvider:
Each kind of View has potentially several capabilities.
For example, an ImageView can be Readable, Writable, Reshapable, Memory, etc. Each of these are listed in the ImageCapability enumeration.
If you use a method linked to a capability that the current ImageView instance does not support, an exception will be thrown.
To check if an ImageView support a capability, you have to use the ImageCapabilitySet instance returned as this:
You also have the possibility to restrict view capabilities by using following helpers:
Region type is a basic type which represents an area (defined by its origin and size). For images, origin and size unit is pixel.
An helper is available to easily create a full region from a view and another one helps to retrieve the size (in pixels for images) of a region:
You will get following result:
But if you want to access a smaller region, you can define your own region:
This time, you will get following result:
When your Region is defined, you can access to data through a method from ImageView:
You have an ImageView and you want to create another view onto a small part of the same image. This can be helpful to isolate a part of a big image and transmit it to a third party, for example.
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.
You will get:
This time, let's say you have a volume (ImageView with 3 dimensions (W,H,D)), and you want to isolate a extractSliceFromAxis of this volume. Solution is to use Region view with the previous section explainations.
Finally, you will get another volume, this time with a flat dimension.
Example: From your volume, you want to isolate the Nth slice from the depth dimension. You will create a region view as follow:
But this new sliceView has now a shape {W, H, 1}. Which means you still have a 3-dimensional ImageView, with a flat last dimension. The issue is even more complicated if you try to isolate a extractSliceFromAxis in other dimensions. Your view would have the shape {1, H, D} or {W, 1, D}.
So, if you wish to handle your extractSliceFromAxis as a 2-dimensional image, you have the possibility to pack the region as follow:
The flat dimension (size = 1) will be removed, and dimensions will be shifted if necessary to obtain an ImageView with 2 consecutives dimensions.
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, dimension, type of scalar, 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. VEC4_UINT8 allows to store RGBA8888 pixel values. etc...
Customer is free to create new types using the following mechanism:
At runtime, this mechanism allows users to know in detail the pixel definition of images which they handle.
This can be very usefull to size a memory buffer before reading a region of an imported ImageView. Here is a simple example:
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:
The following variant is also mainly used, and should be preffered if you only care for the DataType's dimension and bit depth:
MultiImageView is a type of View (as ImageView) which allows to store many ImageViews.
Following code shows how to create a MultiImageView with Read/Write capabilities.
These capabilities allow to read MultiImageView content and also modify it (by removing or adding new sub-images)
Following code allows to directly create a MultiImageView filled with a list of ImageViews:
MultiImageView has two capabilities which you can check before accessing to its content:
READ
to access to any ImageViews which it containsWRITE
to add or remove ImageViews in itInternal frames are indexed from 0 to (frameCount - 1) like a C array.
A MultiImageView can contain many ImageView of any shape, type or dimension. And each frame (internal ImageView) can have heterogenous capabilities.
A MultiImageView content can be stacked.
In first stack method call, the dimension to add is not given. A new dimension will be added, but no information will be available in dimension info properties to indicate the type of the result image.
In second stack method call, the dimension to add is given. This dimension shall not already exist in dimension info properties of the stacked frames. If the stacked images had a known ImageType
in dimension info properties, this information will be updated with the added dimension.
Region type is a basic type which represents an area (defined by its origin and size). For images, origin and size unit is pixel.
An helper is available to easily create a full region from a view. Another one help to retrieve the size (in pixels for images) of a region.
You will get following result:
But if you want to access a smaller region, you can define your own region:
This time, you will get following result:
When your Region is defined, you can access to data through the read region
method:
A channel is one component of your pixel. Your pixel is defined according to the Data Type of your view.
A factory allows you to extract an ImageView from another one, which will only contain a specific channel (RED, GREEN, ALPHA, ....).
Another factory allows you to create a MultiImageView from your deinterlaced ImageView:
Another factory allows you to create an interlaced ImageView from your MultiImageView if contained frames have same shape, datatype, and are mono-channel:
You can have to handle raw image data, which means any DataAccess containing un-encoded image data (no header, no metadata, no compression). This case is particular because it is the only one that IOLink can directly manage.
Following code shows how to create an readable Imageview from a pointer of data.
An ImageView can be created from a RandomAccess using the appropriate method from ImageViewFactory. As a RandomAccess describes only an array of bytes, you must pass shape and datatype information to this method.
An example of an ImageView creation from a file using a RandomAccess:
In ImageView, IOLink uses a canonical order of dimensions for reading and writing.
Dimensions are always ordered as following:
Of course, some dimensions can be missing in your ImageView, depending on your data. But the order is always the same.
(i.e. a sequence of image will contain dimensions COLUMN, ROW and SEQUENCE. In this order)
Data which are not initially created with IOLink, as show in the previous chapter, may need to be re-ordered to follow this convention.
You can use a method from ImageViewFactory to indicate the dimension order of your data. This will indicate to IOLink how to access them properly.
In previous example, original view contains 3 dimensions (SLICE, COLUMN, ROW). This view does not follow IOLink order. After a call to appropriate method from ImageViewFactory, you get a View which follows IOLink order: COLUMN, ROW, SLICE.
And now, as dimensions are identified and ordered with IOLink convention, your ImageView's ImageType will be recognized as an IMAGE_SEQUENCE.
In IOLink, for images, we distinguish 2 kinds of additional information:
Metadata are all the informations stored with the image, either from a file, or added by the user. It can be anything, these informations are provided in the view through a tree of key/data peer.
Properties are specific to the "image" view type , and are considered as mandatory to correctly interpret the image content. These properties are either provided in the original image file (as Metadata), or set by the user or to default values at initialization.
Spatial Calibration properties (see SpatialCalibrationProperty):
This property describes the position of the image in space, by giving all attributes of the local axes of the image's referential.
fields | type |
---|---|
origin() | Vector3d |
spacing() | Vector3d |
directions() | SpacialDirections |
unit() | string |
Image Info properties (see ImageInfoProperty):
This property is used to know what the image represents (a volume, a sequence...) and how its pixels (voxels, elements) should be interpreted.
fields | type |
---|---|
interpretation() | ImageInterpretation |
hasAlpha() | bool |
axesRepresentation() | ImageType |
bitDepth() | uint8_t |
valueRange() | Vector2d |
Properties and Metadata are readable by default.
You must have WRITE access to properties.
No default metadata is set at ImageView creation. The user can add them on the fly. Or he can directly access to metadata extracted from an image file.
Classes to create and/or handle Metadata are:
And you get:
As you see, conversion of raw metadata value can be tricky and involve some explicit reinterpretations. See next chapter to see how to simplify this task.
A converter is available for basic conversion of metadata. See VariantDataValueConverter. If metadata value is a numerical, you can directly cast it into any numerical type (if conversion is possible) or transform it into String.
You can also parse all Metadata without knowing its content.
The recursive way is the easiest one, but you can also parse Metadata by the iterative way.
Here is an example of recursive method:
If you haven't read the Storage concept part, I would recommend you to have a look on it for basic informations.
As already explained, RandomAccess and StreamAccess are two different types of accessor, because they don't provide the same access method to data. First one needs to have the full access to whole accessor to jump to desired part at any moment. Second one does not allow to always access to any part of data, and not directly either, but is more convenient for data loading (since you don't necessary need to entirely load the data)
There is a method from RandomAccessFactory to transform StreamAccess into RandomAccess, and another one for the opposite.
Here is an example of conversion from StreamAccess into RandomAccess:
Remark: given stream must have seek capability, otherwise RandomAccess cannot be created
Here is an example of conversion from RandomAccess into StreamAccess:
You can have to handle raw image data, which means any accessor containing un-encoded image data (no header, no metadata, no compression). This case is particular because it is the only one that IOLink can directly manage.
To consider raw data as an image, you have the possibility to create an ImageView from a RandomAccess using ImageViewFactory. You just have to define the shape and data type to use at creation.
Following code shows how to create an readable Imageview from a pointer of data.
If you want to create an ImageView, this time, onto raw data coming from a file, you must create a RandomAccess onto these file data. Of course, you must know in advance the shape and data type of your image.