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

#include <worklist_scu.h>

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

Public Member Functions

 worklist_scu (std::shared_ptr< di::ILogger > logger=nullptr)
 Construct a Worklist SCU with default configuration.
 
 worklist_scu (const worklist_scu_config &config, std::shared_ptr< di::ILogger > logger=nullptr)
 Construct a Worklist SCU with custom configuration.
 
 ~worklist_scu ()=default
 
 worklist_scu (const worklist_scu &)=delete
 
worklist_scuoperator= (const worklist_scu &)=delete
 
 worklist_scu (worklist_scu &&)=delete
 
worklist_scuoperator= (worklist_scu &&)=delete
 
network::Result< worklist_resultquery (network::association &assoc, const worklist_query_keys &keys)
 Perform a MWL C-FIND query with typed keys.
 
network::Result< worklist_resultquery (network::association &assoc, const core::dicom_dataset &query_keys)
 Perform a MWL C-FIND query with raw dataset.
 
network::Result< worklist_resultquery_today (network::association &assoc, std::string_view station_ae, std::string_view modality="")
 Query today's worklist for a station.
 
network::Result< worklist_resultquery_date_range (network::association &assoc, std::string_view start_date, std::string_view end_date, std::string_view modality="")
 Query worklist by date range.
 
network::Result< worklist_resultquery_patient (network::association &assoc, std::string_view patient_id)
 Query worklist by patient ID.
 
network::Result< size_t > query_streaming (network::association &assoc, const worklist_query_keys &keys, worklist_streaming_callback callback)
 Perform a streaming MWL query for large worklists.
 
network::Result< std::monostate > cancel (network::association &assoc, uint16_t message_id)
 Send a C-CANCEL request to stop an ongoing query.
 
void set_config (const worklist_scu_config &config)
 Update the SCU configuration.
 
const worklist_scu_configconfig () const noexcept
 Get the current configuration.
 
size_t queries_performed () const noexcept
 Get the number of queries performed since construction.
 
size_t total_items () const noexcept
 Get the total number of items received since construction.
 
void reset_statistics () noexcept
 Reset statistics counters to zero.
 

Private Member Functions

worklist_item parse_worklist_item (const core::dicom_dataset &ds) const
 Parse a worklist item from a response dataset.
 
core::dicom_dataset build_query_dataset (const worklist_query_keys &keys) const
 Build query dataset from typed keys.
 
uint16_t next_message_id () noexcept
 Get the next message ID for DIMSE operations.
 
network::Result< worklist_resultquery_impl (network::association &assoc, const core::dicom_dataset &query_keys, uint16_t message_id)
 Internal query implementation.
 

Static Private Member Functions

static std::string get_today_date ()
 Get today's date in DICOM format (YYYYMMDD)
 

Private Attributes

std::shared_ptr< di::ILoggerlogger_
 Logger instance for service logging.
 
worklist_scu_config config_
 Configuration.
 
std::atomic< uint16_t > message_id_counter_ {1}
 Message ID counter.
 
std::atomic< size_t > queries_performed_ {0}
 Statistics: number of queries performed.
 
std::atomic< size_t > total_items_ {0}
 Statistics: total number of items received.
 

Detailed Description

Examples
worklist_scu/main.cpp.

Definition at line 357 of file worklist_scu.h.

Constructor & Destructor Documentation

◆ worklist_scu() [1/4]

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

Construct a Worklist SCU with default configuration.

Parameters
loggerLogger instance for service logging (nullptr uses null_logger)

Definition at line 31 of file worklist_scu.cpp.

32 : 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

◆ worklist_scu() [2/4]

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

Construct a Worklist SCU with custom configuration.

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

Definition at line 34 of file worklist_scu.cpp.

36 : logger_(logger ? std::move(logger) : di::null_logger()), config_(config) {}
const worklist_scu_config & config() const noexcept
Get the current configuration.
worklist_scu_config config_
Configuration.

◆ ~worklist_scu()

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

◆ worklist_scu() [3/4]

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

◆ worklist_scu() [4/4]

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

Member Function Documentation

◆ build_query_dataset()

core::dicom_dataset kcenon::pacs::services::worklist_scu::build_query_dataset ( const worklist_query_keys & keys) const
nodiscardprivate

Build query dataset from typed keys.

Definition at line 441 of file worklist_scu.cpp.

442 {
443
444 using namespace core;
445 using namespace encoding;
446
447 dicom_dataset ds;
448
449 // Patient demographics (return keys with optional search criteria)
450 ds.set_string(tags::patient_name, vr_type::PN, keys.patient_name);
451 ds.set_string(tags::patient_id, vr_type::LO, keys.patient_id);
452
453 if (!keys.patient_birth_date.empty()) {
454 ds.set_string(tags::patient_birth_date, vr_type::DA, keys.patient_birth_date);
455 } else {
456 ds.set_string(tags::patient_birth_date, vr_type::DA, "");
457 }
458
459 if (!keys.patient_sex.empty()) {
460 ds.set_string(tags::patient_sex, vr_type::CS, keys.patient_sex);
461 } else {
462 ds.set_string(tags::patient_sex, vr_type::CS, "");
463 }
464
465 // Study-level return keys
466 ds.set_string(tags::study_instance_uid, vr_type::UI, "");
467 ds.set_string(tags::accession_number, vr_type::SH, keys.accession_number);
468 ds.set_string(tags::referring_physician_name, vr_type::PN, keys.referring_physician);
469 ds.set_string(tags::institution_name, vr_type::LO, keys.institution);
470
471 // Requested Procedure attributes
472 ds.set_string(tags::requested_procedure_id, vr_type::SH, keys.requested_procedure_id);
473 ds.set_string(tags::study_description, vr_type::LO, keys.requested_procedure_description);
474
475 // Scheduled Procedure Step attributes (flat structure)
476 ds.set_string(tags::scheduled_station_ae_title, vr_type::AE, keys.scheduled_station_ae);
477 ds.set_string(tags::scheduled_procedure_step_start_date, vr_type::DA, keys.scheduled_date);
478 ds.set_string(tags::scheduled_procedure_step_start_time, vr_type::TM, keys.scheduled_time);
479 ds.set_string(tags::modality, vr_type::CS, keys.modality);
480 ds.set_string(tags::scheduled_performing_physician_name, vr_type::PN, keys.scheduled_physician);
481 ds.set_string(tags::scheduled_procedure_step_id, vr_type::SH, keys.scheduled_procedure_step_id);
482
483 // Additional return keys
484 ds.set_string(tags::scheduled_procedure_step_description, vr_type::LO, "");
485 ds.set_string(tags::scheduled_station_name, vr_type::SH, "");
486 ds.set_string(tags::scheduled_procedure_step_location, vr_type::SH, "");
487
488 return ds;
489}
constexpr dicom_tag referring_physician_name
Referring Physician's Name.
constexpr dicom_tag scheduled_procedure_step_description
Scheduled Procedure Step Description.
constexpr dicom_tag institution_name
Institution Name.
constexpr dicom_tag study_description
Study Description.
constexpr dicom_tag patient_id
Patient ID.
constexpr dicom_tag scheduled_procedure_step_start_date
Scheduled Procedure Step Start Date.
constexpr dicom_tag scheduled_procedure_step_location
Scheduled Procedure Step Location.
constexpr dicom_tag patient_birth_date
Patient's Birth Date.
constexpr dicom_tag scheduled_performing_physician_name
Scheduled Performing Physician's Name.
constexpr dicom_tag accession_number
Accession Number.
constexpr dicom_tag modality
Modality.
constexpr dicom_tag scheduled_station_ae_title
Scheduled Station AE Title.
constexpr dicom_tag patient_sex
Patient's Sex.
constexpr dicom_tag study_instance_uid
Study Instance UID.
constexpr dicom_tag scheduled_station_name
Scheduled Station Name.
constexpr dicom_tag requested_procedure_id
Requested Procedure ID.
constexpr dicom_tag patient_name
Patient's Name.
constexpr dicom_tag scheduled_procedure_step_start_time
Scheduled Procedure Step Start Time.
constexpr dicom_tag scheduled_procedure_step_id
Scheduled Procedure Step ID.

References kcenon::pacs::core::tags::accession_number, kcenon::pacs::services::worklist_query_keys::accession_number, kcenon::pacs::services::worklist_query_keys::institution, kcenon::pacs::core::tags::institution_name, kcenon::pacs::core::tags::modality, kcenon::pacs::services::worklist_query_keys::modality, kcenon::pacs::core::tags::patient_birth_date, kcenon::pacs::services::worklist_query_keys::patient_birth_date, kcenon::pacs::core::tags::patient_id, kcenon::pacs::services::worklist_query_keys::patient_id, kcenon::pacs::core::tags::patient_name, kcenon::pacs::services::worklist_query_keys::patient_name, kcenon::pacs::core::tags::patient_sex, kcenon::pacs::services::worklist_query_keys::patient_sex, kcenon::pacs::services::worklist_query_keys::referring_physician, kcenon::pacs::core::tags::referring_physician_name, kcenon::pacs::services::worklist_query_keys::requested_procedure_description, kcenon::pacs::core::tags::requested_procedure_id, kcenon::pacs::services::worklist_query_keys::requested_procedure_id, kcenon::pacs::services::worklist_query_keys::scheduled_date, kcenon::pacs::core::tags::scheduled_performing_physician_name, kcenon::pacs::services::worklist_query_keys::scheduled_physician, kcenon::pacs::core::tags::scheduled_procedure_step_description, kcenon::pacs::core::tags::scheduled_procedure_step_id, kcenon::pacs::services::worklist_query_keys::scheduled_procedure_step_id, kcenon::pacs::core::tags::scheduled_procedure_step_location, kcenon::pacs::core::tags::scheduled_procedure_step_start_date, kcenon::pacs::core::tags::scheduled_procedure_step_start_time, kcenon::pacs::services::worklist_query_keys::scheduled_station_ae, kcenon::pacs::core::tags::scheduled_station_ae_title, kcenon::pacs::core::tags::scheduled_station_name, kcenon::pacs::services::worklist_query_keys::scheduled_time, kcenon::pacs::core::tags::study_description, and kcenon::pacs::core::tags::study_instance_uid.

Referenced by query(), and query_streaming().

Here is the caller graph for this function:

◆ cancel()

network::Result< std::monostate > kcenon::pacs::services::worklist_scu::cancel ( network::association & assoc,
uint16_t message_id )
nodiscard

Send a C-CANCEL request to stop an ongoing query.

Parameters
assocThe association on which the query is running
message_idThe message ID of the query to cancel
Returns
Success or error

Definition at line 335 of file worklist_scu.cpp.

337 {
338
339 using namespace network::dimse;
340
341 auto context_id = assoc.accepted_context_id(worklist_find_sop_class_uid);
342 if (!context_id) {
345 "No accepted presentation context for cancel");
346 }
347
348 // Build C-CANCEL-RQ
349 dimse_message cancel_rq{command_field::c_cancel_rq, message_id};
350 return assoc.send_dimse(*context_id, cancel_rq);
351}
constexpr dicom_tag message_id
Message ID.
constexpr int no_acceptable_context
Definition result.h:103
constexpr std::string_view worklist_find_sop_class_uid
Modality Worklist Information Model - FIND SOP Class UID.
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::network::association::accepted_context_id(), kcenon::pacs::error_codes::no_acceptable_context, kcenon::pacs::pacs_error(), kcenon::pacs::network::association::send_dimse(), and kcenon::pacs::services::worklist_find_sop_class_uid.

Referenced by query_impl(), and query_streaming().

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

◆ config()

const worklist_scu_config & kcenon::pacs::services::worklist_scu::config ( ) const
nodiscardnoexcept

Get the current configuration.

Returns
Reference to the current configuration

Definition at line 361 of file worklist_scu.cpp.

361 {
362 return config_;
363}

References config_.

Referenced by set_config().

Here is the caller graph for this function:

◆ get_today_date()

std::string kcenon::pacs::services::worklist_scu::get_today_date ( )
staticnodiscardprivate

Get today's date in DICOM format (YYYYMMDD)

Definition at line 395 of file worklist_scu.cpp.

395 {
396 auto now = std::time(nullptr);
397 auto* tm = std::localtime(&now);
398 std::ostringstream oss;
399 oss << std::put_time(tm, "%Y%m%d");
400 return oss.str();
401}

Referenced by query_today().

Here is the caller graph for this function:

◆ next_message_id()

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

Get the next message ID for DIMSE operations.

Definition at line 386 of file worklist_scu.cpp.

386 {
387 uint16_t id = message_id_counter_.fetch_add(1, std::memory_order_relaxed);
388 // Wrap around at 0xFFFF, skip 0 (reserved)
389 if (id == 0) {
390 id = message_id_counter_.fetch_add(1, std::memory_order_relaxed);
391 }
392 return id;
393}
std::atomic< uint16_t > message_id_counter_
Message ID counter.
@ id
Implant Displaced (alternate code)

References message_id_counter_.

Referenced by query(), and query_streaming().

Here is the caller graph for this function:

◆ operator=() [1/2]

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

◆ operator=() [2/2]

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

◆ parse_worklist_item()

worklist_item kcenon::pacs::services::worklist_scu::parse_worklist_item ( const core::dicom_dataset & ds) const
nodiscardprivate

Parse a worklist item from a response dataset.

Definition at line 403 of file worklist_scu.cpp.

404 {
405
406 using namespace core;
407
408 worklist_item item;
409
410 // Patient demographics
411 item.patient_name = ds.get_string(tags::patient_name);
412 item.patient_id = ds.get_string(tags::patient_id);
413 item.patient_birth_date = ds.get_string(tags::patient_birth_date);
414 item.patient_sex = ds.get_string(tags::patient_sex);
415
416 // Study-level attributes
417 item.study_instance_uid = ds.get_string(tags::study_instance_uid);
418 item.accession_number = ds.get_string(tags::accession_number);
419 item.referring_physician = ds.get_string(tags::referring_physician_name);
420 item.institution = ds.get_string(tags::institution_name);
421
422 // Requested Procedure attributes
423 item.requested_procedure_id = ds.get_string(tags::requested_procedure_id);
424 item.requested_procedure_description = ds.get_string(tags::study_description);
425
426 // Scheduled Procedure Step attributes (flat structure)
427 item.scheduled_station_ae = ds.get_string(tags::scheduled_station_ae_title);
428 item.modality = ds.get_string(tags::modality);
429 item.scheduled_date = ds.get_string(tags::scheduled_procedure_step_start_date);
430 item.scheduled_time = ds.get_string(tags::scheduled_procedure_step_start_time);
431 item.scheduled_procedure_step_id = ds.get_string(tags::scheduled_procedure_step_id);
432 item.scheduled_procedure_step_description =
434
435 // Store original dataset for full access
436 item.dataset = ds;
437
438 return item;
439}
constexpr dicom_tag item
Item.

References kcenon::pacs::core::tags::accession_number, kcenon::pacs::core::dicom_dataset::get_string(), kcenon::pacs::core::tags::institution_name, kcenon::pacs::core::tags::modality, 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::referring_physician_name, kcenon::pacs::core::tags::requested_procedure_id, kcenon::pacs::core::tags::scheduled_procedure_step_description, kcenon::pacs::core::tags::scheduled_procedure_step_id, kcenon::pacs::core::tags::scheduled_procedure_step_start_date, kcenon::pacs::core::tags::scheduled_procedure_step_start_time, kcenon::pacs::core::tags::scheduled_station_ae_title, kcenon::pacs::core::tags::study_description, and kcenon::pacs::core::tags::study_instance_uid.

Referenced by query_impl(), and query_streaming().

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

◆ queries_performed()

size_t kcenon::pacs::services::worklist_scu::queries_performed ( ) const
nodiscardnoexcept

Get the number of queries performed since construction.

Returns
Count of C-FIND requests sent
Examples
worklist_scu/main.cpp.

Definition at line 369 of file worklist_scu.cpp.

369 {
370 return queries_performed_.load(std::memory_order_relaxed);
371}
std::atomic< size_t > queries_performed_
Statistics: number of queries performed.

References queries_performed_.

◆ query() [1/2]

network::Result< worklist_result > kcenon::pacs::services::worklist_scu::query ( network::association & assoc,
const core::dicom_dataset & query_keys )
nodiscard

Perform a MWL C-FIND query with raw dataset.

Sends a C-FIND request with the provided raw query dataset. Use this when you need full control over the query attributes.

Parameters
assocThe established association to use
query_keysThe DICOM dataset containing query keys
Returns
Result containing worklist_result on success, or error message

Definition at line 50 of file worklist_scu.cpp.

52 {
53
54 return query_impl(assoc, query_keys, next_message_id());
55}
network::Result< worklist_result > query_impl(network::association &assoc, const core::dicom_dataset &query_keys, uint16_t message_id)
Internal query implementation.
uint16_t next_message_id() noexcept
Get the next message ID for DIMSE operations.

References next_message_id(), and query_impl().

Here is the call graph for this function:

◆ query() [2/2]

network::Result< worklist_result > kcenon::pacs::services::worklist_scu::query ( network::association & assoc,
const worklist_query_keys & keys )
nodiscard

Perform a MWL C-FIND query with typed keys.

Sends a C-FIND request with the provided query keys and collects all matching worklist items from the SCP.

Parameters
assocThe established association to use
keysThe typed query keys for filtering
Returns
Result containing worklist_result on success, or error message
Examples
worklist_scu/main.cpp.

Definition at line 42 of file worklist_scu.cpp.

44 {
45
46 auto query_ds = build_query_dataset(keys);
47 return query(assoc, query_ds);
48}
network::Result< worklist_result > query(network::association &assoc, const worklist_query_keys &keys)
Perform a MWL C-FIND query with typed keys.
core::dicom_dataset build_query_dataset(const worklist_query_keys &keys) const
Build query dataset from typed keys.

References build_query_dataset(), and query().

Referenced by query(), query_date_range(), query_patient(), and query_today().

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

◆ query_date_range()

network::Result< worklist_result > kcenon::pacs::services::worklist_scu::query_date_range ( network::association & assoc,
std::string_view start_date,
std::string_view end_date,
std::string_view modality = "" )
nodiscard

Query worklist by date range.

Query scheduled procedures within a date range, optionally filtered by modality.

Parameters
assocThe established association to use
start_dateStart date in YYYYMMDD format
end_dateEnd date in YYYYMMDD format
modalityModality filter (empty = any)
Returns
Result containing worklist_result on success

Definition at line 192 of file worklist_scu.cpp.

196 {
197
198 worklist_query_keys keys;
199 // DICOM date range format: YYYYMMDD-YYYYMMDD
200 keys.scheduled_date = std::string(start_date) + "-" + std::string(end_date);
201 keys.modality = std::string(modality);
202
203 return query(assoc, keys);
204}

References kcenon::pacs::services::worklist_query_keys::modality, query(), and kcenon::pacs::services::worklist_query_keys::scheduled_date.

Here is the call graph for this function:

◆ query_impl()

network::Result< worklist_result > kcenon::pacs::services::worklist_scu::query_impl ( network::association & assoc,
const core::dicom_dataset & query_keys,
uint16_t message_id )
nodiscardprivate

Internal query implementation.

Definition at line 57 of file worklist_scu.cpp.

60 {
61
62 using namespace network::dimse;
63
64 auto start_time = std::chrono::steady_clock::now();
65
66 // Verify association is established
67 if (!assoc.is_established()) {
70 "Association not established");
71 }
72
73 // Get accepted presentation context for MWL
74 auto context_id = assoc.accepted_context_id(worklist_find_sop_class_uid);
75 if (!context_id) {
78 "No accepted presentation context for Modality Worklist: " +
79 std::string(worklist_find_sop_class_uid));
80 }
81
82 // Build C-FIND-RQ message
83 auto request = make_c_find_rq(message_id, worklist_find_sop_class_uid);
84 request.set_dataset(query_keys);
85
86 logger_->debug_fmt("Sending MWL C-FIND request (message_id={})", message_id);
87
88 // Send the request
89 auto send_result = assoc.send_dimse(*context_id, request);
90 if (send_result.is_err()) {
91 return send_result.error();
92 }
93
94 // Receive responses
95 worklist_result result;
96 bool query_complete = false;
97
98 while (!query_complete) {
99 auto recv_result = assoc.receive_dimse(config_.timeout);
100 if (recv_result.is_err()) {
101 return recv_result.error();
102 }
103
104 const auto& [recv_context_id, response] = recv_result.value();
105
106 // Verify it's a C-FIND response
107 if (response.command() != command_field::c_find_rsp) {
110 "Expected C-FIND-RSP but received " +
111 std::string(to_string(response.command())));
112 }
113
114 auto status = response.status();
115
116 if (status == status_pending || status == status_pending_warning) {
117 ++result.total_pending;
118
119 if (response.has_dataset()) {
120 // Check if we should collect this result
121 if (config_.max_results == 0 ||
122 result.items.size() < config_.max_results) {
123
124 auto dataset_result = response.dataset();
125 if (dataset_result.is_ok()) {
126 result.items.push_back(
127 parse_worklist_item(dataset_result.value().get()));
128 }
129 }
130
131 // Check if we should cancel due to max_results
132 if (config_.max_results > 0 &&
133 result.items.size() >= config_.max_results &&
135
136 logger_->debug_fmt(
137 "Max results ({}) reached, sending C-CANCEL",
139
140 // Send C-CANCEL
141 auto cancel_result = cancel(assoc, message_id);
142 if (cancel_result.is_err()) {
143 logger_->warn_fmt("Failed to send C-CANCEL: {}",
144 cancel_result.error().message);
145 }
146 }
147 }
148 } else if (status == status_success) {
149 query_complete = true;
150 result.status = static_cast<uint16_t>(status);
151 } else if (status == status_cancel) {
152 query_complete = true;
153 result.status = static_cast<uint16_t>(status);
154 } else {
155 // Error status
156 query_complete = true;
157 result.status = static_cast<uint16_t>(status);
158 }
159 }
160
161 auto end_time = std::chrono::steady_clock::now();
162 result.elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
163 end_time - start_time);
164
165 // Update statistics
166 queries_performed_.fetch_add(1, std::memory_order_relaxed);
167 total_items_.fetch_add(result.items.size(), std::memory_order_relaxed);
168
169 logger_->debug_fmt("MWL C-FIND completed: {} items in {} ms",
170 result.items.size(), result.elapsed.count());
171
172 return result;
173}
std::atomic< size_t > total_items_
Statistics: total number of items received.
network::Result< std::monostate > cancel(network::association &assoc, uint16_t message_id)
Send a C-CANCEL request to stop an ongoing query.
worklist_item parse_worklist_item(const core::dicom_dataset &ds) const
Parse a worklist item from a response dataset.
constexpr dicom_tag status
Status.
constexpr int find_unexpected_command
Definition result.h:157
constexpr int association_not_established
Definition result.h:202
auto to_string(mpps_status status) -> std::string_view
Convert mpps_status to DICOM string representation.
Definition mpps_scp.h:60
std::chrono::milliseconds timeout
Timeout for receiving query responses (milliseconds)
size_t max_results
Maximum number of results to return (0 = unlimited)
bool cancel_on_max
Send C-CANCEL when max_results is reached.

References kcenon::pacs::network::association::accepted_context_id(), kcenon::pacs::error_codes::association_not_established, cancel(), kcenon::pacs::services::worklist_scu_config::cancel_on_max, config_, kcenon::pacs::services::worklist_result::elapsed, kcenon::pacs::error_codes::find_unexpected_command, kcenon::pacs::network::association::is_established(), kcenon::pacs::services::worklist_result::items, logger_, kcenon::pacs::services::worklist_scu_config::max_results, kcenon::pacs::error_codes::no_acceptable_context, kcenon::pacs::pacs_error(), parse_worklist_item(), queries_performed_, kcenon::pacs::network::association::receive_dimse(), kcenon::pacs::network::association::send_dimse(), kcenon::pacs::services::worklist_result::status, kcenon::pacs::services::worklist_scu_config::timeout, kcenon::pacs::services::to_string(), total_items_, kcenon::pacs::services::worklist_result::total_pending, and kcenon::pacs::services::worklist_find_sop_class_uid.

Referenced by query().

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

◆ query_patient()

network::Result< worklist_result > kcenon::pacs::services::worklist_scu::query_patient ( network::association & assoc,
std::string_view patient_id )
nodiscard

Query worklist by patient ID.

Query all scheduled procedures for a specific patient.

Parameters
assocThe established association to use
patient_idThe patient ID to query
Returns
Result containing worklist_result on success

Definition at line 206 of file worklist_scu.cpp.

208 {
209
210 worklist_query_keys keys;
211 keys.patient_id = std::string(patient_id);
212
213 return query(assoc, keys);
214}

References kcenon::pacs::services::worklist_query_keys::patient_id, and query().

Here is the call graph for this function:

◆ query_streaming()

network::Result< size_t > kcenon::pacs::services::worklist_scu::query_streaming ( network::association & assoc,
const worklist_query_keys & keys,
worklist_streaming_callback callback )
nodiscard

Perform a streaming MWL query for large worklists.

Sends a C-FIND request and calls the callback for each pending response. This is more memory-efficient for large worklists.

Parameters
assocThe established association to use
keysThe typed query keys for filtering
callbackCalled for each worklist item; return false to cancel
Returns
Result containing the number of items processed, or error

Definition at line 220 of file worklist_scu.cpp.

223 {
224
225 using namespace network::dimse;
226
228 auto start_time = std::chrono::steady_clock::now();
229
230 // Verify association is established
231 if (!assoc.is_established()) {
234 "Association not established");
235 }
236
237 // Get accepted presentation context for MWL
238 auto context_id = assoc.accepted_context_id(worklist_find_sop_class_uid);
239 if (!context_id) {
242 "No accepted presentation context for Modality Worklist");
243 }
244
245 // Build query dataset and C-FIND-RQ message
246 auto query_ds = build_query_dataset(keys);
247 auto request = make_c_find_rq(message_id, worklist_find_sop_class_uid);
248 request.set_dataset(query_ds);
249
250 logger_->debug_fmt("Sending streaming MWL C-FIND request (message_id={})",
251 message_id);
252
253 // Send the request
254 auto send_result = assoc.send_dimse(*context_id, request);
255 if (send_result.is_err()) {
256 return send_result.error();
257 }
258
259 // Receive responses
260 size_t count = 0;
261 bool query_complete = false;
262 bool should_cancel = false;
263
264 while (!query_complete) {
265 auto recv_result = assoc.receive_dimse(config_.timeout);
266 if (recv_result.is_err()) {
267 return recv_result.error();
268 }
269
270 const auto& [recv_context_id, response] = recv_result.value();
271
272 // Verify it's a C-FIND response
273 if (response.command() != command_field::c_find_rsp) {
276 "Expected C-FIND-RSP but received " +
277 std::string(to_string(response.command())));
278 }
279
280 auto status = response.status();
281
282 if (status == status_pending || status == status_pending_warning) {
283 if (response.has_dataset()) {
284 auto dataset_result = response.dataset();
285 if (dataset_result.is_ok()) {
286 ++count;
287 auto item = parse_worklist_item(dataset_result.value().get());
288
289 // Call the callback
290 if (callback) {
291 if (!callback(item)) {
292 should_cancel = true;
293 }
294 }
295
296 // Check if we need to cancel
297 if (should_cancel ||
298 (config_.max_results > 0 &&
299 count >= config_.max_results &&
301
302 logger_->debug("Cancelling streaming MWL query");
303 auto cancel_result = cancel(assoc, message_id);
304 if (cancel_result.is_err()) {
305 logger_->warn_fmt("Failed to send C-CANCEL: {}",
306 cancel_result.error().message);
307 }
308 should_cancel = false; // Already sent cancel
309 }
310 }
311 }
312 } else {
313 query_complete = true;
314 }
315 }
316
317 // Update statistics
318 queries_performed_.fetch_add(1, std::memory_order_relaxed);
319 total_items_.fetch_add(count, std::memory_order_relaxed);
320
321 auto end_time = std::chrono::steady_clock::now();
322 auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
323 end_time - start_time);
324
325 logger_->debug_fmt("Streaming MWL C-FIND completed: {} items in {} ms",
326 count, elapsed.count());
327
328 return count;
329}

References kcenon::pacs::network::association::accepted_context_id(), kcenon::pacs::error_codes::association_not_established, build_query_dataset(), cancel(), kcenon::pacs::services::worklist_scu_config::cancel_on_max, config_, kcenon::pacs::error_codes::find_unexpected_command, kcenon::pacs::network::association::is_established(), logger_, kcenon::pacs::services::worklist_scu_config::max_results, next_message_id(), kcenon::pacs::error_codes::no_acceptable_context, kcenon::pacs::pacs_error(), parse_worklist_item(), queries_performed_, kcenon::pacs::network::association::receive_dimse(), kcenon::pacs::network::association::send_dimse(), kcenon::pacs::services::worklist_scu_config::timeout, kcenon::pacs::services::to_string(), total_items_, and kcenon::pacs::services::worklist_find_sop_class_uid.

Here is the call graph for this function:

◆ query_today()

network::Result< worklist_result > kcenon::pacs::services::worklist_scu::query_today ( network::association & assoc,
std::string_view station_ae,
std::string_view modality = "" )
nodiscard

Query today's worklist for a station.

Convenience method to query scheduled procedures for today, optionally filtered by station AE title and modality.

Parameters
assocThe established association to use
station_aeScheduled Station AE Title (empty = any)
modalityModality filter (empty = any)
Returns
Result containing worklist_result on success

Definition at line 179 of file worklist_scu.cpp.

182 {
183
184 worklist_query_keys keys;
185 keys.scheduled_station_ae = std::string(station_ae);
186 keys.modality = std::string(modality);
187 keys.scheduled_date = get_today_date();
188
189 return query(assoc, keys);
190}
static std::string get_today_date()
Get today's date in DICOM format (YYYYMMDD)

References get_today_date(), kcenon::pacs::services::worklist_query_keys::modality, query(), kcenon::pacs::services::worklist_query_keys::scheduled_date, and kcenon::pacs::services::worklist_query_keys::scheduled_station_ae.

Here is the call graph for this function:

◆ reset_statistics()

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

Reset statistics counters to zero.

Definition at line 377 of file worklist_scu.cpp.

377 {
378 queries_performed_.store(0, std::memory_order_relaxed);
379 total_items_.store(0, std::memory_order_relaxed);
380}

References queries_performed_, and total_items_.

◆ set_config()

void kcenon::pacs::services::worklist_scu::set_config ( const worklist_scu_config & config)

Update the SCU configuration.

Parameters
configNew configuration options

Definition at line 357 of file worklist_scu.cpp.

357 {
358 config_ = config;
359}

References config(), and config_.

Here is the call graph for this function:

◆ total_items()

size_t kcenon::pacs::services::worklist_scu::total_items ( ) const
nodiscardnoexcept

Get the total number of items received since construction.

Returns
Total count of worklist items received
Examples
worklist_scu/main.cpp.

Definition at line 373 of file worklist_scu.cpp.

373 {
374 return total_items_.load(std::memory_order_relaxed);
375}

References total_items_.

Member Data Documentation

◆ config_

worklist_scu_config kcenon::pacs::services::worklist_scu::config_
private

Configuration.

Definition at line 587 of file worklist_scu.h.

Referenced by config(), query_impl(), query_streaming(), and set_config().

◆ logger_

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

Logger instance for service logging.

Definition at line 584 of file worklist_scu.h.

Referenced by query_impl(), and query_streaming().

◆ message_id_counter_

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

Message ID counter.

Definition at line 590 of file worklist_scu.h.

590{1};

Referenced by next_message_id().

◆ queries_performed_

std::atomic<size_t> kcenon::pacs::services::worklist_scu::queries_performed_ {0}
private

Statistics: number of queries performed.

Definition at line 593 of file worklist_scu.h.

593{0};

Referenced by queries_performed(), query_impl(), query_streaming(), and reset_statistics().

◆ total_items_

std::atomic<size_t> kcenon::pacs::services::worklist_scu::total_items_ {0}
private

Statistics: total number of items received.

Definition at line 596 of file worklist_scu.h.

596{0};

Referenced by query_impl(), query_streaming(), reset_statistics(), and total_items().


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