Skip to content

Commit

Permalink
BUG: Casting negative numbers to unsigned char error
Browse files Browse the repository at this point in the history
Casting negative numbers to unsigned char is an undefined
behavior.  Apple Silicon results in zero, Intel results in
wrap around values.

This test cast an image of Vector<double,3> pixels to
and image of Vector<unsigned char, 3> pixels inconsistently
across platforms.  Then the test was performed to compute
differences of the output images.

The test was comparison was re-written to take the magnitude of
the Vector<double,3> pixels and multiplies them into a range
of approximately 0-255 for the (now) scalar output image.  This
output image is easier to visualize and compute results.

ctest -V -R itkBSplineScatteredDataPointSetToImageFilterTest05
ctest -V -R itkSymmetricEigenAnalysisImageFilterTestDoNotOrder
ctest -V -R itkSymmetricEigenAnalysisImageFilterTestOrderByMagnitude
ctest -V -R itkSymmetricEigenAnalysisImageFilterTestOrderByValue
  • Loading branch information
hjmjohnson committed Mar 8, 2023
1 parent 028583b commit 1d6b3e9
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 88 deletions.

This file was deleted.

This file was deleted.

@@ -0,0 +1 @@
60ff7a61fc2ced761db98f50462f12b64a38024a7fb48e4b3b0fd580ae57555e7ddd08582800bd82cda800546f043cacb616daa28b280775b0d5558b2401e914
6 changes: 3 additions & 3 deletions Modules/Filtering/ImageGrid/test/CMakeLists.txt
Expand Up @@ -89,9 +89,9 @@ itk_add_test(NAME itkBSplineScatteredDataPointSetToImageFilterTest04
COMMAND ITKImageGridTestDriver itkBSplineScatteredDataPointSetToImageFilterTest4)
itk_add_test(NAME itkBSplineScatteredDataPointSetToImageFilterTest05
COMMAND ITKImageGridTestDriver
--compare DATA{Baseline/itkBSplineScatteredDataPointSetToImageFilterTest05.mha}
${ITK_TEST_OUTPUT_DIR}/itkBSplineScatteredDataPointSetToImageFilterTest05.mha
itkBSplineScatteredDataPointSetToImageFilterTest5 ${ITK_TEST_OUTPUT_DIR}/itkBSplineScatteredDataPointSetToImageFilterTest05.mha)
--compare DATA{Baseline/itkBSplineScatteredDataPointSetToImageFilterTest05_magnitude.png}
${ITK_TEST_OUTPUT_DIR}/itkBSplineScatteredDataPointSetToImageFilterTest05_magnitude.png
itkBSplineScatteredDataPointSetToImageFilterTest5 ${ITK_TEST_OUTPUT_DIR}/itkBSplineScatteredDataPointSetToImageFilterTest05_magnitude.png)
itk_add_test(NAME itkBSplineControlPointImageFilterTest1
COMMAND ITKImageGridTestDriver
--compare ${ITK_TEST_OUTPUT_DIR}/N4ControlPoints_2D_output.nii.gz
Expand Down
Expand Up @@ -21,6 +21,58 @@
#include "itkPointSet.h"
#include "itkBSplineScatteredDataPointSetToImageFilter.h"
#include "itkTestingMacros.h"
#include "itkImageRegionIterator.h"


/**
* Function to convert image of double precison vector pixels to a
* representation of scalar values that are easy to view and test.
* NOTE: Similar function in itkSymmetricEigenAnalysisImageFilterTest.cxx
*/
template <typename InternalImageType>
static void
makeTestableScalarImage(typename InternalImageType::Pointer internalImage, std::string outputFilename)
{ // using OutputPixelType = unsigned char;
using OutputPixelType = uint8_t;
using OutputImageType = itk::Image<OutputPixelType, 2>;

OutputImageType::Pointer outputImage = OutputImageType::New();
outputImage->CopyInformation(internalImage);
outputImage->SetRegions(internalImage->GetBufferedRegion());
outputImage->Allocate(true);

auto myiterator = itk::ImageRegionConstIterator<InternalImageType>(internalImage, internalImage->GetBufferedRegion());
auto myOutiterator = itk::ImageRegionIterator<OutputImageType>(outputImage, outputImage->GetBufferedRegion());

// Convert vector image to magnitude and scale to use range of png values
float max_magnitude_value = 0.0;
while (!myiterator.IsAtEnd())
{
const auto arr = myiterator.Get();
const auto magvalue = std::sqrt(arr[0] * arr[0] + arr[1] * arr[1] + arr[2] * arr[2]);
max_magnitude_value = std::max<float>(max_magnitude_value, magvalue);
++myiterator;
}
const float scale_factor = 255.0 / ceil(max_magnitude_value);
myOutiterator.GoToBegin();
myiterator.GoToBegin();
while (!myOutiterator.IsAtEnd())
{
// Convert vector image to magnitude and scale to use range of png values
const auto arr = myiterator.Get();
const auto magvalue = std::sqrt(arr[0] * arr[0] + arr[1] * arr[1] + arr[2] * arr[2]);
myOutiterator.Set(magvalue * scale_factor);
++myiterator;
++myOutiterator;
}

// Write the result image
using WriterType = itk::ImageFileWriter<OutputImageType>;
auto writer = WriterType::New();
writer->SetFileName(outputFilename);
writer->SetInput(outputImage);
writer->Update();
}


/**
Expand All @@ -45,11 +97,9 @@ itkBSplineScatteredDataPointSetToImageFilterTest5(int argc, char * argv[])
constexpr unsigned int DataDimension = 3;

using RealType = double;
using OutputPixelType = unsigned char;

using VectorType = itk::Vector<RealType, DataDimension>;
using OutputVectorType = itk::Vector<OutputPixelType, DataDimension>;
using ImageType = itk::Image<VectorType, ParametricDimension>;
using OutputImageType = itk::Image<OutputVectorType, ParametricDimension>;

using PointSetType = itk::PointSet<VectorType, ParametricDimension>;

Expand All @@ -65,7 +115,7 @@ itkBSplineScatteredDataPointSetToImageFilterTest5(int argc, char * argv[])
PointSetType::PointType point;
point[0] = (u + 2.0 * itk::Math::pi) / (4.0 * itk::Math::pi);
point[1] = (v + itk::Math::pi) / (2.0 * itk::Math::pi);
unsigned long i = pointSet->GetNumberOfPoints();
const unsigned long i = pointSet->GetNumberOfPoints();
pointSet->SetPoint(i, point);

VectorType V;
Expand Down Expand Up @@ -115,21 +165,11 @@ itkBSplineScatteredDataPointSetToImageFilterTest5(int argc, char * argv[])
close.Fill(1);
filter->SetCloseDimension(close);


ITK_TRY_EXPECT_NO_EXCEPTION(filter->Update());

// Cast the PhiLattice
using CastImageFilterType = itk::CastImageFilter<FilterType::PointDataImageType, OutputImageType>;
auto caster = CastImageFilterType::New();
caster->SetInput(filter->GetPhiLattice());

// Write the PhiLattice
using WriterType = itk::ImageFileWriter<OutputImageType>;
auto writer = WriterType::New();
writer->SetFileName(argv[1]);
writer->SetInput(caster->GetOutput());

ITK_TRY_EXPECT_NO_EXCEPTION(writer->Update());
const std::string outputFilename = argv[1];
auto phiLatticeImage = filter->GetPhiLattice();
ITK_TRY_EXPECT_NO_EXCEPTION(makeTestableScalarImage<FilterType::PointDataImageType>(phiLatticeImage, outputFilename));

return EXIT_SUCCESS;
}
12 changes: 6 additions & 6 deletions Modules/Filtering/ImageIntensity/test/CMakeLists.txt
Expand Up @@ -138,19 +138,19 @@ itk_add_test(NAME itkInvertIntensityImageFilterTest
itk_add_test(NAME itkSymmetricEigenAnalysisImageFilterTestOrderByValue
COMMAND ITKImageIntensityTestDriver
--compare-MD5 ${ITK_TEST_OUTPUT_DIR}/itkSymmetricEigenAnalysisImageFilterTestOrderByValue.png
941cdbc107bea093a564a83ed5993d6d
8e3e4221d36144133d2f6692b2ad980b
--compare-MD5 ${ITK_TEST_OUTPUT_DIR}/itkSymmetricEigenAnalysisFixedDimensionImageFilterTestOrderByValue.png
941cdbc107bea093a564a83ed5993d6d
8e3e4221d36144133d2f6692b2ad980b
itkSymmetricEigenAnalysisImageFilterTest
${ITK_TEST_OUTPUT_DIR}/itkSymmetricEigenAnalysisImageFilterTestOrderByValue.png
1
${ITK_TEST_OUTPUT_DIR}/itkSymmetricEigenAnalysisFixedDimensionImageFilterTestOrderByValue.png)
itk_add_test(NAME itkSymmetricEigenAnalysisImageFilterTestOrderByMagnitude
COMMAND ITKImageIntensityTestDriver
--compare-MD5 ${ITK_TEST_OUTPUT_DIR}/itkSymmetricEigenAnalysisImageFilterTestOrderByMagnitude.png
dbff47eae29f458759de12b3dee95176
8e3e4221d36144133d2f6692b2ad980b
--compare-MD5 ${ITK_TEST_OUTPUT_DIR}/itkSymmetricEigenAnalysisFixedDimensionImageFilterTestOrderByMagnitude.png
dbff47eae29f458759de12b3dee95176
8e3e4221d36144133d2f6692b2ad980b
itkSymmetricEigenAnalysisImageFilterTest
${ITK_TEST_OUTPUT_DIR}/itkSymmetricEigenAnalysisImageFilterTestOrderByMagnitude.png
2
Expand All @@ -160,9 +160,9 @@ itk_add_test(NAME itkSymmetricEigenAnalysisImageFilterTestOrderByMagnitude
itk_add_test(NAME itkSymmetricEigenAnalysisImageFilterTestDoNotOrder
COMMAND ITKImageIntensityTestDriver
--compare-MD5 ${ITK_TEST_OUTPUT_DIR}/itkSymmetricEigenAnalysisImageFilterTestDoNotOrder.png
dbff47eae29f458759de12b3dee95176
8e3e4221d36144133d2f6692b2ad980b
--compare-MD5 ${ITK_TEST_OUTPUT_DIR}/itkSymmetricEigenAnalysisFixedDimensionImageFilterTestDoNotOrder.png
941cdbc107bea093a564a83ed5993d6d
8e3e4221d36144133d2f6692b2ad980b
itkSymmetricEigenAnalysisImageFilterTest
${ITK_TEST_OUTPUT_DIR}/itkSymmetricEigenAnalysisImageFilterTestDoNotOrder.png
3
Expand Down
Expand Up @@ -21,11 +21,62 @@
#include "itkSymmetricSecondRankTensor.h"
#include "itkSymmetricEigenAnalysisImageFilter.h"
#include "itkTestingMacros.h"
#include "itkImageRegionIterator.h"

/**
* Function to convert image of double precison vector pixels to a
* representation of scalar values that are easy to view and test.
* NOTE: Similar function in itkBSplineScatteredDataPointSetToImageFilterTest5.cxx
*/
template <typename InternalImageType>
static void
makeTestableScalarImage(typename InternalImageType::Pointer internalImage, std::string outputFilename)
{ // using OutputPixelType = unsigned char;
using OutputPixelType = uint8_t;
using OutputImageType = itk::Image<OutputPixelType, 3>;

OutputImageType::Pointer outputImage = OutputImageType::New();
outputImage->CopyInformation(internalImage);
outputImage->SetRegions(internalImage->GetBufferedRegion());
outputImage->Allocate(true);

auto myiterator = itk::ImageRegionConstIterator<InternalImageType>(internalImage, internalImage->GetBufferedRegion());
auto myOutiterator = itk::ImageRegionIterator<OutputImageType>(outputImage, outputImage->GetBufferedRegion());

// Convert vector image to magnitude and scale to use range of png values
float max_magnitude_value = 0.0;
while (!myiterator.IsAtEnd())
{
const auto arr = myiterator.Get();
const auto magvalue = std::sqrt(arr[0] * arr[0] + arr[1] * arr[1] + arr[2] * arr[2]);
max_magnitude_value = std::max<float>(max_magnitude_value, magvalue);
++myiterator;
}
const float scale_factor = 255.0 / ceil(max_magnitude_value);
myOutiterator.GoToBegin();
myiterator.GoToBegin();
while (!myOutiterator.IsAtEnd())
{
// Convert vector image to magnitude and scale to use range of png values
const auto arr = myiterator.Get();
const auto magvalue = std::sqrt(arr[0] * arr[0] + arr[1] * arr[1] + arr[2] * arr[2]);
myOutiterator.Set(magvalue * scale_factor);
++myiterator;
++myOutiterator;
}

// Write the result image
using WriterType = itk::ImageFileWriter<OutputImageType>;
auto writer = WriterType::New();
writer->SetFileName(outputFilename);
writer->SetInput(outputImage);
writer->Update();
}

namespace itk
{


template <typename TInputImage, typename TInternalImage, typename TOutputImage>
class SymmetricEigenAnalysisImageFilterHelper : public SymmetricEigenAnalysisImageFilter<TInputImage, TInternalImage>
{
Expand All @@ -39,8 +90,6 @@ class SymmetricEigenAnalysisImageFilterHelper : public SymmetricEigenAnalysisIma

using InputImageType = TInputImage;
using InternalImageType = TInternalImage;
using OutputImageType = TOutputImage;


itkTypeMacro(SymmetricEigenAnalysisImageFilterHelper, SymmetricEigenAnalysisImageFilter);

Expand Down Expand Up @@ -84,9 +133,6 @@ class SymmetricEigenAnalysisImageFilterHelper : public SymmetricEigenAnalysisIma
// Declare Iterator type for the input image
using IteratorType = itk::ImageRegionIteratorWithIndex<InputImageType>;

// Create one iterator for the input image (this is a light object)
IteratorType it(inputImage, inputImage->GetRequestedRegion());

typename InputImageType::PixelType tensorValue;

tensorValue(0, 0) = 19.0;
Expand All @@ -96,16 +142,15 @@ class SymmetricEigenAnalysisImageFilterHelper : public SymmetricEigenAnalysisIma
tensorValue(1, 2) = 37.0;
tensorValue(2, 2) = 39.0;

it.GoToBegin();

// Create one iterator for the input image (this is a light object)
IteratorType it(inputImage, inputImage->GetRequestedRegion());
// Initialize the content of the input image
while (!it.IsAtEnd())
{
it.Set(tensorValue);
++it;
}


// Create the filter
auto filter = SymmetricEigenAnalysisImageFilterType::New();

Expand All @@ -130,32 +175,15 @@ class SymmetricEigenAnalysisImageFilterHelper : public SymmetricEigenAnalysisIma
// Because the object connected to the output may be changed
// by another during GenerateData() call
typename InternalImageType::Pointer internalImage = filter->GetOutput();
ITK_TRY_EXPECT_NO_EXCEPTION(makeTestableScalarImage<InternalImageType>(internalImage, outputFilename));

// Get the output image to a writable format
using CastImageFilterType = itk::CastImageFilter<InternalImageType, OutputImageType>;

auto roundImageFilter = CastImageFilterType::New();

roundImageFilter->SetInput(internalImage);

ITK_TRY_EXPECT_NO_EXCEPTION(roundImageFilter->Update());

// Write the result image
using WriterType = itk::ImageFileWriter<OutputImageType>;

auto writer = WriterType::New();

writer->SetFileName(outputFilename);

writer->SetInput(roundImageFilter->GetOutput());

ITK_TRY_EXPECT_NO_EXCEPTION(writer->Update());

std::cout << "Test succeeded." << std::endl;
return EXIT_SUCCESS;
}
};


template <unsigned int TMatrixDimension, typename TInputImage, typename TInternalImage, typename TOutputImage>
class SymmetricEigenAnalysisFixedDimensionImageFilterHelper
: public SymmetricEigenAnalysisFixedDimensionImageFilter<TMatrixDimension, TInputImage, TInternalImage>
Expand All @@ -167,14 +195,11 @@ class SymmetricEigenAnalysisFixedDimensionImageFilterHelper
using Pointer = SmartPointer<Self>;
using ConstPointer = SmartPointer<const Self>;


using InputImageType = TInputImage;
using InternalImageType = TInternalImage;
using OutputImageType = TOutputImage;


itkTypeMacro(SymmetricEigenAnalysisFixedDimensionImageFilterHelper, SymmetricEigenAnalysisFixedDimensionImageFilter);

itkNewMacro(Self);

static int
Expand Down Expand Up @@ -216,9 +241,6 @@ class SymmetricEigenAnalysisFixedDimensionImageFilterHelper
// Declare Iterator type for the input image
using IteratorType = itk::ImageRegionIteratorWithIndex<InputImageType>;

// Create one iterator for the input image (this is a light object)
IteratorType it(inputImage, inputImage->GetRequestedRegion());

typename InputImageType::PixelType tensorValue;

tensorValue(0, 0) = 19.0;
Expand All @@ -228,8 +250,8 @@ class SymmetricEigenAnalysisFixedDimensionImageFilterHelper
tensorValue(1, 2) = 37.0;
tensorValue(2, 2) = 39.0;

it.GoToBegin();

// Create one iterator for the input image (this is a light object)
IteratorType it(inputImage, inputImage->GetRequestedRegion());
// Initialize the content of the input image
while (!it.IsAtEnd())
{
Expand All @@ -243,9 +265,7 @@ class SymmetricEigenAnalysisFixedDimensionImageFilterHelper

// Set the input image
filter->SetInput(inputImage);

filter->SetFunctor(filter->GetFunctor());

filter->OrderEigenValuesBy(order);

// Execute the filter
Expand All @@ -256,26 +276,7 @@ class SymmetricEigenAnalysisFixedDimensionImageFilterHelper
// Because the object connected to the output may be changed
// by another during GenerateData() call
typename InternalImageType::Pointer internalImage = filter->GetOutput();

// Get the output image to a writable format
using CastImageFilterType = itk::CastImageFilter<InternalImageType, OutputImageType>;

auto roundImageFilter = CastImageFilterType::New();

roundImageFilter->SetInput(internalImage);

ITK_TRY_EXPECT_NO_EXCEPTION(roundImageFilter->Update());

// Write the result image
using WriterType = itk::ImageFileWriter<OutputImageType>;

auto writer = WriterType::New();

writer->SetFileName(outputFilename);

writer->SetInput(roundImageFilter->GetOutput());

ITK_TRY_EXPECT_NO_EXCEPTION(writer->Update());
ITK_TRY_EXPECT_NO_EXCEPTION(makeTestableScalarImage<InternalImageType>(internalImage, outputFilename));

std::cout << "Test succeeded." << std::endl;
return EXIT_SUCCESS;
Expand Down Expand Up @@ -330,7 +331,7 @@ itkSymmetricEigenAnalysisImageFilterTest(int argc, char * argv[])
// Get the input arguments
auto order = static_cast<itk::EigenValueOrderEnum>(std::stoi(argv[2]));

std::string outputFilename = argv[1];
const std::string outputFilename = argv[1];


// Test the filter
Expand All @@ -348,8 +349,8 @@ itkSymmetricEigenAnalysisImageFilterTest(int argc, char * argv[])
using FilterFixedDimensionType =
itk::SymmetricEigenAnalysisFixedDimensionImageFilter<Dimension, InputImageType, InternalImageType>;

auto orderFixedDimension = static_cast<itk::EigenValueOrderEnum>(std::stoi(argv[2]));
std::string outputFilenameFixedDimension = argv[3];
auto orderFixedDimension = static_cast<itk::EigenValueOrderEnum>(std::stoi(argv[2]));
const std::string outputFilenameFixedDimension = argv[3];
// Create an instance to exercise basic object methods
auto filterFixedDimension = FilterFixedDimensionType::New();
ITK_EXERCISE_BASIC_OBJECT_METHODS(
Expand Down

0 comments on commit 1d6b3e9

Please sign in to comment.