Mercurial > hg > orthanc-dicomweb
changeset 364:6074e7e39b58
Support of "/studies/.../series/.../rendered"
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Sat, 14 Sep 2019 16:35:50 +0200 |
parents | 518485e4bfa4 |
children | 66a62adbd978 |
files | NEWS Plugin/Plugin.cpp Plugin/WadoRs.cpp Plugin/WadoRs.h Plugin/WadoRsRetrieveRendered.cpp Status.txt |
diffstat | 6 files changed, 205 insertions(+), 146 deletions(-) [+] |
line wrap: on
line diff
--- a/NEWS Wed Sep 11 18:03:18 2019 +0200 +++ b/NEWS Sat Sep 14 16:35:50 2019 +0200 @@ -5,6 +5,7 @@ ------------ * Support of "window", "viewport" and "quality" parameters in "Retrieve Rendered Transaction" +* Support of "/studies/.../series/.../rendered" * QIDO-RS: Allow to query against a list of multiple values separated by commas Maintenance
--- a/Plugin/Plugin.cpp Wed Sep 11 18:03:18 2019 +0200 +++ b/Plugin/Plugin.cpp Sat Sep 14 16:35:50 2019 +0200 @@ -530,6 +530,7 @@ OrthancPlugins::RegisterRestCallback<GetClientInformation>(root + "info", true); + OrthancPlugins::RegisterRestCallback<RetrieveSeriesRendered>(root + "studies/([^/]*)/series/([^/]*)/rendered", true); OrthancPlugins::RegisterRestCallback<RetrieveInstanceRendered>(root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/rendered", true); OrthancPlugins::RegisterRestCallback<RetrieveFrameRendered>(root + "studies/([^/]*)/series/([^/]*)/instances/([^/]*)/frames/([^/]*)/rendered", true);
--- a/Plugin/WadoRs.cpp Wed Sep 11 18:03:18 2019 +0200 +++ b/Plugin/WadoRs.cpp Sat Sep 14 16:35:50 2019 +0200 @@ -520,9 +520,9 @@ } -static bool LocateSeries(OrthancPluginRestOutput* output, - std::string& publicId, - const OrthancPluginHttpRequest* request) +bool LocateSeries(OrthancPluginRestOutput* output, + std::string& publicId, + const OrthancPluginHttpRequest* request) { OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
--- a/Plugin/WadoRs.h Wed Sep 11 18:03:18 2019 +0200 +++ b/Plugin/WadoRs.h Sat Sep 14 16:35:50 2019 +0200 @@ -24,6 +24,10 @@ #include "Configuration.h" +bool LocateSeries(OrthancPluginRestOutput* output, + std::string& uri, + const OrthancPluginHttpRequest* request); + bool LocateInstance(OrthancPluginRestOutput* output, std::string& uri, const OrthancPluginHttpRequest* request); @@ -67,3 +71,7 @@ void RetrieveFrameRendered(OrthancPluginRestOutput* output, const char* url, const OrthancPluginHttpRequest* request); + +void RetrieveSeriesRendered(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request);
--- a/Plugin/WadoRsRetrieveRendered.cpp Wed Sep 11 18:03:18 2019 +0200 +++ b/Plugin/WadoRsRetrieveRendered.cpp Sat Sep 14 16:35:50 2019 +0200 @@ -685,165 +685,174 @@ } + static void AnswerFrameRendered(OrthancPluginRestOutput* output, + std::string instanceId, int frame, const OrthancPluginHttpRequest* request) { static const char* const RESCALE_INTERCEPT = "0028,1052"; static const char* const RESCALE_SLOPE = "0028,1053"; - OrthancPluginContext* context = OrthancPlugins::GetGlobalContext(); + Orthanc::MimeType mime = Orthanc::MimeType_Jpeg; // This is the default in DICOMweb + + for (uint32_t i = 0; i < request->headersCount; i++) + { + if (boost::iequals(request->headersKeys[i], "Accept") && + !boost::iequals(request->headersValues[i], "*/*")) + { + try + { + // TODO - Support conversion to GIF + + mime = Orthanc::StringToMimeType(request->headersValues[i]); + if (mime != Orthanc::MimeType_Png && + mime != Orthanc::MimeType_Jpeg) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + catch (Orthanc::OrthancException&) + { + LOG(ERROR) << "Unsupported MIME type in WADO-RS rendered frame: " + << request->headersValues[i]; + throw; + } + } + } + + RenderingParameters parameters(request); + + OrthancPlugins::MemoryBuffer buffer; + bool badFrameError = false; + + if (parameters.HasCustomization()) + { + if (frame <= 0) + { + badFrameError = true; + } + else + { + buffer.GetDicomInstance(instanceId); + + Json::Value tags; + buffer.DicomToJson(tags, OrthancPluginDicomToJsonFormat_Short, OrthancPluginDicomToJsonFlags_None, 255); + + if (tags.isMember(RESCALE_SLOPE) && + tags[RESCALE_SLOPE].type() == Json::stringValue) + { + try + { + parameters.SetRescaleSlope + (boost::lexical_cast<float> + (Orthanc::Toolbox::StripSpaces(tags[RESCALE_SLOPE].asString()))); + } + catch (boost::bad_lexical_cast&) + { + } + } + + if (tags.isMember(RESCALE_INTERCEPT) && + tags[RESCALE_INTERCEPT].type() == Json::stringValue) + { + try + { + parameters.SetRescaleIntercept + (boost::lexical_cast<float> + (Orthanc::Toolbox::StripSpaces(tags[RESCALE_INTERCEPT].asString()))); + } + catch (boost::bad_lexical_cast&) + { + } + } + OrthancPlugins::OrthancImage dicom; + dicom.DecodeDicomImage(buffer.GetData(), buffer.GetSize(), static_cast<unsigned int>(frame - 1)); + + Orthanc::PixelFormat targetFormat; + OrthancPluginPixelFormat sdkFormat; + if (dicom.GetPixelFormat() == OrthancPluginPixelFormat_RGB24) + { + targetFormat = Orthanc::PixelFormat_RGB24; + sdkFormat = OrthancPluginPixelFormat_RGB24; + } + else + { + targetFormat = Orthanc::PixelFormat_Grayscale8; + sdkFormat = OrthancPluginPixelFormat_Grayscale8; + } + + Orthanc::ImageAccessor source; + source.AssignReadOnly(Convert(dicom.GetPixelFormat()), + dicom.GetWidth(), dicom.GetHeight(), dicom.GetPitch(), dicom.GetBuffer()); + + Orthanc::Image target(targetFormat, parameters.GetTargetWidth(source.GetWidth()), + parameters.GetTargetHeight(source.GetHeight()), false); + + ApplyRendering(target, source, parameters); + + switch (mime) + { + case Orthanc::MimeType_Png: + OrthancPluginCompressAndAnswerPngImage(OrthancPlugins::GetGlobalContext(), output, sdkFormat, + target.GetWidth(), target.GetHeight(), target.GetPitch(), target.GetBuffer()); + break; + + case Orthanc::MimeType_Jpeg: + OrthancPluginCompressAndAnswerJpegImage(OrthancPlugins::GetGlobalContext(), output, sdkFormat, + target.GetWidth(), target.GetHeight(), target.GetPitch(), target.GetBuffer(), + parameters.GetQuality()); + break; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); + } + } + } + else + { + // No customization of the rendering. Return the default + // preview of Orthanc. + std::map<std::string, std::string> headers; + headers["Accept"] = Orthanc::EnumerationToString(mime); + + // In DICOMweb, the "frame" parameter is in the range [1..N], + // whereas Orthanc uses range [0..N-1], hence the "-1" below. + if (buffer.RestApiGet("/instances/" + instanceId + "/frames/" + + boost::lexical_cast<std::string>(frame - 1) + "/preview", headers, false)) + { + OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, buffer.GetData(), + buffer.GetSize(), Orthanc::EnumerationToString(mime)); + } + else + { + badFrameError = true; + } + } + + if (badFrameError) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, + "Inexistent frame index in this image: " + boost::lexical_cast<std::string>(frame)); + } +} + + +static void AnswerFrameRendered(OrthancPluginRestOutput* output, + int frame, + const OrthancPluginHttpRequest* request) +{ if (request->method != OrthancPluginHttpMethod_Get) { - OrthancPluginSendMethodNotAllowed(context, output, "GET"); + OrthancPluginSendMethodNotAllowed(OrthancPlugins::GetGlobalContext(), output, "GET"); } else { std::string instanceId; if (LocateInstance(output, instanceId, request)) { - Orthanc::MimeType mime = Orthanc::MimeType_Jpeg; // This is the default in DICOMweb - - for (uint32_t i = 0; i < request->headersCount; i++) - { - if (boost::iequals(request->headersKeys[i], "Accept") && - !boost::iequals(request->headersValues[i], "*/*")) - { - try - { - // TODO - Support conversion to GIF - - mime = Orthanc::StringToMimeType(request->headersValues[i]); - if (mime != Orthanc::MimeType_Png && - mime != Orthanc::MimeType_Jpeg) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - } - catch (Orthanc::OrthancException&) - { - LOG(ERROR) << "Unsupported MIME type in WADO-RS rendered frame: " << request->headersValues[i]; - throw; - } - } - } - - RenderingParameters parameters(request); - - OrthancPlugins::MemoryBuffer buffer; - bool badFrameError = false; - - if (parameters.HasCustomization()) - { - if (frame <= 0) - { - badFrameError = true; - } - else - { - buffer.GetDicomInstance(instanceId); - - Json::Value tags; - buffer.DicomToJson(tags, OrthancPluginDicomToJsonFormat_Short, OrthancPluginDicomToJsonFlags_None, 255); - - if (tags.isMember(RESCALE_SLOPE) && - tags[RESCALE_SLOPE].type() == Json::stringValue) - { - try - { - parameters.SetRescaleSlope - (boost::lexical_cast<float> - (Orthanc::Toolbox::StripSpaces(tags[RESCALE_SLOPE].asString()))); - } - catch (boost::bad_lexical_cast&) - { - } - } - - if (tags.isMember(RESCALE_INTERCEPT) && - tags[RESCALE_INTERCEPT].type() == Json::stringValue) - { - try - { - parameters.SetRescaleIntercept - (boost::lexical_cast<float> - (Orthanc::Toolbox::StripSpaces(tags[RESCALE_INTERCEPT].asString()))); - } - catch (boost::bad_lexical_cast&) - { - } - } - - OrthancPlugins::OrthancImage dicom; - dicom.DecodeDicomImage(buffer.GetData(), buffer.GetSize(), static_cast<unsigned int>(frame - 1)); - - Orthanc::PixelFormat targetFormat; - OrthancPluginPixelFormat sdkFormat; - if (dicom.GetPixelFormat() == OrthancPluginPixelFormat_RGB24) - { - targetFormat = Orthanc::PixelFormat_RGB24; - sdkFormat = OrthancPluginPixelFormat_RGB24; - } - else - { - targetFormat = Orthanc::PixelFormat_Grayscale8; - sdkFormat = OrthancPluginPixelFormat_Grayscale8; - } - - Orthanc::ImageAccessor source; - source.AssignReadOnly(Convert(dicom.GetPixelFormat()), - dicom.GetWidth(), dicom.GetHeight(), dicom.GetPitch(), dicom.GetBuffer()); - - Orthanc::Image target(targetFormat, parameters.GetTargetWidth(source.GetWidth()), - parameters.GetTargetHeight(source.GetHeight()), false); - - ApplyRendering(target, source, parameters); - - switch (mime) - { - case Orthanc::MimeType_Png: - OrthancPluginCompressAndAnswerPngImage(OrthancPlugins::GetGlobalContext(), output, sdkFormat, - target.GetWidth(), target.GetHeight(), target.GetPitch(), target.GetBuffer()); - break; - - case Orthanc::MimeType_Jpeg: - OrthancPluginCompressAndAnswerJpegImage(OrthancPlugins::GetGlobalContext(), output, sdkFormat, - target.GetWidth(), target.GetHeight(), target.GetPitch(), target.GetBuffer(), - parameters.GetQuality()); - break; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented); - } - } - } - else - { - // No customization of the rendering. Return the default - // preview of Orthanc. - std::map<std::string, std::string> headers; - headers["Accept"] = Orthanc::EnumerationToString(mime); - - // In DICOMweb, the "frame" parameter is in the range [1..N], - // whereas Orthanc uses range [0..N-1], hence the "-1" below. - if (buffer.RestApiGet("/instances/" + instanceId + "/frames/" + - boost::lexical_cast<std::string>(frame - 1) + "/preview", headers, false)) - { - OrthancPluginAnswerBuffer(context, output, buffer.GetData(), - buffer.GetSize(), Orthanc::EnumerationToString(mime)); - } - else - { - badFrameError = true; - } - } - - if (badFrameError) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange, - "Inexistent frame index in this image: " + boost::lexical_cast<std::string>(frame)); - } + AnswerFrameRendered(output, instanceId, frame, request); } else { @@ -857,6 +866,7 @@ const char* url, const OrthancPluginHttpRequest* request) { + assert(request->groupsCount == 3); AnswerFrameRendered(output, 1 /* first frame */, request); } @@ -870,3 +880,41 @@ AnswerFrameRendered(output, boost::lexical_cast<int>(frame), request); } + + +void RetrieveSeriesRendered(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + static const char* const INSTANCES = "Instances"; + + assert(request->groupsCount == 2); + + if (request->method != OrthancPluginHttpMethod_Get) + { + OrthancPluginSendMethodNotAllowed(OrthancPlugins::GetGlobalContext(), output, "GET"); + } + else + { + std::string seriesId; + if (LocateSeries(output, seriesId, request)) + { + Json::Value series; + if (OrthancPlugins::RestApiGet(series, "/series/" + seriesId, false) && + series.type() == Json::objectValue && + series.isMember(INSTANCES) && + series[INSTANCES].type() == Json::arrayValue && + series[INSTANCES].size() > 0) + { + Json::Value::ArrayIndex i = series[INSTANCES].size() / 2; + if (series[INSTANCES][i].type() == Json::stringValue) + { + std::string instanceId = series[INSTANCES][i].asString(); + AnswerFrameRendered(output, instanceId, 1 /* first frame */, request); + } + } + } + + throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem, "Inexistent series"); + } +}
--- a/Status.txt Wed Sep 11 18:03:18 2019 +0200 +++ b/Status.txt Sat Sep 14 16:35:50 2019 +0200 @@ -103,6 +103,7 @@ * GIF output * The following "Retrieve Rendered Query Parameters" (table 6.5.8-2): annotation, charset, iccprofile +* URI "/studies/.../rendered" (only available for series, instances and frames)