comparison OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp @ 4044:d25f4c0fa160 framework

splitting code into OrthancFramework and OrthancServer
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 10 Jun 2020 20:30:34 +0200
parents OrthancServer/ServerJobs/ResourceModificationJob.cpp@5fe8c6d3212e
children 05b8fd21089c
comparison
equal deleted inserted replaced
4043:6c6239aec462 4044:d25f4c0fa160
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-2020 Osimis S.A., Belgium
6 *
7 * This program is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
11 *
12 * In addition, as a special exception, the copyright holders of this
13 * program give permission to link the code of its release with the
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it
15 * that use the same license as the "OpenSSL" library), and distribute
16 * the linked executables. You must obey the GNU General Public License
17 * in all respects for all of the code used other than "OpenSSL". If you
18 * modify file(s) with this exception, you may extend this exception to
19 * your version of the file(s), but you are not obligated to do so. If
20 * you do not wish to do so, delete this exception statement from your
21 * version. If you delete this exception statement from all source files
22 * in the program, then also delete it here.
23 *
24 * This program is distributed in the hope that it will be useful, but
25 * WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27 * General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program. If not, see <http://www.gnu.org/licenses/>.
31 **/
32
33
34 #include "../PrecompiledHeadersServer.h"
35 #include "ResourceModificationJob.h"
36
37 #include "../../Core/Logging.h"
38 #include "../../Core/SerializationToolbox.h"
39 #include "../ServerContext.h"
40
41 #include <dcmtk/dcmdata/dcfilefo.h>
42 #include <dcmtk/dcmdata/dcdeftag.h>
43 #include <cassert>
44
45 namespace Orthanc
46 {
47 class ResourceModificationJob::Output : public boost::noncopyable
48 {
49 private:
50 ResourceType level_;
51 bool isFirst_;
52 std::string id_;
53 std::string patientId_;
54
55 public:
56 Output(ResourceType level) :
57 level_(level),
58 isFirst_(true)
59 {
60 if (level_ != ResourceType_Patient &&
61 level_ != ResourceType_Study &&
62 level_ != ResourceType_Series)
63 {
64 throw OrthancException(ErrorCode_ParameterOutOfRange);
65 }
66 }
67
68 ResourceType GetLevel() const
69 {
70 return level_;
71 }
72
73
74 void Update(DicomInstanceHasher& hasher)
75 {
76 if (isFirst_)
77 {
78 switch (level_)
79 {
80 case ResourceType_Series:
81 id_ = hasher.HashSeries();
82 break;
83
84 case ResourceType_Study:
85 id_ = hasher.HashStudy();
86 break;
87
88 case ResourceType_Patient:
89 id_ = hasher.HashPatient();
90 break;
91
92 default:
93 throw OrthancException(ErrorCode_InternalError);
94 }
95
96 patientId_ = hasher.HashPatient();
97 isFirst_ = false;
98 }
99 }
100
101
102 bool Format(Json::Value& target)
103 {
104 if (isFirst_)
105 {
106 return false;
107 }
108 else
109 {
110 target = Json::objectValue;
111 target["Type"] = EnumerationToString(level_);
112 target["ID"] = id_;
113 target["Path"] = GetBasePath(level_, id_);
114 target["PatientID"] = patientId_;
115 return true;
116 }
117 }
118
119
120 bool GetIdentifier(std::string& id)
121 {
122 if (isFirst_)
123 {
124 return false;
125 }
126 else
127 {
128 id = id_;
129 return true;
130 }
131 }
132 };
133
134
135
136
137 bool ResourceModificationJob::HandleInstance(const std::string& instance)
138 {
139 if (modification_.get() == NULL ||
140 output_.get() == NULL)
141 {
142 throw OrthancException(ErrorCode_BadSequenceOfCalls,
143 "No modification was provided for this job");
144 }
145
146
147 LOG(INFO) << "Modifying instance in a job: " << instance;
148
149
150 /**
151 * Retrieve the original instance from the DICOM cache.
152 **/
153
154 std::unique_ptr<DicomInstanceHasher> originalHasher;
155 std::unique_ptr<ParsedDicomFile> modified;
156
157 try
158 {
159 ServerContext::DicomCacheLocker locker(GetContext(), instance);
160 ParsedDicomFile& original = locker.GetDicom();
161
162 originalHasher.reset(new DicomInstanceHasher(original.GetHasher()));
163 modified.reset(original.Clone(true));
164 }
165 catch (OrthancException& e)
166 {
167 LOG(WARNING) << "An error occurred while executing a Modification job on instance " << instance << ": " << e.GetDetails();
168 return false;
169 }
170
171
172 /**
173 * Compute the resulting DICOM instance.
174 **/
175
176 modification_->Apply(*modified);
177
178 const std::string modifiedUid = IDicomTranscoder::GetSopInstanceUid(modified->GetDcmtkObject());
179
180 if (transcode_)
181 {
182 std::set<DicomTransferSyntax> syntaxes;
183 syntaxes.insert(transferSyntax_);
184
185 IDicomTranscoder::DicomImage source;
186 source.AcquireParsed(*modified); // "modified" is invalid below this point
187
188 IDicomTranscoder::DicomImage transcoded;
189 if (GetContext().Transcode(transcoded, source, syntaxes, true))
190 {
191 modified.reset(transcoded.ReleaseAsParsedDicomFile());
192
193 // Fix the SOP instance UID in order the preserve the
194 // references between instance UIDs in the DICOM hierarchy
195 // (the UID might have changed in the case of lossy transcoding)
196 if (modified.get() == NULL ||
197 modified->GetDcmtkObject().getDataset() == NULL ||
198 !modified->GetDcmtkObject().getDataset()->putAndInsertString(
199 DCM_SOPInstanceUID, modifiedUid.c_str(), OFTrue /* replace */).good())
200 {
201 throw OrthancException(ErrorCode_InternalError);
202 }
203 }
204 else
205 {
206 LOG(WARNING) << "Cannot transcode instance, keeping original transfer syntax: " << instance;
207 modified.reset(source.ReleaseAsParsedDicomFile());
208 }
209 }
210
211 assert(modifiedUid == IDicomTranscoder::GetSopInstanceUid(modified->GetDcmtkObject()));
212
213 DicomInstanceToStore toStore;
214 toStore.SetOrigin(origin_);
215 toStore.SetParsedDicomFile(*modified);
216
217
218 /**
219 * Prepare the metadata information to associate with the
220 * resulting DICOM instance (AnonymizedFrom/ModifiedFrom).
221 **/
222
223 DicomInstanceHasher modifiedHasher = modified->GetHasher();
224
225 MetadataType metadataType = (isAnonymization_ ?
226 MetadataType_AnonymizedFrom :
227 MetadataType_ModifiedFrom);
228
229 if (originalHasher->HashSeries() != modifiedHasher.HashSeries())
230 {
231 toStore.AddMetadata(ResourceType_Series, metadataType, originalHasher->HashSeries());
232 }
233
234 if (originalHasher->HashStudy() != modifiedHasher.HashStudy())
235 {
236 toStore.AddMetadata(ResourceType_Study, metadataType, originalHasher->HashStudy());
237 }
238
239 if (originalHasher->HashPatient() != modifiedHasher.HashPatient())
240 {
241 toStore.AddMetadata(ResourceType_Patient, metadataType, originalHasher->HashPatient());
242 }
243
244 assert(instance == originalHasher->HashInstance());
245 toStore.AddMetadata(ResourceType_Instance, metadataType, instance);
246
247
248 /**
249 * Store the resulting DICOM instance into the Orthanc store.
250 **/
251
252 std::string modifiedInstance;
253 if (GetContext().Store(modifiedInstance, toStore,
254 StoreInstanceMode_Default) != StoreStatus_Success)
255 {
256 throw OrthancException(ErrorCode_CannotStoreInstance,
257 "Error while storing a modified instance " + instance);
258 }
259
260 /**
261 * The assertion below will fail if automated transcoding to a
262 * lossy transfer syntax is enabled in the Orthanc core, and if
263 * the source instance is not in this transfer syntax.
264 **/
265 // assert(modifiedInstance == modifiedHasher.HashInstance());
266
267 output_->Update(modifiedHasher);
268
269 return true;
270 }
271
272
273 ResourceModificationJob::ResourceModificationJob(ServerContext& context) :
274 CleaningInstancesJob(context, true /* by default, keep source */),
275 modification_(new DicomModification),
276 isAnonymization_(false),
277 transcode_(false)
278 {
279 }
280
281
282 void ResourceModificationJob::SetModification(DicomModification* modification,
283 ResourceType level,
284 bool isAnonymization)
285 {
286 if (modification == NULL)
287 {
288 throw OrthancException(ErrorCode_NullPointer);
289 }
290 else if (IsStarted())
291 {
292 throw OrthancException(ErrorCode_BadSequenceOfCalls);
293 }
294 else
295 {
296 modification_.reset(modification);
297 output_.reset(new Output(level));
298 isAnonymization_ = isAnonymization;
299 }
300 }
301
302
303 void ResourceModificationJob::SetOrigin(const DicomInstanceOrigin& origin)
304 {
305 if (IsStarted())
306 {
307 throw OrthancException(ErrorCode_BadSequenceOfCalls);
308 }
309 else
310 {
311 origin_ = origin;
312 }
313 }
314
315
316 void ResourceModificationJob::SetOrigin(const RestApiCall& call)
317 {
318 SetOrigin(DicomInstanceOrigin::FromRest(call));
319 }
320
321
322 const DicomModification& ResourceModificationJob::GetModification() const
323 {
324 if (modification_.get() == NULL)
325 {
326 throw OrthancException(ErrorCode_BadSequenceOfCalls);
327 }
328 else
329 {
330 return *modification_;
331 }
332 }
333
334
335 DicomTransferSyntax ResourceModificationJob::GetTransferSyntax() const
336 {
337 if (transcode_)
338 {
339 return transferSyntax_;
340 }
341 else
342 {
343 throw OrthancException(ErrorCode_BadSequenceOfCalls);
344 }
345 }
346
347
348 void ResourceModificationJob::SetTranscode(DicomTransferSyntax syntax)
349 {
350 if (IsStarted())
351 {
352 throw OrthancException(ErrorCode_BadSequenceOfCalls);
353 }
354 else
355 {
356 transcode_ = true;
357 transferSyntax_ = syntax;
358 }
359 }
360
361
362 void ResourceModificationJob::SetTranscode(const std::string& transferSyntaxUid)
363 {
364 DicomTransferSyntax s;
365 if (LookupTransferSyntax(s, transferSyntaxUid))
366 {
367 SetTranscode(s);
368 }
369 else
370 {
371 throw OrthancException(ErrorCode_BadFileFormat,
372 "Unknown transfer syntax UID: " + transferSyntaxUid);
373 }
374 }
375
376
377 void ResourceModificationJob::ClearTranscode()
378 {
379 if (IsStarted())
380 {
381 throw OrthancException(ErrorCode_BadSequenceOfCalls);
382 }
383 else
384 {
385 transcode_ = false;
386 }
387 }
388
389
390 void ResourceModificationJob::GetPublicContent(Json::Value& value)
391 {
392 CleaningInstancesJob::GetPublicContent(value);
393
394 value["IsAnonymization"] = isAnonymization_;
395
396 if (output_.get() != NULL)
397 {
398 output_->Format(value);
399 }
400
401 if (transcode_)
402 {
403 value["Transcode"] = GetTransferSyntaxUid(transferSyntax_);
404 }
405 }
406
407
408 static const char* MODIFICATION = "Modification";
409 static const char* ORIGIN = "Origin";
410 static const char* IS_ANONYMIZATION = "IsAnonymization";
411 static const char* TRANSCODE = "Transcode";
412
413
414 ResourceModificationJob::ResourceModificationJob(ServerContext& context,
415 const Json::Value& serialized) :
416 CleaningInstancesJob(context, serialized, true /* by default, keep source */)
417 {
418 assert(serialized.type() == Json::objectValue);
419
420 isAnonymization_ = SerializationToolbox::ReadBoolean(serialized, IS_ANONYMIZATION);
421 origin_ = DicomInstanceOrigin(serialized[ORIGIN]);
422 modification_.reset(new DicomModification(serialized[MODIFICATION]));
423
424 if (serialized.isMember(TRANSCODE))
425 {
426 SetTranscode(SerializationToolbox::ReadString(serialized, TRANSCODE));
427 }
428 else
429 {
430 transcode_ = false;
431 }
432 }
433
434 bool ResourceModificationJob::Serialize(Json::Value& value)
435 {
436 if (!CleaningInstancesJob::Serialize(value))
437 {
438 return false;
439 }
440 else
441 {
442 assert(value.type() == Json::objectValue);
443
444 value[IS_ANONYMIZATION] = isAnonymization_;
445
446 if (transcode_)
447 {
448 value[TRANSCODE] = GetTransferSyntaxUid(transferSyntax_);
449 }
450
451 origin_.Serialize(value[ORIGIN]);
452
453 Json::Value tmp;
454 modification_->Serialize(tmp);
455 value[MODIFICATION] = tmp;
456
457 return true;
458 }
459 }
460 }