From d45f8604a9f9d85e7a99f84a5f18952dd1d5c8da Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 10 Mar 2017 16:08:42 -0500 Subject: [PATCH 001/116] WIP: refactoring of the multiframe conversion implementation This is an intermediate commit that aims to improve organization of the conversion functionality, motivated by the various standing issues (e.g., #217, #192 and #150) that are difficult to address with the current level of disarray in the code. Some of the ideas are the following: - introduce basic hierarchy of classes to allow reuse of the common functionality between PM and SEG converters (MultiFrameConverter parent for ParametricMapConverter and SegmentationImageConverter) - do not use static functions, instead make better use of converter classes - factor out containers to keep information about ImageVolume and DICOMFrame to simplify conversion and book keeping - improve consistency of naming and code style My plan to proceed with the refactoring is to implement "shadow" functions in the updated classes while keeping the original functions initially, and then phase out the original functions. The current commit is just an intermediate milestone to open this for feedback and keep development history at least somewhat incremental. --- apps/paramaps/itkimage2paramap.cxx | 4 +- apps/paramaps/paramap2itkimage.cxx | 4 +- apps/seg/itkimage2segimage.cxx | 4 +- apps/seg/segimage2itkimage.cxx | 4 +- include/dcmqi/DICOMFrame.h | 36 +++ include/dcmqi/ImageVolume.h | 66 ++++++ ...{ConverterBase.h => MultiframeConverter.h} | 7 +- ...apConverter.h => ParametricMapConverter.h} | 41 +++- include/dcmqi/SegmentVolume.h | 17 ++ ...nverter.h => SegmentationImageConverter.h} | 4 +- libsrc/CMakeLists.txt | 173 ++++++++------ libsrc/DICOMFrame.cpp | 5 + libsrc/ImageVolume.cpp | 214 ++++++++++++++++++ ...verterBase.cpp => MultiframeConverter.cpp} | 8 +- ...nverter.cpp => ParametricMapConverter.cpp} | 30 ++- libsrc/SegmentVolume.cpp | 5 + ...ter.cpp => SegmentationImageConverter.cpp} | 8 +- 17 files changed, 522 insertions(+), 108 deletions(-) create mode 100644 include/dcmqi/DICOMFrame.h create mode 100644 include/dcmqi/ImageVolume.h rename include/dcmqi/{ConverterBase.h => MultiframeConverter.h} (98%) rename include/dcmqi/{ParaMapConverter.h => ParametricMapConverter.h} (53%) create mode 100644 include/dcmqi/SegmentVolume.h rename include/dcmqi/{ImageSEGConverter.h => SegmentationImageConverter.h} (93%) create mode 100644 libsrc/DICOMFrame.cpp create mode 100644 libsrc/ImageVolume.cpp rename libsrc/{ConverterBase.cpp => MultiframeConverter.cpp} (81%) rename libsrc/{ParaMapConverter.cpp => ParametricMapConverter.cpp} (95%) create mode 100644 libsrc/SegmentVolume.cpp rename libsrc/{ImageSEGConverter.cpp => SegmentationImageConverter.cpp} (98%) diff --git a/apps/paramaps/itkimage2paramap.cxx b/apps/paramaps/itkimage2paramap.cxx index 583b86d9..f71252b9 100644 --- a/apps/paramaps/itkimage2paramap.cxx +++ b/apps/paramaps/itkimage2paramap.cxx @@ -3,7 +3,7 @@ // DCMQI includes #undef HAVE_SSTREAM // Avoid redefinition warning -#include "dcmqi/ParaMapConverter.h" +#include "dcmqi/ParametricMapConverter.h" #include "dcmqi/internal/VersionConfigure.h" @@ -44,7 +44,7 @@ int main(int argc, char *argv[]) std::string metadata( (std::istreambuf_iterator(metainfoStream) ), (std::istreambuf_iterator())); - DcmDataset* result = dcmqi::ParaMapConverter::itkimage2paramap(parametricMapImage, dcmDatasets, metadata); + DcmDataset* result = dcmqi::ParametricMapConverter::itkimage2paramap(parametricMapImage, dcmDatasets, metadata); if (result == NULL) { return EXIT_FAILURE; diff --git a/apps/paramaps/paramap2itkimage.cxx b/apps/paramaps/paramap2itkimage.cxx index aa1def1d..31908f8b 100644 --- a/apps/paramaps/paramap2itkimage.cxx +++ b/apps/paramaps/paramap2itkimage.cxx @@ -3,7 +3,7 @@ // DCMQI includes #undef HAVE_SSTREAM // Avoid redefinition warning -#include "dcmqi/ParaMapConverter.h" +#include "dcmqi/ParametricMapConverter.h" #include "dcmqi/internal/VersionConfigure.h" @@ -25,7 +25,7 @@ int main(int argc, char *argv[]) CHECK_COND(sliceFF.loadFile(inputFileName.c_str())); DcmDataset* dataset = sliceFF.getDataset(); - pair result = dcmqi::ParaMapConverter::paramap2itkimage(dataset); + pair result = dcmqi::ParametricMapConverter::paramap2itkimage(dataset); string fileExtension = helper::getFileExtensionFromType(outputType); diff --git a/apps/seg/itkimage2segimage.cxx b/apps/seg/itkimage2segimage.cxx index 5c0d65ad..68f32d96 100644 --- a/apps/seg/itkimage2segimage.cxx +++ b/apps/seg/itkimage2segimage.cxx @@ -3,7 +3,7 @@ // DCMQI includes #undef HAVE_SSTREAM // Avoid redefinition warning -#include "dcmqi/ImageSEGConverter.h" +#include "dcmqi/SegmentationImageConverter.h" #include "dcmqi/internal/VersionConfigure.h" typedef dcmqi::Helper helper; @@ -55,7 +55,7 @@ int main(int argc, char *argv[]) ifstream metainfoStream(metaDataFileName.c_str(), ios_base::binary); std::string metadata( (std::istreambuf_iterator(metainfoStream) ), (std::istreambuf_iterator())); - DcmDataset* result = dcmqi::ImageSEGConverter::itkimage2dcmSegmentation(dcmDatasets, segmentations, metadata, skipEmptySlices); + DcmDataset* result = dcmqi::SegmentationImageConverter::itkimage2dcmSegmentation(dcmDatasets, segmentations, metadata, skipEmptySlices); if (result == NULL){ return EXIT_FAILURE; diff --git a/apps/seg/segimage2itkimage.cxx b/apps/seg/segimage2itkimage.cxx index 7784e311..0545778a 100644 --- a/apps/seg/segimage2itkimage.cxx +++ b/apps/seg/segimage2itkimage.cxx @@ -3,7 +3,7 @@ // DCMQI includes #undef HAVE_SSTREAM // Avoid redefinition warning -#include "dcmqi/ImageSEGConverter.h" +#include "dcmqi/SegmentationImageConverter.h" #include "dcmqi/internal/VersionConfigure.h" @@ -24,7 +24,7 @@ int main(int argc, char *argv[]) CHECK_COND(sliceFF.loadFile(inputSEGFileName.c_str())); DcmDataset* dataset = sliceFF.getDataset(); - pair , string> result = dcmqi::ImageSEGConverter::dcmSegmentation2itkimage(dataset); + pair , string> result = dcmqi::SegmentationImageConverter::dcmSegmentation2itkimage(dataset); string outputPrefix = prefix.empty() ? "" : prefix + "-"; diff --git a/include/dcmqi/DICOMFrame.h b/include/dcmqi/DICOMFrame.h new file mode 100644 index 00000000..f58a6955 --- /dev/null +++ b/include/dcmqi/DICOMFrame.h @@ -0,0 +1,36 @@ +// +// Created by Andrey Fedorov on 3/9/17. +// + +#ifndef DCMQI_DICOMFRAME_H +#define DCMQI_DICOMFRAME_H + +#include + +namespace dcmqi { + + // Describe input/output frames for the purposes of sorting and associating with the + // slices of the ITK volume + class DICOMFrame { + public: + // distinguish between the frames from the legacy data and enhanced multiframe objects + enum { + LegacyInstanceFrame = 0, + EnhancedInstanceFrame + }; + + DICOMFrame(DcmDataset *dataset, int number) : + frameNumber(number), + frameDataset(dataset) { + + }; + + private: + int frameType; + DcmDataset *frameDataset; + int frameNumber; + OFString originStr; // revisit this + }; +} + +#endif //DCMQI_DICOMFRAME_H diff --git a/include/dcmqi/ImageVolume.h b/include/dcmqi/ImageVolume.h new file mode 100644 index 00000000..13d37ef4 --- /dev/null +++ b/include/dcmqi/ImageVolume.h @@ -0,0 +1,66 @@ +// +// Created by Andrey Fedorov on 3/9/17. +// + +#ifndef DCMQI_IMAGEVOLUME_H +#define DCMQI_IMAGEVOLUME_H + +#include +#include +#include + +#include + +namespace dcmqi { + + // Maintain properties of the image volume + // Attributes ara parallel to those of itkImageData, but limited to what we need for the task of conversion, + // and the class is not templated over the pixel type + class ImageVolume { + public: + // pixel types that are relevant for the types of objects we want to support + enum { + FLOAT32 = 0, // PM + FLOAT64, // PM + UINT16 // PM or SEG + }; + + typedef IODFloatingPointImagePixelModule::value_type Float32PixelType; + typedef itk::Image Float32ITKImageType; + + ImageVolume(){ + rowDirection.set_size(3); + columnDirection.set_size(3); + sliceDirection.set_size(3); + origin.set_size(3); + spacing.set_size(3); + pixelData = NULL; + } + + // while going from DICOM PM/SEG, we get volume information from FGInterface + int initializeFromDICOM(FGInterface&); + int initializeFromITK(Float32ITKImageType::Pointer); + + // TODO - inherited class? or separate segments before passing to this one? + // int initializeFromSegment(FGInterface&, unsigned); + + protected: + int initializeDirections(FGInterface &); + int initializeExtent(FGInterface &); + bool getDeclaredSpacing(FGInterface&); + + private: + + // use vnl_vector to simplify support of vector calculations + vnl_vector rowDirection, columnDirection, sliceDirection; + vnl_vector origin; + unsigned sliceExtent; + vnl_vector spacing; + void* pixelData; + int pixelDataType; + }; + +}; + + +#endif //DCMQI_IMAGEVOLUME_H diff --git a/include/dcmqi/ConverterBase.h b/include/dcmqi/MultiframeConverter.h similarity index 98% rename from include/dcmqi/ConverterBase.h rename to include/dcmqi/MultiframeConverter.h index ad7e9f49..45b1a3c9 100644 --- a/include/dcmqi/ConverterBase.h +++ b/include/dcmqi/MultiframeConverter.h @@ -35,13 +35,16 @@ using namespace std; +// common type definitions typedef short ShortPixelType; typedef itk::Image ShortImageType; typedef itk::ImageFileReader ShortReaderType; namespace dcmqi { - class ConverterBase { + class MultiframeConverter { + public: + virtual int convert(); protected: static IODGeneralEquipmentModule::EquipmentInfo getEquipmentInfo(); @@ -124,7 +127,7 @@ namespace dcmqi { */ // Determine ordering of the frames, keep mapping from ImagePositionPatient string - // to the distance, and keep track (just out of curiousity) how many frames overlap + // to the distance, and keep track (just out of curiosity) how many frames overlap vnl_vector refOrigin(3); { OFBool isPerFrame; diff --git a/include/dcmqi/ParaMapConverter.h b/include/dcmqi/ParametricMapConverter.h similarity index 53% rename from include/dcmqi/ParaMapConverter.h rename to include/dcmqi/ParametricMapConverter.h index fcabbf49..6545e39c 100644 --- a/include/dcmqi/ParaMapConverter.h +++ b/include/dcmqi/ParametricMapConverter.h @@ -18,8 +18,9 @@ #include // DCMQI includes -#include "dcmqi/ConverterBase.h" +#include "dcmqi/MultiframeConverter.h" #include "dcmqi/JSONParametricMapMetaInformationHandler.h" +#include "dcmqi/ImageVolume.h" typedef IODFloatingPointImagePixelModule::value_type FloatPixelType; typedef itk::Image FloatImageType; @@ -28,22 +29,52 @@ typedef itk::MinimumMaximumImageCalculator MinMaxCalculatorType; using namespace std; - namespace dcmqi { - class ParaMapConverter : public ConverterBase { + DcmDataset* itkimage2paramapReplacement(const FloatImageType::Pointer ¶metricMapImage, vector dcmDatasets, + const string &metaData); + pair paramap2itkimageReplacement(DcmDataset *pmapDataset); + + class ParametricMapConverter : public MultiframeConverter { public: - static DcmDataset* itkimage2paramap(const FloatImageType::Pointer ¶metricMapImage, vector dcmDatasets, - const string &metaData); + // placeholder to replace static function going from ITK to DICOM + ParametricMapConverter(const FloatImageType::Pointer ¶metricMapImage, vector dcmDatasets, + const string &metaData); + // placeholder to replace static function going from DICOM to ITK + ParametricMapConverter(DcmDataset*); + + + static DcmDataset* itkimage2paramap(const FloatImageType::Pointer ¶metricMapImage, vector dcmDatasets, + const string &metaData); static pair paramap2itkimage(DcmDataset *pmapDataset); + + // do the conversion + int convert(); + + // get the result + FloatImageType::Pointer getFloat32ITKImage() const; + string getMetaData() const; + DcmDataset* getDataset() const; + protected: + + ParametricMapConverter() : parametricMapVolume(NULL), parametricMapDataset(NULL) {}; + static OFCondition addFrame(DPMParametricMapIOD &map, const FloatImageType::Pointer ¶metricMapImage, const JSONParametricMapMetaInformationHandler &metaInfo, const unsigned long frameNo, OFVector perFrameGroups); static void populateMetaInformationFromDICOM(DcmDataset *pmapDataset, JSONParametricMapMetaInformationHandler &metaInfo); + private: + // these are the items we convert to and from, essentially + ImageVolume* parametricMapVolume; + DcmDataset* parametricMapDataset; + + // these are the items we will need in the process of conversion + vector referencedDatasets; + string metaData; }; } diff --git a/include/dcmqi/SegmentVolume.h b/include/dcmqi/SegmentVolume.h new file mode 100644 index 00000000..504693cd --- /dev/null +++ b/include/dcmqi/SegmentVolume.h @@ -0,0 +1,17 @@ +// +// Created by Andrey Fedorov on 3/9/17. +// + +#ifndef DCMQI_SEGMENTVOLUME_H +#define DCMQI_SEGMENTVOLUME_H + +#include "dcmqi/ImageVolume.h" + +namespace dcmqi { + class SegmentVolume : public ImageVolume { + + }; +} + + +#endif //DCMQI_SEGMENTVOLUME_H diff --git a/include/dcmqi/ImageSEGConverter.h b/include/dcmqi/SegmentationImageConverter.h similarity index 93% rename from include/dcmqi/ImageSEGConverter.h rename to include/dcmqi/SegmentationImageConverter.h index deb8e7f4..6158fc1f 100644 --- a/include/dcmqi/ImageSEGConverter.h +++ b/include/dcmqi/SegmentationImageConverter.h @@ -22,7 +22,7 @@ #include // DCMQI includes -#include "dcmqi/ConverterBase.h" +#include "dcmqi/MultiframeConverter.h" #include "dcmqi/JSONSegmentationMetaInformationHandler.h" using namespace std; @@ -32,7 +32,7 @@ typedef itk::LabelImageToLabelMapFilter LabelToLabelMapFilterTyp namespace dcmqi { - class ImageSEGConverter : public ConverterBase { + class SegmentationImageConverter : public MultiframeConverter { public: static DcmDataset* itkimage2dcmSegmentation(vector dcmDatasets, diff --git a/libsrc/CMakeLists.txt b/libsrc/CMakeLists.txt index 02e7caf2..b354c700 100644 --- a/libsrc/CMakeLists.txt +++ b/libsrc/CMakeLists.txt @@ -1,75 +1,98 @@ - -#----------------------------------------------------------------------------- -set(ADDITIONAL_SRCS) - -set(INCLUDE_DIR ../include/dcmqi) - -set(HDRS - ${INCLUDE_DIR}/preproc.h - ${INCLUDE_DIR}/QIICRConstants.h - ${INCLUDE_DIR}/QIICRUIDs.h - ${INCLUDE_DIR}/ConverterBase.h - ${INCLUDE_DIR}/Exceptions.h - ${INCLUDE_DIR}/framesorter.h - ${INCLUDE_DIR}/ImageSEGConverter.h - ${INCLUDE_DIR}/ParaMapConverter - ${INCLUDE_DIR}/Helper.h - ${INCLUDE_DIR}/JSONMetaInformationHandlerBase.h - ${INCLUDE_DIR}/JSONParametricMapMetaInformationHandler.h - ${INCLUDE_DIR}/JSONSegmentationMetaInformationHandler.h - ${INCLUDE_DIR}/SegmentAttributes.h - ) - -set(SRCS - ConverterBase.cpp - ImageSEGConverter.cpp - ParaMapConverter.cpp - Helper.cpp - JSONMetaInformationHandlerBase.cpp - JSONParametricMapMetaInformationHandler.cpp - JSONSegmentationMetaInformationHandler.cpp - SegmentAttributes.cpp - ) - - -if(DCMQI_BUILTIN_JSONCPP) - list(APPEND ADDITIONAL_SRCS - ${DCMQI_SOURCE_DIR}/jsoncpp/jsoncpp.cpp - ) - set(JsonCpp_INCLUDE_DIR ${DCMQI_SOURCE_DIR}/jsoncpp) -endif() - -set(lib_name dcmqi) - -add_library(${lib_name} STATIC - ${HDRS} - ${SRCS} - ${ADDITIONAL_SRCS} - ) - -if(DCMQI_LIBRARY_PROPERTIES) - set_target_properties(${lib_name} PROPERTIES ${DCMQI_LIBRARY_PROPERTIES}) -endif() - -set_property(GLOBAL APPEND PROPERTY ${CMAKE_PROJECT_NAME}_TARGETS ${lib_name}) - -set(${lib_name}_INCLUDE_DIRS - ${DCMTK_INCLUDE_DIRS} - ${ITK_INCLUDE_DIRS} - ${DCMQI_SOURCE_DIR}/include - ${DCMQI_BINARY_DIR}/include - ${JsonCpp_INCLUDE_DIR} - ) -if(ITK_INSTALL_PREFIX) - list(APPEND ${lib_name}_INCLUDE_DIRS - ${ITK_INSTALL_PREFIX}/include/vxl/core - ${ITK_INSTALL_PREFIX}/include/vxl/vcl - ) -endif() -target_include_directories(${lib_name} PUBLIC ${${lib_name}_INCLUDE_DIRS}) - -target_link_libraries(${lib_name} PUBLIC - ${DCMTK_LIBRARIES} - ${ITK_LIBRARIES} - $<$>:${JsonCpp_LIBRARY}> - ) + +#----------------------------------------------------------------------------- + +set(INCLUDE_DIR ../include/dcmqi) + +set(HDRS + ${INCLUDE_DIR}/DICOMFrame.h + ${INCLUDE_DIR}/ImageVolume.h + ${INCLUDE_DIR}/MultiframeConverter.h + ${INCLUDE_DIR}/Helper.h + ${INCLUDE_DIR}/SegmentationImageConverter.h + ${INCLUDE_DIR}/ParametricMapConverter.h + ${INCLUDE_DIR}/JSONMetaInformationHandlerBase.h + ${INCLUDE_DIR}/JSONParametricMapMetaInformationHandler.h + ${INCLUDE_DIR}/JSONSegmentationMetaInformationHandler.h + ${INCLUDE_DIR}/SegmentAttributes.h + ) + +set(SRCS + DICOMFrame.cpp + ImageVolume.cpp + MultiframeConverter.cpp + Helper.cpp + SegmentationImageConverter.cpp + ParametricMapConverter.cpp + JSONMetaInformationHandlerBase.cpp + JSONParametricMapMetaInformationHandler.cpp + JSONSegmentationMetaInformationHandler.cpp + SegmentAttributes.cpp + ) + +set(ADDITIONAL_HDRS + ${INCLUDE_DIR}/DICOMFrame.h + ${INCLUDE_DIR}/ImageVolume.h + ${INCLUDE_DIR}/SegmentVolume.h + ${INCLUDE_DIR}/MultiframeObject.h + ${INCLUDE_DIR}/ParametricMapObject.h + ${INCLUDE_DIR}/SegmentationImageObject.h + ${INCLUDE_DIR}/ImageVolumeGeometry.h + ${INCLUDE_DIR}/Exceptions.h + ${INCLUDE_DIR}/framesorter.h + ${INCLUDE_DIR}/preproc.h + ${INCLUDE_DIR}/QIICRConstants.h + ${INCLUDE_DIR}/QIICRUIDs.h + ) + +set(ADDITIONAL_SRCS + DICOMFrame.cpp + ImageVolume.cpp + SegmentVolume.cpp + MultiframeObject.cpp + ParametricMapObject.cpp + SegmentationImageObject.cpp + ImageVolumeGeometry.cpp + ) + +if(DCMQI_BUILTIN_JSONCPP) + list(APPEND ADDITIONAL_SRCS + ${DCMQI_SOURCE_DIR}/jsoncpp/jsoncpp.cpp + ) + set(JsonCpp_INCLUDE_DIR ${DCMQI_SOURCE_DIR}/jsoncpp) +endif() + +set(lib_name dcmqi) + +add_library(${lib_name} STATIC + ${HDRS} + ${SRCS} + ${ADDITIONAL_HDRS} + ${ADDITIONAL_SRCS} + ) + +if(DCMQI_LIBRARY_PROPERTIES) + set_target_properties(${lib_name} PROPERTIES ${DCMQI_LIBRARY_PROPERTIES}) +endif() + +set_property(GLOBAL APPEND PROPERTY ${CMAKE_PROJECT_NAME}_TARGETS ${lib_name}) + +set(${lib_name}_INCLUDE_DIRS + ${DCMTK_INCLUDE_DIRS} + ${ITK_INCLUDE_DIRS} + ${DCMQI_SOURCE_DIR}/include + ${DCMQI_BINARY_DIR}/include + ${JsonCpp_INCLUDE_DIR} + ) +if(ITK_INSTALL_PREFIX) + list(APPEND ${lib_name}_INCLUDE_DIRS + ${ITK_INSTALL_PREFIX}/include/vxl/core + ${ITK_INSTALL_PREFIX}/include/vxl/vcl + ) +endif() +target_include_directories(${lib_name} PUBLIC ${${lib_name}_INCLUDE_DIRS}) + +target_link_libraries(${lib_name} PUBLIC + ${DCMTK_LIBRARIES} + ${ITK_LIBRARIES} + $<$>:${JsonCpp_LIBRARY}> + ) \ No newline at end of file diff --git a/libsrc/DICOMFrame.cpp b/libsrc/DICOMFrame.cpp new file mode 100644 index 00000000..ecc42038 --- /dev/null +++ b/libsrc/DICOMFrame.cpp @@ -0,0 +1,5 @@ +// +// Created by Andrey Fedorov on 3/9/17. +// + +#include "dcmqi/DICOMFrame.h" diff --git a/libsrc/ImageVolume.cpp b/libsrc/ImageVolume.cpp new file mode 100644 index 00000000..2f12ecd2 --- /dev/null +++ b/libsrc/ImageVolume.cpp @@ -0,0 +1,214 @@ +// +// Created by Andrey Fedorov on 3/9/17. +// + +#include +#include +#include + +#include +#include +#include +#include "dcmqi/ImageVolume.h" + +using namespace std; + +int dcmqi::ImageVolume::initializeFromDICOM(FGInterface& fgInterface) { + if(initializeDirections(fgInterface)) + return EXIT_FAILURE; + if(initializeExtent(fgInterface)) + return EXIT_FAILURE; + return EXIT_SUCCESS; +} + + +int dcmqi::ImageVolume::initializeFromITK(Float32ITKImageType::Pointer itkImage){ + return EXIT_SUCCESS; +} + +int dcmqi::ImageVolume::initializeDirections(FGInterface &fgInterface) { + // TODO: handle the situation when FoR is not initialized + OFBool isPerFrame; + + FGPlaneOrientationPatient *planorfg = OFstatic_cast(FGPlaneOrientationPatient*, + fgInterface.get(0, DcmFGTypes::EFG_PLANEORIENTPATIENT, isPerFrame)); + if(!planorfg){ + cerr << "Plane Orientation (Patient) is missing, cannot parse input " << endl; + return EXIT_FAILURE; + } + + OFString orientStr; + for(int i=0;i<3;i++){ + if(planorfg->getImageOrientationPatient(orientStr, i).good()){ + rowDirection[i] = atof(orientStr.c_str()); + } else { + cerr << "Failed to get orientation " << i << endl; + return EXIT_FAILURE; + } + } + for(int i=3;i<6;i++){ + if(planorfg->getImageOrientationPatient(orientStr, i).good()){ + columnDirection[i-3] = atof(orientStr.c_str()); + } else { + cerr << "Failed to get orientation " << i << endl; + return EXIT_FAILURE; + } + } + vnl_vector sliceDirection = vnl_cross_3d(rowDirection, columnDirection); + sliceDirection.normalize(); + + cout << "Row direction: " << rowDirection << endl; + cout << "Col direction: " << columnDirection << endl; + cout << "Z direction: " << sliceDirection << endl; + + return EXIT_SUCCESS; +} + + +int dcmqi::ImageVolume::initializeExtent(FGInterface &fgInterface) { + // Size + // Rows/Columns can be read directly from the respective attributes + // For number of slices, consider that all segments must have the same number of frames. + // If we have FoR UID initialized, this means every segment should also have Plane + // Position (Patient) initialized. So we can get the number of slices by looking + // how many per-frame functional groups a segment has. + + vector originDistances; + map originStr2distance; + map frame2overlap; + double minDistance = 0.0; + + double calculatedSliceSpacing = 0; + + unsigned numFrames = fgInterface.getNumberOfFrames(); + + /* Framesorter is to be moved to DCMTK at some point + * in the future. For now it is causing build issues on windows + + FrameSorterIPP fsIPP; + FrameSorterIPP::Results sortResults; + fsIPP.setSorterInput(&fgInterface); + fsIPP.sort(sortResults); + + */ + + // Determine ordering of the frames, keep mapping from ImagePositionPatient string + // to the distance, and keep track (just out of curiosity) how many frames overlap + vnl_vector refOrigin(3); + { + OFBool isPerFrame; + FGPlanePosPatient *planposfg = OFstatic_cast(FGPlanePosPatient*, + fgInterface.get(0, DcmFGTypes::EFG_PLANEPOSPATIENT, isPerFrame)); + if(!isPerFrame){ + cerr << "PlanePositionPatient FG is shared ... cannot handle this!" << endl; + return EXIT_FAILURE; + } + + for(int j=0;j<3;j++){ + OFString planposStr; + if(planposfg->getImagePositionPatient(planposStr, j).good()){ + refOrigin[j] = atof(planposStr.c_str()); + } else { + cerr << "Failed to read patient position" << endl; + } + } + } + + for(size_t frameId=0;frameId sOrigin; + OFString sOriginStr = ""; + sOrigin.set_size(3); + for(int j=0;j<3;j++){ + OFString planposStr; + if(planposfg->getImagePositionPatient(planposStr, j).good()){ + sOrigin[j] = atof(planposStr.c_str()); + sOriginStr += planposStr; + if(j<2) + sOriginStr+='/'; + } else { + cerr << "Failed to read patient position" << endl; + return EXIT_FAILURE; + } + } + + // check if this frame has already been encountered + if(originStr2distance.find(sOriginStr) == originStr2distance.end()){ + vnl_vector difference; + difference.set_size(3); + difference[0] = sOrigin[0]-refOrigin[0]; + difference[1] = sOrigin[1]-refOrigin[1]; + difference[2] = sOrigin[2]-refOrigin[2]; + double dist = dot_product(difference,sliceDirection); + frame2overlap[sOriginStr] = 1; + originStr2distance[sOriginStr] = dist; + assert(originStr2distance.find(sOriginStr) != originStr2distance.end()); + originDistances.push_back(dist); + + if(frameId==0){ + minDistance = dist; + origin[0] = sOrigin[0]; + origin[1] = sOrigin[1]; + origin[2] = sOrigin[2]; + } + else if(dist1){ + // WARNING: this should be improved further. Spacing should be calculated for + // consecutive frames of the individual segment. Right now, all frames are considered + // indiscriminately. Question is whether it should be computed at all, considering we do + // not have any information about whether the 2 frames are adjacent or not, so perhaps we should + // always rely on the declared spacing, and not even try to compute it? + // TODO: discuss this with the QIICR team! + + // sort all unique distances, this will be used to check consistency of + // slice spacing, and also to locate the slice position from ImagePositionPatient + // later when we read the segments + sort(originDistances.begin(), originDistances.end()); + + calculatedSliceSpacing = fabs(originDistances[0]-originDistances[1]); + + for(size_t i=1;i::const_iterator it=frame2overlap.begin(); + it!=frame2overlap.end();++it){ + if(it->second>1) + overlappingFramesCnt++; + } + + cout << "Total frames: " << numFrames << endl; + cout << "Total frames with unique IPP: " << originDistances.size() << endl; + cout << "Total overlapping frames: " << overlappingFramesCnt << endl; + cout << "Origin: " << origin << endl; + } + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/libsrc/ConverterBase.cpp b/libsrc/MultiframeConverter.cpp similarity index 81% rename from libsrc/ConverterBase.cpp rename to libsrc/MultiframeConverter.cpp index 836209eb..1d4dce88 100644 --- a/libsrc/ConverterBase.cpp +++ b/libsrc/MultiframeConverter.cpp @@ -1,11 +1,11 @@ // DCMQI includes -#include "dcmqi/ConverterBase.h" +#include "dcmqi/MultiframeConverter.h" namespace dcmqi { - IODGeneralEquipmentModule::EquipmentInfo ConverterBase::getEquipmentInfo() { + IODGeneralEquipmentModule::EquipmentInfo MultiframeConverter::getEquipmentInfo() { // TODO: change to following for most recent dcmtk // return IODGeneralEquipmentModule::EquipmentInfo(QIICR_MANUFACTURER, QIICR_DEVICE_SERIAL_NUMBER, // QIICR_MANUFACTURER_MODEL_NAME, QIICR_SOFTWARE_VERSIONS); @@ -17,13 +17,13 @@ namespace dcmqi { return eq; } - IODEnhGeneralEquipmentModule::EquipmentInfo ConverterBase::getEnhEquipmentInfo() { + IODEnhGeneralEquipmentModule::EquipmentInfo MultiframeConverter::getEnhEquipmentInfo() { return IODEnhGeneralEquipmentModule::EquipmentInfo(QIICR_MANUFACTURER, QIICR_DEVICE_SERIAL_NUMBER, QIICR_MANUFACTURER_MODEL_NAME, QIICR_SOFTWARE_VERSIONS); } // TODO: defaults for sub classes needs to be defined - ContentIdentificationMacro ConverterBase::createContentIdentificationInformation(JSONMetaInformationHandlerBase &metaInfo) { + ContentIdentificationMacro MultiframeConverter::createContentIdentificationInformation(JSONMetaInformationHandlerBase &metaInfo) { ContentIdentificationMacro ident; CHECK_COND(ident.setContentCreatorName("dcmqi")); if(metaInfo.metaInfoRoot["seriesAttributes"].isMember("ContentDescription")){ diff --git a/libsrc/ParaMapConverter.cpp b/libsrc/ParametricMapConverter.cpp similarity index 95% rename from libsrc/ParaMapConverter.cpp rename to libsrc/ParametricMapConverter.cpp index ca3685f7..0541fb6e 100644 --- a/libsrc/ParaMapConverter.cpp +++ b/libsrc/ParametricMapConverter.cpp @@ -4,15 +4,29 @@ #include // DCMQI includes -#include "dcmqi/ParaMapConverter.h" -#include "dcmqi/ImageSEGConverter.h" +#include "dcmqi/ParametricMapConverter.h" +#include "dcmqi/SegmentationImageConverter.h" using namespace std; namespace dcmqi { - DcmDataset* ParaMapConverter::itkimage2paramap(const FloatImageType::Pointer ¶metricMapImage, vector dcmDatasets, + DcmDataset* itkimage2paramapReplacement(const FloatImageType::Pointer ¶metricMapImage, vector dcmDatasets, const string &metaData) { + /* + ParametricMapConverter pmConverter(parametricMapImage, dcmDatasets, metaData); + pmConverter.convert(); + + return convert.getDataset(); */ + return NULL; + } + + pair paramap2itkimageReplacement(DcmDataset *pmapDataset){ + return pair(); + }; + + DcmDataset* ParametricMapConverter::itkimage2paramap(const FloatImageType::Pointer ¶metricMapImage, vector dcmDatasets, + const string &metaData) { MinMaxCalculatorType::Pointer calculator = MinMaxCalculatorType::New(); calculator->SetImage(parametricMapImage); @@ -24,8 +38,8 @@ namespace dcmqi { metaInfo.setFirstValueMapped(calculator->GetMinimum()); metaInfo.setLastValueMapped(calculator->GetMaximum()); - IODEnhGeneralEquipmentModule::EquipmentInfo eq = getEnhEquipmentInfo(); - ContentIdentificationMacro contentID = createContentIdentificationInformation(metaInfo); + IODEnhGeneralEquipmentModule::EquipmentInfo eq = ParametricMapConverter::getEnhEquipmentInfo(); + ContentIdentificationMacro contentID = ParametricMapConverter::createContentIdentificationInformation(metaInfo); CHECK_COND(contentID.setInstanceNumber(metaInfo.getInstanceNumber().c_str())); // TODO: following should maybe be moved to meta info @@ -432,7 +446,7 @@ namespace dcmqi { return output; } - pair ParaMapConverter::paramap2itkimage(DcmDataset *pmapDataset) { + pair ParametricMapConverter::paramap2itkimage(DcmDataset *pmapDataset) { DcmRLEDecoderRegistration::registerCodecs(); @@ -548,7 +562,7 @@ namespace dcmqi { return pair (pmImage, metaInfo.getJSONOutputAsString()); } - OFCondition ParaMapConverter::addFrame(DPMParametricMapIOD &map, const FloatImageType::Pointer ¶metricMapImage, + OFCondition ParametricMapConverter::addFrame(DPMParametricMapIOD &map, const FloatImageType::Pointer ¶metricMapImage, const JSONParametricMapMetaInformationHandler &metaInfo, const unsigned long frameNo, OFVector groups) { @@ -608,7 +622,7 @@ namespace dcmqi { return result; } - void ParaMapConverter::populateMetaInformationFromDICOM(DcmDataset *pmapDataset, + void ParametricMapConverter::populateMetaInformationFromDICOM(DcmDataset *pmapDataset, JSONParametricMapMetaInformationHandler &metaInfo) { OFvariant result = DPMParametricMapIOD::loadDataset(*pmapDataset); diff --git a/libsrc/SegmentVolume.cpp b/libsrc/SegmentVolume.cpp new file mode 100644 index 00000000..e37c473a --- /dev/null +++ b/libsrc/SegmentVolume.cpp @@ -0,0 +1,5 @@ +// +// Created by Andrey Fedorov on 3/9/17. +// + +#include "dcmqi/SegmentVolume.h" diff --git a/libsrc/ImageSEGConverter.cpp b/libsrc/SegmentationImageConverter.cpp similarity index 98% rename from libsrc/ImageSEGConverter.cpp rename to libsrc/SegmentationImageConverter.cpp index 81c974cf..7a85a322 100644 --- a/libsrc/ImageSEGConverter.cpp +++ b/libsrc/SegmentationImageConverter.cpp @@ -1,11 +1,11 @@ // DCMQI includes -#include "dcmqi/ImageSEGConverter.h" +#include "dcmqi/SegmentationImageConverter.h" namespace dcmqi { - DcmDataset* ImageSEGConverter::itkimage2dcmSegmentation(vector dcmDatasets, + DcmDataset* SegmentationImageConverter::itkimage2dcmSegmentation(vector dcmDatasets, vector segmentations, const string &metaData, bool skipEmptySlices) { @@ -447,7 +447,7 @@ namespace dcmqi { } - pair , string> ImageSEGConverter::dcmSegmentation2itkimage(DcmDataset *segDataset) { + pair , string> SegmentationImageConverter::dcmSegmentation2itkimage(DcmDataset *segDataset) { DcmRLEDecoderRegistration::registerCodecs(); @@ -703,7 +703,7 @@ namespace dcmqi { return pair , string>(segment2image, metaInfo.getJSONOutputAsString()); } - void ImageSEGConverter::populateMetaInformationFromDICOM(DcmDataset *segDataset, DcmSegmentation *segdoc, + void SegmentationImageConverter::populateMetaInformationFromDICOM(DcmDataset *segDataset, DcmSegmentation *segdoc, JSONSegmentationMetaInformationHandler &metaInfo) { OFString creatorName, sessionID, timePointID, seriesDescription, seriesNumber, instanceNumber, bodyPartExamined, coordinatingCenter; From 9266df0dfe8bc5d3c6795b62c99c079b11edbcdd Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Sat, 11 Mar 2017 12:24:19 -0500 Subject: [PATCH 002/116] BUG: initialize modality from the source dataset --- libsrc/ParametricMapConverter.cpp | 36 ++++++++++++++++++------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/libsrc/ParametricMapConverter.cpp b/libsrc/ParametricMapConverter.cpp index 0541fb6e..5d7d1f75 100644 --- a/libsrc/ParametricMapConverter.cpp +++ b/libsrc/ParametricMapConverter.cpp @@ -25,9 +25,20 @@ namespace dcmqi { return pair(); }; + + ParametricMapConverter::ParametricMapConverter(const FloatImageType::Pointer ¶metricMapImage, vector dcmDatasets, + const string &metaData){ + this->originalRepresentation = ITK_REPR; + } + + ParametricMapConverter::ParametricMapConverter(DcmDataset* dcmDataset){ + this->originalRepresentation = DICOM_REPR; + } + DcmDataset* ParametricMapConverter::itkimage2paramap(const FloatImageType::Pointer ¶metricMapImage, vector dcmDatasets, const string &metaData) { + // Calculate intensity range - required MinMaxCalculatorType::Pointer calculator = MinMaxCalculatorType::New(); calculator->SetImage(parametricMapImage); calculator->Compute(); @@ -42,15 +53,15 @@ namespace dcmqi { ContentIdentificationMacro contentID = ParametricMapConverter::createContentIdentificationInformation(metaInfo); CHECK_COND(contentID.setInstanceNumber(metaInfo.getInstanceNumber().c_str())); - // TODO: following should maybe be moved to meta info - OFString imageFlavor = "VOLUME"; - OFString pixContrast = "NONE"; - if(metaInfo.metaInfoRoot.isMember("DerivedPixelContrast")){ - pixContrast = metaInfo.metaInfoRoot["DerivedPixelContrast"].asCString(); - } - // TODO: initialize modality from the source / add to schema? - OFString modality = "MR"; + DcmDataset* srcDataset = NULL; + if(dcmDatasets.size()) { + srcDataset = dcmDatasets[0]; + } else { + return NULL; + } + OFString modality; + srcDataset->findAndGetOFString(DCM_Modality, modality); FloatImageType::SizeType inputSize = parametricMapImage->GetBufferedRegion().GetSize(); cout << "Input image size: " << inputSize << endl; @@ -59,18 +70,13 @@ namespace dcmqi { DPMParametricMapIOD::create(modality, metaInfo.getSeriesNumber().c_str(), metaInfo.getInstanceNumber().c_str(), inputSize[1], inputSize[0], eq, contentID, - imageFlavor, pixContrast, DPMTypes::CQ_RESEARCH); + "VOLUME", "QUANTITY", DPMTypes::CQ_RESEARCH); if (OFCondition* pCondition = OFget(&obj)) return NULL; DPMParametricMapIOD* pMapDoc = OFget(&obj); - DcmDataset* srcDataset = NULL; - if(dcmDatasets.size()){ - srcDataset = dcmDatasets[0]; - } - if (srcDataset) - CHECK_COND(pMapDoc->import(*srcDataset, OFTrue, OFTrue, OFFalse, OFTrue)); + CHECK_COND(pMapDoc->import(*srcDataset, OFTrue, OFTrue, OFFalse, OFTrue)); /* Initialize dimension module */ char dimUID[128]; From 5fbb7f1e113cba6f8cabaec0a87ce92c82b1abf6 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Tue, 14 Mar 2017 12:18:05 -0400 Subject: [PATCH 003/116] ENH: a different shot at refactoring --- include/dcmqi/Helper.h | 4 +- include/dcmqi/ImageVolume.h | 12 +- include/dcmqi/ImageVolumeGeometry.h | 47 +++++++ .../JSONSegmentationMetaInformationHandler.h | 1 + include/dcmqi/MultiframeConverter.h | 7 +- include/dcmqi/MultiframeObject.h | 131 ++++++++++++++++++ include/dcmqi/ParametricMapConverter.h | 3 +- include/dcmqi/ParametricMapObject.h | 62 +++++++++ include/dcmqi/SegmentationImageObject.h | 16 +++ libsrc/Helper.cpp | 11 ++ libsrc/ImageVolumeGeometry.cpp | 42 ++++++ libsrc/MultiframeObject.cpp | 86 ++++++++++++ libsrc/ParametricMapConverter.cpp | 63 ++++----- libsrc/ParametricMapObject.cpp | 90 ++++++++++++ libsrc/SegmentationImageObject.cpp | 5 + 15 files changed, 543 insertions(+), 37 deletions(-) create mode 100644 include/dcmqi/ImageVolumeGeometry.h create mode 100644 include/dcmqi/MultiframeObject.h create mode 100644 include/dcmqi/ParametricMapObject.h create mode 100644 include/dcmqi/SegmentationImageObject.h create mode 100644 libsrc/ImageVolumeGeometry.cpp create mode 100644 libsrc/MultiframeObject.cpp create mode 100644 libsrc/ParametricMapObject.cpp create mode 100644 libsrc/SegmentationImageObject.cpp diff --git a/include/dcmqi/Helper.h b/include/dcmqi/Helper.h index 903bd8d0..ea2dbba3 100644 --- a/include/dcmqi/Helper.h +++ b/include/dcmqi/Helper.h @@ -19,7 +19,6 @@ using namespace std; - namespace dcmqi { @@ -64,6 +63,9 @@ namespace dcmqi { static void checkValidityOfFirstSrcImage(DcmSegmentation *segdoc); static CodeSequenceMacro* createNewCodeSequence(const string& code, const string& designator, const string& meaning); + + static OFString generateUID(); + static OFString getTagAsOFString(DcmDataset*, DcmTagKey); }; } diff --git a/include/dcmqi/ImageVolume.h b/include/dcmqi/ImageVolume.h index 13d37ef4..b6ebf83e 100644 --- a/include/dcmqi/ImageVolume.h +++ b/include/dcmqi/ImageVolume.h @@ -15,7 +15,11 @@ namespace dcmqi { // Maintain properties of the image volume // Attributes ara parallel to those of itkImageData, but limited to what we need for the task of conversion, - // and the class is not templated over the pixel type + // and the class is not templated over the pixel type, since we may not know the pixel type + // at the time class is instantiated. + // + // Initially, limit implementation and support to just Float32 used by the original PM converter. + class ImageVolume { public: // pixel types that are relevant for the types of objects we want to support @@ -47,7 +51,11 @@ namespace dcmqi { protected: int initializeDirections(FGInterface &); int initializeExtent(FGInterface &); - bool getDeclaredSpacing(FGInterface&); + bool getDeclaredSliceSpacing(FGInterface&); + bool getCalculatedSliceSpacing(); + + int setDirections(vnl_vector rowDirection, vnl_vector columnDirection, vnl_vector sliceDirection); + int setOrigin(vnl_vector); private: diff --git a/include/dcmqi/ImageVolumeGeometry.h b/include/dcmqi/ImageVolumeGeometry.h new file mode 100644 index 00000000..3857027c --- /dev/null +++ b/include/dcmqi/ImageVolumeGeometry.h @@ -0,0 +1,47 @@ +// +// Created by Andrey Fedorov on 3/11/17. +// + +#ifndef DCMQI_IMAGEVOLUMEGEOMETRY_H +#define DCMQI_IMAGEVOLUMEGEOMETRY_H + + +#include +#include +#include +#include + +class ImageVolumeGeometry { + + friend class MultiframeObject; + friend class ParametricMapObject; + +public: + typedef itk::Vector DoubleVectorType; + typedef itk::Size<3> SizeType; + + // implementation of the image volume geometry + // NB: duplicated from MultiframeObject! + typedef unsigned char DummyPixelType; + typedef itk::Image DummyImageType; + typedef DummyImageType::PointType PointType; + typedef DummyImageType::DirectionType DirectionType; + + ImageVolumeGeometry(); + + int setSpacing(DoubleVectorType); + int setOrigin(PointType); + int setExtent(SizeType); + int setDirections(DirectionType); + +protected: + // use vnl_vector to simplify support of vector calculations + vnl_vector rowDirection, columnDirection, sliceDirection; + vnl_vector origin; + vnl_vector extent; + vnl_vector spacing; + +}; + + +#endif //DCMQI_IMAGEVOLUMEGEOMETRY_H diff --git a/include/dcmqi/JSONSegmentationMetaInformationHandler.h b/include/dcmqi/JSONSegmentationMetaInformationHandler.h index 219b0087..7a893b15 100644 --- a/include/dcmqi/JSONSegmentationMetaInformationHandler.h +++ b/include/dcmqi/JSONSegmentationMetaInformationHandler.h @@ -47,6 +47,7 @@ namespace dcmqi { void readSegmentAttributes(); Json::Value createAndGetSegmentAttributes(); + }; } diff --git a/include/dcmqi/MultiframeConverter.h b/include/dcmqi/MultiframeConverter.h index 45b1a3c9..ff0b5527 100644 --- a/include/dcmqi/MultiframeConverter.h +++ b/include/dcmqi/MultiframeConverter.h @@ -44,9 +44,12 @@ namespace dcmqi { class MultiframeConverter { public: - virtual int convert(); + //virtual int convert(); + MultiframeConverter(){ + }; + + - protected: static IODGeneralEquipmentModule::EquipmentInfo getEquipmentInfo(); static IODEnhGeneralEquipmentModule::EquipmentInfo getEnhEquipmentInfo(); static ContentIdentificationMacro createContentIdentificationInformation(JSONMetaInformationHandlerBase &metaInfo); diff --git a/include/dcmqi/MultiframeObject.h b/include/dcmqi/MultiframeObject.h new file mode 100644 index 00000000..715b45fc --- /dev/null +++ b/include/dcmqi/MultiframeObject.h @@ -0,0 +1,131 @@ +// +// Created by Andrey Fedorov on 3/11/17. +// + +#ifndef DCMQI_MULTIFRAMEOBJECT_H +#define DCMQI_MULTIFRAMEOBJECT_H + + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "dcmqi/Exceptions.h" +#include "dcmqi/ImageVolumeGeometry.h" + +using namespace std; +/* + * Class to support conversion between DICOM and ITK representations + * of multiframe objects + * + * + */ +class MultiframeObject { + +public: + + // initialization from DICOM is always from the dataset(s) + // that encode DICOM representation, optionally augmented by the + // derivation datasets that potentially can be helpful in some + // situations. + int initializeFromDICOM(std::vector sourceDataset); + int initializeFromDICOM(std::vector sourceDatasets, + std::vector derivationDatasets); + + // initialization from ITK will be specific to the derived types, + // although there will be some common initialization of the metadata + + // Output is always a single DcmDataset, since this is a multiframe + // object + DcmDataset* getDcmDataset() const; + Json::Value getMetaDataJson() const; + + // get ITK representation will be specific to the derived classes, + // since the type of the ITK image and the number of ITK images is + // different between PM and SEG + +protected: + + // Helpers to convert to dummy image type to support common + // implementation of the image volume geometry + typedef unsigned char DummyPixelType; + typedef itk::Image DummyImageType; + typedef DummyImageType::PointType PointType; + typedef DummyImageType::DirectionType DirectionType; + + int initializeMetaDataFromString(const std::string&); + // what this function does depends on whether we are coming from + // DICOM or from ITK. No parameters, since all it does is exchange + // between DICOM and MetaData + int initializeEquipmentInfo(); + int initializeContentIdentification(); + + // from ITK + int initializeVolumeGeometryFromITK(DummyImageType::Pointer); + + virtual int initializeCompositeContext(); + virtual bool metaDataIsComplete(); + + // List of tags, and FGs they belong to, for initializing dimensions module + int initializeDimensions(IODMultiframeDimensionModule&, std::vector >); + + // constants to describe original representation of the data being converted + enum { + DICOM_REPR = 0, + ITK_REPR + }; + int sourceRepresentationType; + + // TODO: abstract this into a different class, which would help with: + // - checking for presence of attributes + // - handling of defaults (in the future, initialized from the schema?) + // - simplifying common access patterns (access to the Code tuples) + Json::Value metaDataJson; + + // Multiframe DICOM object representation + DcmDataset* dcmRepresentation; + + ImageVolumeGeometry volumeGeometry; + + // DcmDataset(s) that hold the original representation of the + // object, when the sourceRepresentationType == DICOM_REPR + OFVector sourceDcmDatasets; + + // Common components present in the derived classes + // TODO: check whether both PM and SEG use Enh module or not, refactor based on that + IODEnhGeneralEquipmentModule::EquipmentInfo equipmentInfoModule; + ContentIdentificationMacro contentIdentificationMacro; + IODMultiframeDimensionModule dimensionsModule; + + // DcmDataset(s) that were used to derive the object + // Probably will only be populated when sourceRepresentationType == ITK_REPR + // Purpose of those: + // 1) initialize derivation derivationImageFG (ITK->) + // 2) initialize CommonInstanceReferenceModule (ITK->) + // 3) initialize common attributes (ITK->) + OFVector derivationDcmDatasets; + + // Functional groups common to all MF objects: + // - Shared + FGPixelMeasures pixelMeasuresFG; + FGPlaneOrientationPatient planeOrientationPatientFG; + // - Per-frame + OFVector planePosPatientFGList; + OFVector frameContentFGList; + OFVector derivationImageFGList; + +}; + + +#endif //DCMQI_MULTIFRAMEOBJECT_H diff --git a/include/dcmqi/ParametricMapConverter.h b/include/dcmqi/ParametricMapConverter.h index 6545e39c..57c244d3 100644 --- a/include/dcmqi/ParametricMapConverter.h +++ b/include/dcmqi/ParametricMapConverter.h @@ -50,7 +50,7 @@ namespace dcmqi { const string &metaData); static pair paramap2itkimage(DcmDataset *pmapDataset); - // do the conversion + // given one representation, generate the parallel one int convert(); // get the result @@ -75,6 +75,7 @@ namespace dcmqi { // these are the items we will need in the process of conversion vector referencedDatasets; string metaData; + }; } diff --git a/include/dcmqi/ParametricMapObject.h b/include/dcmqi/ParametricMapObject.h new file mode 100644 index 00000000..32cbcf3f --- /dev/null +++ b/include/dcmqi/ParametricMapObject.h @@ -0,0 +1,62 @@ +// +// Created by Andrey Fedorov on 3/11/17. +// + +#ifndef DCMQI_PARAMETRICMAPOBJECT_H +#define DCMQI_PARAMETRICMAPOBJECT_H + + +#include +#include +#include +#include +#include +#include + +#include "MultiframeObject.h" + +/* + * + */ +class ParametricMapObject : public MultiframeObject { +public: + typedef IODFloatingPointImagePixelModule::value_type Float32PixelType; + typedef itk::Image Float32ITKImageType; + + // metadata is mandatory, since not all of the attributes can be present + // in the derivation DcmDataset(s) + int initializeFromITK(Float32ITKImageType::Pointer, const string&); + // metadata is mandatory, optionally, derivation DcmDataset(s) can + // help + int initializeFromITK(Float32ITKImageType::Pointer, const string&, + std::vector); + + int updateMetaDataFromDICOM(std::vector); + +protected: + typedef itk::CastImageFilter + Float32ToDummyCasterType; + + int initializeVolumeGeometry(); + int createParametricMap(); + int initializeDimensionsModule(); + int initializeCompositeContext(); + + // Functional groups initialization + + // Functional groups specific to PM: + // - Shared + FGFrameAnatomy frameAnatomyFG; + FGIdentityPixelValueTransformation identityPixelValueTransformationFG; + FGParametricMapFrameType parametricMapFrameTypeFG; + FGRealWorldValueMapping rwvmFG; + + // Data containers specific to this object + Float32ITKImageType::Pointer itkImage; + +private: + DPMParametricMapIOD* parametricMap; +}; + + +#endif //DCMQI_PARAMETRICMAPOBJECT_H diff --git a/include/dcmqi/SegmentationImageObject.h b/include/dcmqi/SegmentationImageObject.h new file mode 100644 index 00000000..74b7e8d8 --- /dev/null +++ b/include/dcmqi/SegmentationImageObject.h @@ -0,0 +1,16 @@ +// +// Created by Andrey Fedorov on 3/11/17. +// + +#ifndef DCMQI_SEGMENTATIONIMAGEOBJECT_H +#define DCMQI_SEGMENTATIONIMAGEOBJECT_H + + +#include "MultiframeObject.h" + +class SegmentationImageObject : public MultiframeObject { + +}; + + +#endif //DCMQI_SEGMENTATIONIMAGEOBJECT_H diff --git a/libsrc/Helper.cpp b/libsrc/Helper.cpp index 92f69c3a..6bcf8ecb 100644 --- a/libsrc/Helper.cpp +++ b/libsrc/Helper.cpp @@ -1,5 +1,6 @@ // DCMQI includes +#include #include "dcmqi/Helper.h" namespace dcmqi { @@ -368,5 +369,15 @@ namespace dcmqi { return new CodeSequenceMacro(code.c_str(), designator.c_str(), meaning.c_str()); } + OFString Helper::generateUID() { + char charUID[128]; + dcmGenerateUniqueIdentifier(charUID, QIICR_UID_ROOT); + return OFString(charUID); + } + OFString Helper::getTagAsOFString(DcmDataset* dcm, DcmTagKey tag) { + OFString value; + CHECK_COND(dcm->findAndGetOFString(tag, value)); + return value; + } } diff --git a/libsrc/ImageVolumeGeometry.cpp b/libsrc/ImageVolumeGeometry.cpp new file mode 100644 index 00000000..10570b0f --- /dev/null +++ b/libsrc/ImageVolumeGeometry.cpp @@ -0,0 +1,42 @@ +// +// Created by Andrey Fedorov on 3/11/17. +// + +#include "dcmqi/ImageVolumeGeometry.h" + +ImageVolumeGeometry::ImageVolumeGeometry() { + rowDirection.set_size(3); + columnDirection.set_size(3); + sliceDirection.set_size(3); + spacing.set_size(3); + extent.set_size(3); + origin.set_size(3); +} + +int ImageVolumeGeometry::setSpacing(DoubleVectorType s) { + for(int i=0;i<3;i++) + spacing[i] = s[i]; + return EXIT_SUCCESS; +} + +int ImageVolumeGeometry::setOrigin(PointType s) { + for(int i=0;i<3;i++) + origin[i] = s[i]; + return EXIT_SUCCESS; +} + +int ImageVolumeGeometry::setExtent(SizeType s) { + for (int i = 0; i < 3; i++) + extent[i] = s[i]; + return EXIT_SUCCESS; +} + +int ImageVolumeGeometry::setDirections(DirectionType d) { + for (int i = 0; i < 3; i++) + rowDirection[i] = d[i][0]; + for (int i = 0; i < 3; i++) + columnDirection[i] = d[i][1]; + for (int i = 0; i < 3; i++) + sliceDirection[i] = d[i][2]; + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp new file mode 100644 index 00000000..563a0012 --- /dev/null +++ b/libsrc/MultiframeObject.cpp @@ -0,0 +1,86 @@ +// +// Created by Andrey Fedorov on 3/11/17. +// + +#include +#include "dcmqi/QIICRConstants.h" +#include "dcmqi/MultiframeObject.h" + +int MultiframeObject::initializeFromDICOM(std::vector sourceDataset) { + return EXIT_SUCCESS; +} + +int MultiframeObject::initializeMetaDataFromString(const std::string &metaDataStr) { + std::istringstream metaDataStream(metaDataStr); + metaDataStream >> metaDataJson; + return EXIT_SUCCESS; +} + +int MultiframeObject::initializeEquipmentInfo() { + if(sourceRepresentationType == ITK_REPR){ + equipmentInfoModule.m_Manufacturer = QIICR_MANUFACTURER; + equipmentInfoModule.m_DeviceSerialNumber = QIICR_DEVICE_SERIAL_NUMBER; + equipmentInfoModule.m_ManufacturerModelName = QIICR_MANUFACTURER_MODEL_NAME; + equipmentInfoModule.m_SoftwareVersions = QIICR_SOFTWARE_VERSIONS; + } else { // DICOM_REPR + } + return EXIT_SUCCESS; +} + +int MultiframeObject::initializeContentIdentification() { + if(sourceRepresentationType == ITK_REPR){ + CHECK_COND(contentIdentificationMacro.setContentCreatorName("dcmqi")); + if(metaDataJson.isMember("ContentDescription")){ + CHECK_COND(contentIdentificationMacro.setContentDescription(metaDataJson["ContentDescription"].asCString())); + } else { + CHECK_COND(contentIdentificationMacro.setContentDescription("DCMQI")); + } + if(metaDataJson.isMember("ContentLabel")){ + CHECK_COND(contentIdentificationMacro.setContentLabel(metaDataJson["ContentLabel"].asCString())); + } else { + CHECK_COND(contentIdentificationMacro.setContentLabel("DCMQI")); + } + return EXIT_SUCCESS; + } else { // DICOM_REPR + } + return EXIT_SUCCESS; +} + +int MultiframeObject::initializeVolumeGeometryFromITK(DummyImageType::Pointer image) { + DummyImageType::SpacingType spacing; + DummyImageType::PointType origin; + DummyImageType::DirectionType directions; + DummyImageType::SizeType extent; + + spacing = image->GetSpacing(); + directions = image->GetDirection(); + extent = image->GetLargestPossibleRegion().GetSize(); + + volumeGeometry.setSpacing(spacing); + volumeGeometry.setOrigin(origin); + volumeGeometry.setExtent(extent); + + volumeGeometry.setDirections(directions); + + return EXIT_SUCCESS; +} + +// for now, we do not support initialization from JSON only, +// and we don't have a way to validate metadata completeness - TODO! +bool MultiframeObject::metaDataIsComplete() { + return false; +} + +// List of tags, and FGs they belong to, for initializing dimensions module +int MultiframeObject::initializeDimensions(IODMultiframeDimensionModule& dimModule, + std::vector > dimTagList){ + OFString dimUID; + + dimUID = dcmqi::Helper::generateUID(); + for(int i=0;i dimTagPair = dimTagList[i]; + CHECK_COND(dimModule.addDimensionIndex(dimTagPair.first, dimUID, dimTagPair.second, + dimTagPair.first.getTagName())); + } + return EXIT_SUCCESS; +} diff --git a/libsrc/ParametricMapConverter.cpp b/libsrc/ParametricMapConverter.cpp index 5d7d1f75..0da3b0ca 100644 --- a/libsrc/ParametricMapConverter.cpp +++ b/libsrc/ParametricMapConverter.cpp @@ -26,34 +26,19 @@ namespace dcmqi { }; - ParametricMapConverter::ParametricMapConverter(const FloatImageType::Pointer ¶metricMapImage, vector dcmDatasets, - const string &metaData){ - this->originalRepresentation = ITK_REPR; - } - - ParametricMapConverter::ParametricMapConverter(DcmDataset* dcmDataset){ - this->originalRepresentation = DICOM_REPR; - } DcmDataset* ParametricMapConverter::itkimage2paramap(const FloatImageType::Pointer ¶metricMapImage, vector dcmDatasets, const string &metaData) { - // Calculate intensity range - required - MinMaxCalculatorType::Pointer calculator = MinMaxCalculatorType::New(); - calculator->SetImage(parametricMapImage); - calculator->Compute(); - JSONParametricMapMetaInformationHandler metaInfo(metaData); metaInfo.read(); - metaInfo.setFirstValueMapped(calculator->GetMinimum()); - metaInfo.setLastValueMapped(calculator->GetMaximum()); - + // Prepare ContentIdentification IODEnhGeneralEquipmentModule::EquipmentInfo eq = ParametricMapConverter::getEnhEquipmentInfo(); ContentIdentificationMacro contentID = ParametricMapConverter::createContentIdentificationInformation(metaInfo); CHECK_COND(contentID.setInstanceNumber(metaInfo.getInstanceNumber().c_str())); - // TODO: initialize modality from the source / add to schema? + // Get image modality from the source dataset DcmDataset* srcDataset = NULL; if(dcmDatasets.size()) { srcDataset = dcmDatasets[0]; @@ -61,11 +46,13 @@ namespace dcmqi { return NULL; } OFString modality; - srcDataset->findAndGetOFString(DCM_Modality, modality); + CHECK_COND(srcDataset->findAndGetOFString(DCM_Modality, modality)); + // Get size of the image to be converted FloatImageType::SizeType inputSize = parametricMapImage->GetBufferedRegion().GetSize(); cout << "Input image size: " << inputSize << endl; + // create Parametric map object OFvariant obj = DPMParametricMapIOD::create(modality, metaInfo.getSeriesNumber().c_str(), metaInfo.getInstanceNumber().c_str(), @@ -76,15 +63,17 @@ namespace dcmqi { DPMParametricMapIOD* pMapDoc = OFget(&obj); + // import metadata from the source datasets + // TODO: not clear why readFoR is set to False CHECK_COND(pMapDoc->import(*srcDataset, OFTrue, OFTrue, OFFalse, OFTrue)); /* Initialize dimension module */ - char dimUID[128]; - dcmGenerateUniqueIdentifier(dimUID, QIICR_UID_ROOT); IODMultiframeDimensionModule &mfdim = pMapDoc->getIODMultiframeDimensionModule(); - OFCondition result = mfdim.addDimensionIndex(DCM_ImagePositionPatient, dimUID, + OFCondition result = mfdim.addDimensionIndex(DCM_ImagePositionPatient, Helper::generateUID(), DCM_RealWorldValueMappingSequence, "Frame position"); + // Initialize PM geometry + // Shared FGs: PixelMeasuresSequence { FGPixelMeasures *pixmsr = new FGPixelMeasures(); @@ -98,6 +87,8 @@ namespace dcmqi { spacingSStream << scientific << labelSpacing[2]; CHECK_COND(pixmsr->setSpacingBetweenSlices(spacingSStream.str().c_str())); CHECK_COND(pixmsr->setSliceThickness(spacingSStream.str().c_str())); + + // SHARED CHECK_COND(pMapDoc->addForAllFrames(*pixmsr)); } @@ -119,9 +110,12 @@ namespace dcmqi { Helper::floatToStrScientific(labelDirMatrix[2][1]).c_str()); //CHECK_COND(planor->setImageOrientationPatient(imageOrientationPatientStr)); + // SHARED CHECK_COND(pMapDoc->addForAllFrames(*planor)); } + + // Initialize metadata - anatomy FGFrameAnatomy frameAnaFG; frameAnaFG.setLaterality(FGFrameAnatomy::str2Laterality(metaInfo.getFrameLaterality().c_str())); if(metaInfo.metaInfoRoot.isMember("AnatomicRegionSequence")){ @@ -132,6 +126,8 @@ namespace dcmqi { } else { frameAnaFG.getAnatomy().getAnatomicRegion().set("T-D0050", "SRT", "Tissue"); } + + // SHARED CHECK_COND(pMapDoc->addForAllFrames(frameAnaFG)); FGIdentityPixelValueTransformation idTransFG; @@ -141,8 +137,11 @@ namespace dcmqi { FGParametricMapFrameType frameTypeFG; std::string frameTypeStr = "DERIVED\\PRIMARY\\VOLUME\\QUANTITY"; frameTypeFG.setFrameType(frameTypeStr.c_str()); + + // SHARED CHECK_COND(pMapDoc->addForAllFrames(frameTypeFG)); + // Initialize RWVM FGRealWorldValueMapping rwvmFG; FGRealWorldValueMapping::RWVMItem* realWorldValueMappingItem = new FGRealWorldValueMapping::RWVMItem(); if (!realWorldValueMappingItem ) @@ -153,8 +152,13 @@ namespace dcmqi { realWorldValueMappingItem->setRealWorldValueSlope(metaInfo.getRealWorldValueSlope()); realWorldValueMappingItem->setRealWorldValueIntercept(atof(metaInfo.getRealWorldValueIntercept().c_str())); - realWorldValueMappingItem->setRealWorldValueFirstValueMappedSigned(metaInfo.getFirstValueMapped()); - realWorldValueMappingItem->setRealWorldValueLastValueMappedSigned(metaInfo.getLastValueMapped()); + // Calculate intensity range - required + MinMaxCalculatorType::Pointer calculator = MinMaxCalculatorType::New(); + calculator->SetImage(parametricMapImage); + calculator->Compute(); + + realWorldValueMappingItem->setRealWorldValueFirstValueMappedSigned(calculator->GetMinimum()); + realWorldValueMappingItem->setRealWorldValueLastValueMappedSigned(calculator->GetMaximum()); CodeSequenceMacro* measurementUnitCode = metaInfo.getMeasurementUnitsCode(); if (measurementUnitCode != NULL) { @@ -183,7 +187,7 @@ namespace dcmqi { realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(quantity); quantity->setValueType(ContentItemMacro::VT_CODE); - // initialize optional items, if available + // initialize optional RWVM items, if available if(metaInfo.metaInfoRoot.isMember("MeasurementMethodCode")){ ContentItemMacro* measureMethod = new ContentItemMacro; CodeSequenceMacro* qCodeName = new CodeSequenceMacro("G-C306", "SRT", "Measurement Method"); @@ -393,12 +397,6 @@ namespace dcmqi { // Frame Content OFCondition result = fgfc->setDimensionIndexValues(sliceNumber+1 /* value within dimension */, 0 /* first dimension */); -#if ADD_DERIMG - // Already pushed above if siVector.size > 0 - // if(fgder) - // perFrameFGs.push_back(fgder); -#endif - DPMParametricMapIOD::FramesType frames = pMapDoc->getFrames(); result = OFget >(&frames)->addFrame(&*data.begin(), frameSize, perFrameFGs); @@ -568,6 +566,8 @@ namespace dcmqi { return pair (pmImage, metaInfo.getJSONOutputAsString()); } + // appears to be not used +#if 0 OFCondition ParametricMapConverter::addFrame(DPMParametricMapIOD &map, const FloatImageType::Pointer ¶metricMapImage, const JSONParametricMapMetaInformationHandler &metaInfo, const unsigned long frameNo, OFVector groups) @@ -627,6 +627,7 @@ namespace dcmqi { } return result; } +#endif void ParametricMapConverter::populateMetaInformationFromDICOM(DcmDataset *pmapDataset, JSONParametricMapMetaInformationHandler &metaInfo) { @@ -665,7 +666,7 @@ namespace dcmqi { Float64 slope; // TODO: replace the following call by following getter once it is available -// item->getRealWorldValueSlope(slope); + //item->getRealWorldValueSlope(slope); item->getData().findAndGetFloat64(DCM_RealWorldValueSlope, slope); metaInfo.setRealWorldValueSlope(slope); diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp new file mode 100644 index 00000000..579e79ea --- /dev/null +++ b/libsrc/ParametricMapObject.cpp @@ -0,0 +1,90 @@ +// +// Created by Andrey Fedorov on 3/11/17. +// + +#include +#include "dcmqi/ParametricMapObject.h" + +int ParametricMapObject::initializeFromITK(Float32ITKImageType::Pointer inputImage, + const string &metaDataStr, + std::vector derivationDatasets) { + sourceRepresentationType = ITK_REPR; + + itkImage = inputImage; + + initializeMetaDataFromString(metaDataStr); + + if(!metaDataIsComplete()){ + updateMetaDataFromDICOM(derivationDatasets); + } + + initializeVolumeGeometry(); + + createParametricMap(); + + initializeCompositeContext(); + + return EXIT_SUCCESS; +} + +int ParametricMapObject::initializeVolumeGeometry() { + if(sourceRepresentationType == ITK_REPR){ + + Float32ToDummyCasterType::Pointer caster = + Float32ToDummyCasterType::New(); + caster->SetInput(itkImage); + caster->Update(); + + MultiframeObject::initializeVolumeGeometryFromITK(caster->GetOutput()); + } else { + + } + return EXIT_SUCCESS; +} + +int ParametricMapObject::updateMetaDataFromDICOM(std::vector dcmList) { + if(!dcmList.size()) + return EXIT_FAILURE; + + DcmDataset* dcm = dcmList[0]; + metaDataJson["Modality"] = + std::string(dcmqi::Helper::getTagAsOFString(dcm, DCM_Modality).c_str()); + + return EXIT_SUCCESS; +} + +int ParametricMapObject::createParametricMap() { + + // create Parametric map object + + OFvariant obj = + DPMParametricMapIOD::create(metaDataJson["Modality"].asCString(), + metaDataJson["SeriesNumber"].asCString(), + metaDataJson["InstanceNumber"].asCString(), + volumeGeometry.extent[0], + volumeGeometry.extent[1], + equipmentInfoModule, + contentIdentificationMacro, + "VOLUME", "QUANTITY", + + DPMTypes::CQ_RESEARCH); + // TODO: look into the following, check with @che85 on the purpose of this line! + if (OFCondition* pCondition = OFget(&obj)) + return EXIT_FAILURE; + + parametricMap = OFget(&obj); + + return EXIT_SUCCESS; +} + +int ParametricMapObject::initializeCompositeContext() { + if(derivationDcmDatasets.size()){ + CHECK_COND(parametricMap->import(*derivationDcmDatasets[0], OFTrue, OFTrue, OFFalse, OFTrue)); + + } else { + // TODO: once we support passing of composite context in metadata, propagate it + // into parametricMap here + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/libsrc/SegmentationImageObject.cpp b/libsrc/SegmentationImageObject.cpp new file mode 100644 index 00000000..aab623e3 --- /dev/null +++ b/libsrc/SegmentationImageObject.cpp @@ -0,0 +1,5 @@ +// +// Created by Andrey Fedorov on 3/11/17. +// + +#include "dcmqi/SegmentationImageObject.h" From 65f336fbd833f3727e167667690ab5ffc70a007c Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Wed, 15 Mar 2017 21:12:33 -0400 Subject: [PATCH 004/116] ENH: adding functionality - temp commit --- docker/dcmqi/imagefiles/docker_entry.sh | 28 ---- include/dcmqi/Helper.h | 5 + include/dcmqi/MultiframeObject.h | 6 +- include/dcmqi/ParametricMapObject.h | 4 +- libsrc/Helper.cpp | 12 ++ libsrc/MultiframeObject.cpp | 33 ++++- libsrc/ParametricMapObject.cpp | 163 ++++++++++++++++++++++++ 7 files changed, 218 insertions(+), 33 deletions(-) delete mode 100644 docker/dcmqi/imagefiles/docker_entry.sh diff --git a/docker/dcmqi/imagefiles/docker_entry.sh b/docker/dcmqi/imagefiles/docker_entry.sh deleted file mode 100644 index 2cecef2d..00000000 --- a/docker/dcmqi/imagefiles/docker_entry.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash - -case "$1" in - itkimage2paramap|paramap2itkimage|itkimage2segimage|segimage2itkimage|tid1500reader|tid1500writer) - $1 "${@:2}" - ;; - *) - echo "ERROR: Unknown command" - echo "" - cat >&2 <:] qiicr/dcmqi [args] - -Run the given dcmqi *command*. - -Available commands are: - itkimage2paramap - paramap2itkimage - itkimage2segimage - segimage2itkimage - tid1500reader - tid1500writer - -For *command* help use: docker run qiicr/dcmqi --help -ENDHELP - exit 1 - ;; -esac - diff --git a/include/dcmqi/Helper.h b/include/dcmqi/Helper.h index ea2dbba3..d1dc966e 100644 --- a/include/dcmqi/Helper.h +++ b/include/dcmqi/Helper.h @@ -17,6 +17,8 @@ // DCMQI includes #include "dcmqi/Exceptions.h" +#include + using namespace std; namespace dcmqi { @@ -59,6 +61,9 @@ namespace dcmqi { static CodeSequenceMacro stringToCodeSequenceMacro(string str); static DSRCodedEntryValue stringToDSRCodedEntryValue(string str); + static string codeSequenceMacroToString(CodeSequenceMacro); + + static CodeSequenceMacro jsonToCodeSequenceMacro(Json::Value); static void checkValidityOfFirstSrcImage(DcmSegmentation *segdoc); diff --git a/include/dcmqi/MultiframeObject.h b/include/dcmqi/MultiframeObject.h index 715b45fc..3e297b4f 100644 --- a/include/dcmqi/MultiframeObject.h +++ b/include/dcmqi/MultiframeObject.h @@ -77,8 +77,12 @@ class MultiframeObject { virtual int initializeCompositeContext(); virtual bool metaDataIsComplete(); + + // List of tags, and FGs they belong to, for initializing dimensions module - int initializeDimensions(IODMultiframeDimensionModule&, std::vector >); + int initializeDimensions(std::vector >); + int initializePixelMeasuresFG(); + int initializePlaneOrientationFG(); // constants to describe original representation of the data being converted enum { diff --git a/include/dcmqi/ParametricMapObject.h b/include/dcmqi/ParametricMapObject.h index 32cbcf3f..eee341f6 100644 --- a/include/dcmqi/ParametricMapObject.h +++ b/include/dcmqi/ParametricMapObject.h @@ -36,11 +36,13 @@ class ParametricMapObject : public MultiframeObject { protected: typedef itk::CastImageFilter Float32ToDummyCasterType; + typedef itk::MinimumMaximumImageCalculator MinMaxCalculatorType; int initializeVolumeGeometry(); int createParametricMap(); - int initializeDimensionsModule(); int initializeCompositeContext(); + int initializeFrameAnatomyFG(); + int initializeRWVMFG(); // Functional groups initialization diff --git a/libsrc/Helper.cpp b/libsrc/Helper.cpp index 6bcf8ecb..9e5b7af1 100644 --- a/libsrc/Helper.cpp +++ b/libsrc/Helper.cpp @@ -380,4 +380,16 @@ namespace dcmqi { CHECK_COND(dcm->findAndGetOFString(tag, value)); return value; } + + CodeSequenceMacro Helper::jsonToCodeSequenceMacro(Json::Value jv){ + return CodeSequenceMacro(jv["CodeValue"].asCString(), + jv["CodingSchemeDesignator"].asCString(), + jv["CodeMeaning"].asCString()); + } + + string Helper::codeSequenceMacroToString(CodeSequenceMacro c){ + OFString codeValue, codingSchemeDesignator, codeMeaning; + string s = string()+codeValue.c_str()+","+codingSchemeDesignator.c_str()+","+codeMeaning.c_str(); + return s; + } } diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index 563a0012..37ac0226 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -72,15 +72,42 @@ bool MultiframeObject::metaDataIsComplete() { } // List of tags, and FGs they belong to, for initializing dimensions module -int MultiframeObject::initializeDimensions(IODMultiframeDimensionModule& dimModule, - std::vector > dimTagList){ +int MultiframeObject::initializeDimensions(std::vector > dimTagList){ OFString dimUID; + dimensionsModule.clearData(); + dimUID = dcmqi::Helper::generateUID(); for(int i=0;i dimTagPair = dimTagList[i]; - CHECK_COND(dimModule.addDimensionIndex(dimTagPair.first, dimUID, dimTagPair.second, + CHECK_COND(dimensionsModule.addDimensionIndex(dimTagPair.first, dimUID, dimTagPair.second, dimTagPair.first.getTagName())); } return EXIT_SUCCESS; } + +int MultiframeObject::initializePixelMeasuresFG(){ + string pixelSpacingStr, sliceSpacingStr; + + pixelSpacingStr = dcmqi::Helper::floatToStrScientific(volumeGeometry.spacing[0])+ + "\\"+dcmqi::Helper::floatToStrScientific(volumeGeometry.spacing[1]); + sliceSpacingStr = dcmqi::Helper::floatToStrScientific(volumeGeometry.spacing[2]); + + CHECK_COND(pixelMeasuresFG.setPixelSpacing(pixelSpacingStr.c_str())); + CHECK_COND(pixelMeasuresFG.setSpacingBetweenSlices(sliceSpacingStr.c_str())); + CHECK_COND(pixelMeasuresFG.setSliceThickness(sliceSpacingStr.c_str())); + + return EXIT_SUCCESS; +} + +int MultiframeObject::initializePlaneOrientationFG() { + planeOrientationPatientFG.setImageOrientationPatient( + dcmqi::Helper::floatToStrScientific(volumeGeometry.rowDirection[0]).c_str(), + dcmqi::Helper::floatToStrScientific(volumeGeometry.rowDirection[1]).c_str(), + dcmqi::Helper::floatToStrScientific(volumeGeometry.rowDirection[2]).c_str(), + dcmqi::Helper::floatToStrScientific(volumeGeometry.columnDirection[0]).c_str(), + dcmqi::Helper::floatToStrScientific(volumeGeometry.columnDirection[1]).c_str(), + dcmqi::Helper::floatToStrScientific(volumeGeometry.columnDirection[2]).c_str() + ); + return EXIT_SUCCESS; +} diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 579e79ea..6bbf9a52 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -3,6 +3,7 @@ // #include +#include #include "dcmqi/ParametricMapObject.h" int ParametricMapObject::initializeFromITK(Float32ITKImageType::Pointer inputImage, @@ -20,10 +21,25 @@ int ParametricMapObject::initializeFromITK(Float32ITKImageType::Pointer inputIma initializeVolumeGeometry(); + // TODO: consider creating parametric map object after all FGs are initialized instead createParametricMap(); + // populate metadata about patient/study, from derivation + // datasets or from metadata initializeCompositeContext(); + // populate functional groups + std::vector > dimensionTags; + dimensionTags.push_back(std::pair(DCM_ImagePositionPatient, DCM_PlanePositionSequence)); + initializeDimensions(dimensionTags); + + initializePixelMeasuresFG(); + initializePlaneOrientationFG(); + + // PM-specific FGs + initializeFrameAnatomyFG(); + initializeRWVMFG(); + return EXIT_SUCCESS; } @@ -74,10 +90,20 @@ int ParametricMapObject::createParametricMap() { parametricMap = OFget(&obj); + // These FG are constant + FGIdentityPixelValueTransformation idTransFG; + CHECK_COND(parametricMap->addForAllFrames(idTransFG)); + + FGParametricMapFrameType frameTypeFG; + std::string frameTypeStr = "DERIVED\\PRIMARY\\VOLUME\\QUANTITY"; + frameTypeFG.setFrameType(frameTypeStr.c_str()); + CHECK_COND(parametricMap->addForAllFrames(frameTypeFG)); + return EXIT_SUCCESS; } int ParametricMapObject::initializeCompositeContext() { + // TODO: should this be done in the parent? if(derivationDcmDatasets.size()){ CHECK_COND(parametricMap->import(*derivationDcmDatasets[0], OFTrue, OFTrue, OFFalse, OFTrue)); @@ -87,4 +113,141 @@ int ParametricMapObject::initializeCompositeContext() { return EXIT_FAILURE; } return EXIT_SUCCESS; +} + +int ParametricMapObject::initializeFrameAnatomyFG() { + if(metaDataJson.isMember("FrameLaterality")) + frameAnatomyFG.setLaterality(FGFrameAnatomy::str2Laterality(metaDataJson["FrameLaterality"].asCString())); + else + frameAnatomyFG.setLaterality(FGFrameAnatomy::str2Laterality("U")); + + // TODO: simplify code initialization from metadata + if(metaDataJson.isMember("AnatomicRegionSequence")){ + frameAnatomyFG.getAnatomy().getAnatomicRegion().set( + metaDataJson["AnatomicRegionSequence"]["CodeValue"].asCString(), + metaDataJson["AnatomicRegionSequence"]["CodingSchemeDesignator"].asCString(), + metaDataJson["AnatomicRegionSequence"]["CodeMeaning"].asCString()); + } else { + frameAnatomyFG.getAnatomy().getAnatomicRegion().set("T-D0050", "SRT", "Tissue"); + } + + return EXIT_SUCCESS; +} + +int ParametricMapObject::initializeRWVMFG() { + FGRealWorldValueMapping::RWVMItem* realWorldValueMappingItem = + new FGRealWorldValueMapping::RWVMItem(); + + if (!realWorldValueMappingItem ) + return EXIT_FAILURE; + + realWorldValueMappingItem->setRealWorldValueSlope(metaDataJson["RealWorldValueSlope"].asFloat()); + realWorldValueMappingItem->setRealWorldValueIntercept(0); + + // Calculate intensity range - required + MinMaxCalculatorType::Pointer calculator = MinMaxCalculatorType::New(); + calculator->SetImage(itkImage); + calculator->Compute(); + + realWorldValueMappingItem->setRealWorldValueFirstValueMappeSigned(calculator->GetMinimum()); + realWorldValueMappingItem->setRealWorldValueLastValueMappedSigned(calculator->GetMaximum()); + + if(metaDataJson.isMember("MeasurementsUnitsCode")){ + CodeSequenceMacro& unitsCodeDcmtk = realWorldValueMappingItem->getMeasurementUnitsCode(); + unitsCodeDcmtk = dcmqi::Helper::jsonToCodeSequenceMacro(metaDataJson["MeasurementsUnitsCode"]); + cout << "Measurements units initialized to " << + dcmqi::Helper::codeSequenceMacroToString(unitsCodeDcmtk); + realWorldValueMappingItem->setLUTExplanation(metaDataJson["MeasurementUnitsCode"]["CodeMeaning"].asCString()); + realWorldValueMappingItem->setLUTLabel(metaDataJson["MeasurementUnitsCode"]["CodeValue"].asCString()); + } + + if(metaDataJson.isMember("QuantityValueCode")){ + CodeSequenceMacro& unitsCodeDcmtk = realWorldValueMappingItem->getMeasurementUnitsCode(); + unitsCodeDcmtk = dcmqi::Helper::jsonToCodeSequenceMacro(metaDataJson["QuantityValueCode"]); + + ContentItemMacro* quantity = new ContentItemMacro; + CodeSequenceMacro* qCodeName = new CodeSequenceMacro("G-C1C6", "SRT", "Quantity"); + CodeSequenceMacro* qSpec = new CodeSequenceMacro( + metaDataJson["QuantityValueCode"]["CodeValue"].asCString(), + metaDataJson["QuantityValueCode"]["CodingSchemeDesignator"].asCString(), + metaDataJson["QuantityValueCode"]["CodeMeaning"].asCString()); + + if (!quantity || !qSpec || !qCodeName) + { + return NULL; + } + + quantity->getEntireConceptNameCodeSequence().push_back(qCodeName); + quantity->getEntireConceptCodeSequence().push_back(qSpec); + realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(quantity); + quantity->setValueType(ContentItemMacro::VT_CODE); + + } + + +#if 0 + + // initialize optional RWVM items, if available + if(metaInfo.metaInfoRoot.isMember("MeasurementMethodCode")){ + ContentItemMacro* measureMethod = new ContentItemMacro; + CodeSequenceMacro* qCodeName = new CodeSequenceMacro("G-C306", "SRT", "Measurement Method"); + CodeSequenceMacro* qSpec = new CodeSequenceMacro( + metaInfo.metaInfoRoot["MeasurementMethodCode"]["CodeValue"].asCString(), + metaInfo.metaInfoRoot["MeasurementMethodCode"]["CodingSchemeDesignator"].asCString(), + metaInfo.metaInfoRoot["MeasurementMethodCode"]["CodeMeaning"].asCString()); + + if (!measureMethod || !qSpec || !qCodeName) + { + return NULL; + } + + measureMethod->getEntireConceptNameCodeSequence().push_back(qCodeName); + measureMethod->getEntireConceptCodeSequence().push_back(qSpec); + realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(measureMethod); + measureMethod->setValueType(ContentItemMacro::VT_CODE); + } + + if(metaInfo.metaInfoRoot.isMember("ModelFittingMethodCode")){ + ContentItemMacro* fittingMethod = new ContentItemMacro; + CodeSequenceMacro* qCodeName = new CodeSequenceMacro("DWMPxxxxx2", "99QIICR", "Model fitting method"); + CodeSequenceMacro* qSpec = new CodeSequenceMacro( + metaInfo.metaInfoRoot["ModelFittingMethodCode"]["CodeValue"].asCString(), + metaInfo.metaInfoRoot["ModelFittingMethodCode"]["CodingSchemeDesignator"].asCString(), + metaInfo.metaInfoRoot["ModelFittingMethodCode"]["CodeMeaning"].asCString()); + + if (!fittingMethod || !qSpec || !qCodeName) + { + return NULL; + } + + fittingMethod->getEntireConceptNameCodeSequence().push_back(qCodeName); + fittingMethod->getEntireConceptCodeSequence().push_back(qSpec); + realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(fittingMethod); + fittingMethod->setValueType(ContentItemMacro::VT_CODE); + } + + if(metaInfo.metaInfoRoot.isMember("SourceImageDiffusionBValues")){ + for(int bvalId=0;bvalIdsetValueType(ContentItemMacro::VT_NUMERIC); + bval->getEntireConceptNameCodeSequence().push_back(qCodeName); + bval->getEntireMeasurementUnitsCodeSequence().push_back(bvalUnits); + if(bval->setNumericValue(metaInfo.metaInfoRoot["SourceImageDiffusionBValues"][bvalId].asCString()).bad()) + cout << "Failed to insert the value!" << endl;; + realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(bval); + cout << bval->toString() << endl; + } + } + + rwvmFG.getRealWorldValueMapping().push_back(realWorldValueMappingItem); +#endif + return EXIT_SUCCESS; } \ No newline at end of file From 7a84538f669e8f264e052c353fb3d2bf191ee3f7 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Tue, 21 Mar 2017 13:26:14 -0400 Subject: [PATCH 005/116] ENH: more updates --- include/dcmqi/MultiframeObject.h | 3 +- libsrc/MultiframeObject.cpp | 18 ++++++++++ libsrc/ParametricMapConverter.cpp | 1 + libsrc/ParametricMapObject.cpp | 57 ++++++++++++++++++++----------- 4 files changed, 59 insertions(+), 20 deletions(-) diff --git a/include/dcmqi/MultiframeObject.h b/include/dcmqi/MultiframeObject.h index 3e297b4f..874abd4f 100644 --- a/include/dcmqi/MultiframeObject.h +++ b/include/dcmqi/MultiframeObject.h @@ -20,6 +20,7 @@ #include #include #include +#include #include "dcmqi/Exceptions.h" #include "dcmqi/ImageVolumeGeometry.h" @@ -77,7 +78,7 @@ class MultiframeObject { virtual int initializeCompositeContext(); virtual bool metaDataIsComplete(); - + ContentItemMacro* initializeContentItemMacro(CodeSequenceMacro, CodeSequenceMacro); // List of tags, and FGs they belong to, for initializing dimensions module int initializeDimensions(std::vector >); diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index 37ac0226..d1396dd6 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -111,3 +111,21 @@ int MultiframeObject::initializePlaneOrientationFG() { ); return EXIT_SUCCESS; } + +ContentItemMacro* MultiframeObject::initializeContentItemMacro(CodeSequenceMacro conceptName, + CodeSequenceMacro conceptCode){ + ContentItemMacro* item = new ContentItemMacro(); + CodeSequenceMacro* concept = new CodeSequenceMacro(conceptName); + CodeSequenceMacro* value = new CodeSequenceMacro(conceptCode); + + if (!item || !concept || !value) + { + return NULL; + } + + item->getEntireConceptNameCodeSequence().push_back(concept); + item->getEntireConceptCodeSequence().push_back(value); + item->setValueType(ContentItemMacro::VT_CODE); + + return EXIT_SUCCESS; +} diff --git a/libsrc/ParametricMapConverter.cpp b/libsrc/ParametricMapConverter.cpp index 0da3b0ca..1a25c812 100644 --- a/libsrc/ParametricMapConverter.cpp +++ b/libsrc/ParametricMapConverter.cpp @@ -170,6 +170,7 @@ namespace dcmqi { // TODO: LutExplanation and LUTLabel should be added as Metainformation realWorldValueMappingItem->setLUTExplanation(metaInfo.metaInfoRoot["MeasurementUnitsCode"]["CodeMeaning"].asCString()); realWorldValueMappingItem->setLUTLabel(metaInfo.metaInfoRoot["MeasurementUnitsCode"]["CodeValue"].asCString()); + ContentItemMacro* quantity = new ContentItemMacro; CodeSequenceMacro* qCodeName = new CodeSequenceMacro("G-C1C6", "SRT", "Quantity"); CodeSequenceMacro* qSpec = new CodeSequenceMacro( diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 6bbf9a52..4e4bf9fc 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -154,37 +154,56 @@ int ParametricMapObject::initializeRWVMFG() { if(metaDataJson.isMember("MeasurementsUnitsCode")){ CodeSequenceMacro& unitsCodeDcmtk = realWorldValueMappingItem->getMeasurementUnitsCode(); - unitsCodeDcmtk = dcmqi::Helper::jsonToCodeSequenceMacro(metaDataJson["MeasurementsUnitsCode"]); + unitsCodeDcmtk.set(metaDataJson["MeasurementsUnitsCode"]["CodeValue"].asCString(), + metaDataJson["MeasurementsUnitsCode"]["CodingSchemeDesignator"].asCString(), + metaDataJson["MeasurementsUnitsCode"]["CodeMeaning"].asCString()); cout << "Measurements units initialized to " << dcmqi::Helper::codeSequenceMacroToString(unitsCodeDcmtk); + realWorldValueMappingItem->setLUTExplanation(metaDataJson["MeasurementUnitsCode"]["CodeMeaning"].asCString()); realWorldValueMappingItem->setLUTLabel(metaDataJson["MeasurementUnitsCode"]["CodeValue"].asCString()); } if(metaDataJson.isMember("QuantityValueCode")){ - CodeSequenceMacro& unitsCodeDcmtk = realWorldValueMappingItem->getMeasurementUnitsCode(); - unitsCodeDcmtk = dcmqi::Helper::jsonToCodeSequenceMacro(metaDataJson["QuantityValueCode"]); - - ContentItemMacro* quantity = new ContentItemMacro; - CodeSequenceMacro* qCodeName = new CodeSequenceMacro("G-C1C6", "SRT", "Quantity"); - CodeSequenceMacro* qSpec = new CodeSequenceMacro( - metaDataJson["QuantityValueCode"]["CodeValue"].asCString(), - metaDataJson["QuantityValueCode"]["CodingSchemeDesignator"].asCString(), - metaDataJson["QuantityValueCode"]["CodeMeaning"].asCString()); - - if (!quantity || !qSpec || !qCodeName) - { - return NULL; - } + ContentItemMacro* item = initializeContentItemMacro(CodeSequenceMacro("G-C1C6", "SRT", "Quantity"), + dcmqi::Helper::jsonToCodeSequenceMacro(metaDataJson["QuantityValueCode"])); + realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(item); + } - quantity->getEntireConceptNameCodeSequence().push_back(qCodeName); - quantity->getEntireConceptCodeSequence().push_back(qSpec); - realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(quantity); - quantity->setValueType(ContentItemMacro::VT_CODE); + // TODO: factor out defined CodeSequenceMacros into definitions as in dcmsr/include/dcmtk/dcmsr/codes + if(metaDataJson.isMember("MeasurementMethodCode")){ + ContentItemMacro* item = initializeContentItemMacro(CodeSequenceMacro("G-C306", "SRT", "Measurement Method"), + dcmqi::Helper::jsonToCodeSequenceMacro(metaDataJson["MeasurementMethodCode"])); + realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(item); + } + if(metaDataJson.isMember("ModelFittingMethodCode")){ + // TODO: update this once CP-1665 is integrated into the standard + ContentItemMacro* item = initializeContentItemMacro(CodeSequenceMacro("xxxxx2", "99DCMCP1665", "Model Fitting Method"), + dcmqi::Helper::jsonToCodeSequenceMacro(metaDataJson["ModelFittingMethodCode"])); + realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(item); } + if(metaDataJson.isMember("SourceImageDiffusionBValues")){ + for(int bvalId=0;bvalIdsetValueType(ContentItemMacro::VT_NUMERIC); + bval->getEntireConceptNameCodeSequence().push_back(qCodeName); + bval->getEntireMeasurementUnitsCodeSequence().push_back(bvalUnits); + if(bval->setNumericValue(metaDataJson["SourceImageDiffusionBValues"][bvalId].asCString()).bad()) + cout << "Failed to insert the value!" << endl;; + realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(bval); + } + } #if 0 // initialize optional RWVM items, if available From c531da9faaf98ff696119c1977d05a5632c3472c Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Sat, 8 Apr 2017 08:24:31 -0400 Subject: [PATCH 006/116] ENH: checkpointing updates to the branch --- include/dcmqi/DICOMFrame.h | 38 ++++++++++++++++- include/dcmqi/ImageVolumeGeometry.h | 5 +++ include/dcmqi/MultiframeConverter.h | 4 ++ include/dcmqi/MultiframeObject.h | 10 +++++ libsrc/DICOMFrame.cpp | 15 +++++++ libsrc/ImageVolume.cpp | 3 +- libsrc/ImageVolumeGeometry.cpp | 34 +++++++++++++++ libsrc/MultiframeObject.cpp | 39 +++++++++++++++++ libsrc/ParametricMapObject.cpp | 65 +---------------------------- 9 files changed, 147 insertions(+), 66 deletions(-) diff --git a/include/dcmqi/DICOMFrame.h b/include/dcmqi/DICOMFrame.h index f58a6955..9a31b611 100644 --- a/include/dcmqi/DICOMFrame.h +++ b/include/dcmqi/DICOMFrame.h @@ -6,6 +6,9 @@ #define DCMQI_DICOMFRAME_H #include +#include +#include "ImageVolumeGeometry.h" +#include "Exceptions.h" namespace dcmqi { @@ -19,18 +22,49 @@ namespace dcmqi { EnhancedInstanceFrame }; - DICOMFrame(DcmDataset *dataset, int number) : + DICOMFrame(DcmDataset *dataset, int number=0) : frameNumber(number), frameDataset(dataset) { + Uint32 numFrames; + if(dataset->findAndGetUint32(DCM_NumberOfFrames, numFrames).good()){ + frameType = EnhancedInstanceFrame; + if(!number){ + std::cerr << "ERROR: DICOMFrame object for an enhanced frame is initialized with frame 0!" << std::endl; + } + } else { + frameType = LegacyInstanceFrame; + initializeFrameGeometryFromLegacyInstance(); + } + }; + int getFrameNumber() const; // 0 for legacy datasets, 1 or above for enhanced objects + OFString getInstanceUID() const; + private: + + int initializeFrameGeometryFromLegacyInstance(); + int initializeFrameGeometryFromEnhancedInstance(); + int frameType; DcmDataset *frameDataset; int frameNumber; - OFString originStr; // revisit this + vnl_vector frameIPP; + + ImageVolumeGeometry frameGeometry; + + }; + + struct DICOMFrame_compare { + bool operator() (const DICOMFrame& lhs, const DICOMFrame& rhs) const{ + std::stringstream s1,s2; + s1 << lhs.getInstanceUID(); + s2 << rhs.getInstanceUID(); + return (s1.str() < s2.str()) && (lhs.getFrameNumber() < rhs.getFrameNumber()); + } }; + } #endif //DCMQI_DICOMFRAME_H diff --git a/include/dcmqi/ImageVolumeGeometry.h b/include/dcmqi/ImageVolumeGeometry.h index 3857027c..30b2d8fb 100644 --- a/include/dcmqi/ImageVolumeGeometry.h +++ b/include/dcmqi/ImageVolumeGeometry.h @@ -10,6 +10,7 @@ #include #include #include +#include class ImageVolumeGeometry { @@ -28,12 +29,16 @@ class ImageVolumeGeometry { typedef DummyImageType::DirectionType DirectionType; ImageVolumeGeometry(); + // initialize from DICOM + ImageVolumeGeometry(DcmDataset*); int setSpacing(DoubleVectorType); int setOrigin(PointType); int setExtent(SizeType); int setDirections(DirectionType); + DummyImageType::Pointer getITKRepresentation(); + protected: // use vnl_vector to simplify support of vector calculations vnl_vector rowDirection, columnDirection, sliceDirection; diff --git a/include/dcmqi/MultiframeConverter.h b/include/dcmqi/MultiframeConverter.h index ff0b5527..84089ab9 100644 --- a/include/dcmqi/MultiframeConverter.h +++ b/include/dcmqi/MultiframeConverter.h @@ -285,6 +285,10 @@ namespace dcmqi { CHECK_COND(dcmDatasets[i]->findAndGetOFString(DCM_ImagePositionPatient, ippStr, j)); ippPoint[j] = atof(ippStr.c_str()); } + // NB: this will map slice origin to index without failure, unless the point is out + // of FOV bounds! + // TODO: do a better job matching volume slices by considering comparison of the origin + // and orientation of the slice within tolerance if(!labelImage->TransformPhysicalPointToIndex(ippPoint, ippIndex)){ //cout << "image position: " << ippPoint << endl; //cerr << "ippIndex: " << ippIndex << endl; diff --git a/include/dcmqi/MultiframeObject.h b/include/dcmqi/MultiframeObject.h index 874abd4f..64568366 100644 --- a/include/dcmqi/MultiframeObject.h +++ b/include/dcmqi/MultiframeObject.h @@ -24,6 +24,7 @@ #include "dcmqi/Exceptions.h" #include "dcmqi/ImageVolumeGeometry.h" +#include "dcmqi/DICOMFrame.h" using namespace std; /* @@ -75,7 +76,10 @@ class MultiframeObject { // from ITK int initializeVolumeGeometryFromITK(DummyImageType::Pointer); + // initialize attributes of the composite context that are common for all multiframe objects virtual int initializeCompositeContext(); + // check whether all of the attributes required for initialization of the object are present in the + // input metadata virtual bool metaDataIsComplete(); ContentItemMacro* initializeContentItemMacro(CodeSequenceMacro, CodeSequenceMacro); @@ -85,6 +89,12 @@ class MultiframeObject { int initializePixelMeasuresFG(); int initializePlaneOrientationFG(); + static int mapVolumeSlicesToDICOMFrames(ImageVolumeGeometry&, const vector, + vector >); + + static std::vector findIntersectingSlices(ImageVolumeGeometry& volume, + dcmqi::DICOMFrame& frame); + // constants to describe original representation of the data being converted enum { DICOM_REPR = 0, diff --git a/libsrc/DICOMFrame.cpp b/libsrc/DICOMFrame.cpp index ecc42038..7a3b1985 100644 --- a/libsrc/DICOMFrame.cpp +++ b/libsrc/DICOMFrame.cpp @@ -3,3 +3,18 @@ // #include "dcmqi/DICOMFrame.h" + +namespace dcmqi { + int DICOMFrame::initializeFrameGeometryFromLegacyInstance() { + OFString ippStr; + for(int j=0;j<3;j++){ + CHECK_COND(frameDataset->findAndGetOFString(DCM_ImagePositionPatient, ippStr, j)); + frameIPP[j] = atof(ippStr.c_str()); + } + return EXIT_SUCCESS; + } + + int DICOMFrame::initializeFrameGeometryFromEnhancedInstance() { + OFString ippStr; + } +} \ No newline at end of file diff --git a/libsrc/ImageVolume.cpp b/libsrc/ImageVolume.cpp index 2f12ecd2..ebf64482 100644 --- a/libsrc/ImageVolume.cpp +++ b/libsrc/ImageVolume.cpp @@ -211,4 +211,5 @@ int dcmqi::ImageVolume::initializeExtent(FGInterface &fgInterface) { } return EXIT_SUCCESS; -} \ No newline at end of file +} + diff --git a/libsrc/ImageVolumeGeometry.cpp b/libsrc/ImageVolumeGeometry.cpp index 10570b0f..d71f6ef9 100644 --- a/libsrc/ImageVolumeGeometry.cpp +++ b/libsrc/ImageVolumeGeometry.cpp @@ -39,4 +39,38 @@ int ImageVolumeGeometry::setDirections(DirectionType d) { for (int i = 0; i < 3; i++) sliceDirection[i] = d[i][2]; return EXIT_SUCCESS; +} + +ImageVolumeGeometry::DummyImageType::Pointer ImageVolumeGeometry::getITKRepresentation(){ + ImageVolumeGeometry::DummyImageType::Pointer image; + ImageVolumeGeometry::DummyImageType::IndexType index; + ImageVolumeGeometry::DummyImageType::SizeType size; + ImageVolumeGeometry::DummyImageType::DirectionType direction; + ImageVolumeGeometry::DummyImageType::SpacingType spacing; + ImageVolumeGeometry::DummyImageType::RegionType region; + + index.Fill(0); + + size[0] = extent[0]; + size[1] = extent[1]; + size[2] = extent[2]; + + region.SetIndex(index); + region.SetSize(size); + + spacing[0] = this->spacing[0]; + spacing[1] = this->spacing[1]; + spacing[2] = this->spacing[2]; + + for (int i = 0; i < 3; i++) + direction[i][0] = rowDirection[i]; + for (int i = 0; i < 3; i++) + direction[i][1] = columnDirection[i]; + for (int i = 0; i < 3; i++) + direction[i][2] = sliceDirection[i]; + + image->SetDirection(direction); + image->SetSpacing(spacing); + + return image; } \ No newline at end of file diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index d1396dd6..d6ef6fbe 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -129,3 +129,42 @@ ContentItemMacro* MultiframeObject::initializeContentItemMacro(CodeSequenceMacro return EXIT_SUCCESS; } + +// populates slice2frame vector that maps each of the volume slices to the set of frames that +// are considered as derivation dataset +int MultiframeObject::mapVolumeSlicesToDICOMFrames(ImageVolumeGeometry& volume, + const vector dcmDatasets, + vector > slice2frame){ + for(int d=0;dfindAndGetUint32(DCM_NumberOfFrames, numFrames).good()){ + // this is a multiframe object + for(int f=0;f intersectingSlices = findIntersectingSlices(volume, frame); + + for(int s=0;s intersectingSlices = findIntersectingSlices(volume, frame); + + for(int s=0;s MultiframeObject::findIntersectingSlices(ImageVolumeGeometry &volume, dcmqi::DICOMFrame &frame) { + std::vector intersectingSlices; + + + + return intersectingSlices; +} \ No newline at end of file diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 4e4bf9fc..dbc06501 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -40,6 +40,8 @@ int ParametricMapObject::initializeFromITK(Float32ITKImageType::Pointer inputIma initializeFrameAnatomyFG(); initializeRWVMFG(); + + return EXIT_SUCCESS; } @@ -204,69 +206,6 @@ int ParametricMapObject::initializeRWVMFG() { realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(bval); } } -#if 0 - - // initialize optional RWVM items, if available - if(metaInfo.metaInfoRoot.isMember("MeasurementMethodCode")){ - ContentItemMacro* measureMethod = new ContentItemMacro; - CodeSequenceMacro* qCodeName = new CodeSequenceMacro("G-C306", "SRT", "Measurement Method"); - CodeSequenceMacro* qSpec = new CodeSequenceMacro( - metaInfo.metaInfoRoot["MeasurementMethodCode"]["CodeValue"].asCString(), - metaInfo.metaInfoRoot["MeasurementMethodCode"]["CodingSchemeDesignator"].asCString(), - metaInfo.metaInfoRoot["MeasurementMethodCode"]["CodeMeaning"].asCString()); - - if (!measureMethod || !qSpec || !qCodeName) - { - return NULL; - } - - measureMethod->getEntireConceptNameCodeSequence().push_back(qCodeName); - measureMethod->getEntireConceptCodeSequence().push_back(qSpec); - realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(measureMethod); - measureMethod->setValueType(ContentItemMacro::VT_CODE); - } - - if(metaInfo.metaInfoRoot.isMember("ModelFittingMethodCode")){ - ContentItemMacro* fittingMethod = new ContentItemMacro; - CodeSequenceMacro* qCodeName = new CodeSequenceMacro("DWMPxxxxx2", "99QIICR", "Model fitting method"); - CodeSequenceMacro* qSpec = new CodeSequenceMacro( - metaInfo.metaInfoRoot["ModelFittingMethodCode"]["CodeValue"].asCString(), - metaInfo.metaInfoRoot["ModelFittingMethodCode"]["CodingSchemeDesignator"].asCString(), - metaInfo.metaInfoRoot["ModelFittingMethodCode"]["CodeMeaning"].asCString()); - - if (!fittingMethod || !qSpec || !qCodeName) - { - return NULL; - } - - fittingMethod->getEntireConceptNameCodeSequence().push_back(qCodeName); - fittingMethod->getEntireConceptCodeSequence().push_back(qSpec); - realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(fittingMethod); - fittingMethod->setValueType(ContentItemMacro::VT_CODE); - } - - if(metaInfo.metaInfoRoot.isMember("SourceImageDiffusionBValues")){ - for(int bvalId=0;bvalIdsetValueType(ContentItemMacro::VT_NUMERIC); - bval->getEntireConceptNameCodeSequence().push_back(qCodeName); - bval->getEntireMeasurementUnitsCodeSequence().push_back(bvalUnits); - if(bval->setNumericValue(metaInfo.metaInfoRoot["SourceImageDiffusionBValues"][bvalId].asCString()).bad()) - cout << "Failed to insert the value!" << endl;; - realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(bval); - cout << bval->toString() << endl; - } - } - rwvmFG.getRealWorldValueMapping().push_back(realWorldValueMappingItem); -#endif return EXIT_SUCCESS; } \ No newline at end of file From 90fa8d88f0454d2221705b449cd814e30e4f4ca7 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 5 May 2017 12:06:36 -0400 Subject: [PATCH 007/116] BUG: fix API typo fixed in dcmtk --- libsrc/ParametricMapObject.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index dbc06501..95b2a6e6 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -151,7 +151,7 @@ int ParametricMapObject::initializeRWVMFG() { calculator->SetImage(itkImage); calculator->Compute(); - realWorldValueMappingItem->setRealWorldValueFirstValueMappeSigned(calculator->GetMinimum()); + realWorldValueMappingItem->setRealWorldValueFirstValueMappedSigned(calculator->GetMinimum()); realWorldValueMappingItem->setRealWorldValueLastValueMappedSigned(calculator->GetMaximum()); if(metaDataJson.isMember("MeasurementsUnitsCode")){ @@ -208,4 +208,4 @@ int ParametricMapObject::initializeRWVMFG() { } return EXIT_SUCCESS; -} \ No newline at end of file +} From d852e1215a8e7cd53a48e2a2167bfac28c209c1b Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 5 May 2017 12:22:02 -0400 Subject: [PATCH 008/116] BUG: add docker_entry removed by accident in ff52820 --- docker/dcmqi/imagefiles/docker_entry.sh | 28 +++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 docker/dcmqi/imagefiles/docker_entry.sh diff --git a/docker/dcmqi/imagefiles/docker_entry.sh b/docker/dcmqi/imagefiles/docker_entry.sh new file mode 100644 index 00000000..2cecef2d --- /dev/null +++ b/docker/dcmqi/imagefiles/docker_entry.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +case "$1" in + itkimage2paramap|paramap2itkimage|itkimage2segimage|segimage2itkimage|tid1500reader|tid1500writer) + $1 "${@:2}" + ;; + *) + echo "ERROR: Unknown command" + echo "" + cat >&2 <:] qiicr/dcmqi [args] + +Run the given dcmqi *command*. + +Available commands are: + itkimage2paramap + paramap2itkimage + itkimage2segimage + segimage2itkimage + tid1500reader + tid1500writer + +For *command* help use: docker run qiicr/dcmqi --help +ENDHELP + exit 1 + ;; +esac + From e8c04ca1f59a3cd97c7e77d6e6853f502d16d6a6 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 5 May 2017 12:39:17 -0400 Subject: [PATCH 009/116] BUG: return value --- libsrc/DICOMFrame.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libsrc/DICOMFrame.cpp b/libsrc/DICOMFrame.cpp index 7a3b1985..bfbff419 100644 --- a/libsrc/DICOMFrame.cpp +++ b/libsrc/DICOMFrame.cpp @@ -16,5 +16,6 @@ namespace dcmqi { int DICOMFrame::initializeFrameGeometryFromEnhancedInstance() { OFString ippStr; + return EXIT_SUCCESS; } -} \ No newline at end of file +} From 13e805b1060af939a5c5226f4990e060fa8adb1d Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Sun, 14 May 2017 14:10:31 -0400 Subject: [PATCH 010/116] ENH: checkpoint commit --- include/dcmqi/DICOMFrame.h | 3 +++ libsrc/MultiframeObject.cpp | 16 +++++++++++++--- libsrc/ParametricMapObject.cpp | 9 +++++++-- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/include/dcmqi/DICOMFrame.h b/include/dcmqi/DICOMFrame.h index 9a31b611..e35eea66 100644 --- a/include/dcmqi/DICOMFrame.h +++ b/include/dcmqi/DICOMFrame.h @@ -41,6 +41,9 @@ namespace dcmqi { int getFrameNumber() const; // 0 for legacy datasets, 1 or above for enhanced objects OFString getInstanceUID() const; + vnl_vector getFrameIPP(){ + return frameIPP; + }; private: diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index d6ef6fbe..f42c67b8 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -163,8 +163,18 @@ int MultiframeObject::mapVolumeSlicesToDICOMFrames(ImageVolumeGeometry& volume, std::vector MultiframeObject::findIntersectingSlices(ImageVolumeGeometry &volume, dcmqi::DICOMFrame &frame) { std::vector intersectingSlices; - - + // for now, adopt a simple strategy that maps origin of the frame to index, and selects the slice corresponding + // to this index as the intersecting one + ImageVolumeGeometry::DummyImageType::Pointer itkvolume = volume.getITKRepresentation(); + ImageVolumeGeometry::DummyImageType::PointType point; + ImageVolumeGeometry::DummyImageType::IndexType index; + vnl_vector frameIPP = frame.getFrameIPP(); + point[0] = frameIPP[0]; + point[1] = frameIPP[1]; + point[2] = frameIPP[2]; + + if(itkvolume->TransformPhysicalPointToIndex(point, index)) + intersectingSlices.push_back(index[2]); return intersectingSlices; -} \ No newline at end of file +} diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 95b2a6e6..f2333eb4 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -21,6 +21,10 @@ int ParametricMapObject::initializeFromITK(Float32ITKImageType::Pointer inputIma initializeVolumeGeometry(); + // NB: the sequence of steps initializing different components of the object parallels that + // in the original converter function. It probably makes sense to revisit the sequence + // of these steps. It does not necessarily need to happen in this order. + // TODO: consider creating parametric map object after all FGs are initialized instead createParametricMap(); @@ -40,7 +44,8 @@ int ParametricMapObject::initializeFromITK(Float32ITKImageType::Pointer inputIma initializeFrameAnatomyFG(); initializeRWVMFG(); - + // initialize referenced instances + ///initializeReferencedInstances(); return EXIT_SUCCESS; } @@ -195,7 +200,7 @@ int ParametricMapObject::initializeRWVMFG() { if (!bval || !bvalUnits || !qCodeName) { - return NULL; + return EXIT_FAILURE; } bval->setValueType(ContentItemMacro::VT_NUMERIC); From c092ec59aeaceeba282b7526e88fd6b3d7337d50 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Mon, 22 May 2017 17:01:07 -0400 Subject: [PATCH 011/116] checkpoint --- include/dcmqi/DICOMFrame.h | 17 +++++++++++++++++ include/dcmqi/MultiframeObject.h | 9 +++++++-- include/dcmqi/ParametricMapObject.h | 2 ++ libsrc/MultiframeObject.cpp | 21 ++++++++++++++++----- libsrc/ParametricMapObject.cpp | 23 ++++++++++++++++++++++- 5 files changed, 64 insertions(+), 8 deletions(-) diff --git a/include/dcmqi/DICOMFrame.h b/include/dcmqi/DICOMFrame.h index e35eea66..8d975906 100644 --- a/include/dcmqi/DICOMFrame.h +++ b/include/dcmqi/DICOMFrame.h @@ -37,6 +37,13 @@ namespace dcmqi { initializeFrameGeometryFromLegacyInstance(); } + OFString seriesUIDOF, instanceUIDOF; + if(dataset->findAndGetOFString(DCM_SeriesInstanceUID, seriesUIDOF).good()){ + seriesUID = seriesUIDOF.c_str(); + } + if(dataset->findAndGetOFString(DCM_SOPInstanceUID, instanceUIDOF).good()){ + instanceUID = instanceUIDOF.c_str(); + } }; int getFrameNumber() const; // 0 for legacy datasets, 1 or above for enhanced objects @@ -45,6 +52,14 @@ namespace dcmqi { return frameIPP; }; + string getSeriesUID(){ + return seriesUID; + } + + string getInstanceUID(){ + return instanceUID; + } + private: int initializeFrameGeometryFromLegacyInstance(); @@ -55,6 +70,8 @@ namespace dcmqi { int frameNumber; vnl_vector frameIPP; + string seriesUID, instanceUID; + ImageVolumeGeometry frameGeometry; }; diff --git a/include/dcmqi/MultiframeObject.h b/include/dcmqi/MultiframeObject.h index 64568366..399a5d52 100644 --- a/include/dcmqi/MultiframeObject.h +++ b/include/dcmqi/MultiframeObject.h @@ -89,12 +89,14 @@ class MultiframeObject { int initializePixelMeasuresFG(); int initializePlaneOrientationFG(); - static int mapVolumeSlicesToDICOMFrames(ImageVolumeGeometry&, const vector, - vector >); + int mapVolumeSlicesToDICOMFrames(ImageVolumeGeometry&, const vector, + vector >&); static std::vector findIntersectingSlices(ImageVolumeGeometry& volume, dcmqi::DICOMFrame& frame); + void insertDerivationSeriesInstance(string seriesUID, string instanceUID); + // constants to describe original representation of the data being converted enum { DICOM_REPR = 0, @@ -140,6 +142,9 @@ class MultiframeObject { OFVector frameContentFGList; OFVector derivationImageFGList; + // Mapping from the derivation items SeriesUIDs to InstanceUIDs + std::map > derivationSeriesToInstanceUIDs; + }; diff --git a/include/dcmqi/ParametricMapObject.h b/include/dcmqi/ParametricMapObject.h index eee341f6..e5c9b83a 100644 --- a/include/dcmqi/ParametricMapObject.h +++ b/include/dcmqi/ParametricMapObject.h @@ -56,6 +56,8 @@ class ParametricMapObject : public MultiframeObject { // Data containers specific to this object Float32ITKImageType::Pointer itkImage; + + private: DPMParametricMapIOD* parametricMap; }; diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index f42c67b8..60f2e1c5 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -132,9 +132,10 @@ ContentItemMacro* MultiframeObject::initializeContentItemMacro(CodeSequenceMacro // populates slice2frame vector that maps each of the volume slices to the set of frames that // are considered as derivation dataset +// TODO: this function assumes that all of the derivation datasets are images, which is probably ok int MultiframeObject::mapVolumeSlicesToDICOMFrames(ImageVolumeGeometry& volume, const vector dcmDatasets, - vector > slice2frame){ + vector > &slice2frame){ for(int d=0;d intersectingSlices = findIntersectingSlices(volume, frame); - for(int s=0;sinsertDerivationSeriesInstance(frame.getSeriesUID(), frame.getInstanceUID()); + } } } else { dcmqi::DICOMFrame frame(dcm); vector intersectingSlices = findIntersectingSlices(volume, frame); - for(int s=0;sinsertDerivationSeriesInstance(frame.getSeriesUID(), frame.getInstanceUID()); + } } } @@ -178,3 +183,9 @@ std::vector MultiframeObject::findIntersectingSlices(ImageVolumeGeometry &v return intersectingSlices; } + +void MultiframeObject::insertDerivationSeriesInstance(string seriesUID, string instanceUID){ + if(derivationSeriesToInstanceUIDs.find(seriesUID) == derivationSeriesToInstanceUIDs.end()) + derivationSeriesToInstanceUIDs[seriesUID] = std::set(); + derivationSeriesToInstanceUIDs[seriesUID].insert(instanceUID); +} \ No newline at end of file diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index f2333eb4..81340c47 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -44,8 +44,29 @@ int ParametricMapObject::initializeFromITK(Float32ITKImageType::Pointer inputIma initializeFrameAnatomyFG(); initializeRWVMFG(); + // Mapping from parametric map volume slices to the DICOM frames + vector > slice2frame; + // initialize referenced instances - ///initializeReferencedInstances(); + mapVolumeSlicesToDICOMFrames(this->volumeGeometry, derivationDatasets, slice2frame); + + // map individual series UIDs to the list of instance UIDs + + + std::map > seriesToInstanceUIDs; + for(int slice=0;slice!=slice2frame.size();slice++){ + for(set::const_iterator frameI=slice2frame[slice].begin(); + frameI!=slice2frame[slice].end();++frameI){ + dcmqi::DICOMFrame frame = *frameI; + if(seriesToInstanceUIDs.find(frame.getSeriesUID()) == seriesToInstanceUIDs.end()){ + seriesToInstanceUIDs[frame.getSeriesUID()] = frame.getInstanceUID(); + } else { + seriesToInstanceUIDs[frame.getSeriesUID()].push_back(frame.getInstanceUID()); + } + } + } + + return EXIT_SUCCESS; } From 18d6d8da36cf780c0b11d1e3b9f96e08a24bcfc2 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Thu, 22 Jun 2017 13:01:56 -0400 Subject: [PATCH 012/116] ENH: fix compile errors, initialize common instance module --- include/dcmqi/DICOMFrame.h | 11 +++++++-- include/dcmqi/MultiframeObject.h | 2 ++ libsrc/MultiframeObject.cpp | 38 ++++++++++++++++++++++++++++++++ libsrc/ParametricMapObject.cpp | 22 +++++------------- 4 files changed, 54 insertions(+), 19 deletions(-) diff --git a/include/dcmqi/DICOMFrame.h b/include/dcmqi/DICOMFrame.h index 8d975906..c0e61833 100644 --- a/include/dcmqi/DICOMFrame.h +++ b/include/dcmqi/DICOMFrame.h @@ -37,13 +37,16 @@ namespace dcmqi { initializeFrameGeometryFromLegacyInstance(); } - OFString seriesUIDOF, instanceUIDOF; + OFString seriesUIDOF, instanceUIDOF, classUIDOF; if(dataset->findAndGetOFString(DCM_SeriesInstanceUID, seriesUIDOF).good()){ seriesUID = seriesUIDOF.c_str(); } if(dataset->findAndGetOFString(DCM_SOPInstanceUID, instanceUIDOF).good()){ instanceUID = instanceUIDOF.c_str(); } + if(dataset->findAndGetOFString(DCM_SOPClassUID, classUIDOF).good()){ + classUID = classUIDOF.c_str(); + } }; int getFrameNumber() const; // 0 for legacy datasets, 1 or above for enhanced objects @@ -60,6 +63,10 @@ namespace dcmqi { return instanceUID; } + string getClassUID(){ + return classUID; + } + private: int initializeFrameGeometryFromLegacyInstance(); @@ -70,7 +77,7 @@ namespace dcmqi { int frameNumber; vnl_vector frameIPP; - string seriesUID, instanceUID; + string seriesUID, instanceUID, classUID; ImageVolumeGeometry frameGeometry; diff --git a/include/dcmqi/MultiframeObject.h b/include/dcmqi/MultiframeObject.h index 399a5d52..838df9e6 100644 --- a/include/dcmqi/MultiframeObject.h +++ b/include/dcmqi/MultiframeObject.h @@ -21,6 +21,7 @@ #include #include #include +#include #include "dcmqi/Exceptions.h" #include "dcmqi/ImageVolumeGeometry.h" @@ -88,6 +89,7 @@ class MultiframeObject { int initializeDimensions(std::vector >); int initializePixelMeasuresFG(); int initializePlaneOrientationFG(); + int initializeCommonInstanceReferenceModule(IODCommonInstanceReferenceModule &, vector >&); int mapVolumeSlicesToDICOMFrames(ImageVolumeGeometry&, const vector, vector >&); diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index 60f2e1c5..389df8e0 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -188,4 +188,42 @@ void MultiframeObject::insertDerivationSeriesInstance(string seriesUID, string i if(derivationSeriesToInstanceUIDs.find(seriesUID) == derivationSeriesToInstanceUIDs.end()) derivationSeriesToInstanceUIDs[seriesUID] = std::set(); derivationSeriesToInstanceUIDs[seriesUID].insert(instanceUID); +} + +int MultiframeObject::initializeCommonInstanceReferenceModule(IODCommonInstanceReferenceModule &commref, vector > &slice2frame){ + + // map individual series UIDs to the list of instance UIDs - we need to have this organization + // to populate Common instance reference module + std::map > series2frame; + for(int slice=0;slice!=slice2frame.size();slice++){ + for(set::const_iterator frameI=slice2frame[slice].begin(); + frameI!=slice2frame[slice].end();++frameI){ + dcmqi::DICOMFrame frame = *frameI; + if(series2frame.find(frame.getSeriesUID()) == series2frame.end()){ + std::set setOfInstances; + setOfInstances.insert(frame); + series2frame[frame.getSeriesUID()] = setOfInstances; + } else { + series2frame[frame.getSeriesUID()].insert(frame); + } + } + } + + // create a new ReferencedSeriesItem for each series, and populate with instances + OFVector &refseries = commref.getReferencedSeriesItems(); + for(std::map >::const_iterator mIt=series2frame.begin(); mIt!=series2frame.end();++mIt){ + IODSeriesAndInstanceReferenceMacro::ReferencedSeriesItem* refseriesItem = new IODSeriesAndInstanceReferenceMacro::ReferencedSeriesItem; + refseriesItem->setSeriesInstanceUID(mIt->first.c_str()); + OFVector &refinstances = refseriesItem->getReferencedInstanceItems(); + + for(std::set::const_iterator sIt=mIt->second.begin();sIt!=mIt->second.end();++sIt){ + dcmqi::DICOMFrame frame = *sIt; + SOPInstanceReferenceMacro *refinstancesItem = new SOPInstanceReferenceMacro(); + CHECK_COND(refinstancesItem->setReferencedSOPClassUID(frame.getClassUID().c_str())); + CHECK_COND(refinstancesItem->setReferencedSOPInstanceUID(frame.getInstanceUID().c_str())); + refinstances.push_back(refinstancesItem); + } + } + + return EXIT_SUCCESS; } \ No newline at end of file diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 81340c47..5de1250b 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -5,6 +5,8 @@ #include #include #include "dcmqi/ParametricMapObject.h" +#include +#include int ParametricMapObject::initializeFromITK(Float32ITKImageType::Pointer inputImage, const string &metaDataStr, @@ -48,25 +50,11 @@ int ParametricMapObject::initializeFromITK(Float32ITKImageType::Pointer inputIma vector > slice2frame; // initialize referenced instances + // this is done using this utility function from the parent class, since this functionality will + // be needed both in the PM and SEG objects mapVolumeSlicesToDICOMFrames(this->volumeGeometry, derivationDatasets, slice2frame); - // map individual series UIDs to the list of instance UIDs - - - std::map > seriesToInstanceUIDs; - for(int slice=0;slice!=slice2frame.size();slice++){ - for(set::const_iterator frameI=slice2frame[slice].begin(); - frameI!=slice2frame[slice].end();++frameI){ - dcmqi::DICOMFrame frame = *frameI; - if(seriesToInstanceUIDs.find(frame.getSeriesUID()) == seriesToInstanceUIDs.end()){ - seriesToInstanceUIDs[frame.getSeriesUID()] = frame.getInstanceUID(); - } else { - seriesToInstanceUIDs[frame.getSeriesUID()].push_back(frame.getInstanceUID()); - } - } - } - - + initializeCommonInstanceReferenceModule(this->parametricMap->getCommonInstanceReference(), slice2frame); return EXIT_SUCCESS; } From 46d567bbe09019f1d016a8bf51b0963ddc34d8d5 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Thu, 22 Jun 2017 16:54:59 -0400 Subject: [PATCH 013/116] ENH: fixing issues identified by tests --- apps/paramaps/itkimage2paramap.cxx | 2 +- include/dcmqi/DICOMFrame.h | 13 ++++++++----- include/dcmqi/MultiframeObject.h | 6 +++++- include/dcmqi/ParametricMapObject.h | 4 ++++ libsrc/DICOMFrame.cpp | 1 + libsrc/MultiframeObject.cpp | 12 ++++++++++++ libsrc/ParametricMapConverter.cpp | 12 ++++++++---- libsrc/ParametricMapObject.cpp | 6 +++++- 8 files changed, 44 insertions(+), 12 deletions(-) diff --git a/apps/paramaps/itkimage2paramap.cxx b/apps/paramaps/itkimage2paramap.cxx index f71252b9..22f5b6f9 100644 --- a/apps/paramaps/itkimage2paramap.cxx +++ b/apps/paramaps/itkimage2paramap.cxx @@ -44,7 +44,7 @@ int main(int argc, char *argv[]) std::string metadata( (std::istreambuf_iterator(metainfoStream) ), (std::istreambuf_iterator())); - DcmDataset* result = dcmqi::ParametricMapConverter::itkimage2paramap(parametricMapImage, dcmDatasets, metadata); + DcmDataset* result = dcmqi::itkimage2paramapReplacement(parametricMapImage, dcmDatasets, metadata); if (result == NULL) { return EXIT_FAILURE; diff --git a/include/dcmqi/DICOMFrame.h b/include/dcmqi/DICOMFrame.h index c0e61833..586c6e9c 100644 --- a/include/dcmqi/DICOMFrame.h +++ b/include/dcmqi/DICOMFrame.h @@ -47,23 +47,26 @@ namespace dcmqi { if(dataset->findAndGetOFString(DCM_SOPClassUID, classUIDOF).good()){ classUID = classUIDOF.c_str(); } + + }; + + int getFrameNumber() const { + return frameNumber; // 0 for legacy datasets, 1 or above for enhanced objects }; - int getFrameNumber() const; // 0 for legacy datasets, 1 or above for enhanced objects - OFString getInstanceUID() const; vnl_vector getFrameIPP(){ return frameIPP; }; - string getSeriesUID(){ + string getSeriesUID() const { return seriesUID; } - string getInstanceUID(){ + string getInstanceUID() const{ return instanceUID; } - string getClassUID(){ + string getClassUID() const { return classUID; } diff --git a/include/dcmqi/MultiframeObject.h b/include/dcmqi/MultiframeObject.h index 838df9e6..e882a1f9 100644 --- a/include/dcmqi/MultiframeObject.h +++ b/include/dcmqi/MultiframeObject.h @@ -78,7 +78,7 @@ class MultiframeObject { int initializeVolumeGeometryFromITK(DummyImageType::Pointer); // initialize attributes of the composite context that are common for all multiframe objects - virtual int initializeCompositeContext(); + //virtual int initializeCompositeContext(); // check whether all of the attributes required for initialization of the object are present in the // input metadata virtual bool metaDataIsComplete(); @@ -113,8 +113,12 @@ class MultiframeObject { Json::Value metaDataJson; // Multiframe DICOM object representation + // probably not needed, since need object-specific DCMTK class in + // derived classes DcmDataset* dcmRepresentation; + // probably not needed at this level, since for SEG each segment will + // have separate geometry definition ImageVolumeGeometry volumeGeometry; // DcmDataset(s) that hold the original representation of the diff --git a/include/dcmqi/ParametricMapObject.h b/include/dcmqi/ParametricMapObject.h index e5c9b83a..4f6c59cd 100644 --- a/include/dcmqi/ParametricMapObject.h +++ b/include/dcmqi/ParametricMapObject.h @@ -33,6 +33,10 @@ class ParametricMapObject : public MultiframeObject { int updateMetaDataFromDICOM(std::vector); + DPMParametricMapIOD* getDICOMRepresentation(){ + return parametricMap; + }; + protected: typedef itk::CastImageFilter Float32ToDummyCasterType; diff --git a/libsrc/DICOMFrame.cpp b/libsrc/DICOMFrame.cpp index bfbff419..f652f26a 100644 --- a/libsrc/DICOMFrame.cpp +++ b/libsrc/DICOMFrame.cpp @@ -7,6 +7,7 @@ namespace dcmqi { int DICOMFrame::initializeFrameGeometryFromLegacyInstance() { OFString ippStr; + frameIPP.set_size(3); for(int j=0;j<3;j++){ CHECK_COND(frameDataset->findAndGetOFString(DCM_ImagePositionPatient, ippStr, j)); frameIPP[j] = atof(ippStr.c_str()); diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index 389df8e0..fd60697b 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -18,16 +18,22 @@ int MultiframeObject::initializeMetaDataFromString(const std::string &metaDataSt int MultiframeObject::initializeEquipmentInfo() { if(sourceRepresentationType == ITK_REPR){ + equipmentInfoModule = IODEnhGeneralEquipmentModule::EquipmentInfo(QIICR_MANUFACTURER, QIICR_DEVICE_SERIAL_NUMBER, + QIICR_MANUFACTURER_MODEL_NAME, QIICR_SOFTWARE_VERSIONS); + /* equipmentInfoModule.m_Manufacturer = QIICR_MANUFACTURER; equipmentInfoModule.m_DeviceSerialNumber = QIICR_DEVICE_SERIAL_NUMBER; equipmentInfoModule.m_ManufacturerModelName = QIICR_MANUFACTURER_MODEL_NAME; equipmentInfoModule.m_SoftwareVersions = QIICR_SOFTWARE_VERSIONS; + */ + } else { // DICOM_REPR } return EXIT_SUCCESS; } int MultiframeObject::initializeContentIdentification() { + if(sourceRepresentationType == ITK_REPR){ CHECK_COND(contentIdentificationMacro.setContentCreatorName("dcmqi")); if(metaDataJson.isMember("ContentDescription")){ @@ -40,6 +46,12 @@ int MultiframeObject::initializeContentIdentification() { } else { CHECK_COND(contentIdentificationMacro.setContentLabel("DCMQI")); } + if(metaDataJson.isMember("InstanceNumber")){ + CHECK_COND(contentIdentificationMacro.setInstanceNumber(metaDataJson["InstanceNumber"].asCString())); + } else { + CHECK_COND(contentIdentificationMacro.setInstanceNumber("1")); + } + CHECK_COND(contentIdentificationMacro.check()) return EXIT_SUCCESS; } else { // DICOM_REPR } diff --git a/libsrc/ParametricMapConverter.cpp b/libsrc/ParametricMapConverter.cpp index 1a25c812..97cafff6 100644 --- a/libsrc/ParametricMapConverter.cpp +++ b/libsrc/ParametricMapConverter.cpp @@ -6,6 +6,7 @@ // DCMQI includes #include "dcmqi/ParametricMapConverter.h" #include "dcmqi/SegmentationImageConverter.h" +#include "dcmqi/ParametricMapObject.h" using namespace std; @@ -13,11 +14,14 @@ namespace dcmqi { DcmDataset* itkimage2paramapReplacement(const FloatImageType::Pointer ¶metricMapImage, vector dcmDatasets, const string &metaData) { - /* - ParametricMapConverter pmConverter(parametricMapImage, dcmDatasets, metaData); - pmConverter.convert(); + ParametricMapObject pm; + pm.initializeFromITK(parametricMapImage, metaData, dcmDatasets); + + DPMParametricMapIOD* pmap = pm.getDICOMRepresentation(); + + DcmDataset* output = new DcmDataset(); + CHECK_COND(pmap->writeDataset(*output)); - return convert.getDataset(); */ return NULL; } diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 5de1250b..cb5f91e5 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -26,6 +26,8 @@ int ParametricMapObject::initializeFromITK(Float32ITKImageType::Pointer inputIma // NB: the sequence of steps initializing different components of the object parallels that // in the original converter function. It probably makes sense to revisit the sequence // of these steps. It does not necessarily need to happen in this order. + initializeEquipmentInfo(); + initializeContentIdentification(); // TODO: consider creating parametric map object after all FGs are initialized instead createParametricMap(); @@ -101,8 +103,10 @@ int ParametricMapObject::createParametricMap() { DPMTypes::CQ_RESEARCH); // TODO: look into the following, check with @che85 on the purpose of this line! - if (OFCondition* pCondition = OFget(&obj)) + if (OFCondition* pCondition = OFget(&obj)) { + std::cerr << "Failed to create parametric map object!" << std::endl; return EXIT_FAILURE; + } parametricMap = OFget(&obj); From 1ea14167f6173b80cfb3c2d2d8a15e09e7f65c53 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 23 Jun 2017 11:41:10 -0400 Subject: [PATCH 014/116] circle_ci another dummy --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 989788ca..1fa94edb 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ - [![OpenHub](https://www.openhub.net/p/dcmqi/widgets/project_thin_badge.gif)](https://www.openhub.net/p/dcmqi) | Docker | [![](https://images.microbadger.com/badges/version/qiicr/dcmqi:v1.0.5.svg)](https://microbadger.com/images/qiicr/dcmqi:v1.0.5) | [![](https://images.microbadger.com/badges/version/qiicr/dcmqi.svg)](https://microbadger.com/images/qiicr/dcmqi) | From 92745e3356bca6a75b7bafeceb5a504806e403fc Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 23 Jun 2017 16:49:31 -0400 Subject: [PATCH 015/116] ENH: fix more issues --- include/dcmqi/ParametricMapObject.h | 10 ++++++++-- libsrc/ImageVolumeGeometry.cpp | 2 ++ libsrc/ParametricMapConverter.cpp | 8 +++----- libsrc/ParametricMapObject.cpp | 1 + 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/include/dcmqi/ParametricMapObject.h b/include/dcmqi/ParametricMapObject.h index 4f6c59cd..922dd8a0 100644 --- a/include/dcmqi/ParametricMapObject.h +++ b/include/dcmqi/ParametricMapObject.h @@ -20,6 +20,11 @@ */ class ParametricMapObject : public MultiframeObject { public: + + ParametricMapObject(){ + parametricMap = NULL; + } + typedef IODFloatingPointImagePixelModule::value_type Float32PixelType; typedef itk::Image Float32ITKImageType; @@ -33,8 +38,9 @@ class ParametricMapObject : public MultiframeObject { int updateMetaDataFromDICOM(std::vector); - DPMParametricMapIOD* getDICOMRepresentation(){ - return parametricMap; + int getDICOMRepresentation(DcmDataset& dcm){ + if(parametricMap) + CHECK_COND(parametricMap->write(dcm)); }; protected: diff --git a/libsrc/ImageVolumeGeometry.cpp b/libsrc/ImageVolumeGeometry.cpp index d71f6ef9..a550ae6d 100644 --- a/libsrc/ImageVolumeGeometry.cpp +++ b/libsrc/ImageVolumeGeometry.cpp @@ -49,6 +49,8 @@ ImageVolumeGeometry::DummyImageType::Pointer ImageVolumeGeometry::getITKRepresen ImageVolumeGeometry::DummyImageType::SpacingType spacing; ImageVolumeGeometry::DummyImageType::RegionType region; + image = ImageVolumeGeometry::DummyImageType::New(); + index.Fill(0); size[0] = extent[0]; diff --git a/libsrc/ParametricMapConverter.cpp b/libsrc/ParametricMapConverter.cpp index 97cafff6..8f95965e 100644 --- a/libsrc/ParametricMapConverter.cpp +++ b/libsrc/ParametricMapConverter.cpp @@ -17,12 +17,10 @@ namespace dcmqi { ParametricMapObject pm; pm.initializeFromITK(parametricMapImage, metaData, dcmDatasets); - DPMParametricMapIOD* pmap = pm.getDICOMRepresentation(); + DcmDataset *output = new DcmDataset(); + pm.getDICOMRepresentation(*output); - DcmDataset* output = new DcmDataset(); - CHECK_COND(pmap->writeDataset(*output)); - - return NULL; + return output; } pair paramap2itkimageReplacement(DcmDataset *pmapDataset){ diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index cb5f91e5..6c16f865 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -8,6 +8,7 @@ #include #include + int ParametricMapObject::initializeFromITK(Float32ITKImageType::Pointer inputImage, const string &metaDataStr, std::vector derivationDatasets) { From 2ba758326d6608d1a73b57e18095b9c6607fbd6d Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Tue, 27 Jun 2017 10:20:09 -0400 Subject: [PATCH 016/116] BUG: fixing wrong initialization of DPMParametricMapIOD --- libsrc/ParametricMapObject.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 6c16f865..24225a46 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -109,7 +109,7 @@ int ParametricMapObject::createParametricMap() { return EXIT_FAILURE; } - parametricMap = OFget(&obj); + parametricMap = new DPMParametricMapIOD( *OFget(&obj) ); // These FG are constant FGIdentityPixelValueTransformation idTransFG; From 04fe69575831bb50fd878f16f4a153d06102f562 Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Tue, 27 Jun 2017 21:07:40 -0400 Subject: [PATCH 017/116] ENH: adding refactored code for paramap from DCM to itk --- apps/paramaps/paramap2itkimage.cxx | 2 +- include/dcmqi/ImageVolumeGeometry.h | 37 ++++- include/dcmqi/MultiframeObject.h | 73 +++++++++- include/dcmqi/ParametricMapObject.h | 9 +- libsrc/ImageVolumeGeometry.cpp | 36 ----- libsrc/MultiframeObject.cpp | 205 +++++++++++++++++++++++++++- libsrc/ParametricMapConverter.cpp | 11 +- libsrc/ParametricMapObject.cpp | 66 +++++++++ 8 files changed, 393 insertions(+), 46 deletions(-) diff --git a/apps/paramaps/paramap2itkimage.cxx b/apps/paramaps/paramap2itkimage.cxx index 31908f8b..13161cbb 100644 --- a/apps/paramaps/paramap2itkimage.cxx +++ b/apps/paramaps/paramap2itkimage.cxx @@ -25,7 +25,7 @@ int main(int argc, char *argv[]) CHECK_COND(sliceFF.loadFile(inputFileName.c_str())); DcmDataset* dataset = sliceFF.getDataset(); - pair result = dcmqi::ParametricMapConverter::paramap2itkimage(dataset); + pair result = dcmqi::paramap2itkimageReplacement(dataset); string fileExtension = helper::getFileExtensionFromType(outputType); diff --git a/include/dcmqi/ImageVolumeGeometry.h b/include/dcmqi/ImageVolumeGeometry.h index 30b2d8fb..98986871 100644 --- a/include/dcmqi/ImageVolumeGeometry.h +++ b/include/dcmqi/ImageVolumeGeometry.h @@ -37,7 +37,42 @@ class ImageVolumeGeometry { int setExtent(SizeType); int setDirections(DirectionType); - DummyImageType::Pointer getITKRepresentation(); + template + typename T::Pointer getITKRepresentation(){ + typename T::Pointer image; + typename T::IndexType index; + typename T::SizeType size; + typename T::DirectionType direction; + typename T::SpacingType spacing; + typename T::RegionType region; + + image = T::New(); + + index.Fill(0); + + size[0] = extent[0]; + size[1] = extent[1]; + size[2] = extent[2]; + + region.SetIndex(index); + region.SetSize(size); + + spacing[0] = this->spacing[0]; + spacing[1] = this->spacing[1]; + spacing[2] = this->spacing[2]; + + for (int i = 0; i < 3; i++) + direction[i][0] = rowDirection[i]; + for (int i = 0; i < 3; i++) + direction[i][1] = columnDirection[i]; + for (int i = 0; i < 3; i++) + direction[i][2] = sliceDirection[i]; + + image->SetDirection(direction); + image->SetSpacing(spacing); + + return image; + } protected: // use vnl_vector to simplify support of vector calculations diff --git a/include/dcmqi/MultiframeObject.h b/include/dcmqi/MultiframeObject.h index e882a1f9..0c1e4b70 100644 --- a/include/dcmqi/MultiframeObject.h +++ b/include/dcmqi/MultiframeObject.h @@ -5,7 +5,7 @@ #ifndef DCMQI_MULTIFRAMEOBJECT_H #define DCMQI_MULTIFRAMEOBJECT_H - +#include #include #include #include @@ -17,6 +17,8 @@ #include #include +#include "vnl/vnl_cross.h" + #include #include #include @@ -52,7 +54,9 @@ class MultiframeObject { // Output is always a single DcmDataset, since this is a multiframe // object DcmDataset* getDcmDataset() const; - Json::Value getMetaDataJson() const; + Json::Value getMetaDataJson() const { + return metaDataJson; + }; // get ITK representation will be specific to the derived classes, // since the type of the ITK image and the number of ITK images is @@ -66,6 +70,8 @@ class MultiframeObject { typedef itk::Image DummyImageType; typedef DummyImageType::PointType PointType; typedef DummyImageType::DirectionType DirectionType; + typedef DummyImageType::SpacingType SpacingType; + typedef DummyImageType::SizeType SizeType; int initializeMetaDataFromString(const std::string&); // what this function does depends on whether we are coming from @@ -77,6 +83,69 @@ class MultiframeObject { // from ITK int initializeVolumeGeometryFromITK(DummyImageType::Pointer); + template + int initializeVolumeGeometryFromDICOM(T iodImage, DcmDataset *dataset) { + SpacingType spacing; + PointType origin; + DirectionType directions; + SizeType extent; + + FGInterface &fgInterface = iodImage->getFunctionalGroups(); + + if (getImageDirections(fgInterface, directions)) { + cerr << "Failed to get image directions" << endl; + throw -1; + } + + cout << directions << endl; + + double computedSliceSpacing, computedVolumeExtent; + vnl_vector sliceDirection(3); + sliceDirection[0] = *directions[0]; + sliceDirection[1] = *directions[1]; + sliceDirection[2] = *directions[2]; + if (computeVolumeExtent(fgInterface, sliceDirection, origin, computedSliceSpacing, computedVolumeExtent)) { + cerr << "Failed to compute origin and/or slice spacing!" << endl; + throw -1; + } + + if (getDeclaredImageSpacing(fgInterface, spacing)) { + cerr << "Failed to get image spacing from DICOM!" << endl; + throw -1; + } + + const double tolerance = 1e-5; + if(!spacing[2]){ + spacing[2] = computedSliceSpacing; + } else if(fabs(spacing[2]-computedSliceSpacing)>tolerance){ + cerr << "WARNING: Declared slice spacing is significantly different from the one declared in DICOM!" << + " Declared = " << spacing[2] << " Computed = " << computedSliceSpacing << endl; + } + + // Region size + { + OFString str; + if(dataset->findAndGetOFString(DCM_Rows, str).good()) + extent[1] = atoi(str.c_str()); + if(dataset->findAndGetOFString(DCM_Columns, str).good()) + extent[0] = atoi(str.c_str()); + } + extent[2] = fgInterface.getNumberOfFrames(); + + volumeGeometry.setSpacing(spacing); + volumeGeometry.setOrigin(origin); + volumeGeometry.setExtent(extent); + volumeGeometry.setDirections(directions); + + return EXIT_SUCCESS; + } + + int getImageDirections(FGInterface& fgInterface, DirectionType &dir); + + int computeVolumeExtent(FGInterface& fgInterface, vnl_vector &sliceDirection, PointType &imageOrigin, + double &sliceSpacing, double &sliceExtent); + int getDeclaredImageSpacing(FGInterface &fgInterface, SpacingType &spacing); + // initialize attributes of the composite context that are common for all multiframe objects //virtual int initializeCompositeContext(); // check whether all of the attributes required for initialization of the object are present in the diff --git a/include/dcmqi/ParametricMapObject.h b/include/dcmqi/ParametricMapObject.h index 922dd8a0..63b30559 100644 --- a/include/dcmqi/ParametricMapObject.h +++ b/include/dcmqi/ParametricMapObject.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "MultiframeObject.h" @@ -43,6 +44,12 @@ class ParametricMapObject : public MultiframeObject { CHECK_COND(parametricMap->write(dcm)); }; + int initializeFromDICOM(DcmDataset * sourceDataset); + + Float32ITKImageType::Pointer getITKRepresentation() const { + return itkImage; + } + protected: typedef itk::CastImageFilter Float32ToDummyCasterType; @@ -66,8 +73,6 @@ class ParametricMapObject : public MultiframeObject { // Data containers specific to this object Float32ITKImageType::Pointer itkImage; - - private: DPMParametricMapIOD* parametricMap; }; diff --git a/libsrc/ImageVolumeGeometry.cpp b/libsrc/ImageVolumeGeometry.cpp index a550ae6d..10570b0f 100644 --- a/libsrc/ImageVolumeGeometry.cpp +++ b/libsrc/ImageVolumeGeometry.cpp @@ -39,40 +39,4 @@ int ImageVolumeGeometry::setDirections(DirectionType d) { for (int i = 0; i < 3; i++) sliceDirection[i] = d[i][2]; return EXIT_SUCCESS; -} - -ImageVolumeGeometry::DummyImageType::Pointer ImageVolumeGeometry::getITKRepresentation(){ - ImageVolumeGeometry::DummyImageType::Pointer image; - ImageVolumeGeometry::DummyImageType::IndexType index; - ImageVolumeGeometry::DummyImageType::SizeType size; - ImageVolumeGeometry::DummyImageType::DirectionType direction; - ImageVolumeGeometry::DummyImageType::SpacingType spacing; - ImageVolumeGeometry::DummyImageType::RegionType region; - - image = ImageVolumeGeometry::DummyImageType::New(); - - index.Fill(0); - - size[0] = extent[0]; - size[1] = extent[1]; - size[2] = extent[2]; - - region.SetIndex(index); - region.SetSize(size); - - spacing[0] = this->spacing[0]; - spacing[1] = this->spacing[1]; - spacing[2] = this->spacing[2]; - - for (int i = 0; i < 3; i++) - direction[i][0] = rowDirection[i]; - for (int i = 0; i < 3; i++) - direction[i][1] = columnDirection[i]; - for (int i = 0; i < 3; i++) - direction[i][2] = sliceDirection[i]; - - image->SetDirection(direction); - image->SetSpacing(spacing); - - return image; } \ No newline at end of file diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index fd60697b..ef74b08b 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -182,7 +182,7 @@ std::vector MultiframeObject::findIntersectingSlices(ImageVolumeGeometry &v std::vector intersectingSlices; // for now, adopt a simple strategy that maps origin of the frame to index, and selects the slice corresponding // to this index as the intersecting one - ImageVolumeGeometry::DummyImageType::Pointer itkvolume = volume.getITKRepresentation(); + ImageVolumeGeometry::DummyImageType::Pointer itkvolume = volume.getITKRepresentation(); ImageVolumeGeometry::DummyImageType::PointType point; ImageVolumeGeometry::DummyImageType::IndexType index; vnl_vector frameIPP = frame.getFrameIPP(); @@ -237,5 +237,208 @@ int MultiframeObject::initializeCommonInstanceReferenceModule(IODCommonInstanceR } } + return EXIT_SUCCESS; +} + +int MultiframeObject::getImageDirections(FGInterface& fgInterface, DirectionType &dir){ + // TODO: handle the situation when FoR is not initialized + OFBool isPerFrame; + vnl_vector rowDirection(3), colDirection(3); + + FGPlaneOrientationPatient *planorfg = OFstatic_cast(FGPlaneOrientationPatient*, + fgInterface.get(0, DcmFGTypes::EFG_PLANEORIENTPATIENT, isPerFrame)); + if(!planorfg){ + cerr << "Plane Orientation (Patient) is missing, cannot parse input " << endl; + return EXIT_FAILURE; + } + OFString orientStr; + for(int i=0;i<3;i++){ + if(planorfg->getImageOrientationPatient(orientStr, i).good()){ + rowDirection[i] = atof(orientStr.c_str()); + } else { + cerr << "Failed to get orientation " << i << endl; + return EXIT_FAILURE; + } + } + for(int i=3;i<6;i++){ + if(planorfg->getImageOrientationPatient(orientStr, i).good()){ + colDirection[i-3] = atof(orientStr.c_str()); + } else { + cerr << "Failed to get orientation " << i << endl; + return EXIT_FAILURE; + } + } + vnl_vector sliceDirection = vnl_cross_3d(rowDirection, colDirection); + sliceDirection.normalize(); + + cout << "Row direction: " << rowDirection << endl; + cout << "Col direction: " << colDirection << endl; + + for(int i=0;i<3;i++){ + dir[i][0] = rowDirection[i]; + dir[i][1] = colDirection[i]; + dir[i][2] = sliceDirection[i]; + } + + cout << "Z direction: " << sliceDirection << endl; + + return 0; +} + +int MultiframeObject::computeVolumeExtent(FGInterface& fgInterface, vnl_vector &sliceDirection, + PointType &imageOrigin, double &sliceSpacing, double &sliceExtent) { + // Size + // Rows/Columns can be read directly from the respective attributes + // For number of slices, consider that all segments must have the same number of frames. + // If we have FoR UID initialized, this means every segment should also have Plane + // Position (Patient) initialized. So we can get the number of slices by looking + // how many per-frame functional groups a segment has. + + vector originDistances; + map originStr2distance; + map frame2overlap; + double minDistance = 0.0; + + sliceSpacing = 0; + + unsigned numFrames = fgInterface.getNumberOfFrames(); + + // Determine ordering of the frames, keep mapping from ImagePositionPatient string + // to the distance, and keep track (just out of curiosity) how many frames overlap + vnl_vector refOrigin(3); + { + OFBool isPerFrame; + FGPlanePosPatient *planposfg = OFstatic_cast(FGPlanePosPatient*, + fgInterface.get(0, DcmFGTypes::EFG_PLANEPOSPATIENT, isPerFrame)); + for(int j=0;j<3;j++){ + OFString planposStr; + if(planposfg->getImagePositionPatient(planposStr, j).good()){ + refOrigin[j] = atof(planposStr.c_str()); + } else { + cerr << "Failed to read patient position" << endl; + } + } + } + + for(size_t frameId=0;frameId sOrigin; + OFString sOriginStr = ""; + sOrigin.set_size(3); + for(int j=0;j<3;j++){ + OFString planposStr; + if(planposfg->getImagePositionPatient(planposStr, j).good()){ + sOrigin[j] = atof(planposStr.c_str()); + sOriginStr += planposStr; + if(j<2) + sOriginStr+='/'; + } else { + cerr << "Failed to read patient position" << endl; + return EXIT_FAILURE; + } + } + + // check if this frame has already been encountered + if(originStr2distance.find(sOriginStr) == originStr2distance.end()){ + vnl_vector difference; + difference.set_size(3); + difference[0] = sOrigin[0]-refOrigin[0]; + difference[1] = sOrigin[1]-refOrigin[1]; + difference[2] = sOrigin[2]-refOrigin[2]; + double dist = dot_product(difference,sliceDirection); + frame2overlap[sOriginStr] = 1; + originStr2distance[sOriginStr] = dist; + assert(originStr2distance.find(sOriginStr) != originStr2distance.end()); + originDistances.push_back(dist); + + if(frameId==0){ + minDistance = dist; + imageOrigin[0] = sOrigin[0]; + imageOrigin[1] = sOrigin[1]; + imageOrigin[2] = sOrigin[2]; + } + else if(dist1){ + // WARNING: this should be improved further. Spacing should be calculated for + // consecutive frames of the individual segment. Right now, all frames are considered + // indiscriminately. Question is whether it should be computed at all, considering we do + // not have any information about whether the 2 frames are adjacent or not, so perhaps we should + // always rely on the declared spacing, and not even try to compute it? + // TODO: discuss this with the QIICR team! + + // sort all unique distances, this will be used to check consistency of + // slice spacing, and also to locate the slice position from ImagePositionPatient + // later when we read the segments + sort(originDistances.begin(), originDistances.end()); + + sliceSpacing = fabs(originDistances[0]-originDistances[1]); + + for(size_t i=1;i::const_iterator it=frame2overlap.begin(); + it!=frame2overlap.end();++it){ + if(it->second>1) + overlappingFramesCnt++; + } + + cout << "Total frames: " << numFrames << endl; + cout << "Total frames with unique IPP: " << originDistances.size() << endl; + cout << "Total overlapping frames: " << overlappingFramesCnt << endl; + cout << "Origin: " << imageOrigin << endl; + } + + return EXIT_SUCCESS; +} + +int MultiframeObject::getDeclaredImageSpacing(FGInterface &fgInterface, SpacingType &spacing){ + OFBool isPerFrame; + FGPixelMeasures *pixelMeasures = OFstatic_cast(FGPixelMeasures*, + fgInterface.get(0, DcmFGTypes::EFG_PIXELMEASURES, isPerFrame)); + if(!pixelMeasures){ + cerr << "Pixel measures FG is missing!" << endl; + return EXIT_FAILURE; + } + + pixelMeasures->getPixelSpacing(spacing[0], 0); + pixelMeasures->getPixelSpacing(spacing[1], 1); + + Float64 spacingFloat; + if(pixelMeasures->getSpacingBetweenSlices(spacingFloat,0).good() && spacingFloat != 0){ + spacing[2] = spacingFloat; + } else if(pixelMeasures->getSliceThickness(spacingFloat,0).good() && spacingFloat != 0){ + // SliceThickness can be carried forward from the source images, and may not be what we need + // As an example, this ePAD example has 1.25 carried from CT, but true computed thickness is 1! + cerr << "WARNING: SliceThickness is present and is " << spacingFloat << ". using it!" << endl; + spacing[2] = spacingFloat; + } return EXIT_SUCCESS; } \ No newline at end of file diff --git a/libsrc/ParametricMapConverter.cpp b/libsrc/ParametricMapConverter.cpp index 8f95965e..5fd22c64 100644 --- a/libsrc/ParametricMapConverter.cpp +++ b/libsrc/ParametricMapConverter.cpp @@ -24,7 +24,12 @@ namespace dcmqi { } pair paramap2itkimageReplacement(DcmDataset *pmapDataset){ - return pair(); + + ParametricMapObject pm; + pm.initializeFromDICOM(pmapDataset); + + return pair (pm.getITKRepresentation(), + pm.getMetaDataJson().asString()); }; @@ -266,7 +271,7 @@ namespace dcmqi { slice2derimg = getSliceMapForSegmentation2DerivationImage(dcmDatasets, cast->GetOutput()); cout << "Mapping from the ITK image slices to the DICOM instances in the input list" << endl; for(int i=0;igetFrames(); result = OFget >(&frames)->addFrame(&*data.begin(), frameSize, perFrameFGs); - cout << "Frame " << sliceNumber << " added" << endl; +// cout << "Frame " << sliceNumber << " added" << endl; } // remove derivation image FG from the per-frame FGs, only if applicable! diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 24225a46..125b25cd 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -228,3 +228,69 @@ int ParametricMapObject::initializeRWVMFG() { return EXIT_SUCCESS; } + + +int ParametricMapObject::initializeFromDICOM(DcmDataset * sourceDataset) { + + DcmRLEDecoderRegistration::registerCodecs(); + + OFLogger dcemfinfLogger = OFLog::getLogger("qiicr.apps"); + dcemfinfLogger.setLogLevel(dcmtk::log4cplus::OFF_LOG_LEVEL); + + OFvariant result = DPMParametricMapIOD::loadDataset(*sourceDataset); + if (OFCondition* pCondition = OFget(&result)) { + throw -1; + } + + DPMParametricMapIOD* pMapDoc = *OFget(&result); + + initializeVolumeGeometryFromDICOM(pMapDoc, sourceDataset); + + // Initialize the image + Float32ITKImageType::Pointer itkImage = volumeGeometry.getITKRepresentation(); + + DPMParametricMapIOD::FramesType obj = pMapDoc->getFrames(); + if (OFCondition* pCondition = OFget(&obj)) { + throw -1; + } + + DPMParametricMapIOD::Frames frames = *OFget >(&obj); + + FGInterface &fgInterface = pMapDoc->getFunctionalGroups(); + for(int frameId=0;frameIdSetPixel(index, frame[pixelPosition]); + } + } + } + + +// informationHandler metaInfo; +// populateMetaInformationFromDICOM(pmapDataset, metaInfo); + + return EXIT_SUCCESS; +} From 426fd9f42a82c5d09cca76c854e0e74e7c19a4de Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Wed, 28 Jun 2017 10:45:32 -0400 Subject: [PATCH 018/116] ENH: added region and origin to ImageVolumeGeometry getITKRepresentation --- include/dcmqi/ImageVolumeGeometry.h | 7 +++++++ libsrc/MultiframeObject.cpp | 1 - libsrc/ParametricMapObject.cpp | 5 ++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/include/dcmqi/ImageVolumeGeometry.h b/include/dcmqi/ImageVolumeGeometry.h index 98986871..92d2a507 100644 --- a/include/dcmqi/ImageVolumeGeometry.h +++ b/include/dcmqi/ImageVolumeGeometry.h @@ -45,6 +45,7 @@ class ImageVolumeGeometry { typename T::DirectionType direction; typename T::SpacingType spacing; typename T::RegionType region; + typename T::PointType origin; image = T::New(); @@ -68,6 +69,12 @@ class ImageVolumeGeometry { for (int i = 0; i < 3; i++) direction[i][2] = sliceDirection[i]; + origin[0] = this->origin[0]; + origin[1] = this->origin[1]; + origin[2] = this->origin[2]; + + image->SetRegions(region); + image->SetOrigin(origin); image->SetDirection(direction); image->SetSpacing(spacing); diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index ef74b08b..89d5eda6 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -71,7 +71,6 @@ int MultiframeObject::initializeVolumeGeometryFromITK(DummyImageType::Pointer im volumeGeometry.setSpacing(spacing); volumeGeometry.setOrigin(origin); volumeGeometry.setExtent(extent); - volumeGeometry.setDirections(directions); return EXIT_SUCCESS; diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 125b25cd..240e370b 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -247,7 +247,10 @@ int ParametricMapObject::initializeFromDICOM(DcmDataset * sourceDataset) { initializeVolumeGeometryFromDICOM(pMapDoc, sourceDataset); // Initialize the image - Float32ITKImageType::Pointer itkImage = volumeGeometry.getITKRepresentation(); + itkImage = volumeGeometry.getITKRepresentation(); + + itkImage->Allocate(); + itkImage->FillBuffer(0); DPMParametricMapIOD::FramesType obj = pMapDoc->getFrames(); if (OFCondition* pCondition = OFget(&obj)) { From 01f8e5d97b5fd06054524e7f60bee8def37b594e Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Wed, 28 Jun 2017 16:06:51 -0400 Subject: [PATCH 019/116] ENH: initialize meta information from DCM into internal data structure --- include/dcmqi/ParametricMapObject.h | 3 +++ libsrc/ParametricMapConverter.cpp | 7 ++++++- libsrc/ParametricMapObject.cpp | 12 +++++++++--- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/include/dcmqi/ParametricMapObject.h b/include/dcmqi/ParametricMapObject.h index 63b30559..a82759a5 100644 --- a/include/dcmqi/ParametricMapObject.h +++ b/include/dcmqi/ParametricMapObject.h @@ -46,6 +46,9 @@ class ParametricMapObject : public MultiframeObject { int initializeFromDICOM(DcmDataset * sourceDataset); + template + void initializeMetaDataFromDICOM(T doc); + Float32ITKImageType::Pointer getITKRepresentation() const { return itkImage; } diff --git a/libsrc/ParametricMapConverter.cpp b/libsrc/ParametricMapConverter.cpp index 5fd22c64..1c2cc1cd 100644 --- a/libsrc/ParametricMapConverter.cpp +++ b/libsrc/ParametricMapConverter.cpp @@ -28,8 +28,13 @@ namespace dcmqi { ParametricMapObject pm; pm.initializeFromDICOM(pmapDataset); + Json::StyledWriter styledWriter; + std::stringstream ss; + + ss << styledWriter.write(pm.getMetaDataJson()); + return pair (pm.getITKRepresentation(), - pm.getMetaDataJson().asString()); + ss.str()); }; diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 240e370b..12a8d3ed 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -291,9 +291,15 @@ int ParametricMapObject::initializeFromDICOM(DcmDataset * sourceDataset) { } } - -// informationHandler metaInfo; -// populateMetaInformationFromDICOM(pmapDataset, metaInfo); + initializeMetaDataFromDICOM(pMapDoc); return EXIT_SUCCESS; } + +template +void ParametricMapObject::initializeMetaDataFromDICOM(T doc) { + + OFString temp; + doc->getSeries().getSeriesDescription(temp); + metaDataJson["SeriesDescription"] = temp.c_str(); +} From 88ac52e2ab7250913d3c248c08f028dff2270869 Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Wed, 28 Jun 2017 16:49:23 -0400 Subject: [PATCH 020/116] ENH: complete initialization of meta information from DCM * added Helper class static methods --- include/dcmqi/Helper.h | 5 +++ libsrc/Helper.cpp | 26 +++++++++++ libsrc/ParametricMapObject.cpp | 81 ++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+) diff --git a/include/dcmqi/Helper.h b/include/dcmqi/Helper.h index d1dc966e..483d773d 100644 --- a/include/dcmqi/Helper.h +++ b/include/dcmqi/Helper.h @@ -71,6 +71,11 @@ namespace dcmqi { static OFString generateUID(); static OFString getTagAsOFString(DcmDataset*, DcmTagKey); + + static string getCodeSequenceValue(CodeSequenceMacro &codeSequence); + static string getCodeSequenceDesignator(CodeSequenceMacro &codeSequence); + static string getCodeSequenceMeaning(CodeSequenceMacro &codeSequence); + static Json::Value codeSequence2Json(CodeSequenceMacro &codeSequence); }; } diff --git a/libsrc/Helper.cpp b/libsrc/Helper.cpp index 9e5b7af1..422db6d6 100644 --- a/libsrc/Helper.cpp +++ b/libsrc/Helper.cpp @@ -392,4 +392,30 @@ namespace dcmqi { string s = string()+codeValue.c_str()+","+codingSchemeDesignator.c_str()+","+codeMeaning.c_str(); return s; } + + Json::Value Helper::codeSequence2Json(CodeSequenceMacro &codeSequence) { + Json::Value value; + value["CodeValue"] = getCodeSequenceValue(codeSequence); + value["CodingSchemeDesignator"] = getCodeSequenceDesignator(codeSequence); + value["CodeMeaning"] = getCodeSequenceMeaning(codeSequence); + return value; + } + + string Helper::getCodeSequenceValue(CodeSequenceMacro &codeSequence) { + OFString value; + codeSequence.getCodeValue(value); + return value.c_str(); + } + + string Helper::getCodeSequenceDesignator(CodeSequenceMacro &codeSequence) { + OFString designator; + codeSequence.getCodingSchemeDesignator(designator); + return designator.c_str(); + } + + string Helper::getCodeSequenceMeaning(CodeSequenceMacro &codeSequence) { + OFString meaning; + codeSequence.getCodeMeaning(meaning); + return meaning.c_str(); + } } diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 12a8d3ed..9e9112c1 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -298,8 +298,89 @@ int ParametricMapObject::initializeFromDICOM(DcmDataset * sourceDataset) { template void ParametricMapObject::initializeMetaDataFromDICOM(T doc) { + // TODO: move shared information retrieval to parent class OFString temp; doc->getSeries().getSeriesDescription(temp); metaDataJson["SeriesDescription"] = temp.c_str(); + + doc->getSeries().getSeriesNumber(temp); + metaDataJson["SeriesNumber"] = temp.c_str(); + + doc->getIODGeneralImageModule().getInstanceNumber(temp); + metaDataJson["InstanceNumber"] = temp.c_str(); + + using namespace dcmqi; + + doc->getSeries().getBodyPartExamined(temp); + metaDataJson["BodyPartExamined"] = temp.c_str(); + + doc->getDPMParametricMapImageModule().getImageType(temp, 3); + metaDataJson["DerivedPixelContrast"] = temp.c_str(); + + if (doc->getNumberOfFrames() > 0) { + FGInterface& fg = doc->getFunctionalGroups(); + FGRealWorldValueMapping* rw = OFstatic_cast(FGRealWorldValueMapping*, + fg.get(0, DcmFGTypes::EFG_REALWORLDVALUEMAPPING)); + if (rw->getRealWorldValueMapping().size() > 0) { + FGRealWorldValueMapping::RWVMItem *item = rw->getRealWorldValueMapping()[0]; + metaDataJson["MeasurementUnitsCode"] = Helper::codeSequence2Json(item->getMeasurementUnitsCode()); + + Float64 slope; + item->getData().findAndGetFloat64(DCM_RealWorldValueSlope, slope); + metaDataJson["RealWorldValueSlope"] = slope; + + vector diffusionBValues; + + for(int quantIdx=0; quantIdxgetEntireQuantityDefinitionSequence().size(); quantIdx++) { +// TODO: what if there are more than one? + ContentItemMacro* macro = item->getEntireQuantityDefinitionSequence()[quantIdx]; + CodeSequenceMacro* codeSequence= macro->getConceptNameCodeSequence(); + if (codeSequence != NULL) { + OFString codeMeaning; + codeSequence->getCodeMeaning(codeMeaning); + OFString designator, meaning, value; + + if (codeMeaning == "Quantity") { + CodeSequenceMacro* quantityValueCode = macro->getConceptCodeSequence(); + if (quantityValueCode != NULL) { + metaDataJson["QuantityValueCode"] = Helper::codeSequence2Json(*quantityValueCode); + } + } else if (codeMeaning == "Measurement Method") { + CodeSequenceMacro* measurementMethodValueCode = macro->getConceptCodeSequence(); + if (measurementMethodValueCode != NULL) { + metaDataJson["MeasurementMethodCode"] = Helper::codeSequence2Json(*measurementMethodValueCode); + } + } else if (codeMeaning == "Source image diffusion b-value") { + macro->getNumericValue(value); + diffusionBValues.push_back(value.c_str()); + } + } + } + + if (diffusionBValues.size() > 0) { + metaDataJson["SourceImageDiffusionBValues"] = Json::Value(Json::arrayValue); + for (vector::iterator it = diffusionBValues.begin() ; it != diffusionBValues.end(); ++it) + metaDataJson["SourceImageDiffusionBValues"].append(*it); + } + } + + FGDerivationImage* derivationImage = OFstatic_cast(FGDerivationImage*, fg.get(0, DcmFGTypes::EFG_DERIVATIONIMAGE)); + OFVector& derivationImageItems = derivationImage->getDerivationImageItems(); + + if(derivationImageItems.size()>0){ + DerivationImageItem* derivationImageItem = derivationImageItems[0]; + CodeSequenceMacro* derivationCode = derivationImageItem->getDerivationCodeItems()[0]; + if (derivationCode != NULL) { + metaDataJson["DerivationCode"] = Helper::codeSequence2Json(*derivationCode); + } + } + + FGFrameAnatomy* fa = OFstatic_cast(FGFrameAnatomy*, fg.get(0, DcmFGTypes::EFG_FRAMEANATOMY)); + metaDataJson["AnatomicRegionSequence"] = Helper::codeSequence2Json(fa->getAnatomy().getAnatomicRegion()); + + FGFrameAnatomy::LATERALITY frameLaterality; + fa->getLaterality(frameLaterality); + metaDataJson["FrameLaterality"] = fa->laterality2Str(frameLaterality).c_str(); + } } From 2e46ce37792f53b6b741060f1f4cb24ca5e0d054 Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Thu, 29 Jun 2017 10:39:11 -0400 Subject: [PATCH 021/116] ENH: adaption to changes in https://github.com/QIICR/dcmqi/pull/271 --- libsrc/ParametricMapObject.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 9e9112c1..7b8d4b2f 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -366,13 +366,14 @@ void ParametricMapObject::initializeMetaDataFromDICOM(T doc) { } FGDerivationImage* derivationImage = OFstatic_cast(FGDerivationImage*, fg.get(0, DcmFGTypes::EFG_DERIVATIONIMAGE)); - OFVector& derivationImageItems = derivationImage->getDerivationImageItems(); - - if(derivationImageItems.size()>0){ - DerivationImageItem* derivationImageItem = derivationImageItems[0]; - CodeSequenceMacro* derivationCode = derivationImageItem->getDerivationCodeItems()[0]; - if (derivationCode != NULL) { - metaDataJson["DerivationCode"] = Helper::codeSequence2Json(*derivationCode); + if(derivationImage) { + OFVector &derivationImageItems = derivationImage->getDerivationImageItems(); + if (derivationImageItems.size() > 0) { + DerivationImageItem *derivationImageItem = derivationImageItems[0]; + CodeSequenceMacro *derivationCode = derivationImageItem->getDerivationCodeItems()[0]; + if (derivationCode != NULL) { + metaDataJson["DerivationCode"] = Helper::codeSequence2Json(*derivationCode); + } } } From 7937ed4caf4e3c9400648c76e795ee09f6c0725b Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 30 Jun 2017 15:11:30 -0400 Subject: [PATCH 022/116] ENH: added initialization of derivation image FG --- include/dcmqi/DICOMFrame.h | 4 +++ include/dcmqi/MultiframeObject.h | 4 +++ include/dcmqi/ParametricMapObject.h | 3 +- libsrc/MultiframeObject.cpp | 48 +++++++++++++++++++++++++++++ libsrc/ParametricMapConverter.cpp | 3 ++ libsrc/ParametricMapObject.cpp | 23 ++++++++++++++ 6 files changed, 84 insertions(+), 1 deletion(-) diff --git a/include/dcmqi/DICOMFrame.h b/include/dcmqi/DICOMFrame.h index 586c6e9c..a2ef39d3 100644 --- a/include/dcmqi/DICOMFrame.h +++ b/include/dcmqi/DICOMFrame.h @@ -70,6 +70,10 @@ namespace dcmqi { return classUID; } + DcmDataset* getDataset() const { + return frameDataset; + } + private: int initializeFrameGeometryFromLegacyInstance(); diff --git a/include/dcmqi/MultiframeObject.h b/include/dcmqi/MultiframeObject.h index 0c1e4b70..bdf653bf 100644 --- a/include/dcmqi/MultiframeObject.h +++ b/include/dcmqi/MultiframeObject.h @@ -166,6 +166,10 @@ class MultiframeObject { static std::vector findIntersectingSlices(ImageVolumeGeometry& volume, dcmqi::DICOMFrame& frame); + int addDerivationItemToDerivationFG(FGDerivationImage* fgder, set frames, + CodeSequenceMacro purposeOfReferenceCode = CodeSequenceMacro("121322","DCM","Source image for image processing operation"), + CodeSequenceMacro derivationCode = CodeSequenceMacro("110001","DCM","Image Processing")); + void insertDerivationSeriesInstance(string seriesUID, string instanceUID); // constants to describe original representation of the data being converted diff --git a/include/dcmqi/ParametricMapObject.h b/include/dcmqi/ParametricMapObject.h index a82759a5..08dd7f53 100644 --- a/include/dcmqi/ParametricMapObject.h +++ b/include/dcmqi/ParametricMapObject.h @@ -63,8 +63,9 @@ class ParametricMapObject : public MultiframeObject { int initializeCompositeContext(); int initializeFrameAnatomyFG(); int initializeRWVMFG(); + int initializeFrames(vector >&); - // Functional groups initialization + // Functional groups initialization // Functional groups specific to PM: // - Shared diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index 89d5eda6..9ef57bf3 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -147,6 +147,8 @@ ContentItemMacro* MultiframeObject::initializeContentItemMacro(CodeSequenceMacro int MultiframeObject::mapVolumeSlicesToDICOMFrames(ImageVolumeGeometry& volume, const vector dcmDatasets, vector > &slice2frame){ + slice2frame.resize(volume.extent[2]); + for(int d=0;d derivationFrames, + CodeSequenceMacro purposeOfReferenceCode, + CodeSequenceMacro derivationCode){ + DerivationImageItem *derimgItem; + // TODO: check what we should do with DerivationDescription + CHECK_COND(fgder->addDerivationImageItem(derivationCode,"",derimgItem)); + + + OFVector siVector; + std::vector frameVector; + + for(set::const_iterator sIt=derivationFrames.begin(); + sIt!=derivationFrames.end();++sIt) { + + // TODO: it seems that with the current dcmtk functionality it is not possible to set the referenced + // frame number. Revisit this after discussing with Michael. + siVector.push_back((*sIt).getDataset()); + frameVector.push_back(*sIt); + } + + OFVector srcimgItems; + CHECK_COND(derimgItem->addSourceImageItems(siVector, purposeOfReferenceCode, srcimgItems)); + + // iterate over source image items (assuming they are in the same order as in siVector!), and initialize + // frame number, if applicable + unsigned siItemCnt=0; + for(OFVector::iterator vIt=srcimgItems.begin(); + vIt!=srcimgItems.end();++vIt,++siItemCnt) { + // TODO: when multuple frames from the same instance are used, they should be referenced within a single + // ImageSOPInstanceReferenceMacro. There would need to be another level of checks over all of the frames + // that are mapped to the given slice to identify those that are from the same instance, and populate the + // list of frames. I can't think of any use case where this would be immediately important, but if we ever + // use multiframe for DCE/DWI, with all of the temporal/b-value frames stored in a single object, there will + // be multiple frames used to derive a single frame in a parametric map, for example. + if(frameVector[siItemCnt].getFrameNumber()) { + OFVector frameNumbersVector; + ImageSOPInstanceReferenceMacro &instRef = (*vIt)->getImageSOPInstanceReference(); + frameNumbersVector.push_back(frameVector[siItemCnt].getFrameNumber()); + instRef.setReferencedFrameNumber(frameNumbersVector); + } + } + + return EXIT_SUCCESS; +} + std::vector MultiframeObject::findIntersectingSlices(ImageVolumeGeometry &volume, dcmqi::DICOMFrame &frame) { std::vector intersectingSlices; // for now, adopt a simple strategy that maps origin of the frame to index, and selects the slice corresponding diff --git a/libsrc/ParametricMapConverter.cpp b/libsrc/ParametricMapConverter.cpp index 1c2cc1cd..c95e4c6b 100644 --- a/libsrc/ParametricMapConverter.cpp +++ b/libsrc/ParametricMapConverter.cpp @@ -354,6 +354,9 @@ namespace dcmqi { CHECK_COND(instRef.getReferencedSOPClassUID(classUID)); CHECK_COND(instRef.getReferencedSOPInstanceUID(instanceUID)); + // AF: I am not sure why this is needed - I would expect these attributes should be initialized + // by DerivationImageItem. Perhaps this is a workaround for an issue that was fixed since then. + // Not including this in the refactored implementation. if(instanceUIDs.find(instanceUID) == instanceUIDs.end()){ SOPInstanceReferenceMacro *refinstancesItem = new SOPInstanceReferenceMacro(); CHECK_COND(refinstancesItem->setReferencedSOPClassUID(classUID)); diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 7b8d4b2f..205efb93 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -59,6 +59,8 @@ int ParametricMapObject::initializeFromITK(Float32ITKImageType::Pointer inputIma initializeCommonInstanceReferenceModule(this->parametricMap->getCommonInstanceReference(), slice2frame); + initializeFrames(slice2frame); + return EXIT_SUCCESS; } @@ -385,3 +387,24 @@ void ParametricMapObject::initializeMetaDataFromDICOM(T doc) { metaDataJson["FrameLaterality"] = fa->laterality2Str(frameLaterality).c_str(); } } + +int ParametricMapObject::initializeFrames(vector >& slice2frame){ + FGPlanePosPatient* fgppp = FGPlanePosPatient::createMinimal("1","1","1"); + FGFrameContent* fgfc = new FGFrameContent(); + FGDerivationImage* fgder = new FGDerivationImage(); + OFVector perFrameFGs; + + perFrameFGs.push_back(fgppp); + perFrameFGs.push_back(fgfc); + + unsigned nSlices = itkImage->GetLargestPossibleRegion().GetSize()[2]; + + for (unsigned long sliceNumber = 0;sliceNumber < nSlices; sliceNumber++) { + if(!slice2frame[sliceNumber].empty()){ + // TODO: read derivation code from metadata, if available, and pass instead of the default + addDerivationItemToDerivationFG(fgder, slice2frame[sliceNumber]); + } + } + + return EXIT_SUCCESS; +} From abdeb774c665b05d9c9b6a98953a2067cb698da0 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 30 Jun 2017 15:51:31 -0400 Subject: [PATCH 023/116] ENH: added initialization of the frame content --- libsrc/ParametricMapConverter.cpp | 1 + libsrc/ParametricMapObject.cpp | 69 ++++++++++++++++++++++++++++++- 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/libsrc/ParametricMapConverter.cpp b/libsrc/ParametricMapConverter.cpp index c95e4c6b..ebfbf6cb 100644 --- a/libsrc/ParametricMapConverter.cpp +++ b/libsrc/ParametricMapConverter.cpp @@ -81,6 +81,7 @@ namespace dcmqi { /* Initialize dimension module */ IODMultiframeDimensionModule &mfdim = pMapDoc->getIODMultiframeDimensionModule(); + // TODO: this is probably incorrect OFCondition result = mfdim.addDimensionIndex(DCM_ImagePositionPatient, Helper::generateUID(), DCM_RealWorldValueMappingSequence, "Frame position"); diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 205efb93..15f268cc 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -122,13 +122,39 @@ int ParametricMapObject::createParametricMap() { frameTypeFG.setFrameType(frameTypeStr.c_str()); CHECK_COND(parametricMap->addForAllFrames(frameTypeFG)); + /* Initialize dimension module */ + IODMultiframeDimensionModule &mfdim = parametricMap->getIODMultiframeDimensionModule(); + + // Initialize dimension index + OFCondition result = mfdim.addDimensionIndex(DCM_ImagePositionPatient, dcmqi::Helper::generateUID(), + DCM_PlanePositionSequence, "ImagePositionPatient"); + return EXIT_SUCCESS; } int ParametricMapObject::initializeCompositeContext() { // TODO: should this be done in the parent? if(derivationDcmDatasets.size()){ - CHECK_COND(parametricMap->import(*derivationDcmDatasets[0], OFTrue, OFTrue, OFFalse, OFTrue)); + /* Import GeneralSeriesModule - content for the reference + from include/dcmtk/dcmiod/modgeneralseries.h: + * Modality: (CS, 1, 1) + * Series Instance Number: (UI, 1, 1) + * Series Number: (IS, 1, 2) + * Laterality: (CS, 1, 2C) + * Series Date: (DA, 1, 3) + * Series Time: (TM, 1, 3) + * Performing Physician's Name: (PN, 1, 3) + * Protocol Name: (LO, 1, 3) + * Series Description: (LO, 1, 3) + * Operators' Name: (PN, 1-n, 3) + * Body Part Examined: (CS, 1, 3) + * Patient Position: (CS, 1, 2C) + */ + CHECK_COND(parametricMap->import(*derivationDcmDatasets[0], + OFTrue, // Patient + OFTrue, // Study + OFTrue, // Frame of reference + OFTrue)); // Series } else { // TODO: once we support passing of composite context in metadata, propagate it @@ -404,6 +430,47 @@ int ParametricMapObject::initializeFrames(vectorGetBufferedRegion().GetSize(); + + sliceIndex[0] = 0; + sliceIndex[1] = 0; + sliceIndex[2] = sliceNumber; + + inputSize[2] = 1; + + sliceRegion.SetIndex(sliceIndex); + sliceRegion.SetSize(inputSize); + + const unsigned frameSize = inputSize[0] * inputSize[1]; + + OFVector data(frameSize); + + itk::ImageRegionConstIteratorWithIndex sliceIterator(itkImage, sliceRegion); + + unsigned framePixelCnt = 0; + for(sliceIterator.GoToBegin();!sliceIterator.IsAtEnd(); ++sliceIterator, ++framePixelCnt){ + data[framePixelCnt] = sliceIterator.Get(); + Float32ITKImageType::IndexType idx = sliceIterator.GetIndex(); + // cout << framePixelCnt << " " << idx[1] << "," << idx[0] << endl; + } + + // Plane Position + Float32ITKImageType::PointType sliceOriginPoint; + itkImage->TransformIndexToPhysicalPoint(sliceIndex, sliceOriginPoint); + fgppp->setImagePositionPatient( + dcmqi::Helper::floatToStrScientific(sliceOriginPoint[0]).c_str(), + dcmqi::Helper::floatToStrScientific(sliceOriginPoint[1]).c_str(), + dcmqi::Helper::floatToStrScientific(sliceOriginPoint[2]).c_str()); + + // Frame Content + OFCondition result = fgfc->setDimensionIndexValues(sliceNumber+1 /* value within dimension */, 0 /* first dimension */); + + DPMParametricMapIOD::FramesType frames = parametricMap->getFrames(); + result = OFget >(&frames)->addFrame(&*data.begin(), frameSize, perFrameFGs); + } return EXIT_SUCCESS; From 65eeb7eec67b8e77c2c0d148a1a89669d155824c Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 30 Jun 2017 16:24:54 -0400 Subject: [PATCH 024/116] ENH: fix initialization of the composite context itkimage2paramap tests are passing! --- include/dcmqi/MultiframeObject.h | 7 +++++++ libsrc/MultiframeObject.cpp | 3 ++- libsrc/ParametricMapObject.cpp | 12 ++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/include/dcmqi/MultiframeObject.h b/include/dcmqi/MultiframeObject.h index bdf653bf..97a9d345 100644 --- a/include/dcmqi/MultiframeObject.h +++ b/include/dcmqi/MultiframeObject.h @@ -172,6 +172,13 @@ class MultiframeObject { void insertDerivationSeriesInstance(string seriesUID, string instanceUID); + int setDerivationDatasets(std::vector derivationDatasets){ + for(std::vector::const_iterator vIt=derivationDatasets.begin(); + vIt!=derivationDatasets.end();++vIt) + derivationDcmDatasets.push_back(*vIt); + return EXIT_SUCCESS; + } + // constants to describe original representation of the data being converted enum { DICOM_REPR = 0, diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index 9ef57bf3..de844afa 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -488,4 +488,5 @@ int MultiframeObject::getDeclaredImageSpacing(FGInterface &fgInterface, SpacingT spacing[2] = spacingFloat; } return EXIT_SUCCESS; -} \ No newline at end of file +} + diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 15f268cc..d54350d6 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -12,6 +12,9 @@ int ParametricMapObject::initializeFromITK(Float32ITKImageType::Pointer inputImage, const string &metaDataStr, std::vector derivationDatasets) { + + setDerivationDatasets(derivationDatasets); + sourceRepresentationType = ITK_REPR; itkImage = inputImage; @@ -156,6 +159,15 @@ int ParametricMapObject::initializeCompositeContext() { OFTrue, // Frame of reference OFTrue)); // Series + { + // DEBUG -> + OFString forUID; + CHECK_COND(parametricMap->getFrameOfReference().getFrameOfReferenceUID(forUID)); + std::cout << "FoR UID:" << forUID << std::endl; + + // <- DEBUG + } + } else { // TODO: once we support passing of composite context in metadata, propagate it // into parametricMap here From 6baea74b4681e62a853eae474b68c763cfb73359 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 30 Jun 2017 17:02:22 -0400 Subject: [PATCH 025/116] ENH: add RWVM FG crash in addForAllFrames()!... --- libsrc/ParametricMapObject.cpp | 33 +++++++++++---------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index d54350d6..9b5c50b3 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -127,8 +127,6 @@ int ParametricMapObject::createParametricMap() { /* Initialize dimension module */ IODMultiframeDimensionModule &mfdim = parametricMap->getIODMultiframeDimensionModule(); - - // Initialize dimension index OFCondition result = mfdim.addDimensionIndex(DCM_ImagePositionPatient, dcmqi::Helper::generateUID(), DCM_PlanePositionSequence, "ImagePositionPatient"); @@ -159,15 +157,6 @@ int ParametricMapObject::initializeCompositeContext() { OFTrue, // Frame of reference OFTrue)); // Series - { - // DEBUG -> - OFString forUID; - CHECK_COND(parametricMap->getFrameOfReference().getFrameOfReferenceUID(forUID)); - std::cout << "FoR UID:" << forUID << std::endl; - - // <- DEBUG - } - } else { // TODO: once we support passing of composite context in metadata, propagate it // into parametricMap here @@ -199,9 +188,6 @@ int ParametricMapObject::initializeRWVMFG() { FGRealWorldValueMapping::RWVMItem* realWorldValueMappingItem = new FGRealWorldValueMapping::RWVMItem(); - if (!realWorldValueMappingItem ) - return EXIT_FAILURE; - realWorldValueMappingItem->setRealWorldValueSlope(metaDataJson["RealWorldValueSlope"].asFloat()); realWorldValueMappingItem->setRealWorldValueIntercept(0); @@ -225,6 +211,7 @@ int ParametricMapObject::initializeRWVMFG() { realWorldValueMappingItem->setLUTLabel(metaDataJson["MeasurementUnitsCode"]["CodeValue"].asCString()); } + if(metaDataJson.isMember("QuantityValueCode")){ ContentItemMacro* item = initializeContentItemMacro(CodeSequenceMacro("G-C1C6", "SRT", "Quantity"), dcmqi::Helper::jsonToCodeSequenceMacro(metaDataJson["QuantityValueCode"])); @@ -245,27 +232,29 @@ int ParametricMapObject::initializeRWVMFG() { realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(item); } - if(metaDataJson.isMember("SourceImageDiffusionBValues")){ - for(int bvalId=0;bvalIdsetValueType(ContentItemMacro::VT_NUMERIC); bval->getEntireConceptNameCodeSequence().push_back(qCodeName); bval->getEntireMeasurementUnitsCodeSequence().push_back(bvalUnits); - if(bval->setNumericValue(metaDataJson["SourceImageDiffusionBValues"][bvalId].asCString()).bad()) + if (bval->setNumericValue(metaDataJson["SourceImageDiffusionBValues"][bvalId].asCString()).bad()) cout << "Failed to insert the value!" << endl;; realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(bval); } } + rwvmFG.getRealWorldValueMapping().push_back(realWorldValueMappingItem); + parametricMap->addForAllFrames(rwvmFG); + return EXIT_SUCCESS; } From 1796e8054409556a2490dcddf59b5cfaad50049a Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 30 Jun 2017 18:09:17 -0400 Subject: [PATCH 026/116] ENH: fix RWVM FG segfault Also: * fix the typo in JSON lookup * replace the temporary codes with the standard ones: CP-1665 became standard in 2017a --- libsrc/ParametricMapObject.cpp | 72 ++++++++++++++++++++++++++++++---- 1 file changed, 65 insertions(+), 7 deletions(-) diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 9b5c50b3..eb7ef19a 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -199,11 +199,11 @@ int ParametricMapObject::initializeRWVMFG() { realWorldValueMappingItem->setRealWorldValueFirstValueMappedSigned(calculator->GetMinimum()); realWorldValueMappingItem->setRealWorldValueLastValueMappedSigned(calculator->GetMaximum()); - if(metaDataJson.isMember("MeasurementsUnitsCode")){ + if(metaDataJson.isMember("MeasurementUnitsCode")){ CodeSequenceMacro& unitsCodeDcmtk = realWorldValueMappingItem->getMeasurementUnitsCode(); - unitsCodeDcmtk.set(metaDataJson["MeasurementsUnitsCode"]["CodeValue"].asCString(), - metaDataJson["MeasurementsUnitsCode"]["CodingSchemeDesignator"].asCString(), - metaDataJson["MeasurementsUnitsCode"]["CodeMeaning"].asCString()); + unitsCodeDcmtk.set(metaDataJson["MeasurementUnitsCode"]["CodeValue"].asCString(), + metaDataJson["MeasurementUnitsCode"]["CodingSchemeDesignator"].asCString(), + metaDataJson["MeasurementUnitsCode"]["CodeMeaning"].asCString()); cout << "Measurements units initialized to " << dcmqi::Helper::codeSequenceMacroToString(unitsCodeDcmtk); @@ -211,33 +211,91 @@ int ParametricMapObject::initializeRWVMFG() { realWorldValueMappingItem->setLUTLabel(metaDataJson["MeasurementUnitsCode"]["CodeValue"].asCString()); } - + /* if(metaDataJson.isMember("QuantityValueCode")){ ContentItemMacro* item = initializeContentItemMacro(CodeSequenceMacro("G-C1C6", "SRT", "Quantity"), dcmqi::Helper::jsonToCodeSequenceMacro(metaDataJson["QuantityValueCode"])); realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(item); + }*/ + + ContentItemMacro* quantity = new ContentItemMacro; + CodeSequenceMacro* qCodeName = new CodeSequenceMacro("G-C1C6", "SRT", "Quantity"); + CodeSequenceMacro* qSpec = new CodeSequenceMacro( + metaDataJson["QuantityValueCode"]["CodeValue"].asCString(), + metaDataJson["QuantityValueCode"]["CodingSchemeDesignator"].asCString(), + metaDataJson["QuantityValueCode"]["CodeMeaning"].asCString()); + + if (!quantity || !qSpec || !qCodeName) + { + return EXIT_FAILURE; } + quantity->getEntireConceptNameCodeSequence().push_back(qCodeName); + quantity->getEntireConceptCodeSequence().push_back(qSpec); + realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(quantity); + quantity->setValueType(ContentItemMacro::VT_CODE); + + /* // TODO: factor out defined CodeSequenceMacros into definitions as in dcmsr/include/dcmtk/dcmsr/codes if(metaDataJson.isMember("MeasurementMethodCode")){ ContentItemMacro* item = initializeContentItemMacro(CodeSequenceMacro("G-C306", "SRT", "Measurement Method"), dcmqi::Helper::jsonToCodeSequenceMacro(metaDataJson["MeasurementMethodCode"])); realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(item); + } */ + + if(metaDataJson.isMember("MeasurementMethodCode")){ + ContentItemMacro* measureMethod = new ContentItemMacro; + CodeSequenceMacro* qCodeName = new CodeSequenceMacro("G-C306", "SRT", "Measurement Method"); + CodeSequenceMacro* qSpec = new CodeSequenceMacro( + metaDataJson["MeasurementMethodCode"]["CodeValue"].asCString(), + metaDataJson["MeasurementMethodCode"]["CodingSchemeDesignator"].asCString(), + metaDataJson["MeasurementMethodCode"]["CodeMeaning"].asCString()); + + if (!measureMethod || !qSpec || !qCodeName) + { + return EXIT_FAILURE; + } + + measureMethod->getEntireConceptNameCodeSequence().push_back(qCodeName); + measureMethod->getEntireConceptCodeSequence().push_back(qSpec); + realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(measureMethod); + measureMethod->setValueType(ContentItemMacro::VT_CODE); } + /* if(metaDataJson.isMember("ModelFittingMethodCode")){ // TODO: update this once CP-1665 is integrated into the standard - ContentItemMacro* item = initializeContentItemMacro(CodeSequenceMacro("xxxxx2", "99DCMCP1665", "Model Fitting Method"), + ContentItemMacro* item = initializeContentItemMacro(CodeSequenceMacro("113241", "DCM", "Model Fitting Method"), dcmqi::Helper::jsonToCodeSequenceMacro(metaDataJson["ModelFittingMethodCode"])); realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(item); + } */ + + if(metaDataJson.isMember("ModelFittingMethodCode")){ + ContentItemMacro* fittingMethod = new ContentItemMacro; + CodeSequenceMacro* qCodeName = new CodeSequenceMacro("113241", "DCM", "Model fitting method"); + CodeSequenceMacro* qSpec = new CodeSequenceMacro( + metaDataJson["ModelFittingMethodCode"]["CodeValue"].asCString(), + metaDataJson["ModelFittingMethodCode"]["CodingSchemeDesignator"].asCString(), + metaDataJson["ModelFittingMethodCode"]["CodeMeaning"].asCString()); + + if (!fittingMethod || !qSpec || !qCodeName) + { + return NULL; + } + + fittingMethod->getEntireConceptNameCodeSequence().push_back(qCodeName); + fittingMethod->getEntireConceptCodeSequence().push_back(qSpec); + realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(fittingMethod); + fittingMethod->setValueType(ContentItemMacro::VT_CODE); } + if(metaDataJson.isMember("SourceImageDiffusionBValues")) { for (int bvalId = 0; bvalId < metaDataJson["SourceImageDiffusionBValues"].size(); bvalId++) { ContentItemMacro *bval = new ContentItemMacro; CodeSequenceMacro *bvalUnits = new CodeSequenceMacro("s/mm2", "UCUM", "s/mm2"); // TODO: update this once CP-1665 is integrated into the standard - CodeSequenceMacro *qCodeName = new CodeSequenceMacro("xxxxx1", "99DCMCP1665", "Source image diffusion b-value"); + CodeSequenceMacro *qCodeName = new CodeSequenceMacro("113240", "DCM", "Source image diffusion b-value"); if (!bval || !bvalUnits || !qCodeName) { return EXIT_FAILURE; From 5a1a72130630ec536425456a6d1660b3cc3b7aa3 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 30 Jun 2017 18:13:05 -0400 Subject: [PATCH 027/116] ENH: replace temporary codes with standard ones CP-1665 became standard in 2017a --- libsrc/ParametricMapConverter.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libsrc/ParametricMapConverter.cpp b/libsrc/ParametricMapConverter.cpp index ebfbf6cb..c7f22f52 100644 --- a/libsrc/ParametricMapConverter.cpp +++ b/libsrc/ParametricMapConverter.cpp @@ -223,7 +223,7 @@ namespace dcmqi { if(metaInfo.metaInfoRoot.isMember("ModelFittingMethodCode")){ ContentItemMacro* fittingMethod = new ContentItemMacro; - CodeSequenceMacro* qCodeName = new CodeSequenceMacro("DWMPxxxxx2", "99QIICR", "Model fitting method"); + CodeSequenceMacro* qCodeName = new CodeSequenceMacro("113241", "DCM", "Model fitting method"); CodeSequenceMacro* qSpec = new CodeSequenceMacro( metaInfo.metaInfoRoot["ModelFittingMethodCode"]["CodeValue"].asCString(), metaInfo.metaInfoRoot["ModelFittingMethodCode"]["CodingSchemeDesignator"].asCString(), @@ -244,7 +244,7 @@ namespace dcmqi { for(int bvalId=0;bvalId Date: Fri, 30 Jun 2017 18:27:05 -0400 Subject: [PATCH 028/116] ENH: fix initialization of PM dciodvfy is happy! --- libsrc/ParametricMapObject.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index eb7ef19a..f809db9b 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -46,7 +46,10 @@ int ParametricMapObject::initializeFromITK(Float32ITKImageType::Pointer inputIma initializeDimensions(dimensionTags); initializePixelMeasuresFG(); + CHECK_COND(parametricMap->addForAllFrames(pixelMeasuresFG)); + initializePlaneOrientationFG(); + CHECK_COND(parametricMap->addForAllFrames(planeOrientationPatientFG)); // PM-specific FGs initializeFrameAnatomyFG(); @@ -181,6 +184,8 @@ int ParametricMapObject::initializeFrameAnatomyFG() { frameAnatomyFG.getAnatomy().getAnatomicRegion().set("T-D0050", "SRT", "Tissue"); } + CHECK_COND(parametricMap->addForAllFrames(frameAnatomyFG)); + return EXIT_SUCCESS; } From f3c29d50debda6c32f9e6c0d78aa429a0c0ec6e0 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 30 Jun 2017 18:28:38 -0400 Subject: [PATCH 029/116] BUG: fix initialization of the ITK PM output geometry rows/columns were flipped on initialization --- libsrc/ParametricMapObject.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index f809db9b..fcf8f01c 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -104,8 +104,8 @@ int ParametricMapObject::createParametricMap() { DPMParametricMapIOD::create(metaDataJson["Modality"].asCString(), metaDataJson["SeriesNumber"].asCString(), metaDataJson["InstanceNumber"].asCString(), - volumeGeometry.extent[0], volumeGeometry.extent[1], + volumeGeometry.extent[0], equipmentInfoModule, contentIdentificationMacro, "VOLUME", "QUANTITY", From 634e88c520da91c6cdde8cae9e2f0ad7b52cabfc Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 30 Jun 2017 18:30:31 -0400 Subject: [PATCH 030/116] ENH: added reminder comment to revisit modality initialization --- libsrc/ParametricMapObject.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index fcf8f01c..d698aeae 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -100,6 +100,8 @@ int ParametricMapObject::createParametricMap() { // create Parametric map object + // TODO: revisit intialization of the modality - if source images are available, modality should match + // that in the source images! OFvariant obj = DPMParametricMapIOD::create(metaDataJson["Modality"].asCString(), metaDataJson["SeriesNumber"].asCString(), From 685a4a8b6c5768475e7e9224cf5dc34d687c032d Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Sat, 11 Mar 2017 12:24:19 -0500 Subject: [PATCH 031/116] BUG: initialize modality from the source dataset --- libsrc/ParametricMapConverter.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libsrc/ParametricMapConverter.cpp b/libsrc/ParametricMapConverter.cpp index c7f22f52..af22c60f 100644 --- a/libsrc/ParametricMapConverter.cpp +++ b/libsrc/ParametricMapConverter.cpp @@ -37,8 +37,6 @@ namespace dcmqi { ss.str()); }; - - DcmDataset* ParametricMapConverter::itkimage2paramap(const FloatImageType::Pointer ¶metricMapImage, vector dcmDatasets, const string &metaData) { @@ -720,7 +718,7 @@ namespace dcmqi { } FGDerivationImage* derivationImage = OFstatic_cast(FGDerivationImage*, fg.get(0, DcmFGTypes::EFG_DERIVATIONIMAGE)); - + if(derivationImage){ OFVector& derivationImageItems = derivationImage->getDerivationImageItems(); if(derivationImageItems.size()>0){ From 6797a007dbe6f7badd8f0630765ee4f3f57f5a4d Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Tue, 27 Jun 2017 21:07:40 -0400 Subject: [PATCH 032/116] ENH: adding refactored code for paramap from DCM to itk --- include/dcmqi/ParametricMapObject.h | 2 + libsrc/CMakeLists.txt | 194 ++++++++++++++-------------- 2 files changed, 99 insertions(+), 97 deletions(-) diff --git a/include/dcmqi/ParametricMapObject.h b/include/dcmqi/ParametricMapObject.h index 08dd7f53..bbda87a7 100644 --- a/include/dcmqi/ParametricMapObject.h +++ b/include/dcmqi/ParametricMapObject.h @@ -77,6 +77,8 @@ class ParametricMapObject : public MultiframeObject { // Data containers specific to this object Float32ITKImageType::Pointer itkImage; + + private: DPMParametricMapIOD* parametricMap; }; diff --git a/libsrc/CMakeLists.txt b/libsrc/CMakeLists.txt index b354c700..17d842be 100644 --- a/libsrc/CMakeLists.txt +++ b/libsrc/CMakeLists.txt @@ -1,98 +1,98 @@ - -#----------------------------------------------------------------------------- - -set(INCLUDE_DIR ../include/dcmqi) - -set(HDRS - ${INCLUDE_DIR}/DICOMFrame.h - ${INCLUDE_DIR}/ImageVolume.h - ${INCLUDE_DIR}/MultiframeConverter.h - ${INCLUDE_DIR}/Helper.h - ${INCLUDE_DIR}/SegmentationImageConverter.h - ${INCLUDE_DIR}/ParametricMapConverter.h - ${INCLUDE_DIR}/JSONMetaInformationHandlerBase.h - ${INCLUDE_DIR}/JSONParametricMapMetaInformationHandler.h - ${INCLUDE_DIR}/JSONSegmentationMetaInformationHandler.h - ${INCLUDE_DIR}/SegmentAttributes.h - ) - -set(SRCS - DICOMFrame.cpp - ImageVolume.cpp - MultiframeConverter.cpp - Helper.cpp - SegmentationImageConverter.cpp - ParametricMapConverter.cpp - JSONMetaInformationHandlerBase.cpp - JSONParametricMapMetaInformationHandler.cpp - JSONSegmentationMetaInformationHandler.cpp - SegmentAttributes.cpp - ) - -set(ADDITIONAL_HDRS - ${INCLUDE_DIR}/DICOMFrame.h - ${INCLUDE_DIR}/ImageVolume.h - ${INCLUDE_DIR}/SegmentVolume.h - ${INCLUDE_DIR}/MultiframeObject.h - ${INCLUDE_DIR}/ParametricMapObject.h - ${INCLUDE_DIR}/SegmentationImageObject.h - ${INCLUDE_DIR}/ImageVolumeGeometry.h - ${INCLUDE_DIR}/Exceptions.h - ${INCLUDE_DIR}/framesorter.h - ${INCLUDE_DIR}/preproc.h - ${INCLUDE_DIR}/QIICRConstants.h - ${INCLUDE_DIR}/QIICRUIDs.h - ) - -set(ADDITIONAL_SRCS - DICOMFrame.cpp - ImageVolume.cpp - SegmentVolume.cpp - MultiframeObject.cpp - ParametricMapObject.cpp - SegmentationImageObject.cpp - ImageVolumeGeometry.cpp - ) - -if(DCMQI_BUILTIN_JSONCPP) - list(APPEND ADDITIONAL_SRCS - ${DCMQI_SOURCE_DIR}/jsoncpp/jsoncpp.cpp - ) - set(JsonCpp_INCLUDE_DIR ${DCMQI_SOURCE_DIR}/jsoncpp) -endif() - -set(lib_name dcmqi) - -add_library(${lib_name} STATIC - ${HDRS} - ${SRCS} - ${ADDITIONAL_HDRS} - ${ADDITIONAL_SRCS} - ) - -if(DCMQI_LIBRARY_PROPERTIES) - set_target_properties(${lib_name} PROPERTIES ${DCMQI_LIBRARY_PROPERTIES}) -endif() - -set_property(GLOBAL APPEND PROPERTY ${CMAKE_PROJECT_NAME}_TARGETS ${lib_name}) - -set(${lib_name}_INCLUDE_DIRS - ${DCMTK_INCLUDE_DIRS} - ${ITK_INCLUDE_DIRS} - ${DCMQI_SOURCE_DIR}/include - ${DCMQI_BINARY_DIR}/include - ${JsonCpp_INCLUDE_DIR} - ) -if(ITK_INSTALL_PREFIX) - list(APPEND ${lib_name}_INCLUDE_DIRS - ${ITK_INSTALL_PREFIX}/include/vxl/core - ${ITK_INSTALL_PREFIX}/include/vxl/vcl - ) -endif() -target_include_directories(${lib_name} PUBLIC ${${lib_name}_INCLUDE_DIRS}) - -target_link_libraries(${lib_name} PUBLIC - ${DCMTK_LIBRARIES} - ${ITK_LIBRARIES} - $<$>:${JsonCpp_LIBRARY}> + +#----------------------------------------------------------------------------- + +set(INCLUDE_DIR ../include/dcmqi) + +set(HDRS + ${INCLUDE_DIR}/DICOMFrame.h + ${INCLUDE_DIR}/ImageVolume.h + ${INCLUDE_DIR}/MultiframeConverter.h + ${INCLUDE_DIR}/Helper.h + ${INCLUDE_DIR}/SegmentationImageConverter.h + ${INCLUDE_DIR}/ParametricMapConverter.h + ${INCLUDE_DIR}/JSONMetaInformationHandlerBase.h + ${INCLUDE_DIR}/JSONParametricMapMetaInformationHandler.h + ${INCLUDE_DIR}/JSONSegmentationMetaInformationHandler.h + ${INCLUDE_DIR}/SegmentAttributes.h + ) + +set(SRCS + DICOMFrame.cpp + ImageVolume.cpp + MultiframeConverter.cpp + Helper.cpp + SegmentationImageConverter.cpp + ParametricMapConverter.cpp + JSONMetaInformationHandlerBase.cpp + JSONParametricMapMetaInformationHandler.cpp + JSONSegmentationMetaInformationHandler.cpp + SegmentAttributes.cpp + ) + +set(ADDITIONAL_HDRS + ${INCLUDE_DIR}/DICOMFrame.h + ${INCLUDE_DIR}/ImageVolume.h + ${INCLUDE_DIR}/SegmentVolume.h + ${INCLUDE_DIR}/MultiframeObject.h + ${INCLUDE_DIR}/ParametricMapObject.h + ${INCLUDE_DIR}/SegmentationImageObject.h + ${INCLUDE_DIR}/ImageVolumeGeometry.h + ${INCLUDE_DIR}/Exceptions.h + ${INCLUDE_DIR}/framesorter.h + ${INCLUDE_DIR}/preproc.h + ${INCLUDE_DIR}/QIICRConstants.h + ${INCLUDE_DIR}/QIICRUIDs.h + ) + +set(ADDITIONAL_SRCS + DICOMFrame.cpp + ImageVolume.cpp + SegmentVolume.cpp + MultiframeObject.cpp + ParametricMapObject.cpp + SegmentationImageObject.cpp + ImageVolumeGeometry.cpp + ) + +if(DCMQI_BUILTIN_JSONCPP) + list(APPEND ADDITIONAL_SRCS + ${DCMQI_SOURCE_DIR}/jsoncpp/jsoncpp.cpp + ) + set(JsonCpp_INCLUDE_DIR ${DCMQI_SOURCE_DIR}/jsoncpp) +endif() + +set(lib_name dcmqi) + +add_library(${lib_name} STATIC + ${HDRS} + ${SRCS} + ${ADDITIONAL_HDRS} + ${ADDITIONAL_SRCS} + ) + +if(DCMQI_LIBRARY_PROPERTIES) + set_target_properties(${lib_name} PROPERTIES ${DCMQI_LIBRARY_PROPERTIES}) +endif() + +set_property(GLOBAL APPEND PROPERTY ${CMAKE_PROJECT_NAME}_TARGETS ${lib_name}) + +set(${lib_name}_INCLUDE_DIRS + ${DCMTK_INCLUDE_DIRS} + ${ITK_INCLUDE_DIRS} + ${DCMQI_SOURCE_DIR}/include + ${DCMQI_BINARY_DIR}/include + ${JsonCpp_INCLUDE_DIR} + ) +if(ITK_INSTALL_PREFIX) + list(APPEND ${lib_name}_INCLUDE_DIRS + ${ITK_INSTALL_PREFIX}/include/vxl/core + ${ITK_INSTALL_PREFIX}/include/vxl/vcl + ) +endif() +target_include_directories(${lib_name} PUBLIC ${${lib_name}_INCLUDE_DIRS}) + +target_link_libraries(${lib_name} PUBLIC + ${DCMTK_LIBRARIES} + ${ITK_LIBRARIES} + $<$>:${JsonCpp_LIBRARY}> ) \ No newline at end of file From 97317bc1246afa55d11600d3d3b5c7aea15d683a Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Thu, 29 Jun 2017 14:23:40 -0400 Subject: [PATCH 033/116] ENH: added first step for segmentation dcm to itk --- include/dcmqi/ImageVolumeGeometry.h | 3 +- include/dcmqi/SegmentationImageObject.h | 45 ++++- libsrc/ParametricMapObject.cpp | 10 +- libsrc/SegmentationImageObject.cpp | 258 ++++++++++++++++++++++++ 4 files changed, 309 insertions(+), 7 deletions(-) diff --git a/include/dcmqi/ImageVolumeGeometry.h b/include/dcmqi/ImageVolumeGeometry.h index 92d2a507..0b1b3da5 100644 --- a/include/dcmqi/ImageVolumeGeometry.h +++ b/include/dcmqi/ImageVolumeGeometry.h @@ -16,8 +16,9 @@ class ImageVolumeGeometry { friend class MultiframeObject; friend class ParametricMapObject; + friend class SegmentationImageObject; -public: + public: typedef itk::Vector DoubleVectorType; typedef itk::Size<3> SizeType; diff --git a/include/dcmqi/SegmentationImageObject.h b/include/dcmqi/SegmentationImageObject.h index 74b7e8d8..326a81e9 100644 --- a/include/dcmqi/SegmentationImageObject.h +++ b/include/dcmqi/SegmentationImageObject.h @@ -5,11 +5,54 @@ #ifndef DCMQI_SEGMENTATIONIMAGEOBJECT_H #define DCMQI_SEGMENTATIONIMAGEOBJECT_H - +// DCMQI includes #include "MultiframeObject.h" +#include "Helper.h" +#include "SegmentAttributes.h" + +// DCMTK includes +#include +#include +#include +#include +#include +#include + +// ITK includes +#include + class SegmentationImageObject : public MultiframeObject { +public: + + typedef short ShortPixelType; + typedef itk::Image ShortImageType; + + SegmentationImageObject(){ + segmentation = NULL; + } + + int initializeFromDICOM(DcmDataset* sourceDataset); + + int initializeMetaDataFromDICOM(DcmDataset*); + +protected: + // Data containers specific to this object + ShortImageType::Pointer itkImage; + + // ITK images corresponding to the individual segments + map segment2image; + + dcmqi::SegmentAttributes* createAndGetNewSegment(unsigned labelID); + + // vector contains one item per input itkImageData label + // each item is a map from labelID to segment attributes + vector > segmentsAttributesMappingList; + + private: + DcmSegmentation* segmentation; + }; diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index d698aeae..1296ff02 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -336,9 +336,9 @@ int ParametricMapObject::initializeFromDICOM(DcmDataset * sourceDataset) { throw -1; } - DPMParametricMapIOD* pMapDoc = *OFget(&result); + parametricMap = *OFget(&result); - initializeVolumeGeometryFromDICOM(pMapDoc, sourceDataset); + initializeVolumeGeometryFromDICOM(parametricMap, sourceDataset); // Initialize the image itkImage = volumeGeometry.getITKRepresentation(); @@ -346,14 +346,14 @@ int ParametricMapObject::initializeFromDICOM(DcmDataset * sourceDataset) { itkImage->Allocate(); itkImage->FillBuffer(0); - DPMParametricMapIOD::FramesType obj = pMapDoc->getFrames(); + DPMParametricMapIOD::FramesType obj = parametricMap->getFrames(); if (OFCondition* pCondition = OFget(&obj)) { throw -1; } DPMParametricMapIOD::Frames frames = *OFget >(&obj); - FGInterface &fgInterface = pMapDoc->getFunctionalGroups(); + FGInterface &fgInterface = parametricMap->getFunctionalGroups(); for(int frameId=0;frameId(); + + itkImage->Allocate(); + itkImage->FillBuffer(0); + + // Iterate over frames, find the matching slice for each of the frames based on + // ImagePositionPatient, set non-zero pixels to the segment number. Notify + // about pixels that are initialized more than once. + + FGInterface &fgInterface = segmentation->getFunctionalGroups(); + + DcmIODTypes::Frame *unpackedFrame = NULL; + + for(size_t frameId=0;frameIdgetFrame(frameId); + bool isPerFrame; + + FGPlanePosPatient *planposfg = + OFstatic_cast(FGPlanePosPatient*,fgInterface.get(frameId, DcmFGTypes::EFG_PLANEPOSPATIENT, isPerFrame)); + assert(planposfg); + +#ifndef NDEBUG + FGFrameContent *fracon = + OFstatic_cast(FGFrameContent*,fgInterface.get(frameId, DcmFGTypes::EFG_FRAMECONTENT, isPerFrame)); + assert(fracon); +#endif + + FGSegmentation *fgseg = + OFstatic_cast(FGSegmentation*,fgInterface.get(frameId, DcmFGTypes::EFG_SEGMENTATION, isPerFrame)); + assert(fgseg); + + Uint16 segmentId = -1; + if(fgseg->getReferencedSegmentNumber(segmentId).bad()){ + cerr << "Failed to get seg number!"; + throw -1; + } + + // WARNING: this is needed only for David's example, which numbers + // (incorrectly!) segments starting from 0, should start from 1 + if(segmentId == 0){ + cerr << "Segment numbers should start from 1!" << endl; + throw -1; + } + + if(segment2image.find(segmentId) == segment2image.end()){ + typedef itk::ImageDuplicator DuplicatorType; + DuplicatorType::Pointer dup = DuplicatorType::New(); + dup->SetInputImage(itkImage); + dup->Update(); + ShortImageType::Pointer newSegmentImage = dup->GetOutput(); + newSegmentImage->FillBuffer(0); + segment2image[segmentId] = newSegmentImage; + } + + // populate meta information needed for Slicer ScalarVolumeNode initialization + // (for example) + { + // NOTE: according to the standard, segment numbering should start from 1, + // not clear if this is intentional behavior or a bug in DCMTK expecting + // it to start from 0 + + DcmSegment *segment = segmentation->getSegment(segmentId); + if (segment == NULL) { + cerr << "Failed to get segment for segment ID " << segmentId << endl; + continue; + } + + // get CIELab color for the segment + Uint16 ciedcm[3]; + unsigned cielabScaled[3]; + float cielab[3], ciexyz[3]; + unsigned rgb[3]; + if (segment->getRecommendedDisplayCIELabValue( + ciedcm[0], ciedcm[1], ciedcm[2] + ).bad()) { + // NOTE: if the call above fails, it overwrites the values anyway, + // not sure if this is a dcmtk bug or not + ciedcm[0] = 43803; + ciedcm[1] = 26565; + ciedcm[2] = 37722; + cerr << "Failed to get CIELab values - initializing to default " << + ciedcm[0] << "," << ciedcm[1] << "," << ciedcm[2] << endl; + } + cielabScaled[0] = unsigned(ciedcm[0]); + cielabScaled[1] = unsigned(ciedcm[1]); + cielabScaled[2] = unsigned(ciedcm[2]); + + Helper::getCIELabFromIntegerScaledCIELab(&cielabScaled[0], &cielab[0]); + Helper::getCIEXYZFromCIELab(&cielab[0], &ciexyz[0]); + Helper::getRGBFromCIEXYZ(&ciexyz[0], &rgb[0]); + + // TODO: factor out SegmentAttributes + SegmentAttributes *segmentAttributes = createAndGetNewSegment(segmentId); + + if (segmentAttributes) { + segmentAttributes->setLabelID(segmentId); + DcmSegTypes::E_SegmentAlgoType algorithmType = segment->getSegmentAlgorithmType(); + string readableAlgorithmType = DcmSegTypes::algoType2OFString(algorithmType).c_str(); + segmentAttributes->setSegmentAlgorithmType(readableAlgorithmType); + + if (algorithmType == DcmSegTypes::SAT_UNKNOWN) { + cerr << "AlgorithmType is not valid with value " << readableAlgorithmType << endl; + throw -1; + } + if (algorithmType != DcmSegTypes::SAT_MANUAL) { + OFString segmentAlgorithmName; + segment->getSegmentAlgorithmName(segmentAlgorithmName); + if (segmentAlgorithmName.length() > 0) + segmentAttributes->setSegmentAlgorithmName(segmentAlgorithmName.c_str()); + } + + OFString segmentDescription; + segment->getSegmentDescription(segmentDescription); + segmentAttributes->setSegmentDescription(segmentDescription.c_str()); + + segmentAttributes->setRecommendedDisplayRGBValue(rgb[0], rgb[1], rgb[2]); + segmentAttributes->setSegmentedPropertyCategoryCodeSequence(segment->getSegmentedPropertyCategoryCode()); + segmentAttributes->setSegmentedPropertyTypeCodeSequence(segment->getSegmentedPropertyTypeCode()); + + if (segment->getSegmentedPropertyTypeModifierCode().size() > 0) { + segmentAttributes->setSegmentedPropertyTypeModifierCodeSequence( + segment->getSegmentedPropertyTypeModifierCode()[0]); + } + + GeneralAnatomyMacro &anatomyMacro = segment->getGeneralAnatomyCode(); + CodeSequenceMacro &anatomicRegionSequence = anatomyMacro.getAnatomicRegion(); + if (anatomicRegionSequence.check(true).good()) { + segmentAttributes->setAnatomicRegionSequence(anatomyMacro.getAnatomicRegion()); + } + if (anatomyMacro.getAnatomicRegionModifier().size() > 0) { + segmentAttributes->setAnatomicRegionModifierSequence(anatomyMacro.getAnatomicRegionModifier()[0]); + } + } + } + + // get string representation of the frame origin + ShortImageType::PointType frameOriginPoint; + ShortImageType::IndexType frameOriginIndex; + for(int j=0;j<3;j++){ + OFString planposStr; + if(planposfg->getImagePositionPatient(planposStr, j).good()){ + frameOriginPoint[j] = atof(planposStr.c_str()); + } + } + + if(!segment2image[segmentId]->TransformPhysicalPointToIndex(frameOriginPoint, frameOriginIndex)){ + cerr << "ERROR: Frame " << frameId << " origin " << frameOriginPoint << + " is outside image geometry!" << frameOriginIndex << endl; + cerr << "Image size: " << segment2image[segmentId]->GetBufferedRegion().GetSize() << endl; + throw -1; + } + + unsigned slice = frameOriginIndex[2]; + + if(segmentation->getSegmentationType() == DcmSegTypes::ST_BINARY) + unpackedFrame = DcmSegUtils::unpackBinaryFrame(frame, + volumeGeometry.extent[1], // Rows + volumeGeometry.extent[0]); // Cols + else + unpackedFrame = new DcmIODTypes::Frame(*frame); + + // initialize slice with the frame content + for(unsigned row=0;rowpixData[bitCnt]; + + if(pixel!=0){ + ShortImageType::IndexType index; + index[0] = col; + index[1] = row; + index[2] = slice; + segment2image[segmentId]->SetPixel(index, segmentId); + } + } + } + + if(unpackedFrame != NULL) + delete unpackedFrame; + } + + initializeMetaDataFromDICOM(sourceDataset); + + return EXIT_SUCCESS; +} + +int SegmentationImageObject::initializeMetaDataFromDICOM(DcmDataset *segDataset) { + + OFString temp; + segmentation->getSeries().getSeriesDescription(temp); + metaDataJson["SeriesDescription"] = temp.c_str(); + + segmentation->getSeries().getSeriesNumber(temp); + metaDataJson["SeriesNumber"] = temp.c_str(); + + segDataset->findAndGetOFString(DCM_InstanceNumber, temp); + metaDataJson["InstanceNumber"] = temp.c_str(); + + segmentation->getSeries().getBodyPartExamined(temp); + metaDataJson["BodyPartExamined"] = temp.c_str(); + + segmentation->getContentIdentification().getContentCreatorName(temp); + metaDataJson["ContentCreatorName"] = temp.c_str(); + + segDataset->findAndGetOFString(DCM_ClinicalTrialTimePointID, temp); + metaDataJson["ClinicalTrialTimePointID"] = temp.c_str(); + + segDataset->findAndGetOFString(DCM_ClinicalTrialSeriesID, temp); + metaDataJson["ClinicalTrialSeriesID"] = temp.c_str(); + + segDataset->findAndGetOFString(DCM_ClinicalTrialCoordinatingCenterName, temp); + if (temp.size()) + metaDataJson["ClinicalTrialCoordinatingCenterName"] = temp.c_str(); + + return EXIT_SUCCESS; +} + +dcmqi::SegmentAttributes *SegmentationImageObject::createAndGetNewSegment(unsigned labelID) { + using namespace dcmqi; + for (vector >::const_iterator vIt = segmentsAttributesMappingList.begin(); + vIt != segmentsAttributesMappingList.end(); ++vIt) { + for(map::const_iterator mIt = vIt->begin();mIt!=vIt->end();++mIt){ + SegmentAttributes *segmentAttributes = mIt->second; + if (segmentAttributes->getLabelID() == labelID) + return NULL; + } + } + + SegmentAttributes *segment = new SegmentAttributes(labelID); + map tempMap; + tempMap[labelID] = segment; + segmentsAttributesMappingList.push_back(tempMap); + return segment; +} \ No newline at end of file From 44d6ee37f28163091ede75b9da76d1fa2de7f3c4 Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Thu, 29 Jun 2017 17:37:33 -0400 Subject: [PATCH 034/116] ENH: reading segmentAttributes into metadata * added replacement method to SegmentationImageConverter --- apps/seg/segimage2itkimage.cxx | 2 +- include/dcmqi/SegmentationImageConverter.h | 2 + include/dcmqi/SegmentationImageObject.h | 11 +- libsrc/SegmentationImageConverter.cpp | 16 ++ libsrc/SegmentationImageObject.cpp | 210 +++++++++++---------- 5 files changed, 142 insertions(+), 99 deletions(-) diff --git a/apps/seg/segimage2itkimage.cxx b/apps/seg/segimage2itkimage.cxx index 0545778a..82721a36 100644 --- a/apps/seg/segimage2itkimage.cxx +++ b/apps/seg/segimage2itkimage.cxx @@ -24,7 +24,7 @@ int main(int argc, char *argv[]) CHECK_COND(sliceFF.loadFile(inputSEGFileName.c_str())); DcmDataset* dataset = sliceFF.getDataset(); - pair , string> result = dcmqi::SegmentationImageConverter::dcmSegmentation2itkimage(dataset); + pair , string> result = dcmqi::dcmSegmentation2itkimageReplacement(dataset); string outputPrefix = prefix.empty() ? "" : prefix + "-"; diff --git a/include/dcmqi/SegmentationImageConverter.h b/include/dcmqi/SegmentationImageConverter.h index 6158fc1f..00d18aee 100644 --- a/include/dcmqi/SegmentationImageConverter.h +++ b/include/dcmqi/SegmentationImageConverter.h @@ -32,6 +32,8 @@ typedef itk::LabelImageToLabelMapFilter LabelToLabelMapFilterTyp namespace dcmqi { + pair , string> dcmSegmentation2itkimageReplacement(DcmDataset *segDataset); + class SegmentationImageConverter : public MultiframeConverter { public: diff --git a/include/dcmqi/SegmentationImageObject.h b/include/dcmqi/SegmentationImageObject.h index 326a81e9..51ac5daf 100644 --- a/include/dcmqi/SegmentationImageObject.h +++ b/include/dcmqi/SegmentationImageObject.h @@ -35,7 +35,10 @@ class SegmentationImageObject : public MultiframeObject { int initializeFromDICOM(DcmDataset* sourceDataset); - int initializeMetaDataFromDICOM(DcmDataset*); + map getITKRepresentation() const { + // TODO: think about naming + return segment2image; + } protected: // Data containers specific to this object @@ -44,13 +47,15 @@ class SegmentationImageObject : public MultiframeObject { // ITK images corresponding to the individual segments map segment2image; - dcmqi::SegmentAttributes* createAndGetNewSegment(unsigned labelID); + int initializeMetaDataFromDICOM(DcmDataset*); + + Json::Value getSegmentAttributesMetadata(); // vector contains one item per input itkImageData label // each item is a map from labelID to segment attributes vector > segmentsAttributesMappingList; - private: +private: DcmSegmentation* segmentation; }; diff --git a/libsrc/SegmentationImageConverter.cpp b/libsrc/SegmentationImageConverter.cpp index 7a85a322..0f222a46 100644 --- a/libsrc/SegmentationImageConverter.cpp +++ b/libsrc/SegmentationImageConverter.cpp @@ -1,10 +1,26 @@ // DCMQI includes #include "dcmqi/SegmentationImageConverter.h" +#include "dcmqi/SegmentationImageObject.h" +using namespace std; namespace dcmqi { + pair , string> dcmSegmentation2itkimageReplacement(DcmDataset *segDataset) { + + SegmentationImageObject seg; + seg.initializeFromDICOM(segDataset); + + Json::StyledWriter styledWriter; + std::stringstream ss; + + ss << styledWriter.write(seg.getMetaDataJson()); + + return pair , string>(seg.getITKRepresentation(), + ss.str()); + }; + DcmDataset* SegmentationImageConverter::itkimage2dcmSegmentation(vector dcmDatasets, vector segmentations, const string &metaData, diff --git a/libsrc/SegmentationImageObject.cpp b/libsrc/SegmentationImageObject.cpp index 89e0505d..46b316e3 100644 --- a/libsrc/SegmentationImageObject.cpp +++ b/libsrc/SegmentationImageObject.cpp @@ -14,7 +14,6 @@ int SegmentationImageObject::initializeFromDICOM(DcmDataset* sourceDataset) { OFLogger dcemfinfLogger = OFLog::getLogger("qiicr.apps"); dcemfinfLogger.setLogLevel(dcmtk::log4cplus::OFF_LOG_LEVEL); - DcmSegmentation *segmentation = NULL; OFCondition cond = DcmSegmentation::loadDataset(*sourceDataset, segmentation); if(!segmentation){ cerr << "Failed to load seg! " << cond.text() << endl; @@ -80,87 +79,6 @@ int SegmentationImageObject::initializeFromDICOM(DcmDataset* sourceDataset) { segment2image[segmentId] = newSegmentImage; } - // populate meta information needed for Slicer ScalarVolumeNode initialization - // (for example) - { - // NOTE: according to the standard, segment numbering should start from 1, - // not clear if this is intentional behavior or a bug in DCMTK expecting - // it to start from 0 - - DcmSegment *segment = segmentation->getSegment(segmentId); - if (segment == NULL) { - cerr << "Failed to get segment for segment ID " << segmentId << endl; - continue; - } - - // get CIELab color for the segment - Uint16 ciedcm[3]; - unsigned cielabScaled[3]; - float cielab[3], ciexyz[3]; - unsigned rgb[3]; - if (segment->getRecommendedDisplayCIELabValue( - ciedcm[0], ciedcm[1], ciedcm[2] - ).bad()) { - // NOTE: if the call above fails, it overwrites the values anyway, - // not sure if this is a dcmtk bug or not - ciedcm[0] = 43803; - ciedcm[1] = 26565; - ciedcm[2] = 37722; - cerr << "Failed to get CIELab values - initializing to default " << - ciedcm[0] << "," << ciedcm[1] << "," << ciedcm[2] << endl; - } - cielabScaled[0] = unsigned(ciedcm[0]); - cielabScaled[1] = unsigned(ciedcm[1]); - cielabScaled[2] = unsigned(ciedcm[2]); - - Helper::getCIELabFromIntegerScaledCIELab(&cielabScaled[0], &cielab[0]); - Helper::getCIEXYZFromCIELab(&cielab[0], &ciexyz[0]); - Helper::getRGBFromCIEXYZ(&ciexyz[0], &rgb[0]); - - // TODO: factor out SegmentAttributes - SegmentAttributes *segmentAttributes = createAndGetNewSegment(segmentId); - - if (segmentAttributes) { - segmentAttributes->setLabelID(segmentId); - DcmSegTypes::E_SegmentAlgoType algorithmType = segment->getSegmentAlgorithmType(); - string readableAlgorithmType = DcmSegTypes::algoType2OFString(algorithmType).c_str(); - segmentAttributes->setSegmentAlgorithmType(readableAlgorithmType); - - if (algorithmType == DcmSegTypes::SAT_UNKNOWN) { - cerr << "AlgorithmType is not valid with value " << readableAlgorithmType << endl; - throw -1; - } - if (algorithmType != DcmSegTypes::SAT_MANUAL) { - OFString segmentAlgorithmName; - segment->getSegmentAlgorithmName(segmentAlgorithmName); - if (segmentAlgorithmName.length() > 0) - segmentAttributes->setSegmentAlgorithmName(segmentAlgorithmName.c_str()); - } - - OFString segmentDescription; - segment->getSegmentDescription(segmentDescription); - segmentAttributes->setSegmentDescription(segmentDescription.c_str()); - - segmentAttributes->setRecommendedDisplayRGBValue(rgb[0], rgb[1], rgb[2]); - segmentAttributes->setSegmentedPropertyCategoryCodeSequence(segment->getSegmentedPropertyCategoryCode()); - segmentAttributes->setSegmentedPropertyTypeCodeSequence(segment->getSegmentedPropertyTypeCode()); - - if (segment->getSegmentedPropertyTypeModifierCode().size() > 0) { - segmentAttributes->setSegmentedPropertyTypeModifierCodeSequence( - segment->getSegmentedPropertyTypeModifierCode()[0]); - } - - GeneralAnatomyMacro &anatomyMacro = segment->getGeneralAnatomyCode(); - CodeSequenceMacro &anatomicRegionSequence = anatomyMacro.getAnatomicRegion(); - if (anatomicRegionSequence.check(true).good()) { - segmentAttributes->setAnatomicRegionSequence(anatomyMacro.getAnatomicRegion()); - } - if (anatomyMacro.getAnatomicRegionModifier().size() > 0) { - segmentAttributes->setAnatomicRegionModifierSequence(anatomyMacro.getAnatomicRegionModifier()[0]); - } - } - } - // get string representation of the frame origin ShortImageType::PointType frameOriginPoint; ShortImageType::IndexType frameOriginIndex; @@ -213,6 +131,8 @@ int SegmentationImageObject::initializeFromDICOM(DcmDataset* sourceDataset) { return EXIT_SUCCESS; } + + int SegmentationImageObject::initializeMetaDataFromDICOM(DcmDataset *segDataset) { OFString temp; @@ -241,23 +161,123 @@ int SegmentationImageObject::initializeMetaDataFromDICOM(DcmDataset *segDataset) if (temp.size()) metaDataJson["ClinicalTrialCoordinatingCenterName"] = temp.c_str(); + metaDataJson["segmentAttributes"] = getSegmentAttributesMetadata(); + return EXIT_SUCCESS; } -dcmqi::SegmentAttributes *SegmentationImageObject::createAndGetNewSegment(unsigned labelID) { +Json::Value SegmentationImageObject::getSegmentAttributesMetadata() { + using namespace dcmqi; - for (vector >::const_iterator vIt = segmentsAttributesMappingList.begin(); - vIt != segmentsAttributesMappingList.end(); ++vIt) { - for(map::const_iterator mIt = vIt->begin();mIt!=vIt->end();++mIt){ - SegmentAttributes *segmentAttributes = mIt->second; - if (segmentAttributes->getLabelID() == labelID) - return NULL; + + FGInterface &fgInterface = segmentation->getFunctionalGroups(); + + Json::Value values(Json::arrayValue); + + for(size_t frameId=0;frameIdgetFrame(frameId); + bool isPerFrame; + + FGSegmentation *fgseg = + OFstatic_cast(FGSegmentation*,fgInterface.get(frameId, DcmFGTypes::EFG_SEGMENTATION, isPerFrame)); + assert(fgseg); + + Uint16 segmentId = -1; + if(fgseg->getReferencedSegmentNumber(segmentId).bad()){ + cerr << "Failed to get seg number!"; + throw -1; } - } - SegmentAttributes *segment = new SegmentAttributes(labelID); - map tempMap; - tempMap[labelID] = segment; - segmentsAttributesMappingList.push_back(tempMap); - return segment; + // populate meta information needed for Slicer ScalarVolumeNode initialization + // (for example) + + // NOTE: according to the standard, segment numbering should start from 1, + // not clear if this is intentional behavior or a bug in DCMTK expecting + // it to start from 0 + DcmSegment *segment = segmentation->getSegment(segmentId); + if (segment == NULL) { + cerr << "Failed to get segment for segment ID " << segmentId << endl; + continue; + } + + // get CIELab color for the segment + Uint16 ciedcm[3]; + unsigned cielabScaled[3]; + float cielab[3], ciexyz[3]; + unsigned rgb[3]; + if (segment->getRecommendedDisplayCIELabValue( + ciedcm[0], ciedcm[1], ciedcm[2] + ).bad()) { + // NOTE: if the call above fails, it overwrites the values anyway, + // not sure if this is a dcmtk bug or not + ciedcm[0] = 43803; + ciedcm[1] = 26565; + ciedcm[2] = 37722; + cerr << "Failed to get CIELab values - initializing to default " << + ciedcm[0] << "," << ciedcm[1] << "," << ciedcm[2] << endl; + } + cielabScaled[0] = unsigned(ciedcm[0]); + cielabScaled[1] = unsigned(ciedcm[1]); + cielabScaled[2] = unsigned(ciedcm[2]); + + Helper::getCIELabFromIntegerScaledCIELab(&cielabScaled[0], &cielab[0]); + Helper::getCIEXYZFromCIELab(&cielab[0], &ciexyz[0]); + Helper::getRGBFromCIEXYZ(&ciexyz[0], &rgb[0]); + + Json::Value segmentEntry; + + OFString temp; + + segmentEntry["labelID"] = segmentId; + + segment->getSegmentDescription(temp); + segmentEntry["SegmentDescription"] = temp.c_str(); + + + DcmSegTypes::E_SegmentAlgoType algorithmType = segment->getSegmentAlgorithmType(); + string readableAlgorithmType = DcmSegTypes::algoType2OFString(algorithmType).c_str(); + segmentEntry["SegmentAlgorithmType"] = readableAlgorithmType; + + if (algorithmType == DcmSegTypes::SAT_UNKNOWN) { + cerr << "AlgorithmType is not valid with value " << readableAlgorithmType << endl; + throw -1; + } + if (algorithmType != DcmSegTypes::SAT_MANUAL) { + segment->getSegmentAlgorithmName(temp); + if (temp.length() > 0) + segmentEntry["SegmentAlgorithmName"] = temp.c_str(); + } + + Json::Value rgbArray(Json::arrayValue); + rgbArray.append(rgb[0]); + rgbArray.append(rgb[1]); + rgbArray.append(rgb[2]); + segmentEntry["recommendedDisplayRGBValue"] = rgbArray; + + segmentEntry["SegmentedPropertyCategoryCodeSequence"] = + Helper::codeSequence2Json(segment->getSegmentedPropertyCategoryCode()); + + segmentEntry["SegmentedPropertyTypeCodeSequence"] = + Helper::codeSequence2Json(segment->getSegmentedPropertyTypeCode()); + + if (segment->getSegmentedPropertyTypeModifierCode().size() > 0) { + segmentEntry["SegmentedPropertyTypeModifierCodeSequence"] = + Helper::codeSequence2Json(*(segment->getSegmentedPropertyTypeModifierCode())[0]); + } + + GeneralAnatomyMacro &anatomyMacro = segment->getGeneralAnatomyCode(); + CodeSequenceMacro &anatomicRegionSequence = anatomyMacro.getAnatomicRegion(); + if (anatomicRegionSequence.check(true).good()) { + segmentEntry["AnatomicRegionSequence"] = Helper::codeSequence2Json(anatomyMacro.getAnatomicRegion()); + } + if (anatomyMacro.getAnatomicRegionModifier().size() > 0) { + segmentEntry["AnatomicRegionModifierSequence"] = + Helper::codeSequence2Json(*(anatomyMacro.getAnatomicRegionModifier()[0])); + } + + Json::Value innerList(Json::arrayValue); + innerList.append(segmentEntry); + values.append(innerList); + } + return values; } \ No newline at end of file From 0796fc610aa00f779e774f5d9eec45109040eeb5 Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Fri, 30 Jun 2017 11:57:30 -0400 Subject: [PATCH 035/116] ENH: ctests for dcm segmentation 2 itk succeed * TODO: refactor even more and generalize --- include/dcmqi/MultiframeObject.h | 17 ++++++++++++----- libsrc/SegmentationImageObject.cpp | 10 +++++++--- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/include/dcmqi/MultiframeObject.h b/include/dcmqi/MultiframeObject.h index 97a9d345..da2cea0e 100644 --- a/include/dcmqi/MultiframeObject.h +++ b/include/dcmqi/MultiframeObject.h @@ -84,7 +84,7 @@ class MultiframeObject { int initializeVolumeGeometryFromITK(DummyImageType::Pointer); template - int initializeVolumeGeometryFromDICOM(T iodImage, DcmDataset *dataset) { + int initializeVolumeGeometryFromDICOM(T iodImage, DcmDataset *dataset, bool useComputedVolumeExtent=false) { SpacingType spacing; PointType origin; DirectionType directions; @@ -101,9 +101,9 @@ class MultiframeObject { double computedSliceSpacing, computedVolumeExtent; vnl_vector sliceDirection(3); - sliceDirection[0] = *directions[0]; - sliceDirection[1] = *directions[1]; - sliceDirection[2] = *directions[2]; + sliceDirection[0] = directions[0][2]; + sliceDirection[1] = directions[1][2]; + sliceDirection[2] = directions[2][2]; if (computeVolumeExtent(fgInterface, sliceDirection, origin, computedSliceSpacing, computedVolumeExtent)) { cerr << "Failed to compute origin and/or slice spacing!" << endl; throw -1; @@ -130,7 +130,14 @@ class MultiframeObject { if(dataset->findAndGetOFString(DCM_Columns, str).good()) extent[0] = atoi(str.c_str()); } - extent[2] = fgInterface.getNumberOfFrames(); + + if (useComputedVolumeExtent) { + extent[2] = ceil(computedVolumeExtent/spacing[2])+1; + } else { + extent[2] = fgInterface.getNumberOfFrames(); + } + + cout << extent << endl; volumeGeometry.setSpacing(spacing); volumeGeometry.setOrigin(origin); diff --git a/libsrc/SegmentationImageObject.cpp b/libsrc/SegmentationImageObject.cpp index 46b316e3..7e53bb29 100644 --- a/libsrc/SegmentationImageObject.cpp +++ b/libsrc/SegmentationImageObject.cpp @@ -20,9 +20,7 @@ int SegmentationImageObject::initializeFromDICOM(DcmDataset* sourceDataset) { throw -1; } - initializeVolumeGeometryFromDICOM(segmentation, sourceDataset); - -// imageSize[2] = ceil(computedVolumeExtent/imageSpacing[2])+1; + initializeVolumeGeometryFromDICOM(segmentation, sourceDataset, true); // Initialize the image itkImage = volumeGeometry.getITKRepresentation(); @@ -173,6 +171,7 @@ Json::Value SegmentationImageObject::getSegmentAttributesMetadata() { FGInterface &fgInterface = segmentation->getFunctionalGroups(); Json::Value values(Json::arrayValue); + vector processedSegmentIDs; for(size_t frameId=0;frameIdgetFrame(frameId); @@ -200,6 +199,11 @@ Json::Value SegmentationImageObject::getSegmentAttributesMetadata() { continue; } + if(std::find(processedSegmentIDs.begin(), processedSegmentIDs.end(), segmentId) != processedSegmentIDs.end()) { + continue; + } + processedSegmentIDs.push_back(segmentId); + // get CIELab color for the segment Uint16 ciedcm[3]; unsigned cielabScaled[3]; From 97ba9672c78b6ea32d6de2744748ed2d80ac38ee Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Fri, 30 Jun 2017 15:17:41 -0400 Subject: [PATCH 036/116] ENH: extraction of more methods --- include/dcmqi/ImageVolumeGeometry.h | 3 + include/dcmqi/SegmentationImageObject.h | 17 +-- libsrc/ParametricMapObject.cpp | 3 - libsrc/SegmentationImageObject.cpp | 136 ++++++++++++------------ 4 files changed, 81 insertions(+), 78 deletions(-) diff --git a/include/dcmqi/ImageVolumeGeometry.h b/include/dcmqi/ImageVolumeGeometry.h index 0b1b3da5..dafa94b5 100644 --- a/include/dcmqi/ImageVolumeGeometry.h +++ b/include/dcmqi/ImageVolumeGeometry.h @@ -79,6 +79,9 @@ class ImageVolumeGeometry { image->SetDirection(direction); image->SetSpacing(spacing); + image->Allocate(); + image->FillBuffer(0); + return image; } diff --git a/include/dcmqi/SegmentationImageObject.h b/include/dcmqi/SegmentationImageObject.h index 51ac5daf..29487529 100644 --- a/include/dcmqi/SegmentationImageObject.h +++ b/include/dcmqi/SegmentationImageObject.h @@ -8,7 +8,6 @@ // DCMQI includes #include "MultiframeObject.h" #include "Helper.h" -#include "SegmentAttributes.h" // DCMTK includes #include @@ -47,17 +46,19 @@ class SegmentationImageObject : public MultiframeObject { // ITK images corresponding to the individual segments map segment2image; - int initializeMetaDataFromDICOM(DcmDataset*); + DcmSegmentation* segmentation; - Json::Value getSegmentAttributesMetadata(); + int iterateOverFramesAndMatchSlices(); - // vector contains one item per input itkImageData label - // each item is a map from labelID to segment attributes - vector > segmentsAttributesMappingList; + int unpackFrameAndWriteSegmentImage(const size_t& frameId, const Uint16& segmentId, const unsigned int& slice); -private: - DcmSegmentation* segmentation; + int initializeMetaDataFromDICOM(DcmDataset*); + + int createNewSegmentImage(Uint16 segmentId); + + Json::Value getSegmentAttributesMetadata(); + Uint16 getSegmentId(FGInterface &fgInterface, size_t frameId) const; }; diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 1296ff02..85e0aa99 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -343,9 +343,6 @@ int ParametricMapObject::initializeFromDICOM(DcmDataset * sourceDataset) { // Initialize the image itkImage = volumeGeometry.getITKRepresentation(); - itkImage->Allocate(); - itkImage->FillBuffer(0); - DPMParametricMapIOD::FramesType obj = parametricMap->getFrames(); if (OFCondition* pCondition = OFget(&obj)) { throw -1; diff --git a/libsrc/SegmentationImageObject.cpp b/libsrc/SegmentationImageObject.cpp index 7e53bb29..3980665e 100644 --- a/libsrc/SegmentationImageObject.cpp +++ b/libsrc/SegmentationImageObject.cpp @@ -21,44 +21,30 @@ int SegmentationImageObject::initializeFromDICOM(DcmDataset* sourceDataset) { } initializeVolumeGeometryFromDICOM(segmentation, sourceDataset, true); - - // Initialize the image itkImage = volumeGeometry.getITKRepresentation(); + iterateOverFramesAndMatchSlices(); + initializeMetaDataFromDICOM(sourceDataset); - itkImage->Allocate(); - itkImage->FillBuffer(0); + return EXIT_SUCCESS; +} +int SegmentationImageObject::iterateOverFramesAndMatchSlices() { // Iterate over frames, find the matching slice for each of the frames based on // ImagePositionPatient, set non-zero pixels to the segment number. Notify // about pixels that are initialized more than once. FGInterface &fgInterface = segmentation->getFunctionalGroups(); - DcmIODTypes::Frame *unpackedFrame = NULL; - for(size_t frameId=0;frameIdgetFrame(frameId); bool isPerFrame; - FGPlanePosPatient *planposfg = - OFstatic_cast(FGPlanePosPatient*,fgInterface.get(frameId, DcmFGTypes::EFG_PLANEPOSPATIENT, isPerFrame)); - assert(planposfg); - #ifndef NDEBUG FGFrameContent *fracon = OFstatic_cast(FGFrameContent*,fgInterface.get(frameId, DcmFGTypes::EFG_FRAMECONTENT, isPerFrame)); assert(fracon); #endif - FGSegmentation *fgseg = - OFstatic_cast(FGSegmentation*,fgInterface.get(frameId, DcmFGTypes::EFG_SEGMENTATION, isPerFrame)); - assert(fgseg); - - Uint16 segmentId = -1; - if(fgseg->getReferencedSegmentNumber(segmentId).bad()){ - cerr << "Failed to get seg number!"; - throw -1; - } + Uint16 segmentId = getSegmentId(fgInterface, frameId); // WARNING: this is needed only for David's example, which numbers // (incorrectly!) segments starting from 0, should start from 1 @@ -67,16 +53,14 @@ int SegmentationImageObject::initializeFromDICOM(DcmDataset* sourceDataset) { throw -1; } - if(segment2image.find(segmentId) == segment2image.end()){ - typedef itk::ImageDuplicator DuplicatorType; - DuplicatorType::Pointer dup = DuplicatorType::New(); - dup->SetInputImage(itkImage); - dup->Update(); - ShortImageType::Pointer newSegmentImage = dup->GetOutput(); - newSegmentImage->FillBuffer(0); - segment2image[segmentId] = newSegmentImage; + if(segment2image.find(segmentId) == segment2image.end()) { + createNewSegmentImage(segmentId); } + FGPlanePosPatient *planposfg = + OFstatic_cast(FGPlanePosPatient*,fgInterface.get(frameId, DcmFGTypes::EFG_PLANEPOSPATIENT, isPerFrame)); + assert(planposfg); + // get string representation of the frame origin ShortImageType::PointType frameOriginPoint; ShortImageType::IndexType frameOriginIndex; @@ -93,43 +77,58 @@ int SegmentationImageObject::initializeFromDICOM(DcmDataset* sourceDataset) { cerr << "Image size: " << segment2image[segmentId]->GetBufferedRegion().GetSize() << endl; throw -1; } - unsigned slice = frameOriginIndex[2]; - if(segmentation->getSegmentationType() == DcmSegTypes::ST_BINARY) - unpackedFrame = DcmSegUtils::unpackBinaryFrame(frame, - volumeGeometry.extent[1], // Rows - volumeGeometry.extent[0]); // Cols - else - unpackedFrame = new DcmIODTypes::Frame(*frame); - - // initialize slice with the frame content - for(unsigned row=0;rowpixData[bitCnt]; - - if(pixel!=0){ - ShortImageType::IndexType index; - index[0] = col; - index[1] = row; - index[2] = slice; - segment2image[segmentId]->SetPixel(index, segmentId); - } + unpackFrameAndWriteSegmentImage(frameId, segmentId, slice); + } + return EXIT_SUCCESS; +} + +int SegmentationImageObject::unpackFrameAndWriteSegmentImage(const size_t& frameId, const Uint16& segmentId, + const unsigned int& slice) { + const DcmIODTypes::Frame *frame = segmentation->getFrame(frameId); + + DcmIODTypes::Frame *unpackedFrame = NULL; + + if(segmentation->getSegmentationType() == DcmSegTypes::ST_BINARY) + unpackedFrame = DcmSegUtils::unpackBinaryFrame(frame, + volumeGeometry.extent[1], // Rows + volumeGeometry.extent[0]); // Cols + else + unpackedFrame = new DcmIODTypes::Frame(*frame); + + for(unsigned row=0; row < volumeGeometry.extent[1]; row++){ + for(unsigned col=0; col < volumeGeometry.extent[0]; col++){ + ShortImageType::PixelType pixel; + unsigned bitCnt = row * volumeGeometry.extent[0] + col; + pixel = unpackedFrame->pixData[bitCnt]; + + if(pixel!=0){ + ShortImageType::IndexType index; + index[0] = col; + index[1] = row; + index[2] = slice; + segment2image[segmentId]->SetPixel(index, segmentId); } } - - if(unpackedFrame != NULL) - delete unpackedFrame; } - initializeMetaDataFromDICOM(sourceDataset); + if(unpackedFrame != NULL) + delete unpackedFrame; return EXIT_SUCCESS; } - +int SegmentationImageObject::createNewSegmentImage(Uint16 segmentId) { + typedef itk::ImageDuplicator DuplicatorType; + DuplicatorType::Pointer dup = DuplicatorType::New(); + dup->SetInputImage(itkImage); + dup->Update(); + ShortImageType::Pointer newSegmentImage = dup->GetOutput(); + newSegmentImage->FillBuffer(0); + segment2image[segmentId] = newSegmentImage; + return EXIT_SUCCESS; +} int SegmentationImageObject::initializeMetaDataFromDICOM(DcmDataset *segDataset) { @@ -174,18 +173,7 @@ Json::Value SegmentationImageObject::getSegmentAttributesMetadata() { vector processedSegmentIDs; for(size_t frameId=0;frameIdgetFrame(frameId); - bool isPerFrame; - - FGSegmentation *fgseg = - OFstatic_cast(FGSegmentation*,fgInterface.get(frameId, DcmFGTypes::EFG_SEGMENTATION, isPerFrame)); - assert(fgseg); - - Uint16 segmentId = -1; - if(fgseg->getReferencedSegmentNumber(segmentId).bad()){ - cerr << "Failed to get seg number!"; - throw -1; - } + Uint16 segmentId = getSegmentId(fgInterface, frameId); // populate meta information needed for Slicer ScalarVolumeNode initialization // (for example) @@ -284,4 +272,18 @@ Json::Value SegmentationImageObject::getSegmentAttributesMetadata() { values.append(innerList); } return values; +} + +Uint16 SegmentationImageObject::getSegmentId(FGInterface &fgInterface, size_t frameId) const { + bool isPerFrame; + FGSegmentation *fgseg = + OFstatic_cast(FGSegmentation*,fgInterface.get(frameId, DcmFGTypes::EFG_SEGMENTATION, isPerFrame)); +// assert(fgseg); + + Uint16 segmentId = -1; + if(fgseg->getReferencedSegmentNumber(segmentId).bad()){ + cerr << "Failed to get seg number!"; +// throw -1; + } + return segmentId; } \ No newline at end of file From 759607ef69ee5cb54f8dcd2d5343be8ea2202896 Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Fri, 30 Jun 2017 16:51:07 -0400 Subject: [PATCH 037/116] STYLE: and uncommented code --- include/dcmqi/ImageVolumeGeometry.h | 2 +- include/dcmqi/ParametricMapObject.h | 2 -- libsrc/SegmentationImageObject.cpp | 4 ++-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/include/dcmqi/ImageVolumeGeometry.h b/include/dcmqi/ImageVolumeGeometry.h index dafa94b5..c437cc89 100644 --- a/include/dcmqi/ImageVolumeGeometry.h +++ b/include/dcmqi/ImageVolumeGeometry.h @@ -18,7 +18,7 @@ class ImageVolumeGeometry { friend class ParametricMapObject; friend class SegmentationImageObject; - public: +public: typedef itk::Vector DoubleVectorType; typedef itk::Size<3> SizeType; diff --git a/include/dcmqi/ParametricMapObject.h b/include/dcmqi/ParametricMapObject.h index bbda87a7..08dd7f53 100644 --- a/include/dcmqi/ParametricMapObject.h +++ b/include/dcmqi/ParametricMapObject.h @@ -77,8 +77,6 @@ class ParametricMapObject : public MultiframeObject { // Data containers specific to this object Float32ITKImageType::Pointer itkImage; - - private: DPMParametricMapIOD* parametricMap; }; diff --git a/libsrc/SegmentationImageObject.cpp b/libsrc/SegmentationImageObject.cpp index 3980665e..612ea78a 100644 --- a/libsrc/SegmentationImageObject.cpp +++ b/libsrc/SegmentationImageObject.cpp @@ -278,12 +278,12 @@ Uint16 SegmentationImageObject::getSegmentId(FGInterface &fgInterface, size_t fr bool isPerFrame; FGSegmentation *fgseg = OFstatic_cast(FGSegmentation*,fgInterface.get(frameId, DcmFGTypes::EFG_SEGMENTATION, isPerFrame)); -// assert(fgseg); + assert(fgseg); Uint16 segmentId = -1; if(fgseg->getReferencedSegmentNumber(segmentId).bad()){ cerr << "Failed to get seg number!"; -// throw -1; + throw -1; } return segmentId; } \ No newline at end of file From 7fa118537bc9adae89ec673a5de0f33f5ce16f5b Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Mon, 3 Jul 2017 17:14:56 -0400 Subject: [PATCH 038/116] BUG: updates to account for standard updates - PixelContrast is no longer needed for PM - use standard code for fitting model type --- doc/examples/pm-example-float.json | 4 ++-- doc/examples/pm-example.json | 4 ++-- libsrc/ParametricMapObject.cpp | 3 --- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/doc/examples/pm-example-float.json b/doc/examples/pm-example-float.json index 7e70af1b..ab3f3528 100644 --- a/doc/examples/pm-example-float.json +++ b/doc/examples/pm-example-float.json @@ -22,8 +22,8 @@ "CodeMeaning": "m2/s" }, "MeasurementMethodCode": { - "CodeValue": "DWMPxxxx10", - "CodingSchemeDesignator": "99QIICR", + "CodeValue": "113290", + "CodingSchemeDesignator": "DCM", "CodeMeaning": "Mono-exponential diffusion model" }, "SourceImageDiffusionBValues": ["0","1400"], diff --git a/doc/examples/pm-example.json b/doc/examples/pm-example.json index f0b2970d..30df68f1 100644 --- a/doc/examples/pm-example.json +++ b/doc/examples/pm-example.json @@ -22,8 +22,8 @@ "CodeMeaning": "um2/s" }, "MeasurementMethodCode": { - "CodeValue": "DWMPxxxx10", - "CodingSchemeDesignator": "99QIICR", + "CodeValue": "113290", + "CodingSchemeDesignator": "DCM", "CodeMeaning": "Mono-exponential diffusion model" }, "SourceImageDiffusionBValues": ["0","1400"], diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 85e0aa99..e9e4ab7f 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -406,9 +406,6 @@ void ParametricMapObject::initializeMetaDataFromDICOM(T doc) { doc->getSeries().getBodyPartExamined(temp); metaDataJson["BodyPartExamined"] = temp.c_str(); - doc->getDPMParametricMapImageModule().getImageType(temp, 3); - metaDataJson["DerivedPixelContrast"] = temp.c_str(); - if (doc->getNumberOfFrames() > 0) { FGInterface& fg = doc->getFunctionalGroups(); FGRealWorldValueMapping* rw = OFstatic_cast(FGRealWorldValueMapping*, From 1ec643f116b3ceed660d1323688131a0851d39ec Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Mon, 3 Jul 2017 17:36:09 -0400 Subject: [PATCH 039/116] BUG: initialize origin from ITK image --- libsrc/MultiframeObject.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index de844afa..f964b26a 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -67,6 +67,7 @@ int MultiframeObject::initializeVolumeGeometryFromITK(DummyImageType::Pointer im spacing = image->GetSpacing(); directions = image->GetDirection(); extent = image->GetLargestPossibleRegion().GetSize(); + origin = image->GetOrigin(); volumeGeometry.setSpacing(spacing); volumeGeometry.setOrigin(origin); @@ -237,8 +238,9 @@ std::vector MultiframeObject::findIntersectingSlices(ImageVolumeGeometry &v point[1] = frameIPP[1]; point[2] = frameIPP[2]; - if(itkvolume->TransformPhysicalPointToIndex(point, index)) + if(itkvolume->TransformPhysicalPointToIndex(point, index)) { intersectingSlices.push_back(index[2]); + } return intersectingSlices; } From 3d206ac7567b0db4fda375be81eefcf77b052c5e Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Mon, 3 Jul 2017 17:53:49 -0400 Subject: [PATCH 040/116] BUG: fix initialization of ReferencedSeriesSequence --- libsrc/MultiframeObject.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index f964b26a..d02115c5 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -168,7 +168,7 @@ int MultiframeObject::mapVolumeSlicesToDICOMFrames(ImageVolumeGeometry& volume, } else { dcmqi::DICOMFrame frame(dcm); vector intersectingSlices = findIntersectingSlices(volume, frame); - + for(int s=0;s &refseries = commref.getReferencedSeriesItems(); for(std::map >::const_iterator mIt=series2frame.begin(); mIt!=series2frame.end();++mIt){ + // TODO: who is supposed to de-allocate this? IODSeriesAndInstanceReferenceMacro::ReferencedSeriesItem* refseriesItem = new IODSeriesAndInstanceReferenceMacro::ReferencedSeriesItem; refseriesItem->setSeriesInstanceUID(mIt->first.c_str()); OFVector &refinstances = refseriesItem->getReferencedInstanceItems(); @@ -284,6 +285,8 @@ int MultiframeObject::initializeCommonInstanceReferenceModule(IODCommonInstanceR CHECK_COND(refinstancesItem->setReferencedSOPInstanceUID(frame.getInstanceUID().c_str())); refinstances.push_back(refinstancesItem); } + + refseries.push_back(refseriesItem); } return EXIT_SUCCESS; From f5bef134dfe0104b7292e7243cef6b727a542213 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Mon, 3 Jul 2017 18:05:50 -0400 Subject: [PATCH 041/116] BUG: pass the initialized derivation FG to addFrame() --- libsrc/MultiframeObject.cpp | 6 +----- libsrc/ParametricMapObject.cpp | 10 +++++++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index d02115c5..5a32ceb9 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -168,7 +168,7 @@ int MultiframeObject::mapVolumeSlicesToDICOMFrames(ImageVolumeGeometry& volume, } else { dcmqi::DICOMFrame frame(dcm); vector intersectingSlices = findIntersectingSlices(volume, frame); - + for(int s=0;saddDerivationImageItem(derivationCode,"",derimgItem)); - OFVector siVector; std::vector frameVector; for(set::const_iterator sIt=derivationFrames.begin(); sIt!=derivationFrames.end();++sIt) { - - // TODO: it seems that with the current dcmtk functionality it is not possible to set the referenced - // frame number. Revisit this after discussing with Michael. siVector.push_back((*sIt).getDataset()); frameVector.push_back(*sIt); } diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index e9e4ab7f..31488165 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -480,15 +480,17 @@ int ParametricMapObject::initializeFrames(vector perFrameFGs; - perFrameFGs.push_back(fgppp); - perFrameFGs.push_back(fgfc); - unsigned nSlices = itkImage->GetLargestPossibleRegion().GetSize()[2]; for (unsigned long sliceNumber = 0;sliceNumber < nSlices; sliceNumber++) { + + perFrameFGs.push_back(fgppp); + perFrameFGs.push_back(fgfc); + if(!slice2frame[sliceNumber].empty()){ // TODO: read derivation code from metadata, if available, and pass instead of the default addDerivationItemToDerivationFG(fgder, slice2frame[sliceNumber]); + perFrameFGs.push_back(fgder); } Float32ITKImageType::RegionType sliceRegion; @@ -531,6 +533,8 @@ int ParametricMapObject::initializeFrames(vectorgetFrames(); result = OFget >(&frames)->addFrame(&*data.begin(), frameSize, perFrameFGs); + perFrameFGs.clear(); + } return EXIT_SUCCESS; From 2043c7eaaa5b48f580af68965c0623bfbcd33ffd Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Mon, 3 Jul 2017 18:11:44 -0400 Subject: [PATCH 042/116] ENH: initialize DerivationCode from input JSON --- libsrc/ParametricMapObject.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 31488165..ec36836a 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -488,8 +488,15 @@ int ParametricMapObject::initializeFrames(vector Date: Wed, 5 Jul 2017 12:28:08 -0400 Subject: [PATCH 043/116] BUG: initialize series-specific attributes from JSON Round-trip tests are now passing --- include/dcmqi/MultiframeObject.h | 5 +++- libsrc/MultiframeObject.cpp | 39 ++++++++++++++++++++++++++++++++ libsrc/ParametricMapObject.cpp | 2 ++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/include/dcmqi/MultiframeObject.h b/include/dcmqi/MultiframeObject.h index da2cea0e..da029105 100644 --- a/include/dcmqi/MultiframeObject.h +++ b/include/dcmqi/MultiframeObject.h @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include #include @@ -154,7 +156,8 @@ class MultiframeObject { int getDeclaredImageSpacing(FGInterface &fgInterface, SpacingType &spacing); // initialize attributes of the composite context that are common for all multiframe objects - //virtual int initializeCompositeContext(); + int initializeCompositeContext(); + int initializeSeriesSpecificAttributes(IODGeneralSeriesModule&, IODGeneralImageModule&); // check whether all of the attributes required for initialization of the object are present in the // input metadata virtual bool metaDataIsComplete(); diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index 5a32ceb9..93619874 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -491,3 +491,42 @@ int MultiframeObject::getDeclaredImageSpacing(FGInterface &fgInterface, SpacingT return EXIT_SUCCESS; } + +int MultiframeObject::initializeSeriesSpecificAttributes(IODGeneralSeriesModule& generalSeriesModule, + IODGeneralImageModule& generalImageModule){ + + // TODO: error checks + string bodyPartExamined; + if(metaDataJson.isMember("BodyPartExamined")){ + bodyPartExamined = metaDataJson["BodyPartExamined"].asCString(); + } + if(derivationDcmDatasets.size() && bodyPartExamined.empty()) { + OFString bodyPartStr; + DcmDataset *srcDataset = derivationDcmDatasets[0]; + if(srcDataset->findAndGetOFString(DCM_BodyPartExamined, bodyPartStr).good()) { + if (!bodyPartStr.empty()) + bodyPartExamined = bodyPartStr.c_str(); + } + } + + if(!bodyPartExamined.empty()) + generalSeriesModule.setBodyPartExamined(bodyPartExamined.c_str()); + + // SeriesDate/Time should be of when parametric map was taken; initialize to when it was saved + { + OFString contentDate, contentTime; + DcmDate::getCurrentDate(contentDate); + DcmTime::getCurrentTime(contentTime); + + // TODO: AcquisitionTime + generalSeriesModule.setSeriesDate(contentDate.c_str()); + generalSeriesModule.setSeriesTime(contentTime.c_str()); + generalImageModule.setContentDate(contentDate.c_str()); + generalImageModule.setContentTime(contentTime.c_str()); + } + + generalSeriesModule.setSeriesDescription(metaDataJson["SeriesDescription"].asCString()); + generalSeriesModule.setSeriesNumber(metaDataJson["SeriesNumber"].asCString()); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index ec36836a..b1ca2198 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -39,6 +39,8 @@ int ParametricMapObject::initializeFromITK(Float32ITKImageType::Pointer inputIma // populate metadata about patient/study, from derivation // datasets or from metadata initializeCompositeContext(); + initializeSeriesSpecificAttributes(parametricMap->getIODGeneralSeriesModule(), + parametricMap->getIODGeneralImageModule()); // populate functional groups std::vector > dimensionTags; From f4d0ca49f43054aa8379d0ae39ddc303fe006205 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Wed, 5 Jul 2017 14:20:27 -0400 Subject: [PATCH 044/116] BUG: fix initialization of DerivationImageSequence If DerivationImageSequence is not empty for at least one frame, it must be present for each of the frames. --- libsrc/ParametricMapObject.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index b1ca2198..c162ccc7 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -484,11 +484,22 @@ int ParametricMapObject::initializeFrames(vectorGetLargestPossibleRegion().GetSize()[2]; + // if there is a derivation item for at least one frame, DerivationImageSequence must be present + // for every frame. + bool derivationFGRequired = false; + for (unsigned long sliceNumber = 0;sliceNumber < nSlices; sliceNumber++) { + if(!slice2frame[sliceNumber].empty()){ + derivationFGRequired = true; + break; + } + } + for (unsigned long sliceNumber = 0;sliceNumber < nSlices; sliceNumber++) { perFrameFGs.push_back(fgppp); perFrameFGs.push_back(fgfc); + fgder->clearData(); if(!slice2frame[sliceNumber].empty()){ if(metaDataJson.isMember("DerivationCode")){ CodeSequenceMacro purposeOfReference = CodeSequenceMacro("121322","DCM","Source image for image processing operation"); @@ -499,9 +510,11 @@ int ParametricMapObject::initializeFrames(vectorGetBufferedRegion().GetSize(); From 49ed047e3db0a9b78c5431a20a9989188278dc52 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Wed, 5 Jul 2017 16:50:18 -0400 Subject: [PATCH 045/116] touch to trigger circleci build --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 1fa94edb..989788ca 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ + [![OpenHub](https://www.openhub.net/p/dcmqi/widgets/project_thin_badge.gif)](https://www.openhub.net/p/dcmqi) | Docker | [![](https://images.microbadger.com/badges/version/qiicr/dcmqi:v1.0.5.svg)](https://microbadger.com/images/qiicr/dcmqi:v1.0.5) | [![](https://images.microbadger.com/badges/version/qiicr/dcmqi.svg)](https://microbadger.com/images/qiicr/dcmqi) | From e4e4b11354c8e5e17298d01d34f998f114a7a28a Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Thu, 6 Jul 2017 23:43:24 -0400 Subject: [PATCH 046/116] ENH: add codecov badge --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 989788ca..f0bdec9f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ -[![OpenHub](https://www.openhub.net/p/dcmqi/widgets/project_thin_badge.gif)](https://www.openhub.net/p/dcmqi) +[![OpenHub](https://www.openhub.net/p/dcmqi/widgets/project_thin_badge.gif)](https://www.openhub.net/p/dcmqi) [![codecov](https://codecov.io/gh/QIICR/dcmqi/branch/master/graph/badge.svg)](https://codecov.io/gh/QIICR/dcmqi) [![Join the chat at https://gitter.im/QIICR/dcmqi](https://badges.gitter.im/QIICR/dcmqi.svg)](https://gitter.im/QIICR/dcmqi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + + | Docker | [![](https://images.microbadger.com/badges/version/qiicr/dcmqi:v1.0.5.svg)](https://microbadger.com/images/qiicr/dcmqi:v1.0.5) | [![](https://images.microbadger.com/badges/version/qiicr/dcmqi.svg)](https://microbadger.com/images/qiicr/dcmqi) | |--------|--------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------| @@ -10,8 +12,6 @@ # Introduction -[![Join the chat at https://gitter.im/QIICR/dcmqi](https://badges.gitter.im/QIICR/dcmqi.svg)](https://gitter.im/QIICR/dcmqi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - dcmqi (DICOM (**dcm**) for Quantitative Imaging (**qi**)) is a collection of libraries and command line tools with minimum dependencies to support standardized communication of [quantitative image analysis](http://journals.sagepub.com/doi/pdf/10.1177/0962280214537333) research data using [DICOM standard](https://en.wikipedia.org/wiki/DICOM). Specifically, the current focus of development for dcmqi is to support conversion of the following data types to and from DICOM: From e74ed7a57c8cc1fc428e850e415c1b654452cc89 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Mon, 10 Jul 2017 09:57:04 -0400 Subject: [PATCH 047/116] BUG: do not cache codecov build directory --- circle.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/circle.yml b/circle.yml index 9c25fc94..a5aa212c 100644 --- a/circle.yml +++ b/circle.yml @@ -14,7 +14,6 @@ machine: dependencies: cache_directories: - "~/dcmqi/build" - - "~/dcmqi/codecov-build" - "~/.pyenv" post: From da677e58fb290da668ac1e6444c6bd29e88fef10 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 14 Jul 2017 16:52:32 -0400 Subject: [PATCH 048/116] ENH: switch to 3-clause BSD license See discussion in #274. --- LICENSE.txt | 225 ++++++---------------------------------------------- 1 file changed, 26 insertions(+), 199 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index a640153e..87199dd8 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,199 +1,26 @@ - -For more information, please see: - - http://www.slicer.org - -The 3D Slicer license below is a BSD style license, with extensions -to cover contributions and other issues specific to 3D Slicer. - - -3D Slicer Contribution and Software License Agreement ("Agreement") -Version 1.0 (December 20, 2005) - -This Agreement covers contributions to and downloads from the 3D -Slicer project ("Slicer") maintained by The Brigham and Women's -Hospital, Inc. ("Brigham"). Part A of this Agreement applies to -contributions of software and/or data to Slicer (including making -revisions of or additions to code and/or data already in Slicer). Part -B of this Agreement applies to downloads of software and/or data from -Slicer. Part C of this Agreement applies to all transactions with -Slicer. If you distribute Software (as defined below) downloaded from -Slicer, all of the paragraphs of Part B of this Agreement must be -included with and apply to such Software. - -Your contribution of software and/or data to Slicer (including prior -to the date of the first publication of this Agreement, each a -"Contribution") and/or downloading, copying, modifying, displaying, -distributing or use of any software and/or data from Slicer -(collectively, the "Software") constitutes acceptance of all of the -terms and conditions of this Agreement. If you do not agree to such -terms and conditions, you have no right to contribute your -Contribution, or to download, copy, modify, display, distribute or use -the Software. - -PART A. CONTRIBUTION AGREEMENT - License to Brigham with Right to -Sublicense ("Contribution Agreement"). - -1. As used in this Contribution Agreement, "you" means the individual - contributing the Contribution to Slicer and the institution or - entity which employs or is otherwise affiliated with such - individual in connection with such Contribution. - -2. This Contribution Agreement applies to all Contributions made to - Slicer, including without limitation Contributions made prior to - the date of first publication of this Agreement. If at any time you - make a Contribution to Slicer, you represent that (i) you are - legally authorized and entitled to make such Contribution and to - grant all licenses granted in this Contribution Agreement with - respect to such Contribution; (ii) if your Contribution includes - any patient data, all such data is de-identified in accordance with - U.S. confidentiality and security laws and requirements, including - but not limited to the Health Insurance Portability and - Accountability Act (HIPAA) and its regulations, and your disclosure - of such data for the purposes contemplated by this Agreement is - properly authorized and in compliance with all applicable laws and - regulations; and (iii) you have preserved in the Contribution all - applicable attributions, copyright notices and licenses for any - third party software or data included in the Contribution. - -3. Except for the licenses granted in this Agreement, you reserve all - right, title and interest in your Contribution. - -4. You hereby grant to Brigham, with the right to sublicense, a - perpetual, worldwide, non-exclusive, no charge, royalty-free, - irrevocable license to use, reproduce, make derivative works of, - display and distribute the Contribution. If your Contribution is - protected by patent, you hereby grant to Brigham, with the right to - sublicense, a perpetual, worldwide, non-exclusive, no-charge, - royalty-free, irrevocable license under your interest in patent - rights covering the Contribution, to make, have made, use, sell and - otherwise transfer your Contribution, alone or in combination with - any other code. - -5. You acknowledge and agree that Brigham may incorporate your - Contribution into Slicer and may make Slicer available to members - of the public on an open source basis under terms substantially in - accordance with the Software License set forth in Part B of this - Agreement. You further acknowledge and agree that Brigham shall - have no liability arising in connection with claims resulting from - your breach of any of the terms of this Agreement. - -6. YOU WARRANT THAT TO THE BEST OF YOUR KNOWLEDGE YOUR CONTRIBUTION - DOES NOT CONTAIN ANY CODE THAT REQURES OR PRESCRIBES AN "OPEN - SOURCE LICENSE" FOR DERIVATIVE WORKS (by way of non-limiting - example, the GNU General Public License or other so-called - "reciprocal" license that requires any derived work to be licensed - under the GNU General Public License or other "open source - license"). - -PART B. DOWNLOADING AGREEMENT - License from Brigham with Right to -Sublicense ("Software License"). - -1. As used in this Software License, "you" means the individual - downloading and/or using, reproducing, modifying, displaying and/or - distributing the Software and the institution or entity which - employs or is otherwise affiliated with such individual in - connection therewith. The Brigham and Women?s Hospital, - Inc. ("Brigham") hereby grants you, with right to sublicense, with - respect to Brigham's rights in the software, and data, if any, - which is the subject of this Software License (collectively, the - "Software"), a royalty-free, non-exclusive license to use, - reproduce, make derivative works of, display and distribute the - Software, provided that: - -(a) you accept and adhere to all of the terms and conditions of this -Software License; - -(b) in connection with any copy of or sublicense of all or any portion -of the Software, all of the terms and conditions in this Software -License shall appear in and shall apply to such copy and such -sublicense, including without limitation all source and executable -forms and on any user documentation, prefaced with the following -words: "All or portions of this licensed product (such portions are -the "Software") have been obtained under license from The Brigham and -Women's Hospital, Inc. and are subject to the following terms and -conditions:" - -(c) you preserve and maintain all applicable attributions, copyright -notices and licenses included in or applicable to the Software; - -(d) modified versions of the Software must be clearly identified and -marked as such, and must not be misrepresented as being the original -Software; and - -(e) you consider making, but are under no obligation to make, the -source code of any of your modifications to the Software freely -available to others on an open source basis. - -2. The license granted in this Software License includes without - limitation the right to (i) incorporate the Software into - proprietary programs (subject to any restrictions applicable to - such programs), (ii) add your own copyright statement to your - modifications of the Software, and (iii) provide additional or - different license terms and conditions in your sublicenses of - modifications of the Software; provided that in each case your use, - reproduction or distribution of such modifications otherwise - complies with the conditions stated in this Software License. - -3. This Software License does not grant any rights with respect to - third party software, except those rights that Brigham has been - authorized by a third party to grant to you, and accordingly you - are solely responsible for (i) obtaining any permissions from third - parties that you need to use, reproduce, make derivative works of, - display and distribute the Software, and (ii) informing your - sublicensees, including without limitation your end-users, of their - obligations to secure any such required permissions. - -4. The Software has been designed for research purposes only and has - not been reviewed or approved by the Food and Drug Administration - or by any other agency. YOU ACKNOWLEDGE AND AGREE THAT CLINICAL - APPLICATIONS ARE NEITHER RECOMMENDED NOR ADVISED. Any - commercialization of the Software is at the sole risk of the party - or parties engaged in such commercialization. You further agree to - use, reproduce, make derivative works of, display and distribute - the Software in compliance with all applicable governmental laws, - regulations and orders, including without limitation those relating - to export and import control. - -5. The Software is provided "AS IS" and neither Brigham nor any - contributor to the software (each a "Contributor") shall have any - obligation to provide maintenance, support, updates, enhancements - or modifications thereto. BRIGHAM AND ALL CONTRIBUTORS SPECIFICALLY - DISCLAIM ALL EXPRESS AND IMPLIED WARRANTIES OF ANY KIND INCLUDING, - BUT NOT LIMITED TO, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR - A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL - BRIGHAM OR ANY CONTRIBUTOR BE LIABLE TO ANY PARTY FOR DIRECT, - INDIRECT, SPECIAL, INCIDENTAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY ARISING IN ANY WAY - RELATED TO THE SOFTWARE, EVEN IF BRIGHAM OR ANY CONTRIBUTOR HAS - BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. TO THE MAXIMUM - EXTENT NOT PROHIBITED BY LAW OR REGULATION, YOU FURTHER ASSUME ALL - LIABILITY FOR YOUR USE, REPRODUCTION, MAKING OF DERIVATIVE WORKS, - DISPLAY, LICENSE OR DISTRIBUTION OF THE SOFTWARE AND AGREE TO - INDEMNIFY AND HOLD HARMLESS BRIGHAM AND ALL CONTRIBUTORS FROM AND - AGAINST ANY AND ALL CLAIMS, SUITS, ACTIONS, DEMANDS AND JUDGMENTS - ARISING THEREFROM. - -6. None of the names, logos or trademarks of Brigham or any of - Brigham's affiliates or any of the Contributors, or any funding - agency, may be used to endorse or promote products produced in - whole or in part by operation of the Software or derived from or - based on the Software without specific prior written permission - from the applicable party. - -7. Any use, reproduction or distribution of the Software which is not - in accordance with this Software License shall automatically revoke - all rights granted to you under this Software License and render - Paragraphs 1 and 2 of this Software License null and void. - -8. This Software License does not grant any rights in or to any - intellectual property owned by Brigham or any Contributor except - those rights expressly granted hereunder. - -PART C. MISCELLANEOUS - -This Agreement shall be governed by and construed in accordance with -the laws of The Commonwealth of Massachusetts without regard to -principles of conflicts of law. This Agreement shall supercede and -replace any license terms that you may have agreed to previously with -respect to Slicer. +Copyright 2017 Brigham and Women's Hospital + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 7b9431aa38026167d16d196b8d4ac1156a7046f0 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 14 Jul 2017 17:02:54 -0400 Subject: [PATCH 049/116] ENH: update license wording in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f0bdec9f..8dc4f16e 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ dcmqi is developed and maintained by the [QIICR](http://qiicr.org) project. # License -dcmqi is distributed under non-restrictive BSD-style license (see full text [here](https://github.com/QIICR/dcmqi/blob/master/LICENSE.txt)) that does not have any constraints on either commercial or academic usage. +dcmqi is distributed under [3-clause BSD license]](https://github.com/QIICR/dcmqi/blob/master/LICENSE.txt)) that does not have any constraints on either commercial or academic usage. Our goal is to support and encourage adoption of the DICOM standard in both academic and research tools. We will be happy to hear about your usage of dcmqi, but you don't have to report back to us. From f2be20e65a5a7549781245472e11282f13d2e72a Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 14 Jul 2017 17:03:32 -0400 Subject: [PATCH 050/116] STYLE: fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8dc4f16e..025e61a6 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ dcmqi is developed and maintained by the [QIICR](http://qiicr.org) project. # License -dcmqi is distributed under [3-clause BSD license]](https://github.com/QIICR/dcmqi/blob/master/LICENSE.txt)) that does not have any constraints on either commercial or academic usage. +dcmqi is distributed under [3-clause BSD license](https://github.com/QIICR/dcmqi/blob/master/LICENSE.txt)) that does not have any constraints on either commercial or academic usage. Our goal is to support and encourage adoption of the DICOM standard in both academic and research tools. We will be happy to hear about your usage of dcmqi, but you don't have to report back to us. From 150d32fda64fb5af2c4ac42505df2c711db604c3 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 14 Jul 2017 17:15:22 -0400 Subject: [PATCH 051/116] ENH: further simplify mentioning of the license --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 025e61a6..868b0843 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ dcmqi is developed and maintained by the [QIICR](http://qiicr.org) project. # License -dcmqi is distributed under [3-clause BSD license](https://github.com/QIICR/dcmqi/blob/master/LICENSE.txt)) that does not have any constraints on either commercial or academic usage. +dcmqi is distributed under [3-clause BSD license](https://github.com/QIICR/dcmqi/blob/master/LICENSE.txt). Our goal is to support and encourage adoption of the DICOM standard in both academic and research tools. We will be happy to hear about your usage of dcmqi, but you don't have to report back to us. From a51201f651b3e31e53675a6066c6d4d90991c0f1 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Mon, 17 Jul 2017 16:51:20 -0400 Subject: [PATCH 052/116] WIP: adding attribute to link segmentAttributes sub-arrays to specific files Compiles, need to test Aims to address #253 --- apps/seg/itkimage2segimage.cxx | 36 +++++++++++++++++++++++++++++++++- doc/schemas/seg-schema.json | 3 +++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/apps/seg/itkimage2segimage.cxx b/apps/seg/itkimage2segimage.cxx index 68f32d96..daa2854a 100644 --- a/apps/seg/itkimage2segimage.cxx +++ b/apps/seg/itkimage2segimage.cxx @@ -55,7 +55,41 @@ int main(int argc, char *argv[]) ifstream metainfoStream(metaDataFileName.c_str(), ios_base::binary); std::string metadata( (std::istreambuf_iterator(metainfoStream) ), (std::istreambuf_iterator())); - DcmDataset* result = dcmqi::SegmentationImageConverter::itkimage2dcmSegmentation(dcmDatasets, segmentations, metadata, skipEmptySlices); + + Json::Value metaRoot; + istringstream metainfoisstream(metadata); + metainfoisstream >> metaRoot; + if(metaRoot.isMember("segmentAttributesFileMapping")){ + if(metaRoot["segmentAttributesFileMapping"].size() != metaRoot["segmentAttributes"].size()){ + cerr << "Number of files in segmentAttributesFileMapping should match the number of entries in segmentAttributes!" << endl; + return EXIT_FAILURE; + } + // otherwise, re-order the entries in the segmentAtrributes list to match the order of files in segmentAttributesFileMapping + Json::Value reorderedSegmentAttributes; + vector fileOrder(segImageFiles.size()); + vector segmentationsReordered(segImageFiles.size()); + for(int filePosition=0;filePosition Date: Tue, 18 Jul 2017 10:07:12 -0400 Subject: [PATCH 053/116] ENH: make mkdir happy if the directory exists --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index a5aa212c..9d654224 100644 --- a/circle.yml +++ b/circle.yml @@ -17,7 +17,7 @@ dependencies: - "~/.pyenv" post: - - sudo apt-get install lcov && sudo apt-get remove cmake && wget --no-check-certificate https://cmake.org/files/v3.6/cmake-3.6.0-rc4-Linux-x86_64.tar.gz && tar zxf cmake-3.6.0-rc4-Linux-x86_64.tar.gz && sudo cp -r cmake-3.6.0-rc4-Linux-x86_64/* /usr && cmake --version && mkdir codecov-build && cd codecov-build && ls -lat /usr/bin && /usr/bin/cmake .. && make -j4 && cd dcmqi-build && ctest . && lcov --directory . --capture --output-file coverage.lcov && lcov --remove coverage.lcov '/usr/*' 'ITK/*' 'DCMTK/*' 'SlicerExecutionModel/*' 'jsoncpp/*' 'dcmqi-build/*' 'codecov-build/*' --output-file coverage_clean.lcov && bash <(curl -s https://codecov.io/bash) -X gcov -C ${CIRCLE_SHA1} -f coverage_clean.lcov -R ../.. + - sudo apt-get install lcov && sudo apt-get remove cmake && wget --no-check-certificate https://cmake.org/files/v3.6/cmake-3.6.0-rc4-Linux-x86_64.tar.gz && tar zxf cmake-3.6.0-rc4-Linux-x86_64.tar.gz && sudo cp -r cmake-3.6.0-rc4-Linux-x86_64/* /usr && cmake --version && mkdir -p codecov-build && cd codecov-build && ls -lat /usr/bin && /usr/bin/cmake .. && make -j4 && cd dcmqi-build && ctest . && lcov --directory . --capture --output-file coverage.lcov && lcov --remove coverage.lcov '/usr/*' 'ITK/*' 'DCMTK/*' 'SlicerExecutionModel/*' 'jsoncpp/*' 'dcmqi-build/*' 'codecov-build/*' --output-file coverage_clean.lcov && bash <(curl -s https://codecov.io/bash) -X gcov -C ${CIRCLE_SHA1} -f coverage_clean.lcov -R ../.. - cd docker && make dcmqi # Install package required by convert.py - pip install lxml==3.6.4 From a9a76be62e83f8649304071847ce28a6f6505479 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Tue, 18 Jul 2017 16:57:48 -0400 Subject: [PATCH 054/116] BUG: reduce intensity tolerance test parameter Default of 2 led to erroneous results of SEG conversion testing Fixing this bug exposed a test error in the ITK-SEG roundtrip process: - input ITK images all have labels 1 - ITK images produced from SEG have labels that match segment number in SEG - there are exactly 3 segments, and since the tolerance was set to 2, the non-zero difference between the ITK baselines and the images produced from SEG were not detected by the test The issue above was fixed by updating the baseline images to have the pixel value matching the segment number, and updating the JSON file used by the test to point to the updated labels. --- data/segmentations/heart_seg.nrrd | Bin 3512 -> 3501 bytes data/segmentations/spine_seg.nrrd | Bin 2948 -> 2936 bytes .../seg-example_multiple_segments.json | 4 ++-- include/dcmqi/itkTestMain.h | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/segmentations/heart_seg.nrrd b/data/segmentations/heart_seg.nrrd index 7d7a81abde122a9150258ca00ef03d8763040da9..9f1f63cab38b63464375fb3769f2b330c9120083 100644 GIT binary patch literal 3501 zcmds(eM}p57{{;ThIVdEjE=oH+GtF+2?e?hW))Gp#cU{p(o(j1j0y+Tl`FP)R$&`# zL`fuzi5VL$6t=7aS>~&OyxyoE& zV$4=rVPe#vu3?xubw&n5QMF3Dm9WiP>#Hg-2D-rfESIkMuD#^0wEcT)Uq zp8Uj;G2IF4ui=`1My=ZUJqFagMi^|J+RM3`EvF^ zDxqfkCtB7E?&PKd(mmOub%>JgVuQQ!$3Pp(c8T(`er54gp|j&xqR?p!pdXf@4oEIyr|pbs2Ff5M^Be;Acaog`v@QJ!FBU1CAD#VF8oTvw+#fkce#ue62U2ruZszS6WqUj5l z0P^Yc7}3VdIyo|~>7xzt3$APWJe^pNXfvpkrGOTffa)zo6(LY<1hfTF7or*{@7)B* zhv*=pT3pc>gNX)M@f;#11c;0mx;qS@5)VNW+aj8Ev4Q8|jAMKO&KrpcNpzqfaIz4{McPDM#b@5$+s5{kIw4W z=8PtF7Y&9n3ro6-f}x~ttph_!NYbqh{tRIcgklF~IT)hXAPhn%-h-I|Ly8A>h|lX` z*XhTN#Fa`cV)v#J%h~r~HzE6ABN_Y&WDZxOGoZ&!XD!O!#=*VGP3y@ICVPtaIAL&! zC|9#B$a?@85Y0u@jHnw>3ZgjQOtFoTas#nSK@B^yp{hdSUnt z|32>M;^wui!!Wy5414jN6&9qqeLhvG&f%a2oZ;}y2GzxJkKHOH7yeaPBnWN(4>;e= z!fbv-SdiESrnkJArOWb4OQHr@I}g3qvO6YP3b1`$jiJ_+ zp(e)|dp0I{O@76AJf+D~7CbI%$}hVDaSk`0OY9cMw-Gu~%!P0j!w_Dc!|V&pILr>_ z5{&4@Y+o+%fY^@V&0J!oSdW3m@KQv!iz`9**P;6dlAi8Wf_1rK@O|I{OVf!e{65G> z#k#nrSE&WZYDJzJ0ZVxf5EvqO5u*8sz$1H=i1GvAGGLpX+!{dPLO@UG{*F)u-yofw z2HbC2_1}@}mK765-AAKQLtl)z2>ljX?F%DnMLS=P=wG~>5eu-dVsKI#z1xW1U53bj zrjOp;is)smWyJC&Q2q>}dQ_DG@*2Qz=X?WA?BoHN@B;$v{D~4!wWEqcqeeS#Mdaq8 zmIX|oHp0XhqAw8H!MnZa-L1i=c8g(N_GsvoDsoC0!H8$#GW*Y{9yz7HEnegk6Zc+9 LAKpO_3y41eib=UB literal 3512 zcmds)eM}p57{`y14ei|c!xz+LYZGI_MqApB7FNcK;$||HbWrGI-lW0}PUw}jcVn(v zri~IqjLCEoolMz8+8z{qVO`pRK{J<(s5tM&26hsYIlP6kES5T&Zaz<$i7qC__}A7P z4^Kb8@AKSqm)!4rEw89}Orz1{$sSUa*=;S&6iX?}fhl&<&6HxN-C?t`3TF#dNAINT ztSoJ36js&<>PD7rG3Ms76lGI8?VO{I0&xSSW+*n7aX9L87i-mxtgTtbzR*G$70yPx zgO%0OHi~gVBco!y%-LeCgOz4#C#!0)J6Xy>+Z`$=*OE$PPI@opG%EC3UFz?KUQtgw zs5+bl*0MF~0Y+uw-DahkzyFg_Og(Ld z8JlUAZKmLBoxPrB8jOmDy>!ds+zPvcZlFPvtapv%)~4N8?xXHXa5 zNb5CvZN5IgK&w<~bp>j}24K3pJZ+xFpetA_lgT^oAX1-o(>8n)ab7=xu8hR!j1A+^cJdb==Df?VHJGOSV-7TdjjW_1xFBmE`#YqlZGF zLXVzvb}rj4{miU5Dx8QssPwtQhO=8IcH0M!1ZID(Eh8tt8GSAE`9aSLj?T|mE`1wV zd7sclu2y;r!^S-$p-;n}i+oPFa53%WSA{q3357lx^Zeg5>{-e+SpIUxX73Z>f^A!a zyLSy9W?;0($*G~y{h?r!N5$<5E^Cs0iQY9Yh>-`B{%}}#*4)`zH|S^PzO1$2wO7Y2 zneT{6(PP8gQ4#s@TDRHab!UqYOY$vT2O#nYpt!d!TioQA*KnPP;EtMuK0urwRNYwU z#X>(6_Fy3|0phQ9SH=;2gJ?)*=ITuDVpqSh|y7_>kQyHZ_(MbsE$@J2mz&QwN z2))s>kTDpd6PQ8hRbv)713_I4J46TU5Z>*$kyI2BdD7-0!YmczZc+{#3G)M%R$Mpv z_}^gD=@3A+1{XJH5(h$$CChr%TVb(KC}E|Q$R7n1FacVHXc?kXR3S?68Dr8lX*3WT zaS!r=0ePQ)Dy&)DcT$gHRpk7uqyEtFa?exT3;D}Bq-fx-`@{&DdSkAa0qsW>1t|J7_BsRT zF!p*rR@NJ>0W?+(y+Ww615!T&y>cvoP7eXv4Tb6*fcS}GR=P5j`S9#ly3}E~fQcb> z7%!xs%D6=yliP62kuxXd85>W0;JQAQHaC6gy?FZN;*sEsxrAjn@RFS9JU#+3i=A~5 z+2U-1@Cu>}!U+sR__YhOH!$Ncqg=#{Kw|b3R0t{zD_lgQ5ZenOg5iWDe~{}(_xGXu z`!e=zA;G#T;_!Y@0hR_$@cp0@6>lxHZy`Sd$I3!pB!Q*8i3kj_a>5Hpg$O)y3wan& zr?+iJ0NXTjuLFv7qt$uAcy30Zf_D%j*N*PDsQS;S;%{e68FhIChA_iiGlCxd7Dg?P zpm%5az!0kt{fl?c2wgZ=FFL6orVpZb4QTKsLbqUZIw*L%JRzILB9(tX_SA(Na7)SI5qKn|&Bzkw&z)RU; l1khwVCAE_hMm$nqZGT4YR6BP|Jjm|Y-3CWL{1mvK_yZ+Lw!r`Z diff --git a/data/segmentations/spine_seg.nrrd b/data/segmentations/spine_seg.nrrd index 870bfd29c62a317e3a205260a37caf1bc10de5be..773c02bdbfe7b849d3b101b09921f06f3762eaf7 100644 GIT binary patch literal 2936 zcmeH|ZA?>V6vv;+kfmhy8%~Y3$v*I6xj+?!f>gtdjwvyqfB|F;T;OWk+qAdxGI{wV ziF5d2l7@*OL1eX9$>s#CMO-khHMod5aX zlc)Fg*PE7}{%Le{^k()aycAk0myis}O9PEpL`g_q5v`Dl8J<#37Enc0ftaCa8Bfdz z!Ck^Ia$!^yLy}TLNvjkEB$$gyLPj!CGKHcr%6~0U!bl}-rd&=6dCC%6!LSP{DJfIJ zAR%uPTPYV8Ku1CrF_Cgw$&d<)RzxaQa^Hfjq$)_IkQW;x@O@tUd4-gMEWmq#URX2{ z9}^wBc?*1t*N1=~&5sU;4*!39{tiDToPC&*6~c%@S}LYw|2?ruSs^8cJxVBsk&sXp z(1nz&Sja1`pyd92^JoQCOo1mXQm`qOh)HVH1cdG43M$%GSYCPQCLo1*-__j$SQfW?rUY^X_G zy!K@xDrGuK*P(^EvX_}iXODGEpzd!?SLguCTF>s6N6_HRdgla+Z(SG#DR(mOdSK17 zFjp5#zH+}i1RWyXaVS-_7=j{H3n56Ta^mz1r&(8Rs#zacla{CVv}BlMth4EAUG?-x zn~fWxdW;=^U`IDJw_yEi8&{~B0x7TCwIpdl0#{mIkE>;$T2~b|`r~1cX2)yO%?7xz z^b$x8Ew{o|OImBC)}VRLz&uBPnKLl56uld>7yCrv+$Y<6I&ahQlDU-}ZR5imMEi?Q zTfgH3g#sVAbJ!17zG%WV|gGzuIz2!D1O1x_RJPj=%q0ofM<@cT-E zGhn%%@B2CL;12ZB-2B`=L*;01V2ysUw<-}Ws4iDwbbfe#5&d?f)ePu-pLGg2i8z>Z zZGDZcY$wn$I9-VWQkR-&mW6-ZB}I)XCZH(u08VB|sb-j%YBpe2@UJ?H23A&I0w!+e zpy?3<24e;jW&;$=SHaN*G-aqUQdtPeow@||2ocAjh%<6&c?ZHtgSNpJVg0e8&Sj`y}(Czdn?Z0G}qHX7FUyIaz)>M!-b>4 z$TVktE=9S<=0guw_5EsS1>lP3doe`=dRrHghwQ+Noz*^Au}yn1-#e_ab+Lvu16^pZ zMr#r=R%;~MXZ_vDIdiov7@fIV>P~5YVqUSWO_m4VoGvu1`5dQ2>>b25C3fUO^KHq! z_d8vI0 zlMC<%(owhIJ^YBE~_-yNHc`~rP6h5wlsxm|pU-0iI v+V3UvGqwV1_H+?H0|#C}3H(9nTfq8X95Y1A^xT)mG#c}tI=;(9=wtLZ91lwd delta 1401 zcmZ9LZA?>F7{|}W6&4YieSs-2{W9a6w1M(gUbGsr5p`pol$Q*yrkRWohg1fYVCii# zmwqr|nG6^z69xzsMqR)a3I$xlh>!_Lu?(bCHwIj6E29N0wB2(Gx|f@O|Mxln=ef7Z z{hhNBa5?affH)Hq7sca7aU){F*j#Q5hZhc4WO#H$Y;-IyB8(lu<#A%-Amc_wMMQaTg^2ioijRc35)r8>o3{=VcyOY+M1E#ZX(+@q=`Put@i z<32YC3L)9%##1kgxpj?=9?!1Zfs?Aijyc`DTfi{+hnkJv1C`qEr9miTW%{WZWe+9L zYC`;;wH0V}_x8wqQSL|jf?cLyjf;&shk;us4V93FHwtMB21Da7Sc<`vU@%6nqsv5a7d@%_Bk9G?*h6~ zds4IIv{R7EfA$cg!n7uU))eMcHr2F4Yf93YW!A-0^E!K8VEPL`MViqhs*FsWvR_M; zCJ=_zXBmkf`iaS8hgMpdoMo-Et_lXwxpdnWG+GNV!>7pAK*rXOH}O=)$E9{>taAo9T$91$1&@;W*wXRtB&J|Am!L`4=E)h8pm6z-GIlf3Y1}wacvd;stp4{}HV?HLG#e3k~hEMZLC_b)tBchQH7{ z>V?%Rh|CZ<6geOoE8kP((~4rx5+`%fZPKIbcRpA};{5NPh$_`3ROWmzoU8kaG?n9O z?g3Nj=@nM!sGs=VWZK<#w!mMUHU!+x=K6@!l9#Do3btr*uX{9JCP|P#gzbyODg3;R z-=A#Sn7Ouza+X%UI?fM+qwUf5#(93-A=*}rQB=Jqs-ZE;YKN%CA$kjbxY`E%vH;ls Y@OBUtKN2Z>Ou;U% Date: Tue, 18 Jul 2017 17:35:51 -0400 Subject: [PATCH 055/116] ENH: finalize file order mapping Added missing functionality, added testing --- apps/seg/Testing/CMakeLists.txt | 33 +++++++ apps/seg/itkimage2segimage.cxx | 24 ++--- ...g-example_multiple_segments_reordered.json | 87 +++++++++++++++++++ 3 files changed, 133 insertions(+), 11 deletions(-) create mode 100644 doc/examples/seg-example_multiple_segments_reordered.json diff --git a/apps/seg/Testing/CMakeLists.txt b/apps/seg/Testing/CMakeLists.txt index 65753fb3..0fbf5a28 100644 --- a/apps/seg/Testing/CMakeLists.txt +++ b/apps/seg/Testing/CMakeLists.txt @@ -40,6 +40,16 @@ dcmqi_add_test( --outputDICOM ${MODULE_TEMP_DIR}/liver_heart_seg.dcm ) + dcmqi_add_test( + NAME ${itk2dcm}_makeSEG_multiple_segment_files_reordered + MODULE_NAME ${MODULE_NAME} + COMMAND $ + --inputMetadata ${CMAKE_SOURCE_DIR}/doc/examples/seg-example_multiple_segments_reordered.json + --inputImageList ${BASELINE}/heart_seg.nrrd,${BASELINE}/liver_seg.nrrd,${BASELINE}/spine_seg.nrrd + --inputDICOMList ${DICOM_DIR}/01.dcm,${DICOM_DIR}/02.dcm,${DICOM_DIR}/03.dcm + --outputDICOM ${MODULE_TEMP_DIR}/liver_heart_seg_reordered.dcm + ) + find_program(DCIODVFY_EXECUTABLE dciodvfy) if(EXISTS ${DCIODVFY_EXECUTABLE}) @@ -59,6 +69,14 @@ if(EXISTS ${DCIODVFY_EXECUTABLE}) TEST_DEPENDS ${itk2dcm}_makeSEG_multiple_segment_files ) + dcmqi_add_test( + NAME ${itk2dcm}_makeSEG_multiple_segment_files_dciodvfy + MODULE_NAME ${MODULE_NAME} + COMMAND ${DCIODVFY_EXECUTABLE} + ${MODULE_TEMP_DIR}/liver_heart_seg_reordered.dcm + TEST_DEPENDS + ${itk2dcm}_makeSEG_multiple_segment_files_reordered + ) else() message(STATUS "Skipping test '${itk2dcm}_dciodvfy': dciodvfy executable not found") endif() @@ -106,6 +124,21 @@ dcmqi_add_test( ${itk2dcm}_makeSEG_multiple_segment_files ) + dcmqi_add_test( + NAME ${dcm2itk}_makeNRRD_multiple_segment_files_reordered + MODULE_NAME ${MODULE_NAME} + COMMAND ${SEM_LAUNCH_COMMAND} $ + --compare ${BASELINE}/liver_seg.nrrd ${MODULE_TEMP_DIR}/makeNRRD_multiple_segments_reordered-1.nrrd + --compare ${BASELINE}/spine_seg.nrrd ${MODULE_TEMP_DIR}/makeNRRD_multiple_segments_reordered-2.nrrd + --compare ${BASELINE}/heart_seg.nrrd ${MODULE_TEMP_DIR}/makeNRRD_multiple_segments_reordered-3.nrrd + ${dcm2itk}Test + --inputDICOM ${MODULE_TEMP_DIR}/liver_heart_seg_reordered.dcm + --outputDirectory ${MODULE_TEMP_DIR} + --prefix makeNRRD_multiple_segments_reordered + TEST_DEPENDS + ${itk2dcm}_makeSEG_multiple_segment_files_reordered + ) + dcmqi_add_test( NAME seg_meta_roundtrip MODULE_NAME ${MODULE_NAME} diff --git a/apps/seg/itkimage2segimage.cxx b/apps/seg/itkimage2segimage.cxx index daa2854a..75e5e84e 100644 --- a/apps/seg/itkimage2segimage.cxx +++ b/apps/seg/itkimage2segimage.cxx @@ -67,24 +67,26 @@ int main(int argc, char *argv[]) // otherwise, re-order the entries in the segmentAtrributes list to match the order of files in segmentAttributesFileMapping Json::Value reorderedSegmentAttributes; vector fileOrder(segImageFiles.size()); + fill(fileOrder.begin(), fileOrder.end(), -1); vector segmentationsReordered(segImageFiles.size()); for(int filePosition=0;filePosition Date: Tue, 18 Jul 2017 17:41:49 -0400 Subject: [PATCH 056/116] BUG: add segmentAttributesFileMapping Remove the attribute added initially. As before, the question is why our schema validation didn't handle the issue during testing?!.. --- doc/schemas/seg-schema.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/doc/schemas/seg-schema.json b/doc/schemas/seg-schema.json index 70bcead6..4cd3924c 100644 --- a/doc/schemas/seg-schema.json +++ b/doc/schemas/seg-schema.json @@ -85,6 +85,12 @@ ], "default": "" }, + "segmentAttributesFileMapping": { + "type": "array", + "items": { + "type": "string" + } + }, "segmentAttributes": { "type": "array", "items": { @@ -99,9 +105,6 @@ "SegmentAlgorithmType" ], "properties": { - "segmentationFileName": { - "type": "string" - }, "labelID": { "type": "integer", "default": 1 From 735be0094505fd351d66959af8ff3b0b041d1b4c Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Tue, 18 Jul 2017 17:51:34 -0400 Subject: [PATCH 057/116] BUG: fix duplicate test name --- apps/seg/Testing/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/seg/Testing/CMakeLists.txt b/apps/seg/Testing/CMakeLists.txt index 0fbf5a28..5411acfa 100644 --- a/apps/seg/Testing/CMakeLists.txt +++ b/apps/seg/Testing/CMakeLists.txt @@ -70,7 +70,7 @@ if(EXISTS ${DCIODVFY_EXECUTABLE}) ${itk2dcm}_makeSEG_multiple_segment_files ) dcmqi_add_test( - NAME ${itk2dcm}_makeSEG_multiple_segment_files_dciodvfy + NAME ${itk2dcm}_makeSEG_multiple_segment_files_reordered_dciodvfy MODULE_NAME ${MODULE_NAME} COMMAND ${DCIODVFY_EXECUTABLE} ${MODULE_TEMP_DIR}/liver_heart_seg_reordered.dcm From 261ad20a2746f46b04c522718bbbd9821902267a Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Wed, 19 Jul 2017 23:12:09 -0400 Subject: [PATCH 058/116] ENH: updated dcmtk version --- CMakeExternals/DCMTK.cmake | 2 +- apps/sr/tid1500reader.cxx | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/CMakeExternals/DCMTK.cmake b/CMakeExternals/DCMTK.cmake index 42091072..ed41993d 100644 --- a/CMakeExternals/DCMTK.cmake +++ b/CMakeExternals/DCMTK.cmake @@ -25,7 +25,7 @@ if(DEFINED DCMTK_DIR AND NOT EXISTS ${DCMTK_DIR}) endif() if(NOT DEFINED DCMTK_DIR AND NOT ${CMAKE_PROJECT_NAME}_USE_SYSTEM_${proj}) - set(revision_tag "04c996a620d09adec7576ad5f2dba273a44b7411") + set(revision_tag "DCMTK-3.6.2") if(${proj}_REVISION_TAG) set(revision_tag ${${proj}_REVISION_TAG}) endif() diff --git a/apps/sr/tid1500reader.cxx b/apps/sr/tid1500reader.cxx index c09b0e3a..77c59f19 100644 --- a/apps/sr/tid1500reader.cxx +++ b/apps/sr/tid1500reader.cxx @@ -135,10 +135,7 @@ Json::Value getMeasurements(DSRDocument &doc) { localMeasurement["units"] = DSRCodedEntryValue2CodeSequence(measurementValue.getMeasurementUnit()); localMeasurement["quantity"] = DSRCodedEntryValue2CodeSequence(st.getCurrentContentItem().getConceptName()); - // Note: if the second argument is set to true (the default), the - // whole tree will be searched! - // See details in https://github.com/QIICR/dcmqi/issues/261 - if(st.gotoNamedChildNode(CODE_DCM_Derivation, false)){ + if(st.gotoNamedChildNode(CODE_DCM_Derivation)){ localMeasurement["derivationModifier"] = DSRCodedEntryValue2CodeSequence(st.getCurrentContentItem().getCodeValue()); st.gotoParent(); } From 5753ee449ed83b4848044b0607053f97885bb732 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 10 Mar 2017 16:08:42 -0500 Subject: [PATCH 059/116] WIP: refactoring of the multiframe conversion implementation This is an intermediate commit that aims to improve organization of the conversion functionality, motivated by the various standing issues (e.g., #217, #192 and #150) that are difficult to address with the current level of disarray in the code. Some of the ideas are the following: - introduce basic hierarchy of classes to allow reuse of the common functionality between PM and SEG converters (MultiFrameConverter parent for ParametricMapConverter and SegmentationImageConverter) - do not use static functions, instead make better use of converter classes - factor out containers to keep information about ImageVolume and DICOMFrame to simplify conversion and book keeping - improve consistency of naming and code style My plan to proceed with the refactoring is to implement "shadow" functions in the updated classes while keeping the original functions initially, and then phase out the original functions. The current commit is just an intermediate milestone to open this for feedback and keep development history at least somewhat incremental. --- apps/seg/itkimage2segimage.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/seg/itkimage2segimage.cxx b/apps/seg/itkimage2segimage.cxx index 75e5e84e..c459fbbd 100644 --- a/apps/seg/itkimage2segimage.cxx +++ b/apps/seg/itkimage2segimage.cxx @@ -91,7 +91,7 @@ int main(int argc, char *argv[]) segmentations = segmentationsReordered; } - DcmDataset* result = dcmqi::ImageSEGConverter::itkimage2dcmSegmentation(dcmDatasets, segmentations, metadata, skipEmptySlices); + DcmDataset* result = dcmqi::SegmentationImageConverter::itkimage2dcmSegmentation(dcmDatasets, segmentations, metadata, skipEmptySlices); if (result == NULL){ return EXIT_FAILURE; From 882e54d44c9803a46412f83154a0bf445c39bc75 Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Fri, 7 Jul 2017 13:46:40 -0400 Subject: [PATCH 060/116] ENH: fixed extent computation for both paramap and seg (issue #275) * initializeVolumeGeometryFromDICOM now takes functional group instead of an iodImage. No need to be defined as a template method anymore * extracted methods --- include/dcmqi/MultiframeObject.h | 70 +------------ libsrc/MultiframeObject.cpp | 159 ++++++++++++++++++----------- libsrc/ParametricMapObject.cpp | 2 +- libsrc/SegmentationImageObject.cpp | 8 +- 4 files changed, 110 insertions(+), 129 deletions(-) diff --git a/include/dcmqi/MultiframeObject.h b/include/dcmqi/MultiframeObject.h index da029105..00d95dbe 100644 --- a/include/dcmqi/MultiframeObject.h +++ b/include/dcmqi/MultiframeObject.h @@ -85,70 +85,7 @@ class MultiframeObject { // from ITK int initializeVolumeGeometryFromITK(DummyImageType::Pointer); - template - int initializeVolumeGeometryFromDICOM(T iodImage, DcmDataset *dataset, bool useComputedVolumeExtent=false) { - SpacingType spacing; - PointType origin; - DirectionType directions; - SizeType extent; - - FGInterface &fgInterface = iodImage->getFunctionalGroups(); - - if (getImageDirections(fgInterface, directions)) { - cerr << "Failed to get image directions" << endl; - throw -1; - } - - cout << directions << endl; - - double computedSliceSpacing, computedVolumeExtent; - vnl_vector sliceDirection(3); - sliceDirection[0] = directions[0][2]; - sliceDirection[1] = directions[1][2]; - sliceDirection[2] = directions[2][2]; - if (computeVolumeExtent(fgInterface, sliceDirection, origin, computedSliceSpacing, computedVolumeExtent)) { - cerr << "Failed to compute origin and/or slice spacing!" << endl; - throw -1; - } - - if (getDeclaredImageSpacing(fgInterface, spacing)) { - cerr << "Failed to get image spacing from DICOM!" << endl; - throw -1; - } - - const double tolerance = 1e-5; - if(!spacing[2]){ - spacing[2] = computedSliceSpacing; - } else if(fabs(spacing[2]-computedSliceSpacing)>tolerance){ - cerr << "WARNING: Declared slice spacing is significantly different from the one declared in DICOM!" << - " Declared = " << spacing[2] << " Computed = " << computedSliceSpacing << endl; - } - - // Region size - { - OFString str; - if(dataset->findAndGetOFString(DCM_Rows, str).good()) - extent[1] = atoi(str.c_str()); - if(dataset->findAndGetOFString(DCM_Columns, str).good()) - extent[0] = atoi(str.c_str()); - } - - if (useComputedVolumeExtent) { - extent[2] = ceil(computedVolumeExtent/spacing[2])+1; - } else { - extent[2] = fgInterface.getNumberOfFrames(); - } - - cout << extent << endl; - - volumeGeometry.setSpacing(spacing); - volumeGeometry.setOrigin(origin); - volumeGeometry.setExtent(extent); - volumeGeometry.setDirections(directions); - - return EXIT_SUCCESS; - } - + int initializeVolumeGeometryFromDICOM(FGInterface &fgInterface, DcmDataset *dataset); int getImageDirections(FGInterface& fgInterface, DirectionType &dir); int computeVolumeExtent(FGInterface& fgInterface, vnl_vector &sliceDirection, PointType &imageOrigin, @@ -241,7 +178,10 @@ class MultiframeObject { // Mapping from the derivation items SeriesUIDs to InstanceUIDs std::map > derivationSeriesToInstanceUIDs; -}; + vnl_vector getFrameOrigin(FGInterface &fgInterface, int frameId) const; + vnl_vector getFrameOrigin(FGPlanePosPatient *planposfg) const; + + }; #endif //DCMQI_MULTIFRAMEOBJECT_H diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index 93619874..bb22599c 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -77,6 +77,64 @@ int MultiframeObject::initializeVolumeGeometryFromITK(DummyImageType::Pointer im return EXIT_SUCCESS; } +int MultiframeObject::initializeVolumeGeometryFromDICOM(FGInterface &fgInterface, DcmDataset *dataset) { + SpacingType spacing; + PointType origin; + DirectionType directions; + SizeType extent; + + if (getImageDirections(fgInterface, directions)) { + cerr << "Failed to get image directions" << endl; + throw -1; + } + + double computedSliceSpacing, computedVolumeExtent; + vnl_vector sliceDirection(3); + sliceDirection[0] = directions[0][2]; + sliceDirection[1] = directions[1][2]; + sliceDirection[2] = directions[2][2]; + if (computeVolumeExtent(fgInterface, sliceDirection, origin, computedSliceSpacing, computedVolumeExtent)) { + cerr << "Failed to compute origin and/or slice spacing!" << endl; + throw -1; + } + + if (getDeclaredImageSpacing(fgInterface, spacing)) { + cerr << "Failed to get image spacing from DICOM!" << endl; + throw -1; + } + + const double tolerance = 1e-5; + if(!spacing[2]){ + spacing[2] = computedSliceSpacing; + } else if(fabs(spacing[2]-computedSliceSpacing)>tolerance){ + cerr << "WARNING: Declared slice spacing is significantly different from the one declared in DICOM!" << + " Declared = " << spacing[2] << " Computed = " << computedSliceSpacing << endl; + } + + // Region size + { + OFString str; + if(dataset->findAndGetOFString(DCM_Rows, str).good()) + extent[1] = atoi(str.c_str()); + if(dataset->findAndGetOFString(DCM_Columns, str).good()) + extent[0] = atoi(str.c_str()); + } + + + cout << "computed extent: " << computedVolumeExtent << "/" << spacing[2] << endl; + + extent[2] = round(computedVolumeExtent/spacing[2] + 1); + + cout << "Extent:" << extent << endl; + + volumeGeometry.setSpacing(spacing); + volumeGeometry.setOrigin(origin); + volumeGeometry.setExtent(extent); + volumeGeometry.setDirections(directions); + + return EXIT_SUCCESS; +} + // for now, we do not support initialization from JSON only, // and we don't have a way to validate metadata completeness - TODO! bool MultiframeObject::metaDataIsComplete() { @@ -299,38 +357,27 @@ int MultiframeObject::getImageDirections(FGInterface& fgInterface, DirectionType cerr << "Plane Orientation (Patient) is missing, cannot parse input " << endl; return EXIT_FAILURE; } - OFString orientStr; - for(int i=0;i<3;i++){ - if(planorfg->getImageOrientationPatient(orientStr, i).good()){ - rowDirection[i] = atof(orientStr.c_str()); - } else { - cerr << "Failed to get orientation " << i << endl; - return EXIT_FAILURE; - } - } - for(int i=3;i<6;i++){ - if(planorfg->getImageOrientationPatient(orientStr, i).good()){ - colDirection[i-3] = atof(orientStr.c_str()); - } else { - cerr << "Failed to get orientation " << i << endl; - return EXIT_FAILURE; - } + + if(planorfg->getImageOrientationPatient(rowDirection[0], rowDirection[1], rowDirection[2], + colDirection[0], colDirection[1], colDirection[2]).bad()){ + cerr << "Failed to get orientation " << endl; + return EXIT_FAILURE; } + vnl_vector sliceDirection = vnl_cross_3d(rowDirection, colDirection); sliceDirection.normalize(); - cout << "Row direction: " << rowDirection << endl; - cout << "Col direction: " << colDirection << endl; - for(int i=0;i<3;i++){ dir[i][0] = rowDirection[i]; dir[i][1] = colDirection[i]; dir[i][2] = sliceDirection[i]; } - cout << "Z direction: " << sliceDirection << endl; + cout << "Row direction: " << rowDirection << endl; + cout << "Col direction: " << colDirection << endl; + cout << "Z direction : " << sliceDirection << endl; - return 0; + return EXIT_SUCCESS; } int MultiframeObject::computeVolumeExtent(FGInterface& fgInterface, vnl_vector &sliceDirection, @@ -349,24 +396,11 @@ int MultiframeObject::computeVolumeExtent(FGInterface& fgInterface, vnl_vector refOrigin(3); - { - OFBool isPerFrame; - FGPlanePosPatient *planposfg = OFstatic_cast(FGPlanePosPatient*, - fgInterface.get(0, DcmFGTypes::EFG_PLANEPOSPATIENT, isPerFrame)); - for(int j=0;j<3;j++){ - OFString planposStr; - if(planposfg->getImagePositionPatient(planposStr, j).good()){ - refOrigin[j] = atof(planposStr.c_str()); - } else { - cerr << "Failed to read patient position" << endl; - } - } - } + vnl_vector refOrigin = getFrameOrigin(fgInterface, 0); + + unsigned numFrames = fgInterface.getNumberOfFrames(); for(size_t frameId=0;frameId sOrigin; - OFString sOriginStr = ""; - sOrigin.set_size(3); - for(int j=0;j<3;j++){ - OFString planposStr; - if(planposfg->getImagePositionPatient(planposStr, j).good()){ - sOrigin[j] = atof(planposStr.c_str()); - sOriginStr += planposStr; - if(j<2) - sOriginStr+='/'; - } else { - cerr << "Failed to read patient position" << endl; - return EXIT_FAILURE; - } - } + vnl_vector sOrigin(3); + sOrigin = getFrameOrigin(planposfg); + std::ostringstream sstream; + sstream << sOrigin[0] << "/" << sOrigin[1] << "/" << sOrigin[2]; + OFString sOriginStr = sstream.str().c_str(); // check if this frame has already been encountered if(originStr2distance.find(sOriginStr) == originStr2distance.end()){ - vnl_vector difference; - difference.set_size(3); + vnl_vector difference(3); difference[0] = sOrigin[0]-refOrigin[0]; difference[1] = sOrigin[1]-refOrigin[1]; difference[2] = sOrigin[2]-refOrigin[2]; - double dist = dot_product(difference,sliceDirection); + + double dist = dot_product(difference, sliceDirection); frame2overlap[sOriginStr] = 1; originStr2distance[sOriginStr] = dist; assert(originStr2distance.find(sOriginStr) != originStr2distance.end()); originDistances.push_back(dist); - if(frameId==0){ + if(frameId == 0 || dist < minDistance){ minDistance = dist; imageOrigin[0] = sOrigin[0]; imageOrigin[1] = sOrigin[1]; imageOrigin[2] = sOrigin[2]; } - else if(dist MultiframeObject::getFrameOrigin(FGInterface &fgInterface, int frameId) const { + OFBool isPerFrame; + FGPlanePosPatient *planposfg = OFstatic_cast(FGPlanePosPatient*, + fgInterface.get(frameId, DcmFGTypes::EFG_PLANEPOSPATIENT, isPerFrame)); + return getFrameOrigin(planposfg); +} + +vnl_vector MultiframeObject::getFrameOrigin(FGPlanePosPatient *planposfg) const { + vnl_vector origin(3); + if(!planposfg->getImagePositionPatient(origin[0], origin[1], origin[2]).good()){ + cerr << "Failed to read patient position" << endl; + throw -1; + } + return origin; +} + int MultiframeObject::getDeclaredImageSpacing(FGInterface &fgInterface, SpacingType &spacing){ OFBool isPerFrame; FGPixelMeasures *pixelMeasures = OFstatic_cast(FGPixelMeasures*, diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index c162ccc7..cef6513e 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -340,7 +340,7 @@ int ParametricMapObject::initializeFromDICOM(DcmDataset * sourceDataset) { parametricMap = *OFget(&result); - initializeVolumeGeometryFromDICOM(parametricMap, sourceDataset); + initializeVolumeGeometryFromDICOM(parametricMap->getFunctionalGroups(), sourceDataset); // Initialize the image itkImage = volumeGeometry.getITKRepresentation(); diff --git a/libsrc/SegmentationImageObject.cpp b/libsrc/SegmentationImageObject.cpp index 612ea78a..f2552645 100644 --- a/libsrc/SegmentationImageObject.cpp +++ b/libsrc/SegmentationImageObject.cpp @@ -20,7 +20,7 @@ int SegmentationImageObject::initializeFromDICOM(DcmDataset* sourceDataset) { throw -1; } - initializeVolumeGeometryFromDICOM(segmentation, sourceDataset, true); + initializeVolumeGeometryFromDICOM(segmentation->getFunctionalGroups(), sourceDataset); itkImage = volumeGeometry.getITKRepresentation(); iterateOverFramesAndMatchSlices(); initializeMetaDataFromDICOM(sourceDataset); @@ -70,13 +70,19 @@ int SegmentationImageObject::iterateOverFramesAndMatchSlices() { frameOriginPoint[j] = atof(planposStr.c_str()); } } + cerr << "Image size: " << segment2image[segmentId]->GetBufferedRegion().GetSize() << endl; if(!segment2image[segmentId]->TransformPhysicalPointToIndex(frameOriginPoint, frameOriginIndex)){ cerr << "ERROR: Frame " << frameId << " origin " << frameOriginPoint << " is outside image geometry!" << frameOriginIndex << endl; cerr << "Image size: " << segment2image[segmentId]->GetBufferedRegion().GetSize() << endl; throw -1; + } else { + cout << "Frame " << frameId << " origin " << frameOriginPoint << + " is inside image geometry!" << frameOriginIndex << endl; + cout << "Image size: " << segment2image[segmentId]->GetBufferedRegion().GetSize() << endl; } + unsigned slice = frameOriginIndex[2]; unpackFrameAndWriteSegmentImage(frameId, segmentId, slice); From c3ba545b91fb1c5ccad3b6755ce07594262647c2 Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Fri, 7 Jul 2017 14:28:45 -0400 Subject: [PATCH 061/116] ENH: reusing initializeVolumeGeometry and reorganizing * renamed method createParametricMap to createDICOMParametricMap and added createITKParametricMap --- include/dcmqi/MultiframeObject.h | 4 +- include/dcmqi/ParametricMapObject.h | 8 +- libsrc/MultiframeObject.cpp | 16 ++-- libsrc/ParametricMapObject.cpp | 114 ++++++++++++++-------------- libsrc/SegmentationImageObject.cpp | 4 +- 5 files changed, 71 insertions(+), 75 deletions(-) diff --git a/include/dcmqi/MultiframeObject.h b/include/dcmqi/MultiframeObject.h index 00d95dbe..c8ef1d71 100644 --- a/include/dcmqi/MultiframeObject.h +++ b/include/dcmqi/MultiframeObject.h @@ -85,7 +85,7 @@ class MultiframeObject { // from ITK int initializeVolumeGeometryFromITK(DummyImageType::Pointer); - int initializeVolumeGeometryFromDICOM(FGInterface &fgInterface, DcmDataset *dataset); + int initializeVolumeGeometryFromDICOM(FGInterface &fgInterface); int getImageDirections(FGInterface& fgInterface, DirectionType &dir); int computeVolumeExtent(FGInterface& fgInterface, vnl_vector &sliceDirection, PointType &imageOrigin, @@ -140,8 +140,6 @@ class MultiframeObject { Json::Value metaDataJson; // Multiframe DICOM object representation - // probably not needed, since need object-specific DCMTK class in - // derived classes DcmDataset* dcmRepresentation; // probably not needed at this level, since for SEG each segment will diff --git a/include/dcmqi/ParametricMapObject.h b/include/dcmqi/ParametricMapObject.h index 08dd7f53..cc242741 100644 --- a/include/dcmqi/ParametricMapObject.h +++ b/include/dcmqi/ParametricMapObject.h @@ -46,8 +46,7 @@ class ParametricMapObject : public MultiframeObject { int initializeFromDICOM(DcmDataset * sourceDataset); - template - void initializeMetaDataFromDICOM(T doc); + void initializeMetaDataFromDICOM(); Float32ITKImageType::Pointer getITKRepresentation() const { return itkImage; @@ -59,13 +58,14 @@ class ParametricMapObject : public MultiframeObject { typedef itk::MinimumMaximumImageCalculator MinMaxCalculatorType; int initializeVolumeGeometry(); - int createParametricMap(); + int createDICOMParametricMap(); + int createITKParametricMap(); int initializeCompositeContext(); int initializeFrameAnatomyFG(); int initializeRWVMFG(); int initializeFrames(vector >&); - // Functional groups initialization + // Functional groups initialization // Functional groups specific to PM: // - Shared diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index bb22599c..30849a60 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -77,7 +77,7 @@ int MultiframeObject::initializeVolumeGeometryFromITK(DummyImageType::Pointer im return EXIT_SUCCESS; } -int MultiframeObject::initializeVolumeGeometryFromDICOM(FGInterface &fgInterface, DcmDataset *dataset) { +int MultiframeObject::initializeVolumeGeometryFromDICOM(FGInterface &fgInterface) { SpacingType spacing; PointType origin; DirectionType directions; @@ -111,15 +111,11 @@ int MultiframeObject::initializeVolumeGeometryFromDICOM(FGInterface &fgInterface " Declared = " << spacing[2] << " Computed = " << computedSliceSpacing << endl; } - // Region size - { - OFString str; - if(dataset->findAndGetOFString(DCM_Rows, str).good()) - extent[1] = atoi(str.c_str()); - if(dataset->findAndGetOFString(DCM_Columns, str).good()) - extent[0] = atoi(str.c_str()); - } - + OFString str; + if(dcmRepresentation->findAndGetOFString(DCM_Rows, str).good()) + extent[1] = atoi(str.c_str()); + if(dcmRepresentation->findAndGetOFString(DCM_Columns, str).good()) + extent[0] = atoi(str.c_str()); cout << "computed extent: " << computedVolumeExtent << "/" << spacing[2] << endl; diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index cef6513e..25ff580b 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -34,7 +34,7 @@ int ParametricMapObject::initializeFromITK(Float32ITKImageType::Pointer inputIma initializeContentIdentification(); // TODO: consider creating parametric map object after all FGs are initialized instead - createParametricMap(); + createDICOMParametricMap(); // populate metadata about patient/study, from derivation // datasets or from metadata @@ -82,7 +82,7 @@ int ParametricMapObject::initializeVolumeGeometry() { MultiframeObject::initializeVolumeGeometryFromITK(caster->GetOutput()); } else { - + MultiframeObject::initializeVolumeGeometryFromDICOM(parametricMap->getFunctionalGroups()); } return EXIT_SUCCESS; } @@ -98,7 +98,7 @@ int ParametricMapObject::updateMetaDataFromDICOM(std::vector dcmLi return EXIT_SUCCESS; } -int ParametricMapObject::createParametricMap() { +int ParametricMapObject::createDICOMParametricMap() { // create Parametric map object @@ -140,6 +140,48 @@ int ParametricMapObject::createParametricMap() { return EXIT_SUCCESS; } +int ParametricMapObject::createITKParametricMap() { + + initializeVolumeGeometry(); + + // Initialize the image + itkImage = volumeGeometry.getITKRepresentation(); + + DPMParametricMapIOD::FramesType obj = parametricMap->getFrames(); + if (OFCondition* pCondition = OFget(&obj)) { + throw -1; + } + + DPMParametricMapIOD::Frames frames = *OFget >(&obj); + + FGInterface &fgInterface = parametricMap->getFunctionalGroups(); + for(int frameId=0;frameIdSetPixel(index, frame[pixelPosition]); + } + } + } + return EXIT_SUCCESS; +} + int ParametricMapObject::initializeCompositeContext() { // TODO: should this be done in the parent? if(derivationDcmDatasets.size()){ @@ -328,6 +370,9 @@ int ParametricMapObject::initializeRWVMFG() { int ParametricMapObject::initializeFromDICOM(DcmDataset * sourceDataset) { + sourceRepresentationType = DICOM_REPR; + dcmRepresentation = sourceDataset; + DcmRLEDecoderRegistration::registerCodecs(); OFLogger dcemfinfLogger = OFLog::getLogger("qiicr.apps"); @@ -340,76 +385,31 @@ int ParametricMapObject::initializeFromDICOM(DcmDataset * sourceDataset) { parametricMap = *OFget(&result); - initializeVolumeGeometryFromDICOM(parametricMap->getFunctionalGroups(), sourceDataset); - - // Initialize the image - itkImage = volumeGeometry.getITKRepresentation(); - - DPMParametricMapIOD::FramesType obj = parametricMap->getFrames(); - if (OFCondition* pCondition = OFget(&obj)) { - throw -1; - } - - DPMParametricMapIOD::Frames frames = *OFget >(&obj); - - FGInterface &fgInterface = parametricMap->getFunctionalGroups(); - for(int frameId=0;frameIdSetPixel(index, frame[pixelPosition]); - } - } - } - - initializeMetaDataFromDICOM(parametricMap); + initializeMetaDataFromDICOM(); + createITKParametricMap(); return EXIT_SUCCESS; } -template -void ParametricMapObject::initializeMetaDataFromDICOM(T doc) { - // TODO: move shared information retrieval to parent class +void ParametricMapObject::initializeMetaDataFromDICOM() { OFString temp; - doc->getSeries().getSeriesDescription(temp); + parametricMap->getSeries().getSeriesDescription(temp); metaDataJson["SeriesDescription"] = temp.c_str(); - doc->getSeries().getSeriesNumber(temp); + parametricMap->getSeries().getSeriesNumber(temp); metaDataJson["SeriesNumber"] = temp.c_str(); - doc->getIODGeneralImageModule().getInstanceNumber(temp); + parametricMap->getIODGeneralImageModule().getInstanceNumber(temp); metaDataJson["InstanceNumber"] = temp.c_str(); using namespace dcmqi; - doc->getSeries().getBodyPartExamined(temp); + parametricMap->getSeries().getBodyPartExamined(temp); metaDataJson["BodyPartExamined"] = temp.c_str(); - if (doc->getNumberOfFrames() > 0) { - FGInterface& fg = doc->getFunctionalGroups(); + if (parametricMap->getNumberOfFrames() > 0) { + FGInterface& fg = parametricMap->getFunctionalGroups(); FGRealWorldValueMapping* rw = OFstatic_cast(FGRealWorldValueMapping*, fg.get(0, DcmFGTypes::EFG_REALWORLDVALUEMAPPING)); if (rw->getRealWorldValueMapping().size() > 0) { diff --git a/libsrc/SegmentationImageObject.cpp b/libsrc/SegmentationImageObject.cpp index f2552645..3b073f87 100644 --- a/libsrc/SegmentationImageObject.cpp +++ b/libsrc/SegmentationImageObject.cpp @@ -6,6 +6,8 @@ int SegmentationImageObject::initializeFromDICOM(DcmDataset* sourceDataset) { + dcmRepresentation = sourceDataset; + // TODO: add SegmentationImageObject to namespace dcmqi using namespace dcmqi; @@ -20,7 +22,7 @@ int SegmentationImageObject::initializeFromDICOM(DcmDataset* sourceDataset) { throw -1; } - initializeVolumeGeometryFromDICOM(segmentation->getFunctionalGroups(), sourceDataset); + initializeVolumeGeometryFromDICOM(segmentation->getFunctionalGroups()); itkImage = volumeGeometry.getITKRepresentation(); iterateOverFramesAndMatchSlices(); initializeMetaDataFromDICOM(sourceDataset); From a06b3919d6761ab1a6999ed9e1ac9a8f2f0462b8 Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Fri, 7 Jul 2017 16:36:49 -0400 Subject: [PATCH 062/116] ENH: added comment for computation of extent including reference --- include/dcmqi/MultiframeObject.h | 2 +- libsrc/MultiframeObject.cpp | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/include/dcmqi/MultiframeObject.h b/include/dcmqi/MultiframeObject.h index c8ef1d71..5ff93a3f 100644 --- a/include/dcmqi/MultiframeObject.h +++ b/include/dcmqi/MultiframeObject.h @@ -89,7 +89,7 @@ class MultiframeObject { int getImageDirections(FGInterface& fgInterface, DirectionType &dir); int computeVolumeExtent(FGInterface& fgInterface, vnl_vector &sliceDirection, PointType &imageOrigin, - double &sliceSpacing, double &sliceExtent); + double &sliceSpacing, double &volumeExtent); int getDeclaredImageSpacing(FGInterface &fgInterface, SpacingType &spacing); // initialize attributes of the composite context that are common for all multiframe objects diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index 30849a60..a5b02ec3 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -119,6 +119,9 @@ int MultiframeObject::initializeVolumeGeometryFromDICOM(FGInterface &fgInterface cout << "computed extent: " << computedVolumeExtent << "/" << spacing[2] << endl; + // difference between the origins of the first and last frames, divide by SpacingBetweenSlices + // (which must be declared for meaningful interpretation of the SEG geometry, as amended in CP-1427), and add 1. + // see also: https://github.com/QIICR/dcmqi/issues/275 extent[2] = round(computedVolumeExtent/spacing[2] + 1); cout << "Extent:" << extent << endl; @@ -377,7 +380,7 @@ int MultiframeObject::getImageDirections(FGInterface& fgInterface, DirectionType } int MultiframeObject::computeVolumeExtent(FGInterface& fgInterface, vnl_vector &sliceDirection, - PointType &imageOrigin, double &sliceSpacing, double &sliceExtent) { + PointType &imageOrigin, double &sliceSpacing, double &volumeExtent) { // Size // Rows/Columns can be read directly from the respective attributes // For number of slices, consider that all segments must have the same number of frames. @@ -462,10 +465,10 @@ int MultiframeObject::computeVolumeExtent(FGInterface& fgInterface, vnl_vector::const_iterator it=frame2overlap.begin(); it!=frame2overlap.end();++it){ From 4cc12cf37ada34359c76ec77ee148ad2259df8e8 Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Tue, 11 Jul 2017 12:08:06 -0400 Subject: [PATCH 063/116] ENH: separated matching frames with segmentId/slice and writing segment images --- include/dcmqi/SegmentationImageObject.h | 6 +- libsrc/SegmentationImageObject.cpp | 101 +++++++++++++----------- 2 files changed, 58 insertions(+), 49 deletions(-) diff --git a/include/dcmqi/SegmentationImageObject.h b/include/dcmqi/SegmentationImageObject.h index 29487529..35d36467 100644 --- a/include/dcmqi/SegmentationImageObject.h +++ b/include/dcmqi/SegmentationImageObject.h @@ -48,10 +48,10 @@ class SegmentationImageObject : public MultiframeObject { DcmSegmentation* segmentation; - int iterateOverFramesAndMatchSlices(); - - int unpackFrameAndWriteSegmentImage(const size_t& frameId, const Uint16& segmentId, const unsigned int& slice); + // returns a vector with a size equal to the number of frames each holding segmentID and sliceNumber + vector< pair > matchFramesWithSegmendIDandSliceNumber(FGInterface &fgInterface); + int unpackFramesAndWriteSegmentImage(vector< pair > matchingSegmentIDsAndSliceNumbers); int initializeMetaDataFromDICOM(DcmDataset*); int createNewSegmentImage(Uint16 segmentId); diff --git a/libsrc/SegmentationImageObject.cpp b/libsrc/SegmentationImageObject.cpp index 3b073f87..ce55cbe0 100644 --- a/libsrc/SegmentationImageObject.cpp +++ b/libsrc/SegmentationImageObject.cpp @@ -24,28 +24,28 @@ int SegmentationImageObject::initializeFromDICOM(DcmDataset* sourceDataset) { initializeVolumeGeometryFromDICOM(segmentation->getFunctionalGroups()); itkImage = volumeGeometry.getITKRepresentation(); - iterateOverFramesAndMatchSlices(); + vector< pair > matchedFramesWithSlices = matchFramesWithSegmendIDandSliceNumber( + segmentation->getFunctionalGroups()); + unpackFramesAndWriteSegmentImage(matchedFramesWithSlices); initializeMetaDataFromDICOM(sourceDataset); return EXIT_SUCCESS; } -int SegmentationImageObject::iterateOverFramesAndMatchSlices() { - // Iterate over frames, find the matching slice for each of the frames based on - // ImagePositionPatient, set non-zero pixels to the segment number. Notify - // about pixels that are initialized more than once. +vector< pair > SegmentationImageObject::matchFramesWithSegmendIDandSliceNumber(FGInterface &fgInterface) { - FGInterface &fgInterface = segmentation->getFunctionalGroups(); + vector< pair > matchedFramesWithSlices; for(size_t frameId=0;frameId create duplicate of itkimage and set all values to 0 if(segment2image.find(segmentId) == segment2image.end()) { createNewSegmentImage(segmentId); } + // get plane position for current frame FGPlanePosPatient *planposfg = OFstatic_cast(FGPlanePosPatient*,fgInterface.get(frameId, DcmFGTypes::EFG_PLANEPOSPATIENT, isPerFrame)); assert(planposfg); @@ -72,57 +74,64 @@ int SegmentationImageObject::iterateOverFramesAndMatchSlices() { frameOriginPoint[j] = atof(planposStr.c_str()); } } - cerr << "Image size: " << segment2image[segmentId]->GetBufferedRegion().GetSize() << endl; + clog << "Image size: " << segment2image[segmentId]->GetBufferedRegion().GetSize() << endl; + // make sure that frame origin is in image geometry if itkimage if(!segment2image[segmentId]->TransformPhysicalPointToIndex(frameOriginPoint, frameOriginIndex)){ cerr << "ERROR: Frame " << frameId << " origin " << frameOriginPoint << " is outside image geometry!" << frameOriginIndex << endl; cerr << "Image size: " << segment2image[segmentId]->GetBufferedRegion().GetSize() << endl; throw -1; - } else { - cout << "Frame " << frameId << " origin " << frameOriginPoint << - " is inside image geometry!" << frameOriginIndex << endl; - cout << "Image size: " << segment2image[segmentId]->GetBufferedRegion().GetSize() << endl; } - unsigned slice = frameOriginIndex[2]; - - unpackFrameAndWriteSegmentImage(frameId, segmentId, slice); + pair temp; + temp.first = segmentId; + temp.second = frameOriginIndex[2]; + matchedFramesWithSlices.push_back(temp); } - return EXIT_SUCCESS; + return matchedFramesWithSlices; } -int SegmentationImageObject::unpackFrameAndWriteSegmentImage(const size_t& frameId, const Uint16& segmentId, - const unsigned int& slice) { - const DcmIODTypes::Frame *frame = segmentation->getFrame(frameId); - - DcmIODTypes::Frame *unpackedFrame = NULL; - - if(segmentation->getSegmentationType() == DcmSegTypes::ST_BINARY) - unpackedFrame = DcmSegUtils::unpackBinaryFrame(frame, - volumeGeometry.extent[1], // Rows - volumeGeometry.extent[0]); // Cols - else - unpackedFrame = new DcmIODTypes::Frame(*frame); - - for(unsigned row=0; row < volumeGeometry.extent[1]; row++){ - for(unsigned col=0; col < volumeGeometry.extent[0]; col++){ - ShortImageType::PixelType pixel; - unsigned bitCnt = row * volumeGeometry.extent[0] + col; - pixel = unpackedFrame->pixData[bitCnt]; - - if(pixel!=0){ - ShortImageType::IndexType index; - index[0] = col; - index[1] = row; - index[2] = slice; - segment2image[segmentId]->SetPixel(index, segmentId); +int SegmentationImageObject::unpackFramesAndWriteSegmentImage( + vector< pair > matchingSegmentIDsAndSliceNumbers) { + + for (std::vector< pair >::iterator it = matchingSegmentIDsAndSliceNumbers.begin(); + it != matchingSegmentIDsAndSliceNumbers.end(); ++it) { + + unsigned frameId = it - matchingSegmentIDsAndSliceNumbers.begin(); + Uint16 segmentId = (*it).first; + long sliceNumber = (*it).second; + + const DcmIODTypes::Frame *frame = segmentation->getFrame(frameId); + + DcmIODTypes::Frame *unpackedFrame = NULL; + + if (segmentation->getSegmentationType() == DcmSegTypes::ST_BINARY) + unpackedFrame = DcmSegUtils::unpackBinaryFrame(frame, + volumeGeometry.extent[1], // Rows + volumeGeometry.extent[0]); // Cols + else + unpackedFrame = new DcmIODTypes::Frame(*frame); + + for (unsigned row = 0; row < volumeGeometry.extent[1]; row++) { + for (unsigned col = 0; col < volumeGeometry.extent[0]; col++) { + ShortImageType::PixelType pixel; + unsigned bitCnt = row * volumeGeometry.extent[0] + col; + pixel = unpackedFrame->pixData[bitCnt]; + + if (pixel != 0) { + ShortImageType::IndexType index; + index[0] = col; + index[1] = row; + index[2] = sliceNumber; + segment2image[segmentId]->SetPixel(index, segmentId); + } } } - } - if(unpackedFrame != NULL) - delete unpackedFrame; + if (unpackedFrame != NULL) + delete unpackedFrame; + } return EXIT_SUCCESS; } @@ -171,7 +180,7 @@ int SegmentationImageObject::initializeMetaDataFromDICOM(DcmDataset *segDataset) return EXIT_SUCCESS; } -Json::Value SegmentationImageObject::getSegmentAttributesMetadata() { +Json::Value SegmentationImageObject::getSegmentAttributesMetadata() { using namespace dcmqi; From 6f582f3fcb7a2cc996480b23c9a9052189e2bea7 Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Tue, 11 Jul 2017 13:56:59 -0400 Subject: [PATCH 064/116] ENH: renamed method, removed some comments and simplified --- include/dcmqi/SegmentationImageObject.h | 2 +- libsrc/SegmentationImageObject.cpp | 22 +++++++--------------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/include/dcmqi/SegmentationImageObject.h b/include/dcmqi/SegmentationImageObject.h index 35d36467..1790bf89 100644 --- a/include/dcmqi/SegmentationImageObject.h +++ b/include/dcmqi/SegmentationImageObject.h @@ -49,7 +49,7 @@ class SegmentationImageObject : public MultiframeObject { DcmSegmentation* segmentation; // returns a vector with a size equal to the number of frames each holding segmentID and sliceNumber - vector< pair > matchFramesWithSegmendIDandSliceNumber(FGInterface &fgInterface); + vector< pair > matchFramesWithSegmendIdAndSliceNumber(FGInterface &fgInterface); int unpackFramesAndWriteSegmentImage(vector< pair > matchingSegmentIDsAndSliceNumbers); int initializeMetaDataFromDICOM(DcmDataset*); diff --git a/libsrc/SegmentationImageObject.cpp b/libsrc/SegmentationImageObject.cpp index ce55cbe0..7430b7a0 100644 --- a/libsrc/SegmentationImageObject.cpp +++ b/libsrc/SegmentationImageObject.cpp @@ -24,7 +24,7 @@ int SegmentationImageObject::initializeFromDICOM(DcmDataset* sourceDataset) { initializeVolumeGeometryFromDICOM(segmentation->getFunctionalGroups()); itkImage = volumeGeometry.getITKRepresentation(); - vector< pair > matchedFramesWithSlices = matchFramesWithSegmendIDandSliceNumber( + vector< pair > matchedFramesWithSlices = matchFramesWithSegmendIdAndSliceNumber( segmentation->getFunctionalGroups()); unpackFramesAndWriteSegmentImage(matchedFramesWithSlices); initializeMetaDataFromDICOM(sourceDataset); @@ -32,9 +32,9 @@ int SegmentationImageObject::initializeFromDICOM(DcmDataset* sourceDataset) { return EXIT_SUCCESS; } -vector< pair > SegmentationImageObject::matchFramesWithSegmendIDandSliceNumber(FGInterface &fgInterface) { +vector< pair > SegmentationImageObject::matchFramesWithSegmendIdAndSliceNumber(FGInterface &fgInterface) { - vector< pair > matchedFramesWithSlices; + vector< pair > matchedFramesWithSlices; for(size_t frameId=0;frameId > SegmentationImageObject::matchFramesWithSegmendIDa assert(frameContent); #endif - // check for valid segment ID Uint16 segmentId = getSegmentId(fgInterface, frameId); // WARNING: this is needed only for David's example, which numbers @@ -55,17 +54,14 @@ vector< pair > SegmentationImageObject::matchFramesWithSegmendIDa throw -1; } - // if not found -> create duplicate of itkimage and set all values to 0 if(segment2image.find(segmentId) == segment2image.end()) { createNewSegmentImage(segmentId); } - // get plane position for current frame FGPlanePosPatient *planposfg = OFstatic_cast(FGPlanePosPatient*,fgInterface.get(frameId, DcmFGTypes::EFG_PLANEPOSPATIENT, isPerFrame)); assert(planposfg); - // get string representation of the frame origin ShortImageType::PointType frameOriginPoint; ShortImageType::IndexType frameOriginIndex; for(int j=0;j<3;j++){ @@ -76,18 +72,13 @@ vector< pair > SegmentationImageObject::matchFramesWithSegmendIDa } clog << "Image size: " << segment2image[segmentId]->GetBufferedRegion().GetSize() << endl; - // make sure that frame origin is in image geometry if itkimage if(!segment2image[segmentId]->TransformPhysicalPointToIndex(frameOriginPoint, frameOriginIndex)){ cerr << "ERROR: Frame " << frameId << " origin " << frameOriginPoint << " is outside image geometry!" << frameOriginIndex << endl; cerr << "Image size: " << segment2image[segmentId]->GetBufferedRegion().GetSize() << endl; throw -1; } - - pair temp; - temp.first = segmentId; - temp.second = frameOriginIndex[2]; - matchedFramesWithSlices.push_back(temp); + matchedFramesWithSlices.push_back(make_pair(segmentId, frameOriginIndex[2])); } return matchedFramesWithSlices; } @@ -106,12 +97,13 @@ int SegmentationImageObject::unpackFramesAndWriteSegmentImage( DcmIODTypes::Frame *unpackedFrame = NULL; - if (segmentation->getSegmentationType() == DcmSegTypes::ST_BINARY) + if (segmentation->getSegmentationType() == DcmSegTypes::ST_BINARY) { unpackedFrame = DcmSegUtils::unpackBinaryFrame(frame, volumeGeometry.extent[1], // Rows volumeGeometry.extent[0]); // Cols - else + } else { unpackedFrame = new DcmIODTypes::Frame(*frame); + } for (unsigned row = 0; row < volumeGeometry.extent[1]; row++) { for (unsigned col = 0; col < volumeGeometry.extent[0]; col++) { From 78c404ccea1247f1b26e0ee0f4e31ca7352b6c85 Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Tue, 11 Jul 2017 14:09:10 -0400 Subject: [PATCH 065/116] ENH: added method for creating CodeSequenceMacro from metadata attribute --- include/dcmqi/ParametricMapObject.h | 2 ++ libsrc/ParametricMapObject.cpp | 29 +++++++++++++---------------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/include/dcmqi/ParametricMapObject.h b/include/dcmqi/ParametricMapObject.h index cc242741..1ed00777 100644 --- a/include/dcmqi/ParametricMapObject.h +++ b/include/dcmqi/ParametricMapObject.h @@ -79,6 +79,8 @@ class ParametricMapObject : public MultiframeObject { private: DPMParametricMapIOD* parametricMap; + + CodeSequenceMacro *createCodeSequenceFromMetadata(const string &codeName) const; }; diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 25ff580b..3b808577 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -113,7 +113,6 @@ int ParametricMapObject::createDICOMParametricMap() { equipmentInfoModule, contentIdentificationMacro, "VOLUME", "QUANTITY", - DPMTypes::CQ_RESEARCH); // TODO: look into the following, check with @che85 on the purpose of this line! if (OFCondition* pCondition = OFget(&obj)) { @@ -253,8 +252,8 @@ int ParametricMapObject::initializeRWVMFG() { if(metaDataJson.isMember("MeasurementUnitsCode")){ CodeSequenceMacro& unitsCodeDcmtk = realWorldValueMappingItem->getMeasurementUnitsCode(); unitsCodeDcmtk.set(metaDataJson["MeasurementUnitsCode"]["CodeValue"].asCString(), - metaDataJson["MeasurementUnitsCode"]["CodingSchemeDesignator"].asCString(), - metaDataJson["MeasurementUnitsCode"]["CodeMeaning"].asCString()); + metaDataJson["MeasurementUnitsCode"]["CodingSchemeDesignator"].asCString(), + metaDataJson["MeasurementUnitsCode"]["CodeMeaning"].asCString()); cout << "Measurements units initialized to " << dcmqi::Helper::codeSequenceMacroToString(unitsCodeDcmtk); @@ -265,16 +264,13 @@ int ParametricMapObject::initializeRWVMFG() { /* if(metaDataJson.isMember("QuantityValueCode")){ ContentItemMacro* item = initializeContentItemMacro(CodeSequenceMacro("G-C1C6", "SRT", "Quantity"), - dcmqi::Helper::jsonToCodeSequenceMacro(metaDataJson["QuantityValueCode"])); + dcmqi::Helper::jsonToCodeSequenceMacro(metaDataJson["QuantityValueCode"])); realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(item); }*/ ContentItemMacro* quantity = new ContentItemMacro; CodeSequenceMacro* qCodeName = new CodeSequenceMacro("G-C1C6", "SRT", "Quantity"); - CodeSequenceMacro* qSpec = new CodeSequenceMacro( - metaDataJson["QuantityValueCode"]["CodeValue"].asCString(), - metaDataJson["QuantityValueCode"]["CodingSchemeDesignator"].asCString(), - metaDataJson["QuantityValueCode"]["CodeMeaning"].asCString()); + CodeSequenceMacro* qSpec = createCodeSequenceFromMetadata("QuantityValueCode"); if (!quantity || !qSpec || !qCodeName) { @@ -297,10 +293,7 @@ int ParametricMapObject::initializeRWVMFG() { if(metaDataJson.isMember("MeasurementMethodCode")){ ContentItemMacro* measureMethod = new ContentItemMacro; CodeSequenceMacro* qCodeName = new CodeSequenceMacro("G-C306", "SRT", "Measurement Method"); - CodeSequenceMacro* qSpec = new CodeSequenceMacro( - metaDataJson["MeasurementMethodCode"]["CodeValue"].asCString(), - metaDataJson["MeasurementMethodCode"]["CodingSchemeDesignator"].asCString(), - metaDataJson["MeasurementMethodCode"]["CodeMeaning"].asCString()); + CodeSequenceMacro* qSpec = createCodeSequenceFromMetadata("MeasurementMethodCode"); if (!measureMethod || !qSpec || !qCodeName) { @@ -324,10 +317,7 @@ int ParametricMapObject::initializeRWVMFG() { if(metaDataJson.isMember("ModelFittingMethodCode")){ ContentItemMacro* fittingMethod = new ContentItemMacro; CodeSequenceMacro* qCodeName = new CodeSequenceMacro("113241", "DCM", "Model fitting method"); - CodeSequenceMacro* qSpec = new CodeSequenceMacro( - metaDataJson["ModelFittingMethodCode"]["CodeValue"].asCString(), - metaDataJson["ModelFittingMethodCode"]["CodingSchemeDesignator"].asCString(), - metaDataJson["ModelFittingMethodCode"]["CodeMeaning"].asCString()); + CodeSequenceMacro* qSpec = createCodeSequenceFromMetadata("ModelFittingMethodCode"); if (!fittingMethod || !qSpec || !qCodeName) { @@ -367,6 +357,13 @@ int ParametricMapObject::initializeRWVMFG() { return EXIT_SUCCESS; } +CodeSequenceMacro *ParametricMapObject::createCodeSequenceFromMetadata(const string &codeName) const { + return new CodeSequenceMacro( + metaDataJson[codeName]["CodeValue"].asCString(), + metaDataJson[codeName]["CodingSchemeDesignator"].asCString(), + metaDataJson[codeName]["CodeMeaning"].asCString()); +} + int ParametricMapObject::initializeFromDICOM(DcmDataset * sourceDataset) { From 0b7878a17ceee058346c79fc74b9d75db1205da9 Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Tue, 11 Jul 2017 14:41:28 -0400 Subject: [PATCH 066/116] ENH: fixed type and renamed type ShortImageType to ShortITKImageType * according to the way it's done for parametric maps --- include/dcmqi/SegmentationImageObject.h | 10 +++++----- libsrc/SegmentationImageObject.cpp | 18 +++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/include/dcmqi/SegmentationImageObject.h b/include/dcmqi/SegmentationImageObject.h index 1790bf89..a95825e4 100644 --- a/include/dcmqi/SegmentationImageObject.h +++ b/include/dcmqi/SegmentationImageObject.h @@ -26,7 +26,7 @@ class SegmentationImageObject : public MultiframeObject { public: typedef short ShortPixelType; - typedef itk::Image ShortImageType; + typedef itk::Image ShortITKImageType; SegmentationImageObject(){ segmentation = NULL; @@ -34,22 +34,22 @@ class SegmentationImageObject : public MultiframeObject { int initializeFromDICOM(DcmDataset* sourceDataset); - map getITKRepresentation() const { + map getITKRepresentation() const { // TODO: think about naming return segment2image; } protected: // Data containers specific to this object - ShortImageType::Pointer itkImage; + ShortITKImageType::Pointer itkImage; // ITK images corresponding to the individual segments - map segment2image; + map segment2image; DcmSegmentation* segmentation; // returns a vector with a size equal to the number of frames each holding segmentID and sliceNumber - vector< pair > matchFramesWithSegmendIdAndSliceNumber(FGInterface &fgInterface); + vector< pair > matchFramesWithSegmentIdAndSliceNumber(FGInterface &fgInterface); int unpackFramesAndWriteSegmentImage(vector< pair > matchingSegmentIDsAndSliceNumbers); int initializeMetaDataFromDICOM(DcmDataset*); diff --git a/libsrc/SegmentationImageObject.cpp b/libsrc/SegmentationImageObject.cpp index 7430b7a0..c648df92 100644 --- a/libsrc/SegmentationImageObject.cpp +++ b/libsrc/SegmentationImageObject.cpp @@ -23,8 +23,8 @@ int SegmentationImageObject::initializeFromDICOM(DcmDataset* sourceDataset) { } initializeVolumeGeometryFromDICOM(segmentation->getFunctionalGroups()); - itkImage = volumeGeometry.getITKRepresentation(); - vector< pair > matchedFramesWithSlices = matchFramesWithSegmendIdAndSliceNumber( + itkImage = volumeGeometry.getITKRepresentation(); + vector< pair > matchedFramesWithSlices = matchFramesWithSegmentIdAndSliceNumber( segmentation->getFunctionalGroups()); unpackFramesAndWriteSegmentImage(matchedFramesWithSlices); initializeMetaDataFromDICOM(sourceDataset); @@ -32,7 +32,7 @@ int SegmentationImageObject::initializeFromDICOM(DcmDataset* sourceDataset) { return EXIT_SUCCESS; } -vector< pair > SegmentationImageObject::matchFramesWithSegmendIdAndSliceNumber(FGInterface &fgInterface) { +vector< pair > SegmentationImageObject::matchFramesWithSegmentIdAndSliceNumber(FGInterface &fgInterface) { vector< pair > matchedFramesWithSlices; @@ -62,8 +62,8 @@ vector< pair > SegmentationImageObject::matchFramesWithSegmendIdA OFstatic_cast(FGPlanePosPatient*,fgInterface.get(frameId, DcmFGTypes::EFG_PLANEPOSPATIENT, isPerFrame)); assert(planposfg); - ShortImageType::PointType frameOriginPoint; - ShortImageType::IndexType frameOriginIndex; + ShortITKImageType::PointType frameOriginPoint; + ShortITKImageType::IndexType frameOriginIndex; for(int j=0;j<3;j++){ OFString planposStr; if(planposfg->getImagePositionPatient(planposStr, j).good()){ @@ -107,12 +107,12 @@ int SegmentationImageObject::unpackFramesAndWriteSegmentImage( for (unsigned row = 0; row < volumeGeometry.extent[1]; row++) { for (unsigned col = 0; col < volumeGeometry.extent[0]; col++) { - ShortImageType::PixelType pixel; + ShortITKImageType::PixelType pixel; unsigned bitCnt = row * volumeGeometry.extent[0] + col; pixel = unpackedFrame->pixData[bitCnt]; if (pixel != 0) { - ShortImageType::IndexType index; + ShortITKImageType::IndexType index; index[0] = col; index[1] = row; index[2] = sliceNumber; @@ -129,11 +129,11 @@ int SegmentationImageObject::unpackFramesAndWriteSegmentImage( } int SegmentationImageObject::createNewSegmentImage(Uint16 segmentId) { - typedef itk::ImageDuplicator DuplicatorType; + typedef itk::ImageDuplicator DuplicatorType; DuplicatorType::Pointer dup = DuplicatorType::New(); dup->SetInputImage(itkImage); dup->Update(); - ShortImageType::Pointer newSegmentImage = dup->GetOutput(); + ShortITKImageType::Pointer newSegmentImage = dup->GetOutput(); newSegmentImage->FillBuffer(0); segment2image[segmentId] = newSegmentImage; return EXIT_SUCCESS; From 92e56c260de009a0351c31e2737a1cd2c8362700 Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Tue, 11 Jul 2017 14:46:10 -0400 Subject: [PATCH 067/116] ENH: added missing type change --- libsrc/SegmentationImageConverter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsrc/SegmentationImageConverter.cpp b/libsrc/SegmentationImageConverter.cpp index 0f222a46..e626bfc6 100644 --- a/libsrc/SegmentationImageConverter.cpp +++ b/libsrc/SegmentationImageConverter.cpp @@ -17,7 +17,7 @@ namespace dcmqi { ss << styledWriter.write(seg.getMetaDataJson()); - return pair , string>(seg.getITKRepresentation(), + return pair , string>(seg.getITKRepresentation(), ss.str()); }; From b740f937f47c5d6d2ce7f21391a4fa6222c3f4ea Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Tue, 11 Jul 2017 22:48:06 -0400 Subject: [PATCH 068/116] ENH: factored out creation of itkimage from frames --- include/dcmqi/ParametricMapObject.h | 1 + libsrc/ParametricMapObject.cpp | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/include/dcmqi/ParametricMapObject.h b/include/dcmqi/ParametricMapObject.h index 1ed00777..2db16dc7 100644 --- a/include/dcmqi/ParametricMapObject.h +++ b/include/dcmqi/ParametricMapObject.h @@ -60,6 +60,7 @@ class ParametricMapObject : public MultiframeObject { int initializeVolumeGeometry(); int createDICOMParametricMap(); int createITKParametricMap(); + int createITKImageFromFrames(FGInterface&, DPMParametricMapIOD::Frames); int initializeCompositeContext(); int initializeFrameAnatomyFG(); int initializeRWVMFG(); diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 3b808577..a3017f29 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -151,9 +151,15 @@ int ParametricMapObject::createITKParametricMap() { throw -1; } + FGInterface &fgInterface = parametricMap->getFunctionalGroups(); DPMParametricMapIOD::Frames frames = *OFget >(&obj); - FGInterface &fgInterface = parametricMap->getFunctionalGroups(); + createITKImageFromFrames(fgInterface, frames); + return EXIT_SUCCESS; +} + +int ParametricMapObject::createITKImageFromFrames(FGInterface &fgInterface, + DPMParametricMapIOD::Frames frames) { for(int frameId=0;frameId Date: Fri, 28 Jul 2017 10:48:43 -0400 Subject: [PATCH 069/116] ENH: started initialization from itk to DICOM for segmentations - moved initialization of EquipmentInfo to specific class since it's different for PM and SEG - auto resolving headers and source files in CMakeLists.txt --- include/dcmqi/MultiframeObject.h | 3 - include/dcmqi/ParametricMapObject.h | 5 + include/dcmqi/SegmentationImageConverter.h | 3 + include/dcmqi/SegmentationImageObject.h | 37 ++++- libsrc/CMakeLists.txt | 57 +------ libsrc/MultiframeObject.cpp | 23 +-- libsrc/ParametricMapObject.cpp | 26 ++- libsrc/SegmentationImageConverter.cpp | 16 +- libsrc/SegmentationImageObject.cpp | 174 ++++++++++++++++++++- 9 files changed, 248 insertions(+), 96 deletions(-) diff --git a/include/dcmqi/MultiframeObject.h b/include/dcmqi/MultiframeObject.h index 5ff93a3f..238c8bb9 100644 --- a/include/dcmqi/MultiframeObject.h +++ b/include/dcmqi/MultiframeObject.h @@ -79,7 +79,6 @@ class MultiframeObject { // what this function does depends on whether we are coming from // DICOM or from ITK. No parameters, since all it does is exchange // between DICOM and MetaData - int initializeEquipmentInfo(); int initializeContentIdentification(); // from ITK @@ -151,8 +150,6 @@ class MultiframeObject { OFVector sourceDcmDatasets; // Common components present in the derived classes - // TODO: check whether both PM and SEG use Enh module or not, refactor based on that - IODEnhGeneralEquipmentModule::EquipmentInfo equipmentInfoModule; ContentIdentificationMacro contentIdentificationMacro; IODMultiframeDimensionModule dimensionsModule; diff --git a/include/dcmqi/ParametricMapObject.h b/include/dcmqi/ParametricMapObject.h index 2db16dc7..66c0cfe4 100644 --- a/include/dcmqi/ParametricMapObject.h +++ b/include/dcmqi/ParametricMapObject.h @@ -12,7 +12,10 @@ #include #include #include + #include +#include + #include "MultiframeObject.h" @@ -57,6 +60,7 @@ class ParametricMapObject : public MultiframeObject { Float32ToDummyCasterType; typedef itk::MinimumMaximumImageCalculator MinMaxCalculatorType; + int initializeEquipmentInfo(); int initializeVolumeGeometry(); int createDICOMParametricMap(); int createITKParametricMap(); @@ -66,6 +70,7 @@ class ParametricMapObject : public MultiframeObject { int initializeRWVMFG(); int initializeFrames(vector >&); + IODEnhGeneralEquipmentModule::EquipmentInfo enhancedEquipmentInfoModule; // Functional groups initialization // Functional groups specific to PM: diff --git a/include/dcmqi/SegmentationImageConverter.h b/include/dcmqi/SegmentationImageConverter.h index 00d18aee..75c9d506 100644 --- a/include/dcmqi/SegmentationImageConverter.h +++ b/include/dcmqi/SegmentationImageConverter.h @@ -32,6 +32,9 @@ typedef itk::LabelImageToLabelMapFilter LabelToLabelMapFilterTyp namespace dcmqi { + DcmDataset* itkimage2paramapReplacement(vector dcmDatasets, vector segmentations, + const string &metaData, bool skipEmptySlices); + pair , string> dcmSegmentation2itkimageReplacement(DcmDataset *segDataset); class SegmentationImageConverter : public MultiframeConverter { diff --git a/include/dcmqi/SegmentationImageObject.h b/include/dcmqi/SegmentationImageObject.h index a95825e4..13240350 100644 --- a/include/dcmqi/SegmentationImageObject.h +++ b/include/dcmqi/SegmentationImageObject.h @@ -8,6 +8,8 @@ // DCMQI includes #include "MultiframeObject.h" #include "Helper.h" +#include "QIICRConstants.h" + // DCMTK includes #include @@ -19,6 +21,7 @@ // ITK includes #include +#include class SegmentationImageObject : public MultiframeObject { @@ -26,39 +29,61 @@ class SegmentationImageObject : public MultiframeObject { public: typedef short ShortPixelType; - typedef itk::Image ShortITKImageType; + typedef itk::Image ShortImageType; SegmentationImageObject(){ segmentation = NULL; } + int initializeFromITK(vector, const string &, vector, bool); + int initializeFromDICOM(DcmDataset* sourceDataset); - map getITKRepresentation() const { + int getDICOMRepresentation(DcmDataset& dcm){ + if(segmentation) + CHECK_COND(segmentation->writeDataset(dcm)); + }; + + map getITKRepresentation() const { // TODO: think about naming return segment2image; } protected: + + typedef itk::CastImageFilter ShortToDummyCasterType; // Data containers specific to this object - ShortITKImageType::Pointer itkImage; + ShortImageType::Pointer itkImage; + vector itkImages; // ITK images corresponding to the individual segments - map segment2image; + map segment2image; DcmSegmentation* segmentation; - // returns a vector with a size equal to the number of frames each holding segmentID and sliceNumber + int initializeEquipmentInfo(); + int initializeVolumeGeometry(); + int initializeCompositeContext(); + + int initializeFrames(vector > slice2derimg); + + + // returns a vector with a size equal to the number of frames each holding segmentID and sliceNumber vector< pair > matchFramesWithSegmentIdAndSliceNumber(FGInterface &fgInterface); int unpackFramesAndWriteSegmentImage(vector< pair > matchingSegmentIDsAndSliceNumbers); int initializeMetaDataFromDICOM(DcmDataset*); - + int createDICOMSegmentation(); int createNewSegmentImage(Uint16 segmentId); Json::Value getSegmentAttributesMetadata(); + IODGeneralEquipmentModule::EquipmentInfo generalEquipmentInfoModule; + Uint16 getSegmentId(FGInterface &fgInterface, size_t frameId) const; + + vector > getSliceMapForSegmentation2DerivationImage(const vector dcmDatasets, + const ShortImageType::Pointer &labelImage); }; diff --git a/libsrc/CMakeLists.txt b/libsrc/CMakeLists.txt index 17d842be..29195d97 100644 --- a/libsrc/CMakeLists.txt +++ b/libsrc/CMakeLists.txt @@ -1,61 +1,16 @@ #----------------------------------------------------------------------------- -set(INCLUDE_DIR ../include/dcmqi) - -set(HDRS - ${INCLUDE_DIR}/DICOMFrame.h - ${INCLUDE_DIR}/ImageVolume.h - ${INCLUDE_DIR}/MultiframeConverter.h - ${INCLUDE_DIR}/Helper.h - ${INCLUDE_DIR}/SegmentationImageConverter.h - ${INCLUDE_DIR}/ParametricMapConverter.h - ${INCLUDE_DIR}/JSONMetaInformationHandlerBase.h - ${INCLUDE_DIR}/JSONParametricMapMetaInformationHandler.h - ${INCLUDE_DIR}/JSONSegmentationMetaInformationHandler.h - ${INCLUDE_DIR}/SegmentAttributes.h - ) - -set(SRCS - DICOMFrame.cpp - ImageVolume.cpp - MultiframeConverter.cpp - Helper.cpp - SegmentationImageConverter.cpp - ParametricMapConverter.cpp - JSONMetaInformationHandlerBase.cpp - JSONParametricMapMetaInformationHandler.cpp - JSONSegmentationMetaInformationHandler.cpp - SegmentAttributes.cpp - ) - -set(ADDITIONAL_HDRS - ${INCLUDE_DIR}/DICOMFrame.h - ${INCLUDE_DIR}/ImageVolume.h - ${INCLUDE_DIR}/SegmentVolume.h - ${INCLUDE_DIR}/MultiframeObject.h - ${INCLUDE_DIR}/ParametricMapObject.h - ${INCLUDE_DIR}/SegmentationImageObject.h - ${INCLUDE_DIR}/ImageVolumeGeometry.h - ${INCLUDE_DIR}/Exceptions.h - ${INCLUDE_DIR}/framesorter.h - ${INCLUDE_DIR}/preproc.h - ${INCLUDE_DIR}/QIICRConstants.h - ${INCLUDE_DIR}/QIICRUIDs.h +file(GLOB_RECURSE HDRS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + ${PROJECT_SOURCE_DIR}/include/dcmqi/*.h ) -set(ADDITIONAL_SRCS - DICOMFrame.cpp - ImageVolume.cpp - SegmentVolume.cpp - MultiframeObject.cpp - ParametricMapObject.cpp - SegmentationImageObject.cpp - ImageVolumeGeometry.cpp +file(GLOB_RECURSE SRCS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + ${PROJECT_SOURCE_DIR}/libsrc/*.cpp ) if(DCMQI_BUILTIN_JSONCPP) - list(APPEND ADDITIONAL_SRCS + list(APPEND SRCS ${DCMQI_SOURCE_DIR}/jsoncpp/jsoncpp.cpp ) set(JsonCpp_INCLUDE_DIR ${DCMQI_SOURCE_DIR}/jsoncpp) @@ -66,8 +21,6 @@ set(lib_name dcmqi) add_library(${lib_name} STATIC ${HDRS} ${SRCS} - ${ADDITIONAL_HDRS} - ${ADDITIONAL_SRCS} ) if(DCMQI_LIBRARY_PROPERTIES) diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index a5b02ec3..f22a67b9 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -3,7 +3,6 @@ // #include -#include "dcmqi/QIICRConstants.h" #include "dcmqi/MultiframeObject.h" int MultiframeObject::initializeFromDICOM(std::vector sourceDataset) { @@ -16,22 +15,6 @@ int MultiframeObject::initializeMetaDataFromString(const std::string &metaDataSt return EXIT_SUCCESS; } -int MultiframeObject::initializeEquipmentInfo() { - if(sourceRepresentationType == ITK_REPR){ - equipmentInfoModule = IODEnhGeneralEquipmentModule::EquipmentInfo(QIICR_MANUFACTURER, QIICR_DEVICE_SERIAL_NUMBER, - QIICR_MANUFACTURER_MODEL_NAME, QIICR_SOFTWARE_VERSIONS); - /* - equipmentInfoModule.m_Manufacturer = QIICR_MANUFACTURER; - equipmentInfoModule.m_DeviceSerialNumber = QIICR_DEVICE_SERIAL_NUMBER; - equipmentInfoModule.m_ManufacturerModelName = QIICR_MANUFACTURER_MODEL_NAME; - equipmentInfoModule.m_SoftwareVersions = QIICR_SOFTWARE_VERSIONS; - */ - - } else { // DICOM_REPR - } - return EXIT_SUCCESS; -} - int MultiframeObject::initializeContentIdentification() { if(sourceRepresentationType == ITK_REPR){ @@ -207,9 +190,9 @@ int MultiframeObject::mapVolumeSlicesToDICOMFrames(ImageVolumeGeometry& volume, vector > &slice2frame){ slice2frame.resize(volume.extent[2]); - for(int d=0;d::const_iterator it=dcmDatasets.begin();it!=dcmDatasets.end();++it) { Uint32 numFrames; - DcmDataset* dcm = dcmDatasets[d]; + DcmDataset* dcm = *it; if(dcm->findAndGetUint32(DCM_NumberOfFrames, numFrames).good()){ // this is a multiframe object for(int f=0;f derivationFrames, CodeSequenceMacro purposeOfReferenceCode, CodeSequenceMacro derivationCode){ @@ -525,7 +507,6 @@ int MultiframeObject::getDeclaredImageSpacing(FGInterface &fgInterface, SpacingT return EXIT_SUCCESS; } - int MultiframeObject::initializeSeriesSpecificAttributes(IODGeneralSeriesModule& generalSeriesModule, IODGeneralImageModule& generalImageModule){ diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index a3017f29..777a7853 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -3,10 +3,8 @@ // #include -#include +#include "dcmqi/QIICRConstants.h" #include "dcmqi/ParametricMapObject.h" -#include -#include int ParametricMapObject::initializeFromITK(Float32ITKImageType::Pointer inputImage, @@ -98,6 +96,24 @@ int ParametricMapObject::updateMetaDataFromDICOM(std::vector dcmLi return EXIT_SUCCESS; } +int ParametricMapObject::initializeEquipmentInfo() { + if(sourceRepresentationType == ITK_REPR){ + enhancedEquipmentInfoModule = IODEnhGeneralEquipmentModule::EquipmentInfo(QIICR_MANUFACTURER, + QIICR_DEVICE_SERIAL_NUMBER, + QIICR_MANUFACTURER_MODEL_NAME, + QIICR_SOFTWARE_VERSIONS); + /* + enhancedEquipmentInfoModule.m_Manufacturer = QIICR_MANUFACTURER; + enhancedEquipmentInfoModule.m_DeviceSerialNumber = QIICR_DEVICE_SERIAL_NUMBER; + enhancedEquipmentInfoModule.m_ManufacturerModelName = QIICR_MANUFACTURER_MODEL_NAME; + enhancedEquipmentInfoModule.m_SoftwareVersions = QIICR_SOFTWARE_VERSIONS; + */ + + } else { // DICOM_REPR + } + return EXIT_SUCCESS; +} + int ParametricMapObject::createDICOMParametricMap() { // create Parametric map object @@ -110,7 +126,7 @@ int ParametricMapObject::createDICOMParametricMap() { metaDataJson["InstanceNumber"].asCString(), volumeGeometry.extent[1], volumeGeometry.extent[0], - equipmentInfoModule, + enhancedEquipmentInfoModule, contentIdentificationMacro, "VOLUME", "QUANTITY", DPMTypes::CQ_RESEARCH); @@ -209,7 +225,7 @@ int ParametricMapObject::initializeCompositeContext() { OFTrue, // Patient OFTrue, // Study OFTrue, // Frame of reference - OFTrue)); // Series + OFFalse)); // Series } else { // TODO: once we support passing of composite context in metadata, propagate it diff --git a/libsrc/SegmentationImageConverter.cpp b/libsrc/SegmentationImageConverter.cpp index e626bfc6..631d15d3 100644 --- a/libsrc/SegmentationImageConverter.cpp +++ b/libsrc/SegmentationImageConverter.cpp @@ -7,6 +7,19 @@ using namespace std; namespace dcmqi { + DcmDataset* itkimage2paramapReplacement(vector dcmDatasets, + vector segmentations, + const string &metaData, + bool skipEmptySlices) { + SegmentationImageObject seg; + seg.initializeFromITK(segmentations, metaData, dcmDatasets, skipEmptySlices); + + DcmDataset *output = new DcmDataset(); + seg.getDICOMRepresentation(*output); + + return output; + } + pair , string> dcmSegmentation2itkimageReplacement(DcmDataset *segDataset) { SegmentationImageObject seg; @@ -17,7 +30,7 @@ namespace dcmqi { ss << styledWriter.write(seg.getMetaDataJson()); - return pair , string>(seg.getITKRepresentation(), + return pair , string>(seg.getITKRepresentation(), ss.str()); }; @@ -131,7 +144,6 @@ namespace dcmqi { if((*vI).size()>0) hasDerivationImages = true; - FGPlanePosPatient* fgppp = FGPlanePosPatient::createMinimal("1","1","1"); FGFrameContent* fgfc = new FGFrameContent(); FGDerivationImage* fgder = new FGDerivationImage(); diff --git a/libsrc/SegmentationImageObject.cpp b/libsrc/SegmentationImageObject.cpp index c648df92..ab167603 100644 --- a/libsrc/SegmentationImageObject.cpp +++ b/libsrc/SegmentationImageObject.cpp @@ -4,6 +4,136 @@ #include "dcmqi/SegmentationImageObject.h" + +int SegmentationImageObject::initializeFromITK(vector itkSegmentations, + const string &metaDataStr, vector derivationDatasets, + bool skipEmptySlices) { + + setDerivationDatasets(derivationDatasets); + + sourceRepresentationType = ITK_REPR; + + // TODO: think about the following code. Do all input segmentations have the same extent? + itkImages = itkSegmentations; + + initializeMetaDataFromString(metaDataStr); + +// if(!metaDataIsComplete()){ +// updateMetaDataFromDICOM(derivationDatasets); +// } + + initializeVolumeGeometry(); + + // NB: the sequence of steps initializing different components of the object parallels that + // in the original converter function. It probably makes sense to revisit the sequence + // of these steps. It does not necessarily need to happen in this order. + initializeEquipmentInfo(); + initializeContentIdentification(); + +// OFString frameOfRefUID; +// if(!segdoc->getFrameOfReference().getFrameOfReferenceUID(frameOfRefUID).good()){ +// // TODO: add FoR UID to the metadata JSON and check that before generating one! +// char frameOfRefUIDchar[128]; +// dcmGenerateUniqueIdentifier(frameOfRefUIDchar, QIICR_UID_ROOT); +// CHECK_COND(segdoc->getFrameOfReference().setFrameOfReferenceUID(frameOfRefUIDchar)); +// } +// + + createDICOMSegmentation(); + + // populate metadata about patient/study, from derivation + // datasets or from metadata + initializeCompositeContext(); + + initializeSeriesSpecificAttributes(segmentation->getSeries(), + segmentation->getGeneralImage()); + + initializePixelMeasuresFG(); + CHECK_COND(segmentation->addForAllFrames(pixelMeasuresFG)); + + initializePlaneOrientationFG(); + CHECK_COND(segmentation->addForAllFrames(planeOrientationPatientFG)); + + // Mapping from parametric map volume slices to the DICOM frames + vector > slice2frame; + + // TODO: not sure about volumeGeometry since it was created from the first segmentation volume only + mapVolumeSlicesToDICOMFrames(volumeGeometry, derivationDatasets, slice2frame); + + initializeCommonInstanceReferenceModule(segmentation->getCommonInstanceReference(), slice2frame); + + // NB this assumes all segmentation files have the same dimensions; alternatively, need to + // do this operation for each segmentation file + vector > slice2derimg = getSliceMapForSegmentation2DerivationImage(derivationDatasets, + itkSegmentations[0]); + + // initializeFrames(slice2derimg); + + return EXIT_SUCCESS; + +} + +int SegmentationImageObject::initializeEquipmentInfo() { + if(sourceRepresentationType == ITK_REPR){ + generalEquipmentInfoModule = IODGeneralEquipmentModule::EquipmentInfo(QIICR_MANUFACTURER, + QIICR_DEVICE_SERIAL_NUMBER, + QIICR_MANUFACTURER_MODEL_NAME, + QIICR_SOFTWARE_VERSIONS); + } else { // DICOM_REPR + } + return EXIT_SUCCESS; +} + +int SegmentationImageObject::initializeVolumeGeometry() { + if(sourceRepresentationType == ITK_REPR){ + + ShortToDummyCasterType::Pointer caster = + ShortToDummyCasterType::New(); + // right now assuming that all input segmentations have the same volume extent + // TODO: make this work when input segmentation have different geometry + caster->SetInput(itkImages[0]); + caster->Update(); + + MultiframeObject::initializeVolumeGeometryFromITK(caster->GetOutput()); + } else { + MultiframeObject::initializeVolumeGeometryFromDICOM(segmentation->getFunctionalGroups()); + } + return EXIT_SUCCESS; +} + +int SegmentationImageObject::createDICOMSegmentation() { + + DcmSegmentation::createBinarySegmentation(segmentation, + volumeGeometry.extent[1], + volumeGeometry.extent[0], + generalEquipmentInfoModule, + contentIdentificationMacro); // content identification + + /* Initialize dimension module */ + IODMultiframeDimensionModule &mfdim = segmentation->getDimensions(); + OFString dimUID = dcmqi::Helper::generateUID(); + CHECK_COND(mfdim.addDimensionIndex(DCM_ReferencedSegmentNumber, dimUID, DCM_SegmentIdentificationSequence, + DcmTag(DCM_ReferencedSegmentNumber).getTagName())); + CHECK_COND(mfdim.addDimensionIndex(DCM_ImagePositionPatient, dimUID, + DCM_PlanePositionSequence, "ImagePositionPatient")); + + return EXIT_SUCCESS; +} + +int SegmentationImageObject::initializeCompositeContext() { + if(derivationDcmDatasets.size()){ + CHECK_COND(segmentation->import(*derivationDcmDatasets[0], + OFTrue, // Patient + OFTrue, // Study + OFTrue, // Frame of reference + OFFalse)); // Series + } else { + // TODO: once we support passing of composite context in metadata, propagate it into segmentation here + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} + int SegmentationImageObject::initializeFromDICOM(DcmDataset* sourceDataset) { dcmRepresentation = sourceDataset; @@ -23,7 +153,7 @@ int SegmentationImageObject::initializeFromDICOM(DcmDataset* sourceDataset) { } initializeVolumeGeometryFromDICOM(segmentation->getFunctionalGroups()); - itkImage = volumeGeometry.getITKRepresentation(); + itkImage = volumeGeometry.getITKRepresentation(); vector< pair > matchedFramesWithSlices = matchFramesWithSegmentIdAndSliceNumber( segmentation->getFunctionalGroups()); unpackFramesAndWriteSegmentImage(matchedFramesWithSlices); @@ -62,8 +192,8 @@ vector< pair > SegmentationImageObject::matchFramesWithSegmentIdAn OFstatic_cast(FGPlanePosPatient*,fgInterface.get(frameId, DcmFGTypes::EFG_PLANEPOSPATIENT, isPerFrame)); assert(planposfg); - ShortITKImageType::PointType frameOriginPoint; - ShortITKImageType::IndexType frameOriginIndex; + ShortImageType::PointType frameOriginPoint; + ShortImageType::IndexType frameOriginIndex; for(int j=0;j<3;j++){ OFString planposStr; if(planposfg->getImagePositionPatient(planposStr, j).good()){ @@ -107,12 +237,12 @@ int SegmentationImageObject::unpackFramesAndWriteSegmentImage( for (unsigned row = 0; row < volumeGeometry.extent[1]; row++) { for (unsigned col = 0; col < volumeGeometry.extent[0]; col++) { - ShortITKImageType::PixelType pixel; + ShortImageType::PixelType pixel; unsigned bitCnt = row * volumeGeometry.extent[0] + col; pixel = unpackedFrame->pixData[bitCnt]; if (pixel != 0) { - ShortITKImageType::IndexType index; + ShortImageType::IndexType index; index[0] = col; index[1] = row; index[2] = sliceNumber; @@ -129,11 +259,11 @@ int SegmentationImageObject::unpackFramesAndWriteSegmentImage( } int SegmentationImageObject::createNewSegmentImage(Uint16 segmentId) { - typedef itk::ImageDuplicator DuplicatorType; + typedef itk::ImageDuplicator DuplicatorType; DuplicatorType::Pointer dup = DuplicatorType::New(); dup->SetInputImage(itkImage); dup->Update(); - ShortITKImageType::Pointer newSegmentImage = dup->GetOutput(); + ShortImageType::Pointer newSegmentImage = dup->GetOutput(); newSegmentImage->FillBuffer(0); segment2image[segmentId] = newSegmentImage; return EXIT_SUCCESS; @@ -295,4 +425,34 @@ Uint16 SegmentationImageObject::getSegmentId(FGInterface &fgInterface, size_t fr throw -1; } return segmentId; +} + +// AF: I could not quickly figure out how to template this function over image type - suggestions are welcomed! +vector > SegmentationImageObject::getSliceMapForSegmentation2DerivationImage(const vector dcmDatasets, + const ShortImageType::Pointer &labelImage) { + // Find mapping from the segmentation slice number to the derivation image + // Assume that orientation of the segmentation is the same as the source series + unsigned numLabelSlices = labelImage->GetLargestPossibleRegion().GetSize()[2]; + vector > slice2derimg(numLabelSlices); + for(size_t i=0;ifindAndGetOFString(DCM_ImagePositionPatient, ippStr, j)); + ippPoint[j] = atof(ippStr.c_str()); + } + // NB: this will map slice origin to index without failure, unless the point is out + // of FOV bounds! + // TODO: do a better job matching volume slices by considering comparison of the origin + // and orientation of the slice within tolerance + if(!labelImage->TransformPhysicalPointToIndex(ippPoint, ippIndex)){ + //cout << "image position: " << ippPoint << endl; + //cerr << "ippIndex: " << ippIndex << endl; + // if certain DICOM instance does not map to a label slice, just skip it + continue; + } + slice2derimg[ippIndex[2]].push_back(i); + } + return slice2derimg; } \ No newline at end of file From e702155ab07bc61bde8c5b7a47dfb23f02f3b22d Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Fri, 28 Jul 2017 15:07:56 -0400 Subject: [PATCH 070/116] ENH: proceeding with further extraction and generalizing code * removed additional parameter ImageVolumeGeometry since it uses the volumeGeometry member of the class * extracted duplicated code into one method * extracted boolean method for checking if derivation FG is required * templated getSliceMapForSegmentation2DerivationImage over imagetype * extracted boolean method for checking if hasDerivationImages --- include/dcmqi/MultiframeObject.h | 10 +- include/dcmqi/ParametricMapObject.h | 5 +- include/dcmqi/SegmentationImageObject.h | 10 +- libsrc/MultiframeObject.cpp | 36 ++- libsrc/ParametricMapObject.cpp | 26 ++- libsrc/SegmentationImageObject.cpp | 281 +++++++++++++++++++++++- 6 files changed, 316 insertions(+), 52 deletions(-) diff --git a/include/dcmqi/MultiframeObject.h b/include/dcmqi/MultiframeObject.h index 238c8bb9..a4eab1b6 100644 --- a/include/dcmqi/MultiframeObject.h +++ b/include/dcmqi/MultiframeObject.h @@ -106,11 +106,10 @@ class MultiframeObject { int initializePlaneOrientationFG(); int initializeCommonInstanceReferenceModule(IODCommonInstanceReferenceModule &, vector >&); - int mapVolumeSlicesToDICOMFrames(ImageVolumeGeometry&, const vector, - vector >&); + int mapVolumeSlicesToDICOMFrames(const vector, + vector >&); - static std::vector findIntersectingSlices(ImageVolumeGeometry& volume, - dcmqi::DICOMFrame& frame); + static std::vector findIntersectingSlices(ImageVolumeGeometry& volume, dcmqi::DICOMFrame& frame); int addDerivationItemToDerivationFG(FGDerivationImage* fgder, set frames, CodeSequenceMacro purposeOfReferenceCode = CodeSequenceMacro("121322","DCM","Source image for image processing operation"), @@ -118,6 +117,9 @@ class MultiframeObject { void insertDerivationSeriesInstance(string seriesUID, string instanceUID); + int findIntersectingSlicesAndAddDerivationSeriesInstance( + vector > &slice2frame, DcmDataset *dcm, int frameNo=0); + int setDerivationDatasets(std::vector derivationDatasets){ for(std::vector::const_iterator vIt=derivationDatasets.begin(); vIt!=derivationDatasets.end();++vIt) diff --git a/include/dcmqi/ParametricMapObject.h b/include/dcmqi/ParametricMapObject.h index 66c0cfe4..8b96c630 100644 --- a/include/dcmqi/ParametricMapObject.h +++ b/include/dcmqi/ParametricMapObject.h @@ -70,7 +70,10 @@ class ParametricMapObject : public MultiframeObject { int initializeRWVMFG(); int initializeFrames(vector >&); - IODEnhGeneralEquipmentModule::EquipmentInfo enhancedEquipmentInfoModule; + bool isDerivationFGRequired(vector >& slice2frame); + + + IODEnhGeneralEquipmentModule::EquipmentInfo enhancedEquipmentInfoModule; // Functional groups initialization // Functional groups specific to PM: diff --git a/include/dcmqi/SegmentationImageObject.h b/include/dcmqi/SegmentationImageObject.h index 13240350..ee6023e4 100644 --- a/include/dcmqi/SegmentationImageObject.h +++ b/include/dcmqi/SegmentationImageObject.h @@ -65,10 +65,7 @@ class SegmentationImageObject : public MultiframeObject { int initializeVolumeGeometry(); int initializeCompositeContext(); - int initializeFrames(vector > slice2derimg); - - - // returns a vector with a size equal to the number of frames each holding segmentID and sliceNumber + // returns a vector with a size equal to the number of frames each holding segmentID and sliceNumber vector< pair > matchFramesWithSegmentIdAndSliceNumber(FGInterface &fgInterface); int unpackFramesAndWriteSegmentImage(vector< pair > matchingSegmentIDsAndSliceNumbers); @@ -82,8 +79,11 @@ class SegmentationImageObject : public MultiframeObject { Uint16 getSegmentId(FGInterface &fgInterface, size_t frameId) const; + template vector > getSliceMapForSegmentation2DerivationImage(const vector dcmDatasets, - const ShortImageType::Pointer &labelImage); + const ImageTypePointer &labelImage); + + bool hasDerivationImages(vector > &slice2derimg) const; }; diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index f22a67b9..20e239f3 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -185,10 +185,9 @@ ContentItemMacro* MultiframeObject::initializeContentItemMacro(CodeSequenceMacro // populates slice2frame vector that maps each of the volume slices to the set of frames that // are considered as derivation dataset // TODO: this function assumes that all of the derivation datasets are images, which is probably ok -int MultiframeObject::mapVolumeSlicesToDICOMFrames(ImageVolumeGeometry& volume, - const vector dcmDatasets, +int MultiframeObject::mapVolumeSlicesToDICOMFrames(const vector dcmDatasets, vector > &slice2frame){ - slice2frame.resize(volume.extent[2]); + slice2frame.resize(volumeGeometry.extent[2]); for(vector::const_iterator it=dcmDatasets.begin();it!=dcmDatasets.end();++it) { Uint32 numFrames; @@ -196,27 +195,26 @@ int MultiframeObject::mapVolumeSlicesToDICOMFrames(ImageVolumeGeometry& volume, if(dcm->findAndGetUint32(DCM_NumberOfFrames, numFrames).good()){ // this is a multiframe object for(int f=0;f intersectingSlices = findIntersectingSlices(volume, frame); - - for(int s=0;sinsertDerivationSeriesInstance(frame.getSeriesUID(), frame.getInstanceUID()); - } + findIntersectingSlicesAndAddDerivationSeriesInstance(slice2frame, dcm, f+1); } } else { - dcmqi::DICOMFrame frame(dcm); - vector intersectingSlices = findIntersectingSlices(volume, frame); - - for(int s=0;sinsertDerivationSeriesInstance(frame.getSeriesUID(), frame.getInstanceUID()); - } + findIntersectingSlicesAndAddDerivationSeriesInstance(slice2frame, dcm); } } + return EXIT_SUCCESS; +} +int MultiframeObject::findIntersectingSlicesAndAddDerivationSeriesInstance( + vector > &slice2frame, DcmDataset *dcm, int frameNo) { + dcmqi::DICOMFrame frame(dcm, frameNo); + vector intersectingSlices = findIntersectingSlices(volumeGeometry, frame); + + if(intersectingSlices.size()) + insertDerivationSeriesInstance(frame.getSeriesUID(), frame.getInstanceUID()); + + for(int s=0;svolumeGeometry, derivationDatasets, slice2frame); + mapVolumeSlicesToDICOMFrames(derivationDatasets, slice2frame); initializeCommonInstanceReferenceModule(this->parametricMap->getCommonInstanceReference(), slice2frame); @@ -343,7 +343,7 @@ int ParametricMapObject::initializeRWVMFG() { if (!fittingMethod || !qSpec || !qCodeName) { - return NULL; + return EXIT_FAILURE; } fittingMethod->getEntireConceptNameCodeSequence().push_back(qCodeName); @@ -495,7 +495,19 @@ void ParametricMapObject::initializeMetaDataFromDICOM() { } } +bool ParametricMapObject::isDerivationFGRequired(vector >& slice2frame) { + // if there is a derivation item for at least one frame, DerivationImageSequence must be present for every frame. + unsigned nSlices = itkImage->GetLargestPossibleRegion().GetSize()[2]; + for (unsigned long sliceNumber=0;sliceNumber >& slice2frame){ + FGPlanePosPatient* fgppp = FGPlanePosPatient::createMinimal("1","1","1"); FGFrameContent* fgfc = new FGFrameContent(); FGDerivationImage* fgder = new FGDerivationImage(); @@ -503,15 +515,7 @@ int ParametricMapObject::initializeFrames(vectorGetLargestPossibleRegion().GetSize()[2]; - // if there is a derivation item for at least one frame, DerivationImageSequence must be present - // for every frame. - bool derivationFGRequired = false; - for (unsigned long sliceNumber = 0;sliceNumber < nSlices; sliceNumber++) { - if(!slice2frame[sliceNumber].empty()){ - derivationFGRequired = true; - break; - } - } + bool derivationFGRequired = isDerivationFGRequired(slice2frame); for (unsigned long sliceNumber = 0;sliceNumber < nSlices; sliceNumber++) { diff --git a/libsrc/SegmentationImageObject.cpp b/libsrc/SegmentationImageObject.cpp index ab167603..4f8b8be5 100644 --- a/libsrc/SegmentationImageObject.cpp +++ b/libsrc/SegmentationImageObject.cpp @@ -54,25 +54,282 @@ int SegmentationImageObject::initializeFromITK(vector i initializePlaneOrientationFG(); CHECK_COND(segmentation->addForAllFrames(planeOrientationPatientFG)); - // Mapping from parametric map volume slices to the DICOM frames vector > slice2frame; + mapVolumeSlicesToDICOMFrames(derivationDatasets, slice2frame); - // TODO: not sure about volumeGeometry since it was created from the first segmentation volume only - mapVolumeSlicesToDICOMFrames(volumeGeometry, derivationDatasets, slice2frame); + // NB: this assumes all segmentation files have the same dimensions; alternatively, need to + // do this operation for each segmentation file + vector > slice2derimg = getSliceMapForSegmentation2DerivationImage(derivationDatasets, + itkSegmentations[0]); initializeCommonInstanceReferenceModule(segmentation->getCommonInstanceReference(), slice2frame); - // NB this assumes all segmentation files have the same dimensions; alternatively, need to - // do this operation for each segmentation file - vector > slice2derimg = getSliceMapForSegmentation2DerivationImage(derivationDatasets, - itkSegmentations[0]); + // initial frames ... - // initializeFrames(slice2derimg); + FGPlanePosPatient* fgppp = FGPlanePosPatient::createMinimal("1","1","1"); + FGFrameContent* fgfc = new FGFrameContent(); + FGDerivationImage* fgder = new FGDerivationImage(); + OFVector perFrameFGs; + + perFrameFGs.push_back(fgppp); + perFrameFGs.push_back(fgfc); + if(hasDerivationImages(slice2derimg)) + perFrameFGs.push_back(fgder); + + int uidfound = 0, uidnotfound = 0; + const unsigned frameSize = volumeGeometry.extent[0] * volumeGeometry.extent[1]; + Uint8 *frameData = new Uint8[frameSize]; + +// for(size_t segFileNumber=0; segFileNumberSetInput(itkImages[segFileNumber]); +// l2lm->Update(); +// +// typedef LabelToLabelMapFilterType::OutputImageType::LabelObjectType LabelType; +// typedef itk::LabelStatisticsImageFilter LabelStatisticsType; +// +// LabelStatisticsType::Pointer labelStats = LabelStatisticsType::New(); +// +// cout << "Found " << l2lm->GetOutput()->GetNumberOfLabelObjects() << " label(s)" << endl; +// labelStats->SetInput(itkImages[segFileNumber]); +// labelStats->SetLabelInput(itkImages[segFileNumber]); +// labelStats->Update(); +// +// // TODO: implement crop segments box +// +// for(unsigned segLabelNumber=0 ; segLabelNumberGetOutput()->GetNumberOfLabelObjects();segLabelNumber++){ +// LabelType* labelObject = l2lm->GetOutput()->GetNthLabelObject(segLabelNumber); +// short label = labelObject->GetLabel(); +// +// if(!label){ +// cout << "Skipping label 0" << endl; +// continue; +// } +// +// cout << "Processing label " << label << endl; +// +// LabelStatisticsType::BoundingBoxType bbox = labelStats->GetBoundingBox(label); +// unsigned firstSlice, lastSlice; +// //bool skipEmptySlices = true; // TODO: what to do with that line? +// //bool skipEmptySlices = false; // TODO: what to do with that line? +// if(skipEmptySlices){ +// firstSlice = bbox[4]; +// lastSlice = bbox[5]+1; +// } else { +// firstSlice = 0; +// lastSlice = inputSize[2]; +// } +// +// cout << "Total non-empty slices that will be encoded in SEG for label " << +// label << " is " << lastSlice-firstSlice << endl << +// " (inclusive from " << firstSlice << " to " << +// lastSlice << ")" << endl; +// +// DcmSegment* segment = NULL; +// if(metaInfo.segmentsAttributesMappingList[segFileNumber].find(label) == metaInfo.segmentsAttributesMappingList[segFileNumber].end()){ +// cerr << "ERROR: Failed to match label from image to the segment metadata!" << endl; +// return NULL; +// } +// +// SegmentAttributes* segmentAttributes = metaInfo.segmentsAttributesMappingList[segFileNumber][label]; +// +// DcmSegTypes::E_SegmentAlgoType algoType = DcmSegTypes::SAT_UNKNOWN; +// string algoName = ""; +// string algoTypeStr = segmentAttributes->getSegmentAlgorithmType(); +// if(algoTypeStr == "MANUAL"){ +// algoType = DcmSegTypes::SAT_MANUAL; +// } else { +// if(algoTypeStr == "AUTOMATIC") +// algoType = DcmSegTypes::SAT_AUTOMATIC; +// if(algoTypeStr == "SEMIAUTOMATIC") +// algoType = DcmSegTypes::SAT_SEMIAUTOMATIC; +// +// algoName = segmentAttributes->getSegmentAlgorithmName(); +// if(algoName == ""){ +// cerr << "ERROR: Algorithm name must be specified for non-manual algorithm types!" << endl; +// return NULL; +// } +// } +// +// CodeSequenceMacro* typeCode = segmentAttributes->getSegmentedPropertyTypeCodeSequence(); +// CodeSequenceMacro* categoryCode = segmentAttributes->getSegmentedPropertyCategoryCodeSequence(); +// assert(typeCode != NULL && categoryCode!= NULL); +// OFString segmentLabel; +// CHECK_COND(typeCode->getCodeMeaning(segmentLabel)); +// CHECK_COND(DcmSegment::create(segment, segmentLabel, *categoryCode, *typeCode, algoType, algoName.c_str())); +// +// if(segmentAttributes->getSegmentDescription().length() > 0) +// segment->setSegmentDescription(segmentAttributes->getSegmentDescription().c_str()); +// +// CodeSequenceMacro* typeModifierCode = segmentAttributes->getSegmentedPropertyTypeModifierCodeSequence(); +// if (typeModifierCode != NULL) { +// OFVector& modifiersVector = segment->getSegmentedPropertyTypeModifierCode(); +// modifiersVector.push_back(typeModifierCode); +// } +// +// GeneralAnatomyMacro &anatomyMacro = segment->getGeneralAnatomyCode(); +// if (segmentAttributes->getAnatomicRegionSequence() != NULL){ +// OFVector& anatomyMacroModifiersVector = anatomyMacro.getAnatomicRegionModifier(); +// CodeSequenceMacro& anatomicRegionSequence = anatomyMacro.getAnatomicRegion(); +// anatomicRegionSequence = *segmentAttributes->getAnatomicRegionSequence(); +// +// if(segmentAttributes->getAnatomicRegionModifierSequence() != NULL){ +// CodeSequenceMacro* anatomicRegionModifierSequence = segmentAttributes->getAnatomicRegionModifierSequence(); +// anatomyMacroModifiersVector.push_back(anatomicRegionModifierSequence); +// } +// } +// +// unsigned* rgb = segmentAttributes->getRecommendedDisplayRGBValue(); +// unsigned cielabScaled[3]; +// float cielab[3], ciexyz[3]; +// +// Helper::getCIEXYZFromRGB(&rgb[0],&ciexyz[0]); +// Helper::getCIELabFromCIEXYZ(&ciexyz[0],&cielab[0]); +// Helper::getIntegerScaledCIELabFromCIELab(&cielab[0],&cielabScaled[0]); +// CHECK_COND(segment->setRecommendedDisplayCIELabValue(cielabScaled[0],cielabScaled[1],cielabScaled[2])); +// +// Uint16 segmentNumber; +// CHECK_COND(segdoc->addSegment(segment, segmentNumber /* returns logical segment number */)); +// +// // TODO: make it possible to skip empty frames (optional) +// // iterate over slices for an individual label and populate output frames +// for(unsigned sliceNumber=firstSlice;sliceNumbersetStackID("1"); // all frames go into the same stack +// CHECK_COND(fgfc->setDimensionIndexValues(segmentNumber, 0)); +// CHECK_COND(fgfc->setDimensionIndexValues(sliceNumber-firstSlice+1, 1)); +// //ostringstream inStackPosSStream; // StackID is not present/needed +// //inStackPosSStream << s+1; +// //fracon->setInStackPositionNumber(s+1); +// +// // PerFrame FG: PlanePositionSequence +// { +// ShortImageType::PointType sliceOriginPoint; +// ShortImageType::IndexType sliceOriginIndex; +// sliceOriginIndex.Fill(0); +// sliceOriginIndex[2] = sliceNumber; +// itkImages[segFileNumber]->TransformIndexToPhysicalPoint(sliceOriginIndex, sliceOriginPoint); +// ostringstream pppSStream; +// if(sliceNumber>0){ +// ShortImageType::PointType prevOrigin; +// ShortImageType::IndexType prevIndex; +// prevIndex.Fill(0); +// prevIndex[2] = sliceNumber-1; +// itkImages[segFileNumber]->TransformIndexToPhysicalPoint(prevIndex, prevOrigin); +// } +// fgppp->setImagePositionPatient( +// Helper::floatToStrScientific(sliceOriginPoint[0]).c_str(), +// Helper::floatToStrScientific(sliceOriginPoint[1]).c_str(), +// Helper::floatToStrScientific(sliceOriginPoint[2]).c_str()); +// } +// +// /* Add frame that references this segment */ +// { +// ShortImageType::RegionType sliceRegion; +// ShortImageType::IndexType sliceIndex; +// ShortImageType::SizeType sliceSize; +// +// sliceIndex[0] = 0; +// sliceIndex[1] = 0; +// sliceIndex[2] = sliceNumber; +// +// sliceSize[0] = volumeGeometry.extent[0]; +// sliceSize[1] = volumeGeometry.extent[1]; +// sliceSize[2] = 1; +// +// sliceRegion.SetIndex(sliceIndex); +// sliceRegion.SetSize(sliceSize); +// +// unsigned framePixelCnt = 0; +// itk::ImageRegionConstIteratorWithIndex sliceIterator(itkImages[segFileNumber], sliceRegion); +// for(sliceIterator.GoToBegin();!sliceIterator.IsAtEnd();++sliceIterator,++framePixelCnt){ +// if(sliceIterator.Get() == label){ +// frameData[framePixelCnt] = 1; +// ShortImageType::IndexType idx = sliceIterator.GetIndex(); +// //cout << framePixelCnt << " " << idx[1] << "," << idx[0] << endl; +// } else +// frameData[framePixelCnt] = 0; +// } +// +// /* +// if(sliceNumber>=dcmDatasets.size()){ +// cerr << "ERROR: trying to access missing DICOM Slice! And sorry, multi-frame not supported at the moment..." << endl; +// return NULL; +// }*/ +// +// OFVector siVector; +// for(size_t derImageInstanceNum=0; +// derImageInstanceNum0){ +// +// DerivationImageItem *derimgItem; +// CHECK_COND(fgder->addDerivationImageItem(CodeSequenceMacro("113076","DCM","Segmentation"),"",derimgItem)); +// +// cout << "Total of " << siVector.size() << " source image items will be added" << endl; +// +// OFVector srcimgItems; +// CHECK_COND(derimgItem->addSourceImageItems(siVector, +// CodeSequenceMacro("121322","DCM","Source image for image processing operation"), +// srcimgItems)); +// +// if(1){ +// // initialize class UID and series instance UID +// ImageSOPInstanceReferenceMacro &instRef = srcimgItems[0]->getImageSOPInstanceReference(); +// OFString instanceUID; +// CHECK_COND(instRef.getReferencedSOPClassUID(classUID)); +// CHECK_COND(instRef.getReferencedSOPInstanceUID(instanceUID)); +// +// if(instanceUIDs.find(instanceUID) == instanceUIDs.end()){ +// SOPInstanceReferenceMacro *refinstancesItem = new SOPInstanceReferenceMacro(); +// CHECK_COND(refinstancesItem->setReferencedSOPClassUID(classUID)); +// CHECK_COND(refinstancesItem->setReferencedSOPInstanceUID(instanceUID)); +// refinstances.push_back(refinstancesItem); +// instanceUIDs.insert(instanceUID); +// uidnotfound++; +// } else { +// uidfound++; +// } +// } +// } +// +// CHECK_COND(segdoc->addFrame(frameData, segmentNumber, perFrameFGs)); +// +// // remove derivation image FG from the per-frame FGs, only if applicable! +// if(siVector.size()>0){ +// // clean up for the next frame +// fgder->clearData(); +// } +// +// } +// } +// } +// } +// +// delete fgfc; +// delete fgppp; +// delete fgder; return EXIT_SUCCESS; } +bool SegmentationImageObject::hasDerivationImages(vector > &slice2derimg) const { + for (vector >::const_iterator vI = slice2derimg.begin(); vI != slice2derimg.end(); ++vI) { + if ((*vI).size() > 0) { + return true; + } + } + return false; +} + int SegmentationImageObject::initializeEquipmentInfo() { if(sourceRepresentationType == ITK_REPR){ generalEquipmentInfoModule = IODGeneralEquipmentModule::EquipmentInfo(QIICR_MANUFACTURER, @@ -427,17 +684,17 @@ Uint16 SegmentationImageObject::getSegmentId(FGInterface &fgInterface, size_t fr return segmentId; } -// AF: I could not quickly figure out how to template this function over image type - suggestions are welcomed! +template vector > SegmentationImageObject::getSliceMapForSegmentation2DerivationImage(const vector dcmDatasets, - const ShortImageType::Pointer &labelImage) { + const ImageTypePointer &labelImage) { // Find mapping from the segmentation slice number to the derivation image // Assume that orientation of the segmentation is the same as the source series unsigned numLabelSlices = labelImage->GetLargestPossibleRegion().GetSize()[2]; vector > slice2derimg(numLabelSlices); for(size_t i=0;ifindAndGetOFString(DCM_ImagePositionPatient, ippStr, j)); ippPoint[j] = atof(ippStr.c_str()); From 86013eb56684318e3d47d614141d53c26f58db07 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 10 Mar 2017 16:08:42 -0500 Subject: [PATCH 071/116] WIP: refactoring of the multiframe conversion implementation This is an intermediate commit that aims to improve organization of the conversion functionality, motivated by the various standing issues (e.g., #217, #192 and #150) that are difficult to address with the current level of disarray in the code. Some of the ideas are the following: - introduce basic hierarchy of classes to allow reuse of the common functionality between PM and SEG converters (MultiFrameConverter parent for ParametricMapConverter and SegmentationImageConverter) - do not use static functions, instead make better use of converter classes - factor out containers to keep information about ImageVolume and DICOMFrame to simplify conversion and book keeping - improve consistency of naming and code style My plan to proceed with the refactoring is to implement "shadow" functions in the updated classes while keeping the original functions initially, and then phase out the original functions. The current commit is just an intermediate milestone to open this for feedback and keep development history at least somewhat incremental. --- apps/paramaps/itkimage2paramap.cxx | 4 +- apps/paramaps/paramap2itkimage.cxx | 4 +- apps/seg/itkimage2segimage.cxx | 2 +- apps/seg/segimage2itkimage.cxx | 4 +- include/dcmqi/DICOMFrame.h | 36 +++ include/dcmqi/ImageVolume.h | 66 ++++++ ...{ConverterBase.h => MultiframeConverter.h} | 7 +- ...apConverter.h => ParametricMapConverter.h} | 41 +++- include/dcmqi/SegmentVolume.h | 17 ++ ...nverter.h => SegmentationImageConverter.h} | 4 +- libsrc/CMakeLists.txt | 173 ++++++++------ libsrc/DICOMFrame.cpp | 5 + libsrc/ImageVolume.cpp | 214 ++++++++++++++++++ ...verterBase.cpp => MultiframeConverter.cpp} | 8 +- ...nverter.cpp => ParametricMapConverter.cpp} | 30 ++- libsrc/SegmentVolume.cpp | 5 + ...ter.cpp => SegmentationImageConverter.cpp} | 8 +- 17 files changed, 521 insertions(+), 107 deletions(-) create mode 100644 include/dcmqi/DICOMFrame.h create mode 100644 include/dcmqi/ImageVolume.h rename include/dcmqi/{ConverterBase.h => MultiframeConverter.h} (98%) rename include/dcmqi/{ParaMapConverter.h => ParametricMapConverter.h} (53%) create mode 100644 include/dcmqi/SegmentVolume.h rename include/dcmqi/{ImageSEGConverter.h => SegmentationImageConverter.h} (93%) create mode 100644 libsrc/DICOMFrame.cpp create mode 100644 libsrc/ImageVolume.cpp rename libsrc/{ConverterBase.cpp => MultiframeConverter.cpp} (81%) rename libsrc/{ParaMapConverter.cpp => ParametricMapConverter.cpp} (95%) create mode 100644 libsrc/SegmentVolume.cpp rename libsrc/{ImageSEGConverter.cpp => SegmentationImageConverter.cpp} (98%) diff --git a/apps/paramaps/itkimage2paramap.cxx b/apps/paramaps/itkimage2paramap.cxx index 583b86d9..f71252b9 100644 --- a/apps/paramaps/itkimage2paramap.cxx +++ b/apps/paramaps/itkimage2paramap.cxx @@ -3,7 +3,7 @@ // DCMQI includes #undef HAVE_SSTREAM // Avoid redefinition warning -#include "dcmqi/ParaMapConverter.h" +#include "dcmqi/ParametricMapConverter.h" #include "dcmqi/internal/VersionConfigure.h" @@ -44,7 +44,7 @@ int main(int argc, char *argv[]) std::string metadata( (std::istreambuf_iterator(metainfoStream) ), (std::istreambuf_iterator())); - DcmDataset* result = dcmqi::ParaMapConverter::itkimage2paramap(parametricMapImage, dcmDatasets, metadata); + DcmDataset* result = dcmqi::ParametricMapConverter::itkimage2paramap(parametricMapImage, dcmDatasets, metadata); if (result == NULL) { return EXIT_FAILURE; diff --git a/apps/paramaps/paramap2itkimage.cxx b/apps/paramaps/paramap2itkimage.cxx index aa1def1d..31908f8b 100644 --- a/apps/paramaps/paramap2itkimage.cxx +++ b/apps/paramaps/paramap2itkimage.cxx @@ -3,7 +3,7 @@ // DCMQI includes #undef HAVE_SSTREAM // Avoid redefinition warning -#include "dcmqi/ParaMapConverter.h" +#include "dcmqi/ParametricMapConverter.h" #include "dcmqi/internal/VersionConfigure.h" @@ -25,7 +25,7 @@ int main(int argc, char *argv[]) CHECK_COND(sliceFF.loadFile(inputFileName.c_str())); DcmDataset* dataset = sliceFF.getDataset(); - pair result = dcmqi::ParaMapConverter::paramap2itkimage(dataset); + pair result = dcmqi::ParametricMapConverter::paramap2itkimage(dataset); string fileExtension = helper::getFileExtensionFromType(outputType); diff --git a/apps/seg/itkimage2segimage.cxx b/apps/seg/itkimage2segimage.cxx index 4619554e..a6187ecb 100644 --- a/apps/seg/itkimage2segimage.cxx +++ b/apps/seg/itkimage2segimage.cxx @@ -3,7 +3,7 @@ // DCMQI includes #undef HAVE_SSTREAM // Avoid redefinition warning -#include "dcmqi/ImageSEGConverter.h" +#include "dcmqi/SegmentationImageConverter.h" #include "dcmqi/internal/VersionConfigure.h" typedef dcmqi::Helper helper; diff --git a/apps/seg/segimage2itkimage.cxx b/apps/seg/segimage2itkimage.cxx index 7784e311..0545778a 100644 --- a/apps/seg/segimage2itkimage.cxx +++ b/apps/seg/segimage2itkimage.cxx @@ -3,7 +3,7 @@ // DCMQI includes #undef HAVE_SSTREAM // Avoid redefinition warning -#include "dcmqi/ImageSEGConverter.h" +#include "dcmqi/SegmentationImageConverter.h" #include "dcmqi/internal/VersionConfigure.h" @@ -24,7 +24,7 @@ int main(int argc, char *argv[]) CHECK_COND(sliceFF.loadFile(inputSEGFileName.c_str())); DcmDataset* dataset = sliceFF.getDataset(); - pair , string> result = dcmqi::ImageSEGConverter::dcmSegmentation2itkimage(dataset); + pair , string> result = dcmqi::SegmentationImageConverter::dcmSegmentation2itkimage(dataset); string outputPrefix = prefix.empty() ? "" : prefix + "-"; diff --git a/include/dcmqi/DICOMFrame.h b/include/dcmqi/DICOMFrame.h new file mode 100644 index 00000000..f58a6955 --- /dev/null +++ b/include/dcmqi/DICOMFrame.h @@ -0,0 +1,36 @@ +// +// Created by Andrey Fedorov on 3/9/17. +// + +#ifndef DCMQI_DICOMFRAME_H +#define DCMQI_DICOMFRAME_H + +#include + +namespace dcmqi { + + // Describe input/output frames for the purposes of sorting and associating with the + // slices of the ITK volume + class DICOMFrame { + public: + // distinguish between the frames from the legacy data and enhanced multiframe objects + enum { + LegacyInstanceFrame = 0, + EnhancedInstanceFrame + }; + + DICOMFrame(DcmDataset *dataset, int number) : + frameNumber(number), + frameDataset(dataset) { + + }; + + private: + int frameType; + DcmDataset *frameDataset; + int frameNumber; + OFString originStr; // revisit this + }; +} + +#endif //DCMQI_DICOMFRAME_H diff --git a/include/dcmqi/ImageVolume.h b/include/dcmqi/ImageVolume.h new file mode 100644 index 00000000..13d37ef4 --- /dev/null +++ b/include/dcmqi/ImageVolume.h @@ -0,0 +1,66 @@ +// +// Created by Andrey Fedorov on 3/9/17. +// + +#ifndef DCMQI_IMAGEVOLUME_H +#define DCMQI_IMAGEVOLUME_H + +#include +#include +#include + +#include + +namespace dcmqi { + + // Maintain properties of the image volume + // Attributes ara parallel to those of itkImageData, but limited to what we need for the task of conversion, + // and the class is not templated over the pixel type + class ImageVolume { + public: + // pixel types that are relevant for the types of objects we want to support + enum { + FLOAT32 = 0, // PM + FLOAT64, // PM + UINT16 // PM or SEG + }; + + typedef IODFloatingPointImagePixelModule::value_type Float32PixelType; + typedef itk::Image Float32ITKImageType; + + ImageVolume(){ + rowDirection.set_size(3); + columnDirection.set_size(3); + sliceDirection.set_size(3); + origin.set_size(3); + spacing.set_size(3); + pixelData = NULL; + } + + // while going from DICOM PM/SEG, we get volume information from FGInterface + int initializeFromDICOM(FGInterface&); + int initializeFromITK(Float32ITKImageType::Pointer); + + // TODO - inherited class? or separate segments before passing to this one? + // int initializeFromSegment(FGInterface&, unsigned); + + protected: + int initializeDirections(FGInterface &); + int initializeExtent(FGInterface &); + bool getDeclaredSpacing(FGInterface&); + + private: + + // use vnl_vector to simplify support of vector calculations + vnl_vector rowDirection, columnDirection, sliceDirection; + vnl_vector origin; + unsigned sliceExtent; + vnl_vector spacing; + void* pixelData; + int pixelDataType; + }; + +}; + + +#endif //DCMQI_IMAGEVOLUME_H diff --git a/include/dcmqi/ConverterBase.h b/include/dcmqi/MultiframeConverter.h similarity index 98% rename from include/dcmqi/ConverterBase.h rename to include/dcmqi/MultiframeConverter.h index ad7e9f49..45b1a3c9 100644 --- a/include/dcmqi/ConverterBase.h +++ b/include/dcmqi/MultiframeConverter.h @@ -35,13 +35,16 @@ using namespace std; +// common type definitions typedef short ShortPixelType; typedef itk::Image ShortImageType; typedef itk::ImageFileReader ShortReaderType; namespace dcmqi { - class ConverterBase { + class MultiframeConverter { + public: + virtual int convert(); protected: static IODGeneralEquipmentModule::EquipmentInfo getEquipmentInfo(); @@ -124,7 +127,7 @@ namespace dcmqi { */ // Determine ordering of the frames, keep mapping from ImagePositionPatient string - // to the distance, and keep track (just out of curiousity) how many frames overlap + // to the distance, and keep track (just out of curiosity) how many frames overlap vnl_vector refOrigin(3); { OFBool isPerFrame; diff --git a/include/dcmqi/ParaMapConverter.h b/include/dcmqi/ParametricMapConverter.h similarity index 53% rename from include/dcmqi/ParaMapConverter.h rename to include/dcmqi/ParametricMapConverter.h index fcabbf49..6545e39c 100644 --- a/include/dcmqi/ParaMapConverter.h +++ b/include/dcmqi/ParametricMapConverter.h @@ -18,8 +18,9 @@ #include // DCMQI includes -#include "dcmqi/ConverterBase.h" +#include "dcmqi/MultiframeConverter.h" #include "dcmqi/JSONParametricMapMetaInformationHandler.h" +#include "dcmqi/ImageVolume.h" typedef IODFloatingPointImagePixelModule::value_type FloatPixelType; typedef itk::Image FloatImageType; @@ -28,22 +29,52 @@ typedef itk::MinimumMaximumImageCalculator MinMaxCalculatorType; using namespace std; - namespace dcmqi { - class ParaMapConverter : public ConverterBase { + DcmDataset* itkimage2paramapReplacement(const FloatImageType::Pointer ¶metricMapImage, vector dcmDatasets, + const string &metaData); + pair paramap2itkimageReplacement(DcmDataset *pmapDataset); + + class ParametricMapConverter : public MultiframeConverter { public: - static DcmDataset* itkimage2paramap(const FloatImageType::Pointer ¶metricMapImage, vector dcmDatasets, - const string &metaData); + // placeholder to replace static function going from ITK to DICOM + ParametricMapConverter(const FloatImageType::Pointer ¶metricMapImage, vector dcmDatasets, + const string &metaData); + // placeholder to replace static function going from DICOM to ITK + ParametricMapConverter(DcmDataset*); + + + static DcmDataset* itkimage2paramap(const FloatImageType::Pointer ¶metricMapImage, vector dcmDatasets, + const string &metaData); static pair paramap2itkimage(DcmDataset *pmapDataset); + + // do the conversion + int convert(); + + // get the result + FloatImageType::Pointer getFloat32ITKImage() const; + string getMetaData() const; + DcmDataset* getDataset() const; + protected: + + ParametricMapConverter() : parametricMapVolume(NULL), parametricMapDataset(NULL) {}; + static OFCondition addFrame(DPMParametricMapIOD &map, const FloatImageType::Pointer ¶metricMapImage, const JSONParametricMapMetaInformationHandler &metaInfo, const unsigned long frameNo, OFVector perFrameGroups); static void populateMetaInformationFromDICOM(DcmDataset *pmapDataset, JSONParametricMapMetaInformationHandler &metaInfo); + private: + // these are the items we convert to and from, essentially + ImageVolume* parametricMapVolume; + DcmDataset* parametricMapDataset; + + // these are the items we will need in the process of conversion + vector referencedDatasets; + string metaData; }; } diff --git a/include/dcmqi/SegmentVolume.h b/include/dcmqi/SegmentVolume.h new file mode 100644 index 00000000..504693cd --- /dev/null +++ b/include/dcmqi/SegmentVolume.h @@ -0,0 +1,17 @@ +// +// Created by Andrey Fedorov on 3/9/17. +// + +#ifndef DCMQI_SEGMENTVOLUME_H +#define DCMQI_SEGMENTVOLUME_H + +#include "dcmqi/ImageVolume.h" + +namespace dcmqi { + class SegmentVolume : public ImageVolume { + + }; +} + + +#endif //DCMQI_SEGMENTVOLUME_H diff --git a/include/dcmqi/ImageSEGConverter.h b/include/dcmqi/SegmentationImageConverter.h similarity index 93% rename from include/dcmqi/ImageSEGConverter.h rename to include/dcmqi/SegmentationImageConverter.h index deb8e7f4..6158fc1f 100644 --- a/include/dcmqi/ImageSEGConverter.h +++ b/include/dcmqi/SegmentationImageConverter.h @@ -22,7 +22,7 @@ #include // DCMQI includes -#include "dcmqi/ConverterBase.h" +#include "dcmqi/MultiframeConverter.h" #include "dcmqi/JSONSegmentationMetaInformationHandler.h" using namespace std; @@ -32,7 +32,7 @@ typedef itk::LabelImageToLabelMapFilter LabelToLabelMapFilterTyp namespace dcmqi { - class ImageSEGConverter : public ConverterBase { + class SegmentationImageConverter : public MultiframeConverter { public: static DcmDataset* itkimage2dcmSegmentation(vector dcmDatasets, diff --git a/libsrc/CMakeLists.txt b/libsrc/CMakeLists.txt index 02e7caf2..b354c700 100644 --- a/libsrc/CMakeLists.txt +++ b/libsrc/CMakeLists.txt @@ -1,75 +1,98 @@ - -#----------------------------------------------------------------------------- -set(ADDITIONAL_SRCS) - -set(INCLUDE_DIR ../include/dcmqi) - -set(HDRS - ${INCLUDE_DIR}/preproc.h - ${INCLUDE_DIR}/QIICRConstants.h - ${INCLUDE_DIR}/QIICRUIDs.h - ${INCLUDE_DIR}/ConverterBase.h - ${INCLUDE_DIR}/Exceptions.h - ${INCLUDE_DIR}/framesorter.h - ${INCLUDE_DIR}/ImageSEGConverter.h - ${INCLUDE_DIR}/ParaMapConverter - ${INCLUDE_DIR}/Helper.h - ${INCLUDE_DIR}/JSONMetaInformationHandlerBase.h - ${INCLUDE_DIR}/JSONParametricMapMetaInformationHandler.h - ${INCLUDE_DIR}/JSONSegmentationMetaInformationHandler.h - ${INCLUDE_DIR}/SegmentAttributes.h - ) - -set(SRCS - ConverterBase.cpp - ImageSEGConverter.cpp - ParaMapConverter.cpp - Helper.cpp - JSONMetaInformationHandlerBase.cpp - JSONParametricMapMetaInformationHandler.cpp - JSONSegmentationMetaInformationHandler.cpp - SegmentAttributes.cpp - ) - - -if(DCMQI_BUILTIN_JSONCPP) - list(APPEND ADDITIONAL_SRCS - ${DCMQI_SOURCE_DIR}/jsoncpp/jsoncpp.cpp - ) - set(JsonCpp_INCLUDE_DIR ${DCMQI_SOURCE_DIR}/jsoncpp) -endif() - -set(lib_name dcmqi) - -add_library(${lib_name} STATIC - ${HDRS} - ${SRCS} - ${ADDITIONAL_SRCS} - ) - -if(DCMQI_LIBRARY_PROPERTIES) - set_target_properties(${lib_name} PROPERTIES ${DCMQI_LIBRARY_PROPERTIES}) -endif() - -set_property(GLOBAL APPEND PROPERTY ${CMAKE_PROJECT_NAME}_TARGETS ${lib_name}) - -set(${lib_name}_INCLUDE_DIRS - ${DCMTK_INCLUDE_DIRS} - ${ITK_INCLUDE_DIRS} - ${DCMQI_SOURCE_DIR}/include - ${DCMQI_BINARY_DIR}/include - ${JsonCpp_INCLUDE_DIR} - ) -if(ITK_INSTALL_PREFIX) - list(APPEND ${lib_name}_INCLUDE_DIRS - ${ITK_INSTALL_PREFIX}/include/vxl/core - ${ITK_INSTALL_PREFIX}/include/vxl/vcl - ) -endif() -target_include_directories(${lib_name} PUBLIC ${${lib_name}_INCLUDE_DIRS}) - -target_link_libraries(${lib_name} PUBLIC - ${DCMTK_LIBRARIES} - ${ITK_LIBRARIES} - $<$>:${JsonCpp_LIBRARY}> - ) + +#----------------------------------------------------------------------------- + +set(INCLUDE_DIR ../include/dcmqi) + +set(HDRS + ${INCLUDE_DIR}/DICOMFrame.h + ${INCLUDE_DIR}/ImageVolume.h + ${INCLUDE_DIR}/MultiframeConverter.h + ${INCLUDE_DIR}/Helper.h + ${INCLUDE_DIR}/SegmentationImageConverter.h + ${INCLUDE_DIR}/ParametricMapConverter.h + ${INCLUDE_DIR}/JSONMetaInformationHandlerBase.h + ${INCLUDE_DIR}/JSONParametricMapMetaInformationHandler.h + ${INCLUDE_DIR}/JSONSegmentationMetaInformationHandler.h + ${INCLUDE_DIR}/SegmentAttributes.h + ) + +set(SRCS + DICOMFrame.cpp + ImageVolume.cpp + MultiframeConverter.cpp + Helper.cpp + SegmentationImageConverter.cpp + ParametricMapConverter.cpp + JSONMetaInformationHandlerBase.cpp + JSONParametricMapMetaInformationHandler.cpp + JSONSegmentationMetaInformationHandler.cpp + SegmentAttributes.cpp + ) + +set(ADDITIONAL_HDRS + ${INCLUDE_DIR}/DICOMFrame.h + ${INCLUDE_DIR}/ImageVolume.h + ${INCLUDE_DIR}/SegmentVolume.h + ${INCLUDE_DIR}/MultiframeObject.h + ${INCLUDE_DIR}/ParametricMapObject.h + ${INCLUDE_DIR}/SegmentationImageObject.h + ${INCLUDE_DIR}/ImageVolumeGeometry.h + ${INCLUDE_DIR}/Exceptions.h + ${INCLUDE_DIR}/framesorter.h + ${INCLUDE_DIR}/preproc.h + ${INCLUDE_DIR}/QIICRConstants.h + ${INCLUDE_DIR}/QIICRUIDs.h + ) + +set(ADDITIONAL_SRCS + DICOMFrame.cpp + ImageVolume.cpp + SegmentVolume.cpp + MultiframeObject.cpp + ParametricMapObject.cpp + SegmentationImageObject.cpp + ImageVolumeGeometry.cpp + ) + +if(DCMQI_BUILTIN_JSONCPP) + list(APPEND ADDITIONAL_SRCS + ${DCMQI_SOURCE_DIR}/jsoncpp/jsoncpp.cpp + ) + set(JsonCpp_INCLUDE_DIR ${DCMQI_SOURCE_DIR}/jsoncpp) +endif() + +set(lib_name dcmqi) + +add_library(${lib_name} STATIC + ${HDRS} + ${SRCS} + ${ADDITIONAL_HDRS} + ${ADDITIONAL_SRCS} + ) + +if(DCMQI_LIBRARY_PROPERTIES) + set_target_properties(${lib_name} PROPERTIES ${DCMQI_LIBRARY_PROPERTIES}) +endif() + +set_property(GLOBAL APPEND PROPERTY ${CMAKE_PROJECT_NAME}_TARGETS ${lib_name}) + +set(${lib_name}_INCLUDE_DIRS + ${DCMTK_INCLUDE_DIRS} + ${ITK_INCLUDE_DIRS} + ${DCMQI_SOURCE_DIR}/include + ${DCMQI_BINARY_DIR}/include + ${JsonCpp_INCLUDE_DIR} + ) +if(ITK_INSTALL_PREFIX) + list(APPEND ${lib_name}_INCLUDE_DIRS + ${ITK_INSTALL_PREFIX}/include/vxl/core + ${ITK_INSTALL_PREFIX}/include/vxl/vcl + ) +endif() +target_include_directories(${lib_name} PUBLIC ${${lib_name}_INCLUDE_DIRS}) + +target_link_libraries(${lib_name} PUBLIC + ${DCMTK_LIBRARIES} + ${ITK_LIBRARIES} + $<$>:${JsonCpp_LIBRARY}> + ) \ No newline at end of file diff --git a/libsrc/DICOMFrame.cpp b/libsrc/DICOMFrame.cpp new file mode 100644 index 00000000..ecc42038 --- /dev/null +++ b/libsrc/DICOMFrame.cpp @@ -0,0 +1,5 @@ +// +// Created by Andrey Fedorov on 3/9/17. +// + +#include "dcmqi/DICOMFrame.h" diff --git a/libsrc/ImageVolume.cpp b/libsrc/ImageVolume.cpp new file mode 100644 index 00000000..2f12ecd2 --- /dev/null +++ b/libsrc/ImageVolume.cpp @@ -0,0 +1,214 @@ +// +// Created by Andrey Fedorov on 3/9/17. +// + +#include +#include +#include + +#include +#include +#include +#include "dcmqi/ImageVolume.h" + +using namespace std; + +int dcmqi::ImageVolume::initializeFromDICOM(FGInterface& fgInterface) { + if(initializeDirections(fgInterface)) + return EXIT_FAILURE; + if(initializeExtent(fgInterface)) + return EXIT_FAILURE; + return EXIT_SUCCESS; +} + + +int dcmqi::ImageVolume::initializeFromITK(Float32ITKImageType::Pointer itkImage){ + return EXIT_SUCCESS; +} + +int dcmqi::ImageVolume::initializeDirections(FGInterface &fgInterface) { + // TODO: handle the situation when FoR is not initialized + OFBool isPerFrame; + + FGPlaneOrientationPatient *planorfg = OFstatic_cast(FGPlaneOrientationPatient*, + fgInterface.get(0, DcmFGTypes::EFG_PLANEORIENTPATIENT, isPerFrame)); + if(!planorfg){ + cerr << "Plane Orientation (Patient) is missing, cannot parse input " << endl; + return EXIT_FAILURE; + } + + OFString orientStr; + for(int i=0;i<3;i++){ + if(planorfg->getImageOrientationPatient(orientStr, i).good()){ + rowDirection[i] = atof(orientStr.c_str()); + } else { + cerr << "Failed to get orientation " << i << endl; + return EXIT_FAILURE; + } + } + for(int i=3;i<6;i++){ + if(planorfg->getImageOrientationPatient(orientStr, i).good()){ + columnDirection[i-3] = atof(orientStr.c_str()); + } else { + cerr << "Failed to get orientation " << i << endl; + return EXIT_FAILURE; + } + } + vnl_vector sliceDirection = vnl_cross_3d(rowDirection, columnDirection); + sliceDirection.normalize(); + + cout << "Row direction: " << rowDirection << endl; + cout << "Col direction: " << columnDirection << endl; + cout << "Z direction: " << sliceDirection << endl; + + return EXIT_SUCCESS; +} + + +int dcmqi::ImageVolume::initializeExtent(FGInterface &fgInterface) { + // Size + // Rows/Columns can be read directly from the respective attributes + // For number of slices, consider that all segments must have the same number of frames. + // If we have FoR UID initialized, this means every segment should also have Plane + // Position (Patient) initialized. So we can get the number of slices by looking + // how many per-frame functional groups a segment has. + + vector originDistances; + map originStr2distance; + map frame2overlap; + double minDistance = 0.0; + + double calculatedSliceSpacing = 0; + + unsigned numFrames = fgInterface.getNumberOfFrames(); + + /* Framesorter is to be moved to DCMTK at some point + * in the future. For now it is causing build issues on windows + + FrameSorterIPP fsIPP; + FrameSorterIPP::Results sortResults; + fsIPP.setSorterInput(&fgInterface); + fsIPP.sort(sortResults); + + */ + + // Determine ordering of the frames, keep mapping from ImagePositionPatient string + // to the distance, and keep track (just out of curiosity) how many frames overlap + vnl_vector refOrigin(3); + { + OFBool isPerFrame; + FGPlanePosPatient *planposfg = OFstatic_cast(FGPlanePosPatient*, + fgInterface.get(0, DcmFGTypes::EFG_PLANEPOSPATIENT, isPerFrame)); + if(!isPerFrame){ + cerr << "PlanePositionPatient FG is shared ... cannot handle this!" << endl; + return EXIT_FAILURE; + } + + for(int j=0;j<3;j++){ + OFString planposStr; + if(planposfg->getImagePositionPatient(planposStr, j).good()){ + refOrigin[j] = atof(planposStr.c_str()); + } else { + cerr << "Failed to read patient position" << endl; + } + } + } + + for(size_t frameId=0;frameId sOrigin; + OFString sOriginStr = ""; + sOrigin.set_size(3); + for(int j=0;j<3;j++){ + OFString planposStr; + if(planposfg->getImagePositionPatient(planposStr, j).good()){ + sOrigin[j] = atof(planposStr.c_str()); + sOriginStr += planposStr; + if(j<2) + sOriginStr+='/'; + } else { + cerr << "Failed to read patient position" << endl; + return EXIT_FAILURE; + } + } + + // check if this frame has already been encountered + if(originStr2distance.find(sOriginStr) == originStr2distance.end()){ + vnl_vector difference; + difference.set_size(3); + difference[0] = sOrigin[0]-refOrigin[0]; + difference[1] = sOrigin[1]-refOrigin[1]; + difference[2] = sOrigin[2]-refOrigin[2]; + double dist = dot_product(difference,sliceDirection); + frame2overlap[sOriginStr] = 1; + originStr2distance[sOriginStr] = dist; + assert(originStr2distance.find(sOriginStr) != originStr2distance.end()); + originDistances.push_back(dist); + + if(frameId==0){ + minDistance = dist; + origin[0] = sOrigin[0]; + origin[1] = sOrigin[1]; + origin[2] = sOrigin[2]; + } + else if(dist1){ + // WARNING: this should be improved further. Spacing should be calculated for + // consecutive frames of the individual segment. Right now, all frames are considered + // indiscriminately. Question is whether it should be computed at all, considering we do + // not have any information about whether the 2 frames are adjacent or not, so perhaps we should + // always rely on the declared spacing, and not even try to compute it? + // TODO: discuss this with the QIICR team! + + // sort all unique distances, this will be used to check consistency of + // slice spacing, and also to locate the slice position from ImagePositionPatient + // later when we read the segments + sort(originDistances.begin(), originDistances.end()); + + calculatedSliceSpacing = fabs(originDistances[0]-originDistances[1]); + + for(size_t i=1;i::const_iterator it=frame2overlap.begin(); + it!=frame2overlap.end();++it){ + if(it->second>1) + overlappingFramesCnt++; + } + + cout << "Total frames: " << numFrames << endl; + cout << "Total frames with unique IPP: " << originDistances.size() << endl; + cout << "Total overlapping frames: " << overlappingFramesCnt << endl; + cout << "Origin: " << origin << endl; + } + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/libsrc/ConverterBase.cpp b/libsrc/MultiframeConverter.cpp similarity index 81% rename from libsrc/ConverterBase.cpp rename to libsrc/MultiframeConverter.cpp index 836209eb..1d4dce88 100644 --- a/libsrc/ConverterBase.cpp +++ b/libsrc/MultiframeConverter.cpp @@ -1,11 +1,11 @@ // DCMQI includes -#include "dcmqi/ConverterBase.h" +#include "dcmqi/MultiframeConverter.h" namespace dcmqi { - IODGeneralEquipmentModule::EquipmentInfo ConverterBase::getEquipmentInfo() { + IODGeneralEquipmentModule::EquipmentInfo MultiframeConverter::getEquipmentInfo() { // TODO: change to following for most recent dcmtk // return IODGeneralEquipmentModule::EquipmentInfo(QIICR_MANUFACTURER, QIICR_DEVICE_SERIAL_NUMBER, // QIICR_MANUFACTURER_MODEL_NAME, QIICR_SOFTWARE_VERSIONS); @@ -17,13 +17,13 @@ namespace dcmqi { return eq; } - IODEnhGeneralEquipmentModule::EquipmentInfo ConverterBase::getEnhEquipmentInfo() { + IODEnhGeneralEquipmentModule::EquipmentInfo MultiframeConverter::getEnhEquipmentInfo() { return IODEnhGeneralEquipmentModule::EquipmentInfo(QIICR_MANUFACTURER, QIICR_DEVICE_SERIAL_NUMBER, QIICR_MANUFACTURER_MODEL_NAME, QIICR_SOFTWARE_VERSIONS); } // TODO: defaults for sub classes needs to be defined - ContentIdentificationMacro ConverterBase::createContentIdentificationInformation(JSONMetaInformationHandlerBase &metaInfo) { + ContentIdentificationMacro MultiframeConverter::createContentIdentificationInformation(JSONMetaInformationHandlerBase &metaInfo) { ContentIdentificationMacro ident; CHECK_COND(ident.setContentCreatorName("dcmqi")); if(metaInfo.metaInfoRoot["seriesAttributes"].isMember("ContentDescription")){ diff --git a/libsrc/ParaMapConverter.cpp b/libsrc/ParametricMapConverter.cpp similarity index 95% rename from libsrc/ParaMapConverter.cpp rename to libsrc/ParametricMapConverter.cpp index 4f674d00..c921a10c 100644 --- a/libsrc/ParaMapConverter.cpp +++ b/libsrc/ParametricMapConverter.cpp @@ -4,15 +4,29 @@ #include // DCMQI includes -#include "dcmqi/ParaMapConverter.h" -#include "dcmqi/ImageSEGConverter.h" +#include "dcmqi/ParametricMapConverter.h" +#include "dcmqi/SegmentationImageConverter.h" using namespace std; namespace dcmqi { - DcmDataset* ParaMapConverter::itkimage2paramap(const FloatImageType::Pointer ¶metricMapImage, vector dcmDatasets, + DcmDataset* itkimage2paramapReplacement(const FloatImageType::Pointer ¶metricMapImage, vector dcmDatasets, const string &metaData) { + /* + ParametricMapConverter pmConverter(parametricMapImage, dcmDatasets, metaData); + pmConverter.convert(); + + return convert.getDataset(); */ + return NULL; + } + + pair paramap2itkimageReplacement(DcmDataset *pmapDataset){ + return pair(); + }; + + DcmDataset* ParametricMapConverter::itkimage2paramap(const FloatImageType::Pointer ¶metricMapImage, vector dcmDatasets, + const string &metaData) { MinMaxCalculatorType::Pointer calculator = MinMaxCalculatorType::New(); calculator->SetImage(parametricMapImage); @@ -24,8 +38,8 @@ namespace dcmqi { metaInfo.setFirstValueMapped(calculator->GetMinimum()); metaInfo.setLastValueMapped(calculator->GetMaximum()); - IODEnhGeneralEquipmentModule::EquipmentInfo eq = getEnhEquipmentInfo(); - ContentIdentificationMacro contentID = createContentIdentificationInformation(metaInfo); + IODEnhGeneralEquipmentModule::EquipmentInfo eq = ParametricMapConverter::getEnhEquipmentInfo(); + ContentIdentificationMacro contentID = ParametricMapConverter::createContentIdentificationInformation(metaInfo); CHECK_COND(contentID.setInstanceNumber(metaInfo.getInstanceNumber().c_str())); // TODO: following should maybe be moved to meta info @@ -434,7 +448,7 @@ namespace dcmqi { return output; } - pair ParaMapConverter::paramap2itkimage(DcmDataset *pmapDataset) { + pair ParametricMapConverter::paramap2itkimage(DcmDataset *pmapDataset) { DcmRLEDecoderRegistration::registerCodecs(); @@ -550,7 +564,7 @@ namespace dcmqi { return pair (pmImage, metaInfo.getJSONOutputAsString()); } - OFCondition ParaMapConverter::addFrame(DPMParametricMapIOD &map, const FloatImageType::Pointer ¶metricMapImage, + OFCondition ParametricMapConverter::addFrame(DPMParametricMapIOD &map, const FloatImageType::Pointer ¶metricMapImage, const JSONParametricMapMetaInformationHandler &metaInfo, const unsigned long frameNo, OFVector groups) { @@ -610,7 +624,7 @@ namespace dcmqi { return result; } - void ParaMapConverter::populateMetaInformationFromDICOM(DcmDataset *pmapDataset, + void ParametricMapConverter::populateMetaInformationFromDICOM(DcmDataset *pmapDataset, JSONParametricMapMetaInformationHandler &metaInfo) { OFvariant result = DPMParametricMapIOD::loadDataset(*pmapDataset); diff --git a/libsrc/SegmentVolume.cpp b/libsrc/SegmentVolume.cpp new file mode 100644 index 00000000..e37c473a --- /dev/null +++ b/libsrc/SegmentVolume.cpp @@ -0,0 +1,5 @@ +// +// Created by Andrey Fedorov on 3/9/17. +// + +#include "dcmqi/SegmentVolume.h" diff --git a/libsrc/ImageSEGConverter.cpp b/libsrc/SegmentationImageConverter.cpp similarity index 98% rename from libsrc/ImageSEGConverter.cpp rename to libsrc/SegmentationImageConverter.cpp index 1eaf4591..f1e0fe1a 100644 --- a/libsrc/ImageSEGConverter.cpp +++ b/libsrc/SegmentationImageConverter.cpp @@ -1,11 +1,11 @@ // DCMQI includes -#include "dcmqi/ImageSEGConverter.h" +#include "dcmqi/SegmentationImageConverter.h" namespace dcmqi { - DcmDataset* ImageSEGConverter::itkimage2dcmSegmentation(vector dcmDatasets, + DcmDataset* SegmentationImageConverter::itkimage2dcmSegmentation(vector dcmDatasets, vector segmentations, const string &metaData, bool skipEmptySlices) { @@ -448,7 +448,7 @@ namespace dcmqi { } - pair , string> ImageSEGConverter::dcmSegmentation2itkimage(DcmDataset *segDataset) { + pair , string> SegmentationImageConverter::dcmSegmentation2itkimage(DcmDataset *segDataset) { DcmRLEDecoderRegistration::registerCodecs(); @@ -704,7 +704,7 @@ namespace dcmqi { return pair , string>(segment2image, metaInfo.getJSONOutputAsString()); } - void ImageSEGConverter::populateMetaInformationFromDICOM(DcmDataset *segDataset, DcmSegmentation *segdoc, + void SegmentationImageConverter::populateMetaInformationFromDICOM(DcmDataset *segDataset, DcmSegmentation *segdoc, JSONSegmentationMetaInformationHandler &metaInfo) { OFString creatorName, sessionID, timePointID, seriesDescription, seriesNumber, instanceNumber, bodyPartExamined, coordinatingCenter; From 9340926b7c02a99299ff31626af4cae93d69596d Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Sat, 11 Mar 2017 12:24:19 -0500 Subject: [PATCH 072/116] BUG: initialize modality from the source dataset --- libsrc/ParametricMapConverter.cpp | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/libsrc/ParametricMapConverter.cpp b/libsrc/ParametricMapConverter.cpp index c921a10c..f1897904 100644 --- a/libsrc/ParametricMapConverter.cpp +++ b/libsrc/ParametricMapConverter.cpp @@ -25,9 +25,20 @@ namespace dcmqi { return pair(); }; + + ParametricMapConverter::ParametricMapConverter(const FloatImageType::Pointer ¶metricMapImage, vector dcmDatasets, + const string &metaData){ + this->originalRepresentation = ITK_REPR; + } + + ParametricMapConverter::ParametricMapConverter(DcmDataset* dcmDataset){ + this->originalRepresentation = DICOM_REPR; + } + DcmDataset* ParametricMapConverter::itkimage2paramap(const FloatImageType::Pointer ¶metricMapImage, vector dcmDatasets, const string &metaData) { + // Calculate intensity range - required MinMaxCalculatorType::Pointer calculator = MinMaxCalculatorType::New(); calculator->SetImage(parametricMapImage); calculator->Compute(); @@ -42,15 +53,15 @@ namespace dcmqi { ContentIdentificationMacro contentID = ParametricMapConverter::createContentIdentificationInformation(metaInfo); CHECK_COND(contentID.setInstanceNumber(metaInfo.getInstanceNumber().c_str())); - // TODO: following should maybe be moved to meta info - OFString imageFlavor = "VOLUME"; - OFString pixContrast = "NONE"; - if(metaInfo.metaInfoRoot.isMember("DerivedPixelContrast")){ - pixContrast = metaInfo.metaInfoRoot["DerivedPixelContrast"].asCString(); - } - // TODO: initialize modality from the source / add to schema? - OFString modality = "MR"; + DcmDataset* srcDataset = NULL; + if(dcmDatasets.size()) { + srcDataset = dcmDatasets[0]; + } else { + return NULL; + } + OFString modality; + srcDataset->findAndGetOFString(DCM_Modality, modality); FloatImageType::SizeType inputSize = parametricMapImage->GetBufferedRegion().GetSize(); cout << "Input image size: " << inputSize << endl; @@ -59,13 +70,12 @@ namespace dcmqi { DPMParametricMapIOD::create(modality, metaInfo.getSeriesNumber().c_str(), metaInfo.getInstanceNumber().c_str(), inputSize[1], inputSize[0], eq, contentID, - imageFlavor, pixContrast, DPMTypes::CQ_RESEARCH); + "VOLUME", "QUANTITY", DPMTypes::CQ_RESEARCH); if (OFCondition* pCondition = OFget(&obj)) return NULL; DPMParametricMapIOD* pMapDoc = OFget(&obj); - DcmDataset* srcDataset = NULL; if(dcmDatasets.size()){ srcDataset = dcmDatasets[0]; } From ab76d22422ea315e0523ee57ac8c900840d22351 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Tue, 14 Mar 2017 12:18:05 -0400 Subject: [PATCH 073/116] ENH: a different shot at refactoring --- include/dcmqi/Helper.h | 4 +- include/dcmqi/ImageVolume.h | 12 +- include/dcmqi/ImageVolumeGeometry.h | 47 +++++++ .../JSONSegmentationMetaInformationHandler.h | 1 + include/dcmqi/MultiframeConverter.h | 7 +- include/dcmqi/MultiframeObject.h | 131 ++++++++++++++++++ include/dcmqi/ParametricMapConverter.h | 3 +- include/dcmqi/ParametricMapObject.h | 62 +++++++++ include/dcmqi/SegmentationImageObject.h | 16 +++ libsrc/Helper.cpp | 11 ++ libsrc/ImageVolumeGeometry.cpp | 42 ++++++ libsrc/MultiframeObject.cpp | 86 ++++++++++++ libsrc/ParametricMapConverter.cpp | 61 ++++---- libsrc/ParametricMapObject.cpp | 90 ++++++++++++ libsrc/SegmentationImageObject.cpp | 5 + 15 files changed, 541 insertions(+), 37 deletions(-) create mode 100644 include/dcmqi/ImageVolumeGeometry.h create mode 100644 include/dcmqi/MultiframeObject.h create mode 100644 include/dcmqi/ParametricMapObject.h create mode 100644 include/dcmqi/SegmentationImageObject.h create mode 100644 libsrc/ImageVolumeGeometry.cpp create mode 100644 libsrc/MultiframeObject.cpp create mode 100644 libsrc/ParametricMapObject.cpp create mode 100644 libsrc/SegmentationImageObject.cpp diff --git a/include/dcmqi/Helper.h b/include/dcmqi/Helper.h index 903bd8d0..ea2dbba3 100644 --- a/include/dcmqi/Helper.h +++ b/include/dcmqi/Helper.h @@ -19,7 +19,6 @@ using namespace std; - namespace dcmqi { @@ -64,6 +63,9 @@ namespace dcmqi { static void checkValidityOfFirstSrcImage(DcmSegmentation *segdoc); static CodeSequenceMacro* createNewCodeSequence(const string& code, const string& designator, const string& meaning); + + static OFString generateUID(); + static OFString getTagAsOFString(DcmDataset*, DcmTagKey); }; } diff --git a/include/dcmqi/ImageVolume.h b/include/dcmqi/ImageVolume.h index 13d37ef4..b6ebf83e 100644 --- a/include/dcmqi/ImageVolume.h +++ b/include/dcmqi/ImageVolume.h @@ -15,7 +15,11 @@ namespace dcmqi { // Maintain properties of the image volume // Attributes ara parallel to those of itkImageData, but limited to what we need for the task of conversion, - // and the class is not templated over the pixel type + // and the class is not templated over the pixel type, since we may not know the pixel type + // at the time class is instantiated. + // + // Initially, limit implementation and support to just Float32 used by the original PM converter. + class ImageVolume { public: // pixel types that are relevant for the types of objects we want to support @@ -47,7 +51,11 @@ namespace dcmqi { protected: int initializeDirections(FGInterface &); int initializeExtent(FGInterface &); - bool getDeclaredSpacing(FGInterface&); + bool getDeclaredSliceSpacing(FGInterface&); + bool getCalculatedSliceSpacing(); + + int setDirections(vnl_vector rowDirection, vnl_vector columnDirection, vnl_vector sliceDirection); + int setOrigin(vnl_vector); private: diff --git a/include/dcmqi/ImageVolumeGeometry.h b/include/dcmqi/ImageVolumeGeometry.h new file mode 100644 index 00000000..3857027c --- /dev/null +++ b/include/dcmqi/ImageVolumeGeometry.h @@ -0,0 +1,47 @@ +// +// Created by Andrey Fedorov on 3/11/17. +// + +#ifndef DCMQI_IMAGEVOLUMEGEOMETRY_H +#define DCMQI_IMAGEVOLUMEGEOMETRY_H + + +#include +#include +#include +#include + +class ImageVolumeGeometry { + + friend class MultiframeObject; + friend class ParametricMapObject; + +public: + typedef itk::Vector DoubleVectorType; + typedef itk::Size<3> SizeType; + + // implementation of the image volume geometry + // NB: duplicated from MultiframeObject! + typedef unsigned char DummyPixelType; + typedef itk::Image DummyImageType; + typedef DummyImageType::PointType PointType; + typedef DummyImageType::DirectionType DirectionType; + + ImageVolumeGeometry(); + + int setSpacing(DoubleVectorType); + int setOrigin(PointType); + int setExtent(SizeType); + int setDirections(DirectionType); + +protected: + // use vnl_vector to simplify support of vector calculations + vnl_vector rowDirection, columnDirection, sliceDirection; + vnl_vector origin; + vnl_vector extent; + vnl_vector spacing; + +}; + + +#endif //DCMQI_IMAGEVOLUMEGEOMETRY_H diff --git a/include/dcmqi/JSONSegmentationMetaInformationHandler.h b/include/dcmqi/JSONSegmentationMetaInformationHandler.h index 219b0087..7a893b15 100644 --- a/include/dcmqi/JSONSegmentationMetaInformationHandler.h +++ b/include/dcmqi/JSONSegmentationMetaInformationHandler.h @@ -47,6 +47,7 @@ namespace dcmqi { void readSegmentAttributes(); Json::Value createAndGetSegmentAttributes(); + }; } diff --git a/include/dcmqi/MultiframeConverter.h b/include/dcmqi/MultiframeConverter.h index 45b1a3c9..ff0b5527 100644 --- a/include/dcmqi/MultiframeConverter.h +++ b/include/dcmqi/MultiframeConverter.h @@ -44,9 +44,12 @@ namespace dcmqi { class MultiframeConverter { public: - virtual int convert(); + //virtual int convert(); + MultiframeConverter(){ + }; + + - protected: static IODGeneralEquipmentModule::EquipmentInfo getEquipmentInfo(); static IODEnhGeneralEquipmentModule::EquipmentInfo getEnhEquipmentInfo(); static ContentIdentificationMacro createContentIdentificationInformation(JSONMetaInformationHandlerBase &metaInfo); diff --git a/include/dcmqi/MultiframeObject.h b/include/dcmqi/MultiframeObject.h new file mode 100644 index 00000000..715b45fc --- /dev/null +++ b/include/dcmqi/MultiframeObject.h @@ -0,0 +1,131 @@ +// +// Created by Andrey Fedorov on 3/11/17. +// + +#ifndef DCMQI_MULTIFRAMEOBJECT_H +#define DCMQI_MULTIFRAMEOBJECT_H + + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "dcmqi/Exceptions.h" +#include "dcmqi/ImageVolumeGeometry.h" + +using namespace std; +/* + * Class to support conversion between DICOM and ITK representations + * of multiframe objects + * + * + */ +class MultiframeObject { + +public: + + // initialization from DICOM is always from the dataset(s) + // that encode DICOM representation, optionally augmented by the + // derivation datasets that potentially can be helpful in some + // situations. + int initializeFromDICOM(std::vector sourceDataset); + int initializeFromDICOM(std::vector sourceDatasets, + std::vector derivationDatasets); + + // initialization from ITK will be specific to the derived types, + // although there will be some common initialization of the metadata + + // Output is always a single DcmDataset, since this is a multiframe + // object + DcmDataset* getDcmDataset() const; + Json::Value getMetaDataJson() const; + + // get ITK representation will be specific to the derived classes, + // since the type of the ITK image and the number of ITK images is + // different between PM and SEG + +protected: + + // Helpers to convert to dummy image type to support common + // implementation of the image volume geometry + typedef unsigned char DummyPixelType; + typedef itk::Image DummyImageType; + typedef DummyImageType::PointType PointType; + typedef DummyImageType::DirectionType DirectionType; + + int initializeMetaDataFromString(const std::string&); + // what this function does depends on whether we are coming from + // DICOM or from ITK. No parameters, since all it does is exchange + // between DICOM and MetaData + int initializeEquipmentInfo(); + int initializeContentIdentification(); + + // from ITK + int initializeVolumeGeometryFromITK(DummyImageType::Pointer); + + virtual int initializeCompositeContext(); + virtual bool metaDataIsComplete(); + + // List of tags, and FGs they belong to, for initializing dimensions module + int initializeDimensions(IODMultiframeDimensionModule&, std::vector >); + + // constants to describe original representation of the data being converted + enum { + DICOM_REPR = 0, + ITK_REPR + }; + int sourceRepresentationType; + + // TODO: abstract this into a different class, which would help with: + // - checking for presence of attributes + // - handling of defaults (in the future, initialized from the schema?) + // - simplifying common access patterns (access to the Code tuples) + Json::Value metaDataJson; + + // Multiframe DICOM object representation + DcmDataset* dcmRepresentation; + + ImageVolumeGeometry volumeGeometry; + + // DcmDataset(s) that hold the original representation of the + // object, when the sourceRepresentationType == DICOM_REPR + OFVector sourceDcmDatasets; + + // Common components present in the derived classes + // TODO: check whether both PM and SEG use Enh module or not, refactor based on that + IODEnhGeneralEquipmentModule::EquipmentInfo equipmentInfoModule; + ContentIdentificationMacro contentIdentificationMacro; + IODMultiframeDimensionModule dimensionsModule; + + // DcmDataset(s) that were used to derive the object + // Probably will only be populated when sourceRepresentationType == ITK_REPR + // Purpose of those: + // 1) initialize derivation derivationImageFG (ITK->) + // 2) initialize CommonInstanceReferenceModule (ITK->) + // 3) initialize common attributes (ITK->) + OFVector derivationDcmDatasets; + + // Functional groups common to all MF objects: + // - Shared + FGPixelMeasures pixelMeasuresFG; + FGPlaneOrientationPatient planeOrientationPatientFG; + // - Per-frame + OFVector planePosPatientFGList; + OFVector frameContentFGList; + OFVector derivationImageFGList; + +}; + + +#endif //DCMQI_MULTIFRAMEOBJECT_H diff --git a/include/dcmqi/ParametricMapConverter.h b/include/dcmqi/ParametricMapConverter.h index 6545e39c..57c244d3 100644 --- a/include/dcmqi/ParametricMapConverter.h +++ b/include/dcmqi/ParametricMapConverter.h @@ -50,7 +50,7 @@ namespace dcmqi { const string &metaData); static pair paramap2itkimage(DcmDataset *pmapDataset); - // do the conversion + // given one representation, generate the parallel one int convert(); // get the result @@ -75,6 +75,7 @@ namespace dcmqi { // these are the items we will need in the process of conversion vector referencedDatasets; string metaData; + }; } diff --git a/include/dcmqi/ParametricMapObject.h b/include/dcmqi/ParametricMapObject.h new file mode 100644 index 00000000..32cbcf3f --- /dev/null +++ b/include/dcmqi/ParametricMapObject.h @@ -0,0 +1,62 @@ +// +// Created by Andrey Fedorov on 3/11/17. +// + +#ifndef DCMQI_PARAMETRICMAPOBJECT_H +#define DCMQI_PARAMETRICMAPOBJECT_H + + +#include +#include +#include +#include +#include +#include + +#include "MultiframeObject.h" + +/* + * + */ +class ParametricMapObject : public MultiframeObject { +public: + typedef IODFloatingPointImagePixelModule::value_type Float32PixelType; + typedef itk::Image Float32ITKImageType; + + // metadata is mandatory, since not all of the attributes can be present + // in the derivation DcmDataset(s) + int initializeFromITK(Float32ITKImageType::Pointer, const string&); + // metadata is mandatory, optionally, derivation DcmDataset(s) can + // help + int initializeFromITK(Float32ITKImageType::Pointer, const string&, + std::vector); + + int updateMetaDataFromDICOM(std::vector); + +protected: + typedef itk::CastImageFilter + Float32ToDummyCasterType; + + int initializeVolumeGeometry(); + int createParametricMap(); + int initializeDimensionsModule(); + int initializeCompositeContext(); + + // Functional groups initialization + + // Functional groups specific to PM: + // - Shared + FGFrameAnatomy frameAnatomyFG; + FGIdentityPixelValueTransformation identityPixelValueTransformationFG; + FGParametricMapFrameType parametricMapFrameTypeFG; + FGRealWorldValueMapping rwvmFG; + + // Data containers specific to this object + Float32ITKImageType::Pointer itkImage; + +private: + DPMParametricMapIOD* parametricMap; +}; + + +#endif //DCMQI_PARAMETRICMAPOBJECT_H diff --git a/include/dcmqi/SegmentationImageObject.h b/include/dcmqi/SegmentationImageObject.h new file mode 100644 index 00000000..74b7e8d8 --- /dev/null +++ b/include/dcmqi/SegmentationImageObject.h @@ -0,0 +1,16 @@ +// +// Created by Andrey Fedorov on 3/11/17. +// + +#ifndef DCMQI_SEGMENTATIONIMAGEOBJECT_H +#define DCMQI_SEGMENTATIONIMAGEOBJECT_H + + +#include "MultiframeObject.h" + +class SegmentationImageObject : public MultiframeObject { + +}; + + +#endif //DCMQI_SEGMENTATIONIMAGEOBJECT_H diff --git a/libsrc/Helper.cpp b/libsrc/Helper.cpp index 92f69c3a..6bcf8ecb 100644 --- a/libsrc/Helper.cpp +++ b/libsrc/Helper.cpp @@ -1,5 +1,6 @@ // DCMQI includes +#include #include "dcmqi/Helper.h" namespace dcmqi { @@ -368,5 +369,15 @@ namespace dcmqi { return new CodeSequenceMacro(code.c_str(), designator.c_str(), meaning.c_str()); } + OFString Helper::generateUID() { + char charUID[128]; + dcmGenerateUniqueIdentifier(charUID, QIICR_UID_ROOT); + return OFString(charUID); + } + OFString Helper::getTagAsOFString(DcmDataset* dcm, DcmTagKey tag) { + OFString value; + CHECK_COND(dcm->findAndGetOFString(tag, value)); + return value; + } } diff --git a/libsrc/ImageVolumeGeometry.cpp b/libsrc/ImageVolumeGeometry.cpp new file mode 100644 index 00000000..10570b0f --- /dev/null +++ b/libsrc/ImageVolumeGeometry.cpp @@ -0,0 +1,42 @@ +// +// Created by Andrey Fedorov on 3/11/17. +// + +#include "dcmqi/ImageVolumeGeometry.h" + +ImageVolumeGeometry::ImageVolumeGeometry() { + rowDirection.set_size(3); + columnDirection.set_size(3); + sliceDirection.set_size(3); + spacing.set_size(3); + extent.set_size(3); + origin.set_size(3); +} + +int ImageVolumeGeometry::setSpacing(DoubleVectorType s) { + for(int i=0;i<3;i++) + spacing[i] = s[i]; + return EXIT_SUCCESS; +} + +int ImageVolumeGeometry::setOrigin(PointType s) { + for(int i=0;i<3;i++) + origin[i] = s[i]; + return EXIT_SUCCESS; +} + +int ImageVolumeGeometry::setExtent(SizeType s) { + for (int i = 0; i < 3; i++) + extent[i] = s[i]; + return EXIT_SUCCESS; +} + +int ImageVolumeGeometry::setDirections(DirectionType d) { + for (int i = 0; i < 3; i++) + rowDirection[i] = d[i][0]; + for (int i = 0; i < 3; i++) + columnDirection[i] = d[i][1]; + for (int i = 0; i < 3; i++) + sliceDirection[i] = d[i][2]; + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp new file mode 100644 index 00000000..563a0012 --- /dev/null +++ b/libsrc/MultiframeObject.cpp @@ -0,0 +1,86 @@ +// +// Created by Andrey Fedorov on 3/11/17. +// + +#include +#include "dcmqi/QIICRConstants.h" +#include "dcmqi/MultiframeObject.h" + +int MultiframeObject::initializeFromDICOM(std::vector sourceDataset) { + return EXIT_SUCCESS; +} + +int MultiframeObject::initializeMetaDataFromString(const std::string &metaDataStr) { + std::istringstream metaDataStream(metaDataStr); + metaDataStream >> metaDataJson; + return EXIT_SUCCESS; +} + +int MultiframeObject::initializeEquipmentInfo() { + if(sourceRepresentationType == ITK_REPR){ + equipmentInfoModule.m_Manufacturer = QIICR_MANUFACTURER; + equipmentInfoModule.m_DeviceSerialNumber = QIICR_DEVICE_SERIAL_NUMBER; + equipmentInfoModule.m_ManufacturerModelName = QIICR_MANUFACTURER_MODEL_NAME; + equipmentInfoModule.m_SoftwareVersions = QIICR_SOFTWARE_VERSIONS; + } else { // DICOM_REPR + } + return EXIT_SUCCESS; +} + +int MultiframeObject::initializeContentIdentification() { + if(sourceRepresentationType == ITK_REPR){ + CHECK_COND(contentIdentificationMacro.setContentCreatorName("dcmqi")); + if(metaDataJson.isMember("ContentDescription")){ + CHECK_COND(contentIdentificationMacro.setContentDescription(metaDataJson["ContentDescription"].asCString())); + } else { + CHECK_COND(contentIdentificationMacro.setContentDescription("DCMQI")); + } + if(metaDataJson.isMember("ContentLabel")){ + CHECK_COND(contentIdentificationMacro.setContentLabel(metaDataJson["ContentLabel"].asCString())); + } else { + CHECK_COND(contentIdentificationMacro.setContentLabel("DCMQI")); + } + return EXIT_SUCCESS; + } else { // DICOM_REPR + } + return EXIT_SUCCESS; +} + +int MultiframeObject::initializeVolumeGeometryFromITK(DummyImageType::Pointer image) { + DummyImageType::SpacingType spacing; + DummyImageType::PointType origin; + DummyImageType::DirectionType directions; + DummyImageType::SizeType extent; + + spacing = image->GetSpacing(); + directions = image->GetDirection(); + extent = image->GetLargestPossibleRegion().GetSize(); + + volumeGeometry.setSpacing(spacing); + volumeGeometry.setOrigin(origin); + volumeGeometry.setExtent(extent); + + volumeGeometry.setDirections(directions); + + return EXIT_SUCCESS; +} + +// for now, we do not support initialization from JSON only, +// and we don't have a way to validate metadata completeness - TODO! +bool MultiframeObject::metaDataIsComplete() { + return false; +} + +// List of tags, and FGs they belong to, for initializing dimensions module +int MultiframeObject::initializeDimensions(IODMultiframeDimensionModule& dimModule, + std::vector > dimTagList){ + OFString dimUID; + + dimUID = dcmqi::Helper::generateUID(); + for(int i=0;i dimTagPair = dimTagList[i]; + CHECK_COND(dimModule.addDimensionIndex(dimTagPair.first, dimUID, dimTagPair.second, + dimTagPair.first.getTagName())); + } + return EXIT_SUCCESS; +} diff --git a/libsrc/ParametricMapConverter.cpp b/libsrc/ParametricMapConverter.cpp index f1897904..94d2c511 100644 --- a/libsrc/ParametricMapConverter.cpp +++ b/libsrc/ParametricMapConverter.cpp @@ -26,34 +26,19 @@ namespace dcmqi { }; - ParametricMapConverter::ParametricMapConverter(const FloatImageType::Pointer ¶metricMapImage, vector dcmDatasets, - const string &metaData){ - this->originalRepresentation = ITK_REPR; - } - - ParametricMapConverter::ParametricMapConverter(DcmDataset* dcmDataset){ - this->originalRepresentation = DICOM_REPR; - } DcmDataset* ParametricMapConverter::itkimage2paramap(const FloatImageType::Pointer ¶metricMapImage, vector dcmDatasets, const string &metaData) { - // Calculate intensity range - required - MinMaxCalculatorType::Pointer calculator = MinMaxCalculatorType::New(); - calculator->SetImage(parametricMapImage); - calculator->Compute(); - JSONParametricMapMetaInformationHandler metaInfo(metaData); metaInfo.read(); - metaInfo.setFirstValueMapped(calculator->GetMinimum()); - metaInfo.setLastValueMapped(calculator->GetMaximum()); - + // Prepare ContentIdentification IODEnhGeneralEquipmentModule::EquipmentInfo eq = ParametricMapConverter::getEnhEquipmentInfo(); ContentIdentificationMacro contentID = ParametricMapConverter::createContentIdentificationInformation(metaInfo); CHECK_COND(contentID.setInstanceNumber(metaInfo.getInstanceNumber().c_str())); - // TODO: initialize modality from the source / add to schema? + // Get image modality from the source dataset DcmDataset* srcDataset = NULL; if(dcmDatasets.size()) { srcDataset = dcmDatasets[0]; @@ -61,11 +46,13 @@ namespace dcmqi { return NULL; } OFString modality; - srcDataset->findAndGetOFString(DCM_Modality, modality); + CHECK_COND(srcDataset->findAndGetOFString(DCM_Modality, modality)); + // Get size of the image to be converted FloatImageType::SizeType inputSize = parametricMapImage->GetBufferedRegion().GetSize(); cout << "Input image size: " << inputSize << endl; + // create Parametric map object OFvariant obj = DPMParametricMapIOD::create(modality, metaInfo.getSeriesNumber().c_str(), metaInfo.getInstanceNumber().c_str(), @@ -85,12 +72,12 @@ namespace dcmqi { CHECK_COND(pMapDoc->import(*srcDataset, OFTrue, OFTrue, OFTrue, OFFalse)); /* Initialize dimension module */ - char dimUID[128]; - dcmGenerateUniqueIdentifier(dimUID, QIICR_UID_ROOT); IODMultiframeDimensionModule &mfdim = pMapDoc->getIODMultiframeDimensionModule(); - OFCondition result = mfdim.addDimensionIndex(DCM_ImagePositionPatient, dimUID, + OFCondition result = mfdim.addDimensionIndex(DCM_ImagePositionPatient, Helper::generateUID(), DCM_RealWorldValueMappingSequence, "Frame position"); + // Initialize PM geometry + // Shared FGs: PixelMeasuresSequence { FGPixelMeasures *pixmsr = new FGPixelMeasures(); @@ -104,6 +91,8 @@ namespace dcmqi { spacingSStream << scientific << labelSpacing[2]; CHECK_COND(pixmsr->setSpacingBetweenSlices(spacingSStream.str().c_str())); CHECK_COND(pixmsr->setSliceThickness(spacingSStream.str().c_str())); + + // SHARED CHECK_COND(pMapDoc->addForAllFrames(*pixmsr)); } @@ -125,9 +114,12 @@ namespace dcmqi { Helper::floatToStrScientific(labelDirMatrix[2][1]).c_str()); //CHECK_COND(planor->setImageOrientationPatient(imageOrientationPatientStr)); + // SHARED CHECK_COND(pMapDoc->addForAllFrames(*planor)); } + + // Initialize metadata - anatomy FGFrameAnatomy frameAnaFG; frameAnaFG.setLaterality(FGFrameAnatomy::str2Laterality(metaInfo.getFrameLaterality().c_str())); if(metaInfo.metaInfoRoot.isMember("AnatomicRegionSequence")){ @@ -138,6 +130,8 @@ namespace dcmqi { } else { frameAnaFG.getAnatomy().getAnatomicRegion().set("T-D0050", "SRT", "Tissue"); } + + // SHARED CHECK_COND(pMapDoc->addForAllFrames(frameAnaFG)); FGIdentityPixelValueTransformation idTransFG; @@ -147,8 +141,11 @@ namespace dcmqi { FGParametricMapFrameType frameTypeFG; std::string frameTypeStr = "DERIVED\\PRIMARY\\VOLUME\\QUANTITY"; frameTypeFG.setFrameType(frameTypeStr.c_str()); + + // SHARED CHECK_COND(pMapDoc->addForAllFrames(frameTypeFG)); + // Initialize RWVM FGRealWorldValueMapping rwvmFG; FGRealWorldValueMapping::RWVMItem* realWorldValueMappingItem = new FGRealWorldValueMapping::RWVMItem(); if (!realWorldValueMappingItem ) @@ -159,8 +156,13 @@ namespace dcmqi { realWorldValueMappingItem->setRealWorldValueSlope(metaInfo.getRealWorldValueSlope()); realWorldValueMappingItem->setRealWorldValueIntercept(atof(metaInfo.getRealWorldValueIntercept().c_str())); - realWorldValueMappingItem->setRealWorldValueFirstValueMappedSigned(metaInfo.getFirstValueMapped()); - realWorldValueMappingItem->setRealWorldValueLastValueMappedSigned(metaInfo.getLastValueMapped()); + // Calculate intensity range - required + MinMaxCalculatorType::Pointer calculator = MinMaxCalculatorType::New(); + calculator->SetImage(parametricMapImage); + calculator->Compute(); + + realWorldValueMappingItem->setRealWorldValueFirstValueMappedSigned(calculator->GetMinimum()); + realWorldValueMappingItem->setRealWorldValueLastValueMappedSigned(calculator->GetMaximum()); CodeSequenceMacro* measurementUnitCode = metaInfo.getMeasurementUnitsCode(); if (measurementUnitCode != NULL) { @@ -189,7 +191,7 @@ namespace dcmqi { realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(quantity); quantity->setValueType(ContentItemMacro::VT_CODE); - // initialize optional items, if available + // initialize optional RWVM items, if available if(metaInfo.metaInfoRoot.isMember("MeasurementMethodCode")){ ContentItemMacro* measureMethod = new ContentItemMacro; CodeSequenceMacro* qCodeName = new CodeSequenceMacro("G-C306", "SRT", "Measurement Method"); @@ -399,12 +401,6 @@ namespace dcmqi { // Frame Content OFCondition result = fgfc->setDimensionIndexValues(sliceNumber+1 /* value within dimension */, 0 /* first dimension */); -#if ADD_DERIMG - // Already pushed above if siVector.size > 0 - // if(fgder) - // perFrameFGs.push_back(fgder); -#endif - DPMParametricMapIOD::FramesType frames = pMapDoc->getFrames(); result = OFget >(&frames)->addFrame(&*data.begin(), frameSize, perFrameFGs); @@ -574,6 +570,8 @@ namespace dcmqi { return pair (pmImage, metaInfo.getJSONOutputAsString()); } + // appears to be not used +#if 0 OFCondition ParametricMapConverter::addFrame(DPMParametricMapIOD &map, const FloatImageType::Pointer ¶metricMapImage, const JSONParametricMapMetaInformationHandler &metaInfo, const unsigned long frameNo, OFVector groups) @@ -633,6 +631,7 @@ namespace dcmqi { } return result; } +#endif void ParametricMapConverter::populateMetaInformationFromDICOM(DcmDataset *pmapDataset, JSONParametricMapMetaInformationHandler &metaInfo) { @@ -671,7 +670,7 @@ namespace dcmqi { Float64 slope; // TODO: replace the following call by following getter once it is available -// item->getRealWorldValueSlope(slope); + //item->getRealWorldValueSlope(slope); item->getData().findAndGetFloat64(DCM_RealWorldValueSlope, slope); metaInfo.setRealWorldValueSlope(slope); diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp new file mode 100644 index 00000000..579e79ea --- /dev/null +++ b/libsrc/ParametricMapObject.cpp @@ -0,0 +1,90 @@ +// +// Created by Andrey Fedorov on 3/11/17. +// + +#include +#include "dcmqi/ParametricMapObject.h" + +int ParametricMapObject::initializeFromITK(Float32ITKImageType::Pointer inputImage, + const string &metaDataStr, + std::vector derivationDatasets) { + sourceRepresentationType = ITK_REPR; + + itkImage = inputImage; + + initializeMetaDataFromString(metaDataStr); + + if(!metaDataIsComplete()){ + updateMetaDataFromDICOM(derivationDatasets); + } + + initializeVolumeGeometry(); + + createParametricMap(); + + initializeCompositeContext(); + + return EXIT_SUCCESS; +} + +int ParametricMapObject::initializeVolumeGeometry() { + if(sourceRepresentationType == ITK_REPR){ + + Float32ToDummyCasterType::Pointer caster = + Float32ToDummyCasterType::New(); + caster->SetInput(itkImage); + caster->Update(); + + MultiframeObject::initializeVolumeGeometryFromITK(caster->GetOutput()); + } else { + + } + return EXIT_SUCCESS; +} + +int ParametricMapObject::updateMetaDataFromDICOM(std::vector dcmList) { + if(!dcmList.size()) + return EXIT_FAILURE; + + DcmDataset* dcm = dcmList[0]; + metaDataJson["Modality"] = + std::string(dcmqi::Helper::getTagAsOFString(dcm, DCM_Modality).c_str()); + + return EXIT_SUCCESS; +} + +int ParametricMapObject::createParametricMap() { + + // create Parametric map object + + OFvariant obj = + DPMParametricMapIOD::create(metaDataJson["Modality"].asCString(), + metaDataJson["SeriesNumber"].asCString(), + metaDataJson["InstanceNumber"].asCString(), + volumeGeometry.extent[0], + volumeGeometry.extent[1], + equipmentInfoModule, + contentIdentificationMacro, + "VOLUME", "QUANTITY", + + DPMTypes::CQ_RESEARCH); + // TODO: look into the following, check with @che85 on the purpose of this line! + if (OFCondition* pCondition = OFget(&obj)) + return EXIT_FAILURE; + + parametricMap = OFget(&obj); + + return EXIT_SUCCESS; +} + +int ParametricMapObject::initializeCompositeContext() { + if(derivationDcmDatasets.size()){ + CHECK_COND(parametricMap->import(*derivationDcmDatasets[0], OFTrue, OFTrue, OFFalse, OFTrue)); + + } else { + // TODO: once we support passing of composite context in metadata, propagate it + // into parametricMap here + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/libsrc/SegmentationImageObject.cpp b/libsrc/SegmentationImageObject.cpp new file mode 100644 index 00000000..aab623e3 --- /dev/null +++ b/libsrc/SegmentationImageObject.cpp @@ -0,0 +1,5 @@ +// +// Created by Andrey Fedorov on 3/11/17. +// + +#include "dcmqi/SegmentationImageObject.h" From ad10310acbfb0b27a26c30b38181b38814e5bd17 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Wed, 15 Mar 2017 21:12:33 -0400 Subject: [PATCH 074/116] ENH: adding functionality - temp commit --- docker/dcmqi/imagefiles/docker_entry.sh | 28 ---- include/dcmqi/Helper.h | 5 + include/dcmqi/MultiframeObject.h | 6 +- include/dcmqi/ParametricMapObject.h | 4 +- libsrc/Helper.cpp | 12 ++ libsrc/MultiframeObject.cpp | 33 ++++- libsrc/ParametricMapObject.cpp | 163 ++++++++++++++++++++++++ 7 files changed, 218 insertions(+), 33 deletions(-) delete mode 100644 docker/dcmqi/imagefiles/docker_entry.sh diff --git a/docker/dcmqi/imagefiles/docker_entry.sh b/docker/dcmqi/imagefiles/docker_entry.sh deleted file mode 100644 index 2cecef2d..00000000 --- a/docker/dcmqi/imagefiles/docker_entry.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash - -case "$1" in - itkimage2paramap|paramap2itkimage|itkimage2segimage|segimage2itkimage|tid1500reader|tid1500writer) - $1 "${@:2}" - ;; - *) - echo "ERROR: Unknown command" - echo "" - cat >&2 <:] qiicr/dcmqi [args] - -Run the given dcmqi *command*. - -Available commands are: - itkimage2paramap - paramap2itkimage - itkimage2segimage - segimage2itkimage - tid1500reader - tid1500writer - -For *command* help use: docker run qiicr/dcmqi --help -ENDHELP - exit 1 - ;; -esac - diff --git a/include/dcmqi/Helper.h b/include/dcmqi/Helper.h index ea2dbba3..d1dc966e 100644 --- a/include/dcmqi/Helper.h +++ b/include/dcmqi/Helper.h @@ -17,6 +17,8 @@ // DCMQI includes #include "dcmqi/Exceptions.h" +#include + using namespace std; namespace dcmqi { @@ -59,6 +61,9 @@ namespace dcmqi { static CodeSequenceMacro stringToCodeSequenceMacro(string str); static DSRCodedEntryValue stringToDSRCodedEntryValue(string str); + static string codeSequenceMacroToString(CodeSequenceMacro); + + static CodeSequenceMacro jsonToCodeSequenceMacro(Json::Value); static void checkValidityOfFirstSrcImage(DcmSegmentation *segdoc); diff --git a/include/dcmqi/MultiframeObject.h b/include/dcmqi/MultiframeObject.h index 715b45fc..3e297b4f 100644 --- a/include/dcmqi/MultiframeObject.h +++ b/include/dcmqi/MultiframeObject.h @@ -77,8 +77,12 @@ class MultiframeObject { virtual int initializeCompositeContext(); virtual bool metaDataIsComplete(); + + // List of tags, and FGs they belong to, for initializing dimensions module - int initializeDimensions(IODMultiframeDimensionModule&, std::vector >); + int initializeDimensions(std::vector >); + int initializePixelMeasuresFG(); + int initializePlaneOrientationFG(); // constants to describe original representation of the data being converted enum { diff --git a/include/dcmqi/ParametricMapObject.h b/include/dcmqi/ParametricMapObject.h index 32cbcf3f..eee341f6 100644 --- a/include/dcmqi/ParametricMapObject.h +++ b/include/dcmqi/ParametricMapObject.h @@ -36,11 +36,13 @@ class ParametricMapObject : public MultiframeObject { protected: typedef itk::CastImageFilter Float32ToDummyCasterType; + typedef itk::MinimumMaximumImageCalculator MinMaxCalculatorType; int initializeVolumeGeometry(); int createParametricMap(); - int initializeDimensionsModule(); int initializeCompositeContext(); + int initializeFrameAnatomyFG(); + int initializeRWVMFG(); // Functional groups initialization diff --git a/libsrc/Helper.cpp b/libsrc/Helper.cpp index 6bcf8ecb..9e5b7af1 100644 --- a/libsrc/Helper.cpp +++ b/libsrc/Helper.cpp @@ -380,4 +380,16 @@ namespace dcmqi { CHECK_COND(dcm->findAndGetOFString(tag, value)); return value; } + + CodeSequenceMacro Helper::jsonToCodeSequenceMacro(Json::Value jv){ + return CodeSequenceMacro(jv["CodeValue"].asCString(), + jv["CodingSchemeDesignator"].asCString(), + jv["CodeMeaning"].asCString()); + } + + string Helper::codeSequenceMacroToString(CodeSequenceMacro c){ + OFString codeValue, codingSchemeDesignator, codeMeaning; + string s = string()+codeValue.c_str()+","+codingSchemeDesignator.c_str()+","+codeMeaning.c_str(); + return s; + } } diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index 563a0012..37ac0226 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -72,15 +72,42 @@ bool MultiframeObject::metaDataIsComplete() { } // List of tags, and FGs they belong to, for initializing dimensions module -int MultiframeObject::initializeDimensions(IODMultiframeDimensionModule& dimModule, - std::vector > dimTagList){ +int MultiframeObject::initializeDimensions(std::vector > dimTagList){ OFString dimUID; + dimensionsModule.clearData(); + dimUID = dcmqi::Helper::generateUID(); for(int i=0;i dimTagPair = dimTagList[i]; - CHECK_COND(dimModule.addDimensionIndex(dimTagPair.first, dimUID, dimTagPair.second, + CHECK_COND(dimensionsModule.addDimensionIndex(dimTagPair.first, dimUID, dimTagPair.second, dimTagPair.first.getTagName())); } return EXIT_SUCCESS; } + +int MultiframeObject::initializePixelMeasuresFG(){ + string pixelSpacingStr, sliceSpacingStr; + + pixelSpacingStr = dcmqi::Helper::floatToStrScientific(volumeGeometry.spacing[0])+ + "\\"+dcmqi::Helper::floatToStrScientific(volumeGeometry.spacing[1]); + sliceSpacingStr = dcmqi::Helper::floatToStrScientific(volumeGeometry.spacing[2]); + + CHECK_COND(pixelMeasuresFG.setPixelSpacing(pixelSpacingStr.c_str())); + CHECK_COND(pixelMeasuresFG.setSpacingBetweenSlices(sliceSpacingStr.c_str())); + CHECK_COND(pixelMeasuresFG.setSliceThickness(sliceSpacingStr.c_str())); + + return EXIT_SUCCESS; +} + +int MultiframeObject::initializePlaneOrientationFG() { + planeOrientationPatientFG.setImageOrientationPatient( + dcmqi::Helper::floatToStrScientific(volumeGeometry.rowDirection[0]).c_str(), + dcmqi::Helper::floatToStrScientific(volumeGeometry.rowDirection[1]).c_str(), + dcmqi::Helper::floatToStrScientific(volumeGeometry.rowDirection[2]).c_str(), + dcmqi::Helper::floatToStrScientific(volumeGeometry.columnDirection[0]).c_str(), + dcmqi::Helper::floatToStrScientific(volumeGeometry.columnDirection[1]).c_str(), + dcmqi::Helper::floatToStrScientific(volumeGeometry.columnDirection[2]).c_str() + ); + return EXIT_SUCCESS; +} diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 579e79ea..6bbf9a52 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -3,6 +3,7 @@ // #include +#include #include "dcmqi/ParametricMapObject.h" int ParametricMapObject::initializeFromITK(Float32ITKImageType::Pointer inputImage, @@ -20,10 +21,25 @@ int ParametricMapObject::initializeFromITK(Float32ITKImageType::Pointer inputIma initializeVolumeGeometry(); + // TODO: consider creating parametric map object after all FGs are initialized instead createParametricMap(); + // populate metadata about patient/study, from derivation + // datasets or from metadata initializeCompositeContext(); + // populate functional groups + std::vector > dimensionTags; + dimensionTags.push_back(std::pair(DCM_ImagePositionPatient, DCM_PlanePositionSequence)); + initializeDimensions(dimensionTags); + + initializePixelMeasuresFG(); + initializePlaneOrientationFG(); + + // PM-specific FGs + initializeFrameAnatomyFG(); + initializeRWVMFG(); + return EXIT_SUCCESS; } @@ -74,10 +90,20 @@ int ParametricMapObject::createParametricMap() { parametricMap = OFget(&obj); + // These FG are constant + FGIdentityPixelValueTransformation idTransFG; + CHECK_COND(parametricMap->addForAllFrames(idTransFG)); + + FGParametricMapFrameType frameTypeFG; + std::string frameTypeStr = "DERIVED\\PRIMARY\\VOLUME\\QUANTITY"; + frameTypeFG.setFrameType(frameTypeStr.c_str()); + CHECK_COND(parametricMap->addForAllFrames(frameTypeFG)); + return EXIT_SUCCESS; } int ParametricMapObject::initializeCompositeContext() { + // TODO: should this be done in the parent? if(derivationDcmDatasets.size()){ CHECK_COND(parametricMap->import(*derivationDcmDatasets[0], OFTrue, OFTrue, OFFalse, OFTrue)); @@ -87,4 +113,141 @@ int ParametricMapObject::initializeCompositeContext() { return EXIT_FAILURE; } return EXIT_SUCCESS; +} + +int ParametricMapObject::initializeFrameAnatomyFG() { + if(metaDataJson.isMember("FrameLaterality")) + frameAnatomyFG.setLaterality(FGFrameAnatomy::str2Laterality(metaDataJson["FrameLaterality"].asCString())); + else + frameAnatomyFG.setLaterality(FGFrameAnatomy::str2Laterality("U")); + + // TODO: simplify code initialization from metadata + if(metaDataJson.isMember("AnatomicRegionSequence")){ + frameAnatomyFG.getAnatomy().getAnatomicRegion().set( + metaDataJson["AnatomicRegionSequence"]["CodeValue"].asCString(), + metaDataJson["AnatomicRegionSequence"]["CodingSchemeDesignator"].asCString(), + metaDataJson["AnatomicRegionSequence"]["CodeMeaning"].asCString()); + } else { + frameAnatomyFG.getAnatomy().getAnatomicRegion().set("T-D0050", "SRT", "Tissue"); + } + + return EXIT_SUCCESS; +} + +int ParametricMapObject::initializeRWVMFG() { + FGRealWorldValueMapping::RWVMItem* realWorldValueMappingItem = + new FGRealWorldValueMapping::RWVMItem(); + + if (!realWorldValueMappingItem ) + return EXIT_FAILURE; + + realWorldValueMappingItem->setRealWorldValueSlope(metaDataJson["RealWorldValueSlope"].asFloat()); + realWorldValueMappingItem->setRealWorldValueIntercept(0); + + // Calculate intensity range - required + MinMaxCalculatorType::Pointer calculator = MinMaxCalculatorType::New(); + calculator->SetImage(itkImage); + calculator->Compute(); + + realWorldValueMappingItem->setRealWorldValueFirstValueMappeSigned(calculator->GetMinimum()); + realWorldValueMappingItem->setRealWorldValueLastValueMappedSigned(calculator->GetMaximum()); + + if(metaDataJson.isMember("MeasurementsUnitsCode")){ + CodeSequenceMacro& unitsCodeDcmtk = realWorldValueMappingItem->getMeasurementUnitsCode(); + unitsCodeDcmtk = dcmqi::Helper::jsonToCodeSequenceMacro(metaDataJson["MeasurementsUnitsCode"]); + cout << "Measurements units initialized to " << + dcmqi::Helper::codeSequenceMacroToString(unitsCodeDcmtk); + realWorldValueMappingItem->setLUTExplanation(metaDataJson["MeasurementUnitsCode"]["CodeMeaning"].asCString()); + realWorldValueMappingItem->setLUTLabel(metaDataJson["MeasurementUnitsCode"]["CodeValue"].asCString()); + } + + if(metaDataJson.isMember("QuantityValueCode")){ + CodeSequenceMacro& unitsCodeDcmtk = realWorldValueMappingItem->getMeasurementUnitsCode(); + unitsCodeDcmtk = dcmqi::Helper::jsonToCodeSequenceMacro(metaDataJson["QuantityValueCode"]); + + ContentItemMacro* quantity = new ContentItemMacro; + CodeSequenceMacro* qCodeName = new CodeSequenceMacro("G-C1C6", "SRT", "Quantity"); + CodeSequenceMacro* qSpec = new CodeSequenceMacro( + metaDataJson["QuantityValueCode"]["CodeValue"].asCString(), + metaDataJson["QuantityValueCode"]["CodingSchemeDesignator"].asCString(), + metaDataJson["QuantityValueCode"]["CodeMeaning"].asCString()); + + if (!quantity || !qSpec || !qCodeName) + { + return NULL; + } + + quantity->getEntireConceptNameCodeSequence().push_back(qCodeName); + quantity->getEntireConceptCodeSequence().push_back(qSpec); + realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(quantity); + quantity->setValueType(ContentItemMacro::VT_CODE); + + } + + +#if 0 + + // initialize optional RWVM items, if available + if(metaInfo.metaInfoRoot.isMember("MeasurementMethodCode")){ + ContentItemMacro* measureMethod = new ContentItemMacro; + CodeSequenceMacro* qCodeName = new CodeSequenceMacro("G-C306", "SRT", "Measurement Method"); + CodeSequenceMacro* qSpec = new CodeSequenceMacro( + metaInfo.metaInfoRoot["MeasurementMethodCode"]["CodeValue"].asCString(), + metaInfo.metaInfoRoot["MeasurementMethodCode"]["CodingSchemeDesignator"].asCString(), + metaInfo.metaInfoRoot["MeasurementMethodCode"]["CodeMeaning"].asCString()); + + if (!measureMethod || !qSpec || !qCodeName) + { + return NULL; + } + + measureMethod->getEntireConceptNameCodeSequence().push_back(qCodeName); + measureMethod->getEntireConceptCodeSequence().push_back(qSpec); + realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(measureMethod); + measureMethod->setValueType(ContentItemMacro::VT_CODE); + } + + if(metaInfo.metaInfoRoot.isMember("ModelFittingMethodCode")){ + ContentItemMacro* fittingMethod = new ContentItemMacro; + CodeSequenceMacro* qCodeName = new CodeSequenceMacro("DWMPxxxxx2", "99QIICR", "Model fitting method"); + CodeSequenceMacro* qSpec = new CodeSequenceMacro( + metaInfo.metaInfoRoot["ModelFittingMethodCode"]["CodeValue"].asCString(), + metaInfo.metaInfoRoot["ModelFittingMethodCode"]["CodingSchemeDesignator"].asCString(), + metaInfo.metaInfoRoot["ModelFittingMethodCode"]["CodeMeaning"].asCString()); + + if (!fittingMethod || !qSpec || !qCodeName) + { + return NULL; + } + + fittingMethod->getEntireConceptNameCodeSequence().push_back(qCodeName); + fittingMethod->getEntireConceptCodeSequence().push_back(qSpec); + realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(fittingMethod); + fittingMethod->setValueType(ContentItemMacro::VT_CODE); + } + + if(metaInfo.metaInfoRoot.isMember("SourceImageDiffusionBValues")){ + for(int bvalId=0;bvalIdsetValueType(ContentItemMacro::VT_NUMERIC); + bval->getEntireConceptNameCodeSequence().push_back(qCodeName); + bval->getEntireMeasurementUnitsCodeSequence().push_back(bvalUnits); + if(bval->setNumericValue(metaInfo.metaInfoRoot["SourceImageDiffusionBValues"][bvalId].asCString()).bad()) + cout << "Failed to insert the value!" << endl;; + realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(bval); + cout << bval->toString() << endl; + } + } + + rwvmFG.getRealWorldValueMapping().push_back(realWorldValueMappingItem); +#endif + return EXIT_SUCCESS; } \ No newline at end of file From 92df60398b21e9005a57176265ab83bc7b046e2a Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Tue, 21 Mar 2017 13:26:14 -0400 Subject: [PATCH 075/116] ENH: more updates --- include/dcmqi/MultiframeObject.h | 3 +- libsrc/MultiframeObject.cpp | 18 ++++++++++ libsrc/ParametricMapConverter.cpp | 1 + libsrc/ParametricMapObject.cpp | 57 ++++++++++++++++++++----------- 4 files changed, 59 insertions(+), 20 deletions(-) diff --git a/include/dcmqi/MultiframeObject.h b/include/dcmqi/MultiframeObject.h index 3e297b4f..874abd4f 100644 --- a/include/dcmqi/MultiframeObject.h +++ b/include/dcmqi/MultiframeObject.h @@ -20,6 +20,7 @@ #include #include #include +#include #include "dcmqi/Exceptions.h" #include "dcmqi/ImageVolumeGeometry.h" @@ -77,7 +78,7 @@ class MultiframeObject { virtual int initializeCompositeContext(); virtual bool metaDataIsComplete(); - + ContentItemMacro* initializeContentItemMacro(CodeSequenceMacro, CodeSequenceMacro); // List of tags, and FGs they belong to, for initializing dimensions module int initializeDimensions(std::vector >); diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index 37ac0226..d1396dd6 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -111,3 +111,21 @@ int MultiframeObject::initializePlaneOrientationFG() { ); return EXIT_SUCCESS; } + +ContentItemMacro* MultiframeObject::initializeContentItemMacro(CodeSequenceMacro conceptName, + CodeSequenceMacro conceptCode){ + ContentItemMacro* item = new ContentItemMacro(); + CodeSequenceMacro* concept = new CodeSequenceMacro(conceptName); + CodeSequenceMacro* value = new CodeSequenceMacro(conceptCode); + + if (!item || !concept || !value) + { + return NULL; + } + + item->getEntireConceptNameCodeSequence().push_back(concept); + item->getEntireConceptCodeSequence().push_back(value); + item->setValueType(ContentItemMacro::VT_CODE); + + return EXIT_SUCCESS; +} diff --git a/libsrc/ParametricMapConverter.cpp b/libsrc/ParametricMapConverter.cpp index 94d2c511..67f8982c 100644 --- a/libsrc/ParametricMapConverter.cpp +++ b/libsrc/ParametricMapConverter.cpp @@ -174,6 +174,7 @@ namespace dcmqi { // TODO: LutExplanation and LUTLabel should be added as Metainformation realWorldValueMappingItem->setLUTExplanation(metaInfo.metaInfoRoot["MeasurementUnitsCode"]["CodeMeaning"].asCString()); realWorldValueMappingItem->setLUTLabel(metaInfo.metaInfoRoot["MeasurementUnitsCode"]["CodeValue"].asCString()); + ContentItemMacro* quantity = new ContentItemMacro; CodeSequenceMacro* qCodeName = new CodeSequenceMacro("G-C1C6", "SRT", "Quantity"); CodeSequenceMacro* qSpec = new CodeSequenceMacro( diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 6bbf9a52..4e4bf9fc 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -154,37 +154,56 @@ int ParametricMapObject::initializeRWVMFG() { if(metaDataJson.isMember("MeasurementsUnitsCode")){ CodeSequenceMacro& unitsCodeDcmtk = realWorldValueMappingItem->getMeasurementUnitsCode(); - unitsCodeDcmtk = dcmqi::Helper::jsonToCodeSequenceMacro(metaDataJson["MeasurementsUnitsCode"]); + unitsCodeDcmtk.set(metaDataJson["MeasurementsUnitsCode"]["CodeValue"].asCString(), + metaDataJson["MeasurementsUnitsCode"]["CodingSchemeDesignator"].asCString(), + metaDataJson["MeasurementsUnitsCode"]["CodeMeaning"].asCString()); cout << "Measurements units initialized to " << dcmqi::Helper::codeSequenceMacroToString(unitsCodeDcmtk); + realWorldValueMappingItem->setLUTExplanation(metaDataJson["MeasurementUnitsCode"]["CodeMeaning"].asCString()); realWorldValueMappingItem->setLUTLabel(metaDataJson["MeasurementUnitsCode"]["CodeValue"].asCString()); } if(metaDataJson.isMember("QuantityValueCode")){ - CodeSequenceMacro& unitsCodeDcmtk = realWorldValueMappingItem->getMeasurementUnitsCode(); - unitsCodeDcmtk = dcmqi::Helper::jsonToCodeSequenceMacro(metaDataJson["QuantityValueCode"]); - - ContentItemMacro* quantity = new ContentItemMacro; - CodeSequenceMacro* qCodeName = new CodeSequenceMacro("G-C1C6", "SRT", "Quantity"); - CodeSequenceMacro* qSpec = new CodeSequenceMacro( - metaDataJson["QuantityValueCode"]["CodeValue"].asCString(), - metaDataJson["QuantityValueCode"]["CodingSchemeDesignator"].asCString(), - metaDataJson["QuantityValueCode"]["CodeMeaning"].asCString()); - - if (!quantity || !qSpec || !qCodeName) - { - return NULL; - } + ContentItemMacro* item = initializeContentItemMacro(CodeSequenceMacro("G-C1C6", "SRT", "Quantity"), + dcmqi::Helper::jsonToCodeSequenceMacro(metaDataJson["QuantityValueCode"])); + realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(item); + } - quantity->getEntireConceptNameCodeSequence().push_back(qCodeName); - quantity->getEntireConceptCodeSequence().push_back(qSpec); - realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(quantity); - quantity->setValueType(ContentItemMacro::VT_CODE); + // TODO: factor out defined CodeSequenceMacros into definitions as in dcmsr/include/dcmtk/dcmsr/codes + if(metaDataJson.isMember("MeasurementMethodCode")){ + ContentItemMacro* item = initializeContentItemMacro(CodeSequenceMacro("G-C306", "SRT", "Measurement Method"), + dcmqi::Helper::jsonToCodeSequenceMacro(metaDataJson["MeasurementMethodCode"])); + realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(item); + } + if(metaDataJson.isMember("ModelFittingMethodCode")){ + // TODO: update this once CP-1665 is integrated into the standard + ContentItemMacro* item = initializeContentItemMacro(CodeSequenceMacro("xxxxx2", "99DCMCP1665", "Model Fitting Method"), + dcmqi::Helper::jsonToCodeSequenceMacro(metaDataJson["ModelFittingMethodCode"])); + realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(item); } + if(metaDataJson.isMember("SourceImageDiffusionBValues")){ + for(int bvalId=0;bvalIdsetValueType(ContentItemMacro::VT_NUMERIC); + bval->getEntireConceptNameCodeSequence().push_back(qCodeName); + bval->getEntireMeasurementUnitsCodeSequence().push_back(bvalUnits); + if(bval->setNumericValue(metaDataJson["SourceImageDiffusionBValues"][bvalId].asCString()).bad()) + cout << "Failed to insert the value!" << endl;; + realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(bval); + } + } #if 0 // initialize optional RWVM items, if available From 4a6c043a3f715fd6507e03147755b2281f13ee28 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Sat, 8 Apr 2017 08:24:31 -0400 Subject: [PATCH 076/116] ENH: checkpointing updates to the branch --- include/dcmqi/DICOMFrame.h | 38 ++++++++++++++++- include/dcmqi/ImageVolumeGeometry.h | 5 +++ include/dcmqi/MultiframeConverter.h | 4 ++ include/dcmqi/MultiframeObject.h | 10 +++++ libsrc/DICOMFrame.cpp | 15 +++++++ libsrc/ImageVolume.cpp | 3 +- libsrc/ImageVolumeGeometry.cpp | 34 +++++++++++++++ libsrc/MultiframeObject.cpp | 39 +++++++++++++++++ libsrc/ParametricMapObject.cpp | 65 +---------------------------- 9 files changed, 147 insertions(+), 66 deletions(-) diff --git a/include/dcmqi/DICOMFrame.h b/include/dcmqi/DICOMFrame.h index f58a6955..9a31b611 100644 --- a/include/dcmqi/DICOMFrame.h +++ b/include/dcmqi/DICOMFrame.h @@ -6,6 +6,9 @@ #define DCMQI_DICOMFRAME_H #include +#include +#include "ImageVolumeGeometry.h" +#include "Exceptions.h" namespace dcmqi { @@ -19,18 +22,49 @@ namespace dcmqi { EnhancedInstanceFrame }; - DICOMFrame(DcmDataset *dataset, int number) : + DICOMFrame(DcmDataset *dataset, int number=0) : frameNumber(number), frameDataset(dataset) { + Uint32 numFrames; + if(dataset->findAndGetUint32(DCM_NumberOfFrames, numFrames).good()){ + frameType = EnhancedInstanceFrame; + if(!number){ + std::cerr << "ERROR: DICOMFrame object for an enhanced frame is initialized with frame 0!" << std::endl; + } + } else { + frameType = LegacyInstanceFrame; + initializeFrameGeometryFromLegacyInstance(); + } + }; + int getFrameNumber() const; // 0 for legacy datasets, 1 or above for enhanced objects + OFString getInstanceUID() const; + private: + + int initializeFrameGeometryFromLegacyInstance(); + int initializeFrameGeometryFromEnhancedInstance(); + int frameType; DcmDataset *frameDataset; int frameNumber; - OFString originStr; // revisit this + vnl_vector frameIPP; + + ImageVolumeGeometry frameGeometry; + + }; + + struct DICOMFrame_compare { + bool operator() (const DICOMFrame& lhs, const DICOMFrame& rhs) const{ + std::stringstream s1,s2; + s1 << lhs.getInstanceUID(); + s2 << rhs.getInstanceUID(); + return (s1.str() < s2.str()) && (lhs.getFrameNumber() < rhs.getFrameNumber()); + } }; + } #endif //DCMQI_DICOMFRAME_H diff --git a/include/dcmqi/ImageVolumeGeometry.h b/include/dcmqi/ImageVolumeGeometry.h index 3857027c..30b2d8fb 100644 --- a/include/dcmqi/ImageVolumeGeometry.h +++ b/include/dcmqi/ImageVolumeGeometry.h @@ -10,6 +10,7 @@ #include #include #include +#include class ImageVolumeGeometry { @@ -28,12 +29,16 @@ class ImageVolumeGeometry { typedef DummyImageType::DirectionType DirectionType; ImageVolumeGeometry(); + // initialize from DICOM + ImageVolumeGeometry(DcmDataset*); int setSpacing(DoubleVectorType); int setOrigin(PointType); int setExtent(SizeType); int setDirections(DirectionType); + DummyImageType::Pointer getITKRepresentation(); + protected: // use vnl_vector to simplify support of vector calculations vnl_vector rowDirection, columnDirection, sliceDirection; diff --git a/include/dcmqi/MultiframeConverter.h b/include/dcmqi/MultiframeConverter.h index ff0b5527..84089ab9 100644 --- a/include/dcmqi/MultiframeConverter.h +++ b/include/dcmqi/MultiframeConverter.h @@ -285,6 +285,10 @@ namespace dcmqi { CHECK_COND(dcmDatasets[i]->findAndGetOFString(DCM_ImagePositionPatient, ippStr, j)); ippPoint[j] = atof(ippStr.c_str()); } + // NB: this will map slice origin to index without failure, unless the point is out + // of FOV bounds! + // TODO: do a better job matching volume slices by considering comparison of the origin + // and orientation of the slice within tolerance if(!labelImage->TransformPhysicalPointToIndex(ippPoint, ippIndex)){ //cout << "image position: " << ippPoint << endl; //cerr << "ippIndex: " << ippIndex << endl; diff --git a/include/dcmqi/MultiframeObject.h b/include/dcmqi/MultiframeObject.h index 874abd4f..64568366 100644 --- a/include/dcmqi/MultiframeObject.h +++ b/include/dcmqi/MultiframeObject.h @@ -24,6 +24,7 @@ #include "dcmqi/Exceptions.h" #include "dcmqi/ImageVolumeGeometry.h" +#include "dcmqi/DICOMFrame.h" using namespace std; /* @@ -75,7 +76,10 @@ class MultiframeObject { // from ITK int initializeVolumeGeometryFromITK(DummyImageType::Pointer); + // initialize attributes of the composite context that are common for all multiframe objects virtual int initializeCompositeContext(); + // check whether all of the attributes required for initialization of the object are present in the + // input metadata virtual bool metaDataIsComplete(); ContentItemMacro* initializeContentItemMacro(CodeSequenceMacro, CodeSequenceMacro); @@ -85,6 +89,12 @@ class MultiframeObject { int initializePixelMeasuresFG(); int initializePlaneOrientationFG(); + static int mapVolumeSlicesToDICOMFrames(ImageVolumeGeometry&, const vector, + vector >); + + static std::vector findIntersectingSlices(ImageVolumeGeometry& volume, + dcmqi::DICOMFrame& frame); + // constants to describe original representation of the data being converted enum { DICOM_REPR = 0, diff --git a/libsrc/DICOMFrame.cpp b/libsrc/DICOMFrame.cpp index ecc42038..7a3b1985 100644 --- a/libsrc/DICOMFrame.cpp +++ b/libsrc/DICOMFrame.cpp @@ -3,3 +3,18 @@ // #include "dcmqi/DICOMFrame.h" + +namespace dcmqi { + int DICOMFrame::initializeFrameGeometryFromLegacyInstance() { + OFString ippStr; + for(int j=0;j<3;j++){ + CHECK_COND(frameDataset->findAndGetOFString(DCM_ImagePositionPatient, ippStr, j)); + frameIPP[j] = atof(ippStr.c_str()); + } + return EXIT_SUCCESS; + } + + int DICOMFrame::initializeFrameGeometryFromEnhancedInstance() { + OFString ippStr; + } +} \ No newline at end of file diff --git a/libsrc/ImageVolume.cpp b/libsrc/ImageVolume.cpp index 2f12ecd2..ebf64482 100644 --- a/libsrc/ImageVolume.cpp +++ b/libsrc/ImageVolume.cpp @@ -211,4 +211,5 @@ int dcmqi::ImageVolume::initializeExtent(FGInterface &fgInterface) { } return EXIT_SUCCESS; -} \ No newline at end of file +} + diff --git a/libsrc/ImageVolumeGeometry.cpp b/libsrc/ImageVolumeGeometry.cpp index 10570b0f..d71f6ef9 100644 --- a/libsrc/ImageVolumeGeometry.cpp +++ b/libsrc/ImageVolumeGeometry.cpp @@ -39,4 +39,38 @@ int ImageVolumeGeometry::setDirections(DirectionType d) { for (int i = 0; i < 3; i++) sliceDirection[i] = d[i][2]; return EXIT_SUCCESS; +} + +ImageVolumeGeometry::DummyImageType::Pointer ImageVolumeGeometry::getITKRepresentation(){ + ImageVolumeGeometry::DummyImageType::Pointer image; + ImageVolumeGeometry::DummyImageType::IndexType index; + ImageVolumeGeometry::DummyImageType::SizeType size; + ImageVolumeGeometry::DummyImageType::DirectionType direction; + ImageVolumeGeometry::DummyImageType::SpacingType spacing; + ImageVolumeGeometry::DummyImageType::RegionType region; + + index.Fill(0); + + size[0] = extent[0]; + size[1] = extent[1]; + size[2] = extent[2]; + + region.SetIndex(index); + region.SetSize(size); + + spacing[0] = this->spacing[0]; + spacing[1] = this->spacing[1]; + spacing[2] = this->spacing[2]; + + for (int i = 0; i < 3; i++) + direction[i][0] = rowDirection[i]; + for (int i = 0; i < 3; i++) + direction[i][1] = columnDirection[i]; + for (int i = 0; i < 3; i++) + direction[i][2] = sliceDirection[i]; + + image->SetDirection(direction); + image->SetSpacing(spacing); + + return image; } \ No newline at end of file diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index d1396dd6..d6ef6fbe 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -129,3 +129,42 @@ ContentItemMacro* MultiframeObject::initializeContentItemMacro(CodeSequenceMacro return EXIT_SUCCESS; } + +// populates slice2frame vector that maps each of the volume slices to the set of frames that +// are considered as derivation dataset +int MultiframeObject::mapVolumeSlicesToDICOMFrames(ImageVolumeGeometry& volume, + const vector dcmDatasets, + vector > slice2frame){ + for(int d=0;dfindAndGetUint32(DCM_NumberOfFrames, numFrames).good()){ + // this is a multiframe object + for(int f=0;f intersectingSlices = findIntersectingSlices(volume, frame); + + for(int s=0;s intersectingSlices = findIntersectingSlices(volume, frame); + + for(int s=0;s MultiframeObject::findIntersectingSlices(ImageVolumeGeometry &volume, dcmqi::DICOMFrame &frame) { + std::vector intersectingSlices; + + + + return intersectingSlices; +} \ No newline at end of file diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 4e4bf9fc..dbc06501 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -40,6 +40,8 @@ int ParametricMapObject::initializeFromITK(Float32ITKImageType::Pointer inputIma initializeFrameAnatomyFG(); initializeRWVMFG(); + + return EXIT_SUCCESS; } @@ -204,69 +206,6 @@ int ParametricMapObject::initializeRWVMFG() { realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(bval); } } -#if 0 - - // initialize optional RWVM items, if available - if(metaInfo.metaInfoRoot.isMember("MeasurementMethodCode")){ - ContentItemMacro* measureMethod = new ContentItemMacro; - CodeSequenceMacro* qCodeName = new CodeSequenceMacro("G-C306", "SRT", "Measurement Method"); - CodeSequenceMacro* qSpec = new CodeSequenceMacro( - metaInfo.metaInfoRoot["MeasurementMethodCode"]["CodeValue"].asCString(), - metaInfo.metaInfoRoot["MeasurementMethodCode"]["CodingSchemeDesignator"].asCString(), - metaInfo.metaInfoRoot["MeasurementMethodCode"]["CodeMeaning"].asCString()); - - if (!measureMethod || !qSpec || !qCodeName) - { - return NULL; - } - - measureMethod->getEntireConceptNameCodeSequence().push_back(qCodeName); - measureMethod->getEntireConceptCodeSequence().push_back(qSpec); - realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(measureMethod); - measureMethod->setValueType(ContentItemMacro::VT_CODE); - } - - if(metaInfo.metaInfoRoot.isMember("ModelFittingMethodCode")){ - ContentItemMacro* fittingMethod = new ContentItemMacro; - CodeSequenceMacro* qCodeName = new CodeSequenceMacro("DWMPxxxxx2", "99QIICR", "Model fitting method"); - CodeSequenceMacro* qSpec = new CodeSequenceMacro( - metaInfo.metaInfoRoot["ModelFittingMethodCode"]["CodeValue"].asCString(), - metaInfo.metaInfoRoot["ModelFittingMethodCode"]["CodingSchemeDesignator"].asCString(), - metaInfo.metaInfoRoot["ModelFittingMethodCode"]["CodeMeaning"].asCString()); - - if (!fittingMethod || !qSpec || !qCodeName) - { - return NULL; - } - - fittingMethod->getEntireConceptNameCodeSequence().push_back(qCodeName); - fittingMethod->getEntireConceptCodeSequence().push_back(qSpec); - realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(fittingMethod); - fittingMethod->setValueType(ContentItemMacro::VT_CODE); - } - - if(metaInfo.metaInfoRoot.isMember("SourceImageDiffusionBValues")){ - for(int bvalId=0;bvalIdsetValueType(ContentItemMacro::VT_NUMERIC); - bval->getEntireConceptNameCodeSequence().push_back(qCodeName); - bval->getEntireMeasurementUnitsCodeSequence().push_back(bvalUnits); - if(bval->setNumericValue(metaInfo.metaInfoRoot["SourceImageDiffusionBValues"][bvalId].asCString()).bad()) - cout << "Failed to insert the value!" << endl;; - realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(bval); - cout << bval->toString() << endl; - } - } - rwvmFG.getRealWorldValueMapping().push_back(realWorldValueMappingItem); -#endif return EXIT_SUCCESS; } \ No newline at end of file From cf4366345ba5ff7a74be900fbe1ad7c309f5a577 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 5 May 2017 12:06:36 -0400 Subject: [PATCH 077/116] BUG: fix API typo fixed in dcmtk --- libsrc/ParametricMapObject.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index dbc06501..95b2a6e6 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -151,7 +151,7 @@ int ParametricMapObject::initializeRWVMFG() { calculator->SetImage(itkImage); calculator->Compute(); - realWorldValueMappingItem->setRealWorldValueFirstValueMappeSigned(calculator->GetMinimum()); + realWorldValueMappingItem->setRealWorldValueFirstValueMappedSigned(calculator->GetMinimum()); realWorldValueMappingItem->setRealWorldValueLastValueMappedSigned(calculator->GetMaximum()); if(metaDataJson.isMember("MeasurementsUnitsCode")){ @@ -208,4 +208,4 @@ int ParametricMapObject::initializeRWVMFG() { } return EXIT_SUCCESS; -} \ No newline at end of file +} From 01d38ee35bb3f9d60fc67b53b4804a12d2449d19 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 5 May 2017 12:22:02 -0400 Subject: [PATCH 078/116] BUG: add docker_entry removed by accident in ff52820 --- docker/dcmqi/imagefiles/docker_entry.sh | 28 +++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 docker/dcmqi/imagefiles/docker_entry.sh diff --git a/docker/dcmqi/imagefiles/docker_entry.sh b/docker/dcmqi/imagefiles/docker_entry.sh new file mode 100644 index 00000000..2cecef2d --- /dev/null +++ b/docker/dcmqi/imagefiles/docker_entry.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +case "$1" in + itkimage2paramap|paramap2itkimage|itkimage2segimage|segimage2itkimage|tid1500reader|tid1500writer) + $1 "${@:2}" + ;; + *) + echo "ERROR: Unknown command" + echo "" + cat >&2 <:] qiicr/dcmqi [args] + +Run the given dcmqi *command*. + +Available commands are: + itkimage2paramap + paramap2itkimage + itkimage2segimage + segimage2itkimage + tid1500reader + tid1500writer + +For *command* help use: docker run qiicr/dcmqi --help +ENDHELP + exit 1 + ;; +esac + From ad1dbea4de1015a3e6772984083d01ee2fdb89a8 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 5 May 2017 12:39:17 -0400 Subject: [PATCH 079/116] BUG: return value --- libsrc/DICOMFrame.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libsrc/DICOMFrame.cpp b/libsrc/DICOMFrame.cpp index 7a3b1985..bfbff419 100644 --- a/libsrc/DICOMFrame.cpp +++ b/libsrc/DICOMFrame.cpp @@ -16,5 +16,6 @@ namespace dcmqi { int DICOMFrame::initializeFrameGeometryFromEnhancedInstance() { OFString ippStr; + return EXIT_SUCCESS; } -} \ No newline at end of file +} From b20726ab3c2b6d7272893f45fe56064e8169edd6 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Sun, 14 May 2017 14:10:31 -0400 Subject: [PATCH 080/116] ENH: checkpoint commit --- include/dcmqi/DICOMFrame.h | 3 +++ libsrc/MultiframeObject.cpp | 16 +++++++++++++--- libsrc/ParametricMapObject.cpp | 9 +++++++-- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/include/dcmqi/DICOMFrame.h b/include/dcmqi/DICOMFrame.h index 9a31b611..e35eea66 100644 --- a/include/dcmqi/DICOMFrame.h +++ b/include/dcmqi/DICOMFrame.h @@ -41,6 +41,9 @@ namespace dcmqi { int getFrameNumber() const; // 0 for legacy datasets, 1 or above for enhanced objects OFString getInstanceUID() const; + vnl_vector getFrameIPP(){ + return frameIPP; + }; private: diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index d6ef6fbe..f42c67b8 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -163,8 +163,18 @@ int MultiframeObject::mapVolumeSlicesToDICOMFrames(ImageVolumeGeometry& volume, std::vector MultiframeObject::findIntersectingSlices(ImageVolumeGeometry &volume, dcmqi::DICOMFrame &frame) { std::vector intersectingSlices; - - + // for now, adopt a simple strategy that maps origin of the frame to index, and selects the slice corresponding + // to this index as the intersecting one + ImageVolumeGeometry::DummyImageType::Pointer itkvolume = volume.getITKRepresentation(); + ImageVolumeGeometry::DummyImageType::PointType point; + ImageVolumeGeometry::DummyImageType::IndexType index; + vnl_vector frameIPP = frame.getFrameIPP(); + point[0] = frameIPP[0]; + point[1] = frameIPP[1]; + point[2] = frameIPP[2]; + + if(itkvolume->TransformPhysicalPointToIndex(point, index)) + intersectingSlices.push_back(index[2]); return intersectingSlices; -} \ No newline at end of file +} diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 95b2a6e6..f2333eb4 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -21,6 +21,10 @@ int ParametricMapObject::initializeFromITK(Float32ITKImageType::Pointer inputIma initializeVolumeGeometry(); + // NB: the sequence of steps initializing different components of the object parallels that + // in the original converter function. It probably makes sense to revisit the sequence + // of these steps. It does not necessarily need to happen in this order. + // TODO: consider creating parametric map object after all FGs are initialized instead createParametricMap(); @@ -40,7 +44,8 @@ int ParametricMapObject::initializeFromITK(Float32ITKImageType::Pointer inputIma initializeFrameAnatomyFG(); initializeRWVMFG(); - + // initialize referenced instances + ///initializeReferencedInstances(); return EXIT_SUCCESS; } @@ -195,7 +200,7 @@ int ParametricMapObject::initializeRWVMFG() { if (!bval || !bvalUnits || !qCodeName) { - return NULL; + return EXIT_FAILURE; } bval->setValueType(ContentItemMacro::VT_NUMERIC); From d1ed72389f68e4d9994b6988aa925f15dda63979 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Mon, 22 May 2017 17:01:07 -0400 Subject: [PATCH 081/116] checkpoint --- include/dcmqi/DICOMFrame.h | 17 +++++++++++++++++ include/dcmqi/MultiframeObject.h | 9 +++++++-- include/dcmqi/ParametricMapObject.h | 2 ++ libsrc/MultiframeObject.cpp | 21 ++++++++++++++++----- libsrc/ParametricMapObject.cpp | 23 ++++++++++++++++++++++- 5 files changed, 64 insertions(+), 8 deletions(-) diff --git a/include/dcmqi/DICOMFrame.h b/include/dcmqi/DICOMFrame.h index e35eea66..8d975906 100644 --- a/include/dcmqi/DICOMFrame.h +++ b/include/dcmqi/DICOMFrame.h @@ -37,6 +37,13 @@ namespace dcmqi { initializeFrameGeometryFromLegacyInstance(); } + OFString seriesUIDOF, instanceUIDOF; + if(dataset->findAndGetOFString(DCM_SeriesInstanceUID, seriesUIDOF).good()){ + seriesUID = seriesUIDOF.c_str(); + } + if(dataset->findAndGetOFString(DCM_SOPInstanceUID, instanceUIDOF).good()){ + instanceUID = instanceUIDOF.c_str(); + } }; int getFrameNumber() const; // 0 for legacy datasets, 1 or above for enhanced objects @@ -45,6 +52,14 @@ namespace dcmqi { return frameIPP; }; + string getSeriesUID(){ + return seriesUID; + } + + string getInstanceUID(){ + return instanceUID; + } + private: int initializeFrameGeometryFromLegacyInstance(); @@ -55,6 +70,8 @@ namespace dcmqi { int frameNumber; vnl_vector frameIPP; + string seriesUID, instanceUID; + ImageVolumeGeometry frameGeometry; }; diff --git a/include/dcmqi/MultiframeObject.h b/include/dcmqi/MultiframeObject.h index 64568366..399a5d52 100644 --- a/include/dcmqi/MultiframeObject.h +++ b/include/dcmqi/MultiframeObject.h @@ -89,12 +89,14 @@ class MultiframeObject { int initializePixelMeasuresFG(); int initializePlaneOrientationFG(); - static int mapVolumeSlicesToDICOMFrames(ImageVolumeGeometry&, const vector, - vector >); + int mapVolumeSlicesToDICOMFrames(ImageVolumeGeometry&, const vector, + vector >&); static std::vector findIntersectingSlices(ImageVolumeGeometry& volume, dcmqi::DICOMFrame& frame); + void insertDerivationSeriesInstance(string seriesUID, string instanceUID); + // constants to describe original representation of the data being converted enum { DICOM_REPR = 0, @@ -140,6 +142,9 @@ class MultiframeObject { OFVector frameContentFGList; OFVector derivationImageFGList; + // Mapping from the derivation items SeriesUIDs to InstanceUIDs + std::map > derivationSeriesToInstanceUIDs; + }; diff --git a/include/dcmqi/ParametricMapObject.h b/include/dcmqi/ParametricMapObject.h index eee341f6..e5c9b83a 100644 --- a/include/dcmqi/ParametricMapObject.h +++ b/include/dcmqi/ParametricMapObject.h @@ -56,6 +56,8 @@ class ParametricMapObject : public MultiframeObject { // Data containers specific to this object Float32ITKImageType::Pointer itkImage; + + private: DPMParametricMapIOD* parametricMap; }; diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index f42c67b8..60f2e1c5 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -132,9 +132,10 @@ ContentItemMacro* MultiframeObject::initializeContentItemMacro(CodeSequenceMacro // populates slice2frame vector that maps each of the volume slices to the set of frames that // are considered as derivation dataset +// TODO: this function assumes that all of the derivation datasets are images, which is probably ok int MultiframeObject::mapVolumeSlicesToDICOMFrames(ImageVolumeGeometry& volume, const vector dcmDatasets, - vector > slice2frame){ + vector > &slice2frame){ for(int d=0;d intersectingSlices = findIntersectingSlices(volume, frame); - for(int s=0;sinsertDerivationSeriesInstance(frame.getSeriesUID(), frame.getInstanceUID()); + } } } else { dcmqi::DICOMFrame frame(dcm); vector intersectingSlices = findIntersectingSlices(volume, frame); - for(int s=0;sinsertDerivationSeriesInstance(frame.getSeriesUID(), frame.getInstanceUID()); + } } } @@ -178,3 +183,9 @@ std::vector MultiframeObject::findIntersectingSlices(ImageVolumeGeometry &v return intersectingSlices; } + +void MultiframeObject::insertDerivationSeriesInstance(string seriesUID, string instanceUID){ + if(derivationSeriesToInstanceUIDs.find(seriesUID) == derivationSeriesToInstanceUIDs.end()) + derivationSeriesToInstanceUIDs[seriesUID] = std::set(); + derivationSeriesToInstanceUIDs[seriesUID].insert(instanceUID); +} \ No newline at end of file diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index f2333eb4..81340c47 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -44,8 +44,29 @@ int ParametricMapObject::initializeFromITK(Float32ITKImageType::Pointer inputIma initializeFrameAnatomyFG(); initializeRWVMFG(); + // Mapping from parametric map volume slices to the DICOM frames + vector > slice2frame; + // initialize referenced instances - ///initializeReferencedInstances(); + mapVolumeSlicesToDICOMFrames(this->volumeGeometry, derivationDatasets, slice2frame); + + // map individual series UIDs to the list of instance UIDs + + + std::map > seriesToInstanceUIDs; + for(int slice=0;slice!=slice2frame.size();slice++){ + for(set::const_iterator frameI=slice2frame[slice].begin(); + frameI!=slice2frame[slice].end();++frameI){ + dcmqi::DICOMFrame frame = *frameI; + if(seriesToInstanceUIDs.find(frame.getSeriesUID()) == seriesToInstanceUIDs.end()){ + seriesToInstanceUIDs[frame.getSeriesUID()] = frame.getInstanceUID(); + } else { + seriesToInstanceUIDs[frame.getSeriesUID()].push_back(frame.getInstanceUID()); + } + } + } + + return EXIT_SUCCESS; } From a58ab739821e7385433600b935a0b3420e47aa31 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Thu, 22 Jun 2017 13:01:56 -0400 Subject: [PATCH 082/116] ENH: fix compile errors, initialize common instance module --- include/dcmqi/DICOMFrame.h | 11 +++++++-- include/dcmqi/MultiframeObject.h | 2 ++ libsrc/MultiframeObject.cpp | 38 ++++++++++++++++++++++++++++++++ libsrc/ParametricMapObject.cpp | 22 +++++------------- 4 files changed, 54 insertions(+), 19 deletions(-) diff --git a/include/dcmqi/DICOMFrame.h b/include/dcmqi/DICOMFrame.h index 8d975906..c0e61833 100644 --- a/include/dcmqi/DICOMFrame.h +++ b/include/dcmqi/DICOMFrame.h @@ -37,13 +37,16 @@ namespace dcmqi { initializeFrameGeometryFromLegacyInstance(); } - OFString seriesUIDOF, instanceUIDOF; + OFString seriesUIDOF, instanceUIDOF, classUIDOF; if(dataset->findAndGetOFString(DCM_SeriesInstanceUID, seriesUIDOF).good()){ seriesUID = seriesUIDOF.c_str(); } if(dataset->findAndGetOFString(DCM_SOPInstanceUID, instanceUIDOF).good()){ instanceUID = instanceUIDOF.c_str(); } + if(dataset->findAndGetOFString(DCM_SOPClassUID, classUIDOF).good()){ + classUID = classUIDOF.c_str(); + } }; int getFrameNumber() const; // 0 for legacy datasets, 1 or above for enhanced objects @@ -60,6 +63,10 @@ namespace dcmqi { return instanceUID; } + string getClassUID(){ + return classUID; + } + private: int initializeFrameGeometryFromLegacyInstance(); @@ -70,7 +77,7 @@ namespace dcmqi { int frameNumber; vnl_vector frameIPP; - string seriesUID, instanceUID; + string seriesUID, instanceUID, classUID; ImageVolumeGeometry frameGeometry; diff --git a/include/dcmqi/MultiframeObject.h b/include/dcmqi/MultiframeObject.h index 399a5d52..838df9e6 100644 --- a/include/dcmqi/MultiframeObject.h +++ b/include/dcmqi/MultiframeObject.h @@ -21,6 +21,7 @@ #include #include #include +#include #include "dcmqi/Exceptions.h" #include "dcmqi/ImageVolumeGeometry.h" @@ -88,6 +89,7 @@ class MultiframeObject { int initializeDimensions(std::vector >); int initializePixelMeasuresFG(); int initializePlaneOrientationFG(); + int initializeCommonInstanceReferenceModule(IODCommonInstanceReferenceModule &, vector >&); int mapVolumeSlicesToDICOMFrames(ImageVolumeGeometry&, const vector, vector >&); diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index 60f2e1c5..389df8e0 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -188,4 +188,42 @@ void MultiframeObject::insertDerivationSeriesInstance(string seriesUID, string i if(derivationSeriesToInstanceUIDs.find(seriesUID) == derivationSeriesToInstanceUIDs.end()) derivationSeriesToInstanceUIDs[seriesUID] = std::set(); derivationSeriesToInstanceUIDs[seriesUID].insert(instanceUID); +} + +int MultiframeObject::initializeCommonInstanceReferenceModule(IODCommonInstanceReferenceModule &commref, vector > &slice2frame){ + + // map individual series UIDs to the list of instance UIDs - we need to have this organization + // to populate Common instance reference module + std::map > series2frame; + for(int slice=0;slice!=slice2frame.size();slice++){ + for(set::const_iterator frameI=slice2frame[slice].begin(); + frameI!=slice2frame[slice].end();++frameI){ + dcmqi::DICOMFrame frame = *frameI; + if(series2frame.find(frame.getSeriesUID()) == series2frame.end()){ + std::set setOfInstances; + setOfInstances.insert(frame); + series2frame[frame.getSeriesUID()] = setOfInstances; + } else { + series2frame[frame.getSeriesUID()].insert(frame); + } + } + } + + // create a new ReferencedSeriesItem for each series, and populate with instances + OFVector &refseries = commref.getReferencedSeriesItems(); + for(std::map >::const_iterator mIt=series2frame.begin(); mIt!=series2frame.end();++mIt){ + IODSeriesAndInstanceReferenceMacro::ReferencedSeriesItem* refseriesItem = new IODSeriesAndInstanceReferenceMacro::ReferencedSeriesItem; + refseriesItem->setSeriesInstanceUID(mIt->first.c_str()); + OFVector &refinstances = refseriesItem->getReferencedInstanceItems(); + + for(std::set::const_iterator sIt=mIt->second.begin();sIt!=mIt->second.end();++sIt){ + dcmqi::DICOMFrame frame = *sIt; + SOPInstanceReferenceMacro *refinstancesItem = new SOPInstanceReferenceMacro(); + CHECK_COND(refinstancesItem->setReferencedSOPClassUID(frame.getClassUID().c_str())); + CHECK_COND(refinstancesItem->setReferencedSOPInstanceUID(frame.getInstanceUID().c_str())); + refinstances.push_back(refinstancesItem); + } + } + + return EXIT_SUCCESS; } \ No newline at end of file diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 81340c47..5de1250b 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -5,6 +5,8 @@ #include #include #include "dcmqi/ParametricMapObject.h" +#include +#include int ParametricMapObject::initializeFromITK(Float32ITKImageType::Pointer inputImage, const string &metaDataStr, @@ -48,25 +50,11 @@ int ParametricMapObject::initializeFromITK(Float32ITKImageType::Pointer inputIma vector > slice2frame; // initialize referenced instances + // this is done using this utility function from the parent class, since this functionality will + // be needed both in the PM and SEG objects mapVolumeSlicesToDICOMFrames(this->volumeGeometry, derivationDatasets, slice2frame); - // map individual series UIDs to the list of instance UIDs - - - std::map > seriesToInstanceUIDs; - for(int slice=0;slice!=slice2frame.size();slice++){ - for(set::const_iterator frameI=slice2frame[slice].begin(); - frameI!=slice2frame[slice].end();++frameI){ - dcmqi::DICOMFrame frame = *frameI; - if(seriesToInstanceUIDs.find(frame.getSeriesUID()) == seriesToInstanceUIDs.end()){ - seriesToInstanceUIDs[frame.getSeriesUID()] = frame.getInstanceUID(); - } else { - seriesToInstanceUIDs[frame.getSeriesUID()].push_back(frame.getInstanceUID()); - } - } - } - - + initializeCommonInstanceReferenceModule(this->parametricMap->getCommonInstanceReference(), slice2frame); return EXIT_SUCCESS; } From a2b5dfbb9eaf99d43b7bf6f882f891272bbb236a Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Thu, 22 Jun 2017 16:54:59 -0400 Subject: [PATCH 083/116] ENH: fixing issues identified by tests --- apps/paramaps/itkimage2paramap.cxx | 2 +- include/dcmqi/DICOMFrame.h | 13 ++++++++----- include/dcmqi/MultiframeObject.h | 6 +++++- include/dcmqi/ParametricMapObject.h | 4 ++++ libsrc/DICOMFrame.cpp | 1 + libsrc/MultiframeObject.cpp | 12 ++++++++++++ libsrc/ParametricMapConverter.cpp | 12 ++++++++---- libsrc/ParametricMapObject.cpp | 6 +++++- 8 files changed, 44 insertions(+), 12 deletions(-) diff --git a/apps/paramaps/itkimage2paramap.cxx b/apps/paramaps/itkimage2paramap.cxx index f71252b9..22f5b6f9 100644 --- a/apps/paramaps/itkimage2paramap.cxx +++ b/apps/paramaps/itkimage2paramap.cxx @@ -44,7 +44,7 @@ int main(int argc, char *argv[]) std::string metadata( (std::istreambuf_iterator(metainfoStream) ), (std::istreambuf_iterator())); - DcmDataset* result = dcmqi::ParametricMapConverter::itkimage2paramap(parametricMapImage, dcmDatasets, metadata); + DcmDataset* result = dcmqi::itkimage2paramapReplacement(parametricMapImage, dcmDatasets, metadata); if (result == NULL) { return EXIT_FAILURE; diff --git a/include/dcmqi/DICOMFrame.h b/include/dcmqi/DICOMFrame.h index c0e61833..586c6e9c 100644 --- a/include/dcmqi/DICOMFrame.h +++ b/include/dcmqi/DICOMFrame.h @@ -47,23 +47,26 @@ namespace dcmqi { if(dataset->findAndGetOFString(DCM_SOPClassUID, classUIDOF).good()){ classUID = classUIDOF.c_str(); } + + }; + + int getFrameNumber() const { + return frameNumber; // 0 for legacy datasets, 1 or above for enhanced objects }; - int getFrameNumber() const; // 0 for legacy datasets, 1 or above for enhanced objects - OFString getInstanceUID() const; vnl_vector getFrameIPP(){ return frameIPP; }; - string getSeriesUID(){ + string getSeriesUID() const { return seriesUID; } - string getInstanceUID(){ + string getInstanceUID() const{ return instanceUID; } - string getClassUID(){ + string getClassUID() const { return classUID; } diff --git a/include/dcmqi/MultiframeObject.h b/include/dcmqi/MultiframeObject.h index 838df9e6..e882a1f9 100644 --- a/include/dcmqi/MultiframeObject.h +++ b/include/dcmqi/MultiframeObject.h @@ -78,7 +78,7 @@ class MultiframeObject { int initializeVolumeGeometryFromITK(DummyImageType::Pointer); // initialize attributes of the composite context that are common for all multiframe objects - virtual int initializeCompositeContext(); + //virtual int initializeCompositeContext(); // check whether all of the attributes required for initialization of the object are present in the // input metadata virtual bool metaDataIsComplete(); @@ -113,8 +113,12 @@ class MultiframeObject { Json::Value metaDataJson; // Multiframe DICOM object representation + // probably not needed, since need object-specific DCMTK class in + // derived classes DcmDataset* dcmRepresentation; + // probably not needed at this level, since for SEG each segment will + // have separate geometry definition ImageVolumeGeometry volumeGeometry; // DcmDataset(s) that hold the original representation of the diff --git a/include/dcmqi/ParametricMapObject.h b/include/dcmqi/ParametricMapObject.h index e5c9b83a..4f6c59cd 100644 --- a/include/dcmqi/ParametricMapObject.h +++ b/include/dcmqi/ParametricMapObject.h @@ -33,6 +33,10 @@ class ParametricMapObject : public MultiframeObject { int updateMetaDataFromDICOM(std::vector); + DPMParametricMapIOD* getDICOMRepresentation(){ + return parametricMap; + }; + protected: typedef itk::CastImageFilter Float32ToDummyCasterType; diff --git a/libsrc/DICOMFrame.cpp b/libsrc/DICOMFrame.cpp index bfbff419..f652f26a 100644 --- a/libsrc/DICOMFrame.cpp +++ b/libsrc/DICOMFrame.cpp @@ -7,6 +7,7 @@ namespace dcmqi { int DICOMFrame::initializeFrameGeometryFromLegacyInstance() { OFString ippStr; + frameIPP.set_size(3); for(int j=0;j<3;j++){ CHECK_COND(frameDataset->findAndGetOFString(DCM_ImagePositionPatient, ippStr, j)); frameIPP[j] = atof(ippStr.c_str()); diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index 389df8e0..fd60697b 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -18,16 +18,22 @@ int MultiframeObject::initializeMetaDataFromString(const std::string &metaDataSt int MultiframeObject::initializeEquipmentInfo() { if(sourceRepresentationType == ITK_REPR){ + equipmentInfoModule = IODEnhGeneralEquipmentModule::EquipmentInfo(QIICR_MANUFACTURER, QIICR_DEVICE_SERIAL_NUMBER, + QIICR_MANUFACTURER_MODEL_NAME, QIICR_SOFTWARE_VERSIONS); + /* equipmentInfoModule.m_Manufacturer = QIICR_MANUFACTURER; equipmentInfoModule.m_DeviceSerialNumber = QIICR_DEVICE_SERIAL_NUMBER; equipmentInfoModule.m_ManufacturerModelName = QIICR_MANUFACTURER_MODEL_NAME; equipmentInfoModule.m_SoftwareVersions = QIICR_SOFTWARE_VERSIONS; + */ + } else { // DICOM_REPR } return EXIT_SUCCESS; } int MultiframeObject::initializeContentIdentification() { + if(sourceRepresentationType == ITK_REPR){ CHECK_COND(contentIdentificationMacro.setContentCreatorName("dcmqi")); if(metaDataJson.isMember("ContentDescription")){ @@ -40,6 +46,12 @@ int MultiframeObject::initializeContentIdentification() { } else { CHECK_COND(contentIdentificationMacro.setContentLabel("DCMQI")); } + if(metaDataJson.isMember("InstanceNumber")){ + CHECK_COND(contentIdentificationMacro.setInstanceNumber(metaDataJson["InstanceNumber"].asCString())); + } else { + CHECK_COND(contentIdentificationMacro.setInstanceNumber("1")); + } + CHECK_COND(contentIdentificationMacro.check()) return EXIT_SUCCESS; } else { // DICOM_REPR } diff --git a/libsrc/ParametricMapConverter.cpp b/libsrc/ParametricMapConverter.cpp index 67f8982c..d6cfe709 100644 --- a/libsrc/ParametricMapConverter.cpp +++ b/libsrc/ParametricMapConverter.cpp @@ -6,6 +6,7 @@ // DCMQI includes #include "dcmqi/ParametricMapConverter.h" #include "dcmqi/SegmentationImageConverter.h" +#include "dcmqi/ParametricMapObject.h" using namespace std; @@ -13,11 +14,14 @@ namespace dcmqi { DcmDataset* itkimage2paramapReplacement(const FloatImageType::Pointer ¶metricMapImage, vector dcmDatasets, const string &metaData) { - /* - ParametricMapConverter pmConverter(parametricMapImage, dcmDatasets, metaData); - pmConverter.convert(); + ParametricMapObject pm; + pm.initializeFromITK(parametricMapImage, metaData, dcmDatasets); + + DPMParametricMapIOD* pmap = pm.getDICOMRepresentation(); + + DcmDataset* output = new DcmDataset(); + CHECK_COND(pmap->writeDataset(*output)); - return convert.getDataset(); */ return NULL; } diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 5de1250b..cb5f91e5 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -26,6 +26,8 @@ int ParametricMapObject::initializeFromITK(Float32ITKImageType::Pointer inputIma // NB: the sequence of steps initializing different components of the object parallels that // in the original converter function. It probably makes sense to revisit the sequence // of these steps. It does not necessarily need to happen in this order. + initializeEquipmentInfo(); + initializeContentIdentification(); // TODO: consider creating parametric map object after all FGs are initialized instead createParametricMap(); @@ -101,8 +103,10 @@ int ParametricMapObject::createParametricMap() { DPMTypes::CQ_RESEARCH); // TODO: look into the following, check with @che85 on the purpose of this line! - if (OFCondition* pCondition = OFget(&obj)) + if (OFCondition* pCondition = OFget(&obj)) { + std::cerr << "Failed to create parametric map object!" << std::endl; return EXIT_FAILURE; + } parametricMap = OFget(&obj); From 8a27285f411056311fe0a86bd507a7ebd94ef88a Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 23 Jun 2017 11:41:10 -0400 Subject: [PATCH 084/116] circle_ci another dummy --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 868b0843..41b0e74b 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,5 @@ - [![OpenHub](https://www.openhub.net/p/dcmqi/widgets/project_thin_badge.gif)](https://www.openhub.net/p/dcmqi) [![codecov](https://codecov.io/gh/QIICR/dcmqi/branch/master/graph/badge.svg)](https://codecov.io/gh/QIICR/dcmqi) [![Join the chat at https://gitter.im/QIICR/dcmqi](https://badges.gitter.im/QIICR/dcmqi.svg)](https://gitter.im/QIICR/dcmqi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - - | Docker | [![](https://images.microbadger.com/badges/version/qiicr/dcmqi:v1.0.5.svg)](https://microbadger.com/images/qiicr/dcmqi:v1.0.5) | [![](https://images.microbadger.com/badges/version/qiicr/dcmqi.svg)](https://microbadger.com/images/qiicr/dcmqi) | |--------|--------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------| From 5a33a8686c633729cd1b45028a5fa446112cb3c9 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 23 Jun 2017 16:49:31 -0400 Subject: [PATCH 085/116] ENH: fix more issues --- include/dcmqi/ParametricMapObject.h | 10 ++++++++-- libsrc/ImageVolumeGeometry.cpp | 2 ++ libsrc/ParametricMapConverter.cpp | 8 +++----- libsrc/ParametricMapObject.cpp | 1 + 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/include/dcmqi/ParametricMapObject.h b/include/dcmqi/ParametricMapObject.h index 4f6c59cd..922dd8a0 100644 --- a/include/dcmqi/ParametricMapObject.h +++ b/include/dcmqi/ParametricMapObject.h @@ -20,6 +20,11 @@ */ class ParametricMapObject : public MultiframeObject { public: + + ParametricMapObject(){ + parametricMap = NULL; + } + typedef IODFloatingPointImagePixelModule::value_type Float32PixelType; typedef itk::Image Float32ITKImageType; @@ -33,8 +38,9 @@ class ParametricMapObject : public MultiframeObject { int updateMetaDataFromDICOM(std::vector); - DPMParametricMapIOD* getDICOMRepresentation(){ - return parametricMap; + int getDICOMRepresentation(DcmDataset& dcm){ + if(parametricMap) + CHECK_COND(parametricMap->write(dcm)); }; protected: diff --git a/libsrc/ImageVolumeGeometry.cpp b/libsrc/ImageVolumeGeometry.cpp index d71f6ef9..a550ae6d 100644 --- a/libsrc/ImageVolumeGeometry.cpp +++ b/libsrc/ImageVolumeGeometry.cpp @@ -49,6 +49,8 @@ ImageVolumeGeometry::DummyImageType::Pointer ImageVolumeGeometry::getITKRepresen ImageVolumeGeometry::DummyImageType::SpacingType spacing; ImageVolumeGeometry::DummyImageType::RegionType region; + image = ImageVolumeGeometry::DummyImageType::New(); + index.Fill(0); size[0] = extent[0]; diff --git a/libsrc/ParametricMapConverter.cpp b/libsrc/ParametricMapConverter.cpp index d6cfe709..0b2a1881 100644 --- a/libsrc/ParametricMapConverter.cpp +++ b/libsrc/ParametricMapConverter.cpp @@ -17,12 +17,10 @@ namespace dcmqi { ParametricMapObject pm; pm.initializeFromITK(parametricMapImage, metaData, dcmDatasets); - DPMParametricMapIOD* pmap = pm.getDICOMRepresentation(); + DcmDataset *output = new DcmDataset(); + pm.getDICOMRepresentation(*output); - DcmDataset* output = new DcmDataset(); - CHECK_COND(pmap->writeDataset(*output)); - - return NULL; + return output; } pair paramap2itkimageReplacement(DcmDataset *pmapDataset){ diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index cb5f91e5..6c16f865 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -8,6 +8,7 @@ #include #include + int ParametricMapObject::initializeFromITK(Float32ITKImageType::Pointer inputImage, const string &metaDataStr, std::vector derivationDatasets) { From c524783f9c84ec1e9e8c48f1bfeb8cc3a0133864 Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Tue, 27 Jun 2017 10:20:09 -0400 Subject: [PATCH 086/116] BUG: fixing wrong initialization of DPMParametricMapIOD --- libsrc/ParametricMapObject.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 6c16f865..24225a46 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -109,7 +109,7 @@ int ParametricMapObject::createParametricMap() { return EXIT_FAILURE; } - parametricMap = OFget(&obj); + parametricMap = new DPMParametricMapIOD( *OFget(&obj) ); // These FG are constant FGIdentityPixelValueTransformation idTransFG; From ee8832e4073ad0542d5b2305683bddcebe041cef Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Tue, 27 Jun 2017 21:07:40 -0400 Subject: [PATCH 087/116] ENH: adding refactored code for paramap from DCM to itk --- apps/paramaps/paramap2itkimage.cxx | 2 +- include/dcmqi/ImageVolumeGeometry.h | 37 ++++- include/dcmqi/MultiframeObject.h | 73 +++++++++- include/dcmqi/ParametricMapObject.h | 9 +- libsrc/ImageVolumeGeometry.cpp | 36 ----- libsrc/MultiframeObject.cpp | 205 +++++++++++++++++++++++++++- libsrc/ParametricMapConverter.cpp | 11 +- libsrc/ParametricMapObject.cpp | 66 +++++++++ 8 files changed, 393 insertions(+), 46 deletions(-) diff --git a/apps/paramaps/paramap2itkimage.cxx b/apps/paramaps/paramap2itkimage.cxx index 31908f8b..13161cbb 100644 --- a/apps/paramaps/paramap2itkimage.cxx +++ b/apps/paramaps/paramap2itkimage.cxx @@ -25,7 +25,7 @@ int main(int argc, char *argv[]) CHECK_COND(sliceFF.loadFile(inputFileName.c_str())); DcmDataset* dataset = sliceFF.getDataset(); - pair result = dcmqi::ParametricMapConverter::paramap2itkimage(dataset); + pair result = dcmqi::paramap2itkimageReplacement(dataset); string fileExtension = helper::getFileExtensionFromType(outputType); diff --git a/include/dcmqi/ImageVolumeGeometry.h b/include/dcmqi/ImageVolumeGeometry.h index 30b2d8fb..98986871 100644 --- a/include/dcmqi/ImageVolumeGeometry.h +++ b/include/dcmqi/ImageVolumeGeometry.h @@ -37,7 +37,42 @@ class ImageVolumeGeometry { int setExtent(SizeType); int setDirections(DirectionType); - DummyImageType::Pointer getITKRepresentation(); + template + typename T::Pointer getITKRepresentation(){ + typename T::Pointer image; + typename T::IndexType index; + typename T::SizeType size; + typename T::DirectionType direction; + typename T::SpacingType spacing; + typename T::RegionType region; + + image = T::New(); + + index.Fill(0); + + size[0] = extent[0]; + size[1] = extent[1]; + size[2] = extent[2]; + + region.SetIndex(index); + region.SetSize(size); + + spacing[0] = this->spacing[0]; + spacing[1] = this->spacing[1]; + spacing[2] = this->spacing[2]; + + for (int i = 0; i < 3; i++) + direction[i][0] = rowDirection[i]; + for (int i = 0; i < 3; i++) + direction[i][1] = columnDirection[i]; + for (int i = 0; i < 3; i++) + direction[i][2] = sliceDirection[i]; + + image->SetDirection(direction); + image->SetSpacing(spacing); + + return image; + } protected: // use vnl_vector to simplify support of vector calculations diff --git a/include/dcmqi/MultiframeObject.h b/include/dcmqi/MultiframeObject.h index e882a1f9..0c1e4b70 100644 --- a/include/dcmqi/MultiframeObject.h +++ b/include/dcmqi/MultiframeObject.h @@ -5,7 +5,7 @@ #ifndef DCMQI_MULTIFRAMEOBJECT_H #define DCMQI_MULTIFRAMEOBJECT_H - +#include #include #include #include @@ -17,6 +17,8 @@ #include #include +#include "vnl/vnl_cross.h" + #include #include #include @@ -52,7 +54,9 @@ class MultiframeObject { // Output is always a single DcmDataset, since this is a multiframe // object DcmDataset* getDcmDataset() const; - Json::Value getMetaDataJson() const; + Json::Value getMetaDataJson() const { + return metaDataJson; + }; // get ITK representation will be specific to the derived classes, // since the type of the ITK image and the number of ITK images is @@ -66,6 +70,8 @@ class MultiframeObject { typedef itk::Image DummyImageType; typedef DummyImageType::PointType PointType; typedef DummyImageType::DirectionType DirectionType; + typedef DummyImageType::SpacingType SpacingType; + typedef DummyImageType::SizeType SizeType; int initializeMetaDataFromString(const std::string&); // what this function does depends on whether we are coming from @@ -77,6 +83,69 @@ class MultiframeObject { // from ITK int initializeVolumeGeometryFromITK(DummyImageType::Pointer); + template + int initializeVolumeGeometryFromDICOM(T iodImage, DcmDataset *dataset) { + SpacingType spacing; + PointType origin; + DirectionType directions; + SizeType extent; + + FGInterface &fgInterface = iodImage->getFunctionalGroups(); + + if (getImageDirections(fgInterface, directions)) { + cerr << "Failed to get image directions" << endl; + throw -1; + } + + cout << directions << endl; + + double computedSliceSpacing, computedVolumeExtent; + vnl_vector sliceDirection(3); + sliceDirection[0] = *directions[0]; + sliceDirection[1] = *directions[1]; + sliceDirection[2] = *directions[2]; + if (computeVolumeExtent(fgInterface, sliceDirection, origin, computedSliceSpacing, computedVolumeExtent)) { + cerr << "Failed to compute origin and/or slice spacing!" << endl; + throw -1; + } + + if (getDeclaredImageSpacing(fgInterface, spacing)) { + cerr << "Failed to get image spacing from DICOM!" << endl; + throw -1; + } + + const double tolerance = 1e-5; + if(!spacing[2]){ + spacing[2] = computedSliceSpacing; + } else if(fabs(spacing[2]-computedSliceSpacing)>tolerance){ + cerr << "WARNING: Declared slice spacing is significantly different from the one declared in DICOM!" << + " Declared = " << spacing[2] << " Computed = " << computedSliceSpacing << endl; + } + + // Region size + { + OFString str; + if(dataset->findAndGetOFString(DCM_Rows, str).good()) + extent[1] = atoi(str.c_str()); + if(dataset->findAndGetOFString(DCM_Columns, str).good()) + extent[0] = atoi(str.c_str()); + } + extent[2] = fgInterface.getNumberOfFrames(); + + volumeGeometry.setSpacing(spacing); + volumeGeometry.setOrigin(origin); + volumeGeometry.setExtent(extent); + volumeGeometry.setDirections(directions); + + return EXIT_SUCCESS; + } + + int getImageDirections(FGInterface& fgInterface, DirectionType &dir); + + int computeVolumeExtent(FGInterface& fgInterface, vnl_vector &sliceDirection, PointType &imageOrigin, + double &sliceSpacing, double &sliceExtent); + int getDeclaredImageSpacing(FGInterface &fgInterface, SpacingType &spacing); + // initialize attributes of the composite context that are common for all multiframe objects //virtual int initializeCompositeContext(); // check whether all of the attributes required for initialization of the object are present in the diff --git a/include/dcmqi/ParametricMapObject.h b/include/dcmqi/ParametricMapObject.h index 922dd8a0..63b30559 100644 --- a/include/dcmqi/ParametricMapObject.h +++ b/include/dcmqi/ParametricMapObject.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "MultiframeObject.h" @@ -43,6 +44,12 @@ class ParametricMapObject : public MultiframeObject { CHECK_COND(parametricMap->write(dcm)); }; + int initializeFromDICOM(DcmDataset * sourceDataset); + + Float32ITKImageType::Pointer getITKRepresentation() const { + return itkImage; + } + protected: typedef itk::CastImageFilter Float32ToDummyCasterType; @@ -66,8 +73,6 @@ class ParametricMapObject : public MultiframeObject { // Data containers specific to this object Float32ITKImageType::Pointer itkImage; - - private: DPMParametricMapIOD* parametricMap; }; diff --git a/libsrc/ImageVolumeGeometry.cpp b/libsrc/ImageVolumeGeometry.cpp index a550ae6d..10570b0f 100644 --- a/libsrc/ImageVolumeGeometry.cpp +++ b/libsrc/ImageVolumeGeometry.cpp @@ -39,40 +39,4 @@ int ImageVolumeGeometry::setDirections(DirectionType d) { for (int i = 0; i < 3; i++) sliceDirection[i] = d[i][2]; return EXIT_SUCCESS; -} - -ImageVolumeGeometry::DummyImageType::Pointer ImageVolumeGeometry::getITKRepresentation(){ - ImageVolumeGeometry::DummyImageType::Pointer image; - ImageVolumeGeometry::DummyImageType::IndexType index; - ImageVolumeGeometry::DummyImageType::SizeType size; - ImageVolumeGeometry::DummyImageType::DirectionType direction; - ImageVolumeGeometry::DummyImageType::SpacingType spacing; - ImageVolumeGeometry::DummyImageType::RegionType region; - - image = ImageVolumeGeometry::DummyImageType::New(); - - index.Fill(0); - - size[0] = extent[0]; - size[1] = extent[1]; - size[2] = extent[2]; - - region.SetIndex(index); - region.SetSize(size); - - spacing[0] = this->spacing[0]; - spacing[1] = this->spacing[1]; - spacing[2] = this->spacing[2]; - - for (int i = 0; i < 3; i++) - direction[i][0] = rowDirection[i]; - for (int i = 0; i < 3; i++) - direction[i][1] = columnDirection[i]; - for (int i = 0; i < 3; i++) - direction[i][2] = sliceDirection[i]; - - image->SetDirection(direction); - image->SetSpacing(spacing); - - return image; } \ No newline at end of file diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index fd60697b..ef74b08b 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -182,7 +182,7 @@ std::vector MultiframeObject::findIntersectingSlices(ImageVolumeGeometry &v std::vector intersectingSlices; // for now, adopt a simple strategy that maps origin of the frame to index, and selects the slice corresponding // to this index as the intersecting one - ImageVolumeGeometry::DummyImageType::Pointer itkvolume = volume.getITKRepresentation(); + ImageVolumeGeometry::DummyImageType::Pointer itkvolume = volume.getITKRepresentation(); ImageVolumeGeometry::DummyImageType::PointType point; ImageVolumeGeometry::DummyImageType::IndexType index; vnl_vector frameIPP = frame.getFrameIPP(); @@ -237,5 +237,208 @@ int MultiframeObject::initializeCommonInstanceReferenceModule(IODCommonInstanceR } } + return EXIT_SUCCESS; +} + +int MultiframeObject::getImageDirections(FGInterface& fgInterface, DirectionType &dir){ + // TODO: handle the situation when FoR is not initialized + OFBool isPerFrame; + vnl_vector rowDirection(3), colDirection(3); + + FGPlaneOrientationPatient *planorfg = OFstatic_cast(FGPlaneOrientationPatient*, + fgInterface.get(0, DcmFGTypes::EFG_PLANEORIENTPATIENT, isPerFrame)); + if(!planorfg){ + cerr << "Plane Orientation (Patient) is missing, cannot parse input " << endl; + return EXIT_FAILURE; + } + OFString orientStr; + for(int i=0;i<3;i++){ + if(planorfg->getImageOrientationPatient(orientStr, i).good()){ + rowDirection[i] = atof(orientStr.c_str()); + } else { + cerr << "Failed to get orientation " << i << endl; + return EXIT_FAILURE; + } + } + for(int i=3;i<6;i++){ + if(planorfg->getImageOrientationPatient(orientStr, i).good()){ + colDirection[i-3] = atof(orientStr.c_str()); + } else { + cerr << "Failed to get orientation " << i << endl; + return EXIT_FAILURE; + } + } + vnl_vector sliceDirection = vnl_cross_3d(rowDirection, colDirection); + sliceDirection.normalize(); + + cout << "Row direction: " << rowDirection << endl; + cout << "Col direction: " << colDirection << endl; + + for(int i=0;i<3;i++){ + dir[i][0] = rowDirection[i]; + dir[i][1] = colDirection[i]; + dir[i][2] = sliceDirection[i]; + } + + cout << "Z direction: " << sliceDirection << endl; + + return 0; +} + +int MultiframeObject::computeVolumeExtent(FGInterface& fgInterface, vnl_vector &sliceDirection, + PointType &imageOrigin, double &sliceSpacing, double &sliceExtent) { + // Size + // Rows/Columns can be read directly from the respective attributes + // For number of slices, consider that all segments must have the same number of frames. + // If we have FoR UID initialized, this means every segment should also have Plane + // Position (Patient) initialized. So we can get the number of slices by looking + // how many per-frame functional groups a segment has. + + vector originDistances; + map originStr2distance; + map frame2overlap; + double minDistance = 0.0; + + sliceSpacing = 0; + + unsigned numFrames = fgInterface.getNumberOfFrames(); + + // Determine ordering of the frames, keep mapping from ImagePositionPatient string + // to the distance, and keep track (just out of curiosity) how many frames overlap + vnl_vector refOrigin(3); + { + OFBool isPerFrame; + FGPlanePosPatient *planposfg = OFstatic_cast(FGPlanePosPatient*, + fgInterface.get(0, DcmFGTypes::EFG_PLANEPOSPATIENT, isPerFrame)); + for(int j=0;j<3;j++){ + OFString planposStr; + if(planposfg->getImagePositionPatient(planposStr, j).good()){ + refOrigin[j] = atof(planposStr.c_str()); + } else { + cerr << "Failed to read patient position" << endl; + } + } + } + + for(size_t frameId=0;frameId sOrigin; + OFString sOriginStr = ""; + sOrigin.set_size(3); + for(int j=0;j<3;j++){ + OFString planposStr; + if(planposfg->getImagePositionPatient(planposStr, j).good()){ + sOrigin[j] = atof(planposStr.c_str()); + sOriginStr += planposStr; + if(j<2) + sOriginStr+='/'; + } else { + cerr << "Failed to read patient position" << endl; + return EXIT_FAILURE; + } + } + + // check if this frame has already been encountered + if(originStr2distance.find(sOriginStr) == originStr2distance.end()){ + vnl_vector difference; + difference.set_size(3); + difference[0] = sOrigin[0]-refOrigin[0]; + difference[1] = sOrigin[1]-refOrigin[1]; + difference[2] = sOrigin[2]-refOrigin[2]; + double dist = dot_product(difference,sliceDirection); + frame2overlap[sOriginStr] = 1; + originStr2distance[sOriginStr] = dist; + assert(originStr2distance.find(sOriginStr) != originStr2distance.end()); + originDistances.push_back(dist); + + if(frameId==0){ + minDistance = dist; + imageOrigin[0] = sOrigin[0]; + imageOrigin[1] = sOrigin[1]; + imageOrigin[2] = sOrigin[2]; + } + else if(dist1){ + // WARNING: this should be improved further. Spacing should be calculated for + // consecutive frames of the individual segment. Right now, all frames are considered + // indiscriminately. Question is whether it should be computed at all, considering we do + // not have any information about whether the 2 frames are adjacent or not, so perhaps we should + // always rely on the declared spacing, and not even try to compute it? + // TODO: discuss this with the QIICR team! + + // sort all unique distances, this will be used to check consistency of + // slice spacing, and also to locate the slice position from ImagePositionPatient + // later when we read the segments + sort(originDistances.begin(), originDistances.end()); + + sliceSpacing = fabs(originDistances[0]-originDistances[1]); + + for(size_t i=1;i::const_iterator it=frame2overlap.begin(); + it!=frame2overlap.end();++it){ + if(it->second>1) + overlappingFramesCnt++; + } + + cout << "Total frames: " << numFrames << endl; + cout << "Total frames with unique IPP: " << originDistances.size() << endl; + cout << "Total overlapping frames: " << overlappingFramesCnt << endl; + cout << "Origin: " << imageOrigin << endl; + } + + return EXIT_SUCCESS; +} + +int MultiframeObject::getDeclaredImageSpacing(FGInterface &fgInterface, SpacingType &spacing){ + OFBool isPerFrame; + FGPixelMeasures *pixelMeasures = OFstatic_cast(FGPixelMeasures*, + fgInterface.get(0, DcmFGTypes::EFG_PIXELMEASURES, isPerFrame)); + if(!pixelMeasures){ + cerr << "Pixel measures FG is missing!" << endl; + return EXIT_FAILURE; + } + + pixelMeasures->getPixelSpacing(spacing[0], 0); + pixelMeasures->getPixelSpacing(spacing[1], 1); + + Float64 spacingFloat; + if(pixelMeasures->getSpacingBetweenSlices(spacingFloat,0).good() && spacingFloat != 0){ + spacing[2] = spacingFloat; + } else if(pixelMeasures->getSliceThickness(spacingFloat,0).good() && spacingFloat != 0){ + // SliceThickness can be carried forward from the source images, and may not be what we need + // As an example, this ePAD example has 1.25 carried from CT, but true computed thickness is 1! + cerr << "WARNING: SliceThickness is present and is " << spacingFloat << ". using it!" << endl; + spacing[2] = spacingFloat; + } return EXIT_SUCCESS; } \ No newline at end of file diff --git a/libsrc/ParametricMapConverter.cpp b/libsrc/ParametricMapConverter.cpp index 0b2a1881..9ed3d68a 100644 --- a/libsrc/ParametricMapConverter.cpp +++ b/libsrc/ParametricMapConverter.cpp @@ -24,7 +24,12 @@ namespace dcmqi { } pair paramap2itkimageReplacement(DcmDataset *pmapDataset){ - return pair(); + + ParametricMapObject pm; + pm.initializeFromDICOM(pmapDataset); + + return pair (pm.getITKRepresentation(), + pm.getMetaDataJson().asString()); }; @@ -270,7 +275,7 @@ namespace dcmqi { slice2derimg = getSliceMapForSegmentation2DerivationImage(dcmDatasets, cast->GetOutput()); cout << "Mapping from the ITK image slices to the DICOM instances in the input list" << endl; for(int i=0;igetFrames(); result = OFget >(&frames)->addFrame(&*data.begin(), frameSize, perFrameFGs); - cout << "Frame " << sliceNumber << " added" << endl; +// cout << "Frame " << sliceNumber << " added" << endl; } // remove derivation image FG from the per-frame FGs, only if applicable! diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 24225a46..125b25cd 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -228,3 +228,69 @@ int ParametricMapObject::initializeRWVMFG() { return EXIT_SUCCESS; } + + +int ParametricMapObject::initializeFromDICOM(DcmDataset * sourceDataset) { + + DcmRLEDecoderRegistration::registerCodecs(); + + OFLogger dcemfinfLogger = OFLog::getLogger("qiicr.apps"); + dcemfinfLogger.setLogLevel(dcmtk::log4cplus::OFF_LOG_LEVEL); + + OFvariant result = DPMParametricMapIOD::loadDataset(*sourceDataset); + if (OFCondition* pCondition = OFget(&result)) { + throw -1; + } + + DPMParametricMapIOD* pMapDoc = *OFget(&result); + + initializeVolumeGeometryFromDICOM(pMapDoc, sourceDataset); + + // Initialize the image + Float32ITKImageType::Pointer itkImage = volumeGeometry.getITKRepresentation(); + + DPMParametricMapIOD::FramesType obj = pMapDoc->getFrames(); + if (OFCondition* pCondition = OFget(&obj)) { + throw -1; + } + + DPMParametricMapIOD::Frames frames = *OFget >(&obj); + + FGInterface &fgInterface = pMapDoc->getFunctionalGroups(); + for(int frameId=0;frameIdSetPixel(index, frame[pixelPosition]); + } + } + } + + +// informationHandler metaInfo; +// populateMetaInformationFromDICOM(pmapDataset, metaInfo); + + return EXIT_SUCCESS; +} From 8944b6c5ed975bd289d01e6275adc584e5b1ff0c Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Wed, 28 Jun 2017 10:45:32 -0400 Subject: [PATCH 088/116] ENH: added region and origin to ImageVolumeGeometry getITKRepresentation --- include/dcmqi/ImageVolumeGeometry.h | 7 +++++++ libsrc/MultiframeObject.cpp | 1 - libsrc/ParametricMapObject.cpp | 5 ++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/include/dcmqi/ImageVolumeGeometry.h b/include/dcmqi/ImageVolumeGeometry.h index 98986871..92d2a507 100644 --- a/include/dcmqi/ImageVolumeGeometry.h +++ b/include/dcmqi/ImageVolumeGeometry.h @@ -45,6 +45,7 @@ class ImageVolumeGeometry { typename T::DirectionType direction; typename T::SpacingType spacing; typename T::RegionType region; + typename T::PointType origin; image = T::New(); @@ -68,6 +69,12 @@ class ImageVolumeGeometry { for (int i = 0; i < 3; i++) direction[i][2] = sliceDirection[i]; + origin[0] = this->origin[0]; + origin[1] = this->origin[1]; + origin[2] = this->origin[2]; + + image->SetRegions(region); + image->SetOrigin(origin); image->SetDirection(direction); image->SetSpacing(spacing); diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index ef74b08b..89d5eda6 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -71,7 +71,6 @@ int MultiframeObject::initializeVolumeGeometryFromITK(DummyImageType::Pointer im volumeGeometry.setSpacing(spacing); volumeGeometry.setOrigin(origin); volumeGeometry.setExtent(extent); - volumeGeometry.setDirections(directions); return EXIT_SUCCESS; diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 125b25cd..240e370b 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -247,7 +247,10 @@ int ParametricMapObject::initializeFromDICOM(DcmDataset * sourceDataset) { initializeVolumeGeometryFromDICOM(pMapDoc, sourceDataset); // Initialize the image - Float32ITKImageType::Pointer itkImage = volumeGeometry.getITKRepresentation(); + itkImage = volumeGeometry.getITKRepresentation(); + + itkImage->Allocate(); + itkImage->FillBuffer(0); DPMParametricMapIOD::FramesType obj = pMapDoc->getFrames(); if (OFCondition* pCondition = OFget(&obj)) { From 1682bd06a205ee2b60e75287aa043483b969e188 Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Wed, 28 Jun 2017 16:06:51 -0400 Subject: [PATCH 089/116] ENH: initialize meta information from DCM into internal data structure --- include/dcmqi/ParametricMapObject.h | 3 +++ libsrc/ParametricMapConverter.cpp | 7 ++++++- libsrc/ParametricMapObject.cpp | 12 +++++++++--- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/include/dcmqi/ParametricMapObject.h b/include/dcmqi/ParametricMapObject.h index 63b30559..a82759a5 100644 --- a/include/dcmqi/ParametricMapObject.h +++ b/include/dcmqi/ParametricMapObject.h @@ -46,6 +46,9 @@ class ParametricMapObject : public MultiframeObject { int initializeFromDICOM(DcmDataset * sourceDataset); + template + void initializeMetaDataFromDICOM(T doc); + Float32ITKImageType::Pointer getITKRepresentation() const { return itkImage; } diff --git a/libsrc/ParametricMapConverter.cpp b/libsrc/ParametricMapConverter.cpp index 9ed3d68a..5b99fb8f 100644 --- a/libsrc/ParametricMapConverter.cpp +++ b/libsrc/ParametricMapConverter.cpp @@ -28,8 +28,13 @@ namespace dcmqi { ParametricMapObject pm; pm.initializeFromDICOM(pmapDataset); + Json::StyledWriter styledWriter; + std::stringstream ss; + + ss << styledWriter.write(pm.getMetaDataJson()); + return pair (pm.getITKRepresentation(), - pm.getMetaDataJson().asString()); + ss.str()); }; diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 240e370b..12a8d3ed 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -291,9 +291,15 @@ int ParametricMapObject::initializeFromDICOM(DcmDataset * sourceDataset) { } } - -// informationHandler metaInfo; -// populateMetaInformationFromDICOM(pmapDataset, metaInfo); + initializeMetaDataFromDICOM(pMapDoc); return EXIT_SUCCESS; } + +template +void ParametricMapObject::initializeMetaDataFromDICOM(T doc) { + + OFString temp; + doc->getSeries().getSeriesDescription(temp); + metaDataJson["SeriesDescription"] = temp.c_str(); +} From 49b676fb5fd6f3d473086fd400bfd3fabbd291d1 Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Wed, 28 Jun 2017 16:49:23 -0400 Subject: [PATCH 090/116] ENH: complete initialization of meta information from DCM * added Helper class static methods --- include/dcmqi/Helper.h | 5 +++ libsrc/Helper.cpp | 26 +++++++++++ libsrc/ParametricMapObject.cpp | 81 ++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+) diff --git a/include/dcmqi/Helper.h b/include/dcmqi/Helper.h index d1dc966e..483d773d 100644 --- a/include/dcmqi/Helper.h +++ b/include/dcmqi/Helper.h @@ -71,6 +71,11 @@ namespace dcmqi { static OFString generateUID(); static OFString getTagAsOFString(DcmDataset*, DcmTagKey); + + static string getCodeSequenceValue(CodeSequenceMacro &codeSequence); + static string getCodeSequenceDesignator(CodeSequenceMacro &codeSequence); + static string getCodeSequenceMeaning(CodeSequenceMacro &codeSequence); + static Json::Value codeSequence2Json(CodeSequenceMacro &codeSequence); }; } diff --git a/libsrc/Helper.cpp b/libsrc/Helper.cpp index 9e5b7af1..422db6d6 100644 --- a/libsrc/Helper.cpp +++ b/libsrc/Helper.cpp @@ -392,4 +392,30 @@ namespace dcmqi { string s = string()+codeValue.c_str()+","+codingSchemeDesignator.c_str()+","+codeMeaning.c_str(); return s; } + + Json::Value Helper::codeSequence2Json(CodeSequenceMacro &codeSequence) { + Json::Value value; + value["CodeValue"] = getCodeSequenceValue(codeSequence); + value["CodingSchemeDesignator"] = getCodeSequenceDesignator(codeSequence); + value["CodeMeaning"] = getCodeSequenceMeaning(codeSequence); + return value; + } + + string Helper::getCodeSequenceValue(CodeSequenceMacro &codeSequence) { + OFString value; + codeSequence.getCodeValue(value); + return value.c_str(); + } + + string Helper::getCodeSequenceDesignator(CodeSequenceMacro &codeSequence) { + OFString designator; + codeSequence.getCodingSchemeDesignator(designator); + return designator.c_str(); + } + + string Helper::getCodeSequenceMeaning(CodeSequenceMacro &codeSequence) { + OFString meaning; + codeSequence.getCodeMeaning(meaning); + return meaning.c_str(); + } } diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 12a8d3ed..9e9112c1 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -298,8 +298,89 @@ int ParametricMapObject::initializeFromDICOM(DcmDataset * sourceDataset) { template void ParametricMapObject::initializeMetaDataFromDICOM(T doc) { + // TODO: move shared information retrieval to parent class OFString temp; doc->getSeries().getSeriesDescription(temp); metaDataJson["SeriesDescription"] = temp.c_str(); + + doc->getSeries().getSeriesNumber(temp); + metaDataJson["SeriesNumber"] = temp.c_str(); + + doc->getIODGeneralImageModule().getInstanceNumber(temp); + metaDataJson["InstanceNumber"] = temp.c_str(); + + using namespace dcmqi; + + doc->getSeries().getBodyPartExamined(temp); + metaDataJson["BodyPartExamined"] = temp.c_str(); + + doc->getDPMParametricMapImageModule().getImageType(temp, 3); + metaDataJson["DerivedPixelContrast"] = temp.c_str(); + + if (doc->getNumberOfFrames() > 0) { + FGInterface& fg = doc->getFunctionalGroups(); + FGRealWorldValueMapping* rw = OFstatic_cast(FGRealWorldValueMapping*, + fg.get(0, DcmFGTypes::EFG_REALWORLDVALUEMAPPING)); + if (rw->getRealWorldValueMapping().size() > 0) { + FGRealWorldValueMapping::RWVMItem *item = rw->getRealWorldValueMapping()[0]; + metaDataJson["MeasurementUnitsCode"] = Helper::codeSequence2Json(item->getMeasurementUnitsCode()); + + Float64 slope; + item->getData().findAndGetFloat64(DCM_RealWorldValueSlope, slope); + metaDataJson["RealWorldValueSlope"] = slope; + + vector diffusionBValues; + + for(int quantIdx=0; quantIdxgetEntireQuantityDefinitionSequence().size(); quantIdx++) { +// TODO: what if there are more than one? + ContentItemMacro* macro = item->getEntireQuantityDefinitionSequence()[quantIdx]; + CodeSequenceMacro* codeSequence= macro->getConceptNameCodeSequence(); + if (codeSequence != NULL) { + OFString codeMeaning; + codeSequence->getCodeMeaning(codeMeaning); + OFString designator, meaning, value; + + if (codeMeaning == "Quantity") { + CodeSequenceMacro* quantityValueCode = macro->getConceptCodeSequence(); + if (quantityValueCode != NULL) { + metaDataJson["QuantityValueCode"] = Helper::codeSequence2Json(*quantityValueCode); + } + } else if (codeMeaning == "Measurement Method") { + CodeSequenceMacro* measurementMethodValueCode = macro->getConceptCodeSequence(); + if (measurementMethodValueCode != NULL) { + metaDataJson["MeasurementMethodCode"] = Helper::codeSequence2Json(*measurementMethodValueCode); + } + } else if (codeMeaning == "Source image diffusion b-value") { + macro->getNumericValue(value); + diffusionBValues.push_back(value.c_str()); + } + } + } + + if (diffusionBValues.size() > 0) { + metaDataJson["SourceImageDiffusionBValues"] = Json::Value(Json::arrayValue); + for (vector::iterator it = diffusionBValues.begin() ; it != diffusionBValues.end(); ++it) + metaDataJson["SourceImageDiffusionBValues"].append(*it); + } + } + + FGDerivationImage* derivationImage = OFstatic_cast(FGDerivationImage*, fg.get(0, DcmFGTypes::EFG_DERIVATIONIMAGE)); + OFVector& derivationImageItems = derivationImage->getDerivationImageItems(); + + if(derivationImageItems.size()>0){ + DerivationImageItem* derivationImageItem = derivationImageItems[0]; + CodeSequenceMacro* derivationCode = derivationImageItem->getDerivationCodeItems()[0]; + if (derivationCode != NULL) { + metaDataJson["DerivationCode"] = Helper::codeSequence2Json(*derivationCode); + } + } + + FGFrameAnatomy* fa = OFstatic_cast(FGFrameAnatomy*, fg.get(0, DcmFGTypes::EFG_FRAMEANATOMY)); + metaDataJson["AnatomicRegionSequence"] = Helper::codeSequence2Json(fa->getAnatomy().getAnatomicRegion()); + + FGFrameAnatomy::LATERALITY frameLaterality; + fa->getLaterality(frameLaterality); + metaDataJson["FrameLaterality"] = fa->laterality2Str(frameLaterality).c_str(); + } } From e396019d925032f2138e9d59d8fcb2eabafa7296 Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Thu, 29 Jun 2017 10:39:11 -0400 Subject: [PATCH 091/116] ENH: adaption to changes in https://github.com/QIICR/dcmqi/pull/271 --- libsrc/ParametricMapObject.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 9e9112c1..7b8d4b2f 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -366,13 +366,14 @@ void ParametricMapObject::initializeMetaDataFromDICOM(T doc) { } FGDerivationImage* derivationImage = OFstatic_cast(FGDerivationImage*, fg.get(0, DcmFGTypes::EFG_DERIVATIONIMAGE)); - OFVector& derivationImageItems = derivationImage->getDerivationImageItems(); - - if(derivationImageItems.size()>0){ - DerivationImageItem* derivationImageItem = derivationImageItems[0]; - CodeSequenceMacro* derivationCode = derivationImageItem->getDerivationCodeItems()[0]; - if (derivationCode != NULL) { - metaDataJson["DerivationCode"] = Helper::codeSequence2Json(*derivationCode); + if(derivationImage) { + OFVector &derivationImageItems = derivationImage->getDerivationImageItems(); + if (derivationImageItems.size() > 0) { + DerivationImageItem *derivationImageItem = derivationImageItems[0]; + CodeSequenceMacro *derivationCode = derivationImageItem->getDerivationCodeItems()[0]; + if (derivationCode != NULL) { + metaDataJson["DerivationCode"] = Helper::codeSequence2Json(*derivationCode); + } } } From d420babc792551b877219372490bb6032b22c8ac Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 30 Jun 2017 15:11:30 -0400 Subject: [PATCH 092/116] ENH: added initialization of derivation image FG --- include/dcmqi/DICOMFrame.h | 4 +++ include/dcmqi/MultiframeObject.h | 4 +++ include/dcmqi/ParametricMapObject.h | 3 +- libsrc/MultiframeObject.cpp | 48 +++++++++++++++++++++++++++++ libsrc/ParametricMapConverter.cpp | 3 ++ libsrc/ParametricMapObject.cpp | 23 ++++++++++++++ 6 files changed, 84 insertions(+), 1 deletion(-) diff --git a/include/dcmqi/DICOMFrame.h b/include/dcmqi/DICOMFrame.h index 586c6e9c..a2ef39d3 100644 --- a/include/dcmqi/DICOMFrame.h +++ b/include/dcmqi/DICOMFrame.h @@ -70,6 +70,10 @@ namespace dcmqi { return classUID; } + DcmDataset* getDataset() const { + return frameDataset; + } + private: int initializeFrameGeometryFromLegacyInstance(); diff --git a/include/dcmqi/MultiframeObject.h b/include/dcmqi/MultiframeObject.h index 0c1e4b70..bdf653bf 100644 --- a/include/dcmqi/MultiframeObject.h +++ b/include/dcmqi/MultiframeObject.h @@ -166,6 +166,10 @@ class MultiframeObject { static std::vector findIntersectingSlices(ImageVolumeGeometry& volume, dcmqi::DICOMFrame& frame); + int addDerivationItemToDerivationFG(FGDerivationImage* fgder, set frames, + CodeSequenceMacro purposeOfReferenceCode = CodeSequenceMacro("121322","DCM","Source image for image processing operation"), + CodeSequenceMacro derivationCode = CodeSequenceMacro("110001","DCM","Image Processing")); + void insertDerivationSeriesInstance(string seriesUID, string instanceUID); // constants to describe original representation of the data being converted diff --git a/include/dcmqi/ParametricMapObject.h b/include/dcmqi/ParametricMapObject.h index a82759a5..08dd7f53 100644 --- a/include/dcmqi/ParametricMapObject.h +++ b/include/dcmqi/ParametricMapObject.h @@ -63,8 +63,9 @@ class ParametricMapObject : public MultiframeObject { int initializeCompositeContext(); int initializeFrameAnatomyFG(); int initializeRWVMFG(); + int initializeFrames(vector >&); - // Functional groups initialization + // Functional groups initialization // Functional groups specific to PM: // - Shared diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index 89d5eda6..9ef57bf3 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -147,6 +147,8 @@ ContentItemMacro* MultiframeObject::initializeContentItemMacro(CodeSequenceMacro int MultiframeObject::mapVolumeSlicesToDICOMFrames(ImageVolumeGeometry& volume, const vector dcmDatasets, vector > &slice2frame){ + slice2frame.resize(volume.extent[2]); + for(int d=0;d derivationFrames, + CodeSequenceMacro purposeOfReferenceCode, + CodeSequenceMacro derivationCode){ + DerivationImageItem *derimgItem; + // TODO: check what we should do with DerivationDescription + CHECK_COND(fgder->addDerivationImageItem(derivationCode,"",derimgItem)); + + + OFVector siVector; + std::vector frameVector; + + for(set::const_iterator sIt=derivationFrames.begin(); + sIt!=derivationFrames.end();++sIt) { + + // TODO: it seems that with the current dcmtk functionality it is not possible to set the referenced + // frame number. Revisit this after discussing with Michael. + siVector.push_back((*sIt).getDataset()); + frameVector.push_back(*sIt); + } + + OFVector srcimgItems; + CHECK_COND(derimgItem->addSourceImageItems(siVector, purposeOfReferenceCode, srcimgItems)); + + // iterate over source image items (assuming they are in the same order as in siVector!), and initialize + // frame number, if applicable + unsigned siItemCnt=0; + for(OFVector::iterator vIt=srcimgItems.begin(); + vIt!=srcimgItems.end();++vIt,++siItemCnt) { + // TODO: when multuple frames from the same instance are used, they should be referenced within a single + // ImageSOPInstanceReferenceMacro. There would need to be another level of checks over all of the frames + // that are mapped to the given slice to identify those that are from the same instance, and populate the + // list of frames. I can't think of any use case where this would be immediately important, but if we ever + // use multiframe for DCE/DWI, with all of the temporal/b-value frames stored in a single object, there will + // be multiple frames used to derive a single frame in a parametric map, for example. + if(frameVector[siItemCnt].getFrameNumber()) { + OFVector frameNumbersVector; + ImageSOPInstanceReferenceMacro &instRef = (*vIt)->getImageSOPInstanceReference(); + frameNumbersVector.push_back(frameVector[siItemCnt].getFrameNumber()); + instRef.setReferencedFrameNumber(frameNumbersVector); + } + } + + return EXIT_SUCCESS; +} + std::vector MultiframeObject::findIntersectingSlices(ImageVolumeGeometry &volume, dcmqi::DICOMFrame &frame) { std::vector intersectingSlices; // for now, adopt a simple strategy that maps origin of the frame to index, and selects the slice corresponding diff --git a/libsrc/ParametricMapConverter.cpp b/libsrc/ParametricMapConverter.cpp index 5b99fb8f..33b72d14 100644 --- a/libsrc/ParametricMapConverter.cpp +++ b/libsrc/ParametricMapConverter.cpp @@ -358,6 +358,9 @@ namespace dcmqi { CHECK_COND(instRef.getReferencedSOPClassUID(classUID)); CHECK_COND(instRef.getReferencedSOPInstanceUID(instanceUID)); + // AF: I am not sure why this is needed - I would expect these attributes should be initialized + // by DerivationImageItem. Perhaps this is a workaround for an issue that was fixed since then. + // Not including this in the refactored implementation. if(instanceUIDs.find(instanceUID) == instanceUIDs.end()){ SOPInstanceReferenceMacro *refinstancesItem = new SOPInstanceReferenceMacro(); CHECK_COND(refinstancesItem->setReferencedSOPClassUID(classUID)); diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 7b8d4b2f..205efb93 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -59,6 +59,8 @@ int ParametricMapObject::initializeFromITK(Float32ITKImageType::Pointer inputIma initializeCommonInstanceReferenceModule(this->parametricMap->getCommonInstanceReference(), slice2frame); + initializeFrames(slice2frame); + return EXIT_SUCCESS; } @@ -385,3 +387,24 @@ void ParametricMapObject::initializeMetaDataFromDICOM(T doc) { metaDataJson["FrameLaterality"] = fa->laterality2Str(frameLaterality).c_str(); } } + +int ParametricMapObject::initializeFrames(vector >& slice2frame){ + FGPlanePosPatient* fgppp = FGPlanePosPatient::createMinimal("1","1","1"); + FGFrameContent* fgfc = new FGFrameContent(); + FGDerivationImage* fgder = new FGDerivationImage(); + OFVector perFrameFGs; + + perFrameFGs.push_back(fgppp); + perFrameFGs.push_back(fgfc); + + unsigned nSlices = itkImage->GetLargestPossibleRegion().GetSize()[2]; + + for (unsigned long sliceNumber = 0;sliceNumber < nSlices; sliceNumber++) { + if(!slice2frame[sliceNumber].empty()){ + // TODO: read derivation code from metadata, if available, and pass instead of the default + addDerivationItemToDerivationFG(fgder, slice2frame[sliceNumber]); + } + } + + return EXIT_SUCCESS; +} From 3448569abd45f2ac9a619875282cea25c866a8a6 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 30 Jun 2017 15:51:31 -0400 Subject: [PATCH 093/116] ENH: added initialization of the frame content --- libsrc/ParametricMapConverter.cpp | 1 + libsrc/ParametricMapObject.cpp | 69 ++++++++++++++++++++++++++++++- 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/libsrc/ParametricMapConverter.cpp b/libsrc/ParametricMapConverter.cpp index 33b72d14..ab64150f 100644 --- a/libsrc/ParametricMapConverter.cpp +++ b/libsrc/ParametricMapConverter.cpp @@ -85,6 +85,7 @@ namespace dcmqi { /* Initialize dimension module */ IODMultiframeDimensionModule &mfdim = pMapDoc->getIODMultiframeDimensionModule(); + // TODO: this is probably incorrect OFCondition result = mfdim.addDimensionIndex(DCM_ImagePositionPatient, Helper::generateUID(), DCM_RealWorldValueMappingSequence, "Frame position"); diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 205efb93..15f268cc 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -122,13 +122,39 @@ int ParametricMapObject::createParametricMap() { frameTypeFG.setFrameType(frameTypeStr.c_str()); CHECK_COND(parametricMap->addForAllFrames(frameTypeFG)); + /* Initialize dimension module */ + IODMultiframeDimensionModule &mfdim = parametricMap->getIODMultiframeDimensionModule(); + + // Initialize dimension index + OFCondition result = mfdim.addDimensionIndex(DCM_ImagePositionPatient, dcmqi::Helper::generateUID(), + DCM_PlanePositionSequence, "ImagePositionPatient"); + return EXIT_SUCCESS; } int ParametricMapObject::initializeCompositeContext() { // TODO: should this be done in the parent? if(derivationDcmDatasets.size()){ - CHECK_COND(parametricMap->import(*derivationDcmDatasets[0], OFTrue, OFTrue, OFFalse, OFTrue)); + /* Import GeneralSeriesModule - content for the reference + from include/dcmtk/dcmiod/modgeneralseries.h: + * Modality: (CS, 1, 1) + * Series Instance Number: (UI, 1, 1) + * Series Number: (IS, 1, 2) + * Laterality: (CS, 1, 2C) + * Series Date: (DA, 1, 3) + * Series Time: (TM, 1, 3) + * Performing Physician's Name: (PN, 1, 3) + * Protocol Name: (LO, 1, 3) + * Series Description: (LO, 1, 3) + * Operators' Name: (PN, 1-n, 3) + * Body Part Examined: (CS, 1, 3) + * Patient Position: (CS, 1, 2C) + */ + CHECK_COND(parametricMap->import(*derivationDcmDatasets[0], + OFTrue, // Patient + OFTrue, // Study + OFTrue, // Frame of reference + OFTrue)); // Series } else { // TODO: once we support passing of composite context in metadata, propagate it @@ -404,6 +430,47 @@ int ParametricMapObject::initializeFrames(vectorGetBufferedRegion().GetSize(); + + sliceIndex[0] = 0; + sliceIndex[1] = 0; + sliceIndex[2] = sliceNumber; + + inputSize[2] = 1; + + sliceRegion.SetIndex(sliceIndex); + sliceRegion.SetSize(inputSize); + + const unsigned frameSize = inputSize[0] * inputSize[1]; + + OFVector data(frameSize); + + itk::ImageRegionConstIteratorWithIndex sliceIterator(itkImage, sliceRegion); + + unsigned framePixelCnt = 0; + for(sliceIterator.GoToBegin();!sliceIterator.IsAtEnd(); ++sliceIterator, ++framePixelCnt){ + data[framePixelCnt] = sliceIterator.Get(); + Float32ITKImageType::IndexType idx = sliceIterator.GetIndex(); + // cout << framePixelCnt << " " << idx[1] << "," << idx[0] << endl; + } + + // Plane Position + Float32ITKImageType::PointType sliceOriginPoint; + itkImage->TransformIndexToPhysicalPoint(sliceIndex, sliceOriginPoint); + fgppp->setImagePositionPatient( + dcmqi::Helper::floatToStrScientific(sliceOriginPoint[0]).c_str(), + dcmqi::Helper::floatToStrScientific(sliceOriginPoint[1]).c_str(), + dcmqi::Helper::floatToStrScientific(sliceOriginPoint[2]).c_str()); + + // Frame Content + OFCondition result = fgfc->setDimensionIndexValues(sliceNumber+1 /* value within dimension */, 0 /* first dimension */); + + DPMParametricMapIOD::FramesType frames = parametricMap->getFrames(); + result = OFget >(&frames)->addFrame(&*data.begin(), frameSize, perFrameFGs); + } return EXIT_SUCCESS; From 9a6d06cb3a69e92a8f1d2363a08ab13b645e95c0 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 30 Jun 2017 16:24:54 -0400 Subject: [PATCH 094/116] ENH: fix initialization of the composite context itkimage2paramap tests are passing! --- include/dcmqi/MultiframeObject.h | 7 +++++++ libsrc/MultiframeObject.cpp | 3 ++- libsrc/ParametricMapObject.cpp | 12 ++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/include/dcmqi/MultiframeObject.h b/include/dcmqi/MultiframeObject.h index bdf653bf..97a9d345 100644 --- a/include/dcmqi/MultiframeObject.h +++ b/include/dcmqi/MultiframeObject.h @@ -172,6 +172,13 @@ class MultiframeObject { void insertDerivationSeriesInstance(string seriesUID, string instanceUID); + int setDerivationDatasets(std::vector derivationDatasets){ + for(std::vector::const_iterator vIt=derivationDatasets.begin(); + vIt!=derivationDatasets.end();++vIt) + derivationDcmDatasets.push_back(*vIt); + return EXIT_SUCCESS; + } + // constants to describe original representation of the data being converted enum { DICOM_REPR = 0, diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index 9ef57bf3..de844afa 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -488,4 +488,5 @@ int MultiframeObject::getDeclaredImageSpacing(FGInterface &fgInterface, SpacingT spacing[2] = spacingFloat; } return EXIT_SUCCESS; -} \ No newline at end of file +} + diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 15f268cc..d54350d6 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -12,6 +12,9 @@ int ParametricMapObject::initializeFromITK(Float32ITKImageType::Pointer inputImage, const string &metaDataStr, std::vector derivationDatasets) { + + setDerivationDatasets(derivationDatasets); + sourceRepresentationType = ITK_REPR; itkImage = inputImage; @@ -156,6 +159,15 @@ int ParametricMapObject::initializeCompositeContext() { OFTrue, // Frame of reference OFTrue)); // Series + { + // DEBUG -> + OFString forUID; + CHECK_COND(parametricMap->getFrameOfReference().getFrameOfReferenceUID(forUID)); + std::cout << "FoR UID:" << forUID << std::endl; + + // <- DEBUG + } + } else { // TODO: once we support passing of composite context in metadata, propagate it // into parametricMap here From 4091f636c0e5e36890c3f062c14ad2af162d21a8 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 30 Jun 2017 17:02:22 -0400 Subject: [PATCH 095/116] ENH: add RWVM FG crash in addForAllFrames()!... --- libsrc/ParametricMapObject.cpp | 33 +++++++++++---------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index d54350d6..9b5c50b3 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -127,8 +127,6 @@ int ParametricMapObject::createParametricMap() { /* Initialize dimension module */ IODMultiframeDimensionModule &mfdim = parametricMap->getIODMultiframeDimensionModule(); - - // Initialize dimension index OFCondition result = mfdim.addDimensionIndex(DCM_ImagePositionPatient, dcmqi::Helper::generateUID(), DCM_PlanePositionSequence, "ImagePositionPatient"); @@ -159,15 +157,6 @@ int ParametricMapObject::initializeCompositeContext() { OFTrue, // Frame of reference OFTrue)); // Series - { - // DEBUG -> - OFString forUID; - CHECK_COND(parametricMap->getFrameOfReference().getFrameOfReferenceUID(forUID)); - std::cout << "FoR UID:" << forUID << std::endl; - - // <- DEBUG - } - } else { // TODO: once we support passing of composite context in metadata, propagate it // into parametricMap here @@ -199,9 +188,6 @@ int ParametricMapObject::initializeRWVMFG() { FGRealWorldValueMapping::RWVMItem* realWorldValueMappingItem = new FGRealWorldValueMapping::RWVMItem(); - if (!realWorldValueMappingItem ) - return EXIT_FAILURE; - realWorldValueMappingItem->setRealWorldValueSlope(metaDataJson["RealWorldValueSlope"].asFloat()); realWorldValueMappingItem->setRealWorldValueIntercept(0); @@ -225,6 +211,7 @@ int ParametricMapObject::initializeRWVMFG() { realWorldValueMappingItem->setLUTLabel(metaDataJson["MeasurementUnitsCode"]["CodeValue"].asCString()); } + if(metaDataJson.isMember("QuantityValueCode")){ ContentItemMacro* item = initializeContentItemMacro(CodeSequenceMacro("G-C1C6", "SRT", "Quantity"), dcmqi::Helper::jsonToCodeSequenceMacro(metaDataJson["QuantityValueCode"])); @@ -245,27 +232,29 @@ int ParametricMapObject::initializeRWVMFG() { realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(item); } - if(metaDataJson.isMember("SourceImageDiffusionBValues")){ - for(int bvalId=0;bvalIdsetValueType(ContentItemMacro::VT_NUMERIC); bval->getEntireConceptNameCodeSequence().push_back(qCodeName); bval->getEntireMeasurementUnitsCodeSequence().push_back(bvalUnits); - if(bval->setNumericValue(metaDataJson["SourceImageDiffusionBValues"][bvalId].asCString()).bad()) + if (bval->setNumericValue(metaDataJson["SourceImageDiffusionBValues"][bvalId].asCString()).bad()) cout << "Failed to insert the value!" << endl;; realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(bval); } } + rwvmFG.getRealWorldValueMapping().push_back(realWorldValueMappingItem); + parametricMap->addForAllFrames(rwvmFG); + return EXIT_SUCCESS; } From 7a27b0214d4e2b806b593c91413836f3c2d3c861 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 30 Jun 2017 18:09:17 -0400 Subject: [PATCH 096/116] ENH: fix RWVM FG segfault Also: * fix the typo in JSON lookup * replace the temporary codes with the standard ones: CP-1665 became standard in 2017a --- libsrc/ParametricMapObject.cpp | 72 ++++++++++++++++++++++++++++++---- 1 file changed, 65 insertions(+), 7 deletions(-) diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 9b5c50b3..eb7ef19a 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -199,11 +199,11 @@ int ParametricMapObject::initializeRWVMFG() { realWorldValueMappingItem->setRealWorldValueFirstValueMappedSigned(calculator->GetMinimum()); realWorldValueMappingItem->setRealWorldValueLastValueMappedSigned(calculator->GetMaximum()); - if(metaDataJson.isMember("MeasurementsUnitsCode")){ + if(metaDataJson.isMember("MeasurementUnitsCode")){ CodeSequenceMacro& unitsCodeDcmtk = realWorldValueMappingItem->getMeasurementUnitsCode(); - unitsCodeDcmtk.set(metaDataJson["MeasurementsUnitsCode"]["CodeValue"].asCString(), - metaDataJson["MeasurementsUnitsCode"]["CodingSchemeDesignator"].asCString(), - metaDataJson["MeasurementsUnitsCode"]["CodeMeaning"].asCString()); + unitsCodeDcmtk.set(metaDataJson["MeasurementUnitsCode"]["CodeValue"].asCString(), + metaDataJson["MeasurementUnitsCode"]["CodingSchemeDesignator"].asCString(), + metaDataJson["MeasurementUnitsCode"]["CodeMeaning"].asCString()); cout << "Measurements units initialized to " << dcmqi::Helper::codeSequenceMacroToString(unitsCodeDcmtk); @@ -211,33 +211,91 @@ int ParametricMapObject::initializeRWVMFG() { realWorldValueMappingItem->setLUTLabel(metaDataJson["MeasurementUnitsCode"]["CodeValue"].asCString()); } - + /* if(metaDataJson.isMember("QuantityValueCode")){ ContentItemMacro* item = initializeContentItemMacro(CodeSequenceMacro("G-C1C6", "SRT", "Quantity"), dcmqi::Helper::jsonToCodeSequenceMacro(metaDataJson["QuantityValueCode"])); realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(item); + }*/ + + ContentItemMacro* quantity = new ContentItemMacro; + CodeSequenceMacro* qCodeName = new CodeSequenceMacro("G-C1C6", "SRT", "Quantity"); + CodeSequenceMacro* qSpec = new CodeSequenceMacro( + metaDataJson["QuantityValueCode"]["CodeValue"].asCString(), + metaDataJson["QuantityValueCode"]["CodingSchemeDesignator"].asCString(), + metaDataJson["QuantityValueCode"]["CodeMeaning"].asCString()); + + if (!quantity || !qSpec || !qCodeName) + { + return EXIT_FAILURE; } + quantity->getEntireConceptNameCodeSequence().push_back(qCodeName); + quantity->getEntireConceptCodeSequence().push_back(qSpec); + realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(quantity); + quantity->setValueType(ContentItemMacro::VT_CODE); + + /* // TODO: factor out defined CodeSequenceMacros into definitions as in dcmsr/include/dcmtk/dcmsr/codes if(metaDataJson.isMember("MeasurementMethodCode")){ ContentItemMacro* item = initializeContentItemMacro(CodeSequenceMacro("G-C306", "SRT", "Measurement Method"), dcmqi::Helper::jsonToCodeSequenceMacro(metaDataJson["MeasurementMethodCode"])); realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(item); + } */ + + if(metaDataJson.isMember("MeasurementMethodCode")){ + ContentItemMacro* measureMethod = new ContentItemMacro; + CodeSequenceMacro* qCodeName = new CodeSequenceMacro("G-C306", "SRT", "Measurement Method"); + CodeSequenceMacro* qSpec = new CodeSequenceMacro( + metaDataJson["MeasurementMethodCode"]["CodeValue"].asCString(), + metaDataJson["MeasurementMethodCode"]["CodingSchemeDesignator"].asCString(), + metaDataJson["MeasurementMethodCode"]["CodeMeaning"].asCString()); + + if (!measureMethod || !qSpec || !qCodeName) + { + return EXIT_FAILURE; + } + + measureMethod->getEntireConceptNameCodeSequence().push_back(qCodeName); + measureMethod->getEntireConceptCodeSequence().push_back(qSpec); + realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(measureMethod); + measureMethod->setValueType(ContentItemMacro::VT_CODE); } + /* if(metaDataJson.isMember("ModelFittingMethodCode")){ // TODO: update this once CP-1665 is integrated into the standard - ContentItemMacro* item = initializeContentItemMacro(CodeSequenceMacro("xxxxx2", "99DCMCP1665", "Model Fitting Method"), + ContentItemMacro* item = initializeContentItemMacro(CodeSequenceMacro("113241", "DCM", "Model Fitting Method"), dcmqi::Helper::jsonToCodeSequenceMacro(metaDataJson["ModelFittingMethodCode"])); realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(item); + } */ + + if(metaDataJson.isMember("ModelFittingMethodCode")){ + ContentItemMacro* fittingMethod = new ContentItemMacro; + CodeSequenceMacro* qCodeName = new CodeSequenceMacro("113241", "DCM", "Model fitting method"); + CodeSequenceMacro* qSpec = new CodeSequenceMacro( + metaDataJson["ModelFittingMethodCode"]["CodeValue"].asCString(), + metaDataJson["ModelFittingMethodCode"]["CodingSchemeDesignator"].asCString(), + metaDataJson["ModelFittingMethodCode"]["CodeMeaning"].asCString()); + + if (!fittingMethod || !qSpec || !qCodeName) + { + return NULL; + } + + fittingMethod->getEntireConceptNameCodeSequence().push_back(qCodeName); + fittingMethod->getEntireConceptCodeSequence().push_back(qSpec); + realWorldValueMappingItem->getEntireQuantityDefinitionSequence().push_back(fittingMethod); + fittingMethod->setValueType(ContentItemMacro::VT_CODE); } + if(metaDataJson.isMember("SourceImageDiffusionBValues")) { for (int bvalId = 0; bvalId < metaDataJson["SourceImageDiffusionBValues"].size(); bvalId++) { ContentItemMacro *bval = new ContentItemMacro; CodeSequenceMacro *bvalUnits = new CodeSequenceMacro("s/mm2", "UCUM", "s/mm2"); // TODO: update this once CP-1665 is integrated into the standard - CodeSequenceMacro *qCodeName = new CodeSequenceMacro("xxxxx1", "99DCMCP1665", "Source image diffusion b-value"); + CodeSequenceMacro *qCodeName = new CodeSequenceMacro("113240", "DCM", "Source image diffusion b-value"); if (!bval || !bvalUnits || !qCodeName) { return EXIT_FAILURE; From 53e7d6868a954bb2a2dfb139adbeb634acdd536b Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 30 Jun 2017 18:13:05 -0400 Subject: [PATCH 097/116] ENH: replace temporary codes with standard ones CP-1665 became standard in 2017a --- libsrc/ParametricMapConverter.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libsrc/ParametricMapConverter.cpp b/libsrc/ParametricMapConverter.cpp index ab64150f..367ac2d6 100644 --- a/libsrc/ParametricMapConverter.cpp +++ b/libsrc/ParametricMapConverter.cpp @@ -227,7 +227,7 @@ namespace dcmqi { if(metaInfo.metaInfoRoot.isMember("ModelFittingMethodCode")){ ContentItemMacro* fittingMethod = new ContentItemMacro; - CodeSequenceMacro* qCodeName = new CodeSequenceMacro("DWMPxxxxx2", "99QIICR", "Model fitting method"); + CodeSequenceMacro* qCodeName = new CodeSequenceMacro("113241", "DCM", "Model fitting method"); CodeSequenceMacro* qSpec = new CodeSequenceMacro( metaInfo.metaInfoRoot["ModelFittingMethodCode"]["CodeValue"].asCString(), metaInfo.metaInfoRoot["ModelFittingMethodCode"]["CodingSchemeDesignator"].asCString(), @@ -248,7 +248,7 @@ namespace dcmqi { for(int bvalId=0;bvalId Date: Fri, 30 Jun 2017 18:27:05 -0400 Subject: [PATCH 098/116] ENH: fix initialization of PM dciodvfy is happy! --- libsrc/ParametricMapObject.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index eb7ef19a..f809db9b 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -46,7 +46,10 @@ int ParametricMapObject::initializeFromITK(Float32ITKImageType::Pointer inputIma initializeDimensions(dimensionTags); initializePixelMeasuresFG(); + CHECK_COND(parametricMap->addForAllFrames(pixelMeasuresFG)); + initializePlaneOrientationFG(); + CHECK_COND(parametricMap->addForAllFrames(planeOrientationPatientFG)); // PM-specific FGs initializeFrameAnatomyFG(); @@ -181,6 +184,8 @@ int ParametricMapObject::initializeFrameAnatomyFG() { frameAnatomyFG.getAnatomy().getAnatomicRegion().set("T-D0050", "SRT", "Tissue"); } + CHECK_COND(parametricMap->addForAllFrames(frameAnatomyFG)); + return EXIT_SUCCESS; } From 5391f64b8f4700d210aff7e51ef87a1037ade5c3 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 30 Jun 2017 18:28:38 -0400 Subject: [PATCH 099/116] BUG: fix initialization of the ITK PM output geometry rows/columns were flipped on initialization --- libsrc/ParametricMapObject.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index f809db9b..fcf8f01c 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -104,8 +104,8 @@ int ParametricMapObject::createParametricMap() { DPMParametricMapIOD::create(metaDataJson["Modality"].asCString(), metaDataJson["SeriesNumber"].asCString(), metaDataJson["InstanceNumber"].asCString(), - volumeGeometry.extent[0], volumeGeometry.extent[1], + volumeGeometry.extent[0], equipmentInfoModule, contentIdentificationMacro, "VOLUME", "QUANTITY", From 69308f64252a5f17b267113d24e2d6dbaa3f074b Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 30 Jun 2017 18:30:31 -0400 Subject: [PATCH 100/116] ENH: added reminder comment to revisit modality initialization --- libsrc/ParametricMapObject.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index fcf8f01c..d698aeae 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -100,6 +100,8 @@ int ParametricMapObject::createParametricMap() { // create Parametric map object + // TODO: revisit intialization of the modality - if source images are available, modality should match + // that in the source images! OFvariant obj = DPMParametricMapIOD::create(metaDataJson["Modality"].asCString(), metaDataJson["SeriesNumber"].asCString(), From 08e5464b9b1cf0ec2142f7a0cc822d5636b2178e Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Sat, 11 Mar 2017 12:24:19 -0500 Subject: [PATCH 101/116] BUG: initialize modality from the source dataset --- libsrc/ParametricMapConverter.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libsrc/ParametricMapConverter.cpp b/libsrc/ParametricMapConverter.cpp index 367ac2d6..88379262 100644 --- a/libsrc/ParametricMapConverter.cpp +++ b/libsrc/ParametricMapConverter.cpp @@ -37,8 +37,6 @@ namespace dcmqi { ss.str()); }; - - DcmDataset* ParametricMapConverter::itkimage2paramap(const FloatImageType::Pointer ¶metricMapImage, vector dcmDatasets, const string &metaData) { @@ -724,7 +722,7 @@ namespace dcmqi { } FGDerivationImage* derivationImage = OFstatic_cast(FGDerivationImage*, fg.get(0, DcmFGTypes::EFG_DERIVATIONIMAGE)); - + if(derivationImage){ OFVector& derivationImageItems = derivationImage->getDerivationImageItems(); if(derivationImageItems.size()>0){ From d50088299c0ac64df60de34c0d651226b96253e8 Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Tue, 27 Jun 2017 21:07:40 -0400 Subject: [PATCH 102/116] ENH: adding refactored code for paramap from DCM to itk --- include/dcmqi/ParametricMapObject.h | 2 + libsrc/CMakeLists.txt | 194 ++++++++++++++-------------- 2 files changed, 99 insertions(+), 97 deletions(-) diff --git a/include/dcmqi/ParametricMapObject.h b/include/dcmqi/ParametricMapObject.h index 08dd7f53..bbda87a7 100644 --- a/include/dcmqi/ParametricMapObject.h +++ b/include/dcmqi/ParametricMapObject.h @@ -77,6 +77,8 @@ class ParametricMapObject : public MultiframeObject { // Data containers specific to this object Float32ITKImageType::Pointer itkImage; + + private: DPMParametricMapIOD* parametricMap; }; diff --git a/libsrc/CMakeLists.txt b/libsrc/CMakeLists.txt index b354c700..17d842be 100644 --- a/libsrc/CMakeLists.txt +++ b/libsrc/CMakeLists.txt @@ -1,98 +1,98 @@ - -#----------------------------------------------------------------------------- - -set(INCLUDE_DIR ../include/dcmqi) - -set(HDRS - ${INCLUDE_DIR}/DICOMFrame.h - ${INCLUDE_DIR}/ImageVolume.h - ${INCLUDE_DIR}/MultiframeConverter.h - ${INCLUDE_DIR}/Helper.h - ${INCLUDE_DIR}/SegmentationImageConverter.h - ${INCLUDE_DIR}/ParametricMapConverter.h - ${INCLUDE_DIR}/JSONMetaInformationHandlerBase.h - ${INCLUDE_DIR}/JSONParametricMapMetaInformationHandler.h - ${INCLUDE_DIR}/JSONSegmentationMetaInformationHandler.h - ${INCLUDE_DIR}/SegmentAttributes.h - ) - -set(SRCS - DICOMFrame.cpp - ImageVolume.cpp - MultiframeConverter.cpp - Helper.cpp - SegmentationImageConverter.cpp - ParametricMapConverter.cpp - JSONMetaInformationHandlerBase.cpp - JSONParametricMapMetaInformationHandler.cpp - JSONSegmentationMetaInformationHandler.cpp - SegmentAttributes.cpp - ) - -set(ADDITIONAL_HDRS - ${INCLUDE_DIR}/DICOMFrame.h - ${INCLUDE_DIR}/ImageVolume.h - ${INCLUDE_DIR}/SegmentVolume.h - ${INCLUDE_DIR}/MultiframeObject.h - ${INCLUDE_DIR}/ParametricMapObject.h - ${INCLUDE_DIR}/SegmentationImageObject.h - ${INCLUDE_DIR}/ImageVolumeGeometry.h - ${INCLUDE_DIR}/Exceptions.h - ${INCLUDE_DIR}/framesorter.h - ${INCLUDE_DIR}/preproc.h - ${INCLUDE_DIR}/QIICRConstants.h - ${INCLUDE_DIR}/QIICRUIDs.h - ) - -set(ADDITIONAL_SRCS - DICOMFrame.cpp - ImageVolume.cpp - SegmentVolume.cpp - MultiframeObject.cpp - ParametricMapObject.cpp - SegmentationImageObject.cpp - ImageVolumeGeometry.cpp - ) - -if(DCMQI_BUILTIN_JSONCPP) - list(APPEND ADDITIONAL_SRCS - ${DCMQI_SOURCE_DIR}/jsoncpp/jsoncpp.cpp - ) - set(JsonCpp_INCLUDE_DIR ${DCMQI_SOURCE_DIR}/jsoncpp) -endif() - -set(lib_name dcmqi) - -add_library(${lib_name} STATIC - ${HDRS} - ${SRCS} - ${ADDITIONAL_HDRS} - ${ADDITIONAL_SRCS} - ) - -if(DCMQI_LIBRARY_PROPERTIES) - set_target_properties(${lib_name} PROPERTIES ${DCMQI_LIBRARY_PROPERTIES}) -endif() - -set_property(GLOBAL APPEND PROPERTY ${CMAKE_PROJECT_NAME}_TARGETS ${lib_name}) - -set(${lib_name}_INCLUDE_DIRS - ${DCMTK_INCLUDE_DIRS} - ${ITK_INCLUDE_DIRS} - ${DCMQI_SOURCE_DIR}/include - ${DCMQI_BINARY_DIR}/include - ${JsonCpp_INCLUDE_DIR} - ) -if(ITK_INSTALL_PREFIX) - list(APPEND ${lib_name}_INCLUDE_DIRS - ${ITK_INSTALL_PREFIX}/include/vxl/core - ${ITK_INSTALL_PREFIX}/include/vxl/vcl - ) -endif() -target_include_directories(${lib_name} PUBLIC ${${lib_name}_INCLUDE_DIRS}) - -target_link_libraries(${lib_name} PUBLIC - ${DCMTK_LIBRARIES} - ${ITK_LIBRARIES} - $<$>:${JsonCpp_LIBRARY}> + +#----------------------------------------------------------------------------- + +set(INCLUDE_DIR ../include/dcmqi) + +set(HDRS + ${INCLUDE_DIR}/DICOMFrame.h + ${INCLUDE_DIR}/ImageVolume.h + ${INCLUDE_DIR}/MultiframeConverter.h + ${INCLUDE_DIR}/Helper.h + ${INCLUDE_DIR}/SegmentationImageConverter.h + ${INCLUDE_DIR}/ParametricMapConverter.h + ${INCLUDE_DIR}/JSONMetaInformationHandlerBase.h + ${INCLUDE_DIR}/JSONParametricMapMetaInformationHandler.h + ${INCLUDE_DIR}/JSONSegmentationMetaInformationHandler.h + ${INCLUDE_DIR}/SegmentAttributes.h + ) + +set(SRCS + DICOMFrame.cpp + ImageVolume.cpp + MultiframeConverter.cpp + Helper.cpp + SegmentationImageConverter.cpp + ParametricMapConverter.cpp + JSONMetaInformationHandlerBase.cpp + JSONParametricMapMetaInformationHandler.cpp + JSONSegmentationMetaInformationHandler.cpp + SegmentAttributes.cpp + ) + +set(ADDITIONAL_HDRS + ${INCLUDE_DIR}/DICOMFrame.h + ${INCLUDE_DIR}/ImageVolume.h + ${INCLUDE_DIR}/SegmentVolume.h + ${INCLUDE_DIR}/MultiframeObject.h + ${INCLUDE_DIR}/ParametricMapObject.h + ${INCLUDE_DIR}/SegmentationImageObject.h + ${INCLUDE_DIR}/ImageVolumeGeometry.h + ${INCLUDE_DIR}/Exceptions.h + ${INCLUDE_DIR}/framesorter.h + ${INCLUDE_DIR}/preproc.h + ${INCLUDE_DIR}/QIICRConstants.h + ${INCLUDE_DIR}/QIICRUIDs.h + ) + +set(ADDITIONAL_SRCS + DICOMFrame.cpp + ImageVolume.cpp + SegmentVolume.cpp + MultiframeObject.cpp + ParametricMapObject.cpp + SegmentationImageObject.cpp + ImageVolumeGeometry.cpp + ) + +if(DCMQI_BUILTIN_JSONCPP) + list(APPEND ADDITIONAL_SRCS + ${DCMQI_SOURCE_DIR}/jsoncpp/jsoncpp.cpp + ) + set(JsonCpp_INCLUDE_DIR ${DCMQI_SOURCE_DIR}/jsoncpp) +endif() + +set(lib_name dcmqi) + +add_library(${lib_name} STATIC + ${HDRS} + ${SRCS} + ${ADDITIONAL_HDRS} + ${ADDITIONAL_SRCS} + ) + +if(DCMQI_LIBRARY_PROPERTIES) + set_target_properties(${lib_name} PROPERTIES ${DCMQI_LIBRARY_PROPERTIES}) +endif() + +set_property(GLOBAL APPEND PROPERTY ${CMAKE_PROJECT_NAME}_TARGETS ${lib_name}) + +set(${lib_name}_INCLUDE_DIRS + ${DCMTK_INCLUDE_DIRS} + ${ITK_INCLUDE_DIRS} + ${DCMQI_SOURCE_DIR}/include + ${DCMQI_BINARY_DIR}/include + ${JsonCpp_INCLUDE_DIR} + ) +if(ITK_INSTALL_PREFIX) + list(APPEND ${lib_name}_INCLUDE_DIRS + ${ITK_INSTALL_PREFIX}/include/vxl/core + ${ITK_INSTALL_PREFIX}/include/vxl/vcl + ) +endif() +target_include_directories(${lib_name} PUBLIC ${${lib_name}_INCLUDE_DIRS}) + +target_link_libraries(${lib_name} PUBLIC + ${DCMTK_LIBRARIES} + ${ITK_LIBRARIES} + $<$>:${JsonCpp_LIBRARY}> ) \ No newline at end of file From 51d45c7b649ad919f88cc47a51520ac25d438270 Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Thu, 29 Jun 2017 14:23:40 -0400 Subject: [PATCH 103/116] ENH: added first step for segmentation dcm to itk --- include/dcmqi/ImageVolumeGeometry.h | 3 +- include/dcmqi/SegmentationImageObject.h | 45 ++++- libsrc/ParametricMapObject.cpp | 10 +- libsrc/SegmentationImageObject.cpp | 258 ++++++++++++++++++++++++ 4 files changed, 309 insertions(+), 7 deletions(-) diff --git a/include/dcmqi/ImageVolumeGeometry.h b/include/dcmqi/ImageVolumeGeometry.h index 92d2a507..0b1b3da5 100644 --- a/include/dcmqi/ImageVolumeGeometry.h +++ b/include/dcmqi/ImageVolumeGeometry.h @@ -16,8 +16,9 @@ class ImageVolumeGeometry { friend class MultiframeObject; friend class ParametricMapObject; + friend class SegmentationImageObject; -public: + public: typedef itk::Vector DoubleVectorType; typedef itk::Size<3> SizeType; diff --git a/include/dcmqi/SegmentationImageObject.h b/include/dcmqi/SegmentationImageObject.h index 74b7e8d8..326a81e9 100644 --- a/include/dcmqi/SegmentationImageObject.h +++ b/include/dcmqi/SegmentationImageObject.h @@ -5,11 +5,54 @@ #ifndef DCMQI_SEGMENTATIONIMAGEOBJECT_H #define DCMQI_SEGMENTATIONIMAGEOBJECT_H - +// DCMQI includes #include "MultiframeObject.h" +#include "Helper.h" +#include "SegmentAttributes.h" + +// DCMTK includes +#include +#include +#include +#include +#include +#include + +// ITK includes +#include + class SegmentationImageObject : public MultiframeObject { +public: + + typedef short ShortPixelType; + typedef itk::Image ShortImageType; + + SegmentationImageObject(){ + segmentation = NULL; + } + + int initializeFromDICOM(DcmDataset* sourceDataset); + + int initializeMetaDataFromDICOM(DcmDataset*); + +protected: + // Data containers specific to this object + ShortImageType::Pointer itkImage; + + // ITK images corresponding to the individual segments + map segment2image; + + dcmqi::SegmentAttributes* createAndGetNewSegment(unsigned labelID); + + // vector contains one item per input itkImageData label + // each item is a map from labelID to segment attributes + vector > segmentsAttributesMappingList; + + private: + DcmSegmentation* segmentation; + }; diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index d698aeae..1296ff02 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -336,9 +336,9 @@ int ParametricMapObject::initializeFromDICOM(DcmDataset * sourceDataset) { throw -1; } - DPMParametricMapIOD* pMapDoc = *OFget(&result); + parametricMap = *OFget(&result); - initializeVolumeGeometryFromDICOM(pMapDoc, sourceDataset); + initializeVolumeGeometryFromDICOM(parametricMap, sourceDataset); // Initialize the image itkImage = volumeGeometry.getITKRepresentation(); @@ -346,14 +346,14 @@ int ParametricMapObject::initializeFromDICOM(DcmDataset * sourceDataset) { itkImage->Allocate(); itkImage->FillBuffer(0); - DPMParametricMapIOD::FramesType obj = pMapDoc->getFrames(); + DPMParametricMapIOD::FramesType obj = parametricMap->getFrames(); if (OFCondition* pCondition = OFget(&obj)) { throw -1; } DPMParametricMapIOD::Frames frames = *OFget >(&obj); - FGInterface &fgInterface = pMapDoc->getFunctionalGroups(); + FGInterface &fgInterface = parametricMap->getFunctionalGroups(); for(int frameId=0;frameId(); + + itkImage->Allocate(); + itkImage->FillBuffer(0); + + // Iterate over frames, find the matching slice for each of the frames based on + // ImagePositionPatient, set non-zero pixels to the segment number. Notify + // about pixels that are initialized more than once. + + FGInterface &fgInterface = segmentation->getFunctionalGroups(); + + DcmIODTypes::Frame *unpackedFrame = NULL; + + for(size_t frameId=0;frameIdgetFrame(frameId); + bool isPerFrame; + + FGPlanePosPatient *planposfg = + OFstatic_cast(FGPlanePosPatient*,fgInterface.get(frameId, DcmFGTypes::EFG_PLANEPOSPATIENT, isPerFrame)); + assert(planposfg); + +#ifndef NDEBUG + FGFrameContent *fracon = + OFstatic_cast(FGFrameContent*,fgInterface.get(frameId, DcmFGTypes::EFG_FRAMECONTENT, isPerFrame)); + assert(fracon); +#endif + + FGSegmentation *fgseg = + OFstatic_cast(FGSegmentation*,fgInterface.get(frameId, DcmFGTypes::EFG_SEGMENTATION, isPerFrame)); + assert(fgseg); + + Uint16 segmentId = -1; + if(fgseg->getReferencedSegmentNumber(segmentId).bad()){ + cerr << "Failed to get seg number!"; + throw -1; + } + + // WARNING: this is needed only for David's example, which numbers + // (incorrectly!) segments starting from 0, should start from 1 + if(segmentId == 0){ + cerr << "Segment numbers should start from 1!" << endl; + throw -1; + } + + if(segment2image.find(segmentId) == segment2image.end()){ + typedef itk::ImageDuplicator DuplicatorType; + DuplicatorType::Pointer dup = DuplicatorType::New(); + dup->SetInputImage(itkImage); + dup->Update(); + ShortImageType::Pointer newSegmentImage = dup->GetOutput(); + newSegmentImage->FillBuffer(0); + segment2image[segmentId] = newSegmentImage; + } + + // populate meta information needed for Slicer ScalarVolumeNode initialization + // (for example) + { + // NOTE: according to the standard, segment numbering should start from 1, + // not clear if this is intentional behavior or a bug in DCMTK expecting + // it to start from 0 + + DcmSegment *segment = segmentation->getSegment(segmentId); + if (segment == NULL) { + cerr << "Failed to get segment for segment ID " << segmentId << endl; + continue; + } + + // get CIELab color for the segment + Uint16 ciedcm[3]; + unsigned cielabScaled[3]; + float cielab[3], ciexyz[3]; + unsigned rgb[3]; + if (segment->getRecommendedDisplayCIELabValue( + ciedcm[0], ciedcm[1], ciedcm[2] + ).bad()) { + // NOTE: if the call above fails, it overwrites the values anyway, + // not sure if this is a dcmtk bug or not + ciedcm[0] = 43803; + ciedcm[1] = 26565; + ciedcm[2] = 37722; + cerr << "Failed to get CIELab values - initializing to default " << + ciedcm[0] << "," << ciedcm[1] << "," << ciedcm[2] << endl; + } + cielabScaled[0] = unsigned(ciedcm[0]); + cielabScaled[1] = unsigned(ciedcm[1]); + cielabScaled[2] = unsigned(ciedcm[2]); + + Helper::getCIELabFromIntegerScaledCIELab(&cielabScaled[0], &cielab[0]); + Helper::getCIEXYZFromCIELab(&cielab[0], &ciexyz[0]); + Helper::getRGBFromCIEXYZ(&ciexyz[0], &rgb[0]); + + // TODO: factor out SegmentAttributes + SegmentAttributes *segmentAttributes = createAndGetNewSegment(segmentId); + + if (segmentAttributes) { + segmentAttributes->setLabelID(segmentId); + DcmSegTypes::E_SegmentAlgoType algorithmType = segment->getSegmentAlgorithmType(); + string readableAlgorithmType = DcmSegTypes::algoType2OFString(algorithmType).c_str(); + segmentAttributes->setSegmentAlgorithmType(readableAlgorithmType); + + if (algorithmType == DcmSegTypes::SAT_UNKNOWN) { + cerr << "AlgorithmType is not valid with value " << readableAlgorithmType << endl; + throw -1; + } + if (algorithmType != DcmSegTypes::SAT_MANUAL) { + OFString segmentAlgorithmName; + segment->getSegmentAlgorithmName(segmentAlgorithmName); + if (segmentAlgorithmName.length() > 0) + segmentAttributes->setSegmentAlgorithmName(segmentAlgorithmName.c_str()); + } + + OFString segmentDescription; + segment->getSegmentDescription(segmentDescription); + segmentAttributes->setSegmentDescription(segmentDescription.c_str()); + + segmentAttributes->setRecommendedDisplayRGBValue(rgb[0], rgb[1], rgb[2]); + segmentAttributes->setSegmentedPropertyCategoryCodeSequence(segment->getSegmentedPropertyCategoryCode()); + segmentAttributes->setSegmentedPropertyTypeCodeSequence(segment->getSegmentedPropertyTypeCode()); + + if (segment->getSegmentedPropertyTypeModifierCode().size() > 0) { + segmentAttributes->setSegmentedPropertyTypeModifierCodeSequence( + segment->getSegmentedPropertyTypeModifierCode()[0]); + } + + GeneralAnatomyMacro &anatomyMacro = segment->getGeneralAnatomyCode(); + CodeSequenceMacro &anatomicRegionSequence = anatomyMacro.getAnatomicRegion(); + if (anatomicRegionSequence.check(true).good()) { + segmentAttributes->setAnatomicRegionSequence(anatomyMacro.getAnatomicRegion()); + } + if (anatomyMacro.getAnatomicRegionModifier().size() > 0) { + segmentAttributes->setAnatomicRegionModifierSequence(anatomyMacro.getAnatomicRegionModifier()[0]); + } + } + } + + // get string representation of the frame origin + ShortImageType::PointType frameOriginPoint; + ShortImageType::IndexType frameOriginIndex; + for(int j=0;j<3;j++){ + OFString planposStr; + if(planposfg->getImagePositionPatient(planposStr, j).good()){ + frameOriginPoint[j] = atof(planposStr.c_str()); + } + } + + if(!segment2image[segmentId]->TransformPhysicalPointToIndex(frameOriginPoint, frameOriginIndex)){ + cerr << "ERROR: Frame " << frameId << " origin " << frameOriginPoint << + " is outside image geometry!" << frameOriginIndex << endl; + cerr << "Image size: " << segment2image[segmentId]->GetBufferedRegion().GetSize() << endl; + throw -1; + } + + unsigned slice = frameOriginIndex[2]; + + if(segmentation->getSegmentationType() == DcmSegTypes::ST_BINARY) + unpackedFrame = DcmSegUtils::unpackBinaryFrame(frame, + volumeGeometry.extent[1], // Rows + volumeGeometry.extent[0]); // Cols + else + unpackedFrame = new DcmIODTypes::Frame(*frame); + + // initialize slice with the frame content + for(unsigned row=0;rowpixData[bitCnt]; + + if(pixel!=0){ + ShortImageType::IndexType index; + index[0] = col; + index[1] = row; + index[2] = slice; + segment2image[segmentId]->SetPixel(index, segmentId); + } + } + } + + if(unpackedFrame != NULL) + delete unpackedFrame; + } + + initializeMetaDataFromDICOM(sourceDataset); + + return EXIT_SUCCESS; +} + +int SegmentationImageObject::initializeMetaDataFromDICOM(DcmDataset *segDataset) { + + OFString temp; + segmentation->getSeries().getSeriesDescription(temp); + metaDataJson["SeriesDescription"] = temp.c_str(); + + segmentation->getSeries().getSeriesNumber(temp); + metaDataJson["SeriesNumber"] = temp.c_str(); + + segDataset->findAndGetOFString(DCM_InstanceNumber, temp); + metaDataJson["InstanceNumber"] = temp.c_str(); + + segmentation->getSeries().getBodyPartExamined(temp); + metaDataJson["BodyPartExamined"] = temp.c_str(); + + segmentation->getContentIdentification().getContentCreatorName(temp); + metaDataJson["ContentCreatorName"] = temp.c_str(); + + segDataset->findAndGetOFString(DCM_ClinicalTrialTimePointID, temp); + metaDataJson["ClinicalTrialTimePointID"] = temp.c_str(); + + segDataset->findAndGetOFString(DCM_ClinicalTrialSeriesID, temp); + metaDataJson["ClinicalTrialSeriesID"] = temp.c_str(); + + segDataset->findAndGetOFString(DCM_ClinicalTrialCoordinatingCenterName, temp); + if (temp.size()) + metaDataJson["ClinicalTrialCoordinatingCenterName"] = temp.c_str(); + + return EXIT_SUCCESS; +} + +dcmqi::SegmentAttributes *SegmentationImageObject::createAndGetNewSegment(unsigned labelID) { + using namespace dcmqi; + for (vector >::const_iterator vIt = segmentsAttributesMappingList.begin(); + vIt != segmentsAttributesMappingList.end(); ++vIt) { + for(map::const_iterator mIt = vIt->begin();mIt!=vIt->end();++mIt){ + SegmentAttributes *segmentAttributes = mIt->second; + if (segmentAttributes->getLabelID() == labelID) + return NULL; + } + } + + SegmentAttributes *segment = new SegmentAttributes(labelID); + map tempMap; + tempMap[labelID] = segment; + segmentsAttributesMappingList.push_back(tempMap); + return segment; +} \ No newline at end of file From ae28c47dd82697650258b6e9ab90692185f90843 Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Thu, 29 Jun 2017 17:37:33 -0400 Subject: [PATCH 104/116] ENH: reading segmentAttributes into metadata * added replacement method to SegmentationImageConverter --- apps/seg/segimage2itkimage.cxx | 2 +- include/dcmqi/SegmentationImageConverter.h | 2 + include/dcmqi/SegmentationImageObject.h | 11 +- libsrc/SegmentationImageConverter.cpp | 16 ++ libsrc/SegmentationImageObject.cpp | 210 +++++++++++---------- 5 files changed, 142 insertions(+), 99 deletions(-) diff --git a/apps/seg/segimage2itkimage.cxx b/apps/seg/segimage2itkimage.cxx index 0545778a..82721a36 100644 --- a/apps/seg/segimage2itkimage.cxx +++ b/apps/seg/segimage2itkimage.cxx @@ -24,7 +24,7 @@ int main(int argc, char *argv[]) CHECK_COND(sliceFF.loadFile(inputSEGFileName.c_str())); DcmDataset* dataset = sliceFF.getDataset(); - pair , string> result = dcmqi::SegmentationImageConverter::dcmSegmentation2itkimage(dataset); + pair , string> result = dcmqi::dcmSegmentation2itkimageReplacement(dataset); string outputPrefix = prefix.empty() ? "" : prefix + "-"; diff --git a/include/dcmqi/SegmentationImageConverter.h b/include/dcmqi/SegmentationImageConverter.h index 6158fc1f..00d18aee 100644 --- a/include/dcmqi/SegmentationImageConverter.h +++ b/include/dcmqi/SegmentationImageConverter.h @@ -32,6 +32,8 @@ typedef itk::LabelImageToLabelMapFilter LabelToLabelMapFilterTyp namespace dcmqi { + pair , string> dcmSegmentation2itkimageReplacement(DcmDataset *segDataset); + class SegmentationImageConverter : public MultiframeConverter { public: diff --git a/include/dcmqi/SegmentationImageObject.h b/include/dcmqi/SegmentationImageObject.h index 326a81e9..51ac5daf 100644 --- a/include/dcmqi/SegmentationImageObject.h +++ b/include/dcmqi/SegmentationImageObject.h @@ -35,7 +35,10 @@ class SegmentationImageObject : public MultiframeObject { int initializeFromDICOM(DcmDataset* sourceDataset); - int initializeMetaDataFromDICOM(DcmDataset*); + map getITKRepresentation() const { + // TODO: think about naming + return segment2image; + } protected: // Data containers specific to this object @@ -44,13 +47,15 @@ class SegmentationImageObject : public MultiframeObject { // ITK images corresponding to the individual segments map segment2image; - dcmqi::SegmentAttributes* createAndGetNewSegment(unsigned labelID); + int initializeMetaDataFromDICOM(DcmDataset*); + + Json::Value getSegmentAttributesMetadata(); // vector contains one item per input itkImageData label // each item is a map from labelID to segment attributes vector > segmentsAttributesMappingList; - private: +private: DcmSegmentation* segmentation; }; diff --git a/libsrc/SegmentationImageConverter.cpp b/libsrc/SegmentationImageConverter.cpp index f1e0fe1a..3756d6b4 100644 --- a/libsrc/SegmentationImageConverter.cpp +++ b/libsrc/SegmentationImageConverter.cpp @@ -1,10 +1,26 @@ // DCMQI includes #include "dcmqi/SegmentationImageConverter.h" +#include "dcmqi/SegmentationImageObject.h" +using namespace std; namespace dcmqi { + pair , string> dcmSegmentation2itkimageReplacement(DcmDataset *segDataset) { + + SegmentationImageObject seg; + seg.initializeFromDICOM(segDataset); + + Json::StyledWriter styledWriter; + std::stringstream ss; + + ss << styledWriter.write(seg.getMetaDataJson()); + + return pair , string>(seg.getITKRepresentation(), + ss.str()); + }; + DcmDataset* SegmentationImageConverter::itkimage2dcmSegmentation(vector dcmDatasets, vector segmentations, const string &metaData, diff --git a/libsrc/SegmentationImageObject.cpp b/libsrc/SegmentationImageObject.cpp index 89e0505d..46b316e3 100644 --- a/libsrc/SegmentationImageObject.cpp +++ b/libsrc/SegmentationImageObject.cpp @@ -14,7 +14,6 @@ int SegmentationImageObject::initializeFromDICOM(DcmDataset* sourceDataset) { OFLogger dcemfinfLogger = OFLog::getLogger("qiicr.apps"); dcemfinfLogger.setLogLevel(dcmtk::log4cplus::OFF_LOG_LEVEL); - DcmSegmentation *segmentation = NULL; OFCondition cond = DcmSegmentation::loadDataset(*sourceDataset, segmentation); if(!segmentation){ cerr << "Failed to load seg! " << cond.text() << endl; @@ -80,87 +79,6 @@ int SegmentationImageObject::initializeFromDICOM(DcmDataset* sourceDataset) { segment2image[segmentId] = newSegmentImage; } - // populate meta information needed for Slicer ScalarVolumeNode initialization - // (for example) - { - // NOTE: according to the standard, segment numbering should start from 1, - // not clear if this is intentional behavior or a bug in DCMTK expecting - // it to start from 0 - - DcmSegment *segment = segmentation->getSegment(segmentId); - if (segment == NULL) { - cerr << "Failed to get segment for segment ID " << segmentId << endl; - continue; - } - - // get CIELab color for the segment - Uint16 ciedcm[3]; - unsigned cielabScaled[3]; - float cielab[3], ciexyz[3]; - unsigned rgb[3]; - if (segment->getRecommendedDisplayCIELabValue( - ciedcm[0], ciedcm[1], ciedcm[2] - ).bad()) { - // NOTE: if the call above fails, it overwrites the values anyway, - // not sure if this is a dcmtk bug or not - ciedcm[0] = 43803; - ciedcm[1] = 26565; - ciedcm[2] = 37722; - cerr << "Failed to get CIELab values - initializing to default " << - ciedcm[0] << "," << ciedcm[1] << "," << ciedcm[2] << endl; - } - cielabScaled[0] = unsigned(ciedcm[0]); - cielabScaled[1] = unsigned(ciedcm[1]); - cielabScaled[2] = unsigned(ciedcm[2]); - - Helper::getCIELabFromIntegerScaledCIELab(&cielabScaled[0], &cielab[0]); - Helper::getCIEXYZFromCIELab(&cielab[0], &ciexyz[0]); - Helper::getRGBFromCIEXYZ(&ciexyz[0], &rgb[0]); - - // TODO: factor out SegmentAttributes - SegmentAttributes *segmentAttributes = createAndGetNewSegment(segmentId); - - if (segmentAttributes) { - segmentAttributes->setLabelID(segmentId); - DcmSegTypes::E_SegmentAlgoType algorithmType = segment->getSegmentAlgorithmType(); - string readableAlgorithmType = DcmSegTypes::algoType2OFString(algorithmType).c_str(); - segmentAttributes->setSegmentAlgorithmType(readableAlgorithmType); - - if (algorithmType == DcmSegTypes::SAT_UNKNOWN) { - cerr << "AlgorithmType is not valid with value " << readableAlgorithmType << endl; - throw -1; - } - if (algorithmType != DcmSegTypes::SAT_MANUAL) { - OFString segmentAlgorithmName; - segment->getSegmentAlgorithmName(segmentAlgorithmName); - if (segmentAlgorithmName.length() > 0) - segmentAttributes->setSegmentAlgorithmName(segmentAlgorithmName.c_str()); - } - - OFString segmentDescription; - segment->getSegmentDescription(segmentDescription); - segmentAttributes->setSegmentDescription(segmentDescription.c_str()); - - segmentAttributes->setRecommendedDisplayRGBValue(rgb[0], rgb[1], rgb[2]); - segmentAttributes->setSegmentedPropertyCategoryCodeSequence(segment->getSegmentedPropertyCategoryCode()); - segmentAttributes->setSegmentedPropertyTypeCodeSequence(segment->getSegmentedPropertyTypeCode()); - - if (segment->getSegmentedPropertyTypeModifierCode().size() > 0) { - segmentAttributes->setSegmentedPropertyTypeModifierCodeSequence( - segment->getSegmentedPropertyTypeModifierCode()[0]); - } - - GeneralAnatomyMacro &anatomyMacro = segment->getGeneralAnatomyCode(); - CodeSequenceMacro &anatomicRegionSequence = anatomyMacro.getAnatomicRegion(); - if (anatomicRegionSequence.check(true).good()) { - segmentAttributes->setAnatomicRegionSequence(anatomyMacro.getAnatomicRegion()); - } - if (anatomyMacro.getAnatomicRegionModifier().size() > 0) { - segmentAttributes->setAnatomicRegionModifierSequence(anatomyMacro.getAnatomicRegionModifier()[0]); - } - } - } - // get string representation of the frame origin ShortImageType::PointType frameOriginPoint; ShortImageType::IndexType frameOriginIndex; @@ -213,6 +131,8 @@ int SegmentationImageObject::initializeFromDICOM(DcmDataset* sourceDataset) { return EXIT_SUCCESS; } + + int SegmentationImageObject::initializeMetaDataFromDICOM(DcmDataset *segDataset) { OFString temp; @@ -241,23 +161,123 @@ int SegmentationImageObject::initializeMetaDataFromDICOM(DcmDataset *segDataset) if (temp.size()) metaDataJson["ClinicalTrialCoordinatingCenterName"] = temp.c_str(); + metaDataJson["segmentAttributes"] = getSegmentAttributesMetadata(); + return EXIT_SUCCESS; } -dcmqi::SegmentAttributes *SegmentationImageObject::createAndGetNewSegment(unsigned labelID) { +Json::Value SegmentationImageObject::getSegmentAttributesMetadata() { + using namespace dcmqi; - for (vector >::const_iterator vIt = segmentsAttributesMappingList.begin(); - vIt != segmentsAttributesMappingList.end(); ++vIt) { - for(map::const_iterator mIt = vIt->begin();mIt!=vIt->end();++mIt){ - SegmentAttributes *segmentAttributes = mIt->second; - if (segmentAttributes->getLabelID() == labelID) - return NULL; + + FGInterface &fgInterface = segmentation->getFunctionalGroups(); + + Json::Value values(Json::arrayValue); + + for(size_t frameId=0;frameIdgetFrame(frameId); + bool isPerFrame; + + FGSegmentation *fgseg = + OFstatic_cast(FGSegmentation*,fgInterface.get(frameId, DcmFGTypes::EFG_SEGMENTATION, isPerFrame)); + assert(fgseg); + + Uint16 segmentId = -1; + if(fgseg->getReferencedSegmentNumber(segmentId).bad()){ + cerr << "Failed to get seg number!"; + throw -1; } - } - SegmentAttributes *segment = new SegmentAttributes(labelID); - map tempMap; - tempMap[labelID] = segment; - segmentsAttributesMappingList.push_back(tempMap); - return segment; + // populate meta information needed for Slicer ScalarVolumeNode initialization + // (for example) + + // NOTE: according to the standard, segment numbering should start from 1, + // not clear if this is intentional behavior or a bug in DCMTK expecting + // it to start from 0 + DcmSegment *segment = segmentation->getSegment(segmentId); + if (segment == NULL) { + cerr << "Failed to get segment for segment ID " << segmentId << endl; + continue; + } + + // get CIELab color for the segment + Uint16 ciedcm[3]; + unsigned cielabScaled[3]; + float cielab[3], ciexyz[3]; + unsigned rgb[3]; + if (segment->getRecommendedDisplayCIELabValue( + ciedcm[0], ciedcm[1], ciedcm[2] + ).bad()) { + // NOTE: if the call above fails, it overwrites the values anyway, + // not sure if this is a dcmtk bug or not + ciedcm[0] = 43803; + ciedcm[1] = 26565; + ciedcm[2] = 37722; + cerr << "Failed to get CIELab values - initializing to default " << + ciedcm[0] << "," << ciedcm[1] << "," << ciedcm[2] << endl; + } + cielabScaled[0] = unsigned(ciedcm[0]); + cielabScaled[1] = unsigned(ciedcm[1]); + cielabScaled[2] = unsigned(ciedcm[2]); + + Helper::getCIELabFromIntegerScaledCIELab(&cielabScaled[0], &cielab[0]); + Helper::getCIEXYZFromCIELab(&cielab[0], &ciexyz[0]); + Helper::getRGBFromCIEXYZ(&ciexyz[0], &rgb[0]); + + Json::Value segmentEntry; + + OFString temp; + + segmentEntry["labelID"] = segmentId; + + segment->getSegmentDescription(temp); + segmentEntry["SegmentDescription"] = temp.c_str(); + + + DcmSegTypes::E_SegmentAlgoType algorithmType = segment->getSegmentAlgorithmType(); + string readableAlgorithmType = DcmSegTypes::algoType2OFString(algorithmType).c_str(); + segmentEntry["SegmentAlgorithmType"] = readableAlgorithmType; + + if (algorithmType == DcmSegTypes::SAT_UNKNOWN) { + cerr << "AlgorithmType is not valid with value " << readableAlgorithmType << endl; + throw -1; + } + if (algorithmType != DcmSegTypes::SAT_MANUAL) { + segment->getSegmentAlgorithmName(temp); + if (temp.length() > 0) + segmentEntry["SegmentAlgorithmName"] = temp.c_str(); + } + + Json::Value rgbArray(Json::arrayValue); + rgbArray.append(rgb[0]); + rgbArray.append(rgb[1]); + rgbArray.append(rgb[2]); + segmentEntry["recommendedDisplayRGBValue"] = rgbArray; + + segmentEntry["SegmentedPropertyCategoryCodeSequence"] = + Helper::codeSequence2Json(segment->getSegmentedPropertyCategoryCode()); + + segmentEntry["SegmentedPropertyTypeCodeSequence"] = + Helper::codeSequence2Json(segment->getSegmentedPropertyTypeCode()); + + if (segment->getSegmentedPropertyTypeModifierCode().size() > 0) { + segmentEntry["SegmentedPropertyTypeModifierCodeSequence"] = + Helper::codeSequence2Json(*(segment->getSegmentedPropertyTypeModifierCode())[0]); + } + + GeneralAnatomyMacro &anatomyMacro = segment->getGeneralAnatomyCode(); + CodeSequenceMacro &anatomicRegionSequence = anatomyMacro.getAnatomicRegion(); + if (anatomicRegionSequence.check(true).good()) { + segmentEntry["AnatomicRegionSequence"] = Helper::codeSequence2Json(anatomyMacro.getAnatomicRegion()); + } + if (anatomyMacro.getAnatomicRegionModifier().size() > 0) { + segmentEntry["AnatomicRegionModifierSequence"] = + Helper::codeSequence2Json(*(anatomyMacro.getAnatomicRegionModifier()[0])); + } + + Json::Value innerList(Json::arrayValue); + innerList.append(segmentEntry); + values.append(innerList); + } + return values; } \ No newline at end of file From 672ac9fa6bdd935bd12969fbe2089e5d1884217f Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Fri, 30 Jun 2017 11:57:30 -0400 Subject: [PATCH 105/116] ENH: ctests for dcm segmentation 2 itk succeed * TODO: refactor even more and generalize --- include/dcmqi/MultiframeObject.h | 17 ++++++++++++----- libsrc/SegmentationImageObject.cpp | 10 +++++++--- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/include/dcmqi/MultiframeObject.h b/include/dcmqi/MultiframeObject.h index 97a9d345..da2cea0e 100644 --- a/include/dcmqi/MultiframeObject.h +++ b/include/dcmqi/MultiframeObject.h @@ -84,7 +84,7 @@ class MultiframeObject { int initializeVolumeGeometryFromITK(DummyImageType::Pointer); template - int initializeVolumeGeometryFromDICOM(T iodImage, DcmDataset *dataset) { + int initializeVolumeGeometryFromDICOM(T iodImage, DcmDataset *dataset, bool useComputedVolumeExtent=false) { SpacingType spacing; PointType origin; DirectionType directions; @@ -101,9 +101,9 @@ class MultiframeObject { double computedSliceSpacing, computedVolumeExtent; vnl_vector sliceDirection(3); - sliceDirection[0] = *directions[0]; - sliceDirection[1] = *directions[1]; - sliceDirection[2] = *directions[2]; + sliceDirection[0] = directions[0][2]; + sliceDirection[1] = directions[1][2]; + sliceDirection[2] = directions[2][2]; if (computeVolumeExtent(fgInterface, sliceDirection, origin, computedSliceSpacing, computedVolumeExtent)) { cerr << "Failed to compute origin and/or slice spacing!" << endl; throw -1; @@ -130,7 +130,14 @@ class MultiframeObject { if(dataset->findAndGetOFString(DCM_Columns, str).good()) extent[0] = atoi(str.c_str()); } - extent[2] = fgInterface.getNumberOfFrames(); + + if (useComputedVolumeExtent) { + extent[2] = ceil(computedVolumeExtent/spacing[2])+1; + } else { + extent[2] = fgInterface.getNumberOfFrames(); + } + + cout << extent << endl; volumeGeometry.setSpacing(spacing); volumeGeometry.setOrigin(origin); diff --git a/libsrc/SegmentationImageObject.cpp b/libsrc/SegmentationImageObject.cpp index 46b316e3..7e53bb29 100644 --- a/libsrc/SegmentationImageObject.cpp +++ b/libsrc/SegmentationImageObject.cpp @@ -20,9 +20,7 @@ int SegmentationImageObject::initializeFromDICOM(DcmDataset* sourceDataset) { throw -1; } - initializeVolumeGeometryFromDICOM(segmentation, sourceDataset); - -// imageSize[2] = ceil(computedVolumeExtent/imageSpacing[2])+1; + initializeVolumeGeometryFromDICOM(segmentation, sourceDataset, true); // Initialize the image itkImage = volumeGeometry.getITKRepresentation(); @@ -173,6 +171,7 @@ Json::Value SegmentationImageObject::getSegmentAttributesMetadata() { FGInterface &fgInterface = segmentation->getFunctionalGroups(); Json::Value values(Json::arrayValue); + vector processedSegmentIDs; for(size_t frameId=0;frameIdgetFrame(frameId); @@ -200,6 +199,11 @@ Json::Value SegmentationImageObject::getSegmentAttributesMetadata() { continue; } + if(std::find(processedSegmentIDs.begin(), processedSegmentIDs.end(), segmentId) != processedSegmentIDs.end()) { + continue; + } + processedSegmentIDs.push_back(segmentId); + // get CIELab color for the segment Uint16 ciedcm[3]; unsigned cielabScaled[3]; From 7c6ec675f2e0936787f8e508a30151b8f820dbc0 Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Fri, 30 Jun 2017 15:17:41 -0400 Subject: [PATCH 106/116] ENH: extraction of more methods --- include/dcmqi/ImageVolumeGeometry.h | 3 + include/dcmqi/SegmentationImageObject.h | 17 +-- libsrc/ParametricMapObject.cpp | 3 - libsrc/SegmentationImageObject.cpp | 136 ++++++++++++------------ 4 files changed, 81 insertions(+), 78 deletions(-) diff --git a/include/dcmqi/ImageVolumeGeometry.h b/include/dcmqi/ImageVolumeGeometry.h index 0b1b3da5..dafa94b5 100644 --- a/include/dcmqi/ImageVolumeGeometry.h +++ b/include/dcmqi/ImageVolumeGeometry.h @@ -79,6 +79,9 @@ class ImageVolumeGeometry { image->SetDirection(direction); image->SetSpacing(spacing); + image->Allocate(); + image->FillBuffer(0); + return image; } diff --git a/include/dcmqi/SegmentationImageObject.h b/include/dcmqi/SegmentationImageObject.h index 51ac5daf..29487529 100644 --- a/include/dcmqi/SegmentationImageObject.h +++ b/include/dcmqi/SegmentationImageObject.h @@ -8,7 +8,6 @@ // DCMQI includes #include "MultiframeObject.h" #include "Helper.h" -#include "SegmentAttributes.h" // DCMTK includes #include @@ -47,17 +46,19 @@ class SegmentationImageObject : public MultiframeObject { // ITK images corresponding to the individual segments map segment2image; - int initializeMetaDataFromDICOM(DcmDataset*); + DcmSegmentation* segmentation; - Json::Value getSegmentAttributesMetadata(); + int iterateOverFramesAndMatchSlices(); - // vector contains one item per input itkImageData label - // each item is a map from labelID to segment attributes - vector > segmentsAttributesMappingList; + int unpackFrameAndWriteSegmentImage(const size_t& frameId, const Uint16& segmentId, const unsigned int& slice); -private: - DcmSegmentation* segmentation; + int initializeMetaDataFromDICOM(DcmDataset*); + + int createNewSegmentImage(Uint16 segmentId); + + Json::Value getSegmentAttributesMetadata(); + Uint16 getSegmentId(FGInterface &fgInterface, size_t frameId) const; }; diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 1296ff02..85e0aa99 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -343,9 +343,6 @@ int ParametricMapObject::initializeFromDICOM(DcmDataset * sourceDataset) { // Initialize the image itkImage = volumeGeometry.getITKRepresentation(); - itkImage->Allocate(); - itkImage->FillBuffer(0); - DPMParametricMapIOD::FramesType obj = parametricMap->getFrames(); if (OFCondition* pCondition = OFget(&obj)) { throw -1; diff --git a/libsrc/SegmentationImageObject.cpp b/libsrc/SegmentationImageObject.cpp index 7e53bb29..3980665e 100644 --- a/libsrc/SegmentationImageObject.cpp +++ b/libsrc/SegmentationImageObject.cpp @@ -21,44 +21,30 @@ int SegmentationImageObject::initializeFromDICOM(DcmDataset* sourceDataset) { } initializeVolumeGeometryFromDICOM(segmentation, sourceDataset, true); - - // Initialize the image itkImage = volumeGeometry.getITKRepresentation(); + iterateOverFramesAndMatchSlices(); + initializeMetaDataFromDICOM(sourceDataset); - itkImage->Allocate(); - itkImage->FillBuffer(0); + return EXIT_SUCCESS; +} +int SegmentationImageObject::iterateOverFramesAndMatchSlices() { // Iterate over frames, find the matching slice for each of the frames based on // ImagePositionPatient, set non-zero pixels to the segment number. Notify // about pixels that are initialized more than once. FGInterface &fgInterface = segmentation->getFunctionalGroups(); - DcmIODTypes::Frame *unpackedFrame = NULL; - for(size_t frameId=0;frameIdgetFrame(frameId); bool isPerFrame; - FGPlanePosPatient *planposfg = - OFstatic_cast(FGPlanePosPatient*,fgInterface.get(frameId, DcmFGTypes::EFG_PLANEPOSPATIENT, isPerFrame)); - assert(planposfg); - #ifndef NDEBUG FGFrameContent *fracon = OFstatic_cast(FGFrameContent*,fgInterface.get(frameId, DcmFGTypes::EFG_FRAMECONTENT, isPerFrame)); assert(fracon); #endif - FGSegmentation *fgseg = - OFstatic_cast(FGSegmentation*,fgInterface.get(frameId, DcmFGTypes::EFG_SEGMENTATION, isPerFrame)); - assert(fgseg); - - Uint16 segmentId = -1; - if(fgseg->getReferencedSegmentNumber(segmentId).bad()){ - cerr << "Failed to get seg number!"; - throw -1; - } + Uint16 segmentId = getSegmentId(fgInterface, frameId); // WARNING: this is needed only for David's example, which numbers // (incorrectly!) segments starting from 0, should start from 1 @@ -67,16 +53,14 @@ int SegmentationImageObject::initializeFromDICOM(DcmDataset* sourceDataset) { throw -1; } - if(segment2image.find(segmentId) == segment2image.end()){ - typedef itk::ImageDuplicator DuplicatorType; - DuplicatorType::Pointer dup = DuplicatorType::New(); - dup->SetInputImage(itkImage); - dup->Update(); - ShortImageType::Pointer newSegmentImage = dup->GetOutput(); - newSegmentImage->FillBuffer(0); - segment2image[segmentId] = newSegmentImage; + if(segment2image.find(segmentId) == segment2image.end()) { + createNewSegmentImage(segmentId); } + FGPlanePosPatient *planposfg = + OFstatic_cast(FGPlanePosPatient*,fgInterface.get(frameId, DcmFGTypes::EFG_PLANEPOSPATIENT, isPerFrame)); + assert(planposfg); + // get string representation of the frame origin ShortImageType::PointType frameOriginPoint; ShortImageType::IndexType frameOriginIndex; @@ -93,43 +77,58 @@ int SegmentationImageObject::initializeFromDICOM(DcmDataset* sourceDataset) { cerr << "Image size: " << segment2image[segmentId]->GetBufferedRegion().GetSize() << endl; throw -1; } - unsigned slice = frameOriginIndex[2]; - if(segmentation->getSegmentationType() == DcmSegTypes::ST_BINARY) - unpackedFrame = DcmSegUtils::unpackBinaryFrame(frame, - volumeGeometry.extent[1], // Rows - volumeGeometry.extent[0]); // Cols - else - unpackedFrame = new DcmIODTypes::Frame(*frame); - - // initialize slice with the frame content - for(unsigned row=0;rowpixData[bitCnt]; - - if(pixel!=0){ - ShortImageType::IndexType index; - index[0] = col; - index[1] = row; - index[2] = slice; - segment2image[segmentId]->SetPixel(index, segmentId); - } + unpackFrameAndWriteSegmentImage(frameId, segmentId, slice); + } + return EXIT_SUCCESS; +} + +int SegmentationImageObject::unpackFrameAndWriteSegmentImage(const size_t& frameId, const Uint16& segmentId, + const unsigned int& slice) { + const DcmIODTypes::Frame *frame = segmentation->getFrame(frameId); + + DcmIODTypes::Frame *unpackedFrame = NULL; + + if(segmentation->getSegmentationType() == DcmSegTypes::ST_BINARY) + unpackedFrame = DcmSegUtils::unpackBinaryFrame(frame, + volumeGeometry.extent[1], // Rows + volumeGeometry.extent[0]); // Cols + else + unpackedFrame = new DcmIODTypes::Frame(*frame); + + for(unsigned row=0; row < volumeGeometry.extent[1]; row++){ + for(unsigned col=0; col < volumeGeometry.extent[0]; col++){ + ShortImageType::PixelType pixel; + unsigned bitCnt = row * volumeGeometry.extent[0] + col; + pixel = unpackedFrame->pixData[bitCnt]; + + if(pixel!=0){ + ShortImageType::IndexType index; + index[0] = col; + index[1] = row; + index[2] = slice; + segment2image[segmentId]->SetPixel(index, segmentId); } } - - if(unpackedFrame != NULL) - delete unpackedFrame; } - initializeMetaDataFromDICOM(sourceDataset); + if(unpackedFrame != NULL) + delete unpackedFrame; return EXIT_SUCCESS; } - +int SegmentationImageObject::createNewSegmentImage(Uint16 segmentId) { + typedef itk::ImageDuplicator DuplicatorType; + DuplicatorType::Pointer dup = DuplicatorType::New(); + dup->SetInputImage(itkImage); + dup->Update(); + ShortImageType::Pointer newSegmentImage = dup->GetOutput(); + newSegmentImage->FillBuffer(0); + segment2image[segmentId] = newSegmentImage; + return EXIT_SUCCESS; +} int SegmentationImageObject::initializeMetaDataFromDICOM(DcmDataset *segDataset) { @@ -174,18 +173,7 @@ Json::Value SegmentationImageObject::getSegmentAttributesMetadata() { vector processedSegmentIDs; for(size_t frameId=0;frameIdgetFrame(frameId); - bool isPerFrame; - - FGSegmentation *fgseg = - OFstatic_cast(FGSegmentation*,fgInterface.get(frameId, DcmFGTypes::EFG_SEGMENTATION, isPerFrame)); - assert(fgseg); - - Uint16 segmentId = -1; - if(fgseg->getReferencedSegmentNumber(segmentId).bad()){ - cerr << "Failed to get seg number!"; - throw -1; - } + Uint16 segmentId = getSegmentId(fgInterface, frameId); // populate meta information needed for Slicer ScalarVolumeNode initialization // (for example) @@ -284,4 +272,18 @@ Json::Value SegmentationImageObject::getSegmentAttributesMetadata() { values.append(innerList); } return values; +} + +Uint16 SegmentationImageObject::getSegmentId(FGInterface &fgInterface, size_t frameId) const { + bool isPerFrame; + FGSegmentation *fgseg = + OFstatic_cast(FGSegmentation*,fgInterface.get(frameId, DcmFGTypes::EFG_SEGMENTATION, isPerFrame)); +// assert(fgseg); + + Uint16 segmentId = -1; + if(fgseg->getReferencedSegmentNumber(segmentId).bad()){ + cerr << "Failed to get seg number!"; +// throw -1; + } + return segmentId; } \ No newline at end of file From 4faeb3f1d40acae37b274523e46ab1d4a0e38345 Mon Sep 17 00:00:00 2001 From: Christian Herz Date: Fri, 30 Jun 2017 16:51:07 -0400 Subject: [PATCH 107/116] STYLE: and uncommented code --- include/dcmqi/ImageVolumeGeometry.h | 2 +- include/dcmqi/ParametricMapObject.h | 2 -- libsrc/SegmentationImageObject.cpp | 4 ++-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/include/dcmqi/ImageVolumeGeometry.h b/include/dcmqi/ImageVolumeGeometry.h index dafa94b5..c437cc89 100644 --- a/include/dcmqi/ImageVolumeGeometry.h +++ b/include/dcmqi/ImageVolumeGeometry.h @@ -18,7 +18,7 @@ class ImageVolumeGeometry { friend class ParametricMapObject; friend class SegmentationImageObject; - public: +public: typedef itk::Vector DoubleVectorType; typedef itk::Size<3> SizeType; diff --git a/include/dcmqi/ParametricMapObject.h b/include/dcmqi/ParametricMapObject.h index bbda87a7..08dd7f53 100644 --- a/include/dcmqi/ParametricMapObject.h +++ b/include/dcmqi/ParametricMapObject.h @@ -77,8 +77,6 @@ class ParametricMapObject : public MultiframeObject { // Data containers specific to this object Float32ITKImageType::Pointer itkImage; - - private: DPMParametricMapIOD* parametricMap; }; diff --git a/libsrc/SegmentationImageObject.cpp b/libsrc/SegmentationImageObject.cpp index 3980665e..612ea78a 100644 --- a/libsrc/SegmentationImageObject.cpp +++ b/libsrc/SegmentationImageObject.cpp @@ -278,12 +278,12 @@ Uint16 SegmentationImageObject::getSegmentId(FGInterface &fgInterface, size_t fr bool isPerFrame; FGSegmentation *fgseg = OFstatic_cast(FGSegmentation*,fgInterface.get(frameId, DcmFGTypes::EFG_SEGMENTATION, isPerFrame)); -// assert(fgseg); + assert(fgseg); Uint16 segmentId = -1; if(fgseg->getReferencedSegmentNumber(segmentId).bad()){ cerr << "Failed to get seg number!"; -// throw -1; + throw -1; } return segmentId; } \ No newline at end of file From c6732a3c193ca2d2ef064a6fb0796347af423c9f Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Mon, 3 Jul 2017 17:14:56 -0400 Subject: [PATCH 108/116] BUG: updates to account for standard updates - PixelContrast is no longer needed for PM - use standard code for fitting model type --- doc/examples/pm-example-float.json | 4 ++-- doc/examples/pm-example.json | 4 ++-- libsrc/ParametricMapObject.cpp | 3 --- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/doc/examples/pm-example-float.json b/doc/examples/pm-example-float.json index 7e70af1b..ab3f3528 100644 --- a/doc/examples/pm-example-float.json +++ b/doc/examples/pm-example-float.json @@ -22,8 +22,8 @@ "CodeMeaning": "m2/s" }, "MeasurementMethodCode": { - "CodeValue": "DWMPxxxx10", - "CodingSchemeDesignator": "99QIICR", + "CodeValue": "113290", + "CodingSchemeDesignator": "DCM", "CodeMeaning": "Mono-exponential diffusion model" }, "SourceImageDiffusionBValues": ["0","1400"], diff --git a/doc/examples/pm-example.json b/doc/examples/pm-example.json index f0b2970d..30df68f1 100644 --- a/doc/examples/pm-example.json +++ b/doc/examples/pm-example.json @@ -22,8 +22,8 @@ "CodeMeaning": "um2/s" }, "MeasurementMethodCode": { - "CodeValue": "DWMPxxxx10", - "CodingSchemeDesignator": "99QIICR", + "CodeValue": "113290", + "CodingSchemeDesignator": "DCM", "CodeMeaning": "Mono-exponential diffusion model" }, "SourceImageDiffusionBValues": ["0","1400"], diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 85e0aa99..e9e4ab7f 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -406,9 +406,6 @@ void ParametricMapObject::initializeMetaDataFromDICOM(T doc) { doc->getSeries().getBodyPartExamined(temp); metaDataJson["BodyPartExamined"] = temp.c_str(); - doc->getDPMParametricMapImageModule().getImageType(temp, 3); - metaDataJson["DerivedPixelContrast"] = temp.c_str(); - if (doc->getNumberOfFrames() > 0) { FGInterface& fg = doc->getFunctionalGroups(); FGRealWorldValueMapping* rw = OFstatic_cast(FGRealWorldValueMapping*, From e2d95efb0e8ea9ee0ea95afc41fced5f8729bce9 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Mon, 3 Jul 2017 17:36:09 -0400 Subject: [PATCH 109/116] BUG: initialize origin from ITK image --- libsrc/MultiframeObject.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index de844afa..f964b26a 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -67,6 +67,7 @@ int MultiframeObject::initializeVolumeGeometryFromITK(DummyImageType::Pointer im spacing = image->GetSpacing(); directions = image->GetDirection(); extent = image->GetLargestPossibleRegion().GetSize(); + origin = image->GetOrigin(); volumeGeometry.setSpacing(spacing); volumeGeometry.setOrigin(origin); @@ -237,8 +238,9 @@ std::vector MultiframeObject::findIntersectingSlices(ImageVolumeGeometry &v point[1] = frameIPP[1]; point[2] = frameIPP[2]; - if(itkvolume->TransformPhysicalPointToIndex(point, index)) + if(itkvolume->TransformPhysicalPointToIndex(point, index)) { intersectingSlices.push_back(index[2]); + } return intersectingSlices; } From 02564daf196e59a4b787b2c7c1d4681ca515cf08 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Mon, 3 Jul 2017 17:53:49 -0400 Subject: [PATCH 110/116] BUG: fix initialization of ReferencedSeriesSequence --- libsrc/MultiframeObject.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index f964b26a..d02115c5 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -168,7 +168,7 @@ int MultiframeObject::mapVolumeSlicesToDICOMFrames(ImageVolumeGeometry& volume, } else { dcmqi::DICOMFrame frame(dcm); vector intersectingSlices = findIntersectingSlices(volume, frame); - + for(int s=0;s &refseries = commref.getReferencedSeriesItems(); for(std::map >::const_iterator mIt=series2frame.begin(); mIt!=series2frame.end();++mIt){ + // TODO: who is supposed to de-allocate this? IODSeriesAndInstanceReferenceMacro::ReferencedSeriesItem* refseriesItem = new IODSeriesAndInstanceReferenceMacro::ReferencedSeriesItem; refseriesItem->setSeriesInstanceUID(mIt->first.c_str()); OFVector &refinstances = refseriesItem->getReferencedInstanceItems(); @@ -284,6 +285,8 @@ int MultiframeObject::initializeCommonInstanceReferenceModule(IODCommonInstanceR CHECK_COND(refinstancesItem->setReferencedSOPInstanceUID(frame.getInstanceUID().c_str())); refinstances.push_back(refinstancesItem); } + + refseries.push_back(refseriesItem); } return EXIT_SUCCESS; From 4006d1b1d0834423c1a0954860db5320c4ed1f96 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Mon, 3 Jul 2017 18:05:50 -0400 Subject: [PATCH 111/116] BUG: pass the initialized derivation FG to addFrame() --- libsrc/MultiframeObject.cpp | 6 +----- libsrc/ParametricMapObject.cpp | 10 +++++++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index d02115c5..5a32ceb9 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -168,7 +168,7 @@ int MultiframeObject::mapVolumeSlicesToDICOMFrames(ImageVolumeGeometry& volume, } else { dcmqi::DICOMFrame frame(dcm); vector intersectingSlices = findIntersectingSlices(volume, frame); - + for(int s=0;saddDerivationImageItem(derivationCode,"",derimgItem)); - OFVector siVector; std::vector frameVector; for(set::const_iterator sIt=derivationFrames.begin(); sIt!=derivationFrames.end();++sIt) { - - // TODO: it seems that with the current dcmtk functionality it is not possible to set the referenced - // frame number. Revisit this after discussing with Michael. siVector.push_back((*sIt).getDataset()); frameVector.push_back(*sIt); } diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index e9e4ab7f..31488165 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -480,15 +480,17 @@ int ParametricMapObject::initializeFrames(vector perFrameFGs; - perFrameFGs.push_back(fgppp); - perFrameFGs.push_back(fgfc); - unsigned nSlices = itkImage->GetLargestPossibleRegion().GetSize()[2]; for (unsigned long sliceNumber = 0;sliceNumber < nSlices; sliceNumber++) { + + perFrameFGs.push_back(fgppp); + perFrameFGs.push_back(fgfc); + if(!slice2frame[sliceNumber].empty()){ // TODO: read derivation code from metadata, if available, and pass instead of the default addDerivationItemToDerivationFG(fgder, slice2frame[sliceNumber]); + perFrameFGs.push_back(fgder); } Float32ITKImageType::RegionType sliceRegion; @@ -531,6 +533,8 @@ int ParametricMapObject::initializeFrames(vectorgetFrames(); result = OFget >(&frames)->addFrame(&*data.begin(), frameSize, perFrameFGs); + perFrameFGs.clear(); + } return EXIT_SUCCESS; From 855cdbf39e50e2b13349cf00af809b276a764dbc Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Mon, 3 Jul 2017 18:11:44 -0400 Subject: [PATCH 112/116] ENH: initialize DerivationCode from input JSON --- libsrc/ParametricMapObject.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index 31488165..ec36836a 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -488,8 +488,15 @@ int ParametricMapObject::initializeFrames(vector Date: Wed, 5 Jul 2017 12:28:08 -0400 Subject: [PATCH 113/116] BUG: initialize series-specific attributes from JSON Round-trip tests are now passing --- include/dcmqi/MultiframeObject.h | 5 +++- libsrc/MultiframeObject.cpp | 39 ++++++++++++++++++++++++++++++++ libsrc/ParametricMapObject.cpp | 2 ++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/include/dcmqi/MultiframeObject.h b/include/dcmqi/MultiframeObject.h index da2cea0e..da029105 100644 --- a/include/dcmqi/MultiframeObject.h +++ b/include/dcmqi/MultiframeObject.h @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include #include @@ -154,7 +156,8 @@ class MultiframeObject { int getDeclaredImageSpacing(FGInterface &fgInterface, SpacingType &spacing); // initialize attributes of the composite context that are common for all multiframe objects - //virtual int initializeCompositeContext(); + int initializeCompositeContext(); + int initializeSeriesSpecificAttributes(IODGeneralSeriesModule&, IODGeneralImageModule&); // check whether all of the attributes required for initialization of the object are present in the // input metadata virtual bool metaDataIsComplete(); diff --git a/libsrc/MultiframeObject.cpp b/libsrc/MultiframeObject.cpp index 5a32ceb9..93619874 100644 --- a/libsrc/MultiframeObject.cpp +++ b/libsrc/MultiframeObject.cpp @@ -491,3 +491,42 @@ int MultiframeObject::getDeclaredImageSpacing(FGInterface &fgInterface, SpacingT return EXIT_SUCCESS; } + +int MultiframeObject::initializeSeriesSpecificAttributes(IODGeneralSeriesModule& generalSeriesModule, + IODGeneralImageModule& generalImageModule){ + + // TODO: error checks + string bodyPartExamined; + if(metaDataJson.isMember("BodyPartExamined")){ + bodyPartExamined = metaDataJson["BodyPartExamined"].asCString(); + } + if(derivationDcmDatasets.size() && bodyPartExamined.empty()) { + OFString bodyPartStr; + DcmDataset *srcDataset = derivationDcmDatasets[0]; + if(srcDataset->findAndGetOFString(DCM_BodyPartExamined, bodyPartStr).good()) { + if (!bodyPartStr.empty()) + bodyPartExamined = bodyPartStr.c_str(); + } + } + + if(!bodyPartExamined.empty()) + generalSeriesModule.setBodyPartExamined(bodyPartExamined.c_str()); + + // SeriesDate/Time should be of when parametric map was taken; initialize to when it was saved + { + OFString contentDate, contentTime; + DcmDate::getCurrentDate(contentDate); + DcmTime::getCurrentTime(contentTime); + + // TODO: AcquisitionTime + generalSeriesModule.setSeriesDate(contentDate.c_str()); + generalSeriesModule.setSeriesTime(contentTime.c_str()); + generalImageModule.setContentDate(contentDate.c_str()); + generalImageModule.setContentTime(contentTime.c_str()); + } + + generalSeriesModule.setSeriesDescription(metaDataJson["SeriesDescription"].asCString()); + generalSeriesModule.setSeriesNumber(metaDataJson["SeriesNumber"].asCString()); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index ec36836a..b1ca2198 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -39,6 +39,8 @@ int ParametricMapObject::initializeFromITK(Float32ITKImageType::Pointer inputIma // populate metadata about patient/study, from derivation // datasets or from metadata initializeCompositeContext(); + initializeSeriesSpecificAttributes(parametricMap->getIODGeneralSeriesModule(), + parametricMap->getIODGeneralImageModule()); // populate functional groups std::vector > dimensionTags; From 6c48bb09c0b66f83901e9a839ed15559cd66eaf6 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Wed, 5 Jul 2017 14:20:27 -0400 Subject: [PATCH 114/116] BUG: fix initialization of DerivationImageSequence If DerivationImageSequence is not empty for at least one frame, it must be present for each of the frames. --- libsrc/ParametricMapObject.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/libsrc/ParametricMapObject.cpp b/libsrc/ParametricMapObject.cpp index b1ca2198..c162ccc7 100644 --- a/libsrc/ParametricMapObject.cpp +++ b/libsrc/ParametricMapObject.cpp @@ -484,11 +484,22 @@ int ParametricMapObject::initializeFrames(vectorGetLargestPossibleRegion().GetSize()[2]; + // if there is a derivation item for at least one frame, DerivationImageSequence must be present + // for every frame. + bool derivationFGRequired = false; + for (unsigned long sliceNumber = 0;sliceNumber < nSlices; sliceNumber++) { + if(!slice2frame[sliceNumber].empty()){ + derivationFGRequired = true; + break; + } + } + for (unsigned long sliceNumber = 0;sliceNumber < nSlices; sliceNumber++) { perFrameFGs.push_back(fgppp); perFrameFGs.push_back(fgfc); + fgder->clearData(); if(!slice2frame[sliceNumber].empty()){ if(metaDataJson.isMember("DerivationCode")){ CodeSequenceMacro purposeOfReference = CodeSequenceMacro("121322","DCM","Source image for image processing operation"); @@ -499,9 +510,11 @@ int ParametricMapObject::initializeFrames(vectorGetBufferedRegion().GetSize(); From ec36addff0c340b448c5459585fe94ea8f6bdcfb Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Wed, 5 Jul 2017 16:50:18 -0400 Subject: [PATCH 115/116] touch to trigger circleci build --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 41b0e74b..394703d9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![OpenHub](https://www.openhub.net/p/dcmqi/widgets/project_thin_badge.gif)](https://www.openhub.net/p/dcmqi) [![codecov](https://codecov.io/gh/QIICR/dcmqi/branch/master/graph/badge.svg)](https://codecov.io/gh/QIICR/dcmqi) [![Join the chat at https://gitter.im/QIICR/dcmqi](https://badges.gitter.im/QIICR/dcmqi.svg)](https://gitter.im/QIICR/dcmqi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![OpenHub](https://www.openhub.net/p/dcmqi/widgets/project_thin_badge.gif)](https://www.openhub.net/p/dcmqi) [![codecov](https://codecov.io/gh/QIICR/dcmqi/branch/master/graph/badge.svg)](https://codecov.io/gh/QIICR/dcmqi) [![Join the chat at https://gitter.im/QIICR/dcmqi](https://badges.gitter.im/QIICR/dcmqi.svg)](https://gitter.im/QIICR/dcmqi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) | Docker | [![](https://images.microbadger.com/badges/version/qiicr/dcmqi:v1.0.5.svg)](https://microbadger.com/images/qiicr/dcmqi:v1.0.5) | [![](https://images.microbadger.com/badges/version/qiicr/dcmqi.svg)](https://microbadger.com/images/qiicr/dcmqi) | |--------|--------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------| From 6ccf62de7d2502e43326920f611d3b65e0b0d51e Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Sun, 30 Jul 2017 16:26:45 -0400 Subject: [PATCH 116/116] ENH: ignore node_modules --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 24ccbf0b..39d1724b 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,5 @@ CMakeLists.txt.user* __pycache__/ *.py[cod] *$py.class + +node_modules