Dear Marco,
Thanks a lot for all of these information, I corrected my implementation.
However I still have the same problem. Honestly I tried a lot of thing and I really don't understand what is wrong. It should work as it is...
Hereafter is a print off the parameters for:
instance 197:
# Used TransferSyntax: Little Endian Explicit
(0008,0008) CS [ORIGINAL\PRIMARY] # 16, 2 ImageType
(0008,0016) UI =PositronEmissionTomographyImageStorage # 28, 1 SOPClassUID
(0008,0018) UI [1.2.276.0.7230010.3.1.4.2088271309.3016061.1647341750.853876] # 60, 1 SOPInstanceUID
(0008,0020) DA [20220315] # 8, 1 StudyDate
(0008,0021) DA [20220315] # 8, 1 SeriesDate
(0008,0022) DA (no value available) # 0, 0 AcquisitionDate
(0008,0030) TM [115550] # 6, 1 StudyTime
(0008,0031) TM [115550] # 6, 1 SeriesTime
(0008,0032) TM (no value available) # 0, 0 AcquisitionTime
(0008,0050) SH (no value available) # 0, 0 AccessionNumber
(0008,0060) CS [PT] # 2, 1 Modality
(0008,0070) LO [Me] # 22, 1 Manufacturer
(0008,0090) PN (no value available) # 0, 0 ReferringPhysicianName
(0010,0010) PN [Jonh^Doe] # 10, 1 PatientName
(0010,0020) LO [1234] # 4, 1 PatientID
(0010,0030) DA (no value available) # 0, 0 PatientBirthDate
(0010,0040) CS (no value available) # 0, 0 PatientSex
(0018,0050) DS [0.9375] # 6, 1 SliceThickness
(0018,1181) CS (no value available) # 0, 0 CollimatorType
(0018,1242) IS (no value available) # 0, 0 ActualFrameDuration
(0020,000d) UI [1.2.276.0.7230010.3.1.4.2088271309.3016061.1647341748.853677] # 60, 1 StudyInstanceUID
(0020,000e) UI [1.2.276.0.7230010.3.1.4.2088271309.3016061.1647341748.853678] # 60, 1 SeriesInstanceUID
(0020,0010) SH [00001] # 6, 1 StudyID
(0020,0011) IS [00001] # 6, 1 SeriesNumber
(0020,0013) IS [197] # 4, 1 InstanceNumber
(0020,0032) DS [-46.875\-46.875\90.9375] # 24, 3 ImagePositionPatient
(0020,0037) DS [1\0\0\0\1\0] # 12, 6 ImageOrientationPatient
(0020,0052) UI [1.2.276.0.7230010.3.1.4.2088271309.3016061.1647341748.853676] # 60, 1 FrameOfReferenceUID
(0020,0060) CS (no value available) # 0, 0 Laterality
(0020,1040) LO (no value available) # 0, 0 PositionReferenceIndicator
(0020,1041) DS [90.9375] # 8, 1 SliceLocation
(0028,0002) US 1 # 2, 1 SamplesPerPixel
(0028,0004) CS [MONOCHROME2] # 12, 1 PhotometricInterpretation
(0028,0010) US 100 # 2, 1 Rows
(0028,0011) US 100 # 2, 1 Columns
(0028,0030) DS [0.9375\0.9375] # 14, 2 PixelSpacing
(0028,0051) CS (no value available) # 0, 0 CorrectedImage
(0028,0100) US 16 # 2, 1 BitsAllocated
(0028,0101) US 16 # 2, 1 BitsStored
(0028,0102) US 15 # 2, 1 HighBit
(0028,0103) US 0 # 2, 1 PixelRepresentation
(0028,1052) DS [0.00000] # 8, 1 RescaleIntercept
(0028,1053) DS [1.00000] # 8, 1 RescaleSlope
(0054,0016) SQ (Sequence with explicit length #=1) # 0, 1 RadiopharmaceuticalInformationSequence
(fffe,e000) na (Item with explicit length #=2) # 0, 1 Item
(0018,1078) DT (no value available) # 0, 0 RadiopharmaceuticalStartDateTime
(0054,0300) SQ (Sequence with explicit length #=1) # 0, 1 RadionuclideCodeSequence
(fffe,e000) na (Item with explicit length #=3) # 0, 1 Item
(0008,0100) SH [C-111A1] # 8, 1 CodeValue
(0008,0102) SH [SRT] # 4, 1 CodingSchemeDesignator
(0008,0104) LO [^18^Fluorine] # 12, 1 CodeMeaning
(fffe,e00d) na (ItemDelimitationItem for re-encoding) # 0, 0 ItemDelimitationItem
(fffe,e0dd) na (SequenceDelimitationItem for re-encod.) # 0, 0 SequenceDelimitationItem
(fffe,e00d) na (ItemDelimitationItem for re-encoding) # 0, 0 ItemDelimitationItem
(fffe,e0dd) na (SequenceDelimitationItem for re-encod.) # 0, 0 SequenceDelimitationItem
(0054,0081) US 1 # 2, 1 NumberOfSlices
(0054,0410) SQ (Sequence with explicit length #=1) # 0, 1 PatientOrientationCodeSequence
(fffe,e000) na (Item with explicit length #=3) # 0, 1 Item
(0008,0100) SH [ F-10450] # 8, 1 CodeValue
(0008,0102) SH [99SDM] # 6, 1 CodingSchemeDesignator
(0008,0104) LO [recumbent] # 10, 1 CodeMeaning
(fffe,e00d) na (ItemDelimitationItem for re-encoding) # 0, 0 ItemDelimitationItem
(fffe,e0dd) na (SequenceDelimitationItem for re-encod.) # 0, 0 SequenceDelimitationItem
(0054,0414) SQ (Sequence with explicit length #=1) # 0, 1 PatientGantryRelationshipCodeSequence
(fffe,e000) na (Item with explicit length #=3) # 0, 1 Item
(0008,0100) SH [F-10470] # 8, 1 CodeValue
(0008,0102) SH [99SDM] # 6, 1 CodingSchemeDesignator
(0008,0104) LO [headfirst] # 10, 1 CodeMeaning
(fffe,e00d) na (ItemDelimitationItem for re-encoding) # 0, 0 ItemDelimitationItem
(fffe,e0dd) na (SequenceDelimitationItem for re-encod.) # 0, 0 SequenceDelimitationItem
(0054,1000) CS [STATIC\IMAGE] # 12, 2 SeriesType
(0054,1001) CS [NONE] # 4, 1 Units
(0054,1002) CS [EMISSION] # 8, 1 CountsSource
(0054,1102) CS [NONE] # 4, 1 DecayCorrection
(0054,1300) DS [0.] # 2, 1 FrameReferenceTime
and instance 198:
# Used TransferSyntax: Little Endian Explicit
(0008,0008) CS [ORIGINAL\PRIMARY] # 16, 2 ImageType
(0008,0016) UI =PositronEmissionTomographyImageStorage # 28, 1 SOPClassUID
(0008,0018) UI [1.2.276.0.7230010.3.1.4.2088271309.3016061.1647341750.853877] # 60, 1 SOPInstanceUID
(0008,0020) DA [20220315] # 8, 1 StudyDate
(0008,0021) DA [20220315] # 8, 1 SeriesDate
(0008,0022) DA (no value available) # 0, 0 AcquisitionDate
(0008,0030) TM [115550] # 6, 1 StudyTime
(0008,0031) TM [115550] # 6, 1 SeriesTime
(0008,0032) TM (no value available) # 0, 0 AcquisitionTime
(0008,0050) SH (no value available) # 0, 0 AccessionNumber
(0008,0060) CS [PT] # 2, 1 Modality
(0008,0070) LO [Me] # 22, 1 Manufacturer
(0008,0090) PN (no value available) # 0, 0 ReferringPhysicianName
(0010,0010) PN [John^Doe] # 10, 1 PatientName
(0010,0020) LO [1234] # 4, 1 PatientID
(0010,0030) DA (no value available) # 0, 0 PatientBirthDate
(0010,0040) CS (no value available) # 0, 0 PatientSex
(0018,0050) DS [0.9375] # 6, 1 SliceThickness
(0018,1181) CS (no value available) # 0, 0 CollimatorType
(0018,1242) IS (no value available) # 0, 0 ActualFrameDuration
(0020,000d) UI [1.2.276.0.7230010.3.1.4.2088271309.3016061.1647341748.853677] # 60, 1 StudyInstanceUID
(0020,000e) UI [1.2.276.0.7230010.3.1.4.2088271309.3016061.1647341748.853678] # 60, 1 SeriesInstanceUID
(0020,0010) SH [00001] # 6, 1 StudyID
(0020,0011) IS [00001] # 6, 1 SeriesNumber
(0020,0013) IS [198] # 4, 1 InstanceNumber
(0020,0032) DS [-46.875\-46.875\91.875] # 22, 3 ImagePositionPatient
(0020,0037) DS [1\0\0\0\1\0] # 12, 6 ImageOrientationPatient
(0020,0052) UI [1.2.276.0.7230010.3.1.4.2088271309.3016061.1647341748.853676] # 60, 1 FrameOfReferenceUID
(0020,0060) CS (no value available) # 0, 0 Laterality
(0020,1040) LO (no value available) # 0, 0 PositionReferenceIndicator
(0020,1041) DS [91.875] # 6, 1 SliceLocation
(0028,0002) US 1 # 2, 1 SamplesPerPixel
(0028,0004) CS [MONOCHROME2] # 12, 1 PhotometricInterpretation
(0028,0010) US 100 # 2, 1 Rows
(0028,0011) US 100 # 2, 1 Columns
(0028,0030) DS [0.9375\0.9375] # 14, 2 PixelSpacing
(0028,0051) CS (no value available) # 0, 0 CorrectedImage
(0028,0100) US 16 # 2, 1 BitsAllocated
(0028,0101) US 16 # 2, 1 BitsStored
(0028,0102) US 15 # 2, 1 HighBit
(0028,0103) US 0 # 2, 1 PixelRepresentation
(0028,1052) DS [0.00000] # 8, 1 RescaleIntercept
(0028,1053) DS [1.00000] # 8, 1 RescaleSlope
(0054,0016) SQ (Sequence with explicit length #=1) # 0, 1 RadiopharmaceuticalInformationSequence
(fffe,e000) na (Item with explicit length #=2) # 0, 1 Item
(0018,1078) DT (no value available) # 0, 0 RadiopharmaceuticalStartDateTime
(0054,0300) SQ (Sequence with explicit length #=1) # 0, 1 RadionuclideCodeSequence
(fffe,e000) na (Item with explicit length #=3) # 0, 1 Item
(0008,0100) SH [C-111A1] # 8, 1 CodeValue
(0008,0102) SH [SRT] # 4, 1 CodingSchemeDesignator
(0008,0104) LO [^18^Fluorine] # 12, 1 CodeMeaning
(fffe,e00d) na (ItemDelimitationItem for re-encoding) # 0, 0 ItemDelimitationItem
(fffe,e0dd) na (SequenceDelimitationItem for re-encod.) # 0, 0 SequenceDelimitationItem
(fffe,e00d) na (ItemDelimitationItem for re-encoding) # 0, 0 ItemDelimitationItem
(fffe,e0dd) na (SequenceDelimitationItem for re-encod.) # 0, 0 SequenceDelimitationItem
(0054,0081) US 1 # 2, 1 NumberOfSlices
(0054,0410) SQ (Sequence with explicit length #=1) # 0, 1 PatientOrientationCodeSequence
(fffe,e000) na (Item with explicit length #=3) # 0, 1 Item
(0008,0100) SH [ F-10450] # 8, 1 CodeValue
(0008,0102) SH [99SDM] # 6, 1 CodingSchemeDesignator
(0008,0104) LO [recumbent] # 10, 1 CodeMeaning
(fffe,e00d) na (ItemDelimitationItem for re-encoding) # 0, 0 ItemDelimitationItem
(fffe,e0dd) na (SequenceDelimitationItem for re-encod.) # 0, 0 SequenceDelimitationItem
(0054,0414) SQ (Sequence with explicit length #=1) # 0, 1 PatientGantryRelationshipCodeSequence
(fffe,e000) na (Item with explicit length #=3) # 0, 1 Item
(0008,0100) SH [F-10470] # 8, 1 CodeValue
(0008,0102) SH [99SDM] # 6, 1 CodingSchemeDesignator
(0008,0104) LO [headfirst] # 10, 1 CodeMeaning
(fffe,e00d) na (ItemDelimitationItem for re-encoding) # 0, 0 ItemDelimitationItem
(fffe,e0dd) na (SequenceDelimitationItem for re-encod.) # 0, 0 SequenceDelimitationItem
(0054,1000) CS [STATIC\IMAGE] # 12, 2 SeriesType
(0054,1001) CS [NONE] # 4, 1 Units
(0054,1002) CS [EMISSION] # 8, 1 CountsSource
(0054,1102) CS [NONE] # 4, 1 DecayCorrection
(0054,1300) DS [0.] # 2, 1 FrameReferenceTime
The instanceUID are different and all the other parameters looks good.
Is there a way to close the file into which an instance is saved after saving?
I noticed that if I save only instance 0, the expected image (plan XY at pixel z = 0) is correctly saved. The file contains only instance 0. The more instances are saved, the more instances are added to the same file. It seems the previous instances are kept in memory somewhere.
Hereafter is the code I am using the save my files. Let me know if you notice something wrong.
In the main.cpp
Code: Select all
DicomFormat dicomImage(vectorOfPixelEntireImage, outputFile);
dicomImage.saveToDicomFormat();
DicomFormat class
Code: Select all
#include "DicomFormat.h"
#include <boost/lexical_cast.hpp>
#include <boost/numeric/conversion/cast.hpp>
DicomFormat::DicomFormat(const std::vector<float>& data, const std::string outputFileName):
m_data(data),
m_outputFile(outputFileName)
{
dcmGenerateUniqueIdentifier(m_frameUID, SITE_INSTANCE_UID_ROOT);
dcmGenerateUniqueIdentifier(m_studyUID, SITE_INSTANCE_UID_ROOT);
dcmGenerateUniqueIdentifier(m_seriesUID, SITE_INSTANCE_UID_ROOT);
}
DicomFormat::~DicomFormat()
{
}
void DicomFormat::saveToDicomFormat()
{
for(uint z = 0; z < nPixelsZDirection; ++z) { //nInstance = 200 (=200 pixels in z direction)
std::string file_extension = m_outputFile + "_image_" + std::to_string(z) +".dcm";
const char* fileName = file_extension.c_str();
DcmFileFormat fileformat;
DcmDataset* dataset = fileformat.getDataset();
setGeneralHeaderParameters(dataset);
setModulePatientParameters(dataset);
setModuleStudyParameters(dataset);
setModuleSeriesParameters(dataset);
dataset->putAndInsertString(DCM_SOPClassUID, "1.2.840.10008.5.1.4.1.1.128"); //PET image class
char instanceUID[100];
dataset->putAndInsertString(DCM_SOPInstanceinstanceUID, dcmGenerateUniqueIdentifier(instanceUID, SITE_INSTANCE_UID_ROOT));
setModuleImageParameters(dataset, z);
fileformat.saveFile(fileName, EXS_LittleEndianExplicit, EET_UndefinedLength, EGL_recalcGL, EPD_withoutPadding, 0, 0, EWM_createNewMeta);
}
}
void DicomFormat::setGeneralHeaderParameters(DcmDataset* dataset)
{
setModuleFrameOfReferenceParameters(dataset);
setModuleEquipementParameters(dataset);
}
void DicomFormat::setModuleFrameOfReferenceParameters(DcmDataset* dataset)
{
// Mandatory Frame of Reference elements
dataset->putAndInsertString(DCM_FrameOfReferenceUID, m_frameUID);
dataset->putAndInsertString(DCM_PositionReferenceIndicator, "");
}
void DicomFormat::setModuleEquipementParameters(DcmDataset* dataset)
{
// Mandatory General equipment elements
dataset->putAndInsertString(DCM_Manufacturer, "Me");
}
void DicomFormat::setModulePatientParameters(DcmDataset* dataset)
{
// Mandatory Patient module attributes
dataset->putAndInsertString(DCM_PatientName, "John^Doe");
dataset->putAndInsertString(DCM_PatientID, "1234"); //read from the GUI
dataset->putAndInsertString(DCM_PatientBirthDate, "");
dataset->putAndInsertString(DCM_PatientSex, "");
}
void DicomFormat::setModuleStudyParameters(DcmDataset* dataset)
{
// Mandatory General Study elements
dataset->putAndInsertString(DCM_StudyInstanceUID, m_studyUID);
OFString s;
DcmDate::getCurrentDate(s);
dataset->putAndInsertOFStringArray(DCM_StudyDate, s);
DcmTime::getCurrentTime(s);
dataset->putAndInsertOFStringArray(DCM_StudyTime, s);
dataset->putAndInsertString(DCM_ReferringPhysicianName, ""); // read from the GUI
dataset->putAndInsertString(DCM_StudyID, "00001"); // study identifier (the clinical trial ID.patientID)
dataset->putAndInsertOFStringArray(DCM_AccessionNumber, "");
}
void DicomFormat::setModuleSeriesParameters(DcmDataset* dataset)
{
// Mandatory General Series elements
dataset->putAndInsertString(DCM_SeriesInstanceUID, m_seriesUID);
dataset->putAndInsertOFStringArray(DCM_Modality, "PT");
dataset->putAndInsertString(DCM_SeriesNumber, "00001"); //number that identifies this serie
dataset->putAndInsertString(DCM_Laterality, "");
// Mandatory PET Series elements
OFString s;
DcmDate::getCurrentDate(s);
dataset->putAndInsertOFStringArray(DCM_SeriesDate, s); //cannot be empty
DcmTime::getCurrentTime(s);
dataset->putAndInsertOFStringArray(DCM_SeriesTime, s); //cannot be empty
dataset->putAndInsertString(DCM_CollimatorType, "");
dataset->putAndInsertString(DCM_CorrectedImage, "");
dataset->putAndInsertUint16(DCM_NumberOfSlices, static_cast<Uint16>(1)); // = number of instances
dataset->putAndInsertString(DCM_SeriesType, R"(STATIC\IMAGE)");
dataset->putAndInsertString(DCM_Units, "NONE");
dataset->putAndInsertString(DCM_CountsSource, "EMISSION");
dataset->putAndInsertString(DCM_DecayCorrection, "NONE");
// Mandatory PET isotope elements
m_dcm_item = nullptr;
dataset->findOrCreateSequenceItem(DCM_RadiopharmaceuticalInformationSequence, m_dcm_item);
m_dcm_item->putAndInsertOFStringArray(DCM_RadiopharmaceuticalStartDateTime, ""); //optionnal but Slicer3D complains if not given
m_dcm_item->findOrCreateSequenceItem(DCM_RadionuclideCodeSequence, m_dcm_item);
m_dcm_item->putAndInsertString(DCM_CodeValue, "C-111A1");
m_dcm_item->putAndInsertString(DCM_CodeMeaning, "^18^Fluorine");
m_dcm_item->putAndInsertString(DCM_CodingSchemeDesignator, "SRT");
// Mandatory NM/PET Patient Orientation elements
m_dcm_item = nullptr;
dataset->findOrCreateSequenceItem(DCM_PatientOrientationCodeSequence, m_dcm_item);
m_dcm_item->putAndInsertString(DCM_CodeValue, " F-10450");
m_dcm_item->putAndInsertString(DCM_CodeMeaning, "recumbent");
m_dcm_item->putAndInsertString(DCM_CodingSchemeDesignator, "99SDM");
m_dcm_item = nullptr;
dataset->findOrCreateSequenceItem(DCM_PatientGantryRelationshipCodeSequence, m_dcm_item);
m_dcm_item->putAndInsertString(DCM_CodeValue, "F-10470");
m_dcm_item->putAndInsertString(DCM_CodeMeaning, "headfirst");
m_dcm_item->putAndInsertString(DCM_CodingSchemeDesignator, "99SDM");
}
void DicomFormat::setModuleImageParameters(DcmDataset* dataset, const uint z)
{
const double slicePosition = -pixelSize*nPixelsZDirection/2 + z*pixelSize;
// Mandatory General Image elements
dataset->putAndInsertString(DCM_InstanceNumber, boost::lexical_cast<std::string>(z).c_str());
// Mandatory Image plane elements
dataset->putAndInsertString(DCM_SliceThickness, boost::lexical_cast<std::string>(pixelSize).c_str());
dataset->putAndInsertString(DCM_SliceLocation, boost::lexical_cast<std::string>(slicePosition).c_str()); //optional
std::ostringstream sstr;
sstr.str("");
sstr << -pixelSize*nPixelsXDirection/2 << R"(\)" << -pixelSize*nPixelYDirection/2 << R"(\)" << slicePosition;
// sstr << -pixelSize*nPixelsXDirection/2 << R"(\)" << -pixelSize*nPixelYDirection/2;
dataset->putAndInsertString(DCM_ImagePositionPatient, sstr.str().c_str());
sstr.str("");
sstr << 1 << R"(\)" << 0 << R"(\)" << 0 << R"(\)" << 0 << R"(\)" << 1 << R"(\)" << 0;
dataset->putAndInsertString(DCM_ImageOrientationPatient, sstr.str().c_str());
sstr.str("");
sstr << pixelSize << R"(\)" << pixelSize;
dataset->putAndInsertString(DCM_PixelSpacing, sstr.str().c_str());
// Mandatory Image pixel elements
dataset->putAndInsertString(DCM_SamplesPerPixel, "1");
dataset->putAndInsertString(DCM_PhotometricInterpretation, "MONOCHROME2");
dataset->putAndInsertUint16(DCM_PixelRepresentation, 0);
dataset->putAndInsertUint16(DCM_Rows, static_cast<Uint16>(nPixelsXDirection));
dataset->putAndInsertUint16(DCM_Columns, static_cast<Uint16>(nPixelYDirection));
dataset->putAndInsertString(DCM_BitsAllocated, "16");
dataset->putAndInsertString(DCM_BitsStored, "16");
dataset->putAndInsertString(DCM_HighBit, "15");
// Mandatory PET Image elements
dataset->putAndInsertString(DCM_ImageType, R"(ORIGINAL\PRIMARY)");
dataset->putAndInsertOFStringArray(DCM_AcquisitionDate, "");
dataset->putAndInsertOFStringArray(DCM_AcquisitionTime, "");
dataset->putAndInsertString(DCM_ActualFrameDuration, "");
dataset->putAndInsertString(DCM_RescaleIntercept, "0.00000"); // always 0. for PET images
dataset->putAndInsertString(DCM_RescaleSlope, "1.00000");
dataset->putAndInsertString(DCM_FrameReferenceTime, "0.");
// Pixel Data
Uint16* pixelData = convertFloatDataIntoUint(z);
fillPixelDataAttribute(dataset, pixelData);
}
void DicomFormat::fillPixelDataAttribute(DcmDataset* dataset, const Uint16* pixelData)
{
auto pixelCount = static_cast<unsigned int>(nPixelsXDirection*nPixelYDirection);
// // first method
// DcmPixelData* newPixelData = new DcmPixelData(DCM_PixelData);
// newPixelData->putUint16Array(pixelData, pixelCount);
// dataset->insert(newPixelData, OFTrue);
// Second method (same results as first)
dataset->putAndInsertUint16Array(DCM_PixelData, pixelData, pixelCount);
}
Uint16* DicomFormat::convertFloatDataIntoUint(const uint z)
{
const int pixelCount = static_cast<int>(nPixelsXDirection*nPixelYDirection);
std::vector<Uint16> temp(pixelCount, 0);
int pixelPos = 0;
for(uint y = 0; y < nPixelYDirection; ++y) {
for(uint x = 0; x < nPixelXDirection; ++x) {
const int32_t vox = getVoxID(x, y, z);
temp[pixelPos] = boost::numeric_cast<Uint16>(m_data[vox]);
pixelPos++;
}
}
Uint16* pixelData = new Uint16[pixelCount];
std::copy(temp.begin(), temp.end(), pixelData);
return pixelData;
}