diff --git a/Modules/Core/include/mitkSurfaceToImageFilter.h b/Modules/Core/include/mitkSurfaceToImageFilter.h index 2030534e46..b2ff5f83b2 100644 --- a/Modules/Core/include/mitkSurfaceToImageFilter.h +++ b/Modules/Core/include/mitkSurfaceToImageFilter.h @@ -1,98 +1,98 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef _mitkSurfaceToImageFilter_h__ #define _mitkSurfaceToImageFilter_h__ #include "mitkCommon.h" #include "MitkCoreExports.h" #include "mitkImageSource.h" #include "mitkSurface.h" -//#include "mitkImage.h" class vtkPolyData; namespace mitk { -//class Mesh; -//class VectorOfContourLines; - /** * * @brief Converts surface data to pixel data. Requires a surface and an * image, which header information defines the output image. * * The resulting image has the same dimension, size, and Geometry3D * as the input image. The image is cut using a vtkStencil. * The user can decide if he wants to keep the original values or create a * binary image by setting MakeBinaryOutputOn (default is \a false). If * set to \a true all voxels inside the surface are set to one and all * outside voxel are set to zero. * * NOTE: Since the reference input image is passed to the vtkStencil in * any case, the image needs to be initialized with pixel values greater than * the numerical minimum of the used pixel type (e.g. at least -127 for * unsigned char images, etc.) to produce a correct binary image * representation of the surface in MakeOutputBinary mode. * * @ingroup SurfaceFilters * @ingroup Process */ class MITKCORE_EXPORT SurfaceToImageFilter : public ImageSource { public: mitkClassMacro(SurfaceToImageFilter, ImageSource); itkFactorylessNewMacro(Self) itkCloneMacro(Self) itkSetMacro(MakeOutputBinary, bool); itkGetMacro(MakeOutputBinary, bool); itkBooleanMacro(MakeOutputBinary); + itkSetMacro(UShortBinaryPixelType, bool); + itkGetMacro(UShortBinaryPixelType, bool); + itkBooleanMacro(UShortBinaryPixelType); + itkGetConstMacro(BackgroundValue,float); itkSetMacro(BackgroundValue,float); virtual void GenerateInputRequestedRegion() override; virtual void GenerateOutputInformation() override; virtual void GenerateData() override; const mitk::Surface *GetInput(void); using itk::ProcessObject::SetInput; virtual void SetInput(const mitk::Surface *surface); void SetImage(const mitk::Image *source); const mitk::Image *GetImage(void); protected: SurfaceToImageFilter(); virtual ~SurfaceToImageFilter(); void Stencil3DImage(int time = 0); bool m_MakeOutputBinary; + bool m_UShortBinaryPixelType; float m_BackgroundValue; - }; } // namespace mitk #endif /* MITKCOONSPATCHFILTER_H_HEADER_INCLUDED_C10B22CD */ diff --git a/Modules/Core/src/Algorithms/mitkSurfaceToImageFilter.cpp b/Modules/Core/src/Algorithms/mitkSurfaceToImageFilter.cpp index 9a0b10d348..f9c014fcd9 100644 --- a/Modules/Core/src/Algorithms/mitkSurfaceToImageFilter.cpp +++ b/Modules/Core/src/Algorithms/mitkSurfaceToImageFilter.cpp @@ -1,221 +1,235 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkSurfaceToImageFilter.h" -#include "mitkTimeHelper.h" +#include #include "mitkImageWriteAccessor.h" +#include "mitkTimeHelper.h" -#include -#include -#include #include +#include +#include #include -#include -#include -#include -#include -#include -#include #include +#include +#include #include #include -#include -#include mitk::SurfaceToImageFilter::SurfaceToImageFilter() : m_MakeOutputBinary( false ), + m_UShortBinaryPixelType( false ), m_BackgroundValue( -10000 ) { } mitk::SurfaceToImageFilter::~SurfaceToImageFilter() { } void mitk::SurfaceToImageFilter::GenerateInputRequestedRegion() { mitk::Image* output = this->GetOutput(); if((output->IsInitialized()==false) ) return; GenerateTimeInInputRegion(output, const_cast< mitk::Image * > ( this->GetImage() )); } void mitk::SurfaceToImageFilter::GenerateOutputInformation() { mitk::Image *inputImage = (mitk::Image*)this->GetImage(); mitk::Image::Pointer output = this->GetOutput(); itkDebugMacro(<<"GenerateOutputInformation()"); if((inputImage == nullptr) || (inputImage->IsInitialized() == false) || (inputImage->GetTimeGeometry() == nullptr)) return; if (m_MakeOutputBinary) - output->Initialize(mitk::MakeScalarPixelType() , *inputImage->GetTimeGeometry()); + { + if (m_UShortBinaryPixelType) + output->Initialize(mitk::MakeScalarPixelType() , *inputImage->GetTimeGeometry()); + else + output->Initialize(mitk::MakeScalarPixelType() , *inputImage->GetTimeGeometry()); + } else output->Initialize(inputImage->GetPixelType(), *inputImage->GetTimeGeometry()); output->SetPropertyList(inputImage->GetPropertyList()->Clone()); } void mitk::SurfaceToImageFilter::GenerateData() { mitk::Image::ConstPointer inputImage = this->GetImage(); mitk::Image::Pointer output = this->GetOutput(); if(inputImage.IsNull()) return; if(output->IsInitialized()==false ) return; mitk::Image::RegionType outputRegion = output->GetRequestedRegion(); int tstart=outputRegion.GetIndex(3); int tmax=tstart+outputRegion.GetSize(3); if ( tmax > 0) { int t; for(t=tstart;tGetOutput(); mitk::Image::Pointer binaryImage = mitk::Image::New(); unsigned int size = sizeof(unsigned char); if (m_MakeOutputBinary) - binaryImage->Initialize(mitk::MakeScalarPixelType(), *this->GetImage()->GetTimeGeometry(),1,1); + if (m_UShortBinaryPixelType) + { + binaryImage->Initialize(mitk::MakeScalarPixelType(), *this->GetImage()->GetTimeGeometry(),1,1); + size = sizeof(unsigned short); + } + else + binaryImage->Initialize(mitk::MakeScalarPixelType(), *this->GetImage()->GetTimeGeometry(),1,1); else { binaryImage->Initialize(this->GetImage()->GetPixelType(), *this->GetImage()->GetTimeGeometry(),1,1); size = this->GetImage()->GetPixelType().GetSize(); } for (unsigned int i = 0; i < binaryImage->GetDimension(); ++i) size *= binaryImage->GetDimension(i); mitk::ImageWriteAccessor accessor( binaryImage ); memset( accessor.GetData(), 1, size ); const mitk::TimeGeometry *surfaceTimeGeometry = GetInput()->GetTimeGeometry(); const mitk::TimeGeometry *imageTimeGeometry = GetImage()->GetTimeGeometry(); // Convert time step from image time-frame to surface time-frame mitk::TimePointType matchingTimePoint = imageTimeGeometry->TimeStepToTimePoint(time); mitk::TimeStepType surfaceTimeStep = surfaceTimeGeometry->TimePointToTimeStep(matchingTimePoint); vtkPolyData * polydata = ( (mitk::Surface*)GetInput() )->GetVtkPolyData( surfaceTimeStep ); if(polydata) { vtkSmartPointer move = vtkSmartPointer::New(); move->SetInputData(polydata); move->ReleaseDataFlagOn(); vtkSmartPointer transform = vtkSmartPointer::New(); BaseGeometry* geometry = surfaceTimeGeometry->GetGeometryForTimeStep( surfaceTimeStep ); if(!geometry) { geometry = GetInput()->GetGeometry(); } transform->PostMultiply(); transform->Concatenate(geometry->GetVtkTransform()->GetMatrix()); // take image geometry into account. vtk-Image information will be changed to unit spacing and zero origin below. BaseGeometry* imageGeometry = imageTimeGeometry->GetGeometryForTimeStep(time); transform->Concatenate(imageGeometry->GetVtkTransform()->GetLinearInverse()); move->SetTransform(transform); vtkSmartPointer normalsFilter = vtkSmartPointer::New(); normalsFilter->SetFeatureAngle(50); normalsFilter->SetConsistency(1); normalsFilter->SetSplitting(1); normalsFilter->SetFlipNormals(0); normalsFilter->ReleaseDataFlagOn(); normalsFilter->SetInputConnection(move->GetOutputPort()); vtkSmartPointer surfaceConverter = vtkSmartPointer::New(); surfaceConverter->SetTolerance( 0.0 ); surfaceConverter->ReleaseDataFlagOn(); surfaceConverter->SetInputConnection( normalsFilter->GetOutputPort() ); vtkImageData *image = m_MakeOutputBinary ? binaryImage->GetVtkImageData() : const_cast(this->GetImage())->GetVtkImageData(time); + // fill the image with foreground voxels: + unsigned char inval = 1; + vtkIdType count = image->GetNumberOfPoints(); + for (vtkIdType i = 0; i < count; ++i) + { + image->GetPointData()->GetScalars()->SetTuple1(i, inval); + } + // Create stencil and use numerical minimum of pixel type as background value vtkSmartPointer stencil = vtkSmartPointer::New(); stencil->SetInputData(image); stencil->ReverseStencilOff(); stencil->ReleaseDataFlagOn(); stencil->SetStencilConnection(surfaceConverter->GetOutputPort()); stencil->SetBackgroundValue(m_MakeOutputBinary ? 0 : m_BackgroundValue); stencil->Update(); output->SetVolume( stencil->GetOutput()->GetScalarPointer(), time ); MITK_INFO << "stencil ref count: " << stencil->GetReferenceCount() << std::endl; } else { memset( accessor.GetData(), 0, size ); output->SetVolume(accessor.GetData(),time); } } const mitk::Surface *mitk::SurfaceToImageFilter::GetInput(void) { if (this->GetNumberOfInputs() < 1) { return nullptr; } return static_cast ( this->ProcessObject::GetInput(0) ); } void mitk::SurfaceToImageFilter::SetInput(const mitk::Surface *input) { // Process object is not const-correct so the const_cast is required here this->ProcessObject::SetNthInput(0, const_cast< mitk::Surface * >( input ) ); } void mitk::SurfaceToImageFilter::SetImage(const mitk::Image *source) { this->ProcessObject::SetNthInput( 1, const_cast< mitk::Image * >( source ) ); } const mitk::Image *mitk::SurfaceToImageFilter::GetImage(void) { return static_cast< const mitk::Image * >(this->ProcessObject::GetInput(1)); } diff --git a/Modules/Core/test/mitkSurfaceToImageFilterTest.cpp b/Modules/Core/test/mitkSurfaceToImageFilterTest.cpp index 1e406743e8..ea06b8abbc 100644 --- a/Modules/Core/test/mitkSurfaceToImageFilterTest.cpp +++ b/Modules/Core/test/mitkSurfaceToImageFilterTest.cpp @@ -1,172 +1,177 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include #include #include #include #include "mitkSurfaceToImageFilter.h" #include #include "mitkTestFixture.h" class mitkSurfaceToImageFilterTestSuite : public mitk::TestFixture { CPPUNIT_TEST_SUITE(mitkSurfaceToImageFilterTestSuite); MITK_TEST(test3DSurfaceValidOutput); MITK_TEST(test3DSurfaceCorrect); MITK_TEST(test3DSurfaceIn4DImage); CPPUNIT_TEST_SUITE_END(); private: /** Members used inside the different test methods. All members are initialized via setUp().*/ mitk::Surface::Pointer m_Surface; public: /** * @brief Setup Always call this method before each Test-case to ensure correct and new intialization of the used members for a new test case. (If the members are not used in a test, the method does not need to be called). */ void setUp() override { m_Surface = mitk::IOUtil::LoadSurface(GetTestDataFilePath("ball.stl")); } void tearDown() override { } void test3DSurfaceValidOutput() { mitk::SurfaceToImageFilter::Pointer surfaceToImageFilter = mitk::SurfaceToImageFilter::New(); mitk::Image::Pointer additionalInputImage = mitk::Image::New(); additionalInputImage->Initialize( mitk::MakeScalarPixelType(), *m_Surface->GetTimeGeometry()); //Arrange the filter surfaceToImageFilter->MakeOutputBinaryOn(); surfaceToImageFilter->SetInput(m_Surface); surfaceToImageFilter->SetImage(additionalInputImage); surfaceToImageFilter->Update(); CPPUNIT_ASSERT_MESSAGE("SurfaceToImageFilter_AnyInputImageAndModeSetToBinary_ResultIsImageWithUCHARPixelType", surfaceToImageFilter->GetOutput()->GetPixelType().GetComponentType() == itk::ImageIOBase::UCHAR); + + surfaceToImageFilter->SetUShortBinaryPixelType(true); + surfaceToImageFilter->Update(); + + CPPUNIT_ASSERT_MESSAGE("SurfaceToImageFilter_AnyInputImageAndModeSetToBinary_ResultIsImageWithUCHARPixelType", surfaceToImageFilter->GetOutput()->GetPixelType().GetComponentType() == itk::ImageIOBase::USHORT); } void test3DSurfaceCorrect() { mitk::SurfaceToImageFilter::Pointer surfaceToImageFilter = mitk::SurfaceToImageFilter::New(); //todo I don't know if this image is always needed. There is no documentation of the filter. Use git blame and ask the author. mitk::Image::Pointer additionalInputImage = mitk::Image::New(); unsigned int* dims = new unsigned int[3]; dims[0] = 32; dims[1] = 32; dims[2] = 32; additionalInputImage->Initialize( mitk::MakeScalarPixelType(),3,dims); additionalInputImage->SetOrigin(m_Surface->GetGeometry()->GetOrigin()); additionalInputImage->GetGeometry()->SetIndexToWorldTransform(m_Surface->GetGeometry()->GetIndexToWorldTransform()); //Arrange the filter //The docu does not really tell if this is always needed. Could we skip SetImage in any case? surfaceToImageFilter->MakeOutputBinaryOn(); surfaceToImageFilter->SetInput(m_Surface); surfaceToImageFilter->SetImage(additionalInputImage); surfaceToImageFilter->Update(); mitk::ImagePixelReadAccessor outputReader(surfaceToImageFilter->GetOutput()); itk::Index<3> idx; bool valuesCorrect = true; //Values outside the ball should be 0 idx[0] = 0; idx[1] = 0, idx[2] = 0; valuesCorrect = valuesCorrect && (outputReader.GetPixelByIndex(idx) == 0); idx[0] = 0; idx[1] = 15, idx[2] = 15; valuesCorrect = valuesCorrect && (outputReader.GetPixelByIndex(idx) == 0); idx[0] = 15; idx[1] = 15, idx[2] = 0; valuesCorrect = valuesCorrect && (outputReader.GetPixelByIndex(idx) == 0); idx[0] = 15; idx[1] = 0, idx[2] = 15; valuesCorrect = valuesCorrect && (outputReader.GetPixelByIndex(idx) == 0); idx[0] = 5; idx[1] = 9, idx[2] = 23; valuesCorrect = valuesCorrect && (outputReader.GetPixelByIndex(idx) == 0); //Values inside the ball should be 1 idx[0] = 15; idx[1] = 15, idx[2] = 15; valuesCorrect = valuesCorrect && (outputReader.GetPixelByIndex(idx) == 1); idx[0] = 31; idx[1] = 15, idx[2] = 15; valuesCorrect = valuesCorrect && (outputReader.GetPixelByIndex(idx) == 1); idx[0] = 2; idx[1] = 15, idx[2] = 15; valuesCorrect = valuesCorrect && (outputReader.GetPixelByIndex(idx) == 1); idx[0] = 15; idx[1] = 15, idx[2] = 2; valuesCorrect = valuesCorrect && (outputReader.GetPixelByIndex(idx) == 1); idx[0] = 15; idx[1] = 2, idx[2] = 15; valuesCorrect = valuesCorrect && (outputReader.GetPixelByIndex(idx) == 1); idx[0] = 6; idx[1] = 9, idx[2] = 23; valuesCorrect = valuesCorrect && (outputReader.GetPixelByIndex(idx) == 1); CPPUNIT_ASSERT_MESSAGE("SurfaceToImageFilter_BallSurfaceAsInput_OutputCorrect", valuesCorrect == true); } void test3DSurfaceIn4DImage() { mitk::SurfaceToImageFilter::Pointer surfaceToImageFilter = mitk::SurfaceToImageFilter::New(); mitk::Image::Pointer additionalInputImage = mitk::Image::New(); unsigned int* dims = new unsigned int[4]; dims[0] = 32; dims[1] = 32; dims[2] = 32; dims[3] = 2; additionalInputImage->Initialize( mitk::MakeScalarPixelType(),4,dims); additionalInputImage->SetOrigin(m_Surface->GetGeometry()->GetOrigin()); additionalInputImage->GetGeometry()->SetIndexToWorldTransform(m_Surface->GetGeometry()->GetIndexToWorldTransform()); mitk::Image::Pointer secondStep = additionalInputImage->Clone(); unsigned int size = sizeof(unsigned char); for (unsigned int i = 0; i < secondStep->GetDimension(); ++i) size *= secondStep->GetDimension(i); mitk::ImageWriteAccessor accessor( secondStep ); memset( accessor.GetData(), 1, size ); additionalInputImage->GetTimeGeometry()->Expand(2); additionalInputImage->GetGeometry(1)->SetSpacing(secondStep->GetGeometry()->GetSpacing()); additionalInputImage->GetGeometry(1)->SetOrigin(secondStep->GetGeometry()->GetOrigin()); additionalInputImage->GetGeometry(1)->SetIndexToWorldTransform(secondStep->GetGeometry()->GetIndexToWorldTransform()); additionalInputImage->SetImportVolume(secondStep->GetData(),0); additionalInputImage->SetImportVolume(secondStep->GetData(),1); //Arrange the filter surfaceToImageFilter->MakeOutputBinaryOn(); surfaceToImageFilter->SetInput(m_Surface); surfaceToImageFilter->SetImage(additionalInputImage); surfaceToImageFilter->Update(); mitk::ImagePixelReadAccessor outputReader(surfaceToImageFilter->GetOutput()); itk::Index<4> idx; bool valuesCorrect = true; //Values outside the ball should be 0 idx[0] = 0; idx[1] = 0, idx[2] = 0; idx[3] = 0; valuesCorrect = valuesCorrect && (outputReader.GetPixelByIndex(idx) == 0); idx[0] = 0; idx[1] = 15, idx[2] = 15; idx[3] = 0; valuesCorrect = valuesCorrect && (outputReader.GetPixelByIndex(idx) == 0); idx[0] = 15; idx[1] = 15, idx[2] = 0; idx[3] = 0; valuesCorrect = valuesCorrect && (outputReader.GetPixelByIndex(idx) == 0); idx[0] = 15; idx[1] = 0, idx[2] = 15; idx[3] = 0; valuesCorrect = valuesCorrect && (outputReader.GetPixelByIndex(idx) == 0); idx[0] = 5; idx[1] = 9, idx[2] = 23; idx[3] = 0; valuesCorrect = valuesCorrect && (outputReader.GetPixelByIndex(idx) == 0); //Values inside the ball should be 1 hould be 1 idx[0] = 15; idx[1] = 15, idx[2] = 15; idx[3] = 0; valuesCorrect = valuesCorrect && (outputReader.GetPixelByIndex(idx) == 1); idx[0] = 31; idx[1] = 15, idx[2] = 15; idx[3] = 0; valuesCorrect = valuesCorrect && (outputReader.GetPixelByIndex(idx) == 1); idx[0] = 2; idx[1] = 15, idx[2] = 15; idx[3] = 0; valuesCorrect = valuesCorrect && (outputReader.GetPixelByIndex(idx) == 1); idx[0] = 15; idx[1] = 15, idx[2] = 2; idx[3] = 0; valuesCorrect = valuesCorrect && (outputReader.GetPixelByIndex(idx) == 1); idx[0] = 15; idx[1] = 2, idx[2] = 15; idx[3] = 0; valuesCorrect = valuesCorrect && (outputReader.GetPixelByIndex(idx) == 1); idx[0] = 6; idx[1] = 9, idx[2] = 23; idx[3] = 0; valuesCorrect = valuesCorrect && (outputReader.GetPixelByIndex(idx) == 1); //Values inside the ball but in the second timestep hould be 0 idx[0] = 15; idx[1] = 15, idx[2] = 15; idx[3] = 1; valuesCorrect = valuesCorrect && (outputReader.GetPixelByIndex(idx) == 0); idx[0] = 31; idx[1] = 15, idx[2] = 15; idx[3] = 1; valuesCorrect = valuesCorrect && (outputReader.GetPixelByIndex(idx) == 0); idx[0] = 2; idx[1] = 15, idx[2] = 15; idx[3] = 1; valuesCorrect = valuesCorrect && (outputReader.GetPixelByIndex(idx) == 0); idx[0] = 15; idx[1] = 15, idx[2] = 2; idx[3] = 1; valuesCorrect = valuesCorrect && (outputReader.GetPixelByIndex(idx) == 0); idx[0] = 15; idx[1] = 2, idx[2] = 15; idx[3] = 1; valuesCorrect = valuesCorrect && (outputReader.GetPixelByIndex(idx) == 0); idx[0] = 6; idx[1] = 9, idx[2] = 23; idx[3] = 1; valuesCorrect = valuesCorrect && (outputReader.GetPixelByIndex(idx) == 0); CPPUNIT_ASSERT_MESSAGE("SurfaceToImageFilter_BallSurfaceAsInput_Output4DCorrect", valuesCorrect == true); } }; MITK_TEST_SUITE_REGISTRATION(mitkSurfaceToImageFilter)