# HG changeset patch # User Sebastien Jodogne # Date 1449737822 -3600 # Node ID 499df60ad0e248365504388fbd3525ce0087e371 # Parent 5fdcb04afb0de3d386a06cedbc9a7b368f949498 reorganization diff -r 5fdcb04afb0d -r 499df60ad0e2 CMakeLists.txt --- a/CMakeLists.txt Thu Dec 10 09:53:46 2015 +0100 +++ b/CMakeLists.txt Thu Dec 10 09:57:02 2015 +0100 @@ -126,6 +126,7 @@ ${CMAKE_SOURCE_DIR}/Plugin/StowRs.cpp ${CMAKE_SOURCE_DIR}/Plugin/Wado.cpp ${CMAKE_SOURCE_DIR}/Plugin/WadoRs.cpp + ${CMAKE_SOURCE_DIR}/Plugin/WadoRsRetrieveFrames.cpp ${AUTOGENERATED_SOURCES} ) diff -r 5fdcb04afb0d -r 499df60ad0e2 Plugin/WadoRs.cpp --- a/Plugin/WadoRs.cpp Thu Dec 10 09:53:46 2015 +0100 +++ b/Plugin/WadoRs.cpp Thu Dec 10 09:57:02 2015 +0100 @@ -329,9 +329,9 @@ } -static bool LocateInstance(OrthancPluginRestOutput* output, - std::string& uri, - const OrthancPluginHttpRequest* request) +bool LocateInstance(OrthancPluginRestOutput* output, + std::string& uri, + const OrthancPluginHttpRequest* request) { if (request->method != OrthancPluginHttpMethod_Get) { @@ -622,488 +622,3 @@ return OrthancPluginErrorCode_Success; } - - - - - -#include -#include -#include -#include -#include - - -static void TokenizeAndNormalize(std::vector& tokens, - const std::string& source, - char separator) -{ - Orthanc::Toolbox::TokenizeString(tokens, source, separator); - - for (size_t i = 0; i < tokens.size(); i++) - { - tokens[i] = Orthanc::Toolbox::StripSpaces(tokens[i]); - Orthanc::Toolbox::ToLowerCase(tokens[i]); - } -} - - - -static gdcm::TransferSyntax ParseTransferSyntax(const OrthancPluginHttpRequest* request) -{ - for (uint32_t i = 0; i < request->headersCount; i++) - { - std::string key(request->headersKeys[i]); - Orthanc::Toolbox::ToLowerCase(key); - - if (key == "accept") - { - std::vector tokens; - TokenizeAndNormalize(tokens, request->headersValues[i], ';'); - - if (tokens.size() == 0 || - tokens[0] == "*/*") - { - return gdcm::TransferSyntax::ImplicitVRLittleEndian; - } - - if (tokens[0] != "multipart/related") - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - std::string type("application/octet-stream"); - std::string transferSyntax; - - for (size_t j = 1; j < tokens.size(); j++) - { - std::vector parsed; - TokenizeAndNormalize(parsed, tokens[j], '='); - - if (parsed.size() != 2) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest); - } - - if (parsed[0] == "type") - { - type = parsed[1]; - } - - if (parsed[0] == "transfer-syntax") - { - transferSyntax = parsed[1]; - } - } - - if (type == "application/octet-stream") - { - if (transferSyntax.empty()) - { - return gdcm::TransferSyntax(gdcm::TransferSyntax::ImplicitVRLittleEndian); - } - else - { - std::string s = ("DICOMweb RetrieveFrames: Cannot specify a transfer syntax (" + - transferSyntax + ") for default Little Endian uncompressed pixel data"); - OrthancPluginLogError(context_, s.c_str()); - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest); - } - } - else - { - // http://dicom.nema.org/medical/dicom/current/output/html/part18.html#table_6.5-1 - if (type == "image/dicom+jpeg" && transferSyntax == "1.2.840.10008.1.2.4.50") - { - return gdcm::TransferSyntax::JPEGBaselineProcess1; - } - else if (type == "image/dicom+jpeg" && transferSyntax == "1.2.840.10008.1.2.4.51") - { - return gdcm::TransferSyntax::JPEGExtendedProcess2_4; - } - else if (type == "image/dicom+jpeg" && transferSyntax == "1.2.840.10008.1.2.4.57") - { - return gdcm::TransferSyntax::JPEGLosslessProcess14; - } - else if (type == "image/dicom+jpeg" && (transferSyntax.empty() || - transferSyntax == "1.2.840.10008.1.2.4.70")) - { - return gdcm::TransferSyntax::JPEGLosslessProcess14_1; - } - else if (type == "image/dicom+rle" && (transferSyntax.empty() || - transferSyntax == "1.2.840.10008.1.2.5")) - { - return gdcm::TransferSyntax::RLELossless; - } - else if (type == "image/dicom+jpeg-ls" && (transferSyntax.empty() || - transferSyntax == "1.2.840.10008.1.2.4.80")) - { - return gdcm::TransferSyntax::JPEGLSLossless; - } - else if (type == "image/dicom+jpeg-ls" && transferSyntax == "1.2.840.10008.1.2.4.81") - { - return gdcm::TransferSyntax::JPEGLSNearLossless; - } - else if (type == "image/dicom+jp2" && (transferSyntax.empty() || - transferSyntax == "1.2.840.10008.1.2.4.90")) - { - return gdcm::TransferSyntax::JPEG2000Lossless; - } - else if (type == "image/dicom+jp2" && transferSyntax == "1.2.840.10008.1.2.4.91") - { - return gdcm::TransferSyntax::JPEG2000; - } - else if (type == "image/dicom+jpx" && (transferSyntax.empty() || - transferSyntax == "1.2.840.10008.1.2.4.92")) - { - return gdcm::TransferSyntax::JPEG2000Part2Lossless; - } - else if (type == "image/dicom+jpx" && transferSyntax == "1.2.840.10008.1.2.4.93") - { - return gdcm::TransferSyntax::JPEG2000Part2; - } - else - { - std::string s = ("DICOMweb RetrieveFrames: Transfer syntax \"" + - transferSyntax + "\" is incompatible with media type \"" + type + "\""); - OrthancPluginLogError(context_, s.c_str()); - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest); - } - } - } - } - - // By default, DICOMweb expectes Little Endian uncompressed pixel data - return gdcm::TransferSyntax::ImplicitVRLittleEndian; -} - - -static void ParseFrameList(std::list& frames, - const OrthancPluginHttpRequest* request) -{ - frames.clear(); - - if (request->groupsCount <= 3 || - request->groups[3] == NULL) - { - return; - } - - std::string source(request->groups[3]); - Orthanc::Toolbox::ToLowerCase(source); - boost::replace_all(source, "%2c", ","); - - std::vector tokens; - Orthanc::Toolbox::TokenizeString(tokens, source, ','); - - for (size_t i = 0; i < tokens.size(); i++) - { - int frame = boost::lexical_cast(tokens[i]); - if (frame <= 0) - { - std::string s = "Invalid frame number (must be > 0): " + tokens[i]; - OrthancPluginLogError(context_, s.c_str()); - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - - frames.push_back(static_cast(frame - 1)); - } -} - - - -static const char* GetMimeType(const gdcm::TransferSyntax& syntax) -{ - switch (syntax) - { - case gdcm::TransferSyntax::ImplicitVRLittleEndian: - return "application/octet-stream"; - - case gdcm::TransferSyntax::JPEGBaselineProcess1: - return "image/dicom+jpeg; transfer-syntax=1.2.840.10008.1.2.4.50"; - - case gdcm::TransferSyntax::JPEGExtendedProcess2_4: - return "image/dicom+jpeg; transfer-syntax=1.2.840.10008.1.2.4.51"; - - case gdcm::TransferSyntax::JPEGLosslessProcess14: - return "image/dicom+jpeg; transfer-syntax=1.2.840.10008.1.2.4.57"; - - case gdcm::TransferSyntax::JPEGLosslessProcess14_1: - return "image/dicom+jpeg; transferSyntax=1.2.840.10008.1.2.4.70"; - - case gdcm::TransferSyntax::RLELossless: - return "image/dicom+rle; transferSyntax=1.2.840.10008.1.2.5"; - - case gdcm::TransferSyntax::JPEGLSLossless: - return "image/dicom+jpeg-ls; transferSyntax=1.2.840.10008.1.2.4.80"; - - case gdcm::TransferSyntax::JPEGLSNearLossless: - return "image/dicom+jpeg-ls; transfer-syntax=1.2.840.10008.1.2.4.81"; - - case gdcm::TransferSyntax::JPEG2000Lossless: - return "image/dicom+jp2; transferSyntax=1.2.840.10008.1.2.4.90"; - - case gdcm::TransferSyntax::JPEG2000: - return "image/dicom+jp2; transfer-syntax=1.2.840.10008.1.2.4.91"; - - case gdcm::TransferSyntax::JPEG2000Part2Lossless: - return "image/dicom+jpx; transferSyntax=1.2.840.10008.1.2.4.92"; - - case gdcm::TransferSyntax::JPEG2000Part2: - return "image/dicom+jpx; transfer-syntax=1.2.840.10008.1.2.4.93"; - - default: - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } -} - - - -static void AnswerSingleFrame(OrthancPluginRestOutput* output, - const OrthancPluginHttpRequest* request, - const OrthancPlugins::ParsedDicomFile& dicom, - const char* frame, - size_t size, - unsigned int frameIndex) -{ - OrthancPluginErrorCode error; - -#if HAS_SEND_MULTIPART_ITEM_2 == 1 - std::string location = dicom.GetWadoUrl(request) + "frames/" + boost::lexical_cast(frameIndex + 1); - const char *keys[] = { "Content-Location" }; - const char *values[] = { location.c_str() }; - error = OrthancPluginSendMultipartItem2(context_, output, frame, size, 1, keys, values); -#else - error = OrthancPluginSendMultipartItem(context_, output, frame, size); -#endif - - if (error != OrthancPluginErrorCode_Success) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); - } -} - - - -static bool AnswerFrames(OrthancPluginRestOutput* output, - const OrthancPluginHttpRequest* request, - const OrthancPlugins::ParsedDicomFile& dicom, - const gdcm::TransferSyntax& syntax, - std::list& frames) -{ - if (!dicom.GetDataSet().FindDataElement(OrthancPlugins::DICOM_TAG_PIXEL_DATA)) - { - return OrthancPluginErrorCode_IncompatibleImageFormat; - } - - const gdcm::DataElement& pixelData = dicom.GetDataSet().GetDataElement(OrthancPlugins::DICOM_TAG_PIXEL_DATA); - const gdcm::SequenceOfFragments* fragments = pixelData.GetSequenceOfFragments(); - - if (OrthancPluginStartMultipartAnswer(context_, output, "related", GetMimeType(syntax)) != OrthancPluginErrorCode_Success) - { - return false; - } - - if (fragments == NULL) - { - // Single-fragment image - - if (pixelData.GetByteValue() == NULL) - { - OrthancPluginLogError(context_, "Image was not properly decoded"); - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - int width, height, bits; - - if (!dicom.GetIntegerTag(height, *dictionary_, OrthancPlugins::DICOM_TAG_ROWS) || - !dicom.GetIntegerTag(width, *dictionary_, OrthancPlugins::DICOM_TAG_COLUMNS) || - !dicom.GetIntegerTag(bits, *dictionary_, OrthancPlugins::DICOM_TAG_BITS_ALLOCATED)) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - size_t frameSize = height * width * bits / 8; - - if (pixelData.GetByteValue()->GetLength() % frameSize != 0) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - size_t framesCount = pixelData.GetByteValue()->GetLength() / frameSize; - - if (frames.empty()) - { - // If no frame is provided, return all the frames (this is an extension) - for (size_t i = 0; i < framesCount; i++) - { - frames.push_back(i); - } - } - - const char* buffer = pixelData.GetByteValue()->GetPointer(); - assert(sizeof(char) == 1); - - for (std::list::const_iterator - frame = frames.begin(); frame != frames.end(); ++frame) - { - if (*frame >= framesCount) - { - std::string s = ("Trying to access frame number " + boost::lexical_cast(*frame + 1) + - " of an image with " + boost::lexical_cast(framesCount) + " frames"); - OrthancPluginLogError(context_, s.c_str()); - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - else - { - const char* p = buffer + (*frame) * frameSize; - AnswerSingleFrame(output, request, dicom, p, frameSize, *frame); - } - } - } - else - { - // Multi-fragment image, we assume that each fragment corresponds to one frame - - if (frames.empty()) - { - // If no frame is provided, return all the frames (this is an extension) - for (size_t i = 0; i < fragments->GetNumberOfFragments(); i++) - { - frames.push_back(i); - } - } - - for (std::list::const_iterator - frame = frames.begin(); frame != frames.end(); ++frame) - { - if (*frame >= fragments->GetNumberOfFragments()) - { - std::string s = ("Trying to access frame number " + boost::lexical_cast(*frame + 1) + - " of an image with " + boost::lexical_cast(fragments->GetNumberOfFragments()) + " frames"); - OrthancPluginLogError(context_, s.c_str()); - throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); - } - else - { - AnswerSingleFrame(output, request, dicom, - fragments->GetFragment(*frame).GetByteValue()->GetPointer(), - fragments->GetFragment(*frame).GetByteValue()->GetLength(), *frame); - } - } - } - - return true; -} - - - -OrthancPluginErrorCode RetrieveFrames(OrthancPluginRestOutput* output, - const char* url, - const OrthancPluginHttpRequest* request) -{ - // storescu -xs localhost 4242 ~/Subversion/orthanc-tests/Database/Multiframe.dcm - - // curl http://localhost:8042/dicom-web/studies/1.3.51.0.1.1.192.168.29.133.1681753.1681732/series/1.3.12.2.1107.5.2.33.37097.2012041612474981424569674.0.0.0/instances/1.3.12.2.1107.5.2.33.37097.2012041612485517294169680/frames/1 - // curl http://localhost:8042/dicom-web/studies/1.3.46.670589.7.5.8.80001255161.20000323.151537.1/series/1.3.46.670589.7.5.7.80001255161.20000323.151537.1/instances/1.3.46.670589.7.5.1.981501.20000323.16172540.1.1.13/frames/1 - - - // http://gdcm.sourceforge.net/html/CompressLossyJPEG_8cs-example.html - - gdcm::TransferSyntax targetSyntax(ParseTransferSyntax(request)); - - std::list frames; - ParseFrameList(frames, request); - - Json::Value header; - std::string uri, content; - if (LocateInstance(output, uri, request) && - OrthancPlugins::RestApiGetString(content, context_, uri + "/file") && - OrthancPlugins::RestApiGetJson(header, context_, uri + "/header?simplify")) - { - { - std::string s = "DICOMweb RetrieveFrames on " + uri + ", frames: "; - for (std::list::const_iterator - frame = frames.begin(); frame != frames.end(); ++frame) - { - s += boost::lexical_cast(*frame + 1) + " "; - } - OrthancPluginLogInfo(context_, s.c_str()); - } - - std::auto_ptr source; - - gdcm::TransferSyntax sourceSyntax; - - if (header.type() == Json::objectValue && - header.isMember("TransferSyntaxUID")) - { - sourceSyntax = gdcm::TransferSyntax::GetTSType(header["TransferSyntaxUID"].asCString()); - } - else - { - source.reset(new OrthancPlugins::ParsedDicomFile(content)); - sourceSyntax = source->GetFile().GetHeader().GetDataSetTransferSyntax(); - } - - if (sourceSyntax == targetSyntax || - (targetSyntax == gdcm::TransferSyntax::ImplicitVRLittleEndian && - sourceSyntax == gdcm::TransferSyntax::ExplicitVRLittleEndian)) - { - // No need to change the transfer syntax - - if (source.get() == NULL) - { - source.reset(new OrthancPlugins::ParsedDicomFile(content)); - } - - AnswerFrames(output, request, *source, targetSyntax, frames); - } - else - { - // Need to convert the transfer syntax - - { - std::string s = ("DICOMweb RetrieveFrames: Transcoding " + uri + " from transfer syntax " + - std::string(sourceSyntax.GetString()) + " to " + std::string(targetSyntax.GetString())); - OrthancPluginLogInfo(context_, s.c_str()); - } - - gdcm::ImageChangeTransferSyntax change; - change.SetTransferSyntax(targetSyntax); - - std::stringstream stream(content); - - gdcm::ImageReader reader; - reader.SetStream(stream); - if (!reader.Read()) - { - OrthancPluginLogError(context_, "Cannot decode the image"); - throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); - } - - change.SetInput(reader.GetImage()); - if (!change.Change()) - { - OrthancPluginLogError(context_, "Cannot change the transfer syntax of the image"); - throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); - } - - gdcm::ImageWriter writer; - writer.SetImage(change.GetOutput()); - writer.SetFile(reader.GetFile()); - - std::stringstream ss; - writer.SetStream(ss); - if (!writer.Write()) - { - throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory); - } - - OrthancPlugins::ParsedDicomFile transcoded(ss.str()); - AnswerFrames(output, request, transcoded, targetSyntax, frames); - } - } - - return OrthancPluginErrorCode_Success; -} diff -r 5fdcb04afb0d -r 499df60ad0e2 Plugin/WadoRs.h --- a/Plugin/WadoRs.h Thu Dec 10 09:53:46 2015 +0100 +++ b/Plugin/WadoRs.h Thu Dec 10 09:57:02 2015 +0100 @@ -23,6 +23,10 @@ #include "Configuration.h" +bool LocateInstance(OrthancPluginRestOutput* output, + std::string& uri, + const OrthancPluginHttpRequest* request); + OrthancPluginErrorCode RetrieveDicomStudy(OrthancPluginRestOutput* output, const char* url, const OrthancPluginHttpRequest* request); diff -r 5fdcb04afb0d -r 499df60ad0e2 Plugin/WadoRsRetrieveFrames.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Plugin/WadoRsRetrieveFrames.cpp Thu Dec 10 09:57:02 2015 +0100 @@ -0,0 +1,500 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * 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 + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + **/ + + +#include "WadoRs.h" + +#include "../Orthanc/Core/Toolbox.h" +#include "../Orthanc/Core/OrthancException.h" +#include "Dicom.h" +#include "Plugin.h" + +#include +#include +#include +#include +#include +#include + + +static void TokenizeAndNormalize(std::vector& tokens, + const std::string& source, + char separator) +{ + Orthanc::Toolbox::TokenizeString(tokens, source, separator); + + for (size_t i = 0; i < tokens.size(); i++) + { + tokens[i] = Orthanc::Toolbox::StripSpaces(tokens[i]); + Orthanc::Toolbox::ToLowerCase(tokens[i]); + } +} + + + +static gdcm::TransferSyntax ParseTransferSyntax(const OrthancPluginHttpRequest* request) +{ + for (uint32_t i = 0; i < request->headersCount; i++) + { + std::string key(request->headersKeys[i]); + Orthanc::Toolbox::ToLowerCase(key); + + if (key == "accept") + { + std::vector tokens; + TokenizeAndNormalize(tokens, request->headersValues[i], ';'); + + if (tokens.size() == 0 || + tokens[0] == "*/*") + { + return gdcm::TransferSyntax::ImplicitVRLittleEndian; + } + + if (tokens[0] != "multipart/related") + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + std::string type("application/octet-stream"); + std::string transferSyntax; + + for (size_t j = 1; j < tokens.size(); j++) + { + std::vector parsed; + TokenizeAndNormalize(parsed, tokens[j], '='); + + if (parsed.size() != 2) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest); + } + + if (parsed[0] == "type") + { + type = parsed[1]; + } + + if (parsed[0] == "transfer-syntax") + { + transferSyntax = parsed[1]; + } + } + + if (type == "application/octet-stream") + { + if (transferSyntax.empty()) + { + return gdcm::TransferSyntax(gdcm::TransferSyntax::ImplicitVRLittleEndian); + } + else + { + std::string s = ("DICOMweb RetrieveFrames: Cannot specify a transfer syntax (" + + transferSyntax + ") for default Little Endian uncompressed pixel data"); + OrthancPluginLogError(context_, s.c_str()); + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest); + } + } + else + { + // http://dicom.nema.org/medical/dicom/current/output/html/part18.html#table_6.5-1 + if (type == "image/dicom+jpeg" && transferSyntax == "1.2.840.10008.1.2.4.50") + { + return gdcm::TransferSyntax::JPEGBaselineProcess1; + } + else if (type == "image/dicom+jpeg" && transferSyntax == "1.2.840.10008.1.2.4.51") + { + return gdcm::TransferSyntax::JPEGExtendedProcess2_4; + } + else if (type == "image/dicom+jpeg" && transferSyntax == "1.2.840.10008.1.2.4.57") + { + return gdcm::TransferSyntax::JPEGLosslessProcess14; + } + else if (type == "image/dicom+jpeg" && (transferSyntax.empty() || + transferSyntax == "1.2.840.10008.1.2.4.70")) + { + return gdcm::TransferSyntax::JPEGLosslessProcess14_1; + } + else if (type == "image/dicom+rle" && (transferSyntax.empty() || + transferSyntax == "1.2.840.10008.1.2.5")) + { + return gdcm::TransferSyntax::RLELossless; + } + else if (type == "image/dicom+jpeg-ls" && (transferSyntax.empty() || + transferSyntax == "1.2.840.10008.1.2.4.80")) + { + return gdcm::TransferSyntax::JPEGLSLossless; + } + else if (type == "image/dicom+jpeg-ls" && transferSyntax == "1.2.840.10008.1.2.4.81") + { + return gdcm::TransferSyntax::JPEGLSNearLossless; + } + else if (type == "image/dicom+jp2" && (transferSyntax.empty() || + transferSyntax == "1.2.840.10008.1.2.4.90")) + { + return gdcm::TransferSyntax::JPEG2000Lossless; + } + else if (type == "image/dicom+jp2" && transferSyntax == "1.2.840.10008.1.2.4.91") + { + return gdcm::TransferSyntax::JPEG2000; + } + else if (type == "image/dicom+jpx" && (transferSyntax.empty() || + transferSyntax == "1.2.840.10008.1.2.4.92")) + { + return gdcm::TransferSyntax::JPEG2000Part2Lossless; + } + else if (type == "image/dicom+jpx" && transferSyntax == "1.2.840.10008.1.2.4.93") + { + return gdcm::TransferSyntax::JPEG2000Part2; + } + else + { + std::string s = ("DICOMweb RetrieveFrames: Transfer syntax \"" + + transferSyntax + "\" is incompatible with media type \"" + type + "\""); + OrthancPluginLogError(context_, s.c_str()); + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest); + } + } + } + } + + // By default, DICOMweb expectes Little Endian uncompressed pixel data + return gdcm::TransferSyntax::ImplicitVRLittleEndian; +} + + +static void ParseFrameList(std::list& frames, + const OrthancPluginHttpRequest* request) +{ + frames.clear(); + + if (request->groupsCount <= 3 || + request->groups[3] == NULL) + { + return; + } + + std::string source(request->groups[3]); + Orthanc::Toolbox::ToLowerCase(source); + boost::replace_all(source, "%2c", ","); + + std::vector tokens; + Orthanc::Toolbox::TokenizeString(tokens, source, ','); + + for (size_t i = 0; i < tokens.size(); i++) + { + int frame = boost::lexical_cast(tokens[i]); + if (frame <= 0) + { + std::string s = "Invalid frame number (must be > 0): " + tokens[i]; + OrthancPluginLogError(context_, s.c_str()); + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + frames.push_back(static_cast(frame - 1)); + } +} + + + +static const char* GetMimeType(const gdcm::TransferSyntax& syntax) +{ + switch (syntax) + { + case gdcm::TransferSyntax::ImplicitVRLittleEndian: + return "application/octet-stream"; + + case gdcm::TransferSyntax::JPEGBaselineProcess1: + return "image/dicom+jpeg; transfer-syntax=1.2.840.10008.1.2.4.50"; + + case gdcm::TransferSyntax::JPEGExtendedProcess2_4: + return "image/dicom+jpeg; transfer-syntax=1.2.840.10008.1.2.4.51"; + + case gdcm::TransferSyntax::JPEGLosslessProcess14: + return "image/dicom+jpeg; transfer-syntax=1.2.840.10008.1.2.4.57"; + + case gdcm::TransferSyntax::JPEGLosslessProcess14_1: + return "image/dicom+jpeg; transferSyntax=1.2.840.10008.1.2.4.70"; + + case gdcm::TransferSyntax::RLELossless: + return "image/dicom+rle; transferSyntax=1.2.840.10008.1.2.5"; + + case gdcm::TransferSyntax::JPEGLSLossless: + return "image/dicom+jpeg-ls; transferSyntax=1.2.840.10008.1.2.4.80"; + + case gdcm::TransferSyntax::JPEGLSNearLossless: + return "image/dicom+jpeg-ls; transfer-syntax=1.2.840.10008.1.2.4.81"; + + case gdcm::TransferSyntax::JPEG2000Lossless: + return "image/dicom+jp2; transferSyntax=1.2.840.10008.1.2.4.90"; + + case gdcm::TransferSyntax::JPEG2000: + return "image/dicom+jp2; transfer-syntax=1.2.840.10008.1.2.4.91"; + + case gdcm::TransferSyntax::JPEG2000Part2Lossless: + return "image/dicom+jpx; transferSyntax=1.2.840.10008.1.2.4.92"; + + case gdcm::TransferSyntax::JPEG2000Part2: + return "image/dicom+jpx; transfer-syntax=1.2.840.10008.1.2.4.93"; + + default: + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } +} + + + +static void AnswerSingleFrame(OrthancPluginRestOutput* output, + const OrthancPluginHttpRequest* request, + const OrthancPlugins::ParsedDicomFile& dicom, + const char* frame, + size_t size, + unsigned int frameIndex) +{ + OrthancPluginErrorCode error; + +#if HAS_SEND_MULTIPART_ITEM_2 == 1 + std::string location = dicom.GetWadoUrl(request) + "frames/" + boost::lexical_cast(frameIndex + 1); + const char *keys[] = { "Content-Location" }; + const char *values[] = { location.c_str() }; + error = OrthancPluginSendMultipartItem2(context_, output, frame, size, 1, keys, values); +#else + error = OrthancPluginSendMultipartItem(context_, output, frame, size); +#endif + + if (error != OrthancPluginErrorCode_Success) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol); + } +} + + + +static bool AnswerFrames(OrthancPluginRestOutput* output, + const OrthancPluginHttpRequest* request, + const OrthancPlugins::ParsedDicomFile& dicom, + const gdcm::TransferSyntax& syntax, + std::list& frames) +{ + if (!dicom.GetDataSet().FindDataElement(OrthancPlugins::DICOM_TAG_PIXEL_DATA)) + { + return OrthancPluginErrorCode_IncompatibleImageFormat; + } + + const gdcm::DataElement& pixelData = dicom.GetDataSet().GetDataElement(OrthancPlugins::DICOM_TAG_PIXEL_DATA); + const gdcm::SequenceOfFragments* fragments = pixelData.GetSequenceOfFragments(); + + if (OrthancPluginStartMultipartAnswer(context_, output, "related", GetMimeType(syntax)) != OrthancPluginErrorCode_Success) + { + return false; + } + + if (fragments == NULL) + { + // Single-fragment image + + if (pixelData.GetByteValue() == NULL) + { + OrthancPluginLogError(context_, "Image was not properly decoded"); + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + int width, height, bits; + + if (!dicom.GetIntegerTag(height, *dictionary_, OrthancPlugins::DICOM_TAG_ROWS) || + !dicom.GetIntegerTag(width, *dictionary_, OrthancPlugins::DICOM_TAG_COLUMNS) || + !dicom.GetIntegerTag(bits, *dictionary_, OrthancPlugins::DICOM_TAG_BITS_ALLOCATED)) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + size_t frameSize = height * width * bits / 8; + + if (pixelData.GetByteValue()->GetLength() % frameSize != 0) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + size_t framesCount = pixelData.GetByteValue()->GetLength() / frameSize; + + if (frames.empty()) + { + // If no frame is provided, return all the frames (this is an extension) + for (size_t i = 0; i < framesCount; i++) + { + frames.push_back(i); + } + } + + const char* buffer = pixelData.GetByteValue()->GetPointer(); + assert(sizeof(char) == 1); + + for (std::list::const_iterator + frame = frames.begin(); frame != frames.end(); ++frame) + { + if (*frame >= framesCount) + { + std::string s = ("Trying to access frame number " + boost::lexical_cast(*frame + 1) + + " of an image with " + boost::lexical_cast(framesCount) + " frames"); + OrthancPluginLogError(context_, s.c_str()); + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + const char* p = buffer + (*frame) * frameSize; + AnswerSingleFrame(output, request, dicom, p, frameSize, *frame); + } + } + } + else + { + // Multi-fragment image, we assume that each fragment corresponds to one frame + + if (frames.empty()) + { + // If no frame is provided, return all the frames (this is an extension) + for (size_t i = 0; i < fragments->GetNumberOfFragments(); i++) + { + frames.push_back(i); + } + } + + for (std::list::const_iterator + frame = frames.begin(); frame != frames.end(); ++frame) + { + if (*frame >= fragments->GetNumberOfFragments()) + { + std::string s = ("Trying to access frame number " + boost::lexical_cast(*frame + 1) + + " of an image with " + boost::lexical_cast(fragments->GetNumberOfFragments()) + " frames"); + OrthancPluginLogError(context_, s.c_str()); + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + else + { + AnswerSingleFrame(output, request, dicom, + fragments->GetFragment(*frame).GetByteValue()->GetPointer(), + fragments->GetFragment(*frame).GetByteValue()->GetLength(), *frame); + } + } + } + + return true; +} + + + +OrthancPluginErrorCode RetrieveFrames(OrthancPluginRestOutput* output, + const char* url, + const OrthancPluginHttpRequest* request) +{ + gdcm::TransferSyntax targetSyntax(ParseTransferSyntax(request)); + + std::list frames; + ParseFrameList(frames, request); + + Json::Value header; + std::string uri, content; + if (LocateInstance(output, uri, request) && + OrthancPlugins::RestApiGetString(content, context_, uri + "/file") && + OrthancPlugins::RestApiGetJson(header, context_, uri + "/header?simplify")) + { + { + std::string s = "DICOMweb RetrieveFrames on " + uri + ", frames: "; + for (std::list::const_iterator + frame = frames.begin(); frame != frames.end(); ++frame) + { + s += boost::lexical_cast(*frame + 1) + " "; + } + OrthancPluginLogInfo(context_, s.c_str()); + } + + std::auto_ptr source; + + gdcm::TransferSyntax sourceSyntax; + + if (header.type() == Json::objectValue && + header.isMember("TransferSyntaxUID")) + { + sourceSyntax = gdcm::TransferSyntax::GetTSType(header["TransferSyntaxUID"].asCString()); + } + else + { + source.reset(new OrthancPlugins::ParsedDicomFile(content)); + sourceSyntax = source->GetFile().GetHeader().GetDataSetTransferSyntax(); + } + + if (sourceSyntax == targetSyntax || + (targetSyntax == gdcm::TransferSyntax::ImplicitVRLittleEndian && + sourceSyntax == gdcm::TransferSyntax::ExplicitVRLittleEndian)) + { + // No need to change the transfer syntax + + if (source.get() == NULL) + { + source.reset(new OrthancPlugins::ParsedDicomFile(content)); + } + + AnswerFrames(output, request, *source, targetSyntax, frames); + } + else + { + // Need to convert the transfer syntax + + { + std::string s = ("DICOMweb RetrieveFrames: Transcoding " + uri + " from transfer syntax " + + std::string(sourceSyntax.GetString()) + " to " + std::string(targetSyntax.GetString())); + OrthancPluginLogInfo(context_, s.c_str()); + } + + gdcm::ImageChangeTransferSyntax change; + change.SetTransferSyntax(targetSyntax); + + std::stringstream stream(content); + + gdcm::ImageReader reader; + reader.SetStream(stream); + if (!reader.Read()) + { + OrthancPluginLogError(context_, "Cannot decode the image"); + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); + } + + change.SetInput(reader.GetImage()); + if (!change.Change()) + { + OrthancPluginLogError(context_, "Cannot change the transfer syntax of the image"); + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + gdcm::ImageWriter writer; + writer.SetImage(change.GetOutput()); + writer.SetFile(reader.GetFile()); + + std::stringstream ss; + writer.SetStream(ss); + if (!writer.Write()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory); + } + + OrthancPlugins::ParsedDicomFile transcoded(ss.str()); + AnswerFrames(output, request, transcoded, targetSyntax, frames); + } + } + + return OrthancPluginErrorCode_Success; +}