From a4e90dfd1db5540f6be0c814f01d439988e28eec Mon Sep 17 00:00:00 2001 From: Krad Date: Fri, 16 Sep 2022 15:28:18 +0800 Subject: [PATCH] Add Multi-frame data load support, change some overlay data load logic. --- src/src/IO/DICOM/DICOMFileObjectCache.cpp | 38 ++ src/src/IO/DICOM/DICOMFileObjectCache.h | 29 ++ src/src/IO/DICOM/DICOMHeaderHelper.cpp | 396 ++++++++++++------ src/src/IO/DICOM/DICOMHeaderHelper.h | 23 +- src/src/IO/DICOM/DICOMPixelDataHelper.cpp | 94 ++++- src/src/IO/DICOM/DICOMPixelDataHelper.h | 10 +- .../IO/DICOM/ExtendMedicalImageProperties.h | 10 +- src/src/IO/DICOM/vtkDICOMImageReader2.cpp | 31 +- 8 files changed, 460 insertions(+), 171 deletions(-) create mode 100644 src/src/IO/DICOM/DICOMFileObjectCache.cpp create mode 100644 src/src/IO/DICOM/DICOMFileObjectCache.h diff --git a/src/src/IO/DICOM/DICOMFileObjectCache.cpp b/src/src/IO/DICOM/DICOMFileObjectCache.cpp new file mode 100644 index 0000000..6d8c21b --- /dev/null +++ b/src/src/IO/DICOM/DICOMFileObjectCache.cpp @@ -0,0 +1,38 @@ +// +// Created by Krad on 2022/9/16. +// + +#include "DICOMFileObjectCache.h" + +#include "dcmtk/dcmdata/dcfilefo.h" + +void DICOMFileObjectCache::store(const char *filename, DcmFileFormat *file) { + if (!file) return; + if (storage.count(filename) > 0){ + delete storage[filename]; + } + storage[filename] = file; +} + +void DICOMFileObjectCache::remove(const char *filename) { + if (storage.count(filename)>0){ + delete storage[filename]; + storage.erase(filename); + } +} + +void DICOMFileObjectCache::clear() { + std::for_each(storage.begin(),storage.end(), [](auto v){ + delete v.second; + }); + storage.clear(); +} + +bool DICOMFileObjectCache::contains(const char *filename) { + return storage.count(filename) > 0; +} + +DcmFileFormat *DICOMFileObjectCache::GetFileObject(const char *filename) { + if (!contains(filename)) return nullptr; + return storage[filename]; +} diff --git a/src/src/IO/DICOM/DICOMFileObjectCache.h b/src/src/IO/DICOM/DICOMFileObjectCache.h new file mode 100644 index 0000000..a7759da --- /dev/null +++ b/src/src/IO/DICOM/DICOMFileObjectCache.h @@ -0,0 +1,29 @@ +// +// Created by Krad on 2022/9/16. +// + +#ifndef OMEGAV_DICOMFILEOBJECTCACHE_H +#define OMEGAV_DICOMFILEOBJECTCACHE_H + +#include + +class DcmFileFormat; +class DICOMFileObjectCache { +public: + static DICOMFileObjectCache* getInstance(){ + static DICOMFileObjectCache cache; + return &cache; + } + + void store(const char* filename, DcmFileFormat* file); + void remove(const char* filename); + void clear(); + bool contains(const char* filename); + DcmFileFormat* GetFileObject(const char* filename); + +private: + std::unordered_map storage; +}; + + +#endif //OMEGAV_DICOMFILEOBJECTCACHE_H diff --git a/src/src/IO/DICOM/DICOMHeaderHelper.cpp b/src/src/IO/DICOM/DICOMHeaderHelper.cpp index 94f89a7..8c2dcac 100644 --- a/src/src/IO/DICOM/DICOMHeaderHelper.cpp +++ b/src/src/IO/DICOM/DICOMHeaderHelper.cpp @@ -4,6 +4,7 @@ #include "DICOMHeaderHelper.h" #include "dcmtk/dcmdata/dcfilefo.h" +#include "dcmtk/dcmdata/dcdeftag.h" #include "dcmtk/dcmdata/dcdatset.h" #include "dcmtk/dcmdata/dcspchrs.h" #include "ExtendMedicalImageProperties.h" @@ -14,42 +15,6 @@ #include #include -struct DICOMTag{ - Uint16 group; - Uint16 element; - const char * des; -}; -DICOMTag dicom_tags[] = { - {0x0002, 0x0002, "Media storage SOP class uid"}, - {0x0002, 0x0003, "Media storage SOP inst uid"}, - {0x0002, 0x0010, "Transfer syntax uid"}, - {0x0002, 0x0012, "Implementation class uid"}, - {0x0008, 0x0018, "Image UID"}, - {0x0008, 0x0020, "Series date"}, - {0x0008, 0x0030, "Series time"}, - {0x0008, 0x0060, "Modality"}, - {0x0008, 0x0070, "Manufacturer"}, - {0x0008, 0x1060, "Physician"}, - {0x0018, 0x0050, "slice thickness"}, - {0x0018, 0x0060, "kV"}, - {0x0018, 0x0088, "slice spacing"}, - {0x0018, 0x1100, "Recon diameter"}, - {0x0018, 0x1151, "mA"}, - {0x0018, 0x1210, "Recon kernel"}, - {0x0020, 0x000d, "Study UID"}, - {0x0020, 0x000e, "Series UID"}, - {0x0020, 0x0013, "Image number"}, - {0x0020, 0x0032, "Patient position"}, - {0x0020, 0x0037, "Patient position cosines"}, - {0x0020, 0x1041, "Slice location"}, - {0x0028, 0x0010, "Num rows"}, - {0x0028, 0x0011, "Num cols"}, - {0x0028, 0x0030, "pixel spacing"}, - {0x0028, 0x0100, "Bits allocated"}, - {0x0028, 0x0120, "pixel padding"}, - {0x0028, 0x1052, "pixel offset"} -}; - void DICOMHeaderHelper::Update() { ReadHeader(); ArrangeSeriesProperty(); @@ -107,71 +72,177 @@ void DICOMHeaderHelper::readHeaderFromFile(const char * filePath){ // read success! if (file.loadFile(filePath).good()) { DcmDataset *dataset = file.getDataset(); - DICOMFileHeader header; - header.FilePath = filePath; - std::string SeriesUID; - dataset->findAndGetOFString(DcmTagKey(0x0020, 0x000e), SeriesUID); - long SeriesNumber = 0; - dataset->findAndGetSint32(DcmTagKey(0x0020, 0x0011), header.SeriesNumber); - long AcquisitionNumber = 0; - dataset->findAndGetSint32(DcmTagKey(0x0020, 0x0012), header.AcquisitionNumber); - long InstanceNumber = 0; - dataset->findAndGetSint32(DcmTagKey(0x0020, 0x0013), header.InstanceNumber); + DICOMFileHeader fileHeader; - #define ReadTAGToProperty(Name, group, element)\ - std::string Name;\ - dataset->findAndGetOFString(DcmTagKey(group, element), Name); + fileHeader.FilePath = filePath; - ReadTAGToProperty(PatientName, 0x0010, 0x0010) - ReadTAGToProperty(StudyUID, 0x0020, 0x000d) + dataset->findAndGetOFString(DCM_SeriesInstanceUID, fileHeader.SeriesUID); - dataset->findAndGetUint16(DcmTagKey(0x0028, 0x0010), header.Rows); - dataset->findAndGetUint16(DcmTagKey(0x0028, 0x0011), header.Columns); + dataset->findAndGetSint32(DCM_SeriesNumber, fileHeader.SeriesNumber); - dataset->findAndGetFloat64(DcmTagKey(0x0028, 0x1050), header.WindowCenter); - dataset->findAndGetFloat64(DcmTagKey(0x0028, 0x1051), header.WindowWidth); + dataset->findAndGetSint32(DCM_AcquisitionNumber, fileHeader.AcquisitionNumber); + dataset->findAndGetSint32(DCM_InstanceNumber, fileHeader.InstanceNumber); + + std::string PatientName; + dataset->findAndGetOFString(DCM_PatientName, PatientName); + + std::string StudyUID; + dataset->findAndGetOFString(DCM_StudyInstanceUID, StudyUID); + + dataset->findAndGetUint16(DCM_Rows, fileHeader.Rows); + dataset->findAndGetUint16(DCM_Columns, fileHeader.Columns); + + if (dataset->findAndGetFloat64(DCM_WindowCenter, fileHeader.WindowCenter).bad()){ + fileHeader.WindowCenter = 128.0; + } + + if (dataset->findAndGetFloat64(DCM_WindowWidth, fileHeader.WindowWidth).bad()){ + fileHeader.WindowWidth = 256.0; + } + + dataset->findAndGetUint16(DCM_SamplesPerPixel, fileHeader.SamplePerPixel); + + //try get pixel spacing auto spacingResult = - dataset->findAndGetFloat64(DcmTagKey(0x0028, 0x0030), header.Spacing[0], 0); - dataset->findAndGetFloat64(DcmTagKey(0x0028, 0x0030), header.Spacing[1], 1); + dataset->findAndGetFloat64(DCM_PixelSpacing, fileHeader.Spacing[0], 0); + dataset->findAndGetFloat64(DCM_PixelSpacing, fileHeader.Spacing[1], 1); //No Pixel spacing , try use Imager Pixel Spacing to instead if (!spacingResult.good()){ spacingResult = - dataset->findAndGetFloat64(DcmTagKey(0x0018, 0x1164), header.Spacing[0], 0); - dataset->findAndGetFloat64(DcmTagKey(0x0018, 0x1164), header.Spacing[1], 1); + dataset->findAndGetFloat64(DCM_ImagerPixelSpacing, fileHeader.Spacing[0], 0); + dataset->findAndGetFloat64(DCM_ImagerPixelSpacing, fileHeader.Spacing[1], 1); } //No Imager Pixel Spacing, use default 1.0 if (!spacingResult.good()){ - header.Spacing[0] = 1.0; - header.Spacing[1] = 1.0; + fileHeader.Spacing[0] = 1.0; + fileHeader.Spacing[1] = 1.0; } - dataset->findAndGetFloat64(DcmTagKey(0x0020, 0x0037), header.Orientation[0], 0); - dataset->findAndGetFloat64(DcmTagKey(0x0020, 0x0037), header.Orientation[1], 1); - dataset->findAndGetFloat64(DcmTagKey(0x0020, 0x0037), header.Orientation[2], 2); - dataset->findAndGetFloat64(DcmTagKey(0x0020, 0x0037), header.Orientation[3], 3); - dataset->findAndGetFloat64(DcmTagKey(0x0020, 0x0037), header.Orientation[4], 4); - dataset->findAndGetFloat64(DcmTagKey(0x0020, 0x0037), header.Orientation[5], 5); - - dataset->findAndGetFloat64(DcmTagKey(0x0020, 0x0032), header.Position[0], 0); - dataset->findAndGetFloat64(DcmTagKey(0x0020, 0x0032), header.Position[1], 1); - dataset->findAndGetFloat64(DcmTagKey(0x0020, 0x0032), header.Position[2], 2); - - dataset->findAndGetUint16(DcmTagKey(0x0028, 0x0002), header.SamplePerPixel); + //thickness + if (dataset->findAndGetFloat64(DCM_SliceThickness,fileHeader.Spacing[2]).bad()){ + if (dataset->findAndGetFloat64(DCM_SpacingBetweenSlices,fileHeader.Spacing[2]).bad()){ + fileHeader.Spacing[2] = 1.0; + } + } std::string uniqueID; uniqueID.append(PatientName); uniqueID.append("_"); uniqueID.append(StudyUID); uniqueID.append("_"); - uniqueID.append(SeriesUID); - header.SeriesUID = SeriesUID; + uniqueID.append(fileHeader.SeriesUID); // series be firstly loaded if (series.count(uniqueID)==0) { series.emplace(uniqueID,DICOMFileList()); } - header.calculateImagePosition(); - series[uniqueID].push_back(std::move(header)); + + //try to read Orientation and Position + dataset->findAndGetFloat64(DcmTagKey(0x0020, 0x0037), fileHeader.Orientation[0], 0); + dataset->findAndGetFloat64(DcmTagKey(0x0020, 0x0037), fileHeader.Orientation[1], 1); + dataset->findAndGetFloat64(DcmTagKey(0x0020, 0x0037), fileHeader.Orientation[2], 2); + dataset->findAndGetFloat64(DcmTagKey(0x0020, 0x0037), fileHeader.Orientation[3], 3); + dataset->findAndGetFloat64(DcmTagKey(0x0020, 0x0037), fileHeader.Orientation[4], 4); + dataset->findAndGetFloat64(DcmTagKey(0x0020, 0x0037), fileHeader.Orientation[5], 5); + + dataset->findAndGetFloat64(DcmTagKey(0x0020, 0x0032), fileHeader.Position[0], 0); + dataset->findAndGetFloat64(DcmTagKey(0x0020, 0x0032), fileHeader.Position[1], 1); + dataset->findAndGetFloat64(DcmTagKey(0x0020, 0x0032), fileHeader.Position[2], 2); + fileHeader.calculateImagePosition(); + + //multi-frame data + long frameCount = 0; + auto frameRet = dataset->findAndGetSint32(DCM_NumberOfFrames, frameCount); + if (frameRet.good()) { + DcmItem* sharedGroup = nullptr; + if (dataset->findAndGetSequenceItem(DCM_SharedFunctionalGroupsSequence, sharedGroup, 0).good()) { + DcmItem* pixelMeasure = nullptr; + if (sharedGroup->findAndGetSequenceItem(DCM_PixelMeasuresSequence,pixelMeasure, 0).good()){ + // use frame pixel spacing + double spacing0 = 0.0; + if(pixelMeasure->findAndGetFloat64(DCM_PixelSpacing,spacing0,0).good()){ + fileHeader.Spacing[0] = spacing0; + pixelMeasure->findAndGetFloat64(DCM_PixelSpacing,fileHeader.Spacing[1],1); + } + + // try get frame thickness + double spacing2 = 0.0; + if (pixelMeasure->findAndGetFloat64(DCM_SliceThickness,spacing2).good()){ + fileHeader.Spacing[2] = spacing2; + } + if (pixelMeasure->findAndGetFloat64(DCM_SpacingBetweenSlices,spacing2).good()){ + fileHeader.Spacing[2] = spacing2; + } + } + + // try to get frame window setting + DcmItem* frameLUT = nullptr; + if (sharedGroup->findAndGetSequenceItem(DCM_FrameVOILUTSequence,frameLUT, 0).good()){ + double wl = 0.0; + if (frameLUT->findAndGetFloat64(DCM_WindowCenter,wl).good()){ + fileHeader.WindowCenter = wl; + frameLUT->findAndGetFloat64(DCM_WindowWidth,fileHeader.WindowWidth); + } + } + } + for (int i = 0; i < frameCount; ++i) { + DICOMFileHeader frameHeader(fileHeader); + frameHeader.FrameIndex = i; + + DcmItem *perFrameGroup = nullptr; + if (dataset->findAndGetSequenceItem(DCM_PerFrameFunctionalGroupsSequence, perFrameGroup, i).good()) { + //calc instance number for frame + DcmItem *frameContent = nullptr; + if (perFrameGroup->findAndGetSequenceItem(DCM_FrameContentSequence, frameContent, 0).good()) { + unsigned long frameNumber = 0; + frameContent->findAndGetUint32(DCM_InStackPositionNumber, frameNumber); + frameHeader.InstanceNumber = frameNumber + fileHeader.InstanceNumber; + } + + // try to get frame position + DcmItem *framePosition = nullptr; + if (perFrameGroup->findAndGetSequenceItem(DCM_PlanePositionSequence, framePosition, 0).good()) { + if(framePosition->findAndGetFloat64(DCM_ImagePositionPatient, frameHeader.Position[0], 0).good()){ + framePosition->findAndGetFloat64(DCM_ImagePositionPatient, frameHeader.Position[1], 1); + framePosition->findAndGetFloat64(DCM_ImagePositionPatient, frameHeader.Position[2], 2); + }else{ + //restore value + frameHeader.Position[0] = fileHeader.Position[0]; + } + } + + // try to get frame orientation + DcmItem *frameOrientation = nullptr; + if (perFrameGroup->findAndGetSequenceItem(DCM_PlaneOrientationSequence, frameOrientation, + 0).good()) { + if (frameOrientation->findAndGetFloat64(DCM_ImageOrientationPatient, + frameHeader.Orientation[0], 0).good()) { + frameOrientation->findAndGetFloat64(DCM_ImageOrientationPatient, frameHeader.Orientation[1], + 1); + frameOrientation->findAndGetFloat64(DCM_ImageOrientationPatient, frameHeader.Orientation[2], + 2); + frameOrientation->findAndGetFloat64(DCM_ImageOrientationPatient, frameHeader.Orientation[3], + 3); + frameOrientation->findAndGetFloat64(DCM_ImageOrientationPatient, frameHeader.Orientation[4], + 4); + frameOrientation->findAndGetFloat64(DCM_ImageOrientationPatient, frameHeader.Orientation[5], + 5); + }else{ + //restore value + frameHeader.Orientation[0] = fileHeader.Orientation[0]; + } + } + } + else{ + frameHeader.InstanceNumber = fileHeader.InstanceNumber + i; + } + frameHeader.calculateImagePosition(); + series[uniqueID].push_back(std::move(frameHeader)); + } + } + else{ + series[uniqueID].push_back(std::move(fileHeader)); + } } } @@ -204,12 +275,14 @@ void DICOMHeaderHelper::ReadHeader() { void DICOMHeaderHelper::ArrangeSeriesProperty() { for (auto item : series) { - //sort by series, instance, AcquisitionNumber + //sort by series, InstanceNumber, AcquisitionNumber std::sort(item.second.begin(), item.second.end(), [](auto v1, auto v2) { return v1.SeriesNumber != v2.SeriesNumber ? (v1.SeriesNumber < v2.SeriesNumber) : (v1.AcquisitionNumber != v2.AcquisitionNumber ? (v1.AcquisitionNumber < v2.AcquisitionNumber) : (v1.InstanceNumber < v2.InstanceNumber)); }); + + // split series into image set auto currentValue = &(item.second[0]); std::vector splitIndex; for (int i = 1; i < item.second.size(); ++i) { @@ -218,10 +291,12 @@ void DICOMHeaderHelper::ArrangeSeriesProperty() { splitIndex.push_back(i); } } - //sort every image set + + // use image set to create image property int imageSetNumber = 0; int beginOffset = 0; for (int j = 0; j < splitIndex.size(); ++j) { + //sort every image set with world coordinate position std::sort(item.second.begin() + beginOffset, item.second.begin() + splitIndex[j], [](auto v1, auto v2) { return v1.image_position < v2.image_position; @@ -246,90 +321,148 @@ void DICOMHeaderHelper::ArrangeSeriesProperty() { } ExtendMedicalImageProperties* DICOMHeaderHelper::createProperty(const std::vector& headerList, int splitIndex, int beginOffset) { - auto property = ExtendMedicalImageProperties::New(); - std::vector files; - for (int i = beginOffset; i < splitIndex; ++i) { - files.push_back(headerList[i].FilePath.c_str()); - } const DICOMFileHeader& header = headerList[beginOffset]; - property->SetFileNames(files); + + // create ExtendMedicalImageProperties and set values + auto property = ExtendMedicalImageProperties::New(); + + // get values from header + property->SetSeriesUID(header.SeriesUID.c_str()); + property->SetSeriesNumber(header.SeriesNumber); + property->SetAcquisitionNumber(header.AcquisitionNumber); + + // image reference property->SetRows(header.Rows); property->SetColumns(header.Columns); property->SetSamplePerPixel(header.SamplePerPixel); - property->SetSeriesNumber(header.SeriesNumber); -// property->SetImageNumber(item.second[beginOffset].InstanceNumber); - property->SetAcquisitionNumber(header.AcquisitionNumber); + + // window property->AddWindowLevelPreset(header.WindowWidth, header.WindowCenter); - property->SetPosition(header.Position[0],header.Position[1], header.Position[2]); - property->SetDirectionCosine(header.Orientation[0],header.Orientation[1],header.Orientation[2], - header.Orientation[3],header.Orientation[4],header.Orientation[5]); + // coordinate + property->SetPosition((double*)header.Position); + property->SetDirectionCosine((double*)header.Orientation); + property->ComputeTransformMatrix(); + + // set files for this property (split series to image set) + std::vector> files; + for (int i = beginOffset; i < splitIndex; ++i) { + files.push_back({headerList[i].FilePath.c_str(),headerList[i].FrameIndex}); + } + property->SetFileNames(files); + + // use file to read some additional information DcmFileFormat file; // read success! if (file.loadFile(header.FilePath).good()) { DcmDataset *dataset = file.getDataset(); - #define ReadTAGToProperty(Name, group, element)\ + + // try to read patient and study information + #define ReadTAGToProperty(Name)\ std::string Name;\ - dataset->findAndGetOFString(DcmTagKey(group, element), Name);\ + dataset->findAndGetOFString(DCM_##Name, Name);\ property->Set##Name(Name.c_str()); + ReadTAGToProperty(PatientBirthDate) + ReadTAGToProperty(PatientSex) + ReadTAGToProperty(PatientAge) + ReadTAGToProperty(StudyDate) + ReadTAGToProperty(AcquisitionDate) + ReadTAGToProperty(StudyTime) + ReadTAGToProperty(AcquisitionTime) + ReadTAGToProperty(Modality) + ReadTAGToProperty(StudyID) - ReadTAGToProperty(PatientBirthDate, 0x0010, 0x0030) - ReadTAGToProperty(PatientSex, 0x0010, 0x0040) - ReadTAGToProperty(PatientAge, 0x0010, 0x1010) - ReadTAGToProperty(StudyDate, 0x0008, 0x0020) - ReadTAGToProperty(AcquisitionDate, 0x0008, 0x0022) - ReadTAGToProperty(StudyTime, 0x0008, 0x0030) - ReadTAGToProperty(AcquisitionTime, 0x0008, 0x0032) - ReadTAGToProperty(Modality, 0x0008, 0x0060) + std::string StudyUID; + dataset->findAndGetOFString(DCM_StudyInstanceUID, StudyUID); + property->SetStudyUID(StudyUID.c_str()); - ReadTAGToProperty(StudyUID, 0x0020, 0x000d) - ReadTAGToProperty(StudyID, 0x0020, 0x0010) + // check has overlay + unsigned short overlayRows = 0; + dataset->findAndGetUint16(DCM_OverlayRows, overlayRows); + if (overlayRows > 0){ + property->SetHasOverlay(true); + } + + // convert text to utf-8 codec std::string charSet = ""; auto needConvert = dataset->findAndGetOFString(DcmTagKey(0x0008, 0x0005), charSet); if (needConvert.good()){ DcmSpecificCharacterSet charSetConvert; charSetConvert.selectCharacterSet(charSet); - #define ReadTAGToPropertyConvert(Name, group, element)\ + + #define ReadTAGToPropertyConvert(Name)\ std::string Name;\ std::string Name##Convert;\ - dataset->findAndGetOFString(DcmTagKey(group, element), Name);\ + dataset->findAndGetOFString(DCM_##Name, Name);\ charSetConvert.convertString(Name,Name##Convert);\ property->Set##Name(Name##Convert.c_str()); - ReadTAGToPropertyConvert(PatientID, 0x0010, 0x0020) - ReadTAGToPropertyConvert(PatientName, 0x0010, 0x0010) - ReadTAGToPropertyConvert(InstitutionName, 0x0008, 0x0080) - ReadTAGToPropertyConvert(StudyDescription, 0x0008, 0x1030) - ReadTAGToPropertyConvert(SeriesDescription, 0x0008, 0x103E) + ReadTAGToPropertyConvert(PatientID) + ReadTAGToPropertyConvert(PatientName) + ReadTAGToPropertyConvert(InstitutionName) + ReadTAGToPropertyConvert(StudyDescription) + ReadTAGToPropertyConvert(SeriesDescription) } else{ - ReadTAGToProperty(PatientID, 0x0010, 0x0020) - ReadTAGToProperty(PatientName, 0x0010, 0x0010) - ReadTAGToProperty(InstitutionName, 0x0008, 0x0080) - ReadTAGToProperty(StudyDescription, 0x0008, 0x1030) - ReadTAGToProperty(SeriesDescription, 0x0008, 0x103E) + ReadTAGToProperty(PatientID) + ReadTAGToProperty(PatientName) + ReadTAGToProperty(InstitutionName) + ReadTAGToProperty(StudyDescription) + ReadTAGToProperty(SeriesDescription) } - double thickness= 1.0; - dataset->findAndGetFloat64(DcmTagKey(0x0008, 0x0050), thickness); - double RescaleSlope= 1.0; - dataset->findAndGetFloat64(DcmTagKey(0x0028, 0x1053), RescaleSlope); - double RescaleOffset= 0.0; - dataset->findAndGetFloat64(DcmTagKey(0x0028, 0x1052), RescaleOffset); + + // read spacing for image data + double thickness = header.Spacing[2]; + + // no frame thickness set, read first file thickness + if((int)thickness == 0){ + if (dataset->findAndGetFloat64(DCM_SliceThickness, thickness).bad()){ + if(dataset->findAndGetFloat64(DCM_SpacingBetweenSlices, thickness).bad()) + { + thickness = 1.0; + } + } + } + property->SetSpacing(header.Spacing[0],header.Spacing[1],thickness); + + // read data allocate reference param unsigned short bitsAllocated= 1; dataset->findAndGetUint16(DcmTagKey(0x0028, 0x0100), bitsAllocated); unsigned short PixelRepresentation= 0; dataset->findAndGetUint16(DcmTagKey(0x0028, 0x0103), PixelRepresentation); - property->SetSeriesUID(header.SeriesUID.c_str()); - property->SetRows(header.Rows); - property->SetColumns(header.Columns); - property->SetSpacing(header.Spacing[0],header.Spacing[1],thickness); - property->SetRescaleSlope(RescaleSlope); - property->SetRescaleOffset(RescaleOffset); property->SetBitsAllocated(bitsAllocated); property->SetPixelRepresentation(PixelRepresentation); - property->SetDirectionCosine((double*)header.Orientation); - property->ComputeTransformMatrix(); + + // read pixel transform(rescale) param + double RescaleSlope = 1.0; + double RescaleOffset = 0.0; + + //try get Rescale params for file first + if (dataset->findAndGetFloat64(DCM_RescaleSlope, RescaleSlope).bad()){ + RescaleSlope = 1.0; + } + dataset->findAndGetFloat64(DCM_RescaleIntercept, RescaleOffset); + + //try get frame Rescale params + long frameCount = 0; + auto frameRet = dataset->findAndGetSint32(DCM_NumberOfFrames, frameCount); + if (frameRet.good()) { + DcmItem *sharedGroup = nullptr; + if (dataset->findAndGetSequenceItem(DCM_SharedFunctionalGroupsSequence, sharedGroup, 0).good()) { + DcmItem *frameTransform = nullptr; + if (sharedGroup->findAndGetSequenceItem(DCM_PixelValueTransformationSequence,frameTransform,0).good()){ + double tempRescaleSlope = 1.0; + if (frameTransform->findAndGetFloat64(DCM_RescaleSlope,tempRescaleSlope).good()){ + RescaleSlope = tempRescaleSlope; + frameTransform->findAndGetFloat64(DCM_RescaleIntercept, RescaleOffset); + } + } + } + } + + property->SetRescaleSlope(RescaleSlope); + property->SetRescaleOffset(RescaleOffset); } else{ printf( " file:%s load error!\r\b", header.FilePath.c_str() ); @@ -337,9 +470,12 @@ ExtendMedicalImageProperties* DICOMHeaderHelper::createProperty(const std::vecto return nullptr; } if (splitIndex-beginOffset>1){ + // try to use real world slice origin point distance to instead thickness const DICOMFileHeader& header2 = headerList[beginOffset+1]; double spacing2 = sqrt(vtkMath::Distance2BetweenPoints(header.Position,header2.Position)); - property->SetSpacing(property->GetSpacing()[0],property->GetSpacing()[1],spacing2); + if((int)spacing2 != 0){ + property->SetSpacing(property->GetSpacing()[0],property->GetSpacing()[1],spacing2); + } } return property; } diff --git a/src/src/IO/DICOM/DICOMHeaderHelper.h b/src/src/IO/DICOM/DICOMHeaderHelper.h index b3f2373..90b0929 100644 --- a/src/src/IO/DICOM/DICOMHeaderHelper.h +++ b/src/src/IO/DICOM/DICOMHeaderHelper.h @@ -18,7 +18,9 @@ struct DICOMFileHeader { long SeriesNumber = 0; long AcquisitionNumber = 0; long InstanceNumber = 0; - double Spacing[2] = {1., 1.}; + long FrameNumber = -1l; + long FrameIndex = -1l; + double Spacing[3] = {1., 1., .0}; unsigned short Rows; unsigned short Columns; unsigned short SamplePerPixel; @@ -27,6 +29,25 @@ struct DICOMFileHeader { double Position[3] = {.0, .0, .0}; double Orientation[6] = {.0, .0, .0, .0, .0, .0}; double image_position = 0.0; + DICOMFileHeader() = default; + DICOMFileHeader(const DICOMFileHeader & other){ + FilePath = other.FilePath; + SeriesUID = other.SeriesUID; + SeriesNumber = other.SeriesNumber; + AcquisitionNumber = other.AcquisitionNumber; + InstanceNumber = other.InstanceNumber; + FrameNumber = other.FrameNumber; + FrameIndex = other.FrameIndex; + Rows = other.Rows; + Columns = other.Columns; + SamplePerPixel = other.SamplePerPixel; + WindowCenter = other.WindowCenter; + WindowWidth = other.WindowWidth; + image_position = other.image_position; + memcpy(Spacing,other.Spacing, 2* sizeof(double)); + memcpy(Position,other.Position, 3* sizeof(double)); + memcpy(Orientation,other.Orientation, 6* sizeof(double)); + } bool equalImageSet(const DICOMFileHeader &v) { return Rows == v.Rows && Columns == v.Columns && diff --git a/src/src/IO/DICOM/DICOMPixelDataHelper.cpp b/src/src/IO/DICOM/DICOMPixelDataHelper.cpp index ebf6eda..73a3eca 100644 --- a/src/src/IO/DICOM/DICOMPixelDataHelper.cpp +++ b/src/src/IO/DICOM/DICOMPixelDataHelper.cpp @@ -14,6 +14,8 @@ #include "openjpeg-2.3/openjpeg.h" #endif +#include "DICOMFileObjectCache.h" + void DICOMPixelDataHelper::InitCodecs() { DcmRLEDecoderRegistration::registerCodecs(OFFalse, OFTrue);//注册解码器 /// register JPEG decompression codecs @@ -42,7 +44,7 @@ void DICOMPixelDataHelper::GetThumbnailData(ExtendMedicalImageProperties *proper DcmFileFormat *fileFormat = new DcmFileFormat();//读取文件获取传输语法 if (fileFormat->loadFile(property->GetThumbnailFileName()).good()) { DcmDataset *dset = fileFormat->getDataset(); - DicomImage dcmImage(fileFormat, dset->getOriginalXfer(), CIF_MayDetachPixelData); + DicomImage dcmImage(fileFormat, dset->getOriginalXfer()); DicomImage *sdcmImage = dcmImage.createScaledImage(100.0, 0.0, 0, 1); sdcmImage->setWindow(wl, ww); sdcmImage->showAllOverlays(); @@ -50,6 +52,7 @@ void DICOMPixelDataHelper::GetThumbnailData(ExtendMedicalImageProperties *proper unsigned char *outputData = (unsigned char *) sdcmImage->getOutputData(8);//按8位的位宽取数据 data = new unsigned char[length]; memcpy(data, outputData, length); + sdcmImage->deleteOutputData(); delete sdcmImage; } delete fileFormat; @@ -155,50 +158,45 @@ void DICOMPixelDataHelper::GetRGBPixelData(const char *path, void *data, unsigne } int DICOMPixelDataHelper::GetOverLayCount(const char *path) { - int ret = 0; - DcmFileFormat fileFormat; - if (fileFormat.loadFile(path).good()) { - DcmDataset * dset = fileFormat.getDataset(); - DicomImage dcmImage(&fileFormat, dset->getOriginalXfer(), CIF_MayDetachPixelData); - ret = dcmImage.getOverlayCount(); - } + int ret =0; + DcmFileFormat fileFormat; + if (fileFormat.loadFile(path).good()) { + DcmDataset * dset = fileFormat.getDataset(); + DicomImage dcmImage(&fileFormat, dset->getOriginalXfer(), CIF_UsePartialAccessToPixelData); + ret = dcmImage.getOverlayCount(); + } return ret; } void DICOMPixelDataHelper::GetOverlayData(const char *path, void *data, - unsigned int& width, unsigned int& height, unsigned int plane) { + unsigned int& width, unsigned int& height) { DcmFileFormat *fileFormat = new DcmFileFormat();//读取文件获取传输语法 if (fileFormat->loadFile(path).good()) { DcmDataset *dset = fileFormat->getDataset(); DicomImage dcmImage(fileFormat, dset->getOriginalXfer(), CIF_MayDetachPixelData); - if(dcmImage.getOverlayCount()>plane){ - const void * overlay = dcmImage.getFullOverlayData(plane,width, height); - int dataLength = width*height; - if (plane == 0){ + for (int j = 0; j < dcmImage.getOverlayCount(); ++j) { + const void *overlay = dcmImage.getFullOverlayData(j, width, height); + int dataLength = width * height; + if (j == 0) { memcpy(data, overlay, dataLength); - } - else{ + } else { int count32 = dataLength / 4; int tailCount8 = dataLength % 4; //OR operation header and content - Uint32 * mergeDst = (Uint32*)data; - Uint32 * mergeSrc = (Uint32*)overlay; + Uint32 *mergeDst = (Uint32 *) data; + Uint32 *mergeSrc = (Uint32 *) overlay; for (int i = 0; i < count32; ++i) { mergeDst[i] = mergeDst[i] | mergeSrc[i]; } //OR operation tail - Uint8 * mergeDst8 = (Uint8*)data; - Uint8 * mergeSrc8 = (Uint8*)overlay; + Uint8 *mergeDst8 = (Uint8 *) data; + Uint8 *mergeSrc8 = (Uint8 *) overlay; for (int i = 1; i <= tailCount8; ++i) { mergeDst8[dataLength - i] = mergeDst8[dataLength - i] | mergeSrc8[dataLength - i]; } } dcmImage.deleteOverlayData(); } - else{ - width=-1; - height=-1; - } } else{ width=-1; @@ -206,3 +204,53 @@ void DICOMPixelDataHelper::GetOverlayData(const char *path, void *data, } delete fileFormat; } + +void DICOMPixelDataHelper::GetFramePixelData(const char *path, void *data, unsigned long &length, const long& frameIndex) { + auto fileFormat = DICOMFileObjectCache::getInstance()->GetFileObject(path); + if (fileFormat) { + DicomImage dcmImage(fileFormat, fileFormat->getDataset()->getOriginalXfer(), CIF_UsePartialAccessToPixelData, + frameIndex, 1); + if (dcmImage.getStatus() == EIS_Normal) { + const DiPixel *pixelData = dcmImage.getInterData();//rescaled + unsigned long dataBits = 1; + switch (pixelData->getRepresentation()) { + case EPR_Uint8: + case EPR_Sint8: + break; + case EPR_Uint16: + case EPR_Sint16: + dataBits = 2; + break; + case EPR_Uint32: + case EPR_Sint32: + dataBits = 4; + break; + } + const void *dd = pixelData->getData(); + length = pixelData->getCount(); + length = length * dataBits; + memcpy(data, dd, length); + } else { + length = -1; + } + } +} + +bool DICOMPixelDataHelper::StoreFileObject(const char *path) { + DcmFileFormat *fileFormat = new DcmFileFormat();//读取文件获取传输语法 + if (fileFormat->loadFile(path).good() && fileFormat->loadAllDataIntoMemory().good()) { + DICOMFileObjectCache::getInstance()->store(path,fileFormat); + return true; + } else{ + delete fileFormat; + return false; + } +} + +void DICOMPixelDataHelper::ClearFileObject() { + DICOMFileObjectCache::getInstance()->clear(); +} + +bool DICOMPixelDataHelper::CheckFileObjectCache(const char *path) { + return DICOMFileObjectCache::getInstance()->contains(path); +} diff --git a/src/src/IO/DICOM/DICOMPixelDataHelper.h b/src/src/IO/DICOM/DICOMPixelDataHelper.h index 93818dd..12366e8 100644 --- a/src/src/IO/DICOM/DICOMPixelDataHelper.h +++ b/src/src/IO/DICOM/DICOMPixelDataHelper.h @@ -26,6 +26,14 @@ public: */ static void GetPixelData(const char * path, void* data, unsigned long& length); + static bool CheckFileObjectCache(const char * path); + + static bool StoreFileObject(const char * path); + + static void ClearFileObject(); + + static void GetFramePixelData(const char * path, void* data, unsigned long& length, const long& frameIndex); + /** * Get Pixel Data with Rescaled, result is calculate as Float, and arrange as Float. * @param path dcm file absolute path @@ -45,7 +53,7 @@ public: static int GetOverLayCount(const char *path); static void GetOverlayData(const char * path, void* data, - unsigned int& width, unsigned int& height,unsigned int plane = 0); + unsigned int& width, unsigned int& height); /** * Init decompress Codecs, must be called before other method. diff --git a/src/src/IO/DICOM/ExtendMedicalImageProperties.h b/src/src/IO/DICOM/ExtendMedicalImageProperties.h index fc06fec..2e55314 100644 --- a/src/src/IO/DICOM/ExtendMedicalImageProperties.h +++ b/src/src/IO/DICOM/ExtendMedicalImageProperties.h @@ -65,11 +65,11 @@ public: vtkGetVector3Macro(ZVector,double); - const std::vector& GetFileNames(){ + const std::vector>& GetFileNames(){ return FileNames; } - void SetFileNames(std::vector& files){ + void SetFileNames(std::vector>& files){ FileNames = std::move(files); } @@ -88,7 +88,7 @@ public: const char* GetThumbnailFileName(){ if (FileNames.empty()) return nullptr; - return FileNames[0].data(); + return FileNames[0].first.data(); } unsigned long long GetSliceCount(){ return FileNames.size(); @@ -135,7 +135,7 @@ protected: unsigned short BitsAllocated; unsigned short PixelRepresentation; bool HasOverlay = false; - std::vector FileNames; + std::vector> FileNames; std::string uniqueID; vtkNew OrientationMatrix; vtkNew WorldToModelMatrix; @@ -143,8 +143,6 @@ protected: private: ExtendMedicalImageProperties(const ExtendMedicalImageProperties&) = delete; void operator=(const ExtendMedicalImageProperties&) = delete; - - }; diff --git a/src/src/IO/DICOM/vtkDICOMImageReader2.cpp b/src/src/IO/DICOM/vtkDICOMImageReader2.cpp index c8565f4..2b2fa42 100644 --- a/src/src/IO/DICOM/vtkDICOMImageReader2.cpp +++ b/src/src/IO/DICOM/vtkDICOMImageReader2.cpp @@ -98,9 +98,9 @@ void vtkDICOMImageReader2::ExecuteDataWithInformation(vtkDataObject *output, DICOMPixelDataHelper::InitCodecs(); for (int i = 0; i < properties->GetFileNames().size(); ++i) { - const std::string &path = properties->GetFileNames()[i]; - int overlayCount = DICOMPixelDataHelper::GetOverLayCount(path.c_str()); - if (overlayCount > 0){ + const std::string &path = properties->GetFileNames()[i].first; + const long &frameIndex = properties->GetFileNames()[i].second; + if (properties->GetHasOverlay()){ if (!OverLayData){ HasOverlay = true; OverLayData = vtkImageData::New(); @@ -112,9 +112,8 @@ void vtkDICOMImageReader2::ExecuteDataWithInformation(vtkDataObject *output, unsigned int width = 0; unsigned int height = 0; void* overlayBuffer = OverLayData->GetScalarPointer(0, 0, i); - for (int j = 0; j < overlayCount; ++j) { - DICOMPixelDataHelper::GetOverlayData(path.c_str(), overlayBuffer, width, height); - } + DICOMPixelDataHelper::GetOverlayData(path.c_str(), overlayBuffer, width, height); + } @@ -129,14 +128,26 @@ void vtkDICOMImageReader2::ExecuteDataWithInformation(vtkDataObject *output, DICOMPixelDataHelper::GetRGBPixelData(path.c_str(), buffer, imageDataLength); } else { - - DICOMPixelDataHelper::GetPixelData(path.c_str(), buffer, imageDataLength); - + if (frameIndex>=0){ + if (!DICOMPixelDataHelper::CheckFileObjectCache(path.c_str())){ + if(!DICOMPixelDataHelper::StoreFileObject(path.c_str())){ + vtkErrorMacro(<< "Fail to read file:"<