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

#include <anonymizer.h>

Collaboration diagram for kcenon::pacs::security::anonymizer:
Collaboration graph

Public Member Functions

 anonymizer (anonymization_profile profile=anonymization_profile::basic)
 Construct with a specific profile.
 
 anonymizer (const anonymizer &other)
 Copy constructor.
 
 anonymizer (anonymizer &&other) noexcept
 Move constructor.
 
auto operator= (const anonymizer &other) -> anonymizer &
 Copy assignment.
 
auto operator= (anonymizer &&other) noexcept -> anonymizer &
 Move assignment.
 
 ~anonymizer ()=default
 Default destructor.
 
auto anonymize (core::dicom_dataset &dataset) -> kcenon::common::Result< anonymization_report >
 Anonymize a DICOM dataset.
 
auto anonymize_with_mapping (core::dicom_dataset &dataset, uid_mapping &mapping) -> kcenon::common::Result< anonymization_report >
 Anonymize with consistent UID mapping.
 
auto get_profile () const noexcept -> anonymization_profile
 Get the current profile.
 
void set_profile (anonymization_profile profile)
 Set a new profile.
 
void set_private_tag_action (private_tag_action action)
 Set the action to take on private tags during anonymization.
 
auto get_private_tag_action () const noexcept -> private_tag_action
 Get the current private tag action.
 
void add_tag_action (core::dicom_tag tag, tag_action_config config)
 Add or override a tag action.
 
void add_tag_actions (const std::map< core::dicom_tag, tag_action_config > &actions)
 Add multiple tag actions.
 
auto remove_tag_action (core::dicom_tag tag) -> bool
 Remove a custom tag action (reverts to profile default)
 
void clear_custom_actions ()
 Clear all custom tag actions.
 
auto get_tag_action (core::dicom_tag tag) const -> tag_action_config
 Get the effective action for a tag.
 
void set_date_offset (std::chrono::days offset)
 Set date offset for longitudinal consistency.
 
auto get_date_offset () const noexcept -> std::optional< std::chrono::days >
 Get the current date offset.
 
void clear_date_offset ()
 Clear the date offset (dates will be zeroed instead)
 
auto set_encryption_key (std::span< const std::uint8_t > key) -> kcenon::common::VoidResult
 Set encryption key for encrypt actions.
 
auto has_encryption_key () const noexcept -> bool
 Check if encryption is configured.
 
void set_hash_salt (std::string salt)
 Set salt for hash operations.
 
auto get_hash_salt () const -> std::optional< std::string >
 Get the current hash salt.
 
void set_detailed_reporting (bool enable)
 Enable detailed action recording.
 
auto is_detailed_reporting () const noexcept -> bool
 Check if detailed reporting is enabled.
 

Static Public Member Functions

static auto generate_random_date_offset (std::chrono::days min_days=std::chrono::days{-365}, std::chrono::days max_days=std::chrono::days{365}) -> std::chrono::days
 Generate a random date offset.
 
static auto get_profile_actions (anonymization_profile profile) -> std::map< core::dicom_tag, tag_action_config >
 Get tags to process for a given profile.
 
static auto get_hipaa_identifier_tags () -> std::vector< core::dicom_tag >
 Get a list of HIPAA Safe Harbor identifier tags.
 
static auto get_gdpr_personal_data_tags () -> std::vector< core::dicom_tag >
 Get a list of GDPR personal data tags.
 

Private Member Functions

auto apply_action (core::dicom_dataset &dataset, core::dicom_tag tag, const tag_action_config &config, uid_mapping *mapping) -> tag_action_record
 
auto shift_date (std::string_view date_string) const -> std::string
 
auto hash_value (std::string_view value) const -> std::string
 
auto encrypt_value (std::string_view value) const -> kcenon::common::Result< std::string >
 
void initialize_profile_actions ()
 

Private Attributes

anonymization_profile profile_
 Current anonymization profile.
 
std::map< core::dicom_tag, tag_action_configcustom_actions_
 Custom tag actions (override profile defaults)
 
std::optional< std::chrono::days > date_offset_
 Date offset for shifting.
 
std::vector< std::uint8_t > encryption_key_
 Encryption key (if set)
 
std::optional< std::string > hash_salt_
 Hash salt.
 
private_tag_action private_tag_action_ {private_tag_action::keep}
 Action for private tags.
 
bool detailed_reporting_ {false}
 Whether to include detailed action records in report.
 

Detailed Description

Definition at line 89 of file anonymizer.h.

Constructor & Destructor Documentation

◆ anonymizer() [1/3]

kcenon::pacs::security::anonymizer::anonymizer ( anonymization_profile profile = anonymization_profile::basic)
explicit

Construct with a specific profile.

Parameters
profileThe anonymization profile to use
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 34 of file anonymizer.cpp.

35 : profile_{profile} {
37}
anonymization_profile profile_
Current anonymization profile.
Definition anonymizer.h:374

References initialize_profile_actions().

Here is the call graph for this function:

◆ anonymizer() [2/3]

kcenon::pacs::security::anonymizer::anonymizer ( const anonymizer & other)

Copy constructor.

Definition at line 39 of file anonymizer.cpp.

40 : profile_{other.profile_}
41 , custom_actions_{other.custom_actions_}
42 , date_offset_{other.date_offset_}
43 , encryption_key_{other.encryption_key_}
44 , hash_salt_{other.hash_salt_}
45 , private_tag_action_{other.private_tag_action_}
46 , detailed_reporting_{other.detailed_reporting_} {}
private_tag_action private_tag_action_
Action for private tags.
Definition anonymizer.h:389
std::optional< std::string > hash_salt_
Hash salt.
Definition anonymizer.h:386
std::optional< std::chrono::days > date_offset_
Date offset for shifting.
Definition anonymizer.h:380
std::vector< std::uint8_t > encryption_key_
Encryption key (if set)
Definition anonymizer.h:383
bool detailed_reporting_
Whether to include detailed action records in report.
Definition anonymizer.h:392
std::map< core::dicom_tag, tag_action_config > custom_actions_
Custom tag actions (override profile defaults)
Definition anonymizer.h:377

◆ anonymizer() [3/3]

kcenon::pacs::security::anonymizer::anonymizer ( anonymizer && other)
noexcept

Move constructor.

Definition at line 48 of file anonymizer.cpp.

49 : profile_{other.profile_}
50 , custom_actions_{std::move(other.custom_actions_)}
51 , date_offset_{other.date_offset_}
52 , encryption_key_{std::move(other.encryption_key_)}
53 , hash_salt_{std::move(other.hash_salt_)}
54 , private_tag_action_{other.private_tag_action_}
55 , detailed_reporting_{other.detailed_reporting_} {}

References kcenon::pacs::security::other.

◆ ~anonymizer()

kcenon::pacs::security::anonymizer::~anonymizer ( )
default

Member Function Documentation

◆ add_tag_action()

void kcenon::pacs::security::anonymizer::add_tag_action ( core::dicom_tag tag,
tag_action_config config )

Add or override a tag action.

Sets a custom action for a specific tag, overriding the profile default.

Parameters
tagThe DICOM tag
configThe action configuration
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 211 of file anonymizer.cpp.

211 {
212 custom_actions_[tag] = std::move(config);
213}

References custom_actions_.

◆ add_tag_actions()

void kcenon::pacs::security::anonymizer::add_tag_actions ( const std::map< core::dicom_tag, tag_action_config > & actions)

Add multiple tag actions.

Parameters
actionsMap of tags to action configurations
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 215 of file anonymizer.cpp.

217 {
218 for (const auto& [tag, config] : actions) {
219 custom_actions_[tag] = config;
220 }
221}

References custom_actions_.

◆ anonymize()

auto kcenon::pacs::security::anonymizer::anonymize ( core::dicom_dataset & dataset) -> kcenon::common::Result<anonymization_report>
nodiscard

Anonymize a DICOM dataset.

Applies the configured profile and any custom tag actions to de-identify the dataset. The dataset is modified in place.

Parameters
datasetThe dataset to anonymize (modified in place)
Returns
Result containing the anonymization report
Note
UIDs are regenerated with new values (no mapping preserved). Use anonymize_with_mapping() for consistent UID handling.
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 83 of file anonymizer.cpp.

84 {
85 uid_mapping temp_mapping;
86 return anonymize_with_mapping(dataset, temp_mapping);
87}
auto anonymize_with_mapping(core::dicom_dataset &dataset, uid_mapping &mapping) -> kcenon::common::Result< anonymization_report >
Anonymize with consistent UID mapping.

◆ anonymize_with_mapping()

auto kcenon::pacs::security::anonymizer::anonymize_with_mapping ( core::dicom_dataset & dataset,
uid_mapping & mapping ) -> kcenon::common::Result<anonymization_report>
nodiscard

Anonymize with consistent UID mapping.

Applies de-identification while maintaining consistent UID mappings across multiple datasets. This is essential for longitudinal studies and research linkage.

Parameters
datasetThe dataset to anonymize (modified in place)
mappingUID mapping for consistent transformation
Returns
Result containing the anonymization report
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 89 of file anonymizer.cpp.

92 {
93 anonymization_report report;
94 report.profile_name = std::string(to_string(profile_));
95 report.date_offset = date_offset_;
96 report.timestamp = std::chrono::system_clock::now();
97
98 // Get profile actions
99 auto profile_actions = get_profile_actions(profile_);
100
101 // Merge with custom actions (custom takes precedence)
102 for (const auto& [tag, config] : custom_actions_) {
103 profile_actions[tag] = config;
104 }
105
106 // Process each tag in the profile
107 for (const auto& [tag, config] : profile_actions) {
108 if (!dataset.contains(tag)) {
109 continue;
110 }
111
112 auto record = apply_action(dataset, tag, config, &mapping);
113 report.total_tags_processed++;
114
115 switch (config.action) {
118 if (record.success) {
119 report.tags_removed++;
120 }
121 break;
123 if (record.success) {
124 report.tags_emptied++;
125 }
126 break;
128 if (record.success) {
129 report.tags_replaced++;
130 }
131 break;
133 if (record.success) {
134 report.uids_replaced++;
135 }
136 break;
137 case tag_action::keep:
138 report.tags_kept++;
139 break;
141 if (record.success) {
142 report.dates_shifted++;
143 }
144 break;
145 case tag_action::hash:
146 if (record.success) {
147 report.values_hashed++;
148 }
149 break;
151 if (record.success) {
152 report.tags_replaced++;
153 }
154 break;
155 }
156
157 if (!record.success && !record.error_message.empty()) {
158 report.errors.push_back(record.error_message);
159 }
160
162 report.action_records.push_back(std::move(record));
163 }
164 }
165
166 // Remove private tags if configured
168 std::vector<dicom_tag> tags_to_remove;
169
170 for (auto it = dataset.begin(); it != dataset.end(); ++it) {
171 auto tag = it->first;
173 if (tag.is_private()) {
174 tags_to_remove.push_back(tag);
175 }
177 if (tag.is_private_data()) {
178 tags_to_remove.push_back(tag);
179 }
180 }
181 }
182
183 for (const auto& tag : tags_to_remove) {
184 if (dataset.remove(tag)) {
185 report.private_tags_removed++;
186 }
187 }
188 }
189
190 return report;
191}
auto apply_action(core::dicom_dataset &dataset, core::dicom_tag tag, const tag_action_config &config, uid_mapping *mapping) -> tag_action_record
static auto get_profile_actions(anonymization_profile profile) -> std::map< core::dicom_tag, tag_action_config >
Get tags to process for a given profile.
constexpr auto to_string(anonymization_profile profile) noexcept -> std::string_view
Convert profile enum to string representation.
@ hash
Hash the value for research linkage.
@ remove
D - Remove the attribute entirely.
@ keep
K - Keep the attribute unchanged.
@ replace_uid
U - Replace UIDs with new values.
@ shift_date
Shift dates by a fixed offset.
@ remove_or_empty
X - Remove or empty based on presence.
@ replace
C - Clean (replace with dummy value)
@ empty
Z - Replace with zero-length value.
@ keep
Preserve all private tags (default for backward compatibility)
@ remove_data
Remove private data elements but keep creators (for auditing)
@ remove_all
Remove all private data elements and their creators.

References kcenon::pacs::security::empty, kcenon::pacs::security::encrypt, kcenon::pacs::security::hash, kcenon::pacs::security::keep, kcenon::pacs::security::anonymization_report::profile_name, kcenon::pacs::security::remove, kcenon::pacs::security::remove_all, kcenon::pacs::security::remove_data, kcenon::pacs::security::remove_or_empty, kcenon::pacs::security::replace, kcenon::pacs::security::replace_uid, kcenon::pacs::security::report, kcenon::pacs::security::shift_date, and kcenon::pacs::security::to_string().

Here is the call graph for this function:

◆ apply_action()

auto kcenon::pacs::security::anonymizer::apply_action ( core::dicom_dataset & dataset,
core::dicom_tag tag,
const tag_action_config & config,
uid_mapping * mapping ) -> tag_action_record
nodiscardprivate
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 471 of file anonymizer.cpp.

476 {
477 tag_action_record record;
478 record.tag = tag;
479 record.action = config.action;
480
481 auto* element = dataset.get(tag);
482 if (element == nullptr) {
483 record.success = false;
484 record.error_message = "Tag not found in dataset";
485 return record;
486 }
487
488 record.original_value = element->as_string().unwrap_or("");
489
490 switch (config.action) {
493 record.success = dataset.remove(tag);
494 break;
495
497 dataset.set_string(tag, element->vr(), "");
498 record.new_value = "";
499 record.success = true;
500 break;
501
502 case tag_action::keep:
503 record.new_value = record.original_value;
504 record.success = true;
505 break;
506
508 dataset.set_string(tag, element->vr(), config.replacement_value);
509 record.new_value = config.replacement_value;
510 record.success = true;
511 break;
512
514 if (mapping != nullptr) {
515 auto result = mapping->get_or_create(record.original_value);
516 if (result.is_ok()) {
517 dataset.set_string(tag, element->vr(), result.value());
518 record.new_value = result.value();
519 record.success = true;
520 } else {
521 record.success = false;
522 record.error_message = "Failed to create UID mapping";
523 }
524 } else {
525 // Generate new UID without mapping
526 uid_mapping temp;
527 auto result = temp.get_or_create(record.original_value);
528 if (result.is_ok()) {
529 dataset.set_string(tag, element->vr(), result.value());
530 record.new_value = result.value();
531 record.success = true;
532 } else {
533 record.success = false;
534 record.error_message = "Failed to generate new UID";
535 }
536 }
537 break;
538
539 case tag_action::hash: {
540 auto hashed = hash_value(record.original_value);
541 if (hashed.empty()) {
542 record.success = false;
543 record.error_message = "Hash computation failed";
544 } else {
545 dataset.set_string(tag, element->vr(), hashed);
546 record.new_value = hashed;
547 record.success = true;
548 }
549 break;
550 }
551
552 case tag_action::encrypt: {
553 auto result = encrypt_value(record.original_value);
554 if (result.is_ok()) {
555 dataset.set_string(tag, element->vr(), result.value());
556 record.new_value = result.value();
557 record.success = true;
558 } else {
559 record.success = false;
560 record.error_message = result.error().message;
561 }
562 break;
563 }
564
566 if (date_offset_.has_value()) {
567 auto shifted = shift_date(record.original_value);
568 dataset.set_string(tag, element->vr(), shifted);
569 record.new_value = shifted;
570 record.success = true;
571 } else {
572 // No offset set - empty the date instead
573 dataset.set_string(tag, element->vr(), "");
574 record.new_value = "";
575 record.success = true;
576 }
577 break;
578 }
579 }
580
581 return record;
582}
auto hash_value(std::string_view value) const -> std::string
auto encrypt_value(std::string_view value) const -> kcenon::common::Result< std::string >

References kcenon::pacs::security::empty, kcenon::pacs::security::encrypt, kcenon::pacs::security::uid_mapping::get_or_create(), kcenon::pacs::security::hash, kcenon::pacs::security::keep, kcenon::pacs::security::remove, kcenon::pacs::security::remove_or_empty, kcenon::pacs::security::replace, kcenon::pacs::security::replace_uid, and kcenon::pacs::security::shift_date.

Here is the call graph for this function:

◆ clear_custom_actions()

void kcenon::pacs::security::anonymizer::clear_custom_actions ( )

Clear all custom tag actions.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 227 of file anonymizer.cpp.

227 {
228 custom_actions_.clear();
229}

References custom_actions_.

◆ clear_date_offset()

void kcenon::pacs::security::anonymizer::clear_date_offset ( )

Clear the date offset (dates will be zeroed instead)

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 255 of file anonymizer.cpp.

255 {
256 date_offset_.reset();
257}

References date_offset_.

◆ encrypt_value()

auto kcenon::pacs::security::anonymizer::encrypt_value ( std::string_view value) const -> kcenon::common::Result<std::string>
nodiscardprivate
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 676 of file anonymizer.cpp.

677 {
678 if (encryption_key_.empty()) {
679 return kcenon::common::make_error<std::string>(
680 1, "No encryption key configured");
681 }
682
683#ifdef PACS_WITH_DIGITAL_SIGNATURES
684 constexpr int iv_length = 12;
685 constexpr int tag_length = 16;
686
687 // Generate random IV
688 std::array<unsigned char, iv_length> iv{};
689 if (RAND_bytes(iv.data(), iv_length) != 1) {
690 return kcenon::common::make_error<std::string>(
691 2, "Failed to generate random IV");
692 }
693
694 // Create cipher context with RAII
695 struct cipher_ctx_deleter {
696 void operator()(EVP_CIPHER_CTX* c) const { EVP_CIPHER_CTX_free(c); }
697 };
698
699 std::unique_ptr<EVP_CIPHER_CTX, cipher_ctx_deleter> ctx(
700 EVP_CIPHER_CTX_new());
701 if (!ctx) {
702 return kcenon::common::make_error<std::string>(
703 3, "Failed to create cipher context");
704 }
705
706 // Initialize AES-256-GCM
707 if (EVP_EncryptInit_ex(
708 ctx.get(), EVP_aes_256_gcm(), nullptr, nullptr, nullptr)
709 != 1) {
710 return kcenon::common::make_error<std::string>(
711 4, "Failed to initialize AES-256-GCM");
712 }
713
714 if (EVP_CIPHER_CTX_ctrl(
715 ctx.get(), EVP_CTRL_GCM_SET_IVLEN, iv_length, nullptr)
716 != 1) {
717 return kcenon::common::make_error<std::string>(
718 5, "Failed to set IV length");
719 }
720
721 if (EVP_EncryptInit_ex(ctx.get(), nullptr, nullptr,
722 encryption_key_.data(), iv.data())
723 != 1) {
724 return kcenon::common::make_error<std::string>(
725 6, "Failed to set encryption key and IV");
726 }
727
728 // Encrypt
729 std::vector<unsigned char> ciphertext(value.size() + EVP_MAX_BLOCK_LENGTH);
730 int out_len = 0;
731 if (EVP_EncryptUpdate(
732 ctx.get(), ciphertext.data(), &out_len,
733 reinterpret_cast<const unsigned char*>(value.data()),
734 static_cast<int>(value.size()))
735 != 1) {
736 return kcenon::common::make_error<std::string>(
737 7, "Encryption failed");
738 }
739 int ciphertext_len = out_len;
740
741 // Finalize
742 if (EVP_EncryptFinal_ex(
743 ctx.get(), ciphertext.data() + out_len, &out_len)
744 != 1) {
745 return kcenon::common::make_error<std::string>(
746 8, "Encryption finalization failed");
747 }
748 ciphertext_len += out_len;
749
750 // Get authentication tag
751 std::array<unsigned char, tag_length> tag{};
752 if (EVP_CIPHER_CTX_ctrl(
753 ctx.get(), EVP_CTRL_GCM_GET_TAG, tag_length, tag.data())
754 != 1) {
755 return kcenon::common::make_error<std::string>(
756 9, "Failed to get authentication tag");
757 }
758
759 // Encode as hex: IV || ciphertext || tag
760 std::ostringstream oss;
761 oss << std::hex << std::setfill('0');
762 for (int i = 0; i < iv_length; ++i) {
763 oss << std::setw(2) << static_cast<int>(iv[i]);
764 }
765 for (int i = 0; i < ciphertext_len; ++i) {
766 oss << std::setw(2) << static_cast<int>(ciphertext[i]);
767 }
768 for (int i = 0; i < tag_length; ++i) {
769 oss << std::setw(2) << static_cast<int>(tag[i]);
770 }
771
772 return kcenon::common::Result<std::string>(oss.str());
773#else
774 (void)value;
775 return kcenon::common::make_error<std::string>(
776 1, "Encryption requires OpenSSL (build with PACS_WITH_DIGITAL_SIGNATURES)");
777#endif
778}

◆ generate_random_date_offset()

auto kcenon::pacs::security::anonymizer::generate_random_date_offset ( std::chrono::days min_days = std::chrono::days{-365},
std::chrono::days max_days = std::chrono::days{365} ) -> std::chrono::days
staticnodiscard

Generate a random date offset.

Generates a random offset within the specified range.

Parameters
min_daysMinimum offset (days)
max_daysMaximum offset (days)
Returns
The generated offset
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 259 of file anonymizer.cpp.

262 {
263 std::random_device rd;
264 std::mt19937 gen(rd());
265 std::uniform_int_distribution<int> dist(
266 static_cast<int>(min_days.count()),
267 static_cast<int>(max_days.count())
268 );
269 return std::chrono::days{dist(gen)};
270}

◆ get_date_offset()

auto kcenon::pacs::security::anonymizer::get_date_offset ( ) const -> std::optional<std::chrono::days>
nodiscardnoexcept

Get the current date offset.

Returns
Optional containing the offset, or nullopt if not set
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 250 of file anonymizer.cpp.

251 {
252 return date_offset_;
253}

References date_offset_.

◆ get_gdpr_personal_data_tags()

auto kcenon::pacs::security::anonymizer::get_gdpr_personal_data_tags ( ) -> std::vector<core::dicom_tag>
staticnodiscard

Get a list of GDPR personal data tags.

Returns
Vector of tags containing personal data per GDPR
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 448 of file anonymizer.cpp.

448 {
449 std::vector<dicom_tag> tags;
450
451 // Direct identifiers
452 tags.push_back(tags::patient_name);
453 tags.push_back(tags::patient_id);
454 tags.push_back(tags::patient_birth_date);
455 tags.push_back(tags::patient_address);
456
457 // Indirect identifiers
458 tags.push_back(tags::study_instance_uid);
459 tags.push_back(tags::series_instance_uid);
460 tags.push_back(tags::sop_instance_uid);
461
462 // Health data (special category under GDPR)
463 tags.push_back(tags::patient_sex);
464 tags.push_back(tags::patient_age);
465 tags.push_back(tags::patient_size);
466 tags.push_back(tags::patient_weight);
467
468 return tags;
469}
constexpr dicom_tag patient_id
Patient ID.
constexpr dicom_tag sop_instance_uid
SOP Instance UID.
constexpr dicom_tag patient_age
Patient's Age.
constexpr dicom_tag patient_address
Patient's Address.
constexpr dicom_tag patient_size
Patient's Size.
constexpr dicom_tag patient_birth_date
Patient's Birth Date.
constexpr dicom_tag patient_sex
Patient's Sex.
constexpr dicom_tag study_instance_uid
Study Instance UID.
constexpr dicom_tag patient_name
Patient's Name.
constexpr dicom_tag series_instance_uid
Series Instance UID.
constexpr dicom_tag patient_weight
Patient's Weight.

References kcenon::pacs::core::tags::patient_address, kcenon::pacs::core::tags::patient_age, kcenon::pacs::core::tags::patient_birth_date, kcenon::pacs::core::tags::patient_id, kcenon::pacs::core::tags::patient_name, kcenon::pacs::core::tags::patient_sex, kcenon::pacs::core::tags::patient_size, kcenon::pacs::core::tags::patient_weight, kcenon::pacs::core::tags::series_instance_uid, kcenon::pacs::core::tags::sop_instance_uid, and kcenon::pacs::core::tags::study_instance_uid.

◆ get_hash_salt()

auto kcenon::pacs::security::anonymizer::get_hash_salt ( ) const -> std::optional<std::string>
nodiscard

Get the current hash salt.

Returns
Optional containing the salt, or nullopt if not set
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 291 of file anonymizer.cpp.

291 {
292 return hash_salt_;
293}

References hash_salt_.

◆ get_hipaa_identifier_tags()

auto kcenon::pacs::security::anonymizer::get_hipaa_identifier_tags ( ) -> std::vector<core::dicom_tag>
staticnodiscard

Get a list of HIPAA Safe Harbor identifier tags.

Returns
Vector of tags that are HIPAA identifiers
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 444 of file anonymizer.cpp.

444 {
446}
auto get_all_identifier_tags() -> std::vector< core::dicom_tag >
Get all HIPAA identifier tags.

References kcenon::pacs::security::hipaa_identifiers::get_all_identifier_tags().

Here is the call graph for this function:

◆ get_private_tag_action()

auto kcenon::pacs::security::anonymizer::get_private_tag_action ( ) const -> private_tag_action
nodiscardnoexcept

Get the current private tag action.

Returns
The configured private tag action
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 206 of file anonymizer.cpp.

207 {
208 return private_tag_action_;
209}

References private_tag_action_.

◆ get_profile()

auto kcenon::pacs::security::anonymizer::get_profile ( ) const -> anonymization_profile
nodiscardnoexcept

Get the current profile.

Returns
The anonymization profile being used
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 193 of file anonymizer.cpp.

193 {
194 return profile_;
195}

References profile_.

◆ get_profile_actions()

auto kcenon::pacs::security::anonymizer::get_profile_actions ( anonymization_profile profile) -> std::map<core::dicom_tag, tag_action_config>
staticnodiscard

Get tags to process for a given profile.

Parameters
profileThe anonymization profile
Returns
Map of tags to their default actions
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 303 of file anonymizer.cpp.

304 {
305 std::map<dicom_tag, tag_action_config> actions;
306
307 // Common patient identifiers
308 auto add_patient_identifiers = [&actions]() {
316 };
317
318 // Institution identifiers
319 auto add_institution_identifiers = [&actions]() {
323 };
324
325 // Personnel identifiers
326 auto add_personnel_identifiers = [&actions]() {
332 };
333
334 // UID replacements
335 auto add_uid_replacements = [&actions]() {
336 actions[tags::study_instance_uid] =
337 {.action = tag_action::replace_uid};
339 {.action = tag_action::replace_uid};
340 actions[tags::sop_instance_uid] =
341 {.action = tag_action::replace_uid};
343 {.action = tag_action::replace_uid};
344 };
345
346 // Study identifiers
347 auto add_study_identifiers = [&actions]() {
349 actions[tags::study_id] = tag_action_config::make_replace("ANON_STUDY");
350 };
351
352 // Dates for shifting
353 auto add_date_shifting = [&actions]() {
354 actions[tags::study_date] = {.action = tag_action::shift_date};
355 actions[tags::series_date] = {.action = tag_action::shift_date};
356 actions[tags::acquisition_date] = {.action = tag_action::shift_date};
357 actions[tags::content_date] = {.action = tag_action::shift_date};
360 };
361
362 switch (profile) {
364 add_patient_identifiers();
365 add_institution_identifiers();
366 add_personnel_identifiers();
367 add_uid_replacements();
368 add_study_identifiers();
369 break;
370
372 add_patient_identifiers();
373 add_institution_identifiers();
374 add_personnel_identifiers();
375 add_uid_replacements();
376 add_study_identifiers();
377 // Note: Pixel data cleaning requires image processing
378 // which is not implemented in this basic version
379 break;
380
382 add_patient_identifiers();
383 add_institution_identifiers();
384 add_personnel_identifiers();
385 add_uid_replacements();
386 add_study_identifiers();
389 break;
390
392 add_patient_identifiers();
393 add_institution_identifiers();
394 add_personnel_identifiers();
395 add_uid_replacements();
396 add_study_identifiers();
397 add_date_shifting();
398 break;
399
401 add_patient_identifiers();
402 add_institution_identifiers();
403 add_personnel_identifiers();
404 add_uid_replacements();
405 add_study_identifiers();
406 // Keep demographic data
411 break;
412
414 add_patient_identifiers();
415 add_institution_identifiers();
416 add_personnel_identifiers();
417 add_uid_replacements();
418 add_study_identifiers();
419 // Remove all HIPAA identifiers
420 for (const auto& tag : get_hipaa_identifier_tags()) {
421 if (actions.find(tag) == actions.end()) {
422 actions[tag] = tag_action_config::make_remove();
423 }
424 }
425 // Dates - keep only year or remove
427 break;
428
430 add_patient_identifiers();
431 add_institution_identifiers();
432 add_personnel_identifiers();
433 add_uid_replacements();
434 add_study_identifiers();
435 // GDPR allows pseudonymization - use hash for linkage
438 break;
439 }
440
441 return actions;
442}
static auto get_hipaa_identifier_tags() -> std::vector< core::dicom_tag >
Get a list of HIPAA Safe Harbor identifier tags.
constexpr dicom_tag patient_comments
Patient Comments.
constexpr dicom_tag referring_physician_name
Referring Physician's Name.
constexpr dicom_tag institution_name
Institution Name.
constexpr dicom_tag study_description
Study Description.
constexpr dicom_tag institution_address
Institution Address.
constexpr dicom_tag name_of_physicians_reading_study
Name of Physician(s) Reading Study.
constexpr dicom_tag frame_of_reference_uid
Frame of Reference UID.
constexpr dicom_tag acquisition_date
Acquisition Date.
constexpr dicom_tag scheduled_performing_physician_name
Scheduled Performing Physician's Name.
constexpr dicom_tag accession_number
Accession Number.
constexpr dicom_tag series_date
Series Date.
constexpr dicom_tag station_name
Station Name.
constexpr dicom_tag operators_name
Operators' Name.
constexpr dicom_tag performing_physician_name
Performing Physician's Name.
constexpr dicom_tag instance_creation_date
Instance Creation Date.
constexpr dicom_tag series_description
Series Description.
constexpr dicom_tag content_date
Content Date.
constexpr dicom_tag study_id
Study ID.
constexpr dicom_tag study_date
Study Date.
@ clean_pixel
Clean Pixel Data - Remove burned-in annotations.
@ hipaa_safe_harbor
HIPAA Safe Harbor - 18 identifier removal.
@ gdpr_compliant
GDPR Compliant - European data protection.
@ retain_longitudinal
Retain Longitudinal - Preserve temporal relationships.
@ retain_patient_characteristics
Retain Patient Characteristics.
@ clean_descriptions
Clean Descriptions - Sanitize text fields.
@ basic
Basic Profile - Remove direct identifiers.
static auto make_remove() -> tag_action_config
Create a remove action config.
Definition tag_action.h:164
static auto make_hash(std::string algorithm="SHA256", bool salt=true) -> tag_action_config
Create a hash action config.
Definition tag_action.h:195
static auto make_keep() -> tag_action_config
Create a keep action config.
Definition tag_action.h:178
static auto make_replace(std::string value) -> tag_action_config
Create a replace action config with a custom value.
Definition tag_action.h:186
static auto make_empty() -> tag_action_config
Create an empty action config.
Definition tag_action.h:171

References kcenon::pacs::core::tags::accession_number, kcenon::pacs::core::tags::acquisition_date, kcenon::pacs::security::basic, kcenon::pacs::security::clean_descriptions, kcenon::pacs::security::clean_pixel, kcenon::pacs::core::tags::content_date, kcenon::pacs::core::tags::frame_of_reference_uid, kcenon::pacs::security::gdpr_compliant, kcenon::pacs::security::hipaa_safe_harbor, kcenon::pacs::core::tags::instance_creation_date, kcenon::pacs::core::tags::institution_address, kcenon::pacs::core::tags::institution_name, kcenon::pacs::security::tag_action_config::make_empty(), kcenon::pacs::security::tag_action_config::make_hash(), kcenon::pacs::security::tag_action_config::make_keep(), kcenon::pacs::security::tag_action_config::make_remove(), kcenon::pacs::security::tag_action_config::make_replace(), kcenon::pacs::core::tags::name_of_physicians_reading_study, kcenon::pacs::core::tags::operators_name, kcenon::pacs::core::tags::patient_address, kcenon::pacs::core::tags::patient_age, kcenon::pacs::core::tags::patient_birth_date, kcenon::pacs::core::tags::patient_comments, kcenon::pacs::core::tags::patient_id, kcenon::pacs::core::tags::patient_name, kcenon::pacs::core::tags::patient_sex, kcenon::pacs::core::tags::patient_size, kcenon::pacs::core::tags::patient_weight, kcenon::pacs::core::tags::performing_physician_name, kcenon::pacs::core::tags::referring_physician_name, kcenon::pacs::security::replace_uid, kcenon::pacs::security::retain_longitudinal, kcenon::pacs::security::retain_patient_characteristics, kcenon::pacs::core::tags::scheduled_performing_physician_name, kcenon::pacs::core::tags::series_date, kcenon::pacs::core::tags::series_description, kcenon::pacs::core::tags::series_instance_uid, kcenon::pacs::security::shift_date, kcenon::pacs::core::tags::sop_instance_uid, kcenon::pacs::core::tags::station_name, kcenon::pacs::core::tags::study_date, kcenon::pacs::core::tags::study_description, kcenon::pacs::core::tags::study_id, and kcenon::pacs::core::tags::study_instance_uid.

Here is the call graph for this function:

◆ get_tag_action()

auto kcenon::pacs::security::anonymizer::get_tag_action ( core::dicom_tag tag) const -> tag_action_config
nodiscard

Get the effective action for a tag.

Returns the custom action if set, otherwise the profile default.

Parameters
tagThe DICOM tag
Returns
The tag action configuration
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 231 of file anonymizer.cpp.

231 {
232 auto it = custom_actions_.find(tag);
233 if (it != custom_actions_.end()) {
234 return it->second;
235 }
236
237 auto profile_actions = get_profile_actions(profile_);
238 auto profile_it = profile_actions.find(tag);
239 if (profile_it != profile_actions.end()) {
240 return profile_it->second;
241 }
242
244}

References kcenon::pacs::security::tag_action_config::make_keep().

Here is the call graph for this function:

◆ has_encryption_key()

auto kcenon::pacs::security::anonymizer::has_encryption_key ( ) const -> bool
nodiscardnoexcept

Check if encryption is configured.

Returns
true if an encryption key is set
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 283 of file anonymizer.cpp.

283 {
284 return !encryption_key_.empty();
285}

References encryption_key_.

◆ hash_value()

auto kcenon::pacs::security::anonymizer::hash_value ( std::string_view value) const -> std::string
nodiscardprivate
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 632 of file anonymizer.cpp.

632 {
633 std::string to_hash = std::string(value);
634
635 if (hash_salt_.has_value()) {
636 to_hash = hash_salt_.value() + to_hash;
637 }
638
639#ifdef PACS_WITH_DIGITAL_SIGNATURES
640 auto* ctx = EVP_MD_CTX_new();
641 if (ctx == nullptr) {
642 return {};
643 }
644
645 struct md_ctx_deleter {
646 void operator()(EVP_MD_CTX* c) const { EVP_MD_CTX_free(c); }
647 };
648 std::unique_ptr<EVP_MD_CTX, md_ctx_deleter> ctx_guard(ctx);
649
650 if (EVP_DigestInit_ex(ctx, EVP_sha256(), nullptr) != 1
651 || EVP_DigestUpdate(ctx, to_hash.data(), to_hash.size()) != 1) {
652 return {};
653 }
654
655 std::array<unsigned char, EVP_MAX_MD_SIZE> digest{};
656 unsigned int digest_len = 0;
657 if (EVP_DigestFinal_ex(ctx, digest.data(), &digest_len) != 1) {
658 return {};
659 }
660
661 std::ostringstream oss;
662 oss << std::hex << std::setfill('0');
663 for (unsigned int i = 0; i < digest_len; ++i) {
664 oss << std::setw(2) << static_cast<int>(digest[i]);
665 }
666 return oss.str();
667#else
668 // Non-cryptographic fallback when OpenSSL is unavailable
669 auto hash = std::hash<std::string>{}(to_hash);
670 std::ostringstream oss;
671 oss << std::hex << std::setfill('0') << std::setw(16) << hash;
672 return oss.str();
673#endif
674}

References kcenon::pacs::security::hash.

◆ initialize_profile_actions()

void kcenon::pacs::security::anonymizer::initialize_profile_actions ( )
private
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 780 of file anonymizer.cpp.

780 {
781 // Profile actions are computed on-demand in get_profile_actions()
782 // This method can be used for any initialization if needed
783}

Referenced by anonymizer(), and set_profile().

Here is the caller graph for this function:

◆ is_detailed_reporting()

auto kcenon::pacs::security::anonymizer::is_detailed_reporting ( ) const -> bool
nodiscardnoexcept

Check if detailed reporting is enabled.

Returns
true if detailed reporting is enabled
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 299 of file anonymizer.cpp.

299 {
300 return detailed_reporting_;
301}

References detailed_reporting_.

◆ operator=() [1/2]

auto kcenon::pacs::security::anonymizer::operator= ( anonymizer && other) -> anonymizer&
noexcept

Move assignment.

Definition at line 70 of file anonymizer.cpp.

70 {
71 if (this != &other) {
72 profile_ = other.profile_;
73 custom_actions_ = std::move(other.custom_actions_);
74 date_offset_ = other.date_offset_;
75 encryption_key_ = std::move(other.encryption_key_);
76 hash_salt_ = std::move(other.hash_salt_);
77 private_tag_action_ = other.private_tag_action_;
78 detailed_reporting_ = other.detailed_reporting_;
79 }
80 return *this;
81}

References kcenon::pacs::security::other.

◆ operator=() [2/2]

auto kcenon::pacs::security::anonymizer::operator= ( const anonymizer & other) -> anonymizer&

Copy assignment.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 57 of file anonymizer.cpp.

57 {
58 if (this != &other) {
59 profile_ = other.profile_;
60 custom_actions_ = other.custom_actions_;
61 date_offset_ = other.date_offset_;
62 encryption_key_ = other.encryption_key_;
63 hash_salt_ = other.hash_salt_;
64 private_tag_action_ = other.private_tag_action_;
65 detailed_reporting_ = other.detailed_reporting_;
66 }
67 return *this;
68}

References kcenon::pacs::security::other.

◆ remove_tag_action()

auto kcenon::pacs::security::anonymizer::remove_tag_action ( core::dicom_tag tag) -> bool

Remove a custom tag action (reverts to profile default)

Parameters
tagThe tag to remove custom action for
Returns
true if an action was removed
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 223 of file anonymizer.cpp.

223 {
224 return custom_actions_.erase(tag) > 0;
225}

◆ set_date_offset()

void kcenon::pacs::security::anonymizer::set_date_offset ( std::chrono::days offset)

Set date offset for longitudinal consistency.

All date/time values will be shifted by this offset, preserving temporal relationships while removing actual dates.

Parameters
offsetThe number of days to shift (can be negative)
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 246 of file anonymizer.cpp.

246 {
247 date_offset_ = offset;
248}

References date_offset_.

◆ set_detailed_reporting()

void kcenon::pacs::security::anonymizer::set_detailed_reporting ( bool enable)

Enable detailed action recording.

When enabled, the anonymization report will include detailed records of each action performed.

Parameters
enabletrue to enable detailed recording
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 295 of file anonymizer.cpp.

295 {
296 detailed_reporting_ = enable;
297}

References detailed_reporting_.

◆ set_encryption_key()

auto kcenon::pacs::security::anonymizer::set_encryption_key ( std::span< const std::uint8_t > key) -> kcenon::common::VoidResult
nodiscard

Set encryption key for encrypt actions.

Parameters
keyThe encryption key (must be 32 bytes for AES-256)
Returns
Result indicating success or error
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 272 of file anonymizer.cpp.

273 {
274 if (key.size() != 32) {
275 return kcenon::common::make_error<std::monostate>(
276 1, "Encryption key must be 32 bytes for AES-256", "anonymizer"
277 );
278 }
279 encryption_key_.assign(key.begin(), key.end());
280 return kcenon::common::ok();
281}

◆ set_hash_salt()

void kcenon::pacs::security::anonymizer::set_hash_salt ( std::string salt)

Set salt for hash operations.

The salt is combined with values before hashing to prevent rainbow table attacks.

Parameters
saltThe salt value
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 287 of file anonymizer.cpp.

287 {
288 hash_salt_ = std::move(salt);
289}

References hash_salt_.

◆ set_private_tag_action()

void kcenon::pacs::security::anonymizer::set_private_tag_action ( private_tag_action action)

Set the action to take on private tags during anonymization.

DICOM PS3.15 Annex E recommends removing private data elements during de-identification, as they may contain PHI in vendor-specific formats that cannot be reliably inspected.

Parameters
actionThe private tag action
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 202 of file anonymizer.cpp.

202 {
203 private_tag_action_ = action;
204}

References private_tag_action_.

◆ set_profile()

void kcenon::pacs::security::anonymizer::set_profile ( anonymization_profile profile)

Set a new profile.

Changes the anonymization profile. Custom tag actions are preserved.

Parameters
profileThe new profile to use
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 197 of file anonymizer.cpp.

197 {
198 profile_ = profile;
200}

References initialize_profile_actions(), and profile_.

Here is the call graph for this function:

◆ shift_date()

auto kcenon::pacs::security::anonymizer::shift_date ( std::string_view date_string) const -> std::string
nodiscardprivate

Definition at line 584 of file anonymizer.cpp.

584 {
585 if (date_string.empty() || !date_offset_.has_value()) {
586 return "";
587 }
588
589 // Parse DICOM date format (YYYYMMDD)
590 if (date_string.length() < 8) {
591 return std::string(date_string);
592 }
593
594 try {
595 int year = std::stoi(std::string(date_string.substr(0, 4)));
596 int month = std::stoi(std::string(date_string.substr(4, 2)));
597 int day = std::stoi(std::string(date_string.substr(6, 2)));
598
599 // Create time point from date
600 std::tm tm = {};
601 tm.tm_year = year - 1900;
602 tm.tm_mon = month - 1;
603 tm.tm_mday = day;
604
605 auto time_point = std::chrono::system_clock::from_time_t(std::mktime(&tm));
606
607 // Apply offset
608 time_point += date_offset_.value();
609
610 // Convert back to date string
611 auto shifted_time = std::chrono::system_clock::to_time_t(time_point);
612 std::tm shifted_tm{};
613#if defined(_WIN32)
614 localtime_s(&shifted_tm, &shifted_time);
615#else
616 localtime_r(&shifted_time, &shifted_tm);
617#endif
618
619 std::ostringstream oss;
620 oss << std::setfill('0')
621 << std::setw(4) << (shifted_tm.tm_year + 1900)
622 << std::setw(2) << (shifted_tm.tm_mon + 1)
623 << std::setw(2) << shifted_tm.tm_mday;
624
625 return oss.str();
626
627 } catch (const std::exception&) {
628 return std::string(date_string);
629 }
630}

Member Data Documentation

◆ custom_actions_

std::map<core::dicom_tag, tag_action_config> kcenon::pacs::security::anonymizer::custom_actions_
private

Custom tag actions (override profile defaults)

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 377 of file anonymizer.h.

Referenced by add_tag_action(), add_tag_actions(), and clear_custom_actions().

◆ date_offset_

std::optional<std::chrono::days> kcenon::pacs::security::anonymizer::date_offset_
private

◆ detailed_reporting_

bool kcenon::pacs::security::anonymizer::detailed_reporting_ {false}
private

Whether to include detailed action records in report.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 392 of file anonymizer.h.

392{false};

Referenced by is_detailed_reporting(), and set_detailed_reporting().

◆ encryption_key_

std::vector<std::uint8_t> kcenon::pacs::security::anonymizer::encryption_key_
private

Encryption key (if set)

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 383 of file anonymizer.h.

Referenced by has_encryption_key().

◆ hash_salt_

std::optional<std::string> kcenon::pacs::security::anonymizer::hash_salt_
private

◆ private_tag_action_

private_tag_action kcenon::pacs::security::anonymizer::private_tag_action_ {private_tag_action::keep}
private

◆ profile_

anonymization_profile kcenon::pacs::security::anonymizer::profile_
private

Current anonymization profile.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/security/anonymizer.h.

Definition at line 374 of file anonymizer.h.

Referenced by get_profile(), and set_profile().


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