diff 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
line wrap: on
line diff
--- a/OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp	Fri Jun 11 10:48:28 2021 +0200
+++ b/OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp	Wed Jun 16 16:44:04 2021 +0200
@@ -44,7 +44,16 @@
 
 namespace Orthanc
 {
-  class ResourceModificationJob::Output : public boost::noncopyable
+  static void FormatResource(Json::Value& target,
+                             ResourceType level,
+                             const std::string& id)
+  {
+    target["Type"] = EnumerationToString(level);
+    target["ID"] = id;
+    target["Path"] = GetBasePath(level, id);
+  }
+  
+  class ResourceModificationJob::SingleOutput : public IOutput
   {
   private:
     ResourceType  level_;
@@ -53,7 +62,7 @@
     std::string   patientId_;
 
   public:
-    explicit Output(ResourceType level) :
+    explicit SingleOutput(ResourceType level) :
       level_(level),
       isFirst_(true)
     {
@@ -65,13 +74,7 @@
       }            
     }
 
-    ResourceType GetLevel() const
-    {
-      return level_;
-    }
-    
-
-    void Update(DicomInstanceHasher& hasher)
+    virtual void Update(DicomInstanceHasher& hasher) ORTHANC_OVERRIDE
     {
       if (isFirst_)
       {
@@ -98,40 +101,78 @@
       }
     }
 
+    virtual void Format(Json::Value& target) const ORTHANC_OVERRIDE
+    {
+      assert(target.type() == Json::objectValue);
 
-    bool Format(Json::Value& target)
-    {
-      if (isFirst_)
+      if (!isFirst_)
       {
-        return false;
-      }
-      else
-      {
-        target = Json::objectValue;
-        target["Type"] = EnumerationToString(level_);
-        target["ID"] = id_;
-        target["Path"] = GetBasePath(level_, id_);
+        FormatResource(target, level_, id_);
         target["PatientID"] = patientId_;
-        return true;
       }
     }
 
-  
-    bool GetIdentifier(std::string& id)
+    virtual bool IsSingleResource() const ORTHANC_OVERRIDE
     {
-      if (isFirst_)
-      {
-        return false;
-      }
-      else
-      {
-        id = id_;
-        return true;
-      }
+      return true;
+    }
+
+    ResourceType GetLevel() const
+    {
+      return level_;
     }
   };
     
 
+  class ResourceModificationJob::MultipleOutputs : public IOutput
+  {
+  private:
+    static void FormatResources(Json::Value& target,
+                                ResourceType level,
+                                const std::set<std::string>& resources)
+    {
+      assert(target.type() == Json::arrayValue);
+
+      for (std::set<std::string>::const_iterator
+             it = resources.begin(); it != resources.end(); ++it)
+      {
+        Json::Value item = Json::objectValue;
+        FormatResource(item, level, *it);
+        target.append(item);        
+      }
+    }
+    
+    std::set<std::string>  instances_;
+    std::set<std::string>  series_;
+    std::set<std::string>  studies_;
+    std::set<std::string>  patients_;
+
+  public:
+    virtual void Update(DicomInstanceHasher& hasher) ORTHANC_OVERRIDE
+    {
+      instances_.insert(hasher.HashInstance());
+      series_.insert(hasher.HashSeries());
+      studies_.insert(hasher.HashStudy());
+      patients_.insert(hasher.HashPatient());
+    }
+
+    virtual void Format(Json::Value& target) const ORTHANC_OVERRIDE
+    {
+      assert(target.type() == Json::objectValue);
+      Json::Value resources = Json::arrayValue;
+      FormatResources(resources, ResourceType_Instance, instances_);
+      FormatResources(resources, ResourceType_Series, series_);
+      FormatResources(resources, ResourceType_Study, studies_);
+      FormatResources(resources, ResourceType_Patient, patients_);
+      target["Resources"] = resources;
+    }
+
+    virtual bool IsSingleResource() const ORTHANC_OVERRIDE
+    {
+      return false;
+    }
+  };
+    
 
 
   bool ResourceModificationJob::HandleInstance(const std::string& instance)
@@ -271,7 +312,6 @@
 
   ResourceModificationJob::ResourceModificationJob(ServerContext& context) :
     CleaningInstancesJob(context, true /* by default, keep source */),
-    modification_(new DicomModification),
     isAnonymization_(false),
     transcode_(false),
     transferSyntax_(DicomTransferSyntax_LittleEndianExplicit)  // dummy initialization
@@ -279,9 +319,9 @@
   }
 
 
-  void ResourceModificationJob::SetModification(DicomModification* modification,
-                                                ResourceType level,
-                                                bool isAnonymization)
+  void ResourceModificationJob::SetSingleResourceModification(DicomModification* modification,
+                                                              ResourceType outputLevel,
+                                                              bool isAnonymization)
   {
     if (modification == NULL)
     {
@@ -294,7 +334,27 @@
     else
     {
       modification_.reset(modification);
-      output_.reset(new Output(level));
+      output_.reset(new SingleOutput(outputLevel));
+      isAnonymization_ = isAnonymization;
+    }
+  }
+
+
+  void ResourceModificationJob::SetMultipleResourcesModification(DicomModification* modification,
+                                                                 bool isAnonymization)
+  {
+    if (modification == NULL)
+    {
+      throw OrthancException(ErrorCode_NullPointer);
+    }
+    else if (IsStarted())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      modification_.reset(modification);
+      output_.reset(new MultipleOutputs);
       isAnonymization_ = isAnonymization;
     }
   }
@@ -387,6 +447,37 @@
   }
 
 
+  bool ResourceModificationJob::IsSingleResourceModification() const
+  {
+    if (modification_.get() == NULL)
+    {
+      assert(output_.get() == NULL);
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      assert(output_.get() != NULL);
+      return output_->IsSingleResource();
+    }
+  }
+  
+
+  ResourceType ResourceModificationJob::GetOutputLevel() const
+  {
+    if (IsSingleResourceModification())
+    {
+      assert(modification_.get() != NULL &&
+             output_.get() != NULL);
+      return dynamic_cast<const SingleOutput&>(*output_).GetLevel();
+    }
+    else
+    {
+      // Not applicable if multiple resources
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
   void ResourceModificationJob::GetPublicContent(Json::Value& value)
   {
     CleaningInstancesJob::GetPublicContent(value);
@@ -409,6 +500,8 @@
   static const char* ORIGIN = "Origin";
   static const char* IS_ANONYMIZATION = "IsAnonymization";
   static const char* TRANSCODE = "Transcode";
+  static const char* OUTPUT_LEVEL = "OutputLevel";
+  static const char* IS_SINGLE_RESOURCE = "IsSingleResource";
   
 
   ResourceModificationJob::ResourceModificationJob(ServerContext& context,
@@ -418,9 +511,7 @@
   {
     assert(serialized.type() == Json::objectValue);
 
-    isAnonymization_ = SerializationToolbox::ReadBoolean(serialized, IS_ANONYMIZATION);
     origin_ = DicomInstanceOrigin(serialized[ORIGIN]);
-    modification_.reset(new DicomModification(serialized[MODIFICATION]));
 
     if (serialized.isMember(TRANSCODE))
     {
@@ -430,11 +521,62 @@
     {
       transcode_ = false;
     }
+
+    bool isSingleResource;
+    if (serialized.isMember(IS_SINGLE_RESOURCE))
+    {
+      isSingleResource = SerializationToolbox::ReadBoolean(serialized, IS_SINGLE_RESOURCE);
+    }
+    else
+    {
+      isSingleResource = true;  // Backward compatibility with Orthanc <= 1.9.3
+    }
+
+    bool isAnonymization = SerializationToolbox::ReadBoolean(serialized, IS_ANONYMIZATION);
+    std::unique_ptr<DicomModification> modification(new DicomModification(serialized[MODIFICATION]));
+
+    if (isSingleResource)
+    {
+      ResourceType outputLevel;
+      
+      if (serialized.isMember(OUTPUT_LEVEL))
+      {
+        // New in Orthanc 1.9.4. This fixes an *incorrect* behavior in
+        // Orthanc <= 1.9.3, in which "outputLevel" would be set to
+        // "modification->GetLevel()"
+        outputLevel = StringToResourceType(SerializationToolbox::ReadString(serialized, OUTPUT_LEVEL).c_str());
+      }
+      else
+      {
+        // Use the buggy convention from Orthanc <= 1.9.3 (which is
+        // the only thing we have at hand)
+        outputLevel = modification->GetLevel();
+
+        if (outputLevel == ResourceType_Instance)
+        {
+          // This should never happen, but as "SingleOutput" doesn't
+          // support instance-level anonymization, don't take any risk
+          // and choose an arbitrary output level
+          outputLevel = ResourceType_Patient;
+        }
+      }
+      
+      SetSingleResourceModification(modification.release(), outputLevel, isAnonymization);
+    }
+    else
+    {
+      // New in Orthanc 1.9.4
+      SetMultipleResourcesModification(modification.release(), isAnonymization);
+    }
   }
   
   bool ResourceModificationJob::Serialize(Json::Value& value)
   {
-    if (!CleaningInstancesJob::Serialize(value))
+    if (modification_.get() == NULL)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else if (!CleaningInstancesJob::Serialize(value))
     {
       return false;
     }
@@ -455,6 +597,13 @@
       modification_->Serialize(tmp);
       value[MODIFICATION] = tmp;
 
+      // New in Orthanc 1.9.4
+      value[IS_SINGLE_RESOURCE] = IsSingleResourceModification();
+      if (IsSingleResourceModification())
+      {
+        value[OUTPUT_LEVEL] = EnumerationToString(GetOutputLevel());
+      }
+      
       return true;
     }
   }