changeset 304:4eea080e6e7a

refactoring
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 19 Dec 2012 14:57:18 +0100
parents c76a35a85c69
children 86bb79522f19
files Core/Enumerations.h Core/OrthancException.cpp Core/RestApi/RestApi.cpp Core/RestApi/RestApi.h OrthancServer/DatabaseWrapper.cpp OrthancServer/DatabaseWrapper.h OrthancServer/FromDcmtkBridge.cpp OrthancServer/FromDcmtkBridge.h OrthancServer/OrthancRestApi.cpp OrthancServer/ServerContext.cpp OrthancServer/ServerContext.h OrthancServer/ServerIndex.cpp OrthancServer/ServerIndex.h UnitTests/ServerIndex.cpp UnitTests/main.cpp
diffstat 15 files changed, 304 insertions(+), 96 deletions(-) [+]
line wrap: on
line diff
--- a/Core/Enumerations.h	Tue Dec 18 19:01:01 2012 +0100
+++ b/Core/Enumerations.h	Wed Dec 19 14:57:18 2012 +0100
@@ -48,6 +48,7 @@
     ErrorCode_BadParameterType,
     ErrorCode_BadSequenceOfCalls,
     ErrorCode_InexistentItem,
+    ErrorCode_BadRequest,
 
     // Specific error codes
     ErrorCode_UriSyntax,
--- a/Core/OrthancException.cpp	Tue Dec 18 19:01:01 2012 +0100
+++ b/Core/OrthancException.cpp	Wed Dec 19 14:57:18 2012 +0100
@@ -99,6 +99,9 @@
       case ErrorCode_InexistentItem:
         return "Accessing an inexistent item";
 
+      case ErrorCode_BadRequest:
+        return "Bad request";
+
       case ErrorCode_Custom:
       default:
         return "???";
--- a/Core/RestApi/RestApi.cpp	Tue Dec 18 19:01:01 2012 +0100
+++ b/Core/RestApi/RestApi.cpp	Wed Dec 19 14:57:18 2012 +0100
@@ -37,6 +37,29 @@
 
 namespace Orthanc
 {
+  bool RestApi::SharedCall::ParseJsonRequestInternal(Json::Value& result,
+                                                     const char* request)
+  {
+    result.clear();
+    Json::Reader reader;
+    return reader.parse(request, result);
+  }
+
+
+  bool RestApi::GetCall::ParseJsonRequest(Json::Value& result) const
+  {
+    result.clear();
+
+    for (HttpHandler::Arguments::const_iterator 
+           it = getArguments_->begin(); it != getArguments_->end(); it++)
+    {
+      result[it->first] = result[it->second];
+    }
+
+    return true;
+  }
+
+
   bool RestApi::IsGetAccepted(const UriComponents& uri)
   {
     for (GetHandlers::const_iterator it = getHandlers_.begin();
--- a/Core/RestApi/RestApi.h	Tue Dec 18 19:01:01 2012 +0100
+++ b/Core/RestApi/RestApi.h	Wed Dec 19 14:57:18 2012 +0100
@@ -55,6 +55,10 @@
       const UriComponents* trailing_;
       const UriComponents* fullUri_;
 
+    protected:
+      static bool ParseJsonRequestInternal(Json::Value& result,
+                                           const char* request);
+
     public:
       RestApiOutput& GetOutput()
       {
@@ -87,6 +91,8 @@
       {
         return HttpHandler::GetArgument(*httpHeaders_, name, defaultValue);
       }
+
+      virtual bool ParseJsonRequest(Json::Value& result) const = 0;
     };
 
  
@@ -109,6 +115,8 @@
       {
         return getArguments_->find(name) != getArguments_->end();
       }
+
+      virtual bool ParseJsonRequest(Json::Value& result) const;
     };
 
     class PutCall : public SharedCall
@@ -119,10 +127,15 @@
       const std::string* data_;
 
     public:
-      const std::string& GetPutBody()
+      const std::string& GetPutBody() const
       {
         return *data_;
       }
+
+      virtual bool ParseJsonRequest(Json::Value& result) const
+      {
+        return ParseJsonRequestInternal(result, GetPutBody().c_str());
+      }      
     };
 
     class PostCall : public SharedCall
@@ -133,14 +146,25 @@
       const std::string* data_;
 
     public:
-      const std::string& GetPostBody()
+      const std::string& GetPostBody() const
       {
         return *data_;
       }
+
+      virtual bool ParseJsonRequest(Json::Value& result) const
+      {
+        return ParseJsonRequestInternal(result, GetPostBody().c_str());
+      }      
     };
 
     class DeleteCall : public SharedCall
     {
+    public:
+      virtual bool ParseJsonRequest(Json::Value& result) const
+      {
+        result.clear();
+        return true;
+      }
     };
 
     typedef void (*GetHandler) (GetCall& call);
--- a/OrthancServer/DatabaseWrapper.cpp	Tue Dec 18 19:01:01 2012 +0100
+++ b/OrthancServer/DatabaseWrapper.cpp	Wed Dec 19 14:57:18 2012 +0100
@@ -275,6 +275,22 @@
     return s.ColumnString(0);
   }
 
+
+  ResourceType DatabaseWrapper::GetResourceType(int64_t resourceId)
+  {
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, 
+                        "SELECT resourceType FROM Resources WHERE internalId=?");
+    s.BindInt(0, resourceId);
+    
+    if (!s.Step())
+    { 
+      throw OrthancException(ErrorCode_UnknownResource);
+    }
+
+    return static_cast<ResourceType>(s.ColumnInt(0));
+  }
+
+
   void DatabaseWrapper::AttachChild(int64_t parent,
                                     int64_t child)
   {
--- a/OrthancServer/DatabaseWrapper.h	Tue Dec 18 19:01:01 2012 +0100
+++ b/OrthancServer/DatabaseWrapper.h	Wed Dec 19 14:57:18 2012 +0100
@@ -94,6 +94,8 @@
 
     std::string GetPublicId(int64_t resourceId);
 
+    ResourceType GetResourceType(int64_t resourceId);
+
     void AttachChild(int64_t parent,
                      int64_t child);
 
--- a/OrthancServer/FromDcmtkBridge.cpp	Tue Dec 18 19:01:01 2012 +0100
+++ b/OrthancServer/FromDcmtkBridge.cpp	Wed Dec 19 14:57:18 2012 +0100
@@ -82,12 +82,12 @@
 
 namespace Orthanc
 {
-  ParsedDicomFile::ParsedDicomFile(const std::string& content)
+  void ParsedDicomFile::Setup(const char* buffer, size_t size)
   {
     DcmInputBufferStream is;
-    if (content.size() > 0)
+    if (size > 0)
     {
-      is.setBuffer(&content[0], content.size());
+      is.setBuffer(buffer, size);
     }
     is.setEos();
 
@@ -141,29 +141,11 @@
             GetCharValue(c[3]));
   }
 
-  static bool ParseTagAndGroup(DcmTagKey& key,
+  static void ParseTagAndGroup(DcmTagKey& key,
                                const std::string& tag)
   {
-    if (tag.size() != 9 ||
-        !isxdigit(tag[0]) ||
-        !isxdigit(tag[1]) ||
-        !isxdigit(tag[2]) ||
-        !isxdigit(tag[3]) ||
-        tag[4] != '-' ||
-        !isxdigit(tag[5]) ||
-        !isxdigit(tag[6]) ||
-        !isxdigit(tag[7]) ||
-        !isxdigit(tag[8]))        
-    {
-      return false;
-    }
-
-    uint16_t group = GetTagValue(tag.c_str());
-    uint16_t element = GetTagValue(tag.c_str() + 5);
-
-    key = DcmTagKey(group, element);
-
-    return true;
+    DicomTag t = FromDcmtkBridge::ParseTag(tag);
+    key = DcmTagKey(t.GetGroup(), t.GetElement());
   }
 
 
@@ -224,10 +206,7 @@
                                    DcmItem& dicom)
   {
     DcmTagKey k;
-    if (!ParseTagAndGroup(k, tag))
-    {
-      return;
-    }
+    ParseTagAndGroup(k, tag);
 
     DcmSequenceOfItems* sequence = NULL;
     if (dicom.findAndGetSequence(k, sequence).good() && 
@@ -268,8 +247,8 @@
 
       DcmTagKey k;
       DcmItem *child = NULL;
-      if (!ParseTagAndGroup(k, uri[2 * pos]) ||
-          !dicom->findAndGetSequenceItem(k, child, index).good() ||
+      ParseTagAndGroup(k, uri[2 * pos]);
+      if (!dicom->findAndGetSequenceItem(k, child, index).good() ||
           child == NULL)
       {
         return;
@@ -1158,8 +1137,24 @@
   }
 
 
-  DicomTag FromDcmtkBridge::FindTag(const char* name)
+  DicomTag FromDcmtkBridge::ParseTag(const char* name)
   {
+    if (strlen(name) == 9 &&
+        isxdigit(name[0]) &&
+        isxdigit(name[1]) &&
+        isxdigit(name[2]) &&
+        isxdigit(name[3]) &&
+        name[4] == '-' &&
+        isxdigit(name[5]) &&
+        isxdigit(name[6]) &&
+        isxdigit(name[7]) &&
+        isxdigit(name[8]))        
+    {
+      uint16_t group = GetTagValue(name);
+      uint16_t element = GetTagValue(name + 5);
+      return DicomTag(group, element);
+    }
+
     const DcmDataDictionary& dict = dcmDataDict.rdlock();
     const DcmDictEntry* entry = dict.findEntry(name);
 
--- a/OrthancServer/FromDcmtkBridge.h	Tue Dec 18 19:01:01 2012 +0100
+++ b/OrthancServer/FromDcmtkBridge.h	Wed Dec 19 14:57:18 2012 +0100
@@ -71,8 +71,23 @@
                          const std::string& value,
                          bool insertOnAbsent);
 
+    void Setup(const char* content,
+               size_t size);
+
   public:
-    ParsedDicomFile(const std::string& content);
+    ParsedDicomFile(const char* content,
+                    size_t size)
+    {
+      Setup(content, size);
+    }
+
+    ParsedDicomFile(const std::string& content)
+    {
+      if (content.size() == 0)
+        Setup(NULL, 0);
+      else
+        Setup(&content[0], content.size());
+    }
 
     DcmFileFormat& GetDicom()
     {
@@ -130,30 +145,30 @@
 
     static std::string GetName(const DicomTag& tag);
 
-    static DicomTag FindTag(const char* name);
+    static DicomTag ParseTag(const char* name);
 
-    static DicomTag FindTag(const std::string& name)
+    static DicomTag ParseTag(const std::string& name)
     {
-      return FindTag(name.c_str());
+      return ParseTag(name.c_str());
     }
 
     static bool HasTag(const DicomMap& fields,
                        const std::string& tagName)
     {
-      return fields.HasTag(FindTag(tagName));
+      return fields.HasTag(ParseTag(tagName));
     }
 
     static const DicomValue& GetValue(const DicomMap& fields,
                                       const std::string& tagName)
     {
-      return fields.GetValue(FindTag(tagName));
+      return fields.GetValue(ParseTag(tagName));
     }
 
     static void SetValue(DicomMap& target,
                          const std::string& tagName,
                          DicomValue* value)
     {
-      target.SetValue(FindTag(tagName), value);
+      target.SetValue(ParseTag(tagName), value);
     }
 
     static void Print(FILE* fp, 
--- a/OrthancServer/OrthancRestApi.cpp	Tue Dec 18 19:01:01 2012 +0100
+++ b/OrthancServer/OrthancRestApi.cpp	Wed Dec 19 14:57:18 2012 +0100
@@ -89,7 +89,7 @@
     Json::Value::Members members = query.getMemberNames();
     for (size_t i = 0; i < members.size(); i++)
     {
-      DicomTag t = FromDcmtkBridge::FindTag(members[i]);
+      DicomTag t = FromDcmtkBridge::ParseTag(members[i]);
       result.SetValue(t, query[members[i]].asString());
     }
 
@@ -765,46 +765,14 @@
 
     LOG(INFO) << "Receiving a DICOM file of " << postData.size() << " bytes through HTTP";
 
-    DcmFileFormat dicomFile;
-
-    {
-      // Prepare an input stream for the memory buffer
-      DcmInputBufferStream is;
-      is.setBuffer(&postData[0], postData.size());
-      is.setEos();
-
-      dicomFile.transferInit();
-      if (!dicomFile.read(is).good())
-      {
-        call.GetOutput().SignalError(Orthanc_HttpStatus_415_UnsupportedMediaType);
-        return;
-      }
-      dicomFile.loadAllDataIntoMemory();
-      dicomFile.transferEnd();
-    }
-
-    DicomMap dicomSummary;
-    FromDcmtkBridge::Convert(dicomSummary, *dicomFile.getDataset());
-
-    DicomInstanceHasher hasher(dicomSummary);
-
-    Json::Value dicomJson;
-    FromDcmtkBridge::ToJson(dicomJson, *dicomFile.getDataset());
-      
-    StoreStatus status = StoreStatus_Failure;
-    if (postData.size() > 0)
-    {
-      status = context.Store
-        (reinterpret_cast<const char*>(&postData[0]), postData.size(), 
-         dicomSummary, dicomJson, "");
-    }   
-
+    std::string publicId;
+    StoreStatus status = context.Store(publicId, postData);
     Json::Value result = Json::objectValue;
 
     if (status != StoreStatus_Failure)
     {
-      result["ID"] = hasher.HashInstance();
-      result["Path"] = GetBasePath(ResourceType_Instance, hasher.HashInstance());
+      result["ID"] = publicId;
+      result["Path"] = GetBasePath(ResourceType_Instance, publicId);
     }
 
     result["Status"] = ToString(status);
@@ -812,6 +780,7 @@
   }
 
 
+
   // DICOM bridge -------------------------------------------------------------
 
   static bool IsExistingModality(const OrthancRestApi::Modalities& modalities,
@@ -870,31 +839,54 @@
 
 
 
-  // Modification of DICOM tags -----------------------------------------------
+  // Modification of DICOM instances ------------------------------------------
+
+  static void ModifyInstanceInternal(ParsedDicomFile& toModify,
+                                     const Json::Value& replacements)
+  {
+    if (!replacements.isObject())
+    {
+      throw OrthancException(ErrorCode_BadRequest);
+    }
+
+    Json::Value::Members members = replacements.getMemberNames();
 
-  template <enum ResourceType resourceType>
-  static void Modify(RestApi::PostCall& call)
+    for (size_t i = 0; i < members.size(); i++)
+    {
+      const std::string& name = members[i];
+      std::string value = replacements[name].asString();
+
+      DicomTag tag = FromDcmtkBridge::ParseTag(name);      
+      toModify.Replace(tag, value);
+    }
+
+    // A new SOP instance UID is automatically generated
+    std::string instanceUid = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Instance);
+    toModify.Replace(DICOM_TAG_SOP_INSTANCE_UID, instanceUid);
+  }
+
+
+  static void ModifyInstance(RestApi::PostCall& call)
   {
     RETRIEVE_CONTEXT(call);
     
     std::string id = call.GetUriComponent("id", "");
     ParsedDicomFile& dicom = context.GetDicomFile(id);
     
-    std::auto_ptr<ParsedDicomFile> modified(dicom.Clone());
+    Json::Value request;
+    if (call.ParseJsonRequest(request))
+    {
+      std::auto_ptr<ParsedDicomFile> modified(dicom.Clone());
+      ModifyInstanceInternal(*modified, request);
+      modified->Answer(call.GetOutput());
+    }
 
-    std::string studyUid = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Study);
+    /*std::string studyUid = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Study);
     std::string seriesUid = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Series);
-    std::string instanceUid = FromDcmtkBridge::GenerateUniqueIdentifier(DicomRootLevel_Instance);
-
-    modified->Replace(DICOM_TAG_SOP_INSTANCE_UID, instanceUid);
     modified->Replace(DICOM_TAG_SERIES_INSTANCE_UID, seriesUid);
-    modified->Replace(DICOM_TAG_STUDY_INSTANCE_UID, studyUid);
+    modified->Replace(DICOM_TAG_STUDY_INSTANCE_UID, studyUid);*/
 
-    modified->InsertOrReplace(DicomTag(0x0010,0x0010), "0.42");
-    //modified->Remove(DicomTag(0x0010,0x0020));
-    /*modified->Insert(DicomTag(0x0018,0x9082), "0.42");
-      modified->Replace(DicomTag(0x0010,0x0010), "Hello");*/
-    modified->Answer(call.GetOutput());
+
   }
 
 
@@ -954,6 +946,6 @@
     Register("/modalities/{id}/find", DicomFind);
     Register("/modalities/{id}/store", DicomStore);
 
-    Register("/instances/{id}/modify", Modify<ResourceType_Instance>);
+    Register("/instances/{id}/modify", ModifyInstance);
   }
 }
--- a/OrthancServer/ServerContext.cpp	Tue Dec 18 19:01:01 2012 +0100
+++ b/OrthancServer/ServerContext.cpp	Wed Dec 19 14:57:18 2012 +0100
@@ -76,7 +76,7 @@
     storage_.Remove(fileUuid);
   }
 
-  StoreStatus ServerContext::Store(const char* dicomFile,
+  StoreStatus ServerContext::Store(const char* dicomInstance,
                                    size_t dicomSize,
                                    const DicomMap& dicomSummary,
                                    const Json::Value& dicomJson,
@@ -91,7 +91,7 @@
       accessor_.SetCompressionForNextOperations(CompressionType_None);
     }      
 
-    FileInfo dicomInfo = accessor_.Write(dicomFile, dicomSize, FileContentType_Dicom);
+    FileInfo dicomInfo = accessor_.Write(dicomInstance, dicomSize, FileContentType_Dicom);
     FileInfo jsonInfo = accessor_.Write(dicomJson.toStyledString(), FileContentType_Json);
 
     ServerIndex::Attachments attachments;
@@ -188,4 +188,54 @@
     return dynamic_cast<ParsedDicomFile&>(dicomCache_.Access(instancePublicId));
 #endif
   }
+
+
+  StoreStatus ServerContext::Store(std::string& resultPublicId,
+                                   DcmFileFormat& dicomInstance,
+                                   const char* dicomBuffer,
+                                   size_t dicomSize)
+  {
+    DicomMap dicomSummary;
+    FromDcmtkBridge::Convert(dicomSummary, *dicomInstance.getDataset());
+
+    DicomInstanceHasher hasher(dicomSummary);
+    resultPublicId = hasher.HashInstance();
+
+    Json::Value dicomJson;
+    FromDcmtkBridge::ToJson(dicomJson, *dicomInstance.getDataset());
+      
+    StoreStatus status = StoreStatus_Failure;
+    if (dicomSize > 0)
+    {
+      status = Store(dicomBuffer, dicomSize, dicomSummary, dicomJson, "");
+    }   
+
+    return status;
+  }
+
+
+  StoreStatus ServerContext::Store(std::string& resultPublicId,
+                                   DcmFileFormat& dicomInstance)
+  {
+    std::string buffer;
+    if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer, dicomInstance.getDataset()))
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    if (buffer.size() == 0)
+      return Store(resultPublicId, dicomInstance, NULL, 0);
+    else
+      return Store(resultPublicId, dicomInstance, &buffer[0], buffer.size());
+  }
+
+
+  StoreStatus ServerContext::Store(std::string& resultPublicId,
+                                   const char* dicomBuffer,
+                                   size_t dicomSize)
+  {
+    ParsedDicomFile dicom(dicomBuffer, dicomSize);
+    return Store(resultPublicId, dicom.GetDicom(), dicomBuffer, dicomSize);
+  }
+
 }
--- a/OrthancServer/ServerContext.h	Tue Dec 18 19:01:01 2012 +0100
+++ b/OrthancServer/ServerContext.h	Wed Dec 19 14:57:18 2012 +0100
@@ -87,12 +87,33 @@
 
     void RemoveFile(const std::string& fileUuid);
 
-    StoreStatus Store(const char* dicomFile,
+    StoreStatus Store(const char* dicomInstance,
                       size_t dicomSize,
                       const DicomMap& dicomSummary,
                       const Json::Value& dicomJson,
                       const std::string& remoteAet);
 
+    StoreStatus Store(std::string& resultPublicId,
+                      DcmFileFormat& dicomInstance,
+                      const char* dicomBuffer,
+                      size_t dicomSize);
+
+    StoreStatus Store(std::string& resultPublicId,
+                      DcmFileFormat& dicomInstance);
+
+    StoreStatus Store(std::string& resultPublicId,
+                      const char* dicomBuffer,
+                      size_t dicomSize);
+
+    StoreStatus Store(std::string& resultPublicId,
+                      const std::string& dicomContent)
+    {
+      if (dicomContent.size() == 0)
+        return Store(resultPublicId, NULL, 0);
+      else
+        return Store(resultPublicId, &dicomContent[0], dicomContent.size());
+    }
+
     void AnswerFile(RestApiOutput& output,
                     const std::string& instancePublicId,
                     FileContentType content);
--- a/OrthancServer/ServerIndex.cpp	Tue Dec 18 19:01:01 2012 +0100
+++ b/OrthancServer/ServerIndex.cpp	Wed Dec 19 14:57:18 2012 +0100
@@ -962,4 +962,54 @@
       LOG(INFO) << "Patient " << publicId << " has been unprotected";
   }
 
+
+  void ServerIndex::GetChildInstances(std::list<std::string>& result,
+                                      const std::string& publicId)
+  {
+    result.clear();
+
+    boost::mutex::scoped_lock lock(mutex_);
+
+    ResourceType type;
+    int64_t top;
+    if (!db_->LookupResource(publicId, top, type))
+    {
+      throw OrthancException(ErrorCode_UnknownResource);
+    }
+
+    if (type == ResourceType_Instance)
+    {
+      // The resource is already an instance: Do not go down the hierarchy
+      result.push_back(publicId);
+      return;
+    }
+
+    std::stack<int64_t> toExplore;
+    toExplore.push(top);
+
+    std::list<int64_t> tmp;
+
+    while (!toExplore.empty())
+    {
+      // Get the internal ID of the current resource
+      int64_t resource = toExplore.top();
+      toExplore.pop();
+
+      if (db_->GetResourceType(resource) == ResourceType_Instance)
+      {
+        result.push_back(db_->GetPublicId(resource));
+      }
+      else
+      {
+        // Tag all the children of this resource as to be explored
+        db_->GetChildrenInternalId(tmp, resource);
+        for (std::list<int64_t>::const_iterator 
+               it = tmp.begin(); it != tmp.end(); it++)
+        {
+          toExplore.push(*it);
+        }
+      }
+    }
+  }
+
 }
--- a/OrthancServer/ServerIndex.h	Tue Dec 18 19:01:01 2012 +0100
+++ b/OrthancServer/ServerIndex.h	Wed Dec 19 14:57:18 2012 +0100
@@ -142,5 +142,9 @@
 
     void SetProtectedPatient(const std::string& publicId,
                              bool isProtected);
+
+    void GetChildInstances(std::list<std::string>& result,
+                           const std::string& publicId);
+
   };
 }
--- a/UnitTests/ServerIndex.cpp	Tue Dec 18 19:01:01 2012 +0100
+++ b/UnitTests/ServerIndex.cpp	Wed Dec 19 14:57:18 2012 +0100
@@ -63,6 +63,14 @@
   ASSERT_EQ("f", index.GetPublicId(a[5]));
   ASSERT_EQ("g", index.GetPublicId(a[6]));
 
+  ASSERT_EQ(ResourceType_Patient, index.GetResourceType(a[0]));
+  ASSERT_EQ(ResourceType_Study, index.GetResourceType(a[1]));
+  ASSERT_EQ(ResourceType_Series, index.GetResourceType(a[2]));
+  ASSERT_EQ(ResourceType_Instance, index.GetResourceType(a[3]));
+  ASSERT_EQ(ResourceType_Instance, index.GetResourceType(a[4]));
+  ASSERT_EQ(ResourceType_Instance, index.GetResourceType(a[5]));
+  ASSERT_EQ(ResourceType_Study, index.GetResourceType(a[6]));
+
   {
     Json::Value t;
     index.GetAllPublicIds(t, ResourceType_Patient);
--- a/UnitTests/main.cpp	Tue Dec 18 19:01:01 2012 +0100
+++ b/UnitTests/main.cpp	Wed Dec 19 14:57:18 2012 +0100
@@ -101,9 +101,13 @@
 {
   ASSERT_EQ("PatientName", FromDcmtkBridge::GetName(DicomTag(0x0010, 0x0010)));
 
-  DicomTag t = FromDcmtkBridge::FindTag("SeriesDescription");
+  DicomTag t = FromDcmtkBridge::ParseTag("SeriesDescription");
   ASSERT_EQ(0x0008, t.GetGroup());
   ASSERT_EQ(0x103E, t.GetElement());
+
+  t = FromDcmtkBridge::ParseTag("0020-e040");
+  ASSERT_EQ(0x0020, t.GetGroup());
+  ASSERT_EQ(0xe040, t.GetElement());
 }