16#include <unordered_map>
21using kcenon::common::ok;
22using kcenon::common::make_error;
30constexpr std::string_view
basic_text_sr =
"1.2.840.10008.5.1.4.1.1.88.11";
31constexpr std::string_view
enhanced_sr =
"1.2.840.10008.5.1.4.1.1.88.22";
36constexpr std::string_view
segmentation =
"1.2.840.10008.5.1.4.1.1.66.4";
54[[nodiscard]]
auto determine_result_type(std::string_view sop_class_uid)
55 -> std::optional<ai_result_type> {
84 -> std::pair<std::string, std::string> {
96 version = dataset.get_string(core::dicom_tag(0x0018, 0x1020));
98 return {
name, version};
109 impl(std::shared_ptr<storage::storage_interface> storage,
110 std::shared_ptr<storage::index_database> database)
122 std::shared_ptr<storage::storage_interface>
storage_;
141 std::vector<std::pair<core::dicom_tag, std::string>> required_tags = {
149 for (
const auto& [tag,
name] : required_tags) {
150 if (dataset.get_string(tag).empty()) {
174 if (study_uid.empty()) {
176 result.
error_message =
"Missing Study Instance UID for reference validation";
183 if (study_uid.length() < 10) {
196 auto store_result =
storage_->store(dataset);
197 if (store_result.is_err()) {
203 auto [algo_name, algo_version] = extract_algorithm_info(dataset);
206 info.sop_instance_uid = sop_instance_uid;
211 info.algorithm_name = algo_name;
212 info.algorithm_version = algo_version;
213 info.received_at = std::chrono::system_clock::now();
239 std::shared_ptr<storage::storage_interface> storage,
240 std::shared_ptr<storage::index_database> database)
241 -> std::unique_ptr<ai_result_handler> {
242 return std::unique_ptr<ai_result_handler>(
247 std::shared_ptr<storage::storage_interface> storage,
248 std::shared_ptr<storage::index_database> database)
249 : pimpl_(std::make_unique<
impl>(std::move(storage), std::move(database))) {}
263 pimpl_->config_ = config;
286 auto type = determine_result_type(sop_class);
288#if KCENON_HAS_COMMON_SYSTEM
289 return kcenon::common::error_info(
"Invalid SOP Class for Structured Report");
291 return std::string(
"Invalid SOP Class for Structured Report");
296 auto common_validation = pimpl_->validate_common_tags(sr);
298#if KCENON_HAS_COMMON_SYSTEM
299 return kcenon::common::error_info(
300 common_validation.error_message.value_or(
"Validation failed"));
302 return common_validation.error_message.value_or(
"Validation failed");
307 if (pimpl_->config_.validate_sr_templates) {
308 auto template_validation = validate_sr_template(sr);
310#if KCENON_HAS_COMMON_SYSTEM
311 return kcenon::common::error_info(
312 template_validation.error_message.value_or(
"SR template validation failed"));
314 return template_validation.error_message.value_or(
"SR template validation failed");
320 auto ref_validation = pimpl_->validate_source_references_exist(sr);
322#if KCENON_HAS_COMMON_SYSTEM
323 return kcenon::common::error_info(
324 ref_validation.error_message.value_or(
"Source reference validation failed"));
326 return ref_validation.error_message.value_or(
"Source reference validation failed");
331 if (pimpl_->pre_store_validator_ &&
333#if KCENON_HAS_COMMON_SYSTEM
334 return kcenon::common::error_info(
"Pre-store validation rejected the SR");
336 return std::string(
"Pre-store validation rejected the SR");
353 if (!pimpl_->config_.accepted_sr_templates.empty()) {
356 auto template_id = sr.get_string(template_id_tag);
358 bool template_found =
false;
359 for (
const auto& accepted : pimpl_->config_.accepted_sr_templates) {
360 if (template_id == accepted) {
361 template_found =
true;
366 if (!template_found && !template_id.empty()) {
368 result.
error_message =
"SR template not in accepted list: " + template_id;
378 auto retrieve_result = pimpl_->storage_->retrieve(sr_sop_instance_uid);
379 if (retrieve_result.is_err()) {
380#if KCENON_HAS_COMMON_SYSTEM
381 return kcenon::common::error_info(
"Failed to retrieve SR: " +
382 std::string(sr_sop_instance_uid));
384 return std::string(
"Failed to retrieve SR: ") + std::string(sr_sop_instance_uid);
391 std::vector<cad_finding> findings;
407 auto type = determine_result_type(sop_class);
409#if KCENON_HAS_COMMON_SYSTEM
410 return kcenon::common::error_info(
"Invalid SOP Class for Segmentation");
412 return std::string(
"Invalid SOP Class for Segmentation");
417 auto common_validation = pimpl_->validate_common_tags(seg);
419#if KCENON_HAS_COMMON_SYSTEM
420 return kcenon::common::error_info(
421 common_validation.error_message.value_or(
"Validation failed"));
423 return common_validation.error_message.value_or(
"Validation failed");
428 auto seg_validation = validate_segmentation(seg);
430#if KCENON_HAS_COMMON_SYSTEM
431 return kcenon::common::error_info(
432 seg_validation.error_message.value_or(
"Segmentation validation failed"));
434 return seg_validation.error_message.value_or(
"Segmentation validation failed");
439 auto ref_validation = pimpl_->validate_source_references_exist(seg);
441#if KCENON_HAS_COMMON_SYSTEM
442 return kcenon::common::error_info(
443 ref_validation.error_message.value_or(
"Source reference validation failed"));
445 return ref_validation.error_message.value_or(
"Source reference validation failed");
450 if (pimpl_->pre_store_validator_ &&
452#if KCENON_HAS_COMMON_SYSTEM
453 return kcenon::common::error_info(
"Pre-store validation rejected the segmentation");
455 return std::string(
"Pre-store validation rejected the segmentation");
474 auto seg_type = seg.get_string(seg_type_tag);
476 if (seg_type !=
"BINARY" && seg_type !=
"FRACTIONAL" && !seg_type.empty()) {
478 result.
error_message =
"Invalid segmentation type: " + seg_type;
483 if (pimpl_->config_.max_segments > 0) {
494 auto retrieve_result = pimpl_->storage_->retrieve(seg_sop_instance_uid);
495 if (retrieve_result.is_err()) {
496#if KCENON_HAS_COMMON_SYSTEM
497 return kcenon::common::error_info(
"Failed to retrieve SEG: " +
498 std::string(seg_sop_instance_uid));
500 return std::string(
"Failed to retrieve SEG: ") + std::string(seg_sop_instance_uid);
505 std::vector<segment_info> segments;
521 auto type = determine_result_type(sop_class);
523#if KCENON_HAS_COMMON_SYSTEM
524 return kcenon::common::error_info(
"Invalid SOP Class for Presentation State");
526 return std::string(
"Invalid SOP Class for Presentation State");
531 auto common_validation = pimpl_->validate_common_tags(pr);
533#if KCENON_HAS_COMMON_SYSTEM
534 return kcenon::common::error_info(
535 common_validation.error_message.value_or(
"Validation failed"));
537 return common_validation.error_message.value_or(
"Validation failed");
542 auto pr_validation = validate_presentation_state(pr);
544#if KCENON_HAS_COMMON_SYSTEM
545 return kcenon::common::error_info(
546 pr_validation.error_message.value_or(
"Presentation state validation failed"));
548 return pr_validation.error_message.value_or(
"Presentation state validation failed");
553 auto ref_validation = pimpl_->validate_source_references_exist(pr);
555#if KCENON_HAS_COMMON_SYSTEM
556 return kcenon::common::error_info(
557 ref_validation.error_message.value_or(
"Source reference validation failed"));
559 return ref_validation.error_message.value_or(
"Source reference validation failed");
564 if (pimpl_->pre_store_validator_ &&
566#if KCENON_HAS_COMMON_SYSTEM
567 return kcenon::common::error_info(
"Pre-store validation rejected the presentation state");
569 return std::string(
"Pre-store validation rejected the presentation state");
587 if (sop_class.empty()) {
600 std::string_view result_uid,
601 std::string_view source_study_uid) ->
VoidResult {
604 return link_to_source(result_uid, ref);
608 std::string_view result_uid,
611 auto it = pimpl_->ai_results_cache_.find(std::string(result_uid));
612 if (it == pimpl_->ai_results_cache_.end()) {
614 if (!pimpl_->storage_->exists(result_uid)) {
615#if KCENON_HAS_COMMON_SYSTEM
616 return kcenon::common::error_info(
"AI result not found: " +
617 std::string(result_uid));
619 return std::string(
"AI result not found: ") + std::string(result_uid);
625 pimpl_->source_links_[std::string(result_uid)] = references;
628 if (it != pimpl_->ai_results_cache_.end()) {
629 it->second.source_study_uid = references.study_instance_uid;
637 auto it = pimpl_->source_links_.find(std::string(result_uid));
638 if (it == pimpl_->source_links_.end()) {
639#if KCENON_HAS_COMMON_SYSTEM
640 return kcenon::common::error_info(
"No source reference found for: " +
641 std::string(result_uid));
643 return std::string(
"No source reference found for: ") + std::string(result_uid);
655 std::vector<ai_result_info> results;
657 for (
const auto& [
uid, info] : pimpl_->ai_results_cache_) {
658 if (info.source_study_uid == study_instance_uid) {
659 results.push_back(info);
664 for (
const auto& [
uid, ref] : pimpl_->source_links_) {
665 if (ref.study_instance_uid == study_instance_uid) {
666 auto it = pimpl_->ai_results_cache_.find(
uid);
667 if (it != pimpl_->ai_results_cache_.end()) {
679 std::string_view study_instance_uid,
681 std::vector<ai_result_info> results;
683 for (
const auto& [
uid, info] : pimpl_->ai_results_cache_) {
684 if (info.source_study_uid == study_instance_uid && info.type == type) {
685 results.push_back(info);
693 -> std::optional<ai_result_info> {
694 auto it = pimpl_->ai_results_cache_.find(std::string(sop_instance_uid));
695 if (it != pimpl_->ai_results_cache_.end()) {
703 if (pimpl_->ai_results_cache_.find(std::string(sop_instance_uid)) !=
704 pimpl_->ai_results_cache_.end()) {
709 return pimpl_->storage_->exists(sop_instance_uid);
717 std::string uid_str(sop_instance_uid);
720 auto remove_result = pimpl_->storage_->remove(sop_instance_uid);
721 if (remove_result.is_err()) {
722 return remove_result;
726 pimpl_->ai_results_cache_.erase(uid_str);
729 pimpl_->source_links_.erase(uid_str);
736 std::size_t removed_count = 0;
739 std::vector<std::string> to_remove;
740 for (
const auto& [
uid, info] : pimpl_->ai_results_cache_) {
741 if (info.source_study_uid == study_instance_uid) {
742 to_remove.push_back(
uid);
747 for (
const auto&
uid : to_remove) {
748 auto result = remove(
uid);
749 if (result.is_ok()) {
754 return removed_count;
Handler for AI-generated DICOM objects (SR, SEG, PR)
Simple result type for error handling.
auto validate_source_references_exist(const core::dicom_dataset &dataset) -> validation_result
std::map< std::string, source_reference > source_links_
pre_store_validator pre_store_validator_
std::map< std::string, ai_result_info > ai_results_cache_
std::shared_ptr< storage::index_database > database_
impl(std::shared_ptr< storage::storage_interface > storage, std::shared_ptr< storage::index_database > database)
auto store_ai_result(const core::dicom_dataset &dataset, ai_result_type type) -> VoidResult
ai_handler_config config_
std::shared_ptr< storage::storage_interface > storage_
ai_result_received_callback received_callback_
auto validate_common_tags(const core::dicom_dataset &dataset) -> validation_result
void set_pre_store_validator(pre_store_validator validator)
Set custom pre-storage validator.
auto receive_presentation_state(const core::dicom_dataset &pr) -> VoidResult
Receive and store an AI-generated Presentation State.
auto get_source_reference(std::string_view result_uid) -> Result< source_reference >
Get source references for an AI result.
auto remove(std::string_view sop_instance_uid) -> VoidResult
Remove an AI result and its source links.
auto receive_structured_report(const core::dicom_dataset &sr) -> VoidResult
Receive and store an AI-generated Structured Report.
auto exists(std::string_view sop_instance_uid) const -> bool
Check if an AI result exists.
auto find_ai_results_by_type(std::string_view study_instance_uid, ai_result_type type) -> Result< std::vector< ai_result_info > >
Find AI results by type.
auto receive_segmentation(const core::dicom_dataset &seg) -> VoidResult
Receive and store an AI-generated Segmentation object.
ai_result_handler(const ai_result_handler &)=delete
auto remove_ai_results_for_study(std::string_view study_instance_uid) -> Result< std::size_t >
Remove all AI results linked to a study.
static auto create(std::shared_ptr< storage::storage_interface > storage, std::shared_ptr< storage::index_database > database) -> std::unique_ptr< ai_result_handler >
Create an AI result handler.
auto find_ai_results_for_study(std::string_view study_instance_uid) -> Result< std::vector< ai_result_info > >
Find all AI results linked to a study.
auto get_ai_result_info(std::string_view sop_instance_uid) -> std::optional< ai_result_info >
Get AI result information by SOP Instance UID.
void set_received_callback(ai_result_received_callback callback)
Set callback for AI result reception notifications.
auto validate_segmentation(const core::dicom_dataset &seg) -> validation_result
Validate segmentation data integrity.
auto get_cad_findings(std::string_view sr_sop_instance_uid) -> Result< std::vector< cad_finding > >
Extract CAD findings from a Structured Report.
auto link_to_source(std::string_view result_uid, std::string_view source_study_uid) -> VoidResult
Link an AI result to its source study.
auto validate_presentation_state(const core::dicom_dataset &pr) -> validation_result
Validate Presentation State.
auto get_segment_info(std::string_view seg_sop_instance_uid) -> Result< std::vector< segment_info > >
Get segment information from a stored Segmentation.
virtual ~ai_result_handler()
Destructor.
auto get_config() const -> ai_handler_config
Get current configuration.
std::unique_ptr< impl > pimpl_
auto validate_sr_template(const core::dicom_dataset &sr) -> validation_result
Validate SR template conformance.
Compile-time constants for commonly used DICOM tags.
constexpr std::string_view pseudo_color_softcopy_ps
constexpr std::string_view comprehensive_3d_sr
constexpr std::string_view grayscale_softcopy_ps
constexpr std::string_view enhanced_sr
constexpr std::string_view comprehensive_sr
constexpr std::string_view blending_softcopy_ps
constexpr std::string_view basic_text_sr
constexpr std::string_view color_softcopy_ps
constexpr std::string_view segmentation
ai_result_type
Types of AI-generated DICOM objects.
@ structured_report
DICOM SR (Structured Report)
@ segmentation
DICOM SEG (Segmentation)
@ presentation_state
DICOM PR (Presentation State)
@ invalid_template
SR template conformance failed.
@ invalid_segment_data
Segmentation data is malformed.
@ valid
All validations passed.
@ invalid_reference
Referenced source images not found.
@ missing_required_tags
Required DICOM tags are missing.
kcenon::common::VoidResult VoidResult
Result type alias for void operations.
std::function< void(const ai_result_info &info)> ai_result_received_callback
Callback for notification when AI result is received.
std::function< bool( const core::dicom_dataset &dataset, ai_result_type type)> pre_store_validator
Callback for pre-storage validation.
Configuration for AI result handler.
bool auto_link_to_source
Whether to auto-link results to source studies.
bool validate_source_references
Whether to validate source references exist in storage.
Information about an AI result stored in the system.
Source reference linking AI result to original images.
std::string study_instance_uid
Study Instance UID.
Validation result containing status and details.
std::vector< std::string > missing_tags
List of missing required tags (if applicable)
validation_status status
Overall validation status.
std::optional< std::string > error_message
Detailed error message if validation failed.
std::vector< std::string > invalid_references
List of invalid references (if applicable)