Mercurial > hg > orthanc
changeset 285:4031f73fe0e4
access to the raw dicom tags
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 12 Dec 2012 17:53:14 +0100 |
parents | 06aa7b7b6723 |
children | 727a6d766dde |
files | Core/Cache/MemoryCache.cpp Core/Cache/MemoryCache.h Core/HttpServer/HttpOutput.h Core/RestApi/RestApi.h Core/RestApi/RestApiOutput.h NEWS OrthancServer/FromDcmtkBridge.cpp OrthancServer/FromDcmtkBridge.h OrthancServer/OrthancRestApi.cpp OrthancServer/ServerContext.cpp OrthancServer/ServerContext.h UnitTests/MemoryCache.cpp |
diffstat | 12 files changed, 300 insertions(+), 49 deletions(-) [+] |
line wrap: on
line diff
--- a/Core/Cache/MemoryCache.cpp Wed Dec 12 15:40:18 2012 +0100 +++ b/Core/Cache/MemoryCache.cpp Wed Dec 12 17:53:14 2012 +0100 @@ -82,9 +82,8 @@ } } - MemoryCache::Accessor* MemoryCache::Access(const std::string& id) + IDynamicObject& MemoryCache::Access(const std::string& id) { - Page& element = Load(id); - return new Accessor(element); + return *Load(id).content_; } }
--- a/Core/Cache/MemoryCache.h Wed Dec 12 15:40:18 2012 +0100 +++ b/Core/Cache/MemoryCache.h Wed Dec 12 17:53:14 2012 +0100 @@ -57,40 +57,11 @@ Page& Load(const std::string& id); public: - class Accessor - { - friend class MemoryCache; - - private: - Page& element_; - - Accessor(Page& element) : - element_(element) - { - } - - public: - const std::string GetId() const - { - return element_.id_; - } - - IDynamicObject& GetContent() - { - return *element_.content_; - } - - const IDynamicObject& GetContent() const - { - return *element_.content_; - } - }; - MemoryCache(ICachePageProvider& provider, size_t cacheSize); ~MemoryCache(); - Accessor* Access(const std::string& id); + IDynamicObject& Access(const std::string& id); }; }
--- a/Core/HttpServer/HttpOutput.h Wed Dec 12 15:40:18 2012 +0100 +++ b/Core/HttpServer/HttpOutput.h Wed Dec 12 17:53:14 2012 +0100 @@ -43,11 +43,6 @@ private: void SendHeaderInternal(Orthanc_HttpStatus status); - void SendOkHeader(const char* contentType, - bool hasContentLength, - uint64_t contentLength, - const char* contentFilename); - public: virtual ~HttpOutput() { @@ -55,6 +50,11 @@ virtual void Send(const void* buffer, size_t length) = 0; + void SendOkHeader(const char* contentType, + bool hasContentLength, + uint64_t contentLength, + const char* contentFilename); + void SendCustomOkHeader(const std::string& customHeader); void SendString(const std::string& s);
--- a/Core/RestApi/RestApi.h Wed Dec 12 15:40:18 2012 +0100 +++ b/Core/RestApi/RestApi.h Wed Dec 12 17:53:14 2012 +0100 @@ -70,6 +70,11 @@ { return *fullUri_; } + + const UriComponents& GetTrailingUri() const + { + return *trailing_; + } std::string GetUriComponent(const std::string& name, const std::string& defaultValue) const
--- a/Core/RestApi/RestApiOutput.h Wed Dec 12 15:40:18 2012 +0100 +++ b/Core/RestApi/RestApiOutput.h Wed Dec 12 17:53:14 2012 +0100 @@ -52,6 +52,16 @@ ~RestApiOutput(); + HttpOutput& GetLowLevelOutput() + { + return output_; + } + + void MarkLowLevelOutputDone() + { + alreadySent_ = true; + } + void AnswerFile(HttpFileSender& sender); void AnswerJson(const Json::Value& value);
--- a/NEWS Wed Dec 12 15:40:18 2012 +0100 +++ b/NEWS Wed Dec 12 17:53:14 2012 +0100 @@ -3,6 +3,7 @@ * Recycling of disk space * Protection of patients against recycling (also in Orthanc Explorer) +* Raw access to the value of the DICOM tags in the REST API Version 0.3.1 (2012/12/05)
--- a/OrthancServer/FromDcmtkBridge.cpp Wed Dec 12 15:40:18 2012 +0100 +++ b/OrthancServer/FromDcmtkBridge.cpp Wed Dec 12 17:53:14 2012 +0100 @@ -63,6 +63,193 @@ namespace Orthanc { + ParsedDicomFile::ParsedDicomFile(const std::string& content) + { + DcmInputBufferStream is; + if (content.size() > 0) + { + is.setBuffer(&content[0], content.size()); + } + is.setEos(); + + file_.reset(new DcmFileFormat); + if (!file_->read(is).good()) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + } + + + static void SendPathValueForDictionary(RestApiOutput& output, + DcmItem& dicom) + { + Json::Value v = Json::arrayValue; + + for (unsigned long i = 0; i < dicom.card(); i++) + { + DcmElement* element = dicom.getElement(i); + if (element) + { + char buf[16]; + sprintf(buf, "%04x-%04x", element->getTag().getGTag(), element->getTag().getETag()); + v.append(buf); + } + } + + output.AnswerJson(v); + } + + static inline uint16_t GetCharValue(char c) + { + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + else + return 0; + } + + static inline uint16_t GetTagValue(const char* c) + { + return ((GetCharValue(c[0]) << 12) + + (GetCharValue(c[1]) << 8) + + (GetCharValue(c[2]) << 4) + + GetCharValue(c[3])); + } + + static bool ParseTagAndGroup(DcmTagKey& key, + const std::string& tag) + { + if (tag.size() != 9 || + !isxdigit(tag[0]) || + !isxdigit(tag[1]) || + !isxdigit(tag[2]) || + !isxdigit(tag[3]) || + tag[4] != '-' || + !isxdigit(tag[5]) || + !isxdigit(tag[6]) || + !isxdigit(tag[7]) || + !isxdigit(tag[8])) + { + return false; + } + + uint16_t group = GetTagValue(tag.c_str()); + uint16_t element = GetTagValue(tag.c_str() + 5); + + key = DcmTagKey(group, element); + + return true; + } + + static void SendPathValueForLeaf(RestApiOutput& output, + const std::string& tag, + DcmItem& dicom) + { + DcmTagKey k; + if (!ParseTagAndGroup(k, tag)) + { + return; + } + + DcmElement* element = NULL; + if (dicom.findAndGetElement(k, element).good() && element != NULL) + { + if (element->getVR() == EVR_SQ) + { + // This element is a sequence + Json::Value v = Json::arrayValue; + DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(*element); + + for (unsigned long i = 0; i < sequence.card(); i++) + { + v.append(boost::lexical_cast<std::string>(i)); + } + + output.AnswerJson(v); + } + else + { + // This element is not a sequence + std::string buffer; + buffer.resize(65536); + Uint32 length = element->getLength(); + Uint32 offset = 0; + + output.GetLowLevelOutput().SendOkHeader("application/octet-stream", true, length, NULL); + + while (offset < length) + { + Uint32 nbytes; + if (length - offset < buffer.size()) + { + nbytes = length - offset; + } + else + { + nbytes = buffer.size(); + } + + if (element->getPartialValue(&buffer[0], offset, nbytes).good()) + { + output.GetLowLevelOutput().Send(&buffer[0], nbytes); + offset += nbytes; + } + else + { + return; + } + } + + output.MarkLowLevelOutputDone(); + } + } + } + + void ParsedDicomFile::SendPathValue(RestApiOutput& output, + const UriComponents& uri) + { + DcmItem* dicom = file_->getDataset(); + + // Go down in the tag hierarchy according to the URI + for (size_t pos = 0; pos < uri.size() / 2; pos++) + { + size_t index; + try + { + index = boost::lexical_cast<size_t>(uri[2 * pos + 1]); + } + catch (boost::bad_lexical_cast&) + { + return; + } + + DcmTagKey k; + DcmItem *child = NULL; + if (!ParseTagAndGroup(k, uri[2 * pos]) || + !dicom->findAndGetSequenceItem(k, child, index).good() || + child == NULL) + { + return; + } + + dicom = child; + } + + // We have reached the end of the URI + if (uri.size() % 2 == 0) + { + SendPathValueForDictionary(output, *dicom); + } + else + { + SendPathValueForLeaf(output, uri.back(), *dicom); + } + } + + void FromDcmtkBridge::Convert(DicomMap& target, DcmDataset& dataset) { target.Clear();
--- a/OrthancServer/FromDcmtkBridge.h Wed Dec 12 15:40:18 2012 +0100 +++ b/OrthancServer/FromDcmtkBridge.h Wed Dec 12 17:53:14 2012 +0100 @@ -33,8 +33,13 @@ #pragma once #include "../Core/DicomFormat/DicomMap.h" +#include "../Core/RestApi/RestApiOutput.h" +#include "../Core/Toolbox.h" + #include <dcmtk/dcmdata/dcdatset.h> +#include <dcmtk/dcmdata/dcfilefo.h> #include <json/json.h> +#include <memory> namespace Orthanc { @@ -52,6 +57,23 @@ DicomRootLevel_Instance }; + class ParsedDicomFile : public IDynamicObject + { + private: + std::auto_ptr<DcmFileFormat> file_; + + public: + ParsedDicomFile(const std::string& content); + + DcmFileFormat& GetDicom() + { + return *file_; + } + + void SendPathValue(RestApiOutput& output, + const UriComponents& uri); + }; + class FromDcmtkBridge { public:
--- a/OrthancServer/OrthancRestApi.cpp Wed Dec 12 15:40:18 2012 +0100 +++ b/OrthancServer/OrthancRestApi.cpp Wed Dec 12 17:53:14 2012 +0100 @@ -847,6 +847,23 @@ + // Raw access to the DICOM tags of an instance ------------------------------ + + static void GetRawContent(RestApi::GetCall& call) + { + // TODO IMPROVE MULTITHREADING + static boost::mutex mutex_; + boost::mutex::scoped_lock lock(mutex_); + + RETRIEVE_CONTEXT(call); + std::string id = call.GetUriComponent("id", ""); + ParsedDicomFile& dicom = context.GetDicomFile(id); + dicom.SendPathValue(call.GetOutput(), call.GetTrailingUri()); + } + + + + // Registration of the various REST handlers -------------------------------- OrthancRestApi::OrthancRestApi(ServerContext& context) : @@ -885,6 +902,7 @@ Register("/instances/{id}/tags", GetInstanceTags<false>); Register("/instances/{id}/simplified-tags", GetInstanceTags<true>); Register("/instances/{id}/frames", ListFrames); + Register("/instances/{id}/content/*", GetRawContent); Register("/instances/{id}/frames/{frame}/preview", GetImage<ImageExtractionMode_Preview>); Register("/instances/{id}/frames/{frame}/image-uint8", GetImage<ImageExtractionMode_UInt8>);
--- a/OrthancServer/ServerContext.cpp Wed Dec 12 15:40:18 2012 +0100 +++ b/OrthancServer/ServerContext.cpp Wed Dec 12 17:53:14 2012 +0100 @@ -37,6 +37,8 @@ #include <glog/logging.h> +static const size_t DICOM_CACHE_SIZE = 2; + /** * IMPORTANT: We make the assumption that the same instance of * FileStorage can be accessed from multiple threads. This seems OK @@ -51,7 +53,9 @@ ServerContext::ServerContext(const boost::filesystem::path& path) : storage_(path.string()), index_(*this, path.string()), - accessor_(storage_) + accessor_(storage_), + provider_(*this), + dicomCache_(provider_, DICOM_CACHE_SIZE) { // TODO RECYCLING SETUP HERE //index_.SetMaximumPatientCount(4); @@ -165,4 +169,18 @@ accessor_.SetCompressionForNextOperations(attachment.GetCompressionType()); accessor_.Read(result, attachment.GetUuid()); } + + + IDynamicObject* ServerContext::DicomCacheProvider::Provide(const std::string& instancePublicId) + { + std::string content; + context_.ReadFile(content, instancePublicId, FileContentType_Dicom); + return new ParsedDicomFile(content); + } + + + ParsedDicomFile& ServerContext::GetDicomFile(const std::string& instancePublicId) + { + return dynamic_cast<ParsedDicomFile&>(dicomCache_.Access(instancePublicId)); + } }
--- a/OrthancServer/ServerContext.h Wed Dec 12 15:40:18 2012 +0100 +++ b/OrthancServer/ServerContext.h Wed Dec 12 17:53:14 2012 +0100 @@ -32,10 +32,12 @@ #pragma once -#include "ServerIndex.h" +#include "../Core/Cache/MemoryCache.h" #include "../Core/FileStorage/CompressedFileStorageAccessor.h" #include "../Core/FileStorage/FileStorage.h" #include "../Core/RestApi/RestApiOutput.h" +#include "ServerIndex.h" +#include "FromDcmtkBridge.h" namespace Orthanc { @@ -47,10 +49,26 @@ class ServerContext { private: + class DicomCacheProvider : public ICachePageProvider + { + private: + ServerContext& context_; + + public: + DicomCacheProvider(ServerContext& context) : context_(context) + { + } + + virtual IDynamicObject* Provide(const std::string& id); + }; + FileStorage storage_; ServerIndex index_; CompressedFileStorageAccessor accessor_; bool compressionEnabled_; + + DicomCacheProvider provider_; + MemoryCache dicomCache_; public: ServerContext(const boost::filesystem::path& path); @@ -86,5 +104,8 @@ void ReadFile(std::string& result, const std::string& instancePublicId, FileContentType content); + + // TODO IMPLEMENT MULTITHREADING FOR THIS METHOD + ParsedDicomFile& GetDicomFile(const std::string& instancePublicId); }; }
--- a/UnitTests/MemoryCache.cpp Wed Dec 12 15:40:18 2012 +0100 +++ b/UnitTests/MemoryCache.cpp Wed Dec 12 17:53:14 2012 +0100 @@ -113,15 +113,14 @@ { Orthanc::MemoryCache cache(provider, 3); - std::auto_ptr<Orthanc::MemoryCache::Accessor> a; - a.reset(cache.Access("42")); // 42 -> exit - a.reset(cache.Access("43")); // 43, 42 -> exit - a.reset(cache.Access("45")); // 45, 43, 42 -> exit - a.reset(cache.Access("42")); // 42, 45, 43 -> exit - a.reset(cache.Access("43")); // 43, 42, 45 -> exit - a.reset(cache.Access("47")); // 45 is removed; 47, 43, 42 -> exit - a.reset(cache.Access("44")); // 42 is removed; 44, 47, 43 -> exit - a.reset(cache.Access("42")); // 43 is removed; 42, 44, 47 -> exit + cache.Access("42"); // 42 -> exit + cache.Access("43"); // 43, 42 -> exit + cache.Access("45"); // 45, 43, 42 -> exit + cache.Access("42"); // 42, 45, 43 -> exit + cache.Access("43"); // 43, 42, 45 -> exit + cache.Access("47"); // 45 is removed; 47, 43, 42 -> exit + cache.Access("44"); // 42 is removed; 44, 47, 43 -> exit + cache.Access("42"); // 43 is removed; 42, 44, 47 -> exit // Closing the cache: 47, 44, 42 are successively removed }