Out of 48 possible orientations for a 3d image, the 24 left-handed orientations are treated incorrectly when saving a multi-frame dicom file or series.
import itk
nifti_image = itk.imread('avg152T1_LR_nifti.nii.gz')
nifti_direction = itk.array_from_vnl_matrix(
nifti_image.GetDirection().GetVnlMatrix().as_matrix())
# save as a dicom
itk.imwrite(nifti_image, 'avg152T1_LR_nifti.dcm')
# read the saved dicom
dicom_image = itk.imread('avg152T1_LR_nifti.dcm')
dicom_direction = itk.array_from_vnl_matrix(
dicom_image.GetDirection().GetVnlMatrix().as_matrix())
print(nifti_direction)
# [[ 1. 0. 0.]
# [ 0. -1. 0.]
# [ 0. 0. 1.]]
print(dicom_direction)
# [[ 1. 0. 0.]
# [ 0. -1. 0.]
# [ 0. 0. -1.]]
Actually, 1 leads to 2.
I should emphasize that this is not a bug of slicer nor itk-snap. The bug is in our dicom writer.
[[ 1. 0. 0.]
[ 0. -1. 0.]
[ 0. 0. 1.]]
The dicom's direction matrix (corresponding to RPS):
[[ 1. 0. 0.]
[ 0. -1. 0.]
[ 0. 0. -1.]]


100% for saving an image with left-handed orientation in multi-frame dicom, both single file (as shown here) and a series of 2d slices.
In general, it's relevant for 3d images with left-handed orientations (50% of all possible orientations).
Itk 5.0.1 (probably irrelevant)
Python 3.7.6, win10 (probably irrelevant)
The issue and some possible solutions have already been discussed here:
https://discourse.itk.org/t/nifti-rpi-orientation-file-exported-to-dicom-wrong-orientation/1612
The dicom orientation is encoded in the 6 numbers of Image Orientation (Patient) tag.
These 6 numbers correspond to the first and second column of the direction matrix. Let's call these columns u, v (vectors of length 3).
When saving the dicom, the 3rd column of nifti_direction - n, is discarded. When reading the dicom, it is inferred as the cross product of u and v:
However, in this way it can never be (-n)!
+1 for Fix 1.
The following table shows the original orientation of the image in itk vs. the orientation of the saved 3d dicom file (.dcm) corresponding to this image.
There are different columns for RGB and non-RGB cases:
Orientation | Right/Left-handed orientation | Success | Saved orientation | RGB success | RGB saved orientation
-- | -- | -- | -- | -- | --
RAI | R | TRUE | RAI | TRUE | RAI
IPR | R | TRUE | IPR | FALSE | RAI
PLS | R | TRUE | PLS | FALSE | RAI
LSP | R | TRUE | LSP | FALSE | RAI
ILP | R | TRUE | ILP | FALSE | RAI
PIL | R | TRUE | PIL | FALSE | RAI
LPI | R | TRUE | LPI | FALSE | RAI
SLA | R | TRUE | SLA | FALSE | RAI
ASL | R | TRUE | ASL | FALSE | RAI
LAS | R | TRUE | LAS | FALSE | RAI
IAL | R | TRUE | IAL | FALSE | RAI
ALI | R | TRUE | ALI | FALSE | RAI
LIA | R | TRUE | LIA | FALSE | RAI
SRP | R | TRUE | SRP | FALSE | RAI
PSR | R | TRUE | PSR | FALSE | RAI
RPS | R | TRUE | RPS | FALSE | RAI
SPL | R | TRUE | SPL | FALSE | RAI
SAR | R | TRUE | SAR | FALSE | RAI
RIP | R | TRUE | RIP | FALSE | RAI
ARS | R | TRUE | ARS | FALSE | RAI
IRA | R | TRUE | IRA | FALSE | RAI
AIR | R | TRUE | AIR | FALSE | RAI
RSA | R | TRUE | RSA | FALSE | RAI
PRI | R | TRUE | PRI | FALSE | RAI
ARI | L | FALSE | ARS | FALSE | RAI
LPS | L | FALSE | LPI | FALSE | RAI
SAL | L | FALSE | SAR | FALSE | RAI
IPL | L | FALSE | IPR | FALSE | RAI
PSL | L | FALSE | PSR | FALSE | RAI
IAR | L | FALSE | IAL | FALSE | RAI
PLI | L | FALSE | PLS | FALSE | RAI
LIP | L | FALSE | LIA | FALSE | RAI
RAS | L | FALSE | RAI | FALSE | RAI
SLP | L | FALSE | SLA | FALSE | RAI
IRP | L | FALSE | IRA | FALSE | RAI
ALS | L | FALSE | ALI | FALSE | RAI
PIR | L | FALSE | PIL | FALSE | RAI
LSA | L | FALSE | LSP | FALSE | RAI
ASR | L | FALSE | ASL | FALSE | RAI
SRA | L | FALSE | SRP | FALSE | RAI
ILA | L | FALSE | ILP | FALSE | RAI
AIL | L | FALSE | AIR | FALSE | RAI
RPI | L | FALSE | RPS | FALSE | RAI
RIA | L | FALSE | RIP | FALSE | RAI
SPR | L | FALSE | SPL | FALSE | RAI
PRS | L | FALSE | PRI | FALSE | RAI
RSP | L | FALSE | RSA | FALSE | RAI
LAI | L | FALSE | LAS | FALSE | RAI
Conclusions:
The table can be viewed also here:
https://docs.google.com/spreadsheets/d/1ieM8uYYDOBUwoA3_O8FBf7E_72HRb7exE5Sj6KFA0CY/edit?usp=sharing
The code for creating this table:
https://gist.github.com/jond01/c77fd1877aa20f6e9ac3cdb8755ec1be
(Edit: sorted the table by R/L.)
@thewtex
So after the issue has been characterized (+-),
I prefer fix 3 with negative Spacing Between Slices because it will not block the user from some orientations.
Regarding the failures in RGB 3d dicom - do you have any idea what is the reason behind that?
@jond01 outstanding investigation! :clap:
Many codes, including ITK, assume that spacing is a physical length that will always be positive, and undefined behavior, which may be wrong, can result when processing, so I think fix 1 is still preferred.
Not sure about the RGB limitation, @malaterre may have ideas.
_Spacing Between Slices_ (0018, 0088) is not taken into account for z-spacing calculation at all in GDCM/ITK in most cases. Long story why, but IMHO it is correct. Most tools rely only on image position (patient) and image orientation (patient) attributes and don't believe anything else.
I've briefly browse through the report, the first thing that pops up is the direct reference to NM module. I must admit I never realized negative value for Spacing Between Slices was acceptable. In my mind this was always like the Enh MR Image Storage:
I'll clarify that GDCM always compute the non-negative Spacing Between Slices in all cases.
Ok, thank you all!
So negative Spacing Between Slices is not a possible/adequate way of fixing left-handed orientations. Therefore, I conclude that there should be no left-handed dicoms, leading us to fix 1.
I hope to make a PR for this issue in the coming weeks, at least for the non-RGB case.
Another peculiar point which I did not mention - saving RGB dicom series is possible with all right-handed orientations and not only RAI.
Update:
The relevant lines in GDCM IO writer:
https://github.com/InsightSoftwareConsortium/ITK/blob/27258efa6ba74228958af7b5a620d005c644da51/Modules/IO/GDCM/src/itkGDCMImageIO.cxx#L1000-L1043
Update regarding the 3d RGB case:
I dug into the writer's internals - including GDCM - and turns out it's all fine.
The direction cosines are set properly, which can be verified with pydicom for example:
import pydicom
ds = pydicom.dcmread('path/to/3d_channelled_ARS.dcm')
orientation = ds.SharedFunctionalGroupsSequence[0].PlaneOrientationSequence[0].ImageOrientationPatient
print(orientation)
# [-0, 1, 0, 1, -0, 0]
Whereas in itk:
import itk
img = itk.imread('path/to/3d_channelled_ARS.dcm')
direction = itk.array_from_vnl_matrix(img.GetDirection().GetVnlMatrix().as_matrix())
print(direction)
# [[1. 0. 0.]
# [0. 1. 0.]
# [0. 0. 1.]]
The expected direction matrix is
[[-0. 1. 0.]
[ 1. -0. 0.]
[ 0. 0. -1.]]
(The first two columns of direction are the orientation in pydicom, the 3rd is their cross product.)
Perhaps, it deserves a different issue and PR as being related to the reader component and not the writer.
I could not reproduce problem with RGB image. Took _MHA_ RGB image in ASL orientation, exported to DICOM (actual ITK master), result is correct - _Multi-frame True Color Secondary Capture Image Storage_ has proper orientation. S. the test and input file (trivial, just read and write).
Import DICOM, export MHA seems to work too. May be something was updated, i remember i saw similar issue in the past, but now seems to work well. May be try latest release or master.

Hi @issakomi, thank you very much for your example and interest!
(I used a very similar example to test the RGB images, after changing the number of dimensions to 3.)
I reproduced your example, but there are still two separate issues regarding RGB dicom images:
python
img = itk.imread('test_rgb/input/t0.mha', pixel_type=itk.RGBPixel[itk.UC])
# the direction matrix is correct:
# itk.array_from_vnl_matrix(img.GetDirection().GetVnlMatrix().as_matrix())
itk.imwrite(img, 'test_rgb/input/t0_itkpy.dcm')
The result of your example is here:
t0.zip
Yes, itk-snap shows wrong orientation. Probably it was fixed here
https://github.com/malaterre/GDCM/commit/1767b5daae50e29da608ecddd3106ffd90c46bf4#diff-646c7508e8b2ed56649fd3d72843b0a5
This issue has been automatically marked as stale because it has not had recent activity. Thank you for your contributions.