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

#include <query_scu.h>

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

Public Member Functions

 query_scu (std::shared_ptr< di::ILogger > logger=nullptr)
 Construct a Query SCU with default configuration.
 
 query_scu (const query_scu_config &config, std::shared_ptr< di::ILogger > logger=nullptr)
 Construct a Query SCU with custom configuration.
 
 ~query_scu ()=default
 
 query_scu (const query_scu &)=delete
 
query_scuoperator= (const query_scu &)=delete
 
 query_scu (query_scu &&)=delete
 
query_scuoperator= (query_scu &&)=delete
 
network::Result< query_resultfind (network::association &assoc, const core::dicom_dataset &query_keys)
 Perform a C-FIND query with raw dataset.
 
network::Result< size_t > find_streaming (network::association &assoc, const core::dicom_dataset &query_keys, query_streaming_callback callback)
 Perform a streaming C-FIND query for large result sets.
 
network::Result< query_resultfind_patients (network::association &assoc, const patient_query_keys &keys)
 Query for patients.
 
network::Result< query_resultfind_studies (network::association &assoc, const study_query_keys &keys)
 Query for studies.
 
network::Result< query_resultfind_series (network::association &assoc, const series_query_keys &keys)
 Query for series within a study.
 
network::Result< query_resultfind_instances (network::association &assoc, const instance_query_keys &keys)
 Query for instances within a series.
 
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 query_scu_config &config)
 Update the SCU configuration.
 
const query_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_matches () const noexcept
 Get the total number of matches received since construction.
 
void reset_statistics () noexcept
 Reset statistics counters to zero.
 

Private Member Functions

network::Result< query_resultfind_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.
 
core::dicom_dataset build_query_dataset (const patient_query_keys &keys) const
 Build query dataset from patient keys.
 
core::dicom_dataset build_query_dataset (const study_query_keys &keys) const
 Build query dataset from study keys.
 
core::dicom_dataset build_query_dataset (const series_query_keys &keys) const
 Build query dataset from series keys.
 
core::dicom_dataset build_query_dataset (const instance_query_keys &keys) const
 Build query dataset from instance keys.
 
std::string_view get_sop_class_uid () const noexcept
 Get SOP Class UID based on current configuration.
 

Private Attributes

std::shared_ptr< di::ILoggerlogger_
 Logger instance for service logging.
 
query_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_matches_ {0}
 Statistics: total number of matches received.
 

Detailed Description

Definition at line 294 of file query_scu.h.

Constructor & Destructor Documentation

◆ query_scu() [1/4]

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

Construct a Query SCU with default configuration.

Parameters
loggerLogger instance for service logging (nullptr uses null_logger)

Definition at line 25 of file query_scu.cpp.

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

◆ query_scu() [2/4]

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

Construct a Query SCU with custom configuration.

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

Definition at line 28 of file query_scu.cpp.

30 : logger_(logger ? std::move(logger) : di::null_logger()), config_(config) {}
query_scu_config config_
Configuration.
Definition query_scu.h:515
const query_scu_config & config() const noexcept
Get the current configuration.

◆ ~query_scu()

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

◆ query_scu() [3/4]

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

◆ query_scu() [4/4]

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

Member Function Documentation

◆ build_query_dataset() [1/4]

core::dicom_dataset kcenon::pacs::services::query_scu::build_query_dataset ( const instance_query_keys & keys) const
nodiscardprivate

Build query dataset from instance keys.

Definition at line 485 of file query_scu.cpp.

486 {
487
488 using namespace core;
489 using namespace encoding;
490
491 dicom_dataset ds;
492
493 // Set Query/Retrieve Level
494 ds.set_string(tags::query_retrieve_level, vr_type::CS, "IMAGE");
495
496 // Series UID (required for instance query)
497 ds.set_string(tags::series_instance_uid, vr_type::UI, keys.series_uid);
498
499 // Instance keys
500 ds.set_string(tags::sop_instance_uid, vr_type::UI, keys.sop_instance_uid);
501 ds.set_string(tags::sop_class_uid, vr_type::UI, "");
502 ds.set_string(tags::instance_number, vr_type::IS, keys.instance_number);
503
504 return ds;
505}
constexpr dicom_tag sop_instance_uid
SOP Instance UID.
constexpr dicom_tag query_retrieve_level
Query/Retrieve Level.
constexpr dicom_tag sop_class_uid
SOP Class UID.
constexpr dicom_tag series_instance_uid
Series Instance UID.
constexpr dicom_tag instance_number
Instance Number.

References kcenon::pacs::core::tags::instance_number, kcenon::pacs::services::instance_query_keys::instance_number, kcenon::pacs::core::tags::query_retrieve_level, kcenon::pacs::core::tags::series_instance_uid, kcenon::pacs::services::instance_query_keys::series_uid, kcenon::pacs::core::tags::sop_class_uid, kcenon::pacs::core::tags::sop_instance_uid, and kcenon::pacs::services::instance_query_keys::sop_instance_uid.

◆ build_query_dataset() [2/4]

core::dicom_dataset kcenon::pacs::services::query_scu::build_query_dataset ( const patient_query_keys & keys) const
nodiscardprivate

Build query dataset from patient keys.

Definition at line 403 of file query_scu.cpp.

404 {
405
406 using namespace core;
407 using namespace encoding;
408
409 dicom_dataset ds;
410
411 // Set Query/Retrieve Level
412 ds.set_string(tags::query_retrieve_level, vr_type::CS, "PATIENT");
413
414 // Set query keys (empty string = return key, non-empty = match key)
415 ds.set_string(tags::patient_name, vr_type::PN, keys.patient_name);
416 ds.set_string(tags::patient_id, vr_type::LO, keys.patient_id);
417 ds.set_string(tags::patient_birth_date, vr_type::DA, keys.birth_date);
418 ds.set_string(tags::patient_sex, vr_type::CS, keys.sex);
419
420 // Add standard return keys
421 ds.set_string(tags::number_of_patient_related_studies, vr_type::IS, "");
422 ds.set_string(tags::number_of_patient_related_series, vr_type::IS, "");
423 ds.set_string(tags::number_of_patient_related_instances, vr_type::IS, "");
424
425 return ds;
426}
constexpr dicom_tag number_of_patient_related_studies
Number of Patient Related Studies.
constexpr dicom_tag patient_id
Patient ID.
constexpr dicom_tag number_of_patient_related_instances
Number of Patient Related Instances.
constexpr dicom_tag patient_birth_date
Patient's Birth Date.
constexpr dicom_tag number_of_patient_related_series
Number of Patient Related Series.
constexpr dicom_tag patient_sex
Patient's Sex.
constexpr dicom_tag patient_name
Patient's Name.

References kcenon::pacs::services::patient_query_keys::birth_date, kcenon::pacs::core::tags::number_of_patient_related_instances, kcenon::pacs::core::tags::number_of_patient_related_series, kcenon::pacs::core::tags::number_of_patient_related_studies, kcenon::pacs::core::tags::patient_birth_date, kcenon::pacs::core::tags::patient_id, kcenon::pacs::services::patient_query_keys::patient_id, kcenon::pacs::core::tags::patient_name, kcenon::pacs::services::patient_query_keys::patient_name, kcenon::pacs::core::tags::patient_sex, kcenon::pacs::core::tags::query_retrieve_level, and kcenon::pacs::services::patient_query_keys::sex.

Referenced by find_instances(), find_patients(), find_series(), and find_studies().

Here is the caller graph for this function:

◆ build_query_dataset() [3/4]

core::dicom_dataset kcenon::pacs::services::query_scu::build_query_dataset ( const series_query_keys & keys) const
nodiscardprivate

Build query dataset from series keys.

Definition at line 459 of file query_scu.cpp.

460 {
461
462 using namespace core;
463 using namespace encoding;
464
465 dicom_dataset ds;
466
467 // Set Query/Retrieve Level
468 ds.set_string(tags::query_retrieve_level, vr_type::CS, "SERIES");
469
470 // Study UID (required for series query)
471 ds.set_string(tags::study_instance_uid, vr_type::UI, keys.study_uid);
472
473 // Series keys
474 ds.set_string(tags::series_instance_uid, vr_type::UI, keys.series_uid);
475 ds.set_string(tags::modality, vr_type::CS, keys.modality);
476 ds.set_string(tags::series_number, vr_type::IS, keys.series_number);
477 ds.set_string(tags::series_description, vr_type::LO, "");
478
479 // Standard return keys
480 ds.set_string(tags::number_of_series_related_instances, vr_type::IS, "");
481
482 return ds;
483}
constexpr dicom_tag modality
Modality.
constexpr dicom_tag study_instance_uid
Study Instance UID.
constexpr dicom_tag series_number
Series Number.
constexpr dicom_tag number_of_series_related_instances
Number of Series Related Instances.
constexpr dicom_tag series_description
Series Description.

References kcenon::pacs::core::tags::modality, kcenon::pacs::services::series_query_keys::modality, kcenon::pacs::core::tags::number_of_series_related_instances, kcenon::pacs::core::tags::query_retrieve_level, kcenon::pacs::core::tags::series_description, kcenon::pacs::core::tags::series_instance_uid, kcenon::pacs::core::tags::series_number, kcenon::pacs::services::series_query_keys::series_number, kcenon::pacs::services::series_query_keys::series_uid, kcenon::pacs::core::tags::study_instance_uid, and kcenon::pacs::services::series_query_keys::study_uid.

◆ build_query_dataset() [4/4]

core::dicom_dataset kcenon::pacs::services::query_scu::build_query_dataset ( const study_query_keys & keys) const
nodiscardprivate

Build query dataset from study keys.

Definition at line 428 of file query_scu.cpp.

429 {
430
431 using namespace core;
432 using namespace encoding;
433
434 dicom_dataset ds;
435
436 // Set Query/Retrieve Level
437 ds.set_string(tags::query_retrieve_level, vr_type::CS, "STUDY");
438
439 // Patient info (return keys)
440 ds.set_string(tags::patient_name, vr_type::PN, "");
441 ds.set_string(tags::patient_id, vr_type::LO, keys.patient_id);
442
443 // Study keys
444 ds.set_string(tags::study_instance_uid, vr_type::UI, keys.study_uid);
445 ds.set_string(tags::study_date, vr_type::DA, keys.study_date);
446 ds.set_string(tags::study_time, vr_type::TM, "");
447 ds.set_string(tags::accession_number, vr_type::SH, keys.accession_number);
448 ds.set_string(tags::study_id, vr_type::SH, "");
449 ds.set_string(tags::study_description, vr_type::LO, keys.study_description);
450 ds.set_string(tags::modalities_in_study, vr_type::CS, keys.modality);
451
452 // Standard return keys
453 ds.set_string(tags::number_of_study_related_series, vr_type::IS, "");
454 ds.set_string(tags::number_of_study_related_instances, vr_type::IS, "");
455
456 return ds;
457}
constexpr dicom_tag number_of_study_related_series
Number of Study Related Series.
constexpr dicom_tag modalities_in_study
Modalities in Study.
constexpr dicom_tag study_description
Study Description.
constexpr dicom_tag number_of_study_related_instances
Number of Study Related Instances.
constexpr dicom_tag accession_number
Accession Number.
constexpr dicom_tag study_time
Study Time.
constexpr dicom_tag study_id
Study ID.
constexpr dicom_tag study_date
Study Date.

References kcenon::pacs::core::tags::accession_number, kcenon::pacs::services::study_query_keys::accession_number, kcenon::pacs::core::tags::modalities_in_study, kcenon::pacs::services::study_query_keys::modality, kcenon::pacs::core::tags::number_of_study_related_instances, kcenon::pacs::core::tags::number_of_study_related_series, kcenon::pacs::core::tags::patient_id, kcenon::pacs::services::study_query_keys::patient_id, kcenon::pacs::core::tags::patient_name, kcenon::pacs::core::tags::query_retrieve_level, kcenon::pacs::core::tags::study_date, kcenon::pacs::services::study_query_keys::study_date, kcenon::pacs::core::tags::study_description, kcenon::pacs::services::study_query_keys::study_description, kcenon::pacs::core::tags::study_id, kcenon::pacs::core::tags::study_instance_uid, kcenon::pacs::core::tags::study_time, and kcenon::pacs::services::study_query_keys::study_uid.

◆ cancel()

network::Result< std::monostate > kcenon::pacs::services::query_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 341 of file query_scu.cpp.

343 {
344
345 using namespace network::dimse;
346
348
349 auto context_id = assoc.accepted_context_id(sop_class_uid);
350 if (!context_id) {
353 "No accepted presentation context for cancel");
354 }
355
356 // Build C-CANCEL-RQ manually since there's no factory function
357 dimse_message cancel_rq{command_field::c_cancel_rq, message_id};
358 return assoc.send_dimse(*context_id, cancel_rq);
359}
std::string_view get_sop_class_uid() const noexcept
Get SOP Class UID based on current configuration.
constexpr dicom_tag message_id
Message ID.
constexpr int no_acceptable_context
Definition result.h:103
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(), get_sop_class_uid(), kcenon::pacs::error_codes::no_acceptable_context, kcenon::pacs::pacs_error(), and kcenon::pacs::network::association::send_dimse().

Referenced by find_impl(), and find_streaming().

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

◆ config()

const query_scu_config & kcenon::pacs::services::query_scu::config ( ) const
nodiscardnoexcept

Get the current configuration.

Returns
Reference to the current configuration

Definition at line 369 of file query_scu.cpp.

369 {
370 return config_;
371}

References config_.

Referenced by set_config().

Here is the caller graph for this function:

◆ find()

network::Result< query_result > kcenon::pacs::services::query_scu::find ( network::association & assoc,
const core::dicom_dataset & query_keys )
nodiscard

Perform a C-FIND query with raw dataset.

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

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

Definition at line 36 of file query_scu.cpp.

38 {
39
40 return find_impl(assoc, query_keys, next_message_id());
41}
uint16_t next_message_id() noexcept
Get the next message ID for DIMSE operations.
network::Result< query_result > find_impl(network::association &assoc, const core::dicom_dataset &query_keys, uint16_t message_id)
Internal query implementation.
Definition query_scu.cpp:43

References find_impl(), and next_message_id().

Referenced by kcenon::pacs::client::job_manager::impl::execute_query_job(), find_instances(), find_patients(), find_series(), find_studies(), and kcenon::pacs::client::sync_manager::impl::query_remote_studies().

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

◆ find_impl()

network::Result< query_result > kcenon::pacs::services::query_scu::find_impl ( network::association & assoc,
const core::dicom_dataset & query_keys,
uint16_t message_id )
nodiscardprivate

Internal query implementation.

Definition at line 43 of file query_scu.cpp.

46 {
47
48 using namespace network::dimse;
49
50 auto start_time = std::chrono::steady_clock::now();
51
52 // Verify association is established
53 if (!assoc.is_established()) {
56 "Association not established");
57 }
58
59 // Get SOP Class UID
61
62 // Get accepted presentation context for this SOP class
63 auto context_id = assoc.accepted_context_id(sop_class_uid);
64 if (!context_id) {
67 "No accepted presentation context for SOP Class: " +
68 std::string(sop_class_uid));
69 }
70
71 // Build C-FIND-RQ message
72 auto request = make_c_find_rq(message_id, sop_class_uid);
73 request.set_dataset(query_keys);
74
75 logger_->debug_fmt("Sending C-FIND request (message_id={}, sop_class={})",
76 message_id, sop_class_uid);
77
78 // Send the request
79 auto send_result = assoc.send_dimse(*context_id, request);
80 if (send_result.is_err()) {
81 return send_result.error();
82 }
83
84 // Receive responses
85 query_result result;
86 bool query_complete = false;
87
88 while (!query_complete) {
89 auto recv_result = assoc.receive_dimse(config_.timeout);
90 if (recv_result.is_err()) {
91 return recv_result.error();
92 }
93
94 const auto& [recv_context_id, response] = recv_result.value();
95
96 // Verify it's a C-FIND response
97 if (response.command() != command_field::c_find_rsp) {
100 "Expected C-FIND-RSP but received " +
101 std::string(to_string(response.command())));
102 }
103
104 auto status = response.status();
105
106 if (status == status_pending || status == status_pending_warning) {
107 ++result.total_pending;
108
109 if (response.has_dataset()) {
110 // Check if we should collect this result
111 if (config_.max_results == 0 ||
112 result.matches.size() < config_.max_results) {
113
114 auto dataset_result = response.dataset();
115 if (dataset_result.is_ok()) {
116 result.matches.push_back(dataset_result.value().get());
117 }
118 }
119
120 // Check if we should cancel due to max_results
121 if (config_.max_results > 0 &&
122 result.matches.size() >= config_.max_results &&
124
125 logger_->debug_fmt(
126 "Max results ({}) reached, sending C-CANCEL",
128
129 // Send C-CANCEL
130 auto cancel_result = cancel(assoc, message_id);
131 if (cancel_result.is_err()) {
132 logger_->warn_fmt("Failed to send C-CANCEL: {}",
133 cancel_result.error().message);
134 }
135 }
136 }
137 } else if (status == status_success) {
138 query_complete = true;
139 result.status = static_cast<uint16_t>(status);
140 } else if (status == status_cancel) {
141 query_complete = true;
142 result.status = static_cast<uint16_t>(status);
143 } else {
144 // Error status
145 query_complete = true;
146 result.status = static_cast<uint16_t>(status);
147 }
148 }
149
150 auto end_time = std::chrono::steady_clock::now();
151 result.elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
152 end_time - start_time);
153
154 // Update statistics
155 queries_performed_.fetch_add(1, std::memory_order_relaxed);
156 total_matches_.fetch_add(result.matches.size(), std::memory_order_relaxed);
157
158 logger_->debug_fmt("C-FIND completed: {} matches in {} ms",
159 result.matches.size(), result.elapsed.count());
160
161 return result;
162}
std::atomic< size_t > queries_performed_
Statistics: number of queries performed.
Definition query_scu.h:521
std::atomic< size_t > total_matches_
Statistics: total number of matches received.
Definition query_scu.h:524
network::Result< std::monostate > cancel(network::association &assoc, uint16_t message_id)
Send a C-CANCEL request to stop an ongoing query.
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)
Definition query_scu.h:176
bool cancel_on_max
Send C-CANCEL when max_results is reached.
Definition query_scu.h:182
size_t max_results
Maximum number of results to return (0 = unlimited)
Definition query_scu.h:179

References kcenon::pacs::network::association::accepted_context_id(), kcenon::pacs::error_codes::association_not_established, cancel(), kcenon::pacs::services::query_scu_config::cancel_on_max, config_, kcenon::pacs::services::query_result::elapsed, kcenon::pacs::error_codes::find_unexpected_command, get_sop_class_uid(), kcenon::pacs::network::association::is_established(), logger_, kcenon::pacs::services::query_result::matches, kcenon::pacs::services::query_scu_config::max_results, kcenon::pacs::error_codes::no_acceptable_context, kcenon::pacs::pacs_error(), queries_performed_, kcenon::pacs::network::association::receive_dimse(), kcenon::pacs::network::association::send_dimse(), kcenon::pacs::services::query_result::status, kcenon::pacs::services::query_scu_config::timeout, kcenon::pacs::services::to_string(), total_matches_, and kcenon::pacs::services::query_result::total_pending.

Referenced by find().

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

◆ find_instances()

network::Result< query_result > kcenon::pacs::services::query_scu::find_instances ( network::association & assoc,
const instance_query_keys & keys )
nodiscard

Query for instances within a series.

Parameters
assocThe established association to use
keysInstance-level query keys (series_uid required)
Returns
Result containing query_result on success

Definition at line 323 of file query_scu.cpp.

325 {
326
327 auto saved_level = config_.level;
329
330 auto query_ds = build_query_dataset(keys);
331 auto result = find(assoc, query_ds);
332
333 config_.level = saved_level;
334 return result;
335}
core::dicom_dataset build_query_dataset(const patient_query_keys &keys) const
Build query dataset from patient keys.
network::Result< query_result > find(network::association &assoc, const core::dicom_dataset &query_keys)
Perform a C-FIND query with raw dataset.
Definition query_scu.cpp:36
@ image
Image (Instance) level - query instance information.
query_level level
Query level (Patient, Study, Series, or Image)
Definition query_scu.h:173

References build_query_dataset(), config_, find(), kcenon::pacs::services::image, and kcenon::pacs::services::query_scu_config::level.

Referenced by kcenon::pacs::web::endpoints::register_remote_nodes_endpoints_impl().

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

◆ find_patients()

network::Result< query_result > kcenon::pacs::services::query_scu::find_patients ( network::association & assoc,
const patient_query_keys & keys )
nodiscard

Query for patients.

Parameters
assocThe established association to use
keysPatient-level query keys
Returns
Result containing query_result on success

Definition at line 281 of file query_scu.cpp.

283 {
284
285 auto saved_level = config_.level;
287
288 auto query_ds = build_query_dataset(keys);
289 auto result = find(assoc, query_ds);
290
291 config_.level = saved_level;
292 return result;
293}
@ patient
Patient level - query patient demographics.

References build_query_dataset(), config_, find(), kcenon::pacs::services::query_scu_config::level, and kcenon::pacs::services::patient.

Referenced by kcenon::pacs::web::endpoints::register_remote_nodes_endpoints_impl().

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

◆ find_series()

network::Result< query_result > kcenon::pacs::services::query_scu::find_series ( network::association & assoc,
const series_query_keys & keys )
nodiscard

Query for series within a study.

Parameters
assocThe established association to use
keysSeries-level query keys (study_uid required)
Returns
Result containing query_result on success

Definition at line 309 of file query_scu.cpp.

311 {
312
313 auto saved_level = config_.level;
315
316 auto query_ds = build_query_dataset(keys);
317 auto result = find(assoc, query_ds);
318
319 config_.level = saved_level;
320 return result;
321}
@ series
Series level - query series information.

References build_query_dataset(), config_, find(), kcenon::pacs::services::query_scu_config::level, and kcenon::pacs::services::series.

Referenced by kcenon::pacs::web::endpoints::register_remote_nodes_endpoints_impl().

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

◆ find_streaming()

network::Result< size_t > kcenon::pacs::services::query_scu::find_streaming ( network::association & assoc,
const core::dicom_dataset & query_keys,
query_streaming_callback callback )
nodiscard

Perform a streaming C-FIND query for large result sets.

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

Parameters
assocThe established association to use
query_keysThe DICOM dataset containing query keys
callbackCalled for each matching dataset; return false to cancel
Returns
Result containing the number of results processed, or error

Definition at line 164 of file query_scu.cpp.

167 {
168
169 using namespace network::dimse;
170
172 auto start_time = std::chrono::steady_clock::now();
173
174 // Verify association is established
175 if (!assoc.is_established()) {
178 "Association not established");
179 }
180
181 // Get SOP Class UID
183
184 // Get accepted presentation context for this SOP class
185 auto context_id = assoc.accepted_context_id(sop_class_uid);
186 if (!context_id) {
189 "No accepted presentation context for SOP Class: " +
190 std::string(sop_class_uid));
191 }
192
193 // Build C-FIND-RQ message
194 auto request = make_c_find_rq(message_id, sop_class_uid);
195 request.set_dataset(query_keys);
196
197 logger_->debug_fmt("Sending streaming C-FIND request (message_id={})",
198 message_id);
199
200 // Send the request
201 auto send_result = assoc.send_dimse(*context_id, request);
202 if (send_result.is_err()) {
203 return send_result.error();
204 }
205
206 // Receive responses
207 size_t count = 0;
208 bool query_complete = false;
209 bool should_cancel = false;
210
211 while (!query_complete) {
212 auto recv_result = assoc.receive_dimse(config_.timeout);
213 if (recv_result.is_err()) {
214 return recv_result.error();
215 }
216
217 const auto& [recv_context_id, response] = recv_result.value();
218
219 // Verify it's a C-FIND response
220 if (response.command() != command_field::c_find_rsp) {
223 "Expected C-FIND-RSP but received " +
224 std::string(to_string(response.command())));
225 }
226
227 auto status = response.status();
228
229 if (status == status_pending || status == status_pending_warning) {
230 if (response.has_dataset()) {
231 auto dataset_result = response.dataset();
232 if (dataset_result.is_ok()) {
233 ++count;
234
235 // Call the callback
236 if (callback) {
237 if (!callback(dataset_result.value().get())) {
238 should_cancel = true;
239 }
240 }
241
242 // Check if we need to cancel
243 if (should_cancel ||
244 (config_.max_results > 0 &&
245 count >= config_.max_results &&
247
248 logger_->debug("Cancelling streaming query");
249 auto cancel_result = cancel(assoc, message_id);
250 if (cancel_result.is_err()) {
251 logger_->warn_fmt("Failed to send C-CANCEL: {}",
252 cancel_result.error().message);
253 }
254 should_cancel = false; // Already sent cancel
255 }
256 }
257 }
258 } else {
259 query_complete = true;
260 }
261 }
262
263 // Update statistics
264 queries_performed_.fetch_add(1, std::memory_order_relaxed);
265 total_matches_.fetch_add(count, std::memory_order_relaxed);
266
267 auto end_time = std::chrono::steady_clock::now();
268 auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
269 end_time - start_time);
270
271 logger_->debug_fmt("Streaming C-FIND completed: {} results in {} ms",
272 count, elapsed.count());
273
274 return count;
275}

References kcenon::pacs::network::association::accepted_context_id(), kcenon::pacs::error_codes::association_not_established, cancel(), kcenon::pacs::services::query_scu_config::cancel_on_max, config_, kcenon::pacs::error_codes::find_unexpected_command, get_sop_class_uid(), kcenon::pacs::network::association::is_established(), logger_, kcenon::pacs::services::query_scu_config::max_results, next_message_id(), kcenon::pacs::error_codes::no_acceptable_context, kcenon::pacs::pacs_error(), queries_performed_, kcenon::pacs::network::association::receive_dimse(), kcenon::pacs::network::association::send_dimse(), kcenon::pacs::services::query_scu_config::timeout, kcenon::pacs::services::to_string(), and total_matches_.

Here is the call graph for this function:

◆ find_studies()

network::Result< query_result > kcenon::pacs::services::query_scu::find_studies ( network::association & assoc,
const study_query_keys & keys )
nodiscard

Query for studies.

Parameters
assocThe established association to use
keysStudy-level query keys
Returns
Result containing query_result on success

Definition at line 295 of file query_scu.cpp.

297 {
298
299 auto saved_level = config_.level;
301
302 auto query_ds = build_query_dataset(keys);
303 auto result = find(assoc, query_ds);
304
305 config_.level = saved_level;
306 return result;
307}
@ study
Study level - query study information.

References build_query_dataset(), config_, find(), kcenon::pacs::services::query_scu_config::level, and kcenon::pacs::services::study.

Referenced by kcenon::pacs::workflow::auto_prefetch_service::query_prior_studies(), and kcenon::pacs::web::endpoints::register_remote_nodes_endpoints_impl().

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

◆ get_sop_class_uid()

std::string_view kcenon::pacs::services::query_scu::get_sop_class_uid ( ) const
nodiscardprivatenoexcept

Get SOP Class UID based on current configuration.

Definition at line 507 of file query_scu.cpp.

507 {
509}
constexpr std::string_view get_find_sop_class_uid(query_model model) noexcept
Get the FIND SOP Class UID for a query model.
Definition query_scu.h:70
query_model model
Query information model (Patient Root or Study Root)
Definition query_scu.h:170

References config_, kcenon::pacs::services::get_find_sop_class_uid(), and kcenon::pacs::services::query_scu_config::model.

Referenced by cancel(), find_impl(), and find_streaming().

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

◆ next_message_id()

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

Get the next message ID for DIMSE operations.

Definition at line 394 of file query_scu.cpp.

394 {
395 uint16_t id = message_id_counter_.fetch_add(1, std::memory_order_relaxed);
396 // Wrap around at 0xFFFF, skip 0 (reserved)
397 if (id == 0) {
398 id = message_id_counter_.fetch_add(1, std::memory_order_relaxed);
399 }
400 return id;
401}
std::atomic< uint16_t > message_id_counter_
Message ID counter.
Definition query_scu.h:518
@ id
Implant Displaced (alternate code)

References message_id_counter_.

Referenced by find(), and find_streaming().

Here is the caller graph for this function:

◆ operator=() [1/2]

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

◆ operator=() [2/2]

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

◆ queries_performed()

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

Get the number of queries performed since construction.

Returns
Count of C-FIND requests sent

Definition at line 377 of file query_scu.cpp.

377 {
378 return queries_performed_.load(std::memory_order_relaxed);
379}

References queries_performed_.

◆ reset_statistics()

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

Reset statistics counters to zero.

Definition at line 385 of file query_scu.cpp.

385 {
386 queries_performed_.store(0, std::memory_order_relaxed);
387 total_matches_.store(0, std::memory_order_relaxed);
388}

References queries_performed_, and total_matches_.

◆ set_config()

void kcenon::pacs::services::query_scu::set_config ( const query_scu_config & config)

Update the SCU configuration.

Parameters
configNew configuration options

Definition at line 365 of file query_scu.cpp.

365 {
366 config_ = config;
367}

References config(), and config_.

Here is the call graph for this function:

◆ total_matches()

size_t kcenon::pacs::services::query_scu::total_matches ( ) const
nodiscardnoexcept

Get the total number of matches received since construction.

Returns
Total count of matching datasets received

Definition at line 381 of file query_scu.cpp.

381 {
382 return total_matches_.load(std::memory_order_relaxed);
383}

References total_matches_.

Member Data Documentation

◆ config_

query_scu_config kcenon::pacs::services::query_scu::config_
private

◆ logger_

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

Logger instance for service logging.

Definition at line 512 of file query_scu.h.

Referenced by find_impl(), and find_streaming().

◆ message_id_counter_

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

Message ID counter.

Definition at line 518 of file query_scu.h.

518{1};

Referenced by next_message_id().

◆ queries_performed_

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

Statistics: number of queries performed.

Definition at line 521 of file query_scu.h.

521{0};

Referenced by find_impl(), find_streaming(), queries_performed(), and reset_statistics().

◆ total_matches_

std::atomic<size_t> kcenon::pacs::services::query_scu::total_matches_ {0}
private

Statistics: total number of matches received.

Definition at line 524 of file query_scu.h.

524{0};

Referenced by find_impl(), find_streaming(), reset_statistics(), and total_matches().


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