changeset 1805:f08978b1f45b worklists

c-find scu for modality worklists
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 20 Nov 2015 17:56:31 +0100
parents d093f998a83b
children 91216c42c6e5
files OrthancServer/DicomProtocol/DicomUserConnection.cpp OrthancServer/DicomProtocol/DicomUserConnection.h OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp OrthancServer/ParsedDicomFile.cpp OrthancServer/ParsedDicomFile.h OrthancServer/main.cpp UnitTestsSources/FromDcmtkTests.cpp
diffstat 7 files changed, 129 insertions(+), 65 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Fri Nov 20 16:42:49 2015 +0100
+++ b/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Fri Nov 20 17:56:31 2015 +0100
@@ -353,7 +353,8 @@
     struct FindPayload
     {
       DicomFindAnswers* answers;
-      std::string       level;
+      const char*       level;
+      bool              isWorklist;
     };
   }
 
@@ -371,15 +372,23 @@
 
     if (responseIdentifiers != NULL)
     {
-      DicomMap m;
-      FromDcmtkBridge::Convert(m, *responseIdentifiers);
-
-      if (!m.HasTag(DICOM_TAG_QUERY_RETRIEVE_LEVEL))
+      if (payload.isWorklist)
+      {
+        ParsedDicomFile answer(*responseIdentifiers);
+        payload.answers->Add(answer);
+      }
+      else
       {
-        m.SetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL, payload.level);
+        DicomMap m;
+        FromDcmtkBridge::Convert(m, *responseIdentifiers);
+
+        if (!m.HasTag(DICOM_TAG_QUERY_RETRIEVE_LEVEL))
+        {
+          m.SetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL, payload.level);
+        }
+
+        payload.answers->Add(m);
       }
-
-      payload.answers->Add(m);
     }
   }
 
@@ -474,6 +483,52 @@
   }
 
 
+  static void ExecuteFind(DicomFindAnswers& answers,
+                          T_ASC_Association* association,
+                          DcmDataset* dataset,
+                          const char* sopClass,
+                          bool isWorklist,
+                          const char* level,
+                          uint32_t dimseTimeout)
+  {
+    assert(isWorklist ^ (level != NULL));
+
+    FindPayload payload;
+    payload.answers = &answers;
+    payload.level = level;
+    payload.isWorklist = isWorklist;
+
+    // Figure out which of the accepted presentation contexts should be used
+    int presID = ASC_findAcceptedPresentationContextID(association, sopClass);
+    if (presID == 0)
+    {
+      throw OrthancException(ErrorCode_DicomFindUnavailable);
+    }
+
+    T_DIMSE_C_FindRQ request;
+    memset(&request, 0, sizeof(request));
+    request.MessageID = association->nextMsgID++;
+    strcpy(request.AffectedSOPClassUID, sopClass);
+    request.DataSetType = DIMSE_DATASET_PRESENT;
+    request.Priority = DIMSE_PRIORITY_MEDIUM;
+
+    T_DIMSE_C_FindRSP response;
+    DcmDataset* statusDetail = NULL;
+    OFCondition cond = DIMSE_findUser(association, presID, &request, dataset,
+                                      FindCallback, &payload,
+                                      /*opt_blockMode*/ DIMSE_BLOCKING, 
+                                      /*opt_dimse_timeout*/ dimseTimeout,
+                                      &response, &statusDetail);
+
+    if (statusDetail)
+    {
+      delete statusDetail;
+    }
+
+    Check(cond);
+  }
+
+
   void DicomUserConnection::Find(DicomFindAnswers& result,
                                  ResourceType level,
                                  const DicomMap& originalFields)
@@ -483,34 +538,32 @@
 
     CheckIsOpen();
 
-    FindPayload payload;
-    payload.answers = &result;
+    std::auto_ptr<DcmDataset> dataset(ConvertQueryFields(fields, manufacturer_));
+    const char* clevel = NULL;
+    const char* sopClass = NULL;
 
-    std::auto_ptr<DcmDataset> dataset(ConvertQueryFields(fields, manufacturer_));
-
-    const char* sopClass;
     switch (level)
     {
       case ResourceType_Patient:
-        payload.level = "PATIENT";
+        clevel = "PATIENT";
         DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "PATIENT");
         sopClass = UID_FINDPatientRootQueryRetrieveInformationModel;
         break;
 
       case ResourceType_Study:
-        payload.level = "STUDY";
+        clevel = "STUDY";
         DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "STUDY");
         sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
         break;
 
       case ResourceType_Series:
-        payload.level = "SERIES";
+        clevel = "SERIES";
         DU_putStringDOElement(dataset.get(), DcmTagKey(0x0008, 0x0052), "SERIES");
         sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
         break;
 
       case ResourceType_Instance:
-        payload.level = "INSTANCE";
+        clevel = "INSTANCE";
         if (manufacturer_ == ModalityManufacturer_ClearCanvas ||
             manufacturer_ == ModalityManufacturer_Dcm4Chee)
         {
@@ -565,34 +618,8 @@
         throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
 
-    // Figure out which of the accepted presentation contexts should be used
-    int presID = ASC_findAcceptedPresentationContextID(pimpl_->assoc_, sopClass);
-    if (presID == 0)
-    {
-      throw OrthancException(ErrorCode_DicomFindUnavailable);
-    }
-
-    T_DIMSE_C_FindRQ request;
-    memset(&request, 0, sizeof(request));
-    request.MessageID = pimpl_->assoc_->nextMsgID++;
-    strcpy(request.AffectedSOPClassUID, sopClass);
-    request.DataSetType = DIMSE_DATASET_PRESENT;
-    request.Priority = DIMSE_PRIORITY_MEDIUM;
-
-    T_DIMSE_C_FindRSP response;
-    DcmDataset* statusDetail = NULL;
-    OFCondition cond = DIMSE_findUser(pimpl_->assoc_, presID, &request, dataset.get(),
-                                      FindCallback, &payload,
-                                      /*opt_blockMode*/ DIMSE_BLOCKING, 
-                                      /*opt_dimse_timeout*/ pimpl_->dimseTimeout_,
-                                      &response, &statusDetail);
-
-    if (statusDetail)
-    {
-      delete statusDetail;
-    }
-
-    Check(cond);
+    assert(clevel != NULL && sopClass != NULL);
+    ExecuteFind(result, pimpl_->assoc_, dataset.get(), sopClass, false, clevel, pimpl_->dimseTimeout_);
   }
 
 
@@ -685,13 +712,14 @@
     defaultStorageSOPClasses_.clear();
 
     // Copy the short list of storage SOP classes from DCMTK, making
-    // room for the 4 SOP classes reserved for C-ECHO, C-FIND, C-MOVE.
+    // room for the 5 SOP classes reserved for C-ECHO, C-FIND, C-MOVE at (**).
 
     std::set<std::string> uncommon;
     uncommon.insert(UID_BlendingSoftcopyPresentationStateStorage);
     uncommon.insert(UID_GrayscaleSoftcopyPresentationStateStorage);
     uncommon.insert(UID_ColorSoftcopyPresentationStateStorage);
     uncommon.insert(UID_PseudoColorSoftcopyPresentationStateStorage);
+    uncommon.insert(UID_XAXRFGrayscaleSoftcopyPresentationStateStorage);
 
     // Add the storage syntaxes for C-STORE
     for (int i = 0; i < numberOfDcmShortSCUStorageSOPClassUIDs - 1; i++)
@@ -721,11 +749,12 @@
     pimpl_->params_ = NULL;
     pimpl_->assoc_ = NULL;
 
-    // SOP classes for C-ECHO, C-FIND and C-MOVE
+    // SOP classes for C-ECHO, C-FIND and C-MOVE (**)
     reservedStorageSOPClasses_.push_back(UID_VerificationSOPClass);
     reservedStorageSOPClasses_.push_back(UID_FINDPatientRootQueryRetrieveInformationModel);
     reservedStorageSOPClasses_.push_back(UID_FINDStudyRootQueryRetrieveInformationModel);
     reservedStorageSOPClasses_.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel);
+    reservedStorageSOPClasses_.push_back(UID_FINDModalityWorklistInformationModel);
 
     ResetStorageSOPClasses();
   }
@@ -1101,4 +1130,15 @@
     CheckStorageSOPClassesInvariant();
   }
 
+
+  void DicomUserConnection::FindWorklist(DicomFindAnswers& result,
+                                         ParsedDicomFile& query)
+  {
+    CheckIsOpen();
+
+    DcmDataset* dataset = query.GetDcmtkObject().getDataset();
+    const char* sopClass = UID_FINDModalityWorklistInformationModel;
+
+    ExecuteFind(result, pimpl_->assoc_, dataset, sopClass, true, NULL, pimpl_->dimseTimeout_);
+  }
 }
--- a/OrthancServer/DicomProtocol/DicomUserConnection.h	Fri Nov 20 16:42:49 2015 +0100
+++ b/OrthancServer/DicomProtocol/DicomUserConnection.h	Fri Nov 20 17:56:31 2015 +0100
@@ -164,5 +164,8 @@
     void SetTimeout(uint32_t seconds);
 
     void DisableTimeout();
+
+    void FindWorklist(DicomFindAnswers& result,
+                      ParsedDicomFile& query);
   };
 }
--- a/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Fri Nov 20 16:42:49 2015 +0100
+++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Fri Nov 20 17:56:31 2015 +0100
@@ -620,7 +620,7 @@
       throw OrthancException(ErrorCode_BadRequest);
     }
 
-    ParsedDicomFile dicom;
+    ParsedDicomFile dicom(true);
 
     {
       Encoding encoding;
@@ -834,7 +834,7 @@
     else
     {
       // Compatibility with Orthanc <= 0.9.3
-      ParsedDicomFile dicom;
+      ParsedDicomFile dicom(true);
       CreateDicomV1(dicom, call, request);
 
       std::string id;
--- a/OrthancServer/ParsedDicomFile.cpp	Fri Nov 20 16:42:49 2015 +0100
+++ b/OrthancServer/ParsedDicomFile.cpp	Fri Nov 20 17:56:31 2015 +0100
@@ -814,13 +814,17 @@
   }
 
 
-  ParsedDicomFile::ParsedDicomFile() : pimpl_(new PImpl)
+  ParsedDicomFile::ParsedDicomFile(bool createIdentifiers) : pimpl_(new PImpl)
   {
     pimpl_->file_.reset(new DcmFileFormat);
-    Replace(DICOM_TAG_PATIENT_ID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient));
-    Replace(DICOM_TAG_STUDY_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study));
-    Replace(DICOM_TAG_SERIES_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series));
-    Replace(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance));
+
+    if (createIdentifiers)
+    {
+      Replace(DICOM_TAG_PATIENT_ID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient));
+      Replace(DICOM_TAG_STUDY_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study));
+      Replace(DICOM_TAG_SERIES_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series));
+      Replace(DICOM_TAG_SOP_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Instance));
+    }
   }
 
 
--- a/OrthancServer/ParsedDicomFile.h	Fri Nov 20 16:42:49 2015 +0100
+++ b/OrthancServer/ParsedDicomFile.h	Fri Nov 20 17:56:31 2015 +0100
@@ -62,7 +62,7 @@
                           bool decodeBinaryTags);
 
   public:
-    ParsedDicomFile();  // Create a minimal DICOM instance
+    ParsedDicomFile(bool createIdentifiers);  // Create a minimal DICOM instance
 
     ParsedDicomFile(const DicomMap& map);
 
--- a/OrthancServer/main.cpp	Fri Nov 20 16:42:49 2015 +0100
+++ b/OrthancServer/main.cpp	Fri Nov 20 17:56:31 2015 +0100
@@ -1080,6 +1080,23 @@
     {
       OrthancInitialize(configurationFile);
 
+      if (0)
+      {
+        // TODO REMOVE THIS TEST
+        DicomUserConnection c;
+        c.SetRemoteHost("localhost");
+        c.SetRemotePort(4243);
+        c.SetRemoteApplicationEntityTitle("ORTHANCTEST");
+        c.Open();
+        ParsedDicomFile f(false);
+        f.Replace(DICOM_TAG_PATIENT_NAME, "M*");
+        DicomFindAnswers a;
+        c.FindWorklist(a, f);
+        Json::Value j;
+        a.ToJson(j, true);
+        std::cout << j;
+      }
+
       bool restart = StartOrthanc(argc, argv, allowDatabaseUpgrade);
       if (restart)
       {
--- a/UnitTestsSources/FromDcmtkTests.cpp	Fri Nov 20 16:42:49 2015 +0100
+++ b/UnitTestsSources/FromDcmtkTests.cpp	Fri Nov 20 17:56:31 2015 +0100
@@ -75,7 +75,7 @@
   //m.Replace(DICOM_TAG_PATIENT_ID, "coucou");
   //m.Replace(DICOM_TAG_PATIENT_NAME, "coucou");
 
-  ParsedDicomFile o;
+  ParsedDicomFile o(true);
   o.SaveToFile("UnitTestsResults/anon.dcm");
 
   for (int i = 0; i < 10; i++)
@@ -103,7 +103,7 @@
   ASSERT_EQ(0x1020, privateTag2.GetElement());
 
   std::string s;
-  ParsedDicomFile o;
+  ParsedDicomFile o(true);
   o.Replace(DICOM_TAG_PATIENT_NAME, "coucou");
   ASSERT_FALSE(o.GetTagValue(s, privateTag));
   o.Insert(privateTag, "private tag", false);
@@ -161,7 +161,7 @@
   ASSERT_EQ(5u, reader.GetWidth());
   ASSERT_EQ(PixelFormat_RGBA32, reader.GetFormat());
 
-  ParsedDicomFile o;
+  ParsedDicomFile o(true);
   o.EmbedContent(s);
   o.SaveToFile("UnitTestsResults/png1.dcm");
 
@@ -267,7 +267,7 @@
     std::string dicom;
 
     {
-      ParsedDicomFile f;
+      ParsedDicomFile f(true);
       f.SetEncoding(testEncodings[i]);
 
       std::string s = Toolbox::ConvertToUtf8(testEncodingsEncoded[i], testEncodings[i]);
@@ -407,7 +407,7 @@
 
 TEST(ParsedDicomFile, InsertReplaceStrings)
 {
-  ParsedDicomFile f;
+  ParsedDicomFile f(true);
 
   f.Insert(DICOM_TAG_PATIENT_NAME, "World", false);
   ASSERT_THROW(f.Insert(DICOM_TAG_PATIENT_ID, "Hello", false), OrthancException);  // Already existing tag
@@ -446,7 +446,7 @@
 
 TEST(ParsedDicomFile, InsertReplaceJson)
 {
-  ParsedDicomFile f;
+  ParsedDicomFile f(true);
 
   Json::Value a;
   CreateSampleJson(a);
@@ -498,7 +498,7 @@
 
 TEST(ParsedDicomFile, JsonEncoding)
 {
-  ParsedDicomFile f;
+  ParsedDicomFile f(true);
 
   for (unsigned int i = 0; i < testEncodingsCount; i++)
   {
@@ -528,7 +528,7 @@
   FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7053, 0x1000), EVR_PN, "MyPrivateTag", 1, 1);
   FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7050, 0x1000), EVR_PN, "Declared public tag", 1, 1);
 
-  ParsedDicomFile f;
+  ParsedDicomFile f(true);
   f.Insert(DicomTag(0x7050, 0x1000), "Some public tag", false);  // Even group => public tag
   f.Insert(DicomTag(0x7052, 0x1000), "Some unknown tag", false);  // Even group => public, unknown tag
   f.Insert(DicomTag(0x7053, 0x1000), "Some private tag", false);  // Odd group => private tag
@@ -575,7 +575,7 @@
 
 TEST(ParsedDicomFile, ToJsonFlags2)
 {
-  ParsedDicomFile f;
+  ParsedDicomFile f(true);
   f.Insert(DICOM_TAG_PIXEL_DATA, "Pixels", false);
 
   Json::Value v;
@@ -620,7 +620,7 @@
   }
 
   {
-    ParsedDicomFile d;
+    ParsedDicomFile d(true);
     d.Replace(DICOM_TAG_PATIENT_ID, "my");
     a.Add(d);
   }