I’m having facing a problem that possibly stems for a misunderstanding of how pipelines work and get updated. I have the following piece of code,
labelfile = labelfilelist[0]
labelpath = os.path.join(label_path, labelfile)
MeshType = itk.Mesh[itk.SS,3]
meshReader = itk.MeshFileReader[MeshType].New()
meshReader.SetFileName(labelpath)
pad_filter = itk.ConstantPadImageFilter[ImageType, ImageType].New()
pad_filter.SetInput(reader.GetOutput())
pad_filter.SetPadLowerBound((1,1,1))
pad_filter.SetPadUpperBound((1,1,1))
mesh_to_image_filter = itk.TriangleMeshToBinaryImageFilter[MeshType, ImageType].New()
mesh_to_image_filter.SetInput(meshReader.GetOutput())
mesh_to_image_filter.SetInfoImage(pad_filter.GetOutput())
crop_filter = itk.CropImageFilter[ImageType, ImageType].New()
crop_filter.SetInput(mesh_to_image_filter.GetOutput())
crop_filter.SetUpperBoundaryCropSize((1,1,1))
crop_filter.SetLowerBoundaryCropSize((1,1,1))
crop_filter.Update()
cropped_image = crop_filter.GetOutput()
image = itk.GetArrayViewFromImage(cropped_image).astype(np.short)
itk_image = itk.GetImageFromArray(image)
itk_image.SetMetaDataDictionary(reader.GetMetaDataDictionary())
header = itk.ChangeInformationImageFilter[ImageType].New()
header.SetInput(itk_image)
header.SetOutputSpacing(reader.GetOutput().GetSpacing())
header.ChangeSpacingOn()
header.SetOutputOrigin(reader.GetOutput().GetOrigin())
header.ChangeOriginOn()
header.SetOutputDirection(reader.GetOutput().GetDirection())
header.ChangeDirectionOn()
header.UpdateOutputInformation()
writer = itk.ImageFileWriter[ImageType].New()
writer.SetInput(header.GetOutput())
segmentation_name = os.path.join(output_label, series_name + '_' +ann+ '.nii.gz')
writer.SetFileName(segmentation_name)
writer.Update()
which gives me an error:
Traceback (most recent call last):
File "dicom_to_nifti.py", line 276, in <module>
separate_labels(spec_data_dar, spec_annotation_directory, output, case_number, phase)
File "dicom_to_nifti.py", line 127, in separate_labels
crop_filter.Update()
RuntimeError: /work/ITK-source/ITK/Modules/Filtering/ImageGrid/include/itkCropImageFilter.hxx:76:
itk::ERROR: itk::ERROR: CropImageFilter(0x55577c544990): The input image's size [0, 0, 0] is less than the total of the crop size!
If I Update()
the mesh_to_image_filter
before passing its output to the crop_filter
, it does not complain anymore, but I don’t understand why I have to in the first place.
dzenanz
(Dženan Zukić)
November 19, 2021, 2:32pm
2
pad_filter
needs to be updated, in order to have the output image’s metadata (size, start index etc) properly computed. As mesh_to_image_filter
depends on it, it calls the update in its update method.
I guess that crop_filter
tries to calculate image metadata before ensuring its input is up to date, thus causing the problem.
I guess this is where the error originates from:
template <typename TInputImage, typename TOutputImage>
void
CropImageFilter<TInputImage, TOutputImage>::VerifyInputInformation() ITKv5_CONST
{
Superclass::VerifyInputInformation();
const TInputImage * inputPtr = this->GetInput();
InputImageSizeType input_sz = inputPtr->GetLargestPossibleRegion().GetSize();
for (unsigned int i = 0; i < InputImageDimension; ++i)
{
if (input_sz[i] < (m_UpperBoundaryCropSize[i] + m_LowerBoundaryCropSize[i]))
{
itkExceptionMacro("The input image's size " << input_sz << " is less than the total of the crop size!");
}
}
}
Wouldn’t it be a bug if I have to explicitly call Update
?
Also, as you see, I’m padding and then cropping by 1 pixel. This is so that I can bypass a bug in TriangleMeshToBinaryImageFilter
. Again, I would expect that in reality the padding and cropping would not affect the result of TriangleMeshToBinaryImageFilter
, but as you can see in the picture, the output seems shifted (red being the one where I cropped and padded, and green without cropping and padding).
dzenanz
(Dženan Zukić)
November 19, 2021, 2:51pm
4
VerifyInputInformation
isn’t supposed to call Update. It might/should call inputPtr->UpdateOutputInformation();
. Does this fix it?
TriangleMeshToBinaryImageFilter
might assume that LargestPossibleRegion starts with index 0. In this case it is wrong. Can you check that this is the problem?
Sorry, I’m not compiling ITK, I’m just using the Python version. I was looking at the code on Github.
It does not seem to ignore the starting index:
/** Update */
template <typename TInputMesh, typename TOutputImage>
void
TriangleMeshToBinaryImageFilter<TInputMesh, TOutputImage>::GenerateData()
{
itkDebugMacro(<< "TriangleMeshToBinaryImageFilter::Update() called");
// Get the input and output pointers
OutputImagePointer OutputImage = this->GetOutput();
if (m_InfoImage == nullptr)
{
if (m_Size[0] == 0 || m_Size[1] == 0 || m_Size[2] == 0)
{
itkExceptionMacro(<< "Must Set Image Size");
}
typename OutputImageType::RegionType region;
region.SetSize(m_Size);
region.SetIndex(m_Index);
OutputImage->SetLargestPossibleRegion(region); //
OutputImage->SetBufferedRegion(region); // set the region
OutputImage->SetRequestedRegion(region); //
OutputImage->SetSpacing(m_Spacing); // set spacing
OutputImage->SetOrigin(m_Origin); // and origin
OutputImage->SetDirection(m_Direction); // direction cosines
}
else
{
itkDebugMacro(<< "Using info image");
m_InfoImage->Update();
OutputImage->CopyInformation(m_InfoImage);
OutputImage->SetRegions(m_InfoImage->GetLargestPossibleRegion());
m_Size = m_InfoImage->GetLargestPossibleRegion().GetSize();
m_Index = m_InfoImage->GetLargestPossibleRegion().GetIndex();
m_Spacing = m_InfoImage->GetSpacing();
m_Origin = m_InfoImage->GetOrigin();
m_Direction = m_InfoImage->GetDirection();
}
dzenanz
(Dženan Zukić)
November 19, 2021, 3:41pm
6
Can you submit these as two separate bugs on the issue tracker ?
ITKCuberille is kind of a higher-quality TriangleMeshToBinaryImageFilter
. That might alleviate your immediate issues.
Thanks I’ll have a look.
Here are the issues I created:
opened 04:07PM - 19 Nov 21 UTC
type:Bug
### Description
Due to a [bug](https://github.com/InsightSoftwareConsortium/I… TK/issues/2416) with `TriangleMeshToBinaryImageFilter` where it can get corrupt near the edges, I tried padding the image and then cropping it by 1 pixel border. Unfortunately, that shifts the final results as you can see here:
<img width="410" alt="image" src="https://user-images.githubusercontent.com/91547393/142653607-b15aa58e-bda1-48a3-8c6f-14b59e998296.png">
Green is with padding/cropping, and white is without.
### Steps to Reproduce
Run the following code
```python
t_mesh_filter.py
import itk
if __name__ == '__main__':
should_pad_and_crop = True
TCoordinate = itk.D
Dimension = 3
TMesh = itk.Mesh[TCoordinate, Dimension].New()
sphere = itk.RegularSphereMeshSource[TMesh].New()
sphere.SetResolution(4)
sphere_mesh = sphere.GetOutput()
mesh_writer = itk.MeshFileWriter[TMesh].New()
mesh_writer.SetFileName( "sphere.vtk" )
mesh_writer.SetInput(sphere_mesh)
mesh_writer.Update()
TMesh = itk.Mesh[itk.SS, Dimension].New()
mesh_reader = itk.MeshFileReader[TMesh].New()
mesh_reader.SetFileName("sphere.vtk")
mesh_reader.Update()
sphere_mesh = mesh_reader.GetOutput()
TPixel = itk.SS
TImage = itk.Image[TPixel, Dimension]
image = itk.Image[TPixel, Dimension].New()
region = itk.ImageRegion[Dimension]()
region.SetSize([128, 128, 128])
region.SetIndex([0, 0, 0])
image.SetRegions(region)
image.Allocate()
image.SetOrigin([-1.28, -1.28, -1.28])
image.SetSpacing([0.02, 0.02, 0.02])
if should_pad_and_crop:
pad_filter = itk.ConstantPadImageFilter[TImage, TImage].New()
pad_filter.SetInput(image)
pad_filter.SetPadLowerBound((1,1,1))
pad_filter.SetPadUpperBound((1,1,1))
pad_filter.Update()
mesh_to_image_filter = itk.TriangleMeshToBinaryImageFilter[TMesh, TImage].New()
mesh_to_image_filter.SetInput(sphere_mesh)
mesh_to_image_filter.SetInfoImage(pad_filter.GetOutput())
mesh_to_image_filter.Update()
crop_filter = itk.CropImageFilter[TImage, TImage].New()
crop_filter.SetInput(mesh_to_image_filter.GetOutput())
crop_filter.SetUpperBoundaryCropSize((1,1,1))
crop_filter.SetLowerBoundaryCropSize((1,1,1))
crop_filter.Update()
itk.imwrite(crop_filter.GetOutput(), "sphere.nii.gz")
else:
mesh_to_image_filter = itk.TriangleMeshToBinaryImageFilter[TMesh, TImage].New()
mesh_to_image_filter.SetInput(sphere_mesh)
mesh_to_image_filter.SetInfoImage(image)
mesh_to_image_filter.Update()
itk.imwrite(mesh_to_image_filter.GetOutput(), "sphere.nii.gz")
```
### Expected behavior
Cropping and padding should not affect the output.
### Versions
Python ITK '5.2.1'
### Environment
Linux, Python 3.8.10
opened 04:12PM - 19 Nov 21 UTC
type:Bug
### Description
When passing a filter output to `CropImageFilter` I get the … following error if I don't update the filter first.
```
Traceback (most recent call last):
File "test_mesh_filter.py", line 52, in <module>
RuntimeError: /work/ITK-source/ITK/Modules/Filtering/ImageGrid/include/itkCropImageFilter.hxx:76:
ITK ERROR: CropImageFilter(0x55a7ea50ac80): The input image's size [0, 0, 0] is less than the total of the crop size!
```
### Steps to Reproduce
Run the following:
```python
import itk
if __name__ == '__main__':
should_pad_and_crop = True
TCoordinate = itk.D
Dimension = 3
TMesh = itk.Mesh[TCoordinate, Dimension].New()
sphere = itk.RegularSphereMeshSource[TMesh].New()
sphere.SetResolution(4)
sphere_mesh = sphere.GetOutput()
mesh_writer = itk.MeshFileWriter[TMesh].New()
mesh_writer.SetFileName( "sphere.vtk" )
mesh_writer.SetInput(sphere_mesh)
mesh_writer.Update()
TMesh = itk.Mesh[itk.SS, Dimension].New()
mesh_reader = itk.MeshFileReader[TMesh].New()
mesh_reader.SetFileName("sphere.vtk")
mesh_reader.Update()
sphere_mesh = mesh_reader.GetOutput()
TPixel = itk.SS
TImage = itk.Image[TPixel, Dimension]
image = itk.Image[TPixel, Dimension].New()
region = itk.ImageRegion[Dimension]()
region.SetSize([128, 128, 128])
region.SetIndex([0, 0, 0])
image.SetRegions(region)
image.Allocate()
image.SetOrigin([-1.28, -1.28, -1.28])
image.SetSpacing([0.02, 0.02, 0.02])
if should_pad_and_crop:
pad_filter = itk.ConstantPadImageFilter[TImage, TImage].New()
pad_filter.SetInput(image)
pad_filter.SetPadLowerBound((1,1,1))
pad_filter.SetPadUpperBound((1,1,1))
mesh_to_image_filter = itk.TriangleMeshToBinaryImageFilter[TMesh, TImage].New()
mesh_to_image_filter.SetInput(sphere_mesh)
mesh_to_image_filter.SetInfoImage(pad_filter.GetOutput())
#mesh_to_image_filter.Update()
crop_filter = itk.CropImageFilter[TImage, TImage].New()
crop_filter.SetInput(mesh_to_image_filter.GetOutput())
crop_filter.SetUpperBoundaryCropSize((1,1,1))
crop_filter.SetLowerBoundaryCropSize((1,1,1))
crop_filter.Update()
itk.imwrite(crop_filter.GetOutput(), "sphere.nii.gz")
else:
mesh_to_image_filter = itk.TriangleMeshToBinaryImageFilter[TMesh, TImage].New()
mesh_to_image_filter.SetInput(sphere_mesh)
mesh_to_image_filter.SetInfoImage(image)
mesh_to_image_filter.Update()
itk.imwrite(mesh_to_image_filter.GetOutput(), "sphere.nii.gz")
```
If the `Update` is uncommented it works fine.
### Expected behavior
I should not need to call `Update` on the filter before passing it as input to `CropImageFilter`.
### Versions
Python ITK '5.2.1'
### Environment
Linux Python 3.8.10
1 Like
@dzenanz From my understanding, ITKCuberille does the inverse of what I want to do, i.e. it is a binary image / implicit surface to mesh filter.
Are there any alternatives or am is e.g. VTK my best alternative?
dzenanz
(Dženan Zukić)
December 1, 2021, 3:38pm
9
This is the only filter in ITK. VTK has vtkPolyDataToImageStencil . There might be some other implementations, but I think the one in VTK is quite mature and heavily used.