Skip to content

Commit

Permalink
BUG: Fix issues caused by invalid Segmentation binary labelmap scalar…
Browse files Browse the repository at this point in the history
… types

Adds the functions ValidateSegmentationImageType and CastToSmallestUnsignedIntegerType to vtkOrientedImageDataResample.
- IsImageScalarTypeValid: Returns True if the scalar type of the image is valid for representing segmentations. False otherwise.
- CastToSmallestUnsignedIntegerType: Casts the contents of the image to the smallest unsigned integer type that can contain all values in the image.
- ValidateSegmentationImageType: Checks to see if the scalar type of the image is a valid type, and casts the image to the smallest valid type if it is not.

The function ValidateSegmentationImageType is used during binary labelmap read/import to automatically convert the labelmap.
If a labelmap representation with an invalid scalar type is added to a segment, we only log a warning and don't automatically convert the labelmap.
Before performing binary labelmap to closed surface conversion, we check that the scalar type is valid, and log/return an error if it is not.

This should prevent errors from arising due to the use of floating point images for segmentations.

Re #6941

Co-authored-by: Jean-Christophe Fillion-Robin <jchris.fillionr@kitware.com>
  • Loading branch information
2 people authored and lassoan committed Mar 7, 2024
1 parent 26f728a commit 024b340
Show file tree
Hide file tree
Showing 10 changed files with 491 additions and 105 deletions.
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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",
"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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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

0 comments on commit 024b340

Please sign in to comment.