# HG changeset patch # User Alain Mazy # Date 1646821028 -3600 # Node ID b7ce2bb6b881bad8ec8cc870b907010af2daa8e2 # Parent 1ce32c1ec4cfcfadc140f2d73d6ce1e142164777 refactored the list of MainDicomTags to be able to change it dynamicaly. Unit tests and Integration tests ok diff -r 1ce32c1ec4cf -r b7ce2bb6b881 OrthancFramework/Resources/CodeGeneration/ErrorCodes.json --- a/OrthancFramework/Resources/CodeGeneration/ErrorCodes.json Mon Mar 07 10:55:43 2022 +0100 +++ b/OrthancFramework/Resources/CodeGeneration/ErrorCodes.json Wed Mar 09 11:17:08 2022 +0100 @@ -245,6 +245,11 @@ "Name": "Revision", "Description": "A bad revision number was provided, which might indicate conflict between multiple writers" }, + { + "Code": 44, + "Name": "MainDicomTagsMultiplyDefined", + "Description": "A main DICOM Tag has been defined multiple times for the same resource level" + }, diff -r 1ce32c1ec4cf -r b7ce2bb6b881 OrthancFramework/Sources/DicomFormat/DicomMap.cpp --- a/OrthancFramework/Sources/DicomFormat/DicomMap.cpp Mon Mar 07 10:55:43 2022 +0100 +++ b/OrthancFramework/Sources/DicomFormat/DicomMap.cpp Wed Mar 09 11:17:08 2022 +0100 @@ -44,26 +44,29 @@ const DicomTag tag_; const char* name_; }; + typedef std::vector MainDicomTags; + } - static const MainDicomTag PATIENT_MAIN_DICOM_TAGS[] = + + static const MainDicomTag DEFAULT_PATIENT_MAIN_DICOM_TAGS[] = { // { DicomTag(0x0010, 0x1010), "PatientAge" }, // { DicomTag(0x0010, 0x1040), "PatientAddress" }, - { DicomTag(0x0010, 0x0010), "PatientName" }, - { DicomTag(0x0010, 0x0030), "PatientBirthDate" }, - { DicomTag(0x0010, 0x0040), "PatientSex" }, - { DicomTag(0x0010, 0x1000), "OtherPatientIDs" }, + { DICOM_TAG_PATIENT_NAME, "PatientName" }, + { DICOM_TAG_PATIENT_BIRTH_DATE, "PatientBirthDate" }, + { DICOM_TAG_PATIENT_SEX, "PatientSex" }, + { DICOM_TAG_OTHER_PATIENT_IDS, "OtherPatientIDs" }, { DICOM_TAG_PATIENT_ID, "PatientID" } }; - - static const MainDicomTag STUDY_MAIN_DICOM_TAGS[] = + + static const MainDicomTag DEFAULT_STUDY_MAIN_DICOM_TAGS[] = { // { DicomTag(0x0010, 0x1020), "PatientSize" }, // { DicomTag(0x0010, 0x1030), "PatientWeight" }, { DICOM_TAG_STUDY_DATE, "StudyDate" }, - { DicomTag(0x0008, 0x0030), "StudyTime" }, - { DicomTag(0x0020, 0x0010), "StudyID" }, + { DICOM_TAG_STUDY_TIME, "StudyTime" }, + { DICOM_TAG_STUDY_ID, "StudyID" }, { DICOM_TAG_STUDY_DESCRIPTION, "StudyDescription" }, { DICOM_TAG_ACCESSION_NUMBER, "AccessionNumber" }, { DICOM_TAG_STUDY_INSTANCE_UID, "StudyInstanceUID" }, @@ -74,20 +77,20 @@ { DICOM_TAG_REQUESTING_PHYSICIAN, "RequestingPhysician" }, { DICOM_TAG_REFERRING_PHYSICIAN_NAME, "ReferringPhysicianName" } }; - - static const MainDicomTag SERIES_MAIN_DICOM_TAGS[] = + + static const MainDicomTag DEFAULT_SERIES_MAIN_DICOM_TAGS[] = { // { DicomTag(0x0010, 0x1080), "MilitaryRank" }, - { DicomTag(0x0008, 0x0021), "SeriesDate" }, - { DicomTag(0x0008, 0x0031), "SeriesTime" }, + { DICOM_TAG_SERIES_DATE, "SeriesDate" }, + { DICOM_TAG_SERIES_TIME, "SeriesTime" }, { DICOM_TAG_MODALITY, "Modality" }, - { DicomTag(0x0008, 0x0070), "Manufacturer" }, - { DicomTag(0x0008, 0x1010), "StationName" }, + { DICOM_TAG_MANUFACTURER, "Manufacturer" }, + { DICOM_TAG_STATION_NAME, "StationName" }, { DICOM_TAG_SERIES_DESCRIPTION, "SeriesDescription" }, - { DicomTag(0x0018, 0x0015), "BodyPartExamined" }, - { DicomTag(0x0018, 0x0024), "SequenceName" }, - { DicomTag(0x0018, 0x1030), "ProtocolName" }, - { DicomTag(0x0020, 0x0011), "SeriesNumber" }, + { DICOM_TAG_BODY_PART_EXAMINED, "BodyPartExamined" }, + { DICOM_TAG_SEQUENCE_NAME, "SequenceName" }, + { DICOM_TAG_PROTOCOL_NAME, "ProtocolName" }, + { DICOM_TAG_SERIES_NUMBER, "SeriesNumber" }, { DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES, "CardiacNumberOfImages" }, { DICOM_TAG_IMAGES_IN_ACQUISITION, "ImagesInAcquisition" }, { DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS, "NumberOfTemporalPositions" }, @@ -103,12 +106,12 @@ { DICOM_TAG_ACQUISITION_DEVICE_PROCESSING_DESCRIPTION, "AcquisitionDeviceProcessingDescription" }, { DICOM_TAG_CONTRAST_BOLUS_AGENT, "ContrastBolusAgent" } }; - - static const MainDicomTag INSTANCE_MAIN_DICOM_TAGS[] = + + static const MainDicomTag DEFAULT_INSTANCE_MAIN_DICOM_TAGS[] = { - { DicomTag(0x0008, 0x0012), "InstanceCreationDate" }, - { DicomTag(0x0008, 0x0013), "InstanceCreationTime" }, - { DicomTag(0x0020, 0x0012), "AcquisitionNumber" }, + { DICOM_TAG_INSTANCE_CREATION_DATE, "InstanceCreationDate" }, + { DICOM_TAG_INSTANCE_CREATION_TIME, "InstanceCreationTime" }, + { DICOM_TAG_ACQUISITION_NUMBER, "AcquisitionNumber" }, { DICOM_TAG_IMAGE_INDEX, "ImageIndex" }, { DICOM_TAG_INSTANCE_NUMBER, "InstanceNumber" }, { DICOM_TAG_NUMBER_OF_FRAMES, "NumberOfFrames" }, @@ -130,57 +133,6 @@ }; - static void LoadMainDicomTags(const MainDicomTag*& tags, - size_t& size, - ResourceType level) - { - switch (level) - { - case ResourceType_Patient: - tags = PATIENT_MAIN_DICOM_TAGS; - size = sizeof(PATIENT_MAIN_DICOM_TAGS) / sizeof(MainDicomTag); - break; - - case ResourceType_Study: - tags = STUDY_MAIN_DICOM_TAGS; - size = sizeof(STUDY_MAIN_DICOM_TAGS) / sizeof(MainDicomTag); - break; - - case ResourceType_Series: - tags = SERIES_MAIN_DICOM_TAGS; - size = sizeof(SERIES_MAIN_DICOM_TAGS) / sizeof(MainDicomTag); - break; - - case ResourceType_Instance: - tags = INSTANCE_MAIN_DICOM_TAGS; - size = sizeof(INSTANCE_MAIN_DICOM_TAGS) / sizeof(MainDicomTag); - break; - - default: - throw OrthancException(ErrorCode_ParameterOutOfRange); - } - } - - - static void LoadMainDicomTags(std::map& target, - ResourceType level) - { - const MainDicomTag* tags = NULL; - size_t size; - LoadMainDicomTags(tags, size, level); - - assert(tags != NULL && - size != 0); - - for (size_t i = 0; i < size; i++) - { - assert(target.find(tags[i].tag_) == target.end()); - - target[tags[i].tag_] = tags[i].name_; - } - } - - namespace { class DicomTag2 : public DicomTag @@ -199,23 +151,132 @@ } - static void LoadMainDicomTags(std::map& target, - ResourceType level) + class DicomMap::MainDicomTagsConfiguration { - const MainDicomTag* tags = NULL; - size_t size; - LoadMainDicomTags(tags, size, level); + private: + friend DicomMap; + + // we keep many "copies" of the same data to guarantee quick access to organized data + // and avoid rebuilding it all the time. + std::map > mainDicomTagsByTag_; + std::map > mainDicomTagsByName_; + std::map > mainDicomTagsByLevel_; + std::set allMainDicomTags_; + + MainDicomTagsConfiguration() + { + ResetDefaultMainDicomTags(); + } + + void ResetDefaultMainDicomTags() + { + mainDicomTagsByTag_.clear(); + mainDicomTagsByName_.clear(); + mainDicomTagsByLevel_.clear(); + allMainDicomTags_.clear(); + + // by default, initialize with the previous static list (up to 1.10.0) + LoadDefaultMainDicomTags(ResourceType_Patient); + LoadDefaultMainDicomTags(ResourceType_Study); + LoadDefaultMainDicomTags(ResourceType_Series); + LoadDefaultMainDicomTags(ResourceType_Instance); + } + + void LoadDefaultMainDicomTags(ResourceType level) + { + assert(mainDicomTagsByTag_.find(level) == mainDicomTagsByTag_.end()); + + const MainDicomTag* tags = NULL; + size_t size; + + switch (level) + { + case ResourceType_Patient: + tags = DEFAULT_PATIENT_MAIN_DICOM_TAGS; + size = sizeof(DEFAULT_PATIENT_MAIN_DICOM_TAGS) / sizeof(MainDicomTag); + break; + + case ResourceType_Study: + tags = DEFAULT_STUDY_MAIN_DICOM_TAGS; + size = sizeof(DEFAULT_STUDY_MAIN_DICOM_TAGS) / sizeof(MainDicomTag); + break; + + case ResourceType_Series: + tags = DEFAULT_SERIES_MAIN_DICOM_TAGS; + size = sizeof(DEFAULT_SERIES_MAIN_DICOM_TAGS) / sizeof(MainDicomTag); + break; + + case ResourceType_Instance: + tags = DEFAULT_INSTANCE_MAIN_DICOM_TAGS; + size = sizeof(DEFAULT_INSTANCE_MAIN_DICOM_TAGS) / sizeof(MainDicomTag); + break; - assert(tags != NULL && - size != 0); + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + assert(tags != NULL && + size != 0); + + for (size_t i = 0; i < size; i++) + { + AddMainDicomTag(tags[i].tag_, tags[i].name_, level); + } - for (size_t i = 0; i < size; i++) + } + + public: + // Singleton pattern + static MainDicomTagsConfiguration& GetInstance() + { + static MainDicomTagsConfiguration parameters; + return parameters; + } + + void AddMainDicomTag(const DicomTag& tag, const std::string& name, ResourceType level) { - assert(target.find(tags[i].name_) == target.end()); - - target[tags[i].name_] = DicomTag2(tags[i].tag_); + if (mainDicomTagsByTag_[level].find(tag) != mainDicomTagsByTag_[level].end()) + { + throw OrthancException(ErrorCode_MainDicomTagsMultiplyDefined, tag.Format() + " is already defined"); + } + + if (mainDicomTagsByName_[level].find(name) != mainDicomTagsByName_[level].end()) + { + throw OrthancException(ErrorCode_MainDicomTagsMultiplyDefined, name + " is already defined"); + } + + mainDicomTagsByTag_[level][tag] = name; + mainDicomTagsByName_[level][name] = DicomTag2(tag); + mainDicomTagsByLevel_[level].insert(tag); + allMainDicomTags_.insert(tag); } - } + + const std::map& GetMainDicomTags(ResourceType level) const + { + assert(mainDicomTagsByTag_.find(level) != mainDicomTagsByTag_.end()); + + return mainDicomTagsByTag_.at(level); + } + + const std::map& GetMainDicomTagsByName(ResourceType level) const + { + assert(mainDicomTagsByName_.find(level) != mainDicomTagsByName_.end()); + + return mainDicomTagsByName_.at(level); + } + + const std::set& GetMainDicomTagsByLevel(ResourceType level) const + { + assert(mainDicomTagsByLevel_.find(level) != mainDicomTagsByLevel_.end()); + + return mainDicomTagsByLevel_.at(level); + } + + const std::set& GetAllMainDicomTags() const + { + return allMainDicomTags_; + } + }; void DicomMap::SetValueInternal(uint16_t group, @@ -296,14 +357,14 @@ static void ExtractTags(DicomMap& result, const DicomMap::Content& source, - const MainDicomTag* tags, - size_t count) + const std::map& mainDicomTags) { result.Clear(); - for (unsigned int i = 0; i < count; i++) + for (std::map::const_iterator itmt = mainDicomTags.begin(); + itmt != mainDicomTags.end(); itmt++) { - DicomMap::Content::const_iterator it = source.find(tags[i].tag_); + DicomMap::Content::const_iterator it = source.find(itmt->first); if (it != source.end()) { result.SetValue(it->first, *it->second /* value will be cloned */); @@ -314,22 +375,26 @@ void DicomMap::ExtractPatientInformation(DicomMap& result) const { - ExtractTags(result, content_, PATIENT_MAIN_DICOM_TAGS, sizeof(PATIENT_MAIN_DICOM_TAGS) / sizeof(MainDicomTag)); + const std::map& mainDicomTags = DicomMap::MainDicomTagsConfiguration::GetInstance().GetMainDicomTags(ResourceType_Patient); + ExtractTags(result, content_, mainDicomTags); } void DicomMap::ExtractStudyInformation(DicomMap& result) const { - ExtractTags(result, content_, STUDY_MAIN_DICOM_TAGS, sizeof(STUDY_MAIN_DICOM_TAGS) / sizeof(MainDicomTag)); + const std::map& mainDicomTags = DicomMap::MainDicomTagsConfiguration::GetInstance().GetMainDicomTags(ResourceType_Study); + ExtractTags(result, content_, mainDicomTags); } void DicomMap::ExtractSeriesInformation(DicomMap& result) const { - ExtractTags(result, content_, SERIES_MAIN_DICOM_TAGS, sizeof(SERIES_MAIN_DICOM_TAGS) / sizeof(MainDicomTag)); + const std::map& mainDicomTags = DicomMap::MainDicomTagsConfiguration::GetInstance().GetMainDicomTags(ResourceType_Series); + ExtractTags(result, content_, mainDicomTags); } void DicomMap::ExtractInstanceInformation(DicomMap& result) const { - ExtractTags(result, content_, INSTANCE_MAIN_DICOM_TAGS, sizeof(INSTANCE_MAIN_DICOM_TAGS) / sizeof(MainDicomTag)); + const std::map& mainDicomTags = DicomMap::MainDicomTagsConfiguration::GetInstance().GetMainDicomTags(ResourceType_Instance); + ExtractTags(result, content_, mainDicomTags); } @@ -415,25 +480,27 @@ static void SetupFindTemplate(DicomMap& result, - const MainDicomTag* tags, - size_t count) + const std::map& mainDicomTags) { result.Clear(); - for (size_t i = 0; i < count; i++) + for (std::map::const_iterator itmt = mainDicomTags.begin(); + itmt != mainDicomTags.end(); itmt++) { - result.SetValue(tags[i].tag_, "", false); + result.SetValue(itmt->first, "", false); } } void DicomMap::SetupFindPatientTemplate(DicomMap& result) { - SetupFindTemplate(result, PATIENT_MAIN_DICOM_TAGS, sizeof(PATIENT_MAIN_DICOM_TAGS) / sizeof(MainDicomTag)); + const std::map& mainDicomTags = DicomMap::MainDicomTagsConfiguration::GetInstance().GetMainDicomTags(ResourceType_Patient); + SetupFindTemplate(result, mainDicomTags); } void DicomMap::SetupFindStudyTemplate(DicomMap& result) { - SetupFindTemplate(result, STUDY_MAIN_DICOM_TAGS, sizeof(STUDY_MAIN_DICOM_TAGS) / sizeof(MainDicomTag)); + const std::map& mainDicomTags = DicomMap::MainDicomTagsConfiguration::GetInstance().GetMainDicomTags(ResourceType_Study); + SetupFindTemplate(result, mainDicomTags); result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false); result.SetValue(DICOM_TAG_PATIENT_ID, "", false); @@ -446,7 +513,8 @@ void DicomMap::SetupFindSeriesTemplate(DicomMap& result) { - SetupFindTemplate(result, SERIES_MAIN_DICOM_TAGS, sizeof(SERIES_MAIN_DICOM_TAGS) / sizeof(MainDicomTag)); + const std::map& mainDicomTags = DicomMap::MainDicomTagsConfiguration::GetInstance().GetMainDicomTags(ResourceType_Series); + SetupFindTemplate(result, mainDicomTags); result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false); result.SetValue(DICOM_TAG_PATIENT_ID, "", false); result.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "", false); @@ -468,7 +536,8 @@ void DicomMap::SetupFindInstanceTemplate(DicomMap& result) { - SetupFindTemplate(result, INSTANCE_MAIN_DICOM_TAGS, sizeof(INSTANCE_MAIN_DICOM_TAGS) / sizeof(MainDicomTag)); + const std::map& mainDicomTags = DicomMap::MainDicomTagsConfiguration::GetInstance().GetMainDicomTags(ResourceType_Instance); + SetupFindTemplate(result, mainDicomTags); result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false); result.SetValue(DICOM_TAG_PATIENT_ID, "", false); result.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "", false); @@ -488,19 +557,8 @@ bool DicomMap::IsMainDicomTag(const DicomTag& tag, ResourceType level) { - const MainDicomTag *tags = NULL; - size_t size; - LoadMainDicomTags(tags, size, level); - - for (size_t i = 0; i < size; i++) - { - if (tags[i].tag_ == tag) - { - return true; - } - } - - return false; + const std::map& mainDicomTags = DicomMap::MainDicomTagsConfiguration::GetInstance().GetMainDicomTags(level); + return mainDicomTags.find(tag) != mainDicomTags.end(); } bool DicomMap::IsMainDicomTag(const DicomTag& tag) @@ -511,37 +569,26 @@ IsMainDicomTag(tag, ResourceType_Instance)); } - - void DicomMap::GetMainDicomTagsInternal(std::set& result, ResourceType level) + const std::set& DicomMap::GetMainDicomTags(ResourceType level) { - const MainDicomTag *tags = NULL; - size_t size; - LoadMainDicomTags(tags, size, level); + return DicomMap::MainDicomTagsConfiguration::GetInstance().GetMainDicomTagsByLevel(level); + } - for (size_t i = 0; i < size; i++) - { - result.insert(tags[i].tag_); - } + const std::set& DicomMap::GetAllMainDicomTags() + { + return DicomMap::MainDicomTagsConfiguration::GetInstance().GetAllMainDicomTags(); } - - void DicomMap::GetMainDicomTags(std::set& result, ResourceType level) + void DicomMap::AddMainDicomTag(const DicomTag& tag, const std::string& name, ResourceType level) { - result.clear(); - GetMainDicomTagsInternal(result, level); + DicomMap::MainDicomTagsConfiguration::GetInstance().AddMainDicomTag(tag, name, level); } - - void DicomMap::GetMainDicomTags(std::set& result) + void DicomMap::ResetDefaultMainDicomTags() { - result.clear(); - GetMainDicomTagsInternal(result, ResourceType_Patient); - GetMainDicomTagsInternal(result, ResourceType_Study); - GetMainDicomTagsInternal(result, ResourceType_Series); - GetMainDicomTagsInternal(result, ResourceType_Instance); + DicomMap::MainDicomTagsConfiguration::GetInstance().ResetDefaultMainDicomTags(); } - void DicomMap::GetTags(std::set& tags) const { tags.clear(); @@ -1201,21 +1248,18 @@ void DicomMap::MergeMainDicomTags(const DicomMap& other, ResourceType level) { - const MainDicomTag* tags = NULL; - size_t size = 0; + const std::map& mainDicomTags = DicomMap::MainDicomTagsConfiguration::GetInstance().GetMainDicomTags(level); - LoadMainDicomTags(tags, size, level); - assert(tags != NULL && size > 0); - - for (size_t i = 0; i < size; i++) + for (std::map::const_iterator itmt = mainDicomTags.begin(); + itmt != mainDicomTags.end(); itmt++) { - Content::const_iterator found = other.content_.find(tags[i].tag_); + Content::const_iterator found = other.content_.find(itmt->first); if (found != other.content_.end() && - content_.find(tags[i].tag_) == content_.end()) + content_.find(itmt->first) == content_.end()) { assert(found->second != NULL); - content_[tags[i].tag_] = found->second->Clone(); + content_[itmt->first] = found->second->Clone(); } } } @@ -1233,14 +1277,11 @@ bool DicomMap::HasOnlyMainDicomTags() const { - // TODO - Speed up possible by making this std::set a global variable - - std::set mainDicomTags; - GetMainDicomTags(mainDicomTags); + const std::set& allMainDicomTags = DicomMap::MainDicomTagsConfiguration::GetInstance().GetAllMainDicomTags(); for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it) { - if (mainDicomTags.find(it->first) == mainDicomTags.end()) + if (allMainDicomTags.find(it->first) == allMainDicomTags.end()) { return false; } @@ -1475,8 +1516,7 @@ void DicomMap::DumpMainDicomTags(Json::Value& target, ResourceType level) const { - std::map mainTags; // TODO - Create a singleton to hold this map - LoadMainDicomTags(mainTags, level); + const std::map& mainDicomTags = DicomMap::MainDicomTagsConfiguration::GetInstance().GetMainDicomTags(level); target = Json::objectValue; @@ -1487,9 +1527,9 @@ if (!it->second->IsBinary() && !it->second->IsNull()) { - std::map::const_iterator found = mainTags.find(it->first); + std::map::const_iterator found = mainDicomTags.find(it->first); - if (found != mainTags.end()) + if (found != mainDicomTags.end()) { target[found->second] = it->second->GetContent(); } @@ -1506,8 +1546,7 @@ throw OrthancException(ErrorCode_BadFileFormat); } - std::map mainTags; // TODO - Create a singleton to hold this map - LoadMainDicomTags(mainTags, level); + const std::map& mainTags = DicomMap::MainDicomTagsConfiguration::GetInstance().GetMainDicomTagsByName(level); Json::Value::Members members = source.getMemberNames(); for (size_t i = 0; i < members.size(); i++) diff -r 1ce32c1ec4cf -r b7ce2bb6b881 OrthancFramework/Sources/DicomFormat/DicomMap.h --- a/OrthancFramework/Sources/DicomFormat/DicomMap.h Mon Mar 07 10:55:43 2022 +0100 +++ b/OrthancFramework/Sources/DicomFormat/DicomMap.h Wed Mar 09 11:17:08 2022 +0100 @@ -31,18 +31,27 @@ #include #include +#if ORTHANC_BUILD_UNIT_TESTS == 1 +# include +#endif + namespace Orthanc { class ORTHANC_PUBLIC DicomMap : public boost::noncopyable { public: typedef std::map Content; - + private: + class MainDicomTagsConfiguration; friend class DicomArray; friend class FromDcmtkBridge; friend class ParsedDicomFile; +#if ORTHANC_BUILD_UNIT_TESTS == 1 + friend class DicomMapMainTagsTests; +#endif + Content content_; // Warning: This takes the ownership of "value" @@ -50,8 +59,8 @@ uint16_t element, DicomValue* value); - static void GetMainDicomTagsInternal(std::set& result, - ResourceType level); + // used for unit tests only + static void ResetDefaultMainDicomTags(); public: ~DicomMap(); @@ -124,9 +133,13 @@ static bool IsMainDicomTag(const DicomTag& tag); - static void GetMainDicomTags(std::set& result, ResourceType level); + static const std::set& GetMainDicomTags(ResourceType level); + + static const std::set& GetAllMainDicomTags(); - static void GetMainDicomTags(std::set& result); + // adds a main dicom tag to the definition of main dicom tags for each level. + // this should be done once at startup before you use MainDicomTags methods + static void AddMainDicomTag(const DicomTag& tag, const std::string& name, ResourceType level); void GetTags(std::set& tags) const; diff -r 1ce32c1ec4cf -r b7ce2bb6b881 OrthancFramework/Sources/DicomFormat/DicomTag.h --- a/OrthancFramework/Sources/DicomFormat/DicomTag.h Mon Mar 07 10:55:43 2022 +0100 +++ b/OrthancFramework/Sources/DicomFormat/DicomTag.h Wed Mar 09 11:17:08 2022 +0100 @@ -166,8 +166,11 @@ static const DicomTag DICOM_TAG_PATIENT_SEX(0x0010, 0x0040); static const DicomTag DICOM_TAG_LATERALITY(0x0020, 0x0060); static const DicomTag DICOM_TAG_BODY_PART_EXAMINED(0x0018, 0x0015); + static const DicomTag DICOM_TAG_SEQUENCE_NAME(0x0018, 0x0024); + static const DicomTag DICOM_TAG_PROTOCOL_NAME(0x0018, 0x1030); static const DicomTag DICOM_TAG_VIEW_POSITION(0x0018, 0x5101); static const DicomTag DICOM_TAG_MANUFACTURER(0x0008, 0x0070); + static const DicomTag DICOM_TAG_STATION_NAME(0x0008, 0x1010); static const DicomTag DICOM_TAG_PATIENT_ORIENTATION(0x0020, 0x0020); static const DicomTag DICOM_TAG_PATIENT_COMMENTS(0x0010, 0x4000); static const DicomTag DICOM_TAG_PATIENT_SPECIES_DESCRIPTION(0x0010, 0x2201); @@ -176,6 +179,7 @@ static const DicomTag DICOM_TAG_PER_FRAME_FUNCTIONAL_GROUP_SEQUENCE(0x5200, 0x9230); static const DicomTag DICOM_TAG_PIXEL_VALUE_TRANSFORMATION_SEQUENCE(0x0028, 0x9145); static const DicomTag DICOM_TAG_FRAME_VOI_LUT_SEQUENCE(0x0028, 0x9132); + static const DicomTag DICOM_TAG_ACQUISITION_NUMBER(0x0020, 0x0012); // Tags used within the Stone of Orthanc static const DicomTag DICOM_TAG_FRAME_INCREMENT_POINTER(0x0028, 0x0009); diff -r 1ce32c1ec4cf -r b7ce2bb6b881 OrthancFramework/Sources/Enumerations.cpp --- a/OrthancFramework/Sources/Enumerations.cpp Mon Mar 07 10:55:43 2022 +0100 +++ b/OrthancFramework/Sources/Enumerations.cpp Wed Mar 09 11:17:08 2022 +0100 @@ -55,7 +55,7 @@ static const char* const MIME_ICO = "image/x-icon"; // This function is autogenerated by the script - // "Resources/GenerateErrorCodes.py" + // "Resources/CodeGeneration/GenerateErrorCodes.py" const char* EnumerationToString(ErrorCode error) { switch (error) @@ -195,6 +195,9 @@ case ErrorCode_Revision: return "A bad revision number was provided, which might indicate conflict between multiple writers"; + case ErrorCode_MainDicomTagsMultiplyDefined: + return "A main DICOM Tag has been defined multiple times for the same resource level"; + case ErrorCode_SQLiteNotOpened: return "SQLite: The database is not opened"; @@ -2159,7 +2162,7 @@ // This function is autogenerated by the script - // "Resources/GenerateErrorCodes.py" + // "Resources/CodeGeneration/GenerateErrorCodes.py" HttpStatus ConvertErrorCodeToHttpStatus(ErrorCode error) { switch (error) diff -r 1ce32c1ec4cf -r b7ce2bb6b881 OrthancFramework/Sources/Enumerations.h --- a/OrthancFramework/Sources/Enumerations.h Mon Mar 07 10:55:43 2022 +0100 +++ b/OrthancFramework/Sources/Enumerations.h Wed Mar 09 11:17:08 2022 +0100 @@ -91,7 +91,7 @@ }; // This enumeration is autogenerated by the script - // "Resources/GenerateErrorCodes.py" + // "Resources/CodeGeneration/GenerateErrorCodes.py" enum ErrorCode { ErrorCode_InternalError = -1 /*!< Internal error */, @@ -139,6 +139,7 @@ ErrorCode_BadRange = 41 /*!< Incorrect range request */, ErrorCode_DatabaseCannotSerialize = 42 /*!< Database could not serialize access due to concurrent update, the transaction should be retried */, ErrorCode_Revision = 43 /*!< A bad revision number was provided, which might indicate conflict between multiple writers */, + ErrorCode_MainDicomTagsMultiplyDefined = 44 /*!< A main DICOM Tag has been defined multiple times for the same resource level */, ErrorCode_SQLiteNotOpened = 1000 /*!< SQLite: The database is not opened */, ErrorCode_SQLiteAlreadyOpened = 1001 /*!< SQLite: Connection is already open */, ErrorCode_SQLiteCannotOpen = 1002 /*!< SQLite: Unable to open the database */, diff -r 1ce32c1ec4cf -r b7ce2bb6b881 OrthancFramework/Sources/SQLite/OrthancSQLiteException.h --- a/OrthancFramework/Sources/SQLite/OrthancSQLiteException.h Mon Mar 07 10:55:43 2022 +0100 +++ b/OrthancFramework/Sources/SQLite/OrthancSQLiteException.h Wed Mar 09 11:17:08 2022 +0100 @@ -52,7 +52,7 @@ { namespace SQLite { - // Auto-generated by "Resources/GenerateErrorCodes.py" + // Auto-generated by "Resources/CodeGeneration/GenerateErrorCodes.py" enum ErrorCode { ErrorCode_ParameterOutOfRange, @@ -83,7 +83,7 @@ { } - // Auto-generated by "Resources/GenerateErrorCodes.py" + // Auto-generated by "Resources/CodeGeneration/GenerateErrorCodes.py" static const char* EnumerationToString(ErrorCode code) { switch (code) diff -r 1ce32c1ec4cf -r b7ce2bb6b881 OrthancFramework/UnitTestsSources/DicomMapTests.cpp --- a/OrthancFramework/UnitTestsSources/DicomMapTests.cpp Mon Mar 07 10:55:43 2022 +0100 +++ b/OrthancFramework/UnitTestsSources/DicomMapTests.cpp Wed Mar 09 11:17:08 2022 +0100 @@ -45,44 +45,101 @@ using namespace Orthanc; -TEST(DicomMap, MainTags) + +namespace Orthanc { - ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_PATIENT_ID)); - ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_PATIENT_ID, ResourceType_Patient)); - ASSERT_FALSE(DicomMap::IsMainDicomTag(DICOM_TAG_PATIENT_ID, ResourceType_Study)); + // The namespace is necessary because of FRIEND_TEST + // http://code.google.com/p/googletest/wiki/AdvancedGuide#Private_Class_Members + + class DicomMapMainTagsTests : public ::testing::Test + { + public: + DicomMapMainTagsTests() + { + } + + virtual void SetUp() ORTHANC_OVERRIDE + { + DicomMap::ResetDefaultMainDicomTags(); + } - ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_STUDY_INSTANCE_UID)); - ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_ACCESSION_NUMBER)); - ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_SERIES_INSTANCE_UID)); - ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_SOP_INSTANCE_UID)); + virtual void TearDown() ORTHANC_OVERRIDE + { + DicomMap::ResetDefaultMainDicomTags(); + } + }; + + TEST_F(DicomMapMainTagsTests, MainTags) + { + ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_PATIENT_ID)); + ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_PATIENT_ID, ResourceType_Patient)); + ASSERT_FALSE(DicomMap::IsMainDicomTag(DICOM_TAG_PATIENT_ID, ResourceType_Study)); - std::set s; - DicomMap::GetMainDicomTags(s); - ASSERT_TRUE(s.end() != s.find(DICOM_TAG_PATIENT_ID)); - ASSERT_TRUE(s.end() != s.find(DICOM_TAG_STUDY_INSTANCE_UID)); - ASSERT_TRUE(s.end() != s.find(DICOM_TAG_ACCESSION_NUMBER)); - ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SERIES_INSTANCE_UID)); - ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SOP_INSTANCE_UID)); + ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_STUDY_INSTANCE_UID)); + ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_ACCESSION_NUMBER)); + ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_SERIES_INSTANCE_UID)); + ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_SOP_INSTANCE_UID)); + + { + const std::set& s = DicomMap::GetAllMainDicomTags(); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_PATIENT_ID)); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_STUDY_INSTANCE_UID)); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_ACCESSION_NUMBER)); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SERIES_INSTANCE_UID)); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SOP_INSTANCE_UID)); + } - DicomMap::GetMainDicomTags(s, ResourceType_Patient); - ASSERT_TRUE(s.end() != s.find(DICOM_TAG_PATIENT_ID)); - ASSERT_TRUE(s.end() == s.find(DICOM_TAG_STUDY_INSTANCE_UID)); + { + const std::set& s = DicomMap::GetMainDicomTags(ResourceType_Patient); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_PATIENT_ID)); + ASSERT_TRUE(s.end() == s.find(DICOM_TAG_STUDY_INSTANCE_UID)); + } - DicomMap::GetMainDicomTags(s, ResourceType_Study); - ASSERT_TRUE(s.end() != s.find(DICOM_TAG_STUDY_INSTANCE_UID)); - ASSERT_TRUE(s.end() != s.find(DICOM_TAG_ACCESSION_NUMBER)); - ASSERT_TRUE(s.end() == s.find(DICOM_TAG_PATIENT_ID)); + { + const std::set& s = DicomMap::GetMainDicomTags(ResourceType_Study); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_STUDY_INSTANCE_UID)); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_ACCESSION_NUMBER)); + ASSERT_TRUE(s.end() == s.find(DICOM_TAG_PATIENT_ID)); + } + + { + const std::set& s = DicomMap::GetMainDicomTags(ResourceType_Series); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SERIES_INSTANCE_UID)); + ASSERT_TRUE(s.end() == s.find(DICOM_TAG_PATIENT_ID)); + } - DicomMap::GetMainDicomTags(s, ResourceType_Series); - ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SERIES_INSTANCE_UID)); - ASSERT_TRUE(s.end() == s.find(DICOM_TAG_PATIENT_ID)); + { + const std::set& s = DicomMap::GetMainDicomTags(ResourceType_Instance); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SOP_INSTANCE_UID)); + ASSERT_TRUE(s.end() == s.find(DICOM_TAG_PATIENT_ID)); + } + } + + TEST_F(DicomMapMainTagsTests, AddMainTags) + { + DicomMap::AddMainDicomTag(DICOM_TAG_BITS_ALLOCATED, "BitsAllocated", ResourceType_Instance); - DicomMap::GetMainDicomTags(s, ResourceType_Instance); - ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SOP_INSTANCE_UID)); - ASSERT_TRUE(s.end() == s.find(DICOM_TAG_PATIENT_ID)); + { + const std::set& s = DicomMap::GetMainDicomTags(ResourceType_Instance); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_BITS_ALLOCATED)); + ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SOP_INSTANCE_UID)); + } + { + const std::set& s = DicomMap::GetMainDicomTags(ResourceType_Series); + ASSERT_TRUE(s.end() == s.find(DICOM_TAG_BITS_ALLOCATED)); + } + + ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_BITS_ALLOCATED)); + ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_BITS_ALLOCATED, ResourceType_Instance)); + + // adding the same tag should throw + ASSERT_THROW(DicomMap::AddMainDicomTag(DICOM_TAG_BITS_ALLOCATED, "BitsAllocated", ResourceType_Instance), OrthancException); + + // adding another tag with same name should throw + ASSERT_THROW(DicomMap::AddMainDicomTag(DICOM_TAG_BITS_STORED, "BitsAllocated", ResourceType_Instance), OrthancException); + } } - TEST(DicomMap, Tags) { std::set s; @@ -157,9 +214,9 @@ // REFERENCE: DICOM PS3.3 2015c - Information Object Definitions // http://dicom.nema.org/medical/dicom/current/output/html/part03.html - std::set moduleTags, main; + std::set moduleTags; + const std::set& main = DicomMap::GetMainDicomTags(level); DicomTag::AddTagsForModule(moduleTags, module); - DicomMap::GetMainDicomTags(main, level); // The main dicom tags are a subset of the module for (std::set::const_iterator it = main.begin(); it != main.end(); ++it) @@ -622,8 +679,7 @@ { ResourceType level = static_cast(i); - std::set tags; - DicomMap::GetMainDicomTags(tags, level); + const std::set& tags = DicomMap::GetMainDicomTags(level); for (std::set::const_iterator it = tags.begin(); it != tags.end(); ++it) { diff -r 1ce32c1ec4cf -r b7ce2bb6b881 OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h --- a/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Mon Mar 07 10:55:43 2022 +0100 +++ b/OrthancServer/Plugins/Include/orthanc/OrthancCPlugin.h Wed Mar 09 11:17:08 2022 +0100 @@ -243,6 +243,7 @@ OrthancPluginErrorCode_BadRange = 41 /*!< Incorrect range request */, OrthancPluginErrorCode_DatabaseCannotSerialize = 42 /*!< Database could not serialize access due to concurrent update, the transaction should be retried */, OrthancPluginErrorCode_Revision = 43 /*!< A bad revision number was provided, which might indicate conflict between multiple writers */, + OrthancPluginErrorCode_MainDicomTagsMultiplyDefined = 44 /*!< A main DICOM Tag has been defined multiple times for the same resource level */, OrthancPluginErrorCode_SQLiteNotOpened = 1000 /*!< SQLite: The database is not opened */, OrthancPluginErrorCode_SQLiteAlreadyOpened = 1001 /*!< SQLite: Connection is already open */, OrthancPluginErrorCode_SQLiteCannotOpen = 1002 /*!< SQLite: Unable to open the database */, diff -r 1ce32c1ec4cf -r b7ce2bb6b881 OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp --- a/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp Mon Mar 07 10:55:43 2022 +0100 +++ b/OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp Wed Mar 09 11:17:08 2022 +0100 @@ -362,8 +362,7 @@ } { - std::set tags; - DicomMap::GetMainDicomTags(tags, level); + const std::set& tags = DicomMap::GetMainDicomTags(level); for (std::set::const_iterator tag = tags.begin(); tag != tags.end(); ++tag) @@ -3100,6 +3099,7 @@ // Attach the user-specified metadata + // MORE_TAGS: TODO store the mainDicomTags list in metadata for (MetadataMap::const_iterator it = metadata_.begin(); it != metadata_.end(); ++it) diff -r 1ce32c1ec4cf -r b7ce2bb6b881 OrthancServer/Sources/Search/DatabaseLookup.cpp --- a/OrthancServer/Sources/Search/DatabaseLookup.cpp Mon Mar 07 10:55:43 2022 +0100 +++ b/OrthancServer/Sources/Search/DatabaseLookup.cpp Wed Mar 09 11:17:08 2022 +0100 @@ -281,14 +281,13 @@ bool DatabaseLookup::HasOnlyMainDicomTags() const { - std::set mainTags; - DicomMap::GetMainDicomTags(mainTags); + const std::set& allMainTags = DicomMap::GetAllMainDicomTags(); for (size_t i = 0; i < constraints_.size(); i++) { assert(constraints_[i] != NULL); - if (mainTags.find(constraints_[i]->GetTag()) == mainTags.end()) + if (allMainTags.find(constraints_[i]->GetTag()) == allMainTags.end()) { // This is not a main DICOM tag return false; diff -r 1ce32c1ec4cf -r b7ce2bb6b881 OrthancServer/Sources/ServerToolbox.cpp --- a/OrthancServer/Sources/ServerToolbox.cpp Mon Mar 07 10:55:43 2022 +0100 +++ b/OrthancServer/Sources/ServerToolbox.cpp Wed Mar 09 11:17:08 2022 +0100 @@ -181,7 +181,7 @@ transaction.ClearMainDicomTags(resource); ResourcesContent tags(false /* prevent the setting of metadata */); - tags.AddResource(resource, level, dicomSummary); + tags.AddResource(resource, level, dicomSummary); // MORE_TAGS: re-set the dicomMainTagsList metadata transaction.SetResourcesContent(tags); } catch (OrthancException&) diff -r 1ce32c1ec4cf -r b7ce2bb6b881 OrthancServer/Sources/main.cpp --- a/OrthancServer/Sources/main.cpp Mon Mar 07 10:55:43 2022 +0100 +++ b/OrthancServer/Sources/main.cpp Wed Mar 09 11:17:08 2022 +0100 @@ -746,7 +746,7 @@ << std::endl << std::endl; // The content of the following brackets is automatically generated - // by the "GenerateErrorCodes.py" script + // by the "Resources/CodeGeneration/GenerateErrorCodes.py" script { PrintErrorCode(ErrorCode_InternalError, "Internal error"); PrintErrorCode(ErrorCode_Success, "Success"); @@ -793,6 +793,7 @@ PrintErrorCode(ErrorCode_BadRange, "Incorrect range request"); PrintErrorCode(ErrorCode_DatabaseCannotSerialize, "Database could not serialize access due to concurrent update, the transaction should be retried"); PrintErrorCode(ErrorCode_Revision, "A bad revision number was provided, which might indicate conflict between multiple writers"); + PrintErrorCode(ErrorCode_MainDicomTagsMultiplyDefined, "A main DICOM Tag has been defined multiple times for the same resource level"); PrintErrorCode(ErrorCode_SQLiteNotOpened, "SQLite: The database is not opened"); PrintErrorCode(ErrorCode_SQLiteAlreadyOpened, "SQLite: Connection is already open"); PrintErrorCode(ErrorCode_SQLiteCannotOpen, "SQLite: Unable to open the database");