|
Tags: 2017 source edit, Replaced |
(One intermediate revision by the same user not shown) |
Line 1: |
Line 1: |
− | <noinclude>{{documentation/versioncheck}}</noinclude> | + | <noinclude>{{documentation/versioncheck}} |
− | <!-- ---------------------------- -->
| + | </noinclude> |
− | {{documentation/{{documentation/version}}/module-header}}
| |
− | <!-- ---------------------------- -->
| |
| | | |
− | {{TOC_right}}
| + | {{documentation/banner |
− | | + | | text = [https://slicer.readthedocs.io/en/latest/user_guide/modules/transforms.html This page has been moved to read-the-docs.] |
− | <!-- ---------------------------- -->
| + | | background-color = 8FBC8F }} |
− | {{documentation/{{documentation/version}}/module-section|Introduction and Acknowledgements}} | |
− | {{documentation/{{documentation/version}}/module-introduction-start|{{documentation/modulename}}}}
| |
− | {{documentation/{{documentation/version}}/module-introduction-row}}
| |
− | :'''Author(s)/Contributor(s):''' Alex Yarmarkovich (Isomics, SPL), Jean-Christophe Fillion-Robin (Kitware), Julien Finet (Kitware), Andras Lasso (PerkLab, Queen's), Franklin King (PerkLab, Queen's)<br>
| |
− | :'''Acknowledgements:''' This work is part of the [http://www.na-mic.org/ National Alliance for Medical Image Computing] (NA-MIC), funded by the National Institutes of Health through the NIH Roadmap for Medical Research, Grant U54 EB005149.<br>
| |
− | :'''Contact:''' Alex Yarmarkovich, <email>alexy@bwh.harvard.edu</email><br>
| |
− | {{documentation/{{documentation/version}}/module-introduction-row}}
| |
− | {{documentation/{{documentation/version}}/module-introduction-logo-gallery
| |
− | |{{collaborator|logo|isomics}}|{{collaborator|longname|isomics}}
| |
− | |{{collaborator|logo|kitware}}|{{collaborator|longname|kitware}}
| |
− | |{{collaborator|logo|namic}}|{{collaborator|longname|namic}}
| |
− | |{{collaborator|logo|nac}}|{{collaborator|longname|nac}}
| |
− | }}
| |
− | {{documentation/{{documentation/version}}/module-introduction-end}}
| |
− | | |
− | | |
− | <!-- ---------------------------- -->
| |
− | {{documentation/{{documentation/version}}/module-section|Module Description}}
| |
− | {{documentation/{{documentation/version}}/module-description}}
| |
− | | |
− | Features:
| |
− | | |
− | * '''Short video demonstrating the main features:''' http://screencast.com/t/Z6dQVjK3m
| |
− | * Support non-linear transforms in the Transforms module: allow Apply, Harden, Invert transform
| |
− | * Transform information displayed in the Transforms module (type of transform, basic properties)
| |
− | * Transform visualization:
| |
− | ** Built into the Transforms module
| |
− | ** Three main modes: Glyphs (show an array of arrows, cones, spheres), Grid (show a deformed grid), or Contour (show isolines/isosurfaces for specified displacement magnitude values)
| |
− | ** All transform types are supported (chains of transforms as well)
| |
− | ** Visualization in the slice viewers
| |
− | ** Visualization in the 3D viewers, in the specified region (region can be a slice viewer, a volume, or a ROI widget)
| |
− | ** Real-time update: if the transform (or any visualization parameter) is changed then the visualization is updated immediately (interactive visualization while editing the transform)
| |
− | ** Built-in colormap editor
| |
− | * MetaImage (mha), NIFTI (nii) vector volumes can be loaded as displacement field (grid) transform
| |
− | * Saving and loading to/from file: transforms are saved using ITK file readers/writers. However, ITK does not know how to interpret inverted transforms. Therefore if an inverted transform is saved to file then standard ITK applications may not be able to use the transform file. A workaround is to export the transform to a displacement field and save that to file.
| |
− | | |
− | <!-- ---------------------------- -->
| |
− | {{documentation/{{documentation/version}}/module-section|Use Cases}}
| |
− | Most frequently Transform module is used for these scenarios:
| |
− | | |
− | * Manual Registration: You can insert a transformation node into your scene, and in the Data module drag a volume or a model under it, making them children of the transformation node. After that any changes to the transformation matrix of this node will be applied to the display of children volumes and models.
| |
− | | |
− | * Visualize the displacement that transforms specify: Transforms can be visualized in both 2D and 3D views, as glyphs representing the displacement vectors as arrows, cones, or spheres; regular grids that are deformed by the transform; or contours that represent lines or surfaces where the displacement magnitude has a specific value.
| |
− | | |
− | * Apply transforms: You can dynamically transform a node by selecting them in the ''Transformable'' list and clicking the ''right arrow'' button. Whenever the transform changes, the transformed nodes are updated accordingly. The ''Harden Transform'' (its button is below the ''left arrow'' button) can be used for applying the transform to nodes permanently. Transforms themselves can be transformed, therefore chain of transforms can be constructed. Non-linear transforms can be concatenated, too. If non-linear transform is hardened on a volume then the volume is resampled using the same spacing and axis directions as the original volume (using linear interpolation). Extents are updated to fully contain the transformed volume. If image resampling modules are used then output image geometry and interpolator can be chosen.
| |
− | | |
− | <!-- ---------------------------- -->
| |
− | {{documentation/{{documentation/version}}/module-section|Tutorials}}
| |
− | * Please use tutorial about [[Documentation/4.0/Training|loading and viewing data]].
| |
− | | |
− | <!-- ---------------------------- -->
| |
− | {{documentation/{{documentation/version}}/module-section|Panels and their use}}
| |
− | | |
− | ===Transform editing and application===
| |
− | {|
| |
− | |-
| |
− | | [[Image:TransformsModule-43.png|thumb|280px|Transforms module panel]]
| |
− | | [[Image:QSlicerTransformsModule.png|thumb|660px|''LinearTransform'' applied to ''Meningioma2''<br>Rotation along IS 44º<br>Translations: LR -65mm, PA -14mm, IS 56mm]]
| |
− | |}
| |
− | | |
− | {{documentation/{{documentation/version}}/module-parametersdescription}}
| |
− | | |
− | ===Transform display options===
| |
− | {|
| |
− | |-
| |
− | | [[Image:GlyphArrow2d.png|thumb|280px|Glyph visualization (arrow, 2D): the arrow shows the displacement vector at the arrow starting point, projected to the slice]]
| |
− | | [[Image:GlyphCone2d.png|thumb|280px|Glyph visualization (cone, 2D): the cone shows the displacement vector at the cone centerpoint, projected to the slice]]
| |
− | | [[Image:GlyphSphere2d.png|thumb|280px|Glyph visualization (sphere, 2D): the circle diameter shows the displacement vector magnitude at the circle centerpoint]]
| |
− | |-
| |
− | | [[Image:GlyphArrow3dSlice.png|thumb|280px|Glyph visualization (arrow, 3D, slice region): the arrow shows the displacement vector at the arrow starting point]]
| |
− | | [[Image:GlyphCone3dVolumeRoi.png|thumb|280px|Glyph visualization (cone, 3D, annotation ROI region): the cone shows the displacement vector at the cone centerpoint]]
| |
− | | [[Image:GlyphSphere3dVolume.png|thumb|280px|Glyph visualization (sphere, 3D, volume region, with glyph magnitude filtering): the sphere diameter shows the displacement vector magnitude at the circle centerpoint]]
| |
− | |-
| |
− | | [[Image:Grid2d.png|thumb|280px|Grid visualization (2D): shows a regular grid, deformed by the displacement vector projected to the slice]]
| |
− | | [[Image:Grid3dSlice.png|thumb|280px|Grid visualization (3D, slice region): shows a regular grid, deformed by the displacement vector]]
| |
− | | [[Image:Grid3dVolume.png|thumb|280px|Grid visualization (3D, annotation ROI region): shows a regular grid, deformed by the displacement vector]]
| |
− | |-
| |
− | | [[Image:Contour2d.png|thumb|280px|Contour visualization (2D): iso-lines corresponding to selected displacement magnitude values]]
| |
− | | [[Image:Contour3dVolume.png|thumb|280px|Grid visualization (3D, volume region): iso-surfaces corresponding to selected displacement magnitude values]]
| |
− | |}
| |
− | | |
− | ==== Coloring ====
| |
− | | |
− | Open Transforms module / Display section / Colors section.
| |
− | If you click on a small circle then above the color bar you can see the small color swatch. On its left side is the points index (an integer that tells which point is being edited and that can be used to jump to the previous/next point), and on its right side is the mm value corresponding to that color.
| |
− | | |
− | The default colormap is:
| |
− | * 1mm (or below) = gray
| |
− | * 2mm = green
| |
− | * 5mm = yellow
| |
− | * 10mm (or above) = red
| |
− | | |
− | You can drag-and-drop any of the small circles or modify the mm value in the editbox. You can also add more color values by clicking on the color bar. Then, you can assign a color and/or adjust the mm value. If you click on a circle and press the DEL key then the color value is deleted.
| |
− | | |
− | If you need to know accurate displacement values at specific positions then switch to contour mode and in the “Levels” list enter all the mm values that you are interested in. For example, if you enter only a single value “3” in the Levels field you will see a curve going through the points where the displacement is exactly 3 mm; on one side of the curve the displacements are smaller, on the other side the displacements are larger.
| |
− | | |
− | You can show both contours and grid or glyph representations by loading the same transform twice and choosing a different representation for each.
| |
− | | |
− | <!-- ---------------------------- -->
| |
− | {{documentation/{{documentation/version}}/module-section|Similar Modules}}
| |
− | * Related modules: [[Documentation/{{documentation/version}}/Modules/Data|Data module]], [[:Category:Documentation/{{documentation/version}}/Modules/Registration|Registration modules]].
| |
− | | |
− | <!-- ---------------------------- -->
| |
− | {{documentation/{{documentation/version}}/module-section|References}}
| |
− | N/A
| |
− | | |
− | <!-- ---------------------------- -->
| |
− | {{documentation/{{documentation/version}}/module-section|Information for Developers}}
| |
− | | |
− | ===Key [[Documentation/{{documentation/version}}/Developers/MRML|MRML]] nodes===
| |
− | * [http://slicer.org/doc/html/classvtkMRMLTransformableNode.html vtkMRMLTransformableNode]: any node that can be transformed
| |
− | * [http://slicer.org/doc/html/classvtkMRMLTransformNode.html vtkMRMLTransformNode]: it can store any linear or deformable transform or composite of multiple transforms
| |
− | ** [http://slicer.org/doc/html/classvtkMRMLLinearTransformNode.html vtkMRMLLinearTransformNode]: Deprecated. The transform does exactly the same as vtkMRMLTransformNode but has a different class name, which are still used for showing only certain transform types in node selectors. In the future this class will be removed. A vtkMRMLLinearTransformNode may contain non-linear components after a non-linear transform is hardened on it. Therefore, to check linearity of a transform the vtkMRMLTransformNode::IsLinear() and vtkMRMLTransformNode::IsTransformToWorldLinear() and vtkMRMLTransformNode::IsTransformToNodeLinear() methods must be used instead of using vtkMRMLLinearTransformNode::SafeDownCast(transform)!=NULL.
| |
− | ** [http://slicer.org/doc/html/classvtkMRMLBSplineTransformNode.html vtkMRMLBSplineTransformNode]: Deprecated. The transform does exactly the same as vtkMRMLTransformNode but has a different class name, which are still used for showing only certain transform types in node selectors. In the future this class will be removed.
| |
− | ** [http://slicer.org/doc/html/classvtkMRMLGridTransformNode.html vtkMRMLGridTransformNode]: Deprecated. The transform does exactly the same as vtkMRMLTransformNode but has a different class name, which are still used for showing only certain transform types in node selectors. In the future this class will be removed.
| |
− | | |
− | ===Examples===
| |
− | | |
− | How to programmatically apply a [http://slicer.org/doc/html/classvtkMRMLTransformNode.html transform] to a transformable node:
| |
− | | |
− | <pre>
| |
− | vtkNew<vtkMRMLTransformNode> transformNode;
| |
− | scene->AddNode(transformNode.GetPointer());
| |
− | ...
| |
− | vtkNew<vtkMatrix4x4> matrix;
| |
− | ...
| |
− | transform->SetMatrixTransformToParent( matrix.GetPointer() );
| |
− | ...
| |
− | vtkMRMLVolumeNode* transformableNode = ...; // or vtkMRMLModelNode*...
| |
− | transformableNode->SetAndObserveTransformNodeID( transformNode->GetID() );
| |
− | </pre>
| |
− | | |
− | How to set a transformation matrix from a numpy array:
| |
− | | |
− | <pre>
| |
− | # Create a 4x4 transformation matrix as numpy array
| |
− | transformNode = ...
| |
− | transformMatrixNP = np.array(
| |
− | [[0.92979,-0.26946,-0.25075,52.64097],
| |
− | [0.03835, 0.74845, -0.66209, -46.12696],
| |
− | [0.36608, 0.60599, 0.70623, -0.48185],
| |
− | [0, 0, 0, 1]])
| |
− | | |
− | # Update matrix in transform node
| |
− | transformNode.SetAndObserveMatrixTransformToParent(slicer.util.vtkMatrixFromArray(transformMatrixNP))
| |
− | </pre>
| |
− | | |
− | Example of moving a volume along a trajectory using a transform
| |
− | | |
− | <pre>
| |
− | # Load sample volume
| |
− | import SampleData
| |
− | sampleDataLogic = SampleData.SampleDataLogic()
| |
− | mrHead = sampleDataLogic.downloadMRHead()
| |
− | | |
− | # Create transform and apply to sample volume
| |
− | transformNode = slicer.vtkMRMLTransformNode()
| |
− | slicer.mrmlScene.AddNode(transformNode)
| |
− | mrHead.SetAndObserveTransformNodeID(transformNode.GetID())
| |
− | | |
− | # How to move a volume along a trajectory using a transform:
| |
− | import time
| |
− | import math
| |
− | transformMatrix = vtk.vtkMatrix4x4()
| |
− | for xPos in range(-30,30):
| |
− | transformMatrix.SetElement(0,3, xPos)
| |
− | transformMatrix.SetElement(1,3, math.sin(xPos)*10)
| |
− | transformNode.SetMatrixTransformToParent(transformMatrix)
| |
− | slicer.app.processEvents()
| |
− | time.sleep(0.02)
| |
− | # Note: for longer animations use qt.QTimer.singleShot(100, callbackFunction)
| |
− | # instead of a for loop.
| |
− | </pre>
| |
− | | |
− | Because a ''transform'' node is also a ''transformable'' node, it is possible to concatenate transforms with each others:
| |
− | [http://slicer.org/doc/html/classvtkMRMLTransformNode.html vtkMRMLTransformNode*] transformNode = ...;
| |
− | [http://slicer.org/doc/html/classvtkMRMLTransformNode.html vtkMRMLTransformNode*] transformNode2 = ...;
| |
− | transformNode2->SetAndObserveTransformNodeID( transformNode->GetID() );
| |
− | ...
| |
− | transformable->SetAndObserveTransformNodeID( transformNode2->GetID() );
| |
− | | |
− | How to convert the transform to a grid transform (also known as displacement field transform)?
| |
− | | |
− | transformNode=slicer.util.getNode('LinearTransform_3')
| |
− | referenceVolumeNode=slicer.util.getNode('MRHead')
| |
− | slicer.modules.transforms.logic().ConvertToGridTransform(transformNode, referenceVolumeNode)
| |
− | | |
− | * Conversion to grid transform is useful because some software cannot use inverse transforms or can only use grid transforms.
| |
− | * Displacement field transforms are saved to file differently than displacement field volumes: displacement vectors in transforms are converted to LPS coordinate system on saving, displacement vectors in volumes are saved to file unchanged.
| |
− | | |
− | How to export the displacement magnitude of the transform as a volume?
| |
− | | |
− | transformNode=slicer.util.getNode('LinearTransform_3')
| |
− | referenceVolumeNode=slicer.util.getNode('MRHead')
| |
− | slicer.modules.transforms.logic().CreateDisplacementVolumeFromTransform(transformNode, referenceVolumeNode, False)
| |
− | | |
− | How to visualize the displacement magnitude as a color volume?
| |
− | | |
− | transformNode=slicer.util.getNode('LinearTransform_3')
| |
− | referenceVolumeNode=slicer.util.getNode('MRHead')
| |
− | slicer.modules.transforms.logic().CreateDisplacementVolumeFromTransform(transformNode, referenceVolumeNode, True)
| |
− | | |
− | ===Transform files===
| |
− | * Slicer stores transforms in VTK classes in memory, but uses ITK transform IO classes to read/write transforms to files. ITK's convention is to use LPS coordinate system as opposed to RAS coordinate system in Slicer (see [[Coordinate systems]] page for details). Conversion between VTK and ITK transform classes are implemented in [https://github.com/Slicer/Slicer/blob/master/Libs/MRML/Core/vtkITKTransformConverter.h vtkITKTransformConverter].
| |
− | * ITK stores the transform in resampling (a.k.a., image processing) convention, i.e., that transforms points from fixed to moving coordinate system. This transform is usable as is for resampling a moving image in the coordinate system of a fixed image. For transforming points and surface models to the fixed coordinate system, one needs the transform in the modeling (a.k.a. computer graphics) convention, i.e., transform from moving to fixed coordinate system (which is the inverse of the "image processing" convention).
| |
− | * Transform nodes in Slicer can store transforms in both modeling (when ToParent transform is set) and resampling way (when FromParent transform is set). When writing transform to ITK files, linear transforms are inverted as needed and written as an AffineTransform. Non-linear transforms cannot be inverted without losing information (in general), therefore if a non-linear transform is defined in resampling convention in Slicer then it is written to ITK file using special "Inverse" transform types (e.g., InverseDisplacementFieldTransform instead of DisplacementFieldTransform). Definition of the inverse classes are available in [https://github.com/Slicer/Slicer/blob/master/Libs/MRML/Core/vtkITKTransformInverse.h vtkITKTransformInverse]. The inverse classes are only usable for file IO, because currently ITK does not provide a generic inverse transform computation method. Options to manage inverse transforms in applications:
| |
− | ** Create VTK transforms from ITK transforms: VTK transforms can compute their inverse, transform can be changed dynamically, the inverse will be always updated automatically in real-time (this approach is used by Slicer)
| |
− | ** Invert transform in ITK statically: by converting to displacement field and inverting the displacement field; whenever the forward transform changes, the complete inverse transform has to be computed again (which is typically very time consuming)
| |
− | ** Avoid inverse non-linear transforms: make sure that non-linear transforms are only set as FromParent
| |
− | * Transforms module in Slicer shows linear transform matrix values in RAS coordinate system, according to resampling convention. Therefore to retrieve the same values from an ITK transforms as shown in Slicer GUI, one has switch between RAS/LPS and modeling/resampling:
| |
− | | |
− | '''C++''':
| |
− | <pre>
| |
− | // Convert from LPS (ITK) to RAS (Slicer)
| |
− | // input: transformVtk_LPS matrix in vtkMatrix4x4 in resampling convention in LPS
| |
− | // output: transformVtk_RAS matrix in vtkMatri4x4 in modeling convention in RAS
| |
− | | |
− | // Tras = lps2ras * Tlps * ras2lps
| |
− | vtkSmartPointer<vtkMatrix4x4> lps2ras = vtkSmartPointer<vtkMatrix4x4>::New();
| |
− | lps2ras->SetElement(0,0,-1);
| |
− | lps2ras->SetElement(1,1,-1);
| |
− | vtkMatrix4x4* ras2lps = lps2ras; // lps2ras is diagonal therefore the inverse is identical
| |
− | vtkMatrix4x4::Multiply4x4(lps2ras, transformVtk_LPS, transformVtk_LPS);
| |
− | vtkMatrix4x4::Multiply4x4(transformVtk_LPS, ras2lps, transformVtk_RAS);
| |
− | | |
− | // Convert the sense of the transform (from ITK resampling to Slicer modeling transform)
| |
− | vtkMatrix4x4::Invert(transformVtk_RAS);
| |
− | </pre>
| |
− | | |
− | '''Python''':
| |
− | <pre>
| |
− | # Copy the content between the following triple-quotes to a file called 'LinearTransform.tfm', and load into Slicer
| |
− | | |
− | tfm_file = """#Insight Transform File V1.0
| |
− | #Transform 0
| |
− | Transform: AffineTransform_double_3_3
| |
− | Parameters: 0.929794207512361 0.03834792453582355 -0.3660767246906854 -0.2694570325150706 0.7484457003494506 -0.6059884002657121 0.2507501531497781 0.6620864522947292 0.7062335947709847 -46.99999999999999 49 17.00000000000002
| |
− | FixedParameters: 0 0 0"""
| |
− | | |
− | import numpy as np
| |
− | | |
− | # get the upper 3x4 transform matrix
| |
− | m = np.array( tfm_file.splitlines()[3].split()[1:], dtype=np.float64 )
| |
− | | |
− | # pad to a 4x4 matrix
| |
− | m2 = np.vstack((m.reshape(4,3).T, [0,0,0,1]))
| |
− | | |
− | def itktfm_to_slicer(tfm):
| |
− | ras2lps = np.diag([-1, -1, 1, 1])
| |
− | mt = ras2lps @ m2 @ ras2lps
| |
− | mt[:3,3] = mt[:3,:3] @ mt[:3,3]
| |
− | return mt
| |
− | | |
− | print( itktfm_to_slicer(m2) )
| |
− | | |
− | # Running the code above in Python should print the following output.
| |
− | # This output should match the display the loaded .tfm file in the Transforms module:
| |
− | # [[ 0.92979 -0.26946 -0.25075 52.64097]
| |
− | # [ 0.03835 0.74845 -0.66209 -46.12696]
| |
− | # [ 0.36608 0.60599 0.70623 -0.48185]
| |
− | # [ 0. 0. 0. 1. ]]
| |
− | </pre>
| |
− | | |
− | ===Events=== | |
− | When a transform node is observed by a transformable node, [http://slicer.org/doc/html/classvtkMRMLTransformableNode.html#ace1c30fc9df552543f00d51a20c038a6a4993bf6e23a6dfc138cb2efc1b9ce43b vtkMRMLTransformableNode::TransformModifiedEvent] is fired on the transformable node at observation time.
| |
− | Anytime a transform is modified, vtkCommand::ModifiedEvent is fired on the transform node and [http://slicer.org/doc/html/classvtkMRMLTransformableNode.html#ace1c30fc9df552543f00d51a20c038a6a4993bf6e23a6dfc138cb2efc1b9ce43b vtkMRMLTransformableNode::TransformModifiedEvent] is fired on the transformable node.
| |
− | | |
− | <!-- ---------------------------- -->
| |
− | {{documentation/{{documentation/version}}/module-footer}}
| |
− | <!-- ---------------------------- -->
| |