Mercurial > hg > orthanc
changeset 5287:c04230962098 am-experimental
wip: 'dicomWeb' json format + 'include' get arguments
line wrap: on
line diff
--- a/NEWS Mon Apr 24 18:13:48 2023 +0200 +++ b/NEWS Fri Apr 28 10:42:27 2023 +0200 @@ -6,7 +6,10 @@ * Fix decoding of YBR_FULL RLE images for which the "Planar Configuration" tag (0028,0006) equals 1 - +* WIP: new dicomWeb Json format for some of the Rest API routes. +* WIP: new 'include' get arguments for some of the Rest API routes to define + the content of the response. Useful since the dicomWeb format is very slow + to serialize. Version 1.12.0 (2023-04-14) ===========================
--- a/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp Mon Apr 24 18:13:48 2023 +0200 +++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp Fri Apr 28 10:42:27 2023 +0200 @@ -41,6 +41,9 @@ #include "../Logging.h" #include "../Toolbox.h" #include "../OrthancException.h" +#include "DicomWebJsonVisitor.h" +#include "ParsedDicomFile.h" +#include <boost/date_time/posix_time/posix_time.hpp> #if ORTHANC_SANDBOXED == 0 # include "../TemporaryFile.h" @@ -1444,6 +1447,16 @@ result.clear(); + if (format == DicomToJsonFormat_DicomWeb) + { + DicomWebJsonVisitor visitor; + + ParsedDicomFile dicom(values, Encoding_Utf8, true); + dicom.Apply(visitor); + result = visitor.GetResult(); + return; + } + for (DicomMap::Content::const_iterator it = values.content_.begin(); it != values.content_.end(); ++it) {
--- a/OrthancFramework/Sources/Enumerations.cpp Mon Apr 24 18:13:48 2023 +0200 +++ b/OrthancFramework/Sources/Enumerations.cpp Fri Apr 28 10:42:27 2023 +0200 @@ -1183,6 +1183,9 @@ case DicomToJsonFormat_Short: return "Short"; + case DicomToJsonFormat_DicomWeb: + return "DicomWeb"; + default: throw OrthancException(ErrorCode_ParameterOutOfRange); } @@ -1884,6 +1887,10 @@ { return DicomToJsonFormat_Human; } + else if (format == "DicomWeb") + { + return DicomToJsonFormat_DicomWeb; + } else { throw OrthancException(ErrorCode_ParameterOutOfRange);
--- a/OrthancFramework/Sources/Enumerations.h Mon Apr 24 18:13:48 2023 +0200 +++ b/OrthancFramework/Sources/Enumerations.h Fri Apr 28 10:42:27 2023 +0200 @@ -581,7 +581,8 @@ { DicomToJsonFormat_Full, DicomToJsonFormat_Short, - DicomToJsonFormat_Human + DicomToJsonFormat_Human, + DicomToJsonFormat_DicomWeb }; enum DicomToJsonFlags
--- a/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp Mon Apr 24 18:13:48 2023 +0200 +++ b/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp Fri Apr 28 10:42:27 2023 +0200 @@ -3216,7 +3216,128 @@ m.FromDicomWeb(v); } - +TEST(DicomMap, MainDicomTagsDicomWebJson) +{ + std::unique_ptr<ParsedDicomFile> dicom; + + { + std::string source = "{" + "\"0008,0005\" : {" + "\"Name\" : \"SpecificCharacterSet\"," + "\"Type\" : \"String\"," + "\"Value\" : \"ISO_IR 192\"" + "}," + "\"0008,0008\" : {" + "\"Name\" : \"ImageType\"," + "\"Type\" : \"String\"," + "\"Value\" : \"DERIVED\\\\PRIMARY\\\\AXIAL\"" + "}," + "\"0008,0016\" : {" + "\"Name\" : \"SOPClassUID\"," + "\"Type\" : \"String\"," + "\"Value\" : \"1.2.840.10008.5.1.4.1.1.2\"" + "}," + "\"0008,0018\" : {" + "\"Name\" : \"SOPInstanceUID\"," + "\"Type\" : \"String\"," + "\"Value\" : \"1.2.276.0.7230010.3.1.4.1215942821.4756.1664833117.11987\"" + "}," + "\"0018,0050\" : {" + "\"Name\" : \"SliceThickness\"," + "\"Type\" : \"String\"," + "\"Value\" : \"0.19816064757161\"" + "}," + "\"0020,0013\" : {" + "\"Name\" : \"InstanceNumber\"," + "\"Type\" : \"String\"," + "\"Value\" : \"0\"" + "}," + "\"0020,0032\" : {" + "\"Name\" : \"ImagePositionPatient\"," + "\"Type\" : \"String\"," + "\"Value\" : \"-79.462419676214\\\\-79.462419676214\\\\-59.150953300125\"" + "}," + "\"0020,0037\" : {" + "\"Name\" : \"ImageOrientationPatient\"," + "\"Type\" : \"String\"," + "\"Value\" : \"1\\\\-0\\\\0\\\\-0\\\\1\\\\-0\"" + "}," + "\"0028,0004\" : {" + "\"Name\" : \"PhotometricInterpretation\"," + "\"Type\" : \"String\"," + "\"Value\" : \"MONOCHROME2\"" + "}," + "\"0028,0010\" : {" + "\"Name\" : \"Rows\"," + "\"Type\" : \"String\"," + "\"Value\" : \"803\"" + "}," + "\"0028,0011\" : {" + "\"Name\" : \"Columns\"," + "\"Type\" : \"String\"," + "\"Value\" : \"803\"" + "}," + "\"0028,0030\" : {" + "\"Name\" : \"PixelSpacing\"," + "\"Type\" : \"String\"," + "\"Value\" : \"0.19816064757161\\\\0.19816064757161\"" + "}," + "\"0028,0100\" : {" + "\"Name\" : \"BitsAllocated\"," + "\"Type\" : \"String\"," + "\"Value\" : \"16\"" + "}," + "\"0028,0101\" : {" + "\"Name\" : \"BitsStored\"," + "\"Type\" : \"String\"," + "\"Value\" : \"16\"" + "}," + "\"0028,0103\" : {" + "\"Name\" : \"PixelRepresentation\"," + "\"Type\" : \"String\"," + "\"Value\" : \"0\"" + "}," + "\"0028,1050\" : {" + "\"Name\" : \"WindowCenter\"," + "\"Type\" : \"String\"," + "\"Value\" : \"1023.96875\"" + "}," + "\"0028,1051\" : {" + "\"Name\" : \"WindowWidth\"," + "\"Type\" : \"String\"," + "\"Value\" : \"4095.9375\"" + "}," + "\"0028,1052\" : {" + "\"Name\" : \"RescaleIntercept\"," + "\"Type\" : \"String\"," + "\"Value\" : \"-1024\"" + "}," + "\"0028,1053\" : {" + "\"Name\" : \"RescaleSlope\"," + "\"Type\" : \"String\"," + "\"Value\" : \"0.0625\"" + "}" + "}"; + + Json::Value v; + Orthanc::Toolbox::ReadJson(v, source); + + LOG(INFO) << source; + LOG(INFO) << v.toStyledString(); + Json::Value result = Json::objectValue; + DicomMap map; + map.FromDicomAsJson(v, false, true); + +// for profiling for (int i=0; i < 100; ++i) + { + result = Json::objectValue; + FromDcmtkBridge::ToJson(result, map, Orthanc::DicomToJsonFormat_DicomWeb); + } + + ASSERT_TRUE(result.isMember("00281053")); + ASSERT_EQ("0.0625", result["00281053"]["Value"].asString()); + } +} #if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
--- a/OrthancServer/Plugins/Samples/MultitenantDicom/OrthancFrameworkDependencies.cpp Mon Apr 24 18:13:48 2023 +0200 +++ b/OrthancServer/Plugins/Samples/MultitenantDicom/OrthancFrameworkDependencies.cpp Fri Apr 28 10:42:27 2023 +0200 @@ -64,6 +64,7 @@ #include "../../../../OrthancFramework/Sources/DicomNetworking/Internals/StoreScp.cpp" #include "../../../../OrthancFramework/Sources/DicomNetworking/RemoteModalityParameters.cpp" #include "../../../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp" +#include "../../../../OrthancFramework/Sources/DicomParsing/DicomWebJsonVisitor.cpp" #include "../../../../OrthancFramework/Sources/DicomParsing/Internals/DicomFrameIndex.cpp" #include "../../../../OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp" #include "../../../../OrthancFramework/Sources/DicomParsing/ParsedDicomFile.cpp"
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp Mon Apr 24 18:13:48 2023 +0200 +++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp Fri Apr 28 10:42:27 2023 +0200 @@ -868,7 +868,8 @@ LookupStringMetadata(target.mainDicomTagsSignature_, target.metadata_, MetadataType_MainDicomTagsSignature); } - if (expandFlags & ExpandResourceFlags_IncludeMainDicomTags) + if (expandFlags & ExpandResourceFlags_IncludeMainDicomTags + || expandFlags & ExpandResourceFlags_IncludeRequestedTags) { // read all tags from DB transaction.GetMainDicomTags(target.GetMainDicomTags(), internalId);
--- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.h Mon Apr 24 18:13:48 2023 +0200 +++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.h Fri Apr 28 10:42:27 2023 +0200 @@ -115,11 +115,13 @@ ExpandResourceFlags_IncludeChildren = (1 << 1), ExpandResourceFlags_IncludeMainDicomTags = (1 << 2), ExpandResourceFlags_IncludeLabels = (1 << 3), + ExpandResourceFlags_IncludeRequestedTags = (1 << 3), ExpandResourceFlags_Default = (ExpandResourceFlags_IncludeMetadata | ExpandResourceFlags_IncludeChildren | ExpandResourceFlags_IncludeMainDicomTags | - ExpandResourceFlags_IncludeLabels) + ExpandResourceFlags_IncludeLabels | + ExpandResourceFlags_IncludeRequestedTags) }; class StatelessDatabaseOperations : public boost::noncopyable
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp Mon Apr 24 18:13:48 2023 +0200 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp Fri Apr 28 10:42:27 2023 +0200 @@ -489,7 +489,9 @@ static const std::string GET_SIMPLIFY = "simplify"; static const std::string GET_FULL = "full"; static const std::string GET_SHORT = "short"; + static const std::string GET_DICOM_WEB = "dicomWeb"; static const std::string GET_REQUESTED_TAGS = "requestedTags"; + static const std::string GET_INCLUDE = "include"; static const std::string POST_SIMPLIFY = "Simplify"; static const std::string POST_FULL = "Full"; @@ -506,6 +508,45 @@ "report the DICOM tags in full format (tags indexed by their hexadecimal " "format, associated with their symbolic name and their value)"; + ExpandResourceFlags OrthancRestApi::GetResponseContent(const RestApiGetCall& call, + ExpandResourceFlags defaultContent) + { + if (call.HasArgument(GET_INCLUDE)) + { + std::vector<std::string> includes; + Toolbox::TokenizeString(includes, call.GetArgument(GET_INCLUDE, ""), ','); + + unsigned int result = static_cast<unsigned int>(ExpandResourceFlags_None); + for (size_t i = 0; i < includes.size(); ++i) + { + if (includes[i] == "MainDicomTags") + { + result |= static_cast<unsigned int>(ExpandResourceFlags_IncludeMainDicomTags); + } + else if (includes[i] == "RequestedTags") + { + result |= static_cast<unsigned int>(ExpandResourceFlags_IncludeRequestedTags); + } + else if (includes[i] == "Labels") + { + result |= static_cast<unsigned int>(ExpandResourceFlags_IncludeLabels); + } + else if (includes[i] == "Children") + { + result |= static_cast<unsigned int>(ExpandResourceFlags_IncludeChildren); + } + else if (includes[i] == "Metadata") + { + result |= static_cast<unsigned int>(ExpandResourceFlags_IncludeMetadata); + } + } + + return static_cast<ExpandResourceFlags>(result); + } + + return defaultContent; + } + DicomToJsonFormat OrthancRestApi::GetDicomFormat(const RestApiGetCall& call, DicomToJsonFormat defaultFormat) @@ -522,6 +563,10 @@ { return DicomToJsonFormat_Full; } + else if (call.HasArgument(GET_DICOM_WEB)) + { + return DicomToJsonFormat_DicomWeb; + } else { return defaultFormat;
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.h Mon Apr 24 18:13:48 2023 +0200 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.h Fri Apr 28 10:42:27 2023 +0200 @@ -28,7 +28,7 @@ #include "../../../OrthancFramework/Sources/RestApi/RestApi.h" #include "../ServerJobs/ThreadedSetOfInstancesJob.h" #include "../ServerEnumerations.h" - +#include "../Database/StatelessDatabaseOperations.h" #include <set> namespace Orthanc @@ -147,6 +147,9 @@ static DicomToJsonFormat GetDicomFormat(const Json::Value& body, DicomToJsonFormat defaultFormat); + static ExpandResourceFlags GetResponseContent(const RestApiGetCall& call, + ExpandResourceFlags defaultContent); + static void DocumentDicomFormat(RestApiGetCall& call, DicomToJsonFormat defaultFormat);
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Mon Apr 24 18:13:48 2023 +0200 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Fri Apr 28 10:42:27 2023 +0200 @@ -158,11 +158,11 @@ *(mainDicomTags->second.get()), instanceId->second, dicomAsJson->second.get(), - level, format, requestedTags, allowStorageAccess); + level, format, requestedTags, ExpandResourceFlags_Default, allowStorageAccess); } else { - context.ExpandResource(expanded, *resource, level, format, requestedTags, allowStorageAccess); + context.ExpandResource(expanded, *resource, level, format, requestedTags, ExpandResourceFlags_Default, allowStorageAccess); } if (expanded.type() == Json::objectValue) @@ -288,7 +288,7 @@ Json::Value json; if (OrthancRestApi::GetContext(call).ExpandResource( - json, call.GetUriComponent("id", ""), resourceType, format, requestedTags, true /* allowStorageAccess */)) + json, call.GetUriComponent("id", ""), resourceType, format, requestedTags, ExpandResourceFlags_Default, true /* allowStorageAccess */)) { call.GetOutput().AnswerJson(json); } @@ -3355,12 +3355,13 @@ Json::Value result = Json::arrayValue; const DicomToJsonFormat format = OrthancRestApi::GetDicomFormat(call, DicomToJsonFormat_Human); + ExpandResourceFlags expandResource = OrthancRestApi::GetResponseContent(call, ExpandResourceFlags_Default); for (std::list<std::string>::const_iterator it = a.begin(); it != a.end(); ++it) { Json::Value resource; - if (OrthancRestApi::GetContext(call).ExpandResource(resource, *it, end, format, requestedTags, true /* allowStorageAccess */)) + if (OrthancRestApi::GetContext(call).ExpandResource(resource, *it, end, format, requestedTags, expandResource, true /* allowStorageAccess */)) { result.append(resource); } @@ -3479,7 +3480,7 @@ const DicomToJsonFormat format = OrthancRestApi::GetDicomFormat(call, DicomToJsonFormat_Human); Json::Value resource; - if (OrthancRestApi::GetContext(call).ExpandResource(resource, current, end, format, requestedTags, true /* allowStorageAccess */)) + if (OrthancRestApi::GetContext(call).ExpandResource(resource, current, end, format, requestedTags, ExpandResourceFlags_Default, true /* allowStorageAccess */)) { call.GetOutput().AnswerJson(resource); } @@ -3886,7 +3887,7 @@ Json::Value item; std::set<DicomTag> emptyRequestedTags; // not supported for bulk content - if (OrthancRestApi::GetContext(call).ExpandResource(item, *it, level, format, emptyRequestedTags, true /* allowStorageAccess */)) + if (OrthancRestApi::GetContext(call).ExpandResource(item, *it, level, format, emptyRequestedTags, ExpandResourceFlags_Default, true /* allowStorageAccess */)) { if (metadata) { @@ -3911,7 +3912,7 @@ std::set<DicomTag> emptyRequestedTags; // not supported for bulk content if (index.LookupResourceType(level, *it) && - OrthancRestApi::GetContext(call).ExpandResource(item, *it, level, format, emptyRequestedTags, true /* allowStorageAccess */)) + OrthancRestApi::GetContext(call).ExpandResource(item, *it, level, format, emptyRequestedTags, ExpandResourceFlags_Default, true /* allowStorageAccess */)) { if (metadata) {
--- a/OrthancServer/Sources/OrthancWebDav.cpp Mon Apr 24 18:13:48 2023 +0200 +++ b/OrthancServer/Sources/OrthancWebDav.cpp Fri Apr 28 10:42:27 2023 +0200 @@ -261,7 +261,7 @@ Json::Value resource; std::set<DicomTag> emptyRequestedTags; // not supported for webdav - if (context_.ExpandResource(resource, publicId, level_, DicomToJsonFormat_Human, emptyRequestedTags, true /* allowStorageAccess */)) + if (context_.ExpandResource(resource, publicId, level_, DicomToJsonFormat_Human, emptyRequestedTags, ExpandResourceFlags_Default, true /* allowStorageAccess */)) { if (success_) {
--- a/OrthancServer/Sources/ServerContext.cpp Mon Apr 24 18:13:48 2023 +0200 +++ b/OrthancServer/Sources/ServerContext.cpp Fri Apr 28 10:42:27 2023 +0200 @@ -1987,7 +1987,8 @@ static void SerializeExpandedResource(Json::Value& target, const ExpandedResource& resource, DicomToJsonFormat format, - const std::set<DicomTag>& requestedTags) + const std::set<DicomTag>& requestedTags, + ExpandResourceFlags expandFlags) { target = Json::objectValue; @@ -2015,42 +2016,45 @@ throw OrthancException(ErrorCode_InternalError); } - switch (resource.GetLevel()) + if (expandFlags & ExpandResourceFlags_IncludeChildren) { - case ResourceType_Patient: - case ResourceType_Study: - case ResourceType_Series: + switch (resource.GetLevel()) { - Json::Value c = Json::arrayValue; + case ResourceType_Patient: + case ResourceType_Study: + case ResourceType_Series: + { + Json::Value c = Json::arrayValue; + + for (std::list<std::string>::const_iterator + it = resource.childrenIds_.begin(); it != resource.childrenIds_.end(); ++it) + { + c.append(*it); + } - for (std::list<std::string>::const_iterator - it = resource.childrenIds_.begin(); it != resource.childrenIds_.end(); ++it) - { - c.append(*it); + if (resource.GetLevel() == ResourceType_Patient) + { + target["Studies"] = c; + } + else if (resource.GetLevel() == ResourceType_Study) + { + target["Series"] = c; + } + else + { + target["Instances"] = c; + } + break; } - if (resource.GetLevel() == ResourceType_Patient) - { - target["Studies"] = c; - } - else if (resource.GetLevel() == ResourceType_Study) - { - target["Series"] = c; - } - else - { - target["Instances"] = c; - } - break; + case ResourceType_Instance: + break; + + default: + throw OrthancException(ErrorCode_InternalError); } - - case ResourceType_Instance: - break; - - default: - throw OrthancException(ErrorCode_InternalError); } - + switch (resource.GetLevel()) { case ResourceType_Patient: @@ -2113,26 +2117,29 @@ } // serialize tags + if (expandFlags & ExpandResourceFlags_IncludeMainDicomTags) + { + static const char* const MAIN_DICOM_TAGS = "MainDicomTags"; + static const char* const PATIENT_MAIN_DICOM_TAGS = "PatientMainDicomTags"; - static const char* const MAIN_DICOM_TAGS = "MainDicomTags"; - static const char* const PATIENT_MAIN_DICOM_TAGS = "PatientMainDicomTags"; - - DicomMap mainDicomTags; - resource.GetMainDicomTags().ExtractResourceInformation(mainDicomTags, resource.GetLevel()); + DicomMap mainDicomTags; + resource.GetMainDicomTags().ExtractResourceInformation(mainDicomTags, resource.GetLevel()); - target[MAIN_DICOM_TAGS] = Json::objectValue; - FromDcmtkBridge::ToJson(target[MAIN_DICOM_TAGS], mainDicomTags, format); - - if (resource.GetLevel() == ResourceType_Study) - { - DicomMap patientMainDicomTags; - resource.GetMainDicomTags().ExtractPatientInformation(patientMainDicomTags); + target[MAIN_DICOM_TAGS] = Json::objectValue; + FromDcmtkBridge::ToJson(target[MAIN_DICOM_TAGS], mainDicomTags, format); + + if (resource.GetLevel() == ResourceType_Study) + { + DicomMap patientMainDicomTags; + resource.GetMainDicomTags().ExtractPatientInformation(patientMainDicomTags); - target[PATIENT_MAIN_DICOM_TAGS] = Json::objectValue; - FromDcmtkBridge::ToJson(target[PATIENT_MAIN_DICOM_TAGS], patientMainDicomTags, format); + target[PATIENT_MAIN_DICOM_TAGS] = Json::objectValue; + FromDcmtkBridge::ToJson(target[PATIENT_MAIN_DICOM_TAGS], patientMainDicomTags, format); + } } - if (requestedTags.size() > 0) + if ((expandFlags & ExpandResourceFlags_IncludeRequestedTags) + && requestedTags.size() > 0) { static const char* const REQUESTED_TAGS = "RequestedTags"; @@ -2144,6 +2151,7 @@ } + if (expandFlags & ExpandResourceFlags_IncludeLabels) { Json::Value labels = Json::arrayValue; @@ -2378,13 +2386,14 @@ ResourceType level, DicomToJsonFormat format, const std::set<DicomTag>& requestedTags, + ExpandResourceFlags expandFlags, bool allowStorageAccess) { std::string unusedInstanceId; Json::Value* unusedDicomAsJson = NULL; DicomMap unusedMainDicomTags; - return ExpandResource(target, publicId, unusedMainDicomTags, unusedInstanceId, unusedDicomAsJson, level, format, requestedTags, allowStorageAccess); + return ExpandResource(target, publicId, unusedMainDicomTags, unusedInstanceId, unusedDicomAsJson, level, format, requestedTags, expandFlags, allowStorageAccess); } bool ServerContext::ExpandResource(Json::Value& target, @@ -2395,13 +2404,14 @@ ResourceType level, DicomToJsonFormat format, const std::set<DicomTag>& requestedTags, + ExpandResourceFlags expandFlags, bool allowStorageAccess) { ExpandedResource resource; - if (ExpandResource(resource, publicId, mainDicomTags, instanceId, dicomAsJson, level, requestedTags, ExpandResourceFlags_Default, allowStorageAccess)) + if (ExpandResource(resource, publicId, mainDicomTags, instanceId, dicomAsJson, level, requestedTags, expandFlags, allowStorageAccess)) { - SerializeExpandedResource(target, resource, format, requestedTags); + SerializeExpandedResource(target, resource, format, requestedTags, expandFlags); return true; }
--- a/OrthancServer/Sources/ServerContext.h Mon Apr 24 18:13:48 2023 +0200 +++ b/OrthancServer/Sources/ServerContext.h Fri Apr 28 10:42:27 2023 +0200 @@ -566,6 +566,7 @@ ResourceType level, DicomToJsonFormat format, const std::set<DicomTag>& requestedTags, + ExpandResourceFlags expandFlags, bool allowStorageAccess); bool ExpandResource(Json::Value& target, @@ -576,6 +577,7 @@ ResourceType level, DicomToJsonFormat format, const std::set<DicomTag>& requestedTags, + ExpandResourceFlags expandFlags, bool allowStorageAccess); bool ExpandResource(ExpandedResource& target,