Align Motion rigid-step conventions with Affine - #1507
Conversation
Motion's per-segment rigid transforms interpreted the same Euler angles and translation differently from Affine: the parameters acted on the reversed grid axes (x, y, z) = (k, j, i), moved the image in the inverse direction, and each translation component was normalized by the extent of the wrong axis, so non-cubic volumes translated by the wrong distance and rotated with an anisotropic shear instead of a rigid rotation. Build the affine_grid matrices by conjugating the inverse of the voxel-space rigid transform into normalized grid coordinates, so degrees and translation now follow the Affine conventions: right-handed rotations about the tensor axes (i, j, k) pivoting on the image center, and translation in voxels along (i, j, k). For an image with an identity affine, Motion's rigid step matches Affine(degrees=..., translation=..., center="image") exactly, and the docstrings state the shared convention and the remaining voxel-space vs world-space distinction. Addresses TorchIO-project#812.
|
Hi About voxel versus mm space for the motion parameter, I do not know if one is preferred. I would say it is an arbitrary choice ... |
|
Hi Romain — thanks for taking a look! The application is augmentation calibration for medical-imaging classifiers. I work on pelvic-MRI models where we sweep motion severity (degrees/translation) to roughly match the artifact levels we actually see in patients, and to test how robust the model is as severity increases. That whole exercise assumes the parameters mean the same geometry everywhere: before this change, a sweep over On voxel vs mm: agreed it's ultimately a convention choice. I kept |
|
thanks for the precision, I still do not fully understand why you need Affine and Motion to perform similarly since you are using them independently. (May be you want to constrain the motion, and therefore you'll need to fully understand the parameters ) your said :" and on non-cubic volumes the "rigid" step included a shear that no real scanner motion produces " this look like a real problem also I do not see why it happend (this does not happen after your fix ?) |
|
Hi Romain, two parts. Why parity, if they are used independently: it is about calibration, and your parenthetical is essentially it. I sweep Where the shear came from: Yes, the fix removes it. The PR conjugates the intended voxel-space rigid transform into normalized coordinates ( |
Addresses #812.
Description
MotionandAffineinterpreted the same Euler angles and translation as different geometric transforms. Pinning down the two parametrizations on an identity-affine test volume:Affine(viaSpatial): right-handed rotations about the world axes,R = Rz @ Ry @ Rx, pivoting on the image center, translation in mm along RAS+, applied push-forward (translation=(8, 0, 0)moves the content +8 along the first axis).Motion's rigid step built itsaffine_gridmatrix directly from the raw parameters, which meant: the components acted on the reversed grid axes ((x, y, z) = (k, j, i)), the content moved in the inverse direction (affine_gridmatrices map output to input coordinates), and each translation component was normalized by the extent of the wrong axis (component 0 divided by the first-axis size but applied along the last axis). Concretely, on a(32, 48, 64)volume,translation=(8, 0, 0)moved the content by −15.75 voxels along the last axis. Rotations were also only rigid for cubic volumes: for non-cubic shapes the per-axis normalization turned them into anisotropic shears, even though the transform documents rigid-body motion.This PR rebuilds the
affine_gridmatrices by conjugating the inverse of the intended voxel-space rigid transform into normalized grid coordinates (axis reversal + per-axis(size − 1) / 2scaling,align_corners=True).Motion'sdegreesandtranslationnow follow the same conventions asAffine(center="image"), interpreted in voxel space:degrees: right-handed Euler angles about the tensor axes(i, j, k), composed asR = R_k @ R_j @ R_i, pivoting on the image center.translation: voxels along(i, j, k), positive direction = increasing index.For an image whose affine is the identity, the rigid step now matches
Affine(degrees=..., translation=..., center="image")to float32 precision (the parity test asserts this on a non-cubic volume for pure translations, pure rotations about each axis, and a combined transform). Rotations are now truly rigid for non-cubic volumes, and integer-voxel translations shift by exactly that many voxels (asserted againsttorch.roll).The docstrings now state the shared convention explicitly, including the one remaining, deliberate difference:
Motionoperates in voxel space and ignores the image affine, whileAffineis world-space. For anisotropic spacings or non-RAS orientations the same numbers therefore still describe different world-space motions.Question for the maintainers: is voxel-space the intended contract for v2's
Motion(the previous docstring suggested so), or would you preferMotionto interpret its parameters in world space using the image affine, fully matchingAffine? The second option is a straightforward follow-up on top of this PR (conjugate through the image affine instead of only the normalization), at the cost of making the k-space corruption depend on image metadata. If voxel-space is the intent, this PR resolves the issue and it can be closed.Notes on compatibility: for the default symmetric usage the sampling distribution is unchanged (axis reversal and sign flips are symmetries of the parameter ranges), but outputs for a fixed seed or for deterministic parameters change, since the previous mapping from parameters to geometry was incorrect. The$\mathcal{U}(-d, d)$ ; they now describe the actual shared value/range/distribution convention (a scalar is deterministic), matching
degrees/translationdocstrings also previously claimed a scalar samples fromSpatial.Checklist
CONTRIBUTINGdocs and have a developer setup ready