# HG changeset patch # User Sebastien Jodogne # Date 1448038591 -3600 # Node ID f08978b1f45b5201603231db2b4e5ad37feec57c # Parent d093f998a83b48c981631843ed169b20df71a2c4 c-find scu for modality worklists diff -r d093f998a83b -r f08978b1f45b OrthancServer/DicomProtocol/DicomUserConnection.cpp --- 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 dataset(ConvertQueryFields(fields, manufacturer_)); + const char* clevel = NULL; + const char* sopClass = NULL; - std::auto_ptr 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 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_); + } } diff -r d093f998a83b -r f08978b1f45b OrthancServer/DicomProtocol/DicomUserConnection.h --- 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); }; } diff -r d093f998a83b -r f08978b1f45b OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp --- 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; diff -r d093f998a83b -r f08978b1f45b OrthancServer/ParsedDicomFile.cpp --- 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)); + } } diff -r d093f998a83b -r f08978b1f45b OrthancServer/ParsedDicomFile.h --- 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); diff -r d093f998a83b -r f08978b1f45b OrthancServer/main.cpp --- 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) { diff -r d093f998a83b -r f08978b1f45b UnitTestsSources/FromDcmtkTests.cpp --- 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); }