Mercurial > hg > orthanc
comparison OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp @ 4693:45bce660ce3a
added routes for bulk anonymization/modification
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 16 Jun 2021 16:44:04 +0200 |
parents | 8f9090b137f1 |
children | f0038043fb97 94616af363ec |
comparison
equal
deleted
inserted
replaced
4692:e68edf92e5cc | 4693:45bce660ce3a |
---|---|
42 #include <dcmtk/dcmdata/dcdeftag.h> | 42 #include <dcmtk/dcmdata/dcdeftag.h> |
43 #include <cassert> | 43 #include <cassert> |
44 | 44 |
45 namespace Orthanc | 45 namespace Orthanc |
46 { | 46 { |
47 class ResourceModificationJob::Output : public boost::noncopyable | 47 static void FormatResource(Json::Value& target, |
48 ResourceType level, | |
49 const std::string& id) | |
50 { | |
51 target["Type"] = EnumerationToString(level); | |
52 target["ID"] = id; | |
53 target["Path"] = GetBasePath(level, id); | |
54 } | |
55 | |
56 class ResourceModificationJob::SingleOutput : public IOutput | |
48 { | 57 { |
49 private: | 58 private: |
50 ResourceType level_; | 59 ResourceType level_; |
51 bool isFirst_; | 60 bool isFirst_; |
52 std::string id_; | 61 std::string id_; |
53 std::string patientId_; | 62 std::string patientId_; |
54 | 63 |
55 public: | 64 public: |
56 explicit Output(ResourceType level) : | 65 explicit SingleOutput(ResourceType level) : |
57 level_(level), | 66 level_(level), |
58 isFirst_(true) | 67 isFirst_(true) |
59 { | 68 { |
60 if (level_ != ResourceType_Patient && | 69 if (level_ != ResourceType_Patient && |
61 level_ != ResourceType_Study && | 70 level_ != ResourceType_Study && |
63 { | 72 { |
64 throw OrthancException(ErrorCode_ParameterOutOfRange); | 73 throw OrthancException(ErrorCode_ParameterOutOfRange); |
65 } | 74 } |
66 } | 75 } |
67 | 76 |
68 ResourceType GetLevel() const | 77 virtual void Update(DicomInstanceHasher& hasher) ORTHANC_OVERRIDE |
69 { | |
70 return level_; | |
71 } | |
72 | |
73 | |
74 void Update(DicomInstanceHasher& hasher) | |
75 { | 78 { |
76 if (isFirst_) | 79 if (isFirst_) |
77 { | 80 { |
78 switch (level_) | 81 switch (level_) |
79 { | 82 { |
96 patientId_ = hasher.HashPatient(); | 99 patientId_ = hasher.HashPatient(); |
97 isFirst_ = false; | 100 isFirst_ = false; |
98 } | 101 } |
99 } | 102 } |
100 | 103 |
101 | 104 virtual void Format(Json::Value& target) const ORTHANC_OVERRIDE |
102 bool Format(Json::Value& target) | 105 { |
103 { | 106 assert(target.type() == Json::objectValue); |
104 if (isFirst_) | 107 |
105 { | 108 if (!isFirst_) |
106 return false; | 109 { |
107 } | 110 FormatResource(target, level_, id_); |
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_; | 111 target["PatientID"] = patientId_; |
115 return true; | 112 } |
116 } | 113 } |
117 } | 114 |
118 | 115 virtual bool IsSingleResource() const ORTHANC_OVERRIDE |
119 | 116 { |
120 bool GetIdentifier(std::string& id) | 117 return true; |
121 { | 118 } |
122 if (isFirst_) | 119 |
123 { | 120 ResourceType GetLevel() const |
124 return false; | 121 { |
125 } | 122 return level_; |
126 else | |
127 { | |
128 id = id_; | |
129 return true; | |
130 } | |
131 } | 123 } |
132 }; | 124 }; |
133 | 125 |
134 | 126 |
127 class ResourceModificationJob::MultipleOutputs : public IOutput | |
128 { | |
129 private: | |
130 static void FormatResources(Json::Value& target, | |
131 ResourceType level, | |
132 const std::set<std::string>& resources) | |
133 { | |
134 assert(target.type() == Json::arrayValue); | |
135 | |
136 for (std::set<std::string>::const_iterator | |
137 it = resources.begin(); it != resources.end(); ++it) | |
138 { | |
139 Json::Value item = Json::objectValue; | |
140 FormatResource(item, level, *it); | |
141 target.append(item); | |
142 } | |
143 } | |
144 | |
145 std::set<std::string> instances_; | |
146 std::set<std::string> series_; | |
147 std::set<std::string> studies_; | |
148 std::set<std::string> patients_; | |
149 | |
150 public: | |
151 virtual void Update(DicomInstanceHasher& hasher) ORTHANC_OVERRIDE | |
152 { | |
153 instances_.insert(hasher.HashInstance()); | |
154 series_.insert(hasher.HashSeries()); | |
155 studies_.insert(hasher.HashStudy()); | |
156 patients_.insert(hasher.HashPatient()); | |
157 } | |
158 | |
159 virtual void Format(Json::Value& target) const ORTHANC_OVERRIDE | |
160 { | |
161 assert(target.type() == Json::objectValue); | |
162 Json::Value resources = Json::arrayValue; | |
163 FormatResources(resources, ResourceType_Instance, instances_); | |
164 FormatResources(resources, ResourceType_Series, series_); | |
165 FormatResources(resources, ResourceType_Study, studies_); | |
166 FormatResources(resources, ResourceType_Patient, patients_); | |
167 target["Resources"] = resources; | |
168 } | |
169 | |
170 virtual bool IsSingleResource() const ORTHANC_OVERRIDE | |
171 { | |
172 return false; | |
173 } | |
174 }; | |
175 | |
135 | 176 |
136 | 177 |
137 bool ResourceModificationJob::HandleInstance(const std::string& instance) | 178 bool ResourceModificationJob::HandleInstance(const std::string& instance) |
138 { | 179 { |
139 if (modification_.get() == NULL || | 180 if (modification_.get() == NULL || |
269 } | 310 } |
270 | 311 |
271 | 312 |
272 ResourceModificationJob::ResourceModificationJob(ServerContext& context) : | 313 ResourceModificationJob::ResourceModificationJob(ServerContext& context) : |
273 CleaningInstancesJob(context, true /* by default, keep source */), | 314 CleaningInstancesJob(context, true /* by default, keep source */), |
274 modification_(new DicomModification), | |
275 isAnonymization_(false), | 315 isAnonymization_(false), |
276 transcode_(false), | 316 transcode_(false), |
277 transferSyntax_(DicomTransferSyntax_LittleEndianExplicit) // dummy initialization | 317 transferSyntax_(DicomTransferSyntax_LittleEndianExplicit) // dummy initialization |
278 { | 318 { |
279 } | 319 } |
280 | 320 |
281 | 321 |
282 void ResourceModificationJob::SetModification(DicomModification* modification, | 322 void ResourceModificationJob::SetSingleResourceModification(DicomModification* modification, |
283 ResourceType level, | 323 ResourceType outputLevel, |
284 bool isAnonymization) | 324 bool isAnonymization) |
285 { | 325 { |
286 if (modification == NULL) | 326 if (modification == NULL) |
287 { | 327 { |
288 throw OrthancException(ErrorCode_NullPointer); | 328 throw OrthancException(ErrorCode_NullPointer); |
289 } | 329 } |
292 throw OrthancException(ErrorCode_BadSequenceOfCalls); | 332 throw OrthancException(ErrorCode_BadSequenceOfCalls); |
293 } | 333 } |
294 else | 334 else |
295 { | 335 { |
296 modification_.reset(modification); | 336 modification_.reset(modification); |
297 output_.reset(new Output(level)); | 337 output_.reset(new SingleOutput(outputLevel)); |
338 isAnonymization_ = isAnonymization; | |
339 } | |
340 } | |
341 | |
342 | |
343 void ResourceModificationJob::SetMultipleResourcesModification(DicomModification* modification, | |
344 bool isAnonymization) | |
345 { | |
346 if (modification == NULL) | |
347 { | |
348 throw OrthancException(ErrorCode_NullPointer); | |
349 } | |
350 else if (IsStarted()) | |
351 { | |
352 throw OrthancException(ErrorCode_BadSequenceOfCalls); | |
353 } | |
354 else | |
355 { | |
356 modification_.reset(modification); | |
357 output_.reset(new MultipleOutputs); | |
298 isAnonymization_ = isAnonymization; | 358 isAnonymization_ = isAnonymization; |
299 } | 359 } |
300 } | 360 } |
301 | 361 |
302 | 362 |
381 throw OrthancException(ErrorCode_BadSequenceOfCalls); | 441 throw OrthancException(ErrorCode_BadSequenceOfCalls); |
382 } | 442 } |
383 else | 443 else |
384 { | 444 { |
385 transcode_ = false; | 445 transcode_ = false; |
446 } | |
447 } | |
448 | |
449 | |
450 bool ResourceModificationJob::IsSingleResourceModification() const | |
451 { | |
452 if (modification_.get() == NULL) | |
453 { | |
454 assert(output_.get() == NULL); | |
455 throw OrthancException(ErrorCode_BadSequenceOfCalls); | |
456 } | |
457 else | |
458 { | |
459 assert(output_.get() != NULL); | |
460 return output_->IsSingleResource(); | |
461 } | |
462 } | |
463 | |
464 | |
465 ResourceType ResourceModificationJob::GetOutputLevel() const | |
466 { | |
467 if (IsSingleResourceModification()) | |
468 { | |
469 assert(modification_.get() != NULL && | |
470 output_.get() != NULL); | |
471 return dynamic_cast<const SingleOutput&>(*output_).GetLevel(); | |
472 } | |
473 else | |
474 { | |
475 // Not applicable if multiple resources | |
476 throw OrthancException(ErrorCode_BadSequenceOfCalls); | |
386 } | 477 } |
387 } | 478 } |
388 | 479 |
389 | 480 |
390 void ResourceModificationJob::GetPublicContent(Json::Value& value) | 481 void ResourceModificationJob::GetPublicContent(Json::Value& value) |
407 | 498 |
408 static const char* MODIFICATION = "Modification"; | 499 static const char* MODIFICATION = "Modification"; |
409 static const char* ORIGIN = "Origin"; | 500 static const char* ORIGIN = "Origin"; |
410 static const char* IS_ANONYMIZATION = "IsAnonymization"; | 501 static const char* IS_ANONYMIZATION = "IsAnonymization"; |
411 static const char* TRANSCODE = "Transcode"; | 502 static const char* TRANSCODE = "Transcode"; |
503 static const char* OUTPUT_LEVEL = "OutputLevel"; | |
504 static const char* IS_SINGLE_RESOURCE = "IsSingleResource"; | |
412 | 505 |
413 | 506 |
414 ResourceModificationJob::ResourceModificationJob(ServerContext& context, | 507 ResourceModificationJob::ResourceModificationJob(ServerContext& context, |
415 const Json::Value& serialized) : | 508 const Json::Value& serialized) : |
416 CleaningInstancesJob(context, serialized, true /* by default, keep source */), | 509 CleaningInstancesJob(context, serialized, true /* by default, keep source */), |
417 transferSyntax_(DicomTransferSyntax_LittleEndianExplicit) // dummy initialization | 510 transferSyntax_(DicomTransferSyntax_LittleEndianExplicit) // dummy initialization |
418 { | 511 { |
419 assert(serialized.type() == Json::objectValue); | 512 assert(serialized.type() == Json::objectValue); |
420 | 513 |
421 isAnonymization_ = SerializationToolbox::ReadBoolean(serialized, IS_ANONYMIZATION); | |
422 origin_ = DicomInstanceOrigin(serialized[ORIGIN]); | 514 origin_ = DicomInstanceOrigin(serialized[ORIGIN]); |
423 modification_.reset(new DicomModification(serialized[MODIFICATION])); | |
424 | 515 |
425 if (serialized.isMember(TRANSCODE)) | 516 if (serialized.isMember(TRANSCODE)) |
426 { | 517 { |
427 SetTranscode(SerializationToolbox::ReadString(serialized, TRANSCODE)); | 518 SetTranscode(SerializationToolbox::ReadString(serialized, TRANSCODE)); |
428 } | 519 } |
429 else | 520 else |
430 { | 521 { |
431 transcode_ = false; | 522 transcode_ = false; |
523 } | |
524 | |
525 bool isSingleResource; | |
526 if (serialized.isMember(IS_SINGLE_RESOURCE)) | |
527 { | |
528 isSingleResource = SerializationToolbox::ReadBoolean(serialized, IS_SINGLE_RESOURCE); | |
529 } | |
530 else | |
531 { | |
532 isSingleResource = true; // Backward compatibility with Orthanc <= 1.9.3 | |
533 } | |
534 | |
535 bool isAnonymization = SerializationToolbox::ReadBoolean(serialized, IS_ANONYMIZATION); | |
536 std::unique_ptr<DicomModification> modification(new DicomModification(serialized[MODIFICATION])); | |
537 | |
538 if (isSingleResource) | |
539 { | |
540 ResourceType outputLevel; | |
541 | |
542 if (serialized.isMember(OUTPUT_LEVEL)) | |
543 { | |
544 // New in Orthanc 1.9.4. This fixes an *incorrect* behavior in | |
545 // Orthanc <= 1.9.3, in which "outputLevel" would be set to | |
546 // "modification->GetLevel()" | |
547 outputLevel = StringToResourceType(SerializationToolbox::ReadString(serialized, OUTPUT_LEVEL).c_str()); | |
548 } | |
549 else | |
550 { | |
551 // Use the buggy convention from Orthanc <= 1.9.3 (which is | |
552 // the only thing we have at hand) | |
553 outputLevel = modification->GetLevel(); | |
554 | |
555 if (outputLevel == ResourceType_Instance) | |
556 { | |
557 // This should never happen, but as "SingleOutput" doesn't | |
558 // support instance-level anonymization, don't take any risk | |
559 // and choose an arbitrary output level | |
560 outputLevel = ResourceType_Patient; | |
561 } | |
562 } | |
563 | |
564 SetSingleResourceModification(modification.release(), outputLevel, isAnonymization); | |
565 } | |
566 else | |
567 { | |
568 // New in Orthanc 1.9.4 | |
569 SetMultipleResourcesModification(modification.release(), isAnonymization); | |
432 } | 570 } |
433 } | 571 } |
434 | 572 |
435 bool ResourceModificationJob::Serialize(Json::Value& value) | 573 bool ResourceModificationJob::Serialize(Json::Value& value) |
436 { | 574 { |
437 if (!CleaningInstancesJob::Serialize(value)) | 575 if (modification_.get() == NULL) |
576 { | |
577 throw OrthancException(ErrorCode_BadSequenceOfCalls); | |
578 } | |
579 else if (!CleaningInstancesJob::Serialize(value)) | |
438 { | 580 { |
439 return false; | 581 return false; |
440 } | 582 } |
441 else | 583 else |
442 { | 584 { |
453 | 595 |
454 Json::Value tmp; | 596 Json::Value tmp; |
455 modification_->Serialize(tmp); | 597 modification_->Serialize(tmp); |
456 value[MODIFICATION] = tmp; | 598 value[MODIFICATION] = tmp; |
457 | 599 |
600 // New in Orthanc 1.9.4 | |
601 value[IS_SINGLE_RESOURCE] = IsSingleResourceModification(); | |
602 if (IsSingleResourceModification()) | |
603 { | |
604 value[OUTPUT_LEVEL] = EnumerationToString(GetOutputLevel()); | |
605 } | |
606 | |
458 return true; | 607 return true; |
459 } | 608 } |
460 } | 609 } |
461 } | 610 } |