Segmentation fault error when running Rigid+Affine Registration

Hello,
I’m trying to run a rigid + affine registration of around 800 images (CT scans), but I keep getting a Segmentation Fault error, when run using Python3 on Ubuntu.
The relevant part of the code is (I think):

    for id_, item in tqdm(data_index.iterrows()):
        try:
            image = sitk.ReadImage(item[image_column])
            print('Sucessfully read image ' + item[image_column])
        except RuntimeError:
            print(f'Could not read image: {id_:s}')
            continue
        try:
            transform, iterations_rig, final_metric_rig, iterations_aff, final_metric_aff, image_resampled_aff = self.register_image_to_atlas(image)
        except RuntimeError or TypeError:
            print(f'Could not register image: {id_:s}')
            continue
        *defined transform_path*
        sitk.WriteTransform(transform, transform_path)
        *defined image_resampled_path*
        sitk.WriteImage(image_resampled_aff, image_resampled_path)

“data_index” is a csv that contains all the paths to the images.
The function self.register_image_to_atlas(image) accepts the CT image path and performs the rigid and affine registration using the sitk.ImageRegistrationMethod() class. At the end, every output from this function is written to the data_index csv file.

I already tried increasing the stack allocated for this process with ulimit -s, but it didn’t work. I also tried running python under gdb, and got the following output, which as a beginner I don’t quite know how to interpret and solve:

Thread 123 "python" received signal SIGSEGV, Segmentation fault.
(gdb) backtrace
#0  0x00007fffec351bd3 in ?? () from path/my-project-env/lib/python3.8/site-packages/SimpleITK/_SimpleITK.cpython-38-x86_64-linux-gnu.so
#1  0x00007fffec346d00 in ?? () from path/my-project-env/lib/python3.8/site-packages/SimpleITK/_SimpleITK.cpython-38-x86_64-linux-gnu.so
#2  0x00007fffec3be611 in ?? () from path/my-project-env/lib/python3.8/site-packages/SimpleITK/_SimpleITK.cpython-38-x86_64-linux-gnu.so
#3  0x00007fffecd86490 in ?? () from path/my-project-env/lib/python3.8/site-packages/SimpleITK/_SimpleITK.cpython-38-x86_64-linux-gnu.so
#4  0x00007fffecd49f63 in ?? () from path/my-project-env/lib/python3.8/site-packages/SimpleITK/_SimpleITK.cpython-38-x86_64-linux-gnu.so
#5  0x00007fffecca24ca in ?? () from path/my-project-env/lib/python3.8/site-packages/SimpleITK/_SimpleITK.cpython-38-x86_64-linux-gnu.so
#6  0x00007ffff36d1ca4 in std::_Function_handler<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> (), std::__future_base::_Task_setter<std::unique_ptr<std::__future_base::_Result<void*>, std::__future_base::_Result_base::_Deleter>, std::__future_base::_Task_state<std::_Bind<void* (*(itk::PoolMultiThreader::ThreadPoolInfoStruct*))(void*)>, std::allocator<int>, void* ()>::_M_run()::{lambda()#1}, void*> >::_M_invoke(std::_Any_data const&) ()
   from /vol/medic02/users/cpicarr1/my-project-env/lib/python3.8/site-packages/SimpleITK/_SimpleITK.cpython-38-x86_64-linux-gnu.so
#7  0x00007ffff36d1d6b in ?? () from path/my-project-env/lib/python3.8/site-packages/SimpleITK/_SimpleITK.cpython-38-x86_64-linux-gnu.so
#8  0x00007ffff7d9147f in __pthread_once_slow (once_control=0x20f23d8, init_routine=0x7fffe8af0b80 <__once_proxy>) at pthread_once.c:116
#9  0x00007ffff36d2012 in ?? () from path/my-project-env/lib/python3.8/site-packages/SimpleITK/_SimpleITK.cpython-38-x86_64-linux-gnu.so
#10 0x00007ffff36d2e8a in ?? () from path/my-project-env/lib/python3.8/site-packages/SimpleITK/_SimpleITK.cpython-38-x86_64-linux-gnu.so
#11 0x00007ffff39ac3df in ?? () from path/my-project-env/lib/python3.8/site-packages/SimpleITK/_SimpleITK.cpython-38-x86_64-linux-gnu.so
#12 0x00007ffff7d88609 in start_thread (arg=<optimised out>) at pthread_create.c:477
#13 0x00007ffff7ec4293 in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95

If anyone has any input on how this problem could be solved, I would be really thankful!

I’ll leave the code for the registration here, in case someone finds it useful for the question:

    def register_image_to_atlas(self, image):
    # image was already read before
    image = sitk.Threshold(image, lower=-1024.0, upper=1e6, outsideValue=-1024.0)
    dimension = self.target_template.GetDimension()
    # Rigid registration of image to target_template

    # Set the initial moving transforms.
    # MOMENTS (center of mass alignment) because it's CT to CT
    initial_transform_rig = sitk.CenteredTransformInitializer(self.target_template, image,
                                                              sitk.Euler3DTransform(),
                                                              sitk.CenteredTransformInitializerFilter.MOMENTS)

    registration_method_rig = sitk.ImageRegistrationMethod()

    # Similarity metric settings:
    registration_method_rig.SetMetricAsMattesMutualInformation(numberOfHistogramBins=32)

    # Sampling settings:
    registration_method_rig.SetMetricSamplingStrategy(registration_method_rig.REGULAR)
    registration_method_rig.SetMetricSamplingPercentage(0.01)

    # Interpolator settings:
    registration_method_rig.SetInterpolator(sitk.sitkLinear)

    # Optimizer settings:
    registration_method_rig.SetOptimizerAsGradientDescentLineSearch(learningRate=1.0,
                                                                    numberOfIterations=100,
                                                                    convergenceMinimumValue=1e-6,
                                                                    convergenceWindowSize=5)
    
    registration_method_rig.SetOptimizerScalesFromPhysicalShift()

    # Setup for the multi-resolution framework:
    registration_method_rig.SetShrinkFactorsPerLevel(shrinkFactors=[4, 2, 1])
    registration_method_rig.SetSmoothingSigmasPerLevel(smoothingSigmas=[4, 2, 1])
    registration_method_rig.SmoothingSigmasAreSpecifiedInPhysicalUnitsOn()  # mm
  

    optimized_transform_rig = sitk.Euler3DTransform()  # Identity
    registration_method_rig.SetMovingInitialTransform(initial_transform_rig)
    registration_method_rig.SetInitialTransform(optimized_transform_rig)

    # self.transform_mni is a transformation that was calculated before (affine+Syn)

    final_rig_transform = sitk.CompositeTransform([self.transform_mni, 
                                                   self.registration_method_rig.Execute(self.target_template, image),
                                                   initial_transform_rig])

    iterations_rig = registration_method_rig.GetOptimizerIteration()
    final_metric_value_rig = registration_method_rig.GetMetricValue()

    ######### Affine registration ################

    registration_method_aff = sitk.ImageRegistrationMethod()
    registration_method_aff.SetMetricAsMattesMutualInformation(numberOfHistogramBins=32)
    registration_method_aff.SetMetricSamplingStrategy(registration_method_aff.REGULAR)
    registration_method_aff.SetMetricSamplingPercentage(0.01)
    registration_method_aff.SetInterpolator(sitk.sitkLinear)
    registration_method_aff.SetOptimizerAsGradientDescentLineSearch(learningRate=1.0,
                                                                    numberOfIterations=100,
                                                                    convergenceMinimumValue=1e-6,
                                                                    convergenceWindowSize=5)
    registration_method_aff.SetOptimizerScalesFromPhysicalShift()
    registration_method_aff.SetShrinkFactorsPerLevel(shrinkFactors=[4, 2, 1])
    registration_method_aff.SetSmoothingSigmasPerLevel(smoothingSigmas=[4, 2, 1])
    registration_method_aff.SmoothingSigmasAreSpecifiedInPhysicalUnitsOn()  # mm

    optimized_transform_aff = sitk.AffineTransform(dimension)
    registration_method_aff.SetMovingInitialTransform(final_rig_transform)
    registration_method_aff.SetInitialTransform(optimized_transform_aff)

    final_aff_transform = sitk.CompositeTransform([self.registration_method_aff.Execute(self.target_template_mni, image), final_rig_transform])
    final_aff_transform.FlattenTransform()

    # Always check the reason optimization terminated.
    iterations_aff = registration_method_aff.GetOptimizerIteration()
    final_metric_value_aff = registration_method_aff.GetMetricValue()

    filter = sitk.MinimumMaximumImageFilter()
    filter.Execute(image)

    resampler = sitk.ResampleImageFilter()
    resampler.SetReferenceImage(self.target_template_mni)
    resampler.SetInterpolator(sitk.sitkLinear)
    resampler.SetDefaultPixelValue(filter.GetMinimum())
    resampler.SetTransform(final_aff_transform)
    image_resampled_aff = resampler.Execute(image)

    return final_aff_transform, iterations_rig, final_metric_value_rig, iterations_aff, final_metric_value_aff, image_resampled_aff

Hello,

I’m not sure what might be the cause from what you have posted. This is a lot of code to narrow down. Can you please reduce this to a minimal working example that demonstrates the segmentation fault. Try removing code segments to narrow down the problem.

Also you may want to try the latest version of SimpleITK as the bug might have already been addressed.

Hello Bradley,

Thank you for your quick reply and sorry for the big code.
My code is summed to:

#Function that registers each CT scan (image) to a CT template on MNI space
def register_image_to_atlas(self, image):
     registration_method_rig = sitk.ImageRegistrationMethod()
     # Define all the parameters for a rigid registration of image to a CT template
     registration_method_rig.Execute(target_template, image)

After this, I compose this rigid transformation with an affine+deformable transformation that was calculated previously, with ANTs. This composed transformation will then serve as the initial transformation for an additional affine registration of the CT scan to the same target_template as before, but now in the MNI space (so I’m basically trying to perform a two-step registration: native space -> template space and template space -> MNI).
This previously calculated transformation I mentioned has 101678KB, and in the meantime I’ve realised that the SegFault error may come from this, given that I tried to replace it with a transformation with only 1KB and didn’t get the SegFault anymore.
However, I haven’t been able to fix the error while applying the heavier transformation.
Hope that was clear enough, but let me know if it wasn’t.

Thank you again for your help, truly appreciate it.

Do you know where the code is segmentation faulting?

Do you even need this registration code to reproduce the problem? If you just do identity affine+ deformable transform does the problem still occur?

If you compile ITK and SimpleITK in RelWithDebInfo or Debug mode, the stack trace will be much more useful as it will have more function names instead of ??.

We should be able to narrow down the Python call where the segfault occurs. Once that is done, and we have a minimal working example of the issue, we will dive into to the C++ code.

@dzenanz @blowekamp , thank you for your help so far.
To try to understand what was happening, I narrowed down the code to only the registration part and then applied it to the same image several times (to understand if the problem was a particular scan or a memory leak). The script that I’m running several times is simply a rigid registration script, similar to the ones on the SimpleITK examples page and I’m just using a for cycle to run it several times. What is happening now is that the first few times the script is run, it goes well, but then on a certain iteration, I get this error:

RuntimeError: Exception thrown in SimpleITK ImageRegistrationMethod_Execute: /tmp/SimpleITK-build/ITK-prefix/include/ITK-5.1/itkMattesMutualInformationImageToImageMetricv4.hxx:316:
itk::ERROR: itk::ERROR: 
MattesMutualInformationImageToImageMetricv4(0x1c49010): All samples map 
outside moving image buffer. The images do not sufficiently overlap. They need to 
be initialized to have more overlap before this metric will work. For instance, you can 
align the image centers by translation.

My goal is that the registration process is independent each time I run it, but that doesn’t seem to be what’s happening here. I’ve tried deleting all the variables created at the end of the script, including the one that contains the registration method (sitk.ImageRegistrationMethod), but I keep getting this error after some iterations. Is there any way to completely “reset” the registration method, without clearing the whole console?