Mercurial > hg > orthanc
changeset 229:f2349bab5fe7
rename
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 30 Nov 2012 11:00:40 +0100 |
parents | 1af3bc092db8 |
children | ae2367145b49 |
files | CMakeLists.txt OrthancServer/OrthancRestApi.cpp OrthancServer/OrthancRestApi.h OrthancServer/OrthancRestApi2.cpp OrthancServer/OrthancRestApi2.h OrthancServer/main.cpp |
diffstat | 6 files changed, 700 insertions(+), 700 deletions(-) [+] |
line wrap: on
line diff
--- a/CMakeLists.txt Fri Nov 30 10:59:55 2012 +0100 +++ b/CMakeLists.txt Fri Nov 30 11:00:40 2012 +0100 @@ -141,7 +141,7 @@ OrthancServer/Internals/MoveScp.cpp OrthancServer/Internals/StoreScp.cpp OrthancServer/OrthancInitialization.cpp - OrthancServer/OrthancRestApi2.cpp + OrthancServer/OrthancRestApi.cpp OrthancServer/ServerIndex.cpp OrthancServer/ToDcmtkBridge.cpp OrthancServer/DatabaseWrapper.cpp
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancRestApi.cpp Fri Nov 30 11:00:40 2012 +0100 @@ -0,0 +1,633 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012 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 "OrthancRestApi2.h" + +#include "../Core/HttpServer/FilesystemHttpSender.h" +#include "../Core/Uuid.h" +#include "DicomProtocol/DicomUserConnection.h" +#include "FromDcmtkBridge.h" +#include "OrthancInitialization.h" +#include "ServerToolbox.h" + +#include <dcmtk/dcmdata/dcistrmb.h> +#include <dcmtk/dcmdata/dcfilefo.h> +#include <boost/lexical_cast.hpp> +#include <glog/logging.h> + + +#define RETRIEVE_CONTEXT(call) \ + OrthancRestApi2& contextApi = \ + dynamic_cast<OrthancRestApi2&>(call.GetContext()); \ + ServerContext& context = contextApi.GetContext() + +#define RETRIEVE_MODALITIES(call) \ + const OrthancRestApi2::Modalities& modalities = \ + dynamic_cast<OrthancRestApi2&>(call.GetContext()).GetModalities(); + + + +namespace Orthanc +{ + // DICOM SCU ---------------------------------------------------------------- + + static void ConnectToModality(DicomUserConnection& connection, + const std::string& name) + { + std::string aet, address; + int port; + GetDicomModality(name, aet, address, port); + connection.SetLocalApplicationEntityTitle(GetGlobalStringParameter("DicomAet", "ORTHANC")); + connection.SetDistantApplicationEntityTitle(aet); + connection.SetDistantHost(address); + connection.SetDistantPort(port); + connection.Open(); + } + + 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::FindTag(members[i]); + result.SetValue(t, query[members[i]].asString()); + } + + return true; + } + + static void DicomFindPatient(RestApi::PostCall& call) + { + DicomMap m; + DicomMap::SetupFindPatientTemplate(m); + if (!MergeQueryAndTemplate(m, call.GetPostBody())) + { + return; + } + + DicomUserConnection connection; + ConnectToModality(connection, call.GetUriComponent("id", "")); + + DicomFindAnswers answers; + connection.FindPatient(answers, m); + + Json::Value result; + answers.ToJson(result); + call.GetOutput().AnswerJson(result); + } + + static void DicomFindStudy(RestApi::PostCall& call) + { + DicomMap m; + DicomMap::SetupFindStudyTemplate(m); + if (!MergeQueryAndTemplate(m, call.GetPostBody())) + { + return; + } + + if (m.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 && + m.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) + { + return; + } + + DicomUserConnection connection; + ConnectToModality(connection, call.GetUriComponent("id", "")); + + DicomFindAnswers answers; + connection.FindStudy(answers, m); + + Json::Value result; + answers.ToJson(result); + call.GetOutput().AnswerJson(result); + } + + static void DicomFindSeries(RestApi::PostCall& call) + { + DicomMap m; + DicomMap::SetupFindSeriesTemplate(m); + if (!MergeQueryAndTemplate(m, 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) + { + return; + } + + DicomUserConnection connection; + ConnectToModality(connection, call.GetUriComponent("id", "")); + + DicomFindAnswers answers; + connection.FindSeries(answers, m); + + Json::Value result; + answers.ToJson(result); + call.GetOutput().AnswerJson(result); + } + + static void DicomFind(RestApi::PostCall& call) + { + DicomMap m; + DicomMap::SetupFindPatientTemplate(m); + if (!MergeQueryAndTemplate(m, call.GetPostBody())) + { + return; + } + + DicomUserConnection connection; + ConnectToModality(connection, call.GetUriComponent("id", "")); + + DicomFindAnswers patients; + connection.FindPatient(patients, 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)); + + DicomMap::SetupFindStudyTemplate(m); + if (!MergeQueryAndTemplate(m, call.GetPostBody())) + { + return; + } + m.CopyTagIfExists(patients.GetAnswer(i), DICOM_TAG_PATIENT_ID); + + DicomFindAnswers studies; + connection.FindStudy(studies, m); + + patient["Studies"] = Json::arrayValue; + + // Loop over the found studies + for (size_t j = 0; j < studies.GetSize(); j++) + { + Json::Value study(Json::objectValue); + FromDcmtkBridge::ToJson(study, studies.GetAnswer(j)); + + DicomMap::SetupFindSeriesTemplate(m); + if (!MergeQueryAndTemplate(m, call.GetPostBody())) + { + return; + } + m.CopyTagIfExists(studies.GetAnswer(j), DICOM_TAG_PATIENT_ID); + m.CopyTagIfExists(studies.GetAnswer(j), DICOM_TAG_STUDY_INSTANCE_UID); + + DicomFindAnswers series; + connection.FindSeries(series, 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)); + study["Series"].append(series2); + } + + patient["Studies"].append(study); + } + + result.append(patient); + } + + call.GetOutput().AnswerJson(result); + } + + + static void DicomStore(RestApi::PostCall& call) + { + RETRIEVE_CONTEXT(call); + + DicomUserConnection connection; + ConnectToModality(connection, call.GetUriComponent("id", "")); + + Json::Value found; + if (context.GetIndex().LookupResource(found, call.GetPostBody(), ResourceType_Series)) + { + // The UUID corresponds to a series + for (Json::Value::ArrayIndex i = 0; i < found["Instances"].size(); i++) + { + std::string instanceId = found["Instances"][i].asString(); + std::string dicom; + context.ReadFile(dicom, instanceId, AttachedFileType_Dicom); + connection.Store(dicom); + } + + call.GetOutput().AnswerBuffer("{}", "application/json"); + } + else if (context.GetIndex().LookupResource(found, call.GetPostBody(), ResourceType_Instance)) + { + // The UUID corresponds to an instance + std::string instanceId = call.GetPostBody(); + std::string dicom; + context.ReadFile(dicom, instanceId, AttachedFileType_Dicom); + connection.Store(dicom); + + call.GetOutput().AnswerBuffer("{}", "application/json"); + } + else + { + // The POST body is not a known resource, assume that it + // contains a raw DICOM instance + connection.Store(call.GetPostBody()); + call.GetOutput().AnswerBuffer("{}", "application/json"); + } + } + + + + // System information ------------------------------------------------------- + + static void ServeRoot(RestApi::GetCall& call) + { + call.GetOutput().Redirect("app/explorer.html"); + } + + static void GetSystemInformation(RestApi::GetCall& call) + { + RETRIEVE_CONTEXT(call); + + Json::Value result = Json::objectValue; + result["Version"] = ORTHANC_VERSION; + result["Name"] = GetGlobalStringParameter("Name", ""); + result["TotalCompressedSize"] = boost::lexical_cast<std::string> + (context.GetIndex().GetTotalCompressedSize()); + result["TotalUncompressedSize"] = boost::lexical_cast<std::string> + (context.GetIndex().GetTotalUncompressedSize()); + + call.GetOutput().AnswerJson(result); + } + + + // List all the patients, studies, series or instances ---------------------- + + template <enum ResourceType resourceType> + static void ListResources(RestApi::GetCall& call) + { + RETRIEVE_CONTEXT(call); + + Json::Value result; + context.GetIndex().GetAllUuids(result, resourceType); + call.GetOutput().AnswerJson(result); + } + + template <enum ResourceType resourceType> + static void GetSingleResource(RestApi::GetCall& call) + { + RETRIEVE_CONTEXT(call); + + Json::Value result; + if (context.GetIndex().LookupResource(result, call.GetUriComponent("id", ""), resourceType)) + { + call.GetOutput().AnswerJson(result); + } + } + + template <enum ResourceType resourceType> + static void DeleteSingleResource(RestApi::DeleteCall& call) + { + RETRIEVE_CONTEXT(call); + + Json::Value result; + if (context.GetIndex().DeleteResource(result, call.GetUriComponent("id", ""), resourceType)) + { + call.GetOutput().AnswerJson(result); + } + } + + + // Changes API -------------------------------------------------------------- + + static void GetChanges(RestApi::GetCall& call) + { + RETRIEVE_CONTEXT(call); + + static const unsigned int MAX_RESULTS = 100; + ServerIndex& index = context.GetIndex(); + + //std::string filter = GetArgument(getArguments, "filter", ""); + int64_t since; + unsigned int limit; + try + { + since = boost::lexical_cast<int64_t>(call.GetArgument("since", "0")); + limit = boost::lexical_cast<unsigned int>(call.GetArgument("limit", "0")); + } + catch (boost::bad_lexical_cast) + { + return; + } + + if (limit == 0 || limit > MAX_RESULTS) + { + limit = MAX_RESULTS; + } + + Json::Value result; + if (index.GetChanges(result, since, limit)) + { + call.GetOutput().AnswerJson(result); + } + } + + + + // Get information about a single instance ---------------------------------- + + static void GetInstanceFile(RestApi::GetCall& call) + { + RETRIEVE_CONTEXT(call); + + std::string publicId = call.GetUriComponent("id", ""); + context.AnswerFile(call.GetOutput(), publicId, AttachedFileType_Dicom); + } + + + template <bool simplify> + static void GetInstanceTags(RestApi::GetCall& call) + { + RETRIEVE_CONTEXT(call); + + std::string publicId = call.GetUriComponent("id", ""); + + Json::Value full; + context.ReadJson(full, publicId); + + if (simplify) + { + Json::Value simplified; + SimplifyTags(simplified, full); + call.GetOutput().AnswerJson(simplified); + } + else + { + call.GetOutput().AnswerJson(full); + } + } + + + static void ListFrames(RestApi::GetCall& call) + { + RETRIEVE_CONTEXT(call); + + Json::Value instance; + if (context.GetIndex().LookupResource(instance, call.GetUriComponent("id", ""), ResourceType_Instance)) + { + unsigned int numberOfFrames = 1; + + try + { + Json::Value tmp = instance["MainDicomTags"]["NumberOfFrames"]; + numberOfFrames = boost::lexical_cast<unsigned int>(tmp.asString()); + } + catch (...) + { + } + + Json::Value result = Json::arrayValue; + for (unsigned int i = 0; i < numberOfFrames; i++) + { + result.append(i); + } + + call.GetOutput().AnswerJson(result); + } + } + + + template <enum ImageExtractionMode mode> + static void GetImage(RestApi::GetCall& call) + { + RETRIEVE_CONTEXT(call); + + std::string frameId = call.GetUriComponent("frame", "0"); + + unsigned int frame; + try + { + frame = boost::lexical_cast<unsigned int>(frameId); + } + catch (boost::bad_lexical_cast) + { + return; + } + + std::string publicId = call.GetUriComponent("id", ""); + std::string dicomContent, png; + context.ReadFile(dicomContent, publicId, AttachedFileType_Dicom); + + try + { + FromDcmtkBridge::ExtractPngImage(png, dicomContent, frame, mode); + call.GetOutput().AnswerBuffer(png, "image/png"); + } + catch (OrthancException& e) + { + if (e.GetErrorCode() == ErrorCode_ParameterOutOfRange) + { + // The frame number is out of the range for this DICOM + // instance, the resource is not existent + } + else + { + std::string root = ""; + for (size_t i = 1; i < call.GetFullUri().size(); i++) + { + root += "../"; + } + + call.GetOutput().Redirect(root + "app/images/unsupported.png"); + } + } + } + + + // Upload of DICOM files through HTTP --------------------------------------- + + static void UploadDicomFile(RestApi::PostCall& call) + { + RETRIEVE_CONTEXT(call); + + const std::string& postData = call.GetPostBody(); + + LOG(INFO) << "Receiving a DICOM file of " << postData.size() << " bytes through HTTP"; + + // Prepare an input stream for the memory buffer + DcmInputBufferStream is; + if (postData.size() > 0) + { + is.setBuffer(&postData[0], postData.size()); + } + is.setEos(); + + DcmFileFormat dicomFile; + if (!dicomFile.read(is).good()) + { + call.GetOutput().SignalError(Orthanc_HttpStatus_415_UnsupportedMediaType); + return; + } + + DicomMap dicomSummary; + FromDcmtkBridge::Convert(dicomSummary, *dicomFile.getDataset()); + + DicomInstanceHasher hasher(dicomSummary); + + Json::Value dicomJson; + FromDcmtkBridge::ToJson(dicomJson, *dicomFile.getDataset()); + + StoreStatus status = StoreStatus_Failure; + if (postData.size() > 0) + { + status = context.Store + (reinterpret_cast<const char*>(&postData[0]), + postData.size(), dicomSummary, dicomJson, ""); + } + + Json::Value result = Json::objectValue; + + if (status != StoreStatus_Failure) + { + result["ID"] = hasher.HashInstance(); + result["Path"] = GetBasePath(ResourceType_Instance, hasher.HashInstance()); + } + + result["Status"] = ToString(status); + call.GetOutput().AnswerJson(result); + } + + + // DICOM bridge ------------------------------------------------------------- + + static bool IsExistingModality(const OrthancRestApi2::Modalities& modalities, + const std::string& id) + { + return modalities.find(id) != modalities.end(); + } + + static void ListModalities(RestApi::GetCall& call) + { + RETRIEVE_MODALITIES(call); + + Json::Value result = Json::arrayValue; + for (OrthancRestApi2::Modalities::const_iterator + it = modalities.begin(); it != modalities.end(); it++) + { + result.append(*it); + } + + call.GetOutput().AnswerJson(result); + } + + + static void ListModalityOperations(RestApi::GetCall& call) + { + RETRIEVE_MODALITIES(call); + + std::string id = call.GetUriComponent("id", ""); + if (IsExistingModality(modalities, id)) + { + Json::Value result = Json::arrayValue; + result.append("find-patient"); + result.append("find-study"); + result.append("find-series"); + result.append("find"); + result.append("store"); + call.GetOutput().AnswerJson(result); + } + } + + + + // Registration of the various REST handlers -------------------------------- + + OrthancRestApi2::OrthancRestApi2(ServerContext& context) : + context_(context) + { + GetListOfDicomModalities(modalities_); + + Register("/", ServeRoot); + Register("/system", GetSystemInformation); + Register("/changes", GetChanges); + + Register("/instances", UploadDicomFile); + Register("/instances", ListResources<ResourceType_Instance>); + Register("/patients", ListResources<ResourceType_Patient>); + Register("/series", ListResources<ResourceType_Series>); + Register("/studies", ListResources<ResourceType_Study>); + + Register("/instances/{id}", DeleteSingleResource<ResourceType_Instance>); + Register("/instances/{id}", GetSingleResource<ResourceType_Instance>); + Register("/patients/{id}", DeleteSingleResource<ResourceType_Patient>); + Register("/patients/{id}", GetSingleResource<ResourceType_Patient>); + Register("/series/{id}", DeleteSingleResource<ResourceType_Series>); + Register("/series/{id}", GetSingleResource<ResourceType_Series>); + Register("/studies/{id}", DeleteSingleResource<ResourceType_Study>); + Register("/studies/{id}", GetSingleResource<ResourceType_Study>); + + Register("/instances/{id}/file", GetInstanceFile); + Register("/instances/{id}/tags", GetInstanceTags<false>); + Register("/instances/{id}/simplified-tags", GetInstanceTags<true>); + Register("/instances/{id}/frames", ListFrames); + + Register("/instances/{id}/frames/{frame}/preview", GetImage<ImageExtractionMode_Preview>); + Register("/instances/{id}/frames/{frame}/image-uint8", GetImage<ImageExtractionMode_UInt8>); + Register("/instances/{id}/frames/{frame}/image-uint16", GetImage<ImageExtractionMode_UInt16>); + Register("/instances/{id}/preview", GetImage<ImageExtractionMode_Preview>); + Register("/instances/{id}/image-uint8", GetImage<ImageExtractionMode_UInt8>); + Register("/instances/{id}/image-uint16", GetImage<ImageExtractionMode_UInt16>); + + Register("/modalities", ListModalities); + Register("/modalities/{id}", ListModalityOperations); + Register("/modalities/{id}/find-patient", DicomFindPatient); + Register("/modalities/{id}/find-study", DicomFindStudy); + Register("/modalities/{id}/find-series", DicomFindSeries); + Register("/modalities/{id}/find", DicomFind); + Register("/modalities/{id}/store", DicomStore); + + // TODO : "content" + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancServer/OrthancRestApi.h Fri Nov 30 11:00:40 2012 +0100 @@ -0,0 +1,64 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012 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 "ServerContext.h" +#include "../Core/RestApi/RestApi.h" + +#include <set> + +namespace Orthanc +{ + class OrthancRestApi2 : public RestApi + { + public: + typedef std::set<std::string> Modalities; + + private: + ServerContext& context_; + Modalities modalities_; + + public: + OrthancRestApi2(ServerContext& context); + + ServerContext& GetContext() + { + return context_; + } + + Modalities& GetModalities() + { + return modalities_; + } + }; +}
--- a/OrthancServer/OrthancRestApi2.cpp Fri Nov 30 10:59:55 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,633 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 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 "OrthancRestApi2.h" - -#include "../Core/HttpServer/FilesystemHttpSender.h" -#include "../Core/Uuid.h" -#include "DicomProtocol/DicomUserConnection.h" -#include "FromDcmtkBridge.h" -#include "OrthancInitialization.h" -#include "ServerToolbox.h" - -#include <dcmtk/dcmdata/dcistrmb.h> -#include <dcmtk/dcmdata/dcfilefo.h> -#include <boost/lexical_cast.hpp> -#include <glog/logging.h> - - -#define RETRIEVE_CONTEXT(call) \ - OrthancRestApi2& contextApi = \ - dynamic_cast<OrthancRestApi2&>(call.GetContext()); \ - ServerContext& context = contextApi.GetContext() - -#define RETRIEVE_MODALITIES(call) \ - const OrthancRestApi2::Modalities& modalities = \ - dynamic_cast<OrthancRestApi2&>(call.GetContext()).GetModalities(); - - - -namespace Orthanc -{ - // DICOM SCU ---------------------------------------------------------------- - - static void ConnectToModality(DicomUserConnection& connection, - const std::string& name) - { - std::string aet, address; - int port; - GetDicomModality(name, aet, address, port); - connection.SetLocalApplicationEntityTitle(GetGlobalStringParameter("DicomAet", "ORTHANC")); - connection.SetDistantApplicationEntityTitle(aet); - connection.SetDistantHost(address); - connection.SetDistantPort(port); - connection.Open(); - } - - 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::FindTag(members[i]); - result.SetValue(t, query[members[i]].asString()); - } - - return true; - } - - static void DicomFindPatient(RestApi::PostCall& call) - { - DicomMap m; - DicomMap::SetupFindPatientTemplate(m); - if (!MergeQueryAndTemplate(m, call.GetPostBody())) - { - return; - } - - DicomUserConnection connection; - ConnectToModality(connection, call.GetUriComponent("id", "")); - - DicomFindAnswers answers; - connection.FindPatient(answers, m); - - Json::Value result; - answers.ToJson(result); - call.GetOutput().AnswerJson(result); - } - - static void DicomFindStudy(RestApi::PostCall& call) - { - DicomMap m; - DicomMap::SetupFindStudyTemplate(m); - if (!MergeQueryAndTemplate(m, call.GetPostBody())) - { - return; - } - - if (m.GetValue(DICOM_TAG_ACCESSION_NUMBER).AsString().size() <= 2 && - m.GetValue(DICOM_TAG_PATIENT_ID).AsString().size() <= 2) - { - return; - } - - DicomUserConnection connection; - ConnectToModality(connection, call.GetUriComponent("id", "")); - - DicomFindAnswers answers; - connection.FindStudy(answers, m); - - Json::Value result; - answers.ToJson(result); - call.GetOutput().AnswerJson(result); - } - - static void DicomFindSeries(RestApi::PostCall& call) - { - DicomMap m; - DicomMap::SetupFindSeriesTemplate(m); - if (!MergeQueryAndTemplate(m, 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) - { - return; - } - - DicomUserConnection connection; - ConnectToModality(connection, call.GetUriComponent("id", "")); - - DicomFindAnswers answers; - connection.FindSeries(answers, m); - - Json::Value result; - answers.ToJson(result); - call.GetOutput().AnswerJson(result); - } - - static void DicomFind(RestApi::PostCall& call) - { - DicomMap m; - DicomMap::SetupFindPatientTemplate(m); - if (!MergeQueryAndTemplate(m, call.GetPostBody())) - { - return; - } - - DicomUserConnection connection; - ConnectToModality(connection, call.GetUriComponent("id", "")); - - DicomFindAnswers patients; - connection.FindPatient(patients, 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)); - - DicomMap::SetupFindStudyTemplate(m); - if (!MergeQueryAndTemplate(m, call.GetPostBody())) - { - return; - } - m.CopyTagIfExists(patients.GetAnswer(i), DICOM_TAG_PATIENT_ID); - - DicomFindAnswers studies; - connection.FindStudy(studies, m); - - patient["Studies"] = Json::arrayValue; - - // Loop over the found studies - for (size_t j = 0; j < studies.GetSize(); j++) - { - Json::Value study(Json::objectValue); - FromDcmtkBridge::ToJson(study, studies.GetAnswer(j)); - - DicomMap::SetupFindSeriesTemplate(m); - if (!MergeQueryAndTemplate(m, call.GetPostBody())) - { - return; - } - m.CopyTagIfExists(studies.GetAnswer(j), DICOM_TAG_PATIENT_ID); - m.CopyTagIfExists(studies.GetAnswer(j), DICOM_TAG_STUDY_INSTANCE_UID); - - DicomFindAnswers series; - connection.FindSeries(series, 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)); - study["Series"].append(series2); - } - - patient["Studies"].append(study); - } - - result.append(patient); - } - - call.GetOutput().AnswerJson(result); - } - - - static void DicomStore(RestApi::PostCall& call) - { - RETRIEVE_CONTEXT(call); - - DicomUserConnection connection; - ConnectToModality(connection, call.GetUriComponent("id", "")); - - Json::Value found; - if (context.GetIndex().LookupResource(found, call.GetPostBody(), ResourceType_Series)) - { - // The UUID corresponds to a series - for (Json::Value::ArrayIndex i = 0; i < found["Instances"].size(); i++) - { - std::string instanceId = found["Instances"][i].asString(); - std::string dicom; - context.ReadFile(dicom, instanceId, AttachedFileType_Dicom); - connection.Store(dicom); - } - - call.GetOutput().AnswerBuffer("{}", "application/json"); - } - else if (context.GetIndex().LookupResource(found, call.GetPostBody(), ResourceType_Instance)) - { - // The UUID corresponds to an instance - std::string instanceId = call.GetPostBody(); - std::string dicom; - context.ReadFile(dicom, instanceId, AttachedFileType_Dicom); - connection.Store(dicom); - - call.GetOutput().AnswerBuffer("{}", "application/json"); - } - else - { - // The POST body is not a known resource, assume that it - // contains a raw DICOM instance - connection.Store(call.GetPostBody()); - call.GetOutput().AnswerBuffer("{}", "application/json"); - } - } - - - - // System information ------------------------------------------------------- - - static void ServeRoot(RestApi::GetCall& call) - { - call.GetOutput().Redirect("app/explorer.html"); - } - - static void GetSystemInformation(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - Json::Value result = Json::objectValue; - result["Version"] = ORTHANC_VERSION; - result["Name"] = GetGlobalStringParameter("Name", ""); - result["TotalCompressedSize"] = boost::lexical_cast<std::string> - (context.GetIndex().GetTotalCompressedSize()); - result["TotalUncompressedSize"] = boost::lexical_cast<std::string> - (context.GetIndex().GetTotalUncompressedSize()); - - call.GetOutput().AnswerJson(result); - } - - - // List all the patients, studies, series or instances ---------------------- - - template <enum ResourceType resourceType> - static void ListResources(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - Json::Value result; - context.GetIndex().GetAllUuids(result, resourceType); - call.GetOutput().AnswerJson(result); - } - - template <enum ResourceType resourceType> - static void GetSingleResource(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - Json::Value result; - if (context.GetIndex().LookupResource(result, call.GetUriComponent("id", ""), resourceType)) - { - call.GetOutput().AnswerJson(result); - } - } - - template <enum ResourceType resourceType> - static void DeleteSingleResource(RestApi::DeleteCall& call) - { - RETRIEVE_CONTEXT(call); - - Json::Value result; - if (context.GetIndex().DeleteResource(result, call.GetUriComponent("id", ""), resourceType)) - { - call.GetOutput().AnswerJson(result); - } - } - - - // Changes API -------------------------------------------------------------- - - static void GetChanges(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - static const unsigned int MAX_RESULTS = 100; - ServerIndex& index = context.GetIndex(); - - //std::string filter = GetArgument(getArguments, "filter", ""); - int64_t since; - unsigned int limit; - try - { - since = boost::lexical_cast<int64_t>(call.GetArgument("since", "0")); - limit = boost::lexical_cast<unsigned int>(call.GetArgument("limit", "0")); - } - catch (boost::bad_lexical_cast) - { - return; - } - - if (limit == 0 || limit > MAX_RESULTS) - { - limit = MAX_RESULTS; - } - - Json::Value result; - if (index.GetChanges(result, since, limit)) - { - call.GetOutput().AnswerJson(result); - } - } - - - - // Get information about a single instance ---------------------------------- - - static void GetInstanceFile(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - std::string publicId = call.GetUriComponent("id", ""); - context.AnswerFile(call.GetOutput(), publicId, AttachedFileType_Dicom); - } - - - template <bool simplify> - static void GetInstanceTags(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - std::string publicId = call.GetUriComponent("id", ""); - - Json::Value full; - context.ReadJson(full, publicId); - - if (simplify) - { - Json::Value simplified; - SimplifyTags(simplified, full); - call.GetOutput().AnswerJson(simplified); - } - else - { - call.GetOutput().AnswerJson(full); - } - } - - - static void ListFrames(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - Json::Value instance; - if (context.GetIndex().LookupResource(instance, call.GetUriComponent("id", ""), ResourceType_Instance)) - { - unsigned int numberOfFrames = 1; - - try - { - Json::Value tmp = instance["MainDicomTags"]["NumberOfFrames"]; - numberOfFrames = boost::lexical_cast<unsigned int>(tmp.asString()); - } - catch (...) - { - } - - Json::Value result = Json::arrayValue; - for (unsigned int i = 0; i < numberOfFrames; i++) - { - result.append(i); - } - - call.GetOutput().AnswerJson(result); - } - } - - - template <enum ImageExtractionMode mode> - static void GetImage(RestApi::GetCall& call) - { - RETRIEVE_CONTEXT(call); - - std::string frameId = call.GetUriComponent("frame", "0"); - - unsigned int frame; - try - { - frame = boost::lexical_cast<unsigned int>(frameId); - } - catch (boost::bad_lexical_cast) - { - return; - } - - std::string publicId = call.GetUriComponent("id", ""); - std::string dicomContent, png; - context.ReadFile(dicomContent, publicId, AttachedFileType_Dicom); - - try - { - FromDcmtkBridge::ExtractPngImage(png, dicomContent, frame, mode); - call.GetOutput().AnswerBuffer(png, "image/png"); - } - catch (OrthancException& e) - { - if (e.GetErrorCode() == ErrorCode_ParameterOutOfRange) - { - // The frame number is out of the range for this DICOM - // instance, the resource is not existent - } - else - { - std::string root = ""; - for (size_t i = 1; i < call.GetFullUri().size(); i++) - { - root += "../"; - } - - call.GetOutput().Redirect(root + "app/images/unsupported.png"); - } - } - } - - - // Upload of DICOM files through HTTP --------------------------------------- - - static void UploadDicomFile(RestApi::PostCall& call) - { - RETRIEVE_CONTEXT(call); - - const std::string& postData = call.GetPostBody(); - - LOG(INFO) << "Receiving a DICOM file of " << postData.size() << " bytes through HTTP"; - - // Prepare an input stream for the memory buffer - DcmInputBufferStream is; - if (postData.size() > 0) - { - is.setBuffer(&postData[0], postData.size()); - } - is.setEos(); - - DcmFileFormat dicomFile; - if (!dicomFile.read(is).good()) - { - call.GetOutput().SignalError(Orthanc_HttpStatus_415_UnsupportedMediaType); - return; - } - - DicomMap dicomSummary; - FromDcmtkBridge::Convert(dicomSummary, *dicomFile.getDataset()); - - DicomInstanceHasher hasher(dicomSummary); - - Json::Value dicomJson; - FromDcmtkBridge::ToJson(dicomJson, *dicomFile.getDataset()); - - StoreStatus status = StoreStatus_Failure; - if (postData.size() > 0) - { - status = context.Store - (reinterpret_cast<const char*>(&postData[0]), - postData.size(), dicomSummary, dicomJson, ""); - } - - Json::Value result = Json::objectValue; - - if (status != StoreStatus_Failure) - { - result["ID"] = hasher.HashInstance(); - result["Path"] = GetBasePath(ResourceType_Instance, hasher.HashInstance()); - } - - result["Status"] = ToString(status); - call.GetOutput().AnswerJson(result); - } - - - // DICOM bridge ------------------------------------------------------------- - - static bool IsExistingModality(const OrthancRestApi2::Modalities& modalities, - const std::string& id) - { - return modalities.find(id) != modalities.end(); - } - - static void ListModalities(RestApi::GetCall& call) - { - RETRIEVE_MODALITIES(call); - - Json::Value result = Json::arrayValue; - for (OrthancRestApi2::Modalities::const_iterator - it = modalities.begin(); it != modalities.end(); it++) - { - result.append(*it); - } - - call.GetOutput().AnswerJson(result); - } - - - static void ListModalityOperations(RestApi::GetCall& call) - { - RETRIEVE_MODALITIES(call); - - std::string id = call.GetUriComponent("id", ""); - if (IsExistingModality(modalities, id)) - { - Json::Value result = Json::arrayValue; - result.append("find-patient"); - result.append("find-study"); - result.append("find-series"); - result.append("find"); - result.append("store"); - call.GetOutput().AnswerJson(result); - } - } - - - - // Registration of the various REST handlers -------------------------------- - - OrthancRestApi2::OrthancRestApi2(ServerContext& context) : - context_(context) - { - GetListOfDicomModalities(modalities_); - - Register("/", ServeRoot); - Register("/system", GetSystemInformation); - Register("/changes", GetChanges); - - Register("/instances", UploadDicomFile); - Register("/instances", ListResources<ResourceType_Instance>); - Register("/patients", ListResources<ResourceType_Patient>); - Register("/series", ListResources<ResourceType_Series>); - Register("/studies", ListResources<ResourceType_Study>); - - Register("/instances/{id}", DeleteSingleResource<ResourceType_Instance>); - Register("/instances/{id}", GetSingleResource<ResourceType_Instance>); - Register("/patients/{id}", DeleteSingleResource<ResourceType_Patient>); - Register("/patients/{id}", GetSingleResource<ResourceType_Patient>); - Register("/series/{id}", DeleteSingleResource<ResourceType_Series>); - Register("/series/{id}", GetSingleResource<ResourceType_Series>); - Register("/studies/{id}", DeleteSingleResource<ResourceType_Study>); - Register("/studies/{id}", GetSingleResource<ResourceType_Study>); - - Register("/instances/{id}/file", GetInstanceFile); - Register("/instances/{id}/tags", GetInstanceTags<false>); - Register("/instances/{id}/simplified-tags", GetInstanceTags<true>); - Register("/instances/{id}/frames", ListFrames); - - Register("/instances/{id}/frames/{frame}/preview", GetImage<ImageExtractionMode_Preview>); - Register("/instances/{id}/frames/{frame}/image-uint8", GetImage<ImageExtractionMode_UInt8>); - Register("/instances/{id}/frames/{frame}/image-uint16", GetImage<ImageExtractionMode_UInt16>); - Register("/instances/{id}/preview", GetImage<ImageExtractionMode_Preview>); - Register("/instances/{id}/image-uint8", GetImage<ImageExtractionMode_UInt8>); - Register("/instances/{id}/image-uint16", GetImage<ImageExtractionMode_UInt16>); - - Register("/modalities", ListModalities); - Register("/modalities/{id}", ListModalityOperations); - Register("/modalities/{id}/find-patient", DicomFindPatient); - Register("/modalities/{id}/find-study", DicomFindStudy); - Register("/modalities/{id}/find-series", DicomFindSeries); - Register("/modalities/{id}/find", DicomFind); - Register("/modalities/{id}/store", DicomStore); - - // TODO : "content" - } -}
--- a/OrthancServer/OrthancRestApi2.h Fri Nov 30 10:59:55 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,64 +0,0 @@ -/** - * Orthanc - A Lightweight, RESTful DICOM Store - * Copyright (C) 2012 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 "ServerContext.h" -#include "../Core/RestApi/RestApi.h" - -#include <set> - -namespace Orthanc -{ - class OrthancRestApi2 : public RestApi - { - public: - typedef std::set<std::string> Modalities; - - private: - ServerContext& context_; - Modalities modalities_; - - public: - OrthancRestApi2(ServerContext& context); - - ServerContext& GetContext() - { - return context_; - } - - Modalities& GetModalities() - { - return modalities_; - } - }; -}
--- a/OrthancServer/main.cpp Fri Nov 30 10:59:55 2012 +0100 +++ b/OrthancServer/main.cpp Fri Nov 30 11:00:40 2012 +0100 @@ -30,7 +30,7 @@ **/ -#include "OrthancRestApi2.h" +#include "OrthancRestApi.h" #include <fstream> #include <glog/logging.h> @@ -250,7 +250,7 @@ httpServer.RegisterHandler(new FilesystemHttpHandler("/app", ORTHANC_PATH "/OrthancExplorer")); #endif - httpServer.RegisterHandler(new OrthancRestApi2(context)); + httpServer.RegisterHandler(new OrthancRestApi(context)); // GO !!! httpServer.Start();