PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
aira_assessment_manager.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2021-2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
12
13#include <algorithm>
14
15namespace kcenon::pacs::ai {
16
17using namespace kcenon::pacs::core;
18
19// =============================================================================
20// DICOM Tags for parsing assessment SR
21// =============================================================================
22
23namespace manager_tags {
24
25constexpr dicom_tag content_sequence{0x0040, 0xA730};
26constexpr dicom_tag value_type{0x0040, 0xA040};
27constexpr dicom_tag concept_name_code_sequence{0x0040, 0xA043};
28constexpr dicom_tag code_value{0x0008, 0x0100};
29constexpr dicom_tag concept_code_sequence{0x0040, 0xA168};
30constexpr dicom_tag person_name{0x0040, 0xA123};
31constexpr dicom_tag completion_flag{0x0040, 0xA491};
32constexpr dicom_tag referenced_sop_sequence{0x0008, 0x1199};
33constexpr dicom_tag referenced_sop_instance_uid{0x0008, 0x1155};
34
35} // namespace manager_tags
36
37// =============================================================================
38// assessment_manager Implementation
39// =============================================================================
40
42 const core::dicom_dataset& assessment_sr) {
43
44 auto uid = assessment_sr.get_string(tags::sop_instance_uid);
45 if (uid.empty()) {
46 return false;
47 }
48
49 auto info = parse_assessment_info(assessment_sr);
50 datasets_[uid] = assessment_sr;
51 metadata_[uid] = std::move(info);
52
53 return true;
54}
55
56std::optional<core::dicom_dataset> assessment_manager::retrieve_assessment(
57 const std::string& assessment_uid) const {
58
59 auto it = datasets_.find(assessment_uid);
60 if (it == datasets_.end()) {
61 return std::nullopt;
62 }
63 return it->second;
64}
65
66std::vector<assessment_info> assessment_manager::find_by_ai_result(
67 const std::string& ai_result_uid) const {
68
69 std::vector<assessment_info> results;
70 for (const auto& [uid, info] : metadata_) {
71 if (info.ai_result_uid == ai_result_uid) {
72 results.push_back(info);
73 }
74 }
75 return results;
76}
77
78std::vector<assessment_info> assessment_manager::find_by_assessor(
79 const std::string& assessor_name) const {
80
81 std::vector<assessment_info> results;
82 for (const auto& [uid, info] : metadata_) {
83 if (info.assessor_name == assessor_name) {
84 results.push_back(info);
85 }
86 }
87 return results;
88}
89
90std::vector<assessment_info> assessment_manager::find_by_type(
91 assessment_type type) const {
92
93 std::vector<assessment_info> results;
94 for (const auto& [uid, info] : metadata_) {
95 if (info.type == type) {
96 results.push_back(info);
97 }
98 }
99 return results;
100}
101
102std::optional<assessment_info> assessment_manager::get_info(
103 const std::string& assessment_uid) const {
104
105 auto it = metadata_.find(assessment_uid);
106 if (it == metadata_.end()) {
107 return std::nullopt;
108 }
109 return it->second;
110}
111
112bool assessment_manager::exists(const std::string& assessment_uid) const {
113 return datasets_.find(assessment_uid) != datasets_.end();
114}
115
116bool assessment_manager::remove(const std::string& assessment_uid) {
117 auto ds_it = datasets_.find(assessment_uid);
118 if (ds_it == datasets_.end()) {
119 return false;
120 }
121 datasets_.erase(ds_it);
122 metadata_.erase(assessment_uid);
123 return true;
124}
125
126size_t assessment_manager::count() const noexcept {
127 return datasets_.size();
128}
129
130std::map<assessment_type, size_t> assessment_manager::get_statistics() const {
131 std::map<assessment_type, size_t> stats;
132 stats[assessment_type::accept] = 0;
133 stats[assessment_type::modify] = 0;
134 stats[assessment_type::reject] = 0;
135
136 for (const auto& [uid, info] : metadata_) {
137 stats[info.type]++;
138 }
139 return stats;
140}
141
142// =============================================================================
143// Private Methods
144// =============================================================================
145
147 const core::dicom_dataset& sr) const {
148
149 assessment_info info;
150 info.assessment_uid = sr.get_string(tags::sop_instance_uid);
151 info.assessment_time = std::chrono::system_clock::now();
152
153 // Parse completion flag to determine status
154 auto completion = sr.get_string(manager_tags::completion_flag);
155 if (completion == "COMPLETE") {
156 info.status = assessment_status::final_;
157 } else {
158 info.status = assessment_status::draft;
159 }
160
161 // Parse Content Sequence for assessment type and assessor
162 const auto* content_elem = sr.get(manager_tags::content_sequence);
163 if (content_elem && content_elem->is_sequence()) {
164 for (const auto& item : content_elem->sequence_items()) {
165 auto vt = item.get_string(manager_tags::value_type);
166
167 // Extract assessment type from CODE items
168 if (vt == "CODE") {
169 const auto* concept_name = item.get(
171 if (concept_name && concept_name->is_sequence() &&
172 !concept_name->sequence_items().empty()) {
173 auto code = concept_name->sequence_items()[0].get_string(
175 if (code == "AIRA-010") {
176 // Assessment Decision item
177 const auto* concept_code = item.get(
179 if (concept_code && concept_code->is_sequence() &&
180 !concept_code->sequence_items().empty()) {
181 auto type_code =
182 concept_code->sequence_items()[0].get_string(
184 if (type_code == "AIRA-ACCEPT") {
185 info.type = assessment_type::accept;
186 } else if (type_code == "AIRA-MODIFY") {
187 info.type = assessment_type::modify;
188 } else if (type_code == "AIRA-REJECT") {
189 info.type = assessment_type::reject;
190 }
191 }
192 }
193 }
194 }
195
196 // Extract assessor name from PNAME items
197 if (vt == "PNAME") {
198 auto name = item.get_string(manager_tags::person_name);
199 if (!name.empty()) {
200 info.assessor_name = name;
201 }
202 }
203 }
204 }
205
206 // Parse Referenced SOP Sequence for AI result UID
207 const auto* ref_elem = sr.get(manager_tags::referenced_sop_sequence);
208 if (ref_elem && ref_elem->is_sequence() &&
209 !ref_elem->sequence_items().empty()) {
210 info.ai_result_uid = ref_elem->sequence_items()[0].get_string(
212 }
213
214 return info;
215}
216
217} // namespace kcenon::pacs::ai
IHE AIRA Assessment Manager Actor.
bool store_assessment(const core::dicom_dataset &assessment_sr)
Store an assessment SR document.
std::map< std::string, assessment_info > metadata_
std::optional< core::dicom_dataset > retrieve_assessment(const std::string &assessment_uid) const
Retrieve an assessment SR dataset by its UID.
bool exists(const std::string &assessment_uid) const
Check if an assessment exists.
std::vector< assessment_info > find_by_assessor(const std::string &assessor_name) const
Find all assessments by a specific assessor.
assessment_info parse_assessment_info(const core::dicom_dataset &sr) const
bool remove(const std::string &assessment_uid)
Remove an assessment.
std::vector< assessment_info > find_by_type(assessment_type type) const
Find all assessments of a specific type.
std::map< std::string, core::dicom_dataset > datasets_
std::map< assessment_type, size_t > get_statistics() const
Get assessments grouped by type with counts.
size_t count() const noexcept
Get total number of stored assessments.
std::optional< assessment_info > get_info(const std::string &assessment_uid) const
Get metadata for a specific assessment.
std::vector< assessment_info > find_by_ai_result(const std::string &ai_result_uid) const
Find all assessments for a given AI result.
auto get_string(dicom_tag tag, std::string_view default_value="") const -> std::string
Get the string value of an element.
Compile-time constants for commonly used DICOM tags.
assessment_type
Assessment decision made by a clinician on an AI result.
@ accept
Clinician accepts AI result as-is.
@ reject
Clinician rejects AI result with reason.
@ modify
Clinician modifies AI result (e.g., edits segmentation)
@ draft
Assessment in progress, not yet finalized.
@ final_
Assessment finalized and signed off.
constexpr dicom_tag sop_instance_uid
SOP Instance UID.
std::string_view code
Information about a stored assessment.
std::string_view uid
std::string_view name