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 }