diff --git a/Modules/MatchPointRegistration/src/Helper/mitkImageMappingHelper.cpp b/Modules/MatchPointRegistration/src/Helper/mitkImageMappingHelper.cpp index ba04efcada..58035fabbf 100644 --- a/Modules/MatchPointRegistration/src/Helper/mitkImageMappingHelper.cpp +++ b/Modules/MatchPointRegistration/src/Helper/mitkImageMappingHelper.cpp @@ -1,482 +1,482 @@ /*============================================================================ 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 <itkInterpolateImageFunction.h> #include <itkNearestNeighborInterpolateImageFunction.h> #include <itkLinearInterpolateImageFunction.h> #include <itkBSplineInterpolateImageFunction.h> #include <itkWindowedSincInterpolateImageFunction.h> #include <mitkImageAccessByItk.h> #include <mitkImageCast.h> #include <mitkGeometry3D.h> #include <mitkImageToItk.h> #include <mitkImageTimeSelector.h> #include <mitkLabelSetImage.h> #include "mapRegistration.h" #include "mitkImageMappingHelper.h" #include "mitkRegistrationHelper.h" template <typename TImage > 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<TImage>::New(); break; } case mitk::ImageMappingInterpolator::BSpline_3: { typename ::itk::BSplineInterpolateImageFunction<TImage>::Pointer spInterpolator = ::itk::BSplineInterpolateImageFunction<TImage>::New(); spInterpolator->SetSplineOrder(3); result = spInterpolator; break; } case mitk::ImageMappingInterpolator::WSinc_Hamming: { result = ::itk::WindowedSincInterpolateImageFunction<TImage,4>::New(); break; } case mitk::ImageMappingInterpolator::WSinc_Welch: { result = ::itk::WindowedSincInterpolateImageFunction<TImage,4,::itk::Function::WelchWindowFunction<4> >::New(); break; } default: { result = ::itk::LinearInterpolateImageFunction<TImage>::New(); break; } } return result; }; template <typename TPixelType, unsigned int VImageDimension > void doMITKMap(const ::itk::Image<TPixelType,VImageDimension>* 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<VImageDimension,VImageDimension> ConcreteRegistrationType; typedef ::map::core::ImageMappingTask<ConcreteRegistrationType, ::itk::Image<TPixelType,VImageDimension>, ::itk::Image<TPixelType,VImageDimension> > 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 ("<<VImageDimension<<") does not equal the moving dimension of the registration object ("<<registration->getMovingDimensions()<<")."; throw mitk::AccessByItkException(str.str()); } if (registration->getTargetDimensions()!=VImageDimension) { map::core::OStringStream str; str << "Dimension of MITK image ("<<VImageDimension<<") does not equal the target dimension of the registration object ("<<registration->getTargetDimensions()<<")."; throw mitk::AccessByItkException(str.str()); } const ConcreteRegistrationType* castedReg = dynamic_cast<const ConcreteRegistrationType*>(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 ("<<registration->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<VImageDimension; ++i) { origin[i] = static_cast<typename ResultImageDescriptorType::PointType::ValueType>(geoOrigin[i]); fieldSpacing[i] = static_cast<typename ResultImageDescriptorType::SpacingType::ValueType>(geoSpacing[i]); size[i] = static_cast<typename ResultImageDescriptorType::SizeType::SizeValueType>(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<TPixelType,VImageDimension> > BaseInterpolatorType; typename BaseInterpolatorType::Pointer interpolator = generateInterpolator< ::itk::Image<TPixelType,VImageDimension> >(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; i<input->GetTimeSteps(); ++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<const LabelSetImage*>(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->ReplaceGroupLabels(layerID, inputLabelSetImage->GetConstLabelsInGroup(layerID)); + resultLabelSetImage->ReplaceGroupLabels(layerID, inputLabelSetImage->GetConstLabelsByValue(inputLabelSetImage->GetLabelValuesByGroup(layerID))); cloneInput->SetActiveLayer(layerID); resultLabelSetImage->SetActiveLayer(layerID); doMapTimesteps(cloneInput, resultLabelSetImage, registration, throwOnOutOfInputAreaError, paddingValue, resultGeometry, throwOnMappingError, errorValue, mitk::ImageMappingInterpolator::Linear); } resultLabelSetImage->SetActiveLayer(inputLabelSetImage->GetActiveLayer()); resultLabelSetImage->SetActiveLabel(inputLabelSetImage->GetActiveLabel()->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::ResultImageGeometryType::Pointer mitk::ImageMappingHelper::GenerateSuperSampledGeometry(const ResultImageGeometryType* inputGeometry, double xScaling, double yScaling, double zScaling) { auto resultGeometry = inputGeometry->Clone(); //change the pixel count and spacing of the geometry mitk::BaseGeometry::BoundsArrayType geoBounds = inputGeometry->GetBounds(); auto oldSpacing = inputGeometry->GetSpacing(); mitk::Vector3D geoSpacing; geoSpacing[0] = oldSpacing[0] / xScaling; geoSpacing[1] = oldSpacing[1] / yScaling; geoSpacing[2] = oldSpacing[2] / zScaling; geoBounds[1] = geoBounds[1] * xScaling; geoBounds[3] = geoBounds[3] * yScaling; geoBounds[5] = geoBounds[5] * zScaling; resultGeometry->SetBounds(geoBounds); resultGeometry->SetSpacing(geoSpacing); auto oldOrigin = inputGeometry->GetOrigin(); //if we change the spacing we must also correct the origin to ensure //that the voxel matrix still covers the same space. This is due the fact //that the origin is not in the corner of the voxel matrix, but in the center // of the voxel that is in the corner. mitk::Point3D newOrigin; for (mitk::Point3D::SizeType i = 0; i < 3; ++i) { newOrigin[i] = 0.5 * (geoSpacing[i] - oldSpacing[i]) + oldOrigin[i]; } return resultGeometry; } 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/autoload/DICOMSegIO/mitkDICOMSegmentationIO.cpp b/Modules/Multilabel/autoload/DICOMSegIO/mitkDICOMSegmentationIO.cpp index cec7e14e05..2d635fb7de 100644 --- a/Modules/Multilabel/autoload/DICOMSegIO/mitkDICOMSegmentationIO.cpp +++ b/Modules/Multilabel/autoload/DICOMSegIO/mitkDICOMSegmentationIO.cpp @@ -1,697 +1,697 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef __mitkDICOMSegmentationIO__cpp #define __mitkDICOMSegmentationIO__cpp #include "mitkDICOMSegmentationIO.h" #include "mitkDICOMSegIOMimeTypes.h" #include "mitkDICOMSegmentationConstants.h" #include <mitkDICOMDCMTKTagScanner.h> #include <mitkDICOMIOHelper.h> #include <mitkDICOMProperty.h> #include <mitkIDICOMTagsOfInterest.h> #include <mitkImageAccessByItk.h> #include <mitkImageCast.h> #include <mitkLocaleSwitch.h> #include <mitkPropertyNameHelper.h> // itk #include <itkThresholdImageFilter.h> // dcmqi #include <dcmqi/ImageSEGConverter.h> // us #include <usGetModuleContext.h> #include <usModuleContext.h> namespace mitk { DICOMSegmentationIO::DICOMSegmentationIO() : AbstractFileIO(LabelSetImage::GetStaticNameOfClass(), mitk::MitkDICOMSEGIOMimeTypes::DICOMSEG_MIMETYPE_NAME(), "DICOM Segmentation") { AbstractFileWriter::SetRanking(10); AbstractFileReader::SetRanking(10); this->RegisterService(); } std::vector<mitk::DICOMTagPath> DICOMSegmentationIO::GetDICOMTagsOfInterest() { std::vector<mitk::DICOMTagPath> result; result.emplace_back(DICOMSegmentationConstants::SEGMENT_SEQUENCE_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_NUMBER_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_LABEL_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_ALGORITHM_TYPE_PATH()); result.emplace_back(DICOMSegmentationConstants::ANATOMIC_REGION_SEQUENCE_PATH()); result.emplace_back(DICOMSegmentationConstants::ANATOMIC_REGION_CODE_VALUE_PATH()); result.emplace_back(DICOMSegmentationConstants::ANATOMIC_REGION_CODE_SCHEME_PATH()); result.emplace_back(DICOMSegmentationConstants::ANATOMIC_REGION_CODE_MEANING_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENTED_PROPERTY_CATEGORY_SEQUENCE_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_CATEGORY_CODE_VALUE_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_CATEGORY_CODE_SCHEME_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_CATEGORY_CODE_MEANING_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENTED_PROPERTY_TYPE_SEQUENCE_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_TYPE_CODE_VALUE_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_TYPE_CODE_SCHEME_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_TYPE_CODE_MEANING_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENTED_PROPERTY_MODIFIER_SEQUENCE_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_MODIFIER_CODE_VALUE_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_MODIFIER_CODE_SCHEME_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_MODIFIER_CODE_MEANING_PATH()); return result; } IFileIO::ConfidenceLevel DICOMSegmentationIO::GetWriterConfidenceLevel() const { if (AbstractFileIO::GetWriterConfidenceLevel() == Unsupported) return Unsupported; // Check if the input file is a segmentation const LabelSetImage *input = dynamic_cast<const LabelSetImage *>(this->GetInput()); if (input) { if ((input->GetDimension() != 3)) { MITK_INFO << "DICOM segmentation writer is tested only with 3D images, sorry."; return Unsupported; } // Check if input file has dicom information for the referenced image (original DICOM image, e.g. CT) Still necessary, see write() mitk::StringLookupTableProperty::Pointer dicomFilesProp = dynamic_cast<mitk::StringLookupTableProperty *>(input->GetProperty("referenceFiles").GetPointer()); if (dicomFilesProp.IsNotNull()) return Supported; } return Unsupported; } void DICOMSegmentationIO::Write() { ValidateOutputLocation(); mitk::LocaleSwitch localeSwitch("C"); LocalFile localFile(this); const std::string path = localFile.GetFileName(); auto input = dynamic_cast<const LabelSetImage *>(this->GetInput()); if (input == nullptr) mitkThrow() << "Cannot write non-image data"; // Get DICOM information from referenced image vector<std::unique_ptr<DcmDataset>> dcmDatasetsSourceImage; std::unique_ptr<DcmFileFormat> readFileFormat(new DcmFileFormat()); try { // TODO: Generate dcmdataset witk DICOM tags from property list; ATM the source are the filepaths from the // property list mitk::StringLookupTableProperty::Pointer filesProp = dynamic_cast<mitk::StringLookupTableProperty *>(input->GetProperty("referenceFiles").GetPointer()); if (filesProp.IsNull()) { mitkThrow() << "No property with dicom file path."; return; } StringLookupTable filesLut = filesProp->GetValue(); const StringLookupTable::LookupTableType &lookUpTableMap = filesLut.GetLookupTable(); for (const auto &it : lookUpTableMap) { const char *fileName = (it.second).c_str(); if (readFileFormat->loadFile(fileName, EXS_Unknown).good()) { std::unique_ptr<DcmDataset> readDCMDataset(readFileFormat->getAndRemoveDataset()); dcmDatasetsSourceImage.push_back(std::move(readDCMDataset)); } } } catch (const std::exception &e) { MITK_ERROR << "An error occurred while getting the dicom informations: " << e.what() << endl; return; } // Iterate over all layers. For each a dcm file will be generated for (unsigned int layer = 0; layer < input->GetNumberOfLayers(); ++layer) { vector<itkInternalImageType::Pointer> segmentations; try { // Hack: Remove the const attribute to switch between the layer images. Normally you could get the different // layer images by input->GetLayerImage(layer) mitk::LabelSetImage *mitkLayerImage = const_cast<mitk::LabelSetImage *>(input); mitkLayerImage->SetActiveLayer(layer); // Cast mitk layer image to itk ImageToItk<itkInputImageType>::Pointer imageToItkFilter = ImageToItk<itkInputImageType>::New(); imageToItkFilter->SetInput(mitkLayerImage); // Cast from original itk type to dcmqi input itk image type typedef itk::CastImageFilter<itkInputImageType, itkInternalImageType> castItkImageFilterType; castItkImageFilterType::Pointer castFilter = castItkImageFilterType::New(); castFilter->SetInput(imageToItkFilter->GetOutput()); castFilter->Update(); itkInternalImageType::Pointer itkLabelImage = castFilter->GetOutput(); itkLabelImage->DisconnectPipeline(); // Iterate over all labels. For each label a segmentation image will be created - auto labelSet = input->GetConstLabelsInGroup(layer); + auto labelSet = input->GetConstLabelsByValue(input->GetLabelValuesByGroup(layer)); for (const auto label : labelSet) { // Thresold over the image with the given label value itk::ThresholdImageFilter<itkInternalImageType>::Pointer thresholdFilter = itk::ThresholdImageFilter<itkInternalImageType>::New(); thresholdFilter->SetInput(itkLabelImage); thresholdFilter->ThresholdOutside(label->GetValue(), label->GetValue()); thresholdFilter->SetOutsideValue(0); thresholdFilter->Update(); itkInternalImageType::Pointer segmentImage = thresholdFilter->GetOutput(); segmentImage->DisconnectPipeline(); segmentations.push_back(segmentImage); } } catch (const itk::ExceptionObject &e) { MITK_ERROR << e.GetDescription() << endl; return; } // Create segmentation meta information const std::string tmpMetaInfoFile = this->CreateMetaDataJsonFile(layer); MITK_INFO << "Writing image: " << path << std::endl; try { //TODO is there a better way? Interface expects a vector of raw pointer. vector<DcmDataset*> rawVecDataset; for (const auto& dcmDataSet : dcmDatasetsSourceImage) rawVecDataset.push_back(dcmDataSet.get()); // Convert itk segmentation images to dicom image std::unique_ptr<dcmqi::ImageSEGConverter> converter = std::make_unique<dcmqi::ImageSEGConverter>(); std::unique_ptr<DcmDataset> result(converter->itkimage2dcmSegmentation(rawVecDataset, segmentations, tmpMetaInfoFile, false)); // Write dicom file DcmFileFormat dcmFileFormat(result.get()); std::string filePath = path.substr(0, path.find_last_of(".")); // If there is more than one layer, we have to write more than 1 dicom file if (input->GetNumberOfLayers() != 1) filePath = filePath + std::to_string(layer) + ".dcm"; else filePath = filePath + ".dcm"; dcmFileFormat.saveFile(filePath.c_str(), EXS_LittleEndianExplicit); } catch (const std::exception &e) { MITK_ERROR << "An error occurred during writing the DICOM Seg: " << e.what() << endl; return; } } // Write a dcm file for the next layer } IFileIO::ConfidenceLevel DICOMSegmentationIO::GetReaderConfidenceLevel() const { if (AbstractFileIO::GetReaderConfidenceLevel() == Unsupported) return Unsupported; const std::string fileName = this->GetLocalFileName(); DcmFileFormat dcmFileFormat; OFCondition status = dcmFileFormat.loadFile(fileName.c_str()); if (status.bad()) return Unsupported; OFString modality; if (dcmFileFormat.getDataset()->findAndGetOFString(DCM_Modality, modality).good()) { if (modality.compare("SEG") == 0) return Supported; else return Unsupported; } return Unsupported; } std::vector<BaseData::Pointer> DICOMSegmentationIO::DoRead() { mitk::LocaleSwitch localeSwitch("C"); LabelSetImage::Pointer labelSetImage; std::vector<BaseData::Pointer> result; const std::string path = this->GetLocalFileName(); MITK_INFO << "loading " << path << std::endl; if (path.empty()) mitkThrow() << "Empty filename in mitk::ItkImageIO "; try { // Get the dcm data set from file path DcmFileFormat dcmFileFormat; OFCondition status = dcmFileFormat.loadFile(path.c_str()); if (status.bad()) mitkThrow() << "Can't read the input file!"; DcmDataset *dataSet = dcmFileFormat.getDataset(); if (dataSet == nullptr) mitkThrow() << "Can't read data from input file!"; //=============================== dcmqi part ==================================== // Read the DICOM SEG images (segItkImages) and DICOM tags (metaInfo) std::unique_ptr<dcmqi::ImageSEGConverter> converter = std::make_unique<dcmqi::ImageSEGConverter>(); pair<map<unsigned, itkInternalImageType::Pointer>, string> dcmqiOutput = converter->dcmSegmentation2itkimage(dataSet); map<unsigned, itkInternalImageType::Pointer> segItkImages = dcmqiOutput.first; dcmqi::JSONSegmentationMetaInformationHandler metaInfo(dcmqiOutput.second.c_str()); metaInfo.read(); MITK_INFO << "Input " << metaInfo.getJSONOutputAsString(); //=============================================================================== // Get the label information from segment attributes for each itk image vector<map<unsigned, dcmqi::SegmentAttributes *>>::const_iterator segmentIter = metaInfo.segmentsAttributesMappingList.begin(); // For each itk image add a layer to the LabelSetImage output for (auto &element : segItkImages) { // Get the labeled image and cast it to mitkImage typedef itk::CastImageFilter<itkInternalImageType, itkInputImageType> castItkImageFilterType; castItkImageFilterType::Pointer castFilter = castItkImageFilterType::New(); castFilter->SetInput(element.second); castFilter->Update(); Image::Pointer layerImage; CastToMitkImage(castFilter->GetOutput(), layerImage); // Get pixel value of the label itkInternalImageType::ValueType segValue = 1; typedef itk::ImageRegionIterator<const itkInternalImageType> IteratorType; // Iterate over the image to find the pixel value of the label IteratorType iter(element.second, element.second->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { itkInputImageType::PixelType value = iter.Get(); if (value != LabelSetImage::UnlabeledValue) { segValue = value; break; } ++iter; } // Get Segment information map map<unsigned, dcmqi::SegmentAttributes *> segmentMap = (*segmentIter); map<unsigned, dcmqi::SegmentAttributes *>::const_iterator segmentMapIter = (*segmentIter).begin(); dcmqi::SegmentAttributes *segmentAttribute = (*segmentMapIter).second; OFString labelName; if (segmentAttribute->getSegmentedPropertyTypeCodeSequence() != nullptr) { segmentAttribute->getSegmentedPropertyTypeCodeSequence()->getCodeMeaning(labelName); if (segmentAttribute->getSegmentedPropertyTypeModifierCodeSequence() != nullptr) { OFString modifier; segmentAttribute->getSegmentedPropertyTypeModifierCodeSequence()->getCodeMeaning(modifier); labelName.append(" (").append(modifier).append(")"); } } else { labelName = std::to_string(segmentAttribute->getLabelID()).c_str(); if (labelName.empty()) labelName = "Unnamed"; } float tmp[3] = { 0.0, 0.0, 0.0 }; if (segmentAttribute->getRecommendedDisplayRGBValue() != nullptr) { tmp[0] = segmentAttribute->getRecommendedDisplayRGBValue()[0] / 255.0; tmp[1] = segmentAttribute->getRecommendedDisplayRGBValue()[1] / 255.0; tmp[2] = segmentAttribute->getRecommendedDisplayRGBValue()[2] / 255.0; } Label *newLabel = nullptr; // If labelSetImage do not exists (first image) if (labelSetImage.IsNull()) { // Initialize the labelSetImage with the read image labelSetImage = LabelSetImage::New(); labelSetImage->InitializeByLabeledImage(layerImage); // Already a label was generated, so set the information to this newLabel = labelSetImage->GetActiveLabel(); newLabel->SetName(labelName.c_str()); newLabel->SetColor(Color(tmp)); newLabel->SetValue(segValue); } else { // Add a new layer to the labelSetImage. Background label is set automatically labelSetImage->AddLayer(layerImage); // Add new label newLabel = new Label; newLabel->SetName(labelName.c_str()); newLabel->SetColor(Color(tmp)); newLabel->SetValue(segValue); labelSetImage->AddLabel(newLabel, labelSetImage->GetActiveLayer()); } // Add some more label properties this->SetLabelProperties(newLabel, segmentAttribute); ++segmentIter; } - labelSetImage->GetLabelSet()->SetAllLabelsVisible(true); + labelSetImage->SetAllLabelsVisible(true); // Add some general DICOM Segmentation properties mitk::IDICOMTagsOfInterest *toiSrv = DICOMIOHelper::GetTagsOfInterestService(); auto tagsOfInterest = toiSrv->GetTagsOfInterest(); DICOMTagPathList tagsOfInterestList; for (const auto &tag : tagsOfInterest) { tagsOfInterestList.push_back(tag.first); } mitk::DICOMDCMTKTagScanner::Pointer scanner = mitk::DICOMDCMTKTagScanner::New(); scanner->SetInputFiles({ GetInputLocation() }); scanner->AddTagPaths(tagsOfInterestList); scanner->Scan(); mitk::DICOMDatasetAccessingImageFrameList frames = scanner->GetFrameInfoList(); if (frames.empty()) { MITK_ERROR << "Error reading the DICOM Seg file" << std::endl; return result; } auto findings = DICOMIOHelper::ExtractPathsOfInterest(tagsOfInterestList, frames); DICOMIOHelper::SetProperties(labelSetImage, findings); // Set active layer to the first layer of the labelset image if (labelSetImage->GetNumberOfLayers() > 1 && labelSetImage->GetActiveLayer() != 0) labelSetImage->SetActiveLayer(0); } catch (const std::exception &e) { MITK_ERROR << "An error occurred while reading the DICOM Seg file: " << e.what(); return result; } catch (...) { MITK_ERROR << "An error occurred in dcmqi while reading the DICOM Seg file"; return result; } result.push_back(labelSetImage.GetPointer()); return result; } const std::string mitk::DICOMSegmentationIO::CreateMetaDataJsonFile(int layer) { const mitk::LabelSetImage *image = dynamic_cast<const mitk::LabelSetImage *>(this->GetInput()); const std::string output; dcmqi::JSONSegmentationMetaInformationHandler handler; // 1. Metadata attributes that will be listed in the resulting DICOM SEG object std::string contentCreatorName; if (!image->GetPropertyList()->GetStringProperty(GeneratePropertyNameForDICOMTag(0x0070, 0x0084).c_str(), contentCreatorName)) contentCreatorName = "MITK"; handler.setContentCreatorName(contentCreatorName); std::string clinicalTrailSeriesId; if (!image->GetPropertyList()->GetStringProperty(GeneratePropertyNameForDICOMTag(0x0012, 0x0071).c_str(), clinicalTrailSeriesId)) clinicalTrailSeriesId = "Session 1"; handler.setClinicalTrialSeriesID(clinicalTrailSeriesId); std::string clinicalTrialTimePointID; if (!image->GetPropertyList()->GetStringProperty(GeneratePropertyNameForDICOMTag(0x0012, 0x0050).c_str(), clinicalTrialTimePointID)) clinicalTrialTimePointID = "0"; handler.setClinicalTrialTimePointID(clinicalTrialTimePointID); std::string clinicalTrialCoordinatingCenterName = ""; if (!image->GetPropertyList()->GetStringProperty(GeneratePropertyNameForDICOMTag(0x0012, 0x0060).c_str(), clinicalTrialCoordinatingCenterName)) clinicalTrialCoordinatingCenterName = "Unknown"; handler.setClinicalTrialCoordinatingCenterName(clinicalTrialCoordinatingCenterName); std::string seriesDescription; if (!image->GetPropertyList()->GetStringProperty("name", seriesDescription)) seriesDescription = "MITK Segmentation"; handler.setSeriesDescription(seriesDescription); handler.setSeriesNumber("0" + std::to_string(layer)); handler.setInstanceNumber("1"); handler.setBodyPartExamined(""); - auto labelSet = image->GetConstLabelsInGroup(layer); + auto labelSet = image->GetConstLabelsByValue(image->GetLabelValuesByGroup(layer)); for (const auto label : labelSet) { if (label != nullptr) { TemporoSpatialStringProperty *segmentNumberProp = dynamic_cast<mitk::TemporoSpatialStringProperty *>(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_NUMBER_PATH()).c_str())); TemporoSpatialStringProperty *segmentLabelProp = dynamic_cast<mitk::TemporoSpatialStringProperty *>(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_LABEL_PATH()).c_str())); TemporoSpatialStringProperty *algorithmTypeProp = dynamic_cast<mitk::TemporoSpatialStringProperty *>(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_ALGORITHM_TYPE_PATH()).c_str())); TemporoSpatialStringProperty *segmentCategoryCodeValueProp = dynamic_cast<mitk::TemporoSpatialStringProperty *>(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_CATEGORY_CODE_VALUE_PATH()).c_str())); TemporoSpatialStringProperty *segmentCategoryCodeSchemeProp = dynamic_cast<mitk::TemporoSpatialStringProperty *>(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_CATEGORY_CODE_SCHEME_PATH()).c_str())); TemporoSpatialStringProperty *segmentCategoryCodeMeaningProp = dynamic_cast<mitk::TemporoSpatialStringProperty *>(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_CATEGORY_CODE_MEANING_PATH()).c_str())); TemporoSpatialStringProperty *segmentTypeCodeValueProp = dynamic_cast<mitk::TemporoSpatialStringProperty *>(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_TYPE_CODE_VALUE_PATH()).c_str())); TemporoSpatialStringProperty *segmentTypeCodeSchemeProp = dynamic_cast<mitk::TemporoSpatialStringProperty *>(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_TYPE_CODE_SCHEME_PATH()).c_str())); TemporoSpatialStringProperty *segmentTypeCodeMeaningProp = dynamic_cast<mitk::TemporoSpatialStringProperty *>(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_TYPE_CODE_MEANING_PATH()).c_str())); TemporoSpatialStringProperty *segmentModifierCodeValueProp = dynamic_cast<mitk::TemporoSpatialStringProperty *>(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_MODIFIER_CODE_VALUE_PATH()).c_str())); TemporoSpatialStringProperty *segmentModifierCodeSchemeProp = dynamic_cast<mitk::TemporoSpatialStringProperty *>(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_MODIFIER_CODE_SCHEME_PATH()).c_str())); TemporoSpatialStringProperty *segmentModifierCodeMeaningProp = dynamic_cast<mitk::TemporoSpatialStringProperty *>(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_MODIFIER_CODE_MEANING_PATH()).c_str())); dcmqi::SegmentAttributes *segmentAttribute = nullptr; if (segmentNumberProp->GetValue() == "") { MITK_ERROR << "Something went wrong with the label ID."; } else { int labelId = std::stoi(segmentNumberProp->GetValue()); segmentAttribute = handler.createAndGetNewSegment(labelId); } if (segmentAttribute != nullptr) { segmentAttribute->setSegmentLabel(segmentLabelProp->GetValueAsString()); segmentAttribute->setSegmentDescription(segmentLabelProp->GetValueAsString()); segmentAttribute->setSegmentAlgorithmType(algorithmTypeProp->GetValueAsString()); segmentAttribute->setSegmentAlgorithmName("MITK Segmentation"); if (segmentCategoryCodeValueProp != nullptr && segmentCategoryCodeSchemeProp != nullptr && segmentCategoryCodeMeaningProp != nullptr) segmentAttribute->setSegmentedPropertyCategoryCodeSequence( segmentCategoryCodeValueProp->GetValueAsString(), segmentCategoryCodeSchemeProp->GetValueAsString(), segmentCategoryCodeMeaningProp->GetValueAsString()); else // some default values segmentAttribute->setSegmentedPropertyCategoryCodeSequence( "M-01000", "SRT", "Morphologically Altered Structure"); if (segmentTypeCodeValueProp != nullptr && segmentTypeCodeSchemeProp != nullptr && segmentTypeCodeMeaningProp != nullptr) { segmentAttribute->setSegmentedPropertyTypeCodeSequence(segmentTypeCodeValueProp->GetValueAsString(), segmentTypeCodeSchemeProp->GetValueAsString(), segmentTypeCodeMeaningProp->GetValueAsString()); handler.setBodyPartExamined(segmentTypeCodeMeaningProp->GetValueAsString()); } else { // some default values segmentAttribute->setSegmentedPropertyTypeCodeSequence("M-03000", "SRT", "Mass"); handler.setBodyPartExamined("Mass"); } if (segmentModifierCodeValueProp != nullptr && segmentModifierCodeSchemeProp != nullptr && segmentModifierCodeMeaningProp != nullptr) segmentAttribute->setSegmentedPropertyTypeModifierCodeSequence( segmentModifierCodeValueProp->GetValueAsString(), segmentModifierCodeSchemeProp->GetValueAsString(), segmentModifierCodeMeaningProp->GetValueAsString()); Color color = label->GetColor(); segmentAttribute->setRecommendedDisplayRGBValue(color[0] * 255, color[1] * 255, color[2] * 255); } } } return handler.getJSONOutputAsString(); } void mitk::DICOMSegmentationIO::SetLabelProperties(mitk::Label *label, dcmqi::SegmentAttributes *segmentAttribute) { // Segment Number:Identification number of the segment.The value of Segment Number(0062, 0004) shall be unique // within the Segmentation instance in which it is created label->SetProperty(DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_NUMBER_PATH()).c_str(), TemporoSpatialStringProperty::New(std::to_string(label->GetValue()))); // Segment Label: User-defined label identifying this segment. label->SetProperty(DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_LABEL_PATH()).c_str(), TemporoSpatialStringProperty::New(label->GetName())); // Segment Algorithm Type: Type of algorithm used to generate the segment. if (!segmentAttribute->getSegmentAlgorithmType().empty()) label->SetProperty(DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_ALGORITHM_TYPE_PATH()).c_str(), TemporoSpatialStringProperty::New(segmentAttribute->getSegmentAlgorithmType())); // Add Segmented Property Category Code Sequence tags auto categoryCodeSequence = segmentAttribute->getSegmentedPropertyCategoryCodeSequence(); if (categoryCodeSequence != nullptr) { OFString codeValue; // (0008,0100) Code Value categoryCodeSequence->getCodeValue(codeValue); label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_CATEGORY_CODE_VALUE_PATH()).c_str(), TemporoSpatialStringProperty::New(codeValue.c_str())); OFString codeScheme; // (0008,0102) Coding Scheme Designator categoryCodeSequence->getCodingSchemeDesignator(codeScheme); label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_CATEGORY_CODE_SCHEME_PATH()).c_str(), TemporoSpatialStringProperty::New(codeScheme.c_str())); OFString codeMeaning; // (0008,0104) Code Meaning categoryCodeSequence->getCodeMeaning(codeMeaning); label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_CATEGORY_CODE_MEANING_PATH()).c_str(), TemporoSpatialStringProperty::New(codeMeaning.c_str())); } // Add Segmented Property Type Code Sequence tags auto typeCodeSequence = segmentAttribute->getSegmentedPropertyTypeCodeSequence(); if (typeCodeSequence != nullptr) { OFString codeValue; // (0008,0100) Code Value typeCodeSequence->getCodeValue(codeValue); label->SetProperty(DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_TYPE_CODE_VALUE_PATH()).c_str(), TemporoSpatialStringProperty::New(codeValue.c_str())); OFString codeScheme; // (0008,0102) Coding Scheme Designator typeCodeSequence->getCodingSchemeDesignator(codeScheme); label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_TYPE_CODE_SCHEME_PATH()).c_str(), TemporoSpatialStringProperty::New(codeScheme.c_str())); OFString codeMeaning; // (0008,0104) Code Meaning typeCodeSequence->getCodeMeaning(codeMeaning); label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_TYPE_CODE_MEANING_PATH()).c_str(), TemporoSpatialStringProperty::New(codeMeaning.c_str())); } // Add Segmented Property Type Modifier Code Sequence tags auto modifierCodeSequence = segmentAttribute->getSegmentedPropertyTypeModifierCodeSequence(); if (modifierCodeSequence != nullptr) { OFString codeValue; // (0008,0100) Code Value modifierCodeSequence->getCodeValue(codeValue); label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_MODIFIER_CODE_VALUE_PATH()).c_str(), TemporoSpatialStringProperty::New(codeValue.c_str())); OFString codeScheme; // (0008,0102) Coding Scheme Designator modifierCodeSequence->getCodingSchemeDesignator(codeScheme); label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_MODIFIER_CODE_SCHEME_PATH()).c_str(), TemporoSpatialStringProperty::New(codeScheme.c_str())); OFString codeMeaning; // (0008,0104) Code Meaning modifierCodeSequence->getCodeMeaning(codeMeaning); label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_MODIFIER_CODE_MEANING_PATH()).c_str(), TemporoSpatialStringProperty::New(codeMeaning.c_str())); } // Add Atomic RegionSequence tags auto atomicRegionSequence = segmentAttribute->getAnatomicRegionSequence(); if (atomicRegionSequence != nullptr) { OFString codeValue; // (0008,0100) Code Value atomicRegionSequence->getCodeValue(codeValue); label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::ANATOMIC_REGION_CODE_VALUE_PATH()).c_str(), TemporoSpatialStringProperty::New(codeValue.c_str())); OFString codeScheme; // (0008,0102) Coding Scheme Designator atomicRegionSequence->getCodingSchemeDesignator(codeScheme); label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::ANATOMIC_REGION_CODE_SCHEME_PATH()).c_str(), TemporoSpatialStringProperty::New(codeScheme.c_str())); OFString codeMeaning; // (0008,0104) Code Meaning atomicRegionSequence->getCodeMeaning(codeMeaning); label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::ANATOMIC_REGION_CODE_MEANING_PATH()).c_str(), TemporoSpatialStringProperty::New(codeMeaning.c_str())); } } DICOMSegmentationIO *DICOMSegmentationIO::IOClone() const { return new DICOMSegmentationIO(*this); } } // namespace #endif //__mitkDICOMSegmentationIO__cpp diff --git a/Modules/Multilabel/mitkLabelSetImage.cpp b/Modules/Multilabel/mitkLabelSetImage.cpp index 27fd5e6a9b..72a936a99a 100644 --- a/Modules/Multilabel/mitkLabelSetImage.cpp +++ b/Modules/Multilabel/mitkLabelSetImage.cpp @@ -1,1505 +1,1592 @@ /*============================================================================ 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 <vtkCell.h> #include <vtkTransform.h> #include <vtkTransformPolyDataFilter.h> #include <itkImageRegionIterator.h> #include <itkQuadEdgeMesh.h> #include <itkTriangleMeshToBinaryImageFilter.h> #include <itkLabelGeometryImageFilter.h> //#include <itkRelabelComponentImageFilter.h> #include <itkCommand.h> #include <itkBinaryFunctorImageFilter.h> namespace mitk { template <typename ImageType> void ClearBufferProcessing(ImageType* itkImage) { itkImage->FillBuffer(0); } void ClearImageBuffer(mitk::Image* image) { if (image->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(image, ClearBufferProcessing, 4); } else { AccessByItk(image, ClearBufferProcessing); } } } mitk::LabelSetImage::LabelSetImage() : mitk::Image(), m_UnlabeledLabelLock(false), m_ActiveLayer(0), m_activeLayerInvalid(false), m_ActiveLabelValue(0) { // 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), m_LookupTable(other.m_LookupTable->Clone()), m_ActiveLabelValue(other.m_ActiveLabelValue) { m_Groups = other.m_Groups; for (auto [value, label] : other.m_LabelMap) { auto labelClone = label->Clone(); auto groupID = other.m_LabelToGroupMap.at(value); this->AddLabelToMap(value, labelClone, groupID); this->RegisterLabel(labelClone); DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(labelClone); } // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } void mitk::LabelSetImage::Initialize(const mitk::Image *other) { mitk::PixelType pixelType(mitk::MakeScalarPixelType<LabelSetImage::PixelType>()); 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 ClearImageBuffer(this); // 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 [value, label] : m_LabelMap) { this->ReleaseLabel(label); } m_LabelMap.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_LayerContainer.size(); } void mitk::LabelSetImage::RemoveGroup(GroupIndexType indexToDelete) { const auto activeIndex = GetActiveLayer(); // 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; } auto relevantLabels = m_GroupToLabelMap[indexToDelete]; // remove labels of group for (auto labelValue : relevantLabels) { auto label = m_LabelMap[labelValue]; //first erase the pixel content (also triggers a LabelModified event) this->EraseLabel(labelValue); this->ReleaseLabel(label); m_LabelToGroupMap.erase(labelValue); m_LabelMap.erase(labelValue); this->m_LabelRemovedMessage.Send(labelValue); } // remove the group entries in the maps and the image. m_Groups.erase(m_Groups.begin() + indexToDelete); m_GroupToLabelMap.erase(m_GroupToLabelMap.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->m_LabelsChangedMessage.Send(relevantLabels); this->m_GroupRemovedMessage.Send(indexToDelete); this->Modified(); } mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::ExtractLabelValuesFromLabelVector(const LabelVectorType& labels) { LabelValueVectorType result; for (auto label : labels) { result.emplace_back(label->GetValue()); } return result; } mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::ExtractLabelValuesFromLabelVector(const ConstLabelVectorType& labels) { LabelValueVectorType result; for (auto label : labels) { result.emplace_back(label->GetValue()); } return result; } mitk::LabelSetImage::ConstLabelVectorType mitk::LabelSetImage::ConvertLabelVectorConst(const LabelVectorType& labels) { ConstLabelVectorType result(labels.begin(), labels.end()); return result; }; mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::GetUsedLabelValues() const { LabelValueVectorType result = { UnlabeledValue }; for (auto [value, label] : m_LabelMap) { result.emplace_back(value); } return result; } mitk::LabelSetImage::GroupIndexType mitk::LabelSetImage::AddLayer(ConstLabelVector labels) { mitk::Image::Pointer newImage = mitk::Image::New(); newImage->Initialize(this->GetPixelType(), this->GetDimension(), this->GetDimensions(), this->GetImageDescriptor()->GetNumberOfChannels()); newImage->SetTimeGeometry(this->GetTimeGeometry()->Clone()); ClearImageBuffer(newImage); return this->AddLayer(newImage, labels); } mitk::LabelSetImage::GroupIndexType mitk::LabelSetImage::AddLayer(mitk::Image::Pointer layerImage, ConstLabelVector labels) { GroupIndexType newGroupID = m_Groups.size(); // push a new working image for the new layer m_LayerContainer.push_back(layerImage); m_Groups.push_back(""); for (auto label : labels) { if (m_LabelMap.end() != m_LabelMap.find(label->GetValue())) { mitkThrow() << "Cannot add layer. Labels that should be added with layer use at least one label value that is already in use. Conflicted label value: " << label->GetValue(); } auto labelClone = label->Clone(); this->AddLabelToMap(labelClone->GetValue(), labelClone, newGroupID); this->RegisterLabel(labelClone); DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(labelClone); } SetActiveLayer(newGroupID); this->Modified(); this->m_GroupAddedMessage.Send(newGroupID); return newGroupID; } void mitk::LabelSetImage::ReplaceGroupLabels(const GroupIndexType groupID, const ConstLabelVectorType& labelSet) { if (m_LayerContainer.size() <= groupID) { mitkThrow() << "Trying to replace labels of non-exising group. Invalid group id: "<<groupID; } //remove old group labels auto oldLabels = this->m_GroupToLabelMap[groupID]; for (auto labelID : this->m_GroupToLabelMap[groupID]) { this->RemoveLabelFromMap(groupID); this->m_LabelRemovedMessage.Send(labelID); } this->m_LabelsChangedMessage.Send(oldLabels); this->m_GroupModifiedMessage.Send(groupID); //add new labels to group for (auto label : labelSet) { this->AddLabel(label->Clone(), groupID, true, false); } } void mitk::LabelSetImage::ReplaceGroupLabels(const GroupIndexType groupID, const LabelVectorType& labelSet) { return ReplaceGroupLabels(groupID, ConvertLabelVectorConst(labelSet)); } mitk::Image* mitk::LabelSetImage::GetGroupImage(GroupIndexType groupID) { if (!this->ExistGroup(groupID)) mitkThrow() << "Error, cannot return group image. Group ID is invalid. Invalid ID: " << groupID; return groupID == this->GetActiveLayer() ? this : m_LayerContainer[groupID]; } const mitk::Image* mitk::LabelSetImage::GetGroupImage(GroupIndexType groupID) const { if (!this->ExistGroup(groupID)) mitkThrow() << "Error, cannot return group image. Group ID is invalid. Invalid ID: " << groupID; return groupID == this->GetActiveLayer() ? this : m_LayerContainer[groupID].GetPointer(); } 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::SetActiveLabel(LabelValueType label) { m_ActiveLabelValue = label; if (label != UnlabeledValue) { auto groupID = this->GetGroupIndexOfLabel(label); this->SetActiveLayer(groupID); } Modified(); } void mitk::LabelSetImage::ClearBuffer() { try { ClearImageBuffer(this); this->Modified(); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } } bool mitk::LabelSetImage::ExistLabelSet(unsigned int layer) const { return layer < m_LayerContainer.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(); } this->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<PixelType>& 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(); } this->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) { if (m_LabelMap.find(pixelValue) == m_LabelMap.end()) return; auto groupID = this->GetGroupIndexOfLabel(pixelValue); //first erase the pixel content (also triggers a LabelModified event) this->EraseLabel(pixelValue); this->RemoveLabelFromMap(pixelValue); if (m_ActiveLabelValue == pixelValue) { this->SetActiveLabel(0); } this->m_LabelRemovedMessage.Send(pixelValue); this->m_LabelsChangedMessage.Send({ pixelValue }); this->m_GroupModifiedMessage.Send(groupID); } void mitk::LabelSetImage::RemoveLabelFromMap(LabelValueType pixelValue) { if (m_LabelMap.find(pixelValue) == m_LabelMap.end()) mitkThrow()<<"Invalid state of of instance. RemoveLabelFromMap was called for unkown label id. invalid label id: "<<pixelValue; auto groupID = this->GetGroupIndexOfLabel(pixelValue); this->ReleaseLabel(m_LabelMap[pixelValue]); //now remove the label entry itself m_LabelMap.erase(pixelValue); m_LabelToGroupMap.erase(pixelValue); auto labelsInGroup = m_GroupToLabelMap[groupID]; labelsInGroup.erase(std::remove(labelsInGroup.begin(), labelsInGroup.end(), pixelValue), labelsInGroup.end()); m_GroupToLabelMap[groupID] = labelsInGroup; } void mitk::LabelSetImage::RemoveLabels(const LabelValueVectorType& vectorOfLabelPixelValues) { for (const auto labelValue : vectorOfLabelPixelValues) { this->RemoveLabel(labelValue); } this->m_LabelsChangedMessage.Send(vectorOfLabelPixelValues); } void mitk::LabelSetImage::EraseLabel(PixelType pixelValue) { try { auto groupID = this->GetGroupIndexOfLabel(pixelValue); 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<PixelType>& VectorOfLabelPixelValues) { for (unsigned int idx = 0; idx < VectorOfLabelPixelValues.size(); idx++) { this->EraseLabel(VectorOfLabelPixelValues[idx]); } } mitk::Label* mitk::LabelSetImage::AddLabel(mitk::Label* label, GroupIndexType groupID, bool addAsClone, bool correctLabelValue) { unsigned int max_size = mitk::Label::MAX_LABEL_VALUE + 1; if (m_LayerContainer.size() >= max_size) return nullptr; mitk::Label::Pointer newLabel = addAsClone ? label->Clone() : Label::Pointer(label); auto pixelValue = newLabel->GetValue(); auto usedValues = this->GetUsedLabelValues(); auto finding = std::find(usedValues.begin(), usedValues.end(), pixelValue); if (!usedValues.empty() && usedValues.end() != finding) { if (correctLabelValue) { pixelValue = usedValues.back() + 1; newLabel->SetValue(pixelValue); } else { mitkThrow() << "Cannot add label due to conflicting label value that already exists in the MultiLabelSegmentation. Conflicting label value: " << pixelValue; } } this->AddLabelToMap(pixelValue, newLabel, groupID); this->RegisterLabel(newLabel); // add DICOM information of the label DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(newLabel); m_LabelAddedMessage.Send(newLabel->GetValue()); m_ActiveLabelValue = newLabel->GetValue(); this->Modified(); return newLabel; } mitk::Label* mitk::LabelSetImage::AddLabel(const std::string& name, const mitk::Color& color, GroupIndexType groupID) { mitk::Label::Pointer newLabel = mitk::Label::New(); newLabel->SetName(name); newLabel->SetColor(color); return AddLabel(newLabel,groupID,false); } void mitk::LabelSetImage::RenameLabel(PixelType pixelValue, const std::string& name, const mitk::Color& color) { mitk::Label* label = GetLabel(pixelValue); label->SetName(name); label->SetColor(color); this->UpdateLookupTable(pixelValue); // change DICOM information of the label DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(label); } mitk::Label *mitk::LabelSetImage::GetActiveLabel() { return m_ActiveLabel == UnlabeledValue ? nullptr : m_LabelMap[m_ActiveLabel]; } const mitk::Label* mitk::LabelSetImage::GetActiveLabel() const { return m_ActiveLabel == UnlabeledValue ? nullptr : m_LabelMap.at(m_ActiveLabel); } void mitk::LabelSetImage::UpdateCenterOfMass(PixelType pixelValue) { this->UpdateCenterOfMass(pixelValue, this->GetGroupIndexOfLabel(pixelValue)); } void mitk::LabelSetImage::SetLookupTable(mitk::LookupTable* lut) { m_LookupTable = lut; this->Modified(); } void mitk::LabelSetImage::UpdateLookupTable(PixelType pixelValue) { const mitk::Color& color = this->GetLabel(pixelValue)->GetColor(); double rgba[4]; m_LookupTable->GetTableValue(static_cast<int>(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<int>(pixelValue), rgba); } void mitk::LabelSetImage::UpdateCenterOfMass(PixelType pixelValue, unsigned int layer) { if (4 == this->GetDimension()) { AccessFixedDimensionByItk_1(this, CalculateCenterOfMassProcessing, 4, pixelValue); } else { AccessByItk_1(this, CalculateCenterOfMassProcessing, pixelValue); } } unsigned int mitk::LabelSetImage::GetNumberOfLabels(unsigned int layer) const { return m_GroupToLabelMap[layer].size(); } unsigned int mitk::LabelSetImage::GetTotalNumberOfLabels() const { return m_LabelMap.size(); } 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) { if (!this->ExistLabel(index)) mitkThrow() << "Error, cannot return label mask. Label ID is invalid. Invalid ID: " << index; auto mask = mitk::Image::New(); // 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()); ClearImageBuffer(mask); const auto groupID = this->GetGroupIndexOfLabel(index); TransferLabelContent(this->GetGroupImage(groupID), mask.GetPointer(), - this->GetConstLabelsInGroup(groupID), + this->GetConstLabelsByValue(this->GetLabelValuesByGroup(groupID)), LabelSetImage::UnlabeledValue, LabelSetImage::UnlabeledValue, false, { { index, 1 } }, MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle::IgnoreLocks); 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<mitk::Image *>(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 <typename LabelSetImageType, typename ImageType> void mitk::LabelSetImage::InitializeByLabeledImageProcessing(LabelSetImageType *labelSetImage, ImageType *image) { typedef itk::ImageRegionConstIteratorWithIndex<ImageType> SourceIteratorType; typedef itk::ImageRegionIterator<LabelSetImageType> TargetIteratorType; TargetIteratorType targetIter(labelSetImage, labelSetImage->GetRequestedRegion()); targetIter.GoToBegin(); SourceIteratorType sourceIter(image, image->GetRequestedRegion()); sourceIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { const auto originalSourceValue = sourceIter.Get(); const auto sourceValue = static_cast<PixelType>(originalSourceValue); if (originalSourceValue > mitk::Label::MAX_LABEL_VALUE) { mitkThrow() << "Cannot initialize MultiLabelSegmentation by image. Image contains a pixel value that exceeds the label value range. Invalid pixel value:" << originalSourceValue; } targetIter.Set(sourceValue); if (LabelSetImage::UnlabeledValue!=sourceValue && !this->ExistLabel(sourceValue)) { if (this->GetNumberOfLabels() >= mitk::Label::MAX_LABEL_VALUE) { mitkThrow() << "Cannot initialize MultiLabelSegmentation by image. Image contains to many labels."; } this->AddLayer(); std::stringstream name; name << "object-" << sourceValue; double rgba[4]; this->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->AddLabel(label,0,false); } ++sourceIter; ++targetIter; } } template <typename ImageType> void mitk::LabelSetImage::MaskStampProcessing(ImageType *itkImage, mitk::Image *mask, bool forceOverwrite) { typename ImageType::Pointer itkMask; mitk::CastToItkImage(mask, itkMask); typedef itk::ImageRegionConstIterator<ImageType> SourceIteratorType; typedef itk::ImageRegionIterator<ImageType> TargetIteratorType; SourceIteratorType sourceIter(itkMask, itkMask->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkImage, itkImage->GetLargestPossibleRegion()); targetIter.GoToBegin(); const auto activeLabel = this->GetActiveLabel()->GetValue(); while (!sourceIter.IsAtEnd()) { PixelType sourceValue = sourceIter.Get(); PixelType targetValue = targetIter.Get(); if ((sourceValue != UnlabeledValue) && (forceOverwrite || !this->IsLabelLocked(targetValue))) // skip unlabeled pixels and locked labels { targetIter.Set(activeLabel); } ++sourceIter; ++targetIter; } this->Modified(); } template <typename ImageType> void mitk::LabelSetImage::CalculateCenterOfMassProcessing(ImageType *itkImage, LabelValueType pixelValue) { if (ImageType::GetImageDimension() != 3) { return; } auto labelGeometryFilter = itk::LabelGeometryImageFilter<ImageType>::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]; this->GetLabel(pixelValue)->SetCenterOfMassIndex(pos); this->GetSlicedGeometry()->IndexToWorld(pos, pos); this->GetLabel(pixelValue)->SetCenterOfMassCoordinates(pos); } template <typename TPixel, unsigned int VImageDimension> void mitk::LabelSetImage::LayerContainerToImageProcessing(itk::Image<TPixel, VImageDimension> *target, unsigned int layer) { typedef itk::Image<TPixel, VImageDimension> ImageType; typename ImageType::Pointer itkSource; // mitk::CastToItkImage(m_LayerContainer[layer], itkSource); itkSource = ImageToItkImage<TPixel, VImageDimension>(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator<ImageType> SourceIteratorType; typedef itk::ImageRegionIterator<ImageType> 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 <typename TPixel, unsigned int VImageDimension> void mitk::LabelSetImage::ImageToLayerContainerProcessing(itk::Image<TPixel, VImageDimension> *source, unsigned int layer) const { typedef itk::Image<TPixel, VImageDimension> ImageType; typename ImageType::Pointer itkTarget; // mitk::CastToItkImage(m_LayerContainer[layer], itkTarget); itkTarget = ImageToItkImage<TPixel, VImageDimension>(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator<ImageType> SourceIteratorType; typedef itk::ImageRegionIterator<ImageType> 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 <typename ImageType> void mitk::LabelSetImage::EraseLabelProcessing(ImageType *itkImage, PixelType pixelValue) { typedef itk::ImageRegionIterator<ImageType> IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { PixelType value = iter.Get(); if (value == pixelValue) { iter.Set(0); } ++iter; } } template <typename ImageType> void mitk::LabelSetImage::MergeLabelProcessing(ImageType *itkImage, PixelType pixelValue, PixelType index) { typedef itk::ImageRegionIterator<ImageType> IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { if (iter.Get() == index) { iter.Set(pixelValue); } ++iter; } } 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; if (!this->ExistGroup(groupID)) mitkThrow() << "Cannot add label. Defined group is unkown. Invalid group index: " << groupID; m_LabelMap[labelValue] = label; m_LabelToGroupMap[labelValue] = groupID; auto groupFinding = std::find(m_GroupToLabelMap[groupID].begin(), m_GroupToLabelMap[groupID].end(), groupID); if (groupFinding == m_GroupToLabelMap[groupID].end()) { m_GroupToLabelMap[groupID] = { labelValue }; } else { m_GroupToLabelMap[groupID].push_back(labelValue); } auto command = itk::MemberCommand<LabelSetImage>::New(); command->SetCallbackFunction(this, &LabelSetImage::OnLabelModified); label->AddObserver(itk::ModifiedEvent(), command); } void mitk::LabelSetImage::RegisterLabel(mitk::Label* label) { UpdateLookupTable(label->GetValue()); auto command = itk::MemberCommand<LabelSetImage>::New(); command->SetCallbackFunction(this, &LabelSetImage::OnLabelModified); label->AddObserver(itk::ModifiedEvent(), command); } void mitk::LabelSetImage::ReleaseLabel(Label* label) { label->RemoveAllObservers(); } +void mitk::LabelSetImage::ApplyToLabels(const LabelValueVectorType& values, std::function<void(Label*)>&& lambda) +{ + auto labels = this->GetLabelsByValue(values); + std::for_each(labels.begin(), labels.end(), lambda); + m_LabelsChangedMessage.Send(values); +} + +void mitk::LabelSetImage::VisitLabels(const LabelValueVectorType& values, std::function<void(const Label*)>&& lambda) const +{ + auto labels = this->GetConstLabelsByValue(values); + std::for_each(labels.begin(), labels.end(), lambda); +} + + void mitk::LabelSetImage::OnLabelModified(const Object* sender, const itk::EventObject&) { auto label = dynamic_cast<const Label*>(sender); if (nullptr == label) mitkThrow() << "LabelSet is in wrong state. LabelModified event is not send by a label instance."; Superclass::Modified(); this->m_LabelModifiedMessage.Send(label->GetValue()); } bool mitk::LabelSetImage::ExistLabel(LabelValueType value) const { auto finding = m_LabelMap.find(value); return m_LabelMap.end() != finding; } 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(GroupIndexType index) const { return index < m_LayerContainer.size(); } bool mitk::LabelSetImage::IsLabelInGroup(LabelValueType value) const { GroupIndexType dummy; return this->IsLabelInGroup(value, dummy); } bool mitk::LabelSetImage::IsLabelInGroup(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::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 == UnlabeledValue) { 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::GetConstLabelsInGroup(GroupIndexType index) const +const mitk::LabelSetImage::LabelVectorType mitk::LabelSetImage::GetLabelsByValue(const LabelValueVectorType& labelValues, bool ignoreMissing) { - if (!this->ExistGroup(index)) - mitkThrow() << "Cannot get labels of an invalid group. Invalid group index: " << index; + LabelVectorType result; + for (const auto& labelValue : labelValues) + { + auto* label = this->GetLabel(labelValue); - mitk::LabelSetImage::ConstLabelVectorType result; - const auto labelValues = m_GroupToLabelMap[index]; + if (label != nullptr) + { + if (ignoreMissing) result.emplace_back(label); + else mitkThrow() << "Error cannot get labels by Value. At least one passed value is unknown. Unknown value: " << labelValue; + } + } + return result; +} +const mitk::LabelSetImage::ConstLabelVectorType mitk::LabelSetImage::GetConstLabelsByValue(const LabelValueVectorType& labelValues, bool ignoreMissing) const +{ + ConstLabelVectorType result; for (const auto& labelValue : labelValues) { const auto* label = this->GetLabel(labelValue); if (label != nullptr) - result.emplace_back(label); + { + if (ignoreMissing) result.emplace_back(label); + else mitkThrow() << "Error cannot get labels by Value. At least one passed value is unknown. Unknown value: " << labelValue; + } } - return result; } -const mitk::LabelSetImage::LabelVectorType mitk::LabelSetImage::GetLabelsInGroup(GroupIndexType index) +const mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::GetLabelValuesByGroup(GroupIndexType index) const { if (!this->ExistGroup(index)) mitkThrow() << "Cannot get labels of an invalid group. Invalid group index: " << index; - mitk::LabelSetImage::LabelVectorType result; - const auto labelValues = m_GroupToLabelMap[index]; + return m_GroupToLabelMap[index]; +} + +const mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::GetLabelValuesByName(GroupIndexType index, std::string_view name) const +{ + LabelValueVectorType result; - for (const auto& labelValue : labelValues) - { - auto* label = this->GetLabel(labelValue); + auto searchName = [&result, name](const Label* l) { if(l->GetName() == name) result.push_back(l->GetValue()); }; - if (label != nullptr) - result.emplace_back(label); - } + this->VisitLabels(this->GetLabelValuesByGroup(index), searchName); return result; } +std::vector<std::string> mitk::LabelSetImage::GetLabelClassNames() const +{ + std::set<std::string> names; + auto searchName = [&names](const Label* l) { names.emplace(l->GetName()); }; + this->VisitLabels(this->GetUsedLabelValues(), searchName); + + return std::vector<std::string>(names.begin(), names.end()); +} + +std::vector<std::string> mitk::LabelSetImage::GetLabelClassNamesByGroup(GroupIndexType index) const +{ + std::set<std::string> names; + auto searchName = [&names](const Label* l) { names.emplace(l->GetName()); }; + this->VisitLabels(this->GetLabelValuesByGroup(index), searchName); + + return std::vector<std::string>(names.begin(), names.end()); +} + +void mitk::LabelSetImage::SetAllLabelsVisible(bool visible) +{ + auto setVisibility = [visible](Label* l) { l->SetVisible(visible); }; + + this->ApplyToLabels(this->GetUsedLabelValues(), setVisibility); +} + +void mitk::LabelSetImage::SetAllLabelsVisibleByGroup(GroupIndexType group, bool visible) +{ + auto setVisibility = [visible](Label* l) { l->SetVisible(visible); }; + + this->ApplyToLabels(this->GetLabelValuesByGroup(group), setVisibility); +} + +void mitk::LabelSetImage::SetAllLabelsVisibleByName(GroupIndexType group, std::string_view name, bool visible) +{ + auto setVisibility = [visible](Label* l) { l->SetVisible(visible); }; + + this->ApplyToLabels(this->GetLabelValuesByName(group, name), setVisibility); +} + +void mitk::LabelSetImage::SetAllLabelsLocked(bool locked) +{ + auto setLock = [locked](Label* l) { l->SetLocked(locked); }; + + this->ApplyToLabels(this->GetUsedLabelValues(), setLock); +} + +void mitk::LabelSetImage::SetAllLabelsLockedByGroup(GroupIndexType group, bool locked) +{ + auto setLock = [locked](Label* l) { l->SetLocked(locked); }; + + this->ApplyToLabels(this->GetLabelValuesByGroup(group), setLock); +} + +void mitk::LabelSetImage::SetAllLabelsLockedByName(GroupIndexType group, std::string_view name, bool locked) +{ + auto setLock = [locked](Label* l) { l->SetLocked(locked); }; + + this->ApplyToLabels(this->GetLabelValuesByName(group, name), setLock); +} + 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; } } if (leftHandSide.GetTotalNumberOfLabels() != rightHandSide.GetTotalNumberOfLabels()) { MITK_INFO(verbose) << "Number of labels are 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; } } // label data - auto leftLabelsInGroup = leftHandSide.GetConstLabelsInGroup(layerIndex); - auto rightLabelsInGroup = rightHandSide.GetConstLabelsInGroup(layerIndex); + auto leftLabelsInGroup = leftHandSide.GetLabelValuesByGroup(layerIndex); + auto rightLabelsInGroup = rightHandSide.GetLabelValuesByGroup(layerIndex); if (leftLabelsInGroup.size()!=rightLabelsInGroup.size()) { MITK_INFO(verbose) << "Number of layer labels is not equal. Invalid layer:" <<layerIndex; return false; } for (ConstLabelVector::size_type index = 0; index < leftLabelsInGroup.size(); ++index) { - if (mitk::Equal(*leftLabelsInGroup[index], *rightLabelsInGroup[index],eps,verbose)) + if (mitk::Equal(*(leftHandSide.GetLabel(leftLabelsInGroup[index])), *(rightHandSide.GetLabel(rightLabelsInGroup[index])),eps,verbose)) { MITK_INFO(verbose) << "At least one label in layer is not equal. Invalid layer:" << layerIndex; return false; } } } return returnValue; } /**Helper function to convert a vector of labels into a label map * @pre every label in the vector has a unique value.*/ using ConstLabelMapType = std::map<mitk::LabelSetImage::LabelValueType, mitk::Label::ConstPointer>; ConstLabelMapType ConvertLabelVectorToMap(const mitk::ConstLabelVector& labelV) { ConstLabelMapType result; for (auto label : labelV) { const auto value = label->GetValue(); auto finding = result.find(value); if (finding != result.end()) mitkThrow() << "Operation failed. Cannot convert label vector into label map, because at least one label value is not unique. Violating label value: " << value; result.insert(std::make_pair(value, label)); } return result; } /** 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 TDestinationPixel, class TSourcePixel, class TOutputpixel> class LabelTransferFunctor { public: LabelTransferFunctor() {}; LabelTransferFunctor(const ConstLabelMapType& destinationLabels, 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_DestinationLabels(destinationLabels), 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_DestinationLabels == other.m_DestinationLabels; } LabelTransferFunctor& operator=(const LabelTransferFunctor& other) { this->m_DestinationLabels = other.m_DestinationLabels; 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 labelFinding = this->m_DestinationLabels.find(existingDestinationValue); if (labelFinding==this->m_DestinationLabels.end() || !labelFinding->second->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: ConstLabelMapType m_DestinationLabels; 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<unsigned int VImageDimension> void TransferLabelContentAtTimeStepHelper(const itk::Image<mitk::Label::PixelType, VImageDimension>* itkSourceImage, mitk::Image* destinationImage, const mitk::ConstLabelVector& destinationLabels, 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<mitk::Label::PixelType, VImageDimension> 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 <mitk::Label::PixelType, mitk::Label::PixelType, mitk::Label::PixelType> LabelTransferFunctorType; typedef itk::BinaryFunctorImageFilter<ContentImageType, ContentImageType, ContentImageType, LabelTransferFunctorType> FilterType; LabelTransferFunctorType transferFunctor(ConvertLabelVectorToMap(destinationLabels), 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::ConstLabelVector& destinationLabels, const TimeStepType timeStep, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, LabelValueMappingVector 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 (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; } auto destLabelMap = ConvertLabelVectorToMap(destinationLabels); for (const auto& [sourceLabel, newDestinationLabel] : labelMapping) { if (LabelSetImage::UnlabeledValue!=newDestinationLabel && destLabelMap.end() != destLabelMap.find(newDestinationLabel)) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep. Defined destination label does not exist in destinationImage. newDestinationLabel: " << newDestinationLabel; } AccessFixedPixelTypeByItk_n(sourceImageAtTimeStep, TransferLabelContentAtTimeStepHelper, (Label::PixelType), (destinationImageAtTimeStep, destinationLabels, sourceBackground, destinationBackground, destinationBackgroundLocked, sourceLabel, newDestinationLabel, mergeStyle, overwriteStlye)); } destinationImage->Modified(); } void mitk::TransferLabelContent( const Image* sourceImage, Image* destinationImage, const mitk::ConstLabelVector& destinationLabels, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, LabelValueMappingVector 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; mismatch between images in number of time steps."; } for (mitk::TimeStepType i = 0; i < sourceTimeStepCount; ++i) { TransferLabelContentAtTimeStep(sourceImage, destinationImage, destinationLabels, i, sourceBackground, destinationBackground, destinationBackgroundLocked, labelMapping, mergeStyle, overwriteStlye); } } void mitk::TransferLabelContentAtTimeStep( const LabelSetImage* sourceImage, LabelSetImage* destinationImage, const TimeStepType timeStep, LabelValueMappingVector labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage must not be null."; } - auto destinationLabels = destinationImage->GetConstLabelsInGroup(destinationImage->GetActiveLayer()); + auto destinationLabels = destinationImage->GetConstLabelsByValue(destinationImage->GetLabelValuesByGroup(destinationImage->GetActiveLayer())); for (const auto& mappingElement : labelMapping) { if (LabelSetImage::UnlabeledValue != 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, destinationLabels, timeStep, LabelSetImage::UnlabeledValue, LabelSetImage::UnlabeledValue, destinationImage->GetUnlabeledLabelLock(), labelMapping, mergeStyle, overwriteStlye); } void mitk::TransferLabelContent( const LabelSetImage* sourceImage, LabelSetImage* destinationImage, LabelValueMappingVector 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 2d0b608a72..7e61677f95 100644 --- a/Modules/Multilabel/mitkLabelSetImage.h +++ b/Modules/Multilabel/mitkLabelSetImage.h @@ -1,669 +1,692 @@ /*============================================================================ 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 <mitkImage.h> #include <mitkLabel.h> #include <mitkMessage.h> #include <mitkLookupTable.h> #include <MitkMultilabelExports.h> 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 planned features are already urgently needed. /////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////// using GroupIndexType = std::size_t; using LabelValueType = mitk::Label::PixelType; const static LabelValueType UnlabeledValue = 0; using ConstLabelVectorType = ConstLabelVector; using LabelVectorType = LabelVector; using LabelValueVectorType = std::vector<LabelValueType>; /** \brief Adds a label instance to a group of the multi label image. * @remark By default, if the pixel value of the label is already used in the image, the label * will get a new none conflicting value assigned. This can be controlled by correctLabelValue. * @param label Instance of an label that should be added or used as template * @param groupID The id of the group the label should be added to. * @param addAsClone Flag that controls, if the passed instance should be added (false; the image will then take ownership, * be aware that e.g. event observers will be added) * a clone of the instance (true). * @param correctLabelValue Flag that controls, if the value of the passed label should be correct, if this value is already used in * the multi label image. True: Conflicting values will be corrected, be assigning a none conflicting value. False: If the value is conflicting * an exception will be thrown. * @return Instance of the label as it was added to the label set. * @pre label must point to a valid instance. * @pre If correctLabelValue==false, label value must be non conflicting. * @pre groupID must indicate an existing group. */ mitk::Label* AddLabel(mitk::Label* label, GroupIndexType groupID, bool addAsClone = true, bool correctLabelValue = true); /** \brief Adds a new label to a group of the image by providing name and color. * @param name (Class) name of the label instance that should be added. * @param color Color of the new label sinstance. * @param groupID The id of the group the label should be added to. * @return Instance of the label as it was added to the label set. * @pre groupID must indicate an existing group. */ mitk::Label* AddLabel(const std::string& name, const Color& color, GroupIndexType groupID); /** \brief */ void RenameLabel(LabelValueType labelValue, const std::string&, const Color&); /** * @brief Removes the label with the given value. * The label is removed from the labelset and * the pixel with the value of the label are set to UnlabeledValue. * @param labelValue the pixel value of the label to be removed */ void RemoveLabel(LabelValueType labelValue); /** * @brief Removes labels from the mitk::MultiLabelSegmentation. * If a label value does not exist, it will be ignored. * @param vectorOfLabelPixelValues a list of labels to be removed */ void RemoveLabels(const LabelValueVectorType& vectorOfLabelPixelValues); /** * @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 group index must be valid. */ void RemoveGroup(GroupIndexType group); /** \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, GroupIndexType groupIndex) 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 IsLabelInGroup(LabelValueType value) const; bool IsLabelInGroup(LabelValueType value, GroupIndexType& groupIndex) const; /** Returns the group id of the based label value. * @pre label value must exists. */ 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(); + const LabelVectorType GetLabelsByValue(const LabelValueVectorType& labelValues, bool ignoreMissing = true); + const ConstLabelVectorType GetConstLabelsByValue(const LabelValueVectorType& labelValues, bool ignoreMissing = false) const; + static LabelValueVectorType ExtractLabelValuesFromLabelVector(const ConstLabelVectorType& labels); static LabelValueVectorType ExtractLabelValuesFromLabelVector(const LabelVectorType& labels); static ConstLabelVectorType ConvertLabelVectorConst(const LabelVectorType& labels); /** - * @brief Returns a vector of all labels located on the specified group. + * @brief Returns a vector of all label values 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 const labels. + * @return the respective vector of label values. * @pre group index must exist. */ - const ConstLabelVectorType GetConstLabelsInGroup(GroupIndexType index) const; + const LabelValueVectorType GetLabelValuesByGroup(GroupIndexType index) const; + /** - * @brief Returns a vector of all labels located on the specified group. + * @brief Returns a vector of all label values located on the specified group having a certain name. * @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. + * @param name Name of the label instances one is looking for. + * @return the respective vector of label values. * @pre group index must exist. */ - const LabelVectorType GetLabelsInGroup(GroupIndexType index); + const LabelValueVectorType GetLabelValuesByName(GroupIndexType index, std::string_view name) const; + + std::vector<std::string> GetLabelClassNames() const; + std::vector<std::string> GetLabelClassNamesByGroup(GroupIndexType index) const; itkGetConstMacro(UnlabeledLabelLock, bool); itkSetMacro(UnlabeledLabelLock, bool); itkBooleanMacro(UnlabeledLabelLock); + /** \brief + */ + void SetAllLabelsVisible(bool visible); + void SetAllLabelsVisibleByGroup(GroupIndexType group, bool visible); + void SetAllLabelsVisibleByName(GroupIndexType group, std::string_view name, bool visible); + + /** \brief + */ + void SetAllLabelsLocked(bool locked); + void SetAllLabelsLockedByGroup(GroupIndexType group, bool locked); + void SetAllLabelsLockedByName(GroupIndexType group, std::string_view name, bool locked); + /** * \brief Replaces the labels of a group with a given vector of labels. * * @remark The passed label instances will be cloned before added to ensure clear ownership * of the new labels. * @remark The pixel content of the old labels will not be removed. * \param groupID The index of the group that should have its labels replaced * \param newLabels The vector of new labels * @pre Group that shuold be replaced must exist. * Qpre new label values must not be used in other groups. */ void ReplaceGroupLabels(const GroupIndexType groupID, const ConstLabelVectorType& newLabels); void ReplaceGroupLabels(const GroupIndexType groupID, const LabelVectorType& newLabels); /** Returns the pointer to the image that containes the labeling of the indicate group. *@pre groupID must reference an existing group.*/ mitk::Image* GetGroupImage(GroupIndexType groupID); /** Returns the pointer to the image that containes the labeling of the indicate group. *@pre groupID must reference an existing group.*/ const mitk::Image* GetGroupImage(GroupIndexType groupID) const; //////////////////////////////////////////////////////////////////// //Message slots that allow to react to changes in an instance using LabelEventType = Message1<LabelValueType>; using LabelsEventType = Message1<LabelValueVectorType>; using GroupEventType = Message1<GroupIndexType>; /** * \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 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, 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, 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, GroupIndexType); protected: void OnLabelModified(const Object* sender, const itk::EventObject&); /** Helper to ensure that the maps are correctly populated for a new label instance.*/ void AddLabelToMap(LabelValueType labelValue, Label* label, GroupIndexType groupID); void RemoveLabelFromMap(LabelValueType labelValue); /** Helper to ensure label events are correctly connected and lookup table is updated for a new label instance.*/ void RegisterLabel(Label* label); /** Helper to ensure label events are unregistered.*/ void ReleaseLabel(Label* label); + void ApplyToLabels(const LabelValueVectorType& values, std::function<void(Label*)>&& lambda); + void VisitLabels(const LabelValueVectorType& values, std::function<void(const Label*)>&& lambda) const; + using LabelMapType = std::map<LabelValueType, Label::Pointer>; LabelMapType m_LabelMap; using GroupNameVectorType = std::vector<std::string>; GroupNameVectorType m_Groups; /**This type is internally used to track which label is currently * associated with which layer.*/ using GroupToLabelMapType = std::vector<LabelValueVectorType>; GroupToLabelMapType m_GroupToLabelMap; using LabelToGroupMapType = std::map<LabelValueType, GroupIndexType>; LabelToGroupMapType m_LabelToGroupMap; LookupTable::Pointer m_LookupTable; LabelValueType m_ActiveLabelValue; 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); itkGetModifiableObjectMacro(LookupTable, mitk::LookupTable); /** \brief */ void SetLookupTable(LookupTable* lut); void UpdateLookupTable(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<PixelType>& 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<PixelType> &VectorOfLabelPixelValues); /** * \brief Returns true if the labelset exists*/ //[[deprecated("Will be removed with T28524")]] DEPRECATED(bool ExistLabelSet(unsigned int layer) const); /** * @brief Gets the ID of the currently active layer * @return the ID of the active layer */ unsigned int GetActiveLayer() const; /** \brief */ Label* GetActiveLabel(); /** \brief */ const Label* GetActiveLabel() 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; /** * \brief */ mitk::Image::Pointer CreateLabelMask(PixelType index); /** * @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); void SetActiveLabel(LabelValueType label); /** * \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 */ GroupIndexType AddLayer(ConstLabelVector labels = {}); /** * \brief Adds a layer based on a provided mitk::Image. * \param layerImage is added to the vector of label images * \param labels labels that will be cloned and added to the new layer if provided * \return the layer ID of the new layer */ GroupIndexType AddLayer(mitk::Image::Pointer layerImage, ConstLabelVector labels = {}); /** * \brief */ mitk::Image *GetLayerImage(unsigned int layer); const mitk::Image *GetLayerImage(unsigned int layer) const; protected: mitkCloneMacro(Self); LabelSetImage(); LabelSetImage(const LabelSetImage &other); ~LabelSetImage() override; template <typename TPixel, unsigned int VImageDimension> void LayerContainerToImageProcessing(itk::Image<TPixel, VImageDimension> *source, unsigned int layer); template <typename TPixel, unsigned int VImageDimension> void ImageToLayerContainerProcessing(itk::Image<TPixel, VImageDimension> *source, unsigned int layer) const; template <typename ImageType> void CalculateCenterOfMassProcessing(ImageType *input, LabelValueType index); template <typename ImageType> void EraseLabelProcessing(ImageType *input, PixelType index); template <typename ImageType> void MergeLabelProcessing(ImageType *input, PixelType pixelValue, PixelType index); template <typename ImageType> void MaskStampProcessing(ImageType *input, mitk::Image *mask, bool forceOverwrite); template <typename LabelSetImageType, typename ImageType> 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; std::vector<Image::Pointer> m_LayerContainer; int m_ActiveLayer; LabelValueType m_ActiveLabel; 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 label 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. }; } using LabelValueMappingVector = std::vector < std::pair<Label::PixelType, Label::PixelType> >; /**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 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, LabelValueMappingVector 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, LabelValueMappingVector 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 destinationImage Pointer to the image that should be used as destination for the transfer. @param destinationLabelVector Reference to the vector of labels (incl. 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 destinationLabelVector must be valid @pre sourceImage and destinationImage must contain the indicated timeStep @pre destinationLabelVector must contain all indicated destinationLabels for mapping.*/ MITKMULTILABEL_EXPORT void TransferLabelContentAtTimeStep(const Image* sourceImage, Image* destinationImage, const mitk::ConstLabelVector& destinationLabelVector, const TimeStepType timeStep, mitk::Label::PixelType sourceBackground = LabelSetImage::UnlabeledValue, mitk::Label::PixelType destinationBackground = LabelSetImage::UnlabeledValue, bool destinationBackgroundLocked = false, LabelValueMappingVector 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::ConstLabelVector& destinationLabelVector, mitk::Label::PixelType sourceBackground = LabelSetImage::UnlabeledValue, mitk::Label::PixelType destinationBackground = LabelSetImage::UnlabeledValue, bool destinationBackgroundLocked = false, LabelValueMappingVector labelMapping = { {1,1} }, MultiLabelSegmentation::MergeStyle mergeStyle = MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle overwriteStlye = MultiLabelSegmentation::OverwriteStyle::RegardLocks); } // namespace mitk #endif diff --git a/Modules/Multilabel/mitkMultiLabelIOHelper.cpp b/Modules/Multilabel/mitkMultiLabelIOHelper.cpp index 0951eece56..c60e2d4dda 100644 --- a/Modules/Multilabel/mitkMultiLabelIOHelper.cpp +++ b/Modules/Multilabel/mitkMultiLabelIOHelper.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 "mitkMultiLabelIOHelper.h" #include "mitkLabelSetImage.h" #include <mitkBasePropertySerializer.h> #include "itkMetaDataDictionary.h" #include "itkMetaDataObject.h" #include <tinyxml2.h> 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); - auto labelsInGroup = inputImage->GetConstLabelsInGroup(layerIndex); + auto labelsInGroup = inputImage->GetConstLabelsByValue(inputImage->GetLabelValuesByGroup(layerIndex)); for (const auto label : labelsInGroup) layerElement->InsertEndChild(MultiLabelIOHelper::GetLabelAsXMLElement(xmlDocument, label)); } 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 (!inputImage->ExistGroup(layerIndex)) { while (!inputImage->ExistGroup(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::UnlabeledValue != labelValue) { if (inputImage->ExistLabel(labelValue)) { // Override existing label with label from preset auto alreadyExistingLabel = inputImage->GetLabel(labelValue); alreadyExistingLabel->ConcatenatePropertyList(label); inputImage->UpdateLookupTable(labelValue); } else { inputImage->AddLabel(label, layerIndex, false); } } 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, const 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<itk::LightObject::Pointer> 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<BasePropertySerializer *>(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<itk::LightObject::Pointer> 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<BasePropertySerializer *>(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<std::string> imgMetaKeys = dic.GetKeys(); std::vector<std::string>::const_iterator itKey = imgMetaKeys.begin(); std::string metaString(""); for (; itKey != imgMetaKeys.end(); itKey++) { itk::ExposeMetaData<std::string>(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<std::string> imgMetaKeys = dic.GetKeys(); std::vector<std::string>::const_iterator itKey = imgMetaKeys.begin(); std::string metaString(""); for (; itKey != imgMetaKeys.end(); itKey++) { itk::ExposeMetaData<std::string>(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::GroupIndexType i = 0; i < inputImage->GetNumberOfLayers(); i++) { nlohmann::json jgroup; nlohmann::json jlabels; - for (const auto& label : inputImage->GetConstLabelsInGroup(i)) + for (const auto& label : inputImage->GetConstLabelsByValue(inputImage->GetLabelValuesByGroup(i))) { jlabels.emplace_back(SerializeLabelToJSON(label)); } jgroup["labels"] = jlabels; result.emplace_back(jgroup); } return result; }; std::vector<mitk::LabelVector> mitk::MultiLabelIOHelper::DeserializeMultiLabelGroupsFromJSON(const nlohmann::json& listOfLabelSets) { std::vector<LabelVector> result; for (const auto& jlabelset : listOfLabelSets) { LabelVector labelSet; if (jlabelset.find("labels") != jlabelset.end()) { auto jlabels = jlabelset["labels"]; for (const auto& jlabel : jlabels) { labelSet.push_back(DeserializeLabelFromJSON(jlabel)); } } 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<typename TValueType> bool GetValueFromJson(const nlohmann::json& labelJson, const std::string& key, TValueType& value) { if (labelJson.find(key) != labelJson.end()) { try { value = labelJson[key].get<TValueType>(); 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<float>()); color.SetGreen(jcolor[1].get<float>()); color.SetBlue(jcolor[2].get<float>()); 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/Algorithms/mitkDiffImageApplier.cpp b/Modules/Segmentation/Algorithms/mitkDiffImageApplier.cpp index b584e07da9..046c4aecb7 100644 --- a/Modules/Segmentation/Algorithms/mitkDiffImageApplier.cpp +++ b/Modules/Segmentation/Algorithms/mitkDiffImageApplier.cpp @@ -1,376 +1,376 @@ /*============================================================================ 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 "mitkDiffImageApplier.h" #include "mitkApplyDiffImageOperation.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImageTimeSelector.h" #include "mitkRenderingManager.h" #include "mitkSegmentationInterpolationController.h" #include <mitkIOUtil.h> #include <itkImageRegionConstIterator.h> #include <itkImageSliceIteratorWithIndex.h> #include <type_traits> mitk::DiffImageApplier::DiffImageApplier() : m_Image(nullptr), m_SliceDifferenceImage(nullptr), m_SliceIndex(0), m_SliceDimension(0), m_TimeStep(0), m_Dimension0(0), m_Dimension1(0), m_DestinationLabel(std::numeric_limits<mitk::Label::PixelType>::max()), m_Factor(1.0) { } mitk::DiffImageApplier::~DiffImageApplier() { } void mitk::DiffImageApplier::SetDestinationLabel(mitk::Label::PixelType label) { m_DestinationLabel = label; } void mitk::DiffImageApplier::ExecuteOperation(Operation *operation) { auto *imageOperation = dynamic_cast<ApplyDiffImageOperation *>(operation); if (imageOperation // we actually have the kind of operation that we can handle && imageOperation->IsImageStillValid()) // AND the image is not yet deleted { m_Image = imageOperation->GetImage(); Image::Pointer image3D = m_Image; // will be changed later in case of 3D+t m_SliceDifferenceImage = imageOperation->GetDiffImage(); m_TimeStep = imageOperation->GetTimeStep(); m_Factor = imageOperation->GetFactor(); if (m_SliceDifferenceImage->GetDimension() == 2) { m_SliceIndex = imageOperation->GetSliceIndex(); m_SliceDimension = imageOperation->GetSliceDimension(); switch (m_SliceDimension) { default: case 2: m_Dimension0 = 0; m_Dimension1 = 1; break; case 1: m_Dimension0 = 0; m_Dimension1 = 2; break; case 0: m_Dimension0 = 1; m_Dimension1 = 2; break; } if (m_SliceDifferenceImage->GetDimension() != 2 || (m_Image->GetDimension() < 3 || m_Image->GetDimension() > 4) || m_SliceDifferenceImage->GetDimension(0) != m_Image->GetDimension(m_Dimension0) || m_SliceDifferenceImage->GetDimension(1) != m_Image->GetDimension(m_Dimension1) || m_SliceIndex >= m_Image->GetDimension(m_SliceDimension)) { itkExceptionMacro( "Slice and image dimensions differ or slice index is too large. Sorry, cannot work like this."); return; } if (m_Image->GetDimension() == 4) { ImageTimeSelector::Pointer timeSelector = ImageTimeSelector::New(); timeSelector->SetInput(m_Image); timeSelector->SetTimeNr(m_TimeStep); timeSelector->UpdateLargestPossibleRegion(); image3D = timeSelector->GetOutput(); } AccessFixedDimensionByItk(image3D, ItkImageSwitch2DDiff, 3); if (m_Factor == 1 || m_Factor == -1) { if (m_Factor == -1) { // multiply diff pixels by factor and then send this diff slice AccessFixedDimensionByItk(m_SliceDifferenceImage, ItkInvertPixelValues, 2); } // just send the diff to SegmentationInterpolationController SegmentationInterpolationController *interpolator = SegmentationInterpolationController::InterpolatorForImage(m_Image); if (interpolator) { interpolator->BlockModified(true); interpolator->SetChangedSlice(m_SliceDifferenceImage, m_SliceDimension, m_SliceIndex, m_TimeStep); } m_Image->Modified(); if (interpolator) { interpolator->BlockModified(false); } if (m_Factor == -1) // return to normal values { AccessFixedDimensionByItk(m_SliceDifferenceImage, ItkInvertPixelValues, 2); } } else // no trivial case, too lazy to do something else { m_Image->Modified(); // check if interpolation is called. prefer to send diff directly } RenderingManager::GetInstance()->RequestUpdateAll(); } else if (m_SliceDifferenceImage->GetDimension() == 3) { // ... if (m_SliceDifferenceImage->GetDimension(0) != m_Image->GetDimension(0) || m_SliceDifferenceImage->GetDimension(1) != m_Image->GetDimension(1) || m_SliceDifferenceImage->GetDimension(2) != m_Image->GetDimension(2) || m_TimeStep >= m_Image->GetDimension(3)) { itkExceptionMacro("Diff image size differs from original image size. Sorry, cannot work like this."); return; } if (m_Image->GetDimension() == 4) { ImageTimeSelector::Pointer timeSelector = ImageTimeSelector::New(); timeSelector->SetInput(m_Image); timeSelector->SetTimeNr(m_TimeStep); timeSelector->UpdateLargestPossibleRegion(); image3D = timeSelector->GetOutput(); } auto labelSetImage = dynamic_cast<mitk::LabelSetImage* >(m_Image.GetPointer()); // this will do a long long if/else to find out both pixel types TransferLabelContentAtTimeStep( m_SliceDifferenceImage, labelSetImage, - labelSetImage->GetConstLabelsInGroup(labelSetImage->GetActiveLayer()), + labelSetImage->GetConstLabelsByValue(labelSetImage->GetLabelValuesByGroup(labelSetImage->GetActiveLayer())), m_TimeStep, 0, 0, false, {{1, m_DestinationLabel}}, mitk::MultiLabelSegmentation::MergeStyle::Merge, mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks); if (m_Factor == 1 || m_Factor == -1) { if (m_Factor == -1) { // multiply diff pixels by factor and then send this diff slice AccessFixedDimensionByItk(m_SliceDifferenceImage, ItkInvertPixelValues, 3); } // just send the diff to SegmentationInterpolationController SegmentationInterpolationController *interpolator = SegmentationInterpolationController::InterpolatorForImage(m_Image); if (interpolator) { interpolator->BlockModified(true); interpolator->SetChangedVolume(m_SliceDifferenceImage, m_TimeStep); } if (interpolator) { interpolator->BlockModified(false); } if (m_Factor == -1) // return to normal values { AccessFixedDimensionByItk(m_SliceDifferenceImage, ItkInvertPixelValues, 3); } } else // no trivial case, too lazy to do something else { m_Image->Modified(); // check if interpolation is called. prefer to send diff directly } RenderingManager::GetInstance()->RequestUpdateAll(); } else { itkExceptionMacro("Diff image must be 2D or 3D. Sorry, cannot work like this."); return; } } m_Image = nullptr; m_SliceDifferenceImage = nullptr; } mitk::DiffImageApplier *mitk::DiffImageApplier::GetInstanceForUndo() { static DiffImageApplier::Pointer s_Instance = DiffImageApplier::New(); return s_Instance; } // basically copied from mitk/Core/Algorithms/mitkImageAccessByItk.h #define myMITKDiffImageApplierFilterAccessByItk(mitkImage, itkImageTypeFunction, pixeltype, dimension, itkimage2) \ if (typeId == MapPixelComponentType<pixeltype>::value) \ \ { \ typedef itk::Image<pixeltype, dimension> ImageType; \ typedef mitk::ImageToItk<ImageType> ImageToItkType; \ itk::SmartPointer<ImageToItkType> imagetoitk = ImageToItkType::New(); \ const mitk::Image *constImage = mitkImage; \ mitk::Image *nonConstImage = const_cast<mitk::Image *>(constImage); \ nonConstImage->Update(); \ imagetoitk->SetInput(nonConstImage); \ imagetoitk->Update(); \ itkImageTypeFunction(imagetoitk->GetOutput(), itkimage2); \ \ } #define myMITKDiffImageApplierFilterAccessAllTypesByItk(mitkImage, itkImageTypeFunction, dimension, itkimage2) \ \ { \ myMITKDiffImageApplierFilterAccessByItk(mitkImage, itkImageTypeFunction, double, dimension, itkimage2) else myMITKDiffImageApplierFilterAccessByItk( \ mitkImage, \ itkImageTypeFunction, \ float, \ dimension, \ itkimage2) else myMITKDiffImageApplierFilterAccessByItk(mitkImage, itkImageTypeFunction, int, dimension, itkimage2) else myMITKDiffImageApplierFilterAccessByItk(mitkImage, \ itkImageTypeFunction, \ unsigned int, \ dimension, \ itkimage2) else myMITKDiffImageApplierFilterAccessByItk(mitkImage, itkImageTypeFunction, short, dimension, itkimage2) else myMITKDiffImageApplierFilterAccessByItk(mitkImage, itkImageTypeFunction, unsigned short, dimension, itkimage2) else myMITKDiffImageApplierFilterAccessByItk(mitkImage, \ itkImageTypeFunction, \ char, \ dimension, \ itkimage2) else myMITKDiffImageApplierFilterAccessByItk(mitkImage, \ itkImageTypeFunction, \ unsigned char, \ dimension, \ itkimage2) \ \ } template <typename TPixel, unsigned int VImageDimension> void mitk::DiffImageApplier::ItkImageSwitch2DDiff(itk::Image<TPixel, VImageDimension> *itkImage) { const auto typeId = m_SliceDifferenceImage->GetPixelType().GetComponentType(); myMITKDiffImageApplierFilterAccessAllTypesByItk(m_SliceDifferenceImage, ItkImageProcessing2DDiff, 2, itkImage); } template <typename TPixel, unsigned int VImageDimension> void mitk::DiffImageApplier::ItkImageSwitch3DDiff(itk::Image<TPixel, VImageDimension> *itkImage) { const auto typeId = m_SliceDifferenceImage->GetPixelType().GetComponentType(); myMITKDiffImageApplierFilterAccessAllTypesByItk(m_SliceDifferenceImage, ItkImageProcessing3DDiff, 3, itkImage); } template <typename TPixel1, unsigned int VImageDimension1, typename TPixel2, unsigned int VImageDimension2> void mitk::DiffImageApplier::ItkImageProcessing2DDiff(itk::Image<TPixel1, VImageDimension1> *diffImage, itk::Image<TPixel2, VImageDimension2> *outputImage) { typedef itk::Image<TPixel1, VImageDimension1> DiffImageType; typedef itk::Image<TPixel2, VImageDimension2> VolumeImageType; typedef itk::ImageSliceIteratorWithIndex<VolumeImageType> OutputSliceIteratorType; typedef itk::ImageRegionConstIterator<DiffImageType> DiffSliceIteratorType; typename VolumeImageType::RegionType sliceInVolumeRegion; sliceInVolumeRegion = outputImage->GetLargestPossibleRegion(); sliceInVolumeRegion.SetSize(m_SliceDimension, 1); // just one slice sliceInVolumeRegion.SetIndex(m_SliceDimension, m_SliceIndex); // exactly this slice, please OutputSliceIteratorType outputIterator(outputImage, sliceInVolumeRegion); outputIterator.SetFirstDirection(m_Dimension0); outputIterator.SetSecondDirection(m_Dimension1); DiffSliceIteratorType diffIterator(diffImage, diffImage->GetLargestPossibleRegion()); // iterate over output slice (and over input slice simultaneously) outputIterator.GoToBegin(); diffIterator.GoToBegin(); while (!outputIterator.IsAtEnd()) { while (!outputIterator.IsAtEndOfSlice()) { while (!outputIterator.IsAtEndOfLine()) { TPixel2 newValue = outputIterator.Get() + (TPixel2)((double)diffIterator.Get() * m_Factor); outputIterator.Set(newValue); ++outputIterator; ++diffIterator; } outputIterator.NextLine(); } outputIterator.NextSlice(); } } template <typename TPixel1, unsigned int VImageDimension1, typename TPixel2, unsigned int VImageDimension2> void mitk::DiffImageApplier::ItkImageProcessing3DDiff(itk::Image<TPixel1, VImageDimension1> *diffImage, itk::Image<TPixel2, VImageDimension2> *outputImage) { typedef itk::Image<TPixel1, VImageDimension1> DiffImageType; typedef itk::Image<TPixel2, VImageDimension2> VolumeImageType; typedef itk::ImageRegionIterator<VolumeImageType> OutputSliceIteratorType; typedef itk::ImageRegionConstIterator<DiffImageType> DiffSliceIteratorType; OutputSliceIteratorType outputIterator(outputImage, outputImage->GetLargestPossibleRegion()); DiffSliceIteratorType diffIterator(diffImage, diffImage->GetLargestPossibleRegion()); // iterate over output slice (and over input slice simultaneously) outputIterator.GoToBegin(); diffIterator.GoToBegin(); while (!outputIterator.IsAtEnd()) { TPixel2 newValue = outputIterator.Get() + (TPixel2)((double)diffIterator.Get() * m_Factor); outputIterator.Set(newValue); ++outputIterator; ++diffIterator; } } #ifdef _MSC_VER # pragma warning(push) # pragma warning(disable:4146) // unary minus operator applied to unsigned type, result still unsigned #endif template <typename TPixel, unsigned int VImageDimension> void mitk::DiffImageApplier::ItkInvertPixelValues(itk::Image<TPixel, VImageDimension> *itkImage) { typedef itk::ImageRegionIterator<itk::Image<TPixel, VImageDimension>> IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { iter.Set(-(iter.Get())); ++iter; } } #ifdef _MSC_VER # pragma warning(pop) #endif diff --git a/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropperView.cpp b/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropperView.cpp index 53ee838915..0129ea7641 100644 --- a/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropperView.cpp +++ b/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropperView.cpp @@ -1,510 +1,510 @@ /*============================================================================ 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 "QmitkImageCropperView.h" #include <mitkBoundingShapeCropper.h> #include <mitkImageStatisticsHolder.h> #include <mitkInteractionConst.h> #include <mitkITKImageImport.h> #include <mitkLabelSetImage.h> #include <mitkNodePredicateDataType.h> #include <mitkNodePredicateAnd.h> #include <mitkNodePredicateNot.h> #include <mitkNodePredicateProperty.h> #include <mitkNodePredicateFunction.h> #include <mitkRenderingManager.h> #include <usModuleRegistry.h> #include <QMessageBox> const std::string QmitkImageCropperView::VIEW_ID = "org.mitk.views.qmitkimagecropper"; QmitkImageCropperView::QmitkImageCropperView(QObject *) : m_ParentWidget(nullptr) , m_BoundingShapeInteractor(nullptr) , m_CropOutsideValue(0) { CreateBoundingShapeInteractor(false); } QmitkImageCropperView::~QmitkImageCropperView() { //disable interactor if (m_BoundingShapeInteractor != nullptr) { m_BoundingShapeInteractor->SetDataNode(nullptr); m_BoundingShapeInteractor->EnableInteraction(false); } } void QmitkImageCropperView::CreateQtPartControl(QWidget *parent) { // create GUI widgets from the Qt Designer's .ui file m_Controls.setupUi(parent); m_Controls.imageSelectionWidget->SetDataStorage(GetDataStorage()); m_Controls.imageSelectionWidget->SetNodePredicate( mitk::NodePredicateAnd::New(mitk::TNodePredicateDataType<mitk::Image>::New(), mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object")))); m_Controls.imageSelectionWidget->SetSelectionIsOptional(true); m_Controls.imageSelectionWidget->SetAutoSelectNewNodes(true); m_Controls.imageSelectionWidget->SetEmptyInfo(QString("Please select an image node")); m_Controls.imageSelectionWidget->SetPopUpTitel(QString("Select image node")); connect(m_Controls.imageSelectionWidget, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkImageCropperView::OnImageSelectionChanged); m_Controls.boundingBoxSelectionWidget->SetDataStorage(GetDataStorage()); m_Controls.boundingBoxSelectionWidget->SetNodePredicate(mitk::NodePredicateAnd::New( mitk::TNodePredicateDataType<mitk::GeometryData>::New(), mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object")))); m_Controls.boundingBoxSelectionWidget->SetSelectionIsOptional(true); m_Controls.boundingBoxSelectionWidget->SetAutoSelectNewNodes(true); m_Controls.boundingBoxSelectionWidget->SetEmptyInfo(QString("Please select a bounding box")); m_Controls.boundingBoxSelectionWidget->SetPopUpTitel(QString("Select bounding box node")); connect(m_Controls.boundingBoxSelectionWidget, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkImageCropperView::OnBoundingBoxSelectionChanged); connect(m_Controls.buttonCreateNewBoundingBox, SIGNAL(clicked()), this, SLOT(OnCreateNewBoundingBox())); connect(m_Controls.buttonCropping, SIGNAL(clicked()), this, SLOT(OnCropping())); connect(m_Controls.buttonMasking, SIGNAL(clicked()), this, SLOT(OnMasking())); auto lambda = [this]() { m_Controls.groupImageSettings->setVisible(!m_Controls.groupImageSettings->isVisible()); }; connect(m_Controls.buttonAdvancedSettings, &ctkExpandButton::clicked, this, lambda); connect(m_Controls.spinBoxOutsidePixelValue, SIGNAL(valueChanged(int)), this, SLOT(OnSliderValueChanged(int))); SetDefaultGUI(); m_ParentWidget = parent; this->OnImageSelectionChanged(m_Controls.imageSelectionWidget->GetSelectedNodes()); this->OnBoundingBoxSelectionChanged(m_Controls.boundingBoxSelectionWidget->GetSelectedNodes()); } void QmitkImageCropperView::OnImageSelectionChanged(QList<mitk::DataNode::Pointer>) { bool rotationEnabled = false; m_Controls.labelWarningRotation->setVisible(false); auto imageNode = m_Controls.imageSelectionWidget->GetSelectedNode(); if (imageNode.IsNull()) { SetDefaultGUI(); return; } auto image = dynamic_cast<mitk::Image*>(imageNode->GetData()); if (nullptr != image) { if (image->GetDimension() < 3) { QMessageBox::warning(nullptr, tr("Invalid image selected"), tr("ImageCropper only works with 3 or more dimensions."), QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton); SetDefaultGUI(); return; } m_ParentWidget->setEnabled(true); m_Controls.buttonCreateNewBoundingBox->setEnabled(true); vtkSmartPointer<vtkMatrix4x4> imageMat = image->GetGeometry()->GetVtkMatrix(); // check whether the image geometry is rotated; if so, no pixel aligned cropping or masking can be performed if ((imageMat->GetElement(1, 0) == 0.0) && (imageMat->GetElement(0, 1) == 0.0) && (imageMat->GetElement(1, 2) == 0.0) && (imageMat->GetElement(2, 1) == 0.0) && (imageMat->GetElement(2, 0) == 0.0) && (imageMat->GetElement(0, 2) == 0.0)) { rotationEnabled = false; m_Controls.labelWarningRotation->setVisible(false); } else { rotationEnabled = true; m_Controls.labelWarningRotation->setStyleSheet(" QLabel { color: rgb(255, 0, 0) }"); m_Controls.labelWarningRotation->setVisible(true); } this->CreateBoundingShapeInteractor(rotationEnabled); if (itk::IOPixelEnum::SCALAR == image->GetPixelType().GetPixelType()) { // Might be changed with the upcoming new image statistics plugin //(recomputation might be very expensive for large images ;) ) auto statistics = image->GetStatistics(); auto minPixelValue = statistics->GetScalarValueMin(); auto maxPixelValue = statistics->GetScalarValueMax(); if (minPixelValue < std::numeric_limits<int>::min()) { minPixelValue = std::numeric_limits<int>::min(); } if (maxPixelValue > std::numeric_limits<int>::max()) { maxPixelValue = std::numeric_limits<int>::max(); } m_Controls.spinBoxOutsidePixelValue->setEnabled(true); m_Controls.spinBoxOutsidePixelValue->setMaximum(static_cast<int>(maxPixelValue)); m_Controls.spinBoxOutsidePixelValue->setMinimum(static_cast<int>(minPixelValue)); m_Controls.spinBoxOutsidePixelValue->setValue(static_cast<int>(minPixelValue)); } else { m_Controls.spinBoxOutsidePixelValue->setEnabled(false); } unsigned int dim = image->GetDimension(); if (dim < 2 || dim > 4) { m_ParentWidget->setEnabled(false); } if (m_Controls.boundingBoxSelectionWidget->GetSelectedNode().IsNotNull()) { m_Controls.buttonCropping->setEnabled(true); m_Controls.buttonMasking->setEnabled(true); m_Controls.buttonAdvancedSettings->setEnabled(true); m_Controls.groupImageSettings->setEnabled(true); } } } void QmitkImageCropperView::OnBoundingBoxSelectionChanged(QList<mitk::DataNode::Pointer>) { auto boundingBoxNode = m_Controls.boundingBoxSelectionWidget->GetSelectedNode(); if (boundingBoxNode.IsNull()) { SetDefaultGUI(); m_BoundingShapeInteractor->EnableInteraction(false); m_BoundingShapeInteractor->SetDataNode(nullptr); if (m_Controls.imageSelectionWidget->GetSelectedNode().IsNotNull()) { m_Controls.buttonCreateNewBoundingBox->setEnabled(true); } return; } auto boundingBox = dynamic_cast<mitk::GeometryData*>(boundingBoxNode->GetData()); if (nullptr != boundingBox) { // node newly selected boundingBoxNode->SetVisibility(true); m_BoundingShapeInteractor->EnableInteraction(true); m_BoundingShapeInteractor->SetDataNode(boundingBoxNode); mitk::RenderingManager::GetInstance()->InitializeViews(); if (m_Controls.imageSelectionWidget->GetSelectedNode().IsNotNull()) { m_Controls.buttonCropping->setEnabled(true); m_Controls.buttonMasking->setEnabled(true); m_Controls.buttonAdvancedSettings->setEnabled(true); m_Controls.groupImageSettings->setEnabled(true); } } } void QmitkImageCropperView::OnCreateNewBoundingBox() { auto imageNode = m_Controls.imageSelectionWidget->GetSelectedNode(); if (imageNode.IsNull()) { return; } if (nullptr == imageNode->GetData()) { return; } QString name = QString::fromStdString(imageNode->GetName() + " Bounding Box"); auto boundingShape = this->GetDataStorage()->GetNode(mitk::NodePredicateFunction::New([&name](const mitk::DataNode *node) { return 0 == node->GetName().compare(name.toStdString()); })); if (nullptr != boundingShape) { name = this->AdaptBoundingObjectName(name); } // get current timestep to support 3d+t images auto renderWindowPart = this->GetRenderWindowPart(mitk::WorkbenchUtil::IRenderWindowPartStrategy::OPEN); const mitk::TimePointType timePoint = renderWindowPart->GetSelectedTimePoint(); const auto imageGeometry = imageNode->GetData()->GetTimeGeometry()->GetGeometryForTimePoint(timePoint); auto boundingBox = mitk::GeometryData::New(); boundingBox->SetGeometry(static_cast<mitk::Geometry3D*>(this->InitializeWithImageGeometry(imageGeometry))); auto boundingBoxNode = mitk::DataNode::New(); boundingBoxNode->SetData(boundingBox); boundingBoxNode->SetProperty("name", mitk::StringProperty::New(name.toStdString())); boundingBoxNode->SetProperty("layer", mitk::IntProperty::New(99)); boundingBoxNode->AddProperty("Bounding Shape.Handle Size Factor", mitk::DoubleProperty::New(0.02)); boundingBoxNode->SetBoolProperty("pickable", true); if (!this->GetDataStorage()->Exists(boundingBoxNode)) { GetDataStorage()->Add(boundingBoxNode, imageNode); } m_Controls.boundingBoxSelectionWidget->SetCurrentSelectedNode(boundingBoxNode); } void QmitkImageCropperView::OnCropping() { this->ProcessImage(false); } void QmitkImageCropperView::OnMasking() { this->ProcessImage(true); } void QmitkImageCropperView::OnSliderValueChanged(int slidervalue) { m_CropOutsideValue = slidervalue; } void QmitkImageCropperView::CreateBoundingShapeInteractor(bool rotationEnabled) { if (m_BoundingShapeInteractor.IsNull()) { m_BoundingShapeInteractor = mitk::BoundingShapeInteractor::New(); m_BoundingShapeInteractor->LoadStateMachine("BoundingShapeInteraction.xml", us::ModuleRegistry::GetModule("MitkBoundingShape")); m_BoundingShapeInteractor->SetEventConfig("BoundingShapeMouseConfig.xml", us::ModuleRegistry::GetModule("MitkBoundingShape")); } m_BoundingShapeInteractor->SetRotationEnabled(rotationEnabled); } mitk::Geometry3D::Pointer QmitkImageCropperView::InitializeWithImageGeometry(const mitk::BaseGeometry* geometry) const { // convert a BaseGeometry into a Geometry3D (otherwise IO is not working properly) if (geometry == nullptr) mitkThrow() << "Geometry is not valid."; auto boundingGeometry = mitk::Geometry3D::New(); boundingGeometry->SetBounds(geometry->GetBounds()); boundingGeometry->SetImageGeometry(geometry->GetImageGeometry()); boundingGeometry->SetOrigin(geometry->GetOrigin()); boundingGeometry->SetSpacing(geometry->GetSpacing()); boundingGeometry->SetIndexToWorldTransform(geometry->GetIndexToWorldTransform()->Clone()); boundingGeometry->Modified(); return boundingGeometry; } void QmitkImageCropperView::ProcessImage(bool mask) { auto renderWindowPart = this->GetRenderWindowPart(mitk::WorkbenchUtil::IRenderWindowPartStrategy::OPEN); const auto timePoint = renderWindowPart->GetSelectedTimePoint(); auto imageNode = m_Controls.imageSelectionWidget->GetSelectedNode(); if (imageNode.IsNull()) { QMessageBox::information(nullptr, "Warning", "Please load and select an image before starting image processing."); return; } auto boundingBoxNode = m_Controls.boundingBoxSelectionWidget->GetSelectedNode(); if (boundingBoxNode.IsNull()) { QMessageBox::information(nullptr, "Warning", "Please load and select a cropping object before starting image processing."); return; } if (!imageNode->GetData()->GetTimeGeometry()->IsValidTimePoint(timePoint)) { QMessageBox::information(nullptr, "Warning", "Please select a time point that is within the time bounds of the selected image."); return; } const auto timeStep = imageNode->GetData()->GetTimeGeometry()->TimePointToTimeStep(timePoint); const auto image = dynamic_cast<mitk::Image*>(imageNode->GetData()); const auto boundingBox = dynamic_cast<mitk::GeometryData*>(boundingBoxNode->GetData()); if (nullptr != image && nullptr != boundingBox) { // Check if initial node name is already in box name std::string imagePrefix = ""; if (boundingBoxNode->GetName().find(imageNode->GetName()) != 0) { imagePrefix = imageNode->GetName() + "_"; } QString imageName; if (mask) { imageName = QString::fromStdString(imagePrefix + boundingBoxNode->GetName() + "_masked"); } else { imageName = QString::fromStdString(imagePrefix + boundingBoxNode->GetName() + "_cropped"); } if (m_Controls.checkBoxCropTimeStepOnly->isChecked()) { imageName = imageName + "_T" + QString::number(timeStep); } // image and bounding shape ok, set as input auto croppedImageNode = mitk::DataNode::New(); auto cutter = mitk::BoundingShapeCropper::New(); cutter->SetGeometry(boundingBox); // adjustable in advanced settings cutter->SetUseWholeInputRegion(mask); //either mask (mask=true) or crop (mask=false) cutter->SetOutsideValue(m_CropOutsideValue); cutter->SetUseCropTimeStepOnly(m_Controls.checkBoxCropTimeStepOnly->isChecked()); cutter->SetCurrentTimeStep(timeStep); // TODO: Add support for MultiLayer (right now only Mulitlabel support) const auto labelsetImageInput = dynamic_cast<mitk::LabelSetImage*>(image); if (nullptr != labelsetImageInput) { cutter->SetInput(labelsetImageInput); // do the actual cutting try { cutter->Update(); } catch (const itk::ExceptionObject& e) { std::string message = std::string("The Cropping filter could not process because of: \n ") + e.GetDescription(); QMessageBox::warning(nullptr, tr("Cropping not possible!"), tr(message.c_str()), QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton); return; } auto labelSetImage = mitk::LabelSetImage::New(); labelSetImage->InitializeByLabeledImage(cutter->GetOutput()); for (unsigned int i = 0; i < labelsetImageInput->GetNumberOfLayers(); i++) { - labelSetImage->ReplaceGroupLabels(i, labelsetImageInput->GetConstLabelsInGroup(i)); + labelSetImage->ReplaceGroupLabels(i, labelsetImageInput->GetConstLabelsByValue(labelsetImageInput->GetLabelValuesByGroup(i))); } croppedImageNode->SetData(labelSetImage); croppedImageNode->SetProperty("name", mitk::StringProperty::New(imageName.toStdString())); //add cropping result to the current data storage as child node to the image node if (!m_Controls.checkOverwriteImage->isChecked()) { if (!this->GetDataStorage()->Exists(croppedImageNode)) { this->GetDataStorage()->Add(croppedImageNode, imageNode); } } else // original image will be overwritten by the result image and the bounding box of the result is adjusted { imageNode->SetData(labelSetImage); imageNode->Modified(); // Adjust coordinate system by doing a reinit on auto tempDataStorage = mitk::DataStorage::SetOfObjects::New(); tempDataStorage->InsertElement(0, imageNode); // initialize the views to the bounding geometry auto bounds = this->GetDataStorage()->ComputeBoundingGeometry3D(tempDataStorage); mitk::RenderingManager::GetInstance()->InitializeViews(bounds); } } else { cutter->SetInput(image); // do the actual cutting try { cutter->Update(); } catch (const itk::ExceptionObject& e) { std::string message = std::string("The Cropping filter could not process because of: \n ") + e.GetDescription(); QMessageBox::warning(nullptr, tr("Cropping not possible!"), tr(message.c_str()), QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton); return; } //add cropping result to the current data storage as child node to the image node if (!m_Controls.checkOverwriteImage->isChecked()) { croppedImageNode->SetData(cutter->GetOutput()); croppedImageNode->SetProperty("name", mitk::StringProperty::New(imageName.toStdString())); croppedImageNode->SetProperty("color", mitk::ColorProperty::New(1.0, 1.0, 1.0)); mitk::LevelWindow levelWindow; imageNode->GetLevelWindow(levelWindow); croppedImageNode->SetLevelWindow(levelWindow); if (!this->GetDataStorage()->Exists(croppedImageNode)) { this->GetDataStorage()->Add(croppedImageNode, imageNode); imageNode->SetVisibility(mask); // Give the user a visual clue that something happened when image was cropped } } else // original image will be overwritten by the result image and the bounding box of the result is adjusted { mitk::LevelWindow levelWindow; imageNode->GetLevelWindow(levelWindow); imageNode->SetData(cutter->GetOutput()); imageNode->SetLevelWindow(levelWindow); // Adjust coordinate system by doing a reinit on auto tempDataStorage = mitk::DataStorage::SetOfObjects::New(); tempDataStorage->InsertElement(0, imageNode); // initialize the views to the bounding geometry auto bounds = this->GetDataStorage()->ComputeBoundingGeometry3D(tempDataStorage); mitk::RenderingManager::GetInstance()->InitializeViews(bounds); } } } else { QMessageBox::information(nullptr, "Warning", "Please load and select an image before starting image processing."); } } void QmitkImageCropperView::SetDefaultGUI() { m_Controls.buttonCreateNewBoundingBox->setEnabled(false); m_Controls.buttonCropping->setEnabled(false); m_Controls.buttonMasking->setEnabled(false); m_Controls.buttonAdvancedSettings->setEnabled(false); m_Controls.groupImageSettings->setEnabled(false); m_Controls.groupImageSettings->setVisible(false); m_Controls.checkOverwriteImage->setChecked(false); m_Controls.checkBoxCropTimeStepOnly->setChecked(false); } QString QmitkImageCropperView::AdaptBoundingObjectName(const QString& name) const { unsigned int counter = 2; QString newName = QString("%1 %2").arg(name).arg(counter); while (nullptr != this->GetDataStorage()->GetNode(mitk::NodePredicateFunction::New([&newName](const mitk::DataNode *node) { return 0 == node->GetName().compare(newName.toStdString()); }))) { newName = QString("%1 %2").arg(name).arg(++counter); } return newName; }