From d45f8604a9f9d85e7a99f84a5f18952dd1d5c8da Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 10 Mar 2017 16:08:42 -0500 Subject: [PATCH] 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;