diff --git a/core/src/CoreInstance.cpp b/core/src/CoreInstance.cpp index a8a840d977..c43882dd9f 100644 --- a/core/src/CoreInstance.cpp +++ b/core/src/CoreInstance.cpp @@ -3236,6 +3236,9 @@ void megamol::core::CoreInstance::loadPlugin( } catch (const vislib::Exception& vex) { megamol::core::utility::log::Log::DefaultLog.WriteMsg( loadFailedLevel, "Unable to load Plugin: %s (%s, &d)", vex.GetMsgA(), vex.GetFile(), vex.GetLine()); + } catch (const std::exception& ex) { + megamol::core::utility::log::Log::DefaultLog.WriteMsg( + loadFailedLevel, "Unable to load Plugin: %s", ex.what()); } catch (...) { megamol::core::utility::log::Log::DefaultLog.WriteMsg( loadFailedLevel, "Unable to load Plugin: unknown exception"); diff --git a/core/src/MegaMolGraph.cpp b/core/src/MegaMolGraph.cpp index ffb717bda6..ebea64af0e 100644 --- a/core/src/MegaMolGraph.cpp +++ b/core/src/MegaMolGraph.cpp @@ -600,7 +600,7 @@ bool megamol::core::MegaMolGraph::add_call(CallInstantiationRequest_t const& req if (!slots.empty()) { slot_names = ""; for (auto x = 0; x < slots.size() - 1; ++x) { - slot_names += slots[x]->Name(); + slot_names += slots[x]->Name() + ", "; } slot_names += slots[slots.size() - 1]->Name(); } @@ -620,7 +620,7 @@ bool megamol::core::MegaMolGraph::add_call(CallInstantiationRequest_t const& req if (!slots.empty()) { slot_names = ""; for (auto x = 0; x < slots.size() - 1; ++x) { - slot_names += slots[x]->Name(); + slot_names += slots[x]->Name() + ", "; } slot_names += slots[slots.size() - 1]->Name(); } diff --git a/plugins/datatools/include/datatools/io/dataformat/CSVDataFormat.h b/plugins/datatools/include/datatools/io/dataformat/CSVDataFormat.h new file mode 100644 index 0000000000..b6a628de0d --- /dev/null +++ b/plugins/datatools/include/datatools/io/dataformat/CSVDataFormat.h @@ -0,0 +1,39 @@ +#pragma once + +#include "DataFormat.h" + +namespace megamol { +namespace datatools { +namespace io { +namespace dataformat { + +struct CSVColumnInfo { + std::string name; + // TODO +}; + +struct CSVFrame : AbstractFrame { + using FrameIndexType = uint32_t; + using DataIndexType = uint32_t; + + FrameIndexType FrameIndex = 0; + DataIndexType NumColumns = 0; + DataIndexType NumRows = 0; + std::vector ColumnInfos; + std::vector Values; + + //bool Read(std::ifstream& io) override { + // return true; + //} + //bool Write(std::ofstream& io) override { + // return true; + //} +}; + +using CSVDataFormat = AbstractDataFormat; +using CSVFileCollection = FolderContainer; + +} // namespace dataformat +} // namespace io +} // namespace datatools +} // namespace megamol diff --git a/plugins/datatools/include/datatools/io/dataformat/DataFormat.h b/plugins/datatools/include/datatools/io/dataformat/DataFormat.h new file mode 100644 index 0000000000..38ad6f8dd1 --- /dev/null +++ b/plugins/datatools/include/datatools/io/dataformat/DataFormat.h @@ -0,0 +1,170 @@ +#pragma once + +#include +#include +#include +#include + +#include "LRUCache.h" + +namespace megamol { +namespace datatools { +namespace io { +namespace dataformat { + +// every call ideally should implement this, as a generic interface for "brain dumps" +// this has *nothing* to do with proper file formats. readers support these to provide data via the said call +struct AbstractFrame { + virtual ~AbstractFrame() = default; + virtual void Read(std::istream& io) = 0; + virtual void Write(std::ostream& io) const = 0; + [[nodiscard]] virtual std::size_t ByteSize() const = 0; +}; + +struct AbstractMetadata { + using FrameIndexType = uint32_t; +}; + +struct AbstractNaming { + virtual ~AbstractNaming() = default; + virtual std::regex Pattern() = 0; +}; + +template +struct BaseNumbering { + virtual ~BaseNumbering() = default; + + BaseNumbering(uint8_t digits = 5) : digits(digits) { + reg = std::regex(std::string("(\d{") + std::to_string(digits) + "})"); + } + + virtual FrameIndexType ExtractNumber(std::string filename) { + std::smatch matches; + if (std::regex_match(filename, matches, reg)) { + return std::stoi(matches[0].str()); + } + return static_cast(0); + } + + virtual std::string MakeFileName(std::string prefix, FrameIndexType idx) { + std::stringstream str; + str << std::setfill('0') << std::setw(digits) << idx; + return prefix + str.str(); + } + +private: + std::regex reg; + uint8_t digits; +}; + +template +class AbstractDataFormat { +public: + virtual ~AbstractDataFormat() = default; + using FrameType = Frame; + using FileType = std::filesystem::directory_entry; + + //virtual std::unique_ptr ReadFrame(std::ifstream& io, FrameIndexType idx) = 0; + //virtual void WriteFrame(std::ofstream& io, Frame const& frame) = 0; +}; + +// TODO read-ahead number +template +class AbstractDataContainer { +public: + using FormatType = Format; + using FrameType = typename Format::FrameType; + using FrameIndexType = typename FrameType::FrameIndexType; + using FrameCollection = LRUCache; + + AbstractDataContainer(FrameIndexType readAhead = 3) + : readAhead(readAhead) + , frames(LRUCache()) { + } + + virtual ~AbstractDataContainer() = default; + + // TODO generic EnumerateFrames that only files the frame indices with empty frames for now? + virtual FrameIndexType EnumerateFrames() = 0; + + std::shared_ptr ReadFrame(FrameIndexType idx) { + // here we should actually grab the frames from some background thread that reads ahead and stuff? + return frames.findOrCreate(idx, *this); + } + + void WriteFrame(FrameIndexType idx, FrameType const& frame) {} + + virtual std::ifstream IndexToIStream(FrameIndexType idx) = 0; + virtual std::ofstream IndexToOStream(FrameIndexType idx) = 0; +protected: + FrameIndexType readAhead; + FrameCollection frames; +}; + +// A directory containing several files, one for each frame +template +class FolderContainer : public AbstractDataContainer { + // this container supports arbitrary insertion and appending +public: + using FileType = typename Format::FileType; + using FileListType = std::vector; + using FrameIndexType = typename Format::FrameIndexType; + + // TODO fix basenumbering + + FolderContainer(std::string location, std::unique_ptr naming, + std::unique_ptr> numbering = std::make_unique>()) + : files(EnumerateFramesInDirectory(FileType(location))) + , naming(std::move(naming)) + , numbering(std::move(numbering)) { + } + + FrameIndexType EnumerateFrames() override { + return static_cast(files.size()); + } + + FileListType EnumerateFramesInDirectory(FileType Path) { + auto r = std::regex(naming->Pattern()); + FileListType fileList; + for (const auto& entry : std::filesystem::directory_iterator(Path)) { + if (std::regex_match(entry.path().filename().string(), r)) { + fileList.push_back(entry); + } + } + std::sort(fileList.begin(), fileList.end()); + return fileList; + } + + std::ifstream IndexToIStream(FrameIndexType idx) override { + return std::ifstream(files[idx].path().string().c_str(), std::ifstream::binary); + } + std::ofstream IndexToOStream(FrameIndexType idx) override { + // TODO some code for making paths when idx > what we already had + // TODO what about sparse stuff, i.e. when the numbers were not consecutive? + // TODO the clang dangling pointer + auto filename = files[idx].path().string().c_str(); + return std::ofstream(filename, std::ifstream::binary); + } + +private: + FileListType files; + std::unique_ptr naming; + std::unique_ptr> numbering; +}; + +// One big blob of data, each frame sitting at some offset +template +class BlobContainer : public AbstractDataContainer { + // TODO this container does not support frame insertion. it should support frame appending. + // totally TODO actually + bool Open(std::string location) { + return true; + } +}; + +// some other containers...? + +} // namespace dataformat +} // namespace io +} // namespace datatools +} // namespace megamol diff --git a/plugins/datatools/include/datatools/io/dataformat/ImageCalls.h b/plugins/datatools/include/datatools/io/dataformat/ImageCalls.h new file mode 100644 index 0000000000..fe21e9325a --- /dev/null +++ b/plugins/datatools/include/datatools/io/dataformat/ImageCalls.h @@ -0,0 +1,319 @@ +#pragma once + +#include "DataFormat.h" +#include "ImageElementType.h" + +namespace megamol { +namespace datatools { +namespace io { +namespace dataformat { + +// do we want to pull out an interface? what kind of code do we write against this? +// which variants do we want to handle dynamically? different channel types probably. dimensionality is unlikely. +// different numbers of components? + +// TODO: get rid of NumComponents. There is only one, keep multiple images if you want more channels. +// exception: RGBA8 as UINT32 ? Does it matter? + +// typename ChannelType does not work, that is not a static decision. +// For some data sources you only know after actually opening the file what's in it. +// TODO: volume ist ein byte-vektor und wir benutzen span. fertig. +template +struct ImageChannel { + static_assert(Dimensions > 0 && Dimensions < 4, "ImageChannel supports 1D, 2D, and 3D Images only"); + + using SizeType = uint32_t; + + void Read(std::istream& io) { + uint8_t dims, chanTypes; + io.read(reinterpret_cast(&dims), sizeof(uint8_t)); + if (dims != Dimensions) + throw std::invalid_argument("ImageChannel::Read: inappropriate number of dimensions in ImageChannel dump!"); + io.read(reinterpret_cast(&chanTypes), sizeof(uint8_t)); + elementType.Set(chanTypes); + io.read(reinterpret_cast(&this->width), sizeof(SizeType)); + io.read(reinterpret_cast(&this->height), sizeof(SizeType)); + io.read(reinterpret_cast(&this->depth), sizeof(SizeType)); + data.resize(static_cast(width) * height * depth); + io.read(reinterpret_cast(this->data.data()), ByteSize()); + } + + void Write(std::ostream& io) const { + uint8_t const dims = Dimensions; + io.write(reinterpret_cast(&dims), sizeof(uint8_t)); + io.write(reinterpret_cast(&elementType), sizeof(uint8_t)); + io.write(reinterpret_cast(&this->width), sizeof(SizeType)); + io.write(reinterpret_cast(&this->height), sizeof(SizeType)); + io.write(reinterpret_cast(&this->depth), sizeof(SizeType)); + io.write(reinterpret_cast(this->data.data()), ByteSize()); + } + + void SetData(std::vector&& data, ImageElementType channelType, SizeType width = 1, SizeType height = 1, + SizeType depth = 1) { + ASSERT(width * height * depth * channelType.ByteSize() == data.size()); + this->data = data; + this->width = width; + this->height = height; + this->depth = depth; + this->elementType = channelType; + } + + // data will be controlled by this instance, caller loses access! + void SetData(uint8_t* data, std::size_t count, ImageElementType channelType, SizeType width = 1, + SizeType height = 1, SizeType depth = 1) { + auto vec = std::vector(data, data + count); + delete[] data; + SetData(vec, channelType, width, height, depth); + } + + template + constexpr const T* ViewAs() const { + return reinterpret_cast(data.data()); + } + + template + std::vector GetCopy() const { + std::vector out; + out.reserve(NumElements()); + Dispatcher>::dispatch(elementType)(this, out); + return out; + } + + template + std::vector GetCopyNormalized(ReturnType maximum = std::numeric_limits::max()) const { + std::vector out; + out.reserve(NumElements()); + Dispatcher>::dispatch(elementType)(this, out, maximum); + return out; + } + + template + ReturnType GetValue(SizeType index) const { + if (index >= NumElements()) + throw std::invalid_argument("ImageChannel::GetValue: index out of bounds"); + return Dispatcher>::dispatch(elementType)(this, index); + } + + // this still feels wonky + template + T* begin() { + if (typeid(T) != elementType.TypeId()) { + throw std::invalid_argument("cannot generate iterator for a type different from the contents"); + } + return &AccessAs()[0]; + } + + template + T* end() { + if (typeid(T) != elementType.TypeId()) { + throw std::invalid_argument("cannot generate iterator for a type different from the contents"); + } + return &AccessAs()[data.size()]; + } + + template + ReturnType GetValueNormalized(SizeType index, ReturnType maximum = std::numeric_limits::max()) const { + if (index >= NumElements()) + throw std::invalid_argument("ImageChannel::GetValueNormalized: index out of bounds"); + return Dispatcher>::dispatch(elementType)(this, index, maximum); + } + + template + void SetValue(SizeType index, InputType val) { + if (index >= NumElements()) + throw std::invalid_argument("ImageChannel::SetValue: index out of bounds"); + Dispatcher>::dispatch(elementType)(this, index, val); + } + + template + void SetValueNormalized(SizeType index, T val, T maximum = std::numeric_limits::max()) { + if (index >= NumElements()) + throw std::invalid_argument("ImageChannel::SetValueNormalized: index out of bounds"); + const double relative = static_cast(val) / maximum; + Dispatcher::dispatch(elementType)(this, index, relative); + } + + [[nodiscard]] SizeType ValueIndex(SizeType x, SizeType y = 0, SizeType z = 0) const { + return (z * height + y) * width + x; + } + + [[nodiscard]] std::size_t ByteSize() const { + return width * height * depth * elementType.ByteSize(); + } + + [[nodiscard]] std::size_t ElementSize() const { + return elementType.ByteSize(); + } + + [[nodiscard]] SizeType NumElements() const { + return width * height * depth; + } + + [[nodiscard]] SizeType Width() const { + return width; + } + + [[nodiscard]] SizeType Height() const { + return height; + } + + [[nodiscard]] SizeType Depth() const { + return depth; + } + +private: + // https://stackoverflow.com/questions/16552166/c-function-dispatch-with-template-parameters + template + struct Dispatcher { + static typename FunctionWrapper::Function* dispatch(ImageElementType::Value it) { + switch (it) { + case ImageElementType::UINT8: + return &FunctionWrapper::template run; + break; + case ImageElementType::UINT16: + return &FunctionWrapper::template run; + break; + case ImageElementType::UINT32: + case ImageElementType::RGBA8: + return &FunctionWrapper::template run; + break; + case ImageElementType::FLOAT: + return &FunctionWrapper::template run; + break; + case ImageElementType::DOUBLE: + return &FunctionWrapper::template run; + break; + default: + throw std::logic_error("ImageChannel::Dispatcher: invalid elementType"); + } + } + }; + + template + T* AccessAs() { + return reinterpret_cast(data.data()); + } + + template + void SetRelative(SizeType index, double relative) { + AccessAs()[index] = static_cast(relative * std::numeric_limits::max()); + } + + template + struct GetAbsolute_FW { + using Function = ReturnType(ImageChannel const*, SizeType); + + template + static ReturnType run(ImageChannel const* that, SizeType index) { + return static_cast(that->ViewAs()[index]); + } + }; + + template + struct GetRelative_FW { + using Function = ReturnType(ImageChannel const*, SizeType, ReturnType); + + template + static ReturnType run(ImageChannel const* that, SizeType index, ReturnType maximum) { + return static_cast( + (that->ViewAs()[index] / static_cast(std::numeric_limits::max())) * maximum); + } + }; + + template + struct SetAbsolute_FW { + using Function = void(ImageChannel*, SizeType, InputType); + + template + static void run(ImageChannel* that, SizeType index, InputType val) { + that->SetAbsolute(index, val); + } + }; + + struct SetRelative_FW { + using Function = void(ImageChannel*, SizeType, double); + + template + static void run(ImageChannel* that, SizeType index, double relative) { + that->SetRelative(index, relative); + } + }; + + template + struct GetCopy_FW { + using Function = void(ImageChannel const*, std::vector&); + + template + static void run(ImageChannel const* that, std::vector& out) { + that->CopyInto(out); + } + }; + + template + struct GetCopyNormalized_FW { + using Function = void(ImageChannel const*, std::vector&, ResultType); + + template + static void run(ImageChannel const* that, std::vector& out, ResultType maximum) { + that->CopyIntoNormalized(out, maximum); + } + }; + + template + void SetAbsolute(SizeType index, Source val) { + AccessAs()[index] = static_cast(val); + } + + template + void CopyInto(std::vector& out) const { + std::transform(ViewAs(), ViewAs() + NumElements(), std::back_inserter(out), + [](const auto& val) { return static_cast(val); }); + } + + template + void CopyIntoNormalized(std::vector& out, Dest maximum) const { + std::transform( + ViewAs(), ViewAs() + NumElements(), std::back_inserter(out), [maximum](const auto& val) { + return static_cast((val / static_cast(std::numeric_limits::max())) * maximum); + }); + } + + std::vector data; + SizeType width = 0, height = 0, depth = 0; + ImageElementType elementType = ImageElementType::UINT8; +}; + +template +struct ImageFrame: AbstractFrame { + std::vector > channels; + + void Read(std::istream& io) override { + uint8_t numChannels; + io.read(reinterpret_cast(&numChannels), sizeof(uint8_t)); + channels.resize(numChannels); + for (auto c = 0; c < numChannels; ++c) { + channels[c].Read(io); + } + } + + void Write(std::ostream& io) const override { + const uint8_t numChannels = static_cast(channels.size()); + io.write(reinterpret_cast(&numChannels), sizeof(uint8_t)); + for (auto c = 0; c < numChannels; ++c) { + channels[c].Write(io); + } + } + + [[nodiscard]] std::size_t ByteSize() const override { + return std::accumulate(channels.begin(), channels.end(), 0, + [](ImageChannel& i) -> std::size_t { return i.ByteSize(); }); + } + +}; + +//using Uint8Image2DFrame = ImageFrame<2>; + +} // namespace dataformat +} // namespace io +} // namespace datatools +} // namespace megamol diff --git a/plugins/datatools/include/datatools/io/dataformat/ImageElementType.h b/plugins/datatools/include/datatools/io/dataformat/ImageElementType.h new file mode 100644 index 0000000000..cb5f385464 --- /dev/null +++ b/plugins/datatools/include/datatools/io/dataformat/ImageElementType.h @@ -0,0 +1,77 @@ +#pragma once + +#include + +namespace megamol { +namespace datatools { +namespace io { +namespace dataformat { + +class ImageElementType { +public: + enum Value : uint8_t { + UINT8 = 1, + UINT16 = 2, + UINT32 = 3, // This is a semantic alias! OK? + RGBA8 = 4, // This is a semantic alias! OK? + FLOAT = 5, + DOUBLE = 6 + }; + ImageElementType() = default; + constexpr ImageElementType(Value ct) : value(ct) {} + constexpr operator Value() const { + return value; + } + explicit operator bool() = delete; + void Set(uint8_t other) { + if (other < 1 || other > 5) + throw std::invalid_argument("value not supported"); + value = static_cast(other); + } + //constexpr operator uint8_t() const { + // return static_cast(value); + //} + + [[nodiscard]] constexpr std::size_t ByteSize() const { + switch (value) { + case UINT8: + return sizeof(uint8_t); + case UINT16: + return sizeof(uint16_t); + case UINT32: + case RGBA8: + return sizeof(uint32_t); + case FLOAT: + return sizeof(float); + case DOUBLE: + return sizeof(double); + } + return 0; + } + + [[nodiscard]] const type_info& TypeId() const { + switch (value) { + case UINT8: + return typeid(uint8_t); + case UINT16: + return typeid(uint16_t); + case UINT32: + case RGBA8: + return typeid(uint32_t); + case FLOAT: + return typeid(float); + case DOUBLE: + return typeid(double); + } + return typeid(void); + } + +private: + Value value = UINT8; +}; + + +} // namespace dataformat +} // namespace io +} // namespace datatools +} // namespace megamol diff --git a/plugins/datatools/include/datatools/io/dataformat/LRUCache.h b/plugins/datatools/include/datatools/io/dataformat/LRUCache.h new file mode 100644 index 0000000000..17016c15db --- /dev/null +++ b/plugins/datatools/include/datatools/io/dataformat/LRUCache.h @@ -0,0 +1,126 @@ +#pragma once + +/** + * MegaMol + * Copyright (c) 2021, MegaMol Dev Team + * All rights reserved. + */ + +#include +#include + +namespace megamol { +namespace datatools { +namespace io { +namespace dataformat { + +template +class LRUCache { +public: + + // TODO thread safety + + using Value = typename Container::FrameType; + using Key = typename Container::FrameType::FrameIndexType; + + LRUCache() = default; + + void clear() { + entries.clear(); + accessCount = 0; + totalByteCount = 0; + } + + std::shared_ptr get(const Key& key) const { + auto result = entries.find(key); + if (result != entries.end()) { + result->second.lastAccess = accessCount++; + return result->second.value; + } else { + return nullptr; + } + } + + std::shared_ptr operator[](const Key& key) const { + return get(key); + } + + std::shared_ptr findOrCreate(const Key& key, Container& container) { + if (maximumSize == 0) { + // I do not keep dibs, I have no space + return std::move(container.ReadFrame(key)); + } + + auto result = entries.find(key); + if (result != entries.end()) { + result->second.lastAccess = accessCount++; + return result->second.value; + } else { + Entry entry; + entry.lastAccess = accessCount++; + entry.value = std::move(container.ReadFrame(key)); + entry.byteCount = entry.value != nullptr ? entry.GetSize() : 0; + + totalByteCount += entry.byteCount; + entries.insert(std::make_pair(key, entry)); + + cleanUp(); + + return entry.value; + } + } + + void setMaximumSize(std::size_t maximumSize) { + if (this->maximumSize != maximumSize) { + this->maximumSize = maximumSize; + cleanUp(); + } + } + + std::size_t getMaximumSize() const { + return maximumSize; + } + +private: + struct Entry { + // I own the data and keep dibs so it does not disappear when unused but we still have space + std::shared_ptr value; + mutable std::size_t lastAccess = 0; + std::size_t byteCount = 0; + }; + + void cleanUp() { + if (maximumSize == 0) { + entries.clear(); + } else if (totalByteCount > maximumSize) { + // Obtain list of entries + std::vector> entryList; + for (auto it = entries.begin(); it != entries.end(); ++it) { + entryList.push_back(*it); + } + + // Sort by last access time (oldest entries last) + std::sort(entryList.begin(), entryList.end(), + [](const auto& a, const auto& b) { return a.lastAccess > b.lastAccess; }); + + // Clean up until total memory usage is below threshold + std::size_t cleanupThreshold = maximumSize * cleanupFactor; + while (totalByteCount > cleanupThreshold && !entryList.empty()) { + totalByteCount -= entryList.back().value.byteCount; + entries.erase(entryList.back().first); + entryList.pop_back(); + } + } + } + + std::unordered_map entries; + mutable std::size_t accessCount = 0; + std::size_t totalByteCount = 0; + std::size_t maximumSize = 0; + float cleanupFactor = 0.9; +}; + +} // namespace dataformat +} // namespace io +} // namespace datatools +} // namespace megamol diff --git a/plugins/datatools/include/datatools/io/dataformat/PNGDataFormat.h b/plugins/datatools/include/datatools/io/dataformat/PNGDataFormat.h new file mode 100644 index 0000000000..2183089a78 --- /dev/null +++ b/plugins/datatools/include/datatools/io/dataformat/PNGDataFormat.h @@ -0,0 +1,30 @@ +#pragma once + +#include "DataFormat.h" +#include "mmcore/utility/graphics/ScreenShotComments.h" + +#include +#include +#include + +namespace megamol { +namespace datatools { +namespace io { +namespace dataformat { + +struct PNGNaming : AbstractNaming { + std::regex Pattern() override { + return std::regex("^.*?\\.png"); + } +}; + +// todo where would we point when we read a mmpld frame? +// todo: end pointer? +// does this map to MMPLD and ADIOS? +//using PNGDataFormat = AbstractDataFormat; +//using PNGFileCollection = FolderContainer; + +} // namespace dataformat +} // namespace io +} // namespace datatools +} // namespace megamol diff --git a/plugins/datatools/src/datatools.cpp b/plugins/datatools/src/datatools.cpp index 8cab402b13..bb23541a51 100644 --- a/plugins/datatools/src/datatools.cpp +++ b/plugins/datatools/src/datatools.cpp @@ -89,6 +89,9 @@ #include "table/TableToParticles.h" #include "table/TableWhere.h" +#include "datatools/io/dataformat/CSVDataFormat.h" +#include "datatools/io/dataformat/PNGDataFormat.h" + namespace megamol::datatools { class DatatoolsPluginInstance : public megamol::core::utility::plugins::AbstractPluginInstance { REGISTERPLUGIN(DatatoolsPluginInstance) @@ -185,6 +188,10 @@ class DatatoolsPluginInstance : public megamol::core::utility::plugins::Abstract this->call_descriptions.RegisterAutoDescription(); this->call_descriptions.RegisterAutoDescription(); this->call_descriptions.RegisterAutoDescription(); + + // TODO BUG HAZARD this is just for testing, it needs to go away! + //io::dataformat::CSVFileCollection coll("c:/temp"); + //io::dataformat::PNGFileCollection coll2("c:\temp", std::make_unique()); } }; } // namespace megamol::datatools