ADTF stores recorded data to an *.adtfdat
file by default when using an ADTFDAT File Recorder.
Because there are plenty of reasons to do further (post)processing with this data, ADTF (or rather say the ADTF File Library)
offers a simple to use Processor
interface to extract streams from an *.adtfdat
files as well as a Reader
interface to import streams to *.adtfdat
files.
This tutorial will implement a Processor
capable of extracting images from an *.adtfdat
file which contains a
previously recorded video stream.
Set up a CMake file that is looking for the required Qt components because the processor uses the QImage
class
to build the concrete image from a memory block. Another dependency is the library for adtfdat processing itself.
Furthermore it gives the project a name (image_processor) and declares a header and source file for the new Processor
.
We set the target property attribute SUFFIX to .adtffileplugin
because this is what the ADTF DAT Tool accepts as additional plugins.
Let's create a new folder and add the following files:
# request CMake version and define project
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
project(image_processor)
# allow to link several 3rd party libraries to be able to build with any desired build type
set(CMAKE_MAP_IMPORTED_CONFIG_RELWITHDEBINFO RelWithDebInfo Release MinSizeRel "")
set(CMAKE_MAP_IMPORTED_CONFIG_RELEASE RelWithDebInfo Release MinSizeRel "")
set(CMAKE_MAP_IMPORTED_CONFIG_MINSIZEREL RelWithDebInfo Release MinSizeRel "")
# get our dependencies
find_package(Qt5 COMPONENTS Gui REQUIRED)
find_package(adtfdat_processing REQUIRED)
# create the processor library
add_library(image_processor MODULE
tutorial_image_processor.h
tutorial_image_processor.cpp
)
# link against our dependencies
target_link_libraries(image_processor Qt5::Gui adtfdat_processing)
# define our adtffileplugin to make it availabe for tooling
set_target_properties(image_processor PROPERTIES
PREFIX ""
SUFFIX ".adtffileplugin"
DEFINE_SYMBOL ""
DEBUG_POSTFIX "d"
)
# create a post build install step
install(TARGETS image_processor
DESTINATION bin
CONFIGURATIONS
Debug
Release
RelWithDebInfo
MinSizeRel
)
# finally we copy the required Qt libraries next to our binary
set(qt_dir ${Qt5_DIR}/../../../)
set(qt_libs Qt5Core Qt5Gui)
if (WIN32)
foreach(qt_lib IN LISTS qt_libs)
install(FILES ${qt_dir}/bin/${CMAKE_SHARED_LIBRARY_PREFIX}${qt_lib}$<$<CONFIG:Debug>:d>.dll DESTINATION bin)
endforeach()
else()
foreach(qt_lib IN LISTS qt_libs)
install(FILES ${qt_dir}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}${qt_lib}.so.${Qt5Core_VERSION} DESTINATION bin/lib)
install(FILES ${qt_dir}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}${qt_lib}.so.5 DESTINATION bin/lib)
endforeach()
endif()
Open the source folder which contains the CMakeLists.txt
file with CMake-GUI.
Each time you press configure, you will have to supply additional information:
lib/cmake/Qt5
directory of the installation folder of Qt
(e. g. ${QT_DIR}/lib/cmake/Qt5
)
${ADTF_DIR}/pkg/adtf_file/lib/cmake/adtfdat_processing
)
lib/cmake/a_util
directory where the a_util binaries are installed
(e. g. ${ADTF_DIR}/pkg/a_util/lib/cmake/a_util
)
cmake
directory where the ddl binaries are installed
(e. g. ${ADTF_DIR}/pkg/ddl/cmake
)
Each processor implements the adtf::dat::Processor
interface which defines the following methods:
getProcessorIdentifier()
isCompatible()
open()
process()
adtf::dat::Configurable
which adds support for settings.
setConfiguration()
getConfiguration()
#pragma once
#include <adtfdat_processing/processor.h>
#include <string>
#include <memory>
#include <QCoreApplication>
#include <QImage>
struct ImageFormat
{
QImage::Format format; // this is the appropriate Qt image format identifier
int pixel_byte_size; // the size of a pixel in bytes, required for QImage
bool rbg_flipped; // whether or not the RGB components need to flipped when writing the images.
};
class ImageFileWriter : public adtfdat_processing::SingleStreamProcessor
{
public:
ImageFileWriter();
std::string getProcessorIdentifier() const override;
bool isCompatible(const adtf_file::Stream& stream) const override;
void open(const adtf_file::Stream& stream, const std::string& destination_file_name) override;
void process(const adtf_file::FileItem& item) override;
private:
uint64_t _sample_counter = 0;
std::string _output_basename;
ImageFormat _format = {};
int _width = 0;
int _height = 0;
std::unique_ptr<QCoreApplication> _qt_app;
};
The constructor calls the setConfiguration()
method with the default values to initialize the settings,
output_basename
will define the format rule for the filenames of the extracted image files.
To make sure the Qt plugin for image types is loaded we initialize the Qt application inside the the constructor.
To implement the adtf::dat::Processor
interface we have to define an unique identifier for the new processor
which is returned by the interface method getProcessorIdentifier()
.
The next method we have to implement is isCompatible()
to check if the type of the given stream (from an *.adtfdat
file)
can be handeled by the processor. In this example we expect the type to be adtf/image
which is common for ADTF videos
in *.adtfdat
files.
Next we implement the open()
method where we do some basic initialization. Here we can have
a look at the first (meta) sample of the stream and find out what the dimensions of the contained images
are. At this point the settings are read out and the destination folder is created where the extracted images are saved to.
The process()
method gets each sample of the video stream and we can decide what we want to do with it.
The image processor employs a sample counter and extracts each containing image of the video stream to the specified
output folder following the rules for the filename defined within output_basename
. If required,
the RGB value has to be swapped, depending on the given format.
#include "tutorial_image_processor.h"
#include <adtf_file/stream_type.h>
#include <adtf_file/sample.h>
#include <QString>
// add our factory to the global adtf_file object list.
static adtf_file::PluginInitializer initializer([] {
adtf_file::getObjects().push_back(
std::make_shared<adtfdat_processing::ProcessorFactoryImplementation<ImageFileWriter>>());
});
// we use this little helper for mapping the received image format
// to valid QImage format
ImageFormat get_qimage_format(const std::string& format_name)
{
static std::map<std::string, ImageFormat> format_map
{
{ "R(8)G(8)B(8)(8)", {QImage::Format::Format_RGB32, 4, false}},
{ "B(8)G(8)R(8)(8)", {QImage::Format::Format_RGB32, 4, true}},
{ "R(5)G(5)B(5)(1)", {QImage::Format::Format_RGB555, 2, false}},
{ "R(8)G(8)B(8)", {QImage::Format::Format_RGB888, 3, false}},
{ "B(8)G(8)R(8)", {QImage::Format::Format_RGB888, 3, true}},
{ "A(8)R(8)G(8)B(8)", {QImage::Format::Format_ARGB32, 4, false}},
{ "R(8)G(8)B(8)A(8)", {QImage::Format::Format_RGBA8888, 4, false}},
{ "B(8)G(8)R(8)A(8)", {QImage::Format::Format_ARGB32, 4, true}},
{ "GREY(8)", {QImage::Format::Format_Grayscale8, 1, false}},
{ "GREY(16)", {QImage::Format::Format_RGB16, 2, false}},
};
auto supported_format = format_map.find(format_name);
if (supported_format == format_map.end())
{
throw std::runtime_error("Unsupported image format: " + format_name);
}
return supported_format->second;
}
ImageFileWriter::ImageFileWriter()
{
// create a qt application if there is none
if (!QCoreApplication::instance())
{
int dummy_argc = 0;
char** dummy_argv = new char*[0];
_qt_app = std::make_unique<QCoreApplication>(dummy_argc, dummy_argv);
}
// set all configuration options
setConfiguration(
{
{ "output_basename",{ "images_%04d.png" } },
});
}
std::string ImageFileWriter::getProcessorIdentifier() const
{
return "image_file_writer";
}
bool ImageFileWriter::isCompatible(const adtf_file::Stream& stream) const
{
// this processor can only handle valid adtf image streams
auto tmp = std::dynamic_pointer_cast<const adtf_file::PropertyStreamType>(stream.initial_type);
if (!tmp)
{
return false;
}
return tmp->getMetaType() == "adtf/image";
}
void ImageFileWriter::open(const adtf_file::Stream& stream, const std::string& destination_file_name)
{
// some checks regarding given output directory
if (destination_file_name.empty())
{
throw std::runtime_error("no output directory specified");
}
if (!a_util::filesystem::exists(destination_file_name))
{
a_util::filesystem::createDirectory(destination_file_name);
}
_output_basename = destination_file_name + "/"
+ adtf_file::getPropertyValue<std::string>(getConfiguration(), "output_basename");
// cast for stream type
auto property_type = std::dynamic_pointer_cast<const adtf_file::PropertyStreamType>(stream.initial_type);
if (!property_type)
{
throw std::runtime_error("The stream type of '" + stream.name + "' is not a property stream type");
}
// get image information and dimensions from the stream type
_format = get_qimage_format(property_type->getProperty("format_name").second);
_width = std::stoi(property_type->getProperty("pixel_width").second);
_height = std::stoi(property_type->getProperty("pixel_height").second);
}
void ImageFileWriter::process(const adtf_file::FileItem& item)
{
auto sample = std::dynamic_pointer_cast<const adtf_file::WriteSample>(item.stream_item);
if (sample)
{
auto buffer = sample->beginBufferRead();
// extract the image from the sample
QImage image(static_cast<const uchar*>(buffer.first),
_width,
_height,
_format.pixel_byte_size * _width,
_format.format);
if (_format.rbg_flipped)
{
image = image.rgbSwapped();
}
auto image_filename = QString::asprintf(_output_basename.c_str(), _sample_counter);
if (!image.save(image_filename))
{
throw std::runtime_error("Could not save image to " + image_filename.toStdString());
}
sample->endBufferRead();
++_sample_counter;
}
}
Next we build the following Visual Studio projects:
After the INSTALL step, there should be the binary and Qt depended libraries (as set up in CMake) in our install folder, dependend on the chosen build type:
image_processor(d).adtffileplugin
Qt5Core(d).dll
Qt5Gui(d).dll
Now we are ready to run the ADTF DAT Tool and use our image processor
plugin. The command line version of the tool expects the following parameters
when extracting images from a video stream of an *.adtfdat
file:
<ADTF_DIR>/bin/adtf_dattool.exe --plugin image_processor.adtffileplugin --export <ADTF_DIR>/src/examples/datfiles/example_file.adtfdat --stream VIDEO --output extracted_images
To change the output filenames extend the call with argument with the output_basename
property, e.g. --property output_basename=my_fancy_output_name_%d.png
Now you should have a detailed look and feeling for ADTF 3. It's time to create your own ADTF Components and use the Framework and Tools. This is currently the last page so go back to home overview.