PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
storage_scp.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
17
18#include <kcenon/common/patterns/event_bus.h>
19
20namespace kcenon::pacs::services {
21
22// =============================================================================
23// Construction
24// =============================================================================
25
26storage_scp::storage_scp(std::shared_ptr<di::ILogger> logger)
27 : scp_service(std::move(logger)) {}
28
30 std::shared_ptr<di::ILogger> logger)
31 : scp_service(std::move(logger)), config_(config) {}
32
33// =============================================================================
34// Handler Registration
35// =============================================================================
36
38 handler_ = std::move(handler);
39}
40
42 pre_store_handler_ = std::move(handler);
43}
44
48
50 std::shared_ptr<kcenon::pacs::security::atna_service_auditor> auditor) {
51 auditor_ = std::move(auditor);
52}
53
54// =============================================================================
55// scp_service Interface Implementation
56// =============================================================================
57
58std::vector<std::string> storage_scp::supported_sop_classes() const {
59 if (!config_.accepted_sop_classes.empty()) {
61 }
63}
64
67 uint8_t context_id,
68 const network::dimse::dimse_message& request) {
69
70 using namespace network::dimse;
71
72 // Verify the message is a C-STORE request
73 if (request.command() != command_field::c_store_rq) {
76 "Expected C-STORE-RQ but received " +
77 std::string(to_string(request.command())));
78 }
79
80 // Extract SOP Class and Instance UIDs from the command set
81 const auto sop_class_uid = request.affected_sop_class_uid();
82 const auto sop_instance_uid = request.affected_sop_instance_uid();
83
84 // Verify the request has a dataset
85 if (!request.has_dataset()) {
86 auto response = make_c_store_rsp(
87 request.message_id(),
88 sop_class_uid,
89 sop_instance_uid,
90 static_cast<status_code>(storage_status::cannot_understand)
91 );
92 return assoc.send_dimse(context_id, response);
93 }
94
95 // Get dataset reference (already validated above)
96 const auto& dataset = request.dataset().value().get();
97
98 // Pre-store validation
99 if (pre_store_handler_ && !pre_store_handler_(dataset)) {
100 auto response = make_c_store_rsp(
101 request.message_id(),
102 sop_class_uid,
103 sop_instance_uid,
104 static_cast<status_code>(storage_status::cannot_understand)
105 );
106 return assoc.send_dimse(context_id, response);
107 }
108
109 // Determine storage status
111
112 if (handler_) {
113 // Call the registered storage handler
114 status = handler_(
115 dataset,
116 std::string(assoc.calling_ae()),
117 sop_class_uid,
118 sop_instance_uid
119 );
120 }
121
122 // Update statistics and notify on success or warning
123 if (!is_failure(status)) {
124 images_received_.fetch_add(1, std::memory_order_relaxed);
125 // Estimate dataset size - use element count as approximation
126 // In production, this would use actual serialized size
127 bytes_received_.fetch_add(
128 dataset.size() * sizeof(uint32_t),
129 std::memory_order_relaxed
130 );
131
132 // Call post-store handler for cache invalidation and notifications
133 auto patient_id = dataset.get_string(core::tags::patient_id);
134 auto study_uid = dataset.get_string(core::tags::study_instance_uid);
135 auto series_uid = dataset.get_string(core::tags::series_instance_uid);
136
139 dataset,
140 patient_id,
141 study_uid,
142 series_uid,
143 sop_instance_uid
144 );
145 }
146
147 // Publish image received event
148 kcenon::common::get_event_bus().publish(
150 patient_id,
151 study_uid,
152 series_uid,
153 sop_instance_uid,
154 sop_class_uid,
155 std::string(assoc.calling_ae()),
156 bytes_received_.load(std::memory_order_relaxed)
157 }
158 );
159 } else {
160 // Storage failed - publish failure event
161 auto patient_id = dataset.get_string(core::tags::patient_id);
162
163 kcenon::common::get_event_bus().publish(
165 patient_id,
166 sop_instance_uid,
167 std::string(assoc.calling_ae()),
168 static_cast<int>(status),
169 "C-STORE operation failed"
170 }
171 );
172 }
173
174 // Emit ATNA audit event
175 if (auditor_) {
176 auto audit_study_uid = dataset.get_string(core::tags::study_instance_uid);
177 auto audit_patient_id = dataset.get_string(core::tags::patient_id);
179 std::string(assoc.calling_ae()),
180 std::string(assoc.called_ae()),
181 audit_study_uid,
182 audit_patient_id,
183 !is_failure(status));
184 }
185
186 // Build and send the response
187 auto response = make_c_store_rsp(
188 request.message_id(),
189 sop_class_uid,
190 sop_instance_uid,
191 static_cast<status_code>(status)
192 );
193
194 return assoc.send_dimse(context_id, response);
195}
196
197std::string_view storage_scp::service_name() const noexcept {
198 return "Storage SCP";
199}
200
201// =============================================================================
202// Statistics
203// =============================================================================
204
205size_t storage_scp::images_received() const noexcept {
206 return images_received_.load(std::memory_order_relaxed);
207}
208
209size_t storage_scp::bytes_received() const noexcept {
210 return bytes_received_.load(std::memory_order_relaxed);
211}
212
214 images_received_.store(0, std::memory_order_relaxed);
215 bytes_received_.store(0, std::memory_order_relaxed);
216}
217
218// =============================================================================
219// Standard Storage SOP Classes
220// =============================================================================
221
222std::vector<std::string> get_standard_storage_sop_classes() {
223 return {
224 // CT
225 std::string(ct_image_storage_uid),
227
228 // MR
229 std::string(mr_image_storage_uid),
231
232 // CR/DX
233 std::string(cr_image_storage_uid),
236
237 // US
238 std::string(us_image_storage_uid),
239
240 // Secondary Capture
242
243 // RT
244 std::string(rt_image_storage_uid),
245 std::string(rt_dose_storage_uid),
246 std::string(rt_structure_set_storage_uid),
247 std::string(rt_plan_storage_uid),
248
249 // NM/PET
250 "1.2.840.10008.5.1.4.1.1.20", // NM Image Storage
251 "1.2.840.10008.5.1.4.1.1.128", // PET Image Storage
252 "1.2.840.10008.5.1.4.1.1.130", // Enhanced PET Image Storage
253
254 // XA/RF
255 "1.2.840.10008.5.1.4.1.1.12.1", // XA Image Storage
256 "1.2.840.10008.5.1.4.1.1.12.2", // XRF Image Storage
257
258 // Mammography
259 "1.2.840.10008.5.1.4.1.1.1.2", // Digital Mammography X-Ray - Presentation
260 "1.2.840.10008.5.1.4.1.1.1.2.1", // Digital Mammography X-Ray - Processing
261
262 // Multi-frame
263 "1.2.840.10008.5.1.4.1.1.7.1", // Multi-frame Single Bit SC
264 "1.2.840.10008.5.1.4.1.1.7.2", // Multi-frame Grayscale Byte SC
265 "1.2.840.10008.5.1.4.1.1.7.3", // Multi-frame Grayscale Word SC
266 "1.2.840.10008.5.1.4.1.1.7.4", // Multi-frame True Color SC
267
268 // SR (Structured Reports)
269 "1.2.840.10008.5.1.4.1.1.88.11", // Basic Text SR
270 "1.2.840.10008.5.1.4.1.1.88.22", // Enhanced SR
271 "1.2.840.10008.5.1.4.1.1.88.33", // Comprehensive SR
272 "1.2.840.10008.5.1.4.1.1.88.34", // Comprehensive 3D SR
273
274 // Other common types
275 "1.2.840.10008.5.1.4.1.1.104.1", // Encapsulated PDF
276 "1.2.840.10008.5.1.4.1.1.104.2", // Encapsulated CDA
277 };
278}
279
280} // namespace kcenon::pacs::services
High-level facade for ATNA audit logging in DICOM services.
Result< std::monostate > send_dimse(uint8_t context_id, const dimse::dimse_message &msg)
Send a DIMSE message.
std::string_view calling_ae() const noexcept
Get calling AE title.
std::string_view called_ae() const noexcept
Get called AE title.
auto message_id() const noexcept -> uint16_t
Get the message ID.
auto affected_sop_class_uid() const -> std::string
Get the Affected SOP Class UID.
auto has_dataset() const noexcept -> bool
Check if the message has an associated data set.
auto dataset() -> kcenon::pacs::Result< std::reference_wrapper< core::dicom_dataset > >
Get mutable reference to the data set.
auto affected_sop_instance_uid() const -> std::string
Get the Affected SOP Instance UID.
auto command() const noexcept -> command_field
Get the command field.
void audit_instance_stored(const std::string &source_ae, const std::string &dest_ae, const std::string &study_uid, const std::string &patient_id, bool success)
Audit a C-STORE (DICOM Instances Transferred) event.
void set_audit_handler(std::shared_ptr< kcenon::pacs::security::atna_service_auditor > auditor)
Set the ATNA audit handler for C-STORE operations.
storage_scp(std::shared_ptr< di::ILogger > logger=nullptr)
Construct a Storage SCP with default configuration.
size_t bytes_received() const noexcept
Get the total bytes received since construction.
void reset_statistics() noexcept
Reset statistics counters to zero.
void set_handler(storage_handler handler)
Set the storage handler callback.
size_t images_received() const noexcept
Get the number of images received since construction.
std::vector< std::string > supported_sop_classes() const override
Get supported SOP Class UIDs.
std::shared_ptr< kcenon::pacs::security::atna_service_auditor > auditor_
ATNA audit handler.
storage_handler handler_
Main storage handler.
network::Result< std::monostate > handle_message(network::association &assoc, uint8_t context_id, const network::dimse::dimse_message &request) override
Handle an incoming DIMSE message (C-STORE-RQ)
storage_scp_config config_
Configuration.
std::atomic< size_t > images_received_
Statistics: number of images received.
void set_post_store_handler(post_store_handler handler)
std::string_view service_name() const noexcept override
Get the service name.
void set_pre_store_handler(pre_store_handler handler)
Set the pre-store validation handler.
pre_store_handler pre_store_handler_
Pre-store validation handler.
post_store_handler post_store_handler_
Post-store notification handler.
std::atomic< size_t > bytes_received_
Statistics: total bytes received.
DIMSE command field enumeration.
Compile-time constants for commonly used DICOM tags.
DICOM event definitions for event-based communication.
constexpr dicom_tag patient_id
Patient ID.
constexpr dicom_tag study_instance_uid
Study Instance UID.
constexpr dicom_tag series_instance_uid
Series Instance UID.
constexpr int store_unexpected_command
Definition result.h:151
constexpr bool is_failure(storage_status status) noexcept
Check if the status indicates a failure.
std::vector< std::string > get_standard_storage_sop_classes()
Get a list of all standard Storage SOP Class UIDs.
constexpr std::string_view mr_image_storage_uid
MR Image Storage.
constexpr std::string_view cr_image_storage_uid
CR Image Storage.
constexpr std::string_view us_image_storage_uid
US Image Storage.
std::function< bool(const core::dicom_dataset &dataset)> pre_store_handler
Callback type for pre-store validation.
Definition storage_scp.h:81
storage_status
Storage operation status codes.
@ success
Success - image stored successfully (0x0000)
@ cannot_understand
Failure: Cannot understand - processing failure (0xC000)
std::function< storage_status( const core::dicom_dataset &dataset, const std::string &calling_ae, const std::string &sop_class_uid, const std::string &sop_instance_uid)> storage_handler
Callback type for handling received DICOM images.
Definition storage_scp.h:66
constexpr std::string_view secondary_capture_image_storage_uid
Secondary Capture Image Storage.
constexpr std::string_view rt_image_storage_uid
RT Image Storage.
std::function< void( const core::dicom_dataset &dataset, const std::string &patient_id, const std::string &study_uid, const std::string &series_uid, const std::string &sop_instance_uid)> post_store_handler
Callback type for post-store notification.
Definition storage_scp.h:95
constexpr std::string_view dx_image_storage_presentation_uid
Digital X-Ray Image Storage - For Presentation.
auto to_string(mpps_status status) -> std::string_view
Convert mpps_status to DICOM string representation.
Definition mpps_scp.h:60
constexpr std::string_view ct_image_storage_uid
CT Image Storage.
constexpr std::string_view rt_plan_storage_uid
RT Plan Storage.
constexpr std::string_view rt_dose_storage_uid
RT Dose Storage.
constexpr std::string_view enhanced_ct_image_storage_uid
Enhanced CT Image Storage.
constexpr std::string_view rt_structure_set_storage_uid
RT Structure Set Storage.
constexpr std::string_view enhanced_mr_image_storage_uid
Enhanced MR Image Storage.
constexpr std::string_view dx_image_storage_processing_uid
Digital X-Ray Image Storage - For Processing.
VoidResult pacs_void_error(int code, const std::string &message, const std::string &details="")
Create a PACS void error result.
Definition result.h:249
Result<T> type aliases and helpers for PACS system.
DIMSE status codes.
DICOM Storage SCP service (C-STORE handler)
Event published when an image is successfully received via C-STORE.
Definition events.h:108
Event published when a C-STORE operation fails.
Definition events.h:138
Configuration for Storage SCP service.
Definition storage_scp.h:49
std::vector< std::string > accepted_sop_classes
List of accepted SOP Class UIDs (empty = accept all standard storage classes)
Definition storage_scp.h:51