Mercurial > hg > orthanc
diff OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp @ 4860:3e9a76464e8a openssl-3.x
integration mainline->openssl-3.x
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 24 Dec 2021 16:52:51 +0100 |
parents | 2e71a08eea15 8b51d65584f0 |
children | 6eff25f70121 |
line wrap: on
line diff
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Thu Nov 25 19:02:38 2021 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Fri Dec 24 16:52:51 2021 +0100 @@ -31,6 +31,7 @@ #include "../../../OrthancFramework/Sources/HttpServer/HttpContentNegociation.h" #include "../../../OrthancFramework/Sources/Images/Image.h" #include "../../../OrthancFramework/Sources/Images/ImageProcessing.h" +#include "../../../OrthancFramework/Sources/Images/NumpyWriter.h" #include "../../../OrthancFramework/Sources/Logging.h" #include "../../../OrthancFramework/Sources/MultiThreading/Semaphore.h" #include "../../../OrthancFramework/Sources/SerializationToolbox.h" @@ -1069,9 +1070,9 @@ windowWidth = 1; } - if (std::abs(rescaleSlope) <= 0.1) + if (std::abs(rescaleSlope) <= 0.0001) { - rescaleSlope = 0.1; + rescaleSlope = 0.0001; } const double scaling = 255.0 * rescaleSlope / windowWidth; @@ -1130,6 +1131,247 @@ } + static void DocumentSharedNumpy(RestApiGetCall& call) + { + call.GetDocumentation() + .SetUriArgument("id", "Orthanc identifier of the DICOM resource of interest") + .SetHttpGetArgument("compress", RestApiCallDocumentation::Type_Boolean, "Compress the file as `.npz`", false) + .SetHttpGetArgument("rescale", RestApiCallDocumentation::Type_Boolean, + "On grayscale images, apply the rescaling and return floating-point values", false) + .AddAnswerType(MimeType_PlainText, "Numpy file: https://numpy.org/devdocs/reference/generated/numpy.lib.format.html"); + } + + + namespace + { + class NumpyVisitor : public boost::noncopyable + { + private: + bool rescale_; + unsigned int depth_; + unsigned int currentDepth_; + unsigned int width_; + unsigned int height_; + PixelFormat format_; + ChunkedBuffer buffer_; + + public: + NumpyVisitor(unsigned int depth /* can be zero if 2D frame */, + bool rescale) : + rescale_(rescale), + depth_(depth), + currentDepth_(0), + width_(0), // dummy initialization + height_(0), // dummy initialization + format_(PixelFormat_Grayscale8) // dummy initialization + { + } + + void WriteFrame(const ParsedDicomFile& dicom, + unsigned int frame) + { + std::unique_ptr<ImageAccessor> decoded(dicom.DecodeFrame(frame)); + + if (decoded.get() == NULL) + { + throw OrthancException(ErrorCode_NotImplemented, "Cannot decode DICOM instance"); + } + + if (currentDepth_ == 0) + { + width_ = decoded->GetWidth(); + height_ = decoded->GetHeight(); + format_ = decoded->GetFormat(); + } + else if (width_ != decoded->GetWidth() || + height_ != decoded->GetHeight()) + { + throw OrthancException(ErrorCode_IncompatibleImageSize, "The size of the frames varies across the instance(s)"); + } + else if (format_ != decoded->GetFormat()) + { + throw OrthancException(ErrorCode_IncompatibleImageFormat, "The pixel format of the frames varies across the instance(s)"); + } + + if (rescale_ && + decoded->GetFormat() != PixelFormat_RGB24) + { + if (currentDepth_ == 0) + { + NumpyWriter::WriteHeader(buffer_, depth_, width_, height_, PixelFormat_Float32); + } + + double rescaleIntercept, rescaleSlope; + dicom.GetRescale(rescaleIntercept, rescaleSlope, frame); + + Image converted(PixelFormat_Float32, decoded->GetWidth(), decoded->GetHeight(), false); + ImageProcessing::Convert(converted, *decoded); + ImageProcessing::ShiftScale2(converted, static_cast<float>(rescaleIntercept), static_cast<float>(rescaleSlope), false); + + NumpyWriter::WritePixels(buffer_, converted); + } + else + { + if (currentDepth_ == 0) + { + NumpyWriter::WriteHeader(buffer_, depth_, width_, height_, format_); + } + + NumpyWriter::WritePixels(buffer_, *decoded); + } + + currentDepth_ ++; + } + + void Answer(RestApiOutput& output, + bool compress) + { + if ((depth_ == 0 && currentDepth_ != 1) || + (depth_ != 0 && currentDepth_ != depth_)) + { + throw OrthancException(ErrorCode_BadSequenceOfCalls); + } + else + { + std::string answer; + NumpyWriter::Finalize(answer, buffer_, compress); + output.AnswerBuffer(answer, MimeType_Binary); + } + } + }; + } + + + static void GetNumpyFrame(RestApiGetCall& call) + { + if (call.IsDocumentation()) + { + DocumentSharedNumpy(call); + call.GetDocumentation() + .SetTag("Instances") + .SetSummary("Decode frame for numpy") + .SetDescription("Decode one frame of interest from the given DICOM instance, for use with numpy in Python. " + "The numpy array has 3 dimensions: (height, width, color channel).") + .SetUriArgument("frame", RestApiCallDocumentation::Type_Number, "Index of the frame (starts at `0`)"); + } + else + { + const std::string instanceId = call.GetUriComponent("id", ""); + const bool compress = call.GetBooleanArgument("compress", false); + const bool rescale = call.GetBooleanArgument("rescale", true); + + uint32_t frame; + if (!SerializationToolbox::ParseUnsignedInteger32(frame, call.GetUriComponent("frame", "0"))) + { + throw OrthancException(ErrorCode_ParameterOutOfRange, "Expected an unsigned integer for the \"frame\" argument"); + } + + NumpyVisitor visitor(0 /* no depth, 2D frame */, rescale); + + { + Semaphore::Locker throttling(throttlingSemaphore_); + ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), instanceId); + + visitor.WriteFrame(locker.GetDicom(), frame); + } + + visitor.Answer(call.GetOutput(), compress); + } + } + + + static void GetNumpyInstance(RestApiGetCall& call) + { + if (call.IsDocumentation()) + { + DocumentSharedNumpy(call); + call.GetDocumentation() + .SetTag("Instances") + .SetSummary("Decode instance for numpy") + .SetDescription("Decode the given DICOM instance, for use with numpy in Python. " + "The numpy array has 4 dimensions: (frame, height, width, color channel)."); + } + else + { + const std::string instanceId = call.GetUriComponent("id", ""); + const bool compress = call.GetBooleanArgument("compress", false); + const bool rescale = call.GetBooleanArgument("rescale", true); + + { + Semaphore::Locker throttling(throttlingSemaphore_); + ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), instanceId); + + const unsigned int depth = locker.GetDicom().GetFramesCount(); + if (depth == 0) + { + throw OrthancException(ErrorCode_BadFileFormat, "Empty DICOM instance"); + } + + NumpyVisitor visitor(depth, rescale); + + for (unsigned int frame = 0; frame < depth; frame++) + { + visitor.WriteFrame(locker.GetDicom(), frame); + } + + visitor.Answer(call.GetOutput(), compress); + } + } + } + + + static void GetNumpySeries(RestApiGetCall& call) + { + if (call.IsDocumentation()) + { + DocumentSharedNumpy(call); + call.GetDocumentation() + .SetTag("Series") + .SetSummary("Decode series for numpy") + .SetDescription("Decode the given DICOM series, for use with numpy in Python. " + "The numpy array has 4 dimensions: (frame, height, width, color channel)."); + } + else + { + const std::string seriesId = call.GetUriComponent("id", ""); + const bool compress = call.GetBooleanArgument("compress", false); + const bool rescale = call.GetBooleanArgument("rescale", true); + + Semaphore::Locker throttling(throttlingSemaphore_); + + ServerIndex& index = OrthancRestApi::GetIndex(call); + SliceOrdering ordering(index, seriesId); + + unsigned int depth = 0; + for (size_t i = 0; i < ordering.GetInstancesCount(); i++) + { + depth += ordering.GetFramesCount(i); + } + + ServerContext& context = OrthancRestApi::GetContext(call); + + NumpyVisitor visitor(depth, rescale); + + for (size_t i = 0; i < ordering.GetInstancesCount(); i++) + { + const std::string& instanceId = ordering.GetInstanceId(i); + unsigned int framesCount = ordering.GetFramesCount(i); + + { + ServerContext::DicomCacheLocker locker(context, instanceId); + + for (unsigned int frame = 0; frame < framesCount; frame++) + { + visitor.WriteFrame(locker.GetDicom(), frame); + } + } + } + + visitor.Answer(call.GetOutput(), compress); + } + } + + static void GetMatlabImage(RestApiGetCall& call) { if (call.IsDocumentation()) @@ -1677,6 +1919,8 @@ .SetSummary("List attachments") .SetDescription("Get the list of attachments that are associated with the given " + r) .SetUriArgument("id", "Orthanc identifier of the " + r + " of interest") + .SetHttpGetArgument("full", RestApiCallDocumentation::Type_String, + "If present, retrieve the attachments list and their numerical ids", false) .AddAnswerType(MimeType_Json, "JSON array containing the names of the attachments") .SetHttpGetSample(GetDocumentationSampleResource(t) + "/attachments", true); return; @@ -1687,12 +1931,28 @@ std::set<FileContentType> attachments; OrthancRestApi::GetIndex(call).ListAvailableAttachments(attachments, publicId, StringToResourceType(resourceType.c_str())); - Json::Value result = Json::arrayValue; - - for (std::set<FileContentType>::const_iterator - it = attachments.begin(); it != attachments.end(); ++it) + Json::Value result; + + if (call.HasArgument("full")) { - result.append(EnumerationToString(*it)); + result = Json::objectValue; + + for (std::set<FileContentType>::const_iterator + it = attachments.begin(); it != attachments.end(); ++it) + { + std::string key = EnumerationToString(*it); + result[key] = static_cast<uint16_t>(*it); + } + } + else + { + result = Json::arrayValue; + + for (std::set<FileContentType>::const_iterator + it = attachments.begin(); it != attachments.end(); ++it) + { + result.append(EnumerationToString(*it)); + } } call.GetOutput().AnswerJson(result); @@ -3384,6 +3644,7 @@ 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}/frames/{frame}/numpy", GetNumpyFrame); // New in Orthanc 1.9.8 Register("/instances/{id}/pdf", ExtractPdf); Register("/instances/{id}/preview", GetImage<ImageExtractionMode_Preview>); Register("/instances/{id}/rendered", GetRenderedFrame); @@ -3392,6 +3653,7 @@ Register("/instances/{id}/image-int16", GetImage<ImageExtractionMode_Int16>); Register("/instances/{id}/matlab", GetMatlabImage); Register("/instances/{id}/header", GetInstanceHeader); + Register("/instances/{id}/numpy", GetNumpyInstance); // New in Orthanc 1.9.8 Register("/patients/{id}/protected", IsProtectedPatient); Register("/patients/{id}/protected", SetPatientProtection); @@ -3450,6 +3712,7 @@ Register("/instances/{id}/content/*", GetRawContent); Register("/series/{id}/ordered-slices", OrderSlices); + Register("/series/{id}/numpy", GetNumpySeries); // New in Orthanc 1.9.8 Register("/patients/{id}/reconstruct", ReconstructResource<ResourceType_Patient>); Register("/studies/{id}/reconstruct", ReconstructResource<ResourceType_Study>);