IOFormat
IOF_v0.33.0_release
|
IOFormat uses a system of plugin for all its reading and writing features. Actually, IOFormat alone does nothing, it mainly acts as a manager that redirects the requests to the fitted plugin. There are two families of plugins:
Plugins are encapsulated in shared libraries that will be loaded at the initialization of IOFormat. Each shared library can declare up to one extractor plugin and up to one writer plugin. By default, they are searched in the folder "ioplugins", relative to the executable path. It can be manually set by using the IOFORMAT_PLUGINS_FOLDER
environnment variable.
See Plugins chapter for a list of plugins natively available in IOFormat package.
Each extractor plugin declares the formats that it supports, and an extension mapping to these formats. For example, the plugin "Jpeg" tells that it supports the "JPEG" format and maps the "jpeg" and "jpg" extension to it. Some plugins supports multiple format, so this can become more complicated.
At the initialization of IOFormat, all the extractor plugins are loaded in lexicographic order. Yet, some plugins, tagged as fallbacks by IOFormat, will always be loaded last. These "fallback" plugins are tagged as such mainly because they offer to handle many format, but may not have sufficient features or performances. The loading order is important because it will etablish the sequence in wich plugins are called for opening a view using a specific format.
The default plugin order can be overriden via a YAML file, using the IOFORMAT_CONFIG_FILE
environment variable. This file should describe a list for each format that should be customized. Formats not present in the configuration will be initialized as usual.
In this example, we use the config file to use ImageAccess first for "PNG" files, overriding its fallback behavior. For Tiff files, we force the use of Bioformat plugin. As ImageAccess suppports "TIFF" too, it will be used if Bioformat fails. All other formats will be initialized as in the default behaviour.
The plugin order initialization, and the resulting format mapping can easily be viewed in the logs, at the LOG_INFO
level. Here is an example of such a log:
Writers plugins behave a lot like extractors ones, but they are not affected by the configuration file described before. The is currently no way to change the way each writer plugin is associated with a format.
Custom plugins can be written in C++ only.
If you want to create your own Extractor or Writer plugin, you can by following the API contained in IOPluginAPI project:
IOPlugin APIs are versioned and each version of IOFormat is only compatible with one version of IOPLugin. All plugins developed with a different IOPlugin version of the one used by IOFormat won't be loaded.
See configuration to know the IOPLugin version compatible with this version of IOFormat.
Firstly you must add the IOPlugin Conan package to your plugin dependencies, so you would have the includes and logging libaries. You must link against this package using the classic Conan way. If you want your plugin to be used in IOFormat, it must be declared as a SHARED
library in CMake.
An IOFormat plugin must implement the matching plugin interface: ExtractorPlugin
or WriterPlugin
, depending on the type of plugin you want to make. When the plugin class is done, you must use one of the plugins macros in order to make the plugin usable by IOFormat's plugin system. These macros depend on the plugin type:
IOPLUGIN_DECLARE_EXTRACTOR_PLUGIN(PluginClass)
for Extractor plugins.IOPLUGIN_DECLARE_WRITER_PLUGIN(PluginClass)
for Writer plugins.The plugin class should be done in a source file (*.cpp or *.cxx) instead of a header file, because we only want to export the instance.
Example for an extractor plugin:
TestPlugin, in this example, is the entry point for the extractor plugin. It's up to this class methods openDataAccess
and openFile
to instanciate and return the appropriate view according to the format of the opened file.
Have a look on the code example provided with IOPLuginAPI.
e.g.: calling openDataAccess
onto PNG plugin will require the instanciation of an object derived from ImageView
, since PNG is only able to manage 2D Images. On the other hand, a TIFF plugin could possibly be either an object derived from ImageView
or from MultiImageView
, depending of the file content (a Tiff can contain either a volume, thus an ImageView, or a list of independant data, thus a MultiImageView
)
There are some guidelines common to the two types of plugins:
api
method, it is already returning the API version of the IOplugin version you are using to create your plugin.initLogger
will do nothing, and logLevel
will always return LogLevel::OFF
.There are two different View
factories in the ExtractorPlugin
interface that you can implement in your custom plugin. First, openFile
that take a path to the source file, and openDataAccess
that take a DataAccess
from which to decode from. The openDataAccess
variant also take a format
argument that enable to select the format to use to decode the accessor's data. This format is automatically selected using the path's extension in the openFile
variant.
When one of these variants is not supported by your custom plugin, just make the method throw an Error
describing that the feature is not supported.
For both cases, a serie of checks and operations should be done immediately:
format
string parameter to adapt your extraction method to the current situation (i.e. when a plugin handles many different formats)path
or dataAccess
are valid (data must exist and be available for reading and most of the time for seeking in the case of streams)View
that your plugin should create and return:properties
immediately (you should better extract these information as soon as possible, and not on the fly
(when properties()
method is called), for threadsafety reasons.on the fly
which could slow down the user application).If any of these checks/operations fails, an exception will be raised.
Below table should help you to determine the kind of (existing) View
to return:
View type | case where this type of view should be created |
---|---|
ImageView | data extracted by your plugin can be stored into a N-dimension array (e.g a 2D Image, a volume, a sequence of image...) |
MultiImageView | data extracted by your plugin corresponds to a set of images which may or may not have relation with each other (shape and type can be different) |
LodImageView | data extracted by your plugin corresponds to one multi-resolution image (many different resolutions of the same N-Dimension image) |
ComposedView | data extracted by your plugins are of miscellaneous types, which can be stored in a hierarchical order or not. (e.g. a zip file) |
Up to you to return the view type corresponding the better to extracted data and which matches your needs.
There are two Writer
factories in the WriterPlugin
interface, all taking the source View
as the first argument. The openFile
one uses a path to specify the writer's output, and will infer the format to use from the file's extension. The openDataAccess
variant use a DataAccess
as output with its format specified with a format
parameter.
When one of these variant is not supported by your custom plugin, just make the method throw an Error
describing that the feature is not supported.
Ideally, your custom writer should support all kind of View
as entry, as long as it fits the underlying format. For example a plugin encoding to an image format could take some ImageView
as input. In the image case, the underlying usually support one particular data layout, but a good plugin should accept different layouts, as long as they are easily adaptable, without damaging the content and without guessing the user intentions. For example, when a plugin uses a format that stores color data in an interleaved order, if the user gives a planar ImageView
(one with a CHANNEL
dimension) as input, it should adapt it, instead of throwing an error, saying that the view is unsupported. Another example is MultiImageView
instances, that can be stacked to feed a volume or sequence format, if it is possible.
As long as there is one possibility to adapt the data to match the writer inputs, the plugin should try. But at the moment when there is more than one possibility to adapt the data, the plugin shall raise an exception to ask the user to make a decision.
In order to provide better features and performances, writers created by your custom plugin should not write all the output in one call. An approach using chunked write calls should be preferred. It will avoid hangs-on and blocking operations when using non-filesystem accessors.