diff --git a/Modules/Core/include/mitkITKImageImport.h b/Modules/Core/include/mitkITKImageImport.h index 76286d4770..6716a467f7 100644 --- a/Modules/Core/include/mitkITKImageImport.h +++ b/Modules/Core/include/mitkITKImageImport.h @@ -1,217 +1,243 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkITKImageImport_h #define mitkITKImageImport_h #include "itkImageToImageFilterDetail.h" #include "mitkImageSource.h" #include namespace mitk { /** * @brief Pipelined import of itk::Image * * The image data contained in the itk::Image is referenced, * not copied. * The easiest way of use is by the function * mitk::ImportItkImage * \code * mitkImage = mitk::ImportItkImage(itkImage); * \endcode * \sa ImportItkImage * @ingroup Adaptor */ template class MITK_EXPORT ITKImageImport : public ImageSource { public: mitkClassMacro(ITKImageImport, ImageSource); itkFactorylessNewMacro(Self); itkCloneMacro(Self); /// \brief The type of the input image. typedef TInputImage InputImageType; typedef typename InputImageType::Pointer InputImagePointer; typedef typename InputImageType::ConstPointer InputImageConstPointer; typedef typename InputImageType::RegionType InputImageRegionType; typedef typename InputImageType::PixelType InputImagePixelType; /** ImageDimension constants */ itkStaticConstMacro(InputImageDimension, unsigned int, TInputImage::ImageDimension); itkStaticConstMacro(RegionDimension, unsigned int, mitk::SlicedData::RegionDimension); /** \brief Set the input itk::Image of this image importer. */ InputImageType *GetInput(void); /** \brief Set the input itk::Image of this image importer. */ void SetInput(const InputImageType *); using itk::ProcessObject::SetInput; /** * \brief Set the Geometry of the result image (optional) * * The Geometry has to fit the dimension and size of * the input image. The Geometry will be cloned, not * referenced! * * Providing the Geometry is optional. * The default behavior is to set the geometry by * the itk::Image::GetDirection() information. */ void SetGeometry(const BaseGeometry *geometry); protected: ITKImageImport(); ~ITKImageImport() override; void GenerateOutputInformation() override; void GenerateInputRequestedRegion() override; void GenerateData() override; void SetNthOutput(DataObjectPointerArraySizeType num, itk::DataObject *output) override; /** Typedef for the region copier function object that converts an * output region to an input region. */ typedef itk::ImageToImageFilterDetail::ImageRegionCopier OutputToInputRegionCopierType; BaseGeometry::Pointer m_Geometry; }; /** * @brief Imports an itk::Image (with a specific type) as an mitk::Image. * @ingroup Adaptor * * Instantiates instance of ITKImageImport. * mitk::ITKImageImport does not cast pixel types etc., it just imports * image data. If you get a compile error, try image.GetPointer(). * * \param itkimage * \param geometry * * \param update if \a true, fill mitk::Image, which will execute the * up-stream pipeline connected to the input itk::Image. Otherwise you * need to make sure that Update() is called on the mitk::Image before * its data is being used, e.g., by connecting it to an mitk-pipeline * and call Update of a downstream filter at some time. * \sa itk::Image::CastToMitkImage */ template Image::Pointer ImportItkImage(const itk::SmartPointer &itkimage, const BaseGeometry *geometry = nullptr, bool update = true); /** * @brief Imports an itk::Image (with a specific type) as an mitk::Image. * @ingroup Adaptor * * Instantiates instance of ITKImageImport * mitk::ITKImageImport does not cast pixel types etc., it just imports * image data. If you get a compile error, try image.GetPointer(). * * \param itkimage * \param geometry * * \param update if \a true, fill mitk::Image, which will execute the * up-stream pipeline connected to the input itk::Image. Otherwise you * need to make sure that Update() is called on the mitk::Image before * its data is being used, e.g., by connecting it to an mitk-pipeline * and call Update of a downstream filter at some time. * * * \note If the source (itk image) and the target (mitk image) do not share the same scope, the * mitk::GrabItkImageMemory * function * has to be used instead. Otherwise the image memory managed by the itk image is lost at a scope level change. This * affects especially the * usage in combination with AccessByItk macros as in following example code * * \snippet test/mitkGrabItkImageMemoryTest.cpp OutOfScopeCall * * which calls an ITK-like filter * * \snippet test/mitkGrabItkImageMemoryTest.cpp ItkThresholdFilter * * * \sa itk::Image::CastToMitkImage * \sa GrabItkImageMemory */ template Image::Pointer ImportItkImage(const ItkOutputImageType *itkimage, const BaseGeometry *geometry = nullptr, bool update = true); /** * @brief Grabs the memory of an itk::Image (with a specific type) * and puts it into an mitk::Image. * @ingroup Adaptor * * The memory is managed by the mitk::Image after calling this * function. The itk::Image remains valid until the mitk::Image * decides to free the memory. * * \param itkimage * \param mitkImage * \param geometry * * \param update if \a true, fill mitk::Image, which will execute the * up-stream pipeline connected to the input itk::Image. Otherwise you * need to make sure that Update() is called on the mitk::Image before * its data is being used, e.g., by connecting it to an mitk-pipeline * and call Update of a downstream filter at some time. * \sa ImportItkImage */ template Image::Pointer GrabItkImageMemory(itk::SmartPointer &itkimage, mitk::Image *mitkImage = nullptr, const BaseGeometry *geometry = nullptr, bool update = true); /** * @brief Grabs the memory of an itk::Image (with a specific type) * and puts it into an mitk::Image. * @ingroup Adaptor * * The memory is managed by the mitk::Image after calling this * function. The itk::Image remains valid until the mitk::Image * decides to free the memory. * * \param itkimage * \param mitkImage * \param geometry * * \param update if \a true, fill mitk::Image, which will execute the * up-stream pipeline connected to the input itk::Image. Otherwise you * need to make sure that Update() is called on the mitk::Image before * its data is being used, e.g., by connecting it to an mitk-pipeline * and call Update of a downstream filter at some time. * \sa ImportItkImage */ template Image::Pointer GrabItkImageMemory(ItkOutputImageType *itkimage, mitk::Image *mitkImage = nullptr, const BaseGeometry *geometry = nullptr, bool update = true); + /** + * @brief Grabs the memory of an itk::Image (with a specific type) + * and puts it into an mitk::Image. + * @ingroup Adaptor + * + * The memory is managed by the mitk::Image after calling this + * function. The itk::Image remains valid until the mitk::Image + * decides to free the memory. + * + * \param itkimage + * \param mitkImage + * \param geometry + * + * \param update if \a true, fill mitk::Image, which will execute the + * up-stream pipeline connected to the input itk::Image. Otherwise you + * need to make sure that Update() is called on the mitk::Image before + * its data is being used, e.g., by connecting it to an mitk-pipeline + * and call Update of a downstream filter at some time. + * \sa ImportItkImage + */ + template + Image::Pointer GrabItkImageMemoryChannel(ItkOutputImageType* itkimage, + const TimeGeometry* geometry = nullptr, + mitk::Image* mitkImage = nullptr, + bool update = true); + } // namespace mitk #ifndef MITK_MANUAL_INSTANTIATION #include "mitkITKImageImport.txx" #endif #endif diff --git a/Modules/Core/include/mitkITKImageImport.txx b/Modules/Core/include/mitkITKImageImport.txx index 046082da2a..76ad0dcc45 100644 --- a/Modules/Core/include/mitkITKImageImport.txx +++ b/Modules/Core/include/mitkITKImageImport.txx @@ -1,187 +1,225 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef __mitkITKImageImport_txx #define __mitkITKImageImport_txx #include "mitkITKImageImport.h" #include "mitkImageReadAccessor.h" template mitk::ITKImageImport::ITKImageImport() { } template mitk::ITKImageImport::~ITKImageImport() { } template typename mitk::ITKImageImport::InputImageType *mitk::ITKImageImport::GetInput(void) { return static_cast(this->ProcessObject::GetInput(0)); } template void mitk::ITKImageImport::SetInput(const InputImageType *input) { this->ProcessObject::SetNthInput(0, const_cast(input)); } template void mitk::ITKImageImport::SetGeometry(const BaseGeometry *geometry) { if (geometry != nullptr) { m_Geometry = static_cast(geometry->Clone().GetPointer()); } else { m_Geometry = nullptr; } Modified(); } template void mitk::ITKImageImport::GenerateOutputInformation() { InputImageConstPointer input = this->GetInput(); mitk::Image::Pointer output = this->GetOutput(); itkDebugMacro(<< "GenerateOutputInformation()"); output->InitializeByItk(input.GetPointer()); if (m_Geometry.IsNotNull()) { output->SetGeometry(m_Geometry); } } template void mitk::ITKImageImport::GenerateData() { InputImageConstPointer input = this->GetInput(); mitk::Image::Pointer output = this->GetOutput(); output->SetImportChannel((void *)input->GetBufferPointer(), 0, mitk::Image::ReferenceMemory); } template void mitk::ITKImageImport::GenerateInputRequestedRegion() { Superclass::GenerateInputRequestedRegion(); // Input is an image, cast away the constness so we can set // the requested region. InputImagePointer input = const_cast(this->GetInput()); // Use the function object RegionCopier to copy the output region // to the input. The default region copier has default implementations // to handle the cases where the input and output are the same // dimension, the input a higher dimension than the output, and the // input a lower dimension than the output. InputImageRegionType inputRegion; OutputToInputRegionCopierType regionCopier; regionCopier(inputRegion, this->GetOutput()->GetRequestedRegion()); input->SetRequestedRegion(inputRegion); } template void mitk::ITKImageImport::SetNthOutput(DataObjectPointerArraySizeType idx, itk::DataObject *output) { if ((output == nullptr) && (idx == 0)) { // we are disconnected from our output: // copy buffer of input to output, because we // cannot guarantee that the input (to which our // output is referring) will stay alive. InputImageConstPointer input = this->GetInput(); mitk::Image::Pointer currentOutput = this->GetOutput(); if (input.IsNotNull() && currentOutput.IsNotNull()) currentOutput->SetChannel(input->GetBufferPointer()); } Superclass::SetNthOutput(idx, output); } template mitk::Image::Pointer mitk::ImportItkImage(const itk::SmartPointer &itkimage, const BaseGeometry *geometry, bool update) { typename mitk::ITKImageImport::Pointer importer = mitk::ITKImageImport::New(); importer->SetInput(itkimage); importer->SetGeometry(geometry); if (update) importer->Update(); return importer->GetOutput(); } template mitk::Image::Pointer mitk::ImportItkImage(const ItkOutputImageType *itkimage, const BaseGeometry *geometry, bool update) { typename mitk::ITKImageImport::Pointer importer = mitk::ITKImageImport::New(); importer->SetInput(itkimage); importer->SetGeometry(geometry); if (update) importer->Update(); return importer->GetOutput(); } template mitk::Image::Pointer mitk::GrabItkImageMemory(itk::SmartPointer &itkimage, mitk::Image *mitkImage, const BaseGeometry *geometry, bool update) { return GrabItkImageMemory(itkimage.GetPointer(), mitkImage, geometry, update); } template mitk::Image::Pointer mitk::GrabItkImageMemory(ItkOutputImageType *itkimage, mitk::Image *mitkImage, const BaseGeometry *geometry, bool update) { if (update) itkimage->Update(); mitk::Image::Pointer resultImage; if (mitkImage != nullptr) { resultImage = mitkImage; // test the pointer equality with read accessor only if mitk Image is initialized, otherwise an Exception is thrown // by the ReadAccessor if (mitkImage->IsInitialized()) { // check the data pointer, for that, we need to ignore the lock of the mitkImage mitk::ImageReadAccessor read_probe(mitk::Image::Pointer(mitkImage), nullptr, mitk::ImageAccessorBase::IgnoreLock); if (itkimage->GetBufferPointer() == read_probe.GetData()) return resultImage; } } else { resultImage = mitk::Image::New(); } resultImage->InitializeByItk(itkimage); resultImage->SetImportVolume(itkimage->GetBufferPointer(), 0, 0, Image::ManageMemory); itkimage->GetPixelContainer()->ContainerManageMemoryOff(); if (geometry != nullptr) resultImage->SetGeometry(static_cast(geometry->Clone().GetPointer())); return resultImage; } +template +mitk::Image::Pointer mitk::GrabItkImageMemoryChannel(ItkOutputImageType* itkimage, + const TimeGeometry* geometry, + mitk::Image* mitkImage, + bool update) +{ + if (update) + itkimage->Update(); + + mitk::Image::Pointer resultImage; + if (mitkImage != nullptr) + { + resultImage = mitkImage; + + // test the pointer equality with read accessor only if mitk Image is initialized, otherwise an Exception is thrown + // by the ReadAccessor + if (mitkImage->IsInitialized()) + { + // check the data pointer, for that, we need to ignore the lock of the mitkImage + mitk::ImageReadAccessor read_probe(mitk::Image::Pointer(mitkImage), nullptr, mitk::ImageAccessorBase::IgnoreLock); + if (itkimage->GetBufferPointer() == read_probe.GetData()) + return resultImage; + } + } + else + { + resultImage = mitk::Image::New(); + } + resultImage->InitializeByItk(itkimage); + resultImage->SetImportChannel(itkimage->GetBufferPointer(), 0, Image::ManageMemory); + itkimage->GetPixelContainer()->ContainerManageMemoryOff(); + + if (geometry != nullptr) + resultImage->SetTimeGeometry(geometry->Clone().GetPointer()); + + return resultImage; +} + #endif //__mitkITKImageImport_txx diff --git a/Modules/Core/include/mitkItkImageIO.h b/Modules/Core/include/mitkItkImageIO.h index 17b87ab80f..77cc90fb26 100644 --- a/Modules/Core/include/mitkItkImageIO.h +++ b/Modules/Core/include/mitkItkImageIO.h @@ -1,94 +1,101 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkItkImageIO_h #define mitkItkImageIO_h #include "mitkAbstractFileIO.h" #include #include namespace mitk { /** * This class wraps ITK image IO objects as mitk::IFileReader and * mitk::IFileWriter objects. * * Instantiating this class with a given itk::ImageIOBase instance * will register corresponding MITK reader/writer services for that * ITK ImageIO object. * For all ITK ImageIOs that support the serialization of MetaData * (e.g. nrrd or mhd) the ItkImageIO ensures the serialization * of Identification UID. */ class MITKCORE_EXPORT ItkImageIO : public AbstractFileIO { public: ItkImageIO(itk::ImageIOBase::Pointer imageIO); ItkImageIO(const CustomMimeType &mimeType, itk::ImageIOBase::Pointer imageIO, int rank); // -------------- AbstractFileReader ------------- using AbstractFileReader::Read; ConfidenceLevel GetReaderConfidenceLevel() const override; // -------------- AbstractFileWriter ------------- void Write() override; ConfidenceLevel GetWriterConfidenceLevel() const override; /**Helper function that can be used to convert a MetaDataDictionary into a PropertyList for a certain mimeType. The function uses the Property serialization service for that. @param mimeTypeName Mime type that should be assumed for the meta data deserialization. @param defaultMetaDataKeys Vector of keys that should be assumed as defaults. For defaults no PropertyInfo will be registered at the PropertyPersistence service, as they are assumed to be handled anyways. For all other keys an info will be registered to ensure that they will be serialized again, even if unkown.*/ static PropertyList::Pointer ExtractMetaDataAsPropertyList(const itk::MetaDataDictionary& dictionary, const std::string& mimeTypeName, const std::vector& defaultMetaDataKeys); /** Helper function that van be used to extract a raw mitk image for the passed path using the also passed ImageIOBase instance. Raw means, that only the pixel data and geometry information is loaded. But e.g. no properties etc...*/ static Image::Pointer LoadRawMitkImageFromImageIO(itk::ImageIOBase* imageIO, const std::string& path); + /** Helper function that van be used to extract a raw mitk image for the passed path using the also passed ImageIOBase instance. + Raw means, that only the pixel data and geometry information is loaded. But e.g. no properties etc...*/ + static void PreparImageIOToWriteImage(itk::ImageIOBase* imageIO, const Image* image); + + static void SavePropertyListAsMetaData(itk::MetaDataDictionary& dictionary, const PropertyList* properties, const std::string& mimeTypeName); + + protected: virtual std::vector FixUpImageIOExtensions(const std::string &imageIOName); virtual void FixUpCustomMimeTypeName(const std::string &imageIOName, CustomMimeType &customMimeType); // Fills the m_DefaultMetaDataKeys vector with default values virtual void InitializeDefaultMetaDataKeys(); // -------------- AbstractFileReader ------------- std::vector> DoRead() override; private: ItkImageIO(const ItkImageIO &other); ItkImageIO *IOClone() const override; itk::ImageIOBase::Pointer m_ImageIO; std::vector m_DefaultMetaDataKeys; }; /**Helper function that converts the content of a meta data into a time point vector. * If MetaData is not valid or cannot be converted an empty vector is returned.*/ MITKCORE_EXPORT std::vector ConvertMetaDataObjectToTimePointList(const itk::MetaDataObjectBase* data); /**Helper function that converts the time points of a passed time geometry to a time point list and stores it in a itk::MetaDataObject. Use ConvertMetaDataObjectToTimePointList() to convert it back to a time point list.*/ MITKCORE_EXPORT itk::MetaDataObjectBase::Pointer ConvertTimePointListToMetaDataObject(const mitk::TimeGeometry* timeGeometry); } // namespace mitk #endif diff --git a/Modules/Core/src/IO/mitkItkImageIO.cpp b/Modules/Core/src/IO/mitkItkImageIO.cpp index 8e328412de..bc52ba2ea0 100644 --- a/Modules/Core/src/IO/mitkItkImageIO.cpp +++ b/Modules/Core/src/IO/mitkItkImageIO.cpp @@ -1,783 +1,793 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkItkImageIO.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mitk { const char *const PROPERTY_NAME_TIMEGEOMETRY_TYPE = "org.mitk.timegeometry.type"; const char *const PROPERTY_NAME_TIMEGEOMETRY_TIMEPOINTS = "org.mitk.timegeometry.timepoints"; const char *const PROPERTY_KEY_TIMEGEOMETRY_TYPE = "org_mitk_timegeometry_type"; const char *const PROPERTY_KEY_TIMEGEOMETRY_TIMEPOINTS = "org_mitk_timegeometry_timepoints"; const char* const PROPERTY_KEY_UID = "org_mitk_uid"; ItkImageIO::ItkImageIO(const ItkImageIO &other) : AbstractFileIO(other), m_ImageIO(dynamic_cast(other.m_ImageIO->Clone().GetPointer())) { this->InitializeDefaultMetaDataKeys(); } std::vector ItkImageIO::FixUpImageIOExtensions(const std::string &imageIOName) { std::vector extensions; // Try to fix-up some known ITK image IO classes if (imageIOName == "GiplImageIO") { extensions.push_back("gipl"); extensions.push_back("gipl.gz"); } else if (imageIOName == "GDCMImageIO") { extensions.push_back("gdcm"); extensions.push_back("dcm"); extensions.push_back("DCM"); extensions.push_back("dc3"); extensions.push_back("DC3"); extensions.push_back("ima"); extensions.push_back("img"); } else if (imageIOName == "PNGImageIO") { extensions.push_back("png"); extensions.push_back("PNG"); } else if (imageIOName == "StimulateImageIO") { extensions.push_back("spr"); } else if (imageIOName == "HDF5ImageIO") { extensions.push_back("hdf"); extensions.push_back("h4"); extensions.push_back("hdf4"); extensions.push_back("h5"); extensions.push_back("hdf5"); extensions.push_back("he4"); extensions.push_back("he5"); extensions.push_back("hd5"); } else if ("GE4ImageIO" == imageIOName || "GE5ImageIO" == imageIOName || "Bruker2dseqImageIO" == imageIOName) { extensions.push_back(""); } if (!extensions.empty()) { MITK_DEBUG << "Fixing up known extensions for " << imageIOName; } return extensions; } void ItkImageIO::FixUpCustomMimeTypeName(const std::string &imageIOName, CustomMimeType &customMimeType) { if ("GE4ImageIO" == imageIOName) { customMimeType.SetName(this->AbstractFileReader::GetMimeTypePrefix() + "ge4"); } else if ("GE5ImageIO" == imageIOName) { customMimeType.SetName(this->AbstractFileReader::GetMimeTypePrefix() + "ge5"); } else if ("Bruker2dseqImageIO" == imageIOName) { customMimeType.SetName(this->AbstractFileReader::GetMimeTypePrefix() + "bruker2dseq"); } } ItkImageIO::ItkImageIO(itk::ImageIOBase::Pointer imageIO) : AbstractFileIO(Image::GetStaticNameOfClass()), m_ImageIO(imageIO) { if (m_ImageIO.IsNull()) { mitkThrow() << "ITK ImageIOBase argument must not be nullptr"; } this->AbstractFileReader::SetMimeTypePrefix(IOMimeTypes::DEFAULT_BASE_NAME() + ".image."); this->InitializeDefaultMetaDataKeys(); std::vector readExtensions = m_ImageIO->GetSupportedReadExtensions(); if (readExtensions.empty()) { std::string imageIOName = m_ImageIO->GetNameOfClass(); MITK_DEBUG << "ITK ImageIOBase " << imageIOName << " does not provide read extensions"; readExtensions = FixUpImageIOExtensions(imageIOName); } CustomMimeType customReaderMimeType; customReaderMimeType.SetCategory("Images"); for (std::vector::const_iterator iter = readExtensions.begin(), endIter = readExtensions.end(); iter != endIter; ++iter) { std::string extension = *iter; if (!extension.empty() && extension[0] == '.') { extension.assign(iter->begin() + 1, iter->end()); } customReaderMimeType.AddExtension(extension); } auto extensions = customReaderMimeType.GetExtensions(); if (extensions.empty() || (extensions.size() == 1 && extensions[0].empty())) { std::string imageIOName = m_ImageIO->GetNameOfClass(); FixUpCustomMimeTypeName(imageIOName, customReaderMimeType); } this->AbstractFileReader::SetMimeType(customReaderMimeType); std::vector writeExtensions = imageIO->GetSupportedWriteExtensions(); if (writeExtensions.empty()) { std::string imageIOName = imageIO->GetNameOfClass(); MITK_DEBUG << "ITK ImageIOBase " << imageIOName << " does not provide write extensions"; writeExtensions = FixUpImageIOExtensions(imageIOName); } if (writeExtensions != readExtensions) { CustomMimeType customWriterMimeType; customWriterMimeType.SetCategory("Images"); for (std::vector::const_iterator iter = writeExtensions.begin(), endIter = writeExtensions.end(); iter != endIter; ++iter) { std::string extension = *iter; if (!extension.empty() && extension[0] == '.') { extension.assign(iter->begin() + 1, iter->end()); } customWriterMimeType.AddExtension(extension); } auto extensions = customWriterMimeType.GetExtensions(); if (extensions.empty() || (extensions.size() == 1 && extensions[0].empty())) { std::string imageIOName = m_ImageIO->GetNameOfClass(); FixUpCustomMimeTypeName(imageIOName, customWriterMimeType); } this->AbstractFileWriter::SetMimeType(customWriterMimeType); } std::string description = std::string("ITK ") + imageIO->GetNameOfClass(); this->SetReaderDescription(description); this->SetWriterDescription(description); this->RegisterService(); } ItkImageIO::ItkImageIO(const CustomMimeType &mimeType, itk::ImageIOBase::Pointer imageIO, int rank) : AbstractFileIO(Image::GetStaticNameOfClass(), mimeType, std::string("ITK ") + imageIO->GetNameOfClass()), m_ImageIO(imageIO) { if (m_ImageIO.IsNull()) { mitkThrow() << "ITK ImageIOBase argument must not be nullptr"; } this->AbstractFileReader::SetMimeTypePrefix(IOMimeTypes::DEFAULT_BASE_NAME() + ".image."); this->InitializeDefaultMetaDataKeys(); if (rank) { this->AbstractFileReader::SetRanking(rank); this->AbstractFileWriter::SetRanking(rank); } this->RegisterService(); } std::vector ConvertMetaDataObjectToTimePointList(const itk::MetaDataObjectBase* data) { const auto* timeGeometryTimeData = dynamic_cast*>(data); std::vector result; if (timeGeometryTimeData) { std::string dataStr = timeGeometryTimeData->GetMetaDataObjectValue(); std::stringstream stream(dataStr); TimePointType tp; while (stream >> tp) { result.push_back(tp); } } return result; }; Image::Pointer ItkImageIO::LoadRawMitkImageFromImageIO(itk::ImageIOBase* imageIO, const std::string& path) { LocaleSwitch localeSwitch("C"); Image::Pointer image = Image::New(); const unsigned int MINDIM = 2; const unsigned int MAXDIM = 4; MITK_INFO << "loading " << path << " via itk::ImageIOFactory... " << std::endl; // Check to see if we can read the file given the name or prefix if (path.empty()) { mitkThrow() << "Empty filename in mitk::ItkImageIO "; } // Got to allocate space for the image. Determine the characteristics of // the image. imageIO->SetFileName(path); imageIO->ReadImageInformation(); unsigned int ndim = imageIO->GetNumberOfDimensions(); if (ndim < MINDIM || ndim > MAXDIM) { MITK_WARN << "Sorry, only dimensions 2, 3 and 4 are supported. The given file has " << ndim << " dimensions! Reading as 4D."; ndim = MAXDIM; } itk::ImageIORegion ioRegion(ndim); itk::ImageIORegion::SizeType ioSize = ioRegion.GetSize(); itk::ImageIORegion::IndexType ioStart = ioRegion.GetIndex(); unsigned int dimensions[MAXDIM]; dimensions[0] = 0; dimensions[1] = 0; dimensions[2] = 0; dimensions[3] = 0; ScalarType spacing[MAXDIM]; spacing[0] = 1.0f; spacing[1] = 1.0f; spacing[2] = 1.0f; spacing[3] = 1.0f; Point3D origin; origin.Fill(0); unsigned int i; for (i = 0; i < ndim; ++i) { ioStart[i] = 0; ioSize[i] = imageIO->GetDimensions(i); if (i < MAXDIM) { dimensions[i] = imageIO->GetDimensions(i); spacing[i] = imageIO->GetSpacing(i); if (spacing[i] <= 0) spacing[i] = 1.0f; } if (i < 3) { origin[i] = imageIO->GetOrigin(i); } } ioRegion.SetSize(ioSize); ioRegion.SetIndex(ioStart); MITK_INFO << "ioRegion: " << ioRegion << std::endl; imageIO->SetIORegion(ioRegion); void* buffer = new unsigned char[imageIO->GetImageSizeInBytes()]; imageIO->Read(buffer); image->Initialize(MakePixelType(imageIO), ndim, dimensions); image->SetImportChannel(buffer, 0, Image::ManageMemory); const itk::MetaDataDictionary& dictionary = imageIO->GetMetaDataDictionary(); // access direction of itk::Image and include spacing mitk::Matrix3D matrix; matrix.SetIdentity(); unsigned int j, itkDimMax3 = (ndim >= 3 ? 3 : ndim); for (i = 0; i < itkDimMax3; ++i) for (j = 0; j < itkDimMax3; ++j) matrix[i][j] = imageIO->GetDirection(j)[i]; // re-initialize PlaneGeometry with origin and direction PlaneGeometry* planeGeometry = image->GetSlicedGeometry(0)->GetPlaneGeometry(0); planeGeometry->SetOrigin(origin); planeGeometry->GetIndexToWorldTransform()->SetMatrix(matrix); // re-initialize SlicedGeometry3D SlicedGeometry3D* slicedGeometry = image->GetSlicedGeometry(0); slicedGeometry->InitializeEvenlySpaced(planeGeometry, image->GetDimension(2)); slicedGeometry->SetSpacing(spacing); MITK_INFO << slicedGeometry->GetCornerPoint(false, false, false); MITK_INFO << slicedGeometry->GetCornerPoint(true, true, true); // re-initialize TimeGeometry TimeGeometry::Pointer timeGeometry; if (dictionary.HasKey(PROPERTY_NAME_TIMEGEOMETRY_TYPE) || dictionary.HasKey(PROPERTY_KEY_TIMEGEOMETRY_TYPE)) { // also check for the name because of backwards compatibility. Past code version stored with the name and not with // the key itk::MetaDataObject::ConstPointer timeGeometryTypeData = nullptr; if (dictionary.HasKey(PROPERTY_NAME_TIMEGEOMETRY_TYPE)) { timeGeometryTypeData = dynamic_cast *>(dictionary.Get(PROPERTY_NAME_TIMEGEOMETRY_TYPE)); } else { timeGeometryTypeData = dynamic_cast *>(dictionary.Get(PROPERTY_KEY_TIMEGEOMETRY_TYPE)); } if (timeGeometryTypeData->GetMetaDataObjectValue() == ArbitraryTimeGeometry::GetStaticNameOfClass()) { MITK_INFO << "used time geometry: " << ArbitraryTimeGeometry::GetStaticNameOfClass(); typedef std::vector TimePointVector; TimePointVector timePoints; if (dictionary.HasKey(PROPERTY_NAME_TIMEGEOMETRY_TIMEPOINTS)) { timePoints = ConvertMetaDataObjectToTimePointList(dictionary.Get(PROPERTY_NAME_TIMEGEOMETRY_TIMEPOINTS)); } else if (dictionary.HasKey(PROPERTY_KEY_TIMEGEOMETRY_TIMEPOINTS)) { timePoints = ConvertMetaDataObjectToTimePointList(dictionary.Get(PROPERTY_KEY_TIMEGEOMETRY_TIMEPOINTS)); } if (timePoints.empty()) { MITK_ERROR << "Stored timepoints are empty. Meta information seems to bee invalid. Switch to ProportionalTimeGeometry fallback"; } else if (timePoints.size() - 1 != image->GetDimension(3)) { MITK_ERROR << "Stored timepoints (" << timePoints.size() - 1 << ") and size of image time dimension (" << image->GetDimension(3) << ") do not match. Switch to ProportionalTimeGeometry fallback"; } else { ArbitraryTimeGeometry::Pointer arbitraryTimeGeometry = ArbitraryTimeGeometry::New(); TimePointVector::const_iterator pos = timePoints.begin(); auto prePos = pos++; for (; pos != timePoints.end(); ++prePos, ++pos) { arbitraryTimeGeometry->AppendNewTimeStepClone(slicedGeometry, *prePos, *pos); } timeGeometry = arbitraryTimeGeometry; } } } if (timeGeometry.IsNull()) { // Fallback. If no other valid time geometry has been created, create a ProportionalTimeGeometry MITK_INFO << "used time geometry: " << ProportionalTimeGeometry::GetStaticNameOfClass(); ProportionalTimeGeometry::Pointer propTimeGeometry = ProportionalTimeGeometry::New(); propTimeGeometry->Initialize(slicedGeometry, image->GetDimension(3)); timeGeometry = propTimeGeometry; } image->SetTimeGeometry(timeGeometry); buffer = nullptr; MITK_INFO << "number of image components: " << image->GetPixelType().GetNumberOfComponents(); return image; } itk::MetaDataObjectBase::Pointer ConvertTimePointListToMetaDataObject(const mitk::TimeGeometry* timeGeometry) { std::stringstream stream; stream << timeGeometry->GetTimeBounds(0)[0]; const auto maxTimePoints = timeGeometry->CountTimeSteps(); for (TimeStepType pos = 0; pos < maxTimePoints; ++pos) { auto timeBounds = timeGeometry->GetTimeBounds(pos); /////////////////////////////////////// // Workaround T27883. See https://phabricator.mitk.org/T27883#219473 for more details. // This workaround should be removed as soon as T28262 is solved! if (pos + 1 == maxTimePoints && timeBounds[0]==timeBounds[1]) { timeBounds[1] = timeBounds[0] + 1.; } // End of workaround for T27883 ////////////////////////////////////// stream << " " << timeBounds[1]; } auto result = itk::MetaDataObject::New(); result->SetMetaDataObjectValue(stream.str()); return result.GetPointer(); }; PropertyList::Pointer ItkImageIO::ExtractMetaDataAsPropertyList(const itk::MetaDataDictionary& dictionary, const std::string& mimeTypeName, const std::vector& defaultMetaDataKeys) { LocaleSwitch localeSwitch("C"); PropertyList::Pointer result = PropertyList::New(); for (auto iter = dictionary.Begin(), iterEnd = dictionary.End(); iter != iterEnd; ++iter) { if (iter->second->GetMetaDataObjectTypeInfo() == typeid(std::string)) { const std::string& key = iter->first; std::string assumedPropertyName = key; std::replace(assumedPropertyName.begin(), assumedPropertyName.end(), '_', '.'); // Check if there is already a info for the key and our mime type. CoreServicePointer propPersistenceService(CoreServices::GetPropertyPersistence()); IPropertyPersistence::InfoResultType infoList = propPersistenceService->GetInfoByKey(key); auto predicate = [&mimeTypeName](const PropertyPersistenceInfo::ConstPointer& x) { return x.IsNotNull() && x->GetMimeTypeName() == mimeTypeName; }; auto finding = std::find_if(infoList.begin(), infoList.end(), predicate); if (finding == infoList.end()) { auto predicateWild = [](const PropertyPersistenceInfo::ConstPointer& x) { return x.IsNotNull() && x->GetMimeTypeName() == PropertyPersistenceInfo::ANY_MIMETYPE_NAME(); }; finding = std::find_if(infoList.begin(), infoList.end(), predicateWild); } PropertyPersistenceInfo::ConstPointer info; if (finding != infoList.end()) { assumedPropertyName = (*finding)->GetName(); info = *finding; } else { // we have not found anything suitable so we generate our own info auto newInfo = PropertyPersistenceInfo::New(); newInfo->SetNameAndKey(assumedPropertyName, key); newInfo->SetMimeTypeName(PropertyPersistenceInfo::ANY_MIMETYPE_NAME()); info = newInfo; } std::string value = dynamic_cast *>(iter->second.GetPointer())->GetMetaDataObjectValue(); mitk::BaseProperty::Pointer loadedProp = info->GetDeserializationFunction()(value); if (loadedProp.IsNull()) { MITK_ERROR << "Property cannot be correctly deserialized and is skipped. Check if data format is valid. Problematic property value string: \"" << value << "\"; Property info used to deserialized: " << info; break; } result->SetProperty(assumedPropertyName.c_str(), loadedProp); // Read properties should be persisted unless they are default properties // which are written anyway bool isDefaultKey(false); for (const auto& defaultKey : defaultMetaDataKeys) { if (defaultKey.length() <= assumedPropertyName.length()) { // does the start match the default key if (assumedPropertyName.substr(0, defaultKey.length()).find(defaultKey) != std::string::npos) { isDefaultKey = true; break; } } } if (!isDefaultKey) { propPersistenceService->AddInfo(info); } } } return result; } std::vector ItkImageIO::DoRead() { std::vector result; auto image = LoadRawMitkImageFromImageIO(this->m_ImageIO, this->GetLocalFileName()); const itk::MetaDataDictionary& dictionary = this->m_ImageIO->GetMetaDataDictionary(); //meta data handling auto props = ExtractMetaDataAsPropertyList(this->m_ImageIO->GetMetaDataDictionary(), this->GetMimeType()->GetName(), this->m_DefaultMetaDataKeys); for (auto& [name, prop] : *(props->GetMap())) { image->SetProperty(name, prop); } // Handle UID if (dictionary.HasKey(PROPERTY_KEY_UID)) { itk::MetaDataObject::ConstPointer uidData = dynamic_cast*>(dictionary.Get(PROPERTY_KEY_UID)); if (uidData.IsNotNull()) { mitk::UIDManipulator uidManipulator(image); uidManipulator.SetUID(uidData->GetMetaDataObjectValue()); } } MITK_INFO << "...finished!"; result.push_back(image.GetPointer()); return result; } AbstractFileIO::ConfidenceLevel ItkImageIO::GetReaderConfidenceLevel() const { return m_ImageIO->CanReadFile(GetLocalFileName().c_str()) ? IFileReader::Supported : IFileReader::Unsupported; } - void ItkImageIO::Write() + void ItkImageIO::PreparImageIOToWriteImage(itk::ImageIOBase* imageIO, const Image* image) { - const auto *image = dynamic_cast(this->GetInput()); - - if (image == nullptr) - { - mitkThrow() << "Cannot write non-image data"; - } - // Switch the current locale to "C" LocaleSwitch localeSwitch("C"); // Clone the image geometry, because we might have to change it // for writing purposes BaseGeometry::Pointer geometry = image->GetGeometry()->Clone(); // Check if geometry information will be lost if (image->GetDimension() == 2 && !geometry->Is2DConvertable()) { MITK_WARN << "Saving a 2D image with 3D geometry information. Geometry information will be lost! You might " - "consider using Convert2Dto3DImageFilter before saving."; + "consider using Convert2Dto3DImageFilter before saving."; // set matrix to identity mitk::AffineTransform3D::Pointer affTrans = mitk::AffineTransform3D::New(); affTrans->SetIdentity(); mitk::Vector3D spacing = geometry->GetSpacing(); mitk::Point3D origin = geometry->GetOrigin(); geometry->SetIndexToWorldTransform(affTrans); geometry->SetSpacing(spacing); geometry->SetOrigin(origin); } - LocalFile localFile(this); - const std::string path = localFile.GetFileName(); + // Implementation of writer using itkImageIO directly. This skips the use + // of templated itkImageFileWriter, which saves the multiplexing on MITK side. + + const unsigned int dimension = image->GetDimension(); + const unsigned int* const dimensions = image->GetDimensions(); + const mitk::PixelType pixelType = image->GetPixelType(); + const mitk::Vector3D mitkSpacing = geometry->GetSpacing(); + const mitk::Point3D mitkOrigin = geometry->GetOrigin(); + + // Due to templating in itk, we are forced to save a 4D spacing and 4D Origin, + // though they are not supported in MITK + itk::Vector spacing4D; + spacing4D[0] = mitkSpacing[0]; + spacing4D[1] = mitkSpacing[1]; + spacing4D[2] = mitkSpacing[2]; + spacing4D[3] = 1; // There is no support for a 4D spacing. However, we should have a valid value here + + itk::Vector origin4D; + origin4D[0] = mitkOrigin[0]; + origin4D[1] = mitkOrigin[1]; + origin4D[2] = mitkOrigin[2]; + origin4D[3] = 0; // There is no support for a 4D origin. However, we should have a valid value here + + // Set the necessary information for imageIO + imageIO->SetNumberOfDimensions(dimension); + imageIO->SetPixelType(pixelType.GetPixelType()); + imageIO->SetComponentType(static_cast(pixelType.GetComponentType()) < PixelComponentUserType + ? pixelType.GetComponentType() + : itk::IOComponentEnum::UNKNOWNCOMPONENTTYPE); + imageIO->SetNumberOfComponents(pixelType.GetNumberOfComponents()); + + itk::ImageIORegion ioRegion(dimension); + + for (unsigned int i = 0; i < dimension; i++) + { + imageIO->SetDimensions(i, dimensions[i]); + imageIO->SetSpacing(i, spacing4D[i]); + imageIO->SetOrigin(i, origin4D[i]); + + mitk::Vector3D mitkDirection(0.0); + mitkDirection.SetVnlVector(geometry->GetIndexToWorldTransform()->GetMatrix().GetVnlMatrix().get_column(i).as_ref()); + itk::Vector direction4D; + direction4D[0] = mitkDirection[0]; + direction4D[1] = mitkDirection[1]; + direction4D[2] = mitkDirection[2]; + + // MITK only supports a 3x3 direction matrix. Due to templating in itk, however, we must + // save a 4x4 matrix for 4D images. in this case, add an homogneous component to the matrix. + if (i == 3) + { + direction4D[3] = 1; // homogenous component + } + else + { + direction4D[3] = 0; + } + vnl_vector axisDirection(dimension); + for (unsigned int j = 0; j < dimension; j++) + { + axisDirection[j] = direction4D[j] / spacing4D[i]; + } + imageIO->SetDirection(i, axisDirection); - MITK_INFO << "Writing image: " << path << std::endl; + ioRegion.SetSize(i, image->GetLargestPossibleRegion().GetSize(i)); + ioRegion.SetIndex(i, image->GetLargestPossibleRegion().GetIndex(i)); + } - try + imageIO->SetIORegion(ioRegion); + + // Handle time geometry + const auto* arbitraryTG = dynamic_cast(image->GetTimeGeometry()); + if (arbitraryTG) { - // Implementation of writer using itkImageIO directly. This skips the use - // of templated itkImageFileWriter, which saves the multiplexing on MITK side. - - const unsigned int dimension = image->GetDimension(); - const unsigned int *const dimensions = image->GetDimensions(); - const mitk::PixelType pixelType = image->GetPixelType(); - const mitk::Vector3D mitkSpacing = geometry->GetSpacing(); - const mitk::Point3D mitkOrigin = geometry->GetOrigin(); - - // Due to templating in itk, we are forced to save a 4D spacing and 4D Origin, - // though they are not supported in MITK - itk::Vector spacing4D; - spacing4D[0] = mitkSpacing[0]; - spacing4D[1] = mitkSpacing[1]; - spacing4D[2] = mitkSpacing[2]; - spacing4D[3] = 1; // There is no support for a 4D spacing. However, we should have a valid value here - - itk::Vector origin4D; - origin4D[0] = mitkOrigin[0]; - origin4D[1] = mitkOrigin[1]; - origin4D[2] = mitkOrigin[2]; - origin4D[3] = 0; // There is no support for a 4D origin. However, we should have a valid value here - - // Set the necessary information for imageIO - m_ImageIO->SetNumberOfDimensions(dimension); - m_ImageIO->SetPixelType(pixelType.GetPixelType()); - m_ImageIO->SetComponentType(static_cast(pixelType.GetComponentType()) < PixelComponentUserType - ? pixelType.GetComponentType() - : itk::IOComponentEnum::UNKNOWNCOMPONENTTYPE); - m_ImageIO->SetNumberOfComponents(pixelType.GetNumberOfComponents()); - - itk::ImageIORegion ioRegion(dimension); - - for (unsigned int i = 0; i < dimension; i++) - { - m_ImageIO->SetDimensions(i, dimensions[i]); - m_ImageIO->SetSpacing(i, spacing4D[i]); - m_ImageIO->SetOrigin(i, origin4D[i]); - - mitk::Vector3D mitkDirection(0.0); - mitkDirection.SetVnlVector(geometry->GetIndexToWorldTransform()->GetMatrix().GetVnlMatrix().get_column(i).as_ref()); - itk::Vector direction4D; - direction4D[0] = mitkDirection[0]; - direction4D[1] = mitkDirection[1]; - direction4D[2] = mitkDirection[2]; - - // MITK only supports a 3x3 direction matrix. Due to templating in itk, however, we must - // save a 4x4 matrix for 4D images. in this case, add an homogneous component to the matrix. - if (i == 3) - { - direction4D[3] = 1; // homogenous component - } - else - { - direction4D[3] = 0; - } - vnl_vector axisDirection(dimension); - for (unsigned int j = 0; j < dimension; j++) - { - axisDirection[j] = direction4D[j] / spacing4D[i]; - } - m_ImageIO->SetDirection(i, axisDirection); + itk::EncapsulateMetaData(imageIO->GetMetaDataDictionary(), + PROPERTY_KEY_TIMEGEOMETRY_TYPE, + ArbitraryTimeGeometry::GetStaticNameOfClass()); - ioRegion.SetSize(i, image->GetLargestPossibleRegion().GetSize(i)); - ioRegion.SetIndex(i, image->GetLargestPossibleRegion().GetIndex(i)); - } + auto metaTimePoints = ConvertTimePointListToMetaDataObject(arbitraryTG); + imageIO->GetMetaDataDictionary().Set(PROPERTY_KEY_TIMEGEOMETRY_TIMEPOINTS, metaTimePoints); + } + } - // use compression if available - m_ImageIO->UseCompressionOn(); + void ItkImageIO::SavePropertyListAsMetaData(itk::MetaDataDictionary& dictionary, const PropertyList* properties, const std::string& mimeTypeName) + { + // Switch the current locale to "C" + LocaleSwitch localeSwitch("C"); - m_ImageIO->SetIORegion(ioRegion); - m_ImageIO->SetFileName(path); + for (const auto& property : *properties->GetMap()) + { + mitk::CoreServicePointer propPersistenceService(mitk::CoreServices::GetPropertyPersistence()); + IPropertyPersistence::InfoResultType infoList = propPersistenceService->GetInfo(property.first, mimeTypeName, true); - // Handle time geometry - const auto *arbitraryTG = dynamic_cast(image->GetTimeGeometry()); - if (arbitraryTG) + if (infoList.empty()) { - itk::EncapsulateMetaData(m_ImageIO->GetMetaDataDictionary(), - PROPERTY_KEY_TIMEGEOMETRY_TYPE, - ArbitraryTimeGeometry::GetStaticNameOfClass()); - - auto metaTimePoints = ConvertTimePointListToMetaDataObject(arbitraryTG); - m_ImageIO->GetMetaDataDictionary().Set(PROPERTY_KEY_TIMEGEOMETRY_TIMEPOINTS, metaTimePoints); + continue; } - // Handle properties - mitk::PropertyList::Pointer imagePropertyList = image->GetPropertyList(); + std::string value = mitk::BaseProperty::VALUE_CANNOT_BE_CONVERTED_TO_STRING; + try + { + value = infoList.front()->GetSerializationFunction()(property.second); + } + catch (const std::exception& e) + { + MITK_ERROR << "Error when serializing content of property. This often indicates the use of an out dated reader. Property will not be stored. Skipped property: " << property.first << ". Reason: " << e.what(); + } + catch (...) + { + MITK_ERROR << "Unknown error when serializing content of property. This often indicates the use of an out dated reader. Property will not be stored. Skipped property: " << property.first; + } - for (const auto &property : *imagePropertyList->GetMap()) + if (value == mitk::BaseProperty::VALUE_CANNOT_BE_CONVERTED_TO_STRING) { - mitk::CoreServicePointer propPersistenceService(mitk::CoreServices::GetPropertyPersistence()); - IPropertyPersistence::InfoResultType infoList = propPersistenceService->GetInfo(property.first, GetMimeType()->GetName(), true); + continue; + } - if (infoList.empty()) - { - continue; - } + std::string key = infoList.front()->GetKey(); - std::string value = mitk::BaseProperty::VALUE_CANNOT_BE_CONVERTED_TO_STRING; - try - { - value = infoList.front()->GetSerializationFunction()(property.second); - } - catch (const std::exception& e) - { - MITK_ERROR << "Error when serializing content of property. This often indicates the use of an out dated reader. Property will not be stored. Skipped property: " << property.first << ". Reason: " << e.what(); - } - catch (...) - { - MITK_ERROR << "Unknown error when serializing content of property. This often indicates the use of an out dated reader. Property will not be stored. Skipped property: " << property.first; - } + itk::EncapsulateMetaData(dictionary, key, value); + } + } - if (value == mitk::BaseProperty::VALUE_CANNOT_BE_CONVERTED_TO_STRING) - { - continue; - } + void ItkImageIO::Write() + { + const auto *image = dynamic_cast(this->GetInput()); + + if (image == nullptr) + { + mitkThrow() << "Cannot write non-image data"; + } - std::string key = infoList.front()->GetKey(); + PreparImageIOToWriteImage(m_ImageIO, image); - itk::EncapsulateMetaData(m_ImageIO->GetMetaDataDictionary(), key, value); - } + LocalFile localFile(this); + const std::string path = localFile.GetFileName(); + + MITK_INFO << "Writing image: " << path << std::endl; + try + { + // Handle properties + SavePropertyListAsMetaData(m_ImageIO->GetMetaDataDictionary(), image->GetPropertyList(), this->GetMimeType()->GetName()); // Handle UID itk::EncapsulateMetaData(m_ImageIO->GetMetaDataDictionary(), PROPERTY_KEY_UID, image->GetUID()); + // use compression if available + m_ImageIO->UseCompressionOn(); + m_ImageIO->SetFileName(path); + ImageReadAccessor imageAccess(image); LocaleSwitch localeSwitch2("C"); m_ImageIO->Write(imageAccess.GetData()); } catch (const std::exception &e) { mitkThrow() << e.what(); } } AbstractFileIO::ConfidenceLevel ItkImageIO::GetWriterConfidenceLevel() const { // Check if the image dimension is supported const auto *image = dynamic_cast(this->GetInput()); if (image == nullptr) { // We cannot write a null object, DUH! return IFileWriter::Unsupported; } //Fix to ensure T29391. Can be removed as soon as T28524 is solved //and the new MultiLabelSegmentation class is in place, as //segmentations won't be confused with simple images anymore. std::string className = this->GetInput()->GetNameOfClass(); if (className == "LabelSetImage") { // We cannot write a null object, DUH! return IFileWriter::Unsupported; } if (!m_ImageIO->SupportsDimension(image->GetDimension())) { // okay, dimension is not supported. We have to look at a special case: // 3D-Image with one slice. We can treat that as a 2D image. if ((image->GetDimension() == 3) && (image->GetSlicedGeometry()->GetSlices() == 1)) return IFileWriter::Supported; else return IFileWriter::Unsupported; } // Check if geometry information will be lost if (image->GetDimension() == 2 && !image->GetGeometry()->Is2DConvertable()) { return IFileWriter::PartiallySupported; } return IFileWriter::Supported; } ItkImageIO *ItkImageIO::IOClone() const { return new ItkImageIO(*this); } void ItkImageIO::InitializeDefaultMetaDataKeys() { this->m_DefaultMetaDataKeys.push_back("NRRD.space"); this->m_DefaultMetaDataKeys.push_back("NRRD.kinds"); this->m_DefaultMetaDataKeys.push_back(PROPERTY_NAME_TIMEGEOMETRY_TYPE); this->m_DefaultMetaDataKeys.push_back(PROPERTY_NAME_TIMEGEOMETRY_TIMEPOINTS); this->m_DefaultMetaDataKeys.push_back("ITK.InputFilterName"); } } diff --git a/Modules/Multilabel/autoload/IO/mitkLegacyLabelSetImageIO.cpp b/Modules/Multilabel/autoload/IO/mitkLegacyLabelSetImageIO.cpp index 9692c653a2..5a32b5ca74 100644 --- a/Modules/Multilabel/autoload/IO/mitkLegacyLabelSetImageIO.cpp +++ b/Modules/Multilabel/autoload/IO/mitkLegacyLabelSetImageIO.cpp @@ -1,296 +1,266 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef __mitkLabelSetImageWriter__cpp #define __mitkLabelSetImageWriter__cpp #include "mitkLegacyLabelSetImageIO.h" #include "mitkBasePropertySerializer.h" #include "mitkMultilabelIOMimeTypes.h" #include "mitkImageAccessByItk.h" #include "mitkMultiLabelIOHelper.h" #include "mitkLabelSetImageConverter.h" #include #include #include #include #include #include // itk #include "itkImageFileReader.h" #include "itkImageFileWriter.h" #include "itkMetaDataDictionary.h" #include "itkMetaDataObject.h" #include "itkNrrdImageIO.h" #include namespace mitk { constexpr char* const OPTION_NAME_MULTI_LAYER = "Multi layer handling"; constexpr char* const OPTION_NAME_MULTI_LAYER_ADAPT = "Adapt label values"; constexpr char* const OPTION_NAME_MULTI_LAYER_SPLIT = "Split layers"; LegacyLabelSetImageIO::LegacyLabelSetImageIO() : AbstractFileReader(MitkMultilabelIOMimeTypes::LEGACYLABELSET_MIMETYPE(), "MITK LabelSetImage (legacy)") { this->InitializeDefaultMetaDataKeys(); AbstractFileReader::SetRanking(10); IFileIO::Options options; std::vector multiLayerStrategy; multiLayerStrategy.push_back(OPTION_NAME_MULTI_LAYER_ADAPT); multiLayerStrategy.push_back(OPTION_NAME_MULTI_LAYER_SPLIT); options[OPTION_NAME_MULTI_LAYER] = multiLayerStrategy; this->SetDefaultOptions(options); this->RegisterService(); } IFileIO::ConfidenceLevel LegacyLabelSetImageIO::GetConfidenceLevel() const { if (AbstractFileReader::GetConfidenceLevel() == Unsupported) return Unsupported; const std::string fileName = this->GetLocalFileName(); itk::NrrdImageIO::Pointer io = itk::NrrdImageIO::New(); io->SetFileName(fileName); io->ReadImageInformation(); itk::MetaDataDictionary imgMetaDataDictionary = io->GetMetaDataDictionary(); std::string value(""); itk::ExposeMetaData(imgMetaDataDictionary, "modality", value); if (value.compare("org.mitk.image.multilabel") == 0) { return Supported; } else return Unsupported; } std::vector ExtractLabelSetsFromMetaData(const itk::MetaDataDictionary& dictionary) { std::vector result; // get labels and add them as properties to the image char keybuffer[256]; - unsigned int numberOfLayers = LegacyLabelSetImageIO::GetIntByKey(dictionary, "layers"); + unsigned int numberOfLayers = MultiLabelIOHelper::GetIntByKey(dictionary, "layers"); std::string _xmlStr; mitk::Label::Pointer label; for (unsigned int layerIdx = 0; layerIdx < numberOfLayers; layerIdx++) { sprintf(keybuffer, "layer_%03u", layerIdx); - int numberOfLabels = LegacyLabelSetImageIO::GetIntByKey(dictionary, keybuffer); + int numberOfLabels = MultiLabelIOHelper::GetIntByKey(dictionary, keybuffer); mitk::LabelSet::Pointer labelSet = mitk::LabelSet::New(); for (int labelIdx = 0; labelIdx < numberOfLabels; labelIdx++) { tinyxml2::XMLDocument doc; sprintf(keybuffer, "label_%03u_%05d", layerIdx, labelIdx); - _xmlStr = LegacyLabelSetImageIO::GetStringByKey(dictionary, keybuffer); + _xmlStr = MultiLabelIOHelper::GetStringByKey(dictionary, keybuffer); doc.Parse(_xmlStr.c_str(), _xmlStr.size()); auto* labelElem = doc.FirstChildElement("Label"); if (labelElem == nullptr) mitkThrow() << "Error parsing NRRD header for mitk::LabelSetImage IO"; label = mitk::MultiLabelIOHelper::LoadLabelFromXMLDocument(labelElem); if (label->GetValue() != mitk::LabelSetImage::UnlabeledLabelValue) { labelSet->AddLabel(label); labelSet->SetLayer(layerIdx); } else { MITK_INFO << "Multi label image contains a label specification for unlabeled pixels. This legacy information is ignored."; } } result.push_back(labelSet); } return result; } std::vector LegacyLabelSetImageIO::DoRead() { itk::NrrdImageIO::Pointer nrrdImageIO = itk::NrrdImageIO::New(); std::vector result; auto rawimage = ItkImageIO::LoadRawMitkImageFromImageIO(nrrdImageIO, this->GetLocalFileName()); const itk::MetaDataDictionary& dictionary = nrrdImageIO->GetMetaDataDictionary(); auto groupImages = SplitVectorImage(rawimage); auto labelsets = ExtractLabelSetsFromMetaData(dictionary); if (labelsets.size() != groupImages.size()) { mitkThrow() << "Loaded data is in an invalid state. Number of extracted layer images and labels sets does not match. Found layer images: " << groupImages.size() << "; found labelsets: " << labelsets.size(); } auto props = ItkImageIO::ExtractMetaDataAsPropertyList(nrrdImageIO->GetMetaDataDictionary(), this->GetMimeType()->GetName(), this->m_DefaultMetaDataKeys); const Options userOptions = this->GetOptions(); const auto multiLayerStrategy = userOptions.find(OPTION_NAME_MULTI_LAYER)->second.ToString(); if (multiLayerStrategy == OPTION_NAME_MULTI_LAYER_SPLIT) { //just split layers in different multi label images auto labelSetIterator = labelsets.begin(); for (auto image : groupImages) { auto output = ConvertImageToLabelSetImage(image); output->AddLabelSetToLayer(0, *labelSetIterator); //meta data handling for (auto& [name, prop] : *(props->GetMap())) { output->SetProperty(name, prop->Clone()); //need to clone to avoid that all outputs pointing to the same prop instances. } // Handle UID //Remark if we split the legacy label set into distinct layer images, the outputs should have new IDs. So we don't get the old one. result.push_back(output.GetPointer()); labelSetIterator++; } } else { //Avoid label id collision. LabelSetImage::LabelValueType maxValue = LabelSetImage::UnlabeledLabelValue; auto imageIterator = groupImages.begin(); std::vector adaptedLabelSets; for (auto labelset : labelsets) { const auto setValues = labelset->GetUsedLabelValues(); //generate mapping table; std::vector > labelMapping; for (auto vIter = setValues.crbegin(); vIter != setValues.crend(); vIter++) { //have to use reverse loop because TransferLabelContent (used to adapt content in the same image; see below) //would potentially corrupt otherwise the content due to "value collision between old values still present //and already adapted values. By going from highest value to lowest, we avoid that. if (LabelSetImage::UnlabeledLabelValue != *vIter) labelMapping.push_back({ *vIter, *vIter + maxValue }); } if (LabelSetImage::UnlabeledLabelValue != maxValue) { //adapt labelset auto mappedLabelSet = GenerateLabelSetWithMappedValues(labelset, labelMapping); adaptedLabelSets.emplace_back(mappedLabelSet); //adapt image (it is an inplace operation. the image instance stays the same. TransferLabelContent(*imageIterator, *imageIterator, mappedLabelSet, LabelSetImage::UnlabeledLabelValue, LabelSetImage::UnlabeledLabelValue, false, labelMapping, MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle::IgnoreLocks); } else { adaptedLabelSets.emplace_back(labelset); } const auto setMaxValue = *(std::max_element(setValues.begin(), setValues.end())); maxValue += setMaxValue; imageIterator++; } auto output = ConvertImageVectorToLabelSetImage(groupImages, rawimage->GetTimeGeometry()); LabelSetImage::SpatialGroupIndexType id = 0; for (auto labelset : adaptedLabelSets) { output->AddLabelSetToLayer(id, labelset); id++; } //meta data handling for (auto& [name, prop] : *(props->GetMap())) { output->SetProperty(name, prop->Clone()); //need to clone to avoid that all outputs pointing to the same prop instances. } // Handle UID if (dictionary.HasKey(PROPERTY_KEY_UID)) { itk::MetaDataObject::ConstPointer uidData = dynamic_cast*>(dictionary.Get(PROPERTY_KEY_UID)); if (uidData.IsNotNull()) { mitk::UIDManipulator uidManipulator(output); uidManipulator.SetUID(uidData->GetMetaDataObjectValue()); } } result.push_back(output.GetPointer()); } MITK_INFO << "...finished!"; return result; } - int LegacyLabelSetImageIO::GetIntByKey(const itk::MetaDataDictionary &dic, const std::string &str) - { - std::vector imgMetaKeys = dic.GetKeys(); - std::vector::const_iterator itKey = imgMetaKeys.begin(); - std::string metaString(""); - for (; itKey != imgMetaKeys.end(); itKey++) - { - itk::ExposeMetaData(dic, *itKey, metaString); - if (itKey->find(str.c_str()) != std::string::npos) - { - return atoi(metaString.c_str()); - } - } - return 0; - } - - std::string LegacyLabelSetImageIO::GetStringByKey(const itk::MetaDataDictionary &dic, const std::string &str) - { - std::vector imgMetaKeys = dic.GetKeys(); - std::vector::const_iterator itKey = imgMetaKeys.begin(); - std::string metaString(""); - for (; itKey != imgMetaKeys.end(); itKey++) - { - itk::ExposeMetaData(dic, *itKey, metaString); - if (itKey->find(str.c_str()) != std::string::npos) - { - return metaString; - } - } - return metaString; - } - LegacyLabelSetImageIO *LegacyLabelSetImageIO::Clone() const { return new LegacyLabelSetImageIO(*this); } void LegacyLabelSetImageIO::InitializeDefaultMetaDataKeys() { this->m_DefaultMetaDataKeys.push_back("NRRD.space"); this->m_DefaultMetaDataKeys.push_back("NRRD.kinds"); this->m_DefaultMetaDataKeys.push_back(PROPERTY_NAME_TIMEGEOMETRY_TYPE); this->m_DefaultMetaDataKeys.push_back(PROPERTY_NAME_TIMEGEOMETRY_TIMEPOINTS); this->m_DefaultMetaDataKeys.push_back("ITK.InputFilterName"); this->m_DefaultMetaDataKeys.push_back("label."); this->m_DefaultMetaDataKeys.push_back("layer."); this->m_DefaultMetaDataKeys.push_back("layers"); + this->m_DefaultMetaDataKeys.push_back("modality"); this->m_DefaultMetaDataKeys.push_back("org.mitk.label."); + this->m_DefaultMetaDataKeys.push_back("MITK.IO."); } } // namespace #endif //__mitkLabelSetImageWriter__cpp diff --git a/Modules/Multilabel/autoload/IO/mitkLegacyLabelSetImageIO.h b/Modules/Multilabel/autoload/IO/mitkLegacyLabelSetImageIO.h index 063ca96a63..0a99aebd99 100644 --- a/Modules/Multilabel/autoload/IO/mitkLegacyLabelSetImageIO.h +++ b/Modules/Multilabel/autoload/IO/mitkLegacyLabelSetImageIO.h @@ -1,64 +1,59 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkLegacyLabelSetImageIO_h #define mitkLegacyLabelSetImageIO_h #include #include namespace mitk { /** * Writes a LabelSetImage to a file. * mitk::Identifiable UID is supported and will be serialized. * @ingroup Process */ // The export macro should be removed. Currently, the unit // tests directly instantiate this class. class LegacyLabelSetImageIO : public mitk::AbstractFileReader { public: typedef mitk::LabelSetImage InputType; LegacyLabelSetImageIO(); // -------------- AbstractFileReader ------------- using AbstractFileReader::Read; ConfidenceLevel GetConfidenceLevel() const override; - // -------------- LegacyLabelSetImageIO specific functions ------------- - - static int GetIntByKey(const itk::MetaDataDictionary &dic, const std::string &str); - static std::string GetStringByKey(const itk::MetaDataDictionary &dic, const std::string &str); - protected: /** * @brief Reads a number of mitk::LabelSetImages from the file system * @return a vector of mitk::LabelSetImages * @throws throws an mitk::Exception if an error ocurrs during parsing the nrrd header */ std::vector> DoRead() override; // Fills the m_DefaultMetaDataKeys vector with default values virtual void InitializeDefaultMetaDataKeys(); private: LegacyLabelSetImageIO *Clone() const override; std::vector m_DefaultMetaDataKeys; }; } // end of namespace mitk #endif diff --git a/Modules/Multilabel/autoload/IO/mitkMultiLabelSegmentationIO.cpp b/Modules/Multilabel/autoload/IO/mitkMultiLabelSegmentationIO.cpp index 1592ef01ba..a42723eab3 100644 --- a/Modules/Multilabel/autoload/IO/mitkMultiLabelSegmentationIO.cpp +++ b/Modules/Multilabel/autoload/IO/mitkMultiLabelSegmentationIO.cpp @@ -1,653 +1,231 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkMultiLabelSegmentationIO.h" #include "mitkBasePropertySerializer.h" #include "mitkIOMimeTypes.h" #include "mitkImageAccessByItk.h" #include "mitkMultiLabelIOHelper.h" #include "mitkLabelSetImageConverter.h" #include #include #include #include #include #include // itk #include "itkImageFileReader.h" #include "itkImageFileWriter.h" #include "itkMetaDataDictionary.h" #include "itkMetaDataObject.h" #include "itkNrrdImageIO.h" #include namespace mitk { - constexpr char* const MULTILABEL_SEGMENTATION_MODALITY_VALUE = "org.mitk.image.multilabel.segmentation"; + constexpr char* const MULTILABEL_SEGMENTATION_MODALITY_KEY = "modality"; + constexpr char* const MULTILABEL_SEGMENTATION_MODALITY_VALUE = "org.mitk.multilabel.segmentation"; + constexpr char* const MULTILABEL_SEGMENTATION_VERSION_KEY = "org.mitk.multilabel.segmentation.version"; + constexpr int MULTILABEL_SEGMENTATION_VERSION_VALUE = 1; + constexpr char* const MULTILABEL_SEGMENTATION_LABELS_INFO_KEY = "org.mitk.multilabel.segmentation.labelgroups"; + constexpr char* const MULTILABEL_SEGMENTATION_UNLABELEDLABEL_LOCK_KEY = "org.mitk.multilabel.segmentation.unlabeledlabellock"; MultiLabelSegmentationIO::MultiLabelSegmentationIO() - : AbstractFileIO(LabelSetImage::GetStaticNameOfClass(), IOMimeTypes::NRRD_MIMETYPE(), "MITK Multilabel Image") + : AbstractFileIO(LabelSetImage::GetStaticNameOfClass(), IOMimeTypes::NRRD_MIMETYPE(), "MITK Multilabel Segmentation") { this->InitializeDefaultMetaDataKeys(); AbstractFileWriter::SetRanking(10); AbstractFileReader::SetRanking(10); this->RegisterService(); } IFileIO::ConfidenceLevel MultiLabelSegmentationIO::GetWriterConfidenceLevel() const { if (AbstractFileIO::GetWriterConfidenceLevel() == Unsupported) return Unsupported; const auto *input = static_cast(this->GetInput()); if (input) return Supported; else return Unsupported; } void MultiLabelSegmentationIO::Write() { ValidateOutputLocation(); auto input = dynamic_cast(this->GetInput()); mitk::LocaleSwitch localeSwitch("C"); mitk::Image::Pointer inputVector = mitk::ConvertLabelSetImageToImage(input); // image write if (inputVector.IsNull()) { mitkThrow() << "Cannot write non-image data"; } itk::NrrdImageIO::Pointer nrrdImageIo = itk::NrrdImageIO::New(); - // Clone the image geometry, because we might have to change it - // for writing purposes - BaseGeometry::Pointer geometry = inputVector->GetGeometry()->Clone(); - - // Check if geometry information will be lost - if (inputVector->GetDimension() == 2 && !geometry->Is2DConvertable()) - { - MITK_WARN << "Saving a 2D image with 3D geometry information. Geometry information will be lost! You might " - "consider using Convert2Dto3DImageFilter before saving."; - - // set matrix to identity - mitk::AffineTransform3D::Pointer affTrans = mitk::AffineTransform3D::New(); - affTrans->SetIdentity(); - mitk::Vector3D spacing = geometry->GetSpacing(); - mitk::Point3D origin = geometry->GetOrigin(); - geometry->SetIndexToWorldTransform(affTrans); - geometry->SetSpacing(spacing); - geometry->SetOrigin(origin); - } + ItkImageIO::PreparImageIOToWriteImage(nrrdImageIo, inputVector); LocalFile localFile(this); const std::string path = localFile.GetFileName(); MITK_INFO << "Writing image: " << path << std::endl; try { - // Implementation of writer using itkImageIO directly. This skips the use - // of templated itkImageFileWriter, which saves the multiplexing on MITK side. - - const unsigned int dimension = inputVector->GetDimension(); - const unsigned int *const dimensions = inputVector->GetDimensions(); - const mitk::PixelType pixelType = inputVector->GetPixelType(); - const mitk::Vector3D mitkSpacing = geometry->GetSpacing(); - const mitk::Point3D mitkOrigin = geometry->GetOrigin(); - - // Due to templating in itk, we are forced to save a 4D spacing and 4D Origin, - // though they are not supported in MITK - itk::Vector spacing4D; - spacing4D[0] = mitkSpacing[0]; - spacing4D[1] = mitkSpacing[1]; - spacing4D[2] = mitkSpacing[2]; - spacing4D[3] = 1; // There is no support for a 4D spacing. However, we should have a valid value here - - itk::Vector origin4D; - origin4D[0] = mitkOrigin[0]; - origin4D[1] = mitkOrigin[1]; - origin4D[2] = mitkOrigin[2]; - origin4D[3] = 0; // There is no support for a 4D origin. However, we should have a valid value here - - // Set the necessary information for imageIO - nrrdImageIo->SetNumberOfDimensions(dimension); - nrrdImageIo->SetPixelType(pixelType.GetPixelType()); - nrrdImageIo->SetComponentType(static_cast(pixelType.GetComponentType()) < PixelComponentUserType - ? pixelType.GetComponentType() - : itk::IOComponentEnum::UNKNOWNCOMPONENTTYPE); - nrrdImageIo->SetNumberOfComponents(pixelType.GetNumberOfComponents()); - - itk::ImageIORegion ioRegion(dimension); - - for (unsigned int i = 0; i < dimension; i++) - { - nrrdImageIo->SetDimensions(i, dimensions[i]); - nrrdImageIo->SetSpacing(i, spacing4D[i]); - nrrdImageIo->SetOrigin(i, origin4D[i]); - - mitk::Vector3D mitkDirection(0.0); - mitkDirection.SetVnlVector(geometry->GetIndexToWorldTransform()->GetMatrix().GetVnlMatrix().get_column(i).as_ref()); - itk::Vector direction4D; - direction4D[0] = mitkDirection[0]; - direction4D[1] = mitkDirection[1]; - direction4D[2] = mitkDirection[2]; - - // MITK only supports a 3x3 direction matrix. Due to templating in itk, however, we must - // save a 4x4 matrix for 4D images. in this case, add an homogneous component to the matrix. - if (i == 3) - { - direction4D[3] = 1; // homogenous component - } - else - { - direction4D[3] = 0; - } - vnl_vector axisDirection(dimension); - for (unsigned int j = 0; j < dimension; j++) - { - axisDirection[j] = direction4D[j] / spacing4D[i]; - } - nrrdImageIo->SetDirection(i, axisDirection); - - ioRegion.SetSize(i, inputVector->GetLargestPossibleRegion().GetSize(i)); - ioRegion.SetIndex(i, inputVector->GetLargestPossibleRegion().GetIndex(i)); - } - - // use compression if available - nrrdImageIo->UseCompressionOn(); - - nrrdImageIo->SetIORegion(ioRegion); - nrrdImageIo->SetFileName(path); - - // label set specific meta data - char keybuffer[512]; - char valbuffer[512]; - - sprintf(keybuffer, "modality"); - sprintf(valbuffer, MULTILABEL_SEGMENTATION_MODALITY_VALUE); itk::EncapsulateMetaData( - nrrdImageIo->GetMetaDataDictionary(), std::string(keybuffer), std::string(valbuffer)); + nrrdImageIo->GetMetaDataDictionary(), std::string(MULTILABEL_SEGMENTATION_MODALITY_KEY), std::string(MULTILABEL_SEGMENTATION_MODALITY_VALUE)); - sprintf(keybuffer, "layers"); - sprintf(valbuffer, "%1d", input->GetNumberOfLayers()); + //nrrd does only support string meta information. So we have to convert before. itk::EncapsulateMetaData( - nrrdImageIo->GetMetaDataDictionary(), std::string(keybuffer), std::string(valbuffer)); + nrrdImageIo->GetMetaDataDictionary(), std::string(MULTILABEL_SEGMENTATION_VERSION_KEY), std::to_string(MULTILABEL_SEGMENTATION_VERSION_VALUE)); - for (unsigned int layerIdx = 0; layerIdx < input->GetNumberOfLayers(); layerIdx++) - { - sprintf(keybuffer, "layer_%03u", layerIdx); // layer idx - sprintf(valbuffer, "%1u", input->GetNumberOfLabels(layerIdx)); // number of labels for the layer - itk::EncapsulateMetaData( - nrrdImageIo->GetMetaDataDictionary(), std::string(keybuffer), std::string(valbuffer)); - - auto iter = input->GetLabelSet(layerIdx)->IteratorConstBegin(); - unsigned int count(0); - while (iter != input->GetLabelSet(layerIdx)->IteratorConstEnd()) - { - tinyxml2::XMLDocument document; - document.InsertEndChild(document.NewDeclaration()); - auto *labelElem = mitk::MultiLabelIOHelper::GetLabelAsXMLElement(document, iter->second); - document.InsertEndChild(labelElem); - tinyxml2::XMLPrinter printer; - document.Print(&printer); - - sprintf(keybuffer, "org.mitk.label_%03u_%05u", layerIdx, count); - itk::EncapsulateMetaData( - nrrdImageIo->GetMetaDataDictionary(), std::string(keybuffer), printer.CStr()); - ++iter; - ++count; - } - } + auto json = MultiLabelIOHelper::SerializeMultLabelGroupsToJSON(input); + itk::EncapsulateMetaData( + nrrdImageIo->GetMetaDataDictionary(), std::string(MULTILABEL_SEGMENTATION_LABELS_INFO_KEY), json.dump()); // end label set specific meta data - // Handle time geometry - const auto* arbitraryTG = dynamic_cast(input->GetTimeGeometry()); - if (arbitraryTG) - { - itk::EncapsulateMetaData(nrrdImageIo->GetMetaDataDictionary(), - PROPERTY_KEY_TIMEGEOMETRY_TYPE, - ArbitraryTimeGeometry::GetStaticNameOfClass()); - - - auto metaTimePoints = ConvertTimePointListToMetaDataObject(arbitraryTG); - nrrdImageIo->GetMetaDataDictionary().Set(PROPERTY_KEY_TIMEGEOMETRY_TIMEPOINTS, metaTimePoints); - } + //nrrd does only support string meta information. So we have to convert before. + itk::EncapsulateMetaData( + nrrdImageIo->GetMetaDataDictionary(), std::string(MULTILABEL_SEGMENTATION_UNLABELEDLABEL_LOCK_KEY), std::to_string(input->GetUnlabeledLabelLock())); // Handle properties - mitk::PropertyList::Pointer imagePropertyList = input->GetPropertyList(); - for (const auto& property : *imagePropertyList->GetMap()) - { - mitk::CoreServicePointer propPersistenceService(mitk::CoreServices::GetPropertyPersistence()); - IPropertyPersistence::InfoResultType infoList = propPersistenceService->GetInfo(property.first, GetMimeType()->GetName(), true); - - if (infoList.empty()) - { - continue; - } - - std::string value = infoList.front()->GetSerializationFunction()(property.second); - - if (value == mitk::BaseProperty::VALUE_CANNOT_BE_CONVERTED_TO_STRING) - { - continue; - } - - std::string key = infoList.front()->GetKey(); - - itk::EncapsulateMetaData(nrrdImageIo->GetMetaDataDictionary(), key, value); - } + ItkImageIO::SavePropertyListAsMetaData(nrrdImageIo->GetMetaDataDictionary(), input->GetPropertyList(), this->GetMimeType()->GetName()); // Handle UID itk::EncapsulateMetaData(nrrdImageIo->GetMetaDataDictionary(), PROPERTY_KEY_UID, input->GetUID()); + // use compression if available + nrrdImageIo->UseCompressionOn(); + nrrdImageIo->SetFileName(path); + ImageReadAccessor imageAccess(inputVector); nrrdImageIo->Write(imageAccess.GetData()); } catch (const std::exception &e) { mitkThrow() << e.what(); } - // end image write } IFileIO::ConfidenceLevel MultiLabelSegmentationIO::GetReaderConfidenceLevel() const { if (AbstractFileIO::GetReaderConfidenceLevel() == Unsupported) return Unsupported; const std::string fileName = this->GetLocalFileName(); itk::NrrdImageIO::Pointer io = itk::NrrdImageIO::New(); io->SetFileName(fileName); io->ReadImageInformation(); itk::MetaDataDictionary imgMetaDataDictionary = io->GetMetaDataDictionary(); std::string value(""); itk::ExposeMetaData(imgMetaDataDictionary, "modality", value); if (value.compare(MULTILABEL_SEGMENTATION_MODALITY_VALUE) == 0) { return Supported; } else return Unsupported; } std::vector MultiLabelSegmentationIO::DoRead() { - mitk::LocaleSwitch localeSwitch("C"); - - // begin regular image loading, adapted from mitkItkImageIO itk::NrrdImageIO::Pointer nrrdImageIO = itk::NrrdImageIO::New(); - Image::Pointer image = Image::New(); - const unsigned int MINDIM = 2; - const unsigned int MAXDIM = 4; - - const std::string path = this->GetLocalFileName(); + std::vector result; - MITK_INFO << "loading " << path << " via itk::ImageIOFactory... " << std::endl; + auto rawimage = ItkImageIO::LoadRawMitkImageFromImageIO(nrrdImageIO, this->GetLocalFileName()); - // Check to see if we can read the file given the name or prefix - if (path.empty()) - { - mitkThrow() << "Empty filename in mitk::ItkImageIO "; - } - - // Got to allocate space for the image. Determine the characteristics of - // the image. - nrrdImageIO->SetFileName(path); - nrrdImageIO->ReadImageInformation(); + const itk::MetaDataDictionary& dictionary = nrrdImageIO->GetMetaDataDictionary(); - unsigned int ndim = nrrdImageIO->GetNumberOfDimensions(); - if (ndim < MINDIM || ndim > MAXDIM) + //check version + auto version = MultiLabelIOHelper::GetIntByKey(dictionary, MULTILABEL_SEGMENTATION_VERSION_KEY); + if (version > MULTILABEL_SEGMENTATION_VERSION_VALUE) { - MITK_WARN << "Sorry, only dimensions 2, 3 and 4 are supported. The given file has " << ndim - << " dimensions! Reading as 4D."; - ndim = MAXDIM; + mitkThrow() << "Data to read has unsupported version. Software is to old to ensure correct reading. Please use a compatible version of MITK or store data in another format. Version of data: " << version << "; Supported versions up to: "< labelsets = MultiLabelIOHelper::DeserializeMultLabelGroupsFromJSON(jlabelsets); - ScalarType spacing[MAXDIM]; - spacing[0] = 1.0f; - spacing[1] = 1.0f; - spacing[2] = 1.0f; - spacing[3] = 1.0f; - - Point3D origin; - origin.Fill(0); - - unsigned int i; - for (i = 0; i < ndim; ++i) + if (labelsets.size() != groupImages.size()) { - ioStart[i] = 0; - ioSize[i] = nrrdImageIO->GetDimensions(i); - if (i < MAXDIM) - { - dimensions[i] = nrrdImageIO->GetDimensions(i); - spacing[i] = nrrdImageIO->GetSpacing(i); - if (spacing[i] <= 0) - spacing[i] = 1.0f; - } - if (i < 3) - { - origin[i] = nrrdImageIO->GetOrigin(i); - } - } - - ioRegion.SetSize(ioSize); - ioRegion.SetIndex(ioStart); - - MITK_INFO << "ioRegion: " << ioRegion << std::endl; - nrrdImageIO->SetIORegion(ioRegion); - void *buffer = new unsigned char[nrrdImageIO->GetImageSizeInBytes()]; - nrrdImageIO->Read(buffer); - - image->Initialize(MakePixelType(nrrdImageIO), ndim, dimensions); - image->SetImportChannel(buffer, 0, Image::ManageMemory); - - // access direction of itk::Image and include spacing - mitk::Matrix3D matrix; - matrix.SetIdentity(); - unsigned int j, itkDimMax3 = (ndim >= 3 ? 3 : ndim); - for (i = 0; i < itkDimMax3; ++i) - for (j = 0; j < itkDimMax3; ++j) - matrix[i][j] = nrrdImageIO->GetDirection(j)[i]; - - // re-initialize PlaneGeometry with origin and direction - PlaneGeometry *planeGeometry = image->GetSlicedGeometry(0)->GetPlaneGeometry(0); - planeGeometry->SetOrigin(origin); - planeGeometry->GetIndexToWorldTransform()->SetMatrix(matrix); - - // re-initialize SlicedGeometry3D - SlicedGeometry3D *slicedGeometry = image->GetSlicedGeometry(0); - slicedGeometry->InitializeEvenlySpaced(planeGeometry, image->GetDimension(2)); - slicedGeometry->SetSpacing(spacing); - - MITK_INFO << slicedGeometry->GetCornerPoint(false, false, false); - MITK_INFO << slicedGeometry->GetCornerPoint(true, true, true); - - // re-initialize TimeGeometry - const itk::MetaDataDictionary& dictionary = nrrdImageIO->GetMetaDataDictionary(); - TimeGeometry::Pointer timeGeometry; - - if (dictionary.HasKey(PROPERTY_NAME_TIMEGEOMETRY_TYPE) || dictionary.HasKey(PROPERTY_KEY_TIMEGEOMETRY_TYPE)) - { // also check for the name because of backwards compatibility. Past code version stored with the name and not with - // the key - itk::MetaDataObject::ConstPointer timeGeometryTypeData; - if (dictionary.HasKey(PROPERTY_NAME_TIMEGEOMETRY_TYPE)) - { - timeGeometryTypeData = - dynamic_cast*>(dictionary.Get(PROPERTY_NAME_TIMEGEOMETRY_TYPE)); - } - else - { - timeGeometryTypeData = - dynamic_cast*>(dictionary.Get(PROPERTY_KEY_TIMEGEOMETRY_TYPE)); - } - - if (timeGeometryTypeData->GetMetaDataObjectValue() == ArbitraryTimeGeometry::GetStaticNameOfClass()) - { - MITK_INFO << "used time geometry: " << ArbitraryTimeGeometry::GetStaticNameOfClass(); - typedef std::vector TimePointVector; - TimePointVector timePoints; - - if (dictionary.HasKey(PROPERTY_NAME_TIMEGEOMETRY_TIMEPOINTS)) - { - timePoints = ConvertMetaDataObjectToTimePointList(dictionary.Get(PROPERTY_NAME_TIMEGEOMETRY_TIMEPOINTS)); - } - else if (dictionary.HasKey(PROPERTY_KEY_TIMEGEOMETRY_TIMEPOINTS)) - { - timePoints = ConvertMetaDataObjectToTimePointList(dictionary.Get(PROPERTY_KEY_TIMEGEOMETRY_TIMEPOINTS)); - } - - if (timePoints.empty()) - { - MITK_ERROR << "Stored timepoints are empty. Meta information seems to bee invalid. Switch to ProportionalTimeGeometry fallback"; - } - else if (timePoints.size() - 1 != image->GetDimension(3)) - { - MITK_ERROR << "Stored timepoints (" << timePoints.size() - 1 << ") and size of image time dimension (" - << image->GetDimension(3) << ") do not match. Switch to ProportionalTimeGeometry fallback"; - } - else - { - ArbitraryTimeGeometry::Pointer arbitraryTimeGeometry = ArbitraryTimeGeometry::New(); - TimePointVector::const_iterator pos = timePoints.begin(); - auto prePos = pos++; - - for (; pos != timePoints.end(); ++prePos, ++pos) - { - arbitraryTimeGeometry->AppendNewTimeStepClone(slicedGeometry, *prePos, *pos); - } - - timeGeometry = arbitraryTimeGeometry; - } - } - } - - if (timeGeometry.IsNull()) - { // Fallback. If no other valid time geometry has been created, create a ProportionalTimeGeometry - MITK_INFO << "used time geometry: " << ProportionalTimeGeometry::GetStaticNameOfClass(); - ProportionalTimeGeometry::Pointer propTimeGeometry = ProportionalTimeGeometry::New(); - propTimeGeometry->Initialize(slicedGeometry, image->GetDimension(3)); - timeGeometry = propTimeGeometry; + mitkThrow() << "Loaded data is in an invalid state. Number of extracted layer images and labels sets does not match. Found layer images: " << groupImages.size() << "; found labelsets: " << labelsets.size(); } - image->SetTimeGeometry(timeGeometry); + //construct multi layer segmentation out of layers and labelset info instances + LabelSetImage::LabelValueType maxValue = LabelSetImage::UnlabeledLabelValue; + auto imageIterator = groupImages.begin(); + std::vector adaptedLabelSets; - buffer = nullptr; - MITK_INFO << "number of image components: " << image->GetPixelType().GetNumberOfComponents(); + auto output = ConvertImageVectorToLabelSetImage(groupImages, rawimage->GetTimeGeometry()); - // end regular image loading - - LabelSetImage::Pointer output = ConvertImageToLabelSetImage(image); - - // get labels and add them as properties to the image - char keybuffer[256]; - - unsigned int numberOfLayers = GetIntByKey(dictionary, "layers"); - std::string _xmlStr; - mitk::Label::Pointer label; - - for (unsigned int layerIdx = 0; layerIdx < numberOfLayers; layerIdx++) + LabelSetImage::SpatialGroupIndexType id = 0; + for (auto labelset : labelsets) { - sprintf(keybuffer, "layer_%03u", layerIdx); - int numberOfLabels = GetIntByKey(dictionary, keybuffer); - - mitk::LabelSet::Pointer labelSet = mitk::LabelSet::New(); - - for (int labelIdx = 0; labelIdx < numberOfLabels; labelIdx++) - { - tinyxml2::XMLDocument doc; - sprintf(keybuffer, "label_%03u_%05d", layerIdx, labelIdx); - _xmlStr = GetStringByKey(dictionary, keybuffer); - doc.Parse(_xmlStr.c_str(), _xmlStr.size()); - - auto *labelElem = doc.FirstChildElement("Label"); - if (labelElem == nullptr) - mitkThrow() << "Error parsing NRRD header for mitk::LabelSetImage IO"; - - label = mitk::MultiLabelIOHelper::LoadLabelFromXMLDocument(labelElem); - - if (label->GetValue() != mitk::LabelSetImage::UnlabeledLabelValue) - { - labelSet->AddLabel(label); - labelSet->SetLayer(layerIdx); - } - else - { - MITK_INFO << "Multi label image contains a label specification for unlabeled pixels. This legacy information is ignored."; - } - } - output->AddLabelSetToLayer(layerIdx, labelSet); + output->AddLabelSetToLayer(id, labelset); + id++; } - for (auto iter = dictionary.Begin(), iterEnd = dictionary.End(); iter != iterEnd; - ++iter) + bool unlabeledLock = MultiLabelIOHelper::GetIntByKey(dictionary, MULTILABEL_SEGMENTATION_UNLABELEDLABEL_LOCK_KEY) != 0; + output->SetUnlabeledLabelLock(unlabeledLock); + + //meta data handling + auto props = ItkImageIO::ExtractMetaDataAsPropertyList(nrrdImageIO->GetMetaDataDictionary(), this->GetMimeType()->GetName(), this->m_DefaultMetaDataKeys); + for (auto& [name, prop] : *(props->GetMap())) { - if (iter->second->GetMetaDataObjectTypeInfo() == typeid(std::string)) - { - const std::string& key = iter->first; - std::string assumedPropertyName = key; - std::replace(assumedPropertyName.begin(), assumedPropertyName.end(), '_', '.'); - - std::string mimeTypeName = GetMimeType()->GetName(); - - // Check if there is already a info for the key and our mime type. - mitk::CoreServicePointer propPersistenceService(mitk::CoreServices::GetPropertyPersistence()); - IPropertyPersistence::InfoResultType infoList = propPersistenceService->GetInfoByKey(key); - - auto predicate = [&mimeTypeName](const PropertyPersistenceInfo::ConstPointer& x) { - return x.IsNotNull() && x->GetMimeTypeName() == mimeTypeName; - }; - auto finding = std::find_if(infoList.begin(), infoList.end(), predicate); - - if (finding == infoList.end()) - { - auto predicateWild = [](const PropertyPersistenceInfo::ConstPointer& x) { - return x.IsNotNull() && x->GetMimeTypeName() == PropertyPersistenceInfo::ANY_MIMETYPE_NAME(); - }; - finding = std::find_if(infoList.begin(), infoList.end(), predicateWild); - } - - PropertyPersistenceInfo::ConstPointer info; - - if (finding != infoList.end()) - { - assumedPropertyName = (*finding)->GetName(); - info = *finding; - } - else - { // we have not found anything suitable so we generate our own info - auto newInfo = PropertyPersistenceInfo::New(); - newInfo->SetNameAndKey(assumedPropertyName, key); - newInfo->SetMimeTypeName(PropertyPersistenceInfo::ANY_MIMETYPE_NAME()); - info = newInfo; - } - - std::string value = - dynamic_cast*>(iter->second.GetPointer())->GetMetaDataObjectValue(); - - mitk::BaseProperty::Pointer loadedProp = info->GetDeserializationFunction()(value); - - if (loadedProp.IsNull()) - { - MITK_ERROR << "Property cannot be correctly deserialized and is skipped. Check if data format is valid. Problematic property value string: \"" << value << "\"; Property info used to deserialized: " << info; - break; - } - - output->SetProperty(assumedPropertyName.c_str(), loadedProp); - - // Read properties should be persisted unless they are default properties - // which are written anyway - bool isDefaultKey = false; - - for (const auto& defaultKey : m_DefaultMetaDataKeys) - { - if (defaultKey.length() <= assumedPropertyName.length()) - { - // does the start match the default key - if (assumedPropertyName.substr(0, defaultKey.length()).find(defaultKey) != std::string::npos) - { - isDefaultKey = true; - break; - } - } - } - - if (!isDefaultKey) - { - propPersistenceService->AddInfo(info); - } - } + output->SetProperty(name, prop->Clone()); //need to clone to avoid that all outputs pointing to the same prop instances. } // Handle UID if (dictionary.HasKey(PROPERTY_KEY_UID)) { itk::MetaDataObject::ConstPointer uidData = dynamic_cast*>(dictionary.Get(PROPERTY_KEY_UID)); if (uidData.IsNotNull()) { mitk::UIDManipulator uidManipulator(output); uidManipulator.SetUID(uidData->GetMetaDataObjectValue()); } } + result.push_back(output.GetPointer()); MITK_INFO << "...finished!"; - - std::vector result; - result.push_back(output.GetPointer()); return result; } - int MultiLabelSegmentationIO::GetIntByKey(const itk::MetaDataDictionary &dic, const std::string &str) - { - std::vector imgMetaKeys = dic.GetKeys(); - std::vector::const_iterator itKey = imgMetaKeys.begin(); - std::string metaString(""); - for (; itKey != imgMetaKeys.end(); itKey++) - { - itk::ExposeMetaData(dic, *itKey, metaString); - if (itKey->find(str.c_str()) != std::string::npos) - { - return atoi(metaString.c_str()); - } - } - return 0; - } - - std::string MultiLabelSegmentationIO::GetStringByKey(const itk::MetaDataDictionary &dic, const std::string &str) - { - std::vector imgMetaKeys = dic.GetKeys(); - std::vector::const_iterator itKey = imgMetaKeys.begin(); - std::string metaString(""); - for (; itKey != imgMetaKeys.end(); itKey++) - { - itk::ExposeMetaData(dic, *itKey, metaString); - if (itKey->find(str.c_str()) != std::string::npos) - { - return metaString; - } - } - return metaString; - } - MultiLabelSegmentationIO *MultiLabelSegmentationIO::IOClone() const { return new MultiLabelSegmentationIO(*this); } void MultiLabelSegmentationIO::InitializeDefaultMetaDataKeys() { this->m_DefaultMetaDataKeys.push_back("NRRD.space"); this->m_DefaultMetaDataKeys.push_back("NRRD.kinds"); this->m_DefaultMetaDataKeys.push_back(PROPERTY_NAME_TIMEGEOMETRY_TYPE); this->m_DefaultMetaDataKeys.push_back(PROPERTY_NAME_TIMEGEOMETRY_TIMEPOINTS); this->m_DefaultMetaDataKeys.push_back("ITK.InputFilterName"); - this->m_DefaultMetaDataKeys.push_back("label."); - this->m_DefaultMetaDataKeys.push_back("layer."); - this->m_DefaultMetaDataKeys.push_back("layers"); - this->m_DefaultMetaDataKeys.push_back("org.mitk.label."); + this->m_DefaultMetaDataKeys.push_back("org.mitk.multilabel."); + this->m_DefaultMetaDataKeys.push_back("MITK.IO."); + this->m_DefaultMetaDataKeys.push_back(MULTILABEL_SEGMENTATION_MODALITY_KEY); } } // namespace diff --git a/Modules/Multilabel/autoload/IO/mitkMultiLabelSegmentationIO.h b/Modules/Multilabel/autoload/IO/mitkMultiLabelSegmentationIO.h index a60e0ed1c3..b2a5757143 100644 --- a/Modules/Multilabel/autoload/IO/mitkMultiLabelSegmentationIO.h +++ b/Modules/Multilabel/autoload/IO/mitkMultiLabelSegmentationIO.h @@ -1,69 +1,64 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkMultiLabelSegmentationIO_h #define mitkMultiLabelSegmentationIO_h #include #include namespace mitk { /** * Writes a LabelSetImage to a file. * mitk::Identifiable UID is supported and will be serialized. * @ingroup Process */ // The export macro should be removed. Currently, the unit // tests directly instantiate this class. class MultiLabelSegmentationIO : public mitk::AbstractFileIO { public: typedef mitk::LabelSetImage InputType; MultiLabelSegmentationIO(); // -------------- AbstractFileReader ------------- using AbstractFileReader::Read; ConfidenceLevel GetReaderConfidenceLevel() const override; // -------------- AbstractFileWriter ------------- void Write() override; ConfidenceLevel GetWriterConfidenceLevel() const override; - // -------------- MultiLabelSegmentationIO specific functions ------------- - - int GetIntByKey(const itk::MetaDataDictionary &dic, const std::string &str); - std::string GetStringByKey(const itk::MetaDataDictionary &dic, const std::string &str); - protected: /** * @brief Reads a number of mitk::LabelSetImages from the file system * @return a vector of mitk::LabelSetImages * @throws throws an mitk::Exception if an error ocurrs during parsing the nrrd header */ std::vector> DoRead() override; // Fills the m_DefaultMetaDataKeys vector with default values virtual void InitializeDefaultMetaDataKeys(); private: MultiLabelSegmentationIO *IOClone() const override; std::vector m_DefaultMetaDataKeys; }; } // end of namespace mitk #endif diff --git a/Modules/Multilabel/mitkLabelSetImage.cpp b/Modules/Multilabel/mitkLabelSetImage.cpp index d24ce93d97..a84515aab5 100644 --- a/Modules/Multilabel/mitkLabelSetImage.cpp +++ b/Modules/Multilabel/mitkLabelSetImage.cpp @@ -1,1515 +1,1556 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkLabelSetImage.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImagePixelReadAccessor.h" #include "mitkImagePixelWriteAccessor.h" #include "mitkInteractionConst.h" #include "mitkLookupTableProperty.h" #include "mitkPadImageFilter.h" #include "mitkRenderingManager.h" #include "mitkDICOMSegmentationPropertyHelper.h" #include "mitkDICOMQIPropertyHelper.h" #include #include #include #include #include #include #include //#include #include #include template void SetToZero(itk::Image *source) { source->FillBuffer(0); } template void CreateLabelMaskProcessing(mitk::Image *layerImage, mitk::Image *mask, mitk::LabelSet::PixelType index) { mitk::ImagePixelReadAccessor readAccessor(layerImage); mitk::ImagePixelWriteAccessor writeAccessor(mask); std::size_t numberOfPixels = 1; for (int dim = 0; dim < static_cast(VImageDimension); ++dim) numberOfPixels *= static_cast(readAccessor.GetDimension(dim)); auto src = readAccessor.GetData(); auto dest = writeAccessor.GetData(); for (std::size_t i = 0; i < numberOfPixels; ++i) { if (index == *(src + i)) *(dest + i) = 1; } } mitk::LabelSetImage::LabelSetImage() : mitk::Image(), m_UnlabeledLabelLock(false), m_ActiveLayer(0), m_activeLayerInvalid(false) { // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } mitk::LabelSetImage::LabelSetImage(const mitk::LabelSetImage &other) : Image(other), m_UnlabeledLabelLock(other.m_UnlabeledLabelLock), m_ActiveLayer(other.GetActiveLayer()), m_activeLayerInvalid(false) { for (unsigned int i = 0; i < other.GetNumberOfLayers(); i++) { // Clone LabelSet data mitk::LabelSet::Pointer lsClone = other.GetLabelSet(i)->Clone(); this->RegisterLabelSet(lsClone); m_LabelSetContainer.push_back(lsClone); // clone layer Image data mitk::Image::Pointer liClone = other.GetLayerImage(i)->Clone(); m_LayerContainer.push_back(liClone); } // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } void mitk::LabelSetImage::OnLabelSetModified() { Superclass::Modified(); } void mitk::LabelSetImage::Initialize(const mitk::Image *other) { mitk::PixelType pixelType(mitk::MakeScalarPixelType()); if (other->GetDimension() == 2) { const unsigned int dimensions[] = {other->GetDimension(0), other->GetDimension(1), 1}; Superclass::Initialize(pixelType, 3, dimensions); } else { Superclass::Initialize(pixelType, other->GetDimension(), other->GetDimensions()); } auto originalGeometry = other->GetTimeGeometry()->Clone(); this->SetTimeGeometry(originalGeometry); // initialize image memory to zero if (4 == this->GetDimension()) { AccessFixedDimensionByItk(this, SetToZero, 4); } else { AccessByItk(this, SetToZero); } // Transfer some general DICOM properties from the source image to derived image (e.g. Patient information,...) DICOMQIPropertyHelper::DeriveDICOMSourceProperties(other, this); // Add a inital LabelSet ans corresponding image data to the stack if (this->GetNumberOfLayers() == 0) { AddLayer(); } } mitk::LabelSetImage::~LabelSetImage() { for (auto ls : m_LabelSetContainer) { this->ReleaseLabelSet(ls); } m_LabelSetContainer.clear(); } mitk::Image *mitk::LabelSetImage::GetLayerImage(unsigned int layer) { return m_LayerContainer[layer]; } const mitk::Image *mitk::LabelSetImage::GetLayerImage(unsigned int layer) const { return m_LayerContainer[layer]; } unsigned int mitk::LabelSetImage::GetActiveLayer() const { return m_ActiveLayer; } unsigned int mitk::LabelSetImage::GetNumberOfLayers() const { return m_LabelSetContainer.size(); } void mitk::LabelSetImage::RegisterLabelSet(mitk::LabelSet* ls) { // add modified event listener to LabelSet (listen to LabelSet changes) itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &mitk::LabelSetImage::OnLabelSetModified); ls->AddObserver(itk::ModifiedEvent(), command); ls->AddLabelEvent.AddListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelAdded)); ls->ModifyLabelEvent.AddListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelModified)); ls->RemoveLabelEvent.AddListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelRemoved)); ls->m_ReservedLabelValuesFunctor = [this]() {return this->GetUsedLabelValues(); }; } void mitk::LabelSetImage::ReleaseLabelSet(mitk::LabelSet* ls) { ls->RemoveAllObservers(); ls->AddLabelEvent.RemoveListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelAdded)); ls->ModifyLabelEvent.RemoveListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelModified)); ls->RemoveLabelEvent.RemoveListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelRemoved)); ls->m_ReservedLabelValuesFunctor = nullptr; } void mitk::LabelSetImage::RemoveLayer() { int layerToDelete = GetActiveLayer(); // remove all observers from active label set GetLabelSet(layerToDelete)->RemoveAllObservers(); // set the active layer to one below, if exists. if (layerToDelete != 0) { SetActiveLayer(layerToDelete - 1); } else { // we are deleting layer zero, it should not be copied back into the vector m_activeLayerInvalid = true; } // remove labelset and image data m_LabelSetContainer.erase(m_LabelSetContainer.begin() + layerToDelete); m_LayerContainer.erase(m_LayerContainer.begin() + layerToDelete); if (layerToDelete == 0) { this->SetActiveLayer(layerToDelete); } this->OnGroupRemoved(layerToDelete); this->Modified(); } void mitk::LabelSetImage::RemoveSpatialGroup(SpatialGroupIndexType indexToDelete) { const auto activeIndex = GetActiveLayer(); // remove all observers from active label set GetLabelSet(indexToDelete)->RemoveAllObservers(); // set the active layer to one below, if exists. if (activeIndex>indexToDelete) { SetActiveLayer(activeIndex - 1); } else if (activeIndex==indexToDelete) { // we are deleting layer zero, it should not be copied back into the vector m_activeLayerInvalid = true; } // remove labelset and image data m_LabelSetContainer.erase(m_LabelSetContainer.begin() + indexToDelete); m_LayerContainer.erase(m_LayerContainer.begin() + indexToDelete); if (indexToDelete == activeIndex) { //enforces the new active layer to be set and copied auto newActiveIndex = indexToDelete < GetNumberOfLayers() ? indexToDelete : GetNumberOfLayers() - 1; this->SetActiveLayer(newActiveIndex); } this->OnGroupRemoved(indexToDelete); this->Modified(); } mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::GetUsedLabelValues() const { LabelValueVectorType result = { UnlabeledLabelValue }; for (auto [value, label] : m_LabelMap) { result.emplace_back(value); } return result; } unsigned int mitk::LabelSetImage::AddLayer(mitk::LabelSet::Pointer labelSet) { mitk::Image::Pointer newImage = mitk::Image::New(); newImage->Initialize(this->GetPixelType(), this->GetDimension(), this->GetDimensions(), this->GetImageDescriptor()->GetNumberOfChannels()); newImage->SetTimeGeometry(this->GetTimeGeometry()->Clone()); if (newImage->GetDimension() < 4) { AccessByItk(newImage, SetToZero); } else { AccessFixedDimensionByItk(newImage, SetToZero, 4); } return this->AddLayer(newImage, labelSet); } unsigned int mitk::LabelSetImage::AddLayer(mitk::Image::Pointer layerImage, mitk::LabelSet::Pointer labelSet) { unsigned int newLabelSetId = m_LayerContainer.size(); // Add labelset to layer mitk::LabelSet::Pointer ls; if (labelSet.IsNotNull()) { ls = labelSet; } else { ls = mitk::LabelSet::New(); ls->SetActiveLabel(UnlabeledLabelValue); } ls->SetLayer(newLabelSetId); // Add exterior Label to label set // mitk::Label::Pointer exteriorLabel = CreateExteriorLabel(); // push a new working image for the new layer m_LayerContainer.push_back(layerImage); // push a new labelset for the new layer m_LabelSetContainer.push_back(ls); RegisterLabelSet(ls); this->ReinitMaps(); SetActiveLayer(newLabelSetId); this->Modified(); this->OnGroupAdded(newLabelSetId); return newLabelSetId; } void mitk::LabelSetImage::AddLabelSetToLayer(const unsigned int layerIdx, const mitk::LabelSet* labelSet) { if (m_LayerContainer.size() <= layerIdx) { mitkThrow() << "Trying to add labelSet to non-existing layer."; } auto clonedLabelSet = labelSet->Clone(); this->RegisterLabelSet(clonedLabelSet); std::vector addedGroups; if (layerIdx < m_LabelSetContainer.size()) { if (m_LabelSetContainer[layerIdx].IsNotNull()) { this->ReleaseLabelSet(m_LabelSetContainer[layerIdx]); } m_LabelSetContainer[layerIdx] = clonedLabelSet; } else { while (layerIdx >= m_LabelSetContainer.size()) { mitk::LabelSet::Pointer defaultLabelSet = mitk::LabelSet::New(); defaultLabelSet->SetActiveLabel(UnlabeledLabelValue); defaultLabelSet->SetLayer(m_LabelSetContainer.size()); this->RegisterLabelSet(defaultLabelSet); this->ReinitMaps(); m_LabelSetContainer.push_back(defaultLabelSet); addedGroups.emplace_back(m_LabelSetContainer.size() - 1); } m_LabelSetContainer.push_back(clonedLabelSet); addedGroups.emplace_back(m_LabelSetContainer.size() - 1); } this->ReinitMaps(); for (auto groupID : addedGroups) { this->m_GroupAddedMessage.Send(groupID); } } void mitk::LabelSetImage::SetActiveLayer(unsigned int layer) { try { if (4 == this->GetDimension()) { if ((layer != GetActiveLayer() || m_activeLayerInvalid) && (layer < this->GetNumberOfLayers())) { BeforeChangeLayerEvent.Send(); if (m_activeLayerInvalid) { // We should not write the invalid layer back to the vector m_activeLayerInvalid = false; } else { AccessFixedDimensionByItk_n(this, ImageToLayerContainerProcessing, 4, (GetActiveLayer())); } m_ActiveLayer = layer; // only at this place m_ActiveLayer should be manipulated!!! Use Getter and Setter AccessFixedDimensionByItk_n(this, LayerContainerToImageProcessing, 4, (GetActiveLayer())); AfterChangeLayerEvent.Send(); } } else { if ((layer != GetActiveLayer() || m_activeLayerInvalid) && (layer < this->GetNumberOfLayers())) { BeforeChangeLayerEvent.Send(); if (m_activeLayerInvalid) { // We should not write the invalid layer back to the vector m_activeLayerInvalid = false; } else { AccessByItk_1(this, ImageToLayerContainerProcessing, GetActiveLayer()); } m_ActiveLayer = layer; // only at this place m_ActiveLayer should be manipulated!!! Use Getter and Setter AccessByItk_1(this, LayerContainerToImageProcessing, GetActiveLayer()); AfterChangeLayerEvent.Send(); } } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->Modified(); } void mitk::LabelSetImage::ClearBuffer() { try { if (this->GetDimension() == 4) { //remark: this extra branch was added, because LabelSetImage instances can be //dynamic (4D), but AccessByItk by support only supports 2D and 3D. //The option to change the CMake default dimensions for AccessByItk was //dropped (for details see discussion in T28756) AccessFixedDimensionByItk(this, ClearBufferProcessing,4); } else { AccessByItk(this, ClearBufferProcessing); } this->Modified(); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } } bool mitk::LabelSetImage::ExistLabel(PixelType pixelValue) const { bool exist = false; for (unsigned int lidx = 0; lidx < GetNumberOfLayers(); lidx++) exist |= m_LabelSetContainer[lidx]->ExistLabel(pixelValue); return exist; } bool mitk::LabelSetImage::ExistLabel(PixelType pixelValue, unsigned int layer) const { bool exist = m_LabelSetContainer[layer]->ExistLabel(pixelValue); return exist; } bool mitk::LabelSetImage::ExistLabelSet(unsigned int layer) const { return layer < m_LabelSetContainer.size(); } void mitk::LabelSetImage::MergeLabel(PixelType pixelValue, PixelType sourcePixelValue, unsigned int layer) { try { AccessByItk_2(this, MergeLabelProcessing, pixelValue, sourcePixelValue); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } GetLabelSet(layer)->SetActiveLabel(pixelValue); this->m_LabelModifiedMessage.Send(sourcePixelValue); this->m_LabelModifiedMessage.Send(pixelValue); this->m_LabelsChangedMessage.Send({ sourcePixelValue, pixelValue }); Modified(); } void mitk::LabelSetImage::MergeLabels(PixelType pixelValue, const std::vector& vectorOfSourcePixelValues, unsigned int layer) { try { for (unsigned int idx = 0; idx < vectorOfSourcePixelValues.size(); idx++) { AccessByItk_2(this, MergeLabelProcessing, pixelValue, vectorOfSourcePixelValues[idx]); this->m_LabelModifiedMessage.Send(vectorOfSourcePixelValues[idx]); } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } GetLabelSet(layer)->SetActiveLabel(pixelValue); this->m_LabelModifiedMessage.Send(pixelValue); auto modifiedValues = vectorOfSourcePixelValues; modifiedValues.push_back(pixelValue); this->m_LabelsChangedMessage.Send(modifiedValues); Modified(); } void mitk::LabelSetImage::RemoveLabel(LabelValueType pixelValue) { auto groupID = this->GetGroupIndexOfLabel(pixelValue); //first erase the pixel content (also triggers a LabelModified event) this->EraseLabel(pixelValue); //now remove the label entry itself this->GetLabelSet(groupID)->RemoveLabel(pixelValue); // in the interim version triggered by label set events: this->m_LabelRemovedMessage.Send(pixelValue); this->m_LabelsChangedMessage.Send({ pixelValue }); this->m_GroupModifiedMessage.Send(groupID); } void mitk::LabelSetImage::RemoveLabels(const std::vector& VectorOfLabelPixelValues) { for (unsigned int idx = 0; idx < VectorOfLabelPixelValues.size(); idx++) { this->RemoveLabel(VectorOfLabelPixelValues[idx]); this->m_LabelsChangedMessage.Send({ VectorOfLabelPixelValues[idx] }); } } void mitk::LabelSetImage::EraseLabel(PixelType pixelValue) { try { mitk::Image* groupImage = this; auto groupID = this->GetGroupIndexOfLabel(pixelValue); if (groupID != this->GetActiveLayer()) { groupImage = this->GetLayerImage(groupID); } if (4 == this->GetDimension()) { AccessFixedDimensionByItk_1(groupImage, EraseLabelProcessing, 4, pixelValue); } else { AccessByItk_1(groupImage, EraseLabelProcessing, pixelValue); } } catch (const itk::ExceptionObject& e) { mitkThrow() << e.GetDescription(); } this->m_LabelModifiedMessage.Send(pixelValue); this->m_LabelsChangedMessage.Send({ pixelValue }); Modified(); } void mitk::LabelSetImage::EraseLabels(const std::vector& VectorOfLabelPixelValues) { for (unsigned int idx = 0; idx < VectorOfLabelPixelValues.size(); idx++) { this->EraseLabel(VectorOfLabelPixelValues[idx]); } } mitk::Label *mitk::LabelSetImage::GetActiveLabel(unsigned int layer) { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer]->GetActiveLabel(); } const mitk::Label* mitk::LabelSetImage::GetActiveLabel(unsigned int layer) const { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer]->GetActiveLabel(); } mitk::Label *mitk::LabelSetImage::GetLabel(PixelType pixelValue, unsigned int layer) const { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer]->GetLabel(pixelValue); } mitk::LabelSet *mitk::LabelSetImage::GetLabelSet(unsigned int layer) { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer].GetPointer(); } const mitk::LabelSet *mitk::LabelSetImage::GetLabelSet(unsigned int layer) const { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer].GetPointer(); } mitk::LabelSet *mitk::LabelSetImage::GetActiveLabelSet() { if (m_LabelSetContainer.size() == 0) return nullptr; else return m_LabelSetContainer[GetActiveLayer()].GetPointer(); } const mitk::LabelSet* mitk::LabelSetImage::GetActiveLabelSet() const { if (m_LabelSetContainer.size() == 0) return nullptr; else return m_LabelSetContainer[GetActiveLayer()].GetPointer(); } void mitk::LabelSetImage::UpdateCenterOfMass(PixelType pixelValue) { this->UpdateCenterOfMass(pixelValue, this->GetGroupIndexOfLabel(pixelValue)); } void mitk::LabelSetImage::UpdateCenterOfMass(PixelType pixelValue, unsigned int layer) { if (4 == this->GetDimension()) { AccessFixedDimensionByItk_2(this, CalculateCenterOfMassProcessing, 4, pixelValue, layer); } else { AccessByItk_2(this, CalculateCenterOfMassProcessing, pixelValue, layer); } } unsigned int mitk::LabelSetImage::GetNumberOfLabels(unsigned int layer) const { return m_LabelSetContainer[layer]->GetNumberOfLabels(); } unsigned int mitk::LabelSetImage::GetTotalNumberOfLabels() const { unsigned int totalLabels(0); auto layerIter = m_LabelSetContainer.begin(); for (; layerIter != m_LabelSetContainer.end(); ++layerIter) totalLabels += (*layerIter)->GetNumberOfLabels(); return totalLabels; } void mitk::LabelSetImage::MaskStamp(mitk::Image *mask, bool forceOverwrite) { try { mitk::PadImageFilter::Pointer padImageFilter = mitk::PadImageFilter::New(); padImageFilter->SetInput(0, mask); padImageFilter->SetInput(1, this); padImageFilter->SetPadConstant(0); padImageFilter->SetBinaryFilter(false); padImageFilter->SetLowerThreshold(0); padImageFilter->SetUpperThreshold(1); padImageFilter->Update(); mitk::Image::Pointer paddedMask = padImageFilter->GetOutput(); if (paddedMask.IsNull()) return; AccessByItk_2(this, MaskStampProcessing, paddedMask, forceOverwrite); } catch (...) { mitkThrow() << "Could not stamp the provided mask on the selected label."; } } mitk::Image::Pointer mitk::LabelSetImage::CreateLabelMask(PixelType index, bool useActiveLayer, unsigned int layer) { auto previousActiveLayer = this->GetActiveLayer(); auto mask = mitk::Image::New(); try { // mask->Initialize(this) does not work here if this label set image has a single slice, // since the mask would be automatically flattened to a 2-d image, whereas we expect the // original dimension of this label set image. Hence, initialize the mask more explicitly: mask->Initialize(this->GetPixelType(), this->GetDimension(), this->GetDimensions()); mask->SetTimeGeometry(this->GetTimeGeometry()->Clone()); auto byteSize = sizeof(LabelSetImage::PixelType); for (unsigned int dim = 0; dim < mask->GetDimension(); ++dim) byteSize *= mask->GetDimension(dim); { ImageWriteAccessor accessor(mask); memset(accessor.GetData(), 0, byteSize); } if (!useActiveLayer) this->SetActiveLayer(layer); if (4 == this->GetDimension()) { ::CreateLabelMaskProcessing<4>(this, mask, index); } else if (3 == this->GetDimension()) { ::CreateLabelMaskProcessing(this, mask, index); } else { mitkThrow(); } } catch (...) { if (!useActiveLayer) this->SetActiveLayer(previousActiveLayer); mitkThrow() << "Could not create a mask out of the selected label."; } if (!useActiveLayer) this->SetActiveLayer(previousActiveLayer); return mask; } void mitk::LabelSetImage::InitializeByLabeledImage(mitk::Image::Pointer image) { if (image.IsNull() || image->IsEmpty() || !image->IsInitialized()) mitkThrow() << "Invalid labeled image."; try { this->Initialize(image); unsigned int byteSize = sizeof(LabelSetImage::PixelType); for (unsigned int dim = 0; dim < image->GetDimension(); ++dim) { byteSize *= image->GetDimension(dim); } mitk::ImageWriteAccessor *accessor = new mitk::ImageWriteAccessor(static_cast(this)); memset(accessor->GetData(), 0, byteSize); delete accessor; auto geometry = image->GetTimeGeometry()->Clone(); this->SetTimeGeometry(geometry); if (image->GetDimension() == 3) { AccessTwoImagesFixedDimensionByItk(this, image, InitializeByLabeledImageProcessing, 3); } else if (image->GetDimension() == 4) { AccessTwoImagesFixedDimensionByItk(this, image, InitializeByLabeledImageProcessing, 4); } else { mitkThrow() << image->GetDimension() << "-dimensional label set images not yet supported"; } } catch (...) { mitkThrow() << "Could not intialize by provided labeled image."; } this->Modified(); } template void mitk::LabelSetImage::InitializeByLabeledImageProcessing(LabelSetImageType *labelSetImage, ImageType *image) { typedef itk::ImageRegionConstIteratorWithIndex SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; TargetIteratorType targetIter(labelSetImage, labelSetImage->GetRequestedRegion()); targetIter.GoToBegin(); SourceIteratorType sourceIter(image, image->GetRequestedRegion()); sourceIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { auto sourceValue = static_cast(sourceIter.Get()); targetIter.Set(sourceValue); if (LabelSetImage::UnlabeledLabelValue!=sourceValue && !this->ExistLabel(sourceValue)) { std::stringstream name; name << "object-" << sourceValue; double rgba[4]; m_LabelSetContainer[this->GetActiveLayer()]->GetLookupTable()->GetTableValue(sourceValue, rgba); mitk::Color color; color.SetRed(rgba[0]); color.SetGreen(rgba[1]); color.SetBlue(rgba[2]); auto label = mitk::Label::New(); label->SetName(name.str().c_str()); label->SetColor(color); label->SetOpacity(rgba[3]); label->SetValue(sourceValue); this->GetLabelSet()->AddLabel(label); if (GetActiveLabelSet()->GetNumberOfLabels() >= mitk::Label::MAX_LABEL_VALUE || sourceValue >= mitk::Label::MAX_LABEL_VALUE) this->AddLayer(); } ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::MaskStampProcessing(ImageType *itkImage, mitk::Image *mask, bool forceOverwrite) { typename ImageType::Pointer itkMask; mitk::CastToItkImage(mask, itkMask); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(itkMask, itkMask->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkImage, itkImage->GetLargestPossibleRegion()); targetIter.GoToBegin(); int activeLabel = this->GetActiveLabel(GetActiveLayer())->GetValue(); while (!sourceIter.IsAtEnd()) { PixelType sourceValue = sourceIter.Get(); PixelType targetValue = targetIter.Get(); if ((sourceValue != UnlabeledLabelValue) && (forceOverwrite || !this->IsLabelLocked(targetValue))) // skip exterior and locked labels { targetIter.Set(activeLabel); } ++sourceIter; ++targetIter; } this->Modified(); } template void mitk::LabelSetImage::CalculateCenterOfMassProcessing(ImageType *itkImage, PixelType pixelValue, unsigned int layer) { if (ImageType::GetImageDimension() != 3) { return; } auto labelGeometryFilter = itk::LabelGeometryImageFilter::New(); labelGeometryFilter->SetInput(itkImage); labelGeometryFilter->Update(); auto centroid = labelGeometryFilter->GetCentroid(pixelValue); mitk::Point3D pos; pos[0] = centroid[0]; pos[1] = centroid[1]; pos[2] = centroid[2]; GetLabelSet(layer)->GetLabel(pixelValue)->SetCenterOfMassIndex(pos); this->GetSlicedGeometry()->IndexToWorld(pos, pos); // TODO: TimeGeometry? GetLabelSet(layer)->GetLabel(pixelValue)->SetCenterOfMassCoordinates(pos); } template void mitk::LabelSetImage::ClearBufferProcessing(ImageType *itkImage) { itkImage->FillBuffer(0); } template void mitk::LabelSetImage::LayerContainerToImageProcessing(itk::Image *target, unsigned int layer) { typedef itk::Image ImageType; typename ImageType::Pointer itkSource; // mitk::CastToItkImage(m_LayerContainer[layer], itkSource); itkSource = ImageToItkImage(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(itkSource, itkSource->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(target, target->GetLargestPossibleRegion()); targetIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { targetIter.Set(sourceIter.Get()); ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::ImageToLayerContainerProcessing(itk::Image *source, unsigned int layer) const { typedef itk::Image ImageType; typename ImageType::Pointer itkTarget; // mitk::CastToItkImage(m_LayerContainer[layer], itkTarget); itkTarget = ImageToItkImage(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(source, source->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkTarget, itkTarget->GetLargestPossibleRegion()); targetIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { targetIter.Set(sourceIter.Get()); ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::EraseLabelProcessing(ImageType *itkImage, PixelType pixelValue) { typedef itk::ImageRegionIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { PixelType value = iter.Get(); if (value == pixelValue) { iter.Set(0); } ++iter; } } template void mitk::LabelSetImage::MergeLabelProcessing(ImageType *itkImage, PixelType pixelValue, PixelType index) { typedef itk::ImageRegionIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { if (iter.Get() == index) { iter.Set(pixelValue); } ++iter; } } void mitk::LabelSetImage::OnLabelAdded(LabelValueType labelValue) { Label* label = nullptr; unsigned int layerID = 0; for (; layerID < this->GetNumberOfLayers(); ++layerID) { label = this->GetLabel(labelValue, layerID); if (nullptr != label) break; } if (!label) mitkThrow() << "Wrong internal state. OnLabelAdded was triggered, but label cannot be found. Invalid label: " << labelValue; AddLabelToMap(labelValue, label, layerID); this->m_LabelAddedMessage.Send(labelValue); } void mitk::LabelSetImage::AddLabelToMap(LabelValueType labelValue, mitk::Label* label, SpatialGroupIndexType groupID) { if (m_LabelMap.find(labelValue)!=m_LabelMap.end()) mitkThrow() << "Segmentation is in an invalid state: Label value collision. A label was added with a LabelValue already in use. LabelValue: " << labelValue; m_LabelMap[labelValue] = label; m_LabelToGroupMap[labelValue] = groupID; auto groupFinding = m_GroupToLabelMap.find(groupID); if (groupFinding == m_GroupToLabelMap.end()) { m_GroupToLabelMap[groupID] = { labelValue }; } else { m_GroupToLabelMap[groupID].push_back(labelValue); } } void mitk::LabelSetImage::OnLabelModified(LabelValueType labelValue) { this->m_LabelModifiedMessage.Send(labelValue); } void mitk::LabelSetImage::OnLabelRemoved(LabelValueType labelValue) { m_LabelMap.erase(labelValue); auto finding = m_LabelToGroupMap.find(labelValue); if (finding != m_LabelToGroupMap.end()) { auto labelsInGroup = m_GroupToLabelMap[finding->second]; auto labelFinding = std::find(labelsInGroup.begin(), labelsInGroup.end(),finding->second); if (labelFinding != labelsInGroup.end()) { labelsInGroup.erase(labelFinding); } m_LabelToGroupMap.erase(labelValue); } this->m_LabelRemovedMessage.Send(labelValue); } void mitk::LabelSetImage::OnGroupAdded(SpatialGroupIndexType groupIndex) { auto result = m_GroupToLabelMap.insert(std::make_pair(groupIndex, LabelValueVectorType())); this->m_GroupAddedMessage.Send(groupIndex); } void mitk::LabelSetImage::OnGroupModified(SpatialGroupIndexType groupIndex) { this->m_GroupModifiedMessage.Send(groupIndex); } void mitk::LabelSetImage::OnGroupRemoved(SpatialGroupIndexType groupIndex) { this->ReinitMaps(); this->m_GroupRemovedMessage.Send(groupIndex); } // future implementation for T28524 //bool mitk::LabelSetImage::ExistLabel(LabelValueType value, SpatialGroupIndexType groupIndex) const //{ // auto finding = m_LabelToGroupMap.find(value); // if (m_LabelToGroupMap.end() != finding) // { // return finding->second == groupIndex; // } // return false; //} // //bool mitk::LabelSetImage::ExistGroup(SpatialGroupIndexType index) const //{ // return index < m_LabelSetContainer.size(); //} +bool mitk::LabelSetImage::ExistGroup(SpatialGroupIndexType index) const +{ + return index < m_LabelSetContainer.size(); +} + bool mitk::LabelSetImage::IsLabeInGroup(LabelValueType value) const { SpatialGroupIndexType dummy; return this->IsLabeInGroup(value, dummy); } bool mitk::LabelSetImage::IsLabeInGroup(LabelValueType value, SpatialGroupIndexType& groupIndex) const { auto finding = m_LabelToGroupMap.find(value); if (m_LabelToGroupMap.end() != finding) { groupIndex = finding->second; return true; } return false; } mitk::LabelSetImage::SpatialGroupIndexType mitk::LabelSetImage::GetGroupIndexOfLabel(LabelValueType value) const { auto finding = m_LabelToGroupMap.find(value); if (m_LabelToGroupMap.end() == finding) { mitkThrow()<< "Cannot deduce group index. Passed label value does not exist. Value: "<< value; } return finding->second; } const mitk::Label* mitk::LabelSetImage::GetLabel(LabelValueType value) const { auto finding = m_LabelMap.find(value); if (m_LabelMap.end() != finding) { return finding->second; } return nullptr; }; mitk::Label* mitk::LabelSetImage::GetLabel(LabelValueType value) { auto finding = m_LabelMap.find(value); if (m_LabelMap.end() != finding) { return finding->second; } return nullptr; }; bool mitk::LabelSetImage::IsLabelLocked(LabelValueType value) const { if (value == UnlabeledLabelValue) { return m_UnlabeledLabelLock; } const auto label = this->GetLabel(value); return label->GetLocked(); } const mitk::LabelSetImage::ConstLabelVectorType mitk::LabelSetImage::GetLabels() const { ConstLabelVectorType result; for (auto [value, label] : m_LabelMap) { result.emplace_back(label); } return result; } const mitk::LabelSetImage::LabelVectorType mitk::LabelSetImage::GetLabels() { LabelVectorType result; for (auto [value, label] : m_LabelMap) { result.emplace_back(label); } return result; } +const mitk::LabelSetImage::ConstLabelVectorType mitk::LabelSetImage::GetLabelsInGroup(SpatialGroupIndexType index) const +{ + if (!this->ExistGroup(index)) + { + mitkThrow() << "Cannot get labels of an invalid group. Invalid group index: " << index; + } + + mitk::LabelSetImage::ConstLabelVectorType result; + + const auto labellist = m_GroupToLabelMap.find(index)->second; + for (const auto& labelvalue : labellist) + { + result.emplace_back(this->GetLabel(labelvalue)); + } + + return result; +} + +const mitk::LabelSetImage::LabelVectorType mitk::LabelSetImage::GetLabelsInGroup(SpatialGroupIndexType index) +{ + if (!this->ExistGroup(index)) + { + mitkThrow() << "Cannot get labels of an invalid group. Invalid group index: " << index; + } + + mitk::LabelSetImage::LabelVectorType result; + + const auto labellist = m_GroupToLabelMap[index]; + for (const auto& labelvalue : labellist) + { + result.emplace_back(this->GetLabel(labelvalue)); + } + + return result; +} + void mitk::LabelSetImage::ReinitMaps() { this->m_LabelMap.clear(); this->m_LabelToGroupMap.clear(); this->m_GroupToLabelMap.clear(); for (SpatialGroupIndexType layerID = 0; layerID < this->GetNumberOfLayers(); ++layerID) { auto labelSet = this->GetLabelSet(layerID); for (auto iter = labelSet->IteratorBegin(); iter != labelSet->IteratorEnd(); ++iter) { if (iter->first != UnlabeledLabelValue) { this->AddLabelToMap(iter->first, iter->second, layerID); } } } } bool mitk::Equal(const mitk::LabelSetImage &leftHandSide, const mitk::LabelSetImage &rightHandSide, ScalarType eps, bool verbose) { bool returnValue = true; /* LabelSetImage members */ MITK_INFO(verbose) << "--- LabelSetImage Equal ---"; // number layers returnValue = leftHandSide.GetNumberOfLayers() == rightHandSide.GetNumberOfLayers(); if (!returnValue) { MITK_INFO(verbose) << "Number of layers not equal."; return false; } // total number labels returnValue = leftHandSide.GetTotalNumberOfLabels() == rightHandSide.GetTotalNumberOfLabels(); if (!returnValue) { MITK_INFO(verbose) << "Total number of labels not equal."; return false; } // active layer returnValue = leftHandSide.GetActiveLayer() == rightHandSide.GetActiveLayer(); if (!returnValue) { MITK_INFO(verbose) << "Active layer not equal."; return false; } if (4 == leftHandSide.GetDimension()) { MITK_INFO(verbose) << "Can not compare image data for 4D images - skipping check."; } else { // working image data returnValue = mitk::Equal((const mitk::Image &)leftHandSide, (const mitk::Image &)rightHandSide, eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Working image data not equal."; return false; } } for (unsigned int layerIndex = 0; layerIndex < leftHandSide.GetNumberOfLayers(); layerIndex++) { if (4 == leftHandSide.GetDimension()) { MITK_INFO(verbose) << "Can not compare image data for 4D images - skipping check."; } else { // layer image data returnValue = mitk::Equal(*leftHandSide.GetLayerImage(layerIndex), *rightHandSide.GetLayerImage(layerIndex), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Layer image data not equal."; return false; } } // layer labelset data returnValue = mitk::Equal(*leftHandSide.GetLabelSet(layerIndex), *rightHandSide.GetLabelSet(layerIndex), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Layer labelset data not equal."; return false; } } return returnValue; } /** Functor class that implements the label transfer and is used in conjunction with the itk::BinaryFunctorImageFilter. * For details regarding the usage of the filter and the functor patterns, please see info of itk::BinaryFunctorImageFilter. */ template class LabelTransferFunctor { public: LabelTransferFunctor() {}; LabelTransferFunctor(const mitk::LabelSet* destinationLabelSet, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, mitk::Label::PixelType sourceLabel, mitk::Label::PixelType newDestinationLabel, mitk::MultiLabelSegmentation::MergeStyle mergeStyle, mitk::MultiLabelSegmentation::OverwriteStyle overwriteStyle) : m_DestinationLabelSet(destinationLabelSet), m_SourceBackground(sourceBackground), m_DestinationBackground(destinationBackground), m_DestinationBackgroundLocked(destinationBackgroundLocked), m_SourceLabel(sourceLabel), m_NewDestinationLabel(newDestinationLabel), m_MergeStyle(mergeStyle), m_OverwriteStyle(overwriteStyle) { }; ~LabelTransferFunctor() {}; bool operator!=(const LabelTransferFunctor& other)const { return !(*this == other); } bool operator==(const LabelTransferFunctor& other) const { return this->m_SourceBackground == other.m_SourceBackground && this->m_DestinationBackground == other.m_DestinationBackground && this->m_DestinationBackgroundLocked == other.m_DestinationBackgroundLocked && this->m_SourceLabel == other.m_SourceLabel && this->m_NewDestinationLabel == other.m_NewDestinationLabel && this->m_MergeStyle == other.m_MergeStyle && this->m_OverwriteStyle == other.m_OverwriteStyle && this->m_DestinationLabelSet == other.m_DestinationLabelSet; } LabelTransferFunctor& operator=(const LabelTransferFunctor& other) { this->m_DestinationLabelSet = other.m_DestinationLabelSet; this->m_SourceBackground = other.m_SourceBackground; this->m_DestinationBackground = other.m_DestinationBackground; this->m_DestinationBackgroundLocked = other.m_DestinationBackgroundLocked; this->m_SourceLabel = other.m_SourceLabel; this->m_NewDestinationLabel = other.m_NewDestinationLabel; this->m_MergeStyle = other.m_MergeStyle; this->m_OverwriteStyle = other.m_OverwriteStyle; return *this; } inline TOutputpixel operator()(const TDestinationPixel& existingDestinationValue, const TSourcePixel& existingSourceValue) { if (existingSourceValue == this->m_SourceLabel) { if (mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks == this->m_OverwriteStyle) { return this->m_NewDestinationLabel; } else { if (existingDestinationValue == m_DestinationBackground) { if (!m_DestinationBackgroundLocked) { return this->m_NewDestinationLabel; } } else { auto label = this->m_DestinationLabelSet->GetLabel(existingDestinationValue); if (nullptr == label || !label->GetLocked()) { return this->m_NewDestinationLabel; } } } } else if (mitk::MultiLabelSegmentation::MergeStyle::Replace == this->m_MergeStyle && existingSourceValue == this->m_SourceBackground && existingDestinationValue == this->m_NewDestinationLabel && (mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks == this->m_OverwriteStyle || !this->m_DestinationBackgroundLocked)) { return this->m_DestinationBackground; } return existingDestinationValue; } private: const mitk::LabelSet* m_DestinationLabelSet = nullptr; mitk::Label::PixelType m_SourceBackground = 0; mitk::Label::PixelType m_DestinationBackground = 0; bool m_DestinationBackgroundLocked = false; mitk::Label::PixelType m_SourceLabel = 1; mitk::Label::PixelType m_NewDestinationLabel = 1; mitk::MultiLabelSegmentation::MergeStyle m_MergeStyle = mitk::MultiLabelSegmentation::MergeStyle::Replace; mitk::MultiLabelSegmentation::OverwriteStyle m_OverwriteStyle = mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks; }; /**Helper function used by TransferLabelContentAtTimeStep to allow the templating over different image dimensions in conjunction of AccessFixedPixelTypeByItk_n.*/ template void TransferLabelContentAtTimeStepHelper(const itk::Image* itkSourceImage, mitk::Image* destinationImage, const mitk::LabelSet* destinationLabelSet, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, mitk::Label::PixelType sourceLabel, mitk::Label::PixelType newDestinationLabel, mitk::MultiLabelSegmentation::MergeStyle mergeStyle, mitk::MultiLabelSegmentation::OverwriteStyle overwriteStyle) { typedef itk::Image ContentImageType; typename ContentImageType::Pointer itkDestinationImage; mitk::CastToItkImage(destinationImage, itkDestinationImage); auto sourceRegion = itkSourceImage->GetLargestPossibleRegion(); auto relevantRegion = itkDestinationImage->GetLargestPossibleRegion(); bool overlapping = relevantRegion.Crop(sourceRegion); if (!overlapping) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage and destinationImage seem to have no overlapping image region."; } typedef LabelTransferFunctor LabelTransferFunctorType; typedef itk::BinaryFunctorImageFilter FilterType; LabelTransferFunctorType transferFunctor(destinationLabelSet, sourceBackground, destinationBackground, destinationBackgroundLocked, sourceLabel, newDestinationLabel, mergeStyle, overwriteStyle); auto transferFilter = FilterType::New(); transferFilter->SetFunctor(transferFunctor); transferFilter->InPlaceOn(); transferFilter->SetInput1(itkDestinationImage); transferFilter->SetInput2(itkSourceImage); transferFilter->GetOutput()->SetRequestedRegion(relevantRegion); transferFilter->Update(); } void mitk::TransferLabelContentAtTimeStep( const Image* sourceImage, Image* destinationImage, const mitk::LabelSet* destinationLabelSet, const TimeStepType timeStep, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, std::vector > labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; destinationImage must not be null."; } if (nullptr == destinationLabelSet) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; destinationLabelSet must not be null"; } if (sourceImage == destinationImage && labelMapping.size() > 1) { MITK_DEBUG << "Warning. Using TransferLabelContentAtTimeStep or TransferLabelContent with equal source and destination and more then on label to transfer, can lead to wrong results. Please see documentation and verify that the usage is OK."; } Image::ConstPointer sourceImageAtTimeStep = SelectImageByTimeStep(sourceImage, timeStep); Image::Pointer destinationImageAtTimeStep = SelectImageByTimeStep(destinationImage, timeStep); if (nullptr == sourceImageAtTimeStep) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage does not have the requested time step: " << timeStep; } if (nullptr == destinationImageAtTimeStep) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; destinationImage does not have the requested time step: " << timeStep; } for (const auto& [sourceLabel, newDestinationLabel] : labelMapping) { if (LabelSetImage::UnlabeledLabelValue!=newDestinationLabel && nullptr == destinationLabelSet->GetLabel(newDestinationLabel)) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep. Defined destination label does not exist in destinationImage. newDestinationLabel: " << newDestinationLabel; } AccessFixedPixelTypeByItk_n(sourceImageAtTimeStep, TransferLabelContentAtTimeStepHelper, (Label::PixelType), (destinationImageAtTimeStep, destinationLabelSet, sourceBackground, destinationBackground, destinationBackgroundLocked, sourceLabel, newDestinationLabel, mergeStyle, overwriteStlye)); destinationLabelSet->ModifyLabelEvent.Send(newDestinationLabel); } destinationImage->Modified(); } void mitk::TransferLabelContent( const Image* sourceImage, Image* destinationImage, const mitk::LabelSet* destinationLabelSet, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, std::vector > labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContent; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContent; destinationImage must not be null."; } const auto sourceTimeStepCount = sourceImage->GetTimeGeometry()->CountTimeSteps(); if (sourceTimeStepCount != destinationImage->GetTimeGeometry()->CountTimeSteps()) { mitkThrow() << "Invalid call of TransferLabelContent; images have no equal number of time steps."; } for (mitk::TimeStepType i = 0; i < sourceTimeStepCount; ++i) { TransferLabelContentAtTimeStep(sourceImage, destinationImage, destinationLabelSet, i, sourceBackground, destinationBackground, destinationBackgroundLocked, labelMapping, mergeStyle, overwriteStlye); } } void mitk::TransferLabelContentAtTimeStep( const LabelSetImage* sourceImage, LabelSetImage* destinationImage, const TimeStepType timeStep, std::vector > labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage must not be null."; } const auto destinationLabelSet = destinationImage->GetLabelSet(destinationImage->GetActiveLayer()); for (const auto& mappingElement : labelMapping) { if (LabelSetImage::UnlabeledLabelValue != mappingElement.first && !sourceImage->ExistLabel(mappingElement.first, sourceImage->GetActiveLayer())) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep. Defined source label does not exist in sourceImage. SourceLabel: " << mappingElement.first; } } TransferLabelContentAtTimeStep(sourceImage, destinationImage, destinationLabelSet, timeStep, LabelSetImage::UnlabeledLabelValue, LabelSetImage::UnlabeledLabelValue, destinationImage->GetUnlabeledLabelLock(), labelMapping, mergeStyle, overwriteStlye); } void mitk::TransferLabelContent( const LabelSetImage* sourceImage, LabelSetImage* destinationImage, std::vector > labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContent; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContent; destinationImage must not be null."; } const auto sourceTimeStepCount = sourceImage->GetTimeGeometry()->CountTimeSteps(); if (sourceTimeStepCount != destinationImage->GetTimeGeometry()->CountTimeSteps()) { mitkThrow() << "Invalid call of TransferLabelContent; images have no equal number of time steps."; } for (mitk::TimeStepType i = 0; i < sourceTimeStepCount; ++i) { TransferLabelContentAtTimeStep(sourceImage, destinationImage, i, labelMapping, mergeStyle, overwriteStlye); } } diff --git a/Modules/Multilabel/mitkLabelSetImage.h b/Modules/Multilabel/mitkLabelSetImage.h index c06b304483..341c7dd1af 100644 --- a/Modules/Multilabel/mitkLabelSetImage.h +++ b/Modules/Multilabel/mitkLabelSetImage.h @@ -1,661 +1,671 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkLabelSetImage_h #define mitkLabelSetImage_h #include #include #include namespace mitk { //##Documentation //## @brief LabelSetImage class for handling labels and layers in a segmentation session. //## //## Handles operations for adding, removing, erasing and editing labels and layers. //## @ingroup Data class MITKMULTILABEL_EXPORT LabelSetImage : public Image { public: mitkClassMacro(LabelSetImage, Image); itkNewMacro(Self); typedef mitk::Label::PixelType PixelType; /** * \brief BeforeChangeLayerEvent (e.g. used for GUI integration) * As soon as active labelset should be changed, the signal emits. * Emitted by SetActiveLayer(int layer); */ Message<> BeforeChangeLayerEvent; /** * \brief AfterchangeLayerEvent (e.g. used for GUI integration) * As soon as active labelset was changed, the signal emits. * Emitted by SetActiveLayer(int layer); */ Message<> AfterChangeLayerEvent; /////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////// // FUTURE MultiLabelSegmentation: // Section that already contains declarations used in the new class. // So this part of the interface will stay after refactoring towards // the new MultiLabelSegmentation class (see T28524). This section was introduced // because some of the planed features are already urgently needed. /////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////// using SpatialGroupIndexType = std::size_t; using LabelValueType = mitk::Label::PixelType; const static LabelValueType UnlabeledLabelValue = 0; using ConstLabelVectorType = std::vector; using LabelVectorType = std::vector; using LabelValueVectorType = std::vector; /** * @brief Removes the label with the given value. * The label is removed from the labelset of the given layer and * the pixel values of the image below the label are reset. * @param labelValue the pixel value of the label to be removed */ void RemoveLabel(LabelValueType labelValue); /** * @brief Removes labels from the mitk::MultiLabelSegmentation. * Calls mitk::MultiLabelSegmentation::EraseLabels() which also removes the labels from within the image. * If a label value does not exist, it will be ignored. * @param labelValues a list of labels to be removed */ void RemoveLabels(const LabelValueVectorType& vectorOfLabelPixelValues); /** * @brief Removes a complete spatial group with all its labels. * @remark with removing a spatial group all spatial groups with greater index will be reindexed to * close the gap. So externaly stored spatial group indeces may become invalid. * @param spatial group index of the spatial group that should be removed. If the spatial group does not exist, an * exception will be raised. * @pre spatial group index must be valid. */ void RemoveSpatialGroup(SpatialGroupIndexType spatielGroup); //future declaration (T28524) currently conflicted with old declaration ///** // * \brief Returns true if the value exists in the MultiLabelSegmentation instance*/ //bool ExistLabel(LabelValueType value) const; ///** // * @brief Checks if a label belongs in a certain spatial group // * @param value the label value // * @param groupIndex Indexp of the spacial group which should be checked for the label // * @return true if the label exists otherwise false // */ //bool ExistLabel(LabelValueType value, SpatialGroupIndexType groupIndex) const; /** * \brief Returns true if the spatial group exists in the MultiLabelSegmentation instance.*/ bool ExistGroup(SpatialGroupIndexType index) const; bool IsLabeInGroup(LabelValueType value) const; bool IsLabeInGroup(LabelValueType value, SpatialGroupIndexType& groupIndex) const; /** Returns the group id of the based label value. * @pre label value must exists. */ SpatialGroupIndexType GetGroupIndexOfLabel(LabelValueType value) const; /** * @brief Returns the mitk::Label with the given value. * @param value the pixel value of the label * @return the mitk::Label if available otherwise nullptr */ const mitk::Label* GetLabel(LabelValueType value) const; mitk::Label* GetLabel(LabelValueType value); /** Returns the lock state of the label (including UnlabeledLabel value). @pre Requested label does exist.*/ bool IsLabelLocked(LabelValueType value) const; /** Returns a vector with all labels currently defined in the MultiLabelSegmentation instance.*/ const ConstLabelVectorType GetLabels() const; const LabelVectorType GetLabels(); + /** + * @brief Returns a vector of all labels located on the specified group. + * @param index the index of the group for which the vector of labels should be retrieved. + * If an invalid index is passed an exception will be raised. + * @return the respective vector of labels. + * @pre group index must exist. + */ + const ConstLabelVectorType GetLabelsInGroup(SpatialGroupIndexType index) const; + const LabelVectorType GetLabelsInGroup(SpatialGroupIndexType index); + itkGetConstMacro(UnlabeledLabelLock, bool); itkSetMacro(UnlabeledLabelLock, bool); itkBooleanMacro(UnlabeledLabelLock); //////////////////////////////////////////////////////////////////// //Message slots that allow to react to changes in an instance using LabelEventType = Message1; using LabelsEventType = Message1; using GroupEventType = Message1; /** * \brief LabelAdded is emitted whenever a new label has been added. * * Observers should register to this event by calling this->AddLabelAddedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been added to the MultiLabelSegmentation. * Observers should unregister by calling this->RemoveLabelAddedListener(myObject, MyObject::MyMethod). * The registered method will be called with the label value of the added label. * @remark the usage of the message object is thread safe. */ mitkNewMessage1Macro(LabelAdded, LabelValueType); /** * \brief LabelModified is emitted whenever a label has been modified. * * A label is modified if either its pixel content was changed, its spatial group or the label instance * information. * If you just want to get notified at the end of a MultiLabelSegmentation instance manipulation in the * case that at least one label was modified (e.g. to avoid getting a signal for each label * individually), use LabelsChanged instead. * Observers should register to this event by calling this->AddLabelModifiedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been added to the MultiLabelSegmentation. * Observers should unregister by calling this->RemoveLabelModifiedListener(myObject, MyObject::MyMethod). * The registered method will be called with the label value of the modified label. * @remark the usage of the message object is thread safe. */ mitkNewMessage1Macro(LabelModified, LabelValueType); /** * \brief LabelRemoved is emitted whenever a label has been removed. * * Observers should register to this event by calling this->AddLabelRemovedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been added to the MultiLabelSegmentation. * Observers should unregister by calling this->RemoveLabelRemovedListener(myObject, MyObject::MyMethod). * The registered method will be called with the label value of the removed label.* * @remark the usage of the message object is thread safe. */ mitkNewMessage1Macro(LabelRemoved, LabelValueType); /** * \brief LabelsChanged is emitted when labels are changed (added, removed, modified). * * In difference to the other label events LabelsChanged is send only *one time* after the modification of the * MultiLableImage instance is finished. So e.g. even if 4 labels are changed by a merge operation, this event will * only be sent one time (compared to LabelRemoved or LabelModified). * Observers should register to this event by calling myMultiLabelSegmentation->AddLabelsChangedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been removed from the MultiLabelSegmentation. * Observers should unregister by calling myMultiLabelSegmentation->RemoveLabelsChangedListener(myObject, * MyObject::MyMethod). * The registered method will be called with the vector of label values of the modified labels.* * @remark the usage of the message object is thread safe. */ mitkNewMessage1Macro(LabelsChanged, LabelValueVectorType); /** * \brief GroupAdded is emitted whenever a new group has been added. * * Observers should register to this event by calling this->AddGroupAddedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new group has been added to the MultiLabelSegmentation. * Observers should unregister by calling this->RemoveGroupAddedListener(myObject, MyObject::MyMethod). * The registered method will be called with the group index of the added group. * @remark the usage of the message object is thread safe. */ mitkNewMessage1Macro(GroupAdded, SpatialGroupIndexType); /** * \brief GroupModified is emitted whenever a group has been modified. * * A group is modified if the set of labels associated with it are changed or the group's meta data. * Observers should register to this event by calling this->AddGroupModifiedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been added to the MultiLabelSegmentation. * Observers should unregister by calling this->RemoveGroupModifiedListener(myObject, MyObject::MyMethod). * The registered method will be called with the group index of the added group. * @remark the usage of the message object is thread safe. */ mitkNewMessage1Macro(GroupModified, SpatialGroupIndexType); /** * \brief GroupRemoved is emitted whenever a label has been removed. * * Observers should register to this event by calling this->AddGroupRemovedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been added to the MultiLabelSegmentation. * Observers should unregister by calling this->RemoveGroupRemovedListener(myObject, MyObject::MyMethod). * The registered method will be called with the group index of the removed group.* * @remark the usage of the message object is thread safe. */ mitkNewMessage1Macro(GroupRemoved, SpatialGroupIndexType); protected: void OnLabelAdded(LabelValueType labelValue); void AddLabelToMap(LabelValueType labelValue, mitk::Label* label, SpatialGroupIndexType groupID); void OnLabelModified(LabelValueType labelValue); void OnLabelRemoved(LabelValueType labelValue); void OnGroupAdded(SpatialGroupIndexType groupIndex); void OnGroupModified(SpatialGroupIndexType groupIndex); void OnGroupRemoved(SpatialGroupIndexType groupIndex); /** Reeinitalizes the internal maps based on the current layer/label content * of the instance. */ void ReinitMaps(); using LabelMapType = std::map; LabelMapType m_LabelMap; /**This type is internally used to track which label is currently * associated with which layer.*/ using GroupToLabelMapType = std::map; GroupToLabelMapType m_GroupToLabelMap; using LabelToGroupMapType = std::map; LabelToGroupMapType m_LabelToGroupMap; private: /** Indicates if the MultiLabelSegmentation allows to overwrite unlabeled pixels in normal pixel manipulation operations (e.g. TransferLabelConent).*/ bool m_UnlabeledLabelLock; public: /** * \brief */ void UpdateCenterOfMass(PixelType pixelValue); /////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////// // END FUTURE MultiLabelSegmentation /////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////// /** * @brief Initialize an empty mitk::LabelSetImage using the information * of an mitk::Image * @param image the image which is used for initializing the mitk::LabelSetImage */ using mitk::Image::Initialize; void Initialize(const mitk::Image *image) override; /** * \brief */ void ClearBuffer(); /** * @brief Merges the mitk::Label with a given target value with the active label * * @param pixelValue the value of the label that should be the new merged label * @param sourcePixelValue the value of the label that should be merged into the specified one * @param layer the layer in which the merge should be performed */ void MergeLabel(PixelType pixelValue, PixelType sourcePixelValue, unsigned int layer = 0); /** * @brief Merges a list of mitk::Labels with the mitk::Label that has a specific value * * @param pixelValue the value of the label that should be the new merged label * @param vectorOfSourcePixelValues the list of label values that should be merge into the specified one * @param layer the layer in which the merge should be performed */ void MergeLabels(PixelType pixelValue, const std::vector& vectorOfSourcePixelValues, unsigned int layer = 0); /** * \brief */ void UpdateCenterOfMass(PixelType pixelValue, unsigned int layer); /** * @brief Erases the label with the given value from the labelset image. * The label itself will not be erased from the respective mitk::LabelSet. In order to * remove the label itself use mitk::LabelSetImage::RemoveLabels() * @param pixelValue the pixel value of the label that will be erased from the labelset image */ void EraseLabel(PixelType pixelValue); /** * @brief Erases a list of labels with the given values from the labelset image. * @param VectorOfLabelPixelValues the list of pixel values of the labels * that will be erased from the labelset image */ void EraseLabels(const std::vector &VectorOfLabelPixelValues); /** * \brief Returns true if the value exists in one of the labelsets*/ //[[deprecated("Will be changed with T28524")]] DEPRECATED(bool ExistLabel(PixelType pixelValue) const); /** * @brief Checks if a label exists in a certain layer * @param pixelValue the label value * @param layer the layer in which should be searched for the label * @return true if the label exists otherwise false */ //[[deprecated("Will be changed with T28524")]] DEPRECATED(bool ExistLabel(PixelType pixelValue, unsigned int layer) const); /** * \brief Returns true if the labelset exists*/ //[[deprecated("Will be removed with T28524")]] DEPRECATED(bool ExistLabelSet(unsigned int layer) const); /** * @brief Returns the active label of a specific layer * @param layer the layer ID for which the active label should be returned * @return the active label of the specified layer */ //[[deprecated("Will be removed with T28524")]] DEPRECATED(mitk::Label *GetActiveLabel(unsigned int layer = 0)); //[[deprecated("Will be removed with T28524")]] DEPRECATED(const mitk::Label* GetActiveLabel(unsigned int layer = 0) const); /** * @brief Returns the mitk::Label with the given pixelValue and for the given layer * @param pixelValue the pixel value of the label * @param layer the layer in which the labels should be located * @return the mitk::Label if available otherwise nullptr */ mitk::Label *GetLabel(PixelType pixelValue, unsigned int layer) const; /** * @brief Returns the currently active mitk::LabelSet * @return the mitk::LabelSet of the active layer or nullptr if non is present */ //[[deprecated ("Will be removed with T28524")]] DEPRECATED(mitk::LabelSet *GetActiveLabelSet()); //[[deprecated("Will be removed with T28524")]] DEPRECATED(const mitk::LabelSet* GetActiveLabelSet() const); /** * @brief Gets the mitk::LabelSet for the given layer * @param layer the layer for which the mitk::LabelSet should be retrieved * @return the respective mitk::LabelSet or nullptr if non exists for the given layer */ mitk::LabelSet *GetLabelSet(unsigned int layer = 0); const mitk::LabelSet *GetLabelSet(unsigned int layer = 0) const; /** * @brief Gets the ID of the currently active layer * @return the ID of the active layer */ unsigned int GetActiveLayer() const; /** * @brief Get the number of all existing mitk::Labels for a given layer * @param layer the layer ID for which the active mitk::Labels should be retrieved * @return the number of all existing mitk::Labels for the given layer */ unsigned int GetNumberOfLabels(unsigned int layer = 0) const; /** * @brief Returns the number of all labels summed up across all layers * @return the overall number of labels across all layers */ unsigned int GetTotalNumberOfLabels() const; // This function will need to be ported to an external class // it requires knowledge of pixeltype and dimension and includes // too much algorithm to be sensibly part of a data class ///** // * \brief */ // void SurfaceStamp(mitk::Surface* surface, bool forceOverwrite); /** * \brief */ mitk::Image::Pointer CreateLabelMask(PixelType index, bool useActiveLayer = true, unsigned int layer = 0); /** * @brief Initialize a new mitk::LabelSetImage by an given image. * For all distinct pixel values of the parameter image new labels will * be created. If the number of distinct pixel values exceeds mitk::Label::MAX_LABEL_VALUE * a new layer will be created * @param image the image which is used for initialization */ void InitializeByLabeledImage(mitk::Image::Pointer image); /** * \brief */ void MaskStamp(mitk::Image *mask, bool forceOverwrite); /** * \brief */ void SetActiveLayer(unsigned int layer); /** * \brief */ unsigned int GetNumberOfLayers() const; /** * \brief Adds a new layer to the LabelSetImage. The new layer will be set as the active one. * \param labelSet a labelset that will be added to the new layer if provided * \return the layer ID of the new layer */ unsigned int AddLayer(mitk::LabelSet::Pointer labelSet = nullptr); /** * \brief Adds a layer based on a provided mitk::Image. * \param layerImage is added to the vector of label images * \param labelSet a labelset that will be added to the new layer if provided * \return the layer ID of the new layer */ unsigned int AddLayer(mitk::Image::Pointer layerImage, mitk::LabelSet::Pointer labelSet = nullptr); /** * \brief Add a cloned LabelSet to an existing layer * * Remark: The passed LabelSet instance will be cloned before added to ensure clear ownership * of the new LabelSet addition. * * This will replace an existing labelSet if one exists. Throws an exceptions if you are trying * to add a labelSet to a non-existing layer. * * If there are no labelSets for layers with an id less than layerIdx default ones will be added * for them. * * \param layerIdx The index of the layer the LabelSet should be added to * \param labelSet The LabelSet that should be added */ void AddLabelSetToLayer(const unsigned int layerIdx, const mitk::LabelSet* labelSet); /** * @brief Removes the active layer and the respective mitk::LabelSet and image information. * The new active layer is the one below, if exists */ void RemoveLayer(); /** * \brief */ mitk::Image *GetLayerImage(unsigned int layer); const mitk::Image *GetLayerImage(unsigned int layer) const; void OnLabelSetModified(); protected: mitkCloneMacro(Self); LabelSetImage(); LabelSetImage(const LabelSetImage &other); ~LabelSetImage() override; template void LayerContainerToImageProcessing(itk::Image *source, unsigned int layer); template void ImageToLayerContainerProcessing(itk::Image *source, unsigned int layer) const; template void CalculateCenterOfMassProcessing(ImageType *input, PixelType index, unsigned int layer); template void ClearBufferProcessing(ImageType *input); template void EraseLabelProcessing(ImageType *input, PixelType index); template void MergeLabelProcessing(ImageType *input, PixelType pixelValue, PixelType index); template void MaskStampProcessing(ImageType *input, mitk::Image *mask, bool forceOverwrite); template void InitializeByLabeledImageProcessing(LabelSetImageType *input, ImageType *other); /** helper needed for ensuring unique values in all layers until the refactoring is done. returns a sorted list of all labels.*/ LabelValueVectorType GetUsedLabelValues() const; //helper function that ensures void RegisterLabelSet(mitk::LabelSet* ls); void ReleaseLabelSet(mitk::LabelSet* ls); std::vector m_LabelSetContainer; std::vector m_LayerContainer; int m_ActiveLayer; bool m_activeLayerInvalid; }; /** * @brief Equal A function comparing two label set images for beeing equal in meta- and imagedata * * @ingroup MITKTestingAPI * * Following aspects are tested for equality: * - LabelSetImage members * - working image data * - layer image data * - labels in label set * * @param rightHandSide An image to be compared * @param leftHandSide An image to be compared * @param eps Tolerance for comparison. You can use mitk::eps in most cases. * @param verbose Flag indicating if the user wants detailed console output or not. * @return true, if all subsequent comparisons are true, false otherwise */ MITKMULTILABEL_EXPORT bool Equal(const mitk::LabelSetImage &leftHandSide, const mitk::LabelSetImage &rightHandSide, ScalarType eps, bool verbose); /** temporery namespace that is used until the new class MultiLabelSegmentation is introduced. It allows to already introduce/use some upcoming definitions, while refactoring code.*/ namespace MultiLabelSegmentation { enum class MergeStyle { Replace, //The old label content of a lable value will be replaced by its new label content. //Therefore pixels that are labeled might become unlabeled again. //(This means that a lock of the value is also ignored). Merge //The union of old and new label content will be generated. }; enum class OverwriteStyle { RegardLocks, //Locked labels in the same spatial group will not be overwritten/changed. IgnoreLocks //Label locks in the same spatial group will be ignored, so these labels might be changed. }; } /**Helper function that transfers pixels of the specified source label from source image to the destination image by using a specified destination label for a specific timestep. Function processes the whole image volume of the specified time step. @remark in its current implementation the function only transfers contents of the active layer of the passed LabelSetImages. @remark the function assumes that it is only called with source and destination image of same geometry. @remark CAUTION: The function is not save, if sourceImage and destinationImage are the same instance and you transfer more then one label, because the changes are made inplace for performance reasons but not in one pass. If a mapped value A equals a "old value" that is later in the mapping, one ends up with a wrong transfer, as a pixel would be first mapped to A and then latter again, because it is also an "old" value in the mapping table. @param sourceImage Pointer to the LabelSetImage which active layer should be used as source for the transfer. @param destinationImage Pointer to the LabelSetImage which active layer should be used as destination for the transfer. @param labelMapping Map that encodes the mappings of all label pixel transfers that should be done. First element is the label in the source image. The second element is the label that transferred pixels should become in the destination image. The order in which the labels will be transfered is the same order of elements in the labelMapping. If you use a heterogeneous label mapping (e.g. (1,2); so changing the label while transfering), keep in mind that for the MergeStyle and OverwriteStyle only the destination label (second element) is relevant (e.g. what should be altered with MergeStyle Replace). @param mergeStyle indicates how the transfer should be done (merge or replace). For more details see documentation of MultiLabelSegmentation::MergeStyle. @param overwriteStlye indicates if label locks in the destination image should be regarded or not. For more details see documentation of MultiLabelSegmentation::OverwriteStyle. @param timeStep indicate the time step that should be transferred. @pre sourceImage and destinationImage must be valid @pre sourceImage and destinationImage must contain the indicated timeStep @pre sourceImage must contain all indicated sourceLabels in its active layer. @pre destinationImage must contain all indicated destinationLabels in its active layer.*/ MITKMULTILABEL_EXPORT void TransferLabelContentAtTimeStep(const LabelSetImage* sourceImage, LabelSetImage* destinationImage, const TimeStepType timeStep, std::vector > labelMapping = { {1,1} }, MultiLabelSegmentation::MergeStyle mergeStyle = MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle overwriteStlye = MultiLabelSegmentation::OverwriteStyle::RegardLocks); /**Helper function that transfers pixels of the specified source label from source image to the destination image by using a specified destination label. Function processes the whole image volume for all time steps. For more details please see TransferLabelContentAtTimeStep for LabelSetImages. @sa TransferLabelContentAtTimeStep*/ MITKMULTILABEL_EXPORT void TransferLabelContent(const LabelSetImage* sourceImage, LabelSetImage* destinationImage, std::vector > labelMapping = { {1,1} }, MultiLabelSegmentation::MergeStyle mergeStyle = MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle overwriteStlye = MultiLabelSegmentation::OverwriteStyle::RegardLocks); /**Helper function that transfers pixels of the specified source label from source image to the destination image by using a specified destination label for a specific timestep. Function processes the whole image volume of the specified time step. @remark the function assumes that it is only called with source and destination image of same geometry. @remark CAUTION: The function is not save, if sourceImage and destinationImage are the same instance and you transfer more then one label, because the changes are made inplace for performance reasons but not in one pass. If a mapped value A equals a "old value" that is later in the mapping, one ends up with a wrong transfer, as a pixel would be first mapped to A and then latter again, because it is also an "old" value in the mapping table. @param sourceImage Pointer to the image that should be used as source for the transfer. @param sourceImage Pointer to the image that should be used as source for the transfer. @param destinationImage Pointer to the image that should be used as destination for the transfer. @param destinationLabelSet Pointer to the label set specifying labels and lock states in the destination image. Unkown pixel values in the destinationImage will be assumed to be unlocked. @param sourceBackground Value indicating the background in the source image. @param destinationBackground Value indicating the background in the destination image. @param destinationBackgroundLocked Value indicating the lock state of the background in the destination image. @param labelMapping Map that encodes the mappings of all label pixel transfers that should be done. First element is the label in the source image. The second element is the label that transferred pixels should become in the destination image. The order in which the labels will be transfered is the same order of elements in the labelMapping. If you use a heterogeneous label mapping (e.g. (1,2); so changing the label while transfering), keep in mind that for the MergeStyle and OverwriteStyle only the destination label (second element) is relevant (e.g. what should be altered with MergeStyle Replace). @param mergeStyle indicates how the transfer should be done (merge or replace). For more details see documentation of MultiLabelSegmentation::MergeStyle. @param overwriteStlye indicates if label locks in the destination image should be regarded or not. For more details see documentation of MultiLabelSegmentation::OverwriteStyle. @param timeStep indicate the time step that should be transferred. @pre sourceImage, destinationImage and destinationLabelSet must be valid @pre sourceImage and destinationImage must contain the indicated timeStep @pre destinationLabelSet must contain all indicated destinationLabels for mapping.*/ MITKMULTILABEL_EXPORT void TransferLabelContentAtTimeStep(const Image* sourceImage, Image* destinationImage, const mitk::LabelSet* destinationLabelSet, const TimeStepType timeStep, mitk::Label::PixelType sourceBackground = LabelSetImage::UnlabeledLabelValue, mitk::Label::PixelType destinationBackground = LabelSetImage::UnlabeledLabelValue, bool destinationBackgroundLocked = false, std::vector > labelMapping = { {1,1} }, MultiLabelSegmentation::MergeStyle mergeStyle = MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle overwriteStlye = MultiLabelSegmentation::OverwriteStyle::RegardLocks); /**Helper function that transfers pixels of the specified source label from source image to the destination image by using a specified destination label. Function processes the whole image volume for all time steps. For more details please see TransferLabelContentAtTimeStep. @sa TransferLabelContentAtTimeStep*/ MITKMULTILABEL_EXPORT void TransferLabelContent(const Image* sourceImage, Image* destinationImage, const mitk::LabelSet* destinationLabelSet, mitk::Label::PixelType sourceBackground = LabelSetImage::UnlabeledLabelValue, mitk::Label::PixelType destinationBackground = LabelSetImage::UnlabeledLabelValue, bool destinationBackgroundLocked = false, std::vector > labelMapping = { {1,1} }, MultiLabelSegmentation::MergeStyle mergeStyle = MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle overwriteStlye = MultiLabelSegmentation::OverwriteStyle::RegardLocks); } // namespace mitk #endif diff --git a/Modules/Multilabel/mitkLabelSetImageConverter.cpp b/Modules/Multilabel/mitkLabelSetImageConverter.cpp index a6ca60fa02..26d7d1be57 100644 --- a/Modules/Multilabel/mitkLabelSetImageConverter.cpp +++ b/Modules/Multilabel/mitkLabelSetImageConverter.cpp @@ -1,197 +1,197 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include #include #include #include #include template static void ConvertLabelSetImageToImage(const itk::Image *, mitk::LabelSetImage::ConstPointer labelSetImage, mitk::Image::Pointer &image) { typedef itk::Image ImageType; typedef itk::ComposeImageFilter ComposeFilterType; typedef itk::ImageDuplicator DuplicatorType; auto numberOfLayers = labelSetImage->GetNumberOfLayers(); if (numberOfLayers > 1) { auto vectorImageComposer = ComposeFilterType::New(); auto activeLayer = labelSetImage->GetActiveLayer(); for (decltype(numberOfLayers) layer = 0; layer < numberOfLayers; ++layer) { auto layerImage = mitk::ImageToItkImage( layer != activeLayer ? labelSetImage->GetLayerImage(layer) : labelSetImage); vectorImageComposer->SetInput(layer, layerImage); } vectorImageComposer->Update(); // mitk::GrabItkImageMemory does not support 4D, this will handle 4D correctly // and create a memory managed copy image = mitk::ImportItkImage(vectorImageComposer->GetOutput())->Clone(); } else { auto layerImage = mitk::ImageToItkImage(labelSetImage); auto duplicator = DuplicatorType::New(); duplicator->SetInputImage(layerImage); duplicator->Update(); // mitk::GrabItkImageMemory does not support 4D, this will handle 4D correctly // and create a memory managed copy image = mitk::ImportItkImage(duplicator->GetOutput())->Clone(); } } mitk::Image::Pointer mitk::ConvertLabelSetImageToImage(LabelSetImage::ConstPointer labelSetImage) { Image::Pointer image; if (labelSetImage->GetNumberOfLayers() > 0) { if (labelSetImage->GetDimension() == 4) { AccessFixedDimensionByItk_n(labelSetImage, ::ConvertLabelSetImageToImage, 4, (labelSetImage, image)); } else { AccessByItk_2(labelSetImage->GetLayerImage(0), ::ConvertLabelSetImageToImage, labelSetImage, image); } image->SetTimeGeometry(labelSetImage->GetTimeGeometry()->Clone()); } return image; } template static void SplitVectorImage(const itk::VectorImage* image, std::vector& result) { typedef itk::VectorImage VectorImageType; typedef itk::Image ImageType; typedef itk::VectorIndexSelectionCastImageFilter VectorIndexSelectorType; auto numberOfLayers = image->GetVectorLength(); for (decltype(numberOfLayers) layer = 0; layer < numberOfLayers; ++layer) { auto layerSelector = VectorIndexSelectorType::New(); layerSelector->SetInput(image); layerSelector->SetIndex(layer); layerSelector->Update(); - mitk::Image::Pointer layerImage = mitk::GrabItkImageMemory(layerSelector->GetOutput(), nullptr, nullptr, false); + mitk::Image::Pointer layerImage = mitk::GrabItkImageMemoryChannel(layerSelector->GetOutput(), nullptr, nullptr, false); result.push_back(layerImage); } } std::vector mitk::SplitVectorImage(const Image* vecImage) { if (nullptr == vecImage) { mitkThrow() << "Invalid usage; nullptr passed to SplitVectorImage."; } if (vecImage->GetChannelDescriptor().GetPixelType().GetPixelType() != itk::IOPixelEnum::VECTOR) { mitkThrow() << "Invalid usage of SplitVectorImage; passed image is not a vector image. Present pixel type: "<< vecImage->GetChannelDescriptor().GetPixelType().GetPixelTypeAsString(); } std::vector result; if (4 == vecImage->GetDimension()) { AccessVectorFixedDimensionByItk_n(vecImage, ::SplitVectorImage, 4, (result)); } else { AccessVectorPixelTypeByItk_n(vecImage, ::SplitVectorImage, (result)); } for (auto image : result) { image->SetTimeGeometry(vecImage->GetTimeGeometry()->Clone()); } return result; } mitk::LabelSetImage::Pointer mitk::ConvertImageToLabelSetImage(Image::Pointer image) { std::vector groupImages; if (image.IsNotNull()) { if (image->GetChannelDescriptor().GetPixelType().GetPixelType() == itk::IOPixelEnum::VECTOR) { groupImages = SplitVectorImage(image); } else { groupImages.push_back(image); } } auto labelSetImage = ConvertImageVectorToLabelSetImage(groupImages, image->GetTimeGeometry()); return labelSetImage; } mitk::LabelSetImage::Pointer mitk::ConvertImageVectorToLabelSetImage(const std::vector& images, const mitk::TimeGeometry* timeGeometry) { LabelSetImage::Pointer labelSetImage = mitk::LabelSetImage::New(); for (auto& groupImage : images) { if (groupImage== images.front()) { labelSetImage->InitializeByLabeledImage(groupImage); } else { labelSetImage->AddLayer(groupImage); } } labelSetImage->SetTimeGeometry(timeGeometry->Clone()); return labelSetImage; } mitk::LabelSet::Pointer mitk::GenerateLabelSetWithMappedValues(const LabelSet* sourceLabelset, std::vector > labelMapping) { if (nullptr == sourceLabelset) { mitkThrow() << "Invalid usage; nullptr passed as labelset to GenerateLabelSetWithMappedValues."; } auto result = LabelSet::New(); for (auto [sourceLabelID, destLabelID] : labelMapping) { auto clonedLabel = sourceLabelset->GetLabel(sourceLabelID)->Clone(); clonedLabel->SetValue(destLabelID); result->AddLabel(clonedLabel, false); } return result; } diff --git a/Modules/Multilabel/mitkMultiLabelIOHelper.cpp b/Modules/Multilabel/mitkMultiLabelIOHelper.cpp index 8593587441..842251aba9 100644 --- a/Modules/Multilabel/mitkMultiLabelIOHelper.cpp +++ b/Modules/Multilabel/mitkMultiLabelIOHelper.cpp @@ -1,280 +1,435 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkMultiLabelIOHelper.h" #include "mitkLabelSetImage.h" #include +#include "itkMetaDataDictionary.h" +#include "itkMetaDataObject.h" + #include namespace { std::string EnsureExtension(const std::string& filename) { const std::string extension = ".lsetp"; if (filename.size() < extension.size() || std::string::npos == filename.find(extension, filename.size() - extension.size())) return filename + extension; return filename; } } bool mitk::MultiLabelIOHelper::SaveLabelSetImagePreset(const std::string &presetFilename, const mitk::LabelSetImage *inputImage) { const auto filename = EnsureExtension(presetFilename); tinyxml2::XMLDocument xmlDocument; xmlDocument.InsertEndChild(xmlDocument.NewDeclaration()); auto *rootElement = xmlDocument.NewElement("LabelSetImagePreset"); rootElement->SetAttribute("layers", inputImage->GetNumberOfLayers()); xmlDocument.InsertEndChild(rootElement); for (unsigned int layerIndex = 0; layerIndex < inputImage->GetNumberOfLayers(); layerIndex++) { auto *layerElement = xmlDocument.NewElement("Layer"); layerElement->SetAttribute("index", layerIndex); layerElement->SetAttribute("labels", inputImage->GetNumberOfLabels(layerIndex)); rootElement->InsertEndChild(layerElement); for (unsigned int labelIndex = 0; labelIndex < inputImage->GetNumberOfLabels(layerIndex); labelIndex++) layerElement->InsertEndChild(MultiLabelIOHelper::GetLabelAsXMLElement(xmlDocument, inputImage->GetLabel(labelIndex, layerIndex))); } return tinyxml2::XML_SUCCESS == xmlDocument.SaveFile(filename.c_str()); } bool mitk::MultiLabelIOHelper::LoadLabelSetImagePreset(const std::string &presetFilename, mitk::LabelSetImage *inputImage) { if (nullptr == inputImage) return false; const auto filename = EnsureExtension(presetFilename); tinyxml2::XMLDocument xmlDocument; if (tinyxml2::XML_SUCCESS != xmlDocument.LoadFile(filename.c_str())) { MITK_WARN << "Label set preset file \"" << filename << "\" does not exist or cannot be opened"; return false; } auto *rootElement = xmlDocument.FirstChildElement("LabelSetImagePreset"); if (nullptr == rootElement) { MITK_WARN << "Not a valid Label set preset"; return false; } auto activeLayerBackup = inputImage->GetActiveLayer(); int numberOfLayers = 0; rootElement->QueryIntAttribute("layers", &numberOfLayers); auto* layerElement = rootElement->FirstChildElement("Layer"); if (nullptr == layerElement) { MITK_WARN << "Label set preset does not contain any layers"; return false; } for (int layerIndex = 0; layerIndex < numberOfLayers; layerIndex++) { int numberOfLabels = 0; layerElement->QueryIntAttribute("labels", &numberOfLabels); if (nullptr == inputImage->GetLabelSet(layerIndex)) { inputImage->AddLayer(); } else { inputImage->SetActiveLayer(layerIndex); } auto *labelElement = layerElement->FirstChildElement("Label"); if (nullptr == labelElement) continue; for (int labelIndex = 0; labelIndex < numberOfLabels; labelIndex++) { auto label = mitk::MultiLabelIOHelper::LoadLabelFromXMLDocument(labelElement); const auto labelValue = label->GetValue(); if (LabelSetImage::UnlabeledLabelValue != labelValue) { auto* labelSet = inputImage->GetLabelSet(layerIndex); auto* alreadyExistingLabel = labelSet->GetLabel(labelValue); if (nullptr != alreadyExistingLabel) { // Override existing label with label from preset alreadyExistingLabel->ConcatenatePropertyList(label); labelSet->UpdateLookupTable(labelValue); } else { labelSet->AddLabel(label); } } labelElement = labelElement->NextSiblingElement("Label"); if (nullptr == labelElement) continue; } layerElement = layerElement->NextSiblingElement("Layer"); if (nullptr == layerElement) continue; } inputImage->SetActiveLayer(activeLayerBackup); return true; } tinyxml2::XMLElement *mitk::MultiLabelIOHelper::GetLabelAsXMLElement(tinyxml2::XMLDocument &doc, Label *label) { auto *labelElem = doc.NewElement("Label"); if (nullptr != label) { // add XML contents const PropertyList::PropertyMap* propmap = label->GetMap(); for (auto iter = propmap->begin(); iter != propmap->end(); ++iter) { std::string key = iter->first; const BaseProperty* property = iter->second; auto* element = PropertyToXMLElement(doc, key, property); if (element) labelElem->InsertEndChild(element); } } return labelElem; } mitk::Label::Pointer mitk::MultiLabelIOHelper::LoadLabelFromXMLDocument(const tinyxml2::XMLElement *labelElem) { // reread auto *propElem = labelElem->FirstChildElement("property"); std::string name; mitk::BaseProperty::Pointer prop; mitk::Label::Pointer label = mitk::Label::New(); while (propElem) { MultiLabelIOHelper::PropertyFromXMLElement(name, prop, propElem); label->SetProperty(name, prop); propElem = propElem->NextSiblingElement("property"); } return label.GetPointer(); } tinyxml2::XMLElement *mitk::MultiLabelIOHelper::PropertyToXMLElement(tinyxml2::XMLDocument &doc, const std::string &key, const BaseProperty *property) { auto *keyelement = doc.NewElement("property"); keyelement->SetAttribute("key", key.c_str()); keyelement->SetAttribute("type", property->GetNameOfClass()); // construct name of serializer class std::string serializername(property->GetNameOfClass()); serializername += "Serializer"; std::list allSerializers = itk::ObjectFactoryBase::CreateAllInstance(serializername.c_str()); if (allSerializers.size() < 1) MITK_ERROR << "No serializer found for " << property->GetNameOfClass() << ". Skipping object"; if (allSerializers.size() > 1) MITK_WARN << "Multiple serializers found for " << property->GetNameOfClass() << "Using arbitrarily the first one."; for (auto iter = allSerializers.begin(); iter != allSerializers.end(); ++iter) { if (auto *serializer = dynamic_cast(iter->GetPointer())) { serializer->SetProperty(property); try { auto *valueelement = serializer->Serialize(doc); if (valueelement) keyelement->InsertEndChild(valueelement); } catch (std::exception &e) { MITK_ERROR << "Serializer " << serializer->GetNameOfClass() << " failed: " << e.what(); } break; } } return keyelement; } bool mitk::MultiLabelIOHelper::PropertyFromXMLElement(std::string &key, mitk::BaseProperty::Pointer &prop, const tinyxml2::XMLElement *elem) { const char* typeC = elem->Attribute("type"); std::string type = nullptr != typeC ? typeC : ""; const char* keyC = elem->Attribute("key"); key = nullptr != keyC ? keyC : ""; // construct name of serializer class std::string serializername(type); serializername += "Serializer"; std::list allSerializers = itk::ObjectFactoryBase::CreateAllInstance(serializername.c_str()); if (allSerializers.size() < 1) MITK_ERROR << "No serializer found for " << type << ". Skipping object"; if (allSerializers.size() > 1) MITK_WARN << "Multiple deserializers found for " << type << "Using arbitrarily the first one."; for (auto iter = allSerializers.begin(); iter != allSerializers.end(); ++iter) { if (auto *serializer = dynamic_cast(iter->GetPointer())) { try { prop = serializer->Deserialize(elem->FirstChildElement()); } catch (std::exception &e) { MITK_ERROR << "Deserializer " << serializer->GetNameOfClass() << " failed: " << e.what(); return false; } break; } } if (prop.IsNull()) return false; return true; } + +int mitk::MultiLabelIOHelper::GetIntByKey(const itk::MetaDataDictionary& dic, const std::string& str) +{ + std::vector imgMetaKeys = dic.GetKeys(); + std::vector::const_iterator itKey = imgMetaKeys.begin(); + std::string metaString(""); + for (; itKey != imgMetaKeys.end(); itKey++) + { + itk::ExposeMetaData(dic, *itKey, metaString); + if (itKey->find(str.c_str()) != std::string::npos) + { + return atoi(metaString.c_str()); + } + } + return 0; +} + +std::string mitk::MultiLabelIOHelper::GetStringByKey(const itk::MetaDataDictionary& dic, const std::string& str) +{ + std::vector imgMetaKeys = dic.GetKeys(); + std::vector::const_iterator itKey = imgMetaKeys.begin(); + std::string metaString(""); + for (; itKey != imgMetaKeys.end(); itKey++) + { + itk::ExposeMetaData(dic, *itKey, metaString); + if (itKey->find(str.c_str()) != std::string::npos) + { + return metaString; + } + } + return metaString; +} + +nlohmann::json mitk::MultiLabelIOHelper::SerializeMultLabelGroupsToJSON(const mitk::LabelSetImage* inputImage) +{ + if (nullptr == inputImage) + { + mitkThrow() << "Invalid call of SerializeMultLabelGroupsToJSON. Passed image pointer is null."; + } + + nlohmann::json result; + + for (LabelSetImage::SpatialGroupIndexType i = 0; i < inputImage->GetNumberOfLayers(); i++) + { + nlohmann::json jgroup; + for (const auto label : inputImage->GetLabelsInGroup(i)) + { + jgroup.emplace_back(SerializeLabelToJSON(label)); + } + result.emplace_back(jgroup); + } + return result; +}; + +std::vector mitk::MultiLabelIOHelper::DeserializeMultLabelGroupsFromJSON(const nlohmann::json& listOfLabelSets) +{ + std::vector result; + + for (const auto& jlabelsets : listOfLabelSets) + { + LabelSet::Pointer labelSet = LabelSet::New(); + for (const auto& jlabel : jlabelsets) + { + auto label = DeserializeLabelFromJSON(jlabel); + labelSet->AddLabel(label, false); + } + + result.emplace_back(labelSet); + } + + return result; +} + +nlohmann::json mitk::MultiLabelIOHelper::SerializeLabelToJSON(const Label* label) +{ + if (nullptr == label) + { + mitkThrow() << "Invalid call of GetLabelAsJSON. Passed label pointer is null."; + } + + nlohmann::json j; + j["name"] = label->GetName(); + + j["value"] = label->GetValue(); + + nlohmann::json jcolor; + jcolor["type"] = "ColorProperty"; + jcolor["value"] = {label->GetColor().GetRed(), label->GetColor().GetGreen(), label->GetColor().GetBlue() }; + j["color"] = jcolor; + + j["locked"] = label->GetLocked(); + j["opacity"] = label->GetOpacity(); + j["visible"] = label->GetVisible(); + return j; +}; + +template bool GetValueFromJson(const nlohmann::json& labelJson, const std::string& key, TValueType& value) +{ + if (labelJson.find(key) != labelJson.end()) + { + try + { + value = labelJson[key].get(); + return true; + } + catch (...) + { + MITK_ERROR << "Unable to read label information from json. Value has wrong type. Failed key: " << key << "; invalid value: " << labelJson[key].dump(); + throw; + } + } + return false; +} + +mitk::Label::Pointer mitk::MultiLabelIOHelper::DeserializeLabelFromJSON(const nlohmann::json& labelJson) +{ + Label::Pointer resultLabel = Label::New(); + + std::string name = "Unkown label name"; + GetValueFromJson(labelJson, "name", name); + resultLabel->SetName(name); + + Label::PixelType value = 1; + GetValueFromJson(labelJson, "value", value); + resultLabel->SetValue(value); + + if (labelJson.find("color") != labelJson.end()) + { + auto jcolor = labelJson["color"]["value"]; + Color color; + color.SetRed(jcolor[0].get()); + color.SetGreen(jcolor[1].get()); + color.SetBlue(jcolor[2].get()); + + resultLabel->SetColor(color); + } + + bool locked = false; + if (GetValueFromJson(labelJson, "locked", locked)) + resultLabel->SetLocked(locked); + + float opacity = 1.; + if (GetValueFromJson(labelJson, "opacity", opacity)) + resultLabel->SetOpacity(opacity); + + + bool visible = true; + if (GetValueFromJson(labelJson, "visible", visible)) + resultLabel->SetVisible(visible); + + return resultLabel; +} diff --git a/Modules/Multilabel/mitkMultiLabelIOHelper.h b/Modules/Multilabel/mitkMultiLabelIOHelper.h index a3bd60abf9..14c3b02648 100644 --- a/Modules/Multilabel/mitkMultiLabelIOHelper.h +++ b/Modules/Multilabel/mitkMultiLabelIOHelper.h @@ -1,105 +1,127 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkMultiLabelIOHelper_h #define mitkMultiLabelIOHelper_h -#include -#include +#include + #include +#include + +#include namespace tinyxml2 { class XMLDocument; class XMLElement; } +namespace itk +{ + class MetaDataDictionary; +} + namespace mitk { - class BaseProperty; class LabelSetImage; - class Label; constexpr char* const PROPERTY_NAME_TIMEGEOMETRY_TYPE = "org.mitk.timegeometry.type"; constexpr char* const PROPERTY_NAME_TIMEGEOMETRY_TIMEPOINTS = "org.mitk.timegeometry.timepoints"; constexpr char* const PROPERTY_KEY_TIMEGEOMETRY_TYPE = "org_mitk_timegeometry_type"; constexpr char* const PROPERTY_KEY_TIMEGEOMETRY_TIMEPOINTS = "org_mitk_timegeometry_timepoints"; constexpr char* const PROPERTY_KEY_UID = "org_mitk_uid"; /** * @brief The MultiLabelIOHelper is a static helper class that supports serialization of mitk::LabelSetImage * * This class provides static functions for converting mitk::Label into XML and also allows the serialization * of mitk::LabelSet as presets */ class MITKMULTILABEL_EXPORT MultiLabelIOHelper { public: /** * @brief Saves the mitk::LabelSet configuration of inputImage to presetFilename. * The preset is stored as "*.lsetp" * @param presetFilename the filename including the filesystem path * @param inputImage the input image from which the preset should be generated * @return true if the serialization was successful and false otherwise */ static bool SaveLabelSetImagePreset(const std::string &presetFilename, const mitk::LabelSetImage *inputImage); /** * @brief Loads an existing preset for a mitk::LabelSetImage from presetFilename and applies it to inputImage * @param presetFilename the filename of the preset including the filesystem path * @param inputImage the image to which the loaded preset will be applied * @return true if the deserilization was successful and false otherwise */ static bool LoadLabelSetImagePreset(const std::string &presetFilename, mitk::LabelSetImage *inputImage); /** * @brief Creates a mitk::Label from an XML element * @param labelElem the xml element from which a mitk::Label will be created * @return the created mitk::Label */ static itk::SmartPointer LoadLabelFromXMLDocument(const tinyxml2::XMLElement *labelElem); /** * @brief Creates an XML element from a mitk::Label * @param doc * @param label the mitk::Label from which the xml element will be created * @return the created XML element */ static tinyxml2::XMLElement *GetLabelAsXMLElement(tinyxml2::XMLDocument &doc, Label *label); /** * @brief Since a mitk::Label is basically a mitk::PropertyList this function coverts the label's properties into * XML * @param doc * @param key the property's key which will be used in the XML element * @param property the mitk::BaseProperty that should be converted * @return the created XML element */ static tinyxml2::XMLElement *PropertyToXMLElement(tinyxml2::XMLDocument& doc, const std::string &key, const BaseProperty *property); /** * @brief Since a mitk::Label is basically a mitk::PropertyList this function coverts a XML element into a property * @param key the property's key * @param prop the mitk::BaseProperty that will be created * @param elem the XML elem from which the property will be created * @return true if the conversion was successful and false otherwise */ static bool PropertyFromXMLElement(std::string &key, itk::SmartPointer &prop, const tinyxml2::XMLElement *elem); + /** Helper that extracts the value of a key in a meta dictionary as int. + * If the key does not exist 0 is returned.*/ + static int GetIntByKey(const itk::MetaDataDictionary& dic, const std::string& key); + /** Helper that extracts the value of a key in a meta dictionary as string. + * If the key does not exist an empty string is returned.*/ + static std::string GetStringByKey(const itk::MetaDataDictionary& dic, const std::string& key); + + + static nlohmann::json SerializeMultLabelGroupsToJSON(const mitk::LabelSetImage* inputImage); + + static std::vector DeserializeMultLabelGroupsFromJSON(const nlohmann::json& listOfLabelSets); + + static nlohmann::json SerializeLabelToJSON(const Label* label); + + static mitk::Label::Pointer DeserializeLabelFromJSON(const nlohmann::json& labelJson); + private: MultiLabelIOHelper(); }; } #endif