Mercurial > hg > orthanc
changeset 1693:558b25228a23
creation of tag hierarchy from json
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Thu, 08 Oct 2015 13:45:33 +0200 |
parents | 4eaf164dd574 |
children | 06d579e82bb8 |
files | OrthancServer/FromDcmtkBridge.cpp OrthancServer/FromDcmtkBridge.h OrthancServer/ParsedDicomFile.cpp OrthancServer/ParsedDicomFile.h UnitTestsSources/FromDcmtkTests.cpp |
diffstat | 5 files changed, 239 insertions(+), 66 deletions(-) [+] |
line wrap: on
line diff
--- a/OrthancServer/FromDcmtkBridge.cpp Thu Oct 08 10:57:29 2015 +0200 +++ b/OrthancServer/FromDcmtkBridge.cpp Thu Oct 08 13:45:33 2015 +0200 @@ -1012,11 +1012,21 @@ } + static bool IsBinaryTag(const DcmTag& key) + { + return key.isPrivate() || key.isUnknownVR(); + } + DcmElement* FromDcmtkBridge::CreateElementForTag(const DicomTag& tag) { DcmTag key(tag.GetGroup(), tag.GetElement()); + if (IsBinaryTag(key)) + { + return new DcmOtherByteOtherWord(key); + } + switch (key.getEVR()) { // http://support.dcmtk.org/docs/dcvr_8h-source.html @@ -1163,9 +1173,9 @@ decoded = &binary; } + DcmTag key(tag.GetGroup(), tag.GetElement()); - if (FromDcmtkBridge::IsPrivateTag(tag) || - FromDcmtkBridge::IsUnknownTag(tag)) + if (IsBinaryTag(key)) { if (element.putUint8Array((const Uint8*) decoded->c_str(), decoded->size()).good()) { @@ -1177,7 +1187,6 @@ } } - DcmTag key(tag.GetGroup(), tag.GetElement()); bool ok = false; try @@ -1314,8 +1323,8 @@ } - DcmElement* FromDcmtkBridge::FromJson(const Json::Value& value, - const DicomTag& tag, + DcmElement* FromDcmtkBridge::FromJson(const DicomTag& tag, + const Json::Value& value, bool decodeBinaryTags) { std::auto_ptr<DcmElement> element; @@ -1345,7 +1354,7 @@ Json::Value::Members members = value[i].getMemberNames(); for (Json::Value::ArrayIndex j = 0; j < members.size(); j++) { - item->insert(FromJson(value[i][members[j]], ParseTag(members[j]), decodeBinaryTags)); + item->insert(FromJson(ParseTag(members[j]), value[i][members[j]], decodeBinaryTags)); } sequence->append(item.release());
--- a/OrthancServer/FromDcmtkBridge.h Thu Oct 08 10:57:29 2015 +0200 +++ b/OrthancServer/FromDcmtkBridge.h Thu Oct 08 13:45:33 2015 +0200 @@ -129,8 +129,8 @@ const std::string& value, bool interpretBinaryTags); - static DcmElement* FromJson(const Json::Value& element, - const DicomTag& tag, + static DcmElement* FromJson(const DicomTag& tag, + const Json::Value& element, bool interpretBinaryTags); }; }
--- a/OrthancServer/ParsedDicomFile.cpp Thu Oct 08 10:57:29 2015 +0200 +++ b/OrthancServer/ParsedDicomFile.cpp Thu Oct 08 13:45:33 2015 +0200 @@ -137,6 +137,7 @@ #include <boost/math/special_functions/round.hpp> #include <dcmtk/dcmdata/dcostrmb.h> +#include <boost/algorithm/string/predicate.hpp> static const char* CONTENT_TYPE_OCTET_STREAM = "application/octet-stream"; @@ -579,52 +580,49 @@ } - void ParsedDicomFile::Insert(const DicomTag& tag, - const std::string& value) + static void InsertInternal(DcmDataset& dicom, + DcmElement* element) { - OFCondition cond; - - if (FromDcmtkBridge::IsPrivateTag(tag) || - FromDcmtkBridge::IsUnknownTag(tag)) - { - // This is a private tag - // http://support.dcmtk.org/redmine/projects/dcmtk/wiki/howto_addprivatedata - - DcmTag key(tag.GetGroup(), tag.GetElement(), EVR_OB); - cond = pimpl_->file_->getDataset()->putAndInsertUint8Array - (key, (const Uint8*) value.c_str(), value.size(), false); - } - else - { - std::auto_ptr<DcmElement> element(FromDcmtkBridge::CreateElementForTag(tag)); - FromDcmtkBridge::FillElementWithString(*element, tag, value, false); - - cond = pimpl_->file_->getDataset()->insert(element.release(), false, false); - } - + OFCondition cond = dicom.insert(element, false, false); if (!cond.good()) { // This field already exists + delete element; throw OrthancException(ErrorCode_InternalError); } } - void ParsedDicomFile::Replace(const DicomTag& tag, - const std::string& value, - DicomReplaceMode mode) + void ParsedDicomFile::Insert(const DicomTag& tag, + const std::string& value) + { + std::auto_ptr<DcmElement> element(FromDcmtkBridge::CreateElementForTag(tag)); + FromDcmtkBridge::FillElementWithString(*element, tag, value, false); + InsertInternal(*pimpl_->file_->getDataset(), element.release()); + } + + + void ParsedDicomFile::Insert(const DicomTag& tag, + const Json::Value& value, + bool decodeBinaryTags) { - DcmTagKey key(tag.GetGroup(), tag.GetElement()); - DcmElement* element = NULL; + std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeBinaryTags)); + InsertInternal(*pimpl_->file_->getDataset(), element.release()); + } + - if (!pimpl_->file_->getDataset()->findAndGetElement(key, element).good() || - element == NULL) + static void ReplaceInternal(DcmDataset& dicom, + std::auto_ptr<DcmElement>& element, + DicomReplaceMode mode) + { + const DcmTagKey& tag = element->getTag(); + + if (!dicom.findAndDeleteElement(tag).good()) { // This field does not exist, act wrt. the specified "mode" switch (mode) { case DicomReplaceMode_InsertIfAbsent: - Insert(tag, value); break; case DicomReplaceMode_ThrowIfAbsent: @@ -634,11 +632,33 @@ return; } } - else + + // Either the tag was not existing, or the replace mode was set to + // "InsertIfAbsent" + InsertInternal(dicom, element.release()); + } + + + void ParsedDicomFile::UpdateStorageUid(const DicomTag& tag, + const std::string& value, + bool decodeBinaryTags) + { + if (tag != DICOM_TAG_SOP_CLASS_UID && + tag != DICOM_TAG_SOP_INSTANCE_UID) { - FromDcmtkBridge::FillElementWithString(*element, tag, value, false); + return; } + std::string binary; + const std::string* decoded = &value; + + if (decodeBinaryTags && + boost::starts_with(value, "data:application/octet-stream;base64,")) + { + std::string mime; + Toolbox::DecodeDataUriScheme(mime, binary, value); + decoded = &binary; + } /** * dcmodify will automatically correct 'Media Storage SOP Class @@ -651,12 +671,44 @@ if (tag == DICOM_TAG_SOP_CLASS_UID) { - Replace(DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID, value, DicomReplaceMode_InsertIfAbsent); + Replace(DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID, *decoded, DicomReplaceMode_InsertIfAbsent); } if (tag == DICOM_TAG_SOP_INSTANCE_UID) { - Replace(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID, value, DicomReplaceMode_InsertIfAbsent); + Replace(DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID, *decoded, DicomReplaceMode_InsertIfAbsent); + } + } + + + void ParsedDicomFile::Replace(const DicomTag& tag, + const std::string& value, + DicomReplaceMode mode) + { + std::auto_ptr<DcmElement> element(FromDcmtkBridge::CreateElementForTag(tag)); + FromDcmtkBridge::FillElementWithString(*element, tag, value, false); + ReplaceInternal(*pimpl_->file_->getDataset(), element, mode); + UpdateStorageUid(tag, value, false); + } + + + void ParsedDicomFile::Replace(const DicomTag& tag, + const Json::Value& value, + bool decodeBinaryTags, + DicomReplaceMode mode) + { + std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeBinaryTags)); + ReplaceInternal(*pimpl_->file_->getDataset(), element, mode); + + if (tag == DICOM_TAG_SOP_CLASS_UID || + tag == DICOM_TAG_SOP_INSTANCE_UID) + { + if (value.type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadParameterType); + } + + UpdateStorageUid(tag, value.asString(), decodeBinaryTags); } } @@ -865,7 +917,7 @@ } else { - LOG(ERROR) << "Unsupported MIME type for the content of a new DICOM file"; + LOG(ERROR) << "Unsupported MIME type for the content of a new DICOM file: " << mime; throw OrthancException(ErrorCode_NotImplemented); } }
--- a/OrthancServer/ParsedDicomFile.h Thu Oct 08 10:57:29 2015 +0200 +++ b/OrthancServer/ParsedDicomFile.h Thu Oct 08 13:45:33 2015 +0200 @@ -53,6 +53,10 @@ void RemovePrivateTagsInternal(const std::set<DicomTag>* toKeep); + void UpdateStorageUid(const DicomTag& tag, + const std::string& value, + bool decodeBinaryTags); + public: ParsedDicomFile(); // Create a minimal DICOM instance @@ -77,6 +81,15 @@ void Insert(const DicomTag& tag, const std::string& value); + void Insert(const DicomTag& tag, + const Json::Value& value, + bool decodeBinaryTags); + + void Replace(const DicomTag& tag, + const Json::Value& value, + bool decodeBinaryTags, + DicomReplaceMode mode = DicomReplaceMode_InsertIfAbsent); + void Replace(const DicomTag& tag, const std::string& value, DicomReplaceMode mode = DicomReplaceMode_InsertIfAbsent);
--- a/UnitTestsSources/FromDcmtkTests.cpp Thu Oct 08 10:57:29 2015 +0200 +++ b/UnitTestsSources/FromDcmtkTests.cpp Thu Oct 08 13:45:33 2015 +0200 @@ -304,16 +304,37 @@ } + +static const DicomTag REFERENCED_STUDY_SEQUENCE(0x0008, 0x1110); +static const DicomTag REFERENCED_PATIENT_SEQUENCE(0x0008, 0x1120); + +static void CreateSampleJson(Json::Value& a) +{ + { + Json::Value b = Json::objectValue; + b["PatientName"] = "Hello"; + b["PatientID"] = "World"; + b["StudyDescription"] = "Toto"; + a.append(b); + } + + { + Json::Value b = Json::objectValue; + b["PatientName"] = "data:application/octet-stream;base64,SGVsbG8y"; // echo -n "Hello2" | base64 + b["PatientID"] = "World2"; + a.append(b); + } +} + + TEST(FromDcmtkBridge, FromJson) { - const DicomTag REFERENCED_STUDY_SEQUENCE(0x0008, 0x1110); - std::auto_ptr<DcmElement> element; { Json::Value a; a = "Hello"; - element.reset(FromDcmtkBridge::FromJson(a, DICOM_TAG_PATIENT_NAME, false)); + element.reset(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_NAME, a, false)); Json::Value b; FromDcmtkBridge::ToJson(b, *element, DicomToJsonFormat_Short, 0, Encoding_Ascii); @@ -324,20 +345,20 @@ Json::Value a; a = "Hello"; // Cannot assign a string to a sequence - ASSERT_THROW(element.reset(FromDcmtkBridge::FromJson(a, REFERENCED_STUDY_SEQUENCE, false)), OrthancException); + ASSERT_THROW(element.reset(FromDcmtkBridge::FromJson(REFERENCED_STUDY_SEQUENCE, a, false)), OrthancException); } { Json::Value a = Json::arrayValue; a.append("Hello"); // Cannot assign an array to a string - ASSERT_THROW(element.reset(FromDcmtkBridge::FromJson(a, DICOM_TAG_PATIENT_NAME, false)), OrthancException); + ASSERT_THROW(element.reset(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_NAME, a, false)), OrthancException); } { Json::Value a; a = "data:application/octet-stream;base64,SGVsbG8="; // echo -n "Hello" | base64 - element.reset(FromDcmtkBridge::FromJson(a, DICOM_TAG_PATIENT_NAME, true)); + element.reset(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_NAME, a, true)); Json::Value b; FromDcmtkBridge::ToJson(b, *element, DicomToJsonFormat_Short, 0, Encoding_Ascii); @@ -346,23 +367,8 @@ { Json::Value a = Json::arrayValue; - - { - Json::Value b = Json::objectValue; - b["PatientName"] = "Hello"; - b["PatientID"] = "World"; - b["StudyDescription"] = "Toto"; - a.append(b); - } - - { - Json::Value b = Json::objectValue; - b["PatientName"] = "Hello2"; - b["PatientID"] = "World2"; - a.append(b); - } - - element.reset(FromDcmtkBridge::FromJson(a, REFERENCED_STUDY_SEQUENCE, false)); + CreateSampleJson(a); + element.reset(FromDcmtkBridge::FromJson(REFERENCED_STUDY_SEQUENCE, a, true)); { Json::Value b; @@ -388,7 +394,100 @@ Json::Value c; SimplifyTags(c, b); + a[1]["PatientName"] = "Hello2"; // To remove the Data URI Scheme encoding ASSERT_EQ(0, c["ReferencedStudySequence"].compare(a)); } } } + + + +TEST(ParsedDicomFile, InsertReplaceStrings) +{ + ParsedDicomFile f; + + f.Insert(DICOM_TAG_PATIENT_NAME, "World"); + ASSERT_THROW(f.Insert(DICOM_TAG_PATIENT_ID, "Hello"), OrthancException); // Already existing tag + f.Replace(DICOM_TAG_SOP_INSTANCE_UID, "Toto"); // (*) + f.Replace(DICOM_TAG_SOP_CLASS_UID, "Tata"); // (**) + + std::string s; + + ASSERT_THROW(f.Replace(DICOM_TAG_ACCESSION_NUMBER, "Accession", DicomReplaceMode_ThrowIfAbsent), OrthancException); + f.Replace(DICOM_TAG_ACCESSION_NUMBER, "Accession", DicomReplaceMode_IgnoreIfAbsent); + ASSERT_FALSE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER)); + f.Replace(DICOM_TAG_ACCESSION_NUMBER, "Accession", DicomReplaceMode_InsertIfAbsent); + ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER)); + ASSERT_EQ(s, "Accession"); + f.Replace(DICOM_TAG_ACCESSION_NUMBER, "Accession2", DicomReplaceMode_IgnoreIfAbsent); + ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER)); + ASSERT_EQ(s, "Accession2"); + f.Replace(DICOM_TAG_ACCESSION_NUMBER, "Accession3", DicomReplaceMode_ThrowIfAbsent); + ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER)); + ASSERT_EQ(s, "Accession3"); + + ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_PATIENT_NAME)); + ASSERT_EQ(s, "World"); + ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_SOP_INSTANCE_UID)); + ASSERT_EQ(s, "Toto"); + ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID)); // Implicitly modified by (*) + ASSERT_EQ(s, "Toto"); + ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_SOP_CLASS_UID)); + ASSERT_EQ(s, "Tata"); + ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID)); // Implicitly modified by (**) + ASSERT_EQ(s, "Tata"); +} + + + + +TEST(ParsedDicomFile, InsertReplaceJson) +{ + ParsedDicomFile f; + + Json::Value a; + CreateSampleJson(a); + + ASSERT_FALSE(f.HasTag(REFERENCED_STUDY_SEQUENCE)); + f.Remove(REFERENCED_STUDY_SEQUENCE); // No effect + f.Insert(REFERENCED_STUDY_SEQUENCE, a, true); + ASSERT_TRUE(f.HasTag(REFERENCED_STUDY_SEQUENCE)); + ASSERT_THROW(f.Insert(REFERENCED_STUDY_SEQUENCE, a, true), OrthancException); + f.Remove(REFERENCED_STUDY_SEQUENCE); + ASSERT_FALSE(f.HasTag(REFERENCED_STUDY_SEQUENCE)); + f.Insert(REFERENCED_STUDY_SEQUENCE, a, true); + ASSERT_TRUE(f.HasTag(REFERENCED_STUDY_SEQUENCE)); + + ASSERT_FALSE(f.HasTag(REFERENCED_PATIENT_SEQUENCE)); + ASSERT_THROW(f.Replace(REFERENCED_PATIENT_SEQUENCE, a, false, DicomReplaceMode_ThrowIfAbsent), OrthancException); + ASSERT_FALSE(f.HasTag(REFERENCED_PATIENT_SEQUENCE)); + f.Replace(REFERENCED_PATIENT_SEQUENCE, a, false, DicomReplaceMode_IgnoreIfAbsent); + ASSERT_FALSE(f.HasTag(REFERENCED_PATIENT_SEQUENCE)); + f.Replace(REFERENCED_PATIENT_SEQUENCE, a, false, DicomReplaceMode_InsertIfAbsent); + ASSERT_TRUE(f.HasTag(REFERENCED_PATIENT_SEQUENCE)); + + { + Json::Value b; + f.ToJson(b, DicomToJsonFormat_Full, 0); + + Json::Value c; + SimplifyTags(c, b); + + ASSERT_EQ(0, c["ReferencedPatientSequence"].compare(a)); + ASSERT_NE(0, c["ReferencedStudySequence"].compare(a)); // Because Data URI Scheme decoding was enabled + } + + a = "data:application/octet-stream;base64,VGF0YQ=="; // echo -n "Tata" | base64 + f.Replace(DICOM_TAG_SOP_INSTANCE_UID, a, false); // (*) + f.Replace(DICOM_TAG_SOP_CLASS_UID, a, true); // (**) + + std::string s; + ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_SOP_INSTANCE_UID)); + ASSERT_EQ(s, a.asString()); + ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID)); // Implicitly modified by (*) + ASSERT_EQ(s, a.asString()); + ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_SOP_CLASS_UID)); + ASSERT_EQ(s, "Tata"); + ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID)); // Implicitly modified by (**) + ASSERT_EQ(s, "Tata"); +}