Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUG: Fix issues caused by invalid Segmentation binary labelmap scalar types #7605

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -33,7 +33,7 @@ Care Ontario.
#include "vtkFractionalLabelmapToClosedSurfaceConversionRule.h"
#include "vtkClosedSurfaceToFractionalLabelmapConversionRule.h"

int vtkMRMLSegmentationStorageNodeTest1(int argc, char * argv[] )
int vtkMRMLSegmentationStorageNodeTest1(int argc, char* argv[])
{
vtkNew<vtkMRMLSegmentationStorageNode> node1;
vtkNew<vtkMRMLScene> scene;
Expand All @@ -43,9 +43,9 @@ int vtkMRMLSegmentationStorageNodeTest1(int argc, char * argv[] )
if (argc != 5)
{
std::cerr << "Line " << __LINE__
<< " - Missing or extra parameters!\n"
<< "Usage: " << argv[0] << " /path/to/ITKSnapSegmentation.nii.gz /path/to/OldSlicerSegmentation.seg.nrrd /path/to/SlicerSegmentation.seg.nrrd"
<< std::endl;
<< " - Missing or extra parameters!\n"
<< "Usage: " << argv[0] << " /path/to/ITKSnapSegmentation.nii.gz /path/to/OldSlicerSegmentation.seg.nrrd /path/to/SlicerSegmentation.seg.nrrd"
<< std::endl;
return EXIT_FAILURE;
}

Expand Down
29 changes: 28 additions & 1 deletion Libs/MRML/Core/vtkMRMLSegmentationStorageNode.cxx
Expand Up @@ -682,7 +682,7 @@ int vtkMRMLSegmentationStorageNode::ReadBinaryLabelmapRepresentation(vtkMRMLSegm
}
else
{
vtkDebugMacro("ReadBinaryLabelmapRepresentation: File is not using a supported format!");
vtkDebugMacro("ReadBinaryLabelmapRepresentation: File is not using a supported format");
return 0;
}

Expand All @@ -694,6 +694,33 @@ int vtkMRMLSegmentationStorageNode::ReadBinaryLabelmapRepresentation(vtkMRMLSegm
}

// Read succeeded
int ret = vtkOrientedImageDataResample::IsImageScalarTypeValid(imageData);
switch (ret)
{
case vtkOrientedImageDataResample::TYPE_CONVERSION_TRUNCATION_NEEDED:
vtkWarningToMessageCollectionMacro(this->GetUserMessages(), "vtkMRMLSegmentationStorageNode::ReadBinaryLabelmapRepresentation",
"Segmentation is a floating point scalar type and will be cast to an integer type by truncation (rounding towards 0).");
break;
case vtkOrientedImageDataResample::TYPE_CONVERSION_CLAMPING_NEEDED:
vtkWarningToMessageCollectionMacro(this->GetUserMessages(), "vtkMRMLSegmentationStorageNode::ReadBinaryLabelmapRepresentation",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to able to return this as userMessages to allow the error message to be displayed in the GUI.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this one returned already?

"Segmentation is a floating point scalar type and are outside of the possible range of integer. Voxel values will be clamped to integer range.");
break;
case vtkOrientedImageDataResample::TYPE_ERROR:
vtkErrorToMessageCollectionMacro(this->GetUserMessages(), "vtkMRMLSegmentationStorageNode::ReadBinaryLabelmapRepresentation",
"Failed to cast image to integer type.");
return 0;
case vtkOrientedImageDataResample::TYPE_OK:
default:
break;
}
if (ret != vtkOrientedImageDataResample::TYPE_OK
&& !vtkOrientedImageDataResample::CastSegmentationToSmallestIntegerType(imageData))
{
vtkErrorToMessageCollectionMacro(this->GetUserMessages(),
"vtkMRMLSegmentationStorageNode::ReadBinaryLabelmapRepresentation",
"Failed to cast image to integer type.");
return 0;
}

int numberOfFrames = imageData->GetNumberOfScalarComponents();

Expand Down
117 changes: 114 additions & 3 deletions Libs/vtkSegmentationCore/Testing/vtkSegmentationTest2.cxx
Expand Up @@ -228,13 +228,13 @@ bool TestSharedLabelmapCollapse()
}


expectedResults =
{
expectedResults =
{
0,
imageCount2 - imageCount3,
imageCount3,
imageCount4,
};
};
for (size_t i = 0; i < segments.size(); ++i)
{
vtkSegment* segment = segments[i];
Expand Down Expand Up @@ -314,6 +314,102 @@ bool TestSharedLabelmapCasting()
return true;
}

//----------------------------------------------------------------------------
bool TestLabelmapFloatToIntegerConversion(int scalarType)
{

int numberOfValues = 5;
double values[5] = { -2.0, -1.0, 0.0, 1.0, 2.0 };
int numberOfOffsets = 7;
double offsets[7] = { -0.3, -0.1, -0.01, 0.0, 0.01, 0.1, 0.3 };

vtkNew<vtkOrientedImageData> labelmap;
labelmap->SetDimensions(numberOfValues, numberOfOffsets, 1);
labelmap->AllocateScalars(scalarType, 1);
for (int i = 0; i < numberOfValues; ++i)
{
for (int j = 0; j < numberOfOffsets; ++j)
{
labelmap->SetScalarComponentFromDouble(i, j, 0, 0, values[i] + offsets[j]);
}
}

if (!vtkOrientedImageDataResample::CastSegmentationToSmallestIntegerType(labelmap))
{
std::cerr << "Unable to convert labelmap of type: " << vtkImageScalarTypeNameMacro(labelmap->GetScalarType()) << " to integer type" << std::endl;
return false;
}

for (int i = 0; i < numberOfValues; ++i)
{
for (int j = 0; j < numberOfOffsets; ++j)
{
double value = labelmap->GetScalarComponentAsDouble(i, j, 0, 0);
double expectedValue = std::trunc(values[i] + offsets[j]); // Casting from float to integer we expect truncation
if (value != expectedValue)
{
std::cerr << "Invalid value at index " << i << ", " << j << std::endl;
std::cerr << "\tOriginal value:" << values[i] << std::endl;
std::cerr << "\tOffset:" << offsets[j] << std::endl;
std::cerr << "\tScalar value: " << value << std::endl;
std::cerr << "\Expected value: " << expectedValue << std::endl;
std::cerr << "\tOriginal type: " << vtkImageScalarTypeNameMacro(scalarType) << std::endl;
std::cerr << "\tInteger type: " << vtkImageScalarTypeNameMacro(labelmap->GetScalarType()) << std::endl;
return false;
}
}
}

return true;
}

//----------------------------------------------------------------------------
bool TestLabelmapValidation()
{
vtkNew<vtkOrientedImageData> labelmap;
labelmap->SetDimensions(1, 1, 1);

int ret = vtkOrientedImageDataResample::TYPE_ERROR;

labelmap->AllocateScalars(VTK_UNSIGNED_CHAR, 1);
labelmap->SetScalarComponentFromDouble(0, 0, 0, 0, 0.0);
ret = vtkOrientedImageDataResample::IsImageScalarTypeValid(labelmap);
if (ret != vtkOrientedImageDataResample::TYPE_OK)
{
std::cerr << "Invalid return value " << ret << " should be " << vtkOrientedImageDataResample::TYPE_OK << std::endl;
return false;
}

labelmap->AllocateScalars(VTK_LONG, 1);
labelmap->SetScalarComponentFromDouble(0, 0, 0, 0, 0.0);
ret = vtkOrientedImageDataResample::IsImageScalarTypeValid(labelmap);
if (ret != vtkOrientedImageDataResample::TYPE_CONVERSION_NEEDED)
{
std::cerr << "Invalid return value " << ret << " should be " << vtkOrientedImageDataResample::TYPE_CONVERSION_NEEDED << std::endl;
return false;
}

labelmap->AllocateScalars(VTK_DOUBLE, 1);
labelmap->SetScalarComponentFromDouble(0, 0, 0, 0, VTK_DOUBLE_MAX);
ret = vtkOrientedImageDataResample::IsImageScalarTypeValid(labelmap);
if (ret != vtkOrientedImageDataResample::TYPE_CONVERSION_CLAMPING_NEEDED)
{
std::cerr << "Invalid return value " << ret << " should be " << vtkOrientedImageDataResample::TYPE_CONVERSION_CLAMPING_NEEDED << std::endl;
return false;
}

labelmap->AllocateScalars(VTK_FLOAT, 1);
labelmap->SetScalarComponentFromDouble(0, 0, 0, 0, 0.0);
ret = vtkOrientedImageDataResample::IsImageScalarTypeValid(labelmap);
if (ret != vtkOrientedImageDataResample::TYPE_CONVERSION_TRUNCATION_NEEDED)
{
std::cerr << "Invalid return value " << ret << " should be " << vtkOrientedImageDataResample::TYPE_CONVERSION_TRUNCATION_NEEDED << std::endl;
return false;
}

return true;
}

//----------------------------------------------------------------------------
int vtkSegmentationTest2(int vtkNotUsed(argc), char* vtkNotUsed(argv)[])
{
Expand All @@ -338,6 +434,21 @@ int vtkSegmentationTest2(int vtkNotUsed(argc), char* vtkNotUsed(argv)[])
return EXIT_FAILURE;
}

if (!TestLabelmapFloatToIntegerConversion(VTK_FLOAT))
{
return EXIT_FAILURE;
}

if (!TestLabelmapFloatToIntegerConversion(VTK_DOUBLE))
{
return EXIT_FAILURE;
}

if (!TestLabelmapValidation())
{
return EXIT_FAILURE;
}

std::cout << "Segmentation test 2 passed." << std::endl;
return EXIT_SUCCESS;
}
Expand Down
Expand Up @@ -23,6 +23,7 @@
#include "vtkSegmentation.h"

#include "vtkOrientedImageData.h"
#include "vtkOrientedImageDataResample.h"

// VTK includes
#include <vtkCompositeDataIterator.h>
Expand Down Expand Up @@ -159,6 +160,12 @@ bool vtkBinaryLabelmapToClosedSurfaceConversionRule::Convert(vtkSegment* segment
return false;
}

if (vtkOrientedImageDataResample::IsImageScalarTypeValid(orientedBinaryLabelmap) != vtkOrientedImageDataResample::TYPE_OK)
{
vtkErrorMacro("Convert: Source representation scalar type is not a valid integer type");
return false;
}

double smoothingFactor = this->ConversionParameters->GetValueAsDouble(GetSmoothingFactorParameterName());
int jointSmoothing = this->ConversionParameters->GetValueAsInt(GetJointSmoothingParameterName());

Expand Down