Mercurial > hg > orthanc
diff OrthancServer/OrthancRestApi/OrthancRestModalities.cpp @ 1368:b22ba8c5edbe query-retrieve
query retrieve
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Tue, 26 May 2015 17:54:34 +0200 |
parents | 6e7e5ed91c2d |
children | d710ea64f0fd |
line wrap: on
line diff
--- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Fri May 22 17:40:10 2015 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Tue May 26 17:54:34 2015 +0200 @@ -39,35 +39,16 @@ #include "../Scheduler/ServerJob.h" #include "../Scheduler/StoreScuCommand.h" #include "../Scheduler/StorePeerCommand.h" +#include "../QueryRetrieveHandler.h" +#include "../ServerToolbox.h" #include <glog/logging.h> namespace Orthanc { - // DICOM SCU ---------------------------------------------------------------- - - static bool MergeQueryAndTemplate(DicomMap& result, - const std::string& postData) - { - Json::Value query; - Json::Reader reader; - - if (!reader.parse(postData, query) || - query.type() != Json::objectValue) - { - return false; - } - - Json::Value::Members members = query.getMemberNames(); - for (size_t i = 0; i < members.size(); i++) - { - DicomTag t = FromDcmtkBridge::ParseTag(members[i]); - result.SetValue(t, query[members[i]].asString()); - } - - return true; - } - + /*************************************************************************** + * DICOM C-Echo SCU + ***************************************************************************/ static void DicomEcho(RestApiPostCall& call) { @@ -94,13 +75,100 @@ } + + /*************************************************************************** + * DICOM C-Find SCU => DEPRECATED! + ***************************************************************************/ + + static bool MergeQueryAndTemplate(DicomMap& result, + const std::string& postData) + { + Json::Value query; + Json::Reader reader; + + if (!reader.parse(postData, query) || + query.type() != Json::objectValue) + { + return false; + } + + Json::Value::Members members = query.getMemberNames(); + for (size_t i = 0; i < members.size(); i++) + { + DicomTag t = FromDcmtkBridge::ParseTag(members[i]); + result.SetValue(t, query[members[i]].asString()); + } + + return true; + } + + + static void FindPatient(DicomFindAnswers& result, + DicomUserConnection& connection, + const DicomMap& fields) + { + // Only keep the filters from "fields" that are related to the patient + DicomMap s; + fields.ExtractPatientInformation(s); + connection.Find(result, ResourceType_Patient, s); + } + + + static void FindStudy(DicomFindAnswers& result, + DicomUserConnection& connection, + const DicomMap& fields) + { + // Only keep the filters from "fields" that are related to the study + DicomMap s; + fields.ExtractStudyInformation(s); + + s.CopyTagIfExists(fields, DICOM_TAG_PATIENT_ID); + s.CopyTagIfExists(fields, DICOM_TAG_ACCESSION_NUMBER); + s.CopyTagIfExists(fields, DICOM_TAG_MODALITIES_IN_STUDY); + + connection.Find(result, ResourceType_Study, s); + } + + static void FindSeries(DicomFindAnswers& result, + DicomUserConnection& connection, + const DicomMap& fields) + { + // Only keep the filters from "fields" that are related to the series + DicomMap s; + fields.ExtractSeriesInformation(s); + + s.CopyTagIfExists(fields, DICOM_TAG_PATIENT_ID); + s.CopyTagIfExists(fields, DICOM_TAG_ACCESSION_NUMBER); + s.CopyTagIfExists(fields, DICOM_TAG_STUDY_INSTANCE_UID); + + connection.Find(result, ResourceType_Series, s); + } + + static void FindInstance(DicomFindAnswers& result, + DicomUserConnection& connection, + const DicomMap& fields) + { + // Only keep the filters from "fields" that are related to the instance + DicomMap s; + fields.ExtractInstanceInformation(s); + + s.CopyTagIfExists(fields, DICOM_TAG_PATIENT_ID); + s.CopyTagIfExists(fields, DICOM_TAG_ACCESSION_NUMBER); + s.CopyTagIfExists(fields, DICOM_TAG_STUDY_INSTANCE_UID); + s.CopyTagIfExists(fields, DICOM_TAG_SERIES_INSTANCE_UID); + + connection.Find(result, ResourceType_Instance, s); + } + + static void DicomFindPatient(RestApiPostCall& call) { + LOG(WARNING) << "This URI is deprecated: " << call.FlattenUri(); ServerContext& context = OrthancRestApi::GetContext(call); - DicomMap m; - DicomMap::SetupFindPatientTemplate(m); - if (!MergeQueryAndTemplate(m, call.GetPostBody())) + DicomMap fields; + DicomMap::SetupFindPatientTemplate(fields); + if (!MergeQueryAndTemplate(fields, call.GetPostBody())) { return; } @@ -109,26 +177,27 @@ ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), remote); DicomFindAnswers answers; - locker.GetConnection().FindPatient(answers, m); + FindPatient(answers, locker.GetConnection(), fields); Json::Value result; - answers.ToJson(result); + answers.ToJson(result, true); call.GetOutput().AnswerJson(result); } static void DicomFindStudy(RestApiPostCall& call) { + LOG(WARNING) << "This URI is deprecated: " << call.FlattenUri(); ServerContext& context = OrthancRestApi::GetContext(call); - DicomMap m; - DicomMap::SetupFindStudyTemplate(m); - if (!MergeQueryAndTemplate(m, call.GetPostBody())) + DicomMap fields; + DicomMap::SetupFindStudyTemplate(fields); + if (!MergeQueryAndTemplate(fields, call.GetPostBody())) { return; } - if (m.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 && - m.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) + if (fields.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 && + fields.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) { return; } @@ -137,27 +206,28 @@ ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), remote); DicomFindAnswers answers; - locker.GetConnection().FindStudy(answers, m); + FindStudy(answers, locker.GetConnection(), fields); Json::Value result; - answers.ToJson(result); + answers.ToJson(result, true); call.GetOutput().AnswerJson(result); } static void DicomFindSeries(RestApiPostCall& call) { + LOG(WARNING) << "This URI is deprecated: " << call.FlattenUri(); ServerContext& context = OrthancRestApi::GetContext(call); - DicomMap m; - DicomMap::SetupFindSeriesTemplate(m); - if (!MergeQueryAndTemplate(m, call.GetPostBody())) + DicomMap fields; + DicomMap::SetupFindSeriesTemplate(fields); + if (!MergeQueryAndTemplate(fields, call.GetPostBody())) { return; } - if ((m.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 && - m.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) || - m.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString().size() <= 2) + if ((fields.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 && + fields.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) || + fields.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString().size() <= 2) { return; } @@ -166,28 +236,29 @@ ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), remote); DicomFindAnswers answers; - locker.GetConnection().FindSeries(answers, m); + FindSeries(answers, locker.GetConnection(), fields); Json::Value result; - answers.ToJson(result); + answers.ToJson(result, true); call.GetOutput().AnswerJson(result); } static void DicomFindInstance(RestApiPostCall& call) { + LOG(WARNING) << "This URI is deprecated: " << call.FlattenUri(); ServerContext& context = OrthancRestApi::GetContext(call); - DicomMap m; - DicomMap::SetupFindInstanceTemplate(m); - if (!MergeQueryAndTemplate(m, call.GetPostBody())) + DicomMap fields; + DicomMap::SetupFindInstanceTemplate(fields); + if (!MergeQueryAndTemplate(fields, call.GetPostBody())) { return; } - if ((m.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 && - m.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) || - m.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString().size() <= 2 || - m.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString().size() <= 2) + if ((fields.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 && + fields.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) || + fields.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).AsString().size() <= 2 || + fields.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).AsString().size() <= 2) { return; } @@ -196,15 +267,17 @@ ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), remote); DicomFindAnswers answers; - locker.GetConnection().FindInstance(answers, m); + FindInstance(answers, locker.GetConnection(), fields); Json::Value result; - answers.ToJson(result); + answers.ToJson(result, true); call.GetOutput().AnswerJson(result); } + static void DicomFind(RestApiPostCall& call) { + LOG(WARNING) << "This URI is deprecated: " << call.FlattenUri(); ServerContext& context = OrthancRestApi::GetContext(call); DicomMap m; @@ -218,14 +291,14 @@ ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), remote); DicomFindAnswers patients; - locker.GetConnection().FindPatient(patients, m); + FindPatient(patients, locker.GetConnection(), m); // Loop over the found patients Json::Value result = Json::arrayValue; for (size_t i = 0; i < patients.GetSize(); i++) { Json::Value patient(Json::objectValue); - FromDcmtkBridge::ToJson(patient, patients.GetAnswer(i)); + FromDcmtkBridge::ToJson(patient, patients.GetAnswer(i), true); DicomMap::SetupFindStudyTemplate(m); if (!MergeQueryAndTemplate(m, call.GetPostBody())) @@ -235,7 +308,7 @@ m.CopyTagIfExists(patients.GetAnswer(i), DICOM_TAG_PATIENT_ID); DicomFindAnswers studies; - locker.GetConnection().FindStudy(studies, m); + FindStudy(studies, locker.GetConnection(), m); patient["Studies"] = Json::arrayValue; @@ -243,7 +316,7 @@ for (size_t j = 0; j < studies.GetSize(); j++) { Json::Value study(Json::objectValue); - FromDcmtkBridge::ToJson(study, studies.GetAnswer(j)); + FromDcmtkBridge::ToJson(study, studies.GetAnswer(j), true); DicomMap::SetupFindSeriesTemplate(m); if (!MergeQueryAndTemplate(m, call.GetPostBody())) @@ -254,14 +327,14 @@ m.CopyTagIfExists(studies.GetAnswer(j), DICOM_TAG_STUDY_INSTANCE_UID); DicomFindAnswers series; - locker.GetConnection().FindSeries(series, m); + FindSeries(series, locker.GetConnection(), m); // Loop over the found series study["Series"] = Json::arrayValue; for (size_t k = 0; k < series.GetSize(); k++) { Json::Value series2(Json::objectValue); - FromDcmtkBridge::ToJson(series2, series.GetAnswer(k)); + FromDcmtkBridge::ToJson(series2, series.GetAnswer(k), true); study["Series"].append(series2); } @@ -275,6 +348,205 @@ } + + /*************************************************************************** + * DICOM C-Find and C-Move SCU => Recommended since Orthanc 0.9.0 + ***************************************************************************/ + + static void DicomQuery(RestApiPostCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + Json::Value request; + + if (call.ParseJsonRequest(request) && + request.type() == Json::objectValue && + request.isMember("Level") && request["Level"].type() == Json::stringValue && + (!request.isMember("Query") || request["Query"].type() == Json::objectValue)) + { + std::auto_ptr<QueryRetrieveHandler> handler(new QueryRetrieveHandler(context)); + + handler->SetModality(call.GetUriComponent("id", "")); + handler->SetLevel(StringToResourceType(request["Level"].asString().c_str())); + + if (request.isMember("Query")) + { + Json::Value::Members tags = request["Query"].getMemberNames(); + for (size_t i = 0; i < tags.size(); i++) + { + handler->SetQuery(FromDcmtkBridge::ParseTag(tags[i].c_str()), + request["Query"][tags[i]].asString()); + } + } + + handler->Run(); + + std::string s = context.GetQueryRetrieveArchive().Add(handler.release()); + Json::Value result = Json::objectValue; + result["ID"] = s; + result["Path"] = "/queries/" + s; + call.GetOutput().AnswerJson(result); + } + } + + + static void ListQueries(RestApiGetCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + + std::list<std::string> queries; + context.GetQueryRetrieveArchive().List(queries); + + Json::Value result = Json::arrayValue; + for (std::list<std::string>::const_iterator + it = queries.begin(); it != queries.end(); ++it) + { + result.append(*it); + } + + call.GetOutput().AnswerJson(result); + } + + + namespace + { + class QueryAccessor + { + private: + ServerContext& context_; + SharedArchive::Accessor accessor_; + QueryRetrieveHandler& handler_; + + public: + QueryAccessor(RestApiCall& call) : + context_(OrthancRestApi::GetContext(call)), + accessor_(context_.GetQueryRetrieveArchive(), call.GetUriComponent("id", "")), + handler_(dynamic_cast<QueryRetrieveHandler&>(accessor_.GetItem())) + { + } + + QueryRetrieveHandler* operator->() + { + return &handler_; + } + }; + + static void AnswerDicomMap(RestApiCall& call, + const DicomMap& value, + bool simplify) + { + Json::Value full = Json::objectValue; + FromDcmtkBridge::ToJson(full, value, simplify); + call.GetOutput().AnswerJson(full); + } + } + + + static void ListQueryAnswers(RestApiGetCall& call) + { + QueryAccessor query(call); + size_t count = query->GetAnswerCount(); + + Json::Value result = Json::arrayValue; + for (size_t i = 0; i < count; i++) + { + result.append(boost::lexical_cast<std::string>(i)); + } + + call.GetOutput().AnswerJson(result); + } + + + static void GetQueryOneAnswer(RestApiGetCall& call) + { + size_t index = boost::lexical_cast<size_t>(call.GetUriComponent("index", "")); + QueryAccessor query(call); + AnswerDicomMap(call, query->GetAnswer(index), call.HasArgument("simplify")); + } + + + static void RetrieveOneAnswer(RestApiPostCall& call) + { + size_t index = boost::lexical_cast<size_t>(call.GetUriComponent("index", "")); + + LOG(WARNING) << "Driving C-Move SCU on modality: " << call.GetPostBody(); + + QueryAccessor query(call); + query->Retrieve(call.GetPostBody(), index); + + // Retrieve has succeeded + call.GetOutput().AnswerBuffer("{}", "application/json"); + } + + + static void RetrieveAllAnswers(RestApiPostCall& call) + { + LOG(WARNING) << "Driving C-Move SCU on modality: " << call.GetPostBody(); + + QueryAccessor query(call); + query->Retrieve(call.GetPostBody()); + + // Retrieve has succeeded + call.GetOutput().AnswerBuffer("{}", "application/json"); + } + + + static void GetQueryArguments(RestApiGetCall& call) + { + QueryAccessor query(call); + AnswerDicomMap(call, query->GetQuery(), call.HasArgument("simplify")); + } + + + static void GetQueryLevel(RestApiGetCall& call) + { + QueryAccessor query(call); + call.GetOutput().AnswerBuffer(EnumerationToString(query->GetLevel()), "text/plain"); + } + + + static void GetQueryModality(RestApiGetCall& call) + { + QueryAccessor query(call); + call.GetOutput().AnswerBuffer(query->GetModalitySymbolicName(), "text/plain"); + } + + + static void DeleteQuery(RestApiDeleteCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + context.GetQueryRetrieveArchive().Remove(call.GetUriComponent("id", "")); + call.GetOutput().AnswerBuffer("", "text/plain"); + } + + + static void ListQueryOperations(RestApiGetCall& call) + { + // Ensure that the query of interest does exist + QueryAccessor query(call); + + RestApi::AutoListChildren(call); + } + + + static void ListQueryAnswerOperations(RestApiGetCall& call) + { + // Ensure that the query of interest does exist + QueryAccessor query(call); + + // Ensure that the answer of interest does exist + size_t index = boost::lexical_cast<size_t>(call.GetUriComponent("index", "")); + query->GetAnswer(index); + + RestApi::AutoListChildren(call); + } + + + + + /*************************************************************************** + * DICOM C-Store SCU + ***************************************************************************/ + static bool GetInstancesToExport(std::list<std::string>& instances, const std::string& remote, RestApiPostCall& call) @@ -379,7 +651,9 @@ } - // Orthanc Peers ------------------------------------------------------------ + /*************************************************************************** + * Orthanc Peers => Store client + ***************************************************************************/ static bool IsExistingPeer(const OrthancRestApi::SetOfStrings& peers, const std::string& id) @@ -543,6 +817,20 @@ Register("/modalities/{id}/find", DicomFind); Register("/modalities/{id}/store", DicomStore); + // For Query/Retrieve + Register("/modalities/{id}/query", DicomQuery); + Register("/queries", ListQueries); + Register("/queries/{id}", DeleteQuery); + Register("/queries/{id}", ListQueryOperations); + Register("/queries/{id}/answers", ListQueryAnswers); + Register("/queries/{id}/answers/{index}", ListQueryAnswerOperations); + Register("/queries/{id}/answers/{index}/content", GetQueryOneAnswer); + Register("/queries/{id}/answers/{index}/retrieve", RetrieveOneAnswer); + Register("/queries/{id}/level", GetQueryLevel); + Register("/queries/{id}/modality", GetQueryModality); + Register("/queries/{id}/query", GetQueryArguments); + Register("/queries/{id}/retrieve", RetrieveAllAnswers); + Register("/peers", ListPeers); Register("/peers/{id}", ListPeerOperations); Register("/peers/{id}", UpdatePeer);