# HG changeset patch # User am@osimis.io # Date 1544710479 -3600 # Node ID 45a4a1571b4eb83a98e96fbd6655c7f8716834ca # Parent c9f93628215ace259bde3a85b63dc6bfad07ebc3# Parent 0e1755e5efd04c6d098cb788bd8086f6cda4e702 merge diff -r c9f93628215a -r 45a4a1571b4e Core/DicomFormat/DicomMap.cpp --- a/Core/DicomFormat/DicomMap.cpp Thu Dec 13 14:48:23 2018 +0100 +++ b/Core/DicomFormat/DicomMap.cpp Thu Dec 13 15:14:39 2018 +0100 @@ -982,6 +982,93 @@ } + void DicomMap::FromDicomAsJson(const Json::Value& dicomAsJson) + { + Clear(); + + Json::Value::Members tags = dicomAsJson.getMemberNames(); + for (Json::Value::Members::const_iterator + it = tags.begin(); it != tags.end(); ++it) + { + DicomTag tag(0, 0); + if (!DicomTag::ParseHexadecimal(tag, it->c_str())) + { + throw OrthancException(ErrorCode_CorruptedFile); + } + + const Json::Value& value = dicomAsJson[*it]; + + if (value.type() != Json::objectValue || + !value.isMember("Type") || + !value.isMember("Value") || + value["Type"].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_CorruptedFile); + } + + if (value["Type"] == "String") + { + if (value["Value"].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_CorruptedFile); + } + else + { + SetValue(tag, value["Value"].asString(), false /* not binary */); + } + } + } + } + + + void DicomMap::Merge(const DicomMap& other) + { + for (Map::const_iterator it = other.map_.begin(); + it != other.map_.end(); ++it) + { + assert(it->second != NULL); + + if (map_.find(it->first) == map_.end()) + { + map_[it->first] = it->second->Clone(); + } + } + } + + + void DicomMap::ExtractMainDicomTagsInternal(const DicomMap& other, + ResourceType level) + { + const DicomTag* tags = NULL; + size_t size = 0; + + LoadMainDicomTags(tags, size, level); + assert(tags != NULL && size > 0); + + for (size_t i = 0; i < size; i++) + { + Map::const_iterator found = other.map_.find(tags[i]); + + if (found != other.map_.end() && + map_.find(tags[i]) == map_.end()) + { + assert(found->second != NULL); + map_[tags[i]] = found->second->Clone(); + } + } + } + + + void DicomMap::ExtractMainDicomTags(const DicomMap& other) + { + Clear(); + ExtractMainDicomTagsInternal(other, ResourceType_Patient); + ExtractMainDicomTagsInternal(other, ResourceType_Study); + ExtractMainDicomTagsInternal(other, ResourceType_Series); + ExtractMainDicomTagsInternal(other, ResourceType_Instance); + } + + void DicomMap::Serialize(Json::Value& target) const { target = Json::objectValue; diff -r c9f93628215a -r 45a4a1571b4e Core/DicomFormat/DicomMap.h --- a/Core/DicomFormat/DicomMap.h Thu Dec 13 14:48:23 2018 +0100 +++ b/Core/DicomFormat/DicomMap.h Thu Dec 13 15:14:39 2018 +0100 @@ -59,7 +59,7 @@ uint16_t element, DicomValue* value); - void SetValue(DicomTag tag, + void SetValue(DicomTag tag, DicomValue* value); void ExtractTags(DicomMap& source, @@ -68,6 +68,9 @@ static void GetMainDicomTagsInternal(std::set& result, ResourceType level); + void ExtractMainDicomTagsInternal(const DicomMap& other, + ResourceType level); + public: DicomMap() { @@ -217,6 +220,12 @@ bool ParseDouble(double& result, const DicomTag& tag) const; + void FromDicomAsJson(const Json::Value& dicomAsJson); + + void Merge(const DicomMap& other); + + void ExtractMainDicomTags(const DicomMap& other); + void Serialize(Json::Value& target) const; void Unserialize(const Json::Value& source); diff -r c9f93628215a -r 45a4a1571b4e LinuxCompilation.txt --- a/LinuxCompilation.txt Thu Dec 13 14:48:23 2018 +0100 +++ b/LinuxCompilation.txt Thu Dec 13 15:14:39 2018 +0100 @@ -115,8 +115,8 @@ -SUPPORTED - Ubuntu 14.04 LTS ----------------------------- +SUPPORTED - Ubuntu 14.04 LTS and 16.04 LTS +------------------------------------------ # sudo apt-get install build-essential unzip cmake mercurial \ uuid-dev libcurl4-openssl-dev liblua5.1-0-dev \ diff -r c9f93628215a -r 45a4a1571b4e NEWS --- a/NEWS Thu Dec 13 14:48:23 2018 +0100 +++ b/NEWS Thu Dec 13 15:14:39 2018 +0100 @@ -28,7 +28,7 @@ * New URI: "/studies/.../merge" to merge a study * New URI: "/studies/.../split" to split a study * POST-ing a DICOM file to "/instances" also answers the patient/study/series ID -* modalities returned by GET "/modalities?expand" and "/modalities/..." are now JSON objects instead of JSON arrays +* GET "/modalities/?expand" now returns a JSON object instead of a JSON array * New "Details" field in HTTP answers on error (cf. "HttpDescribeErrors" option) * New options to URI "/queries/.../answers": "?expand" and "?simplify" * New URIs to launch new C-FIND to explore the hierarchy of a C-FIND answer: diff -r c9f93628215a -r 45a4a1571b4e OrthancServer/OrthancFindRequestHandler.cpp --- a/OrthancServer/OrthancFindRequestHandler.cpp Thu Dec 13 14:48:23 2018 +0100 +++ b/OrthancServer/OrthancFindRequestHandler.cpp Thu Dec 13 15:14:39 2018 +0100 @@ -521,6 +521,47 @@ } + class OrthancFindRequestHandler::LookupVisitor : public LookupResource::IVisitor + { + private: + DicomFindAnswers& answers_; + ServerContext& context_; + ResourceType level_; + const DicomMap& filteredInput_; + const std::list& sequencesToReturn_; + DicomArray query_; + + public: + LookupVisitor(DicomFindAnswers& answers, + ServerContext& context, + ResourceType level, + const DicomMap& filteredInput, + const std::list& sequencesToReturn) : + answers_(answers), + context_(context), + level_(level), + filteredInput_(filteredInput), + sequencesToReturn_(sequencesToReturn), + query_(filteredInput) + { + answers_.SetComplete(false); + } + + virtual void MarkAsComplete() + { + answers_.SetComplete(true); + } + + virtual void Visit(const std::string& publicId, + const std::string& instanceId, + const Json::Value& dicom) + { + std::auto_ptr counters(ComputeCounters(context_, instanceId, level_, filteredInput_)); + AddAnswer(answers_, dicom, query_, sequencesToReturn_, counters.get()); + } + }; + + void OrthancFindRequestHandler::Handle(DicomFindAnswers& answers, const DicomMap& input, const std::list& sequencesToReturn, @@ -623,8 +664,6 @@ if (FilterQueryTag(value, level, tag, manufacturer)) { - // TODO - Move this to "ResourceLookup::AddDicomConstraint()" - ValueRepresentation vr = FromDcmtkBridge::LookupValueRepresentation(tag); // DICOM specifies that searches must be case sensitive, except @@ -651,42 +690,8 @@ size_t limit = (level == ResourceType_Instance) ? maxInstances_ : maxResults_; - // TODO - Use ServerContext::Apply() at this point, in order to - // share the code with the "/tools/find" REST URI - std::vector resources, instances; - context_.GetIndex().FindCandidates(resources, instances, lookup); - - LOG(INFO) << "Number of candidate resources after fast DB filtering: " << resources.size(); - - assert(resources.size() == instances.size()); - bool complete = true; - - for (size_t i = 0; i < instances.size(); i++) - { - // TODO - Don't read the full JSON from the disk if only "main - // DICOM tags" are to be returned - Json::Value dicom; - context_.ReadDicomAsJson(dicom, instances[i]); - - if (lookup.IsMatch(dicom)) - { - if (limit != 0 && - answers.GetSize() >= limit) - { - complete = false; - break; - } - else - { - std::auto_ptr counters(ComputeCounters(context_, instances[i], level, *filteredInput)); - AddAnswer(answers, dicom, query, sequencesToReturn, counters.get()); - } - } - } - - LOG(INFO) << "Number of matching resources: " << answers.GetSize(); - - answers.SetComplete(complete); + LookupVisitor visitor(answers, context_, level, *filteredInput, sequencesToReturn); + context_.Apply(visitor, lookup, 0 /* "since" is not relevant to C-FIND */, limit); } diff -r c9f93628215a -r 45a4a1571b4e OrthancServer/OrthancFindRequestHandler.h --- a/OrthancServer/OrthancFindRequestHandler.h Thu Dec 13 14:48:23 2018 +0100 +++ b/OrthancServer/OrthancFindRequestHandler.h Thu Dec 13 15:14:39 2018 +0100 @@ -41,6 +41,8 @@ class OrthancFindRequestHandler : public IFindRequestHandler { private: + class LookupVisitor; + ServerContext& context_; unsigned int maxResults_; unsigned int maxInstances_; diff -r c9f93628215a -r 45a4a1571b4e OrthancServer/OrthancRestApi/OrthancRestResources.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Thu Dec 13 14:48:23 2018 +0100 +++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Thu Dec 13 15:14:39 2018 +0100 @@ -1269,6 +1269,43 @@ } + namespace + { + class FindVisitor : public LookupResource::IVisitor + { + private: + bool isComplete_; + std::list resources_; + + public: + FindVisitor() : + isComplete_(false) + { + } + + virtual void MarkAsComplete() + { + isComplete_ = true; // Unused information as of Orthanc 1.5.0 + } + + virtual void Visit(const std::string& publicId, + const std::string& instanceId /* unused */, + const Json::Value& dicom /* unused */) + { + 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"; @@ -1281,15 +1318,43 @@ ServerContext& context = OrthancRestApi::GetContext(call); Json::Value request; - if (call.ParseJsonRequest(request) && - request.type() == Json::objectValue && - request.isMember(KEY_LEVEL) && - request.isMember(KEY_QUERY) && - request[KEY_LEVEL].type() == Json::stringValue && - request[KEY_QUERY].type() == Json::objectValue && - (!request.isMember(KEY_CASE_SENSITIVE) || request[KEY_CASE_SENSITIVE].type() == Json::booleanValue) && - (!request.isMember(KEY_LIMIT) || request[KEY_LIMIT].type() == Json::intValue) && - (!request.isMember(KEY_SINCE) || request[KEY_SINCE].type() == Json::intValue)) + 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)) @@ -1309,7 +1374,8 @@ int tmp = request[KEY_LIMIT].asInt(); if (tmp < 0) { - throw OrthancException(ErrorCode_ParameterOutOfRange); + throw OrthancException(ErrorCode_ParameterOutOfRange, + "Field \"" + std::string(KEY_LIMIT) + "\" should be a positive integer"); } limit = static_cast(tmp); @@ -1321,7 +1387,8 @@ int tmp = request[KEY_SINCE].asInt(); if (tmp < 0) { - throw OrthancException(ErrorCode_ParameterOutOfRange); + throw OrthancException(ErrorCode_ParameterOutOfRange, + "Field \"" + std::string(KEY_SINCE) + "\" should be a positive integer"); } since = static_cast(tmp); @@ -1336,7 +1403,8 @@ { if (request[KEY_QUERY][members[i]].type() != Json::stringValue) { - throw OrthancException(ErrorCode_BadRequest); + throw OrthancException(ErrorCode_BadRequest, + "Tag \"" + members[i] + "\" should be associated with a string"); } query.AddDicomConstraint(FromDcmtkBridge::ParseTag(members[i]), @@ -1344,15 +1412,9 @@ caseSensitive); } - bool isComplete; - std::list resources; - context.Apply(isComplete, resources, query, since, limit); - AnswerListOfResources(call.GetOutput(), context.GetIndex(), - resources, query.GetLevel(), expand); - } - else - { - throw OrthancException(ErrorCode_BadRequest); + FindVisitor visitor; + context.Apply(visitor, query, since, limit); + visitor.Answer(call.GetOutput(), context.GetIndex(), query.GetLevel(), expand); } } diff -r c9f93628215a -r 45a4a1571b4e OrthancServer/Search/LookupIdentifierQuery.cpp --- a/OrthancServer/Search/LookupIdentifierQuery.cpp Thu Dec 13 14:48:23 2018 +0100 +++ b/OrthancServer/Search/LookupIdentifierQuery.cpp Thu Dec 13 15:14:39 2018 +0100 @@ -34,9 +34,10 @@ #include "../PrecompiledHeadersServer.h" #include "LookupIdentifierQuery.h" +#include "../../Core/DicomParsing/FromDcmtkBridge.h" #include "../../Core/OrthancException.h" +#include "../ServerToolbox.h" #include "SetOfResources.h" -#include "../../Core/DicomParsing/FromDcmtkBridge.h" #include @@ -44,6 +45,28 @@ namespace Orthanc { + LookupIdentifierQuery::SingleConstraint:: + SingleConstraint(const DicomTag& tag, + IdentifierConstraintType type, + const std::string& value) : + tag_(tag), + type_(type), + value_(ServerToolbox::NormalizeIdentifier(value)) + { + } + + + LookupIdentifierQuery::RangeConstraint:: + RangeConstraint(const DicomTag& tag, + const std::string& start, + const std::string& end) : + tag_(tag), + start_(ServerToolbox::NormalizeIdentifier(start)), + end_(ServerToolbox::NormalizeIdentifier(end)) + { + } + + LookupIdentifierQuery::Disjunction::~Disjunction() { for (size_t i = 0; i < singleConstraints_.size(); i++) @@ -84,6 +107,12 @@ } + bool LookupIdentifierQuery::IsIdentifier(const DicomTag& tag) + { + return ServerToolbox::IsIdentifier(tag, level_); + } + + void LookupIdentifierQuery::AddConstraint(DicomTag tag, IdentifierConstraintType type, const std::string& value) diff -r c9f93628215a -r 45a4a1571b4e OrthancServer/Search/LookupIdentifierQuery.h --- a/OrthancServer/Search/LookupIdentifierQuery.h Thu Dec 13 14:48:23 2018 +0100 +++ b/OrthancServer/Search/LookupIdentifierQuery.h Thu Dec 13 15:14:39 2018 +0100 @@ -33,7 +33,6 @@ #pragma once -#include "../ServerToolbox.h" #include "../IDatabaseWrapper.h" #include "SetOfResources.h" @@ -79,12 +78,7 @@ public: SingleConstraint(const DicomTag& tag, IdentifierConstraintType type, - const std::string& value) : - tag_(tag), - type_(type), - value_(ServerToolbox::NormalizeIdentifier(value)) - { - } + const std::string& value); const DicomTag& GetTag() const { @@ -113,12 +107,7 @@ public: RangeConstraint(const DicomTag& tag, const std::string& start, - const std::string& end) : - tag_(tag), - start_(ServerToolbox::NormalizeIdentifier(start)), - end_(ServerToolbox::NormalizeIdentifier(end)) - { - } + const std::string& end); const DicomTag& GetTag() const { @@ -189,10 +178,7 @@ ~LookupIdentifierQuery(); - bool IsIdentifier(const DicomTag& tag) - { - return ServerToolbox::IsIdentifier(tag, level_); - } + bool IsIdentifier(const DicomTag& tag); void AddConstraint(DicomTag tag, IdentifierConstraintType type, diff -r c9f93628215a -r 45a4a1571b4e OrthancServer/Search/LookupResource.cpp --- a/OrthancServer/Search/LookupResource.cpp Thu Dec 13 14:48:23 2018 +0100 +++ b/OrthancServer/Search/LookupResource.cpp Thu Dec 13 15:14:39 2018 +0100 @@ -113,6 +113,7 @@ } else { + // This is not a main DICOM tag return false; } } diff -r c9f93628215a -r 45a4a1571b4e OrthancServer/Search/LookupResource.h --- a/OrthancServer/Search/LookupResource.h Thu Dec 13 14:48:23 2018 +0100 +++ b/OrthancServer/Search/LookupResource.h Thu Dec 13 15:14:39 2018 +0100 @@ -70,7 +70,7 @@ ResourceType level_; Levels levels_; - Constraints unoptimizedConstraints_; + Constraints unoptimizedConstraints_; // Constraints on non-main DICOM tags std::auto_ptr modalitiesInStudy_; bool AddInternal(ResourceType level, @@ -82,6 +82,20 @@ IDatabaseWrapper& database) const; public: + class IVisitor : public boost::noncopyable + { + public: + virtual ~IVisitor() + { + } + + virtual void MarkAsComplete() = 0; + + virtual void Visit(const std::string& publicId, + const std::string& instanceId, + const Json::Value& dicom) = 0; + }; + LookupResource(ResourceType level); ~LookupResource(); diff -r c9f93628215a -r 45a4a1571b4e OrthancServer/ServerContext.cpp --- a/OrthancServer/ServerContext.cpp Thu Dec 13 14:48:23 2018 +0100 +++ b/OrthancServer/ServerContext.cpp Thu Dec 13 15:14:39 2018 +0100 @@ -773,21 +773,22 @@ } - void ServerContext::Apply(bool& isComplete, - std::list& result, + void ServerContext::Apply(LookupResource::IVisitor& visitor, const ::Orthanc::LookupResource& lookup, size_t since, size_t limit) { - result.clear(); - isComplete = true; - std::vector resources, instances; GetIndex().FindCandidates(resources, instances, lookup); + LOG(INFO) << "Number of candidate resources after fast DB filtering on main DICOM tags: " << resources.size(); + assert(resources.size() == instances.size()); + size_t countResults = 0; size_t skipped = 0; + bool complete = true; + for (size_t i = 0; i < instances.size(); i++) { // TODO - Don't read the full JSON from the disk if only "main @@ -802,17 +803,26 @@ skipped++; } else if (limit != 0 && - result.size() >= limit) + countResults >= limit) { - isComplete = false; - return; // too many results + // Too many results, don't mark as complete + complete = false; + break; } else { - result.push_back(resources[i]); + visitor.Visit(resources[i], instances[i], dicom); + countResults ++; } } } + + if (complete) + { + visitor.MarkAsComplete(); + } + + LOG(INFO) << "Number of matching resources: " << countResults; } diff -r c9f93628215a -r 45a4a1571b4e OrthancServer/ServerContext.h --- a/OrthancServer/ServerContext.h Thu Dec 13 14:48:23 2018 +0100 +++ b/OrthancServer/ServerContext.h Thu Dec 13 15:14:39 2018 +0100 @@ -38,6 +38,7 @@ #include "LuaScripting.h" #include "OrthancHttpHandler.h" #include "ServerIndex.h" +#include "Search/LookupResource.h" #include "../Core/Cache/MemoryCache.h" #include "../Core/Cache/SharedArchive.h" @@ -334,8 +335,7 @@ void Stop(); - void Apply(bool& isComplete, - std::list& result, + void Apply(LookupResource::IVisitor& visitor, const ::Orthanc::LookupResource& lookup, size_t since, size_t limit); diff -r c9f93628215a -r 45a4a1571b4e OrthancServer/ServerIndex.cpp --- a/OrthancServer/ServerIndex.cpp Thu Dec 13 14:48:23 2018 +0100 +++ b/OrthancServer/ServerIndex.cpp Thu Dec 13 15:14:39 2018 +0100 @@ -2248,6 +2248,78 @@ } + bool ServerIndex::GetAllMainDicomTags(DicomMap& result, + const std::string& instancePublicId) + { + result.Clear(); + + boost::mutex::scoped_lock lock(mutex_); + + // Lookup for the requested resource + int64_t instance; + ResourceType type; + if (!db_.LookupResource(instance, type, instancePublicId) || + type != ResourceType_Instance) + { + return false; + } + else + { + DicomMap tmp; + + db_.GetMainDicomTags(tmp, instance); + result.Merge(tmp); + + int64_t series; + if (!db_.LookupParent(series, instance)) + { + throw OrthancException(ErrorCode_InternalError); + } + + tmp.Clear(); + db_.GetMainDicomTags(tmp, series); + result.Merge(tmp); + + int64_t study; + if (!db_.LookupParent(study, series)) + { + throw OrthancException(ErrorCode_InternalError); + } + + tmp.Clear(); + db_.GetMainDicomTags(tmp, study); + result.Merge(tmp); + +#ifndef NDEBUG + { + // Sanity test to check that all the main DICOM tags from the + // patient level are copied at the study level + + int64_t patient; + if (!db_.LookupParent(patient, study)) + { + throw OrthancException(ErrorCode_InternalError); + } + + tmp.Clear(); + db_.GetMainDicomTags(tmp, study); + + std::set patientTags; + tmp.GetTags(patientTags); + + for (std::set::const_iterator + it = patientTags.begin(); it != patientTags.end(); ++it) + { + assert(result.HasTag(*it)); + } + } +#endif + + return true; + } + } + + bool ServerIndex::LookupResourceType(ResourceType& type, const std::string& publicId) { diff -r c9f93628215a -r 45a4a1571b4e OrthancServer/ServerIndex.h --- a/OrthancServer/ServerIndex.h Thu Dec 13 14:48:23 2018 +0100 +++ b/OrthancServer/ServerIndex.h Thu Dec 13 15:14:39 2018 +0100 @@ -275,6 +275,10 @@ ResourceType expectedType, ResourceType levelOfInterest); + // Only applicable at the instance level + bool GetAllMainDicomTags(DicomMap& result, + const std::string& instancePublicId); + bool LookupResourceType(ResourceType& type, const std::string& publicId); diff -r c9f93628215a -r 45a4a1571b4e UnitTestsSources/DicomMapTests.cpp --- a/UnitTestsSources/DicomMapTests.cpp Thu Dec 13 14:48:23 2018 +0100 +++ b/UnitTestsSources/DicomMapTests.cpp Thu Dec 13 15:14:39 2018 +0100 @@ -37,8 +37,12 @@ #include "../Core/OrthancException.h" #include "../Core/DicomFormat/DicomMap.h" #include "../Core/DicomParsing/FromDcmtkBridge.h" +#include "../Core/DicomParsing/ParsedDicomFile.h" + +#include "../OrthancServer/DicomInstanceToStore.h" #include +#include using namespace Orthanc; @@ -409,3 +413,135 @@ ASSERT_THROW(v->GetContent(), OrthancException); } } + + + +TEST(DicomMap, DicomAsJson) +{ + // This is a Latin-1 test string: "crane" with a circumflex accent + const unsigned char raw[] = { 0x63, 0x72, 0xe2, 0x6e, 0x65 }; + std::string latin1((char*) &raw[0], sizeof(raw) / sizeof(char)); + + std::string utf8 = Toolbox::ConvertToUtf8(latin1, Encoding_Latin1); + + ParsedDicomFile dicom(false); + dicom.SetEncoding(Encoding_Latin1); + dicom.ReplacePlainString(DICOM_TAG_PATIENT_NAME, "Hello"); + dicom.ReplacePlainString(DICOM_TAG_STUDY_DESCRIPTION, utf8); + dicom.ReplacePlainString(DICOM_TAG_SERIES_DESCRIPTION, std::string(ORTHANC_MAXIMUM_TAG_LENGTH, 'a')); + dicom.ReplacePlainString(DICOM_TAG_MANUFACTURER, std::string(ORTHANC_MAXIMUM_TAG_LENGTH + 1, 'a')); + dicom.ReplacePlainString(DICOM_TAG_PIXEL_DATA, "binary"); + dicom.ReplacePlainString(DICOM_TAG_ROWS, "512"); + + DcmDataset& dataset = *dicom.GetDcmtkObject().getDataset(); + dataset.insertEmptyElement(DCM_StudyID, OFFalse); + + { + std::auto_ptr sequence(new DcmSequenceOfItems(DCM_ReferencedSeriesSequence)); + + { + std::auto_ptr item(new DcmItem); + item->putAndInsertString(DCM_ReferencedSOPInstanceUID, "nope", OFFalse); + ASSERT_TRUE(sequence->insert(item.release(), false, false).good()); + } + + ASSERT_TRUE(dataset.insert(sequence.release(), false, false).good()); + } + + + // Check re-encoding + DcmElement* element = NULL; + ASSERT_TRUE(dataset.findAndGetElement(DCM_StudyDescription, element).good() && + element != NULL); + + char* c = NULL; + ASSERT_TRUE(element != NULL && + element->isLeaf() && + element->isaString() && + element->getString(c).good()); + ASSERT_EQ(0, memcmp(c, raw, latin1.length())); + + ASSERT_TRUE(dataset.findAndGetElement(DCM_Rows, element).good() && + element != NULL && + element->getTag().getEVR() == EVR_US); + + DicomInstanceToStore toStore; + toStore.SetParsedDicomFile(dicom); + + DicomMap m; + m.FromDicomAsJson(toStore.GetJson()); + + ASSERT_EQ("ISO_IR 100", m.GetValue(DICOM_TAG_SPECIFIC_CHARACTER_SET).GetContent()); + + ASSERT_FALSE(m.GetValue(DICOM_TAG_PATIENT_NAME).IsBinary()); + ASSERT_EQ("Hello", m.GetValue(DICOM_TAG_PATIENT_NAME).GetContent()); + + ASSERT_FALSE(m.GetValue(DICOM_TAG_STUDY_DESCRIPTION).IsBinary()); + ASSERT_EQ(utf8, m.GetValue(DICOM_TAG_STUDY_DESCRIPTION).GetContent()); + + ASSERT_FALSE(m.HasTag(DICOM_TAG_MANUFACTURER)); // Too long + ASSERT_FALSE(m.HasTag(DICOM_TAG_PIXEL_DATA)); // Pixel data + ASSERT_FALSE(m.HasTag(DICOM_TAG_REFERENCED_SERIES_SEQUENCE)); // Sequence + ASSERT_EQ(DICOM_TAG_REFERENCED_SERIES_SEQUENCE.GetGroup(), DCM_ReferencedSeriesSequence.getGroup()); + ASSERT_EQ(DICOM_TAG_REFERENCED_SERIES_SEQUENCE.GetElement(), DCM_ReferencedSeriesSequence.getElement()); + + ASSERT_TRUE(m.HasTag(DICOM_TAG_SERIES_DESCRIPTION)); // Maximum length + ASSERT_FALSE(m.GetValue(DICOM_TAG_SERIES_DESCRIPTION).IsBinary()); + ASSERT_EQ(ORTHANC_MAXIMUM_TAG_LENGTH, m.GetValue(DICOM_TAG_SERIES_DESCRIPTION).GetContent().length()); + + ASSERT_FALSE(m.GetValue(DICOM_TAG_ROWS).IsBinary()); + ASSERT_EQ("512", m.GetValue(DICOM_TAG_ROWS).GetContent()); + + ASSERT_FALSE(m.GetValue(DICOM_TAG_STUDY_ID).IsNull()); + ASSERT_FALSE(m.GetValue(DICOM_TAG_STUDY_ID).IsBinary()); + ASSERT_EQ("", m.GetValue(DICOM_TAG_STUDY_ID).GetContent()); + + DicomArray a(m); + ASSERT_EQ(6u, a.GetSize()); + + + //dicom.SaveToFile("/tmp/test.dcm"); + //std::cout << toStore.GetJson() << std::endl; + //a.Print(stdout); +} + + + +TEST(DicomMap, ExtractMainDicomTags) +{ + DicomMap b; + b.SetValue(DICOM_TAG_PATIENT_NAME, "E", false); + + { + DicomMap a; + a.SetValue(DICOM_TAG_PATIENT_NAME, "A", false); + a.SetValue(DICOM_TAG_STUDY_DESCRIPTION, "B", false); + a.SetValue(DICOM_TAG_SERIES_DESCRIPTION, "C", false); + a.SetValue(DICOM_TAG_NUMBER_OF_FRAMES, "D", false); + a.SetValue(DICOM_TAG_SLICE_THICKNESS, "F", false); + b.ExtractMainDicomTags(a); + } + + ASSERT_EQ(4u, b.GetSize()); + ASSERT_EQ("A", b.GetValue(DICOM_TAG_PATIENT_NAME).GetContent()); + ASSERT_EQ("B", b.GetValue(DICOM_TAG_STUDY_DESCRIPTION).GetContent()); + ASSERT_EQ("C", b.GetValue(DICOM_TAG_SERIES_DESCRIPTION).GetContent()); + ASSERT_EQ("D", b.GetValue(DICOM_TAG_NUMBER_OF_FRAMES).GetContent()); + ASSERT_FALSE(b.HasTag(DICOM_TAG_SLICE_THICKNESS)); + + b.SetValue(DICOM_TAG_PATIENT_NAME, "G", false); + + { + DicomMap a; + a.SetValue(DICOM_TAG_PATIENT_NAME, "A", false); + a.SetValue(DICOM_TAG_SLICE_THICKNESS, "F", false); + b.Merge(a); + } + + ASSERT_EQ(5u, b.GetSize()); + ASSERT_EQ("G", b.GetValue(DICOM_TAG_PATIENT_NAME).GetContent()); + ASSERT_EQ("B", b.GetValue(DICOM_TAG_STUDY_DESCRIPTION).GetContent()); + ASSERT_EQ("C", b.GetValue(DICOM_TAG_SERIES_DESCRIPTION).GetContent()); + ASSERT_EQ("D", b.GetValue(DICOM_TAG_NUMBER_OF_FRAMES).GetContent()); + ASSERT_EQ("F", b.GetValue(DICOM_TAG_SLICE_THICKNESS).GetContent()); +} diff -r c9f93628215a -r 45a4a1571b4e UnitTestsSources/ServerIndexTests.cpp --- a/UnitTestsSources/ServerIndexTests.cpp Thu Dec 13 14:48:23 2018 +0100 +++ b/UnitTestsSources/ServerIndexTests.cpp Thu Dec 13 15:14:39 2018 +0100 @@ -38,9 +38,10 @@ #include "../Core/FileStorage/MemoryStorageArea.h" #include "../Core/Logging.h" #include "../OrthancServer/DatabaseWrapper.h" +#include "../OrthancServer/Search/LookupIdentifierQuery.h" #include "../OrthancServer/ServerContext.h" #include "../OrthancServer/ServerIndex.h" -#include "../OrthancServer/Search/LookupIdentifierQuery.h" +#include "../OrthancServer/ServerToolbox.h" #include #include