5608
|
1 /**
|
|
2 * Orthanc - A Lightweight, RESTful DICOM Store
|
|
3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
|
|
4 * Department, University Hospital of Liege, Belgium
|
|
5 * Copyright (C) 2017-2024 Osimis S.A., Belgium
|
|
6 * Copyright (C) 2021-2024 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
|
|
7 *
|
|
8 * This program is free software: you can redistribute it and/or
|
|
9 * modify it under the terms of the GNU General Public License as
|
|
10 * published by the Free Software Foundation, either version 3 of the
|
|
11 * License, or (at your option) any later version.
|
|
12 *
|
|
13 * This program is distributed in the hope that it will be useful, but
|
|
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
16 * General Public License for more details.
|
|
17 *
|
|
18 * You should have received a copy of the GNU General Public License
|
|
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
20 **/
|
|
21
|
|
22
|
|
23 #include "PrecompiledHeadersServer.h"
|
|
24 #include "ResourceFinder.h"
|
|
25
|
|
26 #include "../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h"
|
|
27 #include "../../OrthancFramework/Sources/OrthancException.h"
|
|
28 #include "../../OrthancFramework/Sources/SerializationToolbox.h"
|
|
29 #include "ServerContext.h"
|
|
30 #include "ServerIndex.h"
|
|
31
|
|
32
|
|
33 namespace Orthanc
|
|
34 {
|
|
35 SeriesStatus ResourceFinder::GetSeriesStatus(uint32_t& expectedNumberOfInstances,
|
|
36 const FindResponse::Resource& resource) const
|
|
37 {
|
|
38 if (request_.GetLevel() != ResourceType_Series)
|
|
39 {
|
|
40 throw OrthancException(ErrorCode_BadParameterType);
|
|
41 }
|
|
42
|
|
43 std::string s;
|
|
44 if (!resource.LookupMetadata(s, ResourceType_Series, MetadataType_Series_ExpectedNumberOfInstances) ||
|
|
45 !SerializationToolbox::ParseUnsignedInteger32(expectedNumberOfInstances, s))
|
|
46 {
|
|
47 return SeriesStatus_Unknown;
|
|
48 }
|
|
49
|
|
50 std::list<std::string> values;
|
|
51 if (!resource.LookupChildrenMetadata(values, MetadataType_Instance_IndexInSeries))
|
|
52 {
|
|
53 throw OrthancException(ErrorCode_BadSequenceOfCalls);
|
|
54 }
|
|
55
|
|
56 std::set<int64_t> instances;
|
|
57
|
|
58 for (std::list<std::string>::const_iterator
|
|
59 it = values.begin(); it != values.end(); ++it)
|
|
60 {
|
|
61 int64_t index;
|
|
62
|
|
63 if (!SerializationToolbox::ParseInteger64(index, *it))
|
|
64 {
|
|
65 return SeriesStatus_Unknown;
|
|
66 }
|
|
67
|
|
68 if (index <= 0 ||
|
|
69 index > static_cast<int64_t>(expectedNumberOfInstances))
|
|
70 {
|
|
71 // Out-of-range instance index
|
|
72 return SeriesStatus_Inconsistent;
|
|
73 }
|
|
74
|
|
75 if (instances.find(index) != instances.end())
|
|
76 {
|
|
77 // Twice the same instance index
|
|
78 return SeriesStatus_Inconsistent;
|
|
79 }
|
|
80
|
|
81 instances.insert(index);
|
|
82 }
|
|
83
|
|
84 if (instances.size() == static_cast<size_t>(expectedNumberOfInstances))
|
|
85 {
|
|
86 return SeriesStatus_Complete;
|
|
87 }
|
|
88 else
|
|
89 {
|
|
90 return SeriesStatus_Missing;
|
|
91 }
|
|
92 }
|
|
93
|
|
94
|
|
95 void ResourceFinder::Expand(Json::Value& target,
|
|
96 const FindResponse::Resource& resource,
|
|
97 ServerIndex& index) const
|
|
98 {
|
|
99 /**
|
|
100 * This method closely follows "SerializeExpandedResource()" in
|
|
101 * "ServerContext.cpp" from Orthanc 1.12.3.
|
|
102 **/
|
|
103
|
|
104 if (resource.GetLevel() != request_.GetLevel())
|
|
105 {
|
|
106 throw OrthancException(ErrorCode_InternalError);
|
|
107 }
|
|
108
|
|
109 if (!requestedTags_.empty())
|
|
110 {
|
|
111 throw OrthancException(ErrorCode_NotImplemented);
|
|
112 }
|
|
113
|
|
114 target = Json::objectValue;
|
|
115
|
|
116 target["Type"] = GetResourceTypeText(resource.GetLevel(), false, true);
|
|
117 target["ID"] = resource.GetIdentifier();
|
|
118
|
|
119 switch (resource.GetLevel())
|
|
120 {
|
|
121 case ResourceType_Patient:
|
|
122 break;
|
|
123
|
|
124 case ResourceType_Study:
|
|
125 target["ParentPatient"] = resource.GetParentIdentifier();
|
|
126 break;
|
|
127
|
|
128 case ResourceType_Series:
|
|
129 target["ParentStudy"] = resource.GetParentIdentifier();
|
|
130 break;
|
|
131
|
|
132 case ResourceType_Instance:
|
|
133 target["ParentSeries"] = resource.GetParentIdentifier();
|
|
134 break;
|
|
135
|
|
136 default:
|
|
137 throw OrthancException(ErrorCode_InternalError);
|
|
138 }
|
|
139
|
|
140 if (resource.GetLevel() != ResourceType_Instance)
|
|
141 {
|
|
142 const std::set<std::string>& children = resource.GetChildrenIdentifiers();
|
|
143
|
|
144 Json::Value c = Json::arrayValue;
|
|
145 for (std::set<std::string>::const_iterator
|
|
146 it = children.begin(); it != children.end(); ++it)
|
|
147 {
|
|
148 c.append(*it);
|
|
149 }
|
|
150
|
|
151 switch (resource.GetLevel())
|
|
152 {
|
|
153 case ResourceType_Patient:
|
|
154 target["Studies"] = c;
|
|
155 break;
|
|
156
|
|
157 case ResourceType_Study:
|
|
158 target["Series"] = c;
|
|
159 break;
|
|
160
|
|
161 case ResourceType_Series:
|
|
162 target["Instances"] = c;
|
|
163 break;
|
|
164
|
|
165 default:
|
|
166 throw OrthancException(ErrorCode_InternalError);
|
|
167 }
|
|
168 }
|
|
169
|
|
170 switch (resource.GetLevel())
|
|
171 {
|
|
172 case ResourceType_Patient:
|
|
173 case ResourceType_Study:
|
|
174 break;
|
|
175
|
|
176 case ResourceType_Series:
|
|
177 {
|
|
178 uint32_t expectedNumberOfInstances;
|
|
179 SeriesStatus status = GetSeriesStatus(expectedNumberOfInstances, resource);
|
|
180
|
|
181 target["Status"] = EnumerationToString(status);
|
|
182
|
|
183 static const char* EXPECTED_NUMBER_OF_INSTANCES = "ExpectedNumberOfInstances";
|
|
184
|
|
185 if (status == SeriesStatus_Unknown)
|
|
186 {
|
|
187 target[EXPECTED_NUMBER_OF_INSTANCES] = Json::nullValue;
|
|
188 }
|
|
189 else
|
|
190 {
|
|
191 target[EXPECTED_NUMBER_OF_INSTANCES] = expectedNumberOfInstances;
|
|
192 }
|
|
193
|
|
194 break;
|
|
195 }
|
|
196
|
|
197 case ResourceType_Instance:
|
|
198 {
|
|
199 FileInfo info;
|
|
200 if (resource.LookupAttachment(info, FileContentType_Dicom))
|
|
201 {
|
|
202 target["FileSize"] = static_cast<Json::UInt64>(info.GetUncompressedSize());
|
|
203 target["FileUuid"] = info.GetUuid();
|
|
204 }
|
|
205 else
|
|
206 {
|
|
207 throw OrthancException(ErrorCode_InternalError);
|
|
208 }
|
|
209
|
|
210 static const char* INDEX_IN_SERIES = "IndexInSeries";
|
|
211
|
|
212 std::string s;
|
|
213 uint32_t index;
|
|
214 if (resource.LookupMetadata(s, ResourceType_Instance, MetadataType_Instance_IndexInSeries) &&
|
|
215 SerializationToolbox::ParseUnsignedInteger32(index, s))
|
|
216 {
|
|
217 target[INDEX_IN_SERIES] = index;
|
|
218 }
|
|
219 else
|
|
220 {
|
|
221 target[INDEX_IN_SERIES] = Json::nullValue;
|
|
222 }
|
|
223
|
|
224 break;
|
|
225 }
|
|
226
|
|
227 default:
|
|
228 throw OrthancException(ErrorCode_InternalError);
|
|
229 }
|
|
230
|
|
231 std::string s;
|
|
232 if (resource.LookupMetadata(s, resource.GetLevel(), MetadataType_AnonymizedFrom))
|
|
233 {
|
|
234 target["AnonymizedFrom"] = s;
|
|
235 }
|
|
236
|
|
237 if (resource.LookupMetadata(s, resource.GetLevel(), MetadataType_ModifiedFrom))
|
|
238 {
|
|
239 target["ModifiedFrom"] = s;
|
|
240 }
|
|
241
|
|
242 if (resource.GetLevel() == ResourceType_Patient ||
|
|
243 resource.GetLevel() == ResourceType_Study ||
|
|
244 resource.GetLevel() == ResourceType_Series)
|
|
245 {
|
|
246 target["IsStable"] = !index.IsUnstableResource(resource.GetLevel(), resource.GetInternalId());
|
|
247
|
|
248 if (resource.LookupMetadata(s, resource.GetLevel(), MetadataType_LastUpdate))
|
|
249 {
|
|
250 target["LastUpdate"] = s;
|
|
251 }
|
|
252 }
|
|
253
|
|
254 {
|
|
255 static const char* const MAIN_DICOM_TAGS = "MainDicomTags";
|
|
256 static const char* const PATIENT_MAIN_DICOM_TAGS = "PatientMainDicomTags";
|
|
257
|
|
258 // TODO-FIND : (expandFlags & ExpandResourceFlags_IncludeMainDicomTags)
|
|
259 DicomMap allMainDicomTags;
|
|
260 resource.GetMainDicomTags(allMainDicomTags, resource.GetLevel());
|
|
261
|
|
262 DicomMap levelMainDicomTags;
|
|
263 allMainDicomTags.ExtractResourceInformation(levelMainDicomTags, resource.GetLevel());
|
|
264
|
|
265 target[MAIN_DICOM_TAGS] = Json::objectValue;
|
|
266 FromDcmtkBridge::ToJson(target[MAIN_DICOM_TAGS], levelMainDicomTags, format_);
|
|
267
|
|
268 if (resource.GetLevel() == ResourceType_Study)
|
|
269 {
|
|
270 DicomMap patientMainDicomTags;
|
|
271 allMainDicomTags.ExtractPatientInformation(patientMainDicomTags);
|
|
272
|
|
273 target[PATIENT_MAIN_DICOM_TAGS] = Json::objectValue;
|
|
274 FromDcmtkBridge::ToJson(target[PATIENT_MAIN_DICOM_TAGS], patientMainDicomTags, format_);
|
|
275 }
|
|
276
|
|
277 /*
|
|
278 TODO-FIND
|
|
279
|
|
280 if (!requestedTags_.empty())
|
|
281 {
|
|
282 static const char* const REQUESTED_TAGS = "RequestedTags";
|
|
283
|
|
284 DicomMap tags;
|
|
285 resource.GetMainDicomTags().ExtractTags(tags, requestedTags);
|
|
286
|
|
287 target[REQUESTED_TAGS] = Json::objectValue;
|
|
288 FromDcmtkBridge::ToJson(target[REQUESTED_TAGS], tags, format);
|
|
289 }
|
|
290 */
|
|
291 }
|
|
292
|
|
293 {
|
|
294 Json::Value labels = Json::arrayValue;
|
|
295
|
|
296 for (std::set<std::string>::const_iterator
|
|
297 it = resource.GetLabels().begin(); it != resource.GetLabels().end(); ++it)
|
|
298 {
|
|
299 labels.append(*it);
|
|
300 }
|
|
301
|
|
302 target["Labels"] = labels;
|
|
303 }
|
|
304
|
|
305 if (includeAllMetadata_) // new in Orthanc 1.12.4
|
|
306 {
|
|
307 const std::map<MetadataType, std::string>& m = resource.GetMetadata(resource.GetLevel());
|
|
308
|
|
309 Json::Value metadata = Json::objectValue;
|
|
310
|
|
311 for (std::map<MetadataType, std::string>::const_iterator it = m.begin(); it != m.end(); ++it)
|
|
312 {
|
|
313 metadata[EnumerationToString(it->first)] = it->second;
|
|
314 }
|
|
315
|
|
316 target["Metadata"] = metadata;
|
|
317 }
|
|
318 }
|
|
319
|
|
320
|
|
321 ResourceFinder::ResourceFinder(ResourceType level,
|
|
322 bool expand) :
|
|
323 request_(level),
|
|
324 expand_(expand),
|
|
325 format_(DicomToJsonFormat_Human),
|
|
326 includeAllMetadata_(false)
|
|
327 {
|
|
328 if (expand)
|
|
329 {
|
|
330 request_.SetRetrieveMainDicomTags(level, true);
|
|
331 request_.SetRetrieveMetadata(level, true);
|
|
332 request_.SetRetrieveLabels(true);
|
|
333
|
|
334 if (level == ResourceType_Series)
|
|
335 {
|
|
336 request_.AddRetrieveChildrenMetadata(MetadataType_Instance_IndexInSeries); // required for the SeriesStatus
|
|
337 }
|
|
338
|
|
339 if (level == ResourceType_Instance)
|
|
340 {
|
|
341 request_.SetRetrieveAttachments(true); // for FileSize & FileUuid
|
|
342 }
|
|
343 else
|
|
344 {
|
|
345 request_.SetRetrieveChildrenIdentifiers(true);
|
|
346 }
|
|
347
|
|
348 if (level != ResourceType_Patient)
|
|
349 {
|
|
350 request_.SetRetrieveParentIdentifier(true);
|
|
351 }
|
|
352 }
|
|
353 }
|
|
354
|
|
355
|
|
356 void ResourceFinder::Execute(Json::Value& target,
|
|
357 ServerContext& context)
|
|
358 {
|
|
359 FindResponse response;
|
|
360 context.GetIndex().ExecuteFind(response, request_);
|
|
361
|
|
362 target = Json::arrayValue;
|
|
363
|
|
364 if (expand_)
|
|
365 {
|
|
366 for (size_t i = 0; i < response.GetSize(); i++)
|
|
367 {
|
|
368 Json::Value item;
|
|
369 Expand(item, response.GetResource(i), context.GetIndex());
|
|
370 target.append(item);
|
|
371 }
|
|
372 }
|
|
373 else
|
|
374 {
|
|
375 for (size_t i = 0; i < response.GetSize(); i++)
|
|
376 {
|
|
377 target.append(response.GetResource(i).GetIdentifier());
|
|
378 }
|
|
379 }
|
|
380 }
|
|
381 }
|