# HG changeset patch # User Sebastien Jodogne # Date 1355848170 -3600 # Node ID 23813408113616ccf24a73c8283a4cbe6924849f # Parent be378326f50b5c6ffb30a5af63e2b4838ee1f0e8 modification of dicom files diff -r be378326f50b -r 238134081136 INSTALL --- a/INSTALL Mon Dec 17 16:30:39 2012 +0100 +++ b/INSTALL Tue Dec 18 17:29:30 2012 +0100 @@ -118,3 +118,15 @@ # cmake "-DDCMTK_LIBRARIES=wrap;oflog" -DSTATIC_BUILD=OFF -DCMAKE_BUILD_TYPE=Debug ~/Orthanc # make + + +Using ccache +------------ + +Under Linux, you have the opportunity to use "ccache" to dramatically +decrease the compilation time when rebuilding Orthanc. This is +especially useful for developers. Under Debian/Ubuntu, you would use: + +# CC="ccache gcc" CXX="ccache g++" cmake "-DDCMTK_LIBRARIES=wrap;oflog" \ + -DSTATIC_BUILD=OFF -DCMAKE_BUILD_TYPE=Debug ~/Orthanc + diff -r be378326f50b -r 238134081136 OrthancServer/FromDcmtkBridge.cpp --- a/OrthancServer/FromDcmtkBridge.cpp Mon Dec 17 16:30:39 2012 +0100 +++ b/OrthancServer/FromDcmtkBridge.cpp Tue Dec 18 17:29:30 2012 +0100 @@ -47,17 +47,34 @@ #include +#include #include #include #include #include +#include + +#include +#include +#include +#include +#include +#include #include #include +#include +#include +#include +#include +#include #include #include +#include +#include +#include #include #include -#include +#include #include #include @@ -86,7 +103,7 @@ static void SendPathValueForDictionary(RestApiOutput& output, - DcmItem& dicom) + DcmItem& dicom) { Json::Value v = Json::arrayValue; @@ -164,8 +181,8 @@ output.AnswerJson(v); } - static void SendField(RestApiOutput& output, - DcmElement& element) + static void AnswerDicomField(RestApiOutput& output, + DcmElement& element) { // This element is not a sequence std::string buffer; @@ -194,6 +211,7 @@ } else { + LOG(ERROR) << "Error while sending a DICOM field"; return; } } @@ -226,7 +244,7 @@ element->getVR() != EVR_UNKNOWN && element->getVR() != EVR_SQ) { - SendField(output, *element); + AnswerDicomField(output, *element); } } @@ -272,6 +290,359 @@ } + + + + static DcmElement* CreateElementForTag(const DicomTag& tag) + { + DcmTag key(tag.GetGroup(), tag.GetElement()); + + switch (key.getEVR()) + { + // http://support.dcmtk.org/docs/dcvr_8h-source.html + + /** + * TODO. + **/ + + case EVR_OB: // other byte + case EVR_OF: // other float + case EVR_OW: // other word + case EVR_AT: // attribute tag + throw OrthancException(ErrorCode_NotImplemented); + + case EVR_UN: // unknown value representation + throw OrthancException(ErrorCode_ParameterOutOfRange); + + + /** + * String types. + * http://support.dcmtk.org/docs/classDcmByteString.html + **/ + + case EVR_AS: // age string + return new DcmAgeString(key); + + case EVR_AE: // application entity title + return new DcmApplicationEntity(key); + + case EVR_CS: // code string + return new DcmCodeString(key); + + case EVR_DA: // date string + return new DcmDate(key); + + case EVR_DT: // date time string + return new DcmDateTime(key); + + case EVR_DS: // decimal string + return new DcmDecimalString(key); + + case EVR_IS: // integer string + return new DcmIntegerString(key); + + case EVR_TM: // time string + return new DcmTime(key); + + case EVR_UI: // unique identifier + return new DcmUniqueIdentifier(key); + + case EVR_ST: // short text + return new DcmShortText(key); + + case EVR_LO: // long string + return new DcmLongString(key); + + case EVR_LT: // long text + return new DcmLongText(key); + + case EVR_UT: // unlimited text + return new DcmUnlimitedText(key); + + case EVR_SH: // short string + return new DcmShortString(key); + + case EVR_PN: // person name + return new DcmPersonName(key); + + + /** + * Numerical types + **/ + + case EVR_SL: // signed long + return new DcmSignedLong(key); + + case EVR_SS: // signed short + return new DcmSignedShort(key); + + case EVR_UL: // unsigned long + return new DcmUnsignedLong(key); + + case EVR_US: // unsigned short + return new DcmUnsignedShort(key); + + case EVR_FL: // float single-precision + return new DcmFloatingPointSingle(key); + + case EVR_FD: // float double-precision + return new DcmFloatingPointDouble(key); + + + /** + * Sequence types, should never occur at this point. + **/ + + case EVR_SQ: // sequence of items + throw OrthancException(ErrorCode_ParameterOutOfRange); + + + /** + * Internal to DCMTK. + **/ + + case EVR_ox: // OB or OW depending on context + case EVR_xs: // SS or US depending on context + case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name) + case EVR_na: // na="not applicable", for data which has no VR + case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor + case EVR_item: // used internally for items + case EVR_metainfo: // used internally for meta info datasets + case EVR_dataset: // used internally for datasets + case EVR_fileFormat: // used internally for DICOM files + case EVR_dicomDir: // used internally for DICOMDIR objects + case EVR_dirRecord: // used internally for DICOMDIR records + case EVR_pixelSQ: // used internally for pixel sequences in a compressed image + case EVR_pixelItem: // used internally for pixel items in a compressed image + case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR) + case EVR_PixelData: // used internally for uncompressed pixeld data + case EVR_OverlayData: // used internally for overlay data + case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR + default: + break; + } + + throw OrthancException(ErrorCode_InternalError); + } + + + + static void FillElementWithString(DcmElement& element, + const DicomTag& tag, + const std::string& value) + { + DcmTag key(tag.GetGroup(), tag.GetElement()); + bool ok = false; + + try + { + switch (key.getEVR()) + { + // http://support.dcmtk.org/docs/dcvr_8h-source.html + + /** + * TODO. + **/ + + case EVR_OB: // other byte + case EVR_OF: // other float + case EVR_OW: // other word + case EVR_AT: // attribute tag + throw OrthancException(ErrorCode_NotImplemented); + + case EVR_UN: // unknown value representation + throw OrthancException(ErrorCode_ParameterOutOfRange); + + + /** + * String types. + **/ + + case EVR_DS: // decimal string + case EVR_IS: // integer string + case EVR_AS: // age string + case EVR_DA: // date string + case EVR_DT: // date time string + case EVR_TM: // time string + case EVR_AE: // application entity title + case EVR_CS: // code string + case EVR_SH: // short string + case EVR_LO: // long string + case EVR_ST: // short text + case EVR_LT: // long text + case EVR_UT: // unlimited text + case EVR_PN: // person name + case EVR_UI: // unique identifier + { + ok = element.putString(value.c_str()).good(); + break; + } + + + /** + * Numerical types + **/ + + case EVR_SL: // signed long + { + ok = element.putSint32(boost::lexical_cast(value)).good(); + break; + } + + case EVR_SS: // signed short + { + ok = element.putSint16(boost::lexical_cast(value)).good(); + break; + } + + case EVR_UL: // unsigned long + { + ok = element.putUint32(boost::lexical_cast(value)).good(); + break; + } + + case EVR_US: // unsigned short + { + ok = element.putUint16(boost::lexical_cast(value)).good(); + break; + } + + case EVR_FL: // float single-precision + { + ok = element.putFloat32(boost::lexical_cast(value)).good(); + break; + } + + case EVR_FD: // float double-precision + { + ok = element.putFloat64(boost::lexical_cast(value)).good(); + break; + } + + + /** + * Sequence types, should never occur at this point. + **/ + + case EVR_SQ: // sequence of items + { + ok = false; + break; + } + + + /** + * Internal to DCMTK. + **/ + + case EVR_ox: // OB or OW depending on context + case EVR_xs: // SS or US depending on context + case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name) + case EVR_na: // na="not applicable", for data which has no VR + case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor + case EVR_item: // used internally for items + case EVR_metainfo: // used internally for meta info datasets + case EVR_dataset: // used internally for datasets + case EVR_fileFormat: // used internally for DICOM files + case EVR_dicomDir: // used internally for DICOMDIR objects + case EVR_dirRecord: // used internally for DICOMDIR records + case EVR_pixelSQ: // used internally for pixel sequences in a compressed image + case EVR_pixelItem: // used internally for pixel items in a compressed image + case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR) + case EVR_PixelData: // used internally for uncompressed pixeld data + case EVR_OverlayData: // used internally for overlay data + case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR + default: + break; + } + } + catch (boost::bad_lexical_cast&) + { + ok = false; + } + + if (!ok) + { + throw OrthancException(ErrorCode_InternalError); + } + } + + + void ParsedDicomFile::Remove(const DicomTag& tag) + { + DcmTagKey key(tag.GetGroup(), tag.GetElement()); + + // TODO This call results in memory leaks inside DCMTK + file_->getDataset()->remove(key); + } + + + + void ParsedDicomFile::Insert(const DicomTag& tag, + const std::string& value) + { + std::auto_ptr element(CreateElementForTag(tag)); + FillElementWithString(*element, tag, value); + + if (!file_->getDataset()->insert(element.release(), false, false).good()) + { + // This field already exists + throw OrthancException(ErrorCode_InternalError); + } + } + + + void ParsedDicomFile::ReplaceInternal(const DicomTag& tag, + const std::string& value, + bool insertOnAbsent) + { + DcmTagKey key(tag.GetGroup(), tag.GetElement()); + DcmElement* element = NULL; + + if (!file_->getDataset()->findAndGetElement(key, element).good() || + element == NULL) + { + if (insertOnAbsent) + { + // This field does not exist, use "Insert()" instead + Insert(tag, value); + } + else + { + throw OrthancException(ErrorCode_InternalError); + } + } + else + { + FillElementWithString(*element, tag, value); + } + } + + + void ParsedDicomFile::Replace(const DicomTag& tag, + const std::string& value) + { + return ReplaceInternal(tag, value, false); + } + + void ParsedDicomFile::InsertOrReplace(const DicomTag& tag, + const std::string& value) + { + return ReplaceInternal(tag, value, true); + } + + + + void ParsedDicomFile::Answer(RestApiOutput& output) + { + std::string serialized; + if (FromDcmtkBridge::SaveToMemoryBuffer(serialized, file_->getDataset())) + { + output.AnswerBuffer(serialized, "application/octet-stream"); + } + } + + void FromDcmtkBridge::Convert(DicomMap& target, DcmDataset& dataset) { target.Clear(); @@ -327,159 +698,159 @@ * TODO. **/ - case EVR_DS: // decimal string - case EVR_IS: // integer string - case EVR_OB: // other byte - case EVR_OF: // other float - case EVR_OW: // other word - case EVR_AS: // age string - case EVR_AT: // attribute tag - case EVR_DA: // date string - case EVR_DT: // date time string - case EVR_TM: // time string - case EVR_UN: // unknown value representation - return new DicomNullValue(); + case EVR_DS: // decimal string + case EVR_IS: // integer string + case EVR_OB: // other byte + case EVR_OF: // other float + case EVR_OW: // other word + case EVR_AS: // age string + case EVR_AT: // attribute tag + case EVR_DA: // date string + case EVR_DT: // date time string + case EVR_TM: // time string + case EVR_UN: // unknown value representation + return new DicomNullValue(); + + + /** + * String types, should never happen at this point because of + * "element.isaString()". + **/ + + case EVR_AE: // application entity title + case EVR_CS: // code string + case EVR_SH: // short string + case EVR_LO: // long string + case EVR_ST: // short text + case EVR_LT: // long text + case EVR_UT: // unlimited text + case EVR_PN: // person name + case EVR_UI: // unique identifier + return new DicomNullValue(); - /** - * String types, should never happen at this point because of - * "element.isaString()". - **/ + /** + * Numerical types + **/ - case EVR_AE: // application entity title - case EVR_CS: // code string - case EVR_SH: // short string - case EVR_LO: // long string - case EVR_ST: // short text - case EVR_LT: // long text - case EVR_UT: // unlimited text - case EVR_PN: // person name - case EVR_UI: // unique identifier - return new DicomNullValue(); + case EVR_SL: // signed long + { + Sint32 f; + if (dynamic_cast(element).getSint32(f).good()) + { + return new DicomString(boost::lexical_cast(f)); + } + else + { + return new DicomNullValue(); + } + } + + case EVR_SS: // signed short + { + Sint16 f; + if (dynamic_cast(element).getSint16(f).good()) + { + return new DicomString(boost::lexical_cast(f)); + } + else + { + return new DicomNullValue(); + } + } + + case EVR_UL: // unsigned long + { + Uint32 f; + if (dynamic_cast(element).getUint32(f).good()) + { + return new DicomString(boost::lexical_cast(f)); + } + else + { + return new DicomNullValue(); + } + } + + case EVR_US: // unsigned short + { + Uint16 f; + if (dynamic_cast(element).getUint16(f).good()) + { + return new DicomString(boost::lexical_cast(f)); + } + else + { + return new DicomNullValue(); + } + } + + case EVR_FL: // float single-precision + { + Float32 f; + if (dynamic_cast(element).getFloat32(f).good()) + { + return new DicomString(boost::lexical_cast(f)); + } + else + { + return new DicomNullValue(); + } + } + + case EVR_FD: // float double-precision + { + Float64 f; + if (dynamic_cast(element).getFloat64(f).good()) + { + return new DicomString(boost::lexical_cast(f)); + } + else + { + return new DicomNullValue(); + } + } /** - * Numerical types - **/ - - case EVR_SL: // signed long - { - Sint32 f; - if (dynamic_cast(element).getSint32(f).good()) - { - return new DicomString(boost::lexical_cast(f)); - } - else - { - return new DicomNullValue(); - } - } - - case EVR_SS: // signed short - { - Sint16 f; - if (dynamic_cast(element).getSint16(f).good()) - { - return new DicomString(boost::lexical_cast(f)); - } - else - { - return new DicomNullValue(); - } - } + * Sequence types, should never occur at this point because of + * "element.isLeaf()". + **/ - case EVR_UL: // unsigned long - { - Uint32 f; - if (dynamic_cast(element).getUint32(f).good()) - { - return new DicomString(boost::lexical_cast(f)); - } - else - { - return new DicomNullValue(); - } - } - - case EVR_US: // unsigned short - { - Uint16 f; - if (dynamic_cast(element).getUint16(f).good()) - { - return new DicomString(boost::lexical_cast(f)); - } - else - { - return new DicomNullValue(); - } - } - - case EVR_FL: // float single-precision - { - Float32 f; - if (dynamic_cast(element).getFloat32(f).good()) - { - return new DicomString(boost::lexical_cast(f)); - } - else - { - return new DicomNullValue(); - } - } - - case EVR_FD: // float double-precision - { - Float64 f; - if (dynamic_cast(element).getFloat64(f).good()) - { - return new DicomString(boost::lexical_cast(f)); - } - else - { - return new DicomNullValue(); - } - } + case EVR_SQ: // sequence of items + return new DicomNullValue; - /** - * Sequence types, should never occur at this point because of - * "element.isLeaf()". - **/ + /** + * Internal to DCMTK. + **/ - case EVR_SQ: // sequence of items - return new DicomNullValue; + case EVR_ox: // OB or OW depending on context + case EVR_xs: // SS or US depending on context + case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name) + case EVR_na: // na="not applicable", for data which has no VR + case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor + case EVR_item: // used internally for items + case EVR_metainfo: // used internally for meta info datasets + case EVR_dataset: // used internally for datasets + case EVR_fileFormat: // used internally for DICOM files + case EVR_dicomDir: // used internally for DICOMDIR objects + case EVR_dirRecord: // used internally for DICOMDIR records + case EVR_pixelSQ: // used internally for pixel sequences in a compressed image + case EVR_pixelItem: // used internally for pixel items in a compressed image + case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR) + case EVR_PixelData: // used internally for uncompressed pixeld data + case EVR_OverlayData: // used internally for overlay data + case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR + return new DicomNullValue; - /** - * Internal to DCMTK. - **/ + /** + * Default case. + **/ - case EVR_ox: // OB or OW depending on context - case EVR_xs: // SS or US depending on context - case EVR_lt: // US, SS or OW depending on context, used for LUT Data (thus the name) - case EVR_na: // na="not applicable", for data which has no VR - case EVR_up: // up="unsigned pointer", used internally for DICOMDIR suppor - case EVR_item: // used internally for items - case EVR_metainfo: // used internally for meta info datasets - case EVR_dataset: // used internally for datasets - case EVR_fileFormat: // used internally for DICOM files - case EVR_dicomDir: // used internally for DICOMDIR objects - case EVR_dirRecord: // used internally for DICOMDIR records - case EVR_pixelSQ: // used internally for pixel sequences in a compressed image - case EVR_pixelItem: // used internally for pixel items in a compressed image - case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR) - case EVR_PixelData: // used internally for uncompressed pixeld data - case EVR_OverlayData: // used internally for overlay data - case EVR_UNKNOWN2B: // used internally for elements with unknown VR with 2-byte length field in explicit VR - return new DicomNullValue; - - - /** - * Default case. - **/ - - default: - return new DicomNullValue; + default: + return new DicomNullValue; } } catch (boost::bad_lexical_cast) @@ -678,17 +1049,17 @@ PixelFormat format; switch (mode) { - case ImageExtractionMode_Preview: - case ImageExtractionMode_UInt8: - format = PixelFormat_Grayscale8; - break; + case ImageExtractionMode_Preview: + case ImageExtractionMode_UInt8: + format = PixelFormat_Grayscale8; + break; - case ImageExtractionMode_UInt16: - format = PixelFormat_Grayscale16; - break; + case ImageExtractionMode_UInt16: + format = PixelFormat_Grayscale16; + break; - default: - throw OrthancException(ErrorCode_NotImplemented); + default: + throw OrthancException(ErrorCode_NotImplemented); } if (accessor.get() == NULL || @@ -702,20 +1073,20 @@ { switch (mode) { - case ImageExtractionMode_Preview: - ExtractPngImagePreview(result, *accessor); - break; + case ImageExtractionMode_Preview: + ExtractPngImagePreview(result, *accessor); + break; - case ImageExtractionMode_UInt8: - ExtractPngImageTruncate(result, *accessor, format); - break; + case ImageExtractionMode_UInt8: + ExtractPngImageTruncate(result, *accessor, format); + break; - case ImageExtractionMode_UInt16: - ExtractPngImageTruncate(result, *accessor, format); - break; + case ImageExtractionMode_UInt16: + ExtractPngImageTruncate(result, *accessor, format); + break; - default: - throw OrthancException(ErrorCode_NotImplemented); + default: + throw OrthancException(ErrorCode_NotImplemented); } } } @@ -828,17 +1199,17 @@ switch (level) { - case DicomRootLevel_Instance: - return dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT); + case DicomRootLevel_Instance: + return dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT); - case DicomRootLevel_Series: - return dcmGenerateUniqueIdentifier(uid, SITE_SERIES_UID_ROOT); + case DicomRootLevel_Series: + return dcmGenerateUniqueIdentifier(uid, SITE_SERIES_UID_ROOT); - case DicomRootLevel_Study: - return dcmGenerateUniqueIdentifier(uid, SITE_STUDY_UID_ROOT); + case DicomRootLevel_Study: + return dcmGenerateUniqueIdentifier(uid, SITE_STUDY_UID_ROOT); - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); } } @@ -889,6 +1260,4 @@ (opt_useMetaheader) ? EWM_fileformat : EWM_dataset); #endif } - - } diff -r be378326f50b -r 238134081136 OrthancServer/FromDcmtkBridge.h --- a/OrthancServer/FromDcmtkBridge.h Mon Dec 17 16:30:39 2012 +0100 +++ b/OrthancServer/FromDcmtkBridge.h Tue Dec 18 17:29:30 2012 +0100 @@ -62,6 +62,15 @@ private: std::auto_ptr file_; + ParsedDicomFile(DcmFileFormat& other) : + file_(dynamic_cast(other.clone())) + { + } + + void ReplaceInternal(const DicomTag& tag, + const std::string& value, + bool insertOnAbsent); + public: ParsedDicomFile(const std::string& content); @@ -70,8 +79,26 @@ return *file_; } + ParsedDicomFile* Clone() + { + return new ParsedDicomFile(*file_); + } + void SendPathValue(RestApiOutput& output, const UriComponents& uri); + + void Answer(RestApiOutput& output); + + void Remove(const DicomTag& tag); + + void Replace(const DicomTag& tag, + const std::string& value); + + void Insert(const DicomTag& tag, + const std::string& value); + + void InsertOrReplace(const DicomTag& tag, + const std::string& value); }; class FromDcmtkBridge diff -r be378326f50b -r 238134081136 OrthancServer/OrthancRestApi.cpp --- a/OrthancServer/OrthancRestApi.cpp Mon Dec 17 16:30:39 2012 +0100 +++ b/OrthancServer/OrthancRestApi.cpp Tue Dec 18 17:29:30 2012 +0100 @@ -870,6 +870,26 @@ + // Modification of DICOM tags ----------------------------------------------- + + template + static void Modify(RestApi::PostCall& call) + { + RETRIEVE_CONTEXT(call); + + std::string id = call.GetUriComponent("id", ""); + ParsedDicomFile& dicom = context.GetDicomFile(id); + + std::auto_ptr modified(dicom.Clone()); + + modified->InsertOrReplace(DicomTag(0x0010,0x0010), "0.42"); + modified->Remove(DicomTag(0x0010,0x0020)); + /*modified->Insert(DicomTag(0x0018,0x9082), "0.42"); + modified->Replace(DicomTag(0x0010,0x0010), "Hello");*/ + modified->Answer(call.GetOutput()); + } + + // Registration of the various REST handlers -------------------------------- @@ -926,6 +946,6 @@ Register("/modalities/{id}/find", DicomFind); Register("/modalities/{id}/store", DicomStore); - // TODO : "content" + Register("/instances/{id}/modify", Modify); } }