It happens because GDCM uses Multi Frame Grayscale Byte SC Image IOD Modules.
Internally GDCM takes origin and adds positions one by one, calculating right handed cross product, so far GDCM does nothing wrong, but RPI orientation is not compatible with this approach. IMHO, IO could catch incompatible orientations and apply “change orientation filter” (RPI to RPS in particular case) to re-slice such images to compatible orientation.
Code to reproduce (trivial, read nifti, save dcm, nothing more): Test
There are already enough messy multi-frame files, were good not produce more.
Sincerely
P.S. It is not related to discussion about sorting frames in multi-frame images several months ago. Here sorting will not help, wrong positions are written in the image.
Sorry i don have this problem in my app and will not work on the issue.
Edit:
another screenshot (exported image), from Slicer
Don’t understand me wrong, ITK handles image orientation ideally, it is not a problem with ITK’s orientation design, it is just required step before exporting to multi-frame DICOM.
If someone cares - link to demo program with required re-orientation step before exporting to multi-frame DICOM . All 48 possible orientations test images (in MHA format with R/L marks) are also inside.
Honestly, i didn’t know where to do the step - in itk::GDCMImageIO void GDCMImageIO::Write(const void *buffer)
works already with void * buffer
and direction is taken from metadata ExposeMetaData< DoubleMatrixType >( dict, key, directionMatrix );
or already set, probably here is “too late”, i have to learn how IO factory works exactly
Looked at image IO.
IMHO, filter could be applied here itkImageFileWriter.hxx if IO is GDCM image IO (DCMTK too? never tried) and if image dimension is 3.
Changing of metadata is not required.
For justification (another one):
For DICOM file 6 values from direction matrix will be taken (x and y direction), because 3rd is defined in DICOM as right handed cross product. From GDCM IO:
// Do the direction now:
// if the meta dictionary contains the tag "0020 0037", use it
const bool hasIOP = ExposeMetaData<std::string>(dict, "0020|0037",tempString);
if (hasIOP)
{
double directions[6];
sscanf(tempString.c_str(), "%lf\\%lf\\%lf\\%lf\\%lf\\%lf", &(directions[0]), &(directions[1]), &(directions[2]),&(directions[3]),&(directions[4]),&(directions[5]));
image.SetDirectionCosines(0, directions[0]);
image.SetDirectionCosines(1, directions[1]);
image.SetDirectionCosines(2, directions[2]);
image.SetDirectionCosines(3, directions[3]);
image.SetDirectionCosines(4, directions[4]);
image.SetDirectionCosines(5, directions[5]);
}
else
{
image.SetDirectionCosines(0, m_Direction[0][0]);
image.SetDirectionCosines(1, m_Direction[0][1]);
if ( m_Direction.size() == 3 )
{
image.SetDirectionCosines(2, m_Direction[0][2]);
}
else
{
image.SetDirectionCosines(2, 0);
}
image.SetDirectionCosines(3, m_Direction[1][0]);
image.SetDirectionCosines(4, m_Direction[1][1]);
if ( m_Direction.size() == 3 )
{
image.SetDirectionCosines(5, m_Direction[1][2]);
}
else
{
image.SetDirectionCosines(5, 0);
}
}
E.g. RAI and RAS:
1 0 0
0 1 0
0 0 1
and
1 0 0
0 1 0
0 0 -1
For both orientations, the same Image Orientation Patient “1, 0, 0, 0, 1, 0” will be assumed and for multi-frame image GDCM will calculate wrong slice origins for RAS image.
It is tricky since itkImageFileWriter.hxx cannot depend on itkGDCMImageIO.h – itkGDCMImageIO.h may not be available if the ITKIOGDCM module is not enabled, and the ITKIOImageBase module cannot depend on on the ITKIOGDCM module (the reverse dependency exists).
In one approach, if DCMTK behaves the same, we could apply the fix for output files with the .dcm.
Or, we could apply the fix in itkGDCMImageIO.cxx, even though it is more difficult since the image has been decomposed.
@matt.mccormick you are right, of course. ImageFileWriter is bad place, the problem is - some things happen in ::Write(), some in ::GenerateData(), always accessing image like const InputImageType *input = this->GetInput();
and buffered region, streaming, pipeline, many possible issues. It is possible, but will require too many changes, not worth… Probably possible ways are either do the job in GDCM IO class, as you have suggested, also tricky, or just give a warning (or error). I shall play again little later… Thank you
Apparently, every dicom (single 3d image or dicom series) with a left-handed orientation (24 out of 48, e.g. RAS) is saved in a wrong way. I think that it is a serious bug, but couldn’t find one in the open issues.
I can provide a short python code which demonstrates the issue.
Maybe a solution will be to add a (-1) factor to the “Spacing Between Slices” tag for left-handed orientations?
i also think it is critical issue, actually best workaround, IMHO, is to apply change orientation filter for incompatible orientations (e.g. RAS to RAI) before exporting to multi-frame DICOM.