Hi everyone,
First off, I want to say how much I appreciate this community!
I'm currently trying to implement a bidirectional mean squares metric for deterministic optimization and was wondering if anyone here has experimented with something similar. If so, I’d love to hear about your experiences or see any implementations you might be willing to share!
Here’s what I’ve tried so far. I implemented the metric as shown below, but I’m encountering two main issues:
- The computed metric values seem way too high.
- It produces surface intersections, which I suspect might indicate an underlying issue with how I’m computing the metric.
Here’s my current code:
import scalismo.common.*
import scalismo.geometry.{NDSpace, _3D}
import scalismo.numerics.Sampler
import scalismo.registration.{MeanSquaresMetric, RegistrationMetric}
import scalismo.transformations.TransformationSpace
import breeze.linalg.DenseVector
/**
* A bidirectional mean squares metric for image registration.
*
* @param fixedImage The fixed image in the registration process.
* @param movingImage The moving image in the registration process.
* @param transformationSpace The transformation space used for the registration.
* @param fixedSampler The sampler for the fixed image.
* @param movingSampler The sampler for the moving image.
*/
case class BidirectionalMeanSquaresMetric(
fixedImage: DifferentiableField[_3D, Float],
movingImage: DifferentiableField[_3D, Float],
transformationSpace: TransformationSpace[_3D],
fixedSampler: Sampler[_3D],
movingSampler: Sampler[_3D]
) extends RegistrationMetric[_3D] {
// Metric for the reference to target direction
private val metricReferenceToTarget = MeanSquaresMetric(fixedImage, movingImage, transformationSpace, fixedSampler)
// Metric for the target to reference direction
private val metricTargetToReference = MeanSquaresMetric(movingImage, fixedImage, transformationSpace, movingSampler)
/**
* Computes the value of the bidirectional mean squares metric.
*
* @param parameters The parameters for the transformation.
* @return The computed value of the metric.
*/
override def value(parameters: DenseVector[Double]): Double = {
val mseReferenceToTarget = metricReferenceToTarget.value(parameters)
val mseTargetToReference = metricTargetToReference.value(parameters)
(mseReferenceToTarget + mseTargetToReference) / 2.0
}
/**
* Computes the value and derivative of the bidirectional mean squares metric.
*
* @param parameters The parameters for the transformation.
* @return The computed value and derivative of the metric.
*/
override def valueAndDerivative(parameters: DenseVector[Double]): RegistrationMetric.ValueAndDerivative = {
val valueAndDerivative1: RegistrationMetric.ValueAndDerivative = metricReferenceToTarget.valueAndDerivative(parameters)
val valueAndDerivative2: RegistrationMetric.ValueAndDerivative = metricTargetToReference.valueAndDerivative(parameters)
val combinedValue = (valueAndDerivative1.value + valueAndDerivative2.value) / 2.0
val combinedDerivative = (valueAndDerivative1.derivative + valueAndDerivative2.derivative) / 2.0
RegistrationMetric.ValueAndDerivative(combinedValue, combinedDerivative)
}
/**
* Computes the derivative of the bidirectional mean squares metric.
*
* @param parameters The parameters for the transformation.
* @return The computed derivative of the metric.
*/
override def derivative(parameters: DenseVector[Double]): DenseVector[Double] = {
val valueAndDeriv = valueAndDerivative(parameters)
valueAndDeriv.derivative
}
implicit val ndSpace: NDSpace[_3D] = implicitly[NDSpace[_3D]]
}
I’d really appreciate it if anyone could point me in the right direction or share tips on what might be going wrong here.
Thanks in advance and kind regards,
Sybren