Transformation graph for quick calculation of concatenated transforms

Hi,

I am looking for a quick way to compute an in-between transformation between two volume nodes that are not directly connected in a transformation graph. Here is a figure of what I mean:

Assume I have several MRI contrasts (T1/T2/T2S and HR), and I want to resample T2S in the voxel grid of a high-resolution volume “HR”. Often, I need a direct transform between the two, i.e. “T2SToHR”, e.g. for a module like “Resample Scalar/Vector/DWI Volume” to function.

In PLUS, this is neatly solved with a transformation graph, and I can simply give a string “T2SToHR” which is parsed to calculate the in-between transform via graph traversal.
Is there a similar mechanism in Slicer?
I want to use the “Resample Scalar/Vector/DWI Volume” module, and I want to avoid coding the transformation chain out explicitly every time I want to do such an operation.

Cheers,
Ahmad

1 Like

Use slicer.vtkMRMLTransformNode.GetTransformBetweenNodes method for general transforms (to get a transformation chain that can include any number of non-linear transforms).

If you only have linear transforms between the nodes you can get the concatenated transforms as a single 4x4 matrix by using
slicer.vtkMRMLTransformNode.GetMatrixTransformBetweenNodes.

To use this transform in a CLI module, create a new transform node and set the concatenated transform into that.

1 Like

Thanks a lot! This looks exactly like what I need. However, I have problems with executing the command. Given the example above, I tried the following:
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(nT2SToT2.SafeDownCast(vtk.vtkGeneralTransform()), nHRToT1.SafeDownCast(vtk.vtkGeneralTransform()), T.SafeDownCast(vtk.vtkGeneralTransform()))

(note: I had to SafeDownCast() - all transforms are linear - to a vtkGeneralTransform. I could have tried the matrix4x4 version you mentioned, but I wanted to try the general approach for more complex scenarios later).
The command runs, but the result is wrong as I receive the identity matrix.
I also received the following warning in the log:
Generic Warning: In /Users/kitware/Dashboards/Package/Slicer-462/Libs/MRML/Core/vtkMRMLTransformNode.cxx, line 472 vtkMRMLTransformNode::GetTransformToNode failed: transformSourceToTarget is invalid

Could you give a hint regarding the syntax?
Thanks!

P.S.: the volumes and transforms (as in the figure above) are arranged in the scene as follows:

Example:

transformAToB = vtk.vtkGeneralTransform()
a = getNode('MRHead').GetParentTransformNode()
b = getNode('CTChest').GetParentTransformNode()
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(a, b, transformAToB)

If you want to use transformAToB in a transform node using SetTransformToParent then you have to deep-copy it using slicer.vtkMRMLTransformNode.DeepCopyTransform. If you only have linear transforms it is much simpler and faster to use GetMatrixTransformBetweenNodes and SetMatrixTransformToParent.

1 Like

Excellent. I’ll try this out!
Again thanks a lot for the quick reply!

OK, I wrote a little utility function. In case it helps anyone, I’ll post it here. Apologies for poor naming etc… Also, indentation is wrong - if someone knows how to properly post formatted pieces of code, please let me know.

  def calculateInbetweenLinearTransform(self,nTrfSource,nTrfTarget,nTrfSourceToTarget):
    # Get concatenated transforms from source to target nodes in the MRML scene. Source 
    # and target nodes are allowed to be NULL, which represents the world coordinate 
    # system. Result is written into nTrfSourceToTarget (has to be a
    # vtkMRMLLinearTransformNode)
    matrixAToB = vtk.vtkMatrix4x4()
    if nTrfSource is not None:
      a = nTrfSource.GetParentTransformNode()
    else:
      a = None
    if nTrfTarget is not None:
      b = nTrfTarget.GetParentTransformNode()
    else:
      b = None
    # calculate the matrix transform
    slicer.vtkMRMLTransformNode.GetMatrixTransformBetweenNodes(a,b,matrixAToB)
    # set the matrix transform into target
    nTrfSourceToTarget.SetMatrixTransformToParent(matrixAToB)

You just need to add 4 spaces to your code, there’s even a button for it on the Discourse GUI.

def calculateInbetweenLinearTransform(self, nTrfSource, nTrfTarget, nTrfSourceToTarget):
    # Get concatenated transforms from source to target nodes in the MRML scene. Source 
    # and target nodes are allowed to be NULL, which represents the world coordinate 
    # system. Result is written into nTrfSourceToTarget (has to be a
    # vtkMRMLLinearTransformNode)
    matrixAToB = vtk.vtkMatrix4x4()
    if nTrfSource is not None:
        a = nTrfSource.GetParentTransformNode()
    else:
        a = None
    if nTrfTarget is not None:
        b = nTrfTarget.GetParentTransformNode()
    else:
        b = None
    # calculate the matrix transform
    slicer.vtkMRMLTransformNode.GetMatrixTransformBetweenNodes(a,b,matrixAToB)
    # set the matrix transform into target
    nTrfSourceToTarget.SetMatrixTransformToParent(matrixAToB)
1 Like

Andras’ answer should be the solution of this topic, not mine.

Hi! I am new for 3D Slicer, and when I to used your method to calculate the transform matrix, I found that
a = getNode().GetParentTransformNode,
a is NONE, same as b. This resulted in transformAToB equaled to Identity Matrix.

But if I directly use
a,b = getNode(),
transformAToB = slicer.vtkMRMLTransformNode()
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(a, b, transformAToB)

I was told method requires a vtkMRMLTransformNode, a vtkMRMLScalarVolumeNode was provided.

I am wondering if anyone could help me find out why this problem comes out.

Appreciate your help!
Thank you.
Cheers,
xxzhou

Please post your actual code that doesn’t work because the one above for example is not valid, and it’s impossible like this to figure out what is going wrong.

Thank you for responding! Sure.

My code is as below:

source = getNode(‘preop’).GetParentTransformNode()
target = getNode(‘intraop’).GetParentTransformNode()
transformNode = vtk.vtkGeneralTransform()
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(source, target, transformNode)

Then I use:
print(transformNode)

Output is:
vtkGeneralTransform (0x608000134fa0)
Debug: Off
Modified Time: 2504871
Reference Count: 1
Registered Events: (none)
Inverse: (0)
Input: (0)
InverseFlag: 0
NumberOfConcatenatedTransforms: 0

There is no error, but actually source and target all equal to NONE, so it seems that transformNode = NONE

When I try to change as below:

source = getNode(‘preop’)
target = getNode(‘intraop’)
TransformMatrix = vtk.vtkGeneralTransform()
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(source, target, TransformMatrix)

GetTransformBetweenNodes argument 1: method requires a vtkMRMLTransformNode, a vtkMRMLScalarVolumeNode was provided.

I think it is because source and target are both vtkMRMLScalarVolumeNode. I am wondering how to change the vtkMRMLScalarVolumeNode to vtkMRMLTransformNode, which is not NONE.

I am also wondering how to apply this transform matrix on the source image. I have tried the code above:

nTrfSourceToTarget = slicer.vtkMRMLTransformNode
nTrfSourceToTarget.SetMatrixTransformToParent(transformNode)

but it seems do not work. The error is:
TypeError: unbound method requires a vtkCommonCorePython.vtkMRMLTransformNode as the first argument

Too many questions…
Thank you for your help!

How about this:

source = getNode(‘preop’)
target = getNode(‘intraop’)
sourceToTargetTransformNode = slicer.vtkMRMLLinearTransformNode()
slicer.mrmlScene.AddNode(sourceToTargetTransformNode)
slicer.vtkMRMLTransformNode.GetTransformBetweenNodes(source, target, sourceToTargetTransformNode)

You can set a parent transform with SetAndObserveTransformNodeID

The problem in your code has been using the wrong types in each case. You need to make sure that you pass/create/get the object you need, and not some related object of a different type.