# HG changeset patch # User Sebastien Jodogne # Date 1450722398 -3600 # Node ID e018037d4d0e644863d520582e688f68a2dd0334 # Parent 7110e2881dc098ef4b0312962b63c2570169a215 Support of optional tags for counting resources in C-Find diff -r 7110e2881dc0 -r e018037d4d0e Core/DicomFormat/DicomTag.h --- a/Core/DicomFormat/DicomTag.h Wed Dec 16 09:23:52 2015 +0100 +++ b/Core/DicomFormat/DicomTag.h Mon Dec 21 19:26:38 2015 +0100 @@ -169,4 +169,14 @@ static const DicomTag DICOM_TAG_IMAGE_COMMENTS(0x0020, 0x4000); static const DicomTag DICOM_TAG_ACQUISITION_DEVICE_PROCESSING_DESCRIPTION(0x0018, 0x1400); static const DicomTag DICOM_TAG_CONTRAST_BOLUS_AGENT(0x0018, 0x0010); + + // Counting patients, studies and series + // https://www.medicalconnections.co.uk/kb/Counting_Studies_Series_and_Instances + static const DicomTag DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES(0x0020, 0x1200); + static const DicomTag DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES(0x0020, 0x1202); + static const DicomTag DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES(0x0020, 0x1204); + static const DicomTag DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES(0x0020, 0x1206); + static const DicomTag DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES(0x0020, 0x1208); + static const DicomTag DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES(0x0020, 0x1209); + static const DicomTag DICOM_TAG_SOP_CLASSES_IN_STUDY(0x0008, 0x0062); } diff -r 7110e2881dc0 -r e018037d4d0e NEWS --- a/NEWS Wed Dec 16 09:23:52 2015 +0100 +++ b/NEWS Mon Dec 21 19:26:38 2015 +0100 @@ -1,6 +1,9 @@ Pending changes in the mainline =============================== +* Support of optional tags for counting resources in C-Find: + 0008-0061, 0008-0062, 0020-1200, 0020-1202, 0020-1204, 0020-1206, 0020-1208, 0020-1209 + Version 1.0.0 (2015/12/15) ========================== diff -r 7110e2881dc0 -r e018037d4d0e OrthancServer/OrthancFindRequestHandler.cpp --- a/OrthancServer/OrthancFindRequestHandler.cpp Wed Dec 16 09:23:52 2015 +0100 +++ b/OrthancServer/OrthancFindRequestHandler.cpp Mon Dec 21 19:26:38 2015 +0100 @@ -46,10 +46,281 @@ namespace Orthanc { + static void GetChildren(std::list& target, + ServerIndex& index, + const std::list& source) + { + target.clear(); + + for (std::list::const_iterator + it = source.begin(); it != source.end(); ++it) + { + std::list tmp; + index.GetChildren(tmp, *it); + target.splice(target.end(), tmp); + } + } + + + static void StoreSetOfStrings(DicomMap& result, + const DicomTag& tag, + const std::set& values) + { + bool isFirst = true; + + std::string s; + for (std::set::const_iterator + it = values.begin(); it != values.end(); ++it) + { + if (isFirst) + { + isFirst = false; + } + else + { + s += "\\"; + } + + s += *it; + } + + result.SetValue(tag, s); + } + + + static void ExtractTagFromMainDicomTags(std::set& target, + ServerIndex& index, + const DicomTag& tag, + const std::list& resources, + ResourceType level) + { + for (std::list::const_iterator + it = resources.begin(); it != resources.end(); ++it) + { + DicomMap tags; + if (index.GetMainDicomTags(tags, *it, level, level) && + tags.HasTag(tag)) + { + target.insert(tags.GetValue(tag).GetContent()); + } + } + } + + + static void ExtractTagFromInstances(std::set& target, + ServerContext& context, + const DicomTag& tag, + const std::list& instances) + { + std::string formatted = tag.Format(); + + for (std::list::const_iterator + it = instances.begin(); it != instances.end(); ++it) + { + Json::Value dicom; + context.ReadJson(dicom, *it); + + if (dicom.isMember(formatted)) + { + const Json::Value& source = dicom[formatted]; + + if (source.type() == Json::objectValue && + source.isMember("Type") && + source.isMember("Value") && + source["Type"].asString() == "String" && + source["Value"].type() == Json::stringValue) + { + target.insert(source["Value"].asString()); + } + } + } + } + + + static void ComputePatientCounters(DicomMap& result, + ServerIndex& index, + const std::string& patient, + const DicomMap& query) + { + std::list studies; + index.GetChildren(studies, patient); + + if (query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES)) + { + result.SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES, + boost::lexical_cast(studies.size())); + } + + if (!query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES) && + !query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES)) + { + return; + } + + std::list series; + GetChildren(series, index, studies); + studies.clear(); // This information is useless below + + if (query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES)) + { + result.SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES, + boost::lexical_cast(series.size())); + } + + if (!query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES)) + { + return; + } + + std::list instances; + GetChildren(instances, index, series); + + if (query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES)) + { + result.SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES, + boost::lexical_cast(instances.size())); + } + } + + + static void ComputeStudyCounters(DicomMap& result, + ServerContext& context, + const std::string& study, + const DicomMap& query) + { + ServerIndex& index = context.GetIndex(); + + std::list series; + index.GetChildren(series, study); + + if (query.HasTag(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES)) + { + result.SetValue(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES, + boost::lexical_cast(series.size())); + } + + if (query.HasTag(DICOM_TAG_MODALITIES_IN_STUDY)) + { + std::set values; + ExtractTagFromMainDicomTags(values, index, DICOM_TAG_MODALITY, series, ResourceType_Series); + StoreSetOfStrings(result, DICOM_TAG_MODALITIES_IN_STUDY, values); + } + + if (!query.HasTag(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES) && + !query.HasTag(DICOM_TAG_SOP_CLASSES_IN_STUDY)) + { + return; + } + + std::list instances; + GetChildren(instances, index, series); + + if (query.HasTag(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES)) + { + result.SetValue(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES, + boost::lexical_cast(instances.size())); + } + + if (query.HasTag(DICOM_TAG_SOP_CLASSES_IN_STUDY)) + { + std::set values; + ExtractTagFromInstances(values, context, DICOM_TAG_SOP_CLASS_UID, instances); + StoreSetOfStrings(result, DICOM_TAG_SOP_CLASSES_IN_STUDY, values); + } + } + + + static void ComputeSeriesCounters(DicomMap& result, + ServerIndex& index, + const std::string& series, + const DicomMap& query) + { + std::list instances; + index.GetChildren(instances, series); + + if (query.HasTag(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES)) + { + result.SetValue(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES, + boost::lexical_cast(instances.size())); + } + } + + + static DicomMap* ComputeCounters(ServerContext& context, + const std::string& instanceId, + ResourceType level, + const DicomMap& query) + { + switch (level) + { + case ResourceType_Patient: + if (!query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES) && + !query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES) && + !query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES)) + { + return NULL; + } + + break; + + case ResourceType_Study: + if (!query.HasTag(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES) && + !query.HasTag(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES) && + !query.HasTag(DICOM_TAG_SOP_CLASSES_IN_STUDY) && + !query.HasTag(DICOM_TAG_MODALITIES_IN_STUDY)) + { + return NULL; + } + + break; + + case ResourceType_Series: + if (!query.HasTag(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES)) + { + return NULL; + } + + break; + + default: + return NULL; + } + + std::string parent; + if (!context.GetIndex().LookupParent(parent, instanceId, level)) + { + throw OrthancException(ErrorCode_UnknownResource); // The resource was deleted in between + } + + std::auto_ptr result(new DicomMap); + + switch (level) + { + case ResourceType_Patient: + ComputePatientCounters(*result, context.GetIndex(), parent, query); + break; + + case ResourceType_Study: + ComputeStudyCounters(*result, context, parent, query); + break; + + case ResourceType_Series: + ComputeSeriesCounters(*result, context.GetIndex(), parent, query); + break; + + default: + throw OrthancException(ErrorCode_InternalError); + } + + return result.release(); + } + + static void AddAnswer(DicomFindAnswers& answers, const Json::Value& resource, const DicomArray& query, - const std::list& sequencesToReturn) + const std::list& sequencesToReturn, + const DicomMap* counters) { DicomMap result; @@ -80,6 +351,15 @@ } } + if (counters != NULL) + { + DicomArray tmp(*counters); + for (size_t i = 0; i < tmp.GetSize(); i++) + { + result.SetValue(tmp.GetElement(i).GetTag(), tmp.GetElement(i).GetValue().GetContent()); + } + } + if (result.GetSize() == 0 && sequencesToReturn.empty()) { @@ -96,8 +376,6 @@ for (std::list::const_iterator tag = sequencesToReturn.begin(); tag != sequencesToReturn.end(); ++tag) { - std::cout << tag->Format(); - const Json::Value& source = resource[tag->Format()]; if (source.type() == Json::objectValue && @@ -379,7 +657,8 @@ } else { - AddAnswer(answers, dicom, query, sequencesToReturn); + std::auto_ptr counters(ComputeCounters(context_, instances[i], level, input)); + AddAnswer(answers, dicom, query, sequencesToReturn, counters.get()); } } } diff -r 7110e2881dc0 -r e018037d4d0e OrthancServer/ServerIndex.cpp --- a/OrthancServer/ServerIndex.cpp Wed Dec 16 09:23:52 2015 +0100 +++ b/OrthancServer/ServerIndex.cpp Mon Dec 21 19:26:38 2015 +0100 @@ -2177,4 +2177,36 @@ instances[pos] = db_.GetPublicId(instance); } } + + + bool ServerIndex::LookupParent(std::string& target, + const std::string& publicId, + ResourceType parentType) + { + boost::mutex::scoped_lock lock(mutex_); + + ResourceType type; + int64_t id; + if (!db_.LookupResource(id, type, publicId)) + { + throw OrthancException(ErrorCode_UnknownResource); + } + + while (type != parentType) + { + int64_t parentId; + + if (type == ResourceType_Patient || // Cannot further go up in hierarchy + !db_.LookupParent(parentId, id)) + { + return false; + } + + id = parentId; + type = GetParentResourceType(type); + } + + target = db_.GetPublicId(id); + return true; + } } diff -r 7110e2881dc0 -r e018037d4d0e OrthancServer/ServerIndex.h --- a/OrthancServer/ServerIndex.h Wed Dec 16 09:23:52 2015 +0100 +++ b/OrthancServer/ServerIndex.h Mon Dec 21 19:26:38 2015 +0100 @@ -264,5 +264,9 @@ void FindCandidates(std::vector& resources, std::vector& instances, const ::Orthanc::LookupResource& lookup); + + bool LookupParent(std::string& target, + const std::string& publicId, + ResourceType parentType); }; }