Contouring with having initial curve using filters in Simple ITK module?

Hello Dear Developers and Users

Consider the following figure. This screenshot contains a 2D image of EPID, named EPID.

Consider the following figure. This screenshot contains the contour of the pelvic bone extracted from the DRR image named contourDRR, using filters such as ClosingByReconstructionImageFilter, OpeningByReconstructionImageFilter and ZeroCrossingBasedEdgeDetectionImageFilter .

I would like to consider this contour (contourDRR) as a starting point (initial contour) and using Active Contour method, (e.g., the filter of geodesicActiveContourLevelSetImageFilter in Simple ITK module) it is deformable to match the edge of the pelvis in the EPID image (EPID).
In other words, I want to use active contour for matching this initial contour (contourDRR) on the edges of pelvis in EPID.

In other words, How can I use a binary image containing the contour of an organ (such as contourDRR) as the initial curve using Simple ITK filters, based on the active contour technique, to extract the contour in a gray scale image (EPID)? As mentioned above, I have two images contourDRR (binary images) and EPID (graycale image) which are geometrically matched with ‌each other. I used the filter of geodesicActiveContourLevelSetImageFilter to deform this initial contour so that the result of this deformation (for example using this filter) is a contour that matches the edge of the pelvis in the EPID image.

Unfortunately, after using this filter, I took an output image that had all the pixels equal to one.

How can I do this contouring with initial contour?
Please guide me to do it.
Best regards.

Shahrokh

Hello @shahrokh_nasseri,

Based on the documentation, the GeodesicActiveContourLevelSetImageFilter expects two inputs:

  1. An initial level set, image which contains the initial contour/surface as the zero level set. In your case the contourDRR is the surface and you just need to convert this binary representation to a level set, readily done via SignedMaurerDistanceMapImageFilter.
  2. Edge potential map computed from the EPID image. Please see the documentation pointed to above for specifics (you’ll be using the GradientMagnitudeImageFilter and ExpImageFilter here).

Hello Dear Ziv Yaniv

Thanks a lot for your guidance.
As you mentioned I used a filter with the name of SignedMaurerDistanceMapImageFilter.
In doing so, I got the error messages that I fixed by doing the following steps.
Also I must mention that I did these steps using ITK Simple Filters module in 3DSlicer.
I must first mention to the specifications of the my data (EPID and contourDRR)

EPID Specification:
Image Dimensions: 611 576 1
Image Spacing: 0.25mm 0.25mm 1.00mm
Scalar Type: double

contourDRR Specification:
Image Dimensions: 270 256 1
Image Spacing: 0.5859mm 0.5859mm 1.00mm

Steps (1-7):

Step #1
Resampling EPID from (611,576, 1) to (611,576, 4)
Module: Resample Scalar/Vector/DWI Volume
Input Volume: EPID
Output Volume: EPID3D
Reference Volume: None
Spacing: 0.25,0.25,0.15
size: 611,576,4

Step #2
Matching Geometery EPID3D and contourDRR
Module: Resample Scalar/Vector/DWI Volume
Input Volume: contourDRR
Output Volume: contourDRRMatched
Reference Volume: EPID3D
Spacing: 0.25,0.25,0.15
size: 611,576,4

Step #3
Rounding contourDRRMatched
Moudule: ITK Simple Filters
Filter: RoundImageFilter
Input Volume: contourDRRMatched
Output Volume: contourDRRMatchedRounded

Step #4
Binary contourDRRMatchedRounded
Moudule: ITK Simple Filters
Filter: CastImageFilter
Input Volume: contourDRRMatchedRounded
Output Pixel Type: uint8_t
Output Volume: contourDRRMatchedRoundedBinary

Step #5
Convert binary representation to a level set
Moudule: ITK Simple Filters
Filter: SignedMaurerDistanceMapImageFilter
Input Volume: contourDRRMatchedRoundedBinary
Output Volume: contourDRRMatchedRoundedBinarySignedMaurerDistanceMapImage

Step #6:
Convert double to float EPID3D
Moudule: Simple Filters
Filter: CastImageFilter
Input Volume: EPID3D
Output Pixel Type: float
Output Volume: EPID3DFloat

Step #7:
Deform contourDRR
Moudule: Simple Filters
Filter: geodesicActiveContourLevelSetImageFilter
Input Volume: EPID3D
Input Volume: contourDRRMatchedRoundedBinarySignedMaurerDistanceMapImage
Note: I did not change parameters and used default parameters
Output Volume: GeodesicActiveContourLevelSetImageFilter Output

After doing these steps, I get the volume node of GeodesicActiveContourLevelSetImageFilter Output that all its pixels are equal to the value of 0.6.
I expected to have a contour deformed after doing these steps that matches the edge of the pelvic bone in the EPID image.

Please guide me.
Best regards.
Shahrokh

Hello @shahrokh_nasseri,

This workflow does not make sense to me, artificially forcing 2D images to be 3D. The inputs are two 2D images, EPID and contour. They just have different sizes and spacings but I assume they occupy the same physical space (origin, direction cosine matrix and size*spacing is the same for both). Given this assumption, I would expect that you:

  1. Resample the contour image using nearest neighbor interpolator onto the EPID image (identity transformation).
  2. Convert binary representation obtained from step 1 into a distance map.
  3. Compute edge potential map from EPID image.
  4. Use outputs from step 2 and 3 as inputs for the GeodesicActiveContourLevelSetImageFilter.

Always check that the output from each step is what you expect it to be.

1 Like

Hello Dear Ziv Yaniv

First of all, I would like to thank you for your guidance and support. Unfortunately, I do not get the desired result. I tried to do the steps you mentioned in your last message.
First of all, I will mention to the specifications of the images (EPID and contourDRR):

EPID Information:
Image Dimensions: 611 576 1
Image Spacing: 0.25mm 0.25mm 1.00mm
ContourDRR Information:
Image Dimensions: 270 256 1
Image Spacing: 0.5859mm 0.5859mm 1.0000mm

These steps are as follows:
You mentioned doing to: “1. Resample the contour image using nearest neighbor interpolator onto the EPID image (identity transformation)”. I did it as following:
Module: Resample Scalar/Vector/DWI Volume
Input Volume: contourDRR (Scalar Type: double)
Output Volume: contourDRRMatched (Scalar Type: double)
Reference Volume (To Set Output Parameters): EPID

You mentioned doing to: “2. Convert binary representation obtained from step 1 into a distance map”.
I should point out that the output of the previous step (contourDRRMatched) was not binary representation (Scalar Type: double). So I had to use two filters called RoundImageFilter and CastImageFilter as following:
Module: ITK Simple Filter
Filter: RoundImageFilter
Input Volume: contourDRRMatched
Output Volume: contourDRRMatchedRounded (Scalar Type: double)

Module: ITK Simple Filter
Filter: CastImageFilter
Input Volume: contourDRRMatchedRounded
Output Pixel Type: uint8_t
Output Volume: contourDRRMatchedRoundedCasted (Scalar Type: unsigned Char)

I should mention that I had to perform the above steps due to getting some errors related to the data type in calculating of distance map.
I assumed contourDRRMatchedRoundedCasted to be the same binary representation you mentioned. Is it true?

In continue…
After doing it, I want to calculate distance map. For it, I saw the following list of filters related to distance map:
ApproximateSignedDistanceMapImageFilter,
DanielssonDistanceMapImageFilter,
IsoContourDistanceImageFilter,
SignedDanielssonDistanceMapImageFilter,
SignedMaurerDistanceMapImageFilter

I could only use the SignedMaurerDistanceMapImageFilter as following:
Module: ITK Simple Filter
Filter: SignedMaurerDistanceMapImageFilter
Input Volume: contourDRRMatchedRounded
Inside Is Positive: Not ticked (default)
Squared Distance: Not ticked (default)
Use Image Spacing: Not ticked (default)
Output Volume: contourDRRMatchedRoundedCasted

You mentioned doing to: “3. Compute edge potential map from EPID image”.
For doing it, I get some errors. I refer to the web page of this filter in ITK Doxygen. I think that I must get the gradient image using the filter of gradientImageFilter as following:
Module: ITK Simple Filter
Filter: gradientImageFilter
Input Volume: EPID
Output Volume: EPIDGradient

Then:
Module: ITK Simple Filter
Filter: EdgePotentialImageFilter
Input Volume: EPIDGradient
Output Volume: EPIDGradientEdgePotential

At finally, You mentioned doing to: “4. Use outputs from step 2 and 3 as inputs for the GeodesicActiveContourLevelSetImageFilter ”.
Module: ITK Simple Filter
Filter: GeodesicActiveContourLevelSetImageFilter
Input Volume: contourDRRMatchedRoundedCastedSignedMaurerDistanceMap
Input Volume: EPIDGradientEdgePotential
Output Volume: ResultGeodesicActiveContourLevelSet

Unfortunately, I get the following error message:

Exception thrown in SimpleITK GeodesicActiveContourLevelSetImageFilter_Execute: /work/Preview/Slicer-0-build/SimpleITK/Code/Common/include/sitkProcessObject.h:354:
sitk::ERROR: Failure to convert SimpleITK image of dimension: 3 and pixel type: “64-bit float” to ITK image of dimension: 3 and pixel type: “32-bit float”!

Please guide me yo solve it.
Best regards.
Shahrokh

Hello @shahrokh_nasseri,

Can you share the code and possibly the contour and EPID images? Will make it much easier to help you. It is hard to follow the textual description and involves some guessing with respect to what is actually being done.

Have you tried CastImageFilter to convert EPIDGradientEdgePotential from double to float?

Also, I would check “use image spacing” in SignedMaurerDistanceMapImageFilter.

Dear Dženan Zukić and Dear Ziv Yaniv

I sincerely thank you for your support and guidance.
I apologize for the delay in sending the images and codes. I send the shared links the following files using Dropbox:

EPID.nrrd
DRR.nrrd
contourDRR.nrrd

At firstly, I should point out that the contourDRR.nrrd file contains the edge of pelvic in the DRR.nrrd file. That’s why I send the shared link DRR.nrrd file.

I have to emphasize that I’m looking for a way (any filter in SimpleITK) to extract the pelvic edge in the EPID image by deforming the edge in the contourDRR image, for example using Active Contour Filters in SimpleITK.

Ziv Yaniv had told me to do the following steps:
1- Resample the contour image using nearest neighbor interpolator onto the EPID image (identity transformation).
2- Convert binary representation obtained from step 1 into a distance map.
3- Compute edge potential map from EPID image.
4- Use outputs from step 2 and 3 as inputs for the GeodesicActiveContourLevelSetImageFilter .

I tried to do these steps as Ziv Yaniv pointed out as following:

Step 1: Resample the contour image using nearest neighbor interpolator onto the EPID image (identity transformation)

#Modules
import SimpleITK as sitk
from SimpleITK import ResampleImageFilter

#Read EPID and contourDRR images in nrrd format
EPID = sitk.ReadImage('EPID.nrrd')
contourDRR = sitk.ReadImage('contourDRR.nrrd')

#Get origin coordinates
originEPID = EPID.GetOrigin()

#Get size (Matrix Size.
matrixSizeEPID = EPID.GetSize()

#Get image spacing ((0.25, 0.25, 1.0)
spacingSizeEPID = EPID.GetSpacing()

#Create and initialize the filter of ResampleImageFilter
myFilterResampleImage = ResampleImageFilter()

#Set Interpolator to NearestNeighbor
myFilterResampleImage.SetInterpolator(sitk.sitkNearestNeighbor)

#Set Origin coordinates according with the origin of EPID.
myFilterResampleImage.SetOutputOrigin((-1*originEPID[0], -1*originEPID[1], originEPID[2]))

#Set spacing according with the spacing of EPID.
myFilterResampleImage.SetOutputSpacing(spacingSizeEPID)

#Set matrix size according with the matrix size of EPID.
myFilterResampleImage.SetSize(matrixSizeEPID)

#Set the pixel value
myFilterResampleImage.SetDefaultPixelValue(0.0)

#Set Pixel Type
myFilterResampleImage.SetOutputPixelType(sitk.sitkInt8)

#Set Transform (Identity Transorm)
transform = sitk.Transform()
transform.SetIdentity()
myFilterResampleImage.SetTransform(transform)

#Execute the filter (ResampleImageFilter) on the input image (contourDRR)
contourDRRMatched = myFilterResampleImage.Execute(contourDRR)

#Save Image (contourDRRMatched) in nrrd format
writerResampleImageFilter = sitk.ImageFileWriter()
writerResampleImageFilter.SetFileName('contourDRRMatched.nrrd')
writerResampleImageFilter.Execute(contourDRRMatched)

Step 2: Convert binary representation obtained from step 1 into a distance map.

#Module
import SimpleITK as sitk
from SimpleITK import SignedMaurerDistanceMapImageFilter

#Read contourDRRMatched image in nrrd format
contourDRRMatched = sitk.ReadImage('contourDRRMatched.nrrd')

#Create the filter of SignedMaurerDistanceMapImageFilter
myFilterSignedMaurerDistanceMap = SignedMaurerDistanceMapImageFilter()

#Set Background Value
myFilterSignedMaurerDistanceMap.SetBackgroundValue(0.0)

#Set if the inside represents positive values in the signed distance map.
myFilterSignedMaurerDistanceMap.SetInsideIsPositive(False)

#Set if the distance should be squared
myFilterSignedMaurerDistanceMap.SetSquaredDistance(True)

#Set if image spacing should be used in computing distances
myFilterSignedMaurerDistanceMap.SetUseImageSpacing(False)

#Execute the filter (SignedMaurerDistanceMapImageFilter) on the input image (contourDRR)
contourDRRMatchedDistanceMap = myFilterSignedMaurerDistanceMap.Execute(contourDRRMatched)

#Save Image (contourDRRMatchedDistanceMap) in nrrd format
writerSignedMaurerDistanceMapImageFilter = sitk.ImageFileWriter()
writerSignedMaurerDistanceMapImageFilter.SetFileName('contourDRRMatchedSignedMaurerDistanceMap.nrrd')
writerSignedMaurerDistanceMapImageFilter.Execute(contourDRRMatchedDistanceMap)

Step 3: Compute edge potential map from EPID image.

It should be noted that the potential edge map of the EPID image cannot be calculated directly through the EdgePotentialImageFilter filter. I get the following error message:

#Module
import SimpleITK as sitk
from SimpleITK import EdgePotentialImageFilter

#Read Graient EPID image in nrrd format
EPID = sitk.ReadImage('EPID.nrrd')

#Create the filter of EdgePotentialImageFilter
myFilterEdgePotential = EdgePotentialImageFilter()

#Execute the filter (EdgePotentialImageFilter) on the input image (EPID)
EPIDGradientEdgePotential = myFilterEdgePotential.Execute(EPID)
...
RuntimeError: Exception thrown in SimpleITK EdgePotentialImageFilter_Execute: /tmp/SimpleITK/Code/Common/include/sitkMemberFunctionFactory.hxx:158:

sitk::ERROR: Pixel type: 64-bit float is not supported in 3D by N3itk6simple24EdgePotentialImageFilterE.

For solving it, I calculated the gradient of EPID image with the following code:

#Module
import SimpleITK as sitk
from SimpleITK import GradientImageFilter

#Read EPID image in nrrd format
EPID = sitk.ReadImage('EPID.nrrd')

#Create the filter of GradientImageFilter
myFilterGradient = GradientImageFilter()

#UseImageDirection flag
myFilterGradient.SetUseImageDirection(False)

#SetUseImageSpacing flag
myFilterGradient.SetUseImageSpacing(True)

#Execute the filter (GradientImageFilter) on the input image (EPID)
EPIDGradient = myFilterGradient.Execute(EPID)

#Save Image (EPIDGradient) in nrrd format
writerGradientImageFilter = sitk.ImageFileWriter()
writerGradientImageFilter.SetFileName('EPIDGradient.nrrd')
writerGradientImageFilter.Execute(EPIDGradient)

At now, I can use EdgePotentialImageFilter as following:

#Module
import SimpleITK as sitk
from SimpleITK import EdgePotentialImageFilter

#Read Graient EPID image in nrrd format
EPIDGradient = sitk.ReadImage('EPIDGradient.nrrd')

#Create the filter of EdgePotentialImageFilter
myFilterEdgePotential = EdgePotentialImageFilter()

#Execute the filter (EdgePotentialImageFilter) on the input image (EPIDGradient)
EPIDGradientEdgePotential = myFilterEdgePotential.Execute(EPIDGradient)

#Save Image (EPIDGradientEdgePotential) in nrrd format
writerEdgePotentialImageFilter = sitk.ImageFileWriter()
writerEdgePotentialImageFilter.SetFileName('EPIDGradientEdgePotential.nrrd')
writerEdgePotentialImageFilter.Execute(EPIDGradientEdgePotential)

Step 4: Use outputs from step 2 and 3 as inputs for the GeodesicActiveContourLevelSetImageFilter .

At the final step, I want to use the filter to deform the edges in the image ` DRRMatched.

I have to emphasize that I expected one of the two inputs of this
filter to be the image DRRMatched, if this is not the case.

Anyway, I execute the following code.

#Modules
import SimpleITK as sitk
from SimpleITK import GeodesicActiveContourLevelSetImageFilter

#Read EPID and contourDRR images in nrrd format
input1 = sitk.ReadImage('contourDRRMatchedSignedMaurerDistanceMap.nrrd')
input2 = sitk.ReadImage('EPIDGradientEdgePotential.nrrd')

#Create the filter of GeodesicActiveContourLevelSetImageFilter
myFilterGeodesicActiveContourLevelSet = GeodesicActiveContourLevelSetImageFilter()
myFilterGeodesicActiveContourLevelSet.SetAdvectionScaling(1.0)
myFilterGeodesicActiveContourLevelSet.SetCurvatureScaling(1.0)
myFilterGeodesicActiveContourLevelSet.SetDebug(False)
myFilterGeodesicActiveContourLevelSet.SetMaximumRMSError(0.01)
myFilterGeodesicActiveContourLevelSet.SetNumberOfIterations(1000)
myFilterGeodesicActiveContourLevelSet.SetNumberOfThreads(8)
myFilterGeodesicActiveContourLevelSet.SetNumberOfWorkUnits(0)
myFilterGeodesicActiveContourLevelSet.SetPropagationScaling(1.0)
myFilterGeodesicActiveContourLevelSet.SetReverseExpansionDirection(False)

#Execute the filter (GeodesicActiveContourLevelSetImageFilter) on the input images (input1, input2)
contourDEformed = myFilterGeodesicActiveContourLevelSet.Execute(input1, input2)

I get the following error message:
Error Message:

RuntimeError:
Exception thrown in SimpleITK
GeodesicActiveContourLevelSetImageFilter_Execute:
/tmp/SimpleITK/Code/Common/include/sitkProcessObject.h:354:

sitk::ERROR:
Failure to convert SimpleITK image of dimension: 3 and pixel type:
"64-bit float" to ITK image of dimension: 3 and pixel type:
"32-bit float"!

I think my pipeline is wrong. As I mentioned above, I want to get the
bone contour in the EPID image by Active Contour method by deforming
the contourDRRMatched contour.

Please guide me.
Best regards.
Shahrokh

Hello @shahrokh_nasseri,

The error is telling us that the expected image should have a pixel type of sitk.sitkFloat32 and it receives an image of type sitk.sitkFloat64. Conversion can be done via the CastImageFilter.

Having looked at the details of the GeodesicActiveContourLevelSetImageFilter., not sure if it is only meant to be used with closed contours or if it will work with open ones too.

Just to help you along, the code can be greatly simplified:

import SimpleITK as sitk

def overlay_zerolevelset(image, levelset, opacity=0.5, title=''):
    spacing = image.GetSpacing()[0]
    label_image = levelset> -spacing and levelset<spacing
    contour_overlay = sitk.LabelOverlay(
        image=sitk.Cast(sitk.RescaleIntensity(image), sitk.sitkUInt8),
        labelImage=label_image,
        opacity=opacity,
        backgroundValue=0)
    sitk.Show(contour_overlay, title)

# images appear to be 3D with a z size of one, make them 2D
drr_contour_image = sitk.ReadImage('contourDRR.nrrd', sitk.sitkUInt8)[...,0]
epid_image = sitk.ReadImage('EPID.nrrd')[...,0]

drr_contour_distance_map = sitk.SignedMaurerDistanceMap(drr_contour_image,
                                                        squaredDistance=False,
                                                        useImageSpacing=True)
drr_contour_distance_map_resampled = sitk.Resample(drr_contour_distance_map,
                                                   epid_image)

overlay_zerolevelset(epid_image, drr_contour_distance_map_resampled,title='initial contour from DRR')

epid_gradmag = sitk.GradientMagnitudeRecursiveGaussian(epid_image, sigma = 2.0)
edge_potential_map_image = sitk.Cast(1/(1+epid_gradmag), sitk.sitkFloat32)

overlay_zerolevelset(edge_potential_map_image, drr_contour_distance_map_resampled, title='initial contour from DRR on EPID derived edge potential map')
1 Like

Thanks a lot.
I appreciate your help so much.
As you mentioned, I checked the type two images.
The type of EPIDGradientEdgePotential is double.
The type of contourDRRMatchedSignedMaurerDistanceMap is float.
I convert double to float with CastImageFilter. After doing it, I execute GeodesicActiveContourLevelSetImageFilter.
Codes:

#Modules
import SimpleITK as sitk
from SimpleITK import GeodesicActiveContourLevelSetImageFilter
from SimpleITK import CastImageFilter

#Read distance map and edge potential map
input1 = sitk.ReadImage('contourDRRMatchedSignedMaurerDistanceMap.nrrd')
input2 = sitk.ReadImage('EPIDGradientEdgePotential.nrrd')

#Create the filter of CastImageFilter
myFilterCastImage = CastImageFilter()

#Set output pixel type (float32)
myFilterCastImage.SetOutputPixelType(sitk.sitkFloat32)

#Execute the filter (CastImageFilter) on the inputs
input1 = myFilterCastImage.Execute(input1)
input2 = myFilterCastImage.Execute(input2)

#Create the filter of GeodesicActiveContourLevelSetImageFilter
myFilterGeodesicActiveContourLevelSet = GeodesicActiveContourLevelSetImageFilter()

#Set the parameters of GeodesicActiveContourLevelSetImageFilter
myFilterGeodesicActiveContourLevelSet = GeodesicActiveContourLevelSetImageFilter()
myFilterGeodesicActiveContourLevelSet.SetAdvectionScaling(1.0)
myFilterGeodesicActiveContourLevelSet.SetCurvatureScaling(1.0)
myFilterGeodesicActiveContourLevelSet.SetDebug(False)
myFilterGeodesicActiveContourLevelSet.SetMaximumRMSError(0.01)
myFilterGeodesicActiveContourLevelSet.SetNumberOfIterations(1000)
myFilterGeodesicActiveContourLevelSet.SetNumberOfThreads(8)
myFilterGeodesicActiveContourLevelSet.SetNumberOfWorkUnits(0)
myFilterGeodesicActiveContourLevelSet.SetPropagationScaling(1.0)
myFilterGeodesicActiveContourLevelSet.SetReverseExpansionDirection(False)

#Execute the filter (GeodesicActiveContourLevelSetImageFilter) on the input images (input1, input2)
contourDEformed = myFilterGeodesicActiveContourLevelSet.Execute(input1, input2)

After running it, I get the following error message:

RuntimeError: Exception thrown in SimpleITK GeodesicActiveContourLevelSetImageFilter_Execute: /tmp/SimpleITK-build/ITK-prefix/include/ITK-5.2/itkImageToImageFilter.hxx:220:
ITK ERROR: GeodesicActiveContourLevelSetImageFilter(0x564cb8db2910): Inputs do not occupy the same physical space! 
InputImage Origin: [7.6250000e+01, 7.1875000e+01, 0.0000000e+00], InputImageInitialImage Origin: [-7.6250000e+01, -7.1875000e+01, 0.0000000e+00]
	Tolerance: 2.5000000e-07
InputImage Direction: -1.0000000e+00 0.0000000e+00 0.0000000e+00
0.0000000e+00 -1.0000000e+00 0.0000000e+00
0.0000000e+00 0.0000000e+00 1.0000000e+00
, InputImageInitialImage Direction: 1.0000000e+00 0.0000000e+00 0.0000000e+00
0.0000000e+00 1.0000000e+00 0.0000000e+00
0.0000000e+00 0.0000000e+00 1.0000000e+00


	Tolerance: 1.0000000e-06

What do you think about this error?

The basic question for me is:
Is there a technique that can deform an edge or contour (contourDRR) based on other image properties (EPID image) that have been extracted these features by filters such as EdgePotentialImageFilter and SignedMaurerDistanceMapImageFilter?

Please guide me.
Best regards.
Shahrokh

At now, using the following code, I think that inputs occupy the same physical space.

In [1]: #Modules
   ...: import SimpleITK as sitk
   ...: from SimpleITK import GeodesicActiveContourLevelSetImageFilter
   ...: from SimpleITK import CastImageFilter
   ...: 
   ...: #Read distance map and edge potential map
   ...: input1 = sitk.ReadImage('contourDRRMatchedSignedMaurerDistanceMap.nrrd')
   ...: input2 = sitk.ReadImage('EPIDGradientEdgePotential.nrrd')

In [2]: originInput1 = input1.GetOrigin()
In [3]: originInput2 = input2.GetOrigin()
In [4]: print(input1.GetOrigin())
(-76.25000000000001, -71.87499999999999, 0.0)

In [5]: print(input2.GetOrigin())
(76.25000000000001, 71.87499999999999, 0.0)

In [6]: input1.SetOrigin([-1*originInput1[0], -1*originInput1[1], originInput1[2]])
In [7]: print(input1.GetOrigin())
(76.25000000000001, 71.87499999999999, 0.0)

In [8]: print(input2.GetOrigin())
(76.25000000000001, 71.87499999999999, 0.0)

Again I execute the following code:

#Create the filter of CastImageFilter
myFilterCastImage = CastImageFilter()

#Set output pixel type (float32)
myFilterCastImage.SetOutputPixelType(sitk.sitkFloat32)

#Execute the filter (CastImageFilter) on the inputs
input1 = myFilterCastImage.Execute(input1)
input2 = myFilterCastImage.Execute(input2)

#Create the filter of GeodesicActiveContourLevelSetImageFilter
myFilterGeodesicActiveContourLevelSet = GeodesicActiveContourLevelSetImageFilter()

#Set the parameters of GeodesicActiveContourLevelSetImageFilter
myFilterGeodesicActiveContourLevelSet = GeodesicActiveContourLevelSetImageFilter()
myFilterGeodesicActiveContourLevelSet.SetAdvectionScaling(1.0)
myFilterGeodesicActiveContourLevelSet.SetCurvatureScaling(1.0)
myFilterGeodesicActiveContourLevelSet.SetDebug(False)
myFilterGeodesicActiveContourLevelSet.SetMaximumRMSError(0.01)
myFilterGeodesicActiveContourLevelSet.SetNumberOfIterations(1000)
myFilterGeodesicActiveContourLevelSet.SetNumberOfThreads(8)
myFilterGeodesicActiveContourLevelSet.SetNumberOfWorkUnits(0)
myFilterGeodesicActiveContourLevelSet.SetPropagationScaling(1.0)
myFilterGeodesicActiveContourLevelSet.SetReverseExpansionDirection(False)

#Execute the filter (GeodesicActiveContourLevelSetImageFilter) on the input images (input1, input2)
contourDEformed = myFilterGeodesicActiveContourLevelSet.Execute(input1, input2)

Again I get the following error message:

ITK ERROR: GeodesicActiveContourLevelSetImageFilter(0x557e94dfb130): Inputs do not occupy the same physical space! 
InputImage Direction: -1.0000000e+00 0.0000000e+00 0.0000000e+00
0.0000000e+00 -1.0000000e+00 0.0000000e+00
0.0000000e+00 0.0000000e+00 1.0000000e+00
, InputImageInitialImage Direction: 1.0000000e+00 0.0000000e+00 0.0000000e+00
0.0000000e+00 1.0000000e+00 0.0000000e+00
0.0000000e+00 0.0000000e+00 1.0000000e+00

	Tolerance: 1.0000000e-06

Please guide me to solve it.

As I mentioned above, my main question is:
Is there a technique that can deform an edge or contour (contourDRR) based on other image properties (EPID image) that have been extracted these features by filters such as EdgePotentialImageFilter and SignedMaurerDistanceMapImageFilter?

Best regards.
Shahrokh

One of your direction matrices in RAS (-1 0 0, 0 -1 0, 0 0 1), the other in LPS (1 0 0, 0 1 0, 0 0 1) format.

Since you are inverting sign of the origin for input1, what you might need instead is OrientImageFilter.

This RAS vs LPS confusion might come from Slicer’s default use of RAS internally. Slicer does write images correctly in LPS format to disk, so you might have done some inversion steps in Slicer which are now causing a problem.

Dear Ziv Yaniv and Dear Dženan Zukić

Thanks a lot for your excellent guidance and support. As you mentioned , I was saving the output of each filter and did not care about changing the direction. It is very interesting to me the code that Ziv Yaniv wrote. Thanks a lot Ziv Yaniv.

The code written by Ziv Yaniv is greatly simplified and very beautiful. As seen in the following lines, the physical space and the direction matrix are the same at both inputs to the filter.

Next I run the filter with two inputs edge_potential_map_image and drr_contour_distance_map_resampled and get the output image of contourDRRDeformed.

import SimpleITK as sitk
from SimpleITK import GeodesicActiveContourLevelSetImageFilter

def overlay_zerolevelset(image, levelset, opacity=0.5, title=''):
    spacing = image.GetSpacing()[0]
    label_image = levelset> -spacing and levelset<spacing
    contour_overlay = sitk.LabelOverlay(
        image=sitk.Cast(sitk.RescaleIntensity(image), sitk.sitkUInt8),
        labelImage=label_image,
        opacity=opacity,
        backgroundValue=0)
    sitk.Show(contour_overlay, title)

#images appear to be 3D with a z size of one, make them 2D
drr_contour_image = sitk.ReadImage('contourDRR.nrrd', sitk.sitkUInt8)[...,0]
epid_image = sitk.ReadImage('EPID.nrrd')[...,0]

drr_contour_distance_map = sitk.SignedMaurerDistanceMap(drr_contour_image,
                                                        squaredDistance=False,
                                                        useImageSpacing=True)
drr_contour_distance_map_resampled = sitk.Resample(drr_contour_distance_map,
                                                   epid_image)

overlay_zerolevelset(epid_image, drr_contour_distance_map_resampled,title='initial contour from DRR')

epid_gradmag = sitk.GradientMagnitudeRecursiveGaussian(epid_image, sigma = 2.0)
edge_potential_map_image = sitk.Cast(1/(1+epid_gradmag), sitk.sitkFloat32)

overlay_zerolevelset(edge_potential_map_image, drr_contour_distance_map_resampled, title='initial contour from DRR on EPID derived edge potential map')

input1 = edge_potential_map_image
input2 = drr_contour_distance_map_resampled

#Check matching physical space
print("Origin input1:", input1.GetOrigin())
print("Origin input2:", input2.GetOrigin())

#Check direction matrix
print("Direction matrix input1:", input1.GetDirection())
print("Direction matrix input2:", input2.GetDirection())

#Create the filter of  GeodesicActiveContourLevelSetImageFilter
myFilterGeodesicActiveContourLevelSet = GeodesicActiveContourLevelSetImageFilter()
myFilterGeodesicActiveContourLevelSet.SetAdvectionScaling(1.0)
myFilterGeodesicActiveContourLevelSet.SetCurvatureScaling(1.0)
myFilterGeodesicActiveContourLevelSet.SetDebug(False)
myFilterGeodesicActiveContourLevelSet.SetMaximumRMSError(0.01)
myFilterGeodesicActiveContourLevelSet.SetNumberOfIterations(1000)
myFilterGeodesicActiveContourLevelSet.SetNumberOfThreads(8)
myFilterGeodesicActiveContourLevelSet.SetNumberOfWorkUnits(0)
myFilterGeodesicActiveContourLevelSet.SetPropagationScaling(1.0)
myFilterGeodesicActiveContourLevelSet.SetReverseExpansionDirection(False)

#Execute the filter (GeodesicActiveContourLevelSetImageFilter) on the input images (input1, input2) 
contourDRRDeformed = myFilterGeodesicActiveContourLevelSet.Execute(input1, input2)

overlay_zerolevelset(epid_image, contourDRRDeformed,title='DRR contour deformed based on EPID features')

sitk.Show(contourDRRDeformed)

contourDRRDeformed_image = sitk.GetArrayFromImage(contourDRRDEformed)
contourDRRDeformed_image.max()
0.75
contourDRRDeformed_image.min()
0.75

Fortunately, with your guidance and support, I do not get any error message and execute filter without any problem but unfortunately, as you can see in the output of the above commands, all pixels in contourDRRDeformed are equal to 0.75.
I do not have a contour deformed (from the initial contour from the DRR image) that matches on the edge of the pelvis in the EPID image.

Please guide me.
Best regards.
Shahrokh

Unfortunately, for this output (contourDRRDeformed: image with all pixel values equal to 0.75) I have not yet reached a convincing and decisive answer.
The only thing that comes to mind, is that this filter (GeodesicActiveContourLevelSetImageFilter ) is only meant to be used with closed contours; the point that Ziv Yaniv mentioned in his message. Of course, I did not see any indication of it (working this filter on closed contours and NOT on opened contours) in SimpleITK: itk::simple::GeodesicActiveContourLevelSetImageFilter Class Reference.
What do you think about it?

Best regards.
Shahrokh

Hello
In the following, I tried to use this filter, GeodesicActiveContourLevelSetImageFilter, in segmenting cameraman.
Steps:
I converted the cameraman image file from png to nrrd. I also drew a closed contour around the cameraman in 3dslicer and saved it in nrrd format. The following image is a demonstration of this.

Please download the following nrrd files from dropbox:
cameraman
initial contour

I execute the following commands to apply GeodesicActiveContourLevelSetImageFilter to segment cameraman.

import SimpleITK as sitk
from SimpleITK import GeodesicActiveContourLevelSetImageFilter

def overlay_zerolevelset(image, levelset, opacity=0.5, title=''):
    spacing = image.GetSpacing()[0]
    label_image = levelset> -spacing and levelset<spacing
    contour_overlay = sitk.LabelOverlay(
        image=sitk.Cast(sitk.RescaleIntensity(image), sitk.sitkUInt8),
        labelImage=label_image,
        opacity=opacity,
        backgroundValue=0)
    sitk.Show(contour_overlay, title)

initialContour = sitk.ReadImage('initial_contour.nrrd')[...,0]
cameraman = sitk.ReadImage('cameraman.nrrd')[...,0]

initialContour_distance_map = sitk.SignedMaurerDistanceMap(initialContour,
                                                        squaredDistance=False,
                                                        useImageSpacing=True)
initialContour_distance_map_resampled = sitk.Resample(initialContour_distance_map,
                                                   cameraman)

sitk.Show(initialContour_distance_map_resampled)
overlay_zerolevelset(cameraman, initialContour_distance_map_resampled)

cameraman_gradmag = sitk.GradientMagnitudeRecursiveGaussian(cameraman, sigma = 2.0)
edge_potential_map_image = sitk.Cast(1/(1+cameraman_gradmag), sitk.sitkFloat32)

overlay_zerolevelset(edge_potential_map_image, initialContour_distance_map_resampled)

#Create the filter of  GeodesicActiveContourLevelSetImageFilter
myFilterGeodesicActiveContourLevelSet = GeodesicActiveContourLevelSetImageFilter()
myFilterGeodesicActiveContourLevelSet.SetAdvectionScaling(1.0)
myFilterGeodesicActiveContourLevelSet.SetCurvatureScaling(1.0)
myFilterGeodesicActiveContourLevelSet.SetDebug(False)
myFilterGeodesicActiveContourLevelSet.SetMaximumRMSError(0.01)
myFilterGeodesicActiveContourLevelSet.SetNumberOfIterations(1000)
myFilterGeodesicActiveContourLevelSet.SetNumberOfThreads(8)
myFilterGeodesicActiveContourLevelSet.SetNumberOfWorkUnits(0)
myFilterGeodesicActiveContourLevelSet.SetPropagationScaling(1.0)
myFilterGeodesicActiveContourLevelSet.SetReverseExpansionDirection(False)

#Execute the filter (GeodesicActiveContourLevelSetImageFilter) on the inputs 
contourDeformed = myFilterGeodesicActiveContourLevelSet.Execute(edge_potential_map_image, initialContour_distance_map_resampled)

overlay_zerolevelset(cameraman, contourDeformed)
sitk.Show(contourDeformed)

Despite using parameters (default) that have not yet been tuned, I expected to get a deformed contour output though wrong. But that did not happen.

Is it due to not tuning the parameters of this filter?

Please guide me in understanding this concept and implementing this filter.
Best regards.
Shahrokh

I read the section entitled 4.3.3 Geodesic Active Contours Segmentation (page 403 of the ITK Software Guide manual).
The major components in using this filter are similar to the code written by Ziv Yaniv.
Excuse me, however I still can not get any contour deformation that is even slightly match on the EPID image.
I’m glad you helped me.

Best regards.
Shahrokh

If you read the active contour paper and other similar work, you get the impression that they are awesome. In practice, finding the right set of parameters for any particular image is not easy. And finding a set of parameters which works for all images of your problem is frequently not possible.

That is exactly what happened to me the last time I tried it.

1 Like

Hello @shahrokh_nasseri,

I concur with @dzenanz’s experience.

I would recommend trying a registration based approach for transferring the segmentation. Possibly try a free form deformation to align the EPID and DRR images and then resample the DRR segmentation onto the EPID (using the transformation from the registration instead of the identity transformation). This notebook shows an MRI/MRI based solution (it uses the Demons registration so not directly applicable but illustrates the idea).