diff --git a/Modules/ContourModel/Algorithms/mitkContourModelUtils.cpp b/Modules/ContourModel/Algorithms/mitkContourModelUtils.cpp index 25c916e624..e53d577736 100755 --- a/Modules/ContourModel/Algorithms/mitkContourModelUtils.cpp +++ b/Modules/ContourModel/Algorithms/mitkContourModelUtils.cpp @@ -1,294 +1,292 @@ /*============================================================================ 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 mitk::ContourModelUtils::ContourModelUtils() { } mitk::ContourModelUtils::~ContourModelUtils() { } mitk::ContourModel::Pointer mitk::ContourModelUtils::ProjectContourTo2DSlice( const Image *slice, const ContourModel *contourIn3D) { if (nullptr == slice || nullptr == contourIn3D) return nullptr; auto projectedContour = ContourModel::New(); projectedContour->Initialize(*contourIn3D); auto sliceGeometry = slice->GetGeometry(); const auto numberOfTimesteps = static_cast(contourIn3D->GetTimeSteps()); for (std::remove_const_t t = 0; t < numberOfTimesteps; ++t) { auto iter = contourIn3D->Begin(t); auto end = contourIn3D->End(t); while (iter != end) { const auto ¤tPointIn3D = (*iter)->Coordinates; Point3D projectedPointIn2D; projectedPointIn2D.Fill(0.0); sliceGeometry->WorldToIndex(currentPointIn3D, projectedPointIn2D); projectedContour->AddVertex(projectedPointIn2D, t); ++iter; } } return projectedContour; } mitk::ContourModel::Pointer mitk::ContourModelUtils::BackProjectContourFrom2DSlice( const BaseGeometry *sliceGeometry, const ContourModel *contourIn2D) { if (nullptr == sliceGeometry || nullptr == contourIn2D) return nullptr; auto worldContour = ContourModel::New(); worldContour->Initialize(*contourIn2D); const auto numberOfTimesteps = static_cast(contourIn2D->GetTimeSteps()); for (std::remove_const_t t = 0; t < numberOfTimesteps; ++t) { auto iter = contourIn2D->Begin(t); auto end = contourIn2D->End(t); while (iter != end) { const auto ¤tPointIn2D = (*iter)->Coordinates; Point3D worldPointIn3D; worldPointIn3D.Fill(0.0); sliceGeometry->IndexToWorld(currentPointIn2D, worldPointIn3D); worldContour->AddVertex(worldPointIn3D, t); ++iter; } } return worldContour; } void mitk::ContourModelUtils::FillContourInSlice2( const ContourModel* projectedContour, Image* sliceImage, int paintingPixelValue) { FillContourInSlice2(projectedContour, 0, sliceImage, paintingPixelValue); } void mitk::ContourModelUtils::FillContourInSlice2( const ContourModel* projectedContour, TimeStepType contourTimeStep, Image* sliceImage, int paintingPixelValue) { if (nullptr == projectedContour) { mitkThrow() << "Cannot fill contour in slice. Passed contour is invalid"; } if (nullptr == sliceImage) { mitkThrow() << "Cannot fill contour in slice. Passed slice is invalid"; } auto contourModelFilter = mitk::ContourModelToSurfaceFilter::New(); contourModelFilter->SetInput(projectedContour); contourModelFilter->Update(); auto surface = mitk::Surface::New(); surface = contourModelFilter->GetOutput(); if (nullptr == surface->GetVtkPolyData(contourTimeStep)) { MITK_WARN << "Could not create surface from contour model."; return; } auto surface2D = vtkSmartPointer::New(); surface2D->SetPoints(surface->GetVtkPolyData(contourTimeStep)->GetPoints()); surface2D->SetLines(surface->GetVtkPolyData(contourTimeStep)->GetLines()); auto polyDataToImageStencil = vtkSmartPointer::New(); // Set a minimal tolerance, so that clipped pixels will be added to contour as well. polyDataToImageStencil->SetTolerance(mitk::eps); polyDataToImageStencil->SetInputData(surface2D); polyDataToImageStencil->Update(); auto imageStencil = vtkSmartPointer::New(); imageStencil->SetInputData(sliceImage->GetVtkImageData()); imageStencil->SetStencilConnection(polyDataToImageStencil->GetOutputPort()); imageStencil->ReverseStencilOn(); imageStencil->SetBackgroundValue(paintingPixelValue); imageStencil->Update(); vtkSmartPointer filledImage = imageStencil->GetOutput(); sliceImage->SetVolume(filledImage->GetScalarPointer()); } void mitk::ContourModelUtils::FillContourInSlice( const ContourModel *projectedContour, Image *sliceImage, const Image* workingImage, int paintingPixelValue) { FillContourInSlice(projectedContour, 0, sliceImage, workingImage, paintingPixelValue); } void mitk::ContourModelUtils::FillContourInSlice( const ContourModel *projectedContour, TimeStepType contourTimeStep, Image *sliceImage, const Image* workingImage, int paintingPixelValue) { if (nullptr == projectedContour) { mitkThrow() << "Cannot fill contour in slice. Passed contour is invalid"; } if (nullptr == sliceImage) { mitkThrow() << "Cannot fill contour in slice. Passed slice is invalid"; } auto contourModelFilter = mitk::ContourModelToSurfaceFilter::New(); contourModelFilter->SetInput(projectedContour); contourModelFilter->Update(); auto surface = mitk::Surface::New(); surface = contourModelFilter->GetOutput(); if (nullptr == surface->GetVtkPolyData(contourTimeStep)) { MITK_WARN << "Could not create surface from contour model."; return; } auto surface2D = vtkSmartPointer::New(); surface2D->SetPoints(surface->GetVtkPolyData(contourTimeStep)->GetPoints()); surface2D->SetLines(surface->GetVtkPolyData(contourTimeStep)->GetLines()); auto image = vtkSmartPointer::New(); image->DeepCopy(sliceImage->GetVtkImageData()); const double FOREGROUND_VALUE = 255.0; const double BACKGROUND_VALUE = 0.0; const vtkIdType count = image->GetNumberOfPoints(); for (std::remove_const_t i = 0; i < count; ++i) image->GetPointData()->GetScalars()->SetTuple1(i, FOREGROUND_VALUE); auto polyDataToImageStencil = vtkSmartPointer::New(); // Set a minimal tolerance, so that clipped pixels will be added to contour as well. polyDataToImageStencil->SetTolerance(mitk::eps); polyDataToImageStencil->SetInputData(surface2D); polyDataToImageStencil->Update(); auto imageStencil = vtkSmartPointer::New(); imageStencil->SetInputData(image); imageStencil->SetStencilConnection(polyDataToImageStencil->GetOutputPort()); imageStencil->ReverseStencilOff(); imageStencil->SetBackgroundValue(BACKGROUND_VALUE); imageStencil->Update(); vtkSmartPointer filledImage = imageStencil->GetOutput(); vtkSmartPointer resultImage = sliceImage->GetVtkImageData(); FillSliceInSlice(filledImage, resultImage, workingImage, paintingPixelValue); sliceImage->SetVolume(resultImage->GetScalarPointer()); } void mitk::ContourModelUtils::FillSliceInSlice( vtkSmartPointer filledImage, vtkSmartPointer resultImage, const Image* image, int paintingPixelValue, double fillForegroundThreshold) { auto labelImage = dynamic_cast(image); const auto numberOfPoints = filledImage->GetNumberOfPoints(); if (nullptr == labelImage) { for (std::remove_const_t i = 0; i < numberOfPoints; ++i) { if (fillForegroundThreshold <= filledImage->GetPointData()->GetScalars()->GetTuple1(i)) resultImage->GetPointData()->GetScalars()->SetTuple1(i, paintingPixelValue); } } else { - const auto backgroundValue = labelImage->GetExteriorLabel()->GetValue(); - - if (paintingPixelValue != backgroundValue) + if (paintingPixelValue != LabelSetImage::UnlabeledValue) { for (std::remove_const_t i = 0; i < numberOfPoints; ++i) { const auto filledValue = filledImage->GetPointData()->GetScalars()->GetTuple1(i); if (fillForegroundThreshold <= filledValue) { const auto existingValue = resultImage->GetPointData()->GetScalars()->GetTuple1(i); - if (!labelImage->GetLabel(existingValue, labelImage->GetActiveLayer())->GetLocked()) + if (!labelImage->IsLabelLocked(existingValue)) resultImage->GetPointData()->GetScalars()->SetTuple1(i, paintingPixelValue); } } } else { const auto activePixelValue = labelImage->GetActiveLabel(labelImage->GetActiveLayer())->GetValue(); for (std::remove_const_t i = 0; i < numberOfPoints; ++i) { if (fillForegroundThreshold <= filledImage->GetPointData()->GetScalars()->GetTuple1(i)) { if (resultImage->GetPointData()->GetScalars()->GetTuple1(i) == activePixelValue) resultImage->GetPointData()->GetScalars()->SetTuple1(i, paintingPixelValue); } } } } } mitk::ContourModel::Pointer mitk::ContourModelUtils::MoveZerothContourTimeStep(const ContourModel *contour, TimeStepType t) { if (nullptr == contour) return nullptr; auto resultContour = ContourModel::New(); resultContour->Expand(t + 1); std::for_each(contour->Begin(), contour->End(), [&resultContour, t](ContourElement::VertexType *vertex) { resultContour->AddVertex(*vertex, t); }); return resultContour; } int mitk::ContourModelUtils::GetActivePixelValue(const Image* workingImage) { auto labelSetImage = dynamic_cast(workingImage); int activePixelValue = 1; if (nullptr != labelSetImage) { activePixelValue = labelSetImage->GetActiveLabel(labelSetImage->GetActiveLayer())->GetValue(); } return activePixelValue; } diff --git a/Modules/ContourModel/Algorithms/mitkContourModelUtils.h b/Modules/ContourModel/Algorithms/mitkContourModelUtils.h index e97d315291..769634ae1d 100644 --- a/Modules/ContourModel/Algorithms/mitkContourModelUtils.h +++ b/Modules/ContourModel/Algorithms/mitkContourModelUtils.h @@ -1,155 +1,155 @@ /*============================================================================ 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 mitkContourModelUtils_h #define mitkContourModelUtils_h #include #include #include #include namespace mitk { /** * \brief Helpful methods for working with contours and images * * */ class MITKCONTOURMODEL_EXPORT ContourModelUtils : public itk::Object { public: mitkClassMacroItkParent(ContourModelUtils, itk::Object); /** \brief Projects a contour onto an image point by point. Converts from world to index coordinates. \param slice \param contourIn3D */ static ContourModel::Pointer ProjectContourTo2DSlice(const Image *slice, const ContourModel *contourIn3D); /** \brief Projects a slice index coordinates of a contour back into world coordinates. \param sliceGeometry \param contourIn2D */ static ContourModel::Pointer BackProjectContourFrom2DSlice(const BaseGeometry *sliceGeometry, const ContourModel *contourIn2D); /** \brief Fill a contour in a 2D slice with a specified pixel value. This version always uses the contour of time step 0 and fills the image. \deprecated Ths function is deprecated. Use FillContourInSlice2() (in conjunction e.g. with TransferLabelContent()) instead. \pre sliceImage points to a valid instance \pre projectedContour points to a valid instance */ - [[deprecated]] - static void FillContourInSlice(const ContourModel *projectedContour, + //[[deprecated]] + DEPRECATED(static void FillContourInSlice(const ContourModel *projectedContour, Image *sliceImage, const Image* workingImage, - int paintingPixelValue = 1); + int paintingPixelValue = 1)); /** \brief Fill a contour in a 2D slice with a specified pixel value. This overloaded version uses the contour at the passed contourTimeStep to fill the passed image slice. \deprecated Ths function is deprecated. Use FillContourInSlice2() (in - conjunction e.g. with TransferLabelContent()) instead. + conjunction e.g. with TransferLabelContentAtTimeStep()) instead. \pre sliceImage points to a valid instance \pre projectedContour points to a valid instance */ - [[deprecated]] - static void FillContourInSlice(const ContourModel *projectedContour, + //[[deprecated]] + DEPRECATED(static void FillContourInSlice(const ContourModel *projectedContour, TimeStepType contourTimeStep, Image *sliceImage, const Image* workingImage, - int paintingPixelValue = 1); + int paintingPixelValue = 1)); /** \brief Fill a contour in a 2D slice with a specified pixel value. This version always uses the contour of time step 0 and fills the image. \param projectedContour Pointer to the contour that should be projected. \param sliceImage Pointer to the image which content should be altered by adding the contour with the specified paintingPixelValue. \param paintingPixelValue \pre sliceImage points to a valid instance \pre projectedContour points to a valid instance */ static void FillContourInSlice2(const ContourModel* projectedContour, Image* sliceImage, int paintingPixelValue = 1); /** \brief Fill a contour in a 2D slice with a specified pixel value. This overloaded version uses the contour at the passed contourTimeStep to fill the passed image slice. \param projectedContour Pointer to the contour that should be projected. \param contourTimeStep \param sliceImage Pointer to the image which content should be altered by \param paintingPixelValue adding the contour with the specified paintingPixelValue. \pre sliceImage points to a valid instance \pre projectedContour points to a valid instance */ static void FillContourInSlice2(const ContourModel* projectedContour, TimeStepType contourTimeStep, Image* sliceImage, int paintingPixelValue = 1); /** \brief Fills the paintingPixelValue into every pixel of resultImage as indicated by filledImage. If a LableSet image is specified it also by incorporating the rules of LabelSet images when filling the content. \param filledImage Pointer to the image content that should be checked to decied of a pixel in resultImage should be filled with paintingPixelValue or not. \param resultImage Pointer to the image content that should be overwritten guided by the content of filledImage. \param image Pointer to an mitk image that allows to define the LabelSet image which states steer the filling process. If an LabelSet instance is passed its states (e.g. locked labels etc...) will be used. If nullptr or an normal image is passed, then simply any pixel position indicated by filledImage will be overwritten. \param paintingPixelValue the pixelvalue/label that should be used in the result image when filling. \param fillForegroundThreshold The threshold value that decides if a pixel in the filled image counts as foreground (>=fillForegroundThreshold) or not. \deprecated Ths function is deprecated. Use TransferLabelContent() instead. */ [[deprecated]] static void FillSliceInSlice(vtkSmartPointer filledImage, vtkSmartPointer resultImage, const Image* image, int paintingPixelValue, double fillForegroundThreshold = 1.0); /** \brief Move the contour in time step 0 to to a new contour model at the given time step. */ static ContourModel::Pointer MoveZerothContourTimeStep(const ContourModel *contour, TimeStepType timeStep); /** \brief Retrieves the active pixel value of a (labelset) image. If the image is basic image, the pixel value 1 (one) will be returned. If the image is actually a labelset image, the pixel value of the active label of the active layer will be returned. \param workingImage The (labelset) image to retrieve the active pixel value of. */ static int GetActivePixelValue(const Image* workingImage); protected: ContourModelUtils(); ~ContourModelUtils() override; }; } #endif 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 2b39c41e63..f64356551f 100644 --- a/Modules/Core/include/mitkItkImageIO.h +++ b/Modules/Core/include/mitkItkImageIO.h @@ -1,82 +1,102 @@ /*============================================================================ 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. + @param dictionary Reference to the meta data dictionary that contains the information that should be extracted.*/ + 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 2ee7b9a45f..bc52ba2ea0 100644 --- a/Modules/Core/src/IO/mitkItkImageIO.cpp +++ b/Modules/Core/src/IO/mitkItkImageIO.cpp @@ -1,764 +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; }; - itk::MetaDataObjectBase::Pointer ConvertTimePointListToMetaDataObject(const mitk::TimeGeometry* timeGeometry) + Image::Pointer ItkImageIO::LoadRawMitkImageFromImageIO(itk::ImageIOBase* imageIO, const std::string& path) { - 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(); - }; - - std::vector ItkImageIO::DoRead() - { - std::vector result; - mitk::LocaleSwitch localeSwitch("C"); + LocaleSwitch localeSwitch("C"); Image::Pointer image = Image::New(); const unsigned int MINDIM = 2; const unsigned int MAXDIM = 4; - const std::string path = this->GetLocalFileName(); - 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. - m_ImageIO->SetFileName(path); - m_ImageIO->ReadImageInformation(); + imageIO->SetFileName(path); + imageIO->ReadImageInformation(); - unsigned int ndim = m_ImageIO->GetNumberOfDimensions(); + 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."; + << " 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] = m_ImageIO->GetDimensions(i); + ioSize[i] = imageIO->GetDimensions(i); if (i < MAXDIM) { - dimensions[i] = m_ImageIO->GetDimensions(i); - spacing[i] = m_ImageIO->GetSpacing(i); + dimensions[i] = imageIO->GetDimensions(i); + spacing[i] = imageIO->GetSpacing(i); if (spacing[i] <= 0) spacing[i] = 1.0f; } if (i < 3) { - origin[i] = m_ImageIO->GetOrigin(i); + origin[i] = imageIO->GetOrigin(i); } } ioRegion.SetSize(ioSize); ioRegion.SetIndex(ioStart); MITK_INFO << "ioRegion: " << ioRegion << std::endl; - m_ImageIO->SetIORegion(ioRegion); - void *buffer = new unsigned char[m_ImageIO->GetImageSizeInBytes()]; - m_ImageIO->Read(buffer); + imageIO->SetIORegion(ioRegion); + void* buffer = new unsigned char[imageIO->GetImageSizeInBytes()]; + imageIO->Read(buffer); - image->Initialize(MakePixelType(m_ImageIO), ndim, dimensions); + image->Initialize(MakePixelType(imageIO), ndim, dimensions); image->SetImportChannel(buffer, 0, Image::ManageMemory); - const itk::MetaDataDictionary &dictionary = m_ImageIO->GetMetaDataDictionary(); + 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] = m_ImageIO->GetDirection(j)[i]; + matrix[i][j] = imageIO->GetDirection(j)[i]; // re-initialize PlaneGeometry with origin and direction - PlaneGeometry *planeGeometry = image->GetSlicedGeometry(0)->GetPlaneGeometry(0); + PlaneGeometry* planeGeometry = image->GetSlicedGeometry(0)->GetPlaneGeometry(0); planeGeometry->SetOrigin(origin); planeGeometry->GetIndexToWorldTransform()->SetMatrix(matrix); // re-initialize SlicedGeometry3D - SlicedGeometry3D *slicedGeometry = image->GetSlicedGeometry(0); + 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"; + << 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) + ++iter) { if (iter->second->GetMetaDataObjectTypeInfo() == typeid(std::string)) { - const std::string &key = iter->first; + 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()); + CoreServicePointer propPersistenceService(CoreServices::GetPropertyPersistence()); IPropertyPersistence::InfoResultType infoList = propPersistenceService->GetInfoByKey(key); - auto predicate = [&mimeTypeName](const PropertyPersistenceInfo::ConstPointer &x) { + 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) { + 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; } - image->SetProperty(assumedPropertyName.c_str(), loadedProp); + 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 : m_DefaultMetaDataKeys) + 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/Testing/files.cmake b/Modules/Multilabel/Testing/files.cmake index bf283fc5a7..0a03695261 100644 --- a/Modules/Multilabel/Testing/files.cmake +++ b/Modules/Multilabel/Testing/files.cmake @@ -1,9 +1,9 @@ set(MODULE_TESTS mitkLabelTest.cpp mitkLabelSetTest.cpp mitkLabelSetImageTest.cpp - mitkLabelSetImageIOTest.cpp + mitkLegacyLabelSetImageIOTest.cpp mitkLabelSetImageSurfaceStampFilterTest.cpp mitkTransferLabelTest.cpp ) diff --git a/Modules/Multilabel/Testing/mitkLabelSetImageIOTest.cpp b/Modules/Multilabel/Testing/mitkLabelSetImageIOTest.cpp deleted file mode 100644 index e5bc2b557d..0000000000 --- a/Modules/Multilabel/Testing/mitkLabelSetImageIOTest.cpp +++ /dev/null @@ -1,235 +0,0 @@ -/*============================================================================ - -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 - -std::string pathToImage; - -class mitkLabelSetImageIOTestSuite : public mitk::TestFixture -{ - CPPUNIT_TEST_SUITE(mitkLabelSetImageIOTestSuite); - MITK_TEST(TestReadWrite3DLabelSetImage); - MITK_TEST(TestReadWrite3DplusTLabelSetImage); - MITK_TEST(TestReadWrite3DplusTLabelSetImageWithArbitraryGeometry); - MITK_TEST(TestReadWriteProperties); - CPPUNIT_TEST_SUITE_END(); - -private: - mitk::Image::Pointer regularImage; - mitk::LabelSetImage::Pointer multilabelImage; - -public: - void setUp() override - { - regularImage = mitk::Image::New(); - } - - void tearDown() override - { - regularImage = nullptr; - multilabelImage = nullptr; - } - - void TestReadWrite3DLabelSetImage() - { - unsigned int dimensions[3] = {30, 20, 10}; - regularImage->Initialize(mitk::MakeScalarPixelType(), 3, dimensions); - - multilabelImage = mitk::LabelSetImage::New(); - multilabelImage->Initialize(regularImage); - mitk::LabelSet::Pointer newlayer = mitk::LabelSet::New(); - newlayer->SetLayer(1); - mitk::Label::Pointer label0 = mitk::Label::New(); - label0->SetName("Background"); - label0->SetValue(0); - - mitk::Label::Pointer label1 = mitk::Label::New(); - label1->SetName("Label1"); - label1->SetValue(1); - - mitk::Label::Pointer label2 = mitk::Label::New(); - label2->SetName("Label2"); - label2->SetValue(200); - - newlayer->AddLabel(label0); - newlayer->AddLabel(label1); - newlayer->AddLabel(label2); - newlayer->SetActiveLabel(200); - - multilabelImage->AddLayer(newlayer); - - pathToImage = mitk::IOUtil::CreateTemporaryDirectory(); - pathToImage.append("/LabelSetTestImage3D.nrrd"); - - mitk::IOUtil::Save(multilabelImage, pathToImage); - - auto loadedImage = - mitk::IOUtil::Load(pathToImage); - - // This information is currently not serialized but also checked within the Equals function - loadedImage->SetActiveLayer(multilabelImage->GetActiveLayer()); - - CPPUNIT_ASSERT_MESSAGE("Error reading label set image", loadedImage.IsNotNull()); - CPPUNIT_ASSERT_MESSAGE("Error reading label set image", mitk::Equal(*multilabelImage, *loadedImage, 0.0001, true)); - CPPUNIT_ASSERT_EQUAL_MESSAGE("Error, read image has different UID", multilabelImage->GetUID(), loadedImage->GetUID()); - - itksys::SystemTools::RemoveFile(pathToImage); - } - - void TestReadWrite3DplusTLabelSetImage() - { - unsigned int dimensions[4] = {30, 20, 15, 10}; - regularImage->Initialize(mitk::MakeScalarPixelType(), 4, dimensions); - - multilabelImage = mitk::LabelSetImage::New(); - multilabelImage->Initialize(regularImage); - mitk::LabelSet::Pointer newlayer = mitk::LabelSet::New(); - newlayer->SetLayer(1); - mitk::Label::Pointer label0 = mitk::Label::New(); - label0->SetName("Background"); - label0->SetValue(0); - - mitk::Label::Pointer label1 = mitk::Label::New(); - label1->SetName("Label1"); - label1->SetValue(1); - - mitk::Label::Pointer label2 = mitk::Label::New(); - label2->SetName("Label2"); - label2->SetValue(200); - - newlayer->AddLabel(label0); - newlayer->AddLabel(label1); - newlayer->AddLabel(label2); - newlayer->SetActiveLabel(200); - - multilabelImage->AddLayer(newlayer); - - pathToImage = mitk::IOUtil::CreateTemporaryDirectory(); - pathToImage.append("/LabelSetTestImage3DplusT.nrrd"); - - mitk::IOUtil::Save(multilabelImage, pathToImage); - - auto loadedImage = - mitk::IOUtil::Load(pathToImage); - - // This information is currently not serialized but also checked within the Equals function - loadedImage->SetActiveLayer(multilabelImage->GetActiveLayer()); - - CPPUNIT_ASSERT_MESSAGE("Error reading label set image", loadedImage.IsNotNull()); - CPPUNIT_ASSERT_MESSAGE("Error reading label set image", mitk::Equal(*multilabelImage, *loadedImage, 0.0001, true)); - CPPUNIT_ASSERT_MESSAGE("Error reading time geometry of label set image", mitk::Equal(*(multilabelImage->GetTimeGeometry()), *(loadedImage->GetTimeGeometry()), 0.000000001, true)); - - itksys::SystemTools::RemoveFile(pathToImage); - } - - void TestReadWrite3DplusTLabelSetImageWithArbitraryGeometry() - { - unsigned int dimensions[4] = { 30, 20, 10, 4 }; - regularImage->Initialize(mitk::MakeScalarPixelType(), 4, dimensions); - - multilabelImage = mitk::LabelSetImage::New(); - multilabelImage->Initialize(regularImage); - mitk::LabelSet::Pointer newlayer = mitk::LabelSet::New(); - newlayer->SetLayer(1); - mitk::Label::Pointer label0 = mitk::Label::New(); - label0->SetName("Background"); - label0->SetValue(0); - - mitk::Label::Pointer label1 = mitk::Label::New(); - label1->SetName("Label1"); - label1->SetValue(1); - - mitk::Label::Pointer label2 = mitk::Label::New(); - label2->SetName("Label2"); - label2->SetValue(200); - - newlayer->AddLabel(label0); - newlayer->AddLabel(label1); - newlayer->AddLabel(label2); - newlayer->SetActiveLabel(200); - - multilabelImage->AddLayer(newlayer); - - auto geometry = multilabelImage->GetGeometry()->Clone(); - - auto refTimeGeometry = mitk::ArbitraryTimeGeometry::New(); - refTimeGeometry->AppendNewTimeStep(geometry, 0., 0.5); - refTimeGeometry->AppendNewTimeStep(geometry, 0.5, 1.); - refTimeGeometry->AppendNewTimeStep(geometry, 1., 2.); - refTimeGeometry->AppendNewTimeStep(geometry, 2., 5.5); - multilabelImage->SetTimeGeometry(refTimeGeometry); - - pathToImage = mitk::IOUtil::CreateTemporaryDirectory(); - pathToImage.append("/LabelSetTestImage3DplusTWithArbitraryTimeGeometry.nrrd"); - - mitk::IOUtil::Save(multilabelImage, pathToImage); - - auto loadedImage = - mitk::IOUtil::Load(pathToImage); - - // This information is currently not serialized but also checked within the Equals function - loadedImage->SetActiveLayer(multilabelImage->GetActiveLayer()); - - CPPUNIT_ASSERT_MESSAGE("Error reading label set image", loadedImage.IsNotNull()); - CPPUNIT_ASSERT_MESSAGE("Error reading label set image", mitk::Equal(*multilabelImage, *loadedImage, 0.0001, true)); - CPPUNIT_ASSERT_MESSAGE("Error reading time geometry of label set image", mitk::Equal(*refTimeGeometry, *(loadedImage->GetTimeGeometry()), 0.000000001, true)); - itksys::SystemTools::RemoveFile(pathToImage); - } - - void TestReadWriteProperties() - { - unsigned int dimensions[3] = { 30, 20, 10 }; - regularImage->Initialize(mitk::MakeScalarPixelType(), 3, dimensions); - - multilabelImage = mitk::LabelSetImage::New(); - multilabelImage->Initialize(regularImage); - mitk::LabelSet::Pointer newlayer = mitk::LabelSet::New(); - newlayer->SetLayer(1); - mitk::Label::Pointer label0 = mitk::Label::New(); - label0->SetName("Background"); - label0->SetValue(0); - newlayer->AddLabel(label0); - multilabelImage->AddLayer(newlayer); - - auto propPersistenceInfo = mitk::PropertyPersistenceInfo::New(); - propPersistenceInfo->SetNameAndKey("my.cool.test.property", "my_cool_test_property"); - mitk::CoreServicePointer propPersService(mitk::CoreServices::GetPropertyPersistence()); - propPersService->AddInfo(propPersistenceInfo); - - multilabelImage->SetProperty("my.cool.test.property", mitk::StringProperty::New("test_content")); - - pathToImage = mitk::IOUtil::CreateTemporaryDirectory(); - pathToImage.append("/LabelSetPropertiesTestImage.nrrd"); - - mitk::IOUtil::Save(multilabelImage, pathToImage); - - auto loadedImage = - mitk::IOUtil::Load(pathToImage); - - auto loadedProp = loadedImage->GetProperty("my.cool.test.property"); - CPPUNIT_ASSERT_MESSAGE("Error reading properties of label set image", loadedProp.IsNotNull()); - CPPUNIT_ASSERT_MESSAGE("Error reading properties of label set image", loadedProp->GetValueAsString() == "test_content"); - itksys::SystemTools::RemoveFile(pathToImage); - } - - -}; - -MITK_TEST_SUITE_REGISTRATION(mitkLabelSetImageIO) diff --git a/Modules/Multilabel/Testing/mitkLabelSetImageTest.cpp b/Modules/Multilabel/Testing/mitkLabelSetImageTest.cpp index 612295804f..700825fdaa 100644 --- a/Modules/Multilabel/Testing/mitkLabelSetImageTest.cpp +++ b/Modules/Multilabel/Testing/mitkLabelSetImageTest.cpp @@ -1,519 +1,514 @@ /*============================================================================ 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 class mitkLabelSetImageTestSuite : public mitk::TestFixture { CPPUNIT_TEST_SUITE(mitkLabelSetImageTestSuite); MITK_TEST(TestInitialize); MITK_TEST(TestAddLayer); MITK_TEST(TestGetActiveLabelSet); MITK_TEST(TestGetActiveLabel); MITK_TEST(TestInitializeByLabeledImage); MITK_TEST(TestGetLabelSet); MITK_TEST(TestGetLabel); - MITK_TEST(TestSetExteriorLabel); + MITK_TEST(TestSetUnlabeledLabelLock); MITK_TEST(TestGetTotalNumberOfLabels); MITK_TEST(TestExistsLabel); MITK_TEST(TestExistsLabelSet); MITK_TEST(TestSetActiveLayer); MITK_TEST(TestRemoveLayer); MITK_TEST(TestRemoveLabels); MITK_TEST(TestEraseLabels); MITK_TEST(TestMergeLabels); MITK_TEST(TestCreateLabelMask); CPPUNIT_TEST_SUITE_END(); private: mitk::LabelSetImage::Pointer m_LabelSetImage; public: void setUp() override { // Create a new labelset image m_LabelSetImage = mitk::LabelSetImage::New(); mitk::Image::Pointer regularImage = mitk::Image::New(); unsigned int dimensions[3] = { 96, 128, 52 }; regularImage->Initialize(mitk::MakeScalarPixelType(), 3, dimensions); m_LabelSetImage->Initialize(regularImage); } void tearDown() override { // Delete LabelSetImage m_LabelSetImage = nullptr; } void TestInitialize() { // LabelSet image should always has the pixel type mitk::Label::PixelType CPPUNIT_ASSERT_MESSAGE("LabelSetImage has wrong pixel type", m_LabelSetImage->GetPixelType() == mitk::MakeScalarPixelType()); mitk::Image::Pointer regularImage = mitk::Image::New(); unsigned int dimensions[3] = { 96, 128, 52 }; regularImage->Initialize(mitk::MakeScalarPixelType(), 3, dimensions); mitk::BaseGeometry::Pointer regularImageGeo = regularImage->GetGeometry(); mitk::BaseGeometry::Pointer labelImageGeo = m_LabelSetImage->GetGeometry(); MITK_ASSERT_EQUAL(labelImageGeo, regularImageGeo, "LabelSetImage has wrong geometry"); - // By default one layer containing the exterior label should be added + // By default one layer should be added CPPUNIT_ASSERT_MESSAGE("Image was not correctly initialized - number of layers is not one", m_LabelSetImage->GetNumberOfLayers() == 1); CPPUNIT_ASSERT_MESSAGE("Image was not correctly initialized - active layer has wrong ID", m_LabelSetImage->GetActiveLayer() == 0); - CPPUNIT_ASSERT_MESSAGE("Image was not correctly initialized - active label is not the exterior label", - m_LabelSetImage->GetActiveLabel()->GetValue() == 0); + CPPUNIT_ASSERT_MESSAGE("Image was not correctly initialized - no active label should be selected", + m_LabelSetImage->GetActiveLabel() == nullptr); } void TestAddLayer() { CPPUNIT_ASSERT_MESSAGE("Number of layers is not zero", m_LabelSetImage->GetNumberOfLayers() == 1); m_LabelSetImage->AddLayer(); CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - number of layers is not one", m_LabelSetImage->GetNumberOfLayers() == 2); CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - active layer has wrong ID", m_LabelSetImage->GetActiveLayer() == 1); - CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - active label is not the exterior label", - m_LabelSetImage->GetActiveLabel()->GetValue() == 0); + CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - no active label should be selected", + m_LabelSetImage->GetActiveLabel() == nullptr); mitk::LabelSet::Pointer newlayer = mitk::LabelSet::New(); mitk::Label::Pointer label1 = mitk::Label::New(); label1->SetName("Label1"); label1->SetValue(1); mitk::Label::Pointer label2 = mitk::Label::New(); label2->SetName("Label2"); label2->SetValue(200); newlayer->AddLabel(label1); newlayer->AddLabel(label2); newlayer->SetActiveLabel(200); unsigned int layerID = m_LabelSetImage->AddLayer(newlayer); CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - number of layers is not two", m_LabelSetImage->GetNumberOfLayers() == 3); CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - active layer has wrong ID", m_LabelSetImage->GetActiveLayer() == layerID); CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - active label is wrong", m_LabelSetImage->GetActiveLabel(layerID)->GetValue() == 200); } void TestGetActiveLabelSet() { mitk::LabelSet::Pointer newlayer = mitk::LabelSet::New(); mitk::Label::Pointer label1 = mitk::Label::New(); label1->SetName("Label1"); label1->SetValue(1); mitk::Label::Pointer label2 = mitk::Label::New(); label2->SetName("Label2"); label2->SetValue(200); newlayer->AddLabel(label1); newlayer->AddLabel(label2); newlayer->SetActiveLabel(200); unsigned int layerID = m_LabelSetImage->AddLayer(newlayer); mitk::LabelSet::Pointer activeLayer = m_LabelSetImage->GetActiveLabelSet(); CPPUNIT_ASSERT_MESSAGE("Wrong layer ID was returned", layerID == 1); CPPUNIT_ASSERT_MESSAGE("Wrong active labelset returned", mitk::Equal(*newlayer, *activeLayer, 0.00001, true)); mitk::LabelSet::ConstPointer constActiveLayer = const_cast(m_LabelSetImage.GetPointer())->GetActiveLabelSet(); CPPUNIT_ASSERT_MESSAGE("Wrong active labelset returned", mitk::Equal(*newlayer, *constActiveLayer, 0.00001, true)); } void TestGetActiveLabel() { mitk::Label::Pointer label1 = mitk::Label::New(); label1->SetName("Label1"); mitk::Label::PixelType value1 = 1; label1->SetValue(value1); mitk::Label::Pointer label2 = mitk::Label::New(); label2->SetName("Label2"); mitk::Label::PixelType value2 = 200; label2->SetValue(value2); m_LabelSetImage->GetActiveLabelSet()->AddLabel(label1); m_LabelSetImage->GetActiveLabelSet()->AddLabel(label2); m_LabelSetImage->GetActiveLabelSet()->SetActiveLabel(1); CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - active label is wrong", m_LabelSetImage->GetActiveLabel()->GetValue() == value1); m_LabelSetImage->GetActiveLabelSet()->SetActiveLabel(value2); CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - active label is wrong", m_LabelSetImage->GetActiveLabel()->GetValue() == value2); CPPUNIT_ASSERT_MESSAGE("Active Label was not correctly retreived with const getter", const_cast(m_LabelSetImage.GetPointer())->GetActiveLabel()->GetValue() == value2); } void TestInitializeByLabeledImage() { mitk::Image::Pointer image = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelSetTestInitializeImage.nrrd")); m_LabelSetImage->InitializeByLabeledImage(image); - CPPUNIT_ASSERT_MESSAGE("Image - number of labels is not 6", m_LabelSetImage->GetNumberOfLabels() == 6); + CPPUNIT_ASSERT_MESSAGE("Image - number of labels is not 5", m_LabelSetImage->GetNumberOfLabels() == 5); } void TestGetLabelSet() { // Test get non existing lset mitk::LabelSet::ConstPointer lset = m_LabelSetImage->GetLabelSet(10000); CPPUNIT_ASSERT_MESSAGE("Non existing labelset is not nullptr", lset.IsNull()); lset = m_LabelSetImage->GetLabelSet(0); CPPUNIT_ASSERT_MESSAGE("Existing labelset is nullptr", lset.IsNotNull()); } void TestGetLabel() { mitk::Label::Pointer label1 = mitk::Label::New(); label1->SetName("Label1"); mitk::Label::PixelType value1 = 1; label1->SetValue(value1); mitk::Label::Pointer label2 = mitk::Label::New(); label2->SetName("Label2"); mitk::Label::PixelType value2 = 200; label2->SetValue(value2); m_LabelSetImage->GetActiveLabelSet()->AddLabel(label1); m_LabelSetImage->AddLayer(); m_LabelSetImage->GetLabelSet(1)->AddLabel(label2); CPPUNIT_ASSERT_MESSAGE("Wrong label retrieved for active layer", mitk::Equal(*m_LabelSetImage->GetLabel(1), *label1, 0.0001, true)); CPPUNIT_ASSERT_MESSAGE("Wrong label retrieved for layer 1", mitk::Equal(*m_LabelSetImage->GetLabel(200, 1), *label2, 0.0001, true)); // Try to get a non existing label mitk::Label *label3 = m_LabelSetImage->GetLabel(1000); CPPUNIT_ASSERT_MESSAGE("Non existing label should be nullptr", label3 == nullptr); // Try to get a label from a non existing layer label3 = m_LabelSetImage->GetLabel(200, 1000); CPPUNIT_ASSERT_MESSAGE("Label from non existing layer should be nullptr", label3 == nullptr); } - void TestSetExteriorLabel() + void TestSetUnlabeledLabelLock() { - mitk::Label::Pointer exteriorLabel = mitk::Label::New(); - exteriorLabel->SetName("MyExteriorSpecialLabel"); - mitk::Label::PixelType value1 = 10000; - exteriorLabel->SetValue(value1); - - m_LabelSetImage->SetExteriorLabel(exteriorLabel); - CPPUNIT_ASSERT_MESSAGE("Wrong label retrieved for layer 1", - mitk::Equal(*m_LabelSetImage->GetExteriorLabel(), *exteriorLabel, 0.0001, true)); - - // Exterior label should be set automatically for each new layer - m_LabelSetImage->AddLayer(); - CPPUNIT_ASSERT_MESSAGE("Wrong label retrieved for layer 1", - mitk::Equal(*m_LabelSetImage->GetLabel(10000, 1), *exteriorLabel, 0.0001, true)); + auto locked = m_LabelSetImage->GetUnlabeledLabelLock(); + CPPUNIT_ASSERT_MESSAGE("Wrong UnlabeledLabelLock default state", + locked == false); + + m_LabelSetImage->SetUnlabeledLabelLock(true); + locked = m_LabelSetImage->GetUnlabeledLabelLock(); + CPPUNIT_ASSERT_MESSAGE("Wrong UnlabeledLabelLock state", + locked == true); } void TestGetTotalNumberOfLabels() { mitk::Label::Pointer label1 = mitk::Label::New(); label1->SetName("Label1"); mitk::Label::PixelType value1 = 1; label1->SetValue(value1); mitk::Label::Pointer label2 = mitk::Label::New(); label2->SetName("Label2"); mitk::Label::PixelType value2 = 200; label2->SetValue(value2); m_LabelSetImage->GetActiveLabelSet()->AddLabel(label1); m_LabelSetImage->AddLayer(); m_LabelSetImage->GetLabelSet(1)->AddLabel(label2); CPPUNIT_ASSERT_MESSAGE( "Wrong total number of labels", - m_LabelSetImage->GetTotalNumberOfLabels() == 4); // added 2 labels + 2 exterior default labels + m_LabelSetImage->GetTotalNumberOfLabels() == 2); } void TestExistsLabel() { mitk::Label::Pointer label = mitk::Label::New(); label->SetName("Label2"); mitk::Label::PixelType value = 200; label->SetValue(value); m_LabelSetImage->AddLayer(); m_LabelSetImage->GetLabelSet(1)->AddLabel(label); m_LabelSetImage->SetActiveLayer(0); CPPUNIT_ASSERT_MESSAGE("Existing label was not found", m_LabelSetImage->ExistLabel(value) == true); CPPUNIT_ASSERT_MESSAGE("Non existing label was found", m_LabelSetImage->ExistLabel(10000) == false); } void TestExistsLabelSet() { // Cache active layer mitk::LabelSet::ConstPointer activeLayer = m_LabelSetImage->GetActiveLabelSet(); // Add new layer mitk::LabelSet::Pointer newlayer = mitk::LabelSet::New(); mitk::Label::Pointer label1 = mitk::Label::New(); label1->SetName("Label1"); label1->SetValue(1); mitk::Label::Pointer label2 = mitk::Label::New(); label2->SetName("Label2"); label2->SetValue(200); newlayer->AddLabel(label1); newlayer->AddLabel(label2); newlayer->SetActiveLabel(200); m_LabelSetImage->AddLayer(newlayer); CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistLabelSet(0) == true); CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistLabelSet(1) == true); CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistLabelSet(20) == false); } void TestSetActiveLayer() { // Cache active layer mitk::LabelSet::ConstPointer activeLayer = m_LabelSetImage->GetActiveLabelSet(); // Add new layer mitk::LabelSet::Pointer newlayer = mitk::LabelSet::New(); mitk::Label::Pointer label1 = mitk::Label::New(); label1->SetName("Label1"); label1->SetValue(1); mitk::Label::Pointer label2 = mitk::Label::New(); label2->SetName("Label2"); label2->SetValue(200); newlayer->AddLabel(label1); newlayer->AddLabel(label2); newlayer->SetActiveLabel(200); unsigned int layerID = m_LabelSetImage->AddLayer(newlayer); // Set initial layer as active layer m_LabelSetImage->SetActiveLayer(0); CPPUNIT_ASSERT_MESSAGE("Wrong active labelset returned", mitk::Equal(*activeLayer, *m_LabelSetImage->GetActiveLabelSet(), 0.00001, true)); // Set previously added layer as active layer m_LabelSetImage->SetActiveLayer(layerID); CPPUNIT_ASSERT_MESSAGE("Wrong active labelset returned", mitk::Equal(*newlayer, *m_LabelSetImage->GetActiveLabelSet(), 0.00001, true)); // Set a non existing layer as active layer - nothing should change m_LabelSetImage->SetActiveLayer(10000); CPPUNIT_ASSERT_MESSAGE("Wrong active labelset returned", mitk::Equal(*newlayer, *m_LabelSetImage->GetActiveLabelSet(), 0.00001, true)); } void TestRemoveLayer() { // Cache active layer mitk::LabelSet::ConstPointer activeLayer = m_LabelSetImage->GetActiveLabelSet(); // Add new layers m_LabelSetImage->AddLayer(); mitk::LabelSet::Pointer newlayer = mitk::LabelSet::New(); mitk::Label::Pointer label1 = mitk::Label::New(); label1->SetName("Label1"); label1->SetValue(1); mitk::Label::Pointer label2 = mitk::Label::New(); label2->SetName("Label2"); label2->SetValue(200); newlayer->AddLabel(label1); newlayer->AddLabel(label2); newlayer->SetActiveLabel(200); m_LabelSetImage->AddLayer(newlayer); CPPUNIT_ASSERT_MESSAGE("Wrong active labelset returned", mitk::Equal(*newlayer, *m_LabelSetImage->GetActiveLabelSet(), 0.00001, true)); m_LabelSetImage->RemoveLayer(); CPPUNIT_ASSERT_MESSAGE("Wrong number of layers, after a layer was removed", m_LabelSetImage->GetNumberOfLayers() == 2); CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistLabelSet(2) == false); CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistLabelSet(1) == true); CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistLabelSet(0) == true); m_LabelSetImage->RemoveLayer(); CPPUNIT_ASSERT_MESSAGE("Wrong number of layers, after a layer was removed", m_LabelSetImage->GetNumberOfLayers() == 1); CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistLabelSet(1) == false); CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistLabelSet(0) == true); CPPUNIT_ASSERT_MESSAGE("Wrong active layer", mitk::Equal(*activeLayer, *m_LabelSetImage->GetActiveLabelSet(), 0.00001, true)); m_LabelSetImage->RemoveLayer(); CPPUNIT_ASSERT_MESSAGE("Wrong number of layers, after a layer was removed", m_LabelSetImage->GetNumberOfLayers() == 0); CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistLabelSet(0) == false); CPPUNIT_ASSERT_MESSAGE("Active layers is not nullptr although all layer have been removed", m_LabelSetImage->GetActiveLabelSet() == nullptr); } void TestRemoveLabels() { mitk::Image::Pointer image = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelSetTestInitializeImage.nrrd")); m_LabelSetImage = nullptr; m_LabelSetImage = mitk::LabelSetImage::New(); m_LabelSetImage->InitializeByLabeledImage(image); - CPPUNIT_ASSERT_MESSAGE("Image - number of labels is not 6", m_LabelSetImage->GetNumberOfLabels() == 6); - // 2ndMin because of the exterior label = 0 + CPPUNIT_ASSERT_MESSAGE("Image - number of labels is not 6", m_LabelSetImage->GetNumberOfLabels() == 5); + // 2ndMin because of unlabeled pixels = 0 CPPUNIT_ASSERT_MESSAGE("Wrong MIN value", m_LabelSetImage->GetStatistics()->GetScalarValue2ndMin() == 1); CPPUNIT_ASSERT_MESSAGE("Wrong MAX value", m_LabelSetImage->GetStatistics()->GetScalarValueMax() == 7); CPPUNIT_ASSERT_MESSAGE("Label with ID 3 does not exist after initialization", m_LabelSetImage->ExistLabel(3) == true); m_LabelSetImage->RemoveLabel(1); std::vector labelsToBeRemoved; labelsToBeRemoved.push_back(3); labelsToBeRemoved.push_back(7); m_LabelSetImage->RemoveLabels(labelsToBeRemoved); CPPUNIT_ASSERT_MESSAGE("Wrong number of labels after some have been removed", - m_LabelSetImage->GetNumberOfLabels() == 3); + m_LabelSetImage->GetNumberOfLabels() == 2); // Values within the image are 0, 1, 3, 5, 6, 7 - New Min / Max value should be 5 / 6 - // 2ndMin because of the exterior label = 0 + // 2ndMin because of unlabeled pixels = 0 CPPUNIT_ASSERT_MESSAGE("Labels with value 1 and 3 were not removed from the image", m_LabelSetImage->GetStatistics()->GetScalarValue2ndMin() == 5); CPPUNIT_ASSERT_MESSAGE("Label with value 7 was not removed from the image", m_LabelSetImage->GetStatistics()->GetScalarValueMax() == 6); } void TestEraseLabels() { mitk::Image::Pointer image = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelSetTestInitializeImage.nrrd")); m_LabelSetImage = nullptr; m_LabelSetImage = mitk::LabelSetImage::New(); m_LabelSetImage->InitializeByLabeledImage(image); - CPPUNIT_ASSERT_MESSAGE("Image - number of labels is not 6", m_LabelSetImage->GetNumberOfLabels() == 6); - // 2ndMin because of the exterior label = 0 + CPPUNIT_ASSERT_MESSAGE("Image - number of labels is not 6", m_LabelSetImage->GetNumberOfLabels() == 5); + // 2ndMin because of unlabeled pixels = 0 CPPUNIT_ASSERT_MESSAGE("Wrong MIN value", m_LabelSetImage->GetStatistics()->GetScalarValue2ndMin() == 1); CPPUNIT_ASSERT_MESSAGE("Wrong MAX value", m_LabelSetImage->GetStatistics()->GetScalarValueMax() == 7); CPPUNIT_ASSERT_MESSAGE("Label with ID 3 does not exist after initialization", m_LabelSetImage->ExistLabel(3) == true); m_LabelSetImage->EraseLabel(1); std::vector labelsToBeErased; labelsToBeErased.push_back(3); labelsToBeErased.push_back(7); m_LabelSetImage->EraseLabels(labelsToBeErased); CPPUNIT_ASSERT_MESSAGE("Wrong number of labels since none have been removed", - m_LabelSetImage->GetNumberOfLabels() == 6); + m_LabelSetImage->GetNumberOfLabels() == 5); // Values within the image are 0, 1, 3, 5, 6, 7 - New Min / Max value should be 5 / 6 - // 2ndMin because of the exterior label = 0 + // 2ndMin because of unlabeled pixels = 0 CPPUNIT_ASSERT_MESSAGE("Labels with value 1 and 3 were not erased from the image", m_LabelSetImage->GetStatistics()->GetScalarValue2ndMin() == 5); CPPUNIT_ASSERT_MESSAGE("Label with value 7 was not erased from the image", m_LabelSetImage->GetStatistics()->GetScalarValueMax() == 6); } void TestMergeLabels() { mitk::Image::Pointer image = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelSetTestInitializeImage.nrrd")); m_LabelSetImage = nullptr; m_LabelSetImage = mitk::LabelSetImage::New(); m_LabelSetImage->InitializeByLabeledImage(image); - CPPUNIT_ASSERT_MESSAGE("Image - number of labels is not 6", m_LabelSetImage->GetNumberOfLabels() == 6); - // 2ndMin because of the exterior label = 0 + CPPUNIT_ASSERT_MESSAGE("Image - number of labels is not 6", m_LabelSetImage->GetNumberOfLabels() == 5); + // 2ndMin because of unlabeled pixels = 0 CPPUNIT_ASSERT_MESSAGE("Wrong MIN value", m_LabelSetImage->GetStatistics()->GetScalarValue2ndMin() == 1); CPPUNIT_ASSERT_MESSAGE("Wrong MAX value", m_LabelSetImage->GetStatistics()->GetScalarValueMax() == 7); CPPUNIT_ASSERT_MESSAGE("Label with ID 6 does not exist after initialization", m_LabelSetImage->ExistLabel(6) == true); // Merge label 7 with label 6. Result should be that label 7 is not present anymore. m_LabelSetImage->MergeLabel(6, 7); CPPUNIT_ASSERT_MESSAGE("Label with value 7 was not removed from the image", m_LabelSetImage->GetStatistics()->GetScalarValueMax() == 6); // Count all pixels with value 6 = 507 // Count all pixels with value 7 = 823 // Check if merged label has 507 + 823 = 1330 pixels CPPUNIT_ASSERT_MESSAGE("Labels were not correctly merged", m_LabelSetImage->GetStatistics()->GetCountOfMaxValuedVoxels() == 1330); CPPUNIT_ASSERT_MESSAGE("Label with ID 3 does not exist after initialization", m_LabelSetImage->ExistLabel(3) == true); CPPUNIT_ASSERT_MESSAGE("Label with ID 5 does not exist after initialization", m_LabelSetImage->ExistLabel(5) == true); // Merge labels 5 and 6 with 3. Result should be that labels 5 and 6 are not present anymore. std::vector vectorOfSourcePixelValues{ 5, 6 }; m_LabelSetImage->MergeLabels(3, vectorOfSourcePixelValues); // Values within the image are 0, 1, 3, 5, 6, 7 - New Max value should be 3 CPPUNIT_ASSERT_MESSAGE("Labels with value 5 and 6 were not removed from the image", m_LabelSetImage->GetStatistics()->GetScalarValueMax() == 3); // Count all pixels with value 3 = 1893 // Count all pixels with value 5 = 2143 // Count all pixels with value 6 = 1330 // Check if merged label has 1893 + 2143 + 1330 = 5366 pixels CPPUNIT_ASSERT_MESSAGE("Labels were not correctly merged", m_LabelSetImage->GetStatistics()->GetCountOfMaxValuedVoxels() == 5366); } void TestCreateLabelMask() { mitk::Image::Pointer image = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelSetTestInitializeImage.nrrd")); m_LabelSetImage = nullptr; m_LabelSetImage = mitk::LabelSetImage::New(); m_LabelSetImage->InitializeByLabeledImage(image); auto labelMask = m_LabelSetImage->CreateLabelMask(6); mitk::AutoCropImageFilter::Pointer cropFilter = mitk::AutoCropImageFilter::New(); cropFilter->SetInput(labelMask); cropFilter->SetBackgroundValue(0); cropFilter->SetMarginFactor(1.15); cropFilter->Update(); auto maskImage = cropFilter->GetOutput(); // Count all pixels with value 6 = 507 CPPUNIT_ASSERT_MESSAGE("Label mask not correctly created", maskImage->GetStatistics()->GetCountOfMaxValuedVoxels() == 507); } }; MITK_TEST_SUITE_REGISTRATION(mitkLabelSetImage) diff --git a/Modules/Multilabel/Testing/mitkLabelSetTest.cpp b/Modules/Multilabel/Testing/mitkLabelSetTest.cpp index 8db2f35179..01adf5f941 100644 --- a/Modules/Multilabel/Testing/mitkLabelSetTest.cpp +++ b/Modules/Multilabel/Testing/mitkLabelSetTest.cpp @@ -1,170 +1,170 @@ /*============================================================================ 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 class mitkLabelSetTestSuite : public mitk::TestFixture { CPPUNIT_TEST_SUITE(mitkLabelSetTestSuite); MITK_TEST(TestSetLayer); MITK_TEST(TestSetActiveLabel); MITK_TEST(TestRemoveLabel); MITK_TEST(TestAddLabel); MITK_TEST(TestRenameLabel); MITK_TEST(TestSetAllLabelsVisible); MITK_TEST(TestSetAllLabelsLocked); MITK_TEST(TestRemoveAllLabels); CPPUNIT_TEST_SUITE_END(); private: mitk::LabelSet::Pointer m_LabelSet; mitk::LabelSet::PixelType m_InitialNumberOfLabels; void AddLabels(mitk::LabelSet::PixelType numOfLabels) { mitk::Label::Pointer label; const std::string namePrefix = "Label_"; const mitk::Color gray(0.5f); for (mitk::Label::PixelType i = 0; i < numOfLabels; ++i) { label = mitk::Label::New(); label->SetName(namePrefix + std::to_string(i)); label->SetValue(i); label->SetVisible((i % 2 == 0)); label->SetLayer(i % 3); label->SetColor(gray); m_LabelSet->AddLabel(label); } } public: void setUp() override { m_InitialNumberOfLabels = 6; m_LabelSet = mitk::LabelSet::New(); this->AddLabels(m_InitialNumberOfLabels); m_LabelSet->SetLayer(0); m_LabelSet->SetActiveLabel(0); } void tearDown() override { m_LabelSet = nullptr; } void TestSetLayer() { CPPUNIT_ASSERT_MESSAGE("Wrong initial layer", m_LabelSet->GetLayer() == 0); m_LabelSet->SetLayer(1); CPPUNIT_ASSERT_MESSAGE("Wrong layer", m_LabelSet->GetLayer() == 1); } void TestSetActiveLabel() { - CPPUNIT_ASSERT_MESSAGE("Wrong initial active label", m_LabelSet->GetActiveLabel()->GetValue() == 0); + CPPUNIT_ASSERT_MESSAGE("Wrong initial active label", m_LabelSet->GetActiveLabel() == nullptr); m_LabelSet->SetActiveLabel(1); CPPUNIT_ASSERT_MESSAGE("Wrong active label", m_LabelSet->GetActiveLabel()->GetValue() == 1); } void TestRemoveLabel() { CPPUNIT_ASSERT_MESSAGE("Wrong initial number of label", m_LabelSet->GetNumberOfLabels() == m_InitialNumberOfLabels); // Remove a label that is not the active label m_LabelSet->SetActiveLabel(2); m_LabelSet->RemoveLabel(1); mitk::LabelSet::PixelType numLabels = m_InitialNumberOfLabels - 1; CPPUNIT_ASSERT_MESSAGE("Label was not removed", m_LabelSet->ExistLabel(1) == false); CPPUNIT_ASSERT_MESSAGE("Wrong number of label", m_LabelSet->GetNumberOfLabels() == numLabels); CPPUNIT_ASSERT_MESSAGE("Wrong active label", m_LabelSet->GetActiveLabel()->GetValue() == 2); // Remove active label - now the succeeding label should be active m_LabelSet->RemoveLabel(2); CPPUNIT_ASSERT_MESSAGE("Wrong active label", m_LabelSet->GetActiveLabel()->GetValue() == 3); CPPUNIT_ASSERT_MESSAGE("Label was not removed", m_LabelSet->ExistLabel(2) == false); CPPUNIT_ASSERT_MESSAGE("Wrong initial number of label", m_LabelSet->GetNumberOfLabels() == --numLabels); } void TestAddLabel() { auto newLabel = mitk::Label::New(); newLabel->SetValue(1); m_LabelSet->AddLabel(newLabel); // Since label with value 1 already exists the new label will get the value m_InitialNumberOfValues - CPPUNIT_ASSERT_MESSAGE("Wrong label value", m_LabelSet->GetActiveLabel()->GetValue() == m_InitialNumberOfLabels); + CPPUNIT_ASSERT_MESSAGE("Wrong label value", m_LabelSet->GetActiveLabel()->GetValue() == m_InitialNumberOfLabels+1); CPPUNIT_ASSERT_MESSAGE("Wrong number of label", m_LabelSet->GetNumberOfLabels() == static_castGetNumberOfLabels())>(m_InitialNumberOfLabels + 1)); } void TestRenameLabel() { const mitk::Color white(1.0f); const std::string name = "MyAwesomeLabel"; - m_LabelSet->RenameLabel(0, name, white); + m_LabelSet->RenameLabel(1, name, white); - const auto* label = m_LabelSet->GetLabel(0); + const auto* label = m_LabelSet->GetLabel(1); CPPUNIT_ASSERT_MESSAGE("Wrong label name", label->GetName() == name ); const auto& color = label->GetColor(); CPPUNIT_ASSERT_MESSAGE("Wrong color", color == white); } void TestSetAllLabelsVisible() { const auto numLabels = static_cast(m_LabelSet->GetNumberOfLabels()); m_LabelSet->SetAllLabelsVisible(true); - for (mitk::LabelSet::PixelType i = 0; i < numLabels; ++i) + for (mitk::LabelSet::PixelType i = 1; i < numLabels; ++i) CPPUNIT_ASSERT_MESSAGE("Label not visible", m_LabelSet->GetLabel(i)->GetVisible() == true); m_LabelSet->SetAllLabelsVisible(false); - for (mitk::LabelSet::PixelType i = 0; i < numLabels; ++i) + for (mitk::LabelSet::PixelType i = 1; i < numLabels; ++i) CPPUNIT_ASSERT_MESSAGE("Label visible", m_LabelSet->GetLabel(i)->GetVisible() == false); } void TestSetAllLabelsLocked() { const auto numLabels = static_cast(m_LabelSet->GetNumberOfLabels()); m_LabelSet->SetAllLabelsLocked(true); - for (mitk::LabelSet::PixelType i = 0; i < numLabels; ++i) + for (mitk::LabelSet::PixelType i = 1; i < numLabels; ++i) CPPUNIT_ASSERT_MESSAGE("Label not locked", m_LabelSet->GetLabel(i)->GetLocked() == true); m_LabelSet->SetAllLabelsLocked(false); - for (mitk::LabelSet::PixelType i = 0; i < numLabels; ++i) + for (mitk::LabelSet::PixelType i = 1; i < numLabels; ++i) CPPUNIT_ASSERT_MESSAGE("Label locked", m_LabelSet->GetLabel(i)->GetLocked() == false); } void TestRemoveAllLabels() { m_LabelSet->RemoveAllLabels(); CPPUNIT_ASSERT_MESSAGE("Not all labels were removed", m_LabelSet->GetNumberOfLabels() == 0); } }; MITK_TEST_SUITE_REGISTRATION(mitkLabelSet) diff --git a/Modules/Multilabel/Testing/mitkLegacyLabelSetImageIOTest.cpp b/Modules/Multilabel/Testing/mitkLegacyLabelSetImageIOTest.cpp new file mode 100644 index 0000000000..80a7eb7fba --- /dev/null +++ b/Modules/Multilabel/Testing/mitkLegacyLabelSetImageIOTest.cpp @@ -0,0 +1,142 @@ +/*============================================================================ + +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 + +std::string pathToImage; + +class mitkLegacyLabelSetImageIOTestSuite : public mitk::TestFixture +{ + CPPUNIT_TEST_SUITE(mitkLegacyLabelSetImageIOTestSuite); + MITK_TEST(TestRead3DLabelSetImage_Default); + MITK_TEST(TestRead3DLabelSetImage_Adapt); + MITK_TEST(TestRead3DLabelSetImage_Split); + MITK_TEST(TestRead3DplusTLabelSetImage); + CPPUNIT_TEST_SUITE_END(); + +private: + mitk::LabelSet::Pointer m_labelSet1; + mitk::LabelSet::Pointer m_labelSet2; + mitk::LabelSet::Pointer m_labelSet2_adapted; + +public: + mitk::Label::Pointer GenerateLabel(mitk::Label::PixelType value, const std::string& name, float r, float g, float b) const + { + auto label = mitk::Label::New(value, name); + mitk::Color color; + color.SetRed(r); + color.SetGreen(g); + color.SetBlue(b); + label->SetColor(color); + + return label; + } + + void setUp() override + { + m_labelSet1 = mitk::LabelSet::New(); + auto label = GenerateLabel(1, "Label 1", 0.745098054f, 0.f, 0.196078435f); + m_labelSet1->AddLabel(label,false); + label = GenerateLabel(2, "Label 2", 0.952941179, 0.764705896, 0); + m_labelSet1->AddLabel(label, false); + + m_labelSet2 = mitk::LabelSet::New(); + label = GenerateLabel(1, "Label 3", 0.552941203, 0.713725507, 0); + m_labelSet2->AddLabel(label, false); + label = GenerateLabel(2, "Label 4", 0.631372571, 0.792156875, 0.945098042); + m_labelSet2->AddLabel(label, false); + label = GenerateLabel(3, "Label 5", 0.639215708, 0.250980407, 0.725490212); + m_labelSet2->AddLabel(label, false); + + m_labelSet2_adapted = mitk::LabelSet::New(); + label = GenerateLabel(3, "Label 3", 0.552941203, 0.713725507, 0); + m_labelSet2_adapted->AddLabel(label, false); + label = GenerateLabel(4, "Label 4", 0.631372571, 0.792156875, 0.945098042); + m_labelSet2_adapted->AddLabel(label, false); + label = GenerateLabel(5, "Label 5", 0.639215708, 0.250980407, 0.725490212); + m_labelSet2_adapted->AddLabel(label, false); + m_labelSet2_adapted->SetLayer(1); + } + + void tearDown() override + { + m_labelSet1 = nullptr; + m_labelSet2 = nullptr; + m_labelSet2_adapted = nullptr; + } + + void TestRead3DLabelSetImage_Default() + { + auto testImages = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LegacyLabelSetTestImage3D.nrrd")); + + CPPUNIT_ASSERT_MESSAGE("Error reading label set image", testImages.size()==1); + + auto lsimage1 = dynamic_cast(testImages[0].GetPointer()); + + CPPUNIT_ASSERT_MESSAGE("Number of layers is not correct", lsimage1->GetNumberOfLayers() == 2); + CPPUNIT_ASSERT_MESSAGE("Error layer 0 is not equal", mitk::Equal(*m_labelSet1, *(lsimage1->GetLabelSet(0)), mitk::eps, true)); + CPPUNIT_ASSERT_MESSAGE("Error layer 1 is not equal", mitk::Equal(*m_labelSet2_adapted, *(lsimage1->GetLabelSet(1)), mitk::eps, true)); + + CPPUNIT_ASSERT_MESSAGE("Error, read image has different UID", "c236532b-f95a-4f22-a4c6-7abe4e41ad10"== lsimage1->GetUID()); + } + + void TestRead3DLabelSetImage_Adapt() + { + mitk::IFileReader::Options options = { {"Multi layer handling", us::Any(std::string("Adapt label values"))} }; + auto testImages = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LegacyLabelSetTestImage3D.nrrd"), options); + + CPPUNIT_ASSERT_MESSAGE("Error reading label set image", testImages.size() == 1); + + auto lsimage1 = dynamic_cast(testImages[0].GetPointer()); + + CPPUNIT_ASSERT_MESSAGE("Number of layers is not correct", lsimage1->GetNumberOfLayers() == 2); + CPPUNIT_ASSERT_MESSAGE("Error layer 0 is not equal", mitk::Equal(*m_labelSet1, *(lsimage1->GetLabelSet(0)), mitk::eps, true)); + CPPUNIT_ASSERT_MESSAGE("Error layer 1 is not equal", mitk::Equal(*m_labelSet2_adapted, *(lsimage1->GetLabelSet(1)), mitk::eps, true)); + + CPPUNIT_ASSERT_MESSAGE("Error, read image has different UID", "c236532b-f95a-4f22-a4c6-7abe4e41ad10" == lsimage1->GetUID()); + } + + void TestRead3DLabelSetImage_Split() + { + mitk::IFileReader::Options options = { {"Multi layer handling", us::Any(std::string("Split layers"))} }; + auto testImages = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LegacyLabelSetTestImage3D.nrrd"), options); + + CPPUNIT_ASSERT_MESSAGE("Error reading label set image", testImages.size() == 2); + + auto lsimage1 = dynamic_cast(testImages[0].GetPointer()); + auto lsimage2 = dynamic_cast(testImages[1].GetPointer()); + + CPPUNIT_ASSERT_MESSAGE("Number of layers in image 1 isnot correct", lsimage1->GetNumberOfLayers() == 1); + CPPUNIT_ASSERT_MESSAGE("Number of layers in image 2 is not correct", lsimage2->GetNumberOfLayers() == 1); + CPPUNIT_ASSERT_MESSAGE("Error layer 0 is not equal", mitk::Equal(*m_labelSet1, *(lsimage1->GetLabelSet(0)), mitk::eps, true)); + CPPUNIT_ASSERT_MESSAGE("Error layer 1 is not equal", mitk::Equal(*m_labelSet2, *(lsimage2->GetLabelSet(0)), mitk::eps, true)); + + CPPUNIT_ASSERT_MESSAGE("Error, read image has same UID", "c236532b-f95a-4f22-a4c6-7abe4e41ad10" != lsimage1->GetUID()); + + } + + void TestRead3DplusTLabelSetImage() + { + + } + +}; + +MITK_TEST_SUITE_REGISTRATION(mitkLegacyLabelSetImageIO) diff --git a/Modules/Multilabel/Testing/mitkTransferLabelTest.cpp b/Modules/Multilabel/Testing/mitkTransferLabelTest.cpp index 6df3b1d69a..be70fd897f 100644 --- a/Modules/Multilabel/Testing/mitkTransferLabelTest.cpp +++ b/Modules/Multilabel/Testing/mitkTransferLabelTest.cpp @@ -1,142 +1,229 @@ /*============================================================================ 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 class mitkTransferLabelTestSuite : public mitk::TestFixture { CPPUNIT_TEST_SUITE(mitkTransferLabelTestSuite); MITK_TEST(TestTransfer_defaults); MITK_TEST(TestTransfer_Merge_RegardLocks); MITK_TEST(TestTransfer_Merge_IgnoreLocks); MITK_TEST(TestTransfer_Replace_RegardLocks); MITK_TEST(TestTransfer_Replace_IgnoreLocks); MITK_TEST(TestTransfer_multipleLabels); + MITK_TEST(TestTransfer_Merge_RegardLocks_AtTimeStep); + MITK_TEST(TestTransfer_Merge_IgnoreLocks_AtTimeStep); + MITK_TEST(TestTransfer_Replace_RegardLocks_AtTimeStep); + MITK_TEST(TestTransfer_Replace_IgnoreLocks_AtTimeStep); + MITK_TEST(TestTransfer_multipleLabels_AtTimeStep); CPPUNIT_TEST_SUITE_END(); private: mitk::LabelSetImage::Pointer m_SourceImage; public: void setUp() override { m_SourceImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_source.nrrd")); } void tearDown() override { m_SourceImage = nullptr; } void TestTransfer_defaults() { auto destinationImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_destination.nrrd")); - auto destinationLockedExteriorImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_destination_lockedExterior.nrrd")); + auto destinationLockedUnlabeledImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_destination_lockedExterior.nrrd")); auto refmage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_result_replace_regardLocks.nrrd")); - auto refLockedExteriorImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_result_replace_regardLocks_lockedExterior.nrrd")); + auto refLockedUnlabeledImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_result_replace_regardLocks_lockedExterior.nrrd")); mitk::TransferLabelContent(m_SourceImage, destinationImage); - mitk::TransferLabelContent(m_SourceImage, destinationLockedExteriorImage); + mitk::TransferLabelContent(m_SourceImage, destinationLockedUnlabeledImage); CPPUNIT_ASSERT_MESSAGE("Transfer with default settings failed", mitk::Equal(*(destinationImage.GetPointer()), *(refmage.GetPointer()), mitk::eps, false)); CPPUNIT_ASSERT_MESSAGE("Transfer with default settings + exterior lock failed", - mitk::Equal(*(destinationLockedExteriorImage.GetPointer()), *(refLockedExteriorImage.GetPointer()), mitk::eps, false)); + mitk::Equal(*(destinationLockedUnlabeledImage.GetPointer()), *(refLockedUnlabeledImage.GetPointer()), mitk::eps, false)); } void TestTransfer_Merge_RegardLocks() { auto destinationImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_destination.nrrd")); - auto destinationLockedExteriorImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_destination_lockedExterior.nrrd")); + auto destinationLockedUnlabeledImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_destination_lockedExterior.nrrd")); auto refmage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_result_merge_regardLocks.nrrd")); - auto refLockedExteriorImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_result_merge_regardLocks_lockedExterior.nrrd")); + auto refLockedUnlabeledImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_result_merge_regardLocks_lockedExterior.nrrd")); mitk::TransferLabelContent(m_SourceImage, destinationImage, { {1,1} }, mitk::MultiLabelSegmentation::MergeStyle::Merge, mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks); - mitk::TransferLabelContent(m_SourceImage, destinationLockedExteriorImage, { {1,1} }, mitk::MultiLabelSegmentation::MergeStyle::Merge, mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks); + mitk::TransferLabelContent(m_SourceImage, destinationLockedUnlabeledImage, { {1,1} }, mitk::MultiLabelSegmentation::MergeStyle::Merge, mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks); CPPUNIT_ASSERT_MESSAGE("Transfer with merge + regardLocks settings failed", mitk::Equal(*(destinationImage.GetPointer()), *(refmage.GetPointer()), mitk::eps, false)); CPPUNIT_ASSERT_MESSAGE("Transfer with merge + regardLocks + exterior lock settings failed", - mitk::Equal(*(destinationLockedExteriorImage.GetPointer()), *(refLockedExteriorImage.GetPointer()), mitk::eps, false)); + mitk::Equal(*(destinationLockedUnlabeledImage.GetPointer()), *(refLockedUnlabeledImage.GetPointer()), mitk::eps, false)); } void TestTransfer_Merge_IgnoreLocks() { auto destinationImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_destination.nrrd")); - auto destinationLockedExteriorImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_destination_lockedExterior.nrrd")); + auto destinationLockedUnlabeledImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_destination_lockedExterior.nrrd")); auto refmage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_result_merge_ignoreLocks.nrrd")); - auto refLockedExteriorImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_result_merge_ignoreLocks_lockedExterior.nrrd")); + auto refLockedUnlabeledImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_result_merge_ignoreLocks_lockedExterior.nrrd")); mitk::TransferLabelContent(m_SourceImage, destinationImage, { {1,1} }, mitk::MultiLabelSegmentation::MergeStyle::Merge, mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks); - mitk::TransferLabelContent(m_SourceImage, destinationLockedExteriorImage, { {1,1} }, mitk::MultiLabelSegmentation::MergeStyle::Merge, mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks); + mitk::TransferLabelContent(m_SourceImage, destinationLockedUnlabeledImage, { {1,1} }, mitk::MultiLabelSegmentation::MergeStyle::Merge, mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks); CPPUNIT_ASSERT_MESSAGE("Transfer with merge + ignoreLocks settings failed", mitk::Equal(*(destinationImage.GetPointer()), *(refmage.GetPointer()), mitk::eps, false)); CPPUNIT_ASSERT_MESSAGE("Transfer with merge + ignoreLocks + exterior lock settings failed", - mitk::Equal(*(destinationLockedExteriorImage.GetPointer()), *(refLockedExteriorImage.GetPointer()), mitk::eps, false)); + mitk::Equal(*(destinationLockedUnlabeledImage.GetPointer()), *(refLockedUnlabeledImage.GetPointer()), mitk::eps, false)); } void TestTransfer_Replace_RegardLocks() { auto destinationImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_destination.nrrd")); - auto destinationLockedExteriorImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_destination_lockedExterior.nrrd")); + auto destinationLockedUnlabeledImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_destination_lockedExterior.nrrd")); auto refmage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_result_replace_regardLocks.nrrd")); - auto refLockedExteriorImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_result_replace_regardLocks_lockedExterior.nrrd")); + auto refLockedUnlabeledImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_result_replace_regardLocks_lockedExterior.nrrd")); mitk::TransferLabelContent(m_SourceImage, destinationImage, { {1,1} }, mitk::MultiLabelSegmentation::MergeStyle::Replace, mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks); - mitk::TransferLabelContent(m_SourceImage, destinationLockedExteriorImage, { {1,1} }, mitk::MultiLabelSegmentation::MergeStyle::Replace, mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks); + mitk::TransferLabelContent(m_SourceImage, destinationLockedUnlabeledImage, { {1,1} }, mitk::MultiLabelSegmentation::MergeStyle::Replace, mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks); CPPUNIT_ASSERT_MESSAGE("Transfer with replace + regardLocks settings failed", mitk::Equal(*(destinationImage.GetPointer()), *(refmage.GetPointer()), mitk::eps, false)); CPPUNIT_ASSERT_MESSAGE("Transfer with replace + regardLocks + exterior lock settings failed", - mitk::Equal(*(destinationLockedExteriorImage.GetPointer()), *(refLockedExteriorImage.GetPointer()), mitk::eps, false)); + mitk::Equal(*(destinationLockedUnlabeledImage.GetPointer()), *(refLockedUnlabeledImage.GetPointer()), mitk::eps, false)); } void TestTransfer_Replace_IgnoreLocks() { auto destinationImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_destination.nrrd")); - auto destinationLockedExteriorImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_destination_lockedExterior.nrrd")); + auto destinationLockedUnlabeledImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_destination_lockedExterior.nrrd")); auto refmage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_result_replace_ignoreLocks.nrrd")); - auto refLockedExteriorImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_result_replace_ignoreLocks_lockedExterior.nrrd")); + auto refLockedUnlabeledImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_result_replace_ignoreLocks_lockedExterior.nrrd")); mitk::TransferLabelContent(m_SourceImage, destinationImage, { {1,1} }, mitk::MultiLabelSegmentation::MergeStyle::Replace, mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks); - mitk::TransferLabelContent(m_SourceImage, destinationLockedExteriorImage, { {1,1} }, mitk::MultiLabelSegmentation::MergeStyle::Replace, mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks); + mitk::TransferLabelContent(m_SourceImage, destinationLockedUnlabeledImage, { {1,1} }, mitk::MultiLabelSegmentation::MergeStyle::Replace, mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks); CPPUNIT_ASSERT_MESSAGE("Transfer with replace + ignoreLocks settings failed", mitk::Equal(*(destinationImage.GetPointer()), *(refmage.GetPointer()), mitk::eps, false)); CPPUNIT_ASSERT_MESSAGE("Transfer with replace + ignoreLocks + exterior lock settings failed", - mitk::Equal(*(destinationLockedExteriorImage.GetPointer()), *(refLockedExteriorImage.GetPointer()), mitk::eps, false)); + mitk::Equal(*(destinationLockedUnlabeledImage.GetPointer()), *(refLockedUnlabeledImage.GetPointer()), mitk::eps, false)); } void TestTransfer_multipleLabels() { auto destinationImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_destination.nrrd")); - auto destinationLockedExteriorImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_destination_lockedExterior.nrrd")); + auto destinationLockedUnlabeledImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_destination_lockedExterior.nrrd")); auto refmage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_result_multipleLabels.nrrd")); - auto refLockedExteriorImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_result_multipleLabels_lockedExterior.nrrd")); + auto refLockedUnlabeledImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_result_multipleLabels_lockedExterior.nrrd")); mitk::TransferLabelContent(m_SourceImage, destinationImage, { {1,1}, {3,1}, {2,4}, {4,2} }, mitk::MultiLabelSegmentation::MergeStyle::Replace, mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks); - mitk::TransferLabelContent(m_SourceImage, destinationLockedExteriorImage, { {1,1}, {3,1}, {2,4}, {4,2} }, mitk::MultiLabelSegmentation::MergeStyle::Replace, mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks); + mitk::TransferLabelContent(m_SourceImage, destinationLockedUnlabeledImage, { {1,1}, {3,1}, {2,4}, {4,2} }, mitk::MultiLabelSegmentation::MergeStyle::Replace, mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks); CPPUNIT_ASSERT_MESSAGE("Transfer multiple labels (1->1, 3->1, 2->4, 4->2) with replace + ignoreLocks settings failed", mitk::Equal(*(destinationImage.GetPointer()), *(refmage.GetPointer()), mitk::eps, false)); CPPUNIT_ASSERT_MESSAGE("Transfer multiple labels (1->1, 3->1, 2->4, 4->2) with replace + ignoreLocks + exterior lock settings failed", - mitk::Equal(*(destinationLockedExteriorImage.GetPointer()), *(refLockedExteriorImage.GetPointer()), mitk::eps, false)); + mitk::Equal(*(destinationLockedUnlabeledImage.GetPointer()), *(refLockedUnlabeledImage.GetPointer()), mitk::eps, false)); } + void TestTransfer_Merge_RegardLocks_AtTimeStep() + { + auto destinationImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_destination.nrrd")); + auto destinationLockedUnlabeledImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_destination_lockedExterior.nrrd")); + auto refmage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_result_merge_regardLocks.nrrd")); + auto refLockedUnlabeledImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_result_merge_regardLocks_lockedExterior.nrrd")); + + mitk::TransferLabelContentAtTimeStep(m_SourceImage, destinationImage, 0, { {1,1} }, mitk::MultiLabelSegmentation::MergeStyle::Merge, mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks); + mitk::TransferLabelContentAtTimeStep(m_SourceImage, destinationLockedUnlabeledImage, 0, { {1,1} }, mitk::MultiLabelSegmentation::MergeStyle::Merge, mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks); + + CPPUNIT_ASSERT_MESSAGE("Transfer with merge + regardLocks settings failed", + mitk::Equal(*(destinationImage.GetPointer()), *(refmage.GetPointer()), mitk::eps, false)); + CPPUNIT_ASSERT_MESSAGE("Transfer with merge + regardLocks + exterior lock settings failed", + mitk::Equal(*(destinationLockedUnlabeledImage.GetPointer()), *(refLockedUnlabeledImage.GetPointer()), mitk::eps, false)); + } + + void TestTransfer_Merge_IgnoreLocks_AtTimeStep() + { + auto destinationImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_destination.nrrd")); + auto destinationLockedUnlabeledImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_destination_lockedExterior.nrrd")); + auto refmage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_result_merge_ignoreLocks.nrrd")); + auto refLockedUnlabeledImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_result_merge_ignoreLocks_lockedExterior.nrrd")); + + mitk::TransferLabelContentAtTimeStep(m_SourceImage, destinationImage, 0, { {1,1} }, mitk::MultiLabelSegmentation::MergeStyle::Merge, mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks); + mitk::TransferLabelContentAtTimeStep(m_SourceImage, destinationLockedUnlabeledImage, 0, { {1,1} }, mitk::MultiLabelSegmentation::MergeStyle::Merge, mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks); + + CPPUNIT_ASSERT_MESSAGE("Transfer with merge + ignoreLocks settings failed", + mitk::Equal(*(destinationImage.GetPointer()), *(refmage.GetPointer()), mitk::eps, false)); + CPPUNIT_ASSERT_MESSAGE("Transfer with merge + ignoreLocks + exterior lock settings failed", + mitk::Equal(*(destinationLockedUnlabeledImage.GetPointer()), *(refLockedUnlabeledImage.GetPointer()), mitk::eps, false)); + } + + void TestTransfer_Replace_RegardLocks_AtTimeStep() + { + auto destinationImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_destination.nrrd")); + auto destinationLockedUnlabeledImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_destination_lockedExterior.nrrd")); + auto refmage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_result_replace_regardLocks.nrrd")); + auto refLockedUnlabeledImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_result_replace_regardLocks_lockedExterior.nrrd")); + + mitk::TransferLabelContentAtTimeStep(m_SourceImage, destinationImage, 0, { {1,1} }, mitk::MultiLabelSegmentation::MergeStyle::Replace, mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks); + mitk::TransferLabelContentAtTimeStep(m_SourceImage, destinationLockedUnlabeledImage, 0, { {1,1} }, mitk::MultiLabelSegmentation::MergeStyle::Replace, mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks); + + CPPUNIT_ASSERT_MESSAGE("Transfer with replace + regardLocks settings failed", + mitk::Equal(*(destinationImage.GetPointer()), *(refmage.GetPointer()), mitk::eps, false)); + CPPUNIT_ASSERT_MESSAGE("Transfer with replace + regardLocks + exterior lock settings failed", + mitk::Equal(*(destinationLockedUnlabeledImage.GetPointer()), *(refLockedUnlabeledImage.GetPointer()), mitk::eps, false)); + } + + void TestTransfer_Replace_IgnoreLocks_AtTimeStep() + { + auto destinationImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_destination.nrrd")); + auto destinationLockedUnlabeledImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_destination_lockedExterior.nrrd")); + auto refmage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_result_replace_ignoreLocks.nrrd")); + auto refLockedUnlabeledImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_result_replace_ignoreLocks_lockedExterior.nrrd")); + + mitk::TransferLabelContentAtTimeStep(m_SourceImage, destinationImage, 0, { {1,1} }, mitk::MultiLabelSegmentation::MergeStyle::Replace, mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks); + mitk::TransferLabelContentAtTimeStep(m_SourceImage, destinationLockedUnlabeledImage, 0, { {1,1} }, mitk::MultiLabelSegmentation::MergeStyle::Replace, mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks); + + CPPUNIT_ASSERT_MESSAGE("Transfer with replace + ignoreLocks settings failed", + mitk::Equal(*(destinationImage.GetPointer()), *(refmage.GetPointer()), mitk::eps, false)); + CPPUNIT_ASSERT_MESSAGE("Transfer with replace + ignoreLocks + exterior lock settings failed", + mitk::Equal(*(destinationLockedUnlabeledImage.GetPointer()), *(refLockedUnlabeledImage.GetPointer()), mitk::eps, false)); + } + + + void TestTransfer_multipleLabels_AtTimeStep() + { + auto destinationImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_destination.nrrd")); + auto destinationLockedUnlabeledImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_destination_lockedExterior.nrrd")); + auto refmage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_result_multipleLabels.nrrd")); + auto refLockedUnlabeledImage = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelTransferTest_result_multipleLabels_lockedExterior.nrrd")); + + mitk::TransferLabelContentAtTimeStep(m_SourceImage, destinationImage, 0, { {1,1}, {3,1}, {2,4}, {4,2} }, mitk::MultiLabelSegmentation::MergeStyle::Replace, mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks); + mitk::TransferLabelContentAtTimeStep(m_SourceImage, destinationLockedUnlabeledImage, 0, { {1,1}, {3,1}, {2,4}, {4,2} }, mitk::MultiLabelSegmentation::MergeStyle::Replace, mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks); + + CPPUNIT_ASSERT_MESSAGE("Transfer multiple labels (1->1, 3->1, 2->4, 4->2) with replace + ignoreLocks settings failed", + mitk::Equal(*(destinationImage.GetPointer()), *(refmage.GetPointer()), mitk::eps, false)); + CPPUNIT_ASSERT_MESSAGE("Transfer multiple labels (1->1, 3->1, 2->4, 4->2) with replace + ignoreLocks + exterior lock settings failed", + mitk::Equal(*(destinationLockedUnlabeledImage.GetPointer()), *(refLockedUnlabeledImage.GetPointer()), mitk::eps, false)); + } + + }; MITK_TEST_SUITE_REGISTRATION(mitkTransferLabel) diff --git a/Modules/Multilabel/autoload/DICOMSegIO/mitkDICOMSegmentationIO.cpp b/Modules/Multilabel/autoload/DICOMSegIO/mitkDICOMSegmentationIO.cpp index 68fad097df..e10302229c 100644 --- a/Modules/Multilabel/autoload/DICOMSegIO/mitkDICOMSegmentationIO.cpp +++ b/Modules/Multilabel/autoload/DICOMSegIO/mitkDICOMSegmentationIO.cpp @@ -1,704 +1,704 @@ /*============================================================================ 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 __mitkDICOMSegmentationIO__cpp #define __mitkDICOMSegmentationIO__cpp #include "mitkDICOMSegmentationIO.h" #include "mitkDICOMSegIOMimeTypes.h" #include "mitkDICOMSegmentationConstants.h" #include #include #include #include #include #include #include #include // itk #include // dcmqi #include // us #include #include namespace mitk { DICOMSegmentationIO::DICOMSegmentationIO() : AbstractFileIO(LabelSetImage::GetStaticNameOfClass(), mitk::MitkDICOMSEGIOMimeTypes::DICOMSEG_MIMETYPE_NAME(), "DICOM Segmentation") { AbstractFileWriter::SetRanking(10); AbstractFileReader::SetRanking(10); this->RegisterService(); } std::vector DICOMSegmentationIO::GetDICOMTagsOfInterest() { std::vector result; result.emplace_back(DICOMSegmentationConstants::SEGMENT_SEQUENCE_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_NUMBER_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_LABEL_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_ALGORITHM_TYPE_PATH()); result.emplace_back(DICOMSegmentationConstants::ANATOMIC_REGION_SEQUENCE_PATH()); result.emplace_back(DICOMSegmentationConstants::ANATOMIC_REGION_CODE_VALUE_PATH()); result.emplace_back(DICOMSegmentationConstants::ANATOMIC_REGION_CODE_SCHEME_PATH()); result.emplace_back(DICOMSegmentationConstants::ANATOMIC_REGION_CODE_MEANING_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENTED_PROPERTY_CATEGORY_SEQUENCE_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_CATEGORY_CODE_VALUE_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_CATEGORY_CODE_SCHEME_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_CATEGORY_CODE_MEANING_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENTED_PROPERTY_TYPE_SEQUENCE_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_TYPE_CODE_VALUE_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_TYPE_CODE_SCHEME_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_TYPE_CODE_MEANING_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENTED_PROPERTY_MODIFIER_SEQUENCE_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_MODIFIER_CODE_VALUE_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_MODIFIER_CODE_SCHEME_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_MODIFIER_CODE_MEANING_PATH()); return result; } IFileIO::ConfidenceLevel DICOMSegmentationIO::GetWriterConfidenceLevel() const { if (AbstractFileIO::GetWriterConfidenceLevel() == Unsupported) return Unsupported; // Check if the input file is a segmentation const LabelSetImage *input = dynamic_cast(this->GetInput()); if (input) { if ((input->GetDimension() != 3)) { MITK_INFO << "DICOM segmentation writer is tested only with 3D images, sorry."; return Unsupported; } // Check if input file has dicom information for the referenced image (original DICOM image, e.g. CT) Still necessary, see write() mitk::StringLookupTableProperty::Pointer dicomFilesProp = dynamic_cast(input->GetProperty("referenceFiles").GetPointer()); if (dicomFilesProp.IsNotNull()) return Supported; } return Unsupported; } void DICOMSegmentationIO::Write() { ValidateOutputLocation(); mitk::LocaleSwitch localeSwitch("C"); LocalFile localFile(this); const std::string path = localFile.GetFileName(); auto input = dynamic_cast(this->GetInput()); if (input == nullptr) mitkThrow() << "Cannot write non-image data"; // Get DICOM information from referenced image vector> dcmDatasetsSourceImage; std::unique_ptr readFileFormat(new DcmFileFormat()); try { // TODO: Generate dcmdataset witk DICOM tags from property list; ATM the source are the filepaths from the // property list mitk::StringLookupTableProperty::Pointer filesProp = dynamic_cast(input->GetProperty("referenceFiles").GetPointer()); if (filesProp.IsNull()) { mitkThrow() << "No property with dicom file path."; return; } StringLookupTable filesLut = filesProp->GetValue(); const StringLookupTable::LookupTableType &lookUpTableMap = filesLut.GetLookupTable(); for (const auto &it : lookUpTableMap) { const char *fileName = (it.second).c_str(); if (readFileFormat->loadFile(fileName, EXS_Unknown).good()) { std::unique_ptr readDCMDataset(readFileFormat->getAndRemoveDataset()); dcmDatasetsSourceImage.push_back(std::move(readDCMDataset)); } } } catch (const std::exception &e) { MITK_ERROR << "An error occurred while getting the dicom informations: " << e.what() << endl; return; } // Iterate over all layers. For each a dcm file will be generated for (unsigned int layer = 0; layer < input->GetNumberOfLayers(); ++layer) { vector segmentations; try { // Hack: Remove the const attribute to switch between the layer images. Normally you could get the different // layer images by input->GetLayerImage(layer) mitk::LabelSetImage *mitkLayerImage = const_cast(input); mitkLayerImage->SetActiveLayer(layer); // Cast mitk layer image to itk ImageToItk::Pointer imageToItkFilter = ImageToItk::New(); imageToItkFilter->SetInput(mitkLayerImage); // Cast from original itk type to dcmqi input itk image type typedef itk::CastImageFilter castItkImageFilterType; castItkImageFilterType::Pointer castFilter = castItkImageFilterType::New(); castFilter->SetInput(imageToItkFilter->GetOutput()); castFilter->Update(); itkInternalImageType::Pointer itkLabelImage = castFilter->GetOutput(); itkLabelImage->DisconnectPipeline(); // Iterate over all labels. For each label a segmentation image will be created const LabelSet *labelSet = input->GetLabelSet(layer); auto labelIter = labelSet->IteratorConstBegin(); // Ignore background label ++labelIter; for (; labelIter != labelSet->IteratorConstEnd(); ++labelIter) { // Thresold over the image with the given label value itk::ThresholdImageFilter::Pointer thresholdFilter = itk::ThresholdImageFilter::New(); thresholdFilter->SetInput(itkLabelImage); thresholdFilter->ThresholdOutside(labelIter->first, labelIter->first); thresholdFilter->SetOutsideValue(0); thresholdFilter->Update(); itkInternalImageType::Pointer segmentImage = thresholdFilter->GetOutput(); segmentImage->DisconnectPipeline(); segmentations.push_back(segmentImage); } } catch (const itk::ExceptionObject &e) { MITK_ERROR << e.GetDescription() << endl; return; } // Create segmentation meta information const std::string tmpMetaInfoFile = this->CreateMetaDataJsonFile(layer); MITK_INFO << "Writing image: " << path << std::endl; try { //TODO is there a better way? Interface expects a vector of raw pointer. vector rawVecDataset; for (const auto& dcmDataSet : dcmDatasetsSourceImage) rawVecDataset.push_back(dcmDataSet.get()); // Convert itk segmentation images to dicom image std::unique_ptr converter = std::make_unique(); std::unique_ptr result(converter->itkimage2dcmSegmentation(rawVecDataset, segmentations, tmpMetaInfoFile, false)); // Write dicom file DcmFileFormat dcmFileFormat(result.get()); std::string filePath = path.substr(0, path.find_last_of(".")); // If there is more than one layer, we have to write more than 1 dicom file if (input->GetNumberOfLayers() != 1) filePath = filePath + std::to_string(layer) + ".dcm"; else filePath = filePath + ".dcm"; dcmFileFormat.saveFile(filePath.c_str(), EXS_LittleEndianExplicit); } catch (const std::exception &e) { MITK_ERROR << "An error occurred during writing the DICOM Seg: " << e.what() << endl; return; } } // Write a dcm file for the next layer } IFileIO::ConfidenceLevel DICOMSegmentationIO::GetReaderConfidenceLevel() const { if (AbstractFileIO::GetReaderConfidenceLevel() == Unsupported) return Unsupported; const std::string fileName = this->GetLocalFileName(); DcmFileFormat dcmFileFormat; OFCondition status = dcmFileFormat.loadFile(fileName.c_str()); if (status.bad()) return Unsupported; OFString modality; if (dcmFileFormat.getDataset()->findAndGetOFString(DCM_Modality, modality).good()) { if (modality.compare("SEG") == 0) return Supported; else return Unsupported; } return Unsupported; } std::vector DICOMSegmentationIO::DoRead() { mitk::LocaleSwitch localeSwitch("C"); LabelSetImage::Pointer labelSetImage; std::vector result; const std::string path = this->GetLocalFileName(); MITK_INFO << "loading " << path << std::endl; if (path.empty()) mitkThrow() << "Empty filename in mitk::ItkImageIO "; try { // Get the dcm data set from file path DcmFileFormat dcmFileFormat; OFCondition status = dcmFileFormat.loadFile(path.c_str()); if (status.bad()) mitkThrow() << "Can't read the input file!"; DcmDataset *dataSet = dcmFileFormat.getDataset(); if (dataSet == nullptr) mitkThrow() << "Can't read data from input file!"; //=============================== dcmqi part ==================================== // Read the DICOM SEG images (segItkImages) and DICOM tags (metaInfo) std::unique_ptr converter = std::make_unique(); pair, string> dcmqiOutput = converter->dcmSegmentation2itkimage(dataSet); map segItkImages = dcmqiOutput.first; dcmqi::JSONSegmentationMetaInformationHandler metaInfo(dcmqiOutput.second.c_str()); metaInfo.read(); MITK_INFO << "Input " << metaInfo.getJSONOutputAsString(); //=============================================================================== // Get the label information from segment attributes for each itk image vector>::const_iterator segmentIter = metaInfo.segmentsAttributesMappingList.begin(); // For each itk image add a layer to the LabelSetImage output for (auto &element : segItkImages) { // Get the labeled image and cast it to mitkImage typedef itk::CastImageFilter castItkImageFilterType; castItkImageFilterType::Pointer castFilter = castItkImageFilterType::New(); castFilter->SetInput(element.second); castFilter->Update(); Image::Pointer layerImage; CastToMitkImage(castFilter->GetOutput(), layerImage); // Get pixel value of the label itkInternalImageType::ValueType segValue = 1; typedef itk::ImageRegionIterator IteratorType; // Iterate over the image to find the pixel value of the label IteratorType iter(element.second, element.second->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { itkInputImageType::PixelType value = iter.Get(); - if (value != 0) + if (value != LabelSetImage::UnlabeledValue) { segValue = value; break; } ++iter; } // Get Segment information map map segmentMap = (*segmentIter); map::const_iterator segmentMapIter = (*segmentIter).begin(); dcmqi::SegmentAttributes *segmentAttribute = (*segmentMapIter).second; OFString labelName; if (segmentAttribute->getSegmentedPropertyTypeCodeSequence() != nullptr) { segmentAttribute->getSegmentedPropertyTypeCodeSequence()->getCodeMeaning(labelName); if (segmentAttribute->getSegmentedPropertyTypeModifierCodeSequence() != nullptr) { OFString modifier; segmentAttribute->getSegmentedPropertyTypeModifierCodeSequence()->getCodeMeaning(modifier); labelName.append(" (").append(modifier).append(")"); } } else { labelName = std::to_string(segmentAttribute->getLabelID()).c_str(); if (labelName.empty()) labelName = "Unnamed"; } float tmp[3] = { 0.0, 0.0, 0.0 }; if (segmentAttribute->getRecommendedDisplayRGBValue() != nullptr) { tmp[0] = segmentAttribute->getRecommendedDisplayRGBValue()[0] / 255.0; tmp[1] = segmentAttribute->getRecommendedDisplayRGBValue()[1] / 255.0; tmp[2] = segmentAttribute->getRecommendedDisplayRGBValue()[2] / 255.0; } Label *newLabel = nullptr; // If labelSetImage do not exists (first image) if (labelSetImage.IsNull()) { // Initialize the labelSetImage with the read image labelSetImage = LabelSetImage::New(); labelSetImage->InitializeByLabeledImage(layerImage); // Already a label was generated, so set the information to this newLabel = labelSetImage->GetActiveLabel(labelSetImage->GetActiveLayer()); newLabel->SetName(labelName.c_str()); newLabel->SetColor(Color(tmp)); newLabel->SetValue(segValue); } else { // Add a new layer to the labelSetImage. Background label is set automatically labelSetImage->AddLayer(layerImage); // Add new label newLabel = new Label; newLabel->SetName(labelName.c_str()); newLabel->SetColor(Color(tmp)); newLabel->SetValue(segValue); labelSetImage->GetLabelSet(labelSetImage->GetActiveLayer())->AddLabel(newLabel); } // Add some more label properties this->SetLabelProperties(newLabel, segmentAttribute); ++segmentIter; } labelSetImage->GetLabelSet()->SetAllLabelsVisible(true); // Add some general DICOM Segmentation properties mitk::IDICOMTagsOfInterest *toiSrv = DICOMIOHelper::GetTagsOfInterestService(); auto tagsOfInterest = toiSrv->GetTagsOfInterest(); DICOMTagPathList tagsOfInterestList; for (const auto &tag : tagsOfInterest) { tagsOfInterestList.push_back(tag.first); } mitk::DICOMDCMTKTagScanner::Pointer scanner = mitk::DICOMDCMTKTagScanner::New(); scanner->SetInputFiles({ GetInputLocation() }); scanner->AddTagPaths(tagsOfInterestList); scanner->Scan(); mitk::DICOMDatasetAccessingImageFrameList frames = scanner->GetFrameInfoList(); if (frames.empty()) { MITK_ERROR << "Error reading the DICOM Seg file" << std::endl; return result; } auto findings = DICOMIOHelper::ExtractPathsOfInterest(tagsOfInterestList, frames); DICOMIOHelper::SetProperties(labelSetImage, findings); // Set active layer to the first layer of the labelset image if (labelSetImage->GetNumberOfLayers() > 1 && labelSetImage->GetActiveLayer() != 0) labelSetImage->SetActiveLayer(0); } catch (const std::exception &e) { MITK_ERROR << "An error occurred while reading the DICOM Seg file: " << e.what(); return result; } catch (...) { MITK_ERROR << "An error occurred in dcmqi while reading the DICOM Seg file"; return result; } result.push_back(labelSetImage.GetPointer()); return result; } const std::string mitk::DICOMSegmentationIO::CreateMetaDataJsonFile(int layer) { const mitk::LabelSetImage *image = dynamic_cast(this->GetInput()); const std::string output; dcmqi::JSONSegmentationMetaInformationHandler handler; // 1. Metadata attributes that will be listed in the resulting DICOM SEG object std::string contentCreatorName; if (!image->GetPropertyList()->GetStringProperty(GeneratePropertyNameForDICOMTag(0x0070, 0x0084).c_str(), contentCreatorName)) contentCreatorName = "MITK"; handler.setContentCreatorName(contentCreatorName); std::string clinicalTrailSeriesId; if (!image->GetPropertyList()->GetStringProperty(GeneratePropertyNameForDICOMTag(0x0012, 0x0071).c_str(), clinicalTrailSeriesId)) clinicalTrailSeriesId = "Session 1"; handler.setClinicalTrialSeriesID(clinicalTrailSeriesId); std::string clinicalTrialTimePointID; if (!image->GetPropertyList()->GetStringProperty(GeneratePropertyNameForDICOMTag(0x0012, 0x0050).c_str(), clinicalTrialTimePointID)) clinicalTrialTimePointID = "0"; handler.setClinicalTrialTimePointID(clinicalTrialTimePointID); std::string clinicalTrialCoordinatingCenterName = ""; if (!image->GetPropertyList()->GetStringProperty(GeneratePropertyNameForDICOMTag(0x0012, 0x0060).c_str(), clinicalTrialCoordinatingCenterName)) clinicalTrialCoordinatingCenterName = "Unknown"; handler.setClinicalTrialCoordinatingCenterName(clinicalTrialCoordinatingCenterName); std::string seriesDescription; if (!image->GetPropertyList()->GetStringProperty("name", seriesDescription)) seriesDescription = "MITK Segmentation"; handler.setSeriesDescription(seriesDescription); handler.setSeriesNumber("0" + std::to_string(layer)); handler.setInstanceNumber("1"); handler.setBodyPartExamined(""); const LabelSet *labelSet = image->GetLabelSet(layer); auto labelIter = labelSet->IteratorConstBegin(); // Ignore background label ++labelIter; for (; labelIter != labelSet->IteratorConstEnd(); ++labelIter) { const Label *label = labelIter->second; if (label != nullptr) { TemporoSpatialStringProperty *segmentNumberProp = dynamic_cast(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_NUMBER_PATH()).c_str())); TemporoSpatialStringProperty *segmentLabelProp = dynamic_cast(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_LABEL_PATH()).c_str())); TemporoSpatialStringProperty *algorithmTypeProp = dynamic_cast(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_ALGORITHM_TYPE_PATH()).c_str())); TemporoSpatialStringProperty *segmentCategoryCodeValueProp = dynamic_cast(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_CATEGORY_CODE_VALUE_PATH()).c_str())); TemporoSpatialStringProperty *segmentCategoryCodeSchemeProp = dynamic_cast(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_CATEGORY_CODE_SCHEME_PATH()).c_str())); TemporoSpatialStringProperty *segmentCategoryCodeMeaningProp = dynamic_cast(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_CATEGORY_CODE_MEANING_PATH()).c_str())); TemporoSpatialStringProperty *segmentTypeCodeValueProp = dynamic_cast(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_TYPE_CODE_VALUE_PATH()).c_str())); TemporoSpatialStringProperty *segmentTypeCodeSchemeProp = dynamic_cast(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_TYPE_CODE_SCHEME_PATH()).c_str())); TemporoSpatialStringProperty *segmentTypeCodeMeaningProp = dynamic_cast(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_TYPE_CODE_MEANING_PATH()).c_str())); TemporoSpatialStringProperty *segmentModifierCodeValueProp = dynamic_cast(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_MODIFIER_CODE_VALUE_PATH()).c_str())); TemporoSpatialStringProperty *segmentModifierCodeSchemeProp = dynamic_cast(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_MODIFIER_CODE_SCHEME_PATH()).c_str())); TemporoSpatialStringProperty *segmentModifierCodeMeaningProp = dynamic_cast(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_MODIFIER_CODE_MEANING_PATH()).c_str())); dcmqi::SegmentAttributes *segmentAttribute = nullptr; if (segmentNumberProp->GetValue() == "") { MITK_ERROR << "Something went wrong with the label ID."; } else { int labelId = std::stoi(segmentNumberProp->GetValue()); segmentAttribute = handler.createAndGetNewSegment(labelId); } if (segmentAttribute != nullptr) { segmentAttribute->setSegmentDescription(segmentLabelProp->GetValueAsString()); segmentAttribute->setSegmentAlgorithmType(algorithmTypeProp->GetValueAsString()); segmentAttribute->setSegmentAlgorithmName("MITK Segmentation"); if (segmentCategoryCodeValueProp != nullptr && segmentCategoryCodeSchemeProp != nullptr && segmentCategoryCodeMeaningProp != nullptr) segmentAttribute->setSegmentedPropertyCategoryCodeSequence( segmentCategoryCodeValueProp->GetValueAsString(), segmentCategoryCodeSchemeProp->GetValueAsString(), segmentCategoryCodeMeaningProp->GetValueAsString()); else // some default values segmentAttribute->setSegmentedPropertyCategoryCodeSequence( "M-01000", "SRT", "Morphologically Altered Structure"); if (segmentTypeCodeValueProp != nullptr && segmentTypeCodeSchemeProp != nullptr && segmentTypeCodeMeaningProp != nullptr) { segmentAttribute->setSegmentedPropertyTypeCodeSequence(segmentTypeCodeValueProp->GetValueAsString(), segmentTypeCodeSchemeProp->GetValueAsString(), segmentTypeCodeMeaningProp->GetValueAsString()); handler.setBodyPartExamined(segmentTypeCodeMeaningProp->GetValueAsString()); } else { // some default values segmentAttribute->setSegmentedPropertyTypeCodeSequence("M-03000", "SRT", "Mass"); handler.setBodyPartExamined("Mass"); } if (segmentModifierCodeValueProp != nullptr && segmentModifierCodeSchemeProp != nullptr && segmentModifierCodeMeaningProp != nullptr) segmentAttribute->setSegmentedPropertyTypeModifierCodeSequence( segmentModifierCodeValueProp->GetValueAsString(), segmentModifierCodeSchemeProp->GetValueAsString(), segmentModifierCodeMeaningProp->GetValueAsString()); Color color = label->GetColor(); segmentAttribute->setRecommendedDisplayRGBValue(color[0] * 255, color[1] * 255, color[2] * 255); } } } return handler.getJSONOutputAsString(); } void mitk::DICOMSegmentationIO::SetLabelProperties(mitk::Label *label, dcmqi::SegmentAttributes *segmentAttribute) { // Segment Number:Identification number of the segment.The value of Segment Number(0062, 0004) shall be unique // within the Segmentation instance in which it is created label->SetProperty(DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_NUMBER_PATH()).c_str(), TemporoSpatialStringProperty::New(std::to_string(label->GetValue()))); // Segment Label: User-defined label identifying this segment. label->SetProperty(DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_LABEL_PATH()).c_str(), TemporoSpatialStringProperty::New(label->GetName())); // Segment Algorithm Type: Type of algorithm used to generate the segment. if (!segmentAttribute->getSegmentAlgorithmType().empty()) label->SetProperty(DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_ALGORITHM_TYPE_PATH()).c_str(), TemporoSpatialStringProperty::New(segmentAttribute->getSegmentAlgorithmType())); // Add Segmented Property Category Code Sequence tags auto categoryCodeSequence = segmentAttribute->getSegmentedPropertyCategoryCodeSequence(); if (categoryCodeSequence != nullptr) { OFString codeValue; // (0008,0100) Code Value categoryCodeSequence->getCodeValue(codeValue); label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_CATEGORY_CODE_VALUE_PATH()).c_str(), TemporoSpatialStringProperty::New(codeValue.c_str())); OFString codeScheme; // (0008,0102) Coding Scheme Designator categoryCodeSequence->getCodingSchemeDesignator(codeScheme); label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_CATEGORY_CODE_SCHEME_PATH()).c_str(), TemporoSpatialStringProperty::New(codeScheme.c_str())); OFString codeMeaning; // (0008,0104) Code Meaning categoryCodeSequence->getCodeMeaning(codeMeaning); label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_CATEGORY_CODE_MEANING_PATH()).c_str(), TemporoSpatialStringProperty::New(codeMeaning.c_str())); } // Add Segmented Property Type Code Sequence tags auto typeCodeSequence = segmentAttribute->getSegmentedPropertyTypeCodeSequence(); if (typeCodeSequence != nullptr) { OFString codeValue; // (0008,0100) Code Value typeCodeSequence->getCodeValue(codeValue); label->SetProperty(DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_TYPE_CODE_VALUE_PATH()).c_str(), TemporoSpatialStringProperty::New(codeValue.c_str())); OFString codeScheme; // (0008,0102) Coding Scheme Designator typeCodeSequence->getCodingSchemeDesignator(codeScheme); label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_TYPE_CODE_SCHEME_PATH()).c_str(), TemporoSpatialStringProperty::New(codeScheme.c_str())); OFString codeMeaning; // (0008,0104) Code Meaning typeCodeSequence->getCodeMeaning(codeMeaning); label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_TYPE_CODE_MEANING_PATH()).c_str(), TemporoSpatialStringProperty::New(codeMeaning.c_str())); } // Add Segmented Property Type Modifier Code Sequence tags auto modifierCodeSequence = segmentAttribute->getSegmentedPropertyTypeModifierCodeSequence(); if (modifierCodeSequence != nullptr) { OFString codeValue; // (0008,0100) Code Value modifierCodeSequence->getCodeValue(codeValue); label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_MODIFIER_CODE_VALUE_PATH()).c_str(), TemporoSpatialStringProperty::New(codeValue.c_str())); OFString codeScheme; // (0008,0102) Coding Scheme Designator modifierCodeSequence->getCodingSchemeDesignator(codeScheme); label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_MODIFIER_CODE_SCHEME_PATH()).c_str(), TemporoSpatialStringProperty::New(codeScheme.c_str())); OFString codeMeaning; // (0008,0104) Code Meaning modifierCodeSequence->getCodeMeaning(codeMeaning); label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_MODIFIER_CODE_MEANING_PATH()).c_str(), TemporoSpatialStringProperty::New(codeMeaning.c_str())); } // Add Atomic RegionSequence tags auto atomicRegionSequence = segmentAttribute->getAnatomicRegionSequence(); if (atomicRegionSequence != nullptr) { OFString codeValue; // (0008,0100) Code Value atomicRegionSequence->getCodeValue(codeValue); label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::ANATOMIC_REGION_CODE_VALUE_PATH()).c_str(), TemporoSpatialStringProperty::New(codeValue.c_str())); OFString codeScheme; // (0008,0102) Coding Scheme Designator atomicRegionSequence->getCodingSchemeDesignator(codeScheme); label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::ANATOMIC_REGION_CODE_SCHEME_PATH()).c_str(), TemporoSpatialStringProperty::New(codeScheme.c_str())); OFString codeMeaning; // (0008,0104) Code Meaning atomicRegionSequence->getCodeMeaning(codeMeaning); label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::ANATOMIC_REGION_CODE_MEANING_PATH()).c_str(), TemporoSpatialStringProperty::New(codeMeaning.c_str())); } } DICOMSegmentationIO *DICOMSegmentationIO::IOClone() const { return new DICOMSegmentationIO(*this); } } // namespace #endif //__mitkDICOMSegmentationIO__cpp diff --git a/Modules/Multilabel/autoload/IO/files.cmake b/Modules/Multilabel/autoload/IO/files.cmake index 71bd04bf86..09d98eca07 100644 --- a/Modules/Multilabel/autoload/IO/files.cmake +++ b/Modules/Multilabel/autoload/IO/files.cmake @@ -1,13 +1,15 @@ set(CPP_FILES - mitkLabelSetImageIO.cpp - mitkLabelSetImageIO.h - mitkLabelSetImageSerializer.cpp - mitkLabelSetImageSerializer.h + mitkLegacyLabelSetImageIO.cpp + mitkLegacyLabelSetImageIO.h + mitkMultiLabelSegmentationIO.cpp + mitkMultiLabelSegmentationIO.h + mitkMultiLabelSegmentationSerializer.cpp + mitkMultiLabelSegmentationSerializer.h mitkMultilabelActivator.cpp mitkMultilabelIOMimeTypes.cpp mitkMultilabelIOMimeTypes.h mitkSegmentationTaskListIO.cpp mitkSegmentationTaskListIO.h mitkSegmentationTaskListSerializer.cpp mitkSegmentationTaskListSerializer.h ) diff --git a/Modules/Multilabel/autoload/IO/mitkLabelSetImageIO.cpp b/Modules/Multilabel/autoload/IO/mitkLabelSetImageIO.cpp deleted file mode 100644 index 78d241cf19..0000000000 --- a/Modules/Multilabel/autoload/IO/mitkLabelSetImageIO.cpp +++ /dev/null @@ -1,657 +0,0 @@ -/*============================================================================ - -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 "mitkLabelSetImageIO.h" -#include "mitkBasePropertySerializer.h" -#include "mitkIOMimeTypes.h" -#include "mitkImageAccessByItk.h" -#include "mitkLabelSetIOHelper.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 -{ - - 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"; - - LabelSetImageIO::LabelSetImageIO() - : AbstractFileIO(LabelSetImage::GetStaticNameOfClass(), IOMimeTypes::NRRD_MIMETYPE(), "MITK Multilabel Image") - { - this->InitializeDefaultMetaDataKeys(); - AbstractFileWriter::SetRanking(10); - AbstractFileReader::SetRanking(10); - this->RegisterService(); - } - - IFileIO::ConfidenceLevel LabelSetImageIO::GetWriterConfidenceLevel() const - { - if (AbstractFileIO::GetWriterConfidenceLevel() == Unsupported) - return Unsupported; - const auto *input = static_cast(this->GetInput()); - if (input) - return Supported; - else - return Unsupported; - } - - void LabelSetImageIO::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); - } - - 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, "org.mitk.image.multilabel"); - itk::EncapsulateMetaData( - nrrdImageIo->GetMetaDataDictionary(), std::string(keybuffer), std::string(valbuffer)); - - sprintf(keybuffer, "layers"); - sprintf(valbuffer, "%1d", input->GetNumberOfLayers()); - itk::EncapsulateMetaData( - nrrdImageIo->GetMetaDataDictionary(), std::string(keybuffer), std::string(valbuffer)); - - 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::LabelSetIOHelper::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; - } - } - // 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); - } - - // 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); - } - - // Handle UID - itk::EncapsulateMetaData(nrrdImageIo->GetMetaDataDictionary(), PROPERTY_KEY_UID, input->GetUID()); - - ImageReadAccessor imageAccess(inputVector); - nrrdImageIo->Write(imageAccess.GetData()); - } - catch (const std::exception &e) - { - mitkThrow() << e.what(); - } - // end image write - } - - IFileIO::ConfidenceLevel LabelSetImageIO::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("org.mitk.image.multilabel") == 0) - { - return Supported; - } - else - return Unsupported; - } - - std::vector LabelSetImageIO::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(); - - 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. - nrrdImageIO->SetFileName(path); - nrrdImageIO->ReadImageInformation(); - - unsigned int ndim = nrrdImageIO->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] = 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; - } - - image->SetTimeGeometry(timeGeometry); - - buffer = nullptr; - MITK_INFO << "number of image components: " << image->GetPixelType().GetNumberOfComponents(); - - // 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++) - { - 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::LabelSetIOHelper::LoadLabelFromXMLDocument(labelElem); - - if (label->GetValue() == 0) // set exterior label is needed to hold exterior information - output->SetExteriorLabel(label); - labelSet->AddLabel(label); - labelSet->SetLayer(layerIdx); - } - output->AddLabelSetToLayer(layerIdx, labelSet); - } - - 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(), '_', '.'); - - 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); - } - } - } - - // 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()); - } - } - - MITK_INFO << "...finished!"; - - std::vector result; - result.push_back(output.GetPointer()); - return result; - } - - int LabelSetImageIO::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 LabelSetImageIO::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; - } - - LabelSetImageIO *LabelSetImageIO::IOClone() const { return new LabelSetImageIO(*this); } - - void LabelSetImageIO::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."); - } - -} // namespace - -#endif //__mitkLabelSetImageWriter__cpp diff --git a/Modules/Multilabel/autoload/IO/mitkLegacyLabelSetImageIO.cpp b/Modules/Multilabel/autoload/IO/mitkLegacyLabelSetImageIO.cpp new file mode 100644 index 0000000000..342097f104 --- /dev/null +++ b/Modules/Multilabel/autoload/IO/mitkLegacyLabelSetImageIO.cpp @@ -0,0 +1,272 @@ +/*============================================================================ + +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 +{ + + const constexpr char* const OPTION_NAME_MULTI_LAYER = "Multi layer handling"; + const constexpr char* const OPTION_NAME_MULTI_LAYER_ADAPT = "Adapt label values"; + const 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 = 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 = 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 = 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::UnlabeledValue) + { + 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(); + + std::vector groupImages = { rawimage }; + if (rawimage->GetChannelDescriptor().GetPixelType().GetPixelType() == itk::IOPixelEnum::VECTOR) + { + 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); + output->GetLabelSet(0)->SetLayer(0); + + //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::UnlabeledValue; + 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::UnlabeledValue != *vIter) + labelMapping.push_back({ *vIter, *vIter + maxValue }); + } + + + if (LabelSetImage::UnlabeledValue != 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::UnlabeledValue, LabelSetImage::UnlabeledValue, + 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::GroupIndexType 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; + } + + 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/mitkLabelSetImageIO.h b/Modules/Multilabel/autoload/IO/mitkLegacyLabelSetImageIO.h similarity index 66% copy from Modules/Multilabel/autoload/IO/mitkLabelSetImageIO.h copy to Modules/Multilabel/autoload/IO/mitkLegacyLabelSetImageIO.h index 4e443688ec..0a99aebd99 100644 --- a/Modules/Multilabel/autoload/IO/mitkLabelSetImageIO.h +++ b/Modules/Multilabel/autoload/IO/mitkLegacyLabelSetImageIO.h @@ -1,69 +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 mitkLabelSetImageIO_h -#define mitkLabelSetImageIO_h +#ifndef mitkLegacyLabelSetImageIO_h +#define mitkLegacyLabelSetImageIO_h -#include +#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 LabelSetImageIO : public mitk::AbstractFileIO + class LegacyLabelSetImageIO : public mitk::AbstractFileReader { public: typedef mitk::LabelSetImage InputType; - LabelSetImageIO(); + LegacyLabelSetImageIO(); // -------------- AbstractFileReader ------------- using AbstractFileReader::Read; - ConfidenceLevel GetReaderConfidenceLevel() const override; - - // -------------- AbstractFileWriter ------------- - - void Write() override; - ConfidenceLevel GetWriterConfidenceLevel() const override; - - // -------------- LabelSetImageIO specific functions ------------- - - int GetIntByKey(const itk::MetaDataDictionary &dic, const std::string &str); - std::string GetStringByKey(const itk::MetaDataDictionary &dic, const std::string &str); + ConfidenceLevel GetConfidenceLevel() const override; 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: - LabelSetImageIO *IOClone() const override; + 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 new file mode 100644 index 0000000000..b9e9fba8ea --- /dev/null +++ b/Modules/Multilabel/autoload/IO/mitkMultiLabelSegmentationIO.cpp @@ -0,0 +1,224 @@ +/*============================================================================ + +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 +{ + + const constexpr char* const MULTILABEL_SEGMENTATION_MODALITY_KEY = "modality"; + const constexpr char* const MULTILABEL_SEGMENTATION_MODALITY_VALUE = "org.mitk.multilabel.segmentation"; + const constexpr char* const MULTILABEL_SEGMENTATION_VERSION_KEY = "org.mitk.multilabel.segmentation.version"; + const constexpr int MULTILABEL_SEGMENTATION_VERSION_VALUE = 1; + const constexpr char* const MULTILABEL_SEGMENTATION_LABELS_INFO_KEY = "org.mitk.multilabel.segmentation.labelgroups"; + const constexpr char* const MULTILABEL_SEGMENTATION_UNLABELEDLABEL_LOCK_KEY = "org.mitk.multilabel.segmentation.unlabeledlabellock"; + + MultiLabelSegmentationIO::MultiLabelSegmentationIO() + : 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(); + + ItkImageIO::PreparImageIOToWriteImage(nrrdImageIo, inputVector); + + LocalFile localFile(this); + const std::string path = localFile.GetFileName(); + + MITK_INFO << "Writing image: " << path << std::endl; + + try + { + itk::EncapsulateMetaData( + nrrdImageIo->GetMetaDataDictionary(), std::string(MULTILABEL_SEGMENTATION_MODALITY_KEY), std::string(MULTILABEL_SEGMENTATION_MODALITY_VALUE)); + + //nrrd does only support string meta information. So we have to convert before. + itk::EncapsulateMetaData( + nrrdImageIo->GetMetaDataDictionary(), std::string(MULTILABEL_SEGMENTATION_VERSION_KEY), std::to_string(MULTILABEL_SEGMENTATION_VERSION_VALUE)); + + auto json = MultiLabelIOHelper::SerializeMultLabelGroupsToJSON(input); + itk::EncapsulateMetaData( + nrrdImageIo->GetMetaDataDictionary(), std::string(MULTILABEL_SEGMENTATION_LABELS_INFO_KEY), json.dump()); + // end label set specific meta data + + //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 + 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(); + } + } + + 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() + { + itk::NrrdImageIO::Pointer nrrdImageIO = itk::NrrdImageIO::New(); + + std::vector result; + + auto rawimage = ItkImageIO::LoadRawMitkImageFromImageIO(nrrdImageIO, this->GetLocalFileName()); + + const itk::MetaDataDictionary& dictionary = nrrdImageIO->GetMetaDataDictionary(); + + //check version + auto version = MultiLabelIOHelper::GetIntByKey(dictionary, MULTILABEL_SEGMENTATION_VERSION_KEY); + if (version > MULTILABEL_SEGMENTATION_VERSION_VALUE) + { + 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::DeserializeMultiLabelGroupsFromJSON(jlabelsets); + + if (labelsets.size() != output->GetNumberOfLayers()) + { + mitkThrow() << "Loaded data is in an invalid state. Number of extracted layer images and labels sets does not match. Found layer images: " << output->GetNumberOfLayers() << "; found labelsets: " << labelsets.size(); + } + + LabelSetImage::GroupIndexType id = 0; + for (auto labelset : labelsets) + { + output->AddLabelSetToLayer(id, labelset); + id++; + } + + 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())) + { + 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; + } + + 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("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/mitkLabelSetImageIO.h b/Modules/Multilabel/autoload/IO/mitkMultiLabelSegmentationIO.h similarity index 78% rename from Modules/Multilabel/autoload/IO/mitkLabelSetImageIO.h rename to Modules/Multilabel/autoload/IO/mitkMultiLabelSegmentationIO.h index 4e443688ec..b2a5757143 100644 --- a/Modules/Multilabel/autoload/IO/mitkLabelSetImageIO.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 mitkLabelSetImageIO_h -#define mitkLabelSetImageIO_h +#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 LabelSetImageIO : public mitk::AbstractFileIO + class MultiLabelSegmentationIO : public mitk::AbstractFileIO { public: typedef mitk::LabelSetImage InputType; - LabelSetImageIO(); + MultiLabelSegmentationIO(); // -------------- AbstractFileReader ------------- using AbstractFileReader::Read; ConfidenceLevel GetReaderConfidenceLevel() const override; // -------------- AbstractFileWriter ------------- void Write() override; ConfidenceLevel GetWriterConfidenceLevel() const override; - // -------------- LabelSetImageIO 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: - LabelSetImageIO *IOClone() const override; + MultiLabelSegmentationIO *IOClone() const override; std::vector m_DefaultMetaDataKeys; }; } // end of namespace mitk #endif diff --git a/Modules/Multilabel/autoload/IO/mitkLabelSetImageSerializer.cpp b/Modules/Multilabel/autoload/IO/mitkMultiLabelSegmentationSerializer.cpp similarity index 82% rename from Modules/Multilabel/autoload/IO/mitkLabelSetImageSerializer.cpp rename to Modules/Multilabel/autoload/IO/mitkMultiLabelSegmentationSerializer.cpp index 892c656dbd..9a05adcc3c 100644 --- a/Modules/Multilabel/autoload/IO/mitkLabelSetImageSerializer.cpp +++ b/Modules/Multilabel/autoload/IO/mitkMultiLabelSegmentationSerializer.cpp @@ -1,64 +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. ============================================================================*/ -#include "mitkLabelSetImageSerializer.h" +#include "mitkMultiLabelSegmentationSerializer.h" #include "mitkLabelSetImage.h" #include #include -MITK_REGISTER_SERIALIZER(LabelSetImageSerializer) +MITK_REGISTER_SERIALIZER(MultiLabelSegmentationSerializer) -mitk::LabelSetImageSerializer::LabelSetImageSerializer() +mitk::MultiLabelSegmentationSerializer::MultiLabelSegmentationSerializer() { } -mitk::LabelSetImageSerializer::~LabelSetImageSerializer() +mitk::MultiLabelSegmentationSerializer::~MultiLabelSegmentationSerializer() { } -std::string mitk::LabelSetImageSerializer::Serialize() +std::string mitk::MultiLabelSegmentationSerializer::Serialize() { const auto *image = dynamic_cast(m_Data.GetPointer()); if (image == nullptr) { MITK_ERROR << " Object at " << (const void *)this->m_Data << " is not an mitk::LabelSetImage. Cannot serialize as LabelSetImage."; return ""; } std::string filename(this->GetUniqueFilenameInWorkingDirectory()); filename += "_"; filename += m_FilenameHint; filename += ".nrrd"; std::string fullname(m_WorkingDirectory); fullname += "/"; fullname += itksys::SystemTools::ConvertToOutputPath(filename.c_str()); try { mitk::IOUtil::Save(image, fullname); // LabelSetImageWriter::Pointer writer = LabelSetImageWriter::New(); // writer->SetFileName(fullname); // writer->SetInput(const_cast(image)); // writer->Write(); } catch (std::exception &e) { MITK_ERROR << " Error serializing object at " << (const void *)this->m_Data << " to " << fullname << ": " << e.what(); return ""; } return filename; } diff --git a/Modules/Multilabel/autoload/IO/mitkLabelSetImageSerializer.h b/Modules/Multilabel/autoload/IO/mitkMultiLabelSegmentationSerializer.h similarity index 66% rename from Modules/Multilabel/autoload/IO/mitkLabelSetImageSerializer.h rename to Modules/Multilabel/autoload/IO/mitkMultiLabelSegmentationSerializer.h index 3b205dd51c..5c6fdab21d 100644 --- a/Modules/Multilabel/autoload/IO/mitkLabelSetImageSerializer.h +++ b/Modules/Multilabel/autoload/IO/mitkMultiLabelSegmentationSerializer.h @@ -1,37 +1,37 @@ /*============================================================================ 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 mitkLabelSetImageSerializer_h -#define mitkLabelSetImageSerializer_h +#ifndef mitkMultiLabelSegmentationSerializer_h +#define mitkMultiLabelSegmentationSerializer_h #include "mitkBaseDataSerializer.h" namespace mitk { /** \brief Serializes mitk::LabelSetImage for mitk::SceneIO */ - class LabelSetImageSerializer : public BaseDataSerializer + class MultiLabelSegmentationSerializer : public BaseDataSerializer { public: - mitkClassMacro(LabelSetImageSerializer, BaseDataSerializer); + mitkClassMacro(MultiLabelSegmentationSerializer, BaseDataSerializer); itkFactorylessNewMacro(Self); itkCloneMacro(Self); std::string Serialize() override; protected: - LabelSetImageSerializer(); - ~LabelSetImageSerializer() override; + MultiLabelSegmentationSerializer(); + ~MultiLabelSegmentationSerializer() override; }; } // namespace #endif diff --git a/Modules/Multilabel/autoload/IO/mitkMultilabelActivator.cpp b/Modules/Multilabel/autoload/IO/mitkMultilabelActivator.cpp index 58201f45c4..237c838341 100644 --- a/Modules/Multilabel/autoload/IO/mitkMultilabelActivator.cpp +++ b/Modules/Multilabel/autoload/IO/mitkMultilabelActivator.cpp @@ -1,55 +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. ============================================================================*/ #include #include #include #include -#include "mitkLabelSetImageIO.h" +#include "mitkLegacyLabelSetImageIO.h" +#include "mitkMultiLabelSegmentationIO.h" #include "mitkMultilabelIOMimeTypes.h" #include "mitkSegmentationTaskListIO.h" namespace mitk { /** \brief Registers services for multilabel module. */ class MultilabelIOModuleActivator : public us::ModuleActivator { std::vector m_FileIOs; + std::unique_ptr m_LegacyLabelSetImageIOReader; public: void Load(us::ModuleContext *context) override { auto mimeTypes = MitkMultilabelIOMimeTypes::Get(); us::ServiceProperties props; props[us::ServiceConstants::SERVICE_RANKING()] = 10; for (const auto &mimeType : mimeTypes) context->RegisterService(mimeType, props); - m_FileIOs.push_back(new LabelSetImageIO()); + m_LegacyLabelSetImageIOReader = std::make_unique(); + + m_FileIOs.push_back(new MultiLabelSegmentationIO()); m_FileIOs.push_back(new SegmentationTaskListIO); } void Unload(us::ModuleContext *) override { for (auto &elem : m_FileIOs) { delete elem; } } }; } US_EXPORT_MODULE_ACTIVATOR(mitk::MultilabelIOModuleActivator) diff --git a/Modules/Multilabel/autoload/IO/mitkMultilabelIOMimeTypes.cpp b/Modules/Multilabel/autoload/IO/mitkMultilabelIOMimeTypes.cpp index 231f8d4aed..aee9aa0727 100644 --- a/Modules/Multilabel/autoload/IO/mitkMultilabelIOMimeTypes.cpp +++ b/Modules/Multilabel/autoload/IO/mitkMultilabelIOMimeTypes.cpp @@ -1,75 +1,146 @@ /*============================================================================ 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 "mitkMultilabelIOMimeTypes.h" #include - +#include #include #include #include +#include +#include "itkMetaDataObject.h" + mitk::MitkMultilabelIOMimeTypes::MitkSegmentationTaskListMimeType::MitkSegmentationTaskListMimeType() : CustomMimeType(SEGMENTATIONTASKLIST_MIMETYPE_NAME()) { this->AddExtension("json"); this->SetCategory("MITK Segmentation Task List"); this->SetComment("MITK Segmentation Task List"); } bool mitk::MitkMultilabelIOMimeTypes::MitkSegmentationTaskListMimeType::AppliesTo(const std::string& path) const { bool result = CustomMimeType::AppliesTo(path); if (!std::filesystem::exists(path)) // T18572 return result; std::ifstream file(path); if (!file.is_open()) return false; auto json = nlohmann::json::parse(file, nullptr, false); if (json.is_discarded() || !json.is_object()) return false; if ("MITK Segmentation Task List" != json.value("FileFormat", "")) return false; if (1 != json.value("Version", 0)) return false; return true; } mitk::MitkMultilabelIOMimeTypes::MitkSegmentationTaskListMimeType* mitk::MitkMultilabelIOMimeTypes::MitkSegmentationTaskListMimeType::Clone() const { return new MitkSegmentationTaskListMimeType(*this); } mitk::MitkMultilabelIOMimeTypes::MitkSegmentationTaskListMimeType mitk::MitkMultilabelIOMimeTypes::SEGMENTATIONTASKLIST_MIMETYPE() { return MitkSegmentationTaskListMimeType(); } std::string mitk::MitkMultilabelIOMimeTypes::SEGMENTATIONTASKLIST_MIMETYPE_NAME() { return IOMimeTypes::DEFAULT_BASE_NAME() + ".segmentationtasklist"; } +mitk::MitkMultilabelIOMimeTypes::LegacyLabelSetMimeType::LegacyLabelSetMimeType() + : CustomMimeType(LEGACYLABELSET_MIMETYPE_NAME()) +{ + this->AddExtension("nrrd"); + this->SetCategory("MITK LabelSetImage"); + this->SetComment("MITK LabelSetImage (legacy format)"); +} + +bool mitk::MitkMultilabelIOMimeTypes::LegacyLabelSetMimeType::AppliesTo(const std::string& path) const +{ + bool canRead = CustomMimeType::AppliesTo(path); + + if (!std::filesystem::exists(path)) // T18572 + return canRead; + + if (!canRead) + { + return false; + } + + std::string value(""); + try + { + std::ifstream file(path); + + if (!file.is_open()) + return false; + + itk::NrrdImageIO::Pointer io = itk::NrrdImageIO::New(); + io->SetFileName(path); + io->ReadImageInformation(); + + itk::MetaDataDictionary imgMetaDataDictionary = io->GetMetaDataDictionary(); + itk::ExposeMetaData(imgMetaDataDictionary, "modality", value); + } + catch(const std::exception& e) + { + MITK_DEBUG << "Error while try to anylize NRRD file for LegacyLabelSetMimeType. File: " << path <<"; Error: " << e.what(); + } + catch(...) + { + MITK_DEBUG << "Unkown error while try to anylize NRRD file for LegacyLabelSetMimeType. File: " << path; + } + + if (value.compare("org.mitk.image.multilabel") != 0) + { + return false; + } + + return true; +} + +mitk::MitkMultilabelIOMimeTypes::LegacyLabelSetMimeType* mitk::MitkMultilabelIOMimeTypes::LegacyLabelSetMimeType::Clone() const +{ + return new LegacyLabelSetMimeType(*this); +} + +mitk::MitkMultilabelIOMimeTypes::LegacyLabelSetMimeType mitk::MitkMultilabelIOMimeTypes::LEGACYLABELSET_MIMETYPE() +{ + return LegacyLabelSetMimeType(); +} + +std::string mitk::MitkMultilabelIOMimeTypes::LEGACYLABELSET_MIMETYPE_NAME() +{ + return IOMimeTypes::DEFAULT_BASE_NAME() + ".legacylabelsetimage"; +} + std::vector mitk::MitkMultilabelIOMimeTypes::Get() { std::vector mimeTypes; mimeTypes.push_back(SEGMENTATIONTASKLIST_MIMETYPE().Clone()); + mimeTypes.push_back(LEGACYLABELSET_MIMETYPE().Clone()); return mimeTypes; } diff --git a/Modules/Multilabel/autoload/IO/mitkMultilabelIOMimeTypes.h b/Modules/Multilabel/autoload/IO/mitkMultilabelIOMimeTypes.h index 7343895f13..1f92d4692a 100644 --- a/Modules/Multilabel/autoload/IO/mitkMultilabelIOMimeTypes.h +++ b/Modules/Multilabel/autoload/IO/mitkMultilabelIOMimeTypes.h @@ -1,39 +1,51 @@ /*============================================================================ 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 mitkMultilabelIOMimeTypes_h #define mitkMultilabelIOMimeTypes_h #include #include namespace mitk { namespace MitkMultilabelIOMimeTypes { class MITKMULTILABELIO_EXPORT MitkSegmentationTaskListMimeType : public CustomMimeType { public: MitkSegmentationTaskListMimeType(); bool AppliesTo(const std::string& path) const override; MitkSegmentationTaskListMimeType* Clone() const override; }; MITKMULTILABELIO_EXPORT MitkSegmentationTaskListMimeType SEGMENTATIONTASKLIST_MIMETYPE(); MITKMULTILABELIO_EXPORT std::string SEGMENTATIONTASKLIST_MIMETYPE_NAME(); + class MITKMULTILABELIO_EXPORT LegacyLabelSetMimeType : public CustomMimeType + { + public: + LegacyLabelSetMimeType(); + + bool AppliesTo(const std::string& path) const override; + LegacyLabelSetMimeType* Clone() const override; + }; + + MITKMULTILABELIO_EXPORT LegacyLabelSetMimeType LEGACYLABELSET_MIMETYPE(); + MITKMULTILABELIO_EXPORT std::string LEGACYLABELSET_MIMETYPE_NAME(); + MITKMULTILABELIO_EXPORT std::vector Get(); } } #endif diff --git a/Modules/Multilabel/files.cmake b/Modules/Multilabel/files.cmake index 0524d4f98c..8723b71a3b 100644 --- a/Modules/Multilabel/files.cmake +++ b/Modules/Multilabel/files.cmake @@ -1,21 +1,21 @@ set(CPP_FILES mitkLabel.cpp mitkLabelSet.cpp mitkLabelSetImage.cpp mitkLabelSetImageConverter.cpp mitkLabelSetImageSource.cpp mitkLabelSetImageHelper.cpp mitkLabelSetImageSurfaceStampFilter.cpp mitkLabelSetImageToSurfaceFilter.cpp mitkLabelSetImageToSurfaceThreadedFilter.cpp mitkLabelSetImageVtkMapper2D.cpp mitkMultilabelObjectFactory.cpp - mitkLabelSetIOHelper.cpp + mitkMultiLabelIOHelper.cpp mitkDICOMSegmentationPropertyHelper.cpp mitkDICOMSegmentationConstants.cpp mitkSegmentationTaskList.cpp ) set(RESOURCE_FILES ) diff --git a/Modules/Multilabel/mitkLabel.cpp b/Modules/Multilabel/mitkLabel.cpp index f2b6df44c5..6972b9f126 100644 --- a/Modules/Multilabel/mitkLabel.cpp +++ b/Modules/Multilabel/mitkLabel.cpp @@ -1,298 +1,304 @@ /*============================================================================ 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 "mitkLabel.h" #include "itkProcessObject.h" #include #include #include #include const mitk::Label::PixelType mitk::Label::MAX_LABEL_VALUE = std::numeric_limits::max(); mitk::Label::Label() : PropertyList() { if (GetProperty("locked") == nullptr) SetLocked(true); if (GetProperty("visible") == nullptr) SetVisible(true); if (GetProperty("opacity") == nullptr) SetOpacity(0.6); if (GetProperty("center.coordinates") == nullptr) { mitk::Point3D pnt; pnt.SetElement(0, 0); pnt.SetElement(1, 0); pnt.SetElement(2, 0); SetCenterOfMassCoordinates(pnt); } if (GetProperty("center.index") == nullptr) { mitk::Point3D pnt; pnt.SetElement(0, 0); pnt.SetElement(1, 0); pnt.SetElement(2, 0); SetCenterOfMassIndex(pnt); } if (GetProperty("color") == nullptr) { mitk::Color col; col.Set(0, 0, 0); SetColor(col); } if (GetProperty("name") == nullptr) SetName("noName!"); if (GetProperty("value") == nullptr) SetValue(0); if (GetProperty("layer") == nullptr) SetLayer(0); DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(this); } +mitk::Label::Label(PixelType value, const std::string& name) : Label() +{ + this->SetValue(value); + this->SetName(name); +} + mitk::Label::Label(const Label &other) : PropertyList(other) // copyconstructer of property List handles the coping action { auto *map = this->GetMap(); auto it = map->begin(); auto end = map->end(); for (; it != end; ++it) { itk::SimpleMemberCommand