changeset 606:ce5d2040c47b find-move-scp

integration mainline -> find-move-scp
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 17 Oct 2013 14:20:13 +0200
parents c2be0a0e049e (diff) 49945044b06d (current diff)
children 0bedf8ff9288
files CMakeLists.txt Core/Enumerations.h OrthancServer/DatabaseWrapper.cpp Resources/Samples/OrthancCppClient/Basic/CMakeLists.txt Resources/Samples/OrthancCppClient/Basic/main.cpp Resources/Samples/OrthancCppClient/OrthancCppClient.cmake Resources/Samples/OrthancCppClient/Vtk/CMakeLists.txt Resources/Samples/OrthancCppClient/Vtk/main.cpp Resources/Samples/RestApi/CMakeLists.txt Resources/Samples/RestApi/Sample.cpp Resources/Samples/RestApiLinuxDynamic/AutoGeneratedCode.cmake Resources/Samples/RestApiLinuxDynamic/CMakeLists.txt Resources/Samples/RestApiLinuxDynamic/README.txt Resources/Samples/RestApiLinuxDynamic/Sample.cpp Resources/sha1/Makefile Resources/sha1/Makefile.nt Resources/sha1/license.txt Resources/sha1/sha.cpp Resources/sha1/sha1.cpp Resources/sha1/sha1.h Resources/sha1/shacmp.cpp Resources/sha1/shatest.cpp
diffstat 17 files changed, 419 insertions(+), 63 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Tue Oct 15 16:23:42 2013 +0200
+++ b/CMakeLists.txt	Thu Oct 17 14:20:13 2013 +0200
@@ -90,6 +90,7 @@
 # Prepare the embedded files
 set(EMBEDDED_FILES
   PREPARE_DATABASE ${CMAKE_CURRENT_SOURCE_DIR}/OrthancServer/PrepareDatabase.sql
+  PREPARE_DATABASE_V4 ${CMAKE_CURRENT_SOURCE_DIR}/OrthancServer/PrepareDatabaseV4.sql
   CONFIGURATION_SAMPLE ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Configuration.json
   LUA_TOOLBOX ${CMAKE_CURRENT_SOURCE_DIR}/Resources/Toolbox.lua
   )
@@ -209,6 +210,7 @@
   OrthancServer/ServerContext.cpp
   OrthancServer/ServerEnumerations.cpp
   OrthancServer/ServerToolbox.cpp
+  OrthancServer/OrthancFindRequestHandler.cpp
   )
 
 # Ensure autogenerated code is built before building ServerLibrary
--- a/Core/DicomFormat/DicomMap.cpp	Tue Oct 15 16:23:42 2013 +0200
+++ b/Core/DicomFormat/DicomMap.cpp	Thu Oct 17 14:20:13 2013 +0200
@@ -280,4 +280,110 @@
       SetValue(tag, source.GetValue(tag));
     }
   }
+
+
+  bool DicomMap::IsMainDicomTag(const DicomTag& tag, ResourceType level)
+  {
+    DicomTag *tags = NULL;
+    size_t size;
+
+    switch (level)
+    {
+      case ResourceType_Patient:
+        tags = patientTags;
+        size = sizeof(patientTags) / sizeof(DicomTag);
+        break;
+
+      case ResourceType_Study:
+        tags = studyTags;
+        size = sizeof(studyTags) / sizeof(DicomTag);
+        break;
+
+      case ResourceType_Series:
+        tags = seriesTags;
+        size = sizeof(seriesTags) / sizeof(DicomTag);
+        break;
+
+      case ResourceType_Instance:
+        tags = instanceTags;
+        size = sizeof(instanceTags) / sizeof(DicomTag);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    for (size_t i = 0; i < size; i++)
+    {
+      if (tags[i] == tag)
+      {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  bool DicomMap::IsMainDicomTag(const DicomTag& tag)
+  {
+    return (IsMainDicomTag(tag, ResourceType_Patient) ||
+            IsMainDicomTag(tag, ResourceType_Study) ||
+            IsMainDicomTag(tag, ResourceType_Series) ||
+            IsMainDicomTag(tag, ResourceType_Instance));
+  }
+
+
+  void DicomMap::GetMainDicomTagsInternal(std::set<DicomTag>& result, ResourceType level)
+  {
+    DicomTag *tags = NULL;
+    size_t size;
+
+    switch (level)
+    {
+      case ResourceType_Patient:
+        tags = patientTags;
+        size = sizeof(patientTags) / sizeof(DicomTag);
+        break;
+
+      case ResourceType_Study:
+        tags = studyTags;
+        size = sizeof(studyTags) / sizeof(DicomTag);
+        break;
+
+      case ResourceType_Series:
+        tags = seriesTags;
+        size = sizeof(seriesTags) / sizeof(DicomTag);
+        break;
+
+      case ResourceType_Instance:
+        tags = instanceTags;
+        size = sizeof(instanceTags) / sizeof(DicomTag);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    for (size_t i = 0; i < size; i++)
+    {
+      result.insert(tags[i]);
+    }
+  }
+
+
+  void DicomMap::GetMainDicomTags(std::set<DicomTag>& result, ResourceType level)
+  {
+    result.clear();
+    GetMainDicomTagsInternal(result, level);
+  }
+
+
+  void DicomMap::GetMainDicomTags(std::set<DicomTag>& result)
+  {
+    result.clear();
+    GetMainDicomTagsInternal(result, ResourceType_Patient);
+    GetMainDicomTagsInternal(result, ResourceType_Study);
+    GetMainDicomTagsInternal(result, ResourceType_Series);
+    GetMainDicomTagsInternal(result, ResourceType_Instance);
+  }
 }
--- a/Core/DicomFormat/DicomMap.h	Tue Oct 15 16:23:42 2013 +0200
+++ b/Core/DicomFormat/DicomMap.h	Thu Oct 17 14:20:13 2013 +0200
@@ -35,7 +35,9 @@
 #include "DicomTag.h"
 #include "DicomValue.h"
 #include "DicomString.h"
+#include "../Enumerations.h"
 
+#include <set>
 #include <map>
 #include <json/json.h>
 
@@ -63,6 +65,8 @@
     void ExtractTags(DicomMap& source,
                      const DicomTag* tags,
                      size_t count) const;
+   
+    static void GetMainDicomTagsInternal(std::set<DicomTag>& result, ResourceType level);
 
   public:
     DicomMap()
@@ -148,5 +152,13 @@
 
     void CopyTagIfExists(const DicomMap& source,
                          const DicomTag& tag);
+
+    static bool IsMainDicomTag(const DicomTag& tag, ResourceType level);
+
+    static bool IsMainDicomTag(const DicomTag& tag);
+
+    static void GetMainDicomTags(std::set<DicomTag>& result, ResourceType level);
+
+    static void GetMainDicomTags(std::set<DicomTag>& result);
   };
 }
--- a/Core/DicomFormat/DicomTag.h	Tue Oct 15 16:23:42 2013 +0200
+++ b/Core/DicomFormat/DicomTag.h	Thu Oct 17 14:20:13 2013 +0200
@@ -110,4 +110,8 @@
   // DICOM tags used for fMRI (thanks to Will Ryder)
   static const DicomTag DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS(0x0020, 0x0105);
   static const DicomTag DICOM_TAG_TEMPORAL_POSITION_IDENTIFIER(0x0020, 0x0100);
+
+  // Tags for C-FIND and C-MOVE
+  static const DicomTag DICOM_TAG_SPECIFIC_CHARACTER_SET(0x0008, 0x0005);
+  static const DicomTag DICOM_TAG_QUERY_RETRIEVE_LEVEL(0x0008, 0x0052);
 }
--- a/Core/Enumerations.cpp	Tue Oct 15 16:23:42 2013 +0200
+++ b/Core/Enumerations.cpp	Thu Oct 17 14:20:13 2013 +0200
@@ -33,6 +33,7 @@
 #include "Enumerations.h"
 
 #include "OrthancException.h"
+#include "Toolbox.h"
 
 namespace Orthanc
 {
@@ -222,4 +223,54 @@
       throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
   }
+
+
+  const char* EnumerationToString(ResourceType type)
+  {
+    switch (type)
+    {
+      case ResourceType_Patient:
+        return "Patient";
+
+      case ResourceType_Study:
+        return "Study";
+
+      case ResourceType_Series:
+        return "Series";
+
+      case ResourceType_Instance:
+        return "Instance";
+      
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  ResourceType StringToResourceType(const char* type)
+  {
+    std::string s(type);
+    Toolbox::ToUpperCase(s);
+
+    if (s == "PATIENT")
+    {
+      return ResourceType_Patient;
+    }
+    else if (s == "STUDY")
+    {
+      return ResourceType_Study;
+    }
+    else if (s == "SERIES")
+    {
+      return ResourceType_Series;
+    }
+    else if (s == "INSTANCE" || s == "IMAGE")
+    {
+      return ResourceType_Instance;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
 }
--- a/Core/Enumerations.h	Tue Oct 15 16:23:42 2013 +0200
+++ b/Core/Enumerations.h	Thu Oct 17 14:20:13 2013 +0200
@@ -228,9 +228,20 @@
     FileContentType_Json = 2
   };
 
+  enum ResourceType
+  {
+    ResourceType_Patient = 1,
+    ResourceType_Study = 2,
+    ResourceType_Series = 3,
+    ResourceType_Instance = 4
+  };
 
 
   const char* EnumerationToString(HttpMethod method);
 
   const char* EnumerationToString(HttpStatus status);
+
+  const char* EnumerationToString(ResourceType type);
+
+  ResourceType StringToResourceType(const char* type);
 }
--- a/OrthancServer/DatabaseWrapper.cpp	Tue Oct 15 16:23:42 2013 +0200
+++ b/OrthancServer/DatabaseWrapper.cpp	Thu Oct 17 14:20:13 2013 +0200
@@ -807,7 +807,7 @@
       db_.Execute(query);
     }
 
-    // Sanity check of the version of the database
+    // Check the version of the database
     std::string version = GetGlobalProperty(GlobalProperty_DatabaseSchemaVersion, "Unknown");
     bool ok = false;
     try
@@ -815,9 +815,27 @@
       LOG(INFO) << "Version of the Orthanc database: " << version;
       unsigned int v = boost::lexical_cast<unsigned int>(version);
 
-      // This version of Orthanc is only compatible with version 3 of
-      // the DB schema (since Orthanc 0.3.2)
-      ok = (v == 3); 
+      // This version of Orthanc is only compatible with versions 3
+      // (Orthanc 0.3.2 to 0.6.1) and 4 (since Orthanc 0.6.2) of the
+      // DB schema
+      ok = (v == 3 || v == 4);
+
+      if (v == 3)
+      {
+        LOG(WARNING) << "Upgrading the database from version 3 to version 4 (reconstructing the index)";
+
+        // Reconstruct the index for case insensitive queries in C-FIND
+        db_.Execute("DROP INDEX IF EXISTS MainDicomTagsIndexValues;");
+        db_.Execute("DROP TABLE IF EXISTS AvailableTags;");
+
+        std::string query;
+        EmbeddedResources::GetFileResource(query, EmbeddedResources::PREPARE_DATABASE_V4);
+        db_.Execute(query);
+
+        db_.Execute("INSERT INTO AvailableTags SELECT DISTINCT tagGroup, tagElement FROM MainDicomTags;");
+
+        //SetGlobalProperty(GlobalProperty_DatabaseSchemaVersion, "4");
+      }
     }
     catch (boost::bad_lexical_cast&)
     {
@@ -828,6 +846,8 @@
       throw OrthancException(ErrorCode_IncompatibleDatabaseVersion);
     }
 
+    CompleteMainDicomTags();
+
     signalRemainingAncestor_ = new Internals::SignalRemainingAncestor;
     db_.Register(signalRemainingAncestor_);
     db_.Register(new Internals::SignalFileDeleted(listener_));
@@ -995,4 +1015,11 @@
       result.push_back(s.ColumnInt64(0));
     }
   }
+
+
+  void DatabaseWrapper::CompleteMainDicomTags()
+  {
+    std::set<DicomTag> requiredTags;
+    
+  }
 }
--- a/OrthancServer/DatabaseWrapper.h	Tue Oct 15 16:23:42 2013 +0200
+++ b/OrthancServer/DatabaseWrapper.h	Thu Oct 17 14:20:13 2013 +0200
@@ -72,6 +72,8 @@
                               int64_t since,
                               unsigned int maxResults);
 
+    void CompleteMainDicomTags();
+
   public:
     void SetGlobalProperty(GlobalProperty property,
                            const std::string& value);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/OrthancFindRequestHandler.cpp	Thu Oct 17 14:20:13 2013 +0200
@@ -0,0 +1,108 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "OrthancFindRequestHandler.h"
+
+#include <glog/logging.h>
+
+#include "../Core/DicomFormat/DicomArray.h"
+
+namespace Orthanc
+{
+  void OrthancFindRequestHandler::Handle(const DicomMap& input,
+                                         DicomFindAnswers& answers)
+  {
+    LOG(WARNING) << "Find-SCU request received";
+
+    /**
+     * Retrieve the query level.
+     **/
+
+    const DicomValue* levelTmp = input.TestAndGetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL);
+    if (levelTmp == NULL) 
+    {
+      throw OrthancException(ErrorCode_BadRequest);
+    }
+
+    ResourceType level = StringToResourceType(levelTmp->AsString().c_str());
+
+    if (level != ResourceType_Patient &&
+        level != ResourceType_Study &&
+        level != ResourceType_Series)
+    {
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+
+
+    /**
+     * Retrieve the constraints of the query.
+     **/
+
+    DicomArray query(input);
+
+    DicomMap constraintsTmp;
+    DicomMap wildcardConstraintsTmp;
+
+    for (size_t i = 0; i < query.GetSize(); i++)
+    {
+      if (!query.GetElement(i).GetValue().IsNull() &&
+          query.GetElement(i).GetTag() != DICOM_TAG_QUERY_RETRIEVE_LEVEL &&
+          query.GetElement(i).GetTag() != DICOM_TAG_SPECIFIC_CHARACTER_SET)
+      {
+        DicomTag tag = query.GetElement(i).GetTag();
+        std::string value = query.GetElement(i).GetValue().AsString();
+
+        if (value.find('*') != std::string::npos ||
+            value.find('?') != std::string::npos ||
+            value.find('\\') != std::string::npos ||
+            value.find('-') != std::string::npos)
+        {
+          wildcardConstraintsTmp.SetValue(tag, value);
+        }
+        else
+        {
+          constraintsTmp.SetValue(tag, value);
+        }
+      }
+    }
+
+    DicomArray constraints(constraintsTmp);
+    DicomArray wildcardConstraints(wildcardConstraintsTmp);
+
+    // http://www.itk.org/Wiki/DICOM_QueryRetrieve_Explained
+    // http://dicomiseasy.blogspot.be/2012/01/dicom-queryretrieve-part-i.html
+
+    constraints.Print(stdout);
+    printf("\n"); fflush(stdout);
+    wildcardConstraints.Print(stdout);
+    printf("\n"); fflush(stdout);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/OrthancFindRequestHandler.h	Thu Oct 17 14:20:13 2013 +0200
@@ -0,0 +1,54 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2013 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include "DicomProtocol/IFindRequestHandler.h"
+
+#include "ServerContext.h"
+
+namespace Orthanc
+{
+  class OrthancFindRequestHandler : public IFindRequestHandler
+  {
+  private:
+    ServerContext& context_;
+
+  public:
+    OrthancFindRequestHandler(ServerContext& context) :
+    context_(context)
+    {
+    }
+
+    virtual void Handle(const DicomMap& input,
+                        DicomFindAnswers& answers);
+  };
+}
--- a/OrthancServer/PrepareDatabase.sql	Tue Oct 15 16:23:42 2013 +0200
+++ b/OrthancServer/PrepareDatabase.sql	Thu Oct 17 14:20:13 2013 +0200
@@ -67,7 +67,6 @@
 
 CREATE INDEX MainDicomTagsIndex1 ON MainDicomTags(id);
 CREATE INDEX MainDicomTagsIndex2 ON MainDicomTags(tagGroup, tagElement);
-CREATE INDEX MainDicomTagsIndexValues ON MainDicomTags(value COLLATE BINARY);
 
 CREATE INDEX ChangesIndex ON Changes(internalId);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/PrepareDatabaseV4.sql	Thu Oct 17 14:20:13 2013 +0200
@@ -0,0 +1,12 @@
+-- New in database version 4
+CREATE TABLE AvailableTags(
+       tagGroup INTEGER,
+       tagElement INTEGER,
+       PRIMARY KEY(tagGroup, tagElement)
+       );
+
+-- Until database version 4, the following index was set to "COLLATE
+-- BINARY". This implies case-sensitive searches, but DICOM C-Find
+-- requires case-insensitive searches.
+-- http://www.sqlite.org/optoverview.html#like_opt
+CREATE INDEX MainDicomTagsIndexValues ON MainDicomTags(value COLLATE NOCASE);
--- a/OrthancServer/ServerEnumerations.cpp	Tue Oct 15 16:23:42 2013 +0200
+++ b/OrthancServer/ServerEnumerations.cpp	Thu Oct 17 14:20:13 2013 +0200
@@ -33,6 +33,7 @@
 
 #include "../Core/OrthancException.h"
 #include "../Core/EnumerationDictionary.h"
+#include "../Core/Toolbox.h"
 
 #include <boost/thread.hpp>
 
@@ -82,27 +83,6 @@
     return dictMetadataType_.Translate(str);
   }
 
-  const char* EnumerationToString(ResourceType type)
-  {
-    switch (type)
-    {
-      case ResourceType_Patient:
-        return "Patient";
-
-      case ResourceType_Study:
-        return "Study";
-
-      case ResourceType_Series:
-        return "Series";
-
-      case ResourceType_Instance:
-        return "Instance";
-      
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
   std::string GetBasePath(ResourceType type,
                           const std::string& publicId)
   {
@@ -289,6 +269,4 @@
       throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
   }
-
-
 }
--- a/OrthancServer/ServerEnumerations.h	Tue Oct 15 16:23:42 2013 +0200
+++ b/OrthancServer/ServerEnumerations.h	Thu Oct 17 14:20:13 2013 +0200
@@ -33,6 +33,8 @@
 
 #include <string>
 
+#include "../Core/Enumerations.h"
+
 namespace Orthanc
 {
   enum SeriesStatus
@@ -71,14 +73,6 @@
     GlobalProperty_AnonymizationSequence = 3
   };
 
-  enum ResourceType
-  {
-    ResourceType_Patient = 1,
-    ResourceType_Study = 2,
-    ResourceType_Series = 3,
-    ResourceType_Instance = 4
-  };
-
   enum MetadataType
   {
     MetadataType_Instance_IndexInSeries = 1,
@@ -122,8 +116,6 @@
 
   MetadataType StringToMetadata(const std::string& str);
 
-  const char* EnumerationToString(ResourceType type);
-
   std::string EnumerationToString(MetadataType type);
 
   const char* EnumerationToString(SeriesStatus status);
--- a/OrthancServer/main.cpp	Tue Oct 15 16:23:42 2013 +0200
+++ b/OrthancServer/main.cpp	Thu Oct 17 14:20:13 2013 +0200
@@ -43,6 +43,7 @@
 #include "DicomProtocol/DicomServer.h"
 #include "OrthancInitialization.h"
 #include "ServerContext.h"
+#include "OrthancFindRequestHandler.h"
 
 using namespace Orthanc;
 
@@ -72,27 +73,6 @@
 };
 
 
-class MyFindRequestHandler : public IFindRequestHandler
-{
-private:
-  ServerContext& context_;
-
-public:
-  MyFindRequestHandler(ServerContext& context) :
-    context_(context)
-  {
-  }
-
-  virtual void Handle(const DicomMap& input,
-                      DicomFindAnswers& answers)
-  {
-    LOG(WARNING) << "Find-SCU request received";
-    DicomArray a(input);
-    a.Print(stdout);
-  }
-};
-
-
 class MyMoveRequestHandler : public IMoveRequestHandler
 {
 private:
@@ -134,7 +114,7 @@
 
   virtual IFindRequestHandler* ConstructFindRequestHandler()
   {
-    return new MyFindRequestHandler(context_);
+    return new OrthancFindRequestHandler(context_);
   }
 
   virtual IMoveRequestHandler* ConstructMoveRequestHandler()
@@ -369,14 +349,13 @@
 
     MyDicomServerFactory serverFactory(context);
     
-
     {
       // DICOM server
       DicomServer dicomServer;
       dicomServer.SetCalledApplicationEntityTitleCheck(GetGlobalBoolParameter("DicomCheckCalledAet", false));
       dicomServer.SetStoreRequestHandlerFactory(serverFactory);
-      //dicomServer.SetMoveRequestHandlerFactory(serverFactory);
-      //dicomServer.SetFindRequestHandlerFactory(serverFactory);
+      dicomServer.SetMoveRequestHandlerFactory(serverFactory);
+      dicomServer.SetFindRequestHandlerFactory(serverFactory);
       dicomServer.SetPortNumber(GetGlobalIntegerParameter("DicomPort", 4242));
       dicomServer.SetApplicationEntityTitle(GetGlobalStringParameter("DicomAet", "ORTHANC"));
 
--- a/UnitTests/ServerIndex.cpp	Tue Oct 15 16:23:42 2013 +0200
+++ b/UnitTests/ServerIndex.cpp	Thu Oct 17 14:20:13 2013 +0200
@@ -487,3 +487,11 @@
 
 
 }
+
+
+TEST(DicomMap, MainTags)
+{
+  ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_PATIENT_ID));
+  ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_PATIENT_ID, ResourceType_Patient));
+  ASSERT_FALSE(DicomMap::IsMainDicomTag(DICOM_TAG_PATIENT_ID, ResourceType_Study));
+}
--- a/UnitTests/main.cpp	Tue Oct 15 16:23:42 2013 +0200
+++ b/UnitTests/main.cpp	Thu Oct 17 14:20:13 2013 +0200
@@ -127,6 +127,10 @@
   t = FromDcmtkBridge::ParseTag("0020-e040");
   ASSERT_EQ(0x0020, t.GetGroup());
   ASSERT_EQ(0xe040, t.GetElement());
+
+  // Test ==() and !=() operators
+  ASSERT_TRUE(DICOM_TAG_PATIENT_ID == DicomTag(0x0010, 0x0020));
+  ASSERT_FALSE(DICOM_TAG_PATIENT_ID != DicomTag(0x0010, 0x0020));
 }
 
 
@@ -387,6 +391,13 @@
   ASSERT_EQ("IndexInSeries", EnumerationToString(MetadataType_Instance_IndexInSeries));
   ASSERT_EQ("LastUpdate", EnumerationToString(MetadataType_LastUpdate));
 
+  ASSERT_EQ(ResourceType_Patient, StringToResourceType("PATienT"));
+  ASSERT_EQ(ResourceType_Study, StringToResourceType("STudy"));
+  ASSERT_EQ(ResourceType_Series, StringToResourceType("SeRiEs"));
+  ASSERT_EQ(ResourceType_Instance, StringToResourceType("INStance"));
+  ASSERT_EQ(ResourceType_Instance, StringToResourceType("IMagE"));
+  ASSERT_THROW(StringToResourceType("heLLo"), OrthancException);
+
   ASSERT_EQ(2047, StringToMetadata("2047"));
   ASSERT_THROW(StringToMetadata("Ceci est un test"), OrthancException);
   ASSERT_THROW(RegisterUserMetadata(128, ""), OrthancException); // too low (< 1024)