[med-svn] [orthanc] 01/04: Imported Upstream version 1.0.0+dfsg

Sebastien Jodogne jodogne-guest at moszumanska.debian.org
Wed Dec 16 12:49:14 UTC 2015


This is an automated email from the git hooks/post-receive script.

jodogne-guest pushed a commit to branch master
in repository orthanc.

commit c66c8cc4566b3e33c9db99e08a4829805ade0455
Author: jodogne-guest <s.jodogne at gmail.com>
Date:   Wed Dec 16 13:24:09 2015 +0100

    Imported Upstream version 1.0.0+dfsg
---
 .hg_archival.txt                                   |   6 +-
 CMakeLists.txt                                     |   2 +-
 Core/HttpServer/HttpOutput.cpp                     |  70 +++--
 Core/HttpServer/HttpOutput.h                       |  12 +-
 NEWS                                               |  11 +
 OrthancServer/DicomDirWriter.cpp                   | 331 +++++++++------------
 .../DicomProtocol/RemoteModalityParameters.cpp     |  13 +-
 OrthancServer/OrthancFindRequestHandler.cpp        |  84 +++++-
 OrthancServer/OrthancFindRequestHandler.h          |   6 +
 OrthancServer/OrthancInitialization.cpp            |  59 ++--
 OrthancServer/Search/HierarchicalMatcher.cpp       |  12 +-
 OrthancServer/Search/RangeConstraint.cpp           |  11 +-
 Plugins/Engine/OrthancPlugins.cpp                  |  47 ++-
 Plugins/Engine/OrthancPlugins.h                    |   4 +
 Plugins/Include/orthanc/OrthancCPlugin.h           |  62 +++-
 Plugins/Samples/ModalityWorklists/Plugin.cpp       |   7 +-
 Resources/CMake/DcmtkConfiguration.cmake           |   6 +-
 .../Samples/Lua/IncomingFindRequestFilter.lua      |  14 +
 18 files changed, 498 insertions(+), 259 deletions(-)

diff --git a/.hg_archival.txt b/.hg_archival.txt
index 2cc1f3b..6473729 100644
--- a/.hg_archival.txt
+++ b/.hg_archival.txt
@@ -1,5 +1,5 @@
 repo: 3959d33612ccaadc0d4d707227fbed09ac35e5fe
-node: b4e8a031b0d86caf89e1a99fb7b86bb94d00c6a8
-branch: Orthanc-0.9.6
+node: 74cf1f350b45473e847fed4116c1b428fa3d7352
+branch: Orthanc-1.0.0
 latesttag: null
-latesttagdistance: 1612
+latesttagdistance: 1626
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2737e27..7f6ae71 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 2.8)
 project(Orthanc)
 
 # Version of the build, should always be "mainline" except in release branches
-set(ORTHANC_VERSION "0.9.6")
+set(ORTHANC_VERSION "1.0.0")
 
 # Version of the database schema. History:
 #   * Orthanc 0.1.0 -> Orthanc 0.3.0 = no versioning
diff --git a/Core/HttpServer/HttpOutput.cpp b/Core/HttpServer/HttpOutput.cpp
index d28ae80..b749e90 100644
--- a/Core/HttpServer/HttpOutput.cpp
+++ b/Core/HttpServer/HttpOutput.cpp
@@ -449,12 +449,59 @@ namespace Orthanc
   }
 
 
-  void HttpOutput::StateMachine::SendMultipartItem(const void* item, size_t length)
+  void HttpOutput::StateMachine::SendMultipartItem(const void* item, 
+                                                   size_t length,
+                                                   const std::map<std::string, std::string>& headers)
   {
+    if (state_ != State_WritingMultipart)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
     std::string header = "--" + multipartBoundary_ + "\r\n";
-    header += "Content-Type: " + multipartContentType_ + "\r\n";
-    header += "Content-Length: " + boost::lexical_cast<std::string>(length) + "\r\n";
-    header += "MIME-Version: 1.0\r\n\r\n";
+
+    bool hasContentType = false;
+    bool hasContentLength = false;
+    bool hasMimeVersion = false;
+
+    for (std::map<std::string, std::string>::const_iterator
+           it = headers.begin(); it != headers.end(); ++it)
+    {
+      header += it->first + ": " + it->second + "\r\n";
+
+      std::string tmp;
+      Toolbox::ToLowerCase(tmp, it->first);
+
+      if (tmp == "content-type")
+      {
+        hasContentType = true;
+      }
+
+      if (tmp == "content-length")
+      {
+        hasContentLength = true;
+      }
+
+      if (tmp == "mime-version")
+      {
+        hasMimeVersion = true;
+      }
+    }
+
+    if (!hasContentType)
+    {
+      header += "Content-Type: " + multipartContentType_ + "\r\n";
+    }
+
+    if (!hasContentLength)
+    {
+      header += "Content-Length: " + boost::lexical_cast<std::string>(length) + "\r\n";
+    }
+
+    if (!hasMimeVersion)
+    {
+      header += "MIME-Version: 1.0\r\n\r\n";
+    }
 
     stream_.Send(false, header.c_str(), header.size());
 
@@ -463,7 +510,7 @@ namespace Orthanc
       stream_.Send(false, item, length);
     }
 
-    stream_.Send(false, "\r\n", 2);
+    stream_.Send(false, "\r\n", 2);    
   }
 
 
@@ -489,19 +536,6 @@ namespace Orthanc
   }
 
 
-  void HttpOutput::SendMultipartItem(const std::string& item)
-  {
-    if (item.size() > 0)
-    {
-      stateMachine_.SendMultipartItem(item.c_str(), item.size());
-    }
-    else
-    {
-      stateMachine_.SendMultipartItem(NULL, 0);
-    }
-  }
-
-
   void HttpOutput::Answer(IHttpStreamAnswer& stream)
   {
     HttpCompression compression = stream.SetupHttpCompression(isGzipAllowed_, isDeflateAllowed_);
diff --git a/Core/HttpServer/HttpOutput.h b/Core/HttpServer/HttpOutput.h
index 370d441..8e2386b 100644
--- a/Core/HttpServer/HttpOutput.h
+++ b/Core/HttpServer/HttpOutput.h
@@ -99,7 +99,9 @@ namespace Orthanc
       void StartMultipart(const std::string& subType,
                           const std::string& contentType);
 
-      void SendMultipartItem(const void* item, size_t length);
+      void SendMultipartItem(const void* item, 
+                             size_t length,
+                             const std::map<std::string, std::string>& headers);
 
       void CloseMultipart();
 
@@ -202,11 +204,11 @@ namespace Orthanc
       stateMachine_.StartMultipart(subType, contentType);
     }
 
-    void SendMultipartItem(const std::string& item);
-
-    void SendMultipartItem(const void* item, size_t size)
+    void SendMultipartItem(const void* item, 
+                           size_t size,
+                           const std::map<std::string, std::string>& headers)
     {
-      stateMachine_.SendMultipartItem(item, size);
+      stateMachine_.SendMultipartItem(item, size, headers);
     }
 
     void CloseMultipart()
diff --git a/NEWS b/NEWS
index b762a16..6120d99 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,17 @@ Pending changes in the mainline
 ===============================
 
 
+Version 1.0.0 (2015/12/15)
+==========================
+
+* Lua: "IncomingFindRequestFilter()" to apply filters to incoming C-Find requests
+* New function in plugin SDK: "OrthancPluginSendMultipartItem2()"
+* Fix of DICOMDIR generation with DCMTK 3.6.1, support of encodings
+* Fix range search if the lower or upper limit is absent
+* Fix modality worklists lookups if tags with UN (unknown) VR are present
+* Warn about badly formatted modality/peer definitions in configuration file at startup
+
+
 Version 0.9.6 (2015/12/08)
 ==========================
 
diff --git a/OrthancServer/DicomDirWriter.cpp b/OrthancServer/DicomDirWriter.cpp
index b5edb85..a7d838d 100644
--- a/OrthancServer/DicomDirWriter.cpp
+++ b/OrthancServer/DicomDirWriter.cpp
@@ -134,173 +134,107 @@ namespace Orthanc
     Index  index_;
 
 
-    /*******************************************************************************
-     * Functions adapted from "dcmdata/libsrc/dcddirif.cc" from DCMTK 3.6.0
-     *******************************************************************************/
-
-    // print an error message to the console (stderr) that something went wrong with an attribute
-    static void printAttributeErrorMessage(const DcmTagKey &key,
-                                           const OFCondition &error,
-                                           const char *operation)
+    DcmDicomDir& GetDicomDir()
     {
-      if (error.bad())
+      if (dir_.get() == NULL)
       {
-        OFString str;
-        if (operation != NULL)
-        {
-          str = "cannot ";
-          str += operation;
-          str += " ";
-        }
-        LOG(ERROR) << error.text() << ": " << str << DcmTag(key).getTagName() << " " << key;
+        dir_.reset(new DcmDicomDir(file_.GetPath().c_str(), 
+                                   fileSetId_.c_str()));
+        //SetTagValue(dir_->getRootRecord(), DCM_SpecificCharacterSet, GetDicomSpecificCharacterSet(Encoding_Utf8));
       }
-    }
 
-    // copy element from dataset to directory record
-    static void copyElement(DcmItem& dataset,
-                            const DcmTagKey &key,
-                            DcmDirectoryRecord& record,
-                            const OFBool optional,
-                            const OFBool copyEmpty)
-    {
-      /* check whether tag exists in source dataset (if optional) */
-      if (!optional || (copyEmpty && dataset.tagExists(key)) || dataset.tagExistsWithValue(key))
-      {
-        DcmElement *delem = NULL;
-        /* get copy of element from source dataset */
-        OFCondition status = dataset.findAndGetElement(key, delem, OFFalse /*searchIntoSub*/, OFTrue /*createCopy*/);
-        if (status.good())
-        {
-          /* ... and insert it into the destination dataset (record) */
-          status = record.insert(delem, OFTrue /*replaceOld*/);
-          if (status.good())
-          {
-            DcmTag tag(key);
-            /* check for correct VR in the dataset */
-            if (delem->getVR() != tag.getEVR())
-            {
-              /* create warning message */
-              LOG(WARNING) << "DICOMDIR: possibly wrong VR: "
-                           << tag.getTagName() << " " << key << " with "
-                           << DcmVR(delem->getVR()).getVRName() << " found, expected "
-                           << tag.getVRName() << " instead";
-            }
-          } else
-            delete delem;
-        } else if (status == EC_TagNotFound)
-          status = record.insertEmptyElement(key);
-        printAttributeErrorMessage(key, status, "insert");
-      }
+      return *dir_;
     }
 
-    // copy optional string value from dataset to directory record
-    static void copyStringWithDefault(DcmItem& dataset,
-                                      const DcmTagKey &key,
-                                      DcmDirectoryRecord& record,
-                                      const char *defaultValue,
-                                      const OFBool printWarning)
+
+    DcmDirectoryRecord& GetRoot()
     {
-        OFCondition status;
-        if (dataset.tagExistsWithValue(key))
-        {
-          OFString stringValue;
-          /* retrieve string value from source dataset and put it into the destination dataset */
-          status = dataset.findAndGetOFStringArray(key, stringValue);
-          if (status.good())
-            status = record.putAndInsertString(key, stringValue.c_str());
-        } else {
-          if (printWarning && (defaultValue != NULL))
-          {
-            /* create warning message */
-            LOG(WARNING) << "DICOMDIR: " << DcmTag(key).getTagName() << " "
-                         << key << " missing, using alternative: " << defaultValue;
-          }
-          /* put default value */
-          status = record.putAndInsertString(key, defaultValue);
-        }
+      return GetDicomDir().getRootRecord();
     }
 
-    // create alternative study date if absent in dataset
-    static OFString &alternativeStudyDate(DcmItem& dataset,
-                                          OFString &result)
+
+    static bool GetUtf8TagValue(std::string& result,
+                                DcmItem& source,
+                                Encoding encoding,
+                                const DcmTagKey& key)
     {
-      /* use another date if present */
-      if (dataset.findAndGetOFStringArray(DCM_SeriesDate, result).bad() || result.empty())
+      DcmElement* element = NULL;
+
+      if (source.findAndGetElement(key, element).good())
       {
-        if (dataset.findAndGetOFStringArray(DCM_AcquisitionDate, result).bad() || result.empty())
+        char* s = NULL;
+        if (element->isLeaf() &&
+            element->getString(s).good() &&
+            s != NULL)
         {
-          if (dataset.findAndGetOFStringArray(DCM_ContentDate, result).bad() || result.empty())
-          {
-            /* use current date, "19000101" in case of error */
-            DcmDate::getCurrentDate(result);
-          }
+          result = Toolbox::ConvertToUtf8(s, encoding);
+          return true;
         }
       }
-      return result;
+
+      result.clear();
+      return false;
     }
 
 
-    // create alternative study time if absent in dataset
-    static OFString &alternativeStudyTime(DcmItem& dataset,
-                                          OFString &result)
+    static void SetTagValue(DcmDirectoryRecord& target,
+                            const DcmTagKey& key,
+                            const std::string& valueUtf8)
     {
-      /* use another time if present */
-      if (dataset.findAndGetOFStringArray(DCM_SeriesTime, result).bad() || result.empty())
+      std::string s = Toolbox::ConvertFromUtf8(valueUtf8, Encoding_Ascii);
+
+      if (!target.putAndInsertString(key, s.c_str()).good())
       {
-        if (dataset.findAndGetOFStringArray(DCM_AcquisitionTime, result).bad() || result.empty())
-        {
-          if (dataset.findAndGetOFStringArray(DCM_ContentTime, result).bad() || result.empty())
-          {
-            /* use current time, "0000" in case of error */
-            DcmTime::getCurrentTime(result);
-          }
-        }
+        throw OrthancException(ErrorCode_InternalError);
       }
-      return result;
     }
+                            
 
 
-    static void copyElementType1(DcmItem& dataset,
-                                 const DcmTagKey &key,
-                                 DcmDirectoryRecord& record)
+    static bool CopyString(DcmDirectoryRecord& target,
+                           DcmDataset& source,
+                           Encoding encoding,
+                           const DcmTagKey& key,
+                           bool optional,
+                           bool copyEmpty)
     {
-      copyElement(dataset, key, record, OFFalse /*optional*/, OFFalse /*copyEmpty*/);
-    }
+      if (optional &&
+          !source.tagExistsWithValue(key) &&
+          !(copyEmpty && source.tagExists(key)))
+      {
+        return false;
+      }
 
-    static void copyElementType1C(DcmItem& dataset,
-                                  const DcmTagKey &key,
-                                  DcmDirectoryRecord& record)
-    {
-      copyElement(dataset, key, record, OFTrue /*optional*/, OFFalse /*copyEmpty*/);
-    }
+      std::string value;
+      bool found = GetUtf8TagValue(value, source, encoding, key);
 
-    static void copyElementType2(DcmItem& dataset,
-                                 const DcmTagKey &key,
-                                 DcmDirectoryRecord& record)
-    {
-      copyElement(dataset, key, record, OFFalse /*optional*/, OFTrue /*copyEmpty*/);
+      SetTagValue(target, key, value);
+      return found;
     }
 
-    /*******************************************************************************
-     * End of functions adapted from "dcmdata/libsrc/dcddirif.cc" from DCMTK 3.6.0
-     *******************************************************************************/
-
 
-    DcmDicomDir& GetDicomDir()
+    static void CopyStringType1(DcmDirectoryRecord& target,
+                                DcmDataset& source,
+                                Encoding encoding,
+                                const DcmTagKey& key)
     {
-      if (dir_.get() == NULL)
-      {
-        dir_.reset(new DcmDicomDir(file_.GetPath().c_str(), 
-                                   fileSetId_.c_str()));
-      }
-
-      return *dir_;
+      CopyString(target, source, encoding, key, false, false);
     }
 
+    static void CopyStringType1C(DcmDirectoryRecord& target,
+                                 DcmDataset& source,
+                                 Encoding encoding,
+                                 const DcmTagKey& key)
+    {
+      CopyString(target, source, encoding, key, true, false);
+    }
 
-    DcmDirectoryRecord& GetRoot()
+    static void CopyStringType2(DcmDirectoryRecord& target,
+                                DcmDataset& source,
+                                Encoding encoding,
+                                const DcmTagKey& key)
     {
-      return GetDicomDir().getRootRecord();
+      CopyString(target, source, encoding, key, false, true);
     }
 
 
@@ -310,109 +244,127 @@ namespace Orthanc
     }
 
     void FillPatient(DcmDirectoryRecord& record,
-                     DcmItem& dicom)
+                     DcmDataset& dicom,
+                     Encoding encoding)
     {
       // cf. "DicomDirInterface::buildPatientRecord()"
 
-      copyElementType1C(dicom, DCM_PatientID, record);
-      copyElementType2(dicom, DCM_PatientName, record);
+      CopyStringType1C(record, dicom, encoding, DCM_PatientID);
+      CopyStringType2(record, dicom, encoding, DCM_PatientName);
     }
 
     void FillStudy(DcmDirectoryRecord& record,
-                   DcmItem& dicom)
+                   DcmDataset& dicom,
+                   Encoding encoding)
     {
       // cf. "DicomDirInterface::buildStudyRecord()"
 
-      OFString tmpString;
+      std::string nowDate, nowTime;
+      Toolbox::GetNowDicom(nowDate, nowTime);
+
+      std::string studyDate;
+      if (!GetUtf8TagValue(studyDate, dicom, encoding, DCM_StudyDate) &&
+          !GetUtf8TagValue(studyDate, dicom, encoding, DCM_SeriesDate) &&
+          !GetUtf8TagValue(studyDate, dicom, encoding, DCM_AcquisitionDate) &&
+          !GetUtf8TagValue(studyDate, dicom, encoding, DCM_ContentDate))
+      {
+        studyDate = nowDate;
+      }
+          
+      std::string studyTime;
+      if (!GetUtf8TagValue(studyTime, dicom, encoding, DCM_StudyTime) &&
+          !GetUtf8TagValue(studyTime, dicom, encoding, DCM_SeriesTime) &&
+          !GetUtf8TagValue(studyTime, dicom, encoding, DCM_AcquisitionTime) &&
+          !GetUtf8TagValue(studyTime, dicom, encoding, DCM_ContentTime))
+      {
+        studyTime = nowTime;
+      }
+
       /* copy attribute values from dataset to study record */
-      copyStringWithDefault(dicom, DCM_StudyDate, record, 
-                            alternativeStudyDate(dicom, tmpString).c_str(), OFTrue /*printWarning*/);
-      copyStringWithDefault(dicom, DCM_StudyTime, record, 
-                            alternativeStudyTime(dicom, tmpString).c_str(), OFTrue /*printWarning*/);
-      copyElementType2(dicom, DCM_StudyDescription, record);
-      copyElementType1(dicom, DCM_StudyInstanceUID, record);
+      SetTagValue(record, DCM_StudyDate, studyDate);
+      SetTagValue(record, DCM_StudyTime, studyTime);
+      CopyStringType2(record, dicom, encoding, DCM_StudyDescription);
+      CopyStringType1(record, dicom, encoding, DCM_StudyInstanceUID);
       /* use type 1C instead of 1 in order to avoid unwanted overwriting */
-      copyElementType1C(dicom, DCM_StudyID, record);
-      copyElementType2(dicom, DCM_AccessionNumber, record);
+      CopyStringType1C(record, dicom, encoding, DCM_StudyID);
+      CopyStringType2(record, dicom, encoding, DCM_AccessionNumber);
     }
 
     void FillSeries(DcmDirectoryRecord& record,
-                    DcmItem& dicom)
+                    DcmDataset& dicom,
+                    Encoding encoding)
     {
       // cf. "DicomDirInterface::buildSeriesRecord()"
 
       /* copy attribute values from dataset to series record */
-      copyElementType1(dicom, DCM_Modality, record);
-      copyElementType1(dicom, DCM_SeriesInstanceUID, record);
+      CopyStringType1(record, dicom, encoding, DCM_Modality);
+      CopyStringType1(record, dicom, encoding, DCM_SeriesInstanceUID);
       /* use type 1C instead of 1 in order to avoid unwanted overwriting */
-      copyElementType1C(dicom, DCM_SeriesNumber, record);
+      CopyStringType1C(record, dicom, encoding, DCM_SeriesNumber);
     }
 
     void FillInstance(DcmDirectoryRecord& record,
-                      DcmItem& dicom,
+                      DcmDataset& dicom,
+                      Encoding encoding,
                       DcmMetaInfo& metaInfo,
                       const char* path)
     {
       // cf. "DicomDirInterface::buildImageRecord()"
 
       /* copy attribute values from dataset to image record */
-      copyElementType1(dicom, DCM_InstanceNumber, record);
-      //copyElementType1C(dicom, DCM_ImageType, record);
-      copyElementType1C(dicom, DCM_ReferencedImageSequence, record);
-
-      OFString tmp;
+      CopyStringType1(record, dicom, encoding, DCM_InstanceNumber);
+      //CopyElementType1C(record, dicom, encoding, DCM_ImageType);
 
-      DcmElement* item = record.remove(DCM_ReferencedImageSequence);
-      if (item != NULL)
-      {
-        delete item;
-      }
+      // REMOVED since 0.9.7: copyElementType1C(dicom, DCM_ReferencedImageSequence, record);
 
-      if (record.putAndInsertString(DCM_ReferencedFileID, path).bad() ||
-          dicom.findAndGetOFStringArray(DCM_SOPClassUID, tmp).bad() ||
-          record.putAndInsertString(DCM_ReferencedSOPClassUIDInFile, tmp.c_str()).bad() ||
-          dicom.findAndGetOFStringArray(DCM_SOPInstanceUID, tmp).bad() ||
-          record.putAndInsertString(DCM_ReferencedSOPInstanceUIDInFile, tmp.c_str()).bad() ||
-          metaInfo.findAndGetOFStringArray(DCM_TransferSyntaxUID, tmp).bad() ||
-          record.putAndInsertString(DCM_ReferencedTransferSyntaxUIDInFile, tmp.c_str()).bad())
+      std::string sopClassUid, sopInstanceUid, transferSyntaxUid;
+      if (!GetUtf8TagValue(sopClassUid, dicom, encoding, DCM_SOPClassUID) ||
+          !GetUtf8TagValue(sopInstanceUid, dicom, encoding, DCM_SOPInstanceUID) ||
+          !GetUtf8TagValue(transferSyntaxUid, metaInfo, encoding, DCM_TransferSyntaxUID))
       {
         throw OrthancException(ErrorCode_BadFileFormat);
       }
+
+      SetTagValue(record, DCM_ReferencedFileID, path);
+      SetTagValue(record, DCM_ReferencedSOPClassUIDInFile, sopClassUid);
+      SetTagValue(record, DCM_ReferencedSOPInstanceUIDInFile, sopInstanceUid);
+      SetTagValue(record, DCM_ReferencedTransferSyntaxUIDInFile, transferSyntaxUid);
     }
 
     
 
     bool CreateResource(DcmDirectoryRecord*& target,
                         ResourceType level,
-                        DcmFileFormat& dicom,
+                        ParsedDicomFile& dicom,
                         const char* filename,
                         const char* path)
     {
-      DcmDataset& dataset = *dicom.getDataset();
+      DcmDataset& dataset = *dicom.GetDcmtkObject().getDataset();
+      Encoding encoding = dicom.GetEncoding();
 
-      OFCondition result;
-      OFString id;
+      bool found;
+      std::string id;
       E_DirRecType type;
 
       switch (level)
       {
         case ResourceType_Patient:
-          result = dataset.findAndGetOFString(DCM_PatientID, id);
+          found = GetUtf8TagValue(id, dataset, encoding, DCM_PatientID);
           type = ERT_Patient;
           break;
 
         case ResourceType_Study:
-          result = dataset.findAndGetOFString(DCM_StudyInstanceUID, id);
+          found = GetUtf8TagValue(id, dataset, encoding, DCM_StudyInstanceUID);
           type = ERT_Study;
           break;
 
         case ResourceType_Series:
-          result = dataset.findAndGetOFString(DCM_SeriesInstanceUID, id);
+          found = GetUtf8TagValue(id, dataset, encoding, DCM_SeriesInstanceUID);
           type = ERT_Series;
           break;
 
         case ResourceType_Instance:
-          result = dataset.findAndGetOFString(DCM_SOPInstanceUID, id);
+          found = GetUtf8TagValue(id, dataset, encoding, DCM_SOPInstanceUID);
           type = ERT_Image;
           break;
 
@@ -420,9 +372,9 @@ namespace Orthanc
           throw OrthancException(ErrorCode_InternalError);
       }
 
-      if (!result.good())
+      if (!found)
       {
-        throw OrthancException(ErrorCode_InternalError);
+        throw OrthancException(ErrorCode_BadFileFormat);
       }
 
       IndexKey key = std::make_pair(level, std::string(id.c_str()));
@@ -439,29 +391,26 @@ namespace Orthanc
       switch (level)
       {
         case ResourceType_Patient:
-          FillPatient(*record, dataset);
+          FillPatient(*record, dataset, encoding);
           break;
 
         case ResourceType_Study:
-          FillStudy(*record, dataset);
+          FillStudy(*record, dataset, encoding);
           break;
 
         case ResourceType_Series:
-          FillSeries(*record, dataset);
+          FillSeries(*record, dataset, encoding);
           break;
 
         case ResourceType_Instance:
-          FillInstance(*record, dataset, *dicom.getMetaInfo(), path);
+          FillInstance(*record, dataset, encoding, *dicom.GetDcmtkObject().getMetaInfo(), path);
           break;
 
         default:
           throw OrthancException(ErrorCode_InternalError);
       }
 
-      if (record->isAffectedBySpecificCharacterSet())
-      {
-        copyElementType1C(dataset, DCM_SpecificCharacterSet, *record);
-      }
+      CopyStringType1C(*record, dataset, encoding, DCM_SpecificCharacterSet);
 
       target = record.get();
       GetRoot().insertSub(record.release());
@@ -527,26 +476,24 @@ namespace Orthanc
       path = directory + '\\' + filename;
     }
 
-    DcmFileFormat& fileFormat = dicom.GetDcmtkObject();
-
     DcmDirectoryRecord* instance;
-    bool isNewInstance = pimpl_->CreateResource(instance, ResourceType_Instance, fileFormat, filename.c_str(), path.c_str());
+    bool isNewInstance = pimpl_->CreateResource(instance, ResourceType_Instance, dicom, filename.c_str(), path.c_str());
     if (isNewInstance)
     {
       DcmDirectoryRecord* series;
-      bool isNewSeries = pimpl_->CreateResource(series, ResourceType_Series, fileFormat, filename.c_str(), NULL);
+      bool isNewSeries = pimpl_->CreateResource(series, ResourceType_Series, dicom, filename.c_str(), NULL);
       series->insertSub(instance);
 
       if (isNewSeries)
       {
         DcmDirectoryRecord* study;
-        bool isNewStudy = pimpl_->CreateResource(study, ResourceType_Study, fileFormat, filename.c_str(), NULL);
+        bool isNewStudy = pimpl_->CreateResource(study, ResourceType_Study, dicom, filename.c_str(), NULL);
         study->insertSub(series);
   
         if (isNewStudy)
         {
           DcmDirectoryRecord* patient;
-          pimpl_->CreateResource(patient, ResourceType_Patient, fileFormat, filename.c_str(), NULL);
+          pimpl_->CreateResource(patient, ResourceType_Patient, dicom, filename.c_str(), NULL);
           patient->insertSub(study);
         }
       }
diff --git a/OrthancServer/DicomProtocol/RemoteModalityParameters.cpp b/OrthancServer/DicomProtocol/RemoteModalityParameters.cpp
index efb8182..1338f33 100644
--- a/OrthancServer/DicomProtocol/RemoteModalityParameters.cpp
+++ b/OrthancServer/DicomProtocol/RemoteModalityParameters.cpp
@@ -33,6 +33,7 @@
 #include "../PrecompiledHeadersServer.h"
 #include "RemoteModalityParameters.h"
 
+#include "../../Core/Logging.h"
 #include "../../Core/OrthancException.h"
 
 #include <boost/lexical_cast.hpp>
@@ -97,7 +98,17 @@ namespace Orthanc
 
     if (modality.size() == 4)
     {
-      SetManufacturer(modality.get(3u, "").asString());
+      const std::string& manufacturer = modality.get(3u, "").asString();
+
+      try
+      {
+        SetManufacturer(manufacturer);
+      }
+      catch (OrthancException&)
+      {
+        LOG(ERROR) << "Unknown modality manufacturer: \"" << manufacturer << "\"";
+        throw;
+      }
     }
     else
     {
diff --git a/OrthancServer/OrthancFindRequestHandler.cpp b/OrthancServer/OrthancFindRequestHandler.cpp
index 27b0832..6844336 100644
--- a/OrthancServer/OrthancFindRequestHandler.cpp
+++ b/OrthancServer/OrthancFindRequestHandler.cpp
@@ -34,6 +34,7 @@
 #include "OrthancFindRequestHandler.h"
 
 #include "../Core/DicomFormat/DicomArray.h"
+#include "../Core/Lua/LuaFunctionCall.h"
 #include "../Core/Logging.h"
 #include "FromDcmtkBridge.h"
 #include "OrthancInitialization.h"
@@ -159,6 +160,71 @@ namespace Orthanc
   }
 
 
+  bool OrthancFindRequestHandler::ApplyLuaFilter(DicomMap& target,
+                                                 const DicomMap& source,
+                                                 const std::string& remoteIp,
+                                                 const std::string& remoteAet,
+                                                 const std::string& calledAet)
+  {
+    Json::Value output;
+
+    {
+      LuaScripting::Locker locker(context_.GetLua());
+      static const char* NAME = "IncomingFindRequestFilter";
+      
+      if (!locker.GetLua().IsExistingFunction(NAME))
+      {
+        return false;
+      }
+
+      Json::Value tmp = Json::objectValue;
+      DicomArray a(source);
+
+      for (size_t i = 0; i < a.GetSize(); i++)
+      {
+        const DicomValue& v = a.GetElement(i).GetValue();
+        std::string s = (v.IsNull() || v.IsBinary()) ? "" : v.GetContent();
+        tmp[a.GetElement(i).GetTag().Format()] = s;
+      }
+
+      Json::Value origin = Json::objectValue;
+      origin["RemoteIp"] = remoteIp;
+      origin["RemoteAet"] = remoteAet;
+      origin["CalledAet"] = calledAet;
+
+      LuaFunctionCall call(locker.GetLua(), NAME);
+      call.PushJson(tmp);
+      call.PushJson(origin);
+
+      call.ExecuteToJson(output, true);
+    }
+
+    // The Lua context is released at this point
+
+    if (output.type() != Json::objectValue)
+    {
+      LOG(ERROR) << "Lua: IncomingFindRequestFilter must return a table";
+      throw OrthancException(ErrorCode_LuaBadOutput);
+    }
+
+    Json::Value::Members members = output.getMemberNames();
+
+    for (size_t i = 0; i < members.size(); i++)
+    {
+      if (output[members[i]].type() != Json::stringValue)
+      {
+        LOG(ERROR) << "Lua: IncomingFindRequestFilter must return a table mapping names of DICOM tags to strings";
+        throw OrthancException(ErrorCode_LuaBadOutput);
+      }
+
+      DicomTag tag(FromDcmtkBridge::ParseTag(members[i]));
+      target.SetValue(tag, output[members[i]].asString());
+    }
+
+    return true;
+  }
+
+
   void OrthancFindRequestHandler::Handle(DicomFindAnswers& answers,
                                          const DicomMap& input,
                                          const std::list<DicomTag>& sequencesToReturn,
@@ -181,10 +247,24 @@ namespace Orthanc
 
 
     /**
+     * Possibly apply the user-supplied Lua filter.
+     **/
+
+    DicomMap lua;
+    const DicomMap* filteredInput = &input;
+
+    if (ApplyLuaFilter(lua, input, remoteIp, remoteAet, calledAet))
+    {
+      filteredInput = &lua;
+    }
+
+
+    /**
      * Retrieve the query level.
      **/
 
-    const DicomValue* levelTmp = input.TestAndGetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL);
+    assert(filteredInput != NULL);
+    const DicomValue* levelTmp = filteredInput->TestAndGetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL);
     if (levelTmp == NULL ||
         levelTmp->IsNull() ||
         levelTmp->IsBinary())
@@ -204,7 +284,7 @@ namespace Orthanc
     }
 
 
-    DicomArray query(input);
+    DicomArray query(*filteredInput);
     LOG(INFO) << "DICOM C-Find request at level: " << EnumerationToString(level);
 
     for (size_t i = 0; i < query.GetSize(); i++)
diff --git a/OrthancServer/OrthancFindRequestHandler.h b/OrthancServer/OrthancFindRequestHandler.h
index b44cef9..601e5d7 100644
--- a/OrthancServer/OrthancFindRequestHandler.h
+++ b/OrthancServer/OrthancFindRequestHandler.h
@@ -52,6 +52,12 @@ namespace Orthanc
                         const DicomTag& tag,
                         ModalityManufacturer manufacturer);
 
+    bool ApplyLuaFilter(DicomMap& target,
+                        const DicomMap& source,
+                        const std::string& remoteIp,
+                        const std::string& remoteAet,
+                        const std::string& calledAet);
+
   public:
     OrthancFindRequestHandler(ServerContext& context) :
       context_(context), 
diff --git a/OrthancServer/OrthancInitialization.cpp b/OrthancServer/OrthancInitialization.cpp
index a273ad5..a28be68 100644
--- a/OrthancServer/OrthancInitialization.cpp
+++ b/OrthancServer/OrthancInitialization.cpp
@@ -71,7 +71,7 @@
 
 namespace Orthanc
 {
-  static boost::mutex globalMutex_;
+  static boost::recursive_mutex globalMutex_;
   static Json::Value configuration_;
   static boost::filesystem::path defaultDirectory_;
   static std::string configurationAbsolutePath_;
@@ -243,6 +243,26 @@ namespace Orthanc
   }
 
 
+  static void ValidateGlobalConfiguration()
+  {
+    std::set<std::string> ids;
+
+    Configuration::GetListOfOrthancPeers(ids);
+    for (std::set<std::string>::const_iterator it = ids.begin(); it != ids.end(); ++it)
+    {
+      OrthancPeerParameters peer;
+      Configuration::GetOrthancPeer(peer, *it);
+    }
+
+    Configuration::GetListOfDicomModalities(ids);
+    for (std::set<std::string>::const_iterator it = ids.begin(); it != ids.end(); ++it)
+    {
+      RemoteModalityParameters modality;
+      Configuration::GetDicomModalityUsingSymbolicName(modality, *it);
+    }
+  }
+
+
   static void RegisterUserMetadata()
   {
     if (configuration_.isMember("UserMetadata"))
@@ -367,7 +387,7 @@ namespace Orthanc
 
   void OrthancInitialize(const char* configurationFile)
   {
-    boost::mutex::scoped_lock lock(globalMutex_);
+    boost::recursive_mutex::scoped_lock lock(globalMutex_);
 
 #if ORTHANC_SSL_ENABLED == 1
     // https://wiki.openssl.org/index.php/Library_Initialization
@@ -385,6 +405,7 @@ namespace Orthanc
 
     // Read the user-provided configuration
     ReadGlobalConfiguration(configurationFile);
+    ValidateGlobalConfiguration();
 
     HttpClient::GlobalInitialize(GetGlobalBoolParameterInternal("HttpsVerifyPeers", true),
                                  GetGlobalStringParameterInternal("HttpsCACertificates", ""));
@@ -412,7 +433,7 @@ namespace Orthanc
 
   void OrthancFinalize()
   {
-    boost::mutex::scoped_lock lock(globalMutex_);
+    boost::recursive_mutex::scoped_lock lock(globalMutex_);
     HttpClient::GlobalFinalize();
 
 #if ORTHANC_JPEG_LOSSLESS_ENABLED == 1
@@ -444,7 +465,7 @@ namespace Orthanc
   std::string Configuration::GetGlobalStringParameter(const std::string& parameter,
                                                       const std::string& defaultValue)
   {
-    boost::mutex::scoped_lock lock(globalMutex_);
+    boost::recursive_mutex::scoped_lock lock(globalMutex_);
     return GetGlobalStringParameterInternal(parameter, defaultValue);
   }
 
@@ -452,7 +473,7 @@ namespace Orthanc
   int Configuration::GetGlobalIntegerParameter(const std::string& parameter,
                                                int defaultValue)
   {
-    boost::mutex::scoped_lock lock(globalMutex_);
+    boost::recursive_mutex::scoped_lock lock(globalMutex_);
 
     if (configuration_.isMember(parameter))
     {
@@ -476,7 +497,7 @@ namespace Orthanc
   bool Configuration::GetGlobalBoolParameter(const std::string& parameter,
                                              bool defaultValue)
   {
-    boost::mutex::scoped_lock lock(globalMutex_);
+    boost::recursive_mutex::scoped_lock lock(globalMutex_);
     return GetGlobalBoolParameterInternal(parameter, defaultValue);
   }
 
@@ -484,7 +505,7 @@ namespace Orthanc
   void Configuration::GetDicomModalityUsingSymbolicName(RemoteModalityParameters& modality,
                                                         const std::string& name)
   {
-    boost::mutex::scoped_lock lock(globalMutex_);
+    boost::recursive_mutex::scoped_lock lock(globalMutex_);
 
     if (!configuration_.isMember("DicomModalities"))
     {
@@ -517,7 +538,7 @@ namespace Orthanc
   void Configuration::GetOrthancPeer(OrthancPeerParameters& peer,
                                      const std::string& name)
   {
-    boost::mutex::scoped_lock lock(globalMutex_);
+    boost::recursive_mutex::scoped_lock lock(globalMutex_);
 
     if (!configuration_.isMember("OrthancPeers"))
     {
@@ -550,7 +571,7 @@ namespace Orthanc
                        const char* parameter,
                        bool onlyAlphanumeric)
   {
-    boost::mutex::scoped_lock lock(globalMutex_);
+    boost::recursive_mutex::scoped_lock lock(globalMutex_);
 
     target.clear();
   
@@ -589,6 +610,8 @@ namespace Orthanc
 
   void Configuration::GetListOfDicomModalities(std::set<std::string>& target)
   {
+    target.clear();
+
     if (!ReadKeys(target, "DicomModalities", true))
     {
       LOG(ERROR) << "Only alphanumeric and dash characters are allowed in the names of the modalities";
@@ -599,6 +622,8 @@ namespace Orthanc
 
   void Configuration::GetListOfOrthancPeers(std::set<std::string>& target)
   {
+    target.clear();
+
     if (!ReadKeys(target, "OrthancPeers", true))
     {
       LOG(ERROR) << "Only alphanumeric and dash characters are allowed in the names of Orthanc peers";
@@ -610,7 +635,7 @@ namespace Orthanc
 
   void Configuration::SetupRegisteredUsers(MongooseServer& httpServer)
   {
-    boost::mutex::scoped_lock lock(globalMutex_);
+    boost::recursive_mutex::scoped_lock lock(globalMutex_);
 
     httpServer.ClearUsers();
 
@@ -664,7 +689,7 @@ namespace Orthanc
 
   std::string Configuration::InterpretStringParameterAsPath(const std::string& parameter)
   {
-    boost::mutex::scoped_lock lock(globalMutex_);
+    boost::recursive_mutex::scoped_lock lock(globalMutex_);
     return InterpretRelativePath(defaultDirectory_.string(), parameter);
   }
 
@@ -672,7 +697,7 @@ namespace Orthanc
   void Configuration::GetGlobalListOfStringsParameter(std::list<std::string>& target,
                                                       const std::string& key)
   {
-    boost::mutex::scoped_lock lock(globalMutex_);
+    boost::recursive_mutex::scoped_lock lock(globalMutex_);
 
     target.clear();
   
@@ -777,7 +802,7 @@ namespace Orthanc
   void Configuration::UpdateModality(const std::string& symbolicName,
                                      const RemoteModalityParameters& modality)
   {
-    boost::mutex::scoped_lock lock(globalMutex_);
+    boost::recursive_mutex::scoped_lock lock(globalMutex_);
 
     if (!configuration_.isMember("DicomModalities"))
     {
@@ -801,7 +826,7 @@ namespace Orthanc
 
   void Configuration::RemoveModality(const std::string& symbolicName)
   {
-    boost::mutex::scoped_lock lock(globalMutex_);
+    boost::recursive_mutex::scoped_lock lock(globalMutex_);
 
     if (!configuration_.isMember("DicomModalities"))
     {
@@ -823,7 +848,7 @@ namespace Orthanc
   void Configuration::UpdatePeer(const std::string& symbolicName,
                                  const OrthancPeerParameters& peer)
   {
-    boost::mutex::scoped_lock lock(globalMutex_);
+    boost::recursive_mutex::scoped_lock lock(globalMutex_);
 
     if (!configuration_.isMember("OrthancPeers"))
     {
@@ -848,7 +873,7 @@ namespace Orthanc
 
   void Configuration::RemovePeer(const std::string& symbolicName)
   {
-    boost::mutex::scoped_lock lock(globalMutex_);
+    boost::recursive_mutex::scoped_lock lock(globalMutex_);
 
     if (!configuration_.isMember("OrthancPeers"))
     {
@@ -980,7 +1005,7 @@ namespace Orthanc
 
   void Configuration::GetConfiguration(Json::Value& result)
   {
-    boost::mutex::scoped_lock lock(globalMutex_);
+    boost::recursive_mutex::scoped_lock lock(globalMutex_);
     result = configuration_;
   }
 
diff --git a/OrthancServer/Search/HierarchicalMatcher.cpp b/OrthancServer/Search/HierarchicalMatcher.cpp
index 115c9f0..f54e396 100644
--- a/OrthancServer/Search/HierarchicalMatcher.cpp
+++ b/OrthancServer/Search/HierarchicalMatcher.cpp
@@ -33,6 +33,7 @@
 #include "../PrecompiledHeadersServer.h"
 #include "HierarchicalMatcher.h"
 
+#include "../../Core/Logging.h"
 #include "../../Core/OrthancException.h"
 #include "../FromDcmtkBridge.h"
 #include "../ToDcmtkBridge.h"
@@ -125,7 +126,14 @@ namespace Orthanc
 
         if (value->IsBinary())
         {
-          throw OrthancException(ErrorCode_BadRequest);
+          if (!value->GetContent().empty())
+          {
+            LOG(WARNING) << "This C-Find modality worklist query contains a non-empty tag ("
+                         << tag.Format() << ") with UN (unknown) value representation. "
+                         << "It will be ignored.";
+          }
+
+          constraints_[tag] = NULL;
         }
         else if (value->IsNull() ||
                  value->GetContent().empty())
@@ -235,7 +243,7 @@ namespace Orthanc
         if (!item.findAndGetSequence(tag, sequence).good() ||
             sequence == NULL)
         {
-          return true;
+          continue;
         }
 
         bool match = false;
diff --git a/OrthancServer/Search/RangeConstraint.cpp b/OrthancServer/Search/RangeConstraint.cpp
index f6c1ed0..f629f16 100644
--- a/OrthancServer/Search/RangeConstraint.cpp
+++ b/OrthancServer/Search/RangeConstraint.cpp
@@ -55,8 +55,15 @@ namespace Orthanc
   void RangeConstraint::Setup(LookupIdentifierQuery& lookup,
                               const DicomTag& tag) const
   {
-    lookup.AddConstraint(tag, IdentifierConstraintType_GreaterOrEqual, lower_);
-    lookup.AddConstraint(tag, IdentifierConstraintType_SmallerOrEqual, upper_);
+    if (!lower_.empty())
+    {
+      lookup.AddConstraint(tag, IdentifierConstraintType_GreaterOrEqual, lower_);
+    }
+
+    if (!upper_.empty())
+    {
+      lookup.AddConstraint(tag, IdentifierConstraintType_SmallerOrEqual, upper_);
+    }
   }
 
 
diff --git a/Plugins/Engine/OrthancPlugins.cpp b/Plugins/Engine/OrthancPlugins.cpp
index 39f0816..32e9682 100644
--- a/Plugins/Engine/OrthancPlugins.cpp
+++ b/Plugins/Engine/OrthancPlugins.cpp
@@ -1574,7 +1574,39 @@ namespace Orthanc
 
     *(p.target) = ReturnImage(result);
   }
-        
+
+
+  void OrthancPlugins::ApplySendMultipartItem(const void* parameters)
+  {
+    // An exception might be raised in this function if the
+    // connection was closed by the HTTP client.
+    const _OrthancPluginAnswerBuffer& p =
+      *reinterpret_cast<const _OrthancPluginAnswerBuffer*>(parameters);
+
+    HttpOutput* output = reinterpret_cast<HttpOutput*>(p.output);
+
+    std::map<std::string, std::string> headers;  // No custom headers
+    output->SendMultipartItem(p.answer, p.answerSize, headers);
+  }
+
+
+  void OrthancPlugins::ApplySendMultipartItem2(const void* parameters)
+  {
+    // An exception might be raised in this function if the
+    // connection was closed by the HTTP client.
+    const _OrthancPluginSendMultipartItem2& p =
+      *reinterpret_cast<const _OrthancPluginSendMultipartItem2*>(parameters);
+    HttpOutput* output = reinterpret_cast<HttpOutput*>(p.output);
+    
+    std::map<std::string, std::string> headers;
+    for (uint32_t i = 0; i < p.headersCount; i++)
+    {
+      headers[p.headersKeys[i]] = p.headersValues[i];
+    }
+    
+    output->SendMultipartItem(p.answer, p.answerSize, headers);
+  }
+      
 
   void OrthancPlugins::DatabaseAnswer(const void* parameters)
   {
@@ -1969,15 +2001,12 @@ namespace Orthanc
       }
 
       case _OrthancPluginService_SendMultipartItem:
-      {
-        // An exception might be raised in this function if the
-        // connection was closed by the HTTP client.
-        const _OrthancPluginAnswerBuffer& p =
-          *reinterpret_cast<const _OrthancPluginAnswerBuffer*>(parameters);
-        HttpOutput* output = reinterpret_cast<HttpOutput*>(p.output);
-        output->SendMultipartItem(p.answer, p.answerSize);
+        ApplySendMultipartItem(parameters);
+        return true;
+
+      case _OrthancPluginService_SendMultipartItem2:
+        ApplySendMultipartItem2(parameters);
         return true;
-      }
 
       case _OrthancPluginService_ReadFile:
       {
diff --git a/Plugins/Engine/OrthancPlugins.h b/Plugins/Engine/OrthancPlugins.h
index 10ab72a..e535a67 100644
--- a/Plugins/Engine/OrthancPlugins.h
+++ b/Plugins/Engine/OrthancPlugins.h
@@ -152,6 +152,10 @@ namespace Orthanc
 
     void ApplyLookupDictionary(const void* parameters);
 
+    void ApplySendMultipartItem(const void* parameters);
+
+    void ApplySendMultipartItem2(const void* parameters);
+
     void ComputeHash(_OrthancPluginService service,
                      const void* parameters);
 
diff --git a/Plugins/Include/orthanc/OrthancCPlugin.h b/Plugins/Include/orthanc/OrthancCPlugin.h
index fe40348..36fc61b 100644
--- a/Plugins/Include/orthanc/OrthancCPlugin.h
+++ b/Plugins/Include/orthanc/OrthancCPlugin.h
@@ -112,9 +112,9 @@
 #define ORTHANC_PLUGINS_API
 #endif
 
-#define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER     0
-#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     9
-#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  5
+#define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER     1
+#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     0
+#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  0
 
 
 
@@ -424,6 +424,7 @@ extern "C"
     _OrthancPluginService_SendMultipartItem = 2009,
     _OrthancPluginService_SendHttpStatus = 2010,
     _OrthancPluginService_CompressAndAnswerImage = 2011,
+    _OrthancPluginService_SendMultipartItem2 = 2012,
 
     /* Access to the Orthanc database and API */
     _OrthancPluginService_GetDicomForInstance = 3000,
@@ -2719,7 +2720,7 @@ extern "C"
    * @param subType The sub-type of the multipart answer ("mixed" or "related").
    * @param contentType The MIME type of the items in the multipart answer.
    * @return 0 if success, or the error code if failure.
-   * @see OrthancPluginSendMultipartItem()
+   * @see OrthancPluginSendMultipartItem(), OrthancPluginSendMultipartItem2()
    * @ingroup REST
    **/
   ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStartMultipartAnswer(
@@ -2748,6 +2749,7 @@ extern "C"
    * @param answerSize Number of bytes of the item.
    * @return 0 if success, or the error code if failure (this notably happens
    * if the connection is closed by the client).
+   * @see OrthancPluginSendMultipartItem2()
    * @ingroup REST
    **/
   ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendMultipartItem(
@@ -4677,6 +4679,58 @@ extern "C"
   }
 
 
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              answer;
+    uint32_t                 answerSize;
+    uint32_t                 headersCount;
+    const char* const*       headersKeys;
+    const char* const*       headersValues;
+  } _OrthancPluginSendMultipartItem2;
+
+  /**
+   * @brief Send an item as a part of some HTTP multipart answer, with custom headers.
+   *
+   * This function sends an item as a part of some HTTP multipart
+   * answer that was initiated by OrthancPluginStartMultipartAnswer(). In addition to
+   * OrthancPluginSendMultipartItem(), this function will set HTTP header associated
+   * with the item.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param answer Pointer to the memory buffer containing the item.
+   * @param answerSize Number of bytes of the item.
+   * @param headersCount The number of HTTP headers.
+   * @param headersKeys Array containing the keys of the HTTP headers.
+   * @param headersValues Array containing the values of the HTTP headers.
+   * @return 0 if success, or the error code if failure (this notably happens
+   * if the connection is closed by the client).
+   * @see OrthancPluginSendMultipartItem()
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendMultipartItem2(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              answer,
+    uint32_t                 answerSize,
+    uint32_t                 headersCount,
+    const char* const*       headersKeys,
+    const char* const*       headersValues)
+  {
+    _OrthancPluginSendMultipartItem2 params;
+    params.output = output;
+    params.answer = answer;
+    params.answerSize = answerSize;
+    params.headersCount = headersCount;
+    params.headersKeys = headersKeys;
+    params.headersValues = headersValues;    
+
+    return context->InvokeService(context, _OrthancPluginService_SendMultipartItem2, &params);
+  }
+
+
 #ifdef  __cplusplus
 }
 #endif
diff --git a/Plugins/Samples/ModalityWorklists/Plugin.cpp b/Plugins/Samples/ModalityWorklists/Plugin.cpp
index 7178d95..4840f31 100644
--- a/Plugins/Samples/ModalityWorklists/Plugin.cpp
+++ b/Plugins/Samples/ModalityWorklists/Plugin.cpp
@@ -131,8 +131,11 @@ OrthancPluginErrorCode Callback(OrthancPluginWorklistAnswers*     answers,
     return OrthancPluginErrorCode_InternalError;
   }
 
-  std::cout << "Received worklist query from remote modality " << remoteAet 
-            << ":" << std::endl << json.toStyledString();
+  {
+    std::string msg = ("Received worklist query from remote modality " + 
+                       std::string(remoteAet) + ":\n" + json.toStyledString());
+    OrthancPluginLogInfo(context_, msg.c_str());
+  }
 
   boost::filesystem::path source(folder_);
   boost::filesystem::directory_iterator end;
diff --git a/Resources/CMake/DcmtkConfiguration.cmake b/Resources/CMake/DcmtkConfiguration.cmake
index 64007a2..4bc5ce2 100644
--- a/Resources/CMake/DcmtkConfiguration.cmake
+++ b/Resources/CMake/DcmtkConfiguration.cmake
@@ -191,7 +191,7 @@ else()
   elseif (EXISTS "${DCMTK_DIR}/config/osconfig.h")  # This is for Arch Linux
     set(DCMTK_CONFIGURATION_FILE "${DCMTK_DIR}/config/osconfig.h")
   else()
-    message(FATAL_ERROR "Please install libdcmtk1-dev")
+    message(FATAL_ERROR "Please install libdcmtk*-dev")
   endif()
 
   # Autodetection of the version of DCMTK
@@ -220,8 +220,12 @@ if (NOT DCMTK_USE_EMBEDDED_DICTIONARIES)
   if (DCMTK_DICTIONARY_DIR STREQUAL "")
     find_path(DCMTK_DICTIONARY_DIR_AUTO dicom.dic
       /usr/share/dcmtk
+      /usr/share/libdcmtk1
       /usr/share/libdcmtk2
+      /usr/share/libdcmtk3
       /usr/share/libdcmtk4
+      /usr/share/libdcmtk5
+      /usr/share/libdcmtk6
       /usr/local/share/dcmtk
       )
 
diff --git a/Resources/Samples/Lua/IncomingFindRequestFilter.lua b/Resources/Samples/Lua/IncomingFindRequestFilter.lua
new file mode 100644
index 0000000..cb5955d
--- /dev/null
+++ b/Resources/Samples/Lua/IncomingFindRequestFilter.lua
@@ -0,0 +1,14 @@
+-- This example solves the following problem:
+-- https://groups.google.com/d/msg/orthanc-users/PLWKqVVaXLs/n_0x4vKhAgAJ
+
+function IncomingFindRequestFilter(source, origin)
+   -- First display the content of the C-Find query
+   PrintRecursive(source)
+   PrintRecursive(origin)
+
+   -- Remove the "PrivateCreator" tag from the query
+   local v = source
+   v['5555,0010'] = nil
+
+   return v
+end

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/orthanc.git



More information about the debian-med-commit mailing list