diff --git a/Grinder/Grinder.pro b/Grinder/Grinder.pro index 338272ee6e8c756636bb8642aadbf6028608fb52..8b2bdfd1e0c97413a83ad57479efaaeb4b82f006 100644 --- a/Grinder/Grinder.pro +++ b/Grinder/Grinder.pro @@ -455,7 +455,8 @@ SOURCES += \ ui/ml/editors/MachineLearningStatePropertyEditor.cpp \ ml/blocks/MachineLearningBlock.cpp \ ml/blocks/TrainingBlock.cpp \ - ml/processors/TrainingProcessor.cpp + ml/processors/TrainingProcessor.cpp \ + project/exporters/HDF5File.cpp HEADERS += \ ui/mainwnd/GrinderWindow.h \ @@ -984,7 +985,8 @@ HEADERS += \ ml/processors/MachineLearningMethodProcessor.impl.h \ ml/processors/MachineLearningProcessor.h \ ml/processors/MachineLearningProcessor.impl.h \ - ml/barista/BaristaClassifierTaskSpawner.impl.h + ml/barista/BaristaClassifierTaskSpawner.impl.h \ + project/exporters/HDF5File.h FORMS += \ ui/mainwnd/GrinderWindow.ui \ diff --git a/Grinder/Version.h b/Grinder/Version.h index f4f176dd4e9425e5d7053624ce1a6cc91eb968db..09743bf4a67b6c9682f3510bdcabca6fc1c43070 100644 --- a/Grinder/Version.h +++ b/Grinder/Version.h @@ -10,14 +10,14 @@ #define GRNDR_INFO_TITLE "Grinder" #define GRNDR_INFO_COPYRIGHT "Copyright (c) WWU Muenster" -#define GRNDR_INFO_DATE "14.8.2019" +#define GRNDR_INFO_DATE "26.8.2019" #define GRNDR_INFO_COMPANY "WWU Muenster" #define GRNDR_INFO_WEBSITE "http://www.uni-muenster.de" #define GRNDR_VERSION_MAJOR 0 #define GRNDR_VERSION_MINOR 15 #define GRNDR_VERSION_REVISION 0 -#define GRNDR_VERSION_BUILD 375 +#define GRNDR_VERSION_BUILD 376 namespace grndr { diff --git a/Grinder/image/DraftItem.cpp b/Grinder/image/DraftItem.cpp index 154e4e9b06bf3d86c1db53ca57ca5ea9e7aeccc8..8c6cb769454df799edc8f8e6c5c1edb3e6bb0743 100644 --- a/Grinder/image/DraftItem.cpp +++ b/Grinder/image/DraftItem.cpp @@ -7,6 +7,7 @@ #include "DraftItem.h" #include "Layer.h" #include "ImageBuild.h" +#include "ImageTags.h" const char* DraftItem::Serialization_Value_Type = "Type"; @@ -31,6 +32,16 @@ int DraftItem::getZOrder() const return _layer->getZOrder(); } +void DraftItem::setDefaultPropertyValues() +{ + // If the primary color of the item matches a tag, assign that tag to the item + if (auto imageTags = _layer->imageBuild()->inputImageTags()) + { + if (auto imageTag = imageTags->tags().selectByColor(*primaryColor())) + imageTagsAllotment()->object().allotTag(imageTag.get()); + } +} + void DraftItem::serialize(SerializationContext& ctx) const { PropertyObject::serialize(ctx); diff --git a/Grinder/image/DraftItem.h b/Grinder/image/DraftItem.h index 6c7654f5bd9961229fa079cae5b3c2184d262e40..7a094e29756ffce2d58d54937ca47575eec651ce 100644 --- a/Grinder/image/DraftItem.h +++ b/Grinder/image/DraftItem.h @@ -44,7 +44,7 @@ namespace grndr auto imageTagsAllotment() const { return dynamic_cast<const ImageTagsAllotmentProperty*>(_imageTagsAllotment.get()); } public: - virtual void setDefaultPropertyValues() { } + virtual void setDefaultPropertyValues(); virtual void setDragPropertyValues(QPoint initialPos, QPoint currentPos) { Q_UNUSED(initialPos); Q_UNUSED(currentPos); } virtual void normalizePropertyValues() { } diff --git a/Grinder/image/ImageTagVector.cpp b/Grinder/image/ImageTagVector.cpp index 5943c287a6c40ef4276d719c895ba9ae0a6d7b0b..7a7c8129a209e20dd2b700dfb926b17b374af93c 100644 --- a/Grinder/image/ImageTagVector.cpp +++ b/Grinder/image/ImageTagVector.cpp @@ -5,6 +5,7 @@ #include "Grinder.h" #include "ImageTagVector.h" +#include "cv/CVUtils.h" const char* ImageTagVector::Serialization_Group = "ImageTags"; const char* ImageTagVector::Serialization_Element = "ImageTag"; @@ -16,5 +17,5 @@ ImageTagVector::pointer_type ImageTagVector::selectByName(QString name) const ImageTagVector::pointer_type ImageTagVector::selectByColor(QColor color) const { - return selectFirst([color](auto imageTag) { return imageTag->getColor() == color; }); + return selectFirst([color](auto imageTag) { return CVUtils::compareColorsNoAlpha(imageTag->getColor(), color); }); } diff --git a/Grinder/image/Layer.cpp b/Grinder/image/Layer.cpp index bef5269fefb3dd27d4a1fe477ca3898066332df6..385ed2756a5572bd8f2a2e807f3901c80a3a76ee 100644 --- a/Grinder/image/Layer.cpp +++ b/Grinder/image/Layer.cpp @@ -59,14 +59,14 @@ std::shared_ptr<DraftItem> Layer::createDraftItem(DraftItemType type) std::shared_ptr<DraftItem> item = DraftItemCatalog::createDraftItem(this, type); try { // Propagate initialization errors to the caller - item->initDraftItem(); + item->initDraftItem(); } catch (...) { throw; } _draftItems.push_back(item); - emit draftItemCreated(item); + emit draftItemCreated(item); return item; } diff --git a/Grinder/ml/barista/BaristaNetwork.cpp b/Grinder/ml/barista/BaristaNetwork.cpp index 5100fceb8a690b53c76ff6491857bd19fc2cc7f7..033ca735e513ddb72e52b6a5af566a8a35db1a43 100644 --- a/Grinder/ml/barista/BaristaNetwork.cpp +++ b/Grinder/ml/barista/BaristaNetwork.cpp @@ -141,13 +141,13 @@ void BaristaNetwork::exportTrainingData(const BaristaNetworkContext& ctx) const try { // Export the images - HDF5Exporter::ExportFlags exportFlags{HDF5Exporter::ExportFlag::ExportTags}; + HDF5File::ExportFlags exportFlags{HDF5File::ExportFlag::ExportTags}; if (_networkInfo.mergeTags()) - exportFlags |= HDF5Exporter::ExportFlag::MergeTags; + exportFlags |= HDF5File::ExportFlag::MergeTags; if (_networkInfo.requiresGrayscale()) - exportFlags |= HDF5Exporter::ExportFlag::ExportAsGrayscale; + exportFlags |= HDF5File::ExportFlag::ExportAsGrayscale; HDF5Exporter exporter{ctx.getLabel(), ctx.getCanvasBlock(), ctx.getImageReferences(), exportFlags}; exporter.exportProject(&grinder()->project(), hdf5File); diff --git a/Grinder/ml/processors/TrainingProcessor.cpp b/Grinder/ml/processors/TrainingProcessor.cpp index dd685ed171e3ddacfc01fbde2a3a27465d2a4cf5..cff78977efa5cf42c8787c6a0dc6103db0748570 100644 --- a/Grinder/ml/processors/TrainingProcessor.cpp +++ b/Grinder/ml/processors/TrainingProcessor.cpp @@ -17,8 +17,8 @@ void TrainingProcessor::execute(EngineExecutionContext& ctx, const MachineLearni // Training is only executed in batch mode if (ctx.hasExecutionFlag(Engine::ExecutionFlag::Batch)) { - // Spawn the training task when the last image is active - if (ctx.isLastImage()) + // Spawn the training task when the first image is active + if (ctx.isFirstImage()) { // TODO: Gather images, labels etc. before spawning spawnTask(SpawnType::Training, method, state); diff --git a/Grinder/project/exporters/HDF5Exporter.cpp b/Grinder/project/exporters/HDF5Exporter.cpp index dcd4891e309535f3733217af8c8b15b7ebb28d2f..c697e189c354fc27627cd9952324b27531abe008 100644 --- a/Grinder/project/exporters/HDF5Exporter.cpp +++ b/Grinder/project/exporters/HDF5Exporter.cpp @@ -11,15 +11,10 @@ #include "image/ImageTags.h" #include "ui/dlg/HDF5ExportDialog.h" -#include <opencv2/imgproc.hpp> - -#define HDF5_DATASET_DATA "data" -#define HDF5_DATASET_TAGS "label" - -HDF5Exporter::HDF5Exporter(Label* label, const Block* canvasBlock, const ImageReferenceSelection& imageReferences, ExportFlags exportFlags) : ProjectExporter("HDF5 Exporter", "*.h5;*.hdf5"), +HDF5Exporter::HDF5Exporter(Label* label, const Block* canvasBlock, const ImageReferenceSelection& imageReferences, HDF5File::ExportFlags exportFlags) : ProjectExporter("HDF5 Exporter", "*.h5;*.hdf5"), _label{label}, _canvasBlock{canvasBlock}, _imageReferences{imageReferences}, _exportFlags{exportFlags} { - H5::Exception::dontPrint(); + } bool HDF5Exporter::invokeUi(const Project* project, QWidget* parent) @@ -35,13 +30,13 @@ bool HDF5Exporter::invokeUi(const Project* project, QWidget* parent) _canvasBlock = dlg.getCanvasBlock(); _imageReferences = dlg.getImageReferences(); - _exportFlags = ExportFlag::None; + _exportFlags = HDF5File::ExportFlag::None; if (dlg.exportAsGrayscale()) - _exportFlags |= ExportFlag::ExportAsGrayscale; + _exportFlags |= HDF5File::ExportFlag::ExportAsGrayscale; if (dlg.exportImageTags()) - _exportFlags |= ExportFlag::ExportTags; + _exportFlags |= HDF5File::ExportFlag::ExportTags; return true; } @@ -58,21 +53,13 @@ void HDF5Exporter::exportProject(const Project* project, QString fileName) // Make sure that all image references can be exported verifyImageReferences(project); - try { - H5::H5File h5File{fileName.toLatin1(), H5F_ACC_TRUNC}; - - // Export images and tags - opExport.setStatusMessage("Images"); - exportImageReferences(project, h5File); + // Prepare the H5 file + HDF5File h5File{fileName}; + h5File.initExport(getImageSize(), _imageReferences.size(), getImageTagsCount(), _exportFlags); - if (_exportFlags.testFlag(ExportFlag::ExportTags)) - { - opExport.setStatusMessage("Tags"); - exportImageTags(project, h5File); - } - } catch (H5::Exception& e) { - throw ExportException{project, _EXCPT(QString{"HDF5 error: %1"}.arg(e.getDetailMsg().data()))}; - } + // Export images; tags will also be exported if necessary + opExport.setStatusMessage("Images"); + exportImageReferences(project, h5File); } void HDF5Exporter::verifyImageReferences(const Project* project) const @@ -85,36 +72,42 @@ void HDF5Exporter::verifyImageReferences(const Project* project) const for (const auto& imgRef : _imageReferences) { - if (imgRef->getImageInfo().imageSize.width() != static_cast<int>(imgSize.first) || imgRef->getImageInfo().imageSize.height() != static_cast<int>(imgSize.second)) - throw ExportException{project, _EXCPT(QString{"All exported images must be of the same size (%1x%2)"}.arg(imgSize.first).arg(imgSize.second))}; + if (imgRef->getImageInfo().imageSize.width() != imgSize.width() || imgRef->getImageInfo().imageSize.height() != imgSize.height()) + throw ExportException{project, _EXCPT(QString{"All exported images must be of the same size (%1x%2)"}.arg(imgSize.width()).arg(imgSize.height()))}; } } -std::pair<hsize_t, hsize_t> HDF5Exporter::getImageSize() const +QSize HDF5Exporter::getImageSize() const { if (!_imageReferences.empty()) + return _imageReferences[0]->getImageInfo().imageSize; + else + return QSize{0, 0}; +} + +unsigned int HDF5Exporter::getImageTagsCount() const +{ + // Get the total number of tags + auto tagsCount = ImageTags::Maximum_Tags_Count; + + if (_canvasBlock) { - QSize imgSize = _imageReferences[0]->getImageInfo().imageSize; - return std::make_pair(static_cast<hsize_t>(imgSize.width()), static_cast<hsize_t>(imgSize.height())); + if (auto imageTagsProperty = _canvasBlock->portProperty<ImageTagsProperty>(PortType::ImageTagsIn, PropertyID::ImageTags)) + tagsCount = imageTagsProperty->object().tags().size(); } - else - return std::make_pair(static_cast<hsize_t>(0), static_cast<hsize_t>(0)); + + return tagsCount; } -void HDF5Exporter::exportImageReferences(const Project* project, H5::H5File& h5File) const +void HDF5Exporter::exportImageReferences(const Project* project, const HDF5File& h5File) const { LongOperation opExportImages{"Exporting images", static_cast<unsigned int>(_imageReferences.size())}; - // Export all images - auto dataSpaceImg = createDataSpace(_exportFlags.testFlag(ExportFlag::ExportAsGrayscale) ? 1 : 3); - auto dataSetImg = createDataSet(h5File, dataSpaceImg, HDF5_DATASET_DATA, H5::PredType::NATIVE_FLOAT); - unsigned int index = 0; - for (const auto& imgRef : _imageReferences) - exportImageReference(project, imgRef, index++, dataSpaceImg, dataSetImg); + exportImageReference(project, imgRef, h5File); } -void HDF5Exporter::exportImageReference(const Project* project, const ImageReference* imgRef, unsigned int index, const H5::DataSpace& dataSpace, const H5::DataSet& dataSet) const +void HDF5Exporter::exportImageReference(const Project* project, const ImageReference* imgRef, const HDF5File& h5File) const { LongOperationStep opExportImage{imgRef->getImageFilePath()}; cv::Mat imgData; @@ -135,88 +128,24 @@ void HDF5Exporter::exportImageReference(const Project* project, const ImageRefer if (imgData.channels() != 3) throw ExportException{project, _EXCPT(QString{"The data for '%1' is invalid"}.arg(imgRef->getImageFilePath()))}; - // Convert the image data to float and map intensities to [0,1] - imgData.convertTo(imgData, CV_32F); - imgData /= 255.0f; - - // Convert colors if necessary - if (imgData.channels() == 1 && !_exportFlags.testFlag(ExportFlag::ExportAsGrayscale)) - cv::cvtColor(imgData, imgData, cv::COLOR_GRAY2BGR); - else if (imgData.channels() == 3 && _exportFlags.testFlag(ExportFlag::ExportAsGrayscale)) - cv::cvtColor(imgData, imgData, cv::COLOR_BGR2GRAY); - - // Write each channel individually; this works for single-channel images as well - cv::Mat imageChannels[3]; // BGR order - cv::split(imgData, imageChannels); - - auto imgSize = getImageSize(); - hsize_t imgDims[] = {imgSize.second, imgSize.first}; - hsize_t count[] = {1, 1, imgSize.second, imgSize.first}; - - for (unsigned int chan = 0; chan < static_cast<unsigned int>(imgData.channels()); ++chan) - { - // Select the proper hyperslab to write the data to - hsize_t start[] = {index, chan, 0, 0}; - dataSpace.selectHyperslab(H5S_SELECT_SET, count, start); - - // Write the image data to the dataset - dataSet.write(imageChannels[chan].data, H5::PredType::NATIVE_FLOAT, H5::DataSpace{2, imgDims}, dataSpace); - } -} - -void HDF5Exporter::exportImageTags(const Project* project, H5::H5File& h5File) const -{ - if (_label && _canvasBlock) - { - // Get the total number of tags - auto tagsCount = ImageTags::Maximum_Tags_Count; - - if (auto imageTagsProperty = _canvasBlock->portProperty<ImageTagsProperty>(PortType::ImageTagsIn, PropertyID::ImageTags)) - tagsCount = imageTagsProperty->object().tags().size(); - - LongOperation opExportImages{"Exporting tags", static_cast<unsigned int>(_imageReferences.size())}; - - // Export the tags of all images - auto dataSpaceTags = createDataSpace(_exportFlags.testFlag(ExportFlag::MergeTags) ? 1 : tagsCount); - auto dataSetTags = createDataSet(h5File, dataSpaceTags, HDF5_DATASET_TAGS, H5::PredType::NATIVE_INT32); - int index = 0; - - for (const auto& imgRef : _imageReferences) - exportImageTags(project, imgRef, tagsCount, index++, dataSpaceTags, dataSetTags); - } -} - -void HDF5Exporter::exportImageTags(const Project* project, const ImageReference* imgRef, unsigned int tagCount, unsigned int index, const H5::DataSpace& dataSpace, const H5::DataSet& dataSet) const -{ - Q_UNUSED(project); + // Generate the tag matrices + std::vector<cv::Mat> tagMatrices; - LongOperationStep opExportTags{imgRef->getImageFilePath()}; - auto imageTagsBitmap = grinder()->engineController().generateImageTagsBitmap(_label, _canvasBlock, imgRef, false); - - if (imageTagsBitmap.isValid()) + if (_exportFlags.testFlag(HDF5File::ExportFlag::ExportTags)) { - std::vector<cv::Mat> flagMatrices; - - if (_exportFlags.testFlag(ExportFlag::MergeTags)) - flagMatrices = exportImageTags_Merged(imageTagsBitmap, tagCount); - else - flagMatrices = exportImageTags_Individually(imageTagsBitmap, tagCount); + auto imageTagsBitmap = grinder()->engineController().generateImageTagsBitmap(_label, _canvasBlock, imgRef, false); - // Export all flag matrices - auto imgSize = getImageSize(); - hsize_t imgDims[] = {imgSize.second, imgSize.first}; - hsize_t count[] = {1, 1, imgSize.second, imgSize.first}; - - for (unsigned int i = 0; i < flagMatrices.size(); ++i) + if (imageTagsBitmap.isValid()) { - // Select the proper hyperslab to write the data to - hsize_t start[] = {index, i, 0, 0}; - dataSpace.selectHyperslab(H5S_SELECT_SET, count, start); - - // Write the tags data to the dataset - dataSet.write(flagMatrices[i].data, H5::PredType::NATIVE_INT32, H5::DataSpace{2, imgDims}, dataSpace); + if (_exportFlags.testFlag(HDF5File::ExportFlag::MergeTags)) + tagMatrices = exportImageTags_Merged(imageTagsBitmap, getImageTagsCount()); + else + tagMatrices = exportImageTags_Individually(imageTagsBitmap, getImageTagsCount()); } } + + // Export the image and tags + h5File.exportImageEx(imgData, tagMatrices); } std::vector<cv::Mat> HDF5Exporter::exportImageTags_Individually(const ImageTagsBitmap& imageTagsBitmap, unsigned int tagCount) const @@ -229,7 +158,7 @@ std::vector<cv::Mat> HDF5Exporter::exportImageTags_Individually(const ImageTagsB std::vector<cv::Mat> flagMatrices; for (unsigned int i = 0; i < tagCount; ++i) - flagMatrices.push_back(cv::Mat::zeros(imgSize.second, imgSize.first, CV_32SC1)); + flagMatrices.push_back(cv::Mat::zeros(imgSize.height(), imgSize.width(), CV_32SC1)); for (int r = 0; r < bitmap.size().height(); ++r) { @@ -253,7 +182,7 @@ std::vector<cv::Mat> HDF5Exporter::exportImageTags_Merged(const ImageTagsBitmap& auto imgSize = getImageSize(); // Create a flag matrix for all tags - cv::Mat flagMatrix = cv::Mat::zeros(imgSize.second, imgSize.first, CV_32SC1); + cv::Mat flagMatrix = cv::Mat::zeros(imgSize.height(), imgSize.width(), CV_32SC1); for (int r = 0; r < bitmap.size().height(); ++r) { @@ -269,26 +198,3 @@ std::vector<cv::Mat> HDF5Exporter::exportImageTags_Merged(const ImageTagsBitmap& return {flagMatrix}; } - -H5::DataSpace HDF5Exporter::createDataSpace(unsigned int channels) const -{ - // Our dataspace is 4D: Index, number of channels, Y and X - auto imgSize = getImageSize(); - hsize_t dataSpaceDims[] = {_imageReferences.size(), channels, imgSize.second, imgSize.first}; - return H5::DataSpace{4, dataSpaceDims}; -} - -H5::DataSet HDF5Exporter::createDataSet(const H5::H5File& h5File, const H5::DataSpace& dataSpace, QString name, H5::PredType predType) const -{ - H5::DSetCreatPropList propList{H5::DSetCreatPropList::DEFAULT}; - -#if defined(H5_HAVE_FILTER_DEFLATE) - // Enable compression on the data - auto imgSize = getImageSize(); - hsize_t chunkDims[] = {1, 1, imgSize.second, imgSize.first}; - propList.setChunk(4, chunkDims); - propList.setDeflate(5); -#endif - - return h5File.createDataSet(name.toLatin1(), predType, dataSpace, propList); -} diff --git a/Grinder/project/exporters/HDF5Exporter.h b/Grinder/project/exporters/HDF5Exporter.h index cd44cb350c7ad085d5a268881b74ccb9152dee2d..db42237dfb1f50bdd528181e233a067832ccd5c7 100644 --- a/Grinder/project/exporters/HDF5Exporter.h +++ b/Grinder/project/exporters/HDF5Exporter.h @@ -6,9 +6,9 @@ #ifndef HDF5EXPORTER_H #define HDF5EXPORTER_H -#include <H5Cpp.h> #include <opencv2/core.hpp> +#include "HDF5File.h" #include "project/ProjectExporter.h" #include "project/ImageReferenceSelection.h" @@ -22,20 +22,7 @@ namespace grndr class HDF5Exporter : public ProjectExporter { public: - enum class ExportFlag : unsigned int - { - None = 0x0000, - - ExportAsGrayscale = 0x0001, - ExportTags = 0x0002, - - MergeTags = 0x0100, - }; - - Q_DECLARE_FLAGS(ExportFlags, ExportFlag) - - public: - HDF5Exporter(Label* label = nullptr, const Block* canvasBlock = nullptr, const ImageReferenceSelection& imageReferences = {}, ExportFlags exportFlags = ExportFlag::ExportTags); + HDF5Exporter(Label* label = nullptr, const Block* canvasBlock = nullptr, const ImageReferenceSelection& imageReferences = {}, HDF5File::ExportFlags exportFlags = HDF5File::ExportFlag::ExportTags); public: virtual bool invokeUi(const Project* project, QWidget* parent) override; @@ -44,29 +31,25 @@ namespace grndr private: void verifyImageReferences(const Project* project) const; - std::pair<hsize_t, hsize_t> getImageSize() const; + QSize getImageSize() const; + unsigned int getImageTagsCount() const; private: - void exportImageReferences(const Project* project, H5::H5File& h5File) const; - void exportImageReference(const Project* project, const ImageReference* imgRef, unsigned int index, const H5::DataSpace& dataSpace, const H5::DataSet& dataSet) const; + void exportImageReferences(const Project* project, const HDF5File& h5File) const; + void exportImageReference(const Project* project, const ImageReference* imgRef, const HDF5File& h5File) const; void exportImageTags(const Project* project, H5::H5File& h5File) const; void exportImageTags(const Project* project, const ImageReference* imgRef, unsigned int tagCount, unsigned int index, const H5::DataSpace& dataSpace, const H5::DataSet& dataSet) const; std::vector<cv::Mat> exportImageTags_Individually(const ImageTagsBitmap& imageTagsBitmap, unsigned int tagCount) const; std::vector<cv::Mat> exportImageTags_Merged(const ImageTagsBitmap& imageTagsBitmap, unsigned int tagCount) const; - H5::DataSpace createDataSpace(unsigned int channels) const; - H5::DataSet createDataSet(const H5::H5File& h5File, const H5::DataSpace& dataSpace, QString name, H5::PredType predType) const; - private: Label* _label{nullptr}; const Block* _canvasBlock{nullptr}; ImageReferenceSelection _imageReferences; - ExportFlags _exportFlags{ExportFlag::None}; + HDF5File::ExportFlags _exportFlags{HDF5File::ExportFlag::None}; }; } -Q_DECLARE_OPERATORS_FOR_FLAGS(grndr::HDF5Exporter::ExportFlags) - #endif diff --git a/Grinder/project/exporters/HDF5File.cpp b/Grinder/project/exporters/HDF5File.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7863a878048b64f89fe3c9af6f4bfc02f1a65970 --- /dev/null +++ b/Grinder/project/exporters/HDF5File.cpp @@ -0,0 +1,167 @@ +/****************************************************************************** + * File: HDF5File.cpp + * Date: 26.8.2019 + *****************************************************************************/ + +#include "Grinder.h" +#include "HDF5File.h" +#include "project/ProjectExceptions.h" + +#include <opencv2/imgproc.hpp> + +#define HDF5_DATASET_DATA "data" +#define HDF5_DATASET_TAGS "label" + +HDF5File::HDF5File(QString filename, bool truncate) +{ + H5::Exception::dontPrint(); + + try { + // Create/open the H5 file + _h5File = H5::H5File{filename.toLatin1(), truncate ? H5F_ACC_TRUNC : 0}; + } catch (H5::Exception& e) { + throwH5Exception(e); + } +} + +void HDF5File::initExport(QSize imageSize, unsigned int imageCount, unsigned int tagsCount, ExportFlags flags) +{ + if (imageSize.isNull()) + throw ExportException{nullptr, _EXCPT("Image size may not be 0x0")}; + + if (imageCount == 0) + throw ExportException{nullptr, _EXCPT("Image count may not be 0")}; + + _imageSize = {imageSize.width(), imageSize.height()}; + _imageCount = imageCount; + + if (flags.testFlag(ExportFlag::ExportTags)) + _tagsCount = flags.testFlag(ExportFlag::MergeTags) ? 1 : tagsCount; + else + _tagsCount = 0; + + _exportFlags = flags; + + try { + // Create the H5 objects + _h5SpaceData = createDataSpace(_exportFlags.testFlag(ExportFlag::ExportAsGrayscale) ? 1 : 3); + _h5SetData = createDataSet(_h5SpaceData, HDF5_DATASET_DATA, H5::PredType::NATIVE_FLOAT); + + if (_exportFlags.testFlag(ExportFlag::ExportTags)) + { + _h5SpaceTags = createDataSpace(_tagsCount); + _h5SetTags = createDataSet(_h5SpaceTags, HDF5_DATASET_TAGS, H5::PredType::NATIVE_INT32); + } + + _currentImage = 0; + } catch (H5::Exception& e) { + throwH5Exception(e); + } +} + +void HDF5File::exportImageEx(const cv::Mat& image, const std::vector<cv::Mat>& tagMatrices) const +{ + // Verify parameters + if (static_cast<hsize_t>(image.rows) != _imageSize.second || static_cast<hsize_t>(image.cols) != _imageSize.first) + throw ExportException{nullptr, _EXCPT(QString{"All exported images must be of the correct size (%1x%2)"}.arg(_imageSize.first).arg(_imageSize.second))}; + + for (auto tagMatrix : tagMatrices) + { + if (static_cast<hsize_t>(tagMatrix.rows) != _imageSize.second || static_cast<hsize_t>(tagMatrix.cols) != _imageSize.first) + throw ExportException{nullptr, _EXCPT(QString{"All exported image tags must be of the correct size (%1x%2)"}.arg(_imageSize.first).arg(_imageSize.second))}; + } + + if (_currentImage >= _imageCount) + throw ExportException{nullptr, _EXCPT(QString{"Tried to export more than %1 image(s)"}.arg(_imageCount))}; + + cv::Mat imgData; + + // Convert the image data to float and map intensities to [0,1] + image.convertTo(imgData, CV_32F); + imgData /= 255.0f; + + // Convert colors if necessary + if (imgData.channels() == 1 && !_exportFlags.testFlag(ExportFlag::ExportAsGrayscale)) + cv::cvtColor(imgData, imgData, cv::COLOR_GRAY2BGR); + else if (imgData.channels() == 3 && _exportFlags.testFlag(ExportFlag::ExportAsGrayscale)) + cv::cvtColor(imgData, imgData, cv::COLOR_BGR2GRAY); + + // Write each channel individually; this works for single-channel images as well + cv::Mat imageChannels[3]; // BGR order + cv::split(imgData, imageChannels); + + hsize_t imgDims[] = {_imageSize.second, _imageSize.first}; + hsize_t count[] = {1, 1, _imageSize.second, _imageSize.first}; + + try { + for (unsigned int chan = 0; chan < static_cast<unsigned int>(imgData.channels()); ++chan) + { + // Select the proper hyperslab to write the data to + hsize_t start[] = {_currentImage, chan, 0, 0}; + _h5SpaceData.selectHyperslab(H5S_SELECT_SET, count, start); + + // Write the image data to the dataset + _h5SetData.write(imageChannels[chan].data, H5::PredType::NATIVE_FLOAT, H5::DataSpace{2, imgDims}, _h5SpaceData); + } + } catch (H5::Exception& e) { + throwH5Exception(e); + } + + if (_exportFlags.testFlag(ExportFlag::ExportTags)) + exportImageTags(tagMatrices); + + _currentImage += 1; +} + +void HDF5File::exportImageTags(const std::vector<cv::Mat>& tagMatrices) const +{ + if (tagMatrices.size() > _tagsCount) + throw ExportException{nullptr, _EXCPT(QString{"Tried to export more than %1 image tag(s)"}.arg(_tagsCount))}; + + // Export all tags matrices + hsize_t imgDims[] = {_imageSize.second, _imageSize.first}; + hsize_t count[] = {1, 1, _imageSize.second, _imageSize.first}; + + try { + for (unsigned int i = 0; i < tagMatrices.size(); ++i) + { + if (static_cast<hsize_t>(tagMatrices[i].rows) != _imageSize.second || static_cast<hsize_t>(tagMatrices[i].cols) != _imageSize.first) + throw ExportException{nullptr, _EXCPT(QString{"All exported tag bitmaps must be of the correct size (%1x%2)"}.arg(_imageSize.first).arg(_imageSize.second))}; + + // Select the proper hyperslab to write the data to + hsize_t start[] = {_currentImage, i, 0, 0}; + _h5SpaceTags.selectHyperslab(H5S_SELECT_SET, count, start); + + // Write the tags data to the dataset + _h5SetTags.write(tagMatrices[i].data, H5::PredType::NATIVE_INT32, H5::DataSpace{2, imgDims}, _h5SpaceTags); + } + } catch (H5::Exception& e) { + throwH5Exception(e); + } +} + +H5::DataSpace HDF5File::createDataSpace(unsigned int channels) const +{ + // Our dataspace is 4D: Index, number of channels, Y and X + hsize_t dataSpaceDims[] = {_imageCount, channels, _imageSize.second, _imageSize.first}; + return H5::DataSpace{4, dataSpaceDims}; +} + +H5::DataSet HDF5File::createDataSet(const H5::DataSpace& dataSpace, QString name, H5::PredType predType) const +{ + H5::DSetCreatPropList propList{H5::DSetCreatPropList::DEFAULT}; + +#if defined(H5_HAVE_FILTER_DEFLATE) + // Enable compression on the data + hsize_t chunkDims[] = {1, 1, _imageSize.second, _imageSize.first}; + propList.setChunk(4, chunkDims); + propList.setDeflate(5); +#endif + + return _h5File.createDataSet(name.toLatin1(), predType, dataSpace, propList); +} + +void HDF5File::throwH5Exception(H5::Exception& e) const +{ + throw ExportException{nullptr, _EXCPT(QString{"HDF5 error: %1"}.arg(e.getDetailMsg().data()))}; +} diff --git a/Grinder/project/exporters/HDF5File.h b/Grinder/project/exporters/HDF5File.h new file mode 100644 index 0000000000000000000000000000000000000000..95ea8d3ff5bb94d0d5ee92d6808ebdaca9597249 --- /dev/null +++ b/Grinder/project/exporters/HDF5File.h @@ -0,0 +1,72 @@ +/****************************************************************************** + * File: HDF5File.h + * Date: 26.8.2019 + *****************************************************************************/ + +#ifndef HDF5FILE_H +#define HDF5FILE_H + +#include <H5Cpp.h> +#include <opencv2/core.hpp> + +#include <QSize> + +namespace grndr +{ + class HDF5File + { + public: + enum class ExportFlag : unsigned int + { + None = 0x0000, + + ExportAsGrayscale = 0x0001, + ExportTags = 0x0002, + + MergeTags = 0x0100, + }; + + Q_DECLARE_FLAGS(ExportFlags, ExportFlag) + + public: + HDF5File(QString filename, bool truncate = true); + + public: + void initExport(QSize imageSize, unsigned int imageCount, unsigned int tagsCount, ExportFlags flags = ExportFlag::ExportTags); + + void exportImage(const cv::Mat& image) const { exportImageEx(image, {}); } + void exportImageEx(const cv::Mat& image, const std::vector<cv::Mat>& tagMatrices) const; + + private: + void exportImageTags(const std::vector<cv::Mat>& tagMatrices) const; + + private: + H5::DataSpace createDataSpace(unsigned int channels) const; + H5::DataSet createDataSet(const H5::DataSpace& dataSpace, QString name, H5::PredType predType) const; + + private: + void throwH5Exception(H5::Exception& e) const; + + private: + std::pair<hsize_t, hsize_t> _imageSize; + unsigned int _imageCount{0}; + unsigned int _tagsCount{0}; + + ExportFlags _exportFlags{ExportFlag::None}; + + private: + H5::H5File _h5File; + + H5::DataSpace _h5SpaceData; + H5::DataSet _h5SetData; + H5::DataSpace _h5SpaceTags; + H5::DataSet _h5SetTags; + + private: + mutable unsigned int _currentImage{0}; + }; +} + +Q_DECLARE_OPERATORS_FOR_FLAGS(grndr::HDF5File::ExportFlags) + +#endif