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

#include <storage_scu.h>

Collaboration diagram for kcenon::pacs::services::storage_scu:
Collaboration graph

Public Member Functions

 storage_scu (std::shared_ptr< di::ILogger > logger=nullptr)
 Construct a Storage SCU with default configuration.
 
 storage_scu (const storage_scu_config &config, std::shared_ptr< di::ILogger > logger=nullptr)
 Construct a Storage SCU with custom configuration.
 
 ~storage_scu ()=default
 
 storage_scu (const storage_scu &)=delete
 
storage_scuoperator= (const storage_scu &)=delete
 
 storage_scu (storage_scu &&)=delete
 
storage_scuoperator= (storage_scu &&)=delete
 
network::Result< store_resultstore (network::association &assoc, const core::dicom_dataset &dataset)
 Store a single DICOM dataset.
 
std::vector< store_resultstore_batch (network::association &assoc, const std::vector< core::dicom_dataset > &datasets, store_progress_callback progress_callback=nullptr)
 Store multiple DICOM datasets.
 
network::Result< store_resultstore_file (network::association &assoc, const std::filesystem::path &file_path)
 Store a DICOM file.
 
std::vector< store_resultstore_files (network::association &assoc, const std::vector< std::filesystem::path > &file_paths, store_progress_callback progress_callback=nullptr)
 Store multiple DICOM files.
 
std::vector< store_resultstore_directory (network::association &assoc, const std::filesystem::path &directory, bool recursive=true, store_progress_callback progress_callback=nullptr)
 Store all DICOM files in a directory.
 
size_t images_sent () const noexcept
 Get the number of images sent since construction.
 
size_t failures () const noexcept
 Get the number of failed store operations since construction.
 
size_t bytes_sent () const noexcept
 Get the total bytes sent since construction.
 
void reset_statistics () noexcept
 Reset statistics counters to zero.
 

Private Member Functions

network::Result< store_resultstore_impl (network::association &assoc, const core::dicom_dataset &dataset, uint16_t message_id)
 Internal implementation of single store operation.
 
uint16_t next_message_id () noexcept
 Get the next message ID for DIMSE operations.
 
std::vector< std::filesystem::path > collect_dicom_files (const std::filesystem::path &directory, bool recursive) const
 Collect DICOM files from a directory.
 

Private Attributes

std::shared_ptr< di::ILoggerlogger_
 Logger instance for service logging.
 
storage_scu_config config_
 Configuration.
 
std::atomic< uint16_t > message_id_counter_ {1}
 Message ID counter.
 
std::atomic< size_t > images_sent_ {0}
 Statistics: number of images sent successfully.
 
std::atomic< size_t > failures_ {0}
 Statistics: number of failed operations.
 
std::atomic< size_t > bytes_sent_ {0}
 Statistics: total bytes sent.
 

Detailed Description

Definition at line 167 of file storage_scu.h.

Constructor & Destructor Documentation

◆ storage_scu() [1/4]

kcenon::pacs::services::storage_scu::storage_scu ( std::shared_ptr< di::ILogger > logger = nullptr)
explicit

Construct a Storage SCU with default configuration.

Parameters
loggerLogger instance for service logging (nullptr uses null_logger)

Definition at line 56 of file storage_scu.cpp.

57 : logger_(logger ? std::move(logger) : di::null_logger()) {}
std::shared_ptr< di::ILogger > logger_
Logger instance for service logging.
std::shared_ptr< ILogger > null_logger()
Get a shared null logger instance.
Definition ilogger.h:271

◆ storage_scu() [2/4]

kcenon::pacs::services::storage_scu::storage_scu ( const storage_scu_config & config,
std::shared_ptr< di::ILogger > logger = nullptr )
explicit

Construct a Storage SCU with custom configuration.

Parameters
configConfiguration options
loggerLogger instance for service logging (nullptr uses null_logger)

Definition at line 59 of file storage_scu.cpp.

61 : logger_(logger ? std::move(logger) : di::null_logger()), config_(config) {}
storage_scu_config config_
Configuration.

◆ ~storage_scu()

kcenon::pacs::services::storage_scu::~storage_scu ( )
default

◆ storage_scu() [3/4]

kcenon::pacs::services::storage_scu::storage_scu ( const storage_scu & )
delete

◆ storage_scu() [4/4]

kcenon::pacs::services::storage_scu::storage_scu ( storage_scu && )
delete

Member Function Documentation

◆ bytes_sent()

size_t kcenon::pacs::services::storage_scu::bytes_sent ( ) const
nodiscardnoexcept

Get the total bytes sent since construction.

Returns
Total bytes of dataset data sent

Definition at line 357 of file storage_scu.cpp.

357 {
358 return bytes_sent_.load(std::memory_order_relaxed);
359}
std::atomic< size_t > bytes_sent_
Statistics: total bytes sent.

References bytes_sent_.

◆ collect_dicom_files()

std::vector< std::filesystem::path > kcenon::pacs::services::storage_scu::collect_dicom_files ( const std::filesystem::path & directory,
bool recursive ) const
nodiscardprivate

Collect DICOM files from a directory.

Definition at line 312 of file storage_scu.cpp.

314 {
315
316 std::vector<std::filesystem::path> files;
317
318 if (!std::filesystem::exists(directory) ||
319 !std::filesystem::is_directory(directory)) {
320 return files;
321 }
322
323 if (recursive) {
324 for (const auto& entry :
325 std::filesystem::recursive_directory_iterator(directory)) {
326 if (entry.is_regular_file() && is_dicom_file(entry.path())) {
327 files.push_back(entry.path());
328 }
329 }
330 } else {
331 for (const auto& entry :
332 std::filesystem::directory_iterator(directory)) {
333 if (entry.is_regular_file() && is_dicom_file(entry.path())) {
334 files.push_back(entry.path());
335 }
336 }
337 }
338
339 // Sort files for deterministic order
340 std::sort(files.begin(), files.end());
341
342 return files;
343}

Referenced by store_directory().

Here is the caller graph for this function:

◆ failures()

size_t kcenon::pacs::services::storage_scu::failures ( ) const
nodiscardnoexcept

Get the number of failed store operations since construction.

Returns
Count of failed operations

Definition at line 353 of file storage_scu.cpp.

353 {
354 return failures_.load(std::memory_order_relaxed);
355}
std::atomic< size_t > failures_
Statistics: number of failed operations.

References failures_.

◆ images_sent()

size_t kcenon::pacs::services::storage_scu::images_sent ( ) const
nodiscardnoexcept

Get the number of images sent since construction.

Returns
Count of successfully sent images

Definition at line 349 of file storage_scu.cpp.

349 {
350 return images_sent_.load(std::memory_order_relaxed);
351}
std::atomic< size_t > images_sent_
Statistics: number of images sent successfully.

References images_sent_.

◆ next_message_id()

uint16_t kcenon::pacs::services::storage_scu::next_message_id ( )
nodiscardprivatenoexcept

Get the next message ID for DIMSE operations.

Definition at line 371 of file storage_scu.cpp.

371 {
372 uint16_t id = message_id_counter_.fetch_add(1, std::memory_order_relaxed);
373 // Wrap around at 0xFFFF, skip 0 (reserved)
374 if (id == 0) {
375 id = message_id_counter_.fetch_add(1, std::memory_order_relaxed);
376 }
377 return id;
378}
std::atomic< uint16_t > message_id_counter_
Message ID counter.
@ id
Implant Displaced (alternate code)

References message_id_counter_.

Referenced by store().

Here is the caller graph for this function:

◆ operator=() [1/2]

storage_scu & kcenon::pacs::services::storage_scu::operator= ( const storage_scu & )
delete

◆ operator=() [2/2]

storage_scu & kcenon::pacs::services::storage_scu::operator= ( storage_scu && )
delete

◆ reset_statistics()

void kcenon::pacs::services::storage_scu::reset_statistics ( )
noexcept

Reset statistics counters to zero.

Definition at line 361 of file storage_scu.cpp.

361 {
362 images_sent_.store(0, std::memory_order_relaxed);
363 failures_.store(0, std::memory_order_relaxed);
364 bytes_sent_.store(0, std::memory_order_relaxed);
365}

References bytes_sent_, failures_, and images_sent_.

◆ store()

network::Result< store_result > kcenon::pacs::services::storage_scu::store ( network::association & assoc,
const core::dicom_dataset & dataset )
nodiscard

Store a single DICOM dataset.

Sends the dataset via C-STORE to the remote SCP. The dataset must contain valid SOP Class UID and SOP Instance UID attributes.

Parameters
assocThe established association to use
datasetThe DICOM dataset to store
Returns
Result containing store_result on success, or error message

Definition at line 67 of file storage_scu.cpp.

69 {
70
71 return store_impl(assoc, dataset, next_message_id());
72}
uint16_t next_message_id() noexcept
Get the next message ID for DIMSE operations.
network::Result< store_result > store_impl(network::association &assoc, const core::dicom_dataset &dataset, uint16_t message_id)
Internal implementation of single store operation.

References next_message_id(), and store_impl().

Referenced by store_batch(), store_file(), TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), TEST_CASE(), and TEST_CASE().

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

◆ store_batch()

std::vector< store_result > kcenon::pacs::services::storage_scu::store_batch ( network::association & assoc,
const std::vector< core::dicom_dataset > & datasets,
store_progress_callback progress_callback = nullptr )
nodiscard

Store multiple DICOM datasets.

Sends multiple datasets via C-STORE operations. If continue_on_error is true (default), continues with remaining datasets after failures.

Parameters
assocThe established association to use
datasetsVector of datasets to store
progress_callbackOptional callback for progress updates
Returns
Vector of store_result for each dataset

Definition at line 177 of file storage_scu.cpp.

180 {
181
182 std::vector<store_result> results;
183 results.reserve(datasets.size());
184
185 const size_t total = datasets.size();
186 size_t completed = 0;
187
188 for (const auto& dataset : datasets) {
189 auto result = store(assoc, dataset);
190
191 if (result.is_ok()) {
192 results.push_back(std::move(result.value()));
193 } else {
194 // Create a failure result
195 store_result failure_result;
196 failure_result.sop_instance_uid = dataset.get_string(tag_sop_instance_uid);
197 failure_result.status = static_cast<uint16_t>(storage_status::cannot_understand);
198 failure_result.error_comment = result.error().message;
199 results.push_back(std::move(failure_result));
200
201 // Stop on error if configured
203 break;
204 }
205 }
206
207 ++completed;
208
209 // Report progress
210 if (progress_callback) {
212 }
213 }
214
215 return results;
216}
network::Result< store_result > store(network::association &assoc, const core::dicom_dataset &dataset)
Store a single DICOM dataset.
@ cannot_understand
Failure: Cannot understand - processing failure (0xC000)
@ completed
Procedure completed successfully.
std::function< bool(std::size_t bytes_transferred, std::size_t total_bytes)> progress_callback
Callback type for upload/download progress tracking.
Definition s3_storage.h:115
bool continue_on_error
Continue batch operation on error (true) or stop on first error (false)
Definition storage_scu.h:88

References kcenon::pacs::services::cannot_understand, kcenon::pacs::services::completed, config_, kcenon::pacs::services::storage_scu_config::continue_on_error, kcenon::pacs::services::store_result::error_comment, kcenon::pacs::services::store_result::sop_instance_uid, kcenon::pacs::services::store_result::status, and store().

Here is the call graph for this function:

◆ store_directory()

std::vector< store_result > kcenon::pacs::services::storage_scu::store_directory ( network::association & assoc,
const std::filesystem::path & directory,
bool recursive = true,
store_progress_callback progress_callback = nullptr )
nodiscard

Store all DICOM files in a directory.

Scans a directory for DICOM files and stores them via C-STORE.

Parameters
assocThe established association to use
directoryPath to the directory
recursiveIf true, scan subdirectories recursively
progress_callbackOptional callback for progress updates
Returns
Vector of store_result for each file

Definition at line 299 of file storage_scu.cpp.

303 {
304
305 // Collect all DICOM files from the directory
306 auto files = collect_dicom_files(directory, recursive);
307
308 // Delegate to store_files for the actual processing
309 return store_files(assoc, files, progress_callback);
310}
std::vector< std::filesystem::path > collect_dicom_files(const std::filesystem::path &directory, bool recursive) const
Collect DICOM files from a directory.
std::vector< store_result > store_files(network::association &assoc, const std::vector< std::filesystem::path > &file_paths, store_progress_callback progress_callback=nullptr)
Store multiple DICOM files.

References collect_dicom_files(), and store_files().

Here is the call graph for this function:

◆ store_file()

network::Result< store_result > kcenon::pacs::services::storage_scu::store_file ( network::association & assoc,
const std::filesystem::path & file_path )
nodiscard

Store a DICOM file.

Reads and parses a DICOM file, then sends it via C-STORE.

Parameters
assocThe established association to use
file_pathPath to the DICOM file
Returns
Result containing store_result on success, or error message

Definition at line 222 of file storage_scu.cpp.

224 {
225
226 // Check if file exists
227 if (!std::filesystem::exists(file_path)) {
230 "File not found: " + file_path.string());
231 }
232
233 // Check if it's a regular file
234 if (!std::filesystem::is_regular_file(file_path)) {
237 "Not a regular file: " + file_path.string());
238 }
239
240 // Parse the DICOM file
241 auto file_result = core::dicom_file::open(file_path);
242 if (file_result.is_err()) {
243 const auto error_msg = "Failed to parse DICOM file: " +
244 file_path.string() + ": " +
245 file_result.error().message;
246 logger_->error(error_msg);
249 }
250
251 // Get the dataset from the parsed file
252 const auto& dataset = file_result.value().dataset();
253
254 // Store the dataset using the existing store method
255 return store(assoc, dataset);
256}
static auto open(const std::filesystem::path &path) -> kcenon::pacs::Result< dicom_file >
Open and read a DICOM file from disk.
constexpr int file_parse_failed
Definition result.h:206
constexpr int not_a_regular_file
Definition result.h:204
constexpr int file_not_found_service
Definition result.h:203
Result< T > pacs_error(int code, const std::string &message, const std::string &details="")
Create a PACS error result with module context.
Definition result.h:234

References kcenon::pacs::error_codes::file_not_found_service, kcenon::pacs::error_codes::file_parse_failed, logger_, kcenon::pacs::error_codes::not_a_regular_file, kcenon::pacs::core::dicom_file::open(), kcenon::pacs::pacs_error(), and store().

Referenced by store_files().

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

◆ store_files()

std::vector< store_result > kcenon::pacs::services::storage_scu::store_files ( network::association & assoc,
const std::vector< std::filesystem::path > & file_paths,
store_progress_callback progress_callback = nullptr )
nodiscard

Store multiple DICOM files.

Reads and parses multiple DICOM files, then sends them via C-STORE. If continue_on_error is true (default), continues with remaining files after failures.

Parameters
assocThe established association to use
file_pathsVector of paths to DICOM files
progress_callbackOptional callback for progress updates
Returns
Vector of store_result for each file

Definition at line 258 of file storage_scu.cpp.

261 {
262
263 std::vector<store_result> results;
264 results.reserve(file_paths.size());
265
266 const size_t total = file_paths.size();
267 size_t completed = 0;
268
269 for (const auto& file_path : file_paths) {
270 auto result = store_file(assoc, file_path);
271
272 if (result.is_ok()) {
273 results.push_back(std::move(result.value()));
274 } else {
275 // Create a failure result
276 store_result failure_result;
277 failure_result.sop_instance_uid = file_path.filename().string();
278 failure_result.status = static_cast<uint16_t>(storage_status::cannot_understand);
279 failure_result.error_comment = result.error().message;
280 results.push_back(std::move(failure_result));
281
282 // Stop on error if configured
284 break;
285 }
286 }
287
288 ++completed;
289
290 // Report progress
291 if (progress_callback) {
293 }
294 }
295
296 return results;
297}
network::Result< store_result > store_file(network::association &assoc, const std::filesystem::path &file_path)
Store a DICOM file.

References kcenon::pacs::services::cannot_understand, kcenon::pacs::services::completed, config_, kcenon::pacs::services::storage_scu_config::continue_on_error, kcenon::pacs::services::store_result::error_comment, kcenon::pacs::services::store_result::sop_instance_uid, kcenon::pacs::services::store_result::status, and store_file().

Referenced by store_directory().

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

◆ store_impl()

network::Result< store_result > kcenon::pacs::services::storage_scu::store_impl ( network::association & assoc,
const core::dicom_dataset & dataset,
uint16_t message_id )
nodiscardprivate

Internal implementation of single store operation.

Definition at line 74 of file storage_scu.cpp.

77 {
78
79 using namespace network::dimse;
80
81 // Extract SOP Class UID from dataset
82 const auto sop_class_uid = dataset.get_string(tag_sop_class_uid);
83 if (sop_class_uid.empty()) {
86 "Missing SOP Class UID in dataset");
87 }
88
89 // Extract SOP Instance UID from dataset
90 const auto sop_instance_uid = dataset.get_string(tag_sop_instance_uid);
91 if (sop_instance_uid.empty()) {
94 "Missing SOP Instance UID in dataset");
95 }
96
97 // Verify association is established
98 if (!assoc.is_established()) {
101 "Association not established");
102 }
103
104 // Get accepted presentation context for this SOP class
105 auto context_id = assoc.accepted_context_id(sop_class_uid);
106 if (!context_id) {
109 "No accepted presentation context for SOP Class: " + sop_class_uid);
110 }
111
112 // Build C-STORE-RQ message
113 auto request = make_c_store_rq(
114 message_id,
115 sop_class_uid,
116 sop_instance_uid,
118 );
119
120 // Attach the dataset
121 request.set_dataset(dataset);
122
123 // Send the request
124 auto send_result = assoc.send_dimse(*context_id, request);
125 if (send_result.is_err()) {
126 failures_.fetch_add(1, std::memory_order_relaxed);
127 return send_result.error();
128 }
129
130 // Receive the response
131 auto recv_result = assoc.receive_dimse(config_.response_timeout);
132 if (recv_result.is_err()) {
133 failures_.fetch_add(1, std::memory_order_relaxed);
134 return recv_result.error();
135 }
136
137 const auto& [recv_context_id, response] = recv_result.value();
138
139 // Verify it's a C-STORE response
140 if (response.command() != command_field::c_store_rsp) {
141 failures_.fetch_add(1, std::memory_order_relaxed);
144 "Expected C-STORE-RSP but received " +
145 std::string(to_string(response.command())));
146 }
147
148 // Build result from response
149 store_result result;
150 result.sop_instance_uid = sop_instance_uid;
151 result.status = static_cast<uint16_t>(response.status());
152
153 // Extract error comment if present
154 if (response.command_set().contains(tag_error_comment)) {
155 result.error_comment = response.command_set().get_string(tag_error_comment);
156 }
157
158 // Update statistics
159 if (result.is_success() || result.is_warning()) {
160 images_sent_.fetch_add(1, std::memory_order_relaxed);
161 // Estimate dataset size
162 bytes_sent_.fetch_add(
163 dataset.size() * sizeof(uint32_t),
164 std::memory_order_relaxed
165 );
166 } else {
167 failures_.fetch_add(1, std::memory_order_relaxed);
168 }
169
170 return result;
171}
constexpr dicom_tag sop_instance_uid
SOP Instance UID.
constexpr dicom_tag sop_class_uid
SOP Class UID.
constexpr int store_unexpected_command
Definition result.h:151
constexpr int store_missing_sop_instance_uid
Definition result.h:147
constexpr int association_not_established
Definition result.h:202
constexpr int store_no_accepted_context
Definition result.h:148
constexpr int store_missing_sop_class_uid
Definition result.h:146
auto to_string(mpps_status status) -> std::string_view
Convert mpps_status to DICOM string representation.
Definition mpps_scp.h:60
std::chrono::milliseconds response_timeout
Timeout for receiving C-STORE response (milliseconds)
Definition storage_scu.h:85
uint16_t default_priority
Default priority for C-STORE requests (0=medium, 1=high, 2=low)
Definition storage_scu.h:82

References kcenon::pacs::network::association::accepted_context_id(), kcenon::pacs::error_codes::association_not_established, bytes_sent_, config_, kcenon::pacs::services::storage_scu_config::default_priority, kcenon::pacs::services::store_result::error_comment, failures_, kcenon::pacs::core::dicom_dataset::get_string(), images_sent_, kcenon::pacs::network::association::is_established(), kcenon::pacs::services::store_result::is_success(), kcenon::pacs::services::store_result::is_warning(), kcenon::pacs::pacs_error(), kcenon::pacs::network::association::receive_dimse(), kcenon::pacs::services::storage_scu_config::response_timeout, kcenon::pacs::network::association::send_dimse(), kcenon::pacs::core::dicom_dataset::size(), kcenon::pacs::services::store_result::sop_instance_uid, kcenon::pacs::services::store_result::status, kcenon::pacs::error_codes::store_missing_sop_class_uid, kcenon::pacs::error_codes::store_missing_sop_instance_uid, kcenon::pacs::error_codes::store_no_accepted_context, kcenon::pacs::error_codes::store_unexpected_command, and kcenon::pacs::services::to_string().

Referenced by store().

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

Member Data Documentation

◆ bytes_sent_

std::atomic<size_t> kcenon::pacs::services::storage_scu::bytes_sent_ {0}
private

Statistics: total bytes sent.

Definition at line 358 of file storage_scu.h.

358{0};

Referenced by bytes_sent(), reset_statistics(), and store_impl().

◆ config_

storage_scu_config kcenon::pacs::services::storage_scu::config_
private

Configuration.

Definition at line 346 of file storage_scu.h.

Referenced by store_batch(), store_files(), and store_impl().

◆ failures_

std::atomic<size_t> kcenon::pacs::services::storage_scu::failures_ {0}
private

Statistics: number of failed operations.

Definition at line 355 of file storage_scu.h.

355{0};

Referenced by failures(), reset_statistics(), and store_impl().

◆ images_sent_

std::atomic<size_t> kcenon::pacs::services::storage_scu::images_sent_ {0}
private

Statistics: number of images sent successfully.

Definition at line 352 of file storage_scu.h.

352{0};

Referenced by images_sent(), reset_statistics(), and store_impl().

◆ logger_

std::shared_ptr<di::ILogger> kcenon::pacs::services::storage_scu::logger_
private

Logger instance for service logging.

Definition at line 343 of file storage_scu.h.

Referenced by store_file().

◆ message_id_counter_

std::atomic<uint16_t> kcenon::pacs::services::storage_scu::message_id_counter_ {1}
private

Message ID counter.

Definition at line 349 of file storage_scu.h.

349{1};

Referenced by next_message_id().


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