PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
kcenon::pacs::ai::assessment_creator Class Reference

IHE AIRA Assessment Creator Actor. More...

#include <aira_assessment.h>

Collaboration diagram for kcenon::pacs::ai::assessment_creator:
Collaboration graph

Public Member Functions

 assessment_creator ()=default
 
assessment_creation_result create_assessment (const ai_assessment &assessment) const
 Create an assessment SR document for an AI result.
 

Static Public Member Functions

static std::string assessment_type_to_code (assessment_type type)
 Convert assessment_type to DICOM coded value.
 
static std::string assessment_type_to_meaning (assessment_type type)
 Convert assessment_type to human-readable meaning.
 
static std::string status_to_completion_flag (assessment_status status)
 Convert assessment_status to DICOM completion flag value.
 

Private Member Functions

void build_patient_module (core::dicom_dataset &sr, const ai_assessment &assessment) const
 
void build_sr_content (core::dicom_dataset &sr, const ai_assessment &assessment) const
 
void build_referenced_sop_sequence (core::dicom_dataset &sr, const ai_assessment &assessment) const
 
std::string generate_uid () const
 

Detailed Description

IHE AIRA Assessment Creator Actor.

Creates structured assessment documents for AI results encoded as DICOM Enhanced SR, following the IHE AIRA profile.

Workflow

  1. Create an ai_assessment structure with the assessment decision
  2. Call create_assessment() to generate the SR document
  3. Store the resulting dataset using a storage backend
Example:
ai_assessment assessment;
assessment.ai_result.sop_instance_uid = "1.2.3.4.5";
assessment.assessor_name = "DR^SMITH";
auto result = creator.create_assessment(assessment);
if (result.success) {
// Store result.sr_dataset
}
IHE AIRA Assessment Creator Actor.
assessment_creation_result create_assessment(const ai_assessment &assessment) const
Create an assessment SR document for an AI result.
@ accept
Clinician accepts AI result as-is.
Complete assessment of an AI result.
assessed_result_reference ai_result
Reference to the AI result being assessed.
assessment_type type
Assessment type (accept/modify/reject)
std::string assessor_name
Clinician who performed the assessment.
std::string sop_instance_uid
SOP Instance UID of the AI result.

Definition at line 210 of file aira_assessment.h.

Constructor & Destructor Documentation

◆ assessment_creator()

kcenon::pacs::ai::assessment_creator::assessment_creator ( )
default

Member Function Documentation

◆ assessment_type_to_code()

std::string kcenon::pacs::ai::assessment_creator::assessment_type_to_code ( assessment_type type)
staticnodiscard

Convert assessment_type to DICOM coded value.

Parameters
typeAssessment type
Returns
Coded string representation

Definition at line 330 of file aira_assessment.cpp.

330 {
331 switch (type) {
332 case assessment_type::accept: return "AIRA-ACCEPT";
333 case assessment_type::modify: return "AIRA-MODIFY";
334 case assessment_type::reject: return "AIRA-REJECT";
335 }
336 return "AIRA-ACCEPT";
337}
@ reject
Clinician rejects AI result with reason.
@ modify
Clinician modifies AI result (e.g., edits segmentation)

References kcenon::pacs::ai::accept, kcenon::pacs::ai::modify, and kcenon::pacs::ai::reject.

Referenced by build_sr_content().

Here is the caller graph for this function:

◆ assessment_type_to_meaning()

std::string kcenon::pacs::ai::assessment_creator::assessment_type_to_meaning ( assessment_type type)
staticnodiscard

Convert assessment_type to human-readable meaning.

Parameters
typeAssessment type
Returns
Display string

Definition at line 339 of file aira_assessment.cpp.

339 {
340 switch (type) {
341 case assessment_type::accept: return "AI Result Accepted";
342 case assessment_type::modify: return "AI Result Modified";
343 case assessment_type::reject: return "AI Result Rejected";
344 }
345 return "AI Result Accepted";
346}

References kcenon::pacs::ai::accept, kcenon::pacs::ai::modify, and kcenon::pacs::ai::reject.

Referenced by build_sr_content().

Here is the caller graph for this function:

◆ build_patient_module()

void kcenon::pacs::ai::assessment_creator::build_patient_module ( core::dicom_dataset & sr,
const ai_assessment & assessment ) const
private

Definition at line 162 of file aira_assessment.cpp.

164 {
165
166 sr.set_string(tags::patient_name, vr_type::PN, "");
167 sr.set_string(tags::patient_id, vr_type::LO, "");
168 sr.set_string(tags::patient_birth_date, vr_type::DA, "");
169 sr.set_string(tags::patient_sex, vr_type::CS, "");
170}
constexpr dicom_tag patient_id
Patient ID.
constexpr dicom_tag patient_birth_date
Patient's Birth Date.
constexpr dicom_tag patient_sex
Patient's Sex.
constexpr dicom_tag patient_name
Patient's Name.

References kcenon::pacs::core::tags::patient_birth_date, kcenon::pacs::core::tags::patient_id, kcenon::pacs::core::tags::patient_name, and kcenon::pacs::core::tags::patient_sex.

Referenced by create_assessment().

Here is the caller graph for this function:

◆ build_referenced_sop_sequence()

void kcenon::pacs::ai::assessment_creator::build_referenced_sop_sequence ( core::dicom_dataset & sr,
const ai_assessment & assessment ) const
private

Definition at line 282 of file aira_assessment.cpp.

284 {
285
286 std::vector<dicom_dataset> ref_items;
287
288 // Reference to the assessed AI result
289 dicom_dataset ref_item;
290 ref_item.set_string(aira_tags::referenced_sop_class_uid, vr_type::UI,
291 assessment.ai_result.sop_class_uid);
292 ref_item.set_string(aira_tags::referenced_sop_instance_uid, vr_type::UI,
293 assessment.ai_result.sop_instance_uid);
294 ref_items.push_back(std::move(ref_item));
295
296 // Reference to modified result (if applicable)
297 if (assessment.type == assessment_type::modify &&
298 assessment.modification &&
299 assessment.modification->modified_result_uid) {
300 dicom_dataset mod_ref;
301 mod_ref.set_string(aira_tags::referenced_sop_class_uid, vr_type::UI,
302 assessment.modification->modified_result_class_uid
303 .value_or(assessment.ai_result.sop_class_uid));
304 mod_ref.set_string(aira_tags::referenced_sop_instance_uid, vr_type::UI,
305 assessment.modification->modified_result_uid.value());
306 ref_items.push_back(std::move(mod_ref));
307 }
308
309 insert_sequence(sr, aira_tags::referenced_sop_sequence,
310 std::move(ref_items));
311}
constexpr dicom_tag referenced_sop_class_uid
constexpr dicom_tag referenced_sop_sequence
constexpr dicom_tag referenced_sop_instance_uid

References kcenon::pacs::ai::ai_assessment::ai_result, kcenon::pacs::ai::ai_assessment::modification, kcenon::pacs::ai::modify, kcenon::pacs::ai::aira_tags::referenced_sop_class_uid, kcenon::pacs::ai::aira_tags::referenced_sop_instance_uid, kcenon::pacs::ai::aira_tags::referenced_sop_sequence, kcenon::pacs::core::dicom_dataset::set_string(), kcenon::pacs::ai::assessed_result_reference::sop_class_uid, kcenon::pacs::ai::assessed_result_reference::sop_instance_uid, and kcenon::pacs::ai::ai_assessment::type.

Referenced by create_assessment().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ build_sr_content()

void kcenon::pacs::ai::assessment_creator::build_sr_content ( core::dicom_dataset & sr,
const ai_assessment & assessment ) const
private

Definition at line 172 of file aira_assessment.cpp.

174 {
175
176 // Root content: CONTAINER
177 sr.set_string(aira_tags::value_type, vr_type::CS, "CONTAINER");
178
179 // Concept Name: AI Result Assessment (custom code)
180 auto concept_name = make_code_item(
181 "AIRA-001", "99PACS", "AI Result Assessment");
182 insert_sequence(sr, aira_tags::concept_name_code_sequence, {concept_name});
183
184 // Content Template Sequence (IHE AIRA assessment template)
185 dicom_dataset template_item;
186 template_item.set_string(aira_tags::template_identifier, vr_type::CS,
187 "AIRA");
188 template_item.set_string(aira_tags::mapping_resource, vr_type::CS, "99PACS");
189 insert_sequence(sr, aira_tags::content_template_sequence, {template_item});
190
191 // Content Sequence items
192 std::vector<dicom_dataset> content_items;
193
194 // Item 1: Assessment Type (CODE)
195 {
196 dicom_dataset item;
197 item.set_string(aira_tags::relationship_type, vr_type::CS, "CONTAINS");
198 item.set_string(aira_tags::value_type, vr_type::CS, "CODE");
199
200 auto name = make_code_item("AIRA-010", "99PACS", "Assessment Decision");
201 insert_sequence(item, aira_tags::concept_name_code_sequence, {name});
202
203 auto value = make_code_item(
204 assessment_type_to_code(assessment.type),
205 "99PACS",
206 assessment_type_to_meaning(assessment.type));
207 // Concept Code Sequence (0040,A168) for CODE value type
208 constexpr dicom_tag concept_code_sequence{0x0040, 0xA168};
209 insert_sequence(item, concept_code_sequence, {value});
210
211 content_items.push_back(std::move(item));
212 }
213
214 // Item 2: Assessor (PNAME)
215 {
216 dicom_dataset item;
217 item.set_string(aira_tags::relationship_type, vr_type::CS, "HAS OBS CONTEXT");
218 item.set_string(aira_tags::value_type, vr_type::CS, "PNAME");
219
220 auto name = make_code_item("AIRA-020", "99PACS", "Assessor");
221 insert_sequence(item, aira_tags::concept_name_code_sequence, {name});
222
223 constexpr dicom_tag person_name{0x0040, 0xA123};
224 item.set_string(person_name, vr_type::PN, assessment.assessor_name);
225
226 content_items.push_back(std::move(item));
227 }
228
229 // Item 3: Comment (TEXT, optional)
230 if (assessment.comment && !assessment.comment->empty()) {
231 dicom_dataset item;
232 item.set_string(aira_tags::relationship_type, vr_type::CS, "CONTAINS");
233 item.set_string(aira_tags::value_type, vr_type::CS, "TEXT");
234
235 auto name = make_code_item("AIRA-030", "99PACS", "Assessment Comment");
236 insert_sequence(item, aira_tags::concept_name_code_sequence, {name});
237
238 item.set_string(aira_tags::text_value, vr_type::UT,
239 assessment.comment.value());
240
241 content_items.push_back(std::move(item));
242 }
243
244 // Item 4: Modification description (TEXT, only for modify type)
245 if (assessment.type == assessment_type::modify && assessment.modification) {
246 dicom_dataset item;
247 item.set_string(aira_tags::relationship_type, vr_type::CS, "CONTAINS");
248 item.set_string(aira_tags::value_type, vr_type::CS, "TEXT");
249
250 auto name = make_code_item("AIRA-040", "99PACS",
251 "Modification Description");
252 insert_sequence(item, aira_tags::concept_name_code_sequence, {name});
253
254 item.set_string(aira_tags::text_value, vr_type::UT,
255 assessment.modification->description);
256
257 content_items.push_back(std::move(item));
258 }
259
260 // Item 5: Rejection reason (CODE, only for reject type)
261 if (assessment.type == assessment_type::reject && assessment.rejection) {
262 dicom_dataset item;
263 item.set_string(aira_tags::relationship_type, vr_type::CS, "CONTAINS");
264 item.set_string(aira_tags::value_type, vr_type::CS, "CODE");
265
266 auto name = make_code_item("AIRA-050", "99PACS", "Rejection Reason");
267 insert_sequence(item, aira_tags::concept_name_code_sequence, {name});
268
269 auto value = make_code_item(
270 assessment.rejection->reason_code,
271 assessment.rejection->reason_scheme,
272 assessment.rejection->reason_description);
273 constexpr dicom_tag concept_code_sequence{0x0040, 0xA168};
274 insert_sequence(item, concept_code_sequence, {value});
275
276 content_items.push_back(std::move(item));
277 }
278
279 insert_sequence(sr, aira_tags::content_sequence, std::move(content_items));
280}
static std::string assessment_type_to_meaning(assessment_type type)
Convert assessment_type to human-readable meaning.
static std::string assessment_type_to_code(assessment_type type)
Convert assessment_type to DICOM coded value.
constexpr dicom_tag template_identifier
constexpr dicom_tag content_sequence
constexpr dicom_tag concept_name_code_sequence
constexpr dicom_tag mapping_resource
constexpr dicom_tag value_type
constexpr dicom_tag content_template_sequence
constexpr dicom_tag text_value
constexpr dicom_tag relationship_type
constexpr dicom_tag item
Item.
std::string_view name

References assessment_type_to_code(), assessment_type_to_meaning(), kcenon::pacs::ai::ai_assessment::assessor_name, kcenon::pacs::ai::ai_assessment::comment, kcenon::pacs::ai::aira_tags::concept_name_code_sequence, kcenon::pacs::ai::aira_tags::content_sequence, kcenon::pacs::ai::aira_tags::content_template_sequence, kcenon::pacs::ai::aira_tags::mapping_resource, kcenon::pacs::ai::ai_assessment::modification, kcenon::pacs::ai::modify, name, kcenon::pacs::ai::reject, kcenon::pacs::ai::ai_assessment::rejection, kcenon::pacs::ai::aira_tags::relationship_type, kcenon::pacs::core::dicom_dataset::set_string(), kcenon::pacs::ai::aira_tags::template_identifier, kcenon::pacs::ai::aira_tags::text_value, kcenon::pacs::ai::ai_assessment::type, and kcenon::pacs::ai::aira_tags::value_type.

Referenced by create_assessment().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ create_assessment()

assessment_creation_result kcenon::pacs::ai::assessment_creator::create_assessment ( const ai_assessment & assessment) const
nodiscard

Create an assessment SR document for an AI result.

Parameters
assessmentThe assessment data
Returns
Creation result with the SR dataset

Definition at line 98 of file aira_assessment.cpp.

99 {
100
101 assessment_creation_result result;
102
103 // Validate required fields
104 if (assessment.ai_result.sop_instance_uid.empty()) {
105 result.error_message = "AI result SOP Instance UID is required";
106 return result;
107 }
108
109 if (assessment.assessor_name.empty()) {
110 result.error_message = "Assessor name is required";
111 return result;
112 }
113
115
116 // --- SOP Common Module ---
117 sr.set_string(tags::sop_class_uid, vr_type::UI,
118 "1.2.840.10008.5.1.4.1.1.88.22"); // Enhanced SR
119 result.assessment_uid = generate_uid();
120 sr.set_string(tags::sop_instance_uid, vr_type::UI, result.assessment_uid);
121
122 // --- Patient Module (Type 2 - empty if not provided) ---
123 build_patient_module(sr, assessment);
124
125 // --- General Study Module ---
126 sr.set_string(tags::study_instance_uid, vr_type::UI,
127 assessment.ai_result.study_instance_uid);
128 sr.set_string(tags::study_date, vr_type::DA, current_date());
129 sr.set_string(tags::study_time, vr_type::TM, current_time());
130 sr.set_string(tags::referring_physician_name, vr_type::PN, "");
131 sr.set_string(tags::study_id, vr_type::SH, "");
132 sr.set_string(tags::accession_number, vr_type::SH, "");
133
134 // --- General Series Module ---
135 sr.set_string(tags::modality, vr_type::CS, "SR");
136 sr.set_string(tags::series_instance_uid, vr_type::UI, generate_uid());
137 sr.set_string(tags::series_number, vr_type::IS, "1");
138
139 // --- General Equipment Module ---
140 sr.set_string(dicom_tag{0x0008, 0x0070}, vr_type::LO, ""); // Manufacturer
141
142 // --- SR Document General Module ---
143 sr.set_string(tags::instance_number, vr_type::IS, "1");
144 sr.set_string(tags::content_date, vr_type::DA, current_date());
145 sr.set_string(tags::content_time, vr_type::TM, current_time());
146 sr.set_string(aira_tags::completion_flag, vr_type::CS,
147 status_to_completion_flag(assessment.status));
148 sr.set_string(aira_tags::verification_flag, vr_type::CS, "UNVERIFIED");
149
150 // --- SR Document Content Module ---
151 build_sr_content(sr, assessment);
152
153 // --- Referenced SOP Sequence ---
154 build_referenced_sop_sequence(sr, assessment);
155
156 result.success = true;
157 result.sr_dataset = std::move(sr);
158
159 return result;
160}
static std::string status_to_completion_flag(assessment_status status)
Convert assessment_status to DICOM completion flag value.
void build_referenced_sop_sequence(core::dicom_dataset &sr, const ai_assessment &assessment) const
void build_patient_module(core::dicom_dataset &sr, const ai_assessment &assessment) const
void build_sr_content(core::dicom_dataset &sr, const ai_assessment &assessment) const
Represents a DICOM Dataset (ordered collection of Data Elements)
constexpr dicom_tag verification_flag
constexpr dicom_tag completion_flag
constexpr dicom_tag referring_physician_name
Referring Physician's Name.
constexpr dicom_tag content_time
Content Time.
constexpr dicom_tag sop_instance_uid
SOP Instance UID.
constexpr dicom_tag accession_number
Accession Number.
constexpr dicom_tag modality
Modality.
constexpr dicom_tag study_time
Study Time.
constexpr dicom_tag study_instance_uid
Study Instance UID.
constexpr dicom_tag series_number
Series Number.
constexpr dicom_tag content_date
Content Date.
constexpr dicom_tag sop_class_uid
SOP Class UID.
constexpr dicom_tag study_id
Study ID.
constexpr dicom_tag study_date
Study Date.
constexpr dicom_tag series_instance_uid
Series Instance UID.
constexpr dicom_tag instance_number
Instance Number.

References kcenon::pacs::core::tags::accession_number, kcenon::pacs::ai::ai_assessment::ai_result, kcenon::pacs::ai::assessment_creation_result::assessment_uid, kcenon::pacs::ai::ai_assessment::assessor_name, build_patient_module(), build_referenced_sop_sequence(), build_sr_content(), kcenon::pacs::ai::aira_tags::completion_flag, kcenon::pacs::core::tags::content_date, kcenon::pacs::core::tags::content_time, kcenon::pacs::ai::assessment_creation_result::error_message, generate_uid(), kcenon::pacs::core::tags::instance_number, kcenon::pacs::core::tags::modality, kcenon::pacs::core::tags::referring_physician_name, kcenon::pacs::core::tags::series_instance_uid, kcenon::pacs::core::tags::series_number, kcenon::pacs::core::tags::sop_class_uid, kcenon::pacs::ai::assessed_result_reference::sop_instance_uid, kcenon::pacs::core::tags::sop_instance_uid, kcenon::pacs::ai::assessment_creation_result::sr_dataset, kcenon::pacs::ai::ai_assessment::status, status_to_completion_flag(), kcenon::pacs::core::tags::study_date, kcenon::pacs::core::tags::study_id, kcenon::pacs::ai::assessed_result_reference::study_instance_uid, kcenon::pacs::core::tags::study_instance_uid, kcenon::pacs::core::tags::study_time, kcenon::pacs::ai::assessment_creation_result::success, and kcenon::pacs::ai::aira_tags::verification_flag.

Here is the call graph for this function:

◆ generate_uid()

std::string kcenon::pacs::ai::assessment_creator::generate_uid ( ) const
nodiscardprivate

Definition at line 313 of file aira_assessment.cpp.

313 {
314 static constexpr const char* uid_root = "1.2.826.0.1.3680043.2.1545.1";
315 static std::mt19937_64 gen{std::random_device{}()};
316 static std::uniform_int_distribution<uint64_t> dist;
317
318 auto now = std::chrono::system_clock::now();
319 auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(
320 now.time_since_epoch()).count();
321
322 return std::string(uid_root) + "." + std::to_string(timestamp) +
323 "." + std::to_string(dist(gen) % 100000);
324}

Referenced by create_assessment().

Here is the caller graph for this function:

◆ status_to_completion_flag()

std::string kcenon::pacs::ai::assessment_creator::status_to_completion_flag ( assessment_status status)
staticnodiscard

Convert assessment_status to DICOM completion flag value.

Parameters
statusAssessment status
Returns
Completion flag string (COMPLETE or PARTIAL)

Definition at line 348 of file aira_assessment.cpp.

349 {
350 switch (status) {
351 case assessment_status::draft: return "PARTIAL";
352 case assessment_status::final_: return "COMPLETE";
353 case assessment_status::amended: return "COMPLETE";
354 }
355 return "PARTIAL";
356}
@ amended
Previously finalized assessment has been amended.
@ draft
Assessment in progress, not yet finalized.
@ final_
Assessment finalized and signed off.

References kcenon::pacs::ai::amended, kcenon::pacs::ai::draft, and kcenon::pacs::ai::final_.

Referenced by create_assessment().

Here is the caller graph for this function:

The documentation for this class was generated from the following files: