# HG changeset patch # User Sebastien Jodogne # Date 1478527996 -3600 # Node ID 9329ba17a06977b5cca00b1602539cb10a86c776 # Parent bfa92c9328d76c35de94e9849383b9e0466969ff Possibility to DELETE "dicom-as-json" attachments to reconstruct them diff -r bfa92c9328d7 -r 9329ba17a069 NEWS --- a/NEWS Mon Nov 07 13:59:36 2016 +0100 +++ b/NEWS Mon Nov 07 15:13:16 2016 +0100 @@ -13,6 +13,7 @@ * "Permissive" flag for URI "/modalities/{...}/store" to ignore C-Store transfer errors * "Asynchronous" flag for URIs "/modalities/{...}/store" and "/peers/{...}/store" to avoid waiting for the completion of image transfers +* Possibility to DELETE "dicom-as-json" attachments to reconstruct them Plugins ------- diff -r bfa92c9328d7 -r 9329ba17a069 OrthancServer/DicomInstanceToStore.cpp --- a/OrthancServer/DicomInstanceToStore.cpp Mon Nov 07 13:59:36 2016 +0100 +++ b/OrthancServer/DicomInstanceToStore.cpp Mon Nov 07 15:13:16 2016 +0100 @@ -112,10 +112,7 @@ { json_.Allocate(); FromDcmtkBridge::ExtractDicomAsJson(json_.GetContent(), - *parsed_.GetContent().GetDcmtkObject().getDataset(), - DicomToJsonFormat_Full, - DicomToJsonFlags_Default, - ORTHANC_MAXIMUM_TAG_LENGTH); + *parsed_.GetContent().GetDcmtkObject().getDataset()); } } diff -r bfa92c9328d7 -r 9329ba17a069 OrthancServer/DicomProtocol/DicomFindAnswers.cpp --- a/OrthancServer/DicomProtocol/DicomFindAnswers.cpp Mon Nov 07 13:59:36 2016 +0100 +++ b/OrthancServer/DicomProtocol/DicomFindAnswers.cpp Mon Nov 07 15:13:16 2016 +0100 @@ -209,7 +209,7 @@ bool simplify) const { DicomToJsonFormat format = (simplify ? DicomToJsonFormat_Human : DicomToJsonFormat_Full); - GetAnswer(index).ToJson(target, format, DicomToJsonFlags_None, 0); + GetAnswer(index).DatasetToJson(target, format, DicomToJsonFlags_None, 0); } diff -r bfa92c9328d7 -r 9329ba17a069 OrthancServer/FromDcmtkBridge.h --- a/OrthancServer/FromDcmtkBridge.h Mon Nov 07 13:59:36 2016 +0100 +++ b/OrthancServer/FromDcmtkBridge.h Mon Nov 07 15:13:16 2016 +0100 @@ -57,6 +57,8 @@ FRIEND_TEST(FromDcmtkBridge, FromJson); #endif + friend class ParsedDicomFile; + private: FromDcmtkBridge(); // Pure static class @@ -79,6 +81,12 @@ unsigned int maxStringLength, Encoding dicomEncoding); + static void ExtractDicomAsJson(Json::Value& target, + DcmDataset& dataset, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength); + public: static void InitializeDictionary(); @@ -110,10 +118,10 @@ Encoding encoding); static void ExtractDicomAsJson(Json::Value& target, - DcmDataset& dataset, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength); + DcmDataset& dataset) + { + ExtractDicomAsJson(target, dataset, DicomToJsonFormat_Full, DicomToJsonFlags_Default, ORTHANC_MAXIMUM_TAG_LENGTH); + } static void ExtractHeaderAsJson(Json::Value& target, DcmMetaInfo& header, diff -r bfa92c9328d7 -r 9329ba17a069 OrthancServer/Internals/StoreScp.cpp --- a/OrthancServer/Internals/StoreScp.cpp Mon Nov 07 13:59:36 2016 +0100 +++ b/OrthancServer/Internals/StoreScp.cpp Mon Nov 07 15:13:16 2016 +0100 @@ -169,10 +169,7 @@ try { FromDcmtkBridge::ExtractDicomSummary(summary, **imageDataSet); - FromDcmtkBridge::ExtractDicomAsJson(dicomJson, **imageDataSet, - DicomToJsonFormat_Full, - DicomToJsonFlags_Default, - ORTHANC_MAXIMUM_TAG_LENGTH); + FromDcmtkBridge::ExtractDicomAsJson(dicomJson, **imageDataSet); if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer, **imageDataSet)) { diff -r bfa92c9328d7 -r 9329ba17a069 OrthancServer/OrthancRestApi/OrthancRestArchive.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp Mon Nov 07 13:59:36 2016 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp Mon Nov 07 15:13:16 2016 +0100 @@ -524,7 +524,7 @@ const FileInfo& dicom) { std::string content; - context_.ReadFile(content, dicom); + context_.ReadAttachment(content, dicom); char filename[24]; snprintf(filename, sizeof(filename) - 1, instanceFormat_, countInstances_); @@ -612,7 +612,7 @@ writer_.OpenFile(filename.c_str()); std::string content; - context_.ReadFile(content, dicom); + context_.ReadAttachment(content, dicom); writer_.Write(content); ParsedDicomFile parsed(content); diff -r bfa92c9328d7 -r 9329ba17a069 OrthancServer/OrthancRestApi/OrthancRestResources.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Mon Nov 07 13:59:36 2016 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Mon Nov 07 15:13:16 2016 +0100 @@ -565,6 +565,10 @@ OrthancRestApi::GetIndex(call).DeleteMetadata(publicId, metadata); call.GetOutput().AnswerBuffer("", "text/plain"); } + else + { + call.GetOutput().SignalError(HttpStatus_403_Forbidden); + } } @@ -585,6 +589,10 @@ OrthancRestApi::GetIndex(call).SetMetadata(publicId, metadata, value); call.GetOutput().AnswerBuffer("", "text/plain"); } + else + { + call.GetOutput().SignalError(HttpStatus_403_Forbidden); + } } @@ -679,7 +687,7 @@ { // Return the raw data (possibly compressed), as stored on the filesystem std::string content; - context.ReadFile(content, publicId, type, false); + context.ReadAttachment(content, publicId, type, false); call.GetOutput().AnswerBuffer(content, "application/octet-stream"); } } @@ -748,7 +756,7 @@ // First check whether the compressed data is correctly stored in the disk std::string data; - context.ReadFile(data, publicId, StringToContentType(name), false); + context.ReadAttachment(data, publicId, StringToContentType(name), false); std::string actualMD5; Toolbox::ComputeMD5(actualMD5, data); @@ -763,7 +771,7 @@ } else { - context.ReadFile(data, publicId, StringToContentType(name), true); + context.ReadAttachment(data, publicId, StringToContentType(name), true); Toolbox::ComputeMD5(actualMD5, data); ok = (actualMD5 == info.GetUncompressedMD5()); } @@ -795,6 +803,10 @@ { call.GetOutput().AnswerBuffer("{}", "application/json"); } + else + { + call.GetOutput().SignalError(HttpStatus_403_Forbidden); + } } @@ -806,11 +818,33 @@ std::string name = call.GetUriComponent("name", ""); FileContentType contentType = StringToContentType(name); - if (IsUserContentType(contentType)) // It is forbidden to delete internal attachments + bool allowed; + if (IsUserContentType(contentType)) + { + allowed = true; + } + else if (Configuration::GetGlobalBoolParameter("StoreDicom", true) && + contentType == FileContentType_DicomAsJson) + { + allowed = true; + } + else + { + // It is forbidden to delete internal attachments, except for + // the "DICOM as JSON" summary as of Orthanc 1.2.0 (this summary + // would be automatically reconstructed on the next GET call) + allowed = false; + } + + if (allowed) { OrthancRestApi::GetIndex(call).DeleteAttachment(publicId, contentType); call.GetOutput().AnswerBuffer("{}", "application/json"); } + else + { + call.GetOutput().SignalError(HttpStatus_403_Forbidden); + } } diff -r bfa92c9328d7 -r 9329ba17a069 OrthancServer/ParsedDicomFile.cpp --- a/OrthancServer/ParsedDicomFile.cpp Mon Nov 07 13:59:36 2016 +0100 +++ b/OrthancServer/ParsedDicomFile.cpp Mon Nov 07 15:13:16 2016 +0100 @@ -1146,16 +1146,22 @@ ReplacePlainString(DICOM_TAG_SPECIFIC_CHARACTER_SET, s); } - void ParsedDicomFile::ToJson(Json::Value& target, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength) + void ParsedDicomFile::DatasetToJson(Json::Value& target, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength) { FromDcmtkBridge::ExtractDicomAsJson(target, *pimpl_->file_->getDataset(), format, flags, maxStringLength); } + void ParsedDicomFile::DatasetToJson(Json::Value& target) + { + FromDcmtkBridge::ExtractDicomAsJson(target, *pimpl_->file_->getDataset()); + } + + void ParsedDicomFile::HeaderToJson(Json::Value& target, DicomToJsonFormat format) { diff -r bfa92c9328d7 -r 9329ba17a069 OrthancServer/ParsedDicomFile.h --- a/OrthancServer/ParsedDicomFile.h Mon Nov 07 13:59:36 2016 +0100 +++ b/OrthancServer/ParsedDicomFile.h Mon Nov 07 15:13:16 2016 +0100 @@ -138,10 +138,14 @@ void SetEncoding(Encoding encoding); - void ToJson(Json::Value& target, - DicomToJsonFormat format, - DicomToJsonFlags flags, - unsigned int maxStringLength); + void DatasetToJson(Json::Value& target, + DicomToJsonFormat format, + DicomToJsonFlags flags, + unsigned int maxStringLength); + + // This version uses the default parameters for + // FileContentType_DicomAsJson + void DatasetToJson(Json::Value& target); void HeaderToJson(Json::Value& target, DicomToJsonFormat format); diff -r bfa92c9328d7 -r 9329ba17a069 OrthancServer/ServerContext.cpp --- a/OrthancServer/ServerContext.cpp Mon Nov 07 13:59:36 2016 +0100 +++ b/OrthancServer/ServerContext.cpp Mon Nov 07 15:13:16 2016 +0100 @@ -371,7 +371,32 @@ void ServerContext::ReadDicomAsJson(std::string& result, const std::string& instancePublicId) { - ReadFile(result, instancePublicId, FileContentType_DicomAsJson, true /* decompress if needed */); + FileInfo attachment; + if (index_.LookupAttachment(attachment, instancePublicId, FileContentType_DicomAsJson)) + { + ReadAttachment(result, attachment); + return; + } + + // The "DICOM as JSON" summary is not available from the Orthanc + // store (most probably deleted), reconstruct it from the DICOM file + std::string dicom; + ReadDicom(dicom, instancePublicId); + + LOG(INFO) << "Reconstructing the missing DICOM-as-JSON summary for instance: " << instancePublicId; + + ParsedDicomFile parsed(dicom); + + Json::Value summary; + parsed.DatasetToJson(summary); + + result = summary.toStyledString(); + + if (!AddAttachment(instancePublicId, FileContentType_DicomAsJson, result.c_str(), result.size())) + { + LOG(WARNING) << "Cannot associate the DICOM-as-JSON summary to instance: " << instancePublicId; + throw OrthancException(ErrorCode_InternalError); + } } @@ -389,21 +414,21 @@ } - void ServerContext::ReadFile(std::string& result, - const std::string& instancePublicId, - FileContentType content, - bool uncompressIfNeeded) + void ServerContext::ReadAttachment(std::string& result, + const std::string& instancePublicId, + FileContentType content, + bool uncompressIfNeeded) { FileInfo attachment; if (!index_.LookupAttachment(attachment, instancePublicId, content)) { + LOG(WARNING) << "Unable to read attachment " << EnumerationToString(content) << " of instance " << instancePublicId; throw OrthancException(ErrorCode_InternalError); } if (uncompressIfNeeded) { - StorageAccessor accessor(area_); - accessor.Read(result, attachment); + ReadAttachment(result, attachment); } else { @@ -414,11 +439,12 @@ } - void ServerContext::ReadFile(std::string& result, - const FileInfo& file) + void ServerContext::ReadAttachment(std::string& result, + const FileInfo& attachment) { + // This will decompress the attachment StorageAccessor accessor(area_); - accessor.Read(result, file); + accessor.Read(result, attachment); } diff -r bfa92c9328d7 -r 9329ba17a069 OrthancServer/ServerContext.h --- a/OrthancServer/ServerContext.h Mon Nov 07 13:59:36 2016 +0100 +++ b/OrthancServer/ServerContext.h Mon Nov 07 15:13:16 2016 +0100 @@ -200,17 +200,17 @@ void ReadDicom(std::string& dicom, const std::string& instancePublicId) { - ReadFile(dicom, instancePublicId, FileContentType_Dicom, true); + ReadAttachment(dicom, instancePublicId, FileContentType_Dicom, true); } // TODO CACHING MECHANISM AT THIS POINT - void ReadFile(std::string& result, - const std::string& instancePublicId, - FileContentType content, - bool uncompressIfNeeded); - - void ReadFile(std::string& result, - const FileInfo& file); + void ReadAttachment(std::string& result, + const std::string& instancePublicId, + FileContentType content, + bool uncompressIfNeeded); + + void ReadAttachment(std::string& result, + const FileInfo& attachment); void SetStoreMD5ForAttachments(bool storeMD5); diff -r bfa92c9328d7 -r 9329ba17a069 Plugins/Engine/OrthancPlugins.cpp --- a/Plugins/Engine/OrthancPlugins.cpp Mon Nov 07 13:59:36 2016 +0100 +++ b/Plugins/Engine/OrthancPlugins.cpp Mon Nov 07 15:13:16 2016 +0100 @@ -1955,8 +1955,8 @@ } Json::Value json; - dicom->ToJson(json, Plugins::Convert(p.format), - static_cast(p.flags), p.maxStringLength); + dicom->DatasetToJson(json, Plugins::Convert(p.format), + static_cast(p.flags), p.maxStringLength); Json::FastWriter writer; *p.result = CopyString(writer.write(json)); diff -r bfa92c9328d7 -r 9329ba17a069 UnitTestsSources/FromDcmtkTests.cpp --- a/UnitTestsSources/FromDcmtkTests.cpp Mon Nov 07 13:59:36 2016 +0100 +++ b/UnitTestsSources/FromDcmtkTests.cpp Mon Nov 07 15:13:16 2016 +0100 @@ -526,7 +526,7 @@ { Json::Value b; - f.ToJson(b, DicomToJsonFormat_Full, DicomToJsonFlags_Default, 0); + f.DatasetToJson(b, DicomToJsonFormat_Full, DicomToJsonFlags_Default, 0); Json::Value c; ServerToolbox::SimplifyTags(c, b, DicomToJsonFormat_Human); @@ -571,7 +571,7 @@ f.Replace(DICOM_TAG_PATIENT_NAME, s, false, DicomReplaceMode_InsertIfAbsent); Json::Value v; - f.ToJson(v, DicomToJsonFormat_Human, DicomToJsonFlags_Default, 0); + f.DatasetToJson(v, DicomToJsonFormat_Human, DicomToJsonFlags_Default, 0); ASSERT_EQ(v["PatientName"].asString(), std::string(testEncodingsExpected[i])); } } @@ -589,7 +589,7 @@ f.Insert(DicomTag(0x7053, 0x1000), "Some private tag", false); // Odd group => private tag Json::Value v; - f.ToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0); + f.DatasetToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0); ASSERT_EQ(Json::objectValue, v.type()); ASSERT_EQ(6u, v.getMemberNames().size()); ASSERT_FALSE(v.isMember("7052,1000")); @@ -598,7 +598,7 @@ ASSERT_EQ(Json::stringValue, v["7050,1000"].type()); ASSERT_EQ("Some public tag", v["7050,1000"].asString()); - f.ToJson(v, DicomToJsonFormat_Short, static_cast(DicomToJsonFlags_IncludePrivateTags | DicomToJsonFlags_ConvertBinaryToNull), 0); + f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast(DicomToJsonFlags_IncludePrivateTags | DicomToJsonFlags_ConvertBinaryToNull), 0); ASSERT_EQ(Json::objectValue, v.type()); ASSERT_EQ(7u, v.getMemberNames().size()); ASSERT_FALSE(v.isMember("7052,1000")); @@ -607,7 +607,7 @@ ASSERT_EQ("Some public tag", v["7050,1000"].asString()); ASSERT_EQ(Json::nullValue, v["7053,1000"].type()); - f.ToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_IncludePrivateTags, 0); + f.DatasetToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_IncludePrivateTags, 0); ASSERT_EQ(Json::objectValue, v.type()); ASSERT_EQ(7u, v.getMemberNames().size()); ASSERT_FALSE(v.isMember("7052,1000")); @@ -620,7 +620,7 @@ ASSERT_EQ("application/octet-stream", mime); ASSERT_EQ("Some private tag", content); - f.ToJson(v, DicomToJsonFormat_Short, static_cast(DicomToJsonFlags_IncludeUnknownTags | DicomToJsonFlags_ConvertBinaryToNull), 0); + f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast(DicomToJsonFlags_IncludeUnknownTags | DicomToJsonFlags_ConvertBinaryToNull), 0); ASSERT_EQ(Json::objectValue, v.type()); ASSERT_EQ(7u, v.getMemberNames().size()); ASSERT_TRUE(v.isMember("7050,1000")); @@ -629,7 +629,7 @@ ASSERT_EQ("Some public tag", v["7050,1000"].asString()); ASSERT_EQ(Json::nullValue, v["7052,1000"].type()); - f.ToJson(v, DicomToJsonFormat_Short, static_cast(DicomToJsonFlags_IncludeUnknownTags), 0); + f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast(DicomToJsonFlags_IncludeUnknownTags), 0); ASSERT_EQ(Json::objectValue, v.type()); ASSERT_EQ(7u, v.getMemberNames().size()); ASSERT_TRUE(v.isMember("7050,1000")); @@ -641,7 +641,7 @@ ASSERT_EQ("application/octet-stream", mime); ASSERT_EQ("Some unknown tag", content); - f.ToJson(v, DicomToJsonFormat_Short, static_cast(DicomToJsonFlags_IncludeUnknownTags | DicomToJsonFlags_IncludePrivateTags | DicomToJsonFlags_ConvertBinaryToNull), 0); + f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast(DicomToJsonFlags_IncludeUnknownTags | DicomToJsonFlags_IncludePrivateTags | DicomToJsonFlags_ConvertBinaryToNull), 0); ASSERT_EQ(Json::objectValue, v.type()); ASSERT_EQ(8u, v.getMemberNames().size()); ASSERT_TRUE(v.isMember("7050,1000")); @@ -659,25 +659,25 @@ f.Insert(DICOM_TAG_PIXEL_DATA, "Pixels", false); Json::Value v; - f.ToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0); + f.DatasetToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0); ASSERT_EQ(Json::objectValue, v.type()); ASSERT_EQ(5u, v.getMemberNames().size()); ASSERT_FALSE(v.isMember("7fe0,0010")); - f.ToJson(v, DicomToJsonFormat_Short, static_cast(DicomToJsonFlags_IncludePixelData | DicomToJsonFlags_ConvertBinaryToNull), 0); + f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast(DicomToJsonFlags_IncludePixelData | DicomToJsonFlags_ConvertBinaryToNull), 0); ASSERT_EQ(Json::objectValue, v.type()); ASSERT_EQ(6u, v.getMemberNames().size()); ASSERT_TRUE(v.isMember("7fe0,0010")); ASSERT_EQ(Json::nullValue, v["7fe0,0010"].type()); - f.ToJson(v, DicomToJsonFormat_Short, static_cast(DicomToJsonFlags_IncludePixelData | DicomToJsonFlags_ConvertBinaryToAscii), 0); + f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast(DicomToJsonFlags_IncludePixelData | DicomToJsonFlags_ConvertBinaryToAscii), 0); ASSERT_EQ(Json::objectValue, v.type()); ASSERT_EQ(6u, v.getMemberNames().size()); ASSERT_TRUE(v.isMember("7fe0,0010")); ASSERT_EQ(Json::stringValue, v["7fe0,0010"].type()); ASSERT_EQ("Pixels", v["7fe0,0010"].asString()); - f.ToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_IncludePixelData, 0); + f.DatasetToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_IncludePixelData, 0); ASSERT_EQ(Json::objectValue, v.type()); ASSERT_EQ(6u, v.getMemberNames().size()); ASSERT_TRUE(v.isMember("7fe0,0010")); @@ -768,7 +768,7 @@ (ParsedDicomFile::CreateFromJson(v, static_cast(DicomFromJsonFlags_GenerateIdentifiers))); Json::Value vv; - dicom->ToJson(vv, DicomToJsonFormat_Human, toJsonFlags, 0); + dicom->DatasetToJson(vv, DicomToJsonFormat_Human, toJsonFlags, 0); ASSERT_EQ(vv["SOPClassUID"].asString(), sopClassUid); ASSERT_EQ(vv["MediaStorageSOPClassUID"].asString(), sopClassUid); @@ -784,7 +784,7 @@ (ParsedDicomFile::CreateFromJson(v, static_cast(DicomFromJsonFlags_GenerateIdentifiers))); Json::Value vv; - dicom->ToJson(vv, DicomToJsonFormat_Human, static_cast(DicomToJsonFlags_IncludePixelData), 0); + dicom->DatasetToJson(vv, DicomToJsonFormat_Human, static_cast(DicomToJsonFlags_IncludePixelData), 0); std::string mime, content; ASSERT_TRUE(Toolbox::DecodeDataUriScheme(mime, content, vv["PixelData"].asString())); @@ -798,7 +798,7 @@ (ParsedDicomFile::CreateFromJson(v, static_cast(DicomFromJsonFlags_DecodeDataUriScheme))); Json::Value vv; - dicom->ToJson(vv, DicomToJsonFormat_Short, toJsonFlags, 0); + dicom->DatasetToJson(vv, DicomToJsonFormat_Short, toJsonFlags, 0); ASSERT_FALSE(vv.isMember("SOPInstanceUID")); ASSERT_FALSE(vv.isMember("SeriesInstanceUID"));