diff --git a/Modules/Core/include/mitkItkImageIO.h b/Modules/Core/include/mitkItkImageIO.h index 77cc90fb26..f64356551f 100644 --- a/Modules/Core/include/mitkItkImageIO.h +++ b/Modules/Core/include/mitkItkImageIO.h @@ -1,101 +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.*/ + 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/MatchPointRegistration/src/Helper/mitkImageMappingHelper.cpp b/Modules/MatchPointRegistration/src/Helper/mitkImageMappingHelper.cpp index e60a0e1730..1721cdf683 100644 --- a/Modules/MatchPointRegistration/src/Helper/mitkImageMappingHelper.cpp +++ b/Modules/MatchPointRegistration/src/Helper/mitkImageMappingHelper.cpp @@ -1,444 +1,444 @@ /*============================================================================ 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 #include #include #include #include "mapRegistration.h" #include "mitkImageMappingHelper.h" #include "mitkRegistrationHelper.h" template typename ::itk::InterpolateImageFunction< TImage >::Pointer generateInterpolator(mitk::ImageMappingInterpolator::Type interpolatorType) { typedef ::itk::InterpolateImageFunction< TImage > BaseInterpolatorType; typename BaseInterpolatorType::Pointer result; switch (interpolatorType) { case mitk::ImageMappingInterpolator::NearestNeighbor: { result = ::itk::NearestNeighborInterpolateImageFunction::New(); break; } case mitk::ImageMappingInterpolator::BSpline_3: { typename ::itk::BSplineInterpolateImageFunction::Pointer spInterpolator = ::itk::BSplineInterpolateImageFunction::New(); spInterpolator->SetSplineOrder(3); result = spInterpolator; break; } case mitk::ImageMappingInterpolator::WSinc_Hamming: { result = ::itk::WindowedSincInterpolateImageFunction::New(); break; } case mitk::ImageMappingInterpolator::WSinc_Welch: { result = ::itk::WindowedSincInterpolateImageFunction >::New(); break; } default: { result = ::itk::LinearInterpolateImageFunction::New(); break; } } return result; }; template void doMITKMap(const ::itk::Image* input, mitk::ImageMappingHelper::ResultImageType::Pointer& result, const mitk::ImageMappingHelper::RegistrationType*& registration, bool throwOnOutOfInputAreaError, const double& paddingValue, const mitk::ImageMappingHelper::ResultImageGeometryType*& resultGeometry, bool throwOnMappingError, const double& errorValue, mitk::ImageMappingInterpolator::Type interpolatorType) { typedef ::map::core::Registration ConcreteRegistrationType; typedef ::map::core::ImageMappingTask, ::itk::Image > MappingTaskType; typename MappingTaskType::Pointer spTask = MappingTaskType::New(); typedef typename MappingTaskType::ResultImageDescriptorType ResultImageDescriptorType; typename ResultImageDescriptorType::Pointer resultDescriptor; //check if image and result geometry fits the passed registration ///////////////////////////////////////////////////////////////// if (registration->getMovingDimensions()!=VImageDimension) { map::core::OStringStream str; str << "Dimension of MITK image ("<getMovingDimensions()<<")."; throw mitk::AccessByItkException(str.str()); } if (registration->getTargetDimensions()!=VImageDimension) { map::core::OStringStream str; str << "Dimension of MITK image ("<getTargetDimensions()<<")."; throw mitk::AccessByItkException(str.str()); } const ConcreteRegistrationType* castedReg = dynamic_cast(registration); if (registration->getTargetDimensions()==2 && resultGeometry) { mitk::ImageMappingHelper::ResultImageGeometryType::BoundsArrayType bounds = resultGeometry->GetBounds(); if (bounds[4]!=0 || bounds[5]!=0) { //array "bounds" is constructed as [min Dim1, max Dim1, min Dim2, max Dim2, min Dim3, max Dim3] //therfore [4] and [5] must be 0 map::core::OStringStream str; str << "Dimension of defined result geometry does not equal the target dimension of the registration object ("<getTargetDimensions()<<")."; throw mitk::AccessByItkException(str.str()); } } //check/create resultDescriptor ///////////////////////// if (resultGeometry) { resultDescriptor = ResultImageDescriptorType::New(); typename ResultImageDescriptorType::PointType origin; typename ResultImageDescriptorType::SizeType size; typename ResultImageDescriptorType::SpacingType fieldSpacing; typename ResultImageDescriptorType::DirectionType matrix; mitk::ImageMappingHelper::ResultImageGeometryType::BoundsArrayType geoBounds = resultGeometry->GetBounds(); mitk::Vector3D geoSpacing = resultGeometry->GetSpacing(); mitk::Point3D geoOrigin = resultGeometry->GetOrigin(); mitk::AffineTransform3D::MatrixType geoMatrix = resultGeometry->GetIndexToWorldTransform()->GetMatrix(); for (unsigned int i = 0; i(geoOrigin[i]); fieldSpacing[i] = static_cast(geoSpacing[i]); size[i] = static_cast(geoBounds[(2*i)+1]-geoBounds[2*i])*fieldSpacing[i]; } //Matrix extraction matrix.SetIdentity(); unsigned int i; unsigned int j; /// \warning 2D MITK images could have a 3D rotation, since they have a 3x3 geometry matrix. /// If it is only a rotation around the transversal plane normal, it can be express with a 2x2 matrix. /// In this case, the ITK image conservs this information and is identical to the MITK image! /// If the MITK image contains any other rotation, the ITK image will have no rotation at all. /// Spacing is of course conserved in both cases. // the following loop devides by spacing now to normalize columns. // counterpart of InitializeByItk in mitkImage.h line 372 of revision 15092. // Check if information is lost if ( VImageDimension == 2) { if ( ( geoMatrix[0][2] != 0) || ( geoMatrix[1][2] != 0) || ( geoMatrix[2][0] != 0) || ( geoMatrix[2][1] != 0) || (( geoMatrix[2][2] != 1) && ( geoMatrix[2][2] != -1) )) { // The 2D MITK image contains 3D rotation information. // This cannot be expressed in a 2D ITK image, so the ITK image will have no rotation } else { // The 2D MITK image can be converted to an 2D ITK image without information loss! for ( i=0; i < 2; ++i) { for( j=0; j < 2; ++j ) { matrix[i][j] = geoMatrix[i][j]/fieldSpacing[j]; } } } } else if (VImageDimension == 3) { // Normal 3D image. Conversion possible without problem! for ( i=0; i < 3; ++i) { for( j=0; j < 3; ++j ) { matrix[i][j] = geoMatrix[i][j]/fieldSpacing[j]; } } } else { assert(0); throw mitk::AccessByItkException("Usage of resultGeometry for 2D images is not yet implemented."); /**@TODO Implement extraction of 2D-Rotation-Matrix out of 3D-Rotation-Matrix * to cover this case as well. * matrix = extract2DRotationMatrix(resultGeometry)*/ } resultDescriptor->setOrigin(origin); resultDescriptor->setSize(size); resultDescriptor->setSpacing(fieldSpacing); resultDescriptor->setDirection(matrix); } //do the mapping ///////////////////////// typedef ::itk::InterpolateImageFunction< ::itk::Image > BaseInterpolatorType; typename BaseInterpolatorType::Pointer interpolator = generateInterpolator< ::itk::Image >(interpolatorType); assert(interpolator.IsNotNull()); spTask->setImageInterpolator(interpolator); spTask->setInputImage(input); spTask->setRegistration(castedReg); spTask->setResultImageDescriptor(resultDescriptor); spTask->setThrowOnMappingError(throwOnMappingError); spTask->setErrorValue(errorValue); spTask->setThrowOnPaddingError(throwOnOutOfInputAreaError); spTask->setPaddingValue(paddingValue); spTask->execute(); mitk::CastToMitkImage<>(spTask->getResultImage(),result); } /**Helper function to ensure the mapping of all time steps of an image.*/ void doMapTimesteps(const mitk::ImageMappingHelper::InputImageType* input, mitk::Image* result, const mitk::ImageMappingHelper::RegistrationType* registration, bool throwOnOutOfInputAreaError,double paddingValue, const mitk::ImageMappingHelper::ResultImageGeometryType* resultGeometry, bool throwOnMappingError, double errorValue, mitk::ImageMappingInterpolator::Type interpolatorType) { for (unsigned int i = 0; iGetTimeSteps(); ++i) { mitk::ImageTimeSelector::Pointer imageTimeSelector = mitk::ImageTimeSelector::New(); imageTimeSelector->SetInput(input); imageTimeSelector->SetTimeNr(i); imageTimeSelector->UpdateLargestPossibleRegion(); mitk::ImageMappingHelper::InputImageType::Pointer timeStepInput = imageTimeSelector->GetOutput(); mitk::ImageMappingHelper::ResultImageType::Pointer timeStepResult; AccessByItk_n(timeStepInput, doMITKMap, (timeStepResult, registration, throwOnOutOfInputAreaError, paddingValue, resultGeometry, throwOnMappingError, errorValue, interpolatorType)); mitk::ImageReadAccessor readAccess(timeStepResult); result->SetVolume(readAccess.GetData(), i); } } mitk::TimeGeometry::Pointer CreateResultTimeGeometry(const mitk::ImageMappingHelper::InputImageType* input, const mitk::ImageMappingHelper::ResultImageGeometryType* resultGeometry) { mitk::TimeGeometry::ConstPointer timeGeometry = input->GetTimeGeometry(); mitk::TimeGeometry::Pointer mappedTimeGeometry = timeGeometry->Clone(); for (unsigned int i = 0; i < input->GetTimeSteps(); ++i) { mitk::ImageMappingHelper::ResultImageGeometryType::Pointer mappedGeometry = resultGeometry->Clone(); mappedTimeGeometry->SetTimeStepGeometry(mappedGeometry, i); } return mappedTimeGeometry; } mitk::ImageMappingHelper::ResultImageType::Pointer mitk::ImageMappingHelper::map(const InputImageType* input, const RegistrationType* registration, bool throwOnOutOfInputAreaError, const double& paddingValue, const ResultImageGeometryType* resultGeometry, bool throwOnMappingError, const double& errorValue, mitk::ImageMappingInterpolator::Type interpolatorType) { if (!registration) { mitkThrow() << "Cannot map image. Passed registration wrapper pointer is nullptr."; } if (!input) { mitkThrow() << "Cannot map image. Passed image pointer is nullptr."; } ResultImageType::Pointer result; auto inputLabelSetImage = dynamic_cast(input); if (nullptr == inputLabelSetImage) { if (input->GetTimeSteps() == 1) { //map the image and done AccessByItk_n(input, doMITKMap, (result, registration, throwOnOutOfInputAreaError, paddingValue, resultGeometry, throwOnMappingError, errorValue, interpolatorType)); } else { //map every time step and compose auto mappedTimeGeometry = CreateResultTimeGeometry(input, resultGeometry); result = mitk::Image::New(); result->Initialize(input->GetPixelType(), *mappedTimeGeometry, 1, input->GetTimeSteps()); doMapTimesteps(input, result, registration, throwOnOutOfInputAreaError, paddingValue, resultGeometry, throwOnMappingError, errorValue, interpolatorType); } } else { auto resultLabelSetImage = LabelSetImage::New(); auto mappedTimeGeometry = CreateResultTimeGeometry(input, resultGeometry); auto resultTemplate = mitk::Image::New(); resultTemplate->Initialize(input->GetPixelType(), *mappedTimeGeometry, 1, input->GetTimeSteps()); resultLabelSetImage->Initialize(resultTemplate); auto cloneInput = inputLabelSetImage->Clone(); //We need to clone the LabelSetImage due to its illposed design. It is state full //and we have to iterate through all layers as active layers to ensure the content //was really stored (directly working with the layer images does not work with the //active layer). The clone wastes rescources but is the easiest and safest way to //ensure 1) correct mapping 2) avoid race conditions with other parts of the //application because we would change the state of the input. //This whole code block should be reworked as soon as T28525 is done. for (unsigned int layerID = 0; layerID < inputLabelSetImage->GetNumberOfLayers(); ++layerID) { if (resultLabelSetImage->GetNumberOfLayers() <= layerID) { resultLabelSetImage->AddLayer(); } - resultLabelSetImage->AddLabelSetToLayer(layerID, inputLabelSetImage->GetLabelSet(layerID)); + resultLabelSetImage->AddLabelSetToLayer(layerID, inputLabelSetImage->GetLabelSet(layerID)->Clone()); cloneInput->SetActiveLayer(layerID); resultLabelSetImage->SetActiveLayer(layerID); doMapTimesteps(cloneInput, resultLabelSetImage, registration, throwOnOutOfInputAreaError, paddingValue, resultGeometry, throwOnMappingError, errorValue, mitk::ImageMappingInterpolator::Linear); } resultLabelSetImage->SetActiveLayer(inputLabelSetImage->GetActiveLayer()); resultLabelSetImage->GetLabelSet(inputLabelSetImage->GetActiveLayer())->SetActiveLabel(inputLabelSetImage->GetActiveLabel(inputLabelSetImage->GetActiveLayer())->GetValue()); result = resultLabelSetImage; } return result; } mitk::ImageMappingHelper::ResultImageType::Pointer mitk::ImageMappingHelper::map(const InputImageType* input, const MITKRegistrationType* registration, bool throwOnOutOfInputAreaError, const double& paddingValue, const ResultImageGeometryType* resultGeometry, bool throwOnMappingError, const double& errorValue, mitk::ImageMappingInterpolator::Type) { if (!registration) { mitkThrow() << "Cannot map image. Passed registration wrapper pointer is nullptr."; } if (!registration->GetRegistration()) { mitkThrow() << "Cannot map image. Passed registration wrapper containes no registration."; } if (!input) { mitkThrow() << "Cannot map image. Passed image pointer is nullptr."; } ResultImageType::Pointer result = map(input, registration->GetRegistration(), throwOnOutOfInputAreaError, paddingValue, resultGeometry, throwOnMappingError, errorValue); return result; } mitk::ImageMappingHelper::ResultImageType::Pointer mitk::ImageMappingHelper:: refineGeometry(const InputImageType* input, const RegistrationType* registration, bool throwOnError) { mitk::ImageMappingHelper::ResultImageType::Pointer result = nullptr; if (!registration) { mitkThrow() << "Cannot refine image geometry. Passed registration pointer is nullptr."; } if (!input) { mitkThrow() << "Cannot refine image geometry. Passed image pointer is nullptr."; } mitk::MITKRegistrationHelper::Affine3DTransformType::Pointer spTransform = mitk::MITKRegistrationHelper::getAffineMatrix(registration,false); if(spTransform.IsNull() && throwOnError) { mitkThrow() << "Cannot refine image geometry. Registration does not contain a suitable direct mapping kernel (3D affine transformation or compatible required)."; } if(spTransform.IsNotNull()) { //copy input image result = input->Clone(); //refine geometries for(unsigned int i = 0; i < result->GetTimeSteps(); ++i) { //refine every time step result->GetGeometry(i)->Compose(spTransform); } result->GetTimeGeometry()->Update(); } return result; } mitk::ImageMappingHelper::ResultImageType::Pointer mitk::ImageMappingHelper:: refineGeometry(const InputImageType* input, const MITKRegistrationType* registration, bool throwOnError) { if (!registration) { mitkThrow() << "Cannot refine image geometry. Passed registration wrapper pointer is nullptr."; } if (!registration->GetRegistration()) { mitkThrow() << "Cannot refine image geometry. Passed registration wrapper containes no registration."; } if (!input) { mitkThrow() << "Cannot refine image geometry. Passed image pointer is nullptr."; } ResultImageType::Pointer result = refineGeometry(input, registration->GetRegistration(), throwOnError); return result; } bool mitk::ImageMappingHelper:: canRefineGeometry(const RegistrationType* registration) { bool result = true; if (!registration) { mitkThrow() << "Cannot check refine capability of registration. Passed registration pointer is nullptr."; } //if the helper does not return null, we can refine the geometry. result = mitk::MITKRegistrationHelper::getAffineMatrix(registration,false).IsNotNull(); return result; } bool mitk::ImageMappingHelper:: canRefineGeometry(const MITKRegistrationType* registration) { if (!registration) { mitkThrow() << "Cannot check refine capability of registration. Passed registration wrapper pointer is nullptr."; } if (!registration->GetRegistration()) { mitkThrow() << "Cannot check refine capability of registration. Passed registration wrapper containes no registration."; } return canRefineGeometry(registration->GetRegistration()); } diff --git a/Modules/Multilabel/Testing/mitkLabelSetImageTest.cpp b/Modules/Multilabel/Testing/mitkLabelSetImageTest.cpp index 40cb73cf09..700825fdaa 100644 --- a/Modules/Multilabel/Testing/mitkLabelSetImageTest.cpp +++ b/Modules/Multilabel/Testing/mitkLabelSetImageTest.cpp @@ -1,515 +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(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 - 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 - 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 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 TestSetUnlabeledLabelLock() { auto locked = m_LabelSetImage->GetUnlabeledLabelLock(); CPPUNIT_ASSERT_MESSAGE("Wrong UnlabeledLabelLock default state", locked == false); - // Exterior label should be set automatically for each new layer 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() == 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() == 5); - // 2ndMin because of the exterior label = 0 + // 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() == 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() == 5); - // 2ndMin because of the exterior label = 0 + // 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() == 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() == 5); - // 2ndMin because of the exterior label = 0 + // 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/mitkTransferLabelTest.cpp b/Modules/Multilabel/Testing/mitkTransferLabelTest.cpp index 218fe15e55..be70fd897f 100644 --- a/Modules/Multilabel/Testing/mitkTransferLabelTest.cpp +++ b/Modules/Multilabel/Testing/mitkTransferLabelTest.cpp @@ -1,229 +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 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::TransferLabelContentAtTimeStep(m_SourceImage, destinationImage, 0, { {1,1} }, mitk::MultiLabelSegmentation::MergeStyle::Merge, mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks); - mitk::TransferLabelContentAtTimeStep(m_SourceImage, destinationLockedExteriorImage, 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(*(destinationLockedExteriorImage.GetPointer()), *(refLockedExteriorImage.GetPointer()), mitk::eps, false)); + 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 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::TransferLabelContentAtTimeStep(m_SourceImage, destinationImage, 0, { {1,1} }, mitk::MultiLabelSegmentation::MergeStyle::Merge, mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks); - mitk::TransferLabelContentAtTimeStep(m_SourceImage, destinationLockedExteriorImage, 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(*(destinationLockedExteriorImage.GetPointer()), *(refLockedExteriorImage.GetPointer()), mitk::eps, false)); + 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 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::TransferLabelContentAtTimeStep(m_SourceImage, destinationImage, 0, { {1,1} }, mitk::MultiLabelSegmentation::MergeStyle::Replace, mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks); - mitk::TransferLabelContentAtTimeStep(m_SourceImage, destinationLockedExteriorImage, 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(*(destinationLockedExteriorImage.GetPointer()), *(refLockedExteriorImage.GetPointer()), mitk::eps, false)); + 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 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::TransferLabelContentAtTimeStep(m_SourceImage, destinationImage, 0, { {1,1} }, mitk::MultiLabelSegmentation::MergeStyle::Replace, mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks); - mitk::TransferLabelContentAtTimeStep(m_SourceImage, destinationLockedExteriorImage, 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(*(destinationLockedExteriorImage.GetPointer()), *(refLockedExteriorImage.GetPointer()), mitk::eps, false)); + mitk::Equal(*(destinationLockedUnlabeledImage.GetPointer()), *(refLockedUnlabeledImage.GetPointer()), mitk::eps, false)); } void TestTransfer_multipleLabels_AtTimeStep() { 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::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, destinationLockedExteriorImage, 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(*(destinationLockedExteriorImage.GetPointer()), *(refLockedExteriorImage.GetPointer()), mitk::eps, false)); + mitk::Equal(*(destinationLockedUnlabeledImage.GetPointer()), *(refLockedUnlabeledImage.GetPointer()), mitk::eps, false)); } }; MITK_TEST_SUITE_REGISTRATION(mitkTransferLabel) diff --git a/Modules/Multilabel/autoload/IO/mitkLegacyLabelSetImageIO.cpp b/Modules/Multilabel/autoload/IO/mitkLegacyLabelSetImageIO.cpp index 461291b126..21e7fe23cc 100644 --- a/Modules/Multilabel/autoload/IO/mitkLegacyLabelSetImageIO.cpp +++ b/Modules/Multilabel/autoload/IO/mitkLegacyLabelSetImageIO.cpp @@ -1,272 +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::UnlabeledLabelValue) { labelSet->AddLabel(label); labelSet->SetLayer(layerIdx); } else { MITK_INFO << "Multi label image contains a label specification for unlabeled pixels. This legacy information is ignored."; } } result.push_back(labelSet); } return result; } std::vector LegacyLabelSetImageIO::DoRead() { itk::NrrdImageIO::Pointer nrrdImageIO = itk::NrrdImageIO::New(); std::vector result; auto rawimage = ItkImageIO::LoadRawMitkImageFromImageIO(nrrdImageIO, this->GetLocalFileName()); const itk::MetaDataDictionary& dictionary = nrrdImageIO->GetMetaDataDictionary(); 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::UnlabeledLabelValue; auto imageIterator = groupImages.begin(); std::vector adaptedLabelSets; for (auto labelset : labelsets) { const auto setValues = labelset->GetUsedLabelValues(); //generate mapping table; std::vector > labelMapping; for (auto vIter = setValues.crbegin(); vIter != setValues.crend(); vIter++) { //have to use reverse loop because TransferLabelContent (used to adapt content in the same image; see below) //would potentially corrupt otherwise the content due to "value collision between old values still present //and already adapted values. By going from highest value to lowest, we avoid that. if (LabelSetImage::UnlabeledLabelValue != *vIter) labelMapping.push_back({ *vIter, *vIter + maxValue }); } if (LabelSetImage::UnlabeledLabelValue != maxValue) { //adapt labelset auto mappedLabelSet = GenerateLabelSetWithMappedValues(labelset, labelMapping); adaptedLabelSets.emplace_back(mappedLabelSet); //adapt image (it is an inplace operation. the image instance stays the same. TransferLabelContent(*imageIterator, *imageIterator, mappedLabelSet, LabelSetImage::UnlabeledLabelValue, LabelSetImage::UnlabeledLabelValue, false, labelMapping, MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle::IgnoreLocks); } else { adaptedLabelSets.emplace_back(labelset); } const auto setMaxValue = *(std::max_element(setValues.begin(), setValues.end())); maxValue += setMaxValue; imageIterator++; } auto output = ConvertImageVectorToLabelSetImage(groupImages, rawimage->GetTimeGeometry()); - LabelSetImage::SpatialGroupIndexType id = 0; + 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/mitkMultiLabelSegmentationIO.cpp b/Modules/Multilabel/autoload/IO/mitkMultiLabelSegmentationIO.cpp index 0e8cb5bee2..18ffe63533 100644 --- a/Modules/Multilabel/autoload/IO/mitkMultiLabelSegmentationIO.cpp +++ b/Modules/Multilabel/autoload/IO/mitkMultiLabelSegmentationIO.cpp @@ -1,224 +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::DeserializeMultLabelGroupsFromJSON(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::SpatialGroupIndexType id = 0; + 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/mitkLabelSet.cpp b/Modules/Multilabel/mitkLabelSet.cpp index aa00e18d04..2ae5d1344d 100644 --- a/Modules/Multilabel/mitkLabelSet.cpp +++ b/Modules/Multilabel/mitkLabelSet.cpp @@ -1,377 +1,385 @@ /*============================================================================ 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 "mitkLabelSet.h" #include "mitkDICOMSegmentationPropertyHelper.h" #include mitk::LabelSet::LabelSet() : m_ActiveLabelValue(0), m_Layer(0) { m_LookupTable = mitk::LookupTable::New(); m_LookupTable->SetType(mitk::LookupTable::MULTILABEL); m_ReservedLabelValuesFunctor = nullptr; } mitk::LabelSet::~LabelSet() { m_LabelContainer.clear(); } mitk::LabelSet::LabelSet(const LabelSet &other) : itk::Object(), m_LookupTable(other.GetLookupTable()->Clone()), m_ActiveLabelValue(other.m_ActiveLabelValue), m_Layer(other.GetLayer()) { // clone Labels auto otherIt = other.IteratorConstBegin(); for (; otherIt != other.IteratorConstEnd(); ++otherIt) { m_LabelContainer[otherIt->first] = otherIt->second->Clone(); - itk::MemberCommand::Pointer command = itk::MemberCommand::New(); + auto command = itk::MemberCommand::New(); command->SetCallbackFunction(this, &LabelSet::OnLabelModified); m_LabelContainer[otherIt->first]->AddObserver(itk::ModifiedEvent(), command); } m_ReservedLabelValuesFunctor = other.m_ReservedLabelValuesFunctor; } std::vector mitk::LabelSet::GetUsedLabelValues() const { std::vector result = { 0 }; if (m_ReservedLabelValuesFunctor != nullptr) { result = m_ReservedLabelValuesFunctor(); } else { for (auto [value, label] : this->m_LabelContainer) { result.emplace_back(value); } } return result; } void mitk::LabelSet::OnLabelModified(const Object* sender, const itk::EventObject&) { auto label = dynamic_cast(sender); - if (nullptr == label) mitkThrow() << "LabelSet is in wrong state. LabelModified event is not send by a label instance."; + if (nullptr == label) + mitkThrow() << "LabelSet is in wrong state. LabelModified event is not send by a label instance."; ModifyLabelEvent.Send(label->GetValue()); Superclass::Modified(); } mitk::LabelSet::LabelContainerConstIteratorType mitk::LabelSet::IteratorConstEnd() const { return m_LabelContainer.end(); } mitk::LabelSet::LabelContainerConstIteratorType mitk::LabelSet::IteratorConstBegin() const { return m_LabelContainer.begin(); } mitk::LabelSet::LabelContainerIteratorType mitk::LabelSet::IteratorEnd() { return m_LabelContainer.end(); } mitk::LabelSet::LabelContainerIteratorType mitk::LabelSet::IteratorBegin() { return m_LabelContainer.begin(); } unsigned int mitk::LabelSet::GetNumberOfLabels() const { return m_LabelContainer.size(); } void mitk::LabelSet::SetLayer(unsigned int layer) { m_Layer = layer; Modified(); } void mitk::LabelSet::SetActiveLabel(PixelType pixelValue) { m_ActiveLabelValue = pixelValue; ActiveLabelEvent.Send(pixelValue); Modified(); } bool mitk::LabelSet::ExistLabel(PixelType pixelValue) { return m_LabelContainer.count(pixelValue) > 0 ? true : false; } mitk::Label* mitk::LabelSet::AddLabel(mitk::Label *label, bool addAsClone) { unsigned int max_size = mitk::Label::MAX_LABEL_VALUE + 1; if (m_LabelContainer.size() >= max_size) return nullptr; mitk::Label::Pointer newLabel = addAsClone ? label->Clone() : Label::Pointer(label); // TODO use layer of label parameter newLabel->SetLayer(m_Layer); - PixelType pixelValue = newLabel->GetValue(); + auto pixelValue = newLabel->GetValue(); auto usedValues = this->GetUsedLabelValues(); auto finding = std::find(usedValues.begin(), usedValues.end(), pixelValue); if (!usedValues.empty() && usedValues.end() != finding) { pixelValue = usedValues.back()+1; MITK_DEBUG << "LabelSet label collision. Tried to add a label with a value already in use. Value will be adapted. Old value: " << newLabel->GetValue() << "; new value: " << pixelValue; newLabel->SetValue(pixelValue); } // new map entry m_LabelContainer[pixelValue] = newLabel; UpdateLookupTable(pixelValue); // add DICOM information of the label DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(newLabel); - itk::MemberCommand::Pointer command = itk::MemberCommand::New(); + auto command = itk::MemberCommand::New(); command->SetCallbackFunction(this, &LabelSet::OnLabelModified); newLabel->AddObserver(itk::ModifiedEvent(), command); AddLabelEvent.Send(newLabel->GetValue()); SetActiveLabel(newLabel->GetValue()); Modified(); return newLabel; } mitk::Label* mitk::LabelSet::AddLabel(const std::string &name, const mitk::Color &color) { mitk::Label::Pointer newLabel = mitk::Label::New(); newLabel->SetName(name); newLabel->SetColor(color); return AddLabel(newLabel); } void mitk::LabelSet::RenameLabel(PixelType pixelValue, const std::string &name, const mitk::Color &color) { mitk::Label *label = GetLabel(pixelValue); label->SetName(name); label->SetColor(color); // change DICOM information of the label DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(label); } void mitk::LabelSet::SetLookupTable(mitk::LookupTable *lut) { m_LookupTable = lut; Modified(); } void mitk::LabelSet::PrintSelf(std::ostream & /*os*/, itk::Indent /*indent*/) const { } void mitk::LabelSet::RemoveLabel(PixelType pixelValue) { - if (LabelSetImage::UnlabeledLabelValue == pixelValue) return; + if (LabelSetImage::UnlabeledLabelValue == pixelValue) + return; auto it = m_LabelContainer.rbegin(); PixelType nextActivePixelValue = it->first; for (; it != m_LabelContainer.rend(); ++it) { if (it->first == pixelValue) { it->second->RemoveAllObservers(); m_LabelContainer.erase(pixelValue); break; } nextActivePixelValue = it->first; } if (m_ActiveLabelValue == pixelValue) { if (ExistLabel(nextActivePixelValue)) - SetActiveLabel(nextActivePixelValue); + { + this->SetActiveLabel(nextActivePixelValue); + } else if (!m_LabelContainer.empty()) - SetActiveLabel(m_LabelContainer.rbegin()->first); + { + this->SetActiveLabel(m_LabelContainer.rbegin()->first); + } else - SetActiveLabel(0); + { + this->SetActiveLabel(0); + } } RemoveLabelEvent.Send(pixelValue); Modified(); } void mitk::LabelSet::RemoveAllLabels() { auto _it = IteratorBegin(); for (; _it != IteratorConstEnd();) { auto labelValue = _it->first; m_LabelContainer.erase(_it++); RemoveLabelEvent.Send(labelValue); } AllLabelsModifiedEvent.Send(); } void mitk::LabelSet::SetNextActiveLabel() { auto it = m_LabelContainer.find(m_ActiveLabelValue); if (it != m_LabelContainer.end()) ++it; if (it == m_LabelContainer.end()) { it = m_LabelContainer.begin(); if (m_LabelContainer.size() > 1) ++it; // ...skip background label! } SetActiveLabel(it->first); } void mitk::LabelSet::SetAllLabelsLocked(bool value) { auto _end = m_LabelContainer.end(); auto _it = m_LabelContainer.begin(); for (; _it != _end; ++_it) _it->second->SetLocked(value); AllLabelsModifiedEvent.Send(); Modified(); } void mitk::LabelSet::SetAllLabelsVisible(bool value) { auto _end = m_LabelContainer.end(); auto _it = m_LabelContainer.begin(); for (; _it != _end; ++_it) { _it->second->SetVisible(value); UpdateLookupTable(_it->first); } AllLabelsModifiedEvent.Send(); Modified(); } void mitk::LabelSet::UpdateLookupTable(PixelType pixelValue) { const mitk::Color &color = GetLabel(pixelValue)->GetColor(); double rgba[4]; m_LookupTable->GetTableValue(static_cast(pixelValue), rgba); rgba[0] = color.GetRed(); rgba[1] = color.GetGreen(); rgba[2] = color.GetBlue(); if (GetLabel(pixelValue)->GetVisible()) rgba[3] = GetLabel(pixelValue)->GetOpacity(); else rgba[3] = 0.0; m_LookupTable->SetTableValue(static_cast(pixelValue), rgba); } mitk::Label *mitk::LabelSet::GetLabel(PixelType pixelValue) { if (m_LabelContainer.find(pixelValue) == m_LabelContainer.end()) return nullptr; return m_LabelContainer[pixelValue]; } const mitk::Label *mitk::LabelSet::GetLabel(PixelType pixelValue) const { auto it = m_LabelContainer.find(pixelValue); if (it == m_LabelContainer.end()) return nullptr; return it->second.GetPointer(); } bool mitk::Equal(const mitk::LabelSet &leftHandSide, const mitk::LabelSet &rightHandSide, ScalarType eps, bool verbose) { bool returnValue = true; // LabelSetmembers MITK_INFO(verbose) << "--- LabelSet Equal ---"; // m_LookupTable; const mitk::LookupTable *lhsLUT = leftHandSide.GetLookupTable(); const mitk::LookupTable *rhsLUT = rightHandSide.GetLookupTable(); returnValue = *lhsLUT == *rhsLUT; if (!returnValue) { MITK_INFO(verbose) << "Lookup tabels not equal."; return returnValue; ; } // m_ActiveLabel; if (leftHandSide.GetActiveLabel() != rightHandSide.GetActiveLabel()) { returnValue = mitk::Equal(*leftHandSide.GetActiveLabel(), *rightHandSide.GetActiveLabel(), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Active label not equal."; return returnValue; ; } } // m_Layer; returnValue = leftHandSide.GetLayer() == rightHandSide.GetLayer(); if (!returnValue) { MITK_INFO(verbose) << "Layer index not equal."; return returnValue; ; } // container size; returnValue = leftHandSide.GetNumberOfLabels() == rightHandSide.GetNumberOfLabels(); if (!returnValue) { MITK_INFO(verbose) << "Number of labels not equal."; return returnValue; ; } // Label container (map) // m_LabelContainer; auto lhsit = leftHandSide.IteratorConstBegin(); auto rhsit = rightHandSide.IteratorConstBegin(); for (; lhsit != leftHandSide.IteratorConstEnd(); ++lhsit, ++rhsit) { returnValue = rhsit->first == lhsit->first; if (!returnValue) { MITK_INFO(verbose) << "Label in label container not equal."; return returnValue; ; } returnValue = mitk::Equal(*(rhsit->second), *(lhsit->second), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Label in label container not equal."; return returnValue; ; } } return returnValue; } diff --git a/Modules/Multilabel/mitkLabelSet.h b/Modules/Multilabel/mitkLabelSet.h index d2bf809a0f..38ea8d4b39 100644 --- a/Modules/Multilabel/mitkLabelSet.h +++ b/Modules/Multilabel/mitkLabelSet.h @@ -1,255 +1,257 @@ /*============================================================================ 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 mitkLabelSet_h #define mitkLabelSet_h #include "MitkMultilabelExports.h" #include #include #include #include #include #include namespace mitk { // // Documentation // @brief LabelSet containing the labels corresponding to a segmentation session. // @ingroup Data // class MITKMULTILABEL_EXPORT LabelSet : public itk::Object { public: mitkClassMacroItkParent(LabelSet, itk::Object); itkNewMacro(Self); typedef mitk::Label::PixelType PixelType; using LabelValueType = mitk::Label::PixelType; typedef std::map LabelContainerType; typedef LabelContainerType::const_iterator LabelContainerConstIteratorType; typedef LabelContainerType::iterator LabelContainerIteratorType; /** * \brief AddLabelEvent is emitted whenever a new label has been added to the LabelSet. * * The registered method will be called with the label value of the added label. * Observers should register to this event by calling myLabelSet->AddLabelEvent.AddListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been added to the LabelSet. * Observers should unregister by calling myLabelSet->AddLabelEvent.RemoveListener(myObject, MyObject::MyMethod). * * member variable is not needed to be locked in multi-threaded scenarios since the LabelSetEvent is a typedef for * a Message1 object which is thread safe */ Message1 AddLabelEvent; /** * \brief RemoveLabelEvent is emitted whenever a new label has been removed from the LabelSet. * + * The registered method will be called with the label value of the removed label. * Observers should register to this event by calling myLabelSet->RemoveLabelEvent.AddListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been removed from the LabelSet. * Observers should unregister by calling myLabelSet->RemoveLabelEvent.RemoveListener(myObject, MyObject::MyMethod). * * member variable is not needed to be locked in multi-threaded scenarios since the LabelSetEvent is a typedef for * a Message object which is thread safe */ Message1 RemoveLabelEvent; /** * \brief ModifyLabelEvent is emitted whenever a label has been modified from the LabelSet. * + * The registered method will be called with the label value of the modified label. * Observers should register to this event by calling myLabelSet->ModifyLabelEvent.AddListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been removed from the LabelSet. * Observers should unregister by calling myLabelSet->ModifyLabelEvent.RemoveListener(myObject, MyObject::MyMethod). * * member variable is not needed to be locked in multi-threaded scenarios since the LabelSetEvent is a typedef for * a Message object which is thread safe */ Message1 ModifyLabelEvent; /** * \brief ActiveLabelEvent is emitted whenever a label has been set as active in the LabelSet. */ Message1 ActiveLabelEvent; /** * \brief AllLabelsModifiedEvent is emitted whenever a new label has been removed from the LabelSet. * * Observers should register to this event by calling myLabelSet->AllLabelsModifiedEvent.AddListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been removed from the LabelSet. * Observers should unregister by calling myLabelSet->AllLabelsModifiedEvent.RemoveListener(myObject, * MyObject::MyMethod). * * member variable is not needed to be locked in multi-threaded scenarios since the LabelSetEvent is a typedef for * a Message object which is thread safe */ Message<> AllLabelsModifiedEvent; /** \brief Returns a const iterator poiting to the begining of the container. */ LabelContainerConstIteratorType IteratorConstBegin() const; /** \brief Returns a const iterator pointing to the end of the container. */ LabelContainerConstIteratorType IteratorConstEnd() const; /** \brief Returns a iterator poiting to the begining of the container. */ LabelContainerIteratorType IteratorBegin(); /** \brief Returns a iterator pointing to the end of the container. */ LabelContainerIteratorType IteratorEnd(); /** \brief * Recall itk::Object::Modified event from a label and send a ModifyLabelEvent */ void OnLabelModified(const Object*, const itk::EventObject&); /** \brief */ void SetLayer(unsigned int); /** \brief */ void SetActiveLabel(PixelType); /** \brief */ void RemoveLabel(PixelType); /** \brief */ bool ExistLabel(PixelType); /** \brief */ mitk::Label* AddLabel(mitk::Label *label, bool addAsClone = true); /** \brief */ mitk::Label* AddLabel(const std::string &name, const Color &color); /** \brief */ void RenameLabel(PixelType, const std::string &, const Color &); /** \brief */ unsigned int GetNumberOfLabels() const; /** \brief */ void SetAllLabelsVisible(bool); /** \brief */ void SetAllLabelsLocked(bool); /** \brief */ void RemoveAllLabels(); void SetNextActiveLabel(); /** \brief */ Label *GetActiveLabel() { return GetLabel(m_ActiveLabelValue); } /** \brief */ const Label *GetActiveLabel() const { return GetLabel(m_ActiveLabelValue); } /** \brief */ Label *GetLabel(PixelType pixelValue); /** \brief */ const Label *GetLabel(PixelType pixelValue) const; itkGetMacro(Layer, int); itkGetConstMacro(Layer, int); itkGetModifiableObjectMacro(LookupTable, mitk::LookupTable); /** \brief */ void SetLookupTable(LookupTable *lut); /** \brief */ void UpdateLookupTable(PixelType pixelValue); using ReservedLabelValuesFunctor = std::function()>; ReservedLabelValuesFunctor m_ReservedLabelValuesFunctor; std::vector GetUsedLabelValues() const; protected: LabelSet(); LabelSet(const LabelSet &); mitkCloneMacro(Self); ~LabelSet() override; void PrintSelf(std::ostream &os, itk::Indent indent) const override; LabelContainerType m_LabelContainer; LookupTable::Pointer m_LookupTable; PixelType m_ActiveLabelValue; unsigned int m_Layer; }; /** * @brief Equal A function comparing two label sets for beeing equal in data * * @ingroup MITKTestingAPI * * Following aspects are tested for equality: * - LabelSetmembers * - Label container (map) * * @param rightHandSide An image to be compared * @param leftHandSide An image to be compared * @param eps Tolarence for comparison. You can use mitk::eps in most cases. * @param verbose Flag indicating if the user wants detailed console output or not. * @return true, if all subsequent comparisons are true, false otherwise */ MITKMULTILABEL_EXPORT bool Equal(const mitk::LabelSet &leftHandSide, const mitk::LabelSet &rightHandSide, ScalarType eps, bool verbose); /** - * Methods takes a label set and generates a new label set with the same labels but updated labels values according to + * Method takes a label set and generates a new label set with the same labels but updated labels values according to * the passed labelMapping. * @pre sourceLabelSet is valid */ MITKMULTILABEL_EXPORT LabelSet::Pointer GenerateLabelSetWithMappedValues(const LabelSet* sourceLabelSet, std::vector > labelMapping = { {1,1} }); } // namespace mitk #endif diff --git a/Modules/Multilabel/mitkLabelSetImage.cpp b/Modules/Multilabel/mitkLabelSetImage.cpp index ab49b0461c..67bf448186 100644 --- a/Modules/Multilabel/mitkLabelSetImage.cpp +++ b/Modules/Multilabel/mitkLabelSetImage.cpp @@ -1,1556 +1,1553 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkLabelSetImage.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImagePixelReadAccessor.h" #include "mitkImagePixelWriteAccessor.h" #include "mitkInteractionConst.h" #include "mitkLookupTableProperty.h" #include "mitkPadImageFilter.h" #include "mitkRenderingManager.h" #include "mitkDICOMSegmentationPropertyHelper.h" #include "mitkDICOMQIPropertyHelper.h" #include #include #include #include #include #include #include //#include #include #include template void SetToZero(itk::Image *source) { source->FillBuffer(0); } template void CreateLabelMaskProcessing(mitk::Image *layerImage, mitk::Image *mask, mitk::LabelSet::PixelType index) { mitk::ImagePixelReadAccessor readAccessor(layerImage); mitk::ImagePixelWriteAccessor writeAccessor(mask); std::size_t numberOfPixels = 1; for (int dim = 0; dim < static_cast(VImageDimension); ++dim) numberOfPixels *= static_cast(readAccessor.GetDimension(dim)); auto src = readAccessor.GetData(); auto dest = writeAccessor.GetData(); for (std::size_t i = 0; i < numberOfPixels; ++i) { if (index == *(src + i)) *(dest + i) = 1; } } mitk::LabelSetImage::LabelSetImage() : mitk::Image(), m_UnlabeledLabelLock(false), m_ActiveLayer(0), m_activeLayerInvalid(false) { // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } mitk::LabelSetImage::LabelSetImage(const mitk::LabelSetImage &other) : Image(other), m_UnlabeledLabelLock(other.m_UnlabeledLabelLock), m_ActiveLayer(other.GetActiveLayer()), m_activeLayerInvalid(false) { for (unsigned int i = 0; i < other.GetNumberOfLayers(); i++) { // Clone LabelSet data mitk::LabelSet::Pointer lsClone = other.GetLabelSet(i)->Clone(); this->RegisterLabelSet(lsClone); m_LabelSetContainer.push_back(lsClone); // clone layer Image data mitk::Image::Pointer liClone = other.GetLayerImage(i)->Clone(); m_LayerContainer.push_back(liClone); } // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } void mitk::LabelSetImage::OnLabelSetModified() { Superclass::Modified(); } void mitk::LabelSetImage::Initialize(const mitk::Image *other) { mitk::PixelType pixelType(mitk::MakeScalarPixelType()); if (other->GetDimension() == 2) { const unsigned int dimensions[] = {other->GetDimension(0), other->GetDimension(1), 1}; Superclass::Initialize(pixelType, 3, dimensions); } else { Superclass::Initialize(pixelType, other->GetDimension(), other->GetDimensions()); } auto originalGeometry = other->GetTimeGeometry()->Clone(); this->SetTimeGeometry(originalGeometry); // initialize image memory to zero if (4 == this->GetDimension()) { AccessFixedDimensionByItk(this, SetToZero, 4); } else { AccessByItk(this, SetToZero); } // Transfer some general DICOM properties from the source image to derived image (e.g. Patient information,...) DICOMQIPropertyHelper::DeriveDICOMSourceProperties(other, this); // Add a inital LabelSet ans corresponding image data to the stack if (this->GetNumberOfLayers() == 0) { AddLayer(); } } mitk::LabelSetImage::~LabelSetImage() { for (auto ls : m_LabelSetContainer) { this->ReleaseLabelSet(ls); } m_LabelSetContainer.clear(); } mitk::Image *mitk::LabelSetImage::GetLayerImage(unsigned int layer) { return m_LayerContainer[layer]; } const mitk::Image *mitk::LabelSetImage::GetLayerImage(unsigned int layer) const { return m_LayerContainer[layer]; } unsigned int mitk::LabelSetImage::GetActiveLayer() const { return m_ActiveLayer; } unsigned int mitk::LabelSetImage::GetNumberOfLayers() const { return m_LabelSetContainer.size(); } void mitk::LabelSetImage::RegisterLabelSet(mitk::LabelSet* ls) { // add modified event listener to LabelSet (listen to LabelSet changes) itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &mitk::LabelSetImage::OnLabelSetModified); ls->AddObserver(itk::ModifiedEvent(), command); ls->AddLabelEvent.AddListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelAdded)); ls->ModifyLabelEvent.AddListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelModified)); ls->RemoveLabelEvent.AddListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelRemoved)); ls->m_ReservedLabelValuesFunctor = [this]() {return this->GetUsedLabelValues(); }; } void mitk::LabelSetImage::ReleaseLabelSet(mitk::LabelSet* ls) { ls->RemoveAllObservers(); ls->AddLabelEvent.RemoveListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelAdded)); ls->ModifyLabelEvent.RemoveListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelModified)); ls->RemoveLabelEvent.RemoveListener(mitk::MessageDelegate1( this, &LabelSetImage::OnLabelRemoved)); ls->m_ReservedLabelValuesFunctor = nullptr; } void mitk::LabelSetImage::RemoveLayer() { int layerToDelete = GetActiveLayer(); // remove all observers from active label set GetLabelSet(layerToDelete)->RemoveAllObservers(); // set the active layer to one below, if exists. if (layerToDelete != 0) { SetActiveLayer(layerToDelete - 1); } else { // we are deleting layer zero, it should not be copied back into the vector m_activeLayerInvalid = true; } // remove labelset and image data m_LabelSetContainer.erase(m_LabelSetContainer.begin() + layerToDelete); m_LayerContainer.erase(m_LayerContainer.begin() + layerToDelete); if (layerToDelete == 0) { this->SetActiveLayer(layerToDelete); } this->OnGroupRemoved(layerToDelete); this->Modified(); } -void mitk::LabelSetImage::RemoveSpatialGroup(SpatialGroupIndexType indexToDelete) +void mitk::LabelSetImage::RemoveGroup(GroupIndexType indexToDelete) { const auto activeIndex = GetActiveLayer(); // remove all observers from active label set GetLabelSet(indexToDelete)->RemoveAllObservers(); // set the active layer to one below, if exists. if (activeIndex>indexToDelete) { SetActiveLayer(activeIndex - 1); } else if (activeIndex==indexToDelete) { // we are deleting layer zero, it should not be copied back into the vector m_activeLayerInvalid = true; } // remove labelset and image data m_LabelSetContainer.erase(m_LabelSetContainer.begin() + indexToDelete); m_LayerContainer.erase(m_LayerContainer.begin() + indexToDelete); if (indexToDelete == activeIndex) { //enforces the new active layer to be set and copied auto newActiveIndex = indexToDelete < GetNumberOfLayers() ? indexToDelete : GetNumberOfLayers() - 1; this->SetActiveLayer(newActiveIndex); } this->OnGroupRemoved(indexToDelete); this->Modified(); } mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::GetUsedLabelValues() const { LabelValueVectorType result = { UnlabeledLabelValue }; for (auto [value, label] : m_LabelMap) { result.emplace_back(value); } return result; } unsigned int mitk::LabelSetImage::AddLayer(mitk::LabelSet::Pointer labelSet) { mitk::Image::Pointer newImage = mitk::Image::New(); newImage->Initialize(this->GetPixelType(), this->GetDimension(), this->GetDimensions(), this->GetImageDescriptor()->GetNumberOfChannels()); newImage->SetTimeGeometry(this->GetTimeGeometry()->Clone()); if (newImage->GetDimension() < 4) { AccessByItk(newImage, SetToZero); } else { AccessFixedDimensionByItk(newImage, SetToZero, 4); } return this->AddLayer(newImage, labelSet); } unsigned int mitk::LabelSetImage::AddLayer(mitk::Image::Pointer layerImage, mitk::LabelSet::Pointer labelSet) { unsigned int newLabelSetId = m_LayerContainer.size(); // Add labelset to layer mitk::LabelSet::Pointer ls; if (labelSet.IsNotNull()) { ls = labelSet; } else { ls = mitk::LabelSet::New(); ls->SetActiveLabel(UnlabeledLabelValue); } ls->SetLayer(newLabelSetId); - // Add exterior Label to label set - // mitk::Label::Pointer exteriorLabel = CreateExteriorLabel(); // push a new working image for the new layer m_LayerContainer.push_back(layerImage); // push a new labelset for the new layer m_LabelSetContainer.push_back(ls); RegisterLabelSet(ls); this->ReinitMaps(); SetActiveLayer(newLabelSetId); this->Modified(); this->OnGroupAdded(newLabelSetId); return newLabelSetId; } void mitk::LabelSetImage::AddLabelSetToLayer(const unsigned int layerIdx, const mitk::LabelSet* labelSet) { if (m_LayerContainer.size() <= layerIdx) { mitkThrow() << "Trying to add labelSet to non-existing layer."; } auto clonedLabelSet = labelSet->Clone(); this->RegisterLabelSet(clonedLabelSet); - std::vector addedGroups; + std::vector addedGroups; if (layerIdx < m_LabelSetContainer.size()) { if (m_LabelSetContainer[layerIdx].IsNotNull()) { this->ReleaseLabelSet(m_LabelSetContainer[layerIdx]); } m_LabelSetContainer[layerIdx] = clonedLabelSet; } else { while (layerIdx >= m_LabelSetContainer.size()) { mitk::LabelSet::Pointer defaultLabelSet = mitk::LabelSet::New(); defaultLabelSet->SetActiveLabel(UnlabeledLabelValue); defaultLabelSet->SetLayer(m_LabelSetContainer.size()); this->RegisterLabelSet(defaultLabelSet); this->ReinitMaps(); m_LabelSetContainer.push_back(defaultLabelSet); addedGroups.emplace_back(m_LabelSetContainer.size() - 1); } m_LabelSetContainer.push_back(clonedLabelSet); addedGroups.emplace_back(m_LabelSetContainer.size() - 1); } this->ReinitMaps(); for (auto groupID : addedGroups) { this->m_GroupAddedMessage.Send(groupID); } } void mitk::LabelSetImage::SetActiveLayer(unsigned int layer) { try { if (4 == this->GetDimension()) { if ((layer != GetActiveLayer() || m_activeLayerInvalid) && (layer < this->GetNumberOfLayers())) { BeforeChangeLayerEvent.Send(); if (m_activeLayerInvalid) { // We should not write the invalid layer back to the vector m_activeLayerInvalid = false; } else { AccessFixedDimensionByItk_n(this, ImageToLayerContainerProcessing, 4, (GetActiveLayer())); } m_ActiveLayer = layer; // only at this place m_ActiveLayer should be manipulated!!! Use Getter and Setter AccessFixedDimensionByItk_n(this, LayerContainerToImageProcessing, 4, (GetActiveLayer())); AfterChangeLayerEvent.Send(); } } else { if ((layer != GetActiveLayer() || m_activeLayerInvalid) && (layer < this->GetNumberOfLayers())) { BeforeChangeLayerEvent.Send(); if (m_activeLayerInvalid) { // We should not write the invalid layer back to the vector m_activeLayerInvalid = false; } else { AccessByItk_1(this, ImageToLayerContainerProcessing, GetActiveLayer()); } m_ActiveLayer = layer; // only at this place m_ActiveLayer should be manipulated!!! Use Getter and Setter AccessByItk_1(this, LayerContainerToImageProcessing, GetActiveLayer()); AfterChangeLayerEvent.Send(); } } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->Modified(); } void mitk::LabelSetImage::ClearBuffer() { try { if (this->GetDimension() == 4) { //remark: this extra branch was added, because LabelSetImage instances can be //dynamic (4D), but AccessByItk by support only supports 2D and 3D. //The option to change the CMake default dimensions for AccessByItk was //dropped (for details see discussion in T28756) AccessFixedDimensionByItk(this, ClearBufferProcessing,4); } else { AccessByItk(this, ClearBufferProcessing); } this->Modified(); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } } bool mitk::LabelSetImage::ExistLabel(PixelType pixelValue) const { bool exist = false; for (unsigned int lidx = 0; lidx < GetNumberOfLayers(); lidx++) exist |= m_LabelSetContainer[lidx]->ExistLabel(pixelValue); return exist; } bool mitk::LabelSetImage::ExistLabel(PixelType pixelValue, unsigned int layer) const { bool exist = m_LabelSetContainer[layer]->ExistLabel(pixelValue); return exist; } bool mitk::LabelSetImage::ExistLabelSet(unsigned int layer) const { return layer < m_LabelSetContainer.size(); } void mitk::LabelSetImage::MergeLabel(PixelType pixelValue, PixelType sourcePixelValue, unsigned int layer) { try { AccessByItk_2(this, MergeLabelProcessing, pixelValue, sourcePixelValue); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } GetLabelSet(layer)->SetActiveLabel(pixelValue); this->m_LabelModifiedMessage.Send(sourcePixelValue); this->m_LabelModifiedMessage.Send(pixelValue); this->m_LabelsChangedMessage.Send({ sourcePixelValue, pixelValue }); Modified(); } void mitk::LabelSetImage::MergeLabels(PixelType pixelValue, const std::vector& vectorOfSourcePixelValues, unsigned int layer) { try { for (unsigned int idx = 0; idx < vectorOfSourcePixelValues.size(); idx++) { AccessByItk_2(this, MergeLabelProcessing, pixelValue, vectorOfSourcePixelValues[idx]); this->m_LabelModifiedMessage.Send(vectorOfSourcePixelValues[idx]); } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } GetLabelSet(layer)->SetActiveLabel(pixelValue); this->m_LabelModifiedMessage.Send(pixelValue); auto modifiedValues = vectorOfSourcePixelValues; modifiedValues.push_back(pixelValue); this->m_LabelsChangedMessage.Send(modifiedValues); Modified(); } void mitk::LabelSetImage::RemoveLabel(LabelValueType pixelValue) { auto groupID = this->GetGroupIndexOfLabel(pixelValue); //first erase the pixel content (also triggers a LabelModified event) this->EraseLabel(pixelValue); //now remove the label entry itself this->GetLabelSet(groupID)->RemoveLabel(pixelValue); // in the interim version triggered by label set events: this->m_LabelRemovedMessage.Send(pixelValue); this->m_LabelsChangedMessage.Send({ pixelValue }); this->m_GroupModifiedMessage.Send(groupID); } void mitk::LabelSetImage::RemoveLabels(const std::vector& VectorOfLabelPixelValues) { for (unsigned int idx = 0; idx < VectorOfLabelPixelValues.size(); idx++) { this->RemoveLabel(VectorOfLabelPixelValues[idx]); this->m_LabelsChangedMessage.Send({ VectorOfLabelPixelValues[idx] }); } } void mitk::LabelSetImage::EraseLabel(PixelType pixelValue) { try { - mitk::Image* groupImage = this; auto groupID = this->GetGroupIndexOfLabel(pixelValue); - if (groupID != this->GetActiveLayer()) - { - groupImage = this->GetLayerImage(groupID); - } + mitk::Image* groupImage = this->GetActiveLayer() != groupID + ? this->GetLayerImage(groupID) + : this; if (4 == this->GetDimension()) { AccessFixedDimensionByItk_1(groupImage, EraseLabelProcessing, 4, pixelValue); } else { AccessByItk_1(groupImage, EraseLabelProcessing, pixelValue); } } catch (const itk::ExceptionObject& e) { mitkThrow() << e.GetDescription(); } this->m_LabelModifiedMessage.Send(pixelValue); this->m_LabelsChangedMessage.Send({ pixelValue }); Modified(); } void mitk::LabelSetImage::EraseLabels(const std::vector& VectorOfLabelPixelValues) { for (unsigned int idx = 0; idx < VectorOfLabelPixelValues.size(); idx++) { this->EraseLabel(VectorOfLabelPixelValues[idx]); } } mitk::Label *mitk::LabelSetImage::GetActiveLabel(unsigned int layer) { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer]->GetActiveLabel(); } const mitk::Label* mitk::LabelSetImage::GetActiveLabel(unsigned int layer) const { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer]->GetActiveLabel(); } mitk::Label *mitk::LabelSetImage::GetLabel(PixelType pixelValue, unsigned int layer) const { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer]->GetLabel(pixelValue); } mitk::LabelSet *mitk::LabelSetImage::GetLabelSet(unsigned int layer) { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer].GetPointer(); } const mitk::LabelSet *mitk::LabelSetImage::GetLabelSet(unsigned int layer) const { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer].GetPointer(); } mitk::LabelSet *mitk::LabelSetImage::GetActiveLabelSet() { if (m_LabelSetContainer.size() == 0) return nullptr; else return m_LabelSetContainer[GetActiveLayer()].GetPointer(); } const mitk::LabelSet* mitk::LabelSetImage::GetActiveLabelSet() const { if (m_LabelSetContainer.size() == 0) return nullptr; else return m_LabelSetContainer[GetActiveLayer()].GetPointer(); } void mitk::LabelSetImage::UpdateCenterOfMass(PixelType pixelValue) { this->UpdateCenterOfMass(pixelValue, this->GetGroupIndexOfLabel(pixelValue)); } void mitk::LabelSetImage::UpdateCenterOfMass(PixelType pixelValue, unsigned int layer) { if (4 == this->GetDimension()) { AccessFixedDimensionByItk_2(this, CalculateCenterOfMassProcessing, 4, pixelValue, layer); } else { AccessByItk_2(this, CalculateCenterOfMassProcessing, pixelValue, layer); } } unsigned int mitk::LabelSetImage::GetNumberOfLabels(unsigned int layer) const { return m_LabelSetContainer[layer]->GetNumberOfLabels(); } unsigned int mitk::LabelSetImage::GetTotalNumberOfLabels() const { unsigned int totalLabels(0); auto layerIter = m_LabelSetContainer.begin(); for (; layerIter != m_LabelSetContainer.end(); ++layerIter) totalLabels += (*layerIter)->GetNumberOfLabels(); return totalLabels; } void mitk::LabelSetImage::MaskStamp(mitk::Image *mask, bool forceOverwrite) { try { mitk::PadImageFilter::Pointer padImageFilter = mitk::PadImageFilter::New(); padImageFilter->SetInput(0, mask); padImageFilter->SetInput(1, this); padImageFilter->SetPadConstant(0); padImageFilter->SetBinaryFilter(false); padImageFilter->SetLowerThreshold(0); padImageFilter->SetUpperThreshold(1); padImageFilter->Update(); mitk::Image::Pointer paddedMask = padImageFilter->GetOutput(); if (paddedMask.IsNull()) return; AccessByItk_2(this, MaskStampProcessing, paddedMask, forceOverwrite); } catch (...) { mitkThrow() << "Could not stamp the provided mask on the selected label."; } } mitk::Image::Pointer mitk::LabelSetImage::CreateLabelMask(PixelType index, bool useActiveLayer, unsigned int layer) { auto previousActiveLayer = this->GetActiveLayer(); auto mask = mitk::Image::New(); try { // mask->Initialize(this) does not work here if this label set image has a single slice, // since the mask would be automatically flattened to a 2-d image, whereas we expect the // original dimension of this label set image. Hence, initialize the mask more explicitly: mask->Initialize(this->GetPixelType(), this->GetDimension(), this->GetDimensions()); mask->SetTimeGeometry(this->GetTimeGeometry()->Clone()); auto byteSize = sizeof(LabelSetImage::PixelType); for (unsigned int dim = 0; dim < mask->GetDimension(); ++dim) byteSize *= mask->GetDimension(dim); { ImageWriteAccessor accessor(mask); memset(accessor.GetData(), 0, byteSize); } if (!useActiveLayer) this->SetActiveLayer(layer); if (4 == this->GetDimension()) { ::CreateLabelMaskProcessing<4>(this, mask, index); } else if (3 == this->GetDimension()) { ::CreateLabelMaskProcessing(this, mask, index); } else { mitkThrow(); } } catch (...) { if (!useActiveLayer) this->SetActiveLayer(previousActiveLayer); mitkThrow() << "Could not create a mask out of the selected label."; } if (!useActiveLayer) this->SetActiveLayer(previousActiveLayer); return mask; } void mitk::LabelSetImage::InitializeByLabeledImage(mitk::Image::Pointer image) { if (image.IsNull() || image->IsEmpty() || !image->IsInitialized()) mitkThrow() << "Invalid labeled image."; try { this->Initialize(image); unsigned int byteSize = sizeof(LabelSetImage::PixelType); for (unsigned int dim = 0; dim < image->GetDimension(); ++dim) { byteSize *= image->GetDimension(dim); } mitk::ImageWriteAccessor *accessor = new mitk::ImageWriteAccessor(static_cast(this)); memset(accessor->GetData(), 0, byteSize); delete accessor; auto geometry = image->GetTimeGeometry()->Clone(); this->SetTimeGeometry(geometry); if (image->GetDimension() == 3) { AccessTwoImagesFixedDimensionByItk(this, image, InitializeByLabeledImageProcessing, 3); } else if (image->GetDimension() == 4) { AccessTwoImagesFixedDimensionByItk(this, image, InitializeByLabeledImageProcessing, 4); } else { mitkThrow() << image->GetDimension() << "-dimensional label set images not yet supported"; } } catch (...) { mitkThrow() << "Could not intialize by provided labeled image."; } this->Modified(); } template void mitk::LabelSetImage::InitializeByLabeledImageProcessing(LabelSetImageType *labelSetImage, ImageType *image) { typedef itk::ImageRegionConstIteratorWithIndex SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; TargetIteratorType targetIter(labelSetImage, labelSetImage->GetRequestedRegion()); targetIter.GoToBegin(); SourceIteratorType sourceIter(image, image->GetRequestedRegion()); sourceIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { auto sourceValue = static_cast(sourceIter.Get()); targetIter.Set(sourceValue); if (LabelSetImage::UnlabeledLabelValue!=sourceValue && !this->ExistLabel(sourceValue)) { std::stringstream name; name << "object-" << sourceValue; double rgba[4]; m_LabelSetContainer[this->GetActiveLayer()]->GetLookupTable()->GetTableValue(sourceValue, rgba); mitk::Color color; color.SetRed(rgba[0]); color.SetGreen(rgba[1]); color.SetBlue(rgba[2]); auto label = mitk::Label::New(); label->SetName(name.str().c_str()); label->SetColor(color); label->SetOpacity(rgba[3]); label->SetValue(sourceValue); this->GetLabelSet()->AddLabel(label); if (GetActiveLabelSet()->GetNumberOfLabels() >= mitk::Label::MAX_LABEL_VALUE || sourceValue >= mitk::Label::MAX_LABEL_VALUE) this->AddLayer(); } ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::MaskStampProcessing(ImageType *itkImage, mitk::Image *mask, bool forceOverwrite) { typename ImageType::Pointer itkMask; mitk::CastToItkImage(mask, itkMask); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(itkMask, itkMask->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkImage, itkImage->GetLargestPossibleRegion()); targetIter.GoToBegin(); int activeLabel = this->GetActiveLabel(GetActiveLayer())->GetValue(); while (!sourceIter.IsAtEnd()) { PixelType sourceValue = sourceIter.Get(); PixelType targetValue = targetIter.Get(); if ((sourceValue != UnlabeledLabelValue) && - (forceOverwrite || !this->IsLabelLocked(targetValue))) // skip exterior and locked labels + (forceOverwrite || !this->IsLabelLocked(targetValue))) // skip unlabeled pixels and locked labels { targetIter.Set(activeLabel); } ++sourceIter; ++targetIter; } this->Modified(); } template void mitk::LabelSetImage::CalculateCenterOfMassProcessing(ImageType *itkImage, PixelType pixelValue, unsigned int layer) { if (ImageType::GetImageDimension() != 3) { return; } auto labelGeometryFilter = itk::LabelGeometryImageFilter::New(); labelGeometryFilter->SetInput(itkImage); labelGeometryFilter->Update(); auto centroid = labelGeometryFilter->GetCentroid(pixelValue); mitk::Point3D pos; pos[0] = centroid[0]; pos[1] = centroid[1]; pos[2] = centroid[2]; GetLabelSet(layer)->GetLabel(pixelValue)->SetCenterOfMassIndex(pos); this->GetSlicedGeometry()->IndexToWorld(pos, pos); // TODO: TimeGeometry? GetLabelSet(layer)->GetLabel(pixelValue)->SetCenterOfMassCoordinates(pos); } template void mitk::LabelSetImage::ClearBufferProcessing(ImageType *itkImage) { itkImage->FillBuffer(0); } template void mitk::LabelSetImage::LayerContainerToImageProcessing(itk::Image *target, unsigned int layer) { typedef itk::Image ImageType; typename ImageType::Pointer itkSource; // mitk::CastToItkImage(m_LayerContainer[layer], itkSource); itkSource = ImageToItkImage(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(itkSource, itkSource->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(target, target->GetLargestPossibleRegion()); targetIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { targetIter.Set(sourceIter.Get()); ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::ImageToLayerContainerProcessing(itk::Image *source, unsigned int layer) const { typedef itk::Image ImageType; typename ImageType::Pointer itkTarget; // mitk::CastToItkImage(m_LayerContainer[layer], itkTarget); itkTarget = ImageToItkImage(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(source, source->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkTarget, itkTarget->GetLargestPossibleRegion()); targetIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { targetIter.Set(sourceIter.Get()); ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::EraseLabelProcessing(ImageType *itkImage, PixelType pixelValue) { typedef itk::ImageRegionIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { PixelType value = iter.Get(); if (value == pixelValue) { iter.Set(0); } ++iter; } } template void mitk::LabelSetImage::MergeLabelProcessing(ImageType *itkImage, PixelType pixelValue, PixelType index) { typedef itk::ImageRegionIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { if (iter.Get() == index) { iter.Set(pixelValue); } ++iter; } } void mitk::LabelSetImage::OnLabelAdded(LabelValueType labelValue) { Label* label = nullptr; + unsigned int layerID = 0; for (; layerID < this->GetNumberOfLayers(); ++layerID) { label = this->GetLabel(labelValue, layerID); if (nullptr != label) break; } if (!label) mitkThrow() << "Wrong internal state. OnLabelAdded was triggered, but label cannot be found. Invalid label: " << labelValue; AddLabelToMap(labelValue, label, layerID); this->m_LabelAddedMessage.Send(labelValue); } -void mitk::LabelSetImage::AddLabelToMap(LabelValueType labelValue, mitk::Label* label, SpatialGroupIndexType groupID) +void mitk::LabelSetImage::AddLabelToMap(LabelValueType labelValue, mitk::Label* label, GroupIndexType groupID) { if (m_LabelMap.find(labelValue)!=m_LabelMap.end()) mitkThrow() << "Segmentation is in an invalid state: Label value collision. A label was added with a LabelValue already in use. LabelValue: " << labelValue; m_LabelMap[labelValue] = label; m_LabelToGroupMap[labelValue] = groupID; auto groupFinding = m_GroupToLabelMap.find(groupID); if (groupFinding == m_GroupToLabelMap.end()) { m_GroupToLabelMap[groupID] = { labelValue }; } else { m_GroupToLabelMap[groupID].push_back(labelValue); } } void mitk::LabelSetImage::OnLabelModified(LabelValueType labelValue) { this->m_LabelModifiedMessage.Send(labelValue); } void mitk::LabelSetImage::OnLabelRemoved(LabelValueType labelValue) { m_LabelMap.erase(labelValue); auto finding = m_LabelToGroupMap.find(labelValue); if (finding != m_LabelToGroupMap.end()) { auto labelsInGroup = m_GroupToLabelMap[finding->second]; auto labelFinding = std::find(labelsInGroup.begin(), labelsInGroup.end(),finding->second); if (labelFinding != labelsInGroup.end()) { labelsInGroup.erase(labelFinding); } m_LabelToGroupMap.erase(labelValue); } this->m_LabelRemovedMessage.Send(labelValue); } -void mitk::LabelSetImage::OnGroupAdded(SpatialGroupIndexType groupIndex) +void mitk::LabelSetImage::OnGroupAdded(GroupIndexType groupIndex) { this->m_GroupToLabelMap.insert(std::make_pair(groupIndex, LabelValueVectorType())); this->m_GroupAddedMessage.Send(groupIndex); } -void mitk::LabelSetImage::OnGroupModified(SpatialGroupIndexType groupIndex) +void mitk::LabelSetImage::OnGroupModified(GroupIndexType groupIndex) { this->m_GroupModifiedMessage.Send(groupIndex); } -void mitk::LabelSetImage::OnGroupRemoved(SpatialGroupIndexType groupIndex) +void mitk::LabelSetImage::OnGroupRemoved(GroupIndexType groupIndex) { this->ReinitMaps(); this->m_GroupRemovedMessage.Send(groupIndex); } // future implementation for T28524 -//bool mitk::LabelSetImage::ExistLabel(LabelValueType value, SpatialGroupIndexType groupIndex) const +//bool mitk::LabelSetImage::ExistLabel(LabelValueType value, GroupIndexType groupIndex) const //{ // auto finding = m_LabelToGroupMap.find(value); // if (m_LabelToGroupMap.end() != finding) // { // return finding->second == groupIndex; // } // return false; //} // -//bool mitk::LabelSetImage::ExistGroup(SpatialGroupIndexType index) const +//bool mitk::LabelSetImage::ExistGroup(GroupIndexType index) const //{ // return index < m_LabelSetContainer.size(); //} -bool mitk::LabelSetImage::ExistGroup(SpatialGroupIndexType index) const +bool mitk::LabelSetImage::ExistGroup(GroupIndexType index) const { return index < m_LabelSetContainer.size(); } bool mitk::LabelSetImage::IsLabeInGroup(LabelValueType value) const { - SpatialGroupIndexType dummy; + GroupIndexType dummy; return this->IsLabeInGroup(value, dummy); } -bool mitk::LabelSetImage::IsLabeInGroup(LabelValueType value, SpatialGroupIndexType& groupIndex) const +bool mitk::LabelSetImage::IsLabeInGroup(LabelValueType value, GroupIndexType& groupIndex) const { auto finding = m_LabelToGroupMap.find(value); if (m_LabelToGroupMap.end() != finding) { groupIndex = finding->second; return true; } return false; } -mitk::LabelSetImage::SpatialGroupIndexType mitk::LabelSetImage::GetGroupIndexOfLabel(LabelValueType value) const +mitk::LabelSetImage::GroupIndexType mitk::LabelSetImage::GetGroupIndexOfLabel(LabelValueType value) const { auto finding = m_LabelToGroupMap.find(value); if (m_LabelToGroupMap.end() == finding) { mitkThrow()<< "Cannot deduce group index. Passed label value does not exist. Value: "<< value; } return finding->second; } const mitk::Label* mitk::LabelSetImage::GetLabel(LabelValueType value) const { auto finding = m_LabelMap.find(value); if (m_LabelMap.end() != finding) { return finding->second; } return nullptr; }; mitk::Label* mitk::LabelSetImage::GetLabel(LabelValueType value) { auto finding = m_LabelMap.find(value); if (m_LabelMap.end() != finding) { return finding->second; } return nullptr; }; bool mitk::LabelSetImage::IsLabelLocked(LabelValueType value) const { if (value == UnlabeledLabelValue) { return m_UnlabeledLabelLock; } const auto label = this->GetLabel(value); return label->GetLocked(); } const mitk::LabelSetImage::ConstLabelVectorType mitk::LabelSetImage::GetLabels() const { ConstLabelVectorType result; for (auto [value, label] : m_LabelMap) { result.emplace_back(label); } return result; } const mitk::LabelSetImage::LabelVectorType mitk::LabelSetImage::GetLabels() { LabelVectorType result; for (auto [value, label] : m_LabelMap) { result.emplace_back(label); } return result; } -const mitk::LabelSetImage::ConstLabelVectorType mitk::LabelSetImage::GetLabelsInGroup(SpatialGroupIndexType index) const +const mitk::LabelSetImage::ConstLabelVectorType mitk::LabelSetImage::GetLabelsInGroup(GroupIndexType index) const { if (!this->ExistGroup(index)) { mitkThrow() << "Cannot get labels of an invalid group. Invalid group index: " << index; } mitk::LabelSetImage::ConstLabelVectorType result; const auto labellist = m_GroupToLabelMap.find(index)->second; for (const auto& labelvalue : labellist) { result.emplace_back(this->GetLabel(labelvalue)); } return result; } -const mitk::LabelSetImage::LabelVectorType mitk::LabelSetImage::GetLabelsInGroup(SpatialGroupIndexType index) +const mitk::LabelSetImage::LabelVectorType mitk::LabelSetImage::GetLabelsInGroup(GroupIndexType index) { if (!this->ExistGroup(index)) { mitkThrow() << "Cannot get labels of an invalid group. Invalid group index: " << index; } mitk::LabelSetImage::LabelVectorType result; const auto labellist = m_GroupToLabelMap[index]; for (const auto& labelvalue : labellist) { result.emplace_back(this->GetLabel(labelvalue)); } return result; } void mitk::LabelSetImage::ReinitMaps() { this->m_LabelMap.clear(); this->m_LabelToGroupMap.clear(); this->m_GroupToLabelMap.clear(); - for (SpatialGroupIndexType layerID = 0; layerID < this->GetNumberOfLayers(); ++layerID) + for (GroupIndexType layerID = 0; layerID < this->GetNumberOfLayers(); ++layerID) { auto labelSet = this->GetLabelSet(layerID); for (auto iter = labelSet->IteratorBegin(); iter != labelSet->IteratorEnd(); ++iter) { if (iter->first != UnlabeledLabelValue) { this->AddLabelToMap(iter->first, iter->second, layerID); } } } } bool mitk::Equal(const mitk::LabelSetImage &leftHandSide, const mitk::LabelSetImage &rightHandSide, ScalarType eps, bool verbose) { bool returnValue = true; /* LabelSetImage members */ MITK_INFO(verbose) << "--- LabelSetImage Equal ---"; // number layers returnValue = leftHandSide.GetNumberOfLayers() == rightHandSide.GetNumberOfLayers(); if (!returnValue) { MITK_INFO(verbose) << "Number of layers not equal."; return false; } // total number labels returnValue = leftHandSide.GetTotalNumberOfLabels() == rightHandSide.GetTotalNumberOfLabels(); if (!returnValue) { MITK_INFO(verbose) << "Total number of labels not equal."; return false; } // active layer returnValue = leftHandSide.GetActiveLayer() == rightHandSide.GetActiveLayer(); if (!returnValue) { MITK_INFO(verbose) << "Active layer not equal."; return false; } if (4 == leftHandSide.GetDimension()) { MITK_INFO(verbose) << "Can not compare image data for 4D images - skipping check."; } else { // working image data returnValue = mitk::Equal((const mitk::Image &)leftHandSide, (const mitk::Image &)rightHandSide, eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Working image data not equal."; return false; } } for (unsigned int layerIndex = 0; layerIndex < leftHandSide.GetNumberOfLayers(); layerIndex++) { if (4 == leftHandSide.GetDimension()) { MITK_INFO(verbose) << "Can not compare image data for 4D images - skipping check."; } else { // layer image data returnValue = mitk::Equal(*leftHandSide.GetLayerImage(layerIndex), *rightHandSide.GetLayerImage(layerIndex), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Layer image data not equal."; return false; } } // layer labelset data returnValue = mitk::Equal(*leftHandSide.GetLabelSet(layerIndex), *rightHandSide.GetLabelSet(layerIndex), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Layer labelset data not equal."; return false; } } return returnValue; } /** Functor class that implements the label transfer and is used in conjunction with the itk::BinaryFunctorImageFilter. * For details regarding the usage of the filter and the functor patterns, please see info of itk::BinaryFunctorImageFilter. */ template class LabelTransferFunctor { public: LabelTransferFunctor() {}; LabelTransferFunctor(const mitk::LabelSet* destinationLabelSet, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, mitk::Label::PixelType sourceLabel, mitk::Label::PixelType newDestinationLabel, mitk::MultiLabelSegmentation::MergeStyle mergeStyle, mitk::MultiLabelSegmentation::OverwriteStyle overwriteStyle) : m_DestinationLabelSet(destinationLabelSet), m_SourceBackground(sourceBackground), m_DestinationBackground(destinationBackground), m_DestinationBackgroundLocked(destinationBackgroundLocked), m_SourceLabel(sourceLabel), m_NewDestinationLabel(newDestinationLabel), m_MergeStyle(mergeStyle), m_OverwriteStyle(overwriteStyle) { }; ~LabelTransferFunctor() {}; bool operator!=(const LabelTransferFunctor& other)const { return !(*this == other); } bool operator==(const LabelTransferFunctor& other) const { return this->m_SourceBackground == other.m_SourceBackground && this->m_DestinationBackground == other.m_DestinationBackground && this->m_DestinationBackgroundLocked == other.m_DestinationBackgroundLocked && this->m_SourceLabel == other.m_SourceLabel && this->m_NewDestinationLabel == other.m_NewDestinationLabel && this->m_MergeStyle == other.m_MergeStyle && this->m_OverwriteStyle == other.m_OverwriteStyle && this->m_DestinationLabelSet == other.m_DestinationLabelSet; } LabelTransferFunctor& operator=(const LabelTransferFunctor& other) { this->m_DestinationLabelSet = other.m_DestinationLabelSet; this->m_SourceBackground = other.m_SourceBackground; this->m_DestinationBackground = other.m_DestinationBackground; this->m_DestinationBackgroundLocked = other.m_DestinationBackgroundLocked; this->m_SourceLabel = other.m_SourceLabel; this->m_NewDestinationLabel = other.m_NewDestinationLabel; this->m_MergeStyle = other.m_MergeStyle; this->m_OverwriteStyle = other.m_OverwriteStyle; return *this; } inline TOutputpixel operator()(const TDestinationPixel& existingDestinationValue, const TSourcePixel& existingSourceValue) { if (existingSourceValue == this->m_SourceLabel) { if (mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks == this->m_OverwriteStyle) { return this->m_NewDestinationLabel; } else { if (existingDestinationValue == m_DestinationBackground) { if (!m_DestinationBackgroundLocked) { return this->m_NewDestinationLabel; } } else { auto label = this->m_DestinationLabelSet->GetLabel(existingDestinationValue); if (nullptr == label || !label->GetLocked()) { return this->m_NewDestinationLabel; } } } } else if (mitk::MultiLabelSegmentation::MergeStyle::Replace == this->m_MergeStyle && existingSourceValue == this->m_SourceBackground && existingDestinationValue == this->m_NewDestinationLabel && (mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks == this->m_OverwriteStyle || !this->m_DestinationBackgroundLocked)) { return this->m_DestinationBackground; } return existingDestinationValue; } private: const mitk::LabelSet* m_DestinationLabelSet = nullptr; mitk::Label::PixelType m_SourceBackground = 0; mitk::Label::PixelType m_DestinationBackground = 0; bool m_DestinationBackgroundLocked = false; mitk::Label::PixelType m_SourceLabel = 1; mitk::Label::PixelType m_NewDestinationLabel = 1; mitk::MultiLabelSegmentation::MergeStyle m_MergeStyle = mitk::MultiLabelSegmentation::MergeStyle::Replace; mitk::MultiLabelSegmentation::OverwriteStyle m_OverwriteStyle = mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks; }; /**Helper function used by TransferLabelContentAtTimeStep to allow the templating over different image dimensions in conjunction of AccessFixedPixelTypeByItk_n.*/ template void TransferLabelContentAtTimeStepHelper(const itk::Image* itkSourceImage, mitk::Image* destinationImage, const mitk::LabelSet* destinationLabelSet, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, mitk::Label::PixelType sourceLabel, mitk::Label::PixelType newDestinationLabel, mitk::MultiLabelSegmentation::MergeStyle mergeStyle, mitk::MultiLabelSegmentation::OverwriteStyle overwriteStyle) { typedef itk::Image ContentImageType; typename ContentImageType::Pointer itkDestinationImage; mitk::CastToItkImage(destinationImage, itkDestinationImage); auto sourceRegion = itkSourceImage->GetLargestPossibleRegion(); auto relevantRegion = itkDestinationImage->GetLargestPossibleRegion(); bool overlapping = relevantRegion.Crop(sourceRegion); if (!overlapping) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage and destinationImage seem to have no overlapping image region."; } typedef LabelTransferFunctor LabelTransferFunctorType; typedef itk::BinaryFunctorImageFilter FilterType; LabelTransferFunctorType transferFunctor(destinationLabelSet, sourceBackground, destinationBackground, destinationBackgroundLocked, sourceLabel, newDestinationLabel, mergeStyle, overwriteStyle); auto transferFilter = FilterType::New(); transferFilter->SetFunctor(transferFunctor); transferFilter->InPlaceOn(); transferFilter->SetInput1(itkDestinationImage); transferFilter->SetInput2(itkSourceImage); transferFilter->GetOutput()->SetRequestedRegion(relevantRegion); transferFilter->Update(); } void mitk::TransferLabelContentAtTimeStep( const Image* sourceImage, Image* destinationImage, const mitk::LabelSet* destinationLabelSet, const TimeStepType timeStep, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, std::vector > labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; destinationImage must not be null."; } if (nullptr == destinationLabelSet) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; destinationLabelSet must not be null"; } if (sourceImage == destinationImage && labelMapping.size() > 1) { MITK_DEBUG << "Warning. Using TransferLabelContentAtTimeStep or TransferLabelContent with equal source and destination and more then on label to transfer, can lead to wrong results. Please see documentation and verify that the usage is OK."; } Image::ConstPointer sourceImageAtTimeStep = SelectImageByTimeStep(sourceImage, timeStep); Image::Pointer destinationImageAtTimeStep = SelectImageByTimeStep(destinationImage, timeStep); if (nullptr == sourceImageAtTimeStep) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage does not have the requested time step: " << timeStep; } if (nullptr == destinationImageAtTimeStep) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; destinationImage does not have the requested time step: " << timeStep; } for (const auto& [sourceLabel, newDestinationLabel] : labelMapping) { if (LabelSetImage::UnlabeledLabelValue!=newDestinationLabel && nullptr == destinationLabelSet->GetLabel(newDestinationLabel)) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep. Defined destination label does not exist in destinationImage. newDestinationLabel: " << newDestinationLabel; } AccessFixedPixelTypeByItk_n(sourceImageAtTimeStep, TransferLabelContentAtTimeStepHelper, (Label::PixelType), (destinationImageAtTimeStep, destinationLabelSet, sourceBackground, destinationBackground, destinationBackgroundLocked, sourceLabel, newDestinationLabel, mergeStyle, overwriteStlye)); destinationLabelSet->ModifyLabelEvent.Send(newDestinationLabel); } destinationImage->Modified(); } void mitk::TransferLabelContent( const Image* sourceImage, Image* destinationImage, const mitk::LabelSet* destinationLabelSet, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, std::vector > labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContent; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContent; destinationImage must not be null."; } const auto sourceTimeStepCount = sourceImage->GetTimeGeometry()->CountTimeSteps(); if (sourceTimeStepCount != destinationImage->GetTimeGeometry()->CountTimeSteps()) { - mitkThrow() << "Invalid call of TransferLabelContent; images have no equal number of time steps."; + mitkThrow() << "Invalid call of TransferLabelContent; mismatch between images in number of time steps."; } for (mitk::TimeStepType i = 0; i < sourceTimeStepCount; ++i) { TransferLabelContentAtTimeStep(sourceImage, destinationImage, destinationLabelSet, i, sourceBackground, destinationBackground, destinationBackgroundLocked, labelMapping, mergeStyle, overwriteStlye); } } void mitk::TransferLabelContentAtTimeStep( const LabelSetImage* sourceImage, LabelSetImage* destinationImage, const TimeStepType timeStep, std::vector > labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage must not be null."; } const auto destinationLabelSet = destinationImage->GetLabelSet(destinationImage->GetActiveLayer()); for (const auto& mappingElement : labelMapping) { if (LabelSetImage::UnlabeledLabelValue != mappingElement.first && !sourceImage->ExistLabel(mappingElement.first, sourceImage->GetActiveLayer())) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep. Defined source label does not exist in sourceImage. SourceLabel: " << mappingElement.first; } } TransferLabelContentAtTimeStep(sourceImage, destinationImage, destinationLabelSet, timeStep, LabelSetImage::UnlabeledLabelValue, LabelSetImage::UnlabeledLabelValue, destinationImage->GetUnlabeledLabelLock(), labelMapping, mergeStyle, overwriteStlye); } void mitk::TransferLabelContent( const LabelSetImage* sourceImage, LabelSetImage* destinationImage, std::vector > labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContent; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContent; destinationImage must not be null."; } const auto sourceTimeStepCount = sourceImage->GetTimeGeometry()->CountTimeSteps(); if (sourceTimeStepCount != destinationImage->GetTimeGeometry()->CountTimeSteps()) { mitkThrow() << "Invalid call of TransferLabelContent; images have no equal number of time steps."; } for (mitk::TimeStepType i = 0; i < sourceTimeStepCount; ++i) { TransferLabelContentAtTimeStep(sourceImage, destinationImage, i, labelMapping, mergeStyle, overwriteStlye); } } diff --git a/Modules/Multilabel/mitkLabelSetImage.h b/Modules/Multilabel/mitkLabelSetImage.h index 341c7dd1af..1fa59f0d9f 100644 --- a/Modules/Multilabel/mitkLabelSetImage.h +++ b/Modules/Multilabel/mitkLabelSetImage.h @@ -1,671 +1,673 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkLabelSetImage_h #define mitkLabelSetImage_h #include #include #include namespace mitk { //##Documentation //## @brief LabelSetImage class for handling labels and layers in a segmentation session. //## //## Handles operations for adding, removing, erasing and editing labels and layers. //## @ingroup Data class MITKMULTILABEL_EXPORT LabelSetImage : public Image { public: mitkClassMacro(LabelSetImage, Image); itkNewMacro(Self); typedef mitk::Label::PixelType PixelType; /** * \brief BeforeChangeLayerEvent (e.g. used for GUI integration) * As soon as active labelset should be changed, the signal emits. * Emitted by SetActiveLayer(int layer); */ Message<> BeforeChangeLayerEvent; /** * \brief AfterchangeLayerEvent (e.g. used for GUI integration) * As soon as active labelset was changed, the signal emits. * Emitted by SetActiveLayer(int layer); */ Message<> AfterChangeLayerEvent; /////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////// // FUTURE MultiLabelSegmentation: // Section that already contains declarations used in the new class. // So this part of the interface will stay after refactoring towards // the new MultiLabelSegmentation class (see T28524). This section was introduced - // because some of the planed features are already urgently needed. + // because some of the planned features are already urgently needed. /////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////// - using SpatialGroupIndexType = std::size_t; + using GroupIndexType = std::size_t; using LabelValueType = mitk::Label::PixelType; const static LabelValueType UnlabeledLabelValue = 0; using ConstLabelVectorType = std::vector; using LabelVectorType = std::vector; using LabelValueVectorType = std::vector; /** * @brief Removes the label with the given value. - * The label is removed from the labelset of the given layer and - * the pixel values of the image below the label are reset. + * The label is removed from the labelset and + * the pixel with the value of the label are set to UnlabeledLabelValue. * @param labelValue the pixel value of the label to be removed */ void RemoveLabel(LabelValueType labelValue); /** * @brief Removes labels from the mitk::MultiLabelSegmentation. - * Calls mitk::MultiLabelSegmentation::EraseLabels() which also removes the labels from within the image. * If a label value does not exist, it will be ignored. - * @param labelValues a list of labels to be removed + * @param vectorOfLabelPixelValues a list of labels to be removed */ void RemoveLabels(const LabelValueVectorType& vectorOfLabelPixelValues); /** - * @brief Removes a complete spatial group with all its labels. - * @remark with removing a spatial group all spatial groups with greater index will be reindexed to - * close the gap. So externaly stored spatial group indeces may become invalid. - * @param spatial group index of the spatial group that should be removed. If the spatial group does not exist, an + * @brief Removes a whole group including all its labels. + * @remark with removing a group all groups with greater index will be reindexed to + * close the gap. Hence externaly stored spatial group indices may become invalid. + * @param group Group index of the spatial group that should be removed. If the spatial group does not exist, an * exception will be raised. - * @pre spatial group index must be valid. + * @pre group index must be valid. */ - void RemoveSpatialGroup(SpatialGroupIndexType spatielGroup); + void RemoveGroup(GroupIndexType group); //future declaration (T28524) currently conflicted with old declaration ///** // * \brief Returns true if the value exists in the MultiLabelSegmentation instance*/ //bool ExistLabel(LabelValueType value) const; ///** // * @brief Checks if a label belongs in a certain spatial group // * @param value the label value // * @param groupIndex Indexp of the spacial group which should be checked for the label // * @return true if the label exists otherwise false // */ - //bool ExistLabel(LabelValueType value, SpatialGroupIndexType groupIndex) const; + //bool ExistLabel(LabelValueType value, GroupIndexType groupIndex) const; /** - * \brief Returns true if the spatial group exists in the MultiLabelSegmentation instance.*/ - bool ExistGroup(SpatialGroupIndexType index) const; + * @brief Returns true if the spatial group exists in the MultiLabelSegmentation instance. + * + * @param index Group index of the group that should be checked for existance. + */ + bool ExistGroup(GroupIndexType index) const; bool IsLabeInGroup(LabelValueType value) const; - bool IsLabeInGroup(LabelValueType value, SpatialGroupIndexType& groupIndex) const; + bool IsLabeInGroup(LabelValueType value, GroupIndexType& groupIndex) const; /** Returns the group id of the based label value. * @pre label value must exists. */ - SpatialGroupIndexType GetGroupIndexOfLabel(LabelValueType value) const; + GroupIndexType GetGroupIndexOfLabel(LabelValueType value) const; /** * @brief Returns the mitk::Label with the given value. * @param value the pixel value of the label * @return the mitk::Label if available otherwise nullptr */ const mitk::Label* GetLabel(LabelValueType value) const; mitk::Label* GetLabel(LabelValueType value); /** Returns the lock state of the label (including UnlabeledLabel value). @pre Requested label does exist.*/ bool IsLabelLocked(LabelValueType value) const; /** Returns a vector with all labels currently defined in the MultiLabelSegmentation instance.*/ const ConstLabelVectorType GetLabels() const; const LabelVectorType GetLabels(); /** * @brief Returns a vector of all labels located on the specified group. * @param index the index of the group for which the vector of labels should be retrieved. * If an invalid index is passed an exception will be raised. * @return the respective vector of labels. * @pre group index must exist. */ - const ConstLabelVectorType GetLabelsInGroup(SpatialGroupIndexType index) const; - const LabelVectorType GetLabelsInGroup(SpatialGroupIndexType index); + const ConstLabelVectorType GetLabelsInGroup(GroupIndexType index) const; + const LabelVectorType GetLabelsInGroup(GroupIndexType index); itkGetConstMacro(UnlabeledLabelLock, bool); itkSetMacro(UnlabeledLabelLock, bool); itkBooleanMacro(UnlabeledLabelLock); //////////////////////////////////////////////////////////////////// //Message slots that allow to react to changes in an instance using LabelEventType = Message1; using LabelsEventType = Message1; - using GroupEventType = Message1; + using GroupEventType = Message1; /** * \brief LabelAdded is emitted whenever a new label has been added. * * Observers should register to this event by calling this->AddLabelAddedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been added to the MultiLabelSegmentation. * Observers should unregister by calling this->RemoveLabelAddedListener(myObject, MyObject::MyMethod). * The registered method will be called with the label value of the added label. * @remark the usage of the message object is thread safe. */ mitkNewMessage1Macro(LabelAdded, LabelValueType); /** * \brief LabelModified is emitted whenever a label has been modified. * * A label is modified if either its pixel content was changed, its spatial group or the label instance * information. * If you just want to get notified at the end of a MultiLabelSegmentation instance manipulation in the * case that at least one label was modified (e.g. to avoid getting a signal for each label * individually), use LabelsChanged instead. * Observers should register to this event by calling this->AddLabelModifiedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been added to the MultiLabelSegmentation. * Observers should unregister by calling this->RemoveLabelModifiedListener(myObject, MyObject::MyMethod). * The registered method will be called with the label value of the modified label. * @remark the usage of the message object is thread safe. */ mitkNewMessage1Macro(LabelModified, LabelValueType); /** * \brief LabelRemoved is emitted whenever a label has been removed. * * Observers should register to this event by calling this->AddLabelRemovedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been added to the MultiLabelSegmentation. * Observers should unregister by calling this->RemoveLabelRemovedListener(myObject, MyObject::MyMethod). * The registered method will be called with the label value of the removed label.* * @remark the usage of the message object is thread safe. */ mitkNewMessage1Macro(LabelRemoved, LabelValueType); /** * \brief LabelsChanged is emitted when labels are changed (added, removed, modified). * * In difference to the other label events LabelsChanged is send only *one time* after the modification of the * MultiLableImage instance is finished. So e.g. even if 4 labels are changed by a merge operation, this event will - * only be sent one time (compared to LabelRemoved or LabelModified). + * only be sent once (compared to LabelRemoved or LabelModified). * Observers should register to this event by calling myMultiLabelSegmentation->AddLabelsChangedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been removed from the MultiLabelSegmentation. * Observers should unregister by calling myMultiLabelSegmentation->RemoveLabelsChangedListener(myObject, * MyObject::MyMethod). * The registered method will be called with the vector of label values of the modified labels.* * @remark the usage of the message object is thread safe. */ mitkNewMessage1Macro(LabelsChanged, LabelValueVectorType); /** * \brief GroupAdded is emitted whenever a new group has been added. * * Observers should register to this event by calling this->AddGroupAddedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new group has been added to the MultiLabelSegmentation. * Observers should unregister by calling this->RemoveGroupAddedListener(myObject, MyObject::MyMethod). * The registered method will be called with the group index of the added group. * @remark the usage of the message object is thread safe. */ - mitkNewMessage1Macro(GroupAdded, SpatialGroupIndexType); + mitkNewMessage1Macro(GroupAdded, GroupIndexType); /** * \brief GroupModified is emitted whenever a group has been modified. * * A group is modified if the set of labels associated with it are changed or the group's meta data. * Observers should register to this event by calling this->AddGroupModifiedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been added to the MultiLabelSegmentation. * Observers should unregister by calling this->RemoveGroupModifiedListener(myObject, MyObject::MyMethod). * The registered method will be called with the group index of the added group. * @remark the usage of the message object is thread safe. */ - mitkNewMessage1Macro(GroupModified, SpatialGroupIndexType); + mitkNewMessage1Macro(GroupModified, GroupIndexType); /** * \brief GroupRemoved is emitted whenever a label has been removed. * * Observers should register to this event by calling this->AddGroupRemovedListener(myObject, * MyObject::MyMethod). * After registering, myObject->MyMethod() will be called every time a new label has been added to the MultiLabelSegmentation. * Observers should unregister by calling this->RemoveGroupRemovedListener(myObject, MyObject::MyMethod). * The registered method will be called with the group index of the removed group.* * @remark the usage of the message object is thread safe. */ - mitkNewMessage1Macro(GroupRemoved, SpatialGroupIndexType); + mitkNewMessage1Macro(GroupRemoved, GroupIndexType); protected: void OnLabelAdded(LabelValueType labelValue); - void AddLabelToMap(LabelValueType labelValue, mitk::Label* label, SpatialGroupIndexType groupID); + void AddLabelToMap(LabelValueType labelValue, mitk::Label* label, GroupIndexType groupID); void OnLabelModified(LabelValueType labelValue); void OnLabelRemoved(LabelValueType labelValue); - void OnGroupAdded(SpatialGroupIndexType groupIndex); - void OnGroupModified(SpatialGroupIndexType groupIndex); - void OnGroupRemoved(SpatialGroupIndexType groupIndex); + void OnGroupAdded(GroupIndexType groupIndex); + void OnGroupModified(GroupIndexType groupIndex); + void OnGroupRemoved(GroupIndexType groupIndex); /** Reeinitalizes the internal maps based on the current layer/label content * of the instance. */ void ReinitMaps(); using LabelMapType = std::map; LabelMapType m_LabelMap; /**This type is internally used to track which label is currently * associated with which layer.*/ - using GroupToLabelMapType = std::map; + using GroupToLabelMapType = std::map; GroupToLabelMapType m_GroupToLabelMap; - using LabelToGroupMapType = std::map; + using LabelToGroupMapType = std::map; LabelToGroupMapType m_LabelToGroupMap; private: /** Indicates if the MultiLabelSegmentation allows to overwrite unlabeled pixels in normal pixel manipulation operations (e.g. TransferLabelConent).*/ bool m_UnlabeledLabelLock; public: /** * \brief */ void UpdateCenterOfMass(PixelType pixelValue); /////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////// // END FUTURE MultiLabelSegmentation /////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////// /** * @brief Initialize an empty mitk::LabelSetImage using the information * of an mitk::Image * @param image the image which is used for initializing the mitk::LabelSetImage */ using mitk::Image::Initialize; void Initialize(const mitk::Image *image) override; /** * \brief */ void ClearBuffer(); /** * @brief Merges the mitk::Label with a given target value with the active label * * @param pixelValue the value of the label that should be the new merged label * @param sourcePixelValue the value of the label that should be merged into the specified one * @param layer the layer in which the merge should be performed */ void MergeLabel(PixelType pixelValue, PixelType sourcePixelValue, unsigned int layer = 0); /** * @brief Merges a list of mitk::Labels with the mitk::Label that has a specific value * * @param pixelValue the value of the label that should be the new merged label * @param vectorOfSourcePixelValues the list of label values that should be merge into the specified one * @param layer the layer in which the merge should be performed */ void MergeLabels(PixelType pixelValue, const std::vector& vectorOfSourcePixelValues, unsigned int layer = 0); /** * \brief */ void UpdateCenterOfMass(PixelType pixelValue, unsigned int layer); /** * @brief Erases the label with the given value from the labelset image. * The label itself will not be erased from the respective mitk::LabelSet. In order to * remove the label itself use mitk::LabelSetImage::RemoveLabels() * @param pixelValue the pixel value of the label that will be erased from the labelset image */ void EraseLabel(PixelType pixelValue); /** * @brief Erases a list of labels with the given values from the labelset image. * @param VectorOfLabelPixelValues the list of pixel values of the labels * that will be erased from the labelset image */ void EraseLabels(const std::vector &VectorOfLabelPixelValues); /** * \brief Returns true if the value exists in one of the labelsets*/ //[[deprecated("Will be changed with T28524")]] DEPRECATED(bool ExistLabel(PixelType pixelValue) const); /** * @brief Checks if a label exists in a certain layer * @param pixelValue the label value * @param layer the layer in which should be searched for the label * @return true if the label exists otherwise false */ //[[deprecated("Will be changed with T28524")]] DEPRECATED(bool ExistLabel(PixelType pixelValue, unsigned int layer) const); /** * \brief Returns true if the labelset exists*/ //[[deprecated("Will be removed with T28524")]] DEPRECATED(bool ExistLabelSet(unsigned int layer) const); /** * @brief Returns the active label of a specific layer * @param layer the layer ID for which the active label should be returned * @return the active label of the specified layer */ //[[deprecated("Will be removed with T28524")]] DEPRECATED(mitk::Label *GetActiveLabel(unsigned int layer = 0)); //[[deprecated("Will be removed with T28524")]] DEPRECATED(const mitk::Label* GetActiveLabel(unsigned int layer = 0) const); /** * @brief Returns the mitk::Label with the given pixelValue and for the given layer * @param pixelValue the pixel value of the label * @param layer the layer in which the labels should be located * @return the mitk::Label if available otherwise nullptr */ mitk::Label *GetLabel(PixelType pixelValue, unsigned int layer) const; /** * @brief Returns the currently active mitk::LabelSet * @return the mitk::LabelSet of the active layer or nullptr if non is present */ //[[deprecated ("Will be removed with T28524")]] DEPRECATED(mitk::LabelSet *GetActiveLabelSet()); //[[deprecated("Will be removed with T28524")]] DEPRECATED(const mitk::LabelSet* GetActiveLabelSet() const); /** * @brief Gets the mitk::LabelSet for the given layer * @param layer the layer for which the mitk::LabelSet should be retrieved * @return the respective mitk::LabelSet or nullptr if non exists for the given layer */ mitk::LabelSet *GetLabelSet(unsigned int layer = 0); const mitk::LabelSet *GetLabelSet(unsigned int layer = 0) const; /** * @brief Gets the ID of the currently active layer * @return the ID of the active layer */ unsigned int GetActiveLayer() const; /** * @brief Get the number of all existing mitk::Labels for a given layer * @param layer the layer ID for which the active mitk::Labels should be retrieved * @return the number of all existing mitk::Labels for the given layer */ unsigned int GetNumberOfLabels(unsigned int layer = 0) const; /** * @brief Returns the number of all labels summed up across all layers * @return the overall number of labels across all layers */ unsigned int GetTotalNumberOfLabels() const; // This function will need to be ported to an external class // it requires knowledge of pixeltype and dimension and includes // too much algorithm to be sensibly part of a data class ///** // * \brief */ // void SurfaceStamp(mitk::Surface* surface, bool forceOverwrite); /** * \brief */ mitk::Image::Pointer CreateLabelMask(PixelType index, bool useActiveLayer = true, unsigned int layer = 0); /** * @brief Initialize a new mitk::LabelSetImage by an given image. * For all distinct pixel values of the parameter image new labels will * be created. If the number of distinct pixel values exceeds mitk::Label::MAX_LABEL_VALUE * a new layer will be created * @param image the image which is used for initialization */ void InitializeByLabeledImage(mitk::Image::Pointer image); /** * \brief */ void MaskStamp(mitk::Image *mask, bool forceOverwrite); /** * \brief */ void SetActiveLayer(unsigned int layer); /** * \brief */ unsigned int GetNumberOfLayers() const; /** * \brief Adds a new layer to the LabelSetImage. The new layer will be set as the active one. * \param labelSet a labelset that will be added to the new layer if provided * \return the layer ID of the new layer */ unsigned int AddLayer(mitk::LabelSet::Pointer labelSet = nullptr); /** * \brief Adds a layer based on a provided mitk::Image. * \param layerImage is added to the vector of label images * \param labelSet a labelset that will be added to the new layer if provided * \return the layer ID of the new layer */ unsigned int AddLayer(mitk::Image::Pointer layerImage, mitk::LabelSet::Pointer labelSet = nullptr); /** * \brief Add a cloned LabelSet to an existing layer * * Remark: The passed LabelSet instance will be cloned before added to ensure clear ownership * of the new LabelSet addition. * * This will replace an existing labelSet if one exists. Throws an exceptions if you are trying * to add a labelSet to a non-existing layer. * * If there are no labelSets for layers with an id less than layerIdx default ones will be added * for them. * * \param layerIdx The index of the layer the LabelSet should be added to * \param labelSet The LabelSet that should be added */ void AddLabelSetToLayer(const unsigned int layerIdx, const mitk::LabelSet* labelSet); /** * @brief Removes the active layer and the respective mitk::LabelSet and image information. * The new active layer is the one below, if exists */ void RemoveLayer(); /** * \brief */ mitk::Image *GetLayerImage(unsigned int layer); const mitk::Image *GetLayerImage(unsigned int layer) const; void OnLabelSetModified(); protected: mitkCloneMacro(Self); LabelSetImage(); LabelSetImage(const LabelSetImage &other); ~LabelSetImage() override; template void LayerContainerToImageProcessing(itk::Image *source, unsigned int layer); template void ImageToLayerContainerProcessing(itk::Image *source, unsigned int layer) const; template void CalculateCenterOfMassProcessing(ImageType *input, PixelType index, unsigned int layer); template void ClearBufferProcessing(ImageType *input); template void EraseLabelProcessing(ImageType *input, PixelType index); template void MergeLabelProcessing(ImageType *input, PixelType pixelValue, PixelType index); template void MaskStampProcessing(ImageType *input, mitk::Image *mask, bool forceOverwrite); template void InitializeByLabeledImageProcessing(LabelSetImageType *input, ImageType *other); /** helper needed for ensuring unique values in all layers until the refactoring is done. returns a sorted list of all labels.*/ LabelValueVectorType GetUsedLabelValues() const; //helper function that ensures void RegisterLabelSet(mitk::LabelSet* ls); void ReleaseLabelSet(mitk::LabelSet* ls); std::vector m_LabelSetContainer; std::vector m_LayerContainer; int m_ActiveLayer; bool m_activeLayerInvalid; }; /** * @brief Equal A function comparing two label set images for beeing equal in meta- and imagedata * * @ingroup MITKTestingAPI * * Following aspects are tested for equality: * - LabelSetImage members * - working image data * - layer image data * - labels in label set * * @param rightHandSide An image to be compared * @param leftHandSide An image to be compared * @param eps Tolerance for comparison. You can use mitk::eps in most cases. * @param verbose Flag indicating if the user wants detailed console output or not. * @return true, if all subsequent comparisons are true, false otherwise */ MITKMULTILABEL_EXPORT bool Equal(const mitk::LabelSetImage &leftHandSide, const mitk::LabelSetImage &rightHandSide, ScalarType eps, bool verbose); /** temporery namespace that is used until the new class MultiLabelSegmentation is introduced. It allows to already introduce/use some upcoming definitions, while refactoring code.*/ namespace MultiLabelSegmentation { enum class MergeStyle { Replace, //The old label content of a lable value will be replaced by its new label content. //Therefore pixels that are labeled might become unlabeled again. //(This means that a lock of the value is also ignored). Merge //The union of old and new label content will be generated. }; enum class OverwriteStyle { RegardLocks, //Locked labels in the same spatial group will not be overwritten/changed. IgnoreLocks //Label locks in the same spatial group will be ignored, so these labels might be changed. }; } /**Helper function that transfers pixels of the specified source label from source image to the destination image by using a specified destination label for a specific timestep. Function processes the whole image volume of the specified time step. @remark in its current implementation the function only transfers contents of the active layer of the passed LabelSetImages. @remark the function assumes that it is only called with source and destination image of same geometry. - @remark CAUTION: The function is not save, if sourceImage and destinationImage are the same instance and you transfer more then one - label, because the changes are made inplace for performance reasons but not in one pass. If a mapped value A equals a "old value" - that is later in the mapping, one ends up with a wrong transfer, as a pixel would be first mapped to A and then latter again, because + @remark CAUTION: The function is not save if sourceImage and destinationImage are the same instance and more than one label is transferred, + because the changes are made in-place for performance reasons in multiple passes. If a mapped value A equals an "old value" + that occurs later in the mapping, one ends up with a wrong transfer, as a pixel would be first mapped to A and then later again, because it is also an "old" value in the mapping table. @param sourceImage Pointer to the LabelSetImage which active layer should be used as source for the transfer. @param destinationImage Pointer to the LabelSetImage which active layer should be used as destination for the transfer. @param labelMapping Map that encodes the mappings of all label pixel transfers that should be done. First element is the label in the source image. The second element is the label that transferred pixels should become in the destination image. The order in which the labels will be transfered is the same order of elements in the labelMapping. If you use a heterogeneous label mapping (e.g. (1,2); so changing the label while transfering), keep in mind that for the MergeStyle and OverwriteStyle only the destination label (second element) is relevant (e.g. what should be altered with MergeStyle Replace). @param mergeStyle indicates how the transfer should be done (merge or replace). For more details see documentation of MultiLabelSegmentation::MergeStyle. @param overwriteStlye indicates if label locks in the destination image should be regarded or not. For more details see documentation of MultiLabelSegmentation::OverwriteStyle. @param timeStep indicate the time step that should be transferred. @pre sourceImage and destinationImage must be valid @pre sourceImage and destinationImage must contain the indicated timeStep @pre sourceImage must contain all indicated sourceLabels in its active layer. @pre destinationImage must contain all indicated destinationLabels in its active layer.*/ MITKMULTILABEL_EXPORT void TransferLabelContentAtTimeStep(const LabelSetImage* sourceImage, LabelSetImage* destinationImage, const TimeStepType timeStep, std::vector > labelMapping = { {1,1} }, MultiLabelSegmentation::MergeStyle mergeStyle = MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle overwriteStlye = MultiLabelSegmentation::OverwriteStyle::RegardLocks); /**Helper function that transfers pixels of the specified source label from source image to the destination image by using a specified destination label. Function processes the whole image volume for all time steps. For more details please see TransferLabelContentAtTimeStep for LabelSetImages. @sa TransferLabelContentAtTimeStep*/ MITKMULTILABEL_EXPORT void TransferLabelContent(const LabelSetImage* sourceImage, LabelSetImage* destinationImage, std::vector > labelMapping = { {1,1} }, MultiLabelSegmentation::MergeStyle mergeStyle = MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle overwriteStlye = MultiLabelSegmentation::OverwriteStyle::RegardLocks); /**Helper function that transfers pixels of the specified source label from source image to the destination image by using a specified destination label for a specific timestep. Function processes the whole image volume of the specified time step. @remark the function assumes that it is only called with source and destination image of same geometry. @remark CAUTION: The function is not save, if sourceImage and destinationImage are the same instance and you transfer more then one label, because the changes are made inplace for performance reasons but not in one pass. If a mapped value A equals a "old value" that is later in the mapping, one ends up with a wrong transfer, as a pixel would be first mapped to A and then latter again, because it is also an "old" value in the mapping table. @param sourceImage Pointer to the image that should be used as source for the transfer. @param sourceImage Pointer to the image that should be used as source for the transfer. @param destinationImage Pointer to the image that should be used as destination for the transfer. @param destinationLabelSet Pointer to the label set specifying labels and lock states in the destination image. Unkown pixel values in the destinationImage will be assumed to be unlocked. @param sourceBackground Value indicating the background in the source image. @param destinationBackground Value indicating the background in the destination image. @param destinationBackgroundLocked Value indicating the lock state of the background in the destination image. @param labelMapping Map that encodes the mappings of all label pixel transfers that should be done. First element is the label in the source image. The second element is the label that transferred pixels should become in the destination image. The order in which the labels will be transfered is the same order of elements in the labelMapping. If you use a heterogeneous label mapping (e.g. (1,2); so changing the label while transfering), keep in mind that for the MergeStyle and OverwriteStyle only the destination label (second element) is relevant (e.g. what should be altered with MergeStyle Replace). @param mergeStyle indicates how the transfer should be done (merge or replace). For more details see documentation of MultiLabelSegmentation::MergeStyle. @param overwriteStlye indicates if label locks in the destination image should be regarded or not. For more details see documentation of MultiLabelSegmentation::OverwriteStyle. @param timeStep indicate the time step that should be transferred. @pre sourceImage, destinationImage and destinationLabelSet must be valid @pre sourceImage and destinationImage must contain the indicated timeStep @pre destinationLabelSet must contain all indicated destinationLabels for mapping.*/ MITKMULTILABEL_EXPORT void TransferLabelContentAtTimeStep(const Image* sourceImage, Image* destinationImage, const mitk::LabelSet* destinationLabelSet, const TimeStepType timeStep, mitk::Label::PixelType sourceBackground = LabelSetImage::UnlabeledLabelValue, mitk::Label::PixelType destinationBackground = LabelSetImage::UnlabeledLabelValue, bool destinationBackgroundLocked = false, std::vector > labelMapping = { {1,1} }, MultiLabelSegmentation::MergeStyle mergeStyle = MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle overwriteStlye = MultiLabelSegmentation::OverwriteStyle::RegardLocks); /**Helper function that transfers pixels of the specified source label from source image to the destination image by using a specified destination label. Function processes the whole image volume for all time steps. For more details please see TransferLabelContentAtTimeStep. @sa TransferLabelContentAtTimeStep*/ MITKMULTILABEL_EXPORT void TransferLabelContent(const Image* sourceImage, Image* destinationImage, const mitk::LabelSet* destinationLabelSet, mitk::Label::PixelType sourceBackground = LabelSetImage::UnlabeledLabelValue, mitk::Label::PixelType destinationBackground = LabelSetImage::UnlabeledLabelValue, bool destinationBackgroundLocked = false, std::vector > labelMapping = { {1,1} }, MultiLabelSegmentation::MergeStyle mergeStyle = MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle overwriteStlye = MultiLabelSegmentation::OverwriteStyle::RegardLocks); } // namespace mitk #endif diff --git a/Modules/Multilabel/mitkLabelSetImageSurfaceStampFilter.cpp b/Modules/Multilabel/mitkLabelSetImageSurfaceStampFilter.cpp index eaf5f37121..31782c8154 100644 --- a/Modules/Multilabel/mitkLabelSetImageSurfaceStampFilter.cpp +++ b/Modules/Multilabel/mitkLabelSetImageSurfaceStampFilter.cpp @@ -1,108 +1,108 @@ /*============================================================================ 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 "mitkLabelSetImageSurfaceStampFilter.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include #include #include #include mitk::LabelSetImageSurfaceStampFilter::LabelSetImageSurfaceStampFilter() : m_ForceOverwrite(false) { this->SetNumberOfIndexedInputs(1); this->SetNumberOfRequiredInputs(1); } mitk::LabelSetImageSurfaceStampFilter::~LabelSetImageSurfaceStampFilter() { } void mitk::LabelSetImageSurfaceStampFilter::GenerateData() { // GenerateOutputInformation(); this->SetNthOutput(0, this->GetInput(0)); mitk::Image::Pointer inputImage = this->GetInput(0); if (m_Surface.IsNull()) { MITK_ERROR << "Input surface is nullptr."; return; } mitk::SurfaceToImageFilter::Pointer surfaceToImageFilter = mitk::SurfaceToImageFilter::New(); surfaceToImageFilter->MakeOutputBinaryOn(); surfaceToImageFilter->SetInput(m_Surface); surfaceToImageFilter->SetImage(inputImage); surfaceToImageFilter->Update(); mitk::Image::Pointer resultImage = surfaceToImageFilter->GetOutput(); AccessByItk_1(inputImage, ItkImageProcessing, resultImage); inputImage->DisconnectPipeline(); } template void mitk::LabelSetImageSurfaceStampFilter::ItkImageProcessing(itk::Image *itkImage, mitk::Image::Pointer resultImage) { typedef itk::Image ImageType; mitk::LabelSetImage::Pointer LabelSetInputImage = dynamic_cast(GetInput()); try { typename ImageType::Pointer itkResultImage = ImageType::New(); mitk::CastToItkImage(resultImage, itkResultImage); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(itkResultImage, itkResultImage->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkImage, itkImage->GetLargestPossibleRegion()); targetIter.GoToBegin(); int activeLabel = (LabelSetInputImage->GetActiveLabel(LabelSetInputImage->GetActiveLayer()))->GetValue(); while (!sourceIter.IsAtEnd()) { auto sourceValue = static_cast(sourceIter.Get()); auto targetValue = static_cast(targetIter.Get()); if ((sourceValue != LabelSetImage::UnlabeledLabelValue) && (m_ForceOverwrite || - !LabelSetInputImage->GetLabel(targetValue)->GetLocked())) // skip exterior and locked labels + !LabelSetInputImage->GetLabel(targetValue)->GetLocked())) // skip unlabled pixels and locked labels { targetIter.Set(activeLabel); } ++sourceIter; ++targetIter; } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->Modified(); } void mitk::LabelSetImageSurfaceStampFilter::GenerateOutputInformation() { mitk::Image::Pointer inputImage = (mitk::Image *)this->GetInput(); mitk::Image::Pointer output = this->GetOutput(); itkDebugMacro(<< "GenerateOutputInformation()"); if (inputImage.IsNull()) return; } diff --git a/Modules/Multilabel/mitkMultiLabelIOHelper.cpp b/Modules/Multilabel/mitkMultiLabelIOHelper.cpp index 6ba04a132e..407e89fa40 100644 --- a/Modules/Multilabel/mitkMultiLabelIOHelper.cpp +++ b/Modules/Multilabel/mitkMultiLabelIOHelper.cpp @@ -1,435 +1,435 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkMultiLabelIOHelper.h" #include "mitkLabelSetImage.h" #include #include "itkMetaDataDictionary.h" #include "itkMetaDataObject.h" #include namespace { std::string EnsureExtension(const std::string& filename) { const std::string extension = ".lsetp"; if (filename.size() < extension.size() || std::string::npos == filename.find(extension, filename.size() - extension.size())) return filename + extension; return filename; } } bool mitk::MultiLabelIOHelper::SaveLabelSetImagePreset(const std::string &presetFilename, const mitk::LabelSetImage *inputImage) { const auto filename = EnsureExtension(presetFilename); tinyxml2::XMLDocument xmlDocument; xmlDocument.InsertEndChild(xmlDocument.NewDeclaration()); auto *rootElement = xmlDocument.NewElement("LabelSetImagePreset"); rootElement->SetAttribute("layers", inputImage->GetNumberOfLayers()); xmlDocument.InsertEndChild(rootElement); for (unsigned int layerIndex = 0; layerIndex < inputImage->GetNumberOfLayers(); layerIndex++) { auto *layerElement = xmlDocument.NewElement("Layer"); layerElement->SetAttribute("index", layerIndex); layerElement->SetAttribute("labels", inputImage->GetNumberOfLabels(layerIndex)); rootElement->InsertEndChild(layerElement); for (unsigned int labelIndex = 0; labelIndex < inputImage->GetNumberOfLabels(layerIndex); labelIndex++) layerElement->InsertEndChild(MultiLabelIOHelper::GetLabelAsXMLElement(xmlDocument, inputImage->GetLabel(labelIndex, layerIndex))); } return tinyxml2::XML_SUCCESS == xmlDocument.SaveFile(filename.c_str()); } bool mitk::MultiLabelIOHelper::LoadLabelSetImagePreset(const std::string &presetFilename, mitk::LabelSetImage *inputImage) { if (nullptr == inputImage) return false; const auto filename = EnsureExtension(presetFilename); tinyxml2::XMLDocument xmlDocument; if (tinyxml2::XML_SUCCESS != xmlDocument.LoadFile(filename.c_str())) { MITK_WARN << "Label set preset file \"" << filename << "\" does not exist or cannot be opened"; return false; } auto *rootElement = xmlDocument.FirstChildElement("LabelSetImagePreset"); if (nullptr == rootElement) { MITK_WARN << "Not a valid Label set preset"; return false; } auto activeLayerBackup = inputImage->GetActiveLayer(); int numberOfLayers = 0; rootElement->QueryIntAttribute("layers", &numberOfLayers); auto* layerElement = rootElement->FirstChildElement("Layer"); if (nullptr == layerElement) { MITK_WARN << "Label set preset does not contain any layers"; return false; } for (int layerIndex = 0; layerIndex < numberOfLayers; layerIndex++) { int numberOfLabels = 0; layerElement->QueryIntAttribute("labels", &numberOfLabels); if (nullptr == inputImage->GetLabelSet(layerIndex)) { inputImage->AddLayer(); } else { inputImage->SetActiveLayer(layerIndex); } auto *labelElement = layerElement->FirstChildElement("Label"); if (nullptr == labelElement) continue; for (int labelIndex = 0; labelIndex < numberOfLabels; labelIndex++) { auto label = mitk::MultiLabelIOHelper::LoadLabelFromXMLDocument(labelElement); const auto labelValue = label->GetValue(); if (LabelSetImage::UnlabeledLabelValue != labelValue) { auto* labelSet = inputImage->GetLabelSet(layerIndex); auto* alreadyExistingLabel = labelSet->GetLabel(labelValue); if (nullptr != alreadyExistingLabel) { // Override existing label with label from preset alreadyExistingLabel->ConcatenatePropertyList(label); labelSet->UpdateLookupTable(labelValue); } else { labelSet->AddLabel(label); } } labelElement = labelElement->NextSiblingElement("Label"); if (nullptr == labelElement) continue; } layerElement = layerElement->NextSiblingElement("Layer"); if (nullptr == layerElement) continue; } inputImage->SetActiveLayer(activeLayerBackup); return true; } tinyxml2::XMLElement *mitk::MultiLabelIOHelper::GetLabelAsXMLElement(tinyxml2::XMLDocument &doc, Label *label) { auto *labelElem = doc.NewElement("Label"); if (nullptr != label) { // add XML contents const PropertyList::PropertyMap* propmap = label->GetMap(); for (auto iter = propmap->begin(); iter != propmap->end(); ++iter) { std::string key = iter->first; const BaseProperty* property = iter->second; auto* element = PropertyToXMLElement(doc, key, property); if (element) labelElem->InsertEndChild(element); } } return labelElem; } mitk::Label::Pointer mitk::MultiLabelIOHelper::LoadLabelFromXMLDocument(const tinyxml2::XMLElement *labelElem) { // reread auto *propElem = labelElem->FirstChildElement("property"); std::string name; mitk::BaseProperty::Pointer prop; mitk::Label::Pointer label = mitk::Label::New(); while (propElem) { MultiLabelIOHelper::PropertyFromXMLElement(name, prop, propElem); label->SetProperty(name, prop); propElem = propElem->NextSiblingElement("property"); } return label.GetPointer(); } tinyxml2::XMLElement *mitk::MultiLabelIOHelper::PropertyToXMLElement(tinyxml2::XMLDocument &doc, const std::string &key, const BaseProperty *property) { auto *keyelement = doc.NewElement("property"); keyelement->SetAttribute("key", key.c_str()); keyelement->SetAttribute("type", property->GetNameOfClass()); // construct name of serializer class std::string serializername(property->GetNameOfClass()); serializername += "Serializer"; std::list allSerializers = itk::ObjectFactoryBase::CreateAllInstance(serializername.c_str()); if (allSerializers.size() < 1) MITK_ERROR << "No serializer found for " << property->GetNameOfClass() << ". Skipping object"; if (allSerializers.size() > 1) MITK_WARN << "Multiple serializers found for " << property->GetNameOfClass() << "Using arbitrarily the first one."; for (auto iter = allSerializers.begin(); iter != allSerializers.end(); ++iter) { if (auto *serializer = dynamic_cast(iter->GetPointer())) { serializer->SetProperty(property); try { auto *valueelement = serializer->Serialize(doc); if (valueelement) keyelement->InsertEndChild(valueelement); } catch (std::exception &e) { MITK_ERROR << "Serializer " << serializer->GetNameOfClass() << " failed: " << e.what(); } break; } } return keyelement; } bool mitk::MultiLabelIOHelper::PropertyFromXMLElement(std::string &key, mitk::BaseProperty::Pointer &prop, const tinyxml2::XMLElement *elem) { const char* typeC = elem->Attribute("type"); std::string type = nullptr != typeC ? typeC : ""; const char* keyC = elem->Attribute("key"); key = nullptr != keyC ? keyC : ""; // construct name of serializer class std::string serializername(type); serializername += "Serializer"; std::list allSerializers = itk::ObjectFactoryBase::CreateAllInstance(serializername.c_str()); if (allSerializers.size() < 1) MITK_ERROR << "No serializer found for " << type << ". Skipping object"; if (allSerializers.size() > 1) MITK_WARN << "Multiple deserializers found for " << type << "Using arbitrarily the first one."; for (auto iter = allSerializers.begin(); iter != allSerializers.end(); ++iter) { if (auto *serializer = dynamic_cast(iter->GetPointer())) { try { prop = serializer->Deserialize(elem->FirstChildElement()); } catch (std::exception &e) { MITK_ERROR << "Deserializer " << serializer->GetNameOfClass() << " failed: " << e.what(); return false; } break; } } if (prop.IsNull()) return false; return true; } int mitk::MultiLabelIOHelper::GetIntByKey(const itk::MetaDataDictionary& dic, const std::string& str) { std::vector imgMetaKeys = dic.GetKeys(); std::vector::const_iterator itKey = imgMetaKeys.begin(); std::string metaString(""); for (; itKey != imgMetaKeys.end(); itKey++) { itk::ExposeMetaData(dic, *itKey, metaString); if (itKey->find(str.c_str()) != std::string::npos) { return atoi(metaString.c_str()); } } return 0; } std::string mitk::MultiLabelIOHelper::GetStringByKey(const itk::MetaDataDictionary& dic, const std::string& str) { std::vector imgMetaKeys = dic.GetKeys(); std::vector::const_iterator itKey = imgMetaKeys.begin(); std::string metaString(""); for (; itKey != imgMetaKeys.end(); itKey++) { itk::ExposeMetaData(dic, *itKey, metaString); if (itKey->find(str.c_str()) != std::string::npos) { return metaString; } } return metaString; } nlohmann::json mitk::MultiLabelIOHelper::SerializeMultLabelGroupsToJSON(const mitk::LabelSetImage* inputImage) { if (nullptr == inputImage) { mitkThrow() << "Invalid call of SerializeMultLabelGroupsToJSON. Passed image pointer is null."; } nlohmann::json result; - for (LabelSetImage::SpatialGroupIndexType i = 0; i < inputImage->GetNumberOfLayers(); i++) + for (LabelSetImage::GroupIndexType i = 0; i < inputImage->GetNumberOfLayers(); i++) { nlohmann::json jgroup; for (const auto& label : inputImage->GetLabelsInGroup(i)) { jgroup.emplace_back(SerializeLabelToJSON(label)); } result.emplace_back(jgroup); } return result; }; std::vector mitk::MultiLabelIOHelper::DeserializeMultLabelGroupsFromJSON(const nlohmann::json& listOfLabelSets) { std::vector result; for (const auto& jlabelsets : listOfLabelSets) { LabelSet::Pointer labelSet = LabelSet::New(); for (const auto& jlabel : jlabelsets) { auto label = DeserializeLabelFromJSON(jlabel); labelSet->AddLabel(label, false); } result.emplace_back(labelSet); } return result; } nlohmann::json mitk::MultiLabelIOHelper::SerializeLabelToJSON(const Label* label) { if (nullptr == label) { mitkThrow() << "Invalid call of GetLabelAsJSON. Passed label pointer is null."; } nlohmann::json j; j["name"] = label->GetName(); j["value"] = label->GetValue(); nlohmann::json jcolor; jcolor["type"] = "ColorProperty"; jcolor["value"] = {label->GetColor().GetRed(), label->GetColor().GetGreen(), label->GetColor().GetBlue() }; j["color"] = jcolor; j["locked"] = label->GetLocked(); j["opacity"] = label->GetOpacity(); j["visible"] = label->GetVisible(); return j; }; template bool GetValueFromJson(const nlohmann::json& labelJson, const std::string& key, TValueType& value) { if (labelJson.find(key) != labelJson.end()) { try { value = labelJson[key].get(); return true; } catch (...) { MITK_ERROR << "Unable to read label information from json. Value has wrong type. Failed key: " << key << "; invalid value: " << labelJson[key].dump(); throw; } } return false; } mitk::Label::Pointer mitk::MultiLabelIOHelper::DeserializeLabelFromJSON(const nlohmann::json& labelJson) { Label::Pointer resultLabel = Label::New(); std::string name = "Unkown label name"; GetValueFromJson(labelJson, "name", name); resultLabel->SetName(name); Label::PixelType value = 1; GetValueFromJson(labelJson, "value", value); resultLabel->SetValue(value); if (labelJson.find("color") != labelJson.end()) { auto jcolor = labelJson["color"]["value"]; Color color; color.SetRed(jcolor[0].get()); color.SetGreen(jcolor[1].get()); color.SetBlue(jcolor[2].get()); resultLabel->SetColor(color); } bool locked = false; if (GetValueFromJson(labelJson, "locked", locked)) resultLabel->SetLocked(locked); float opacity = 1.; if (GetValueFromJson(labelJson, "opacity", opacity)) resultLabel->SetOpacity(opacity); bool visible = true; if (GetValueFromJson(labelJson, "visible", visible)) resultLabel->SetVisible(visible); return resultLabel; } diff --git a/Modules/Segmentation/Interactions/mitkPickingTool.cpp b/Modules/Segmentation/Interactions/mitkPickingTool.cpp index 8591207b22..4a9fce5c55 100644 --- a/Modules/Segmentation/Interactions/mitkPickingTool.cpp +++ b/Modules/Segmentation/Interactions/mitkPickingTool.cpp @@ -1,257 +1,256 @@ /*============================================================================ 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 "mitkPickingTool.h" #include "mitkProperties.h" #include "mitkToolManager.h" #include "mitkInteractionPositionEvent.h" // us #include #include #include #include #include "mitkITKImageImport.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImageTimeSelector.h" #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, PickingTool, "PickingTool"); } mitk::PickingTool::PickingTool() : SegWithPreviewTool(false, "PressMoveReleaseAndPointSetting") { this->ResetsToEmptyPreviewOn(); } mitk::PickingTool::~PickingTool() { } const char **mitk::PickingTool::GetXPM() const { return nullptr; } const char *mitk::PickingTool::GetName() const { return "Picking"; } us::ModuleResource mitk::PickingTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("Picking.svg"); return resource; } void mitk::PickingTool::Activated() { Superclass::Activated(); m_PointSet = mitk::PointSet::New(); //ensure that the seed points are visible for all timepoints. dynamic_cast(m_PointSet->GetTimeGeometry())->SetStepDuration(std::numeric_limits::max()); m_PointSetNode = mitk::DataNode::New(); m_PointSetNode->SetData(m_PointSet); m_PointSetNode->SetName(std::string(this->GetName()) + "_PointSet"); m_PointSetNode->SetBoolProperty("helper object", true); m_PointSetNode->SetColor(0.0, 1.0, 0.0); m_PointSetNode->SetVisibility(true); this->GetDataStorage()->Add(m_PointSetNode, this->GetToolManager()->GetWorkingData(0)); } void mitk::PickingTool::Deactivated() { this->ClearSeeds(); // remove from data storage and disable interaction GetDataStorage()->Remove(m_PointSetNode); m_PointSetNode = nullptr; m_PointSet = nullptr; Superclass::Deactivated(); } void mitk::PickingTool::ConnectActionsAndFunctions() { CONNECT_FUNCTION("ShiftSecondaryButtonPressed", OnAddPoint); CONNECT_FUNCTION("ShiftPrimaryButtonPressed", OnAddPoint); CONNECT_FUNCTION("DeletePoint", OnDelete); } void mitk::PickingTool::OnAddPoint(StateMachineAction*, InteractionEvent* interactionEvent) { if (!this->IsUpdating() && m_PointSet.IsNotNull()) { const auto positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { m_PointSet->InsertPoint(m_PointSet->GetSize(), positionEvent->GetPositionInWorld()); this->UpdatePreview(); } } } void mitk::PickingTool::OnDelete(StateMachineAction*, InteractionEvent* /*interactionEvent*/) { if (!this->IsUpdating() && m_PointSet.IsNotNull()) { // delete last seed point if (this->m_PointSet->GetSize() > 0) { m_PointSet->RemovePointAtEnd(0); this->UpdatePreview(); } } } void mitk::PickingTool::ClearPicks() { this->ClearSeeds(); this->UpdatePreview(); } bool mitk::PickingTool::HasPicks() const { return this->m_PointSet.IsNotNull() && this->m_PointSet->GetSize()>0; } void mitk::PickingTool::ClearSeeds() { if (this->m_PointSet.IsNotNull()) { // renew pointset this->m_PointSet = mitk::PointSet::New(); //ensure that the seed points are visible for all timepoints. dynamic_cast(m_PointSet->GetTimeGeometry())->SetStepDuration(std::numeric_limits::max()); this->m_PointSetNode->SetData(this->m_PointSet); } } template void DoITKRegionGrowing(const itk::Image* oldSegImage, mitk::Image* segmentation, const mitk::PointSet* seedPoints, unsigned int timeStep, const mitk::BaseGeometry* inputGeometry, const mitk::Label::PixelType outputValue, const mitk::Label::PixelType backgroundValue, bool& emptyTimeStep) { typedef itk::Image InputImageType; typedef itk::Image OutputImageType; typedef typename InputImageType::IndexType IndexType; typedef itk::ConnectedThresholdImageFilter RegionGrowingFilterType; using IndexMapType = std::map < mitk::Label::PixelType, std::vector >; IndexMapType indexMap; // convert world coordinates to image indices for (auto pos = seedPoints->Begin(); pos != seedPoints->End(); ++pos) { IndexType seedIndex; inputGeometry->WorldToIndex(pos->Value(), seedIndex); const auto selectedLabel = oldSegImage->GetPixel(seedIndex); if (selectedLabel != backgroundValue) { indexMap[selectedLabel].push_back(seedIndex); } } typename OutputImageType::Pointer itkResultImage; try { bool first = true; typename RegionGrowingFilterType::Pointer regionGrower = RegionGrowingFilterType::New(); regionGrower->SetInput(oldSegImage); regionGrower->SetReplaceValue(outputValue); for (const auto& [label, indeces] : indexMap) { // perform region growing in desired segmented region regionGrower->ClearSeeds(); for (const auto& index : indeces) { regionGrower->AddSeed(index); } regionGrower->SetLower(label); regionGrower->SetUpper(label); regionGrower->Update(); if (first) { itkResultImage = regionGrower->GetOutput(); } else { typename itk::OrImageFilter::Pointer orFilter = itk::OrImageFilter::New(); orFilter->SetInput1(regionGrower->GetOutput()); orFilter->SetInput2(itkResultImage); orFilter->Update(); itkResultImage = orFilter->GetOutput(); } first = false; itkResultImage->DisconnectPipeline(); } } catch (const itk::ExceptionObject&) { return; // can't work } catch (...) { return; } if (itkResultImage.IsNotNull()) { segmentation->SetVolume((void*)(itkResultImage->GetPixelContainer()->GetBufferPointer()),timeStep); } emptyTimeStep = itkResultImage.IsNull(); } void mitk::PickingTool::DoUpdatePreview(const Image* /*inputAtTimeStep*/, const Image* oldSegAtTimeStep, LabelSetImage* previewImage, TimeStepType timeStep) { if (nullptr != oldSegAtTimeStep && nullptr != previewImage && m_PointSet.IsNotNull()) { bool emptyTimeStep = true; if (this->HasPicks()) { - Label::PixelType backgroundValue = mitk::LabelSetImage::UnlabeledLabelValue; - AccessFixedDimensionByItk_n(oldSegAtTimeStep, DoITKRegionGrowing, 3, (previewImage, this->m_PointSet, timeStep, oldSegAtTimeStep->GetGeometry(), this->GetUserDefinedActiveLabel(), backgroundValue, emptyTimeStep)); + AccessFixedDimensionByItk_n(oldSegAtTimeStep, DoITKRegionGrowing, 3, (previewImage, this->m_PointSet, timeStep, oldSegAtTimeStep->GetGeometry(), this->GetUserDefinedActiveLabel(), mitk::LabelSetImage::UnlabeledLabelValue, emptyTimeStep)); } if (emptyTimeStep) { this->ResetPreviewContentAtTimeStep(timeStep); } } } diff --git a/Modules/SegmentationUI/Qmitk/QmitkLabelColorItemDelegate.cpp b/Modules/SegmentationUI/Qmitk/QmitkLabelColorItemDelegate.cpp index e879f7d91b..5d7adbb801 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkLabelColorItemDelegate.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkLabelColorItemDelegate.cpp @@ -1,76 +1,69 @@ /*============================================================================ 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 "QmitkLabelColorItemDelegate.h" #include #include #include QmitkLabelColorItemDelegate::QmitkLabelColorItemDelegate(QObject * /*parent*/) { } void QmitkLabelColorItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QVariant data = index.data(Qt::EditRole); if (data.canConvert()) { QColor color = data.value(); painter->fillRect(option.rect, color); } else { QStyledItemDelegate::paint(painter, option, index); } } bool QmitkLabelColorItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &, const QModelIndex &index) { Q_ASSERT(event); Q_ASSERT(model); // make sure that the item is checkable Qt::ItemFlags flags = model->flags(index); if (!(flags & Qt::ItemIsEditable) || !(flags & Qt::ItemIsEnabled)) { return false; } // make sure that we have the right event type QMouseEvent* mouseEvent = dynamic_cast(event); - if (!mouseEvent) + if (nullptr == mouseEvent || mouseEvent->type() != QEvent::MouseButtonRelease || mouseEvent->button() != Qt::LeftButton) { return false; } - else - { - if (mouseEvent->type() != QEvent::MouseButtonRelease || mouseEvent->button() != Qt::LeftButton) - { - return false; - } - } - QColor oldcolor = index.data(Qt::EditRole).value(); - QColor newColor = QColorDialog::getColor(oldcolor, nullptr); + QColor oldColor = index.data(Qt::EditRole).value(); + QColor newColor = QColorDialog::getColor(oldColor, nullptr); if (newColor.isValid()) { return model->setData(index, QVariant(newColor), Qt::EditRole); } return false; }; diff --git a/Modules/SegmentationUI/Qmitk/QmitkLabelColorItemDelegate.h b/Modules/SegmentationUI/Qmitk/QmitkLabelColorItemDelegate.h index 01f6e4b143..015ef0dab8 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkLabelColorItemDelegate.h +++ b/Modules/SegmentationUI/Qmitk/QmitkLabelColorItemDelegate.h @@ -1,40 +1,41 @@ /*============================================================================ 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 QmitkLabelColorItemDelegate_h #define QmitkLabelColorItemDelegate_h #include -#include "MitkSegmentationUIExports.h" +#include -/** \class QmitkLabelColorItemDelegate -\brief An item delegate for rendering and editing label color in a QMultiLabelTreeView.*/ +/** +\brief An item delegate for rendering and editing label color in a QmitkMultiLabelTreeView. +*/ class MITKSEGMENTATIONUI_EXPORT QmitkLabelColorItemDelegate : public QStyledItemDelegate { Q_OBJECT public: /// /// Creates a new PropertyDelegate. /// explicit QmitkLabelColorItemDelegate(QObject *parent = nullptr); bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override; void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkLabelToggleItemDelegate.cpp b/Modules/SegmentationUI/Qmitk/QmitkLabelToggleItemDelegate.cpp index b3ec0bdb96..997f001082 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkLabelToggleItemDelegate.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkLabelToggleItemDelegate.cpp @@ -1,90 +1,82 @@ /*============================================================================ 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 "QmitkLabelToggleItemDelegate.h" #include #include -QmitkLabelToggleItemDelegate::QmitkLabelToggleItemDelegate(const QIcon& onIcon, const QIcon& offIcon, QObject * parent) : QStyledItemDelegate(parent), m_OnIcon(onIcon), m_OffIcon(offIcon) +QmitkLabelToggleItemDelegate::QmitkLabelToggleItemDelegate(const QIcon& onIcon, const QIcon& offIcon, QObject * parent) + :QStyledItemDelegate(parent), + m_OnIcon(onIcon), + m_OffIcon(offIcon) { } void QmitkLabelToggleItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QVariant data = index.data(Qt::EditRole); if (data.canConvert()) { if (data.toBool()) { m_OnIcon.paint(painter, option.rect); } else { m_OffIcon.paint(painter, option.rect); } } else { QStyledItemDelegate::paint(painter, option, index); } } QSize QmitkLabelToggleItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { auto defaultSize = QStyledItemDelegate::sizeHint(option, index); return QSize(defaultSize.height(), defaultSize.height()); } bool QmitkLabelToggleItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &, const QModelIndex &index) { Q_ASSERT(event); Q_ASSERT(model); // make sure that the item is checkable Qt::ItemFlags flags = model->flags(index); if (!(flags & Qt::ItemIsEditable) || !(flags & Qt::ItemIsEnabled)) { return false; } // make sure that we have the right event type QMouseEvent* mouseEvent = dynamic_cast(event); - if (!mouseEvent) + if (nullptr == mouseEvent || mouseEvent->type() != QEvent::MouseButtonRelease || mouseEvent->button() != Qt::LeftButton) { return false; } - else - { - if (mouseEvent->type() != QEvent::MouseButtonRelease || mouseEvent->button() != Qt::LeftButton) - { - return false; - } - } auto visVar = index.data(Qt::EditRole); - if (!visVar.isValid()) - { - return model->setData(index, QVariant(true), Qt::EditRole); - } - else - { - return model->setData(index, QVariant(!(visVar.toBool())), Qt::EditRole); - } + auto data = visVar.isValid() + ? QVariant(!(visVar.toBool())) + : QVariant(true); + return model->setData(index, data, Qt::EditRole); return false; }; diff --git a/Modules/SegmentationUI/Qmitk/QmitkLabelToggleItemDelegate.h b/Modules/SegmentationUI/Qmitk/QmitkLabelToggleItemDelegate.h index a69287826d..37d9aeb330 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkLabelToggleItemDelegate.h +++ b/Modules/SegmentationUI/Qmitk/QmitkLabelToggleItemDelegate.h @@ -1,47 +1,48 @@ /*============================================================================ 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 QmitkLabelToggleItemDelegate_h #define QmitkLabelToggleItemDelegate_h #include #include -#include "MitkSegmentationUIExports.h" +#include -/** \class QmitkLabelToggleItemDelegate -\brief An item delegate for rendering and editing properties that can be toggled (e.g. visibility).*/ +/** +\brief An item delegate for rendering and editing properties that can be toggled (e.g. visibility). +*/ class MITKSEGMENTATIONUI_EXPORT QmitkLabelToggleItemDelegate : public QStyledItemDelegate { Q_OBJECT public: /// /// Creates a new PropertyDelegate. /// explicit QmitkLabelToggleItemDelegate(const QIcon& onIcon, const QIcon& offIcon, QObject* parent = nullptr); bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index) override; void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override; protected: QIcon m_OnIcon; QIcon m_OffIcon; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp index 5ab9f16037..e44c5f9c71 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp @@ -1,1099 +1,1066 @@ /*============================================================================ 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 // mitk #include #include #include // Qmitk #include #include #include #include // Qt #include #include #include #include -#include "ui_QmitkMultiLabelInspectorControls.h" +#include QmitkMultiLabelInspector::QmitkMultiLabelInspector(QWidget* parent/* = nullptr*/) : QWidget(parent), m_Controls(new Ui::QmitkMultiLabelInspector) { m_Controls->setupUi(this); m_Model = new QmitkMultiLabelTreeModel(this); m_Controls->view->setModel(m_Model); m_ColorItemDelegate = new QmitkLabelColorItemDelegate(this); auto visibleIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")); auto invisibleIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/invisible.svg")); m_VisibilityItemDelegate = new QmitkLabelToggleItemDelegate(visibleIcon, invisibleIcon, this); auto lockIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/lock.svg")); auto unlockIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/unlock.svg")); m_LockItemDelegate = new QmitkLabelToggleItemDelegate(lockIcon, unlockIcon, this); - this->m_Controls->view->setItemDelegateForColumn(1, m_LockItemDelegate); - this->m_Controls->view->setItemDelegateForColumn(2, m_ColorItemDelegate); - this->m_Controls->view->setItemDelegateForColumn(3, m_VisibilityItemDelegate); + auto* view = this->m_Controls->view; + view->setItemDelegateForColumn(1, m_LockItemDelegate); + view->setItemDelegateForColumn(2, m_ColorItemDelegate); + view->setItemDelegateForColumn(3, m_VisibilityItemDelegate); - this->m_Controls->view->header()->setSectionResizeMode(0,QHeaderView::Stretch); - this->m_Controls->view->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents); - this->m_Controls->view->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents); - this->m_Controls->view->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents); - this->m_Controls->view->setContextMenuPolicy(Qt::CustomContextMenu); + auto* header = view->header(); + header->setSectionResizeMode(0,QHeaderView::Stretch); + header->setSectionResizeMode(1, QHeaderView::ResizeToContents); + header->setSectionResizeMode(2, QHeaderView::ResizeToContents); + header->setSectionResizeMode(3, QHeaderView::ResizeToContents); + view->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_Model, &QAbstractItemModel::modelReset, this, &QmitkMultiLabelInspector::OnModelReset); - connect(m_Controls->view->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), SLOT(OnChangeModelSelection(const QItemSelection&, const QItemSelection&))); - connect(m_Controls->view, &QAbstractItemView::customContextMenuRequested, this, &QmitkMultiLabelInspector::OnContextMenuRequested); - connect(m_Controls->view, &QAbstractItemView::doubleClicked, this, &QmitkMultiLabelInspector::OnItemDoubleClicked); + connect(view->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), SLOT(OnChangeModelSelection(const QItemSelection&, const QItemSelection&))); + connect(view, &QAbstractItemView::customContextMenuRequested, this, &QmitkMultiLabelInspector::OnContextMenuRequested); + connect(view, &QAbstractItemView::doubleClicked, this, &QmitkMultiLabelInspector::OnItemDoubleClicked); } QmitkMultiLabelInspector::~QmitkMultiLabelInspector() { delete m_Controls; } void QmitkMultiLabelInspector::Initialize() { m_LastValidSelectedLabels = {}; m_ModelManipulationOngoing = false; m_Model->SetSegmentation(m_Segmentation); m_Controls->view->expandAll(); m_LastValidSelectedLabels = {}; - if (m_Segmentation.IsNotNull() && !this->GetMultiSelectionMode() && m_Segmentation->GetTotalNumberOfLabels() > 0 ) - { //in singel selection mode, if at least one label exist select the first label of the mode. + + //in singel selection mode, if at least one label exist select the first label of the mode. + if (m_Segmentation.IsNotNull() && !this->GetMultiSelectionMode() && m_Segmentation->GetTotalNumberOfLabels() > 0) + { auto firstIndex = m_Model->FirstLabelInstanceIndex(QModelIndex()); auto labelVariant = firstIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); if (labelVariant.isValid()) { this->SetSelectedLabel(labelVariant.value()); m_Controls->view->selectionModel()->setCurrentIndex(firstIndex, QItemSelectionModel::NoUpdate); } } } void QmitkMultiLabelInspector::SetMultiSelectionMode(bool multiMode) { - if (multiMode) - { - m_Controls->view->setSelectionMode(QAbstractItemView::SelectionMode::MultiSelection); - } - else - { - m_Controls->view->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection); - } + m_Controls->view->setSelectionMode(multiMode + ? QAbstractItemView::SelectionMode::MultiSelection + : QAbstractItemView::SelectionMode::SingleSelection); } bool QmitkMultiLabelInspector::GetMultiSelectionMode() const { return QAbstractItemView::SelectionMode::MultiSelection == m_Controls->view->selectionMode(); } -void QmitkMultiLabelInspector::SetAllowVisibilityModification(bool vmod) +void QmitkMultiLabelInspector::SetAllowVisibilityModification(bool visibilityMod) { - m_AllowVisibilityModification = vmod; - this->m_Model->SetAllowVisibilityModification(vmod); + m_AllowVisibilityModification = visibilityMod; + this->m_Model->SetAllowVisibilityModification(visibilityMod); } void QmitkMultiLabelInspector::SetAllowLabelModification(bool labelMod) { m_AllowLabelModification = labelMod; } bool QmitkMultiLabelInspector::GetAllowVisibilityModification() const { return m_AllowVisibilityModification; } -void QmitkMultiLabelInspector::SetAllowLockModification(bool lmod) +void QmitkMultiLabelInspector::SetAllowLockModification(bool lockMod) { - m_AllowLockModification = lmod; - this->m_Model->SetAllowLockModification(lmod); + m_AllowLockModification = lockMod; + this->m_Model->SetAllowLockModification(lockMod); } bool QmitkMultiLabelInspector::GetAllowLockModification() const { return m_AllowLockModification; } bool QmitkMultiLabelInspector::GetAllowLabelModification() const { return m_AllowLabelModification; } void QmitkMultiLabelInspector::SetDefaultLabelNaming(bool defaultLabelNaming) { m_DefaultLabelNaming = defaultLabelNaming; } void QmitkMultiLabelInspector::SetMultiLabelSegmentation(mitk::LabelSetImage* segmentation) { if (segmentation != m_Segmentation) { m_Segmentation = segmentation; this->Initialize(); } } void QmitkMultiLabelInspector::OnModelReset() { m_LastValidSelectedLabels = {}; m_ModelManipulationOngoing = false; } bool EqualLabelSelections(const QmitkMultiLabelInspector::LabelValueVectorType& selection1, const QmitkMultiLabelInspector::LabelValueVectorType& selection2) { if (selection1.size() == selection2.size()) { // lambda to compare node pointer inside both lists auto lambda = [](mitk::LabelSetImage::LabelValueType lhs, mitk::LabelSetImage::LabelValueType rhs) { return lhs == rhs; }; return std::is_permutation(selection1.begin(), selection1.end(), selection2.begin(), selection2.end(), lambda); } return false; } void QmitkMultiLabelInspector::SetSelectedLabels(const LabelValueVectorType& selectedLabels) { - bool equal = EqualLabelSelections(this->GetSelectedLabels(), selectedLabels); - if (equal) + if (EqualLabelSelections(this->GetSelectedLabels(), selectedLabels)) { return; } this->UpdateSelectionModel(selectedLabels); m_LastValidSelectedLabels = selectedLabels; } void QmitkMultiLabelInspector::UpdateSelectionModel(const LabelValueVectorType& selectedLabels) { // create new selection by retrieving the corresponding indices of the labels QItemSelection newCurrentSelection; for (const auto& labelID : selectedLabels) { QModelIndexList matched = m_Model->match(m_Model->index(0, 0), QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole, QVariant(labelID), 1, Qt::MatchRecursive); if (!matched.empty()) { newCurrentSelection.select(matched.front(), matched.front()); } } m_Controls->view->selectionModel()->select(newCurrentSelection, QItemSelectionModel::ClearAndSelect|QItemSelectionModel::Current); } void QmitkMultiLabelInspector::SetSelectedLabel(mitk::LabelSetImage::LabelValueType selectedLabel) { this->SetSelectedLabels({ selectedLabel }); } QmitkMultiLabelInspector::LabelValueVectorType QmitkMultiLabelInspector::GetSelectedLabelsFromSelectionModel() const { LabelValueVectorType result; QModelIndexList selectedIndexes = m_Controls->view->selectionModel()->selectedIndexes(); for (const auto& index : qAsConst(selectedIndexes)) { QVariant qvariantDataNode = m_Model->data(index, QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); if (qvariantDataNode.canConvert()) { result.push_back(qvariantDataNode.value()); } } return result; } QmitkMultiLabelInspector::LabelValueVectorType QmitkMultiLabelInspector::GetSelectedLabels() const { return m_LastValidSelectedLabels; } mitk::Label* QmitkMultiLabelInspector::GetFirstSelectedLabelObject() const { - if (m_LastValidSelectedLabels.empty() || m_Segmentation.IsNull()) return nullptr; + if (m_LastValidSelectedLabels.empty() || m_Segmentation.IsNull()) + return nullptr; return m_Segmentation->GetLabel(m_LastValidSelectedLabels.front()); } void QmitkMultiLabelInspector::OnChangeModelSelection(const QItemSelection& /*selected*/, const QItemSelection& /*deselected*/) { if (!m_ModelManipulationOngoing) { auto internalSelection = GetSelectedLabelsFromSelectionModel(); if (internalSelection.empty()) { //empty selections are not allowed by UI interactions, there should always be at least on label selected. //but selections are e.g. also cleared if the model is updated (e.g. due to addition of labels) UpdateSelectionModel(m_LastValidSelectedLabels); } else { m_LastValidSelectedLabels = internalSelection; emit CurrentSelectionChanged(GetSelectedLabels()); } } } void QmitkMultiLabelInspector::WaitCursorOn() const { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); } void QmitkMultiLabelInspector::WaitCursorOff() const { this->RestoreOverrideCursor(); } void QmitkMultiLabelInspector::RestoreOverrideCursor() const { QApplication::restoreOverrideCursor(); } mitk::Label* QmitkMultiLabelInspector::GetCurrentLabel() const { auto currentIndex = this->m_Controls->view->currentIndex(); auto labelVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelDataRole); mitk::Label::Pointer currentIndexLabel = nullptr; if (labelVariant.isValid()) { auto uncastedLabel = labelVariant.value(); currentIndexLabel = static_cast(uncastedLabel); } return currentIndexLabel; } QmitkMultiLabelInspector::IndexLevelType QmitkMultiLabelInspector::GetCurrentLevelType() const { auto currentIndex = this->m_Controls->view->currentIndex(); auto labelInstanceVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceDataRole); auto labelVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelDataRole); if (labelInstanceVariant.isValid() ) { return IndexLevelType::LabelInstance; } else if (labelVariant.isValid()) { return IndexLevelType::LabelClass; } return IndexLevelType::Group; } QmitkMultiLabelInspector::LabelValueVectorType QmitkMultiLabelInspector::GetCurrentlyAffactedLabelInstances() const { - auto currentIndex = this->m_Controls->view->currentIndex(); - return this->m_Model->GetLabelsInSubTree(currentIndex); + auto currentIndex = m_Controls->view->currentIndex(); + return m_Model->GetLabelsInSubTree(currentIndex); } mitk::Label* QmitkMultiLabelInspector::AddNewLabelInstanceInternal(mitk::Label* templateLabel) { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of AddNewLabelInstance."; if (nullptr == templateLabel) mitkThrow() << "QmitkMultiLabelInspector is in an invalid state. AddNewLabelInstanceInternal was called with a non existing label as template"; auto groupID = m_Segmentation->GetGroupIndexOfLabel(templateLabel->GetValue()); auto group = m_Segmentation->GetLabelSet(groupID); m_ModelManipulationOngoing = true; auto newLabel = group->AddLabel(templateLabel, true); m_ModelManipulationOngoing = false; this->SetSelectedLabel(newLabel->GetValue()); auto index = m_Model->indexOfLabel(newLabel->GetValue()); if (index.isValid()) { m_Controls->view->expand(index.parent()); } else { mitkThrow() << "Segmentation or QmitkMultiLabelTreeModel is in an invalid state. Label is not present in the model after adding it to the segmentation. Label value: " << newLabel->GetValue(); } return newLabel; } mitk::Label* QmitkMultiLabelInspector::AddNewLabelInstance() { auto currentLabel = this->GetFirstSelectedLabelObject(); if (nullptr == currentLabel) return nullptr; return this->AddNewLabelInstanceInternal(currentLabel); } -mitk::Label* QmitkMultiLabelInspector::AddNewLabelInternal(const mitk::LabelSetImage::SpatialGroupIndexType& containingGroup) +mitk::Label* QmitkMultiLabelInspector::AddNewLabelInternal(const mitk::LabelSetImage::GroupIndexType& containingGroup) { mitk::Label::Pointer newLabel = mitk::LabelSetImageHelper::CreateNewLabel(m_Segmentation); if (!m_DefaultLabelNaming) { emit LabelRenameRequested(newLabel, false); } auto group = m_Segmentation->GetLabelSet(containingGroup); m_ModelManipulationOngoing = true; group->AddLabel(newLabel, false); m_ModelManipulationOngoing = false; this->SetSelectedLabel(newLabel->GetValue()); auto index = m_Model->indexOfLabel(newLabel->GetValue()); if (index.isValid()) { m_Controls->view->expand(index.parent()); } else { mitkThrow() << "Segmentation or QmitkMultiLabelTreeModel is in an invalid state. Label is not present in the model after adding it to the segmentation. Label value: " << newLabel->GetValue(); } return newLabel; } mitk::Label* QmitkMultiLabelInspector::AddNewLabel() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of AddNewLabel."; if (m_Segmentation.IsNull()) { return nullptr; } auto currentLabel = this->GetFirstSelectedLabelObject(); - mitk::LabelSetImage::SpatialGroupIndexType groupID = nullptr == currentLabel ? 0 : m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); + mitk::LabelSetImage::GroupIndexType groupID = nullptr != currentLabel + ? m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()) + : 0; return AddNewLabelInternal(groupID); } void QmitkMultiLabelInspector::RemoveLabel() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) { return; } - auto currentLabel = GetFirstSelectedLabelObject(); - QString question = "Do you really want to remove label \""; - question.append( - QString::fromStdString(currentLabel->GetName())); - question.append("\"?"); + auto currentLabel = this->GetFirstSelectedLabelObject(); + auto question = "Do you really want to remove label \"" + QString::fromStdString(currentLabel->GetName()) + "\"?"; - QMessageBox::StandardButton answerButton = - QMessageBox::question(this, "Remove label", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); + auto answerButton = QMessageBox::question(this, QString("Remove label"), question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->DeleteLabelInternal({ currentLabel->GetValue() }); } } void QmitkMultiLabelInspector::DeleteLabelInternal(const LabelValueVectorType& labelValues) { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of DeleteLabelInternal."; if (m_Segmentation.IsNull()) { return; } QVariant nextLabelVariant; this->WaitCursorOn(); m_ModelManipulationOngoing = true; for (auto labelValue : labelValues) { if (labelValue == labelValues.back()) { auto currentIndex = m_Model->indexOfLabel(labelValue); auto nextIndex = m_Model->ClosestLabelInstanceIndex(currentIndex); nextLabelVariant = nextIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); } m_Segmentation->RemoveLabel(labelValue); } m_ModelManipulationOngoing = false; this->WaitCursorOff(); if (nextLabelVariant.isValid()) { auto newLabelValue = nextLabelVariant.value(); this->SetSelectedLabel(newLabelValue); auto index = m_Model->indexOfLabel(newLabelValue); //we have to get index again, because it could have changed due to remove operation. if (index.isValid()) { m_Controls->view->expand(index.parent()); } else { mitkThrow() << "Segmentation or QmitkMultiLabelTreeModel is in an invalid state. Label is not present in the model after adding it to the segmentation. Label value: " << newLabelValue; } } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } mitk::Label* QmitkMultiLabelInspector::AddNewGroup() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of AddNewLabel."; if (m_Segmentation.IsNull()) { return nullptr; } - mitk::LabelSetImage::SpatialGroupIndexType groupID = 0; + mitk::LabelSetImage::GroupIndexType groupID = 0; mitk::Label* newLabel = nullptr; m_ModelManipulationOngoing = true; try { this->WaitCursorOn(); groupID = m_Segmentation->AddLayer(); this->WaitCursorOff(); - newLabel = AddNewLabelInternal(groupID); + newLabel = this->AddNewLabelInternal(groupID); } catch (mitk::Exception& e) { this->WaitCursorOff(); m_ModelManipulationOngoing = false; MITK_ERROR << "Exception caught: " << e.GetDescription(); - QMessageBox::information( - this, "Add group", "Could not add a new group. See error log for details.\n"); + QMessageBox::information(this, "Add group", "Could not add a new group. See error log for details."); } m_ModelManipulationOngoing = false; return newLabel; } -void QmitkMultiLabelInspector::RemoveGroupInternal(const mitk::LabelSetImage::SpatialGroupIndexType& groupID) +void QmitkMultiLabelInspector::RemoveGroupInternal(const mitk::LabelSetImage::GroupIndexType& groupID) { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) { return; } auto currentIndex = m_Model->indexOfGroup(groupID); auto nextIndex = m_Model->ClosestLabelInstanceIndex(currentIndex); auto labelVariant = nextIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); try { this->WaitCursorOn(); m_ModelManipulationOngoing = true; - m_Segmentation->RemoveSpatialGroup(groupID); + m_Segmentation->RemoveGroup(groupID); m_ModelManipulationOngoing = false; this->WaitCursorOff(); } catch (mitk::Exception& e) { m_ModelManipulationOngoing = false; this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); - QMessageBox::information( - this, "Delete group", "Could not delete the currently active group. See error log for details.\n"); + QMessageBox::information(this, "Delete group", "Could not delete the currently active group. See error log for details."); return; } if (labelVariant.isValid()) { auto newLabelValue = labelVariant.value(); this->SetSelectedLabel(newLabelValue); auto index = m_Model->indexOfLabel(newLabelValue); //we have to get index again, because it could have changed due to remove operation. if (index.isValid()) { m_Controls->view->expand(index.parent()); } else { mitkThrow() << "Segmentation or QmitkMultiLabelTreeModel is in an invalid state. Label is not present in the model after adding it to the segmentation. Label value: " << newLabelValue; } } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkMultiLabelInspector::RemoveGroup() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) { return; } QString question = "Do you really want to delete the group of the selected label with all labels?"; QMessageBox::StandardButton answerButton = QMessageBox::question( this, "Delete group", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton != QMessageBox::Yes) { return; } auto currentLabel = GetFirstSelectedLabelObject(); const auto currentGroup = m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); this->RemoveGroupInternal(currentGroup); } void QmitkMultiLabelInspector::OnDeleteGroup() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) { return; } auto currentIndex = this->m_Controls->view->currentIndex(); auto groupIDVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::GroupIDRole); if (groupIDVariant.isValid()) { - auto groupID = groupIDVariant.value(); + auto groupID = groupIDVariant.value(); QString question = "Do you really want to delete the current group with all labels?"; QMessageBox::StandardButton answerButton = QMessageBox::question( this, QString("Delete group ") + QString::number(groupID), question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton != QMessageBox::Yes) { return; } this->RemoveGroupInternal(groupID); } }; void QmitkMultiLabelInspector::OnContextMenuRequested(const QPoint& /*pos*/) { if (m_Segmentation.IsNull() || !this->isEnabled()) return; const auto indexLevel = this->GetCurrentLevelType(); if (IndexLevelType::Group == indexLevel) { QMenu* menu = new QMenu(this); if (m_AllowLabelModification) { QAction* addInstanceAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "&Add label", this); - addInstanceAction->setEnabled(true); QObject::connect(addInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnAddLabel); menu->addAction(addInstanceAction); QAction* removeAction = new QAction(QIcon(":/Qmitk/RemoveLabel.png"), "Delete group", this); - removeAction->setEnabled(true); QObject::connect(removeAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnDeleteGroup); menu->addAction(removeAction); } if (m_AllowLockModification) { menu->addSeparator(); QAction* lockAllAction = new QAction(QIcon(":/Qmitk/lock.png"), "Lock group", this); - lockAllAction->setEnabled(true); QObject::connect(lockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnLockAffectedLabels); menu->addAction(lockAllAction); QAction* unlockAllAction = new QAction(QIcon(":/Qmitk/unlock.png"), "Unlock group", this); - unlockAllAction->setEnabled(true); QObject::connect(unlockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnUnlockAffectedLabels); menu->addAction(unlockAllAction); } if (m_AllowVisibilityModification) { menu->addSeparator(); QAction* viewAllAction = new QAction(QIcon(":/Qmitk/visible.png"), "View group", this); - viewAllAction->setEnabled(true); QObject::connect(viewAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsVisible); menu->addAction(viewAllAction); QAction* hideAllAction = new QAction(QIcon(":/Qmitk/invisible.png"), "Hide group", this); - hideAllAction->setEnabled(true); QObject::connect(hideAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsInvisible); menu->addAction(hideAllAction); menu->addSeparator(); auto opacityAction = this->CreateOpacityAction(); - if (nullptr != opacityAction) menu->addAction(opacityAction); + if (nullptr != opacityAction) + menu->addAction(opacityAction); } menu->popup(QCursor::pos()); } else if (IndexLevelType::LabelClass == indexLevel) { QMenu* menu = new QMenu(this); if (m_AllowLabelModification) { QAction* addInstanceAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "Add label instance", this); - addInstanceAction->setEnabled(true); QObject::connect(addInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnAddLabelInstance); menu->addAction(addInstanceAction); QAction* renameAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "&Rename label class", this); - renameAction->setEnabled(true); QObject::connect(renameAction, SIGNAL(triggered(bool)), this, SLOT(OnRenameLabel(bool))); menu->addAction(renameAction); QAction* removeAction = new QAction(QIcon(":/Qmitk/RemoveLabel.png"), "&Delete label class", this); - removeAction->setEnabled(true); QObject::connect(removeAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnDeleteAffectedLabel); menu->addAction(removeAction); } if (m_AllowLockModification) { menu->addSeparator(); QAction* lockAllAction = new QAction(QIcon(":/Qmitk/lock.png"), "Lock label instances", this); - lockAllAction->setEnabled(true); QObject::connect(lockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnLockAffectedLabels); menu->addAction(lockAllAction); QAction* unlockAllAction = new QAction(QIcon(":/Qmitk/unlock.png"), "Unlock label instances", this); - unlockAllAction->setEnabled(true); QObject::connect(unlockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnUnlockAffectedLabels); menu->addAction(unlockAllAction); } if (m_AllowVisibilityModification) { menu->addSeparator(); QAction* viewAllAction = new QAction(QIcon(":/Qmitk/visible.png"), "View label instances", this); - viewAllAction->setEnabled(true); QObject::connect(viewAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsVisible); menu->addAction(viewAllAction); QAction* hideAllAction = new QAction(QIcon(":/Qmitk/invisible.png"), "Hide label instances", this); - hideAllAction->setEnabled(true); QObject::connect(hideAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsInvisible); menu->addAction(hideAllAction); QAction* viewOnlyAction = new QAction(QIcon(":/Qmitk/visible.png"), "View only", this); - viewOnlyAction->setEnabled(true); QObject::connect(viewOnlyAction, SIGNAL(triggered(bool)), this, SLOT(OnSetOnlyActiveLabelVisible(bool))); menu->addAction(viewOnlyAction); menu->addSeparator(); auto opacityAction = this->CreateOpacityAction(); - if (nullptr!=opacityAction) menu->addAction(opacityAction); + if (nullptr!=opacityAction) + menu->addAction(opacityAction); } menu->popup(QCursor::pos()); } else { auto selectedLabelValues = this->GetSelectedLabels(); if (selectedLabelValues.empty()) return; QMenu* menu = new QMenu(this); if (this->GetMultiSelectionMode() && selectedLabelValues.size() > 1) { QAction* mergeAction = new QAction(QIcon(":/Qmitk/MergeLabels.png"), "Merge selection on current label", this); - mergeAction->setEnabled(true); QObject::connect(mergeAction, SIGNAL(triggered(bool)), this, SLOT(OnMergeLabels(bool))); menu->addAction(mergeAction); QAction* removeLabelsAction = new QAction(QIcon(":/Qmitk/RemoveLabel.png"), "&Delete selected labels", this); - removeLabelsAction->setEnabled(true); QObject::connect(removeLabelsAction, SIGNAL(triggered(bool)), this, SLOT(OnRemoveLabels(bool))); menu->addAction(removeLabelsAction); QAction* clearLabelsAction = new QAction(QIcon(":/Qmitk/EraseLabel.png"), "&Clear selected labels", this); - clearLabelsAction->setEnabled(true); QObject::connect(clearLabelsAction, SIGNAL(triggered(bool)), this, SLOT(OnClearLabels(bool))); menu->addAction(clearLabelsAction); } else { if (m_AllowLabelModification) { QAction* addInstanceAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "&Add label instance...", this); - addInstanceAction->setEnabled(true); QObject::connect(addInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnAddLabelInstance); menu->addAction(addInstanceAction); QAction* renameAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "&Rename", this); - renameAction->setEnabled(true); QObject::connect(renameAction, SIGNAL(triggered(bool)), this, SLOT(OnRenameLabel(bool))); menu->addAction(renameAction); QAction* removeAction = new QAction(QIcon(":/Qmitk/RemoveLabel.png"), "&Delete", this); - removeAction->setEnabled(true); QObject::connect(removeAction, &QAction::triggered, this, &QmitkMultiLabelInspector::RemoveLabel); menu->addAction(removeAction); QAction* clearAction = new QAction(QIcon(":/Qmitk/EraseLabel.png"), "&Clear content", this); - clearAction->setEnabled(true); QObject::connect(clearAction, SIGNAL(triggered(bool)), this, SLOT(OnClearLabel(bool))); menu->addAction(clearAction); } if (m_AllowVisibilityModification) { menu->addSeparator(); QAction* viewOnlyAction = new QAction(QIcon(":/Qmitk/visible.png"), "View only", this); - viewOnlyAction->setEnabled(true); QObject::connect(viewOnlyAction, SIGNAL(triggered(bool)), this, SLOT(OnSetOnlyActiveLabelVisible(bool))); menu->addAction(viewOnlyAction); menu->addSeparator(); auto opacityAction = this->CreateOpacityAction(); - if (nullptr != opacityAction) menu->addAction(opacityAction); + if (nullptr != opacityAction) + menu->addAction(opacityAction); } } menu->popup(QCursor::pos()); } } QWidgetAction* QmitkMultiLabelInspector::CreateOpacityAction() { auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); std::vector relevantLabels; if (!relevantLabelValues.empty()) { //we assume here that all affacted label belong to one group. auto groupID = m_Segmentation->GetGroupIndexOfLabel(relevantLabelValues.front()); auto group = m_Segmentation->GetLabelSet(groupID); for (auto value : relevantLabelValues) { auto label = this->m_Segmentation->GetLabel(value); if (nullptr == label) mitkThrow() << "Invalid state. Internal model returned a label value that does not exist in segmentation. Invalid value:" << value; relevantLabels.emplace_back(label); } - QSlider* opacitySlider = new QSlider; + auto* opacitySlider = new QSlider; opacitySlider->setMinimum(0); opacitySlider->setMaximum(100); opacitySlider->setOrientation(Qt::Horizontal); auto opacity = relevantLabels.front()->GetOpacity(); opacitySlider->setValue(static_cast(opacity * 100)); auto segmentation = m_Segmentation; QObject::connect(opacitySlider, &QSlider::valueChanged, this, [segmentation, relevantLabels, group](const int value) { - float opacity = static_cast(value) / 100.0f; + auto opacity = static_cast(value) / 100.0f; for (auto label : relevantLabels) { label->SetOpacity(opacity); group->UpdateLookupTable(label->GetValue()); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } ); - QLabel* _OpacityLabel = new QLabel("Opacity: "); - QVBoxLayout* _OpacityWidgetLayout = new QVBoxLayout; - _OpacityWidgetLayout->setContentsMargins(4, 4, 4, 4); - _OpacityWidgetLayout->addWidget(_OpacityLabel); - _OpacityWidgetLayout->addWidget(opacitySlider); - QWidget* _OpacityWidget = new QWidget; - _OpacityWidget->setLayout(_OpacityWidgetLayout); + QLabel* opacityLabel = new QLabel("Opacity: "); + QVBoxLayout* opacityWidgetLayout = new QVBoxLayout; + opacityWidgetLayout->setContentsMargins(4, 4, 4, 4); + opacityWidgetLayout->addWidget(opacityLabel); + opacityWidgetLayout->addWidget(opacitySlider); + QWidget* opacityWidget = new QWidget; + opacityWidget->setLayout(opacityWidgetLayout); QWidgetAction* opacityAction = new QWidgetAction(this); - opacityAction->setDefaultWidget(_OpacityWidget); + opacityAction->setDefaultWidget(opacityWidget); return opacityAction; } return nullptr; } void QmitkMultiLabelInspector::OnClearLabels(bool /*value*/) { QString question = "Do you really want to clear the selected labels?"; QMessageBox::StandardButton answerButton = QMessageBox::question( this, "Clear selected labels", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); m_Segmentation->EraseLabels(this->GetSelectedLabels()); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnDeleteAffectedLabel() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) { return; } auto affectedLabels = GetCurrentlyAffactedLabelInstances(); auto currentLabel = m_Segmentation->GetLabel(affectedLabels.front()); - QString question = "Do you really want to delete all label instances of class \""; - question.append( - QString::fromStdString(currentLabel->GetName())); - question.append("\"?"); + QString question = "Do you really want to delete all label instances of class \"" + QString::fromStdString(currentLabel->GetName()) + "\"?"; QMessageBox::StandardButton answerButton = QMessageBox::question(this, "Delete label class", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->DeleteLabelInternal(affectedLabels); } } void QmitkMultiLabelInspector::OnRemoveLabels(bool /*value*/) { QString question = "Do you really want to remove the selected labels?"; QMessageBox::StandardButton answerButton = QMessageBox::question( this, "Remove selected labels", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); m_Segmentation->RemoveLabels(this->GetSelectedLabels()); this->WaitCursorOff(); } } void QmitkMultiLabelInspector::OnMergeLabels(bool /*value*/) { auto currentLabel = GetCurrentLabel(); - QString question = "Do you really want to merge selected labels into \""; - question.append( - QString::fromStdString(currentLabel->GetName())); - question.append("\"?"); + QString question = "Do you really want to merge selected labels into \"" + QString::fromStdString(currentLabel->GetName())+"\"?"; QMessageBox::StandardButton answerButton = QMessageBox::question( this, "Merge selected label", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); m_Segmentation->MergeLabels(currentLabel->GetValue(), this->GetSelectedLabels(), m_Segmentation->GetActiveLayer()); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnAddLabel() { auto currentIndex = this->m_Controls->view->currentIndex(); auto groupIDVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::GroupIDRole); if (groupIDVariant.isValid()) { - auto groupID = groupIDVariant.value(); + auto groupID = groupIDVariant.value(); this->AddNewLabelInternal(groupID); } } void QmitkMultiLabelInspector::OnAddLabelInstance() { auto currentLabel = this->GetCurrentLabel(); if (nullptr == currentLabel) return; this->AddNewLabelInstanceInternal(currentLabel); } void QmitkMultiLabelInspector::OnClearLabel(bool /*value*/) { auto currentLabel = GetFirstSelectedLabelObject(); - QString question = "Do you really want to clear the contents of label \""; - question.append( - QString::fromStdString(currentLabel->GetName())); - question.append("\"?"); + QString question = "Do you really want to clear the contents of label \"" + QString::fromStdString(currentLabel->GetName())+"\"?"; QMessageBox::StandardButton answerButton = QMessageBox::question(this, "Clear label", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); m_Segmentation->EraseLabel(currentLabel->GetValue()); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnRenameLabel(bool /*value*/) { auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); auto currentLabel = this->GetCurrentLabel(); emit LabelRenameRequested(currentLabel, true); //we assume here that all affacted label belong to one group. auto groupID = m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); auto group = m_Segmentation->GetLabelSet(groupID); for (auto value : relevantLabelValues) { if (value != currentLabel->GetValue()) { auto label = this->m_Segmentation->GetLabel(value); if (nullptr == label) mitkThrow() << "Invalid state. Internal model returned a label value that does not exist in segmentation. Invalid value:" << value; label->SetName(currentLabel->GetName()); label->SetColor(currentLabel->GetColor()); group->UpdateLookupTable(label->GetValue()); mitk::DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(label); } } } void QmitkMultiLabelInspector::SetLockOfAffectedLabels(bool locked) const { auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); if (!relevantLabelValues.empty()) { for (auto value : relevantLabelValues) { auto label = this->m_Segmentation->GetLabel(value); if (nullptr == label) mitkThrow() << "Invalid state. Internal model returned a label value that does not exist in segmentation. Invalid value:" << value; label->SetLocked(locked); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnUnlockAffectedLabels() { this->SetLockOfAffectedLabels(false); } void QmitkMultiLabelInspector::OnLockAffectedLabels() { this->SetLockOfAffectedLabels(true); } void QmitkMultiLabelInspector::SetVisibilityOfAffectedLabels(bool visible) const { auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); if (!relevantLabelValues.empty()) { //we assume here that all affacted label belong to one group. auto groupID = m_Segmentation->GetGroupIndexOfLabel(relevantLabelValues.front()); auto group = m_Segmentation->GetLabelSet(groupID); for (auto value : relevantLabelValues) { auto label = this->m_Segmentation->GetLabel(value); if (nullptr == label) mitkThrow() << "Invalid state. Internal model returned a label value that does not exist in segmentation. Invalid value:" << value; label->SetVisible(visible); group->UpdateLookupTable(label->GetValue()); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnSetAffectedLabelsVisible() { this->SetVisibilityOfAffectedLabels(true); } void QmitkMultiLabelInspector::OnSetAffectedLabelsInvisible() { this->SetVisibilityOfAffectedLabels(false); } void QmitkMultiLabelInspector::OnSetOnlyActiveLabelVisible(bool /*value*/) { auto currentLabel = GetFirstSelectedLabelObject(); const auto labelID = currentLabel->GetValue(); auto groupID = m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); auto group = m_Segmentation->GetLabelSet(groupID); group->SetAllLabelsVisible(false); currentLabel->SetVisible(true); group->UpdateLookupTable(labelID); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); this->PrepareGoToLabel(labelID); } void QmitkMultiLabelInspector::OnItemDoubleClicked(const QModelIndex& index) { if (!index.isValid()) return; if (index.column() > 0) return; auto labelVariant = index.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); if (!labelVariant.isValid()) return; const auto labelID = labelVariant.value(); if (QApplication::queryKeyboardModifiers().testFlag(Qt::AltModifier)) { this->OnRenameLabel(false); return; } this->PrepareGoToLabel(labelID); } void QmitkMultiLabelInspector::PrepareGoToLabel(mitk::Label::PixelType labelID) const { this->WaitCursorOn(); m_Segmentation->UpdateCenterOfMass(labelID); const auto currentLabel = m_Segmentation->GetLabel(labelID); const mitk::Point3D& pos = currentLabel->GetCenterOfMassCoordinates(); this->WaitCursorOff(); if (pos.GetVnlVector().max_value() > 0.0) { emit GoToLabel(currentLabel->GetValue(), pos); } } diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.h b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.h index 867be63e7f..b0536b9c87 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.h +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.h @@ -1,250 +1,281 @@ /*============================================================================ 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 QMITKMULTILABELSEGMENTATIONINSPECTOR_H #define QMITKMULTILABELSEGMENTATIONINSPECTOR_H #include #include #include +#include #include -#include "ui_QmitkMultiLabelInspectorControls.h" class QmitkMultiLabelTreeModel; class QStyledItemDelegate; class QWidgetAction; namespace Ui { class QmitkMultiLabelInspector; } /* -* @brief This is an inspector that offers a simple list view on a data storage. +* @brief This is an inspector that offers a tree view on the labels and groups of a MultiLabelSegmentation instance. +* It also allows some manipulation operations an the labels/groups accordin to the UI/selection state. */ class MITKSEGMENTATIONUI_EXPORT QmitkMultiLabelInspector : public QWidget { Q_OBJECT public: QmitkMultiLabelInspector(QWidget* parent = nullptr); ~QmitkMultiLabelInspector(); bool GetMultiSelectionMode() const; bool GetAllowVisibilityModification() const; bool GetAllowLockModification() const; bool GetAllowLabelModification() const; using LabelValueType = mitk::LabelSetImage::LabelValueType; using LabelValueVectorType = mitk::LabelSetImage::LabelValueVectorType; /** * @brief Retrieve the currently selected labels (equals the last CurrentSelectionChanged values). */ LabelValueVectorType GetSelectedLabels() const; - /** Returns the label that currently has the focus in the tree view (indicated by QTreeView::currentIndex, - thus the mouse is over it and it has a dashed border line). - The current label must not equal the seleted label(s). If the mouse - is not over a label (label class or instance item), the method will return a null pointer.*/ + /** @brief Returns the label that currently has the focus in the tree view. + * + * The focus is indicated by QTreeView::currentIndex, thus the mouse is over it and it has a dashed border line. + * + * The current label must not equal the selected label(s). If the mouse is not hovering above a label + * (label class or instance item), the method will return nullptr. + */ mitk::Label* GetCurrentLabel() const; enum class IndexLevelType { Group, LabelClass, LabelInstance }; - /** Returns the level of the index currently has the focus in the tree view (indicated by QTreeView::currentIndex, - thus the mouse is over it and it has a dashed border line).*/ + /** @brief Returns the level of the index that currently has the focus in the tree view. + * + * The focus is indicated by QTreeView::currentIndex, thus the mouse is over it and it has a dashed border line. + */ IndexLevelType GetCurrentLevelType() const; - /** - * @brief Returns the all labels values that are currently affacted. - Affected means that these labels (including the one returned by GetCurrentLabel) are in the subtree of the tree - view element that currently has the focus. - (indicated by QTreeView::currentIndex, thus the mouse is over it and it has a dashed border line) - */ + /** @brief Returns all label values that are currently affected. + * + * Affected means that these labels (including the one returned by GetCurrentLabel) are in the subtree of the tree + * view element that currently has the focus (indicated by QTreeView::currentIndex, thus the mouse is over it and + * it has a dashed border line. + */ LabelValueVectorType GetCurrentlyAffactedLabelInstances() const; Q_SIGNALS: /** * @brief A signal that will be emitted if the selected labels change. * * @param labels A list of label values that are now selected. */ void CurrentSelectionChanged(LabelValueVectorType labels) const; - void GoToLabel(LabelValueType label, const mitk::Point3D&) const; + /** + * @brief A signal that will be emitted if the user has requested to "go to" a certain label. + * + * Going to a label would be e.g. to focus the renderwindows on the centroid of the label. + * @param label The label that should be focused. + * @param point in World coordinate that should be focused. + */ + void GoToLabel(LabelValueType label, const mitk::Point3D& point) const; - /** Signal that is emitted, if a label should be (re) named and default - label naming is deactivated. The instance for which a new name is requested - is passed with the signal. - @param label Pointer to the instance that needs a (new) name. - @param rename Indicates if it is a renaming or naming of a new label.*/ + /** @brief Signal that is emitted, if a label should be (re)named and default + * label naming is deactivated. + * + * The instance for which a new name is requested is passed with the signal. + * @param label Pointer to the instance that needs a (new) name. + * @param rename Indicates if it is a renaming or naming of a new label. + */ void LabelRenameRequested(mitk::Label* label, bool rename) const; public Q_SLOTS: /** - * @brief Transform a list label values into the new selection of the inspector. - * @param selectedNodes A list of selected label values. + * @brief Transform a list of label values into the new selection of the inspector. + * @param selectedLabels A list of selected label values. * @remark Using this method to select labels will not trigger the CurrentSelectionChanged signal. Observers * should regard that to avoid signal loops. */ void SetSelectedLabels(const LabelValueVectorType& selectedLabels); /** * @brief The passed label will be used as new selection in the widget * @param selectedLabel Value of the selected label. * @remark Using this method to select labels will not trigger the CurrentSelectionChanged signal. Observers * should regard that to avoid signal loops. */ void SetSelectedLabel(mitk::LabelSetImage::LabelValueType selectedLabel); - /** - * @brief Sets the segmentation that will be used /monitored by the widget. - * - * @param segmentation A pointer to the segmentation to set. + /** @brief Sets the segmentation that will be used and monitored by the widget. */ void SetMultiLabelSegmentation(mitk::LabelSetImage* segmentation); void SetMultiSelectionMode(bool multiMode); - void SetAllowVisibilityModification(bool vmod); - void SetAllowLockModification(bool lmod); + void SetAllowVisibilityModification(bool visiblityMod); + void SetAllowLockModification(bool lockMod); void SetAllowLabelModification(bool labelMod); void SetDefaultLabelNaming(bool defaultLabelNaming); - /** Adds an instance of the same label/class like the first label instance - * indicated by GetSelectedLabels() to the segmentation. This new label - * instance is returned by the function. If the inspector has no selected label, + /** @brief Adds an instance of the same label/class like the first label instance + * indicated by GetSelectedLabels() to the segmentation. + * + * This new label instance is returned by the function. If the inspector has no selected label, * no new instance will be generated and nullptr will be returned. - * @remark The new label instance is a clone of the selected label instance. Therefore - * all properties but the LabelValue will be the same. - * @pre AllowLabeModification must be set to true.*/ + * + * @remark The new label instance is a clone of the selected label instance. + * Therefore all properties but the LabelValue will be the same. + * + * @pre AllowLabeModification must be set to true. + */ mitk::Label* AddNewLabelInstance(); - /** Adds a new label to the segmentation. Depending on the settings the name of + /** @brief Adds a new label to the segmentation. + * Depending on the settings the name of * the label will be either default generated or the rename delegate will be used. The label * will be added to the same group as the first currently selected label. + * * @pre AllowLabeModification must be set to true.*/ mitk::Label* AddNewLabel(); - /** Removes the first currently selected label of the segmentation. If no label is selected + /** @brief Removes the first currently selected label of the segmentation. + * If no label is selected * nothing will happen. + * * @pre AllowLabeModification must be set to true.*/ void RemoveLabel(); - /** Adds a new group with a new label to segmentation. + /** @brief Adds a new group with a new label to segmentation. + * * @pre AllowLabeModification must be set to true.*/ mitk::Label* AddNewGroup(); - /** Removes the group of the first currently selected label of the segmentation. If no label is selected - * nothing will happen. + /** @brief Removes the group of the first currently selected label of the segmentation. + *If no label is selected nothing will happen. + * * @pre AllowLabeModification must be set to true.*/ void RemoveGroup(); void SetVisibilityOfAffectedLabels(bool visible) const; void SetLockOfAffectedLabels(bool visible) const; protected: void Initialize(); void OnModelReset(); QmitkMultiLabelTreeModel* m_Model; mitk::LabelSetImage::Pointer m_Segmentation; LabelValueVectorType m_LastValidSelectedLabels; QStyledItemDelegate* m_LockItemDelegate; QStyledItemDelegate* m_ColorItemDelegate; QStyledItemDelegate* m_VisibilityItemDelegate; Ui::QmitkMultiLabelInspector* m_Controls; LabelValueVectorType GetSelectedLabelsFromSelectionModel() const; void UpdateSelectionModel(const LabelValueVectorType& selectedLabels); - /** Helper that returns the label object (if multiple labels are selected the first).*/ + /** @brief Helper that returns the label object (if multiple labels are selected the first). + */ mitk::Label* GetFirstSelectedLabelObject() const; - mitk::Label* AddNewLabelInternal(const mitk::LabelSetImage::SpatialGroupIndexType& containingGroup); + mitk::Label* AddNewLabelInternal(const mitk::LabelSetImage::GroupIndexType& containingGroup); - /** Adds an instance of the same label/class like the passed label value*/ + /**@brief Adds an instance of the same label/class like the passed label value + */ mitk::Label* AddNewLabelInstanceInternal(mitk::Label* templateLabel); - void RemoveGroupInternal(const mitk::LabelSetImage::SpatialGroupIndexType& groupID); + void RemoveGroupInternal(const mitk::LabelSetImage::GroupIndexType& groupID); void DeleteLabelInternal(const LabelValueVectorType& labelValues); private Q_SLOTS: - /** - * @brief Transform a labels selection into a data node list and emit the 'CurrentSelectionChanged'-signal. + /** @brief Transform a labels selection into a data node list and emit the 'CurrentSelectionChanged'-signal. * - * The function adds the selected nodes from the original selection that could not be modified, if - * 'm_SelectOnlyVisibleNodes' is false. - * This slot is internally connected to the 'selectionChanged'-signal of the selection model of the private member item view. + * The function adds the selected nodes from the original selection that could not be modified, if + * m_SelectOnlyVisibleNodes is false. + * This slot is internally connected to the 'selectionChanged'-signal of the selection model of the private member item view. * * @param selected The newly selected items. * @param deselected The newly deselected items. */ void OnChangeModelSelection(const QItemSelection& selected, const QItemSelection& deselected); void OnContextMenuRequested(const QPoint&); void OnAddLabel(); void OnAddLabelInstance(); void OnDeleteGroup(); void OnDeleteAffectedLabel(); void OnRemoveLabels(bool); void OnClearLabels(bool); void OnMergeLabels(bool); void OnRenameLabel(bool); void OnClearLabel(bool); void OnUnlockAffectedLabels(); void OnLockAffectedLabels(); void OnSetAffectedLabelsVisible(); void OnSetAffectedLabelsInvisible(); void OnSetOnlyActiveLabelVisible(bool); void OnItemDoubleClicked(const QModelIndex& index); void WaitCursorOn() const; void WaitCursorOff() const; void RestoreOverrideCursor() const; void PrepareGoToLabel(LabelValueType labelID) const; QWidgetAction* CreateOpacityAction(); private: bool m_ShowVisibility = true; bool m_ShowLock = true; bool m_ShowOther = false; - /** Indicates if the context menu allows changes in visiblity. - Visiblity includes also color*/ + /** @brief Indicates if the context menu allows changes in visiblity. + * + * Visiblity includes also color + */ bool m_AllowVisibilityModification = true; + + /** @brief Indicates if the context menu allows changes in lock state. + */ bool m_AllowLockModification = true; + + /** @brief Indicates if the context menu allows label modifications (adding, removing, renaming ...) + */ bool m_AllowLabelModification = false; bool m_DefaultLabelNaming = true; bool m_ModelManipulationOngoing = false; }; #endif // QMITKDATASTORAGELISTINSPECTOR_H diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.h b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.h index a8f04c15ee..f5eb5c7410 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.h +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.h @@ -1,155 +1,169 @@ /*============================================================================ 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 QmitkMultiLabelManager_h #define QmitkMultiLabelManager_h #include #include #include #include #include class QmitkDataStorageComboBox; class QCompleter; namespace Ui { class QmitkMultiLabelManagerControls; } namespace mitk { class DataStorage; } class MITKSEGMENTATIONUI_EXPORT QmitkMultiLabelManager : public QWidget { Q_OBJECT public: explicit QmitkMultiLabelManager(QWidget *parent = nullptr); ~QmitkMultiLabelManager() override; using LabelValueVectorType = mitk::LabelSetImage::LabelValueVectorType; /** * @brief Retrieve the currently selected labels (equals the last CurrentSelectionChanged values). */ LabelValueVectorType GetSelectedLabels() const; Q_SIGNALS: /** * @brief A signal that will be emitted if the selected labels change. * * @param labels A list of label values that are now selected. */ void CurrentSelectionChanged(LabelValueVectorType labels); - void GoToLabel(mitk::LabelSetImage::LabelValueType label, const mitk::Point3D&) const; - - /** Signal that is emitted, if a label should be (re) named and default - label naming is deactivated. The instance for which a new name is requested - is passed with the signal. - @param label Pointer to the instance that needs a (new) name. - @param rename Indicates if it is a renaming or naming of a new label.*/ + /** + * @brief A signal that will be emitted if the user has requested to "go to" a certain label. + * + * Going to a label would be e.g. to focus the renderwindows on the centroid of the label. + * @param label The label that should be focused. + * @param point in World coordinate that should be focused. + */ + void GoToLabel(mitk::LabelSetImage::LabelValueType label, const mitk::Point3D& point) const; + + /** @brief Signal that is emitted, if a label should be (re)named and default + * label naming is deactivated. + * + * The instance for which a new name is requested is passed with the signal. + * @param label Pointer to the instance that needs a (new) name. + * @param rename Indicates if it is a renaming or naming of a new label. + */ void LabelRenameRequested(mitk::Label* label, bool rename) const; public Q_SLOTS: /** * @brief Transform a list label values into a model selection and set this as a new selection of the view * - * @param selectedNodes A list of data nodes that should be newly selected. + * @param selectedLabels A list of data nodes that should be newly selected. */ void SetSelectedLabels(const LabelValueVectorType& selectedLabels); - void SetSelectedLabel(mitk::LabelSetImage::LabelValueType selectedLabel); + + /** + * @brief Selects the passed label instance and sets a new selection of the view + * + * @param selectedLabel Value of the label instance that should be selected. + */ void SetSelectedLabel(mitk::LabelSetImage::LabelValueType selectedLabel); /** * @brief Sets the segmentation that will be used /monitored by the widget. * * @param segmentation A pointer to the segmentation to set. */ void SetMultiLabelSegmentation(mitk::LabelSetImage* segmentation); void SetDataStorage(mitk::DataStorage *storage); void UpdateControls(); virtual void setEnabled(bool enabled); QStringList &GetLabelStringList(); void SetDefaultLabelNaming(bool defaultLabelNaming); private Q_SLOTS: // LabelSet dependent void OnRenameLabelShortcutActivated(); // reaction to "returnPressed" signal from ... void OnSearchLabel(); // reaction to the change of labels. If multiple labels are selected, it is ignored. void OnSelectedLabelChanged(LabelValueVectorType labels); // LabelSetImage Dependet void OnCreateDetailedSurface(bool); void OnCreateSmoothedSurface(bool); // reaction to the signal "createMask" from QmitkLabelSetTableWidget void OnCreateMask(bool); // reaction to the signal "createCroppedMask" from QmitkLabelSetTableWidget void OnCreateCroppedMask(bool); void OnSavePreset(); void OnLoadPreset(); void OnGoToLabel(mitk::LabelSetImage::LabelValueType label, const mitk::Point3D& position) const; void OnLabelRenameRequested(mitk::Label* label, bool rename) const; private: enum TableColumns { NAME_COL = 0, LOCKED_COL, COLOR_COL, VISIBLE_COL }; void WaitCursorOn(); void WaitCursorOff(); void RestoreOverrideCursor(); void OnThreadedCalculationDone(); Ui::QmitkMultiLabelManagerControls* m_Controls; QCompleter *m_Completer; QStringList m_OrganColors; QStringList m_LabelStringList; bool m_ProcessingManualSelection; mitk::LabelSetImage::Pointer m_Segmentation; mitk::DataNode::Pointer m_SegmentationNode; mitk::DataStorage* m_DataStorage; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp index 6c2e88af8e..0f702f5919 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp @@ -1,971 +1,971 @@ /*============================================================================ 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 "QmitkMultiLabelTreeModel.h" #include "mitkRenderingManager.h" #include "QmitkStyleManager.h" class QmitkMultiLabelSegTreeItem { public: enum class ItemType { Group, Label, Instance }; QmitkMultiLabelSegTreeItem() { }; explicit QmitkMultiLabelSegTreeItem(ItemType type, QmitkMultiLabelSegTreeItem* parentItem, mitk::Label* label = nullptr, std::string className = ""): m_parentItem(parentItem), m_ItemType(type), m_Label(label), m_ClassName(className) { }; ~QmitkMultiLabelSegTreeItem() { for (auto item : m_childItems) { delete item; } }; void AppendChild(QmitkMultiLabelSegTreeItem* child) { m_childItems.push_back(child); }; void RemoveChild(std::size_t row) { if (row < m_childItems.size()) { delete m_childItems[row]; m_childItems.erase(m_childItems.begin() + row); } }; int Row() const { if (m_parentItem) { auto finding = std::find(m_parentItem->m_childItems.begin(), m_parentItem->m_childItems.end(), this); if (finding != m_parentItem->m_childItems.end()) { return std::distance(m_parentItem->m_childItems.begin(), finding); } } return 0; }; QmitkMultiLabelSegTreeItem* ParentItem() { return m_parentItem; }; const QmitkMultiLabelSegTreeItem* ParentItem() const { return m_parentItem; }; const QmitkMultiLabelSegTreeItem* NextSibblingItem() const { if (m_parentItem) { const std::vector::size_type row = this->Row(); if (row + 1 < m_parentItem->m_childItems.size()) return m_parentItem->m_childItems[row+1]; } return nullptr; }; const QmitkMultiLabelSegTreeItem* PrevSibblingItem() const { if (m_parentItem) { const std::vector::size_type row = this->Row(); if (row > 0) return m_parentItem->m_childItems[row-1]; } return nullptr; }; const QmitkMultiLabelSegTreeItem* RootItem() const { auto item = this; while (item->m_parentItem != nullptr) { item = item->m_parentItem; } return item; }; std::size_t GetGroupID() const { auto root = this->RootItem(); auto item = this; if (root == this) return 0; while (root != item->m_parentItem) { item = item->m_parentItem; } auto iter = std::find(root->m_childItems.begin(), root->m_childItems.end(), item); if (root->m_childItems.end() == iter) mitkThrow() << "Invalid internal state of QmitkMultiLabelTreeModel. Root does not have an currentItem as child that has root as parent."; return std::distance(root->m_childItems.begin(), iter); } bool HandleAsInstance() const { return (ItemType::Instance == m_ItemType) || ((ItemType::Label == m_ItemType) && (m_childItems.size() == 1)); } mitk::Label* GetLabel() const { if (ItemType::Instance == m_ItemType) { return m_Label; } if (ItemType::Label == m_ItemType) { if (m_childItems.empty()) mitkThrow() << "Invalid internal state of QmitkMultiLabelTreeModel. Internal label currentItem has no instance currentItem."; return m_childItems[0]->GetLabel(); } return nullptr; }; mitk::LabelSetImage::LabelValueType GetLabelValue() const { auto label = this->GetLabel(); if (nullptr == label) { mitkThrow() << "Invalid internal state of QmitkMultiLabelTreeModel. Called GetLabelValue on an group currentItem."; } return label->GetValue(); }; /** returns a vector containing all label values of referenced by this item or its child items.*/ std::vector< mitk::LabelSetImage::LabelValueType> GetLabelsInSubTree() const { if (this->m_ItemType == ItemType::Instance) { return { this->GetLabelValue() }; } std::vector< mitk::LabelSetImage::LabelValueType> result; for (const auto child : this->m_childItems) { auto childresult = child->GetLabelsInSubTree(); result.reserve(result.size() + childresult.size()); result.insert(result.end(), childresult.begin(), childresult.end()); } return result; } std::vector m_childItems; QmitkMultiLabelSegTreeItem* m_parentItem = nullptr; ItemType m_ItemType = ItemType::Group; mitk::Label::Pointer m_Label; std::string m_ClassName; }; QModelIndex GetIndexByItem(const QmitkMultiLabelSegTreeItem* start, const QmitkMultiLabelTreeModel* model) { QModelIndex parentIndex = QModelIndex(); if (nullptr != start->m_parentItem) { parentIndex = GetIndexByItem(start->m_parentItem, model); } else { return parentIndex; } return model->index(start->Row(), 0, parentIndex); } -QmitkMultiLabelSegTreeItem* GetGroupItem(QmitkMultiLabelTreeModel::SpatialGroupIndexType groupIndex, QmitkMultiLabelSegTreeItem* root) +QmitkMultiLabelSegTreeItem* GetGroupItem(QmitkMultiLabelTreeModel::GroupIndexType groupIndex, QmitkMultiLabelSegTreeItem* root) { if (nullptr != root && groupIndex < root->m_childItems.size()) { return root->m_childItems[groupIndex]; } return nullptr; } QmitkMultiLabelSegTreeItem* GetInstanceItem(QmitkMultiLabelTreeModel::LabelValueType labelValue, QmitkMultiLabelSegTreeItem* root) { QmitkMultiLabelSegTreeItem* result = nullptr; for (auto item : root->m_childItems) { result = GetInstanceItem(labelValue, item); if (nullptr != result) return result; } if (root->m_ItemType == QmitkMultiLabelSegTreeItem::ItemType::Instance && root->GetLabelValue() == labelValue) { return root; } return nullptr; } const QmitkMultiLabelSegTreeItem* GetFirstInstanceLikeItem(const QmitkMultiLabelSegTreeItem* startItem) { const QmitkMultiLabelSegTreeItem* result = nullptr; if (nullptr != startItem) { if (startItem->HandleAsInstance()) { result = startItem; } else if (!startItem->m_childItems.empty()) { result = GetFirstInstanceLikeItem(startItem->m_childItems.front()); } } return result; } QmitkMultiLabelSegTreeItem* GetLabelItemInGroup(const std::string& labelName, QmitkMultiLabelSegTreeItem* group) { if (nullptr != group) { auto predicate = [labelName](const QmitkMultiLabelSegTreeItem* item) { return labelName == item->m_ClassName; }; auto finding = std::find_if(group->m_childItems.begin(), group->m_childItems.end(), predicate); if (group->m_childItems.end() != finding) { return *finding; } } return nullptr; } QmitkMultiLabelTreeModel::QmitkMultiLabelTreeModel(QObject *parent) : QAbstractItemModel(parent) , m_Observed(false) { m_RootItem = std::make_unique(); } QmitkMultiLabelTreeModel ::~QmitkMultiLabelTreeModel() { this->SetSegmentation(nullptr); }; int QmitkMultiLabelTreeModel::columnCount(const QModelIndex& /*parent*/) const { return 4; } int QmitkMultiLabelTreeModel::rowCount(const QModelIndex &parent) const { if (parent.column() > 0) return 0; if (m_Segmentation.IsNull()) return 0; QmitkMultiLabelSegTreeItem* parentItem = m_RootItem.get(); if (parent.isValid()) parentItem = static_cast(parent.internalPointer()); if (parentItem->HandleAsInstance()) { return 0; } return parentItem->m_childItems.size(); } QVariant QmitkMultiLabelTreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); auto item = static_cast(index.internalPointer()); if (!item) return QVariant(); if (role == Qt::DisplayRole||role == Qt::EditRole) { if (TableColumns::NAME_COL == index.column()) { switch (item->m_ItemType) { case QmitkMultiLabelSegTreeItem::ItemType::Group: return QVariant(QString("Group ")+QString::number(item->GetGroupID())); case QmitkMultiLabelSegTreeItem::ItemType::Label: { auto label = item->GetLabel(); if (nullptr == label) mitkThrow() << "Invalid internal state. QmitkMultiLabelTreeModel currentItem is refering to a label that does not exist."; QString name = QString::fromStdString(label->GetName()); if (!item->HandleAsInstance()) { name = name + QString(" (") + QString::number(item->m_childItems.size()) + QString(" instances)"); } return QVariant(name); } case QmitkMultiLabelSegTreeItem::ItemType::Instance: { auto label = item->GetLabel(); if (nullptr == label) mitkThrow() << "Invalid internal state. QmitkMultiLabelTreeModel currentItem is refering to a label that does not exist."; return QVariant(QString::fromStdString(label->GetName()) + QString(" #") + QString::number(item->GetLabelValue())); } } } else { if (item->HandleAsInstance()) { auto label = item->GetLabel(); if (TableColumns::LOCKED_COL == index.column()) { return QVariant(label->GetLocked()); } else if (TableColumns::COLOR_COL == index.column()) { return QVariant(QColor(label->GetColor().GetRed() * 255, label->GetColor().GetGreen() * 255, label->GetColor().GetBlue() * 255)); } else if (TableColumns::VISIBLE_COL == index.column()) { return QVariant(label->GetVisible()); } } else { } } } else if (role == ItemModelRole::LabelDataRole) { auto label = item->GetLabel(); if (nullptr!=label) return QVariant::fromValue(label); } else if (role == ItemModelRole::LabelValueRole) { auto label = item->GetLabel(); if (nullptr != label) return QVariant(label->GetValue()); } else if (role == ItemModelRole::LabelInstanceDataRole) { if (item->HandleAsInstance()) { auto label = item->GetLabel(); return QVariant::fromValue(label); } } else if (role == ItemModelRole::LabelInstanceValueRole) { if (item->HandleAsInstance()) { auto label = item->GetLabel(); return QVariant(label->GetValue()); } } else if (role == ItemModelRole::GroupIDRole) { QVariant v; v.setValue(item->GetGroupID()); return v; } return QVariant(); } mitk::Color QtToMitk(const QColor& color) { mitk::Color mitkColor; mitkColor.SetRed(color.red() / 255.0f); mitkColor.SetGreen(color.green() / 255.0f); mitkColor.SetBlue(color.blue() / 255.0f); return mitkColor; } bool QmitkMultiLabelTreeModel::setData(const QModelIndex& index, const QVariant& value, int role) { if (!index.isValid()) return false; auto item = static_cast(index.internalPointer()); if (!item) return false; if (role == Qt::EditRole) { if (TableColumns::NAME_COL != index.column()) { if (item->HandleAsInstance()) { auto label = item->GetLabel(); if (TableColumns::LOCKED_COL == index.column()) { label->SetLocked(value.toBool()); } else if (TableColumns::COLOR_COL == index.column()) { label->SetColor(QtToMitk(value.value())); } else if (TableColumns::VISIBLE_COL == index.column()) { label->SetVisible(value.toBool()); } auto groupID = m_Segmentation->GetGroupIndexOfLabel(label->GetValue()); m_Segmentation->GetLabelSet(groupID)->UpdateLookupTable(label->GetValue()); m_Segmentation->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } else { } return true; } } return false; } QModelIndex QmitkMultiLabelTreeModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) return QModelIndex(); auto parentItem = m_RootItem.get(); if (parent.isValid()) parentItem = static_cast(parent.internalPointer()); QmitkMultiLabelSegTreeItem *childItem = parentItem->m_childItems[row]; if (childItem) return createIndex(row, column, childItem); else return QModelIndex(); } QModelIndex QmitkMultiLabelTreeModel::indexOfLabel(mitk::Label::PixelType labelValue) const { if (labelValue == mitk::LabelSetImage::UnlabeledLabelValue) return QModelIndex(); auto relevantItem = GetInstanceItem(labelValue, this->m_RootItem.get()); if (nullptr == relevantItem) QModelIndex(); auto labelItem = relevantItem->ParentItem(); if (labelItem->m_childItems.size() == 1) { //was the only instance of the label, therefor return the label item instat. relevantItem = labelItem; } return GetIndexByItem(relevantItem, this); } -QModelIndex QmitkMultiLabelTreeModel::indexOfGroup(mitk::LabelSetImage::SpatialGroupIndexType groupIndex) const +QModelIndex QmitkMultiLabelTreeModel::indexOfGroup(mitk::LabelSetImage::GroupIndexType groupIndex) const { auto relevantItem = GetGroupItem(groupIndex, this->m_RootItem.get()); if (nullptr == relevantItem) QModelIndex(); return GetIndexByItem(relevantItem, this); } QModelIndex QmitkMultiLabelTreeModel::parent(const QModelIndex &child) const { if (!child.isValid()) return QModelIndex(); QmitkMultiLabelSegTreeItem *childItem = static_cast(child.internalPointer()); QmitkMultiLabelSegTreeItem *parentItem = childItem->ParentItem(); if (parentItem == m_RootItem.get()) return QModelIndex(); return createIndex(parentItem->Row(), 0, parentItem); } QModelIndex QmitkMultiLabelTreeModel::ClosestLabelInstanceIndex(const QModelIndex& currentIndex) const { if (!currentIndex.isValid()) return QModelIndex(); auto currentItem = static_cast(currentIndex.internalPointer()); if (!currentItem) return QModelIndex(); if (currentItem->RootItem() != this->m_RootItem.get()) mitkThrow() << "Invalid call. Passed currentIndex does not seem to be a valid index of this model. It is either outdated or from another model."; const QmitkMultiLabelSegTreeItem* resultItem = nullptr; auto searchItem = currentItem; const auto rootItem = currentItem->RootItem(); while (searchItem != rootItem) { resultItem = GetFirstInstanceLikeItem(searchItem->NextSibblingItem()); if (nullptr != resultItem) break; //no next closest label instance on this level -> check for closest before resultItem = GetFirstInstanceLikeItem(searchItem->PrevSibblingItem()); if (nullptr != resultItem) break; //no closest label instance before current on this level -> moeve one level up searchItem = searchItem->ParentItem(); } if (nullptr == resultItem) return QModelIndex(); return GetIndexByItem(resultItem, this); } QModelIndex QmitkMultiLabelTreeModel::FirstLabelInstanceIndex(const QModelIndex& currentIndex) const { const QmitkMultiLabelSegTreeItem* currentItem = nullptr; if (!currentIndex.isValid()) { currentItem = this->m_RootItem.get(); } else { currentItem = static_cast(currentIndex.internalPointer()); } if (!currentItem) return QModelIndex(); if (currentItem->RootItem() != this->m_RootItem.get()) mitkThrow() << "Invalid call. Passed currentIndex does not seem to be a valid index of this model. It is either outdated or from another model."; const QmitkMultiLabelSegTreeItem* resultItem = nullptr; resultItem = GetFirstInstanceLikeItem(currentItem); if (nullptr == resultItem) return QModelIndex(); return GetIndexByItem(resultItem, this); } ///** Returns the index to the next node in the tree that behaves like an instance (label node with only one instance //or instance node). If current index is at the end, an invalid index is returned.*/ //QModelIndex QmitkMultiLabelTreeModel::PrevLabelInstanceIndex(const QModelIndex& currentIndex) const; std::vector QmitkMultiLabelTreeModel::GetLabelsInSubTree(const QModelIndex& currentIndex) const { const QmitkMultiLabelSegTreeItem* currentItem = nullptr; if (!currentIndex.isValid()) { currentItem = this->m_RootItem.get(); } else { currentItem = static_cast(currentIndex.internalPointer()); } if (!currentItem) return {}; return currentItem->GetLabelsInSubTree(); } Qt::ItemFlags QmitkMultiLabelTreeModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::NoItemFlags; if (!index.isValid()) return Qt::NoItemFlags; auto item = static_cast(index.internalPointer()); if (!item) return Qt::NoItemFlags; if (TableColumns::NAME_COL != index.column()) { if (item->HandleAsInstance() && ((TableColumns::VISIBLE_COL == index.column() && m_AllowVisibilityModification) || (TableColumns::COLOR_COL == index.column() && m_AllowVisibilityModification) || //m_AllowVisibilityModification controls visibility and color (TableColumns::LOCKED_COL == index.column() && m_AllowLockModification))) { return Qt::ItemIsEnabled | Qt::ItemIsEditable; } else { return Qt::ItemIsEnabled; } } else { if (item->HandleAsInstance()) { return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } else { return Qt::ItemIsEnabled; } } return Qt::NoItemFlags; } QVariant QmitkMultiLabelTreeModel::headerData(int section, Qt::Orientation orientation, int role) const { if ((Qt::DisplayRole == role) && (Qt::Horizontal == orientation)) { if (TableColumns::NAME_COL == section) { return "Name"; } else if (TableColumns::LOCKED_COL == section) { return "Locked"; } else if (TableColumns::COLOR_COL == section) { return "Color"; } else if (TableColumns::VISIBLE_COL == section) { return "Visibility"; } } return QVariant(); } const mitk::LabelSetImage* QmitkMultiLabelTreeModel::GetSegmentation() const { return m_Segmentation; } void QmitkMultiLabelTreeModel::SetSegmentation(mitk::LabelSetImage* segmentation) { if (m_Segmentation != segmentation) { this->RemoveObserver(); this->m_Segmentation = segmentation; this->AddObserver(); this->UpdateInternalTree(); } } /**Helper function that adds a labek into the item tree. Passes back the new created instance iten*/ QmitkMultiLabelSegTreeItem* AddLabelToGroupTree(mitk::Label* label, QmitkMultiLabelSegTreeItem* groupItem, bool& newLabelItemCreated) { if (nullptr == groupItem) return nullptr; if (nullptr == label) return nullptr; newLabelItemCreated = false; std::set labelNames; for (auto labelItem : groupItem->m_childItems) { labelNames.emplace(labelItem->GetLabel()->GetName()); } QmitkMultiLabelSegTreeItem* labelItem = nullptr; auto finding = labelNames.find(label->GetName()); if (finding != labelNames.end()) { //other label with same name exists labelItem = groupItem->m_childItems[std::distance(labelNames.begin(), finding)]; } else { newLabelItemCreated = true; labelItem = new QmitkMultiLabelSegTreeItem(QmitkMultiLabelSegTreeItem::ItemType::Label, groupItem, nullptr, label->GetName()); auto predicate = [label](const std::string& name) { return name > label->GetName(); }; auto insertFinding = std::find_if(labelNames.begin(), labelNames.end(), predicate); groupItem->m_childItems.insert(groupItem->m_childItems.begin() + std::distance(labelNames.begin(), insertFinding), labelItem); } auto instanceItem = new QmitkMultiLabelSegTreeItem(QmitkMultiLabelSegTreeItem::ItemType::Instance, labelItem, label); auto predicate = [label](const QmitkMultiLabelSegTreeItem* item) { return item->GetLabelValue() > label->GetValue(); }; auto insertFinding = std::find_if(labelItem->m_childItems.begin(), labelItem->m_childItems.end(), predicate); labelItem->m_childItems.insert(labelItem->m_childItems.begin() + std::distance(labelItem->m_childItems.begin(), insertFinding), instanceItem); return instanceItem; } void QmitkMultiLabelTreeModel::GenerateInternalGroupTree(unsigned int groupID, QmitkMultiLabelSegTreeItem* groupItem) { auto labelSet = m_Segmentation->GetLabelSet(groupID); for (auto lIter = labelSet->IteratorConstBegin(); lIter != labelSet->IteratorConstEnd(); lIter++) { if (lIter->first== mitk::LabelSetImage::UnlabeledLabelValue) continue; bool newItemCreated = false; AddLabelToGroupTree(lIter->second, groupItem, newItemCreated); } } QmitkMultiLabelSegTreeItem* QmitkMultiLabelTreeModel::GenerateInternalTree() { auto rootItem = new QmitkMultiLabelSegTreeItem(); if (m_Segmentation.IsNotNull()) { for (unsigned int groupID = 0; groupID < m_Segmentation->GetNumberOfLayers(); ++groupID) { auto groupItem = new QmitkMultiLabelSegTreeItem(QmitkMultiLabelSegTreeItem::ItemType::Group, rootItem); rootItem->AppendChild(groupItem); GenerateInternalGroupTree(groupID, groupItem); } } return rootItem; } void QmitkMultiLabelTreeModel::UpdateInternalTree() { emit beginResetModel(); auto newTree = this->GenerateInternalTree(); this->m_RootItem.reset(newTree); emit endResetModel(); emit modelChanged(); } void QmitkMultiLabelTreeModel::AddObserver() { if (this->m_Segmentation.IsNotNull()) { if (m_Observed) { MITK_DEBUG << "Invalid observer state in QmitkMultiLabelTreeModel. There is already a registered observer. Internal logic is not correct. May be an old observer was not removed."; } this->m_Segmentation->AddLabelAddedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnLabelAdded)); this->m_Segmentation->AddLabelModifiedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnLabelModified)); this->m_Segmentation->AddLabelRemovedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnLabelRemoved)); - this->m_Segmentation->AddGroupAddedListener(mitk::MessageDelegate1( + this->m_Segmentation->AddGroupAddedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnGroupAdded)); - this->m_Segmentation->AddGroupModifiedListener(mitk::MessageDelegate1( + this->m_Segmentation->AddGroupModifiedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnGroupModified)); - this->m_Segmentation->AddGroupRemovedListener(mitk::MessageDelegate1( + this->m_Segmentation->AddGroupRemovedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnGroupRemoved)); m_Observed = true; } } void QmitkMultiLabelTreeModel::RemoveObserver() { if (this->m_Segmentation.IsNotNull()) { this->m_Segmentation->RemoveLabelAddedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnLabelAdded)); this->m_Segmentation->RemoveLabelModifiedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnLabelModified)); this->m_Segmentation->RemoveLabelRemovedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnLabelRemoved)); - this->m_Segmentation->RemoveGroupAddedListener(mitk::MessageDelegate1( + this->m_Segmentation->RemoveGroupAddedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnGroupAdded)); - this->m_Segmentation->RemoveGroupModifiedListener(mitk::MessageDelegate1( + this->m_Segmentation->RemoveGroupModifiedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnGroupModified)); - this->m_Segmentation->RemoveGroupRemovedListener(mitk::MessageDelegate1( + this->m_Segmentation->RemoveGroupRemovedListener(mitk::MessageDelegate1( this, &QmitkMultiLabelTreeModel::OnGroupRemoved)); } m_Observed = false; } void QmitkMultiLabelTreeModel::OnLabelAdded(LabelValueType labelValue) { - SpatialGroupIndexType groupIndex = 0; + GroupIndexType groupIndex = 0; if (m_Segmentation->IsLabeInGroup(labelValue, groupIndex)) { auto label = m_Segmentation->GetLabel(labelValue); if (nullptr == label) mitkThrow() << "Invalid internal state. Segmentation signaled the addition of an label that does not exist in the segmentation. Invalid label value:" << labelValue; if (labelValue == mitk::LabelSetImage::UnlabeledLabelValue) return; auto groupItem = GetGroupItem(groupIndex, this->m_RootItem.get()); bool newLabelCreated = false; auto instanceItem = AddLabelToGroupTree(label, groupItem, newLabelCreated); if (newLabelCreated) { if (groupItem->m_childItems.size() == 1) { //first label added auto groupIndex = GetIndexByItem(groupItem, this); emit dataChanged(groupIndex, groupIndex); } else { //whole new label level added to group item auto groupIndex = GetIndexByItem(groupItem, this); this->beginInsertRows(groupIndex, instanceItem->ParentItem()->Row(), instanceItem->ParentItem()->Row()); this->endInsertRows(); } } else { if (instanceItem->ParentItem()->m_childItems.size() < 3) { //second instance item was added, so label item will now able to colapse // -> the whole label node has to be updated. auto labelIndex = GetIndexByItem(instanceItem->ParentItem(), this); emit dataChanged(labelIndex, labelIndex); this->beginInsertRows(labelIndex, 0, instanceItem->ParentItem()->m_childItems.size()-1); this->endInsertRows(); } else { // instance item was added to existing label item with multiple instances //-> just notify the row insertion auto labelIndex = GetIndexByItem(instanceItem->ParentItem(), this); this->beginInsertRows(labelIndex, instanceItem->Row(), instanceItem->Row()); this->endInsertRows(); } } } else { mitkThrow() << "Group less labels are not supported in the current implementation."; } } void QmitkMultiLabelTreeModel::OnLabelModified(LabelValueType labelValue) { if (labelValue == mitk::LabelSetImage::UnlabeledLabelValue) return; auto instanceItem = GetInstanceItem(labelValue, this->m_RootItem.get()); if (nullptr == instanceItem) { mitkThrow() << "Internal invalid state. QmitkMultiLabelTreeModel recieved a LabelModified signal for a label that is not represented in the model. Invalid label: " << labelValue; } auto labelItem = instanceItem->ParentItem(); if (labelItem->m_ClassName == instanceItem->GetLabel()->GetName()) { //only the state of the label changed, but not its position in the model tree. auto index = GetIndexByItem(labelItem, this); emit dataChanged(index, index); } else { //the name of the label changed and thus its place in the model tree, delete the current item and add a new one this->OnLabelRemoved(labelValue); this->OnLabelAdded(labelValue); } } void QmitkMultiLabelTreeModel::OnLabelRemoved(LabelValueType labelValue) { if (labelValue == mitk::LabelSetImage::UnlabeledLabelValue) return; auto instanceItem = GetInstanceItem(labelValue, this->m_RootItem.get()); if (nullptr == instanceItem) mitkThrow() << "Internal invalid state. QmitkMultiLabelTreeModel recieved a LabelRemoved signal for a label that is not represented in the model. Invalid label: " << labelValue; auto labelItem = instanceItem->ParentItem(); if (labelItem->m_childItems.size() > 2) { auto labelIndex = GetIndexByItem(labelItem, this); this->beginRemoveRows(labelIndex, instanceItem->Row(), instanceItem->Row()); labelItem->RemoveChild(instanceItem->Row()); this->endRemoveRows(); } else if (labelItem->m_childItems.size() == 2) { //After removal only one label is left -> the whole label node is about to be changed (no instances are shown any more). auto labelIndex = GetIndexByItem(labelItem, this); this->beginRemoveRows(labelIndex, instanceItem->Row(), instanceItem->Row()); labelItem->RemoveChild(instanceItem->Row()); this->endRemoveRows(); emit dataChanged(labelIndex, labelIndex); } else { //was the only instance of the label, therefor also remove the label node from the tree. auto groupItem = labelItem->ParentItem(); auto groupIndex = GetIndexByItem(groupItem, this); this->beginRemoveRows(groupIndex, labelItem->Row(), labelItem->Row()); groupItem->RemoveChild(labelItem->Row()); this->endRemoveRows(); } } -void QmitkMultiLabelTreeModel::OnGroupAdded(SpatialGroupIndexType groupIndex) +void QmitkMultiLabelTreeModel::OnGroupAdded(GroupIndexType groupIndex) { if (m_ShowGroups) { this->beginInsertRows(QModelIndex(), groupIndex, groupIndex); auto rootItem = m_RootItem.get(); auto groupItem = new QmitkMultiLabelSegTreeItem(QmitkMultiLabelSegTreeItem::ItemType::Group, rootItem); rootItem->AppendChild(groupItem); this->GenerateInternalGroupTree(groupIndex, groupItem); this->endInsertRows(); } } -void QmitkMultiLabelTreeModel::OnGroupModified(SpatialGroupIndexType /*groupIndex*/) +void QmitkMultiLabelTreeModel::OnGroupModified(GroupIndexType /*groupIndex*/) { //currently not needed } -void QmitkMultiLabelTreeModel::OnGroupRemoved(SpatialGroupIndexType groupIndex) +void QmitkMultiLabelTreeModel::OnGroupRemoved(GroupIndexType groupIndex) { if (m_ShowGroups) { this->beginRemoveRows(QModelIndex(), groupIndex, groupIndex); auto root = m_RootItem.get(); root->RemoveChild(groupIndex); this->endRemoveRows(); } } void QmitkMultiLabelTreeModel::SetAllowVisibilityModification(bool vmod) { m_AllowVisibilityModification = vmod; } bool QmitkMultiLabelTreeModel::GetAllowVisibilityModification() const { return m_AllowVisibilityModification; } void QmitkMultiLabelTreeModel::SetAllowLockModification(bool lmod) { m_AllowLockModification = lmod; } bool QmitkMultiLabelTreeModel::GetAllowLockModification() const { return m_AllowLockModification; } diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.h b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.h index b1d76ede55..967f804603 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.h +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.h @@ -1,158 +1,158 @@ /*============================================================================ 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 QmitkMultiLabelTreeModel_h #define QmitkMultiLabelTreeModel_h #include "mitkLabelSetImage.h" // qt #include #include "MitkSegmentationUIExports.h" class QmitkMultiLabelSegTreeItem; /*! \class QmitkMultiLabelTreeModel The class is used to represent the information of an MITK MultiLabel segmentation instance (labels, spacial groups...). */ class MITKSEGMENTATIONUI_EXPORT QmitkMultiLabelTreeModel : public QAbstractItemModel { Q_OBJECT public: using LabelValueType = mitk::LabelSetImage::LabelValueType; - using SpatialGroupIndexType = mitk::LabelSetImage::SpatialGroupIndexType; + using GroupIndexType = mitk::LabelSetImage::GroupIndexType; QmitkMultiLabelTreeModel(QObject *parent = nullptr); ~QmitkMultiLabelTreeModel() override; void SetSegmentation(mitk::LabelSetImage* segmentation); const mitk::LabelSetImage* GetSegmentation() const; Qt::ItemFlags flags(const QModelIndex &index) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &child) const override; /** returns the index of a passed label value (always first column). If label value does not exist in segmentation or segmentation is not set an invalid index will be returned.*/ QModelIndex indexOfLabel(mitk::Label::PixelType labelValue) const; - QModelIndex indexOfGroup(mitk::LabelSetImage::SpatialGroupIndexType groupIndex) const; + QModelIndex indexOfGroup(mitk::LabelSetImage::GroupIndexType groupIndex) const; /** Returns the index to the next node in the tree that behaves like an instance (label node with only one instance or instance node). If current index is at the end, an invalid index is returned.*/ QModelIndex ClosestLabelInstanceIndex(const QModelIndex& currentIndex) const; /** Returns the index to the first child node (or itself) in the tree that behaves like an instance (label node with only one instance or instance node). If current index is at the end, an invalid index is returned. If an invalid index is passed into the methods, the search starts at the root; thus the whole tree is search for the first label instance.*/ QModelIndex FirstLabelInstanceIndex(const QModelIndex& currentIndex) const; ///** Returns the index to the next node in the tree that behaves like an instance (label node with only one instance //or instance node). If current index is at the end, an invalid index is returned.*/ //QModelIndex PrevLabelInstanceIndex(const QModelIndex& currentIndex) const; /** Returns a vector containing all label values of the passed currentIndex or its child items.*/ std::vector GetLabelsInSubTree(const QModelIndex& currentIndex) const; enum TableColumns { NAME_COL = 0, LOCKED_COL, COLOR_COL, VISIBLE_COL }; enum ItemModelRole { /**This role returns the label object that is associated with an index. - On group level it always returns an invalid QVariant - On label level (with multiple instances) it returns the first label instance). - On instance level it returns the label instance object.*/ LabelDataRole = 64, /**This role returns only the label value of the label that would be returned by LabelDataRole.*/ LabelValueRole = 65, /**Simelar to LabelDataRole, but only returns a valid QVariant if index points only to a specific instance (so either instance level or label level with only one instance). You can use that role if you want to assure that only one specific label instance is referenced by the index.*/ LabelInstanceDataRole = 66, /**Simelar to LabelValueRole, but like LabelInstanceDataRole only returns a valid QVariant if index points only to a specific instance (so either instance level or label level with only one instance). You can use that role if you want to assure that only one specific label instance is referenced by the index.*/ LabelInstanceValueRole = 67, /**This role returns the group ID the item/index belongs to.*/ GroupIDRole = 68 }; bool GetAllowVisibilityModification() const; bool GetAllowLockModification() const; public Q_SLOTS: void SetAllowVisibilityModification(bool vmod); void SetAllowLockModification(bool lmod); Q_SIGNALS: void dataAvailable(); /** Is emitted whenever the model changes are finished (usually a bit later than dataAvailable()).*/ void modelChanged(); protected: void OnLabelAdded(LabelValueType labelValue); void OnLabelModified(LabelValueType labelValue); void OnLabelRemoved(LabelValueType labelValue); - void OnGroupAdded(SpatialGroupIndexType groupIndex); - void OnGroupModified(SpatialGroupIndexType groupIndex); - void OnGroupRemoved(SpatialGroupIndexType groupIndex); + void OnGroupAdded(GroupIndexType groupIndex); + void OnGroupModified(GroupIndexType groupIndex); + void OnGroupRemoved(GroupIndexType groupIndex); private: void AddObserver(); void RemoveObserver(); void UpdateInternalTree(); void GenerateInternalGroupTree(unsigned int layerID, QmitkMultiLabelSegTreeItem* layerItem); QmitkMultiLabelSegTreeItem* GenerateInternalTree(); /* builds a hierarchical tree model for the image statistics 1. Level: Image --> 2. Level: Mask [if exist] --> 3. Level: Timestep [if >1 exist] */ void BuildHierarchicalModel(); mitk::LabelSetImage::Pointer m_Segmentation; std::mutex m_Mutex; std::unique_ptr m_RootItem; bool m_Observed; bool m_ShowGroups = true; bool m_ShowVisibility = true; bool m_ShowLock = true; bool m_ShowOther = false; bool m_AllowVisibilityModification = true; bool m_AllowLockModification = true; }; #endif // mitkQmitkMultiLabelTreeModel_h diff --git a/Modules/SegmentationUI/Qmitk/QmitknnUNetFolderParser.h b/Modules/SegmentationUI/Qmitk/QmitknnUNetFolderParser.h index a6e12828c5..0cf1cc66bb 100644 --- a/Modules/SegmentationUI/Qmitk/QmitknnUNetFolderParser.h +++ b/Modules/SegmentationUI/Qmitk/QmitknnUNetFolderParser.h @@ -1,259 +1,258 @@ /*============================================================================ 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 QmitknnUNetFolderParser_h #define QmitknnUNetFolderParser_h #include #include #include #include #include /** * @brief Struct to store each (Folder) Node of the hierarchy tree structure. * */ struct FolderNode { QString name; QString path; // parent std::vector> subFolders; }; /** * @brief Class to store and retreive folder hierarchy information * of RESULTS_FOLDER. Only Root node is explicitly stored in m_RootNode. * No. of sub levels in the hierachry is defined in the LEVEL constant. * */ class QmitknnUNetFolderParser { public: /** * @brief Construct a new QmitknnUNetFolderParser object * Initializes root folder node object pointer calls * @param parentFolder */ QmitknnUNetFolderParser(const QString parentFolder); /** * @brief Destroy the QmitknnUNetFolderParser object * */ ~QmitknnUNetFolderParser() = default; /** * @brief Returns the "Results Folder" string which is parent path of the root node. * * @return QString */ QString getResultsFolder(); /** * @brief Returns the Model Names from root node. Template function, * type can be any of stl or Qt containers which supports push_back call. * * @tparam T * @return T (any of stl or Qt containers which supports push_back call) */ template T getModelNames() { auto models = GetSubFolderNamesFromNode(m_RootNode); return models; } /** * @brief Returns the task names for a given model. Template function, * type can be any of stl or Qt containers which supports push_back call. * * @tparam T * @param modelName * @return T (any of stl or Qt containers which supports push_back call) */ template T getTasksForModel(const QString &modelName) { std::shared_ptr modelNode = GetSubNodeMatchingNameCrietria(modelName, m_RootNode); auto tasks = GetSubFolderNamesFromNode(modelNode); return tasks; } /** * @brief Returns the models names for a given task. Template function, * type can be any of stl or Qt containers which supports push_back call. * * @tparam T * @param taskName * @return T (any of stl or Qt containers which supports push_back call) */ template T getModelsForTask(const QString &taskName) { T modelsForTask; auto models = GetSubFolderNamesFromNode(m_RootNode); foreach (QString model, models) { QStringList taskList = getTasksForModel(model); if (taskList.contains(taskName, Qt::CaseInsensitive)) { modelsForTask << model; } } return modelsForTask; } /** * @brief Returns all the task names present in the root node with possible duplicates. * Template function, type can be any of stl or Qt containers which supports push_back call. * - * @param T - * @param taskName + * @tparam T * @return T (any of stl or Qt containers which supports push_back call) */ template T getAllTasks() { T allTasks; auto models = GetSubFolderNamesFromNode(m_RootNode); foreach (QString model, models) { allTasks << getTasksForModel(model); } return allTasks; } /** * @brief Returns the trainer / planner names for a given task & model. Template function, * type can be any of stl or Qt containers which supports push_back call. * * @tparam T * @param taskName * @param modelName * @return T (any of stl or Qt containers which supports push_back call) */ template T getTrainerPlannersForTask(const QString &taskName, const QString &modelName) { std::shared_ptr modelNode = GetSubNodeMatchingNameCrietria(modelName, m_RootNode); std::shared_ptr taskNode = GetSubNodeMatchingNameCrietria(taskName, modelNode); auto tps = GetSubFolderNamesFromNode(taskNode); return tps; } /** * @brief Returns the Folds names for a given trainer,planner,task & model name. Template function, * type can be any of stl or Qt containers which supports push_back call. * * @tparam T * @param trainer * @param planner * @param taskName * @param modelName * @return T (any of stl or Qt containers which supports push_back call) */ template T getFoldsForTrainerPlanner(const QString &trainer, const QString &planner, const QString &taskName, const QString &modelName) { std::shared_ptr modelNode = GetSubNodeMatchingNameCrietria(modelName, m_RootNode); std::shared_ptr taskNode = GetSubNodeMatchingNameCrietria(taskName, modelNode); QString trainerPlanner = trainer + QString("__") + planner; std::shared_ptr tpNode = GetSubNodeMatchingNameCrietria(trainerPlanner, taskNode); auto folds = GetSubFolderNamesFromNode(tpNode); return folds; } private: const int m_LEVEL = 4; std::shared_ptr m_RootNode; /** * @brief Returns rule function wrapper to check for specific files at given Result_Folder hierarchy level. * * @param level * @return std::function */ std::function RuleEngine(int level); /** * @brief Iterates through the root node and returns the sub FolderNode object Matching Name Crietria * * @param queryName * @param parentNode * @return std::shared_ptr */ std::shared_ptr GetSubNodeMatchingNameCrietria(const QString &queryName, std::shared_ptr parentNode); /** * @brief Returns the sub folder names for a folder node object. Template function, * type can be any of stl or Qt containers which supports push_back call. * * @tparam T * @param std::shared_ptr * @return T (any of stl or Qt containers which supports push_back call) */ template T GetSubFolderNamesFromNode(const std::shared_ptr parent) { T folders; std::vector> subNodes = parent->subFolders; for (std::shared_ptr folder : subNodes) { folders.push_back(folder->name); } return folders; } /** * @brief Iterates through the sub folder hierarchy upto a level provided * and create a tree structure. * * @param parent * @param level */ void InitDirs(std::shared_ptr parent, int level); /** * @brief Iterates through the sub folder hierarchy upto a level provided * and clears the sub folder std::vector from each node. * * @param parent * @param level */ void DeleteDirs(std::shared_ptr parent, int level); /** * @brief Template function to fetch all folders inside a given path. * The type can be any of stl or Qt containers which supports push_back call. * * @tparam T * @param path * @return T */ template T FetchFoldersFromDir(const QString &path, std::function callback) { T folders; for (QDirIterator it(path, QDir::AllDirs, QDirIterator::NoIteratorFlags); it.hasNext();) { it.next(); if (!it.fileName().startsWith('.') && callback(it.filePath())) { folders.push_back(it.fileName()); } } return folders; } }; #endif