From 693c81929807517756fab1d69c65f24da787989e Mon Sep 17 00:00:00 2001 From: Andras Lasso Date: Thu, 18 Aug 2022 23:46:21 +0200 Subject: [PATCH] ENH: Expose segment conversion paths in Python Previously, complex STL containers were used that were not Python-wrappable. Now conversion paths and parameters are all VTK objects. This allows converting segmentations using a custom path, when there are multiple conversion paths are available. For example, multiple conversion paths are available after installing SlicerRT extension, which provides additional segment representations and conversion rules. --- Libs/vtkSegmentationCore/CMakeLists.txt | 4 + .../Testing/vtkSegmentationConverterTest1.cxx | 68 +++-- ...yLabelmapToClosedSurfaceConversionRule.cxx | 18 +- ...dSurfaceToBinaryLabelmapConversionRule.cxx | 16 +- ...lLabelmapToClosedSurfaceConversionRule.cxx | 16 +- Libs/vtkSegmentationCore/vtkSegmentation.cxx | 79 +++--- Libs/vtkSegmentationCore/vtkSegmentation.h | 15 +- .../vtkSegmentationConversionParameters.cxx | 266 ++++++++++++++++++ .../vtkSegmentationConversionParameters.h | 108 +++++++ .../vtkSegmentationConversionPath.cxx | 142 ++++++++++ .../vtkSegmentationConversionPath.h | 150 ++++++++++ .../vtkSegmentationConverter.cxx | 201 +++++++------ .../vtkSegmentationConverter.h | 23 +- .../vtkSegmentationConverterRule.cxx | 43 ++- .../vtkSegmentationConverterRule.h | 27 +- .../vtkSlicerSegmentationsModuleLogic.cxx | 16 +- .../Python/SegmentationsModuleTest1.py | 50 ++++ ...SegmentationConversionParametersWidget.cxx | 126 +++++---- ...MLSegmentationConversionParametersWidget.h | 7 +- .../Loadable/Volumes/qSlicerVolumesReader.cxx | 4 +- 20 files changed, 1061 insertions(+), 318 deletions(-) create mode 100644 Libs/vtkSegmentationCore/vtkSegmentationConversionParameters.cxx create mode 100644 Libs/vtkSegmentationCore/vtkSegmentationConversionParameters.h create mode 100644 Libs/vtkSegmentationCore/vtkSegmentationConversionPath.cxx create mode 100644 Libs/vtkSegmentationCore/vtkSegmentationConversionPath.h diff --git a/Libs/vtkSegmentationCore/CMakeLists.txt b/Libs/vtkSegmentationCore/CMakeLists.txt index 4c1f6f82214..4b11ca3692d 100644 --- a/Libs/vtkSegmentationCore/CMakeLists.txt +++ b/Libs/vtkSegmentationCore/CMakeLists.txt @@ -39,6 +39,10 @@ set(vtkSegmentationCore_SRCS vtkSegment.h vtkSegmentation.cxx vtkSegmentation.h + vtkSegmentationConversionParameters.cxx + vtkSegmentationConversionParameters.h + vtkSegmentationConversionPath.cxx + vtkSegmentationConversionPath.h vtkSegmentationConverter.cxx vtkSegmentationConverter.h vtkSegmentationConverterFactory.cxx diff --git a/Libs/vtkSegmentationCore/Testing/vtkSegmentationConverterTest1.cxx b/Libs/vtkSegmentationCore/Testing/vtkSegmentationConverterTest1.cxx index 44f01b6063a..686ee21d85f 100644 --- a/Libs/vtkSegmentationCore/Testing/vtkSegmentationConverterTest1.cxx +++ b/Libs/vtkSegmentationCore/Testing/vtkSegmentationConverterTest1.cxx @@ -97,11 +97,23 @@ RULE(C, E, 4); RULE(D, E, 2); RULE(E, D, 1); -void PrintPath(const vtkSegmentationConverter::ConversionPathType& path) +void PrintPath(vtkSegmentationConversionPath* path) { - for (vtkSegmentationConverter::ConversionPathType::const_iterator ruleIt = path.begin(); ruleIt != path.end(); ++ruleIt) + for (int i=0; i < path->GetNumberOfRules(); i++) { - std::cout << " " << (*ruleIt)->GetName() << "(" << (*ruleIt)->GetConversionCost() << ")" << std::endl; + vtkSegmentationConverterRule* rule = path->GetRule(i); + std::cout << " " << rule->GetName() << "(" << rule->GetConversionCost() << ")" << std::endl; + } +} + +void PrintPaths(vtkSegmentationConversionPaths* paths) +{ + vtkSegmentationConversionPath* path = nullptr; + vtkCollectionSimpleIterator it; + for (paths->InitTraversal(it); (path = paths->GetNextPath(it));) + { + std::cout << " Path: (total cost = " << path->GetCost() << ")" << std::endl; + PrintPath(path); } } @@ -145,58 +157,44 @@ int vtkSegmentationConverterTest1(int vtkNotUsed(argc), char* vtkNotUsed(argv)[] vtkSmartPointer converter = vtkSmartPointer::New(); - vtkSegmentationConverter::ConversionPathAndCostListType pathsCosts; - vtkSegmentationConverter::ConversionPathType shortestPath; - // A->E paths: ABCE, ABCDE, ADE std::cout << "Conversion from RepA to RepE" << std::endl; std::cout << " All paths:" << std::endl; - converter->GetPossibleConversions("RepA", "RepE", pathsCosts); - for (vtkSegmentationConverter::ConversionPathAndCostListType::iterator pathsCostsIt = pathsCosts.begin(); pathsCostsIt != pathsCosts.end(); ++pathsCostsIt) - { - std::cout << " Path: (total cost = " << pathsCostsIt->second << ")" << std::endl; - PrintPath(pathsCostsIt->first); - } - VERIFY_EQUAL("number of paths from representation A to E", pathsCosts.size(), 3); + vtkNew paths; + converter->GetPossibleConversions("RepA", "RepE", paths); + PrintPaths(paths); + VERIFY_EQUAL("number of paths from representation A to E", paths->GetNumberOfPaths(), 3); std::cout << " Cheapest path:" << std::endl; - shortestPath = vtkSegmentationConverter::GetCheapestPath(pathsCosts); + vtkSegmentationConversionPath* shortestPath = vtkSegmentationConverter::GetCheapestPath(paths); PrintPath(shortestPath); - VERIFY_EQUAL("number of paths from representation A to E", shortestPath.size(), 3); + VERIFY_EQUAL("minimum number of rules from representation A to E", shortestPath->GetNumberOfRules(), 3); // E->A paths: none std::cout << "Conversion from RepE to RepA" << std::endl; - converter->GetPossibleConversions("RepE", "RepA", pathsCosts); - VERIFY_EQUAL("number of paths from representation E to A", pathsCosts.size(), 0); + converter->GetPossibleConversions("RepE", "RepA", paths); + VERIFY_EQUAL("number of paths from representation E to A", paths->GetNumberOfPaths(), 0); // B->D paths: BAD, BCD, BCED std::cout << "Conversion from RepB to RepD" << std::endl; std::cout << " All paths:" << std::endl; - converter->GetPossibleConversions("RepB", "RepD", pathsCosts); - for (vtkSegmentationConverter::ConversionPathAndCostListType::iterator pathsCostsIt = pathsCosts.begin(); pathsCostsIt != pathsCosts.end(); ++pathsCostsIt) - { - std::cout << " Path: (total cost = " << pathsCostsIt->second << ")" << std::endl; - PrintPath(pathsCostsIt->first); - } - VERIFY_EQUAL("number of paths from representation B to D", pathsCosts.size(), 3); + converter->GetPossibleConversions("RepB", "RepD", paths); + PrintPaths(paths); + VERIFY_EQUAL("number of paths from representation B to D", paths->GetNumberOfPaths(), 3); std::cout << " Cheapest path:" << std::endl; - shortestPath = vtkSegmentationConverter::GetCheapestPath(pathsCosts); + shortestPath = vtkSegmentationConverter::GetCheapestPath(paths); PrintPath(shortestPath); - VERIFY_EQUAL("number of paths from representation B to D", shortestPath.size(), 2); + VERIFY_EQUAL("number of paths from representation B to D", shortestPath->GetNumberOfRules(), 2); // C->D paths: CD, CED std::cout << "Conversion from RepC to RepD" << std::endl; std::cout << " All paths:" << std::endl; - converter->GetPossibleConversions("RepC", "RepD", pathsCosts); - for (vtkSegmentationConverter::ConversionPathAndCostListType::iterator pathsCostsIt = pathsCosts.begin(); pathsCostsIt != pathsCosts.end(); ++pathsCostsIt) - { - std::cout << " Path: (total cost = " << pathsCostsIt->second << ")" << std::endl; - PrintPath(pathsCostsIt->first); - } - VERIFY_EQUAL("number of paths from representation C to D", pathsCosts.size(), 2); + converter->GetPossibleConversions("RepC", "RepD", paths); + PrintPaths(paths); + VERIFY_EQUAL("number of paths from representation C to D", paths->GetNumberOfPaths(), 2); std::cout << " Cheapest path:" << std::endl; - shortestPath = vtkSegmentationConverter::GetCheapestPath(pathsCosts); + shortestPath = vtkSegmentationConverter::GetCheapestPath(paths); PrintPath(shortestPath); - VERIFY_EQUAL("number of paths from representation C to D", shortestPath.size(), 1); + VERIFY_EQUAL("number of paths from representation C to D", shortestPath->GetNumberOfRules(), 1); std::cout << "Test passed." << std::endl; return EXIT_SUCCESS; diff --git a/Libs/vtkSegmentationCore/vtkBinaryLabelmapToClosedSurfaceConversionRule.cxx b/Libs/vtkSegmentationCore/vtkBinaryLabelmapToClosedSurfaceConversionRule.cxx index 69092030ab5..c4ba8a66d22 100644 --- a/Libs/vtkSegmentationCore/vtkBinaryLabelmapToClosedSurfaceConversionRule.cxx +++ b/Libs/vtkSegmentationCore/vtkBinaryLabelmapToClosedSurfaceConversionRule.cxx @@ -69,15 +69,15 @@ vtkSegmentationConverterRuleNewMacro(vtkBinaryLabelmapToClosedSurfaceConversionR //---------------------------------------------------------------------------- vtkBinaryLabelmapToClosedSurfaceConversionRule::vtkBinaryLabelmapToClosedSurfaceConversionRule() { - this->ConversionParameters[GetDecimationFactorParameterName()] = std::make_pair("0.0", + this->ConversionParameters->SetParameter(GetDecimationFactorParameterName(), "0.0", "Desired reduction in the total number of polygons. Range: 0.0 (no decimation) to 1.0 (as much simplification as possible)." " Value of 0.8 typically reduces data set size by 80% without losing too much details."); - this->ConversionParameters[GetSmoothingFactorParameterName()] = std::make_pair("0.5", + this->ConversionParameters->SetParameter(GetSmoothingFactorParameterName(), "0.5", "Smoothing factor. Range: 0.0 (no smoothing) to 1.0 (strong smoothing)."); - this->ConversionParameters[GetComputeSurfaceNormalsParameterName()] = std::make_pair("1", + this->ConversionParameters->SetParameter(GetComputeSurfaceNormalsParameterName(), "1", "Compute surface normals. 1 (default) = surface normals are computed. " "0 = surface normals are not computed (slightly faster but produces less smooth surface display)."); - this->ConversionParameters[GetJointSmoothingParameterName()] = std::make_pair("0", + this->ConversionParameters->SetParameter(GetJointSmoothingParameterName(), "0", "Perform joint smoothing."); } @@ -150,8 +150,8 @@ bool vtkBinaryLabelmapToClosedSurfaceConversionRule::Convert(vtkSegment* segment return false; } - double smoothingFactor = vtkVariant(this->ConversionParameters[GetSmoothingFactorParameterName()].first).ToDouble(); - int jointSmoothing = vtkVariant(this->ConversionParameters[GetJointSmoothingParameterName()].first).ToInt(); + double smoothingFactor = this->ConversionParameters->GetValueAsDouble(GetSmoothingFactorParameterName()); + int jointSmoothing = this->ConversionParameters->GetValueAsInt(GetJointSmoothingParameterName()); if (jointSmoothing > 0 && smoothingFactor > 0) { @@ -285,9 +285,9 @@ bool vtkBinaryLabelmapToClosedSurfaceConversionRule::CreateClosedSurface(vtkOrie binaryLabelmapWithIdentityGeometry->SetSpacing(1.0, 1.0, 1.0); // Get conversion parameters - double decimationFactor = vtkVariant(this->ConversionParameters[GetDecimationFactorParameterName()].first).ToDouble(); - double smoothingFactor = vtkVariant(this->ConversionParameters[GetSmoothingFactorParameterName()].first).ToDouble(); - int computeSurfaceNormals = vtkVariant(this->ConversionParameters[GetComputeSurfaceNormalsParameterName()].first).ToInt(); + double decimationFactor = this->ConversionParameters->GetValueAsDouble(GetDecimationFactorParameterName()); + double smoothingFactor = this->ConversionParameters->GetValueAsDouble(GetSmoothingFactorParameterName()); + int computeSurfaceNormals = this->ConversionParameters->GetValueAsInt(GetComputeSurfaceNormalsParameterName()); vtkNew marchingCubes; marchingCubes->SetInputData(binaryLabelmapWithIdentityGeometry); diff --git a/Libs/vtkSegmentationCore/vtkClosedSurfaceToBinaryLabelmapConversionRule.cxx b/Libs/vtkSegmentationCore/vtkClosedSurfaceToBinaryLabelmapConversionRule.cxx index 9214307c597..a6391183111 100644 --- a/Libs/vtkSegmentationCore/vtkClosedSurfaceToBinaryLabelmapConversionRule.cxx +++ b/Libs/vtkSegmentationCore/vtkClosedSurfaceToBinaryLabelmapConversionRule.cxx @@ -57,19 +57,19 @@ vtkClosedSurfaceToBinaryLabelmapConversionRule::vtkClosedSurfaceToBinaryLabelmap this->ReplaceTargetRepresentation = true; // Reference image geometry parameter - this->ConversionParameters[vtkSegmentationConverter::GetReferenceImageGeometryParameterName()] = std::make_pair("", + this->ConversionParameters->SetParameter(vtkSegmentationConverter::GetReferenceImageGeometryParameterName(), "", "Image geometry description string determining the geometry of the labelmap that is created in course of conversion." " Can be copied from a volume, using the button."); // Oversampling factor parameter - this->ConversionParameters[GetOversamplingFactorParameterName()] = std::make_pair("1", + this->ConversionParameters->SetParameter(GetOversamplingFactorParameterName(), "1", "Determines the oversampling of the reference image geometry. If it's a number, then all segments are oversampled" " with the same value (value of 1 means no oversampling). If it has the value \"A\", then automatic oversampling is calculated."); // Crop to reference geometry parameter - this->ConversionParameters[GetCropToReferenceImageGeometryParameterName()] = std::make_pair("0", + this->ConversionParameters->SetParameter(GetCropToReferenceImageGeometryParameterName(), "0", "Crop the model to the extent of reference geometry. 0 (default) = created labelmap will contain the entire model." " 1 = created labelmap extent will be within reference image extent."); // Collapse labelmaps parameter - this->ConversionParameters[GetCollapseLabelmapsParameterName()] = std::make_pair("1", + this->ConversionParameters->SetParameter(GetCollapseLabelmapsParameterName(), "1", "Merge the labelmaps into as few shared labelmaps as possible" " 1 = created labelmaps will be shared if possible without overwriting each other."); } @@ -255,7 +255,7 @@ bool vtkClosedSurfaceToBinaryLabelmapConversionRule::Convert(vtkSegment* segment //---------------------------------------------------------------------------- bool vtkClosedSurfaceToBinaryLabelmapConversionRule::PostConvert(vtkSegmentation* segmentation) { - int collapseLabelmaps = vtkVariant(this->ConversionParameters[GetCollapseLabelmapsParameterName()].first).ToInt(); + int collapseLabelmaps = this->ConversionParameters->GetValueAsInt(GetCollapseLabelmapsParameterName()); if (collapseLabelmaps > 0) { segmentation->CollapseBinaryLabelmaps(false); @@ -278,7 +278,7 @@ bool vtkClosedSurfaceToBinaryLabelmapConversionRule::CalculateOutputGeometry(vtk } // Get reference image geometry from parameters - std::string geometryString = this->ConversionParameters[vtkSegmentationConverter::GetReferenceImageGeometryParameterName()].first; + std::string geometryString = this->ConversionParameters->GetValue(vtkSegmentationConverter::GetReferenceImageGeometryParameterName()); if (geometryString.empty() || !vtkSegmentationConverter::DeserializeImageGeometry(geometryString, geometryImageData)) { geometryString = this->GetDefaultImageGeometryStringForPolyData(closedSurfacePolyData); @@ -299,7 +299,7 @@ bool vtkClosedSurfaceToBinaryLabelmapConversionRule::CalculateOutputGeometry(vtk } // Get oversampling factor - std::string oversamplingString = this->ConversionParameters[GetOversamplingFactorParameterName()].first; + std::string oversamplingString = this->ConversionParameters->GetValue(GetOversamplingFactorParameterName()); double oversamplingFactor = 1.0; if (!oversamplingString.compare("A")) { @@ -334,7 +334,7 @@ bool vtkClosedSurfaceToBinaryLabelmapConversionRule::CalculateOutputGeometry(vtk int cropToReferenceImageGeometry = 0; { - std::string cropToReferenceImageGeometryString = this->ConversionParameters[GetCropToReferenceImageGeometryParameterName()].first; + std::string cropToReferenceImageGeometryString = this->ConversionParameters->GetValue(GetCropToReferenceImageGeometryParameterName()); std::stringstream ss; ss << cropToReferenceImageGeometryString; ss >> cropToReferenceImageGeometry; diff --git a/Libs/vtkSegmentationCore/vtkFractionalLabelmapToClosedSurfaceConversionRule.cxx b/Libs/vtkSegmentationCore/vtkFractionalLabelmapToClosedSurfaceConversionRule.cxx index 8f59fc6de5a..23feacfd0f9 100644 --- a/Libs/vtkSegmentationCore/vtkFractionalLabelmapToClosedSurfaceConversionRule.cxx +++ b/Libs/vtkSegmentationCore/vtkFractionalLabelmapToClosedSurfaceConversionRule.cxx @@ -47,8 +47,10 @@ vtkSegmentationConverterRuleNewMacro(vtkFractionalLabelmapToClosedSurfaceConvers vtkFractionalLabelmapToClosedSurfaceConversionRule::vtkFractionalLabelmapToClosedSurfaceConversionRule() : vtkBinaryLabelmapToClosedSurfaceConversionRule() { - this->ConversionParameters[this->GetFractionalLabelMapOversamplingFactorParameterName()] = std::make_pair("1", "Determines the oversampling of the reference image geometry. All segments are oversampled with the same value (value of 1 means no oversampling)."); - this->ConversionParameters[this->GetThresholdFractionParameterName()] = std::make_pair("0.5", "Determines the threshold that the closed surface is created at as a fractional value between 0 and 1."); + this->ConversionParameters->SetParameter(this->GetFractionalLabelMapOversamplingFactorParameterName(), "1", + "Determines the oversampling of the reference image geometry. All segments are oversampled with the same value (value of 1 means no oversampling)."); + this->ConversionParameters->SetParameter(this->GetThresholdFractionParameterName(), "0.5", + "Determines the threshold that the closed surface is created at as a fractional value between 0 and 1."); } //---------------------------------------------------------------------------- @@ -144,11 +146,11 @@ bool vtkFractionalLabelmapToClosedSurfaceConversionRule::Convert(vtkSegment* seg } // Get conversion parameters - double decimationFactor = vtkVariant(this->ConversionParameters[this->GetDecimationFactorParameterName()].first).ToDouble(); - double smoothingFactor = vtkVariant(this->ConversionParameters[this->GetSmoothingFactorParameterName()].first).ToDouble(); - int computeSurfaceNormals = vtkVariant(this->ConversionParameters[GetComputeSurfaceNormalsParameterName()].first).ToInt(); - double fractionalOversamplingFactor = vtkVariant(this->ConversionParameters[this->GetFractionalLabelMapOversamplingFactorParameterName()].first).ToDouble(); - double fractionalThreshold = vtkVariant(this->ConversionParameters[this->GetThresholdFractionParameterName()].first).ToDouble(); + double decimationFactor = this->ConversionParameters->GetValueAsDouble(this->GetDecimationFactorParameterName()); + double smoothingFactor = this->ConversionParameters->GetValueAsDouble(this->GetSmoothingFactorParameterName()); + int computeSurfaceNormals = this->ConversionParameters->GetValueAsInt(GetComputeSurfaceNormalsParameterName()); + double fractionalOversamplingFactor = this->ConversionParameters->GetValueAsDouble(this->GetFractionalLabelMapOversamplingFactorParameterName()); + double fractionalThreshold = this->ConversionParameters->GetValueAsDouble(this->GetThresholdFractionParameterName()); if (fractionalThreshold < 0 || fractionalThreshold > 1) { diff --git a/Libs/vtkSegmentationCore/vtkSegmentation.cxx b/Libs/vtkSegmentationCore/vtkSegmentation.cxx index 65ef7a73de2..36c273dd326 100644 --- a/Libs/vtkSegmentationCore/vtkSegmentation.cxx +++ b/Libs/vtkSegmentationCore/vtkSegmentation.cxx @@ -385,20 +385,19 @@ bool vtkSegmentation::AddSegment(vtkSegment* segment, std::string segmentId/*="" if (!segment->GetRepresentation(this->MasterRepresentationName)) { // Collect all available paths to master representation - vtkSegmentationConverter::ConversionPathAndCostListType allPathsToMaster; + vtkNew allPathsToMaster; for (std::vector::iterator reprIt = containedRepresentationNamesInAddedSegment.begin(); reprIt != containedRepresentationNamesInAddedSegment.end(); ++reprIt) { - vtkSegmentationConverter::ConversionPathAndCostListType pathsFromCurrentRepresentationToMaster; + vtkNew pathsFromCurrentRepresentationToMaster; this->Converter->GetPossibleConversions((*reprIt), this->MasterRepresentationName, pathsFromCurrentRepresentationToMaster); // Append paths from current representation to master to all found paths to master - allPathsToMaster.insert(allPathsToMaster.end(), - pathsFromCurrentRepresentationToMaster.begin(), pathsFromCurrentRepresentationToMaster.end()); + allPathsToMaster->AddPaths(pathsFromCurrentRepresentationToMaster); } // Get cheapest path from any representation to master and try to convert - vtkSegmentationConverter::ConversionPathType cheapestPath = + vtkSegmentationConversionPath* cheapestPath = vtkSegmentationConverter::GetCheapestPath(allPathsToMaster); - if (cheapestPath.empty() || !this->ConvertSegmentUsingPath(segment, cheapestPath)) + if (!cheapestPath || !this->ConvertSegmentUsingPath(segment, cheapestPath)) { // Return if cannot convert to master representation vtkErrorMacro("AddSegment: Unable to create master representation!"); @@ -428,11 +427,11 @@ bool vtkSegmentation::AddSegment(vtkSegment* segment, std::string segmentId/*="" } // Convert using the cheapest available path - vtkSegmentationConverter::ConversionPathAndCostListType pathsToCurrentRepresentation; + vtkNew pathsToCurrentRepresentation; this->Converter->GetPossibleConversions(this->MasterRepresentationName, (*reprIt), pathsToCurrentRepresentation); - vtkSegmentationConverter::ConversionPathType cheapestPath = + vtkSegmentationConversionPath* cheapestPath = vtkSegmentationConverter::GetCheapestPath(pathsToCurrentRepresentation); - if (cheapestPath.empty()) + if (!cheapestPath) { vtkErrorMacro("AddSegment: Unable to perform conversion"); // Sanity check, it should never happen representationsCreated = false; @@ -1015,7 +1014,7 @@ void vtkSegmentation::ApplyNonLinearTransform(vtkAbstractTransform* transform) } //----------------------------------------------------------------------------- -bool vtkSegmentation::ConvertSegmentsUsingPath(std::vector segmentIDs, vtkSegmentationConverter::ConversionPathType path, bool overwriteExisting) +bool vtkSegmentation::ConvertSegmentsUsingPath(std::vector segmentIDs, vtkSegmentationConversionPath* path, bool overwriteExisting) { if (segmentIDs.empty()) { @@ -1023,10 +1022,10 @@ bool vtkSegmentation::ConvertSegmentsUsingPath(std::vector segmentI } // Execute each conversion step in the selected path - vtkSegmentationConverter::ConversionPathType::iterator pathIt; - for (pathIt = path.begin(); pathIt != path.end(); ++pathIt) + int numberOfRules = (path == nullptr ? 0 : path->GetNumberOfRules()); + for (int ruleIndex = 0; ruleIndex < numberOfRules; ++ruleIndex) { - vtkSegmentationConverterRule* currentConversionRule = (*pathIt); + vtkSegmentationConverterRule* currentConversionRule = path->GetRule(ruleIndex); if (!currentConversionRule) { vtkErrorMacro("ConvertSegmentsUsingPath: Invalid converter rule!"); @@ -1067,13 +1066,13 @@ bool vtkSegmentation::ConvertSegmentsUsingPath(std::vector segmentI } //----------------------------------------------------------------------------- -bool vtkSegmentation::ConvertSegmentUsingPath(vtkSegment* segment, vtkSegmentationConverter::ConversionPathType path, bool overwriteExisting/*=false*/) +bool vtkSegmentation::ConvertSegmentUsingPath(vtkSegment* segment, vtkSegmentationConversionPath* path, bool overwriteExisting/*=false*/) { // Execute each conversion step in the selected path - vtkSegmentationConverter::ConversionPathType::iterator pathIt; - for (pathIt = path.begin(); pathIt != path.end(); ++pathIt) + int numberOfRules = (path == nullptr ? 0 : path->GetNumberOfRules()); + for (int ruleIndex = 0; ruleIndex < numberOfRules; ++ruleIndex) { - vtkSegmentationConverterRule* currentConversionRule = (*pathIt); + vtkSegmentationConverterRule* currentConversionRule = path->GetRule(ruleIndex); if (!currentConversionRule) { vtkErrorMacro("ConvertSegmentUsingPath: Invalid converter rule!"); @@ -1139,14 +1138,14 @@ bool vtkSegmentation::CreateRepresentation(const std::string& targetRepresentati // Get conversion path with lowest cost. // If always convert, then only consider conversions from master, otherwise consider all available representations - vtkSegmentationConverter::ConversionPathAndCostListType pathCosts; + vtkNew paths; if (alwaysConvert) { - this->Converter->GetPossibleConversions(this->MasterRepresentationName, targetRepresentationName, pathCosts); + this->Converter->GetPossibleConversions(this->MasterRepresentationName, targetRepresentationName, paths); } else { - vtkSegmentationConverter::ConversionPathAndCostListType currentPathCosts; + vtkNew currentPaths; std::vector representationNames; this->GetContainedRepresentationNames(representationNames); for (std::vector::iterator reprIt=representationNames.begin(); reprIt!=representationNames.end(); ++reprIt) @@ -1155,16 +1154,13 @@ bool vtkSegmentation::CreateRepresentation(const std::string& targetRepresentati { continue; // No paths if source and target representations are the same } - this->Converter->GetPossibleConversions((*reprIt), targetRepresentationName, currentPathCosts); - for (vtkSegmentationConverter::ConversionPathAndCostListType::const_iterator pathIt = currentPathCosts.begin(); pathIt != currentPathCosts.end(); ++pathIt) - { - pathCosts.push_back(*pathIt); - } + this->Converter->GetPossibleConversions((*reprIt), targetRepresentationName, currentPaths); + paths->AddPaths(currentPaths); } } // Get cheapest path from found conversion paths - vtkSegmentationConverter::ConversionPathType cheapestPath = vtkSegmentationConverter::GetCheapestPath(pathCosts); - if (cheapestPath.empty()) + vtkSegmentationConversionPath* cheapestPath = vtkSegmentationConverter::GetCheapestPath(paths); + if (!cheapestPath) { return false; } @@ -1220,16 +1216,17 @@ bool vtkSegmentation::CreateRepresentation(const std::string& targetRepresentati } //--------------------------------------------------------------------------- -bool vtkSegmentation::CreateRepresentation(vtkSegmentationConverter::ConversionPathType path, - vtkSegmentationConverterRule::ConversionParameterListType parameters) +bool vtkSegmentation::CreateRepresentation(vtkSegmentationConversionPath* path, + vtkSegmentationConversionParameters* parameters) { if (!this->Converter) { - vtkErrorMacro("CreateRepresentation: Invalid converter!"); + vtkErrorMacro("CreateRepresentation: Invalid converter"); return false; } - if (path.empty()) + if (!path) { + vtkErrorMacro("CreateRepresentation: Invalid path"); return false; } @@ -1746,9 +1743,9 @@ bool vtkSegmentation::CanAcceptRepresentation(std::string representationName) // Otherwise if the representation can be converted to the master representation, then // it can be accepted, if cannot be converted then not. - vtkSegmentationConverter::ConversionPathAndCostListType pathCosts; - this->Converter->GetPossibleConversions(representationName, this->MasterRepresentationName, pathCosts); - return !pathCosts.empty(); + vtkNew paths; + this->Converter->GetPossibleConversions(representationName, this->MasterRepresentationName, paths); + return (paths->GetNumberOfPaths() > 0); } //----------------------------------------------------------------------------- @@ -1838,10 +1835,10 @@ std::string vtkSegmentation::AddEmptySegment(std::string segmentId/*=""*/, std:: //----------------------------------------------------------------------------- void vtkSegmentation::GetPossibleConversions(const std::string& targetRepresentationName, - vtkSegmentationConverter::ConversionPathAndCostListType &pathsCosts) + vtkSegmentationConversionPaths* paths) { - pathsCosts.clear(); - this->Converter->GetPossibleConversions(this->MasterRepresentationName, targetRepresentationName, pathsCosts); + paths->RemoveAllItems(); + this->Converter->GetPossibleConversions(this->MasterRepresentationName, targetRepresentationName, paths); }; //----------------------------------------------------------------------------- @@ -2154,11 +2151,11 @@ bool vtkSegmentation::ConvertSingleSegment(std::string segmentId, std::string ta } // Get possible conversion paths from master to the requested target representation - vtkSegmentationConverter::ConversionPathAndCostListType pathCosts; - this->Converter->GetPossibleConversions(this->MasterRepresentationName, targetRepresentationName, pathCosts); + vtkNew paths; + this->Converter->GetPossibleConversions(this->MasterRepresentationName, targetRepresentationName, paths); // Get cheapest path from found conversion paths - vtkSegmentationConverter::ConversionPathType cheapestPath = vtkSegmentationConverter::GetCheapestPath(pathCosts); - if (cheapestPath.empty()) + vtkSegmentationConversionPath* cheapestPath = vtkSegmentationConverter::GetCheapestPath(paths); + if (!cheapestPath) { return false; } diff --git a/Libs/vtkSegmentationCore/vtkSegmentation.h b/Libs/vtkSegmentationCore/vtkSegmentation.h index 8494c49666d..84dab04d677 100644 --- a/Libs/vtkSegmentationCore/vtkSegmentation.h +++ b/Libs/vtkSegmentationCore/vtkSegmentation.h @@ -41,6 +41,7 @@ class vtkAbstractTransform; class vtkCallbackCommand; class vtkCollection; class vtkIntArray; +class vtkSegmentationConversionPath; class vtkStringArray; /// \ingroup SegmentationCore @@ -422,8 +423,8 @@ class vtkSegmentationCore_EXPORT vtkSegmentation : public vtkObject /// Conversion starts from the master representation, and all representations along the /// path get overwritten. /// \return true on success - bool CreateRepresentation(vtkSegmentationConverter::ConversionPathType path, - vtkSegmentationConverterRule::ConversionParameterListType parameters); + bool CreateRepresentation(vtkSegmentationConversionPath* path, + vtkSegmentationConversionParameters* parameters); /// Removes a representation from all segments if present void RemoveRepresentation(const std::string& representationName); @@ -447,7 +448,7 @@ class vtkSegmentationCore_EXPORT vtkSegmentation : public vtkObject /// Get all possible conversions between the master representation and a specified target representation void GetPossibleConversions(const std::string& targetRepresentationName, - vtkSegmentationConverter::ConversionPathAndCostListType &pathsCosts); + vtkSegmentationConversionPaths* paths); /// Set a conversion parameter to all rules having this parameter void SetConversionParameter(const std::string& name, const std::string& value) { this->Converter->SetConversionParameter(name, value); }; @@ -457,8 +458,8 @@ class vtkSegmentationCore_EXPORT vtkSegmentation : public vtkObject std::string GetConversionParameter(const std::string& name) { return this->Converter->GetConversionParameter(name); }; /// Get names of all conversion parameters used by the selected conversion path - void GetConversionParametersForPath(vtkSegmentationConverterRule::ConversionParameterListType& conversionParameters, - const vtkSegmentationConverter::ConversionPathType& path) { this->Converter->GetConversionParametersForPath(conversionParameters, path); }; + void GetConversionParametersForPath(vtkSegmentationConversionParameters* conversionParameters, + vtkSegmentationConversionPath* path) { this->Converter->GetConversionParametersForPath(conversionParameters, path); }; /// Serialize all conversion parameters. /// The resulting string can be parsed in a segmentation object using /sa DeserializeConversionParameters @@ -483,7 +484,7 @@ class vtkSegmentationCore_EXPORT vtkSegmentation : public vtkObject std::map& cachedRepresentations); protected: - bool ConvertSegmentsUsingPath(std::vector segmentIDs, vtkSegmentationConverter::ConversionPathType path, bool overwriteExisting = false); + bool ConvertSegmentsUsingPath(std::vector segmentIDs, vtkSegmentationConversionPath* path, bool overwriteExisting = false); /// Convert given segment along a specified path /// \param segment Segment to convert @@ -491,7 +492,7 @@ class vtkSegmentationCore_EXPORT vtkSegmentation : public vtkObject /// \param overwriteExisting If true then do each conversion step regardless the target representation /// exists. If false then skip those conversion steps that would overwrite existing representation /// \return Success flag - bool ConvertSegmentUsingPath(vtkSegment* segment, vtkSegmentationConverter::ConversionPathType path, bool overwriteExisting = false); + bool ConvertSegmentUsingPath(vtkSegment* segment, vtkSegmentationConversionPath* path, bool overwriteExisting = false); /// Converts a single segment to a representation. bool ConvertSingleSegment(std::string segmentId, std::string targetRepresentationName); diff --git a/Libs/vtkSegmentationCore/vtkSegmentationConversionParameters.cxx b/Libs/vtkSegmentationCore/vtkSegmentationConversionParameters.cxx new file mode 100644 index 00000000000..53648e667cb --- /dev/null +++ b/Libs/vtkSegmentationCore/vtkSegmentationConversionParameters.cxx @@ -0,0 +1,266 @@ +/*============================================================================== + + Copyright (c) Laboratory for Percutaneous Surgery (PerkLab) + Queen's University, Kingston, ON, Canada. All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +==============================================================================*/ + +// SegmentationCore includes +#include "vtkSegmentationConversionParameters.h" + +// VTK includes +#include +#include + +//---------------------------------------------------------------------------- +vtkStandardNewMacro(vtkSegmentationConversionParameters); + +//---------------------------------------------------------------------------- +vtkSegmentationConversionParameters::vtkSegmentationConversionParameters() +{ +} + +//---------------------------------------------------------------------------- +vtkSegmentationConversionParameters::~vtkSegmentationConversionParameters() +{ +} + +//---------------------------------------------------------------------------- +void vtkSegmentationConversionParameters::PrintSelf(ostream& os, vtkIndent indent) +{ + for (const ConversionParameterType& parameter : this->ParameterList) + { + os << indent << parameter.Name << ": " << parameter.Value + << " (" << parameter.Description << ")\n"; + } +} + +//---------------------------------------------------------------------------- +std::string vtkSegmentationConversionParameters::GetName(int index) +{ + if (index < 0 || index >= this->GetNumberOfParameters()) + { + return ""; + } + return this->ParameterList[index].Name; +} + +//---------------------------------------------------------------------------- +void vtkSegmentationConversionParameters::SetName(int index, const std::string& name) VTK_EXPECTS(0 <= index && index < GetNumberOfParameters()) +{ + if (index < 0 || index >= this->GetNumberOfParameters()) + { + vtkErrorMacro("SetName failed: invalid index"); + return; + } + this->ParameterList[index].Name = name; +} + +//---------------------------------------------------------------------------- +std::string vtkSegmentationConversionParameters::GetDescription(int index) +{ + if (index < 0 || index >= this->GetNumberOfParameters()) + { + return ""; + } + return this->ParameterList[index].Description; +} + +//---------------------------------------------------------------------------- +std::string vtkSegmentationConversionParameters::GetDescription(const std::string& name) +{ + int index = this->GetIndexFromName(name); + if (index < 0) + { + return ""; + } + else + { + return this->ParameterList[index].Description; + } +} + +//---------------------------------------------------------------------------- +void vtkSegmentationConversionParameters::SetDescription(int index, const std::string& description) +{ + if (index < 0 || index >= this->GetNumberOfParameters()) + { + vtkErrorMacro("SetDescription failed: invalid index"); + return; + } + this->ParameterList[index].Description = description; +} + +//---------------------------------------------------------------------------- +void vtkSegmentationConversionParameters::SetDescription(const std::string& name, const std::string& description) +{ + int index = this->GetIndexFromName(name); + if (index < 0) + { + ConversionParameterType parameter; + parameter.Name = name; + parameter.Description = description; + this->ParameterList.push_back(parameter); + } + else + { + this->ParameterList[index].Description = description; + } +} + +//---------------------------------------------------------------------------- +std::string vtkSegmentationConversionParameters::GetValue(int index) +{ + if (index < 0 || index >= this->GetNumberOfParameters()) + { + return ""; + } + return this->ParameterList[index].Value; +} + +//---------------------------------------------------------------------------- +double vtkSegmentationConversionParameters::GetValueAsDouble(const std::string& name) +{ + double value = vtkVariant(this->GetValue(name)).ToDouble(); + return value; +} + +//---------------------------------------------------------------------------- +int vtkSegmentationConversionParameters::GetValueAsInt(const std::string& name) +{ + double value = vtkVariant(this->GetValue(name)).ToInt(); + return value; +} + +//---------------------------------------------------------------------------- +std::string vtkSegmentationConversionParameters::GetValue(const std::string& name) +{ + int index = this->GetIndexFromName(name); + if (index < 0) + { + return ""; + } + else + { + return this->ParameterList[index].Value; + } +} + +//---------------------------------------------------------------------------- +void vtkSegmentationConversionParameters::SetValue(int index, const std::string& value) +{ + if (index < 0 || index >= this->GetNumberOfParameters()) + { + vtkErrorMacro("SetValue failed: invalid index"); + return; + } + this->ParameterList[index].Value = value; +} + +//---------------------------------------------------------------------------- +void vtkSegmentationConversionParameters::SetValue(const std::string& name, const std::string& value) +{ + int index = this->GetIndexFromName(name); + if (index < 0) + { + ConversionParameterType parameter; + parameter.Name = name; + parameter.Value = value; + this->ParameterList.push_back(parameter); + } + else + { + this->ParameterList[index].Value = value; + } +} + +//---------------------------------------------------------------------------- +int vtkSegmentationConversionParameters::GetIndexFromName(const std::string name) +{ + int index = 0; + for (const ConversionParameterType& parameter : ParameterList) + { + if (parameter.Name == name) + { + return index; + } + index += 1; + } + return -1; +} + +//---------------------------------------------------------------------------- +int vtkSegmentationConversionParameters::GetNumberOfParameters() +{ + return this->ParameterList.size(); +} + +//---------------------------------------------------------------------------- +void vtkSegmentationConversionParameters::RemoveAllParameters() +{ + this->ParameterList.clear(); +} + +//---------------------------------------------------------------------------- +void vtkSegmentationConversionParameters::RemoveParameter(int index) +{ + if (index < 0 || index >= this->GetNumberOfParameters()) + { + vtkErrorMacro("RemoveParameter failed: invalid index"); + return; + } + this->ParameterList.erase(this->ParameterList.begin()+index); +} + +//---------------------------------------------------------------------------- +int vtkSegmentationConversionParameters::SetParameter(const std::string& name, + const std::string& value, const std::string& description/*=""*/) +{ + int parameterIndex = this->GetIndexFromName(name); + if (parameterIndex < 0) + { + ConversionParameterType newParameter; + this->ParameterList.push_back(newParameter); + parameterIndex = this->ParameterList.size() - 1; + } + ConversionParameterType parameter; + parameter.Name = name; + parameter.Value = value; + parameter.Description = description; + this->ParameterList[parameterIndex] = parameter; + return parameterIndex; +} + +//---------------------------------------------------------------------------- +void vtkSegmentationConversionParameters::DeepCopy(vtkSegmentationConversionParameters* source) +{ + if (!source) + { + vtkErrorMacro("DeepCopy failed: invalid source object"); + return; + } + this->ParameterList = source->ParameterList; +} + +//---------------------------------------------------------------------------- +void vtkSegmentationConversionParameters::CopyParameter(vtkSegmentationConversionParameters* source, int sourceIndex) +{ + if (!source || sourceIndex < 0 || sourceIndex >= source->GetNumberOfParameters()) + { + vtkErrorMacro("DeepCopy failed: invalid source object or index"); + return; + } + this->SetParameter( + source->ParameterList[sourceIndex].Name, + source->ParameterList[sourceIndex].Value, + source->ParameterList[sourceIndex].Description); +} diff --git a/Libs/vtkSegmentationCore/vtkSegmentationConversionParameters.h b/Libs/vtkSegmentationCore/vtkSegmentationConversionParameters.h new file mode 100644 index 00000000000..85d721c6fce --- /dev/null +++ b/Libs/vtkSegmentationCore/vtkSegmentationConversionParameters.h @@ -0,0 +1,108 @@ +/*============================================================================== + + Copyright (c) Laboratory for Percutaneous Surgery (PerkLab) + Queen's University, Kingston, ON, Canada. All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +==============================================================================*/ + +#ifndef __vtkSegmentationConversionParameters_h +#define __vtkSegmentationConversionParameters_h + +// VTK includes +#include + +// STD includes +#include + +// Segmentation includes +#include "vtkSegmentationCoreConfigure.h" + +/// \ingroup SegmentationCore +/// \brief Store a list of conversion parameters. +/// \details +/// Stores properties, such as name, description (that may be displayed +/// as tooltip in the GUI) and value (current value or default value) +/// of multiple segmentation conversion parameters. +class vtkSegmentationCore_EXPORT vtkSegmentationConversionParameters : public vtkObject +{ +public: + static vtkSegmentationConversionParameters* New(); + vtkTypeMacro(vtkSegmentationConversionParameters, vtkObject); + void PrintSelf(ostream& os, vtkIndent indent) override; + + /// @{ + /// Get/Set parameter name + std::string GetName(int index) VTK_EXPECTS(0 <= index && index < GetNumberOfParameters()); + void SetName(int index, const std::string& name) VTK_EXPECTS(0 <= index && index < GetNumberOfParameters()); + /// @} + + /// @{ + /// Get/Set parameter description + std::string GetDescription(int index) VTK_EXPECTS(0 <= index && index < GetNumberOfParameters()); + std::string GetDescription(const std::string& name); + void SetDescription(int index, const std::string& description) VTK_EXPECTS(0 <= index && index < GetNumberOfParameters()); + void SetDescription(const std::string& name, const std::string& description); + /// @} + + /// @{ + /// Get/Set parameter default value + std::string GetValue(int index) VTK_EXPECTS(0 <= index && index < GetNumberOfParameters()); + std::string GetValue(const std::string& name); + double GetValueAsDouble(const std::string& name); + int GetValueAsInt(const std::string& name); + void SetValue(int index, const std::string& value) VTK_EXPECTS(0 <= index && index < GetNumberOfParameters()); + void SetValue(const std::string& name, const std::string& value); + /// @} + + /// Get parameter index from name. + /// Returns -1 if parameter is not found. + int GetIndexFromName(const std::string name); + + /// Return number of parameters + int GetNumberOfParameters(); + + /// Delete all parameters + void RemoveAllParameters(); + + /// Delete parameter + void RemoveParameter(int index) VTK_EXPECTS(0 <= index && index < GetNumberOfParameters()); + + /// Set a conversion parameter + int SetParameter(const std::string& name, const std::string& value, const std::string& description = ""); + + /// Replace parameters with content of another parameter list + void DeepCopy(vtkSegmentationConversionParameters* source); + + /// Replace parameters with content of another parameter list + void CopyParameter(vtkSegmentationConversionParameters* source, int sourceIndex); + +protected: + + struct ConversionParameterType + { + std::string Name; + std::string Description; + std::string Value; + }; + + std::vector ParameterList; + +protected: + vtkSegmentationConversionParameters(); + ~vtkSegmentationConversionParameters() override; + +private: + vtkSegmentationConversionParameters(const vtkSegmentationConversionParameters&) = delete; + void operator=(const vtkSegmentationConversionParameters&) = delete; +}; + +#endif // __vtkSegment_h diff --git a/Libs/vtkSegmentationCore/vtkSegmentationConversionPath.cxx b/Libs/vtkSegmentationCore/vtkSegmentationConversionPath.cxx new file mode 100644 index 00000000000..ad5cd14afa0 --- /dev/null +++ b/Libs/vtkSegmentationCore/vtkSegmentationConversionPath.cxx @@ -0,0 +1,142 @@ +/*============================================================================== + + Copyright (c) Laboratory for Percutaneous Surgery (PerkLab) + Queen's University, Kingston, ON, Canada. All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +==============================================================================*/ + +// SegmentationCore includes +#include "vtkSegmentationConversionPath.h" +#include "vtkSegmentationConverterRule.h" + +// VTK includes +#include +#include +#include + +//---------------------------------------------------------------------------- +vtkStandardNewMacro(vtkSegmentationConversionPath); +vtkStandardNewMacro(vtkSegmentationConversionPaths); + +//---------------------------------------------------------------------------- +vtkSegmentationConversionPath::vtkSegmentationConversionPath() +{ +} + +//---------------------------------------------------------------------------- +vtkSegmentationConversionPath::~vtkSegmentationConversionPath() +{ +} + +//---------------------------------------------------------------------------- +void vtkSegmentationConversionPath::PrintSelf(ostream& os, vtkIndent indent) +{ + os << indent << "Cost: " << this->GetCost() << "\n"; + for (const vtkSmartPointer& rule : this->Rules) + { + os << indent << "Rule:\n"; + rule->PrintSelf(os, indent.GetNextIndent()); + } +} + +//---------------------------------------------------------------------------- +int vtkSegmentationConversionPath::GetNumberOfRules() +{ + return this->Rules.size(); +} + +//---------------------------------------------------------------------------- +void vtkSegmentationConversionPath::RemoveAllRules() +{ + this->Rules.clear(); +} + +//---------------------------------------------------------------------------- +vtkSegmentationConverterRule* vtkSegmentationConversionPath::GetRule(int index) +{ + if (index < 0 || index >= this->GetNumberOfRules()) + { + vtkErrorMacro("GetRule failed: invalid index: " << index); + return nullptr; + } + return this->Rules[index].GetPointer(); +} + +//---------------------------------------------------------------------------- +void vtkSegmentationConversionPath::RemoveRule(int index) +{ + if (index < 0 || index >= this->GetNumberOfRules()) + { + vtkErrorMacro("RemoveRule failed: invalid index: " << index); + return; + } + this->Rules.erase(this->Rules.begin()+index); +} + +//---------------------------------------------------------------------------- +int vtkSegmentationConversionPath::AddRule(vtkSegmentationConverterRule* rule) +{ + this->Rules.push_back(rule); + return this->GetNumberOfRules() - 1; +} + +//---------------------------------------------------------------------------- +void vtkSegmentationConversionPath::AddRules(vtkSegmentationConversionPath* path) +{ + int numberOfRules = path->GetNumberOfRules(); + for (int ruleIndex = 0; ruleIndex < numberOfRules; ++ruleIndex) + { + this->AddRule(path->GetRule(ruleIndex)); + } +} + +//---------------------------------------------------------------------------- +void vtkSegmentationConversionPath::Copy(vtkSegmentationConversionPath* source) +{ + if (!source) + { + vtkErrorMacro("DeepCopy failed: invalid source object"); + return; + } + this->Rules = source->Rules; +} + +//---------------------------------------------------------------------------- +unsigned int vtkSegmentationConversionPath::GetCost() +{ + unsigned int cost = 0; + for (const vtkSmartPointer& rule : this->Rules) + { + cost += rule->GetConversionCost(); + } + return cost; +} + +//---------------------------------------------------------------------------- +void vtkSegmentationConversionPaths::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); + + vtkSegmentationConversionPath* path = nullptr; + vtkCollectionSimpleIterator it; + for (this->InitTraversal(it); (path = this->GetNextPath(it));) + { + os << indent << "Path:\n"; + path->PrintSelf(os, indent.GetNextIndent()); + } +} + +//---------------------------------------------------------------------------- +vtkSegmentationConversionPath* vtkSegmentationConversionPaths::GetNextPath(vtkCollectionSimpleIterator& cookie) +{ + return vtkSegmentationConversionPath::SafeDownCast(this->GetNextItemAsObject(cookie)); +} diff --git a/Libs/vtkSegmentationCore/vtkSegmentationConversionPath.h b/Libs/vtkSegmentationCore/vtkSegmentationConversionPath.h new file mode 100644 index 00000000000..84433fdf8e1 --- /dev/null +++ b/Libs/vtkSegmentationCore/vtkSegmentationConversionPath.h @@ -0,0 +1,150 @@ +/*============================================================================== + + Copyright (c) Laboratory for Percutaneous Surgery (PerkLab) + Queen's University, Kingston, ON, Canada. All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +==============================================================================*/ + +#ifndef __vtkSegmentationConversionPath_h +#define __vtkSegmentationConversionPath_h + +// VTK includes +#include +#include + +// STD includes +#include + +// Segmentation includes +#include "vtkSegmentationCoreConfigure.h" + +class vtkSegmentationConverterRule; + +/// \ingroup SegmentationCore +/// \brief Store a segmentation conversion path. +/// \details +/// Stores conversion path as a list of conversion rules and their parameters. +class vtkSegmentationCore_EXPORT vtkSegmentationConversionPath : public vtkObject +{ +public: + static vtkSegmentationConversionPath* New(); + vtkTypeMacro(vtkSegmentationConversionPath, vtkObject); + void PrintSelf(ostream& os, vtkIndent indent) override; + + /// Get conversion "cost". The lower the cost is the better (faster computation, + /// higher accuracy, etc). The cost can be used to choose the best conversion paths + /// when multiple paths are available. + unsigned int GetCost(); + + /// Add a rule to the end of the rule list. Return the index of this rule. + int AddRule(vtkSegmentationConverterRule* rule); + + /// Concatenate all rules in "path" and to this conversion path. + void AddRules(vtkSegmentationConversionPath* path); + + /// Return number of rules that make up this conversion path. + int GetNumberOfRules(); + + /// Get index-th rule. + vtkSegmentationConverterRule* GetRule(int index) VTK_EXPECTS(0 <= index && index < GetNumberOfRules()); + + /// Remove index-th rule. + void RemoveRule(int index) VTK_EXPECTS(0 <= index && index < GetNumberOfRules()); + + /// Remove all rules from this path. + void RemoveAllRules(); + + /// Replace paths with content of the source path. + /// The rules are shallow-copied. + void Copy(vtkSegmentationConversionPath* source); + +protected: + + std::vector< vtkSmartPointer > Rules; + +protected: + vtkSegmentationConversionPath(); + ~vtkSegmentationConversionPath() override; + +private: + vtkSegmentationConversionPath(const vtkSegmentationConversionPath&) = delete; + void operator=(const vtkSegmentationConversionPath&) = delete; +}; + + +/// \ingroup SegmentationCore +/// \brief Store multiple segmentation conversion paths. +/// \details +/// Stores multiple conversion paths, each defined by a list of segmentation conversion rules. +class vtkSegmentationCore_EXPORT vtkSegmentationConversionPaths : public vtkCollection +{ +public: + vtkTypeMacro(vtkSegmentationConversionPaths, vtkCollection); + static vtkSegmentationConversionPaths* New(); + void PrintSelf(ostream& os, vtkIndent indent) override; + + /// Add a path to the list. + void AddPath(vtkSegmentationConversionPath*); + + /// Add paths to the list. + void AddPaths(vtkSegmentationConversionPaths*); + + /// Get the next path in the list. + vtkSegmentationConversionPath* GetNextPath(); + + /// Get the index-th path the list. + vtkSegmentationConversionPath* GetPath(int index) { return vtkSegmentationConversionPath::SafeDownCast(this->GetItemAsObject(index)); } + + /// Reentrant safe way to get an object in a collection. Just pass the + /// same cookie back and forth. + vtkSegmentationConversionPath* GetNextPath(vtkCollectionSimpleIterator& cookie); + + /// Get number of conversion paths stored in the collection. + int GetNumberOfPaths() { return this->GetNumberOfItems(); }; + +protected: + vtkSegmentationConversionPaths() = default; + ~vtkSegmentationConversionPaths() override = default; + +private: + // hide the standard AddItem from the user and the compiler. + void AddItem(vtkObject* o) { this->vtkCollection::AddItem(o); } + +private: + vtkSegmentationConversionPaths(const vtkSegmentationConversionPaths&) = delete; + void operator=(const vtkSegmentationConversionPaths&) = delete; +}; + +//---------------------------------------------------------------------------- +inline void vtkSegmentationConversionPaths::AddPath(vtkSegmentationConversionPath* f) +{ + this->vtkCollection::AddItem(f); +} + +//---------------------------------------------------------------------------- +inline void vtkSegmentationConversionPaths::AddPaths(vtkSegmentationConversionPaths* paths) +{ + vtkSegmentationConversionPath* path = nullptr; + vtkCollectionSimpleIterator it; + for (paths->InitTraversal(it); (path = paths->GetNextPath(it));) + { + this->AddItem(path); + } +} + +//---------------------------------------------------------------------------- +inline vtkSegmentationConversionPath* vtkSegmentationConversionPaths::GetNextPath() +{ + return static_cast(this->GetNextItemAsObject()); +} + +#endif // __vtkSegment_h diff --git a/Libs/vtkSegmentationCore/vtkSegmentationConverter.cxx b/Libs/vtkSegmentationCore/vtkSegmentationConverter.cxx index 8aa4dd0d444..01d36e49050 100644 --- a/Libs/vtkSegmentationCore/vtkSegmentationConverter.cxx +++ b/Libs/vtkSegmentationCore/vtkSegmentationConverter.cxx @@ -60,17 +60,10 @@ vtkSegmentationConverter::~vtkSegmentationConverter() = default; void vtkSegmentationConverter::PrintSelf(ostream& os, vtkIndent indent) { Superclass::PrintSelf(os,indent); - - ConverterRulesListType::iterator ruleIt; - for (ruleIt = this->ConverterRules.begin(); ruleIt != this->ConverterRules.end(); ++ruleIt) + for (auto rule : this->ConverterRules) { - vtkSegmentationConverterRule* rule = (*ruleIt); - os << indent << "Rule: " << (rule->GetSourceRepresentationName() ? rule->GetSourceRepresentationName() : "NULL") << " -> " << (rule->GetTargetRepresentationName() ? rule->GetTargetRepresentationName() : "NULL") << "\n"; - vtkSegmentationConverterRule::ConversionParameterListType::iterator paramIt; - for (paramIt = rule->ConversionParameters.begin(); paramIt != rule->ConversionParameters.end(); ++paramIt) - { - os << indent << " Parameter: " << paramIt->first << " = " << paramIt->second.first << " (" << paramIt->second.second << ")\n"; - } + os << indent << "Rule:"; + rule->PrintSelf(os, indent.GetNextIndent()); } } @@ -83,13 +76,13 @@ void vtkSegmentationConverter::DeepCopy(vtkSegmentationConverter* aConverter) } // Copy all conversion parameters - ConverterRulesListType::iterator ruleIt; - for (ruleIt = aConverter->ConverterRules.begin(); ruleIt != aConverter->ConverterRules.end(); ++ruleIt) + for (auto& rule : aConverter->ConverterRules) { - vtkSegmentationConverterRule::ConversionParameterListType::iterator paramIt; - for (paramIt = (*ruleIt)->ConversionParameters.begin(); paramIt != (*ruleIt)->ConversionParameters.end(); ++paramIt) + int numberOfParameters = rule->ConversionParameters->GetNumberOfParameters(); + for (int parameterIndex = 0; parameterIndex < numberOfParameters; parameterIndex++) { - this->SetConversionParameter(paramIt->first, paramIt->second.first); + this->SetConversionParameter(rule->ConversionParameters->GetName(parameterIndex), + rule->ConversionParameters->GetValue(parameterIndex)); } } } @@ -229,12 +222,14 @@ bool vtkSegmentationConverter::DeserializeImageGeometry( } //---------------------------------------------------------------------------- -void vtkSegmentationConverter::SetConversionParameters(vtkSegmentationConverterRule::ConversionParameterListType parameters) +void vtkSegmentationConverter::SetConversionParameters(vtkSegmentationConversionParameters* parameters) { - vtkSegmentationConverterRule::ConversionParameterListType::iterator paramIt; - for (paramIt = parameters.begin(); paramIt != parameters.end(); ++paramIt) + int numberOfParameters = parameters->GetNumberOfParameters(); + for (int parameterIndex = 0; parameterIndex < numberOfParameters; parameterIndex++) { - this->SetConversionParameter(paramIt->first, paramIt->second.first); + this->SetConversionParameter( + parameters->GetName(parameterIndex), + parameters->GetValue(parameterIndex)); } } @@ -301,17 +296,19 @@ std::string vtkSegmentationConverter::GetConversionParameterDescription(const st } //---------------------------------------------------------------------------- -vtkSegmentationConverter::ConversionPathType vtkSegmentationConverter::GetCheapestPath(const ConversionPathAndCostListType &pathsCosts) +vtkSegmentationConversionPath* vtkSegmentationConverter::GetCheapestPath(vtkSegmentationConversionPaths* paths) { unsigned int cheapestPathCost = VTK_UNSIGNED_INT_MAX; unsigned int cheapestPathNumberOfConversions = 0; - ConversionPathType cheapestPath; - for (ConversionPathAndCostListType::const_iterator pathIt = pathsCosts.begin(); pathIt != pathsCosts.end(); ++pathIt) + vtkSegmentationConversionPath* cheapestPath = nullptr; + vtkSegmentationConversionPath* path = nullptr; + vtkCollectionSimpleIterator it; + for (paths->InitTraversal(it); (path = paths->GetNextPath(it));) { - if (pathIt->second <= cheapestPathCost) + if (path->GetCost() <= cheapestPathCost) { - size_t numberOfConversions = pathIt->first.size(); - if (pathIt->second == cheapestPathCost) + size_t numberOfConversions = path->GetNumberOfRules(); + if (path->GetCost() == cheapestPathCost) { // If the path cost is exactly the same then compare the number of conversions too if (numberOfConversions > cheapestPathNumberOfConversions) @@ -320,129 +317,128 @@ vtkSegmentationConverter::ConversionPathType vtkSegmentationConverter::GetCheape continue; } } - cheapestPathCost = pathIt->second; + cheapestPathCost = path->GetCost(); cheapestPathNumberOfConversions = (unsigned int)numberOfConversions; - cheapestPath = pathIt->first; + cheapestPath = path; } } return cheapestPath; } //---------------------------------------------------------------------------- -void vtkSegmentationConverter::GetPossibleConversions(const std::string& sourceRepresentationName, const std::string& targetRepresentationName, ConversionPathAndCostListType &pathsCosts) +void vtkSegmentationConverter::GetPossibleConversions(const std::string& sourceRepresentationName, + const std::string& targetRepresentationName, vtkSegmentationConversionPaths* paths) { - pathsCosts.clear(); - std::set skipRepresentations; - this->FindPath(sourceRepresentationName, targetRepresentationName, pathsCosts, skipRepresentations); + paths->RemoveAllItems(); + vtkNew skipRepresentations; + this->FindPath(sourceRepresentationName, targetRepresentationName, paths, skipRepresentations); } //---------------------------------------------------------------------------- -void vtkSegmentationConverter::FindPath(const std::string& sourceRepresentationName, const std::string& targetRepresentationName, ConversionPathAndCostListType &pathsCosts, std::set& skipRepresentations) +void vtkSegmentationConverter::FindPath(const std::string& sourceRepresentationName, + const std::string& targetRepresentationName, vtkSegmentationConversionPaths* pathsToSource, + vtkStringArray* skipRepresentations) { if (sourceRepresentationName == targetRepresentationName) { - vtkErrorMacro("FindPath failed: source and target representation names are the same - "<RulesGraph[sourceRepresentationName]; if (rulesFromSourceRepresentation.empty()) { - // dead end, no more rules from here + // dead end, no more rules from source return; } - // Get all the paths from here to the target - ConversionPathAndCostListType pathsCostsFromHere; - skipRepresentations.insert(sourceRepresentationName); + // Get all the paths from source to the target + vtkNew pathsFromSource; + + // skipRepresentations is reused, so we don't modify it + vtkNew skipRepresentationsNew; + skipRepresentationsNew->DeepCopy(skipRepresentations); + skipRepresentationsNew->InsertNextValue(sourceRepresentationName); + for (RulesListType::iterator representationRuleIt=rulesFromSourceRepresentation.begin(); representationRuleIt!=rulesFromSourceRepresentation.end(); ++representationRuleIt) { - if (skipRepresentations.find((*representationRuleIt)->GetTargetRepresentationName()) != skipRepresentations.end()) + if (skipRepresentationsNew->LookupValue((*representationRuleIt)->GetTargetRepresentationName()) >= 0) { // representation has to be ignored continue; } const std::string& thisRuleTargetRepresentationName = (*representationRuleIt)->GetTargetRepresentationName(); - if (thisRuleTargetRepresentationName==targetRepresentationName) + if (thisRuleTargetRepresentationName == targetRepresentationName) { // this rule leads to target directly - ConversionPathAndCostType pathCostFromHere; - pathCostFromHere.first.push_back(*representationRuleIt); // path - pathCostFromHere.second = (*representationRuleIt)->GetConversionCost(); // cost - pathsCostsFromHere.push_back(pathCostFromHere); + vtkNew pathFromSource; + pathFromSource->AddRule(*representationRuleIt); + pathsFromSource->AddPath(pathFromSource); } else { // this rule may lead to the target indirectly - ConversionPathAndCostListType pathsCostsFromNext; - this->FindPath(thisRuleTargetRepresentationName, targetRepresentationName, pathsCostsFromNext, skipRepresentations); - if (!pathsCostsFromNext.empty()) + vtkNew pathsFromNext; + this->FindPath(thisRuleTargetRepresentationName, targetRepresentationName, pathsFromNext, skipRepresentationsNew); + if (pathsFromNext->GetNumberOfPaths() > 0) { - for (ConversionPathAndCostListType::iterator pathCostFromNextIt = pathsCostsFromNext.begin(); pathCostFromNextIt != pathsCostsFromNext.end(); ++pathCostFromNextIt) + vtkSegmentationConversionPath* pathFromNext = nullptr; + vtkCollectionSimpleIterator it; + for (pathsFromNext->InitTraversal(it); (pathFromNext = pathsFromNext->GetNextPath(it));) { - ConversionPathAndCostType pathCostFromHere; - pathCostFromHere.first.push_back(*representationRuleIt); // path - pathCostFromHere.second = (*representationRuleIt)->GetConversionCost(); // cost - pathCostFromHere.first.insert(pathCostFromHere.first.end(), pathCostFromNextIt->first.begin(), pathCostFromNextIt->first.end()); // path append - pathCostFromHere.second += pathCostFromNextIt->second; // cost - pathsCostsFromHere.push_back(pathCostFromHere); + vtkNew pathFromSource; + pathFromSource->AddRule(*representationRuleIt); + pathFromSource->AddRules(pathFromNext); + pathsFromSource->AddPath(pathFromSource); } } } } - // skipRepresentations is reused, so make sure we restore the original contents - skipRepresentations.erase(sourceRepresentationName); - if (pathsCostsFromHere.empty()) + if (pathsFromSource->GetNumberOfPaths() == 0) { - // no paths from here to the target + // no paths from source to the target return; } - if (pathsCosts.empty()) + if (pathsToSource->GetNumberOfPaths() == 0) { - // pathsCosts to here is empty, so we are at the starting point - pathsCosts = pathsCostsFromHere; + // pathsToSource to source is empty, so we are at the starting point + pathsToSource->AddPaths(pathsFromSource); return; } - if (pathsCostsFromHere.size()==1) + if (pathsFromSource->GetNumberOfPaths() == 1) { - // Special case: there is just one possible continuation from here - // just append the only possible continuation here to all known paths to here and return - for (ConversionPathAndCostListType::iterator pathsCostsIt = pathsCosts.begin(); pathsCostsIt != pathsCosts.end(); ++pathsCostsIt) + // Special case: there is just one possible continuation from source + // just append the only possible continuation here to all known paths to the source and return + vtkSegmentationConversionPath* pathToSource = nullptr; + vtkCollectionSimpleIterator it; + for (pathsToSource->InitTraversal(it); (pathToSource = pathsToSource->GetNextPath(it));) { - pathsCostsIt->first.insert(pathsCostsIt->first.end(),pathsCostsFromHere[0].first.begin(),pathsCostsFromHere[0].first.end()); - pathsCostsIt->second += pathsCostsFromHere[0].second; + pathToSource->AddRules(pathsFromSource->GetPath(0)); } return; } - // There are multiple possible continuations from here, append that to all known paths to here (we'll have a full combination of all the paths to here and from here) - ConversionPathAndCostListType pathsCostsToHere = pathsCosts; // first save all the possible paths to here - for (ConversionPathAndCostListType::iterator pathCostsFromHereIt = pathsCostsFromHere.begin(); pathCostsFromHereIt != pathsCostsFromHere.end(); ++pathCostsFromHereIt) + // There are multiple possible continuations from source, append that to all known paths + // to source (we'll have a full combination of all the paths to source and from source) + vtkNew pathsToSourceOriginal; + pathsToSourceOriginal->AddPaths(pathsToSource); // first save all the possible paths to source + pathsToSource->RemoveAllItems(); + vtkSegmentationConversionPath* pathFromSource = nullptr; + vtkCollectionSimpleIterator itFromSource; + for (pathsFromSource->InitTraversal(itFromSource); (pathFromSource = pathsFromSource->GetNextPath(itFromSource));) { - if (pathCostsFromHereIt == pathsCostsFromHere.begin()) + // append each path from source to a copy of the original pathsToSource + vtkSegmentationConversionPath* pathToSource = nullptr; + vtkCollectionSimpleIterator itToSource; + for (pathsToSourceOriginal->InitTraversal(itToSource); (pathToSource = pathsToSourceOriginal->GetNextPath(itToSource));) { - // first path from here, just append it to the current pathsCosts - for (ConversionPathAndCostListType::iterator pathsCostsToHereIt = pathsCosts.begin(); pathsCostsToHereIt != pathsCosts.end(); ++pathsCostsToHereIt) - { - pathsCostsToHereIt->first.insert(pathsCostsToHereIt->first.end(), pathCostsFromHereIt->first.begin(), pathCostsFromHereIt->first.end()); - pathsCostsToHereIt->second += pathCostsFromHereIt->second; - } - } - else - { - // additional path from here, append it to a copy of the original pathsCosts (pathsCosts to here) - for (ConversionPathAndCostListType::iterator pathsCostsToHereIt = pathsCostsToHere.begin(); pathsCostsToHereIt != pathsCostsToHere.end(); ++pathsCostsToHereIt) - { - ConversionPathAndCostType pathCost; - pathCost.first = pathsCostsToHereIt->first; // path - pathCost.second = pathsCostsToHereIt->second; // cost - pathCost.first.insert(pathCost.first.end(), pathCostsFromHereIt->first.begin(), pathCostsFromHereIt->first.end()); // path append - pathCost.second += pathCostsFromHereIt->second; // cost - pathsCosts.push_back(pathCost); - } + vtkNew path; + path->AddRules(pathToSource); + path->AddRules(pathFromSource); + pathsToSource->AddPath(path); } } } @@ -470,21 +466,22 @@ void vtkSegmentationConverter::GetAvailableRepresentationNames(std::setRemoveAllParameters(); + int numberOfRules = path->GetNumberOfRules(); + for (int ruleIndex = 0; ruleIndex < numberOfRules; ++ruleIndex) { - (*ruleIt)->GetRuleConversionParameters(conversionParameters); + path->GetRule(ruleIndex)->GetRuleConversionParameters(conversionParameters); } } //---------------------------------------------------------------------------- -void vtkSegmentationConverter::GetAllConversionParameters(vtkSegmentationConverterRule::ConversionParameterListType& conversionParameters) +void vtkSegmentationConverter::GetAllConversionParameters( + vtkSegmentationConversionParameters* conversionParameters) { - conversionParameters.clear(); + conversionParameters->RemoveAllParameters(); for (ConverterRulesListType::iterator ruleIt = this->ConverterRules.begin(); ruleIt != this->ConverterRules.end(); ++ruleIt) { (*ruleIt)->GetRuleConversionParameters(conversionParameters); @@ -495,13 +492,15 @@ void vtkSegmentationConverter::GetAllConversionParameters(vtkSegmentationConvert std::string vtkSegmentationConverter::SerializeAllConversionParameters() { std::stringstream ssParameters; - vtkSegmentationConverterRule::ConversionParameterListType parameters; + vtkNew parameters; this->GetAllConversionParameters(parameters); - vtkSegmentationConverterRule::ConversionParameterListType::iterator paramIt; - for (paramIt = parameters.begin(); paramIt != parameters.end(); ++paramIt) + int numberOfParameters = parameters->GetNumberOfParameters(); + for (int parameterIndex = 0; parameterIndex < numberOfParameters; parameterIndex++) { - ssParameters << paramIt->first << SERIALIZATION_SEPARATOR_INNER << paramIt->second.first << SERIALIZATION_SEPARATOR_INNER << paramIt->second.second << SERIALIZATION_SEPARATOR; + ssParameters << parameters->GetName(parameterIndex) << SERIALIZATION_SEPARATOR_INNER + << parameters->GetValue(parameterIndex) << SERIALIZATION_SEPARATOR_INNER + << parameters->GetDescription(parameterIndex) << SERIALIZATION_SEPARATOR; } return ssParameters.str(); diff --git a/Libs/vtkSegmentationCore/vtkSegmentationConverter.h b/Libs/vtkSegmentationCore/vtkSegmentationConverter.h index 470c5fc475c..32594511fd0 100644 --- a/Libs/vtkSegmentationCore/vtkSegmentationConverter.h +++ b/Libs/vtkSegmentationCore/vtkSegmentationConverter.h @@ -24,6 +24,7 @@ // VTK includes #include #include +#include // STD includes #include @@ -35,6 +36,7 @@ #include "vtkSegmentationCoreConfigure.h" #include "vtkSegmentationConverterRule.h" +#include "vtkSegmentationConversionPath.h" class vtkAbstractTransform; class vtkSegment; @@ -47,11 +49,6 @@ class vtkOrientedImageData; class vtkSegmentationCore_EXPORT vtkSegmentationConverter : public vtkObject { public: - typedef std::vector< vtkSmartPointer > ConverterRulesListType; - - typedef std::vector ConversionPathType; // Contains a list of converter rule names - typedef std::pair ConversionPathAndCostType; - typedef std::vector ConversionPathAndCostListType; /// Default representation types /// In binary and fractional labelmaps values <=0 are considered background voxels (outside), values>0 are foreground (inside). @@ -87,16 +84,17 @@ class vtkSegmentationCore_EXPORT vtkSegmentationConverter : public vtkObject void GetAvailableRepresentationNames(std::set& representationNames); /// Get all possible conversions between two representations - void GetPossibleConversions(const std::string& sourceRepresentationName, const std::string& targetRepresentationName, ConversionPathAndCostListType &pathsCosts); + void GetPossibleConversions(const std::string& sourceRepresentationName, + const std::string& targetRepresentationName, vtkSegmentationConversionPaths* paths); /// Get all conversion parameters used by the selected conversion path - void GetConversionParametersForPath(vtkSegmentationConverterRule::ConversionParameterListType& conversionParameters, const ConversionPathType& path); + void GetConversionParametersForPath(vtkSegmentationConversionParameters* conversionParameters, vtkSegmentationConversionPath* path); /// Get all conversion parameters in this converter. Aggregates all parameters from all rules - void GetAllConversionParameters(vtkSegmentationConverterRule::ConversionParameterListType& conversionParameters); + void GetAllConversionParameters(vtkSegmentationConversionParameters* conversionParameters); /// Set a list of conversion parameters to all rules (cannot change the description, only the value) - void SetConversionParameters(vtkSegmentationConverterRule::ConversionParameterListType parameters); + void SetConversionParameters(vtkSegmentationConversionParameters* parameters); /// Set a conversion parameter to all rules having this parameter void SetConversionParameter(const std::string& name, const std::string& value, const std::string& description=""); @@ -125,7 +123,7 @@ class vtkSegmentationCore_EXPORT vtkSegmentationConverter : public vtkObject // Utility functions public: /// Return cheapest path from a list of paths with costs - static ConversionPathType GetCheapestPath(const ConversionPathAndCostListType &pathsCosts); + static vtkSegmentationConversionPath* GetCheapestPath(vtkSegmentationConversionPaths* paths); /// Utility function for serializing geometry of oriented image data static std::string SerializeImageGeometry(vtkOrientedImageData* orientedImageData); @@ -165,13 +163,16 @@ class vtkSegmentationCore_EXPORT vtkSegmentationConverter : public vtkObject /// \param skipRepresentations Representations that should be ignored (e.g., because they are /// used already). The caller should pass an empty set (when the method is called recursively /// the set is not empty). - void FindPath(const std::string& sourceRepresentationName, const std::string& targetRepresentationName, ConversionPathAndCostListType &pathsCosts, std::set& skipRepresentations); + void FindPath(const std::string& sourceRepresentationName, const std::string& targetRepresentationName, + vtkSegmentationConversionPaths* paths, vtkStringArray* skipRepresentations); protected: vtkSegmentationConverter(); ~vtkSegmentationConverter() override; protected: + typedef std::vector< vtkSmartPointer > ConverterRulesListType; + /// Converter rules. When the class is created it contains just the default converter rules but then /// rules may be customized with parameters and may store segment-specific information. /// Therefore, the rules should not be reused in other segments. diff --git a/Libs/vtkSegmentationCore/vtkSegmentationConverterRule.cxx b/Libs/vtkSegmentationCore/vtkSegmentationConverterRule.cxx index ba31ceca306..ac498ce19af 100644 --- a/Libs/vtkSegmentationCore/vtkSegmentationConverterRule.cxx +++ b/Libs/vtkSegmentationCore/vtkSegmentationConverterRule.cxx @@ -34,14 +34,29 @@ vtkSegmentationConverterRule::vtkSegmentationConverterRule() = default; //---------------------------------------------------------------------------- vtkSegmentationConverterRule::~vtkSegmentationConverterRule() { - this->ConversionParameters.clear(); +} + +//---------------------------------------------------------------------------- +void vtkSegmentationConverterRule::PrintSelf(ostream& os, vtkIndent indent) +{ + Superclass::PrintSelf(os, indent); + os << indent << + (this->GetSourceRepresentationName() ? this->GetSourceRepresentationName() : "NULL") + << " -> " + << (this->GetTargetRepresentationName() ? this->GetTargetRepresentationName() : "NULL") + << "\n"; + if (this->ConversionParameters->GetNumberOfParameters() > 0) + { + os << indent << "ConversionParameters:\n"; + this->ConversionParameters->PrintSelf(os, indent.GetNextIndent()); + } } //---------------------------------------------------------------------------- vtkSegmentationConverterRule* vtkSegmentationConverterRule::Clone() { vtkSegmentationConverterRule* clone = this->CreateRuleInstance(); - clone->ConversionParameters = this->ConversionParameters; + clone->ConversionParameters->DeepCopy(this->ConversionParameters); return clone; } @@ -63,41 +78,45 @@ bool vtkSegmentationConverterRule::CreateTargetRepresentation(vtkSegment* segmen } //---------------------------------------------------------------------------- -void vtkSegmentationConverterRule::GetRuleConversionParameters(ConversionParameterListType& conversionParameters) +void vtkSegmentationConverterRule::GetRuleConversionParameters(vtkSegmentationConversionParameters* conversionParameters) { // Copy rule conversion parameters into aggregated path parameters - ConversionParameterListType::iterator paramIt; - for (paramIt = this->ConversionParameters.begin(); paramIt != this->ConversionParameters.end(); ++paramIt) + if (!conversionParameters) + { + vtkErrorMacro("GetRuleConversionParameters failed: invalid conversionParameters"); + return; + } + int numberOfParameters = this->ConversionParameters->GetNumberOfParameters(); + for (int parameterIndex = 0; parameterIndex < numberOfParameters; parameterIndex++) { - conversionParameters[paramIt->first] = paramIt->second; + conversionParameters->CopyParameter(this->ConversionParameters, parameterIndex); } } //---------------------------------------------------------------------------- void vtkSegmentationConverterRule::SetConversionParameter(const std::string& name, const std::string& value, const std::string& description/*=""*/) { - this->ConversionParameters[name].first = value; - + this->ConversionParameters->SetValue(name, value); if (!description.empty()) { - this->ConversionParameters[name].second = description; + this->ConversionParameters->SetDescription(name, description); } } //---------------------------------------------------------------------------- std::string vtkSegmentationConverterRule::GetConversionParameter(const std::string& name) { - return this->ConversionParameters[name].first; + return this->ConversionParameters->GetValue(name); } //---------------------------------------------------------------------------- std::string vtkSegmentationConverterRule::GetConversionParameterDescription(const std::string& name) { - return this->ConversionParameters[name].second; + return this->ConversionParameters->GetDescription(name); } //---------------------------------------------------------------------------- bool vtkSegmentationConverterRule::HasConversionParameter(const std::string& name) { - return (this->ConversionParameters.count(name) > 0); + return (this->ConversionParameters->GetIndexFromName(name) >= 0); } diff --git a/Libs/vtkSegmentationCore/vtkSegmentationConverterRule.h b/Libs/vtkSegmentationCore/vtkSegmentationConverterRule.h index 858eb9dc9f3..86de65a0106 100644 --- a/Libs/vtkSegmentationCore/vtkSegmentationConverterRule.h +++ b/Libs/vtkSegmentationCore/vtkSegmentationConverterRule.h @@ -24,17 +24,15 @@ #include "vtkSegmentationCoreConfigure.h" // VTK includes +#include #include -// STD includes -#include -#include -#include - class vtkDataObject; class vtkSegmentation; class vtkSegment; +#include "vtkSegmentationConversionParameters.h" + /// Helper macro for supporting cloning of rules #ifndef vtkSegmentationConverterRuleNewMacro #define vtkSegmentationConverterRuleNewMacro(newClass) \ @@ -44,19 +42,18 @@ class vtkSegment; return newClass::New(); \ } #endif - /// \ingroup SegmentationCore /// \brief Abstract converter rule class. Subclasses perform conversions between specific /// representation types. They define source and target type and provide ways to create those /// types of objects. +/// +/// Each conversion rule defines its required/possible conversion parameters, +/// and sets possible default values whenever applicable. Required parameters have empty defaults. +/// When the user changes the parameter value, then the default is being overwritten to contain the +/// custom value, but for new segmentations, it is initially the default. class vtkSegmentationCore_EXPORT vtkSegmentationConverterRule : public vtkObject { public: - /// Conversion parameter list type. Maps the conversion parameter name to a pair consisting of the - /// value of the parameter (the default value if it is defined in the converter rule) and the - /// description of the parameter that appears as tooltip in the conversion parameters widget - /// ( name => (value, description) ) - typedef std::map > ConversionParameterListType; /// Constant to use for converter rules with "infinite" computational cost (i.e. disabled) /// It's about UINT_MAX / 400 (allows us to have a few hundred disabled rules) @@ -65,6 +62,7 @@ class vtkSegmentationCore_EXPORT vtkSegmentationConverterRule : public vtkObject public: //static vtkSegmentationConverterRule* New(); vtkTypeMacro(vtkSegmentationConverterRule, vtkObject); + void PrintSelf(ostream& os, vtkIndent indent) override; /// Create instance of the default node. Similar to New but virtual method. /// Subclasses should implement this method by @@ -117,8 +115,9 @@ class vtkSegmentationCore_EXPORT vtkSegmentationConverterRule : public vtkObject virtual const char* GetTargetRepresentationName() = 0; /// Get rule conversion parameters for aggregated path parameters. - /// Existing values in the map are overwritten, missing name&values are added. - virtual void GetRuleConversionParameters(ConversionParameterListType& conversionParameters); + /// Existing values in the provided conversionParameters object overwritten, + /// missing name and values are added. + virtual void GetRuleConversionParameters(vtkSegmentationConversionParameters* conversionParameters) VTK_EXPECTS(conversionParameters != nullptr); /// Set a conversion parameter virtual void SetConversionParameter(const std::string& name, const std::string& value, const std::string& description=""); @@ -146,7 +145,7 @@ class vtkSegmentationCore_EXPORT vtkSegmentationConverterRule : public vtkObject /// and sets possible default values whenever applicable. Required parameters have empty defaults. /// When the user changes the parameter value, then the default is being overwritten to contain the /// custom value, but for new segmentations, it is initially the default. - ConversionParameterListType ConversionParameters; + vtkNew ConversionParameters; /// Used when calling createTargetRepresentation /// If true, replaces the target representation of the segment with a new object, even if one already exists diff --git a/Modules/Loadable/Segmentations/Logic/vtkSlicerSegmentationsModuleLogic.cxx b/Modules/Loadable/Segmentations/Logic/vtkSlicerSegmentationsModuleLogic.cxx index cb4cbc1fa31..9666fd5f362 100644 --- a/Modules/Loadable/Segmentations/Logic/vtkSlicerSegmentationsModuleLogic.cxx +++ b/Modules/Loadable/Segmentations/Logic/vtkSlicerSegmentationsModuleLogic.cxx @@ -1910,12 +1910,12 @@ bool vtkSlicerSegmentationsModuleLogic::SetBinaryLabelmapToSegment( std::string targetRepresentationName = (*reprIt); if (targetRepresentationName.compare(vtkSegmentationConverter::GetSegmentationBinaryLabelmapRepresentationName())) { - vtkSegmentationConverter::ConversionPathAndCostListType pathCosts; - segmentation->GetPossibleConversions(targetRepresentationName, pathCosts); + vtkNew paths; + segmentation->GetPossibleConversions(targetRepresentationName, paths); // Get cheapest path from found conversion paths - vtkSegmentationConverter::ConversionPathType cheapestPath = vtkSegmentationConverter::GetCheapestPath(pathCosts); - if (cheapestPath.empty()) + vtkSegmentationConversionPath* cheapestPath = vtkSegmentationConverter::GetCheapestPath(paths); + if (!cheapestPath) { continue; } @@ -2647,12 +2647,12 @@ bool vtkSlicerSegmentationsModuleLogic::ReconvertAllRepresentations(vtkMRMLSegme std::string targetRepresentationName = (*reprIt); if (targetRepresentationName.compare(segmentation->MasterRepresentationName)) { - vtkSegmentationConverter::ConversionPathAndCostListType pathCosts; - segmentation->GetPossibleConversions(targetRepresentationName, pathCosts); + vtkNew paths; + segmentation->GetPossibleConversions(targetRepresentationName, paths); // Get cheapest path from found conversion paths - vtkSegmentationConverter::ConversionPathType cheapestPath = vtkSegmentationConverter::GetCheapestPath(pathCosts); - if (!cheapestPath.empty()) + vtkSegmentationConversionPath* cheapestPath = vtkSegmentationConverter::GetCheapestPath(paths); + if (cheapestPath) { conversionHappened |= segmentationNode->GetSegmentation()->ConvertSegmentsUsingPath(segmentIDsToConvert, cheapestPath, true); } diff --git a/Modules/Loadable/Segmentations/Testing/Python/SegmentationsModuleTest1.py b/Modules/Loadable/Segmentations/Testing/Python/SegmentationsModuleTest1.py index 7bbe0a04b04..c3b0d28e9c2 100644 --- a/Modules/Loadable/Segmentations/Testing/Python/SegmentationsModuleTest1.py +++ b/Modules/Loadable/Segmentations/Testing/Python/SegmentationsModuleTest1.py @@ -32,6 +32,7 @@ def test_SegmentationsModuleTest1(self): self.TestSection_RetrieveInputData() self.TestSection_LoadInputData() self.TestSection_AddRemoveSegment() + self.TestSection_ConvertBetweenRepresentations() self.TestSection_MergeLabelmapWithDifferentGeometries() self.TestSection_ImportExportSegment() self.TestSection_ImportExportSegment2() @@ -171,6 +172,55 @@ def TestSection_AddRemoveSegment(self): self.inputSegmentationNode.GetSegmentation().RemoveSegment(self.sphereSegmentName) self.assertEqual(self.inputSegmentationNode.GetSegmentation().GetNumberOfSegments(), 2) + # ------------------------------------------------------------------------------ + def TestSection_ConvertBetweenRepresentations(self): + # Test conversion between segment representations with custom conversion path + logging.info('Test section: Convert between representations') + + segmentation = self.inputSegmentationNode.GetSegmentation() + + # Get all possible conversions + conversionPaths = slicer.vtkSegmentationConversionPaths() + segmentation.GetPossibleConversions("Binary labelmap", conversionPaths) + print(conversionPaths) + self.assertEqual(conversionPaths.GetNumberOfPaths(), 1) + + # Choose a conversion path + conversionPath = conversionPaths.GetPath(0) + self.assertEqual(conversionPath.GetCost(), 500) + self.assertEqual(conversionPath.GetNumberOfRules(), 1) + + # Get information about a conversion rule + rule = conversionPath.GetRule(0) + self.assertEqual(rule.GetName(), 'Closed surface to binary labelmap (simple image stencil)') + self.assertEqual(rule.GetSourceRepresentationName(), 'Closed surface') + self.assertEqual(rule.GetTargetRepresentationName(), 'Binary labelmap') + + # Adjust conversion parameter + conversionParameters = slicer.vtkSegmentationConversionParameters() + segmentation.GetConversionParametersForPath(conversionParameters, conversionPath) + print(conversionParameters) + self.assertEqual(conversionParameters.GetNumberOfParameters(), 4) + conversionParameters.SetValue('Oversampling factor', str(2)) + self.assertEqual(conversionParameters.GetValueAsDouble('Oversampling factor'), 2) + + # Convert + segment = segmentation.GetNthSegment(0) + self.assertTrue(segmentation.CreateRepresentation(conversionPath, conversionParameters)) + dim = segment.GetRepresentation(self.binaryLabelmapReprName).GetDimensions() + oversampledVoxelCount = dim[0] * dim[1] * dim[2] + + conversionParameters.SetValue('Oversampling factor', str(1)) + self.assertTrue(segmentation.CreateRepresentation(conversionPath, conversionParameters)) + dim = segment.GetRepresentation(self.binaryLabelmapReprName).GetDimensions() + nonOversampledVoxelCount = dim[0] * dim[1] * dim[2] + + # Oversampling by 2x would increment the voxel count by 2x2x2 = 8x, but the + # labelmap extent is very small, so in this case the voxel count is increased + # only by about 6x. Therefore, in the test we check if oversampling increases + # the voxel count by at least 5x. + self.assertGreater(oversampledVoxelCount, nonOversampledVoxelCount * 5) + # ------------------------------------------------------------------------------ def TestSection_MergeLabelmapWithDifferentGeometries(self): # Merge labelmap when segments containing labelmaps with different geometries (both same directions, different directions) diff --git a/Modules/Loadable/Segmentations/Widgets/qMRMLSegmentationConversionParametersWidget.cxx b/Modules/Loadable/Segmentations/Widgets/qMRMLSegmentationConversionParametersWidget.cxx index 90bfe977268..3ccac78b879 100644 --- a/Modules/Loadable/Segmentations/Widgets/qMRMLSegmentationConversionParametersWidget.cxx +++ b/Modules/Loadable/Segmentations/Widgets/qMRMLSegmentationConversionParametersWidget.cxx @@ -69,7 +69,9 @@ class qMRMLSegmentationConversionParametersWidgetPrivate: public Ui_qMRMLSegment QString TargetRepresentationName; /// Possible conversion paths for the current segmentation and target representation - std::vector PossiblePaths; + vtkNew PossiblePaths; + + vtkNew ConversionParameters; private: QStringList PathsColumnLabels; @@ -206,7 +208,7 @@ void qMRMLSegmentationConversionParametersWidget::populatePathsTable() d->PathsTable->blockSignals(true); d->PathsTable->clearContents(); - d->PossiblePaths.clear(); + d->PossiblePaths->RemoveAllItems(); if (!d->SegmentationNode || d->TargetRepresentationName.isEmpty()) { @@ -223,11 +225,10 @@ void qMRMLSegmentationConversionParametersWidget::populatePathsTable() // Get possible paths vtkSegmentation* segmentation = d->SegmentationNode->GetSegmentation(); - vtkSegmentationConverter::ConversionPathAndCostListType pathsCosts; - segmentation->GetPossibleConversions(d->TargetRepresentationName.toUtf8().constData(), pathsCosts); + segmentation->GetPossibleConversions(d->TargetRepresentationName.toUtf8().constData(), d->PossiblePaths); - if (pathsCosts.empty()) - { + if (d->PossiblePaths->GetNumberOfPaths() == 0) + { d->PathsTable->setRowCount(1); QTableWidgetItem* noPathsItem = new QTableWidgetItem("No path available!"); QFont boldFont; @@ -238,32 +239,33 @@ void qMRMLSegmentationConversionParametersWidget::populatePathsTable() d->PathsTable->blockSignals(false); return; - } + } - vtkSegmentationConverter::ConversionPathAndCostListType::iterator pathIt; - d->PathsTable->setRowCount(pathsCosts.size()); + int rowCount = d->PossiblePaths->GetNumberOfPaths(); + d->PathsTable->setRowCount(rowCount); int row = 0; - for (pathIt=pathsCosts.begin(); pathIt!=pathsCosts.end(); ++pathIt, ++row) + for (int row = 0; row< d->PossiblePaths->GetNumberOfPaths(); ++row) { // Path cost - QString costString = QString::number(pathIt->second); + vtkSegmentationConversionPath* path = d->PossiblePaths->GetPath(row); + QString costString = QString::number(path->GetCost()); QTableWidgetItem* costItem = new QTableWidgetItem(costString); costItem->setFlags(costItem->flags() & ~Qt::ItemIsEditable); costItem->setData(Qt::UserRole, QVariant(row)); d->PathsTable->setItem(row, d->pathsColumnIndex("Cost"), costItem); // Path - vtkSegmentationConverter::ConversionPathType path = pathIt->first; - d->PossiblePaths.push_back(path); - vtkSegmentationConverterRule* firstRule = (*path.begin()); - QString pathString(firstRule->GetSourceRepresentationName()); // Add source (master) representation - - vtkSegmentationConverter::ConversionPathType::iterator ruleIt; - for (ruleIt=path.begin(); ruleIt!=path.end(); ++ruleIt) + QString pathString; + for (int ruleIndex = 0; ruleIndex < path->GetNumberOfRules(); ruleIndex++) { - vtkSegmentationConverterRule* currentRule = (*ruleIt); + vtkSegmentationConverterRule* rule = path->GetRule(ruleIndex); + if (ruleIndex == 0) + { + // Add source (master) representation + pathString.append(rule->GetSourceRepresentationName()); + } pathString.append(" -> "); - pathString.append(currentRule->GetTargetRepresentationName()); + pathString.append(rule->GetTargetRepresentationName()); } QTableWidgetItem* pathItem = new QTableWidgetItem(pathString); pathItem->setFlags(pathItem->flags() & ~Qt::ItemIsEditable); @@ -292,9 +294,9 @@ void qMRMLSegmentationConversionParametersWidget::populateParametersTable() return; } - vtkSegmentationConverter::ConversionPathType selectedPath = this->selectedPath(); - if (selectedPath.empty()) - { + vtkSegmentationConversionPath* selectedPath = this->selectedPath(); + if (!selectedPath) + { d->ParametersTable->setRowCount(1); QTableWidgetItem* noParametersItem = new QTableWidgetItem("No path selected"); QFont boldFont; @@ -305,14 +307,14 @@ void qMRMLSegmentationConversionParametersWidget::populateParametersTable() d->ParametersTable->blockSignals(false); return; - } + } // Get parameters for selected path vtkSegmentation* segmentation = d->SegmentationNode->GetSegmentation(); - vtkSegmentationConverterRule::ConversionParameterListType parameters; + vtkNew parameters; segmentation->GetConversionParametersForPath(parameters, selectedPath); - if (parameters.empty()) + if (parameters->GetNumberOfParameters() == 0) { d->ParametersTable->setRowCount(1); QTableWidgetItem* noParametersItem = new QTableWidgetItem("No parameters for selected path"); @@ -326,21 +328,20 @@ void qMRMLSegmentationConversionParametersWidget::populateParametersTable() return; } - vtkSegmentationConverterRule::ConversionParameterListType::iterator paramIt; - d->ParametersTable->setRowCount(parameters.size()); - int row = 0; - for (paramIt=parameters.begin(); paramIt!=parameters.end(); ++paramIt, ++row) + int numberOfParameters = parameters->GetNumberOfParameters(); + d->ParametersTable->setRowCount(numberOfParameters); + for (int row = 0; row < numberOfParameters; ++row) { // Parameter name - QString parameterName(paramIt->first.c_str()); - QString parameterDescription(paramIt->second.second.c_str()); + QString parameterName = QString::fromStdString(parameters->GetName(row)); + QString parameterDescription = QString::fromStdString(parameters->GetDescription(row)); QTableWidgetItem* nameItem = new QTableWidgetItem(parameterName); nameItem->setToolTip(parameterDescription); nameItem->setFlags(nameItem->flags() & ~Qt::ItemIsEditable); d->ParametersTable->setItem(row, d->parametersColumnIndex("Name"), nameItem); // Parameter value - QString parameterValue(paramIt->second.first.c_str()); + QString parameterValue = QString::fromStdString(parameters->GetValue(row)); // Special case: reference image geometry if (!parameterName.compare(vtkSegmentationConverter::GetReferenceImageGeometryParameterName().c_str())) @@ -397,12 +398,12 @@ void qMRMLSegmentationConversionParametersWidget::applyConversion() // Perform conversion using selected path and chosen conversion parameters QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); - if (!d->SegmentationNode->GetSegmentation()->CreateRepresentation( - this->selectedPath(), this->conversionParameters())) - { + vtkSegmentationConversionPath* selectedPath = this->selectedPath(); + if (!d->SegmentationNode->GetSegmentation()->CreateRepresentation(selectedPath, this->conversionParameters())) + { QString message = QString("Failed to convert %1 to %2!").arg(d->SegmentationNode->GetName()).arg(d->TargetRepresentationName); QMessageBox::warning(nullptr, tr("Conversion failed"), message); - } + } QApplication::restoreOverrideCursor(); emit conversionDone(); @@ -448,50 +449,53 @@ void qMRMLSegmentationConversionParametersWidget::onSpecifyGeometryButtonClicked } //----------------------------------------------------------------------------- -vtkSegmentationConverter::ConversionPathType qMRMLSegmentationConversionParametersWidget::selectedPath() +vtkSegmentationConversionPath* qMRMLSegmentationConversionParametersWidget::selectedPath() { Q_D(qMRMLSegmentationConversionParametersWidget); - if (d->PossiblePaths.empty()) - { - vtkSegmentationConverter::ConversionPathType emptyPath; - return emptyPath; - } + if (d->PossiblePaths->GetNumberOfPaths() == 0) + { + return nullptr; + } QList selectedItems = d->PathsTable->selectedItems(); if (selectedItems.isEmpty()) - { - vtkSegmentationConverter::ConversionPathType emptyPath; - return emptyPath; - } + { + return nullptr; + } QTableWidgetItem* firstItem = (*selectedItems.begin()); int selectedRow = firstItem->data(Qt::UserRole).toInt(); + if (selectedRow < 0 || selectedRow >= d->PossiblePaths->GetNumberOfPaths()) + { + return nullptr; + } - return d->PossiblePaths[selectedRow]; + return d->PossiblePaths->GetPath(selectedRow); } //----------------------------------------------------------------------------- -vtkSegmentationConverterRule::ConversionParameterListType qMRMLSegmentationConversionParametersWidget::conversionParameters() +vtkSegmentationConversionParameters* qMRMLSegmentationConversionParametersWidget::conversionParameters() { Q_D(qMRMLSegmentationConversionParametersWidget); - + d->ConversionParameters->RemoveAllParameters(); if (!d->SegmentationNode) { - vtkSegmentationConverterRule::ConversionParameterListType emptyParameters; - return emptyParameters; + return d->ConversionParameters; } - vtkSegmentationConverter::ConversionPathType selectedPath = this->selectedPath(); - if (selectedPath.empty()) - { - vtkSegmentationConverterRule::ConversionParameterListType emptyParameters; - return emptyParameters; - } + vtkSegmentationConversionPath* selectedPath = this->selectedPath(); + if (!selectedPath) + { + return d->ConversionParameters; + } // Get parameters for selected path vtkSegmentation* segmentation = d->SegmentationNode->GetSegmentation(); - vtkSegmentationConverterRule::ConversionParameterListType parameters; - segmentation->GetConversionParametersForPath(parameters, selectedPath); - return parameters; + if (!segmentation) + { + return d->ConversionParameters; + } + segmentation->GetConversionParametersForPath(d->ConversionParameters, selectedPath); + return d->ConversionParameters; } diff --git a/Modules/Loadable/Segmentations/Widgets/qMRMLSegmentationConversionParametersWidget.h b/Modules/Loadable/Segmentations/Widgets/qMRMLSegmentationConversionParametersWidget.h index bcbafc8b16f..86c1ef5d15c 100644 --- a/Modules/Loadable/Segmentations/Widgets/qMRMLSegmentationConversionParametersWidget.h +++ b/Modules/Loadable/Segmentations/Widgets/qMRMLSegmentationConversionParametersWidget.h @@ -61,10 +61,11 @@ class Q_SLICER_MODULE_SEGMENTATIONS_WIDGETS_EXPORT qMRMLSegmentationConversionPa QString targetRepresentationName(); /// Return selected path - vtkSegmentationConverter::ConversionPathType selectedPath(); + vtkSegmentationConversionPath* selectedPath(); - /// Return chosen conversion parameters - vtkSegmentationConverterRule::ConversionParameterListType conversionParameters(); + /// Get chosen conversion parameters. + /// Always returns a valid object. + vtkSegmentationConversionParameters* conversionParameters(); signals: /// Emitted when conversion is done diff --git a/Modules/Loadable/Volumes/qSlicerVolumesReader.cxx b/Modules/Loadable/Volumes/qSlicerVolumesReader.cxx index f1f2f637fba..adf691e1542 100644 --- a/Modules/Loadable/Volumes/qSlicerVolumesReader.cxx +++ b/Modules/Loadable/Volumes/qSlicerVolumesReader.cxx @@ -166,7 +166,9 @@ bool qSlicerVolumesReader::load(const IOProperties& properties) } } Q_ASSERT(d->Logic); - vtkMRMLVolumeNode* node = d->Logic->AddArchetypeVolume( + // Weak pointer is used because the node may be deleted if the scene is closed + // right after reading. + vtkWeakPointer node = d->Logic->AddArchetypeVolume( fileName.toUtf8(), name.toUtf8(), options,