changeset 1555:d6a93e12b1c1

Creation of DICOM files with encapsulated PDF
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 20 Aug 2015 15:18:13 +0200
parents 89ab71a68fcf
children b8dc2f855a83
files Core/DicomFormat/DicomTag.h Core/Enumerations.cpp Core/Enumerations.h Core/Toolbox.cpp Core/Toolbox.h NEWS OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp OrthancServer/OrthancRestApi/OrthancRestResources.cpp OrthancServer/ParsedDicomFile.cpp OrthancServer/ParsedDicomFile.h OrthancServer/ServerEnumerations.cpp OrthancServer/ServerEnumerations.h OrthancServer/ServerIndex.cpp OrthancServer/ServerIndex.h
diffstat 14 files changed, 426 insertions(+), 103 deletions(-) [+]
line wrap: on
line diff
--- a/Core/DicomFormat/DicomTag.h	Thu Aug 20 11:56:42 2015 +0200
+++ b/Core/DicomFormat/DicomTag.h	Thu Aug 20 15:18:13 2015 +0200
@@ -106,11 +106,7 @@
   static const DicomTag DICOM_TAG_NUMBER_OF_FRAMES(0x0028, 0x0008);
   static const DicomTag DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES(0x0018, 0x1090);
   static const DicomTag DICOM_TAG_IMAGES_IN_ACQUISITION(0x0020, 0x1002);
-
   static const DicomTag DICOM_TAG_PATIENT_NAME(0x0010, 0x0010);
-  static const DicomTag DICOM_TAG_STUDY_DATE(0x0008, 0x0020);
-  static const DicomTag DICOM_TAG_SERIES_DATE(0x0008, 0x0021);
-  static const DicomTag DICOM_TAG_PATIENT_BIRTH_DATE(0x0010, 0x0030);
 
   // The following is used for "modify/anonymize" operations
   static const DicomTag DICOM_TAG_SOP_CLASS_UID(0x0008, 0x0016);
@@ -137,4 +133,18 @@
   static const DicomTag DICOM_TAG_PIXEL_REPRESENTATION(0x0028, 0x0103);
   static const DicomTag DICOM_TAG_PLANAR_CONFIGURATION(0x0028, 0x0006);
   static const DicomTag DICOM_TAG_PHOTOMETRIC_INTERPRETATION(0x0028, 0x0004);
+
+  // Tags related to date and time
+  static const DicomTag DICOM_TAG_ACQUISITION_DATE(0x0008, 0x0022);
+  static const DicomTag DICOM_TAG_ACQUISITION_TIME(0x0008, 0x0032);
+  static const DicomTag DICOM_TAG_CONTENT_DATE(0x0008, 0x0023);
+  static const DicomTag DICOM_TAG_CONTENT_TIME(0x0008, 0x0033);
+  static const DicomTag DICOM_TAG_INSTANCE_CREATION_DATE(0x0008, 0x0012);
+  static const DicomTag DICOM_TAG_INSTANCE_CREATION_TIME(0x0008, 0x0013);
+  static const DicomTag DICOM_TAG_PATIENT_BIRTH_DATE(0x0010, 0x0030);
+  static const DicomTag DICOM_TAG_PATIENT_BIRTH_TIME(0x0010, 0x0032);
+  static const DicomTag DICOM_TAG_SERIES_DATE(0x0008, 0x0021);
+  static const DicomTag DICOM_TAG_SERIES_TIME(0x0008, 0x0031);
+  static const DicomTag DICOM_TAG_STUDY_DATE(0x0008, 0x0020);
+  static const DicomTag DICOM_TAG_STUDY_TIME(0x0008, 0x0030);
 }
--- a/Core/Enumerations.cpp	Thu Aug 20 11:56:42 2015 +0200
+++ b/Core/Enumerations.cpp	Thu Aug 20 15:18:13 2015 +0200
@@ -691,4 +691,61 @@
         return "";
     }
   }
+
+
+  ResourceType GetChildResourceType(ResourceType type)
+  {
+    switch (type)
+    {
+      case ResourceType_Patient:
+        return ResourceType_Study;
+
+      case ResourceType_Study:
+        return ResourceType_Series;
+        
+      case ResourceType_Series:
+        return ResourceType_Instance;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  ResourceType GetParentResourceType(ResourceType type)
+  {
+    switch (type)
+    {
+      case ResourceType_Study:
+        return ResourceType_Patient;
+        
+      case ResourceType_Series:
+        return ResourceType_Study;
+
+      case ResourceType_Instance:
+        return ResourceType_Series;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  DicomModule GetModule(ResourceType type)
+  {
+    switch (type)
+    {
+      case ResourceType_Patient:
+        return DicomModule_Patient;
+
+      case ResourceType_Study:
+        return DicomModule_Study;
+        
+      case ResourceType_Series:
+        return DicomModule_Series;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
 }
--- a/Core/Enumerations.h	Thu Aug 20 11:56:42 2015 +0200
+++ b/Core/Enumerations.h	Thu Aug 20 15:18:13 2015 +0200
@@ -380,4 +380,10 @@
   const char* GetMimeType(FileContentType type);
 
   const char* GetFileExtension(FileContentType type);
+
+  ResourceType GetChildResourceType(ResourceType type);
+
+  ResourceType GetParentResourceType(ResourceType type);
+
+  DicomModule GetModule(ResourceType type);
 }
--- a/Core/Toolbox.cpp	Thu Aug 20 11:56:42 2015 +0200
+++ b/Core/Toolbox.cpp	Thu Aug 20 15:18:13 2015 +0200
@@ -776,6 +776,21 @@
     boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
     return boost::posix_time::to_iso_string(now);
   }
+
+  void Toolbox::GetNowDicom(std::string& date,
+                            std::string& time)
+  {
+    boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
+    tm tm = boost::posix_time::to_tm(now);
+
+    char s[32];
+    sprintf(s, "%04d%02d%02d", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
+    date.assign(s);
+
+    // TODO milliseconds
+    sprintf(s, "%02d%02d%02d.%06d", tm.tm_hour, tm.tm_min, tm.tm_sec, 0);
+    time.assign(s);
+  }
 #endif
 
 
--- a/Core/Toolbox.h	Thu Aug 20 11:56:42 2015 +0200
+++ b/Core/Toolbox.h	Thu Aug 20 15:18:13 2015 +0200
@@ -128,6 +128,9 @@
 
 #if BOOST_HAS_DATE_TIME == 1
     std::string GetNowIsoString();
+
+    void GetNowDicom(std::string& date,
+                     std::string& time);
 #endif
 
     // In-place percent-decoding for URL
--- a/NEWS	Thu Aug 20 11:56:42 2015 +0200
+++ b/NEWS	Thu Aug 20 15:18:13 2015 +0200
@@ -1,6 +1,8 @@
 Pending changes in the mainline
 ===============================
 
+
+* Creation of DICOM files with encapsulated PDF through "/tools/create-dicom"
 * "limit" and "since" arguments while retrieving DICOM resources in the REST API
 * Support of "deflate" and "gzip" content-types in HTTP requests
 * Options to validate peers against CA certificates in HTTPS requests
--- a/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Thu Aug 20 11:56:42 2015 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Thu Aug 20 15:18:13 2015 +0200
@@ -428,46 +428,256 @@
   }
 
 
-  static void CreateDicom(RestApiPostCall& call)
+  static bool CreateDicomV1(ParsedDicomFile& dicom,
+                            const Json::Value& request)
   {
     // curl http://localhost:8042/tools/create-dicom -X POST -d '{"PatientName":"Hello^World"}'
     // curl http://localhost:8042/tools/create-dicom -X POST -d '{"PatientName":"Hello^World","PixelData":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUGDDcB53FulQAAAElJREFUGNNtj0sSAEEEQ1+U+185s1CtmRkblQ9CZldsKHJDk6DLGLJa6chjh0ooQmpjXMM86zPwydGEj6Ed/UGykkEM8X+p3u8/8LcOJIWLGeMAAAAASUVORK5CYII="}'
 
-    Json::Value replacements;
-    if (call.ParseJsonRequest(replacements) && replacements.isObject())
+    assert(request.isObject());
+    LOG(WARNING) << "Using a deprecated call to /tools/create-dicom";
+
+    Json::Value::Members members = request.getMemberNames();
+    for (size_t i = 0; i < members.size(); i++)
+    {
+      const std::string& name = members[i];
+      if (request[name].type() != Json::stringValue)
+      {
+        LOG(ERROR) << "Only string values are supported when creating DICOM instances";
+        return false;
+      }
+
+      std::string value = request[name].asString();
+
+      DicomTag tag = FromDcmtkBridge::ParseTag(name);
+      if (tag == DICOM_TAG_PIXEL_DATA)
+      {
+        dicom.EmbedImage(value);
+      }
+      else
+      {
+        dicom.Replace(tag, value);
+      }
+    }
+
+    return true;
+  }
+
+
+  static bool CreateDicomV2(ParsedDicomFile& dicom,
+                            ServerContext& context,
+                            const Json::Value& request)
+  {
+    assert(request.isObject());
+
+    if (!request.isMember("Tags") ||
+        request["Tags"].type() != Json::objectValue)
+    {
+      return false;
+    }
+
+    ResourceType parentType = ResourceType_Instance;
+
+    if (request.isMember("Parent"))
+    {
+      // Locate the parent tags
+      std::string parent = request["Parent"].asString();
+      if (!context.GetIndex().LookupResourceType(parentType, parent))
+      {
+        LOG(ERROR) << "Trying to attach a new DICOM instance to an inexistent resource: " << parent;
+        return false;
+      }
+
+      if (parentType == ResourceType_Instance)
+      {
+        LOG(ERROR) << "Trying to attach a new DICOM instance to an instance (must be a series, study or patient): " << parent;
+        return false;
+      }
+
+      // Select one existing child instance of the parent resource, to
+      // retrieve all its tags
+      Json::Value siblingTags;
+
+      {
+        // Retrieve all the instances of the parent resource
+        std::list<std::string>  siblingInstances;
+        context.GetIndex().GetChildInstances(siblingInstances, parent);
+
+        if (siblingInstances.empty())
+        {
+          return false;   // Error: No instance (should never happen)
+        }
+
+        context.ReadJson(siblingTags, siblingInstances.front());
+      }
+
+      // Retrieve the tags for all the parent modules
+      typedef std::set<DicomTag> ModuleTags;
+      ModuleTags moduleTags;
+
+      ResourceType type = parentType;
+      for (;;)
+      {
+        DicomTag::AddTagsForModule(moduleTags, GetModule(type));
+      
+        if (type == ResourceType_Patient)
+        {
+          break;   // We're done
+        }
+
+        // Go up
+        std::string tmp;
+        if (!context.GetIndex().LookupParent(tmp, parent))
+        {
+          return false;
+        }
+
+        parent = tmp;
+        type = GetParentResourceType(type);
+      }
+
+      for (ModuleTags::const_iterator it = moduleTags.begin();
+           it != moduleTags.end(); it++)
+      {
+        std::string t = it->Format();
+        if (siblingTags.isMember(t))
+        {
+          const Json::Value& tag = siblingTags[t];
+          if (tag["Type"] == "Null")
+          {
+            dicom.Replace(*it, "");
+          }
+          else if (tag["Type"] == "String")
+          {
+            dicom.Replace(*it, tag["Value"].asString());
+          }
+        }
+      }
+    }
+
+
+    // Inject time-related information
+    std::string date, time;
+    Toolbox::GetNowDicom(date, time);
+    dicom.Replace(DICOM_TAG_ACQUISITION_DATE, date);
+    dicom.Replace(DICOM_TAG_ACQUISITION_TIME, time);
+    dicom.Replace(DICOM_TAG_CONTENT_DATE, date);
+    dicom.Replace(DICOM_TAG_CONTENT_TIME, time);
+    dicom.Replace(DICOM_TAG_INSTANCE_CREATION_DATE, date);
+    dicom.Replace(DICOM_TAG_INSTANCE_CREATION_TIME, time);
+
+    if (parentType == ResourceType_Patient ||
+        parentType == ResourceType_Study ||
+        parentType == ResourceType_Instance /* no parent */)
+    {
+      dicom.Replace(DICOM_TAG_SERIES_DATE, date);
+      dicom.Replace(DICOM_TAG_SERIES_TIME, time);
+    }
+
+    if (parentType == ResourceType_Patient ||
+        parentType == ResourceType_Instance /* no parent */)
+    {
+      dicom.Replace(DICOM_TAG_STUDY_DATE, date);
+      dicom.Replace(DICOM_TAG_STUDY_TIME, time);
+    }
+
+
+    // Inject the user-specified tags
+    Json::Value::Members members = request["Tags"].getMemberNames();
+    for (size_t i = 0; i < members.size(); i++)
+    {
+      const std::string& name = members[i];
+      if (request["Tags"][name].type() != Json::stringValue)
+      {
+        LOG(ERROR) << "Only string values are supported when creating DICOM instances";
+        return false;
+      }
+
+      std::string value = request["Tags"][name].asString();
+
+      DicomTag tag = FromDcmtkBridge::ParseTag(name);
+      if (dicom.HasTag(tag))
+      {
+        LOG(ERROR) << "Trying to override a value inherited from a parent module";
+        return false;
+      }
+
+      if (tag == DICOM_TAG_PIXEL_DATA)
+      {
+        LOG(ERROR) << "Use \"Content\" to inject an image into a new DICOM instance";
+        return false;
+      }
+      else
+      {
+        dicom.Replace(tag, value);
+      }
+    }
+
+
+    // Inject the content (either an image, or a PDF file)
+    if (request.isMember("Content"))
+    {
+      if (request["Content"].type() != Json::stringValue)
+      {
+        LOG(ERROR) << "The payload of the DICOM instance must be specified according to Data URI scheme";
+        return false;
+      }
+
+      std::string mime, base64;
+      Toolbox::DecodeDataUriScheme(mime, base64, request["Content"].asString());
+      Toolbox::ToLowerCase(mime);
+
+      std::string content;
+      Toolbox::DecodeBase64(content, base64);
+
+      if (mime == "image/png")
+      {
+        dicom.EmbedImage(mime, content);
+      }
+      else if (mime == "application/pdf")
+      {
+        dicom.EmbedPdf(content);
+      }
+      else
+      {
+        LOG(ERROR) << "Unsupported MIME type for the content of a new DICOM file";
+        return false;
+      }
+    }
+
+
+    return true;
+  }
+
+
+  static void CreateDicom(RestApiPostCall& call)
+  {
+    ServerContext& context = OrthancRestApi::GetContext(call);
+
+    Json::Value request;
+    if (call.ParseJsonRequest(request) && 
+        request.isObject())
     {
       ParsedDicomFile dicom;
 
-      Json::Value::Members members = replacements.getMemberNames();
-      for (size_t i = 0; i < members.size(); i++)
+      if (request.isMember("Tags") ? 
+          CreateDicomV2(dicom, context, request) :
+          CreateDicomV1(dicom, request))
       {
-        const std::string& name = members[i];
-        std::string value = replacements[name].asString();
+        DicomInstanceToStore toStore;
+        toStore.SetParsedDicomFile(dicom);
 
-        DicomTag tag = FromDcmtkBridge::ParseTag(name);
-        if (tag == DICOM_TAG_PIXEL_DATA)
+        std::string id;
+        StoreStatus status = OrthancRestApi::GetContext(call).Store(id, toStore);
+
+        if (status == StoreStatus_Failure)
         {
-          dicom.EmbedImage(value);
-        }
-        else
-        {
-          dicom.Replace(tag, value);
+          LOG(ERROR) << "Error while storing a manually-created instance";
+          return;
         }
+
+        OrthancRestApi::GetApi(call).AnswerStoredInstance(call, id, status);
       }
-
-      DicomInstanceToStore toStore;
-      toStore.SetParsedDicomFile(dicom);
-
-      std::string id;
-      StoreStatus status = OrthancRestApi::GetContext(call).Store(id, toStore);
-
-      if (status == StoreStatus_Failure)
-      {
-        LOG(ERROR) << "Error while storing a manually-created instance";
-        return;
-      }
-
-      OrthancRestApi::GetApi(call).AnswerStoredInstance(call, id, status);
     }
   }
 
--- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Thu Aug 20 11:56:42 2015 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Thu Aug 20 15:18:13 2015 +0200
@@ -956,23 +956,7 @@
         b.splice(b.begin(), c);
       }
 
-      switch (type)
-      {
-        case ResourceType_Patient:
-          type = ResourceType_Study;
-          break;
-
-        case ResourceType_Study:
-          type = ResourceType_Series;
-          break;
-
-        case ResourceType_Series:
-          type = ResourceType_Instance;
-          break;
-
-        default:
-          throw OrthancException(ErrorCode_InternalError);
-      }
+      type = GetChildResourceType(type);
 
       a.clear();
       a.splice(a.begin(), b);
@@ -1053,13 +1037,7 @@
       }
       
       current = parent;
-      switch (currentType)
-      {
-        case ResourceType_Instance:  currentType = ResourceType_Series; break;
-        case ResourceType_Series:    currentType = ResourceType_Study; break;
-        case ResourceType_Study:     currentType = ResourceType_Patient; break;
-        default:                     throw OrthancException(ErrorCode_InternalError);
-      }
+      currentType = GetParentResourceType(currentType);
     }
 
     assert(currentType == end);
--- a/OrthancServer/ParsedDicomFile.cpp	Thu Aug 20 11:56:42 2015 +0200
+++ b/OrthancServer/ParsedDicomFile.cpp	Thu Aug 20 15:18:13 2015 +0200
@@ -107,6 +107,7 @@
 #include <dcmtk/dcmdata/dcistrmb.h>
 #include <dcmtk/dcmdata/dcuid.h>
 #include <dcmtk/dcmdata/dcmetinf.h>
+#include <dcmtk/dcmdata/dcdeftag.h>
 
 #include <dcmtk/dcmdata/dcvrae.h>
 #include <dcmtk/dcmdata/dcvras.h>
@@ -1133,16 +1134,23 @@
 
   void ParsedDicomFile::EmbedImage(const std::string& dataUriScheme)
   {
-    std::string mime, content;
-    Toolbox::DecodeDataUriScheme(mime, content, dataUriScheme);
+    std::string mime, base64;
+    Toolbox::DecodeDataUriScheme(mime, base64, dataUriScheme);
+
+    std::string content;
+    Toolbox::DecodeBase64(content, base64);
 
-    std::string decoded;
-    Toolbox::DecodeBase64(decoded, content);
+    EmbedImage(mime, content);
+  }
+
 
+  void ParsedDicomFile::EmbedImage(const std::string& mime,
+                                   const std::string& content)
+  {
     if (mime == "image/png")
     {
       PngReader reader;
-      reader.ReadFromMemory(decoded);
+      reader.ReadFromMemory(content);
       EmbedImage(reader);
     }
     else
@@ -1408,4 +1416,59 @@
       FromDcmtkBridge::ToJson(target, *pimpl_->file_->getDataset());
     }
   }
+
+
+  bool ParsedDicomFile::HasTag(const DicomTag& tag) const
+  {
+    DcmTag key(tag.GetGroup(), tag.GetElement());
+    return pimpl_->file_->getDataset()->tagExists(key);
+  }
+
+
+  void ParsedDicomFile::EmbedPdf(const std::string& pdf)
+  {
+    if (pdf.size() < 5 ||  // (*)
+        strncmp("%PDF-", pdf.c_str(), 5) != 0)
+    {
+      LOG(ERROR) << "Not a PDF file";
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    Replace(DICOM_TAG_SOP_CLASS_UID, UID_EncapsulatedPDFStorage);
+    Replace(FromDcmtkBridge::Convert(DCM_Modality), "OT");
+    Replace(FromDcmtkBridge::Convert(DCM_ConversionType), "WSD");
+    Replace(FromDcmtkBridge::Convert(DCM_MIMETypeOfEncapsulatedDocument), "application/pdf");
+    Replace(FromDcmtkBridge::Convert(DCM_SeriesNumber), "1");
+
+    std::auto_ptr<DcmPolymorphOBOW> element(new DcmPolymorphOBOW(DCM_EncapsulatedDocument));
+
+    size_t s = pdf.size();
+    if (s & 1)
+    {
+      // The size of the buffer must be even
+      s += 1;
+    }
+
+    Uint8* bytes = NULL;
+    OFCondition result = element->createUint8Array(s, bytes);
+    if (!result.good() || bytes == NULL)
+    {
+      throw OrthancException(ErrorCode_NotEnoughMemory);
+    }
+
+    // Blank pad byte (no access violation, as "pdf.size() >= 5" because of (*) )
+    bytes[s - 1] = 0;
+
+    memcpy(bytes, pdf.c_str(), pdf.size());
+      
+    DcmPolymorphOBOW* obj = element.release();
+    result = pimpl_->file_->getDataset()->insert(obj);
+
+    if (!result.good())
+    {
+      delete obj;
+      throw OrthancException(ErrorCode_NotEnoughMemory);
+    }
+  }
+
 }
--- a/OrthancServer/ParsedDicomFile.h	Thu Aug 20 11:56:42 2015 +0200
+++ b/OrthancServer/ParsedDicomFile.h	Thu Aug 20 15:18:13 2015 +0200
@@ -104,6 +104,9 @@
 
     void EmbedImage(const std::string& dataUriScheme);
 
+    void EmbedImage(const std::string& mime,
+                    const std::string& content);
+
     void ExtractImage(ImageBuffer& result,
                       unsigned int frame);
 
@@ -121,6 +124,10 @@
 
     void ToJson(Json::Value& target, 
                 bool simplify);
+
+    bool HasTag(const DicomTag& tag) const;
+
+    void EmbedPdf(const std::string& pdf);
   };
 
 }
--- a/OrthancServer/ServerEnumerations.cpp	Thu Aug 20 11:56:42 2015 +0200
+++ b/OrthancServer/ServerEnumerations.cpp	Thu Aug 20 15:18:13 2015 +0200
@@ -243,44 +243,6 @@
   }
 
 
-  ResourceType GetParentResourceType(ResourceType type)
-  {
-    switch (type)
-    {
-      case ResourceType_Study:
-        return ResourceType_Patient;
-
-      case ResourceType_Series:
-        return ResourceType_Study;
-
-      case ResourceType_Instance:
-        return ResourceType_Series;
-      
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  ResourceType GetChildResourceType(ResourceType type)
-  {
-    switch (type)
-    {
-      case ResourceType_Patient:
-        return ResourceType_Study;
-
-      case ResourceType_Study:
-        return ResourceType_Series;
-
-      case ResourceType_Series:
-        return ResourceType_Instance;
-      
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
   const char* EnumerationToString(ModalityManufacturer manufacturer)
   {
     switch (manufacturer)
--- a/OrthancServer/ServerEnumerations.h	Thu Aug 20 11:56:42 2015 +0200
+++ b/OrthancServer/ServerEnumerations.h	Thu Aug 20 15:18:13 2015 +0200
@@ -187,8 +187,4 @@
   const char* EnumerationToString(TransferSyntax syntax);
 
   ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer);
-
-  ResourceType GetParentResourceType(ResourceType type);
-
-  ResourceType GetChildResourceType(ResourceType type);
 }
--- a/OrthancServer/ServerIndex.cpp	Thu Aug 20 11:56:42 2015 +0200
+++ b/OrthancServer/ServerIndex.cpp	Thu Aug 20 15:18:13 2015 +0200
@@ -2097,4 +2097,15 @@
       return true;
     }    
   }
+
+
+  bool ServerIndex::LookupResourceType(ResourceType& type,
+                                       const std::string& publicId)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    int64_t id;
+    return db_.LookupResource(id, type, publicId);
+  }
+
 }
--- a/OrthancServer/ServerIndex.h	Thu Aug 20 11:56:42 2015 +0200
+++ b/OrthancServer/ServerIndex.h	Thu Aug 20 15:18:13 2015 +0200
@@ -264,5 +264,8 @@
     bool GetMainDicomTags(DicomMap& result,
                           const std::string& publicId,
                           ResourceType expectedType);
+
+    bool LookupResourceType(ResourceType& type,
+                            const std::string& publicId);
   };
 }