InsightSoftwareConsortium / ITKElastix

An ITK Python interface to elastix, a toolbox for rigid and nonrigid registration of images
Apache License 2.0
198 stars 22 forks source link

Reset random seed for deterministic registration #279

Open Pedro-Filipe opened 4 months ago

Pedro-Filipe commented 4 months ago

Hi, I was wondering if there is a way to reset the random seed used by ITKElastix.

In my Python code if I register a set of images, and then if I repeat this registration in the same dataset in the next loop iteration, I get slightly different pixel values for the registered images. I believe at least some of the randomness comes from this part of my bspline registration recipe:

// **************** Image sampling **********************
(NumberOfSpatialSamples 2048)
(NewSamplesEveryIteration "true")
(ImageSampler "Random")
//(ImageSampler "Full")

If I set sampler to (ImageSampler "Full") then the registration is deterministic, and I get the same results every iteration, but unfortunately it also takes significantly longer to register.

So my question is: is there a way to reset the random seed in the python code?

dzenanz commented 4 months ago

How about parameter_map['RandomSeed'] = ['30101983']? In the parameter file that might be (RandomSeed 30101983).

dzenanz commented 4 months ago

You should be able too look up all the Elastix parameters here, but the page is not working for me right now. @N-Dekker @ViktorvdValk Is that just me, or is the page really down?

N-Dekker commented 4 months ago

@Pedro-Filipe Thanks for asking! It may be helpful to reset the global ITK random number generator before each registration.

Which version of itk-elastix do you have installed? ITK v5.4rc02 introduces a new function, MersenneTwisterRandomVariateGenerator.ResetNextSeed(), which should be of help. I think it should be available with the latest itk-elastix release.

The idea is to call MersenneTwisterRandomVariateGenerator.ResetNextSeed() before each registration. Please let us know if that does indeed solve the problem!

N-Dekker commented 4 months ago

You should be able too look up all the Elastix parameters here, but the page is not working for me right now. @N-Dekker @ViktorvdValk Is that just me, or is the page really down?

Thanks for reporting this problem @dzenanz It's hopefully back online soon. For the time being, you may use the Internet Archive: https://web.archive.org/web/20230204115458/https://elastix.lumc.nl/doxygen/parameter.html

Pedro-Filipe commented 4 months ago

Thank you both for your comments. Really appreciate the support.

How about parameter_map['RandomSeed'] = ['30101983']? In the parameter file that might be (RandomSeed 30101983).

@dzenanz The parameter RandomSeed sounds like an obvious answer to the problem but in practice it doesn't seem to do anything. I found a couple of posts on the SimpleElastix GitHub reporting similar behaviour.

Make SimpleElastix more deterministic by setting RandomSeed #460

multiple SimpleElastix instances/runs ignore RandomSeed in ParameterFile #122


@Pedro-Filipe Thanks for asking! It may be helpful to reset the global ITK random number generator before each registration.

Which version of itk-elastix do you have installed? ITK v5.4rc02 introduces a new function, MersenneTwisterRandomVariateGenerator.ResetNextSeed(), which should be of help. I think it should be available with the latest itk-elastix release.

The idea is to call MersenneTwisterRandomVariateGenerator.ResetNextSeed() before each registration. Please let us know if that does indeed solve the problem!

@N-Dekker I have these versions currently installed:

itk                          5.4rc2
itk-core                     5.4rc2
itk-elastix                  0.19.1
itk-filtering                5.4rc2
itk-io                       5.4rc2
itk-numerics                 5.4rc2
itk-registration             5.4rc2
itk-segmentation             5.4rc2

I have added this line of code before calling each registration:

itk.MersenneTwisterRandomVariateGenerator.ResetNextSeed()

It does reduce significantly the variation, although unfortunately not 100%. The values of the registered images differ after a few decimal places.

N-Dekker commented 4 months ago

@Pedro-Filipe I'm glad to hear that ResetNextSeed() reduces the variation significantly. You might consider to also do a SetSeed (while calling ResetNextSeed() as well), using a fixed constant seed, something like:

itk.MersenneTwisterRandomVariateGenerator.GetInstance().SetSeed(1)

(Not 100% sure about the syntax.) Does that further reduce the variation?

Another cause of variation could be multi-threading. You see, internally, elastix may do some floating point operations in a different order, depending on the order in which the threads process the work units. Mathematically, it should not matter, but the order of those floating point operations might affect the floating point rounding errors. You may eliminate those effects by setting the number of threads to one, globally:

itk.MultiThreaderBase.SetGlobalMaximumNumberOfThreads(1)

Obviously this may slow down the registration process. But it might still be interesting to know if that will totally eliminate all variations... I'm interested to hear what you will find!

Pedro-Filipe commented 4 months ago

Thanks for the additional suggestions @N-Dekker .

Results

Also using itk.MersenneTwisterRandomVariateGenerator.GetInstance().SetSeed(1)

Syntax seems OK, but didn't seem to have a significant effect.

Reducing threads with itk.MultiThreaderBase.SetGlobalMaximumNumberOfThreads(1)

Slows down registration as expected, but no effect on the randomness. I was sort of expecting this, as I use multithreading with the parameter (ImageSampler "Full") and I get reproducible results.


So at the moment, if I want deterministic results I can use: