changeset 306:326d5a4a5af3

modification of instances
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 20 Dec 2012 16:44:50 +0100
parents 86bb79522f19
children 485db3b07740
files NEWS OrthancServer/FromDcmtkBridge.cpp OrthancServer/FromDcmtkBridge.h OrthancServer/OrthancRestApi.cpp OrthancServer/ServerEnumerations.h OrthancServer/ServerIndex.cpp OrthancServer/ServerIndex.h
diffstat 7 files changed, 270 insertions(+), 49 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Thu Dec 20 13:01:46 2012 +0100
+++ b/NEWS	Thu Dec 20 16:44:50 2012 +0100
@@ -1,6 +1,8 @@
 Pending changes in the mainline
 ===============================
 
+* Support for the private tags
+
 
 Version 0.4.0 (2012/12/14)
 ==========================
--- a/OrthancServer/FromDcmtkBridge.cpp	Thu Dec 20 13:01:46 2012 +0100
+++ b/OrthancServer/FromDcmtkBridge.cpp	Thu Dec 20 16:44:50 2012 +0100
@@ -39,10 +39,12 @@
 #include "../Core/Toolbox.h"
 #include "../Core/OrthancException.h"
 #include "../Core/PngWriter.h"
+#include "../Core/Uuid.h"
 #include "../Core/DicomFormat/DicomString.h"
 #include "../Core/DicomFormat/DicomNullValue.h"
 #include "../Core/DicomFormat/DicomIntegerPixelAccessor.h"
 
+#include <list>
 #include <limits>
 
 #include <boost/lexical_cast.hpp>
@@ -550,9 +552,41 @@
   void ParsedDicomFile::Remove(const DicomTag& tag)
   {
     DcmTagKey key(tag.GetGroup(), tag.GetElement());
+    DcmElement* element = file_->getDataset()->remove(key);
+    if (element != NULL)
+    {
+      delete element;
+    }
+  }
 
-    // TODO This call results in memory leaks inside DCMTK
-    file_->getDataset()->remove(key);
+
+
+  void ParsedDicomFile::RemovePrivateTags()
+  {
+    typedef std::list<DcmElement*> Tags;
+
+    Tags privateTags;
+
+    DcmDataset& dataset = *file_->getDataset();
+    for (unsigned long i = 0; i < dataset.card(); i++)
+    {
+      DcmElement* element = dataset.getElement(i);
+      DcmTag tag(element->getTag());
+      if (tag.getPrivateCreator() != NULL)
+      {
+        privateTags.push_back(element);
+      }
+    }
+
+    for (Tags::iterator it = privateTags.begin(); 
+         it != privateTags.end(); it++)
+    {
+      DcmElement* tmp = dataset.remove(*it);
+      if (tmp != NULL)
+      {
+        delete tmp;
+      }
+    }
   }
 
 
@@ -681,26 +715,25 @@
         /**
          * TODO.
          **/
-    
-        case EVR_DS:  // decimal string
-        case EVR_IS:  // integer string
+
         case EVR_OB:  // other byte
         case EVR_OF:  // other float
         case EVR_OW:  // other word
-        case EVR_AS:  // age string
         case EVR_AT:  // attribute tag
-        case EVR_DA:  // date string
-        case EVR_DT:  // date time string
-        case EVR_TM:  // time string
         case EVR_UN:  // unknown value representation
           return new DicomNullValue();
-
-
+    
           /**
            * String types, should never happen at this point because of
            * "element.isaString()".
            **/
       
+        case EVR_DS:  // decimal string
+        case EVR_IS:  // integer string
+        case EVR_AS:  // age string
+        case EVR_DA:  // date string
+        case EVR_DT:  // date time string
+        case EVR_TM:  // time string
         case EVR_AE:  // application entity title
         case EVR_CS:  // code string
         case EVR_SH:  // short string
@@ -721,78 +754,54 @@
         {
           Sint32 f;
           if (dynamic_cast<DcmSignedLong&>(element).getSint32(f).good())
-          {
             return new DicomString(boost::lexical_cast<std::string>(f));
-          }
           else
-          {
             return new DicomNullValue();
-          }
         }
 
         case EVR_SS:  // signed short
         {
           Sint16 f;
           if (dynamic_cast<DcmSignedShort&>(element).getSint16(f).good())
-          {
             return new DicomString(boost::lexical_cast<std::string>(f));
-          }
           else
-          {
             return new DicomNullValue();
-          }
         }
 
         case EVR_UL:  // unsigned long
         {
           Uint32 f;
           if (dynamic_cast<DcmUnsignedLong&>(element).getUint32(f).good())
-          {
             return new DicomString(boost::lexical_cast<std::string>(f));
-          }
           else
-          {
             return new DicomNullValue();
-          }
         }
 
         case EVR_US:  // unsigned short
         {
           Uint16 f;
           if (dynamic_cast<DcmUnsignedShort&>(element).getUint16(f).good())
-          {
             return new DicomString(boost::lexical_cast<std::string>(f));
-          }
           else
-          {
             return new DicomNullValue();
-          }
         }
 
         case EVR_FL:  // float single-precision
         {
           Float32 f;
           if (dynamic_cast<DcmFloatingPointSingle&>(element).getFloat32(f).good())
-          {
             return new DicomString(boost::lexical_cast<std::string>(f));
-          }
           else
-          {
             return new DicomNullValue();
-          }
         }
 
         case EVR_FD:  // float double-precision
         {
           Float64 f;
           if (dynamic_cast<DcmFloatingPointDouble&>(element).getFloat64(f).good())
-          {
             return new DicomString(boost::lexical_cast<std::string>(f));
-          }
           else
-          {
             return new DicomNullValue();
-          }
         }
 
 
@@ -841,6 +850,10 @@
     {
       return new DicomNullValue;
     }
+    catch (std::bad_cast)
+    {
+      return new DicomNullValue;
+    }
   }
 
 
@@ -876,7 +889,7 @@
 #else
     // This version of the code gives access to the name of the private tags
     DcmTag tagbis(element.getTag());
-    const std::string tagName(tagbis.getTagName());
+    const std::string tagName(tagbis.getTagName());      
 #endif
 
     if (element.isLeaf())
@@ -884,6 +897,11 @@
       Json::Value value(Json::objectValue);
       value["Name"] = tagName;
 
+      if (tagbis.getPrivateCreator() != NULL)
+      {
+        value["PrivateCreator"] = tagbis.getPrivateCreator();
+      }
+
       std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(element));
       if (v->IsNull())
       {
@@ -1124,7 +1142,7 @@
     const DcmDataDictionary& dict = dcmDataDict.rdlock();
     const DcmDictEntry* entry = dict.findEntry(tag, NULL);
 
-    std::string s("Unknown");
+    std::string s(DcmTag_ERROR_TagName);
     if (entry != NULL)
     {
       s = std::string(entry->getTagName());
@@ -1137,7 +1155,7 @@
     const char* name = tag.getTagName();
     if (name == NULL)
     {
-      return "Unknown";
+      return DcmTag_ERROR_TagName;
     }
     else
     {
@@ -1231,6 +1249,19 @@
 
     switch (level)
     {
+      case DicomRootLevel_Patient:
+      {
+        std::string uuid = Toolbox::GenerateUuid();
+        std::string id;
+        id.reserve(uuid.size());
+        for (size_t i = 0; i < uuid.size() && i < 8; i++)
+        {
+          id.push_back(toupper(uuid[i]));
+        }
+
+        return id;
+      }
+
       case DicomRootLevel_Instance:
         return dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT);
 
--- a/OrthancServer/FromDcmtkBridge.h	Thu Dec 20 13:01:46 2012 +0100
+++ b/OrthancServer/FromDcmtkBridge.h	Thu Dec 20 16:44:50 2012 +0100
@@ -52,6 +52,7 @@
 
   enum DicomRootLevel
   {
+    DicomRootLevel_Patient,
     DicomRootLevel_Study,
     DicomRootLevel_Series,
     DicomRootLevel_Instance
@@ -115,6 +116,8 @@
     void Replace(const DicomTag& tag,
                  const std::string& value,
                  DicomReplaceMode mode);
+
+    void RemovePrivateTags();
   };
 
   class FromDcmtkBridge
--- a/OrthancServer/OrthancRestApi.cpp	Thu Dec 20 13:01:46 2012 +0100
+++ b/OrthancServer/OrthancRestApi.cpp	Thu Dec 20 16:44:50 2012 +0100
@@ -842,16 +842,23 @@
   // Modification of DICOM instances ------------------------------------------
 
   static void ReplaceInstanceInternal(ParsedDicomFile& toModify,
+                                      const Json::Value& removals,
                                       const Json::Value& replacements,
                                       DicomReplaceMode mode)
   {
-    if (!replacements.isObject())
+    if (!replacements.isObject() ||
+        !removals.isArray())
     {
       throw OrthancException(ErrorCode_BadRequest);
     }
 
+    for (Json::Value::ArrayIndex i = 0; i < removals.size(); i++)
+    {
+      DicomTag tag = FromDcmtkBridge::ParseTag(removals[i].asString());
+      toModify.Remove(tag);
+    }
+
     Json::Value::Members members = replacements.getMemberNames();
-
     for (size_t i = 0; i < members.size(); i++)
     {
       const std::string& name = members[i];
@@ -866,8 +873,35 @@
     toModify.Replace(DICOM_TAG_SOP_INSTANCE_UID, instanceUid, DicomReplaceMode_InsertIfAbsent);
   }
 
+ 
+  static bool ParseModifyRequest(Json::Value& removals,
+                                 Json::Value& replacements,
+                                 const RestApi::PostCall& call)
+  {
+    Json::Value request;
+    if (call.ParseJsonRequest(request) &&
+        request.isObject())
+    {
+      removals = Json::arrayValue;
+      replacements = Json::objectValue;
 
-  
+      if (request.isMember("Remove"))
+      {
+        removals = request["Remove"];
+      }
+
+      if (request.isMember("Replace"))
+      {
+        replacements = request["Replace"];
+      }
+
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
 
 
   static void ModifyInstance(RestApi::PostCall& call)
@@ -877,18 +911,98 @@
     std::string id = call.GetUriComponent("id", "");
     ParsedDicomFile& dicom = context.GetDicomFile(id);
     
-    Json::Value request;
-    if (call.ParseJsonRequest(request))
+    Json::Value removals, replacements;
+    if (ParseModifyRequest(removals, replacements, call))
     {
       std::auto_ptr<ParsedDicomFile> modified(dicom.Clone());
-      ReplaceInstanceInternal(*modified, request, DicomReplaceMode_InsertIfAbsent);
+      ReplaceInstanceInternal(*modified, removals, replacements, DicomReplaceMode_InsertIfAbsent);
+      context.GetIndex().SetMetadata(id, MetadataType_ModifiedFrom, id);
       modified->Answer(call.GetOutput());
     }
+  }
+
+
+  template <enum ResourceType resourceType>
+  static void ModifyInplace(RestApi::PostCall& call)
+  {
+    typedef std::list<std::string> Instances;
+
+    RETRIEVE_CONTEXT(call);
+    
+    Instances instances;
+    std::string id = call.GetUriComponent("id", "");
+    context.GetIndex().GetChildInstances(instances, id);
+
+    if (instances.size() == 0)
+    {
+      return;
+    }
 
-    /*std::string studyUid = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Study);
-    std::string seriesUid = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Series);
-    modified->Replace(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid);
-    modified->Replace(DICOM_TAG_STUDY_INSTANCE_UID, studyUid);*/
+    Json::Value removals, replacements;
+    if (ParseModifyRequest(removals, replacements, call))
+    {
+      switch (resourceType)
+      {
+        // DO NOT ADD "break" OR CHANGE THE ORDER BELOW
+        case ResourceType_Patient:
+          replacements["0010-0020"] = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Patient);
+
+        case ResourceType_Study:
+          replacements["0020-000d"] = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Study);
+
+        case ResourceType_Series:
+          replacements["0020-000e"] = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Series);
+          break;
+          
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+
+      std::string modifiedId;
+      for (Instances::const_iterator it = instances.begin(); 
+           it != instances.end(); it++)
+      {
+        LOG(INFO) << "Modifying instance " << *it;
+        ParsedDicomFile& dicom = context.GetDicomFile(*it);
+        std::auto_ptr<ParsedDicomFile> modified(dicom.Clone());
+        ReplaceInstanceInternal(*modified, removals, replacements, DicomReplaceMode_InsertIfAbsent);
+
+        if (context.Store(modifiedId, modified->GetDicom()) != StoreStatus_Success)
+        {
+          LOG(ERROR) << "Error while modifying the instance " << *it;
+          return;
+        }
+
+        context.GetIndex().SetMetadata(modifiedId, MetadataType_ModifiedFrom, *it);
+      }
+
+
+      int level;
+      std::string id;
+      switch (resourceType)
+      {
+        case ResourceType_Series:  level = 1; break;
+        case ResourceType_Study:   level = 2; break;
+        case ResourceType_Patient: level = 3; break;
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+
+      for (int i = 0; i < level; i++)
+      {
+        if (!context.GetIndex().LookupParent(id, modifiedId))
+        {
+          throw OrthancException(ErrorCode_InternalError);
+        }
+        
+        modifiedId = id;
+      }
+
+      Json::Value result = Json::objectValue;
+      result["ID"] = id;
+      result["Path"] = GetBasePath(resourceType, id);
+      call.GetOutput().AnswerJson(result);
+    }
   }
 
 
@@ -949,5 +1063,8 @@
     Register("/modalities/{id}/store", DicomStore);
 
     Register("/instances/{id}/modify", ModifyInstance);
+    Register("/series/{id}/modify", ModifyInplace<ResourceType_Series>);
+    Register("/studies/{id}/modify", ModifyInplace<ResourceType_Study>);
+    Register("/patients/{id}/modify", ModifyInplace<ResourceType_Patient>);
   }
 }
--- a/OrthancServer/ServerEnumerations.h	Thu Dec 20 13:01:46 2012 +0100
+++ b/OrthancServer/ServerEnumerations.h	Thu Dec 20 16:44:50 2012 +0100
@@ -76,7 +76,8 @@
     MetadataType_Instance_IndexInSeries = 1,
     MetadataType_Instance_ReceptionDate = 2,
     MetadataType_Instance_RemoteAet = 3,
-    MetadataType_Series_ExpectedNumberOfInstances = 4
+    MetadataType_Series_ExpectedNumberOfInstances = 4,
+    MetadataType_ModifiedFrom = 5
   };
 
   enum ChangeType
--- a/OrthancServer/ServerIndex.cpp	Thu Dec 20 13:01:46 2012 +0100
+++ b/OrthancServer/ServerIndex.cpp	Thu Dec 20 16:44:50 2012 +0100
@@ -1012,4 +1012,61 @@
     }
   }
 
+
+  void ServerIndex::SetMetadata(const std::string& publicId,
+                                MetadataType type,
+                                const std::string& value)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    ResourceType rtype;
+    int64_t id;
+    if (!db_->LookupResource(publicId, id, rtype))
+    {
+      throw OrthancException(ErrorCode_UnknownResource);
+    }
+
+    db_->SetMetadata(id, type, value);
+  }
+
+  bool ServerIndex::LookupMetadata(std::string& target,
+                                   const std::string& publicId,
+                                   MetadataType type)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    ResourceType rtype;
+    int64_t id;
+    if (!db_->LookupResource(publicId, id, rtype))
+    {
+      throw OrthancException(ErrorCode_UnknownResource);
+    }
+
+    return db_->LookupMetadata(target, id, type);
+  }
+
+
+  bool ServerIndex::LookupParent(std::string& target,
+                                 const std::string& publicId)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    ResourceType type;
+    int64_t id;
+    if (!db_->LookupResource(publicId, id, type))
+    {
+      throw OrthancException(ErrorCode_UnknownResource);
+    }
+
+    int64_t parentId;
+    if (db_->LookupParent(parentId, id))
+    {
+      target = db_->GetPublicId(parentId);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
 }
--- a/OrthancServer/ServerIndex.h	Thu Dec 20 13:01:46 2012 +0100
+++ b/OrthancServer/ServerIndex.h	Thu Dec 20 16:44:50 2012 +0100
@@ -146,5 +146,15 @@
     void GetChildInstances(std::list<std::string>& result,
                            const std::string& publicId);
 
+    void SetMetadata(const std::string& publicId,
+                     MetadataType type,
+                     const std::string& value);
+
+    bool LookupMetadata(std::string& target,
+                        const std::string& publicId,
+                        MetadataType type);
+
+    bool LookupParent(std::string& target,
+                      const std::string& publicId);
   };
 }