BTK
0.3dev.0
Open-source library to visualize/process biomechanical data
|
The simplest way to integrate BTK in your programs is to use the file FindBTK.cmake
included with the source code in the CMake
folder or in the subfolder share/CMake
if you use a binary release. This file will be used by CMake to find the include path, the libraries and all resources required to used BTK as an external library in you program.
For example, the file CMakeLists.txt
used to compile the example AcquisitionConverted
(see the folder /Examples/AcquisitionConverter
in the sources) can be easily modify to be built as an external project and integrating BTK.
Old CMakeLists.txt
file used to build the example AcquisitionConverter
when compiling BTK:
New CMakeLists.txt
file to build AcquisitionConverter
as an independent project:
To simplify the way to code and manage the memory used by BTK, it is proposed to use shared pointer. A shared pointer is like a pointer but without owner. Its content is shared by all the variables assigned to the shared pointer and will be deleted when no variable use it anymore (meaning, the memory is freed automatically).
To do that the most transparent as possible, each class in BTK has the type definition Pointer
and the static method(s) New
. So you cannot instantiate an object by using a constructor but by using one of the New
methods proposed in each class with return a Pointer
(i.e. a shared pointer). So, to create a object btk::Acquisition (or strictly speaking a shared pointer associated with a btk::Acquisition object), you can use the following code:
By using this code, you create a new shared pointer assigned to the variable acq
. If you use a second variable and assign it to the first one, then the modification done by one is reflected in the other (as a regular pointer).
The main difference compared to a regular pointer is when you use the delete
function (or used implicitly in the class' destructor). If you delete the content of acq
, then you can use again the variable foo
to access/modify the acquisition. In the case of a regular pointer, this behavior is not possible and will crash your application.
The only way to copy (or more exactly to create a deep copy) of the object is to use the method Clone
.
This is the same thing when you use a shared pointer as an input/output variable in a function. You do not need to manage the memory of the shared pointer or think about the use of a reference.
The library BTK can read a dozen of acquisition files with the most popular, the C3D file format. All the known file format read and written in BTK are presented in this page.
To read an acquisition file you need to use the class btk::AcquisitionFileReader. This class, embedded in the module BTKIO, is a process with one output: the read acquisition.
In the previous example, the selection of the file format is done automatically by checking the file's content and/or the file's extension. But, you can also force the file format to use. For that, you have to use the method AcquisitionFileReader::SetAcquisitionIO and give it the chosen file I/O.
All the IO classes are listed in the page Read and written files in BTK
An acquisition (btk::Acquisition) is mostly a generic container to store points (btk::Point), analog channels (btk::Analog), events (btk::Event) and metadata (btk::MetaData). You can easily construct a new acquisition or load it from an acquisition file as shown in the above examples.
The others informations stored in an acquisition are the following:
Note: In BTK, the term "point" means all data with 3 components along the time. So, a point can be also a force, a moment, a power, etc. Check btk::Point::GetType() to know the point's type.
In an acquisition, points are stored in a btk::PointCollection::Pointer object. To access to this collection, you can use the method btk::Acquisition::GetPoints.
To access to a point and its content, you can use the methods btk::Acquisition::GetPoint() which return a btk::Point::Pointer object based on the given index or label. If you give an invalid index or an invalid point's label, an exception (btk::OutOfRangeException) will be thrown.
Another way to access to the content of a specific point when you don't know it exist or not in the acquisition is to use the method btk::Acquisition::FindPoint(). Given a point's label, this method returns an iterator associated to the found point. In the case the point is not found, the iterator will point to the end of the list of points.
When you know the iterator is valid, the best way to access to the content of the point is to use the following code (*itP)->
. By using the code *itP
. or itP->
, you will access only to the methods of the shared pointer object (and not the pointed object by the shared pointer). This (little) drawback is due to the storage of pointers in the list instead of objects.
The use of an iterator has also an other main advantage. It gives you the possibility to access to all the point by iterating it. This gives you a simple and fast way to access to the points' data. To do this, you can use the methods btk::Acquisition::BeginPoint() and btk::Acquisition::EndPoint().
The code above, iterate in a for
loop, the const iterator it
from the first point (it = acq->BeginPoint()
) to the last one (it != acq->EndPoint()
), one by one (++it
). The use of an Iterator
or a ConstIterator
depends of you need. An Iterator
gives you the possibility to modify the associated object (see Modifying acquisition's data), while a ConstIterator
is a read-only iterator which is enough to access to the points' data.
In an acquisition, analog channels are stored in a btk::AnalogCollection::Pointer object. To access to this collection, you can use the method btk::Acquisition::GetAnalogs.
The way to access to the analog channels is similar to the points. you can use the method btk::Acquisition::GetAnalog() or an iterator (btk::Acquisition::AnalogIterator, btk::Acquisition::AnalogConstIterator with the methods btk::Acquisition::BeginAnalog() and btk::Acquisition::EndAnalog()). You can also use the method btk::Acquisition::FindAnalog() if you don't know if the analog exists in the acquisition.
In an acquisition, events are stored in a btk::EventCollection::Pointer object. To access to this collection, you can use the method btk::Acquisition::GetEvents.
As the points and the analog channels, you can access to the events stored in an acquisition by using the method btk::Acquisition::GetEvent() or an iterator (btk::Acquisition::EventIterator, btk::Acquisition::EventConstIterator with the methods btk::Acquisition::BeginEvent() and btk::Acquisition::EndEvent()). You can also use the method btk::Acquisition::FindEvent() if you don't know if the event exists in the acquisition.
A metadata is a generic container to store the acquisition configuration. Or said differently, a metadata contains every informations which cannot be set into markers' trajectories nor analog channels' measures, nor events. For example, if you want to include subject's informations (sex, weight, height, ...) or force platform's configuration (corner's coordinates, analog channel used, ...), then the metadata are the right place to do this. In the C3D file format, the metadata are known as groups and parameters.
To access to the acquisition's metadata, you have to use the method btk::Acquisition::GetMetaData which will return the root of the metadata. From this point, you have to use the methods of the class btk::MetaData. In the case where you want to access to the informations of a metadata, you will use the method btk::MetaData::GetInfo. This method returns a btk::MetaDataInfo::Pointer object which contains the values associated with the metadata.
For example, if you want to access to the value of the metadata SUBJECTS:WEIGHT, you can use the following code:
In the code above, the metadata WEIGHT is the child of the metadata SUBJECTS which is at the root of the metadata in the acquisition. It contains informations and at least one real value inside it.
Even if this code is the most direct way to access to a metadata and its information, it can thrown an exception if the metadata doesn't exist, or worst, can crash your app if there is no info (in this case, the method btk::MetaData::GetInfo returns an empty pointer). You can also extract the wrong (or corrupted) value, if this metadata is not used to store the weight as a real but as a string. The safe way to do this is the following:
This safe code can be summarized by the method btk::MetaData::ExtractChildInfo(). The corresponding code is the following.
Starting from an acquisition you can easily modify it by, for example, removing points, adding events, resizing the number of frames, populates points or analogs values, etc. Lots of these actions are available by using the methods of the class btk::Acquisition.
All the classes contains methods to modify their members. It is easy to extract a point from an acquisition and then set its label or description. This is the same for every object (including analogs channels, events or metadata).
In some case, these methods are not enough due to the complexity of the class. For example with the metadata, lots of methods were added to facilitate their creation with values (single or multiple values). It is the same if you want to extract them in a particular format (integer, float, double or string).
Finally, in some case, you want to add children in the metadata but the requirement to check if they exist pollute your code, then you can use the functions btk::MetaDataCreateChild. These functions declared in the file "btkMetaDataUtils.h" do for you the steps required to include a children in a metadata.
Note: Even, if you can create an infinite level of metadata, it is strongly advised to only use 2 levels of metadata. The first one corresponds to groups (without informations) and the second one to parameters. This use was introduced in the C3D file format.
Saving an acquisition is as simple as reading one. If you want to save a modified acquisition or would like to convert a format into another one, you simply have to create an btk::AcquisitionFileWriter object, plug the acquisition in the input, set the filename and update the process.
The choice of the format for the saving is based on the extension of the filename. But you can also set an AcquisitionIO as with the reader.
The library BTK offers a mechanism of pipelines to easily process an acquisition or one of these parts (points, analogs channels, etc.). A pipeline is defined as a collection of processes linked together by their input(s) and output(s). You can create loops, branches or any more complex workflow.
To link the output(s) and input(s) of processes, you have just to use the methods SetInput()
and GetOutput()
. Check the documentation of each class for more information.
When your pipeline is created, you need only to update the latest level of processes by using the method Update()
. This process will check if all of these inputs are updated before to use them to generate its output. If these inputs are not updated, then the previous processes will be updated to be sure to use valid inputs.
Note: You can also update a process by using its output (if it has one). Use its method Update()
, and it will update its parent's process (can be useful when you prefer to store inputs/outputs instead of process).
As a simple example, you can create a file converter.
The following code is based on the example AcquisitionConverter
. Depending the given inputs, this code try to convert/save an acquisition file into another one. With this code, you can transform a C3D to a TRC file, or the reverse, etc.
A more complex example, is the possibility to create a merger of acquisitions files, convert their units and write the new acquisition in a C3D file.
To create a such pipeline, we need to declare each process (readers, merger, converter and writer), link them and update the pipeline to generate the result.