comparison OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp @ 4936:8422e4f99a18 more-tags

Handling RequestedTags in ExpandResource -> read parent main dicom tags if required. Not yet getting missing tags from file. Integration tests ok
author Alain Mazy <am@osimis.io>
date Fri, 11 Mar 2022 17:38:16 +0100
parents acd3f72e2a21
children 3f9b9865c8cc
comparison
equal deleted inserted replaced
4935:acd3f72e2a21 4936:8422e4f99a18
128 static void AnswerListOfResources(RestApiOutput& output, 128 static void AnswerListOfResources(RestApiOutput& output,
129 ServerContext& context, 129 ServerContext& context,
130 const std::list<std::string>& resources, 130 const std::list<std::string>& resources,
131 ResourceType level, 131 ResourceType level,
132 bool expand, 132 bool expand,
133 DicomToJsonFormat format) 133 DicomToJsonFormat format,
134 const std::set<DicomTag>& requestedTags)
134 { 135 {
135 Json::Value answer = Json::arrayValue; 136 Json::Value answer = Json::arrayValue;
136 137
137 for (std::list<std::string>::const_iterator 138 for (std::list<std::string>::const_iterator
138 resource = resources.begin(); resource != resources.end(); ++resource) 139 resource = resources.begin(); resource != resources.end(); ++resource)
139 { 140 {
140 if (expand) 141 if (expand)
141 { 142 {
142 Json::Value expanded; 143 Json::Value expanded;
143 if (context.ExpandResource(expanded, *resource, level, format)) 144 if (context.ExpandResource(expanded, *resource, level, format, requestedTags))
144 { 145 {
145 answer.append(expanded); 146 answer.append(expanded);
146 } 147 }
147 } 148 }
148 else 149 else
159 static void ListResources(RestApiGetCall& call) 160 static void ListResources(RestApiGetCall& call)
160 { 161 {
161 if (call.IsDocumentation()) 162 if (call.IsDocumentation())
162 { 163 {
163 OrthancRestApi::DocumentDicomFormat(call, DicomToJsonFormat_Human); 164 OrthancRestApi::DocumentDicomFormat(call, DicomToJsonFormat_Human);
165 OrthancRestApi::DocumentRequestedTags(call);
164 166
165 const std::string resources = GetResourceTypeText(resourceType, true /* plural */, false /* lower case */); 167 const std::string resources = GetResourceTypeText(resourceType, true /* plural */, false /* lower case */);
166 call.GetDocumentation() 168 call.GetDocumentation()
167 .SetTag(GetResourceTypeText(resourceType, true /* plural */, true /* upper case */)) 169 .SetTag(GetResourceTypeText(resourceType, true /* plural */, true /* upper case */))
168 .SetSummary("List the available " + resources) 170 .SetSummary("List the available " + resources)
180 ServerIndex& index = OrthancRestApi::GetIndex(call); 182 ServerIndex& index = OrthancRestApi::GetIndex(call);
181 ServerContext& context = OrthancRestApi::GetContext(call); 183 ServerContext& context = OrthancRestApi::GetContext(call);
182 184
183 std::list<std::string> result; 185 std::list<std::string> result;
184 186
187 std::set<DicomTag> requestedTags;
188 OrthancRestApi::GetRequestedTags(requestedTags, call);
189
185 if (call.HasArgument("limit") || 190 if (call.HasArgument("limit") ||
186 call.HasArgument("since")) 191 call.HasArgument("since"))
187 { 192 {
188 if (!call.HasArgument("limit")) 193 if (!call.HasArgument("limit"))
189 { 194 {
207 { 212 {
208 index.GetAllUuids(result, resourceType); 213 index.GetAllUuids(result, resourceType);
209 } 214 }
210 215
211 AnswerListOfResources(call.GetOutput(), context, result, resourceType, call.HasArgument("expand"), 216 AnswerListOfResources(call.GetOutput(), context, result, resourceType, call.HasArgument("expand"),
212 OrthancRestApi::GetDicomFormat(call, DicomToJsonFormat_Human)); 217 OrthancRestApi::GetDicomFormat(call, DicomToJsonFormat_Human),
218 requestedTags);
213 } 219 }
214 220
215 221
216 222
217 template <enum ResourceType resourceType> 223 template <enum ResourceType resourceType>
218 static void GetSingleResource(RestApiGetCall& call) 224 static void GetSingleResource(RestApiGetCall& call)
219 { 225 {
220 if (call.IsDocumentation()) 226 if (call.IsDocumentation())
221 { 227 {
222 OrthancRestApi::DocumentDicomFormat(call, DicomToJsonFormat_Human); 228 OrthancRestApi::DocumentDicomFormat(call, DicomToJsonFormat_Human);
229 OrthancRestApi::DocumentRequestedTags(call);
223 230
224 const std::string resource = GetResourceTypeText(resourceType, false /* plural */, false /* lower case */); 231 const std::string resource = GetResourceTypeText(resourceType, false /* plural */, false /* lower case */);
225 call.GetDocumentation() 232 call.GetDocumentation()
226 .SetTag(GetResourceTypeText(resourceType, true /* plural */, true /* upper case */)) 233 .SetTag(GetResourceTypeText(resourceType, true /* plural */, true /* upper case */))
227 .SetSummary("Get information about some " + resource) 234 .SetSummary("Get information about some " + resource)
228 .SetDescription("Get detailed information about the DICOM " + resource + " whose Orthanc identifier is provided in the URL") 235 .SetDescription("Get detailed information about the DICOM " + resource + " whose Orthanc identifier is provided in the URL")
229 .SetUriArgument("id", "Orthanc identifier of the " + resource + " of interest") 236 .SetUriArgument("id", "Orthanc identifier of the " + resource + " of interest")
230 .SetHttpGetArgument("requestedTags", RestApiCallDocumentation::Type_String,
231 "If present, list the DICOM Tags you want to list in the response. This argument is a semi-column separated list "
232 "of DICOM Tags identifiers; e.g: 'requestedTags=0010,0010;PatientBirthDate'. "
233 "The tags requested tags are returned in the 'RequestedTags' field in the response. "
234 "Note that, if you are requesting tags that are not listed in the Main Dicom Tags stored in DB, building the response "
235 "might be slow since Orthanc will need to access the DICOM files. If not specified, Orthanc will return ", false)
236 .AddAnswerType(MimeType_Json, "Information about the DICOM " + resource) 237 .AddAnswerType(MimeType_Json, "Information about the DICOM " + resource)
237 .SetHttpGetSample(GetDocumentationSampleResource(resourceType), true); 238 .SetHttpGetSample(GetDocumentationSampleResource(resourceType), true);
238 return; 239 return;
239 } 240 }
240 241
241 const DicomToJsonFormat format = OrthancRestApi::GetDicomFormat(call, DicomToJsonFormat_Human); 242 const DicomToJsonFormat format = OrthancRestApi::GetDicomFormat(call, DicomToJsonFormat_Human);
242 243
243 std::set<DicomTag> responseTags; 244 std::set<DicomTag> requestedTags;
244 if (call.HasArgument("requestedTags")) 245 OrthancRestApi::GetRequestedTags(requestedTags, call);
245 {
246 try
247 {
248 FromDcmtkBridge::ParseListOfTags(responseTags, call.GetArgument("requestedTags", ""));
249 }
250 catch (OrthancException& ex)
251 {
252 throw OrthancException(ErrorCode_BadRequest, std::string("Invalid requestedTags argument: ") + ex.What() + " " + ex.GetDetails());
253 }
254 }
255 246
256 Json::Value json; 247 Json::Value json;
257 if (OrthancRestApi::GetContext(call).ExpandResource( 248 if (OrthancRestApi::GetContext(call).ExpandResource(
258 json, call.GetUriComponent("id", ""), resourceType, format)) // TODO, requestedTags)) 249 json, call.GetUriComponent("id", ""), resourceType, format, requestedTags))
259 { 250 {
260 call.GetOutput().AnswerJson(json); 251 call.GetOutput().AnswerJson(json);
261 } 252 }
262 } 253 }
263 254
2865 } 2856 }
2866 2857
2867 void Answer(RestApiOutput& output, 2858 void Answer(RestApiOutput& output,
2868 ServerContext& context, 2859 ServerContext& context,
2869 ResourceType level, 2860 ResourceType level,
2870 bool expand) const 2861 bool expand,
2871 { 2862 const std::set<DicomTag>& requestedTags) const
2872 AnswerListOfResources(output, context, resources_, level, expand, format_); 2863 {
2864 AnswerListOfResources(output, context, resources_, level, expand, format_, requestedTags);
2873 } 2865 }
2874 }; 2866 };
2875 } 2867 }
2876 2868
2877 2869
2909 "A list of DICOM tags to include in the response (applicable only if \"Expand\" is set to true). " 2901 "A list of DICOM tags to include in the response (applicable only if \"Expand\" is set to true). "
2910 "The tags requested tags are returned in the 'RequestedTags' field in the response. " 2902 "The tags requested tags are returned in the 'RequestedTags' field in the response. "
2911 "Note that, if you are requesting tags that are not listed in the Main Dicom Tags stored in DB, building the response " 2903 "Note that, if you are requesting tags that are not listed in the Main Dicom Tags stored in DB, building the response "
2912 "might be slow since Orthanc will need to access the DICOM files. If not specified, Orthanc will return " 2904 "might be slow since Orthanc will need to access the DICOM files. If not specified, Orthanc will return "
2913 "all Main Dicom Tags to keep backward compatibility with Orthanc prior to 1.11.0.", false) 2905 "all Main Dicom Tags to keep backward compatibility with Orthanc prior to 1.11.0.", false)
2914
2915 .SetRequestField(KEY_QUERY, RestApiCallDocumentation::Type_JsonObject, 2906 .SetRequestField(KEY_QUERY, RestApiCallDocumentation::Type_JsonObject,
2916 "Associative array containing the filter on the values of the DICOM tags", true) 2907 "Associative array containing the filter on the values of the DICOM tags", true)
2917 .AddAnswerType(MimeType_Json, "JSON array containing either the Orthanc identifiers, or detailed information " 2908 .AddAnswerType(MimeType_Json, "JSON array containing either the Orthanc identifiers, or detailed information "
2918 "about the reported resources (if `Expand` argument is `true`)"); 2909 "about the reported resources (if `Expand` argument is `true`)");
2919 return; 2910 return;
2920 } 2911 }
2921 2912
2922 ServerContext& context = OrthancRestApi::GetContext(call); 2913 ServerContext& context = OrthancRestApi::GetContext(call);
2923 2914
2924 // MORE_TAGS: TODO: handle RequestedTags
2925
2926 Json::Value request; 2915 Json::Value request;
2927 if (!call.ParseJsonRequest(request) || 2916 if (!call.ParseJsonRequest(request) ||
2928 request.type() != Json::objectValue) 2917 request.type() != Json::objectValue)
2929 { 2918 {
2930 throw OrthancException(ErrorCode_BadRequest, 2919 throw OrthancException(ErrorCode_BadRequest,
2958 request[KEY_SINCE].type() != Json::intValue) 2947 request[KEY_SINCE].type() != Json::intValue)
2959 { 2948 {
2960 throw OrthancException(ErrorCode_BadRequest, 2949 throw OrthancException(ErrorCode_BadRequest,
2961 "Field \"" + std::string(KEY_SINCE) + "\" should be an integer"); 2950 "Field \"" + std::string(KEY_SINCE) + "\" should be an integer");
2962 } 2951 }
2952 else if (request.isMember(KEY_REQUESTED_TAGS) &&
2953 request[KEY_REQUESTED_TAGS].type() != Json::arrayValue)
2954 {
2955 throw OrthancException(ErrorCode_BadRequest,
2956 "Field \"" + std::string(KEY_REQUESTED_TAGS) + "\" should be an array");
2957 }
2963 else 2958 else
2964 { 2959 {
2965 bool expand = false; 2960 bool expand = false;
2966 if (request.isMember(KEY_EXPAND)) 2961 if (request.isMember(KEY_EXPAND))
2967 { 2962 {
2996 throw OrthancException(ErrorCode_ParameterOutOfRange, 2991 throw OrthancException(ErrorCode_ParameterOutOfRange,
2997 "Field \"" + std::string(KEY_SINCE) + "\" should be a positive integer"); 2992 "Field \"" + std::string(KEY_SINCE) + "\" should be a positive integer");
2998 } 2993 }
2999 2994
3000 since = static_cast<size_t>(tmp); 2995 since = static_cast<size_t>(tmp);
2996 }
2997
2998 std::set<DicomTag> requestedTags;
2999
3000 if (request.isMember(KEY_REQUESTED_TAGS))
3001 {
3002 FromDcmtkBridge::ParseListOfTags(requestedTags, request[KEY_REQUESTED_TAGS]);
3001 } 3003 }
3002 3004
3003 ResourceType level = StringToResourceType(request[KEY_LEVEL].asCString()); 3005 ResourceType level = StringToResourceType(request[KEY_LEVEL].asCString());
3004 3006
3005 DatabaseLookup query; 3007 DatabaseLookup query;
3025 } 3027 }
3026 } 3028 }
3027 3029
3028 FindVisitor visitor(OrthancRestApi::GetDicomFormat(request, DicomToJsonFormat_Human)); 3030 FindVisitor visitor(OrthancRestApi::GetDicomFormat(request, DicomToJsonFormat_Human));
3029 context.Apply(visitor, query, level, since, limit); 3031 context.Apply(visitor, query, level, since, limit);
3030 visitor.Answer(call.GetOutput(), context, level, expand); 3032 visitor.Answer(call.GetOutput(), context, level, expand, requestedTags);
3031 } 3033 }
3032 } 3034 }
3033 3035
3034 3036
3035 template <enum ResourceType start, 3037 template <enum ResourceType start,
3037 static void GetChildResources(RestApiGetCall& call) 3039 static void GetChildResources(RestApiGetCall& call)
3038 { 3040 {
3039 if (call.IsDocumentation()) 3041 if (call.IsDocumentation())
3040 { 3042 {
3041 OrthancRestApi::DocumentDicomFormat(call, DicomToJsonFormat_Human); 3043 OrthancRestApi::DocumentDicomFormat(call, DicomToJsonFormat_Human);
3044 OrthancRestApi::DocumentRequestedTags(call);
3042 3045
3043 const std::string children = GetResourceTypeText(end, true /* plural */, false /* lower case */); 3046 const std::string children = GetResourceTypeText(end, true /* plural */, false /* lower case */);
3044 const std::string resource = GetResourceTypeText(start, false /* plural */, false /* lower case */); 3047 const std::string resource = GetResourceTypeText(start, false /* plural */, false /* lower case */);
3045 call.GetDocumentation() 3048 call.GetDocumentation()
3046 .SetTag(GetResourceTypeText(start, true /* plural */, true /* upper case */)) 3049 .SetTag(GetResourceTypeText(start, true /* plural */, true /* upper case */))
3053 return; 3056 return;
3054 } 3057 }
3055 3058
3056 ServerIndex& index = OrthancRestApi::GetIndex(call); 3059 ServerIndex& index = OrthancRestApi::GetIndex(call);
3057 3060
3061 std::set<DicomTag> requestedTags;
3062 OrthancRestApi::GetRequestedTags(requestedTags, call);
3063
3058 std::list<std::string> a, b, c; 3064 std::list<std::string> a, b, c;
3059 a.push_back(call.GetUriComponent("id", "")); 3065 a.push_back(call.GetUriComponent("id", ""));
3060 3066
3061 ResourceType type = start; 3067 ResourceType type = start;
3062 while (type != end) 3068 while (type != end)
3082 3088
3083 for (std::list<std::string>::const_iterator 3089 for (std::list<std::string>::const_iterator
3084 it = a.begin(); it != a.end(); ++it) 3090 it = a.begin(); it != a.end(); ++it)
3085 { 3091 {
3086 Json::Value resource; 3092 Json::Value resource;
3087 if (OrthancRestApi::GetContext(call).ExpandResource(resource, *it, end, format)) 3093 if (OrthancRestApi::GetContext(call).ExpandResource(resource, *it, end, format, requestedTags))
3088 { 3094 {
3089 result.append(resource); 3095 result.append(resource);
3090 } 3096 }
3091 } 3097 }
3092 3098
3160 assert(start > end); 3166 assert(start > end);
3161 3167
3162 if (call.IsDocumentation()) 3168 if (call.IsDocumentation())
3163 { 3169 {
3164 OrthancRestApi::DocumentDicomFormat(call, DicomToJsonFormat_Human); 3170 OrthancRestApi::DocumentDicomFormat(call, DicomToJsonFormat_Human);
3171 OrthancRestApi::DocumentRequestedTags(call);
3165 3172
3166 const std::string parent = GetResourceTypeText(end, false /* plural */, false /* lower case */); 3173 const std::string parent = GetResourceTypeText(end, false /* plural */, false /* lower case */);
3167 const std::string resource = GetResourceTypeText(start, false /* plural */, false /* lower case */); 3174 const std::string resource = GetResourceTypeText(start, false /* plural */, false /* lower case */);
3168 call.GetDocumentation() 3175 call.GetDocumentation()
3169 .SetTag(GetResourceTypeText(start, true /* plural */, true /* upper case */)) 3176 .SetTag(GetResourceTypeText(start, true /* plural */, true /* upper case */))
3175 .SetTruncatedJsonHttpGetSample(GetDocumentationSampleResource(start) + "/" + parent, 10); 3182 .SetTruncatedJsonHttpGetSample(GetDocumentationSampleResource(start) + "/" + parent, 10);
3176 return; 3183 return;
3177 } 3184 }
3178 3185
3179 ServerIndex& index = OrthancRestApi::GetIndex(call); 3186 ServerIndex& index = OrthancRestApi::GetIndex(call);
3180 3187
3188 std::set<DicomTag> requestedTags;
3189 OrthancRestApi::GetRequestedTags(requestedTags, call);
3190
3181 std::string current = call.GetUriComponent("id", ""); 3191 std::string current = call.GetUriComponent("id", "");
3182 ResourceType currentType = start; 3192 ResourceType currentType = start;
3183 while (currentType > end) 3193 while (currentType > end)
3184 { 3194 {
3185 std::string parent; 3195 std::string parent;
3197 assert(currentType == end); 3207 assert(currentType == end);
3198 3208
3199 const DicomToJsonFormat format = OrthancRestApi::GetDicomFormat(call, DicomToJsonFormat_Human); 3209 const DicomToJsonFormat format = OrthancRestApi::GetDicomFormat(call, DicomToJsonFormat_Human);
3200 3210
3201 Json::Value resource; 3211 Json::Value resource;
3202 if (OrthancRestApi::GetContext(call).ExpandResource(resource, current, end, format)) 3212 if (OrthancRestApi::GetContext(call).ExpandResource(resource, current, end, format, requestedTags))
3203 { 3213 {
3204 call.GetOutput().AnswerJson(resource); 3214 call.GetOutput().AnswerJson(resource);
3205 } 3215 }
3206 } 3216 }
3207 3217
3436 3446
3437 static void BulkContent(RestApiPostCall& call) 3447 static void BulkContent(RestApiPostCall& call)
3438 { 3448 {
3439 static const char* const LEVEL = "Level"; 3449 static const char* const LEVEL = "Level";
3440 static const char* const METADATA = "Metadata"; 3450 static const char* const METADATA = "Metadata";
3441 3451
3442 if (call.IsDocumentation()) 3452 if (call.IsDocumentation())
3443 { 3453 {
3444 OrthancRestApi::DocumentDicomFormat(call, DicomToJsonFormat_Human); 3454 OrthancRestApi::DocumentDicomFormat(call, DicomToJsonFormat_Human);
3445 3455
3446 call.GetDocumentation() 3456 call.GetDocumentation()
3448 .SetSummary("Describe a set of resources") 3458 .SetSummary("Describe a set of resources")
3449 .SetRequestField("Resources", RestApiCallDocumentation::Type_JsonListOfStrings, 3459 .SetRequestField("Resources", RestApiCallDocumentation::Type_JsonListOfStrings,
3450 "List of the Orthanc identifiers of the patients/studies/series/instances of interest.", true) 3460 "List of the Orthanc identifiers of the patients/studies/series/instances of interest.", true)
3451 .SetRequestField(LEVEL, RestApiCallDocumentation::Type_String, 3461 .SetRequestField(LEVEL, RestApiCallDocumentation::Type_String,
3452 "This optional argument specifies the level of interest (can be `Patient`, `Study`, `Series` or " 3462 "This optional argument specifies the level of interest (can be `Patient`, `Study`, `Series` or "
3453 "`Instance`). Orthanc will loop over the items inside `Resources`, and explorer upward or " 3463 "`Instance`). Orthanc will loop over the items inside `Resources`, and explore upward or "
3454 "downward in the DICOM hierarchy in order to find the level of interest.", false) 3464 "downward in the DICOM hierarchy in order to find the level of interest.", false)
3455 .SetRequestField(METADATA, RestApiCallDocumentation::Type_Boolean, 3465 .SetRequestField(METADATA, RestApiCallDocumentation::Type_Boolean,
3456 "If set to `true` (default value), the metadata associated with the resources will also be retrieved.", false) 3466 "If set to `true` (default value), the metadata associated with the resources will also be retrieved.", false)
3457 .SetDescription("Get the content all the DICOM patients, studies, series or instances " 3467 .SetDescription("Get the content all the DICOM patients, studies, series or instances "
3458 "whose identifiers are provided in the `Resources` field, in one single call."); 3468 "whose identifiers are provided in the `Resources` field, in one single call.");
3569 3579
3570 for (std::set<std::string>::const_iterator 3580 for (std::set<std::string>::const_iterator
3571 it = interest.begin(); it != interest.end(); ++it) 3581 it = interest.begin(); it != interest.end(); ++it)
3572 { 3582 {
3573 Json::Value item; 3583 Json::Value item;
3574 if (OrthancRestApi::GetContext(call).ExpandResource(item, *it, level, format)) 3584 std::set<DicomTag> emptyRequestedTags; // not supported for bulk content
3585
3586 if (OrthancRestApi::GetContext(call).ExpandResource(item, *it, level, format, emptyRequestedTags))
3575 { 3587 {
3576 if (metadata) 3588 if (metadata)
3577 { 3589 {
3578 AddMetadata(item[METADATA], index, *it, level); 3590 AddMetadata(item[METADATA], index, *it, level);
3579 } 3591 }
3591 for (std::list<std::string>::const_iterator 3603 for (std::list<std::string>::const_iterator
3592 it = resources.begin(); it != resources.end(); ++it) 3604 it = resources.begin(); it != resources.end(); ++it)
3593 { 3605 {
3594 ResourceType level; 3606 ResourceType level;
3595 Json::Value item; 3607 Json::Value item;
3608 std::set<DicomTag> emptyRequestedTags; // not supported for bulk content
3609
3596 if (index.LookupResourceType(level, *it) && 3610 if (index.LookupResourceType(level, *it) &&
3597 OrthancRestApi::GetContext(call).ExpandResource(item, *it, level, format)) 3611 OrthancRestApi::GetContext(call).ExpandResource(item, *it, level, format, emptyRequestedTags))
3598 { 3612 {
3599 if (metadata) 3613 if (metadata)
3600 { 3614 {
3601 AddMetadata(item[METADATA], index, *it, level); 3615 AddMetadata(item[METADATA], index, *it, level);
3602 } 3616 }