Mercurial > hg > orthanc
view OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp @ 4403:ad646ff506d0
cont openapi
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 23 Dec 2020 18:32:13 +0100 |
parents | 354ea95b294a |
children | f34634916d8c |
line wrap: on
line source
/** * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium * Copyright (C) 2017-2020 Osimis S.A., 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 "../PrecompiledHeadersServer.h" #include "OrthancRestApi.h" #include "../../../OrthancFramework/Sources/Compression/GzipCompressor.h" #include "../../../OrthancFramework/Sources/DicomFormat/DicomImageInformation.h" #include "../../../OrthancFramework/Sources/DicomParsing/DicomWebJsonVisitor.h" #include "../../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h" #include "../../../OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.h" #include "../../../OrthancFramework/Sources/HttpServer/HttpContentNegociation.h" #include "../../../OrthancFramework/Sources/Images/Image.h" #include "../../../OrthancFramework/Sources/Images/ImageProcessing.h" #include "../../../OrthancFramework/Sources/Logging.h" #include "../../../OrthancFramework/Sources/MultiThreading/Semaphore.h" #include "../OrthancConfiguration.h" #include "../Search/DatabaseLookup.h" #include "../ServerContext.h" #include "../ServerToolbox.h" #include "../SliceOrdering.h" #include "../../Plugins/Engine/OrthancPlugins.h" // This "include" is mandatory for Release builds using Linux Standard Base #include <boost/math/special_functions/round.hpp> /** * This semaphore is used to limit the number of concurrent HTTP * requests on CPU-intensive routes of the REST API, in order to * prevent exhaustion of resources (new in Orthanc 1.7.0). **/ static Orthanc::Semaphore throttlingSemaphore_(4); // TODO => PARAMETER? namespace Orthanc { static void AnswerDicomAsJson(RestApiCall& call, const Json::Value& dicom, DicomToJsonFormat mode) { if (mode != DicomToJsonFormat_Full) { Json::Value simplified; Toolbox::SimplifyDicomAsJson(simplified, dicom, mode); call.GetOutput().AnswerJson(simplified); } else { call.GetOutput().AnswerJson(dicom); } } static DicomToJsonFormat GetDicomFormat(const RestApiGetCall& call) { if (call.HasArgument("simplify")) { return DicomToJsonFormat_Human; } else if (call.HasArgument("short")) { return DicomToJsonFormat_Short; } else { return DicomToJsonFormat_Full; } } static void AnswerDicomAsJson(RestApiGetCall& call, const Json::Value& dicom) { AnswerDicomAsJson(call, dicom, GetDicomFormat(call)); } static void ParseSetOfTags(std::set<DicomTag>& target, const RestApiGetCall& call, const std::string& argument) { target.clear(); if (call.HasArgument(argument)) { std::vector<std::string> tags; Toolbox::TokenizeString(tags, call.GetArgument(argument, ""), ','); for (size_t i = 0; i < tags.size(); i++) { target.insert(FromDcmtkBridge::ParseTag(tags[i])); } } } // List all the patients, studies, series or instances ---------------------- static void AnswerListOfResources(RestApiOutput& output, ServerIndex& index, const std::list<std::string>& resources, ResourceType level, bool expand) { Json::Value answer = Json::arrayValue; for (std::list<std::string>::const_iterator resource = resources.begin(); resource != resources.end(); ++resource) { if (expand) { Json::Value item; if (index.LookupResource(item, *resource, level)) { answer.append(item); } } else { answer.append(*resource); } } output.AnswerJson(answer); } template <enum ResourceType resourceType> static void ListResources(RestApiGetCall& call) { if (call.IsDocumentation()) { const std::string resources = GetResourceTypeText(resourceType, true /* plural */, false /* lower case */); call.GetDocumentation() .SetTag(GetResourceTypeText(resourceType, true /* plural */, true /* upper case */)) .SetSummary("List the available " + resources) .SetDescription("List the Orthanc identifiers of all the available DICOM " + resources) .SetHttpGetArgument("limit", RestApiCallDocumentation::Type_Number, "Limit the number of results", false) .SetHttpGetArgument("since", RestApiCallDocumentation::Type_Number, "Show only the resources since the provided index", false) .SetHttpGetArgument("expand", RestApiCallDocumentation::Type_String, "If present, retrieve detailed information about the individual " + resources, false) .SetHttpGetSample("https://demo.orthanc-server.com/" + resources + "?since=0&limit=2", true); return; } ServerIndex& index = OrthancRestApi::GetIndex(call); std::list<std::string> result; if (call.HasArgument("limit") || call.HasArgument("since")) { if (!call.HasArgument("limit")) { throw OrthancException(ErrorCode_BadRequest, "Missing \"limit\" argument for GET request against: " + call.FlattenUri()); } if (!call.HasArgument("since")) { throw OrthancException(ErrorCode_BadRequest, "Missing \"since\" argument for GET request against: " + call.FlattenUri()); } size_t since = boost::lexical_cast<size_t>(call.GetArgument("since", "")); size_t limit = boost::lexical_cast<size_t>(call.GetArgument("limit", "")); index.GetAllUuids(result, resourceType, since, limit); } else { index.GetAllUuids(result, resourceType); } AnswerListOfResources(call.GetOutput(), index, result, resourceType, call.HasArgument("expand")); } template <enum ResourceType resourceType> static void GetSingleResource(RestApiGetCall& call) { if (call.IsDocumentation()) { std::string sampleUrl; switch (resourceType) { case Orthanc::ResourceType_Instance: sampleUrl = "https://demo.orthanc-server.com/instances/d94d9a03-3003b047-a4affc69-322313b2-680530a2"; break; case Orthanc::ResourceType_Series: sampleUrl = "https://demo.orthanc-server.com/series/37836232-d13a2350-fa1dedc5-962b31aa-010f8e52"; break; case Orthanc::ResourceType_Study: sampleUrl = "https://demo.orthanc-server.com/studies/27f7126f-4f66fb14-03f4081b-f9341db2-53925988"; break; case Orthanc::ResourceType_Patient: sampleUrl = "https://demo.orthanc-server.com/patients/46e6332c-677825b6-202fcf7c-f787bc5f-7b07c382"; break; default: throw OrthancException(ErrorCode_ParameterOutOfRange); } const std::string resource = GetResourceTypeText(resourceType, false /* plural */, false /* lower case */); call.GetDocumentation() .SetTag(GetResourceTypeText(resourceType, true /* plural */, true /* upper case */)) .SetSummary("Get information about some " + resource) .SetDescription("Get information about the DICOM " + resource + " of interest whose Orthanc identifier is provided in the URL") .SetUriArgument("id", RestApiCallDocumentation::Type_String, "Orthanc identifier of the " + resource + " of interest") .SetHttpGetSample(sampleUrl, true); return; } Json::Value result; if (OrthancRestApi::GetIndex(call).LookupResource(result, call.GetUriComponent("id", ""), resourceType)) { call.GetOutput().AnswerJson(result); } } template <enum ResourceType resourceType> static void DeleteSingleResource(RestApiDeleteCall& call) { if (call.IsDocumentation()) { const std::string resource = GetResourceTypeText(resourceType, false /* plural */, false /* lower case */); call.GetDocumentation() .SetTag(GetResourceTypeText(resourceType, true /* plural */, true /* upper case */)) .SetSummary("Delete some " + resource) .SetDescription("Delete the DICOM " + resource + " of interest whose Orthanc identifier is provided in the URL") .SetUriArgument("id", RestApiCallDocumentation::Type_String, "Orthanc identifier of the " + resource + " of interest"); return; } Json::Value result; if (OrthancRestApi::GetContext(call).DeleteResource(result, call.GetUriComponent("id", ""), resourceType)) { call.GetOutput().AnswerJson(result); } } // Get information about a single patient ----------------------------------- static void IsProtectedPatient(RestApiGetCall& call) { if (call.IsDocumentation()) { call.GetDocumentation() .SetTag("Patients") .SetSummary("Is the patient protected against recycling?") .SetUriArgument("id", RestApiCallDocumentation::Type_String, "Orthanc identifier of the patient of interest") .AddAnswerType(MimeType_PlainText, "`1` if protected, `0` if not protected"); return; } std::string publicId = call.GetUriComponent("id", ""); bool isProtected = OrthancRestApi::GetIndex(call).IsProtectedPatient(publicId); call.GetOutput().AnswerBuffer(isProtected ? "1" : "0", MimeType_PlainText); } static void SetPatientProtection(RestApiPutCall& call) { if (call.IsDocumentation()) { call.GetDocumentation() .SetTag("Patients") .SetSummary("Protect one patient against recycling") .SetDescription("Check out configuration options `MaximumStorageSize` and `MaximumPatientCount`") .SetUriArgument("id", RestApiCallDocumentation::Type_String, "Orthanc identifier of the patient of interest"); return; } ServerContext& context = OrthancRestApi::GetContext(call); std::string publicId = call.GetUriComponent("id", ""); std::string body; call.BodyToString(body); body = Toolbox::StripSpaces(body); if (body == "0") { context.GetIndex().SetProtectedPatient(publicId, false); call.GetOutput().AnswerBuffer("", MimeType_PlainText); } else if (body == "1") { context.GetIndex().SetProtectedPatient(publicId, true); call.GetOutput().AnswerBuffer("", MimeType_PlainText); } else { // Bad request } } // Get information about a single instance ---------------------------------- static void GetInstanceFile(RestApiGetCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); std::string publicId = call.GetUriComponent("id", ""); HttpToolbox::Arguments::const_iterator accept = call.GetHttpHeaders().find("accept"); if (accept != call.GetHttpHeaders().end()) { // New in Orthanc 1.5.4 try { MimeType mime = StringToMimeType(accept->second.c_str()); if (mime == MimeType_DicomWebJson || mime == MimeType_DicomWebXml) { DicomWebJsonVisitor visitor; { ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), publicId); locker.GetDicom().Apply(visitor); } if (mime == MimeType_DicomWebJson) { std::string s = visitor.GetResult().toStyledString(); call.GetOutput().AnswerBuffer(s, MimeType_DicomWebJson); } else { std::string xml; visitor.FormatXml(xml); call.GetOutput().AnswerBuffer(xml, MimeType_DicomWebXml); } return; } } catch (OrthancException&) { } } context.AnswerAttachment(call.GetOutput(), publicId, FileContentType_Dicom); } static void ExportInstanceFile(RestApiPostCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); std::string publicId = call.GetUriComponent("id", ""); std::string dicom; context.ReadDicom(dicom, publicId); std::string target; call.BodyToString(target); SystemToolbox::WriteFile(dicom, target); call.GetOutput().AnswerBuffer("{}", MimeType_Json); } template <DicomToJsonFormat format> static void GetInstanceTags(RestApiGetCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); std::string publicId = call.GetUriComponent("id", ""); std::set<DicomTag> ignoreTagLength; ParseSetOfTags(ignoreTagLength, call, "ignore-length"); if (format != DicomToJsonFormat_Full || !ignoreTagLength.empty()) { Json::Value full; context.ReadDicomAsJson(full, publicId, ignoreTagLength); AnswerDicomAsJson(call, full, format); } else { // This path allows one to avoid the JSON decoding if no // simplification is asked, and if no "ignore-length" argument // is present std::string full; context.ReadDicomAsJson(full, publicId); call.GetOutput().AnswerBuffer(full, MimeType_Json); } } static void GetInstanceTagsBis(RestApiGetCall& call) { switch (GetDicomFormat(call)) { case DicomToJsonFormat_Human: GetInstanceTags<DicomToJsonFormat_Human>(call); break; case DicomToJsonFormat_Short: GetInstanceTags<DicomToJsonFormat_Short>(call); break; case DicomToJsonFormat_Full: GetInstanceTags<DicomToJsonFormat_Full>(call); break; default: throw OrthancException(ErrorCode_InternalError); } } static void ListFrames(RestApiGetCall& call) { std::string publicId = call.GetUriComponent("id", ""); unsigned int numberOfFrames; { ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), publicId); numberOfFrames = locker.GetDicom().GetFramesCount(); } Json::Value result = Json::arrayValue; for (unsigned int i = 0; i < numberOfFrames; i++) { result.append(i); } call.GetOutput().AnswerJson(result); } namespace { class ImageToEncode { private: std::unique_ptr<ImageAccessor>& image_; ImageExtractionMode mode_; bool invert_; MimeType format_; std::string answer_; public: ImageToEncode(std::unique_ptr<ImageAccessor>& image, ImageExtractionMode mode, bool invert) : image_(image), mode_(mode), invert_(invert), format_(MimeType_Binary) { } void Answer(RestApiOutput& output) { output.AnswerBuffer(answer_, format_); } void EncodeUsingPng() { format_ = MimeType_Png; DicomImageDecoder::ExtractPngImage(answer_, image_, mode_, invert_); } void EncodeUsingPam() { format_ = MimeType_Pam; DicomImageDecoder::ExtractPamImage(answer_, image_, mode_, invert_); } void EncodeUsingJpeg(uint8_t quality) { format_ = MimeType_Jpeg; DicomImageDecoder::ExtractJpegImage(answer_, image_, mode_, invert_, quality); } }; class EncodePng : public HttpContentNegociation::IHandler { private: ImageToEncode& image_; public: explicit EncodePng(ImageToEncode& image) : image_(image) { } virtual void Handle(const std::string& type, const std::string& subtype) ORTHANC_OVERRIDE { assert(type == "image"); assert(subtype == "png"); image_.EncodeUsingPng(); } }; class EncodePam : public HttpContentNegociation::IHandler { private: ImageToEncode& image_; public: explicit EncodePam(ImageToEncode& image) : image_(image) { } virtual void Handle(const std::string& type, const std::string& subtype) ORTHANC_OVERRIDE { assert(type == "image"); assert(subtype == "x-portable-arbitrarymap"); image_.EncodeUsingPam(); } }; class EncodeJpeg : public HttpContentNegociation::IHandler { private: ImageToEncode& image_; unsigned int quality_; public: EncodeJpeg(ImageToEncode& image, const RestApiGetCall& call) : image_(image) { std::string v = call.GetArgument("quality", "90" /* default JPEG quality */); bool ok = false; try { quality_ = boost::lexical_cast<unsigned int>(v); ok = (quality_ >= 1 && quality_ <= 100); } catch (boost::bad_lexical_cast&) { } if (!ok) { throw OrthancException( ErrorCode_BadRequest, "Bad quality for a JPEG encoding (must be a number between 0 and 100): " + v); } } virtual void Handle(const std::string& type, const std::string& subtype) ORTHANC_OVERRIDE { assert(type == "image"); assert(subtype == "jpeg"); image_.EncodeUsingJpeg(quality_); } }; } namespace { class IDecodedFrameHandler : public boost::noncopyable { public: virtual ~IDecodedFrameHandler() { } virtual void Handle(RestApiGetCall& call, std::unique_ptr<ImageAccessor>& decoded, const DicomMap& dicom) = 0; virtual bool RequiresDicomTags() const = 0; static void Apply(RestApiGetCall& call, IDecodedFrameHandler& handler) { ServerContext& context = OrthancRestApi::GetContext(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; } DicomMap dicom; std::unique_ptr<ImageAccessor> decoded; try { std::string publicId = call.GetUriComponent("id", ""); decoded.reset(context.DecodeDicomFrame(publicId, frame)); if (decoded.get() == NULL) { throw OrthancException(ErrorCode_NotImplemented, "Cannot decode DICOM instance with ID: " + publicId); } if (handler.RequiresDicomTags()) { /** * Retrieve a summary of the DICOM tags, which is * necessary to deal with MONOCHROME1 photometric * interpretation, and with windowing parameters. **/ ServerContext::DicomCacheLocker locker(context, publicId); OrthancConfiguration::DefaultExtractDicomSummary(dicom, locker.GetDicom()); } } catch (OrthancException& e) { if (e.GetErrorCode() == ErrorCode_ParameterOutOfRange || e.GetErrorCode() == ErrorCode_UnknownResource) { // 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"); } return; } handler.Handle(call, decoded, dicom); } static void DefaultHandler(RestApiGetCall& call, std::unique_ptr<ImageAccessor>& decoded, ImageExtractionMode mode, bool invert) { ImageToEncode image(decoded, mode, invert); HttpContentNegociation negociation; EncodePng png(image); negociation.Register(MIME_PNG, png); EncodeJpeg jpeg(image, call); negociation.Register(MIME_JPEG, jpeg); EncodePam pam(image); negociation.Register(MIME_PAM, pam); if (negociation.Apply(call.GetHttpHeaders())) { image.Answer(call.GetOutput()); } } }; class GetImageHandler : public IDecodedFrameHandler { private: ImageExtractionMode mode_; public: explicit GetImageHandler(ImageExtractionMode mode) : mode_(mode) { } virtual void Handle(RestApiGetCall& call, std::unique_ptr<ImageAccessor>& decoded, const DicomMap& dicom) ORTHANC_OVERRIDE { bool invert = false; if (mode_ == ImageExtractionMode_Preview) { DicomImageInformation info(dicom); invert = (info.GetPhotometricInterpretation() == PhotometricInterpretation_Monochrome1); } DefaultHandler(call, decoded, mode_, invert); } virtual bool RequiresDicomTags() const ORTHANC_OVERRIDE { return mode_ == ImageExtractionMode_Preview; } }; class RenderedFrameHandler : public IDecodedFrameHandler { private: static void GetDicomParameters(bool& invert, float& rescaleSlope, float& rescaleIntercept, float& windowWidth, float& windowCenter, const DicomMap& dicom) { DicomImageInformation info(dicom); invert = (info.GetPhotometricInterpretation() == PhotometricInterpretation_Monochrome1); rescaleSlope = 1.0f; rescaleIntercept = 0.0f; if (dicom.HasTag(Orthanc::DICOM_TAG_RESCALE_SLOPE) && dicom.HasTag(Orthanc::DICOM_TAG_RESCALE_INTERCEPT)) { dicom.ParseFloat(rescaleSlope, Orthanc::DICOM_TAG_RESCALE_SLOPE); dicom.ParseFloat(rescaleIntercept, Orthanc::DICOM_TAG_RESCALE_INTERCEPT); } windowWidth = static_cast<float>(1 << info.GetBitsStored()) * rescaleSlope; windowCenter = windowWidth / 2.0f + rescaleIntercept; if (dicom.HasTag(Orthanc::DICOM_TAG_WINDOW_CENTER) && dicom.HasTag(Orthanc::DICOM_TAG_WINDOW_WIDTH)) { dicom.ParseFirstFloat(windowCenter, Orthanc::DICOM_TAG_WINDOW_CENTER); dicom.ParseFirstFloat(windowWidth, Orthanc::DICOM_TAG_WINDOW_WIDTH); } } static void GetUserArguments(float& windowWidth /* inout */, float& windowCenter /* inout */, unsigned int& argWidth, unsigned int& argHeight, bool& smooth, const RestApiGetCall& call) { static const char* ARG_WINDOW_CENTER = "window-center"; static const char* ARG_WINDOW_WIDTH = "window-width"; static const char* ARG_WIDTH = "width"; static const char* ARG_HEIGHT = "height"; static const char* ARG_SMOOTH = "smooth"; if (call.HasArgument(ARG_WINDOW_WIDTH)) { try { windowWidth = boost::lexical_cast<float>(call.GetArgument(ARG_WINDOW_WIDTH, "")); } catch (boost::bad_lexical_cast&) { throw OrthancException(ErrorCode_ParameterOutOfRange, "Bad value for argument: " + std::string(ARG_WINDOW_WIDTH)); } } if (call.HasArgument(ARG_WINDOW_CENTER)) { try { windowCenter = boost::lexical_cast<float>(call.GetArgument(ARG_WINDOW_CENTER, "")); } catch (boost::bad_lexical_cast&) { throw OrthancException(ErrorCode_ParameterOutOfRange, "Bad value for argument: " + std::string(ARG_WINDOW_CENTER)); } } argWidth = 0; argHeight = 0; if (call.HasArgument(ARG_WIDTH)) { try { int tmp = boost::lexical_cast<int>(call.GetArgument(ARG_WIDTH, "")); if (tmp < 0) { throw OrthancException(ErrorCode_ParameterOutOfRange, "Argument cannot be negative: " + std::string(ARG_WIDTH)); } else { argWidth = static_cast<unsigned int>(tmp); } } catch (boost::bad_lexical_cast&) { throw OrthancException(ErrorCode_ParameterOutOfRange, "Bad value for argument: " + std::string(ARG_WIDTH)); } } if (call.HasArgument(ARG_HEIGHT)) { try { int tmp = boost::lexical_cast<int>(call.GetArgument(ARG_HEIGHT, "")); if (tmp < 0) { throw OrthancException(ErrorCode_ParameterOutOfRange, "Argument cannot be negative: " + std::string(ARG_HEIGHT)); } else { argHeight = static_cast<unsigned int>(tmp); } } catch (boost::bad_lexical_cast&) { throw OrthancException(ErrorCode_ParameterOutOfRange, "Bad value for argument: " + std::string(ARG_HEIGHT)); } } smooth = false; if (call.HasArgument(ARG_SMOOTH)) { std::string value = call.GetArgument(ARG_SMOOTH, ""); if (value == "0" || value == "false") { smooth = false; } else if (value == "1" || value == "true") { smooth = true; } else { throw OrthancException(ErrorCode_ParameterOutOfRange, "Argument must be Boolean: " + std::string(ARG_SMOOTH)); } } } public: virtual void Handle(RestApiGetCall& call, std::unique_ptr<ImageAccessor>& decoded, const DicomMap& dicom) ORTHANC_OVERRIDE { bool invert; float rescaleSlope, rescaleIntercept, windowWidth, windowCenter; GetDicomParameters(invert, rescaleSlope, rescaleIntercept, windowWidth, windowCenter, dicom); unsigned int argWidth, argHeight; bool smooth; GetUserArguments(windowWidth, windowCenter, argWidth, argHeight, smooth, call); unsigned int targetWidth = decoded->GetWidth(); unsigned int targetHeight = decoded->GetHeight(); if (decoded->GetWidth() != 0 && decoded->GetHeight() != 0) { float ratio = 1; if (argWidth != 0 && argHeight != 0) { float ratioX = static_cast<float>(argWidth) / static_cast<float>(decoded->GetWidth()); float ratioY = static_cast<float>(argHeight) / static_cast<float>(decoded->GetHeight()); ratio = std::min(ratioX, ratioY); } else if (argWidth != 0) { ratio = static_cast<float>(argWidth) / static_cast<float>(decoded->GetWidth()); } else if (argHeight != 0) { ratio = static_cast<float>(argHeight) / static_cast<float>(decoded->GetHeight()); } targetWidth = boost::math::iround(ratio * static_cast<float>(decoded->GetWidth())); targetHeight = boost::math::iround(ratio * static_cast<float>(decoded->GetHeight())); } if (decoded->GetFormat() == PixelFormat_RGB24) { if (targetWidth == decoded->GetWidth() && targetHeight == decoded->GetHeight()) { DefaultHandler(call, decoded, ImageExtractionMode_Preview, false); } else { std::unique_ptr<ImageAccessor> resized( new Image(decoded->GetFormat(), targetWidth, targetHeight, false)); if (smooth && (targetWidth < decoded->GetWidth() || targetHeight < decoded->GetHeight())) { ImageProcessing::SmoothGaussian5x5(*decoded, false /* be fast, don't round */); } ImageProcessing::Resize(*resized, *decoded); DefaultHandler(call, resized, ImageExtractionMode_Preview, false); } } else { // Grayscale image: (1) convert to Float32, (2) apply // windowing to get a Grayscale8, (3) possibly resize Image converted(PixelFormat_Float32, decoded->GetWidth(), decoded->GetHeight(), false); ImageProcessing::Convert(converted, *decoded); // Avoid divisions by zero if (windowWidth <= 1.0f) { windowWidth = 1; } if (std::abs(rescaleSlope) <= 0.1f) { rescaleSlope = 0.1f; } const float scaling = 255.0f * rescaleSlope / windowWidth; const float offset = (rescaleIntercept - windowCenter + windowWidth / 2.0f) / rescaleSlope; std::unique_ptr<ImageAccessor> rescaled(new Image(PixelFormat_Grayscale8, decoded->GetWidth(), decoded->GetHeight(), false)); ImageProcessing::ShiftScale(*rescaled, converted, offset, scaling, false); if (targetWidth == decoded->GetWidth() && targetHeight == decoded->GetHeight()) { DefaultHandler(call, rescaled, ImageExtractionMode_UInt8, invert); } else { std::unique_ptr<ImageAccessor> resized( new Image(PixelFormat_Grayscale8, targetWidth, targetHeight, false)); if (smooth && (targetWidth < decoded->GetWidth() || targetHeight < decoded->GetHeight())) { ImageProcessing::SmoothGaussian5x5(*rescaled, false /* be fast, don't round */); } ImageProcessing::Resize(*resized, *rescaled); DefaultHandler(call, resized, ImageExtractionMode_UInt8, invert); } } } virtual bool RequiresDicomTags() const ORTHANC_OVERRIDE { return true; } }; } template <enum ImageExtractionMode mode> static void GetImage(RestApiGetCall& call) { Semaphore::Locker locker(throttlingSemaphore_); GetImageHandler handler(mode); IDecodedFrameHandler::Apply(call, handler); } static void GetRenderedFrame(RestApiGetCall& call) { Semaphore::Locker locker(throttlingSemaphore_); RenderedFrameHandler handler; IDecodedFrameHandler::Apply(call, handler); } static void GetMatlabImage(RestApiGetCall& call) { Semaphore::Locker locker(throttlingSemaphore_); ServerContext& context = OrthancRestApi::GetContext(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::unique_ptr<ImageAccessor> decoded(context.DecodeDicomFrame(publicId, frame)); if (decoded.get() == NULL) { throw OrthancException(ErrorCode_NotImplemented, "Cannot decode DICOM instance with ID: " + publicId); } else { std::string result; decoded->ToMatlabString(result); call.GetOutput().AnswerBuffer(result, MimeType_PlainText); } } template <bool GzipCompression> static void GetRawFrame(RestApiGetCall& 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 raw; MimeType mime; { ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), publicId); locker.GetDicom().GetRawFrame(raw, mime, frame); } if (GzipCompression) { GzipCompressor gzip; std::string compressed; gzip.Compress(compressed, raw.empty() ? NULL : raw.c_str(), raw.size()); call.GetOutput().AnswerBuffer(compressed, MimeType_Gzip); } else { call.GetOutput().AnswerBuffer(raw, mime); } } static void GetResourceStatistics(RestApiGetCall& call) { static const uint64_t MEGA_BYTES = 1024 * 1024; std::string publicId = call.GetUriComponent("id", ""); ResourceType type; uint64_t diskSize, uncompressedSize, dicomDiskSize, dicomUncompressedSize; unsigned int countStudies, countSeries, countInstances; OrthancRestApi::GetIndex(call).GetResourceStatistics( type, diskSize, uncompressedSize, countStudies, countSeries, countInstances, dicomDiskSize, dicomUncompressedSize, publicId); Json::Value result = Json::objectValue; result["DiskSize"] = boost::lexical_cast<std::string>(diskSize); result["DiskSizeMB"] = static_cast<unsigned int>(diskSize / MEGA_BYTES); result["UncompressedSize"] = boost::lexical_cast<std::string>(uncompressedSize); result["UncompressedSizeMB"] = static_cast<unsigned int>(uncompressedSize / MEGA_BYTES); result["DicomDiskSize"] = boost::lexical_cast<std::string>(dicomDiskSize); result["DicomDiskSizeMB"] = static_cast<unsigned int>(dicomDiskSize / MEGA_BYTES); result["DicomUncompressedSize"] = boost::lexical_cast<std::string>(dicomUncompressedSize); result["DicomUncompressedSizeMB"] = static_cast<unsigned int>(dicomUncompressedSize / MEGA_BYTES); switch (type) { // Do NOT add "break" below this point! case ResourceType_Patient: result["CountStudies"] = countStudies; case ResourceType_Study: result["CountSeries"] = countSeries; case ResourceType_Series: result["CountInstances"] = countInstances; case ResourceType_Instance: default: break; } call.GetOutput().AnswerJson(result); } // Handling of metadata ----------------------------------------------------- static void CheckValidResourceType(const RestApiCall& call) { std::string resourceType = call.GetUriComponent("resourceType", ""); StringToResourceType(resourceType.c_str()); } static void ListMetadata(RestApiGetCall& call) { CheckValidResourceType(call); std::string publicId = call.GetUriComponent("id", ""); std::map<MetadataType, std::string> metadata; OrthancRestApi::GetIndex(call).GetAllMetadata(metadata, publicId); Json::Value result; if (call.HasArgument("expand")) { result = Json::objectValue; for (std::map<MetadataType, std::string>::const_iterator it = metadata.begin(); it != metadata.end(); ++it) { std::string key = EnumerationToString(it->first); result[key] = it->second; } } else { result = Json::arrayValue; for (std::map<MetadataType, std::string>::const_iterator it = metadata.begin(); it != metadata.end(); ++it) { result.append(EnumerationToString(it->first)); } } call.GetOutput().AnswerJson(result); } static void GetMetadata(RestApiGetCall& call) { CheckValidResourceType(call); std::string publicId = call.GetUriComponent("id", ""); std::string name = call.GetUriComponent("name", ""); MetadataType metadata = StringToMetadata(name); std::string value; if (OrthancRestApi::GetIndex(call).LookupMetadata(value, publicId, metadata)) { call.GetOutput().AnswerBuffer(value, MimeType_PlainText); } } static void DeleteMetadata(RestApiDeleteCall& call) { CheckValidResourceType(call); std::string publicId = call.GetUriComponent("id", ""); std::string name = call.GetUriComponent("name", ""); MetadataType metadata = StringToMetadata(name); if (IsUserMetadata(metadata)) // It is forbidden to modify internal metadata { OrthancRestApi::GetIndex(call).DeleteMetadata(publicId, metadata); call.GetOutput().AnswerBuffer("", MimeType_PlainText); } else { call.GetOutput().SignalError(HttpStatus_403_Forbidden); } } static void SetMetadata(RestApiPutCall& call) { CheckValidResourceType(call); std::string publicId = call.GetUriComponent("id", ""); std::string name = call.GetUriComponent("name", ""); MetadataType metadata = StringToMetadata(name); std::string value; call.BodyToString(value); if (IsUserMetadata(metadata)) // It is forbidden to modify internal metadata { // It is forbidden to modify internal metadata OrthancRestApi::GetIndex(call).SetMetadata(publicId, metadata, value); call.GetOutput().AnswerBuffer("", MimeType_PlainText); } else { call.GetOutput().SignalError(HttpStatus_403_Forbidden); } } // Handling of attached files ----------------------------------------------- static void ListAttachments(RestApiGetCall& call) { std::string resourceType = call.GetUriComponent("resourceType", ""); std::string publicId = call.GetUriComponent("id", ""); std::list<FileContentType> attachments; OrthancRestApi::GetIndex(call).ListAvailableAttachments(attachments, publicId, StringToResourceType(resourceType.c_str())); Json::Value result = Json::arrayValue; for (std::list<FileContentType>::const_iterator it = attachments.begin(); it != attachments.end(); ++it) { result.append(EnumerationToString(*it)); } call.GetOutput().AnswerJson(result); } static bool GetAttachmentInfo(FileInfo& info, RestApiCall& call) { CheckValidResourceType(call); std::string publicId = call.GetUriComponent("id", ""); std::string name = call.GetUriComponent("name", ""); FileContentType contentType = StringToContentType(name); return OrthancRestApi::GetIndex(call).LookupAttachment(info, publicId, contentType); } static void GetAttachmentOperations(RestApiGetCall& call) { FileInfo info; if (GetAttachmentInfo(info, call)) { Json::Value operations = Json::arrayValue; operations.append("compress"); operations.append("compressed-data"); if (info.GetCompressedMD5() != "") { operations.append("compressed-md5"); } operations.append("compressed-size"); operations.append("data"); operations.append("is-compressed"); if (info.GetUncompressedMD5() != "") { operations.append("md5"); } operations.append("size"); operations.append("uncompress"); if (info.GetCompressedMD5() != "" && info.GetUncompressedMD5() != "") { operations.append("verify-md5"); } call.GetOutput().AnswerJson(operations); } } template <int uncompress> static void GetAttachmentData(RestApiGetCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); CheckValidResourceType(call); std::string publicId = call.GetUriComponent("id", ""); FileContentType type = StringToContentType(call.GetUriComponent("name", "")); if (uncompress) { context.AnswerAttachment(call.GetOutput(), publicId, type); } else { // Return the raw data (possibly compressed), as stored on the filesystem std::string content; context.ReadAttachment(content, publicId, type, false); call.GetOutput().AnswerBuffer(content, MimeType_Binary); } } static void GetAttachmentSize(RestApiGetCall& call) { FileInfo info; if (GetAttachmentInfo(info, call)) { call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(info.GetUncompressedSize()), MimeType_PlainText); } } static void GetAttachmentCompressedSize(RestApiGetCall& call) { FileInfo info; if (GetAttachmentInfo(info, call)) { call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(info.GetCompressedSize()), MimeType_PlainText); } } static void GetAttachmentMD5(RestApiGetCall& call) { FileInfo info; if (GetAttachmentInfo(info, call) && info.GetUncompressedMD5() != "") { call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(info.GetUncompressedMD5()), MimeType_PlainText); } } static void GetAttachmentCompressedMD5(RestApiGetCall& call) { FileInfo info; if (GetAttachmentInfo(info, call) && info.GetCompressedMD5() != "") { call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(info.GetCompressedMD5()), MimeType_PlainText); } } static void VerifyAttachment(RestApiPostCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); CheckValidResourceType(call); std::string publicId = call.GetUriComponent("id", ""); std::string name = call.GetUriComponent("name", ""); FileInfo info; if (!GetAttachmentInfo(info, call) || info.GetCompressedMD5() == "" || info.GetUncompressedMD5() == "") { // Inexistent resource, or no MD5 available return; } bool ok = false; // First check whether the compressed data is correctly stored in the disk std::string data; context.ReadAttachment(data, publicId, StringToContentType(name), false); std::string actualMD5; Toolbox::ComputeMD5(actualMD5, data); if (actualMD5 == info.GetCompressedMD5()) { // The compressed data is OK. If a compression algorithm was // applied to it, now check the MD5 of the uncompressed data. if (info.GetCompressionType() == CompressionType_None) { ok = true; } else { context.ReadAttachment(data, publicId, StringToContentType(name), true); Toolbox::ComputeMD5(actualMD5, data); ok = (actualMD5 == info.GetUncompressedMD5()); } } if (ok) { CLOG(INFO, HTTP) << "The attachment " << name << " of resource " << publicId << " has the right MD5"; call.GetOutput().AnswerBuffer("{}", MimeType_Json); } else { CLOG(INFO, HTTP) << "The attachment " << name << " of resource " << publicId << " has bad MD5!"; } } static void UploadAttachment(RestApiPutCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); CheckValidResourceType(call); std::string publicId = call.GetUriComponent("id", ""); std::string name = call.GetUriComponent("name", ""); FileContentType contentType = StringToContentType(name); if (IsUserContentType(contentType) && // It is forbidden to modify internal attachments context.AddAttachment(publicId, StringToContentType(name), call.GetBodyData(), call.GetBodySize())) { call.GetOutput().AnswerBuffer("{}", MimeType_Json); } else { call.GetOutput().SignalError(HttpStatus_403_Forbidden); } } static void DeleteAttachment(RestApiDeleteCall& call) { CheckValidResourceType(call); std::string publicId = call.GetUriComponent("id", ""); std::string name = call.GetUriComponent("name", ""); FileContentType contentType = StringToContentType(name); bool allowed; if (IsUserContentType(contentType)) { allowed = true; } else { OrthancConfiguration::ReaderLock lock; if (lock.GetConfiguration().GetBooleanParameter("StoreDicom", true) && contentType == FileContentType_DicomAsJson) { allowed = true; } else { // It is forbidden to delete internal attachments, except for // the "DICOM as JSON" summary as of Orthanc 1.2.0 (this summary // would be automatically reconstructed on the next GET call) allowed = false; } } if (allowed) { OrthancRestApi::GetIndex(call).DeleteAttachment(publicId, contentType); call.GetOutput().AnswerBuffer("{}", MimeType_Json); } else { call.GetOutput().SignalError(HttpStatus_403_Forbidden); } } template <enum CompressionType compression> static void ChangeAttachmentCompression(RestApiPostCall& call) { CheckValidResourceType(call); std::string publicId = call.GetUriComponent("id", ""); std::string name = call.GetUriComponent("name", ""); FileContentType contentType = StringToContentType(name); OrthancRestApi::GetContext(call).ChangeAttachmentCompression(publicId, contentType, compression); call.GetOutput().AnswerBuffer("{}", MimeType_Json); } static void IsAttachmentCompressed(RestApiGetCall& call) { FileInfo info; if (GetAttachmentInfo(info, call)) { std::string answer = (info.GetCompressionType() == CompressionType_None) ? "0" : "1"; call.GetOutput().AnswerBuffer(answer, MimeType_PlainText); } } // Raw access to the DICOM tags of an instance ------------------------------ static void GetRawContent(RestApiGetCall& call) { std::string id = call.GetUriComponent("id", ""); ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), id); locker.GetDicom().SendPathValue(call.GetOutput(), call.GetTrailingUri()); } static bool ExtractSharedTags(Json::Value& shared, ServerContext& context, const std::string& publicId) { // Retrieve all the instances of this patient/study/series typedef std::list<std::string> Instances; Instances instances; context.GetIndex().GetChildInstances(instances, publicId); // (*) // Loop over the instances bool isFirst = true; shared = Json::objectValue; for (Instances::const_iterator it = instances.begin(); it != instances.end(); ++it) { // Get the tags of the current instance, in the simplified format Json::Value tags; try { context.ReadDicomAsJson(tags, *it); } catch (OrthancException&) { // Race condition: This instance has been removed since // (*). Ignore this instance. continue; } if (tags.type() != Json::objectValue) { return false; // Error } // Only keep the tags that are mapped to a string Json::Value::Members members = tags.getMemberNames(); for (size_t i = 0; i < members.size(); i++) { const Json::Value& tag = tags[members[i]]; if (tag.type() != Json::objectValue || tag["Type"].type() != Json::stringValue || tag["Type"].asString() != "String") { tags.removeMember(members[i]); } } if (isFirst) { // This is the first instance, keep its tags as such shared = tags; isFirst = false; } else { // Loop over all the members of the shared tags extracted so // far. If the value of one of these tags does not match its // value in the current instance, remove it. members = shared.getMemberNames(); for (size_t i = 0; i < members.size(); i++) { if (!tags.isMember(members[i]) || tags[members[i]]["Value"].asString() != shared[members[i]]["Value"].asString()) { shared.removeMember(members[i]); } } } } return true; } static void GetSharedTags(RestApiGetCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); std::string publicId = call.GetUriComponent("id", ""); Json::Value sharedTags; if (ExtractSharedTags(sharedTags, context, publicId)) { // Success: Send the value of the shared tags AnswerDicomAsJson(call, sharedTags); } } static void GetModuleInternal(RestApiGetCall& call, ResourceType resourceType, DicomModule module) { if (!((resourceType == ResourceType_Patient && module == DicomModule_Patient) || (resourceType == ResourceType_Study && module == DicomModule_Patient) || (resourceType == ResourceType_Study && module == DicomModule_Study) || (resourceType == ResourceType_Series && module == DicomModule_Series) || (resourceType == ResourceType_Instance && module == DicomModule_Instance) || (resourceType == ResourceType_Instance && module == DicomModule_Image))) { throw OrthancException(ErrorCode_NotImplemented); } ServerContext& context = OrthancRestApi::GetContext(call); std::string publicId = call.GetUriComponent("id", ""); std::set<DicomTag> ignoreTagLength; ParseSetOfTags(ignoreTagLength, call, "ignore-length"); typedef std::set<DicomTag> ModuleTags; ModuleTags moduleTags; DicomTag::AddTagsForModule(moduleTags, module); Json::Value tags; if (resourceType != ResourceType_Instance) { // Retrieve all the instances of this patient/study/series typedef std::list<std::string> Instances; Instances instances; context.GetIndex().GetChildInstances(instances, publicId); if (instances.empty()) { return; // Error: No instance (should never happen) } // Select one child instance publicId = instances.front(); } context.ReadDicomAsJson(tags, publicId, ignoreTagLength); // Filter the tags of the instance according to the module Json::Value result = Json::objectValue; for (ModuleTags::const_iterator tag = moduleTags.begin(); tag != moduleTags.end(); ++tag) { std::string s = tag->Format(); if (tags.isMember(s)) { result[s] = tags[s]; } } AnswerDicomAsJson(call, result); } template <enum ResourceType resourceType, enum DicomModule module> static void GetModule(RestApiGetCall& call) { GetModuleInternal(call, resourceType, module); } namespace { typedef std::list< std::pair<ResourceType, std::string> > LookupResults; } static void AccumulateLookupResults(LookupResults& result, ServerIndex& index, const DicomTag& tag, const std::string& value, ResourceType level) { std::vector<std::string> tmp; index.LookupIdentifierExact(tmp, level, tag, value); for (size_t i = 0; i < tmp.size(); i++) { result.push_back(std::make_pair(level, tmp[i])); } } static void Lookup(RestApiPostCall& call) { std::string tag; call.BodyToString(tag); LookupResults resources; ServerIndex& index = OrthancRestApi::GetIndex(call); AccumulateLookupResults(resources, index, DICOM_TAG_PATIENT_ID, tag, ResourceType_Patient); AccumulateLookupResults(resources, index, DICOM_TAG_STUDY_INSTANCE_UID, tag, ResourceType_Study); AccumulateLookupResults(resources, index, DICOM_TAG_SERIES_INSTANCE_UID, tag, ResourceType_Series); AccumulateLookupResults(resources, index, DICOM_TAG_SOP_INSTANCE_UID, tag, ResourceType_Instance); Json::Value result = Json::arrayValue; for (LookupResults::const_iterator it = resources.begin(); it != resources.end(); ++it) { ResourceType type = it->first; const std::string& id = it->second; Json::Value item = Json::objectValue; item["Type"] = EnumerationToString(type); item["ID"] = id; item["Path"] = GetBasePath(type, id); result.append(item); } call.GetOutput().AnswerJson(result); } namespace { class FindVisitor : public ServerContext::ILookupVisitor { private: bool isComplete_; std::list<std::string> resources_; public: FindVisitor() : isComplete_(false) { } virtual bool IsDicomAsJsonNeeded() const ORTHANC_OVERRIDE { return false; // (*) } virtual void MarkAsComplete() ORTHANC_OVERRIDE { isComplete_ = true; // Unused information as of Orthanc 1.5.0 } virtual void Visit(const std::string& publicId, const std::string& instanceId /* unused */, const DicomMap& mainDicomTags /* unused */, const Json::Value* dicomAsJson /* unused (*) */) ORTHANC_OVERRIDE { resources_.push_back(publicId); } void Answer(RestApiOutput& output, ServerIndex& index, ResourceType level, bool expand) const { AnswerListOfResources(output, index, resources_, level, expand); } }; } static void Find(RestApiPostCall& call) { static const char* const KEY_CASE_SENSITIVE = "CaseSensitive"; static const char* const KEY_EXPAND = "Expand"; static const char* const KEY_LEVEL = "Level"; static const char* const KEY_LIMIT = "Limit"; static const char* const KEY_QUERY = "Query"; static const char* const KEY_SINCE = "Since"; ServerContext& context = OrthancRestApi::GetContext(call); Json::Value request; if (!call.ParseJsonRequest(request) || request.type() != Json::objectValue) { throw OrthancException(ErrorCode_BadRequest, "The body must contain a JSON object"); } else if (!request.isMember(KEY_LEVEL) || request[KEY_LEVEL].type() != Json::stringValue) { throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_LEVEL) + "\" is missing, or should be a string"); } else if (!request.isMember(KEY_QUERY) && request[KEY_QUERY].type() != Json::objectValue) { throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_QUERY) + "\" is missing, or should be a JSON object"); } else if (request.isMember(KEY_CASE_SENSITIVE) && request[KEY_CASE_SENSITIVE].type() != Json::booleanValue) { throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_CASE_SENSITIVE) + "\" should be a Boolean"); } else if (request.isMember(KEY_LIMIT) && request[KEY_LIMIT].type() != Json::intValue) { throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_LIMIT) + "\" should be an integer"); } else if (request.isMember(KEY_SINCE) && request[KEY_SINCE].type() != Json::intValue) { throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_SINCE) + "\" should be an integer"); } else { bool expand = false; if (request.isMember(KEY_EXPAND)) { expand = request[KEY_EXPAND].asBool(); } bool caseSensitive = false; if (request.isMember(KEY_CASE_SENSITIVE)) { caseSensitive = request[KEY_CASE_SENSITIVE].asBool(); } size_t limit = 0; if (request.isMember(KEY_LIMIT)) { int tmp = request[KEY_LIMIT].asInt(); if (tmp < 0) { throw OrthancException(ErrorCode_ParameterOutOfRange, "Field \"" + std::string(KEY_LIMIT) + "\" should be a positive integer"); } limit = static_cast<size_t>(tmp); } size_t since = 0; if (request.isMember(KEY_SINCE)) { int tmp = request[KEY_SINCE].asInt(); if (tmp < 0) { throw OrthancException(ErrorCode_ParameterOutOfRange, "Field \"" + std::string(KEY_SINCE) + "\" should be a positive integer"); } since = static_cast<size_t>(tmp); } ResourceType level = StringToResourceType(request[KEY_LEVEL].asCString()); DatabaseLookup query; Json::Value::Members members = request[KEY_QUERY].getMemberNames(); for (size_t i = 0; i < members.size(); i++) { if (request[KEY_QUERY][members[i]].type() != Json::stringValue) { throw OrthancException(ErrorCode_BadRequest, "Tag \"" + members[i] + "\" should be associated with a string"); } const std::string value = request[KEY_QUERY][members[i]].asString(); if (!value.empty()) { // An empty string corresponds to an universal constraint, // so we ignore it. This mimics the behavior of class // "OrthancFindRequestHandler" query.AddRestConstraint(FromDcmtkBridge::ParseTag(members[i]), value, caseSensitive, true); } } FindVisitor visitor; context.Apply(visitor, query, level, since, limit); visitor.Answer(call.GetOutput(), context.GetIndex(), level, expand); } } template <enum ResourceType start, enum ResourceType end> static void GetChildResources(RestApiGetCall& call) { ServerIndex& index = OrthancRestApi::GetIndex(call); std::list<std::string> a, b, c; a.push_back(call.GetUriComponent("id", "")); ResourceType type = start; while (type != end) { b.clear(); for (std::list<std::string>::const_iterator it = a.begin(); it != a.end(); ++it) { index.GetChildren(c, *it); b.splice(b.begin(), c); } type = GetChildResourceType(type); a.clear(); a.splice(a.begin(), b); } Json::Value result = Json::arrayValue; for (std::list<std::string>::const_iterator it = a.begin(); it != a.end(); ++it) { Json::Value item; if (OrthancRestApi::GetIndex(call).LookupResource(item, *it, end)) { result.append(item); } } call.GetOutput().AnswerJson(result); } static void GetChildInstancesTags(RestApiGetCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); std::string publicId = call.GetUriComponent("id", ""); DicomToJsonFormat format = GetDicomFormat(call); std::set<DicomTag> ignoreTagLength; ParseSetOfTags(ignoreTagLength, call, "ignore-length"); // Retrieve all the instances of this patient/study/series typedef std::list<std::string> Instances; Instances instances; context.GetIndex().GetChildInstances(instances, publicId); // (*) Json::Value result = Json::objectValue; for (Instances::const_iterator it = instances.begin(); it != instances.end(); ++it) { Json::Value full; context.ReadDicomAsJson(full, *it, ignoreTagLength); if (format != DicomToJsonFormat_Full) { Json::Value simplified; Toolbox::SimplifyDicomAsJson(simplified, full, format); result[*it] = simplified; } else { result[*it] = full; } } call.GetOutput().AnswerJson(result); } template <enum ResourceType start, enum ResourceType end> static void GetParentResource(RestApiGetCall& call) { assert(start > end); ServerIndex& index = OrthancRestApi::GetIndex(call); std::string current = call.GetUriComponent("id", ""); ResourceType currentType = start; while (currentType > end) { std::string parent; if (!index.LookupParent(parent, current)) { // Error that could happen if the resource gets deleted by // another concurrent call return; } current = parent; currentType = GetParentResourceType(currentType); } assert(currentType == end); Json::Value result; if (index.LookupResource(result, current, end)) { call.GetOutput().AnswerJson(result); } } static void ExtractPdf(RestApiGetCall& call) { const std::string id = call.GetUriComponent("id", ""); std::string pdf; ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), id); if (locker.GetDicom().ExtractPdf(pdf)) { call.GetOutput().AnswerBuffer(pdf, MimeType_Pdf); return; } } static void OrderSlices(RestApiGetCall& call) { const std::string id = call.GetUriComponent("id", ""); ServerIndex& index = OrthancRestApi::GetIndex(call); SliceOrdering ordering(index, id); Json::Value result; ordering.Format(result); call.GetOutput().AnswerJson(result); } static void GetInstanceHeader(RestApiGetCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); std::string publicId = call.GetUriComponent("id", ""); std::string dicomContent; context.ReadDicom(dicomContent, publicId); // TODO Consider using "DicomMap::ParseDicomMetaInformation()" to // speed up things here ParsedDicomFile dicom(dicomContent); Json::Value header; OrthancConfiguration::DefaultDicomHeaderToJson(header, dicom); AnswerDicomAsJson(call, header); } static void InvalidateTags(RestApiPostCall& call) { ServerIndex& index = OrthancRestApi::GetIndex(call); // Loop over the instances, grouping them by parent studies so as // to avoid large memory consumption std::list<std::string> studies; index.GetAllUuids(studies, ResourceType_Study); for (std::list<std::string>::const_iterator study = studies.begin(); study != studies.end(); ++study) { std::list<std::string> instances; index.GetChildInstances(instances, *study); for (std::list<std::string>::const_iterator instance = instances.begin(); instance != instances.end(); ++instance) { index.DeleteAttachment(*instance, FileContentType_DicomAsJson); } } call.GetOutput().AnswerBuffer("", MimeType_PlainText); } template <enum ResourceType type> static void ReconstructResource(RestApiPostCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); ServerToolbox::ReconstructResource(context, call.GetUriComponent("id", "")); call.GetOutput().AnswerBuffer("", MimeType_PlainText); } static void ReconstructAllResources(RestApiPostCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); std::list<std::string> studies; context.GetIndex().GetAllUuids(studies, ResourceType_Study); for (std::list<std::string>::const_iterator study = studies.begin(); study != studies.end(); ++study) { ServerToolbox::ReconstructResource(context, *study); } call.GetOutput().AnswerBuffer("", MimeType_PlainText); } void OrthancRestApi::RegisterResources() { 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}/statistics", GetResourceStatistics); Register("/patients/{id}/statistics", GetResourceStatistics); Register("/studies/{id}/statistics", GetResourceStatistics); Register("/series/{id}/statistics", GetResourceStatistics); Register("/patients/{id}/shared-tags", GetSharedTags); Register("/series/{id}/shared-tags", GetSharedTags); Register("/studies/{id}/shared-tags", GetSharedTags); Register("/instances/{id}/module", GetModule<ResourceType_Instance, DicomModule_Instance>); Register("/patients/{id}/module", GetModule<ResourceType_Patient, DicomModule_Patient>); Register("/series/{id}/module", GetModule<ResourceType_Series, DicomModule_Series>); Register("/studies/{id}/module", GetModule<ResourceType_Study, DicomModule_Study>); Register("/studies/{id}/module-patient", GetModule<ResourceType_Study, DicomModule_Patient>); Register("/instances/{id}/file", GetInstanceFile); Register("/instances/{id}/export", ExportInstanceFile); Register("/instances/{id}/tags", GetInstanceTagsBis); Register("/instances/{id}/simplified-tags", GetInstanceTags<DicomToJsonFormat_Human>); Register("/instances/{id}/frames", ListFrames); Register("/instances/{id}/frames/{frame}/preview", GetImage<ImageExtractionMode_Preview>); Register("/instances/{id}/frames/{frame}/rendered", GetRenderedFrame); Register("/instances/{id}/frames/{frame}/image-uint8", GetImage<ImageExtractionMode_UInt8>); Register("/instances/{id}/frames/{frame}/image-uint16", GetImage<ImageExtractionMode_UInt16>); Register("/instances/{id}/frames/{frame}/image-int16", GetImage<ImageExtractionMode_Int16>); Register("/instances/{id}/frames/{frame}/matlab", GetMatlabImage); Register("/instances/{id}/frames/{frame}/raw", GetRawFrame<false>); Register("/instances/{id}/frames/{frame}/raw.gz", GetRawFrame<true>); Register("/instances/{id}/pdf", ExtractPdf); Register("/instances/{id}/preview", GetImage<ImageExtractionMode_Preview>); Register("/instances/{id}/rendered", GetRenderedFrame); Register("/instances/{id}/image-uint8", GetImage<ImageExtractionMode_UInt8>); Register("/instances/{id}/image-uint16", GetImage<ImageExtractionMode_UInt16>); Register("/instances/{id}/image-int16", GetImage<ImageExtractionMode_Int16>); Register("/instances/{id}/matlab", GetMatlabImage); Register("/instances/{id}/header", GetInstanceHeader); Register("/patients/{id}/protected", IsProtectedPatient); Register("/patients/{id}/protected", SetPatientProtection); Register("/{resourceType}/{id}/metadata", ListMetadata); Register("/{resourceType}/{id}/metadata/{name}", DeleteMetadata); Register("/{resourceType}/{id}/metadata/{name}", GetMetadata); Register("/{resourceType}/{id}/metadata/{name}", SetMetadata); Register("/{resourceType}/{id}/attachments", ListAttachments); Register("/{resourceType}/{id}/attachments/{name}", DeleteAttachment); Register("/{resourceType}/{id}/attachments/{name}", GetAttachmentOperations); Register("/{resourceType}/{id}/attachments/{name}", UploadAttachment); Register("/{resourceType}/{id}/attachments/{name}/compress", ChangeAttachmentCompression<CompressionType_ZlibWithSize>); Register("/{resourceType}/{id}/attachments/{name}/compressed-data", GetAttachmentData<0>); Register("/{resourceType}/{id}/attachments/{name}/compressed-md5", GetAttachmentCompressedMD5); Register("/{resourceType}/{id}/attachments/{name}/compressed-size", GetAttachmentCompressedSize); Register("/{resourceType}/{id}/attachments/{name}/data", GetAttachmentData<1>); Register("/{resourceType}/{id}/attachments/{name}/is-compressed", IsAttachmentCompressed); Register("/{resourceType}/{id}/attachments/{name}/md5", GetAttachmentMD5); Register("/{resourceType}/{id}/attachments/{name}/size", GetAttachmentSize); Register("/{resourceType}/{id}/attachments/{name}/uncompress", ChangeAttachmentCompression<CompressionType_None>); Register("/{resourceType}/{id}/attachments/{name}/verify-md5", VerifyAttachment); Register("/tools/invalidate-tags", InvalidateTags); Register("/tools/lookup", Lookup); Register("/tools/find", Find); Register("/patients/{id}/studies", GetChildResources<ResourceType_Patient, ResourceType_Study>); Register("/patients/{id}/series", GetChildResources<ResourceType_Patient, ResourceType_Series>); Register("/patients/{id}/instances", GetChildResources<ResourceType_Patient, ResourceType_Instance>); Register("/studies/{id}/series", GetChildResources<ResourceType_Study, ResourceType_Series>); Register("/studies/{id}/instances", GetChildResources<ResourceType_Study, ResourceType_Instance>); Register("/series/{id}/instances", GetChildResources<ResourceType_Series, ResourceType_Instance>); Register("/studies/{id}/patient", GetParentResource<ResourceType_Study, ResourceType_Patient>); Register("/series/{id}/patient", GetParentResource<ResourceType_Series, ResourceType_Patient>); Register("/series/{id}/study", GetParentResource<ResourceType_Series, ResourceType_Study>); Register("/instances/{id}/patient", GetParentResource<ResourceType_Instance, ResourceType_Patient>); Register("/instances/{id}/study", GetParentResource<ResourceType_Instance, ResourceType_Study>); Register("/instances/{id}/series", GetParentResource<ResourceType_Instance, ResourceType_Series>); Register("/patients/{id}/instances-tags", GetChildInstancesTags); Register("/studies/{id}/instances-tags", GetChildInstancesTags); Register("/series/{id}/instances-tags", GetChildInstancesTags); Register("/instances/{id}/content/*", GetRawContent); Register("/series/{id}/ordered-slices", OrderSlices); Register("/patients/{id}/reconstruct", ReconstructResource<ResourceType_Patient>); Register("/studies/{id}/reconstruct", ReconstructResource<ResourceType_Study>); Register("/series/{id}/reconstruct", ReconstructResource<ResourceType_Series>); Register("/instances/{id}/reconstruct", ReconstructResource<ResourceType_Instance>); Register("/tools/reconstruct", ReconstructAllResources); } }