32 : logger_(logger ? std::move(logger) : di::null_logger()) {}
35 std::shared_ptr<di::ILogger> logger)
36 : logger_(logger ? std::move(logger) : di::null_logger()), config_(config) {}
47 return query(assoc, query_ds);
60 uint16_t message_id) {
62 using namespace network::dimse;
64 auto start_time = std::chrono::steady_clock::now();
70 "Association not established");
78 "No accepted presentation context for Modality Worklist: " +
84 request.set_dataset(query_keys);
86 logger_->debug_fmt(
"Sending MWL C-FIND request (message_id={})", message_id);
89 auto send_result = assoc.
send_dimse(*context_id, request);
90 if (send_result.is_err()) {
91 return send_result.error();
96 bool query_complete =
false;
98 while (!query_complete) {
100 if (recv_result.is_err()) {
101 return recv_result.error();
104 const auto& [recv_context_id, response] = recv_result.value();
107 if (response.command() != command_field::c_find_rsp) {
110 "Expected C-FIND-RSP but received " +
111 std::string(
to_string(response.command())));
114 auto status = response.status();
116 if (status == status_pending || status == status_pending_warning) {
119 if (response.has_dataset()) {
124 auto dataset_result = response.dataset();
125 if (dataset_result.is_ok()) {
126 result.
items.push_back(
137 "Max results ({}) reached, sending 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);
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);
156 query_complete =
true;
157 result.
status =
static_cast<uint16_t
>(status);
161 auto end_time = std::chrono::steady_clock::now();
162 result.
elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
163 end_time - start_time);
169 logger_->debug_fmt(
"MWL C-FIND completed: {} items in {} ms",
181 std::string_view station_ae,
182 std::string_view modality) {
186 keys.
modality = std::string(modality);
189 return query(assoc, keys);
194 std::string_view start_date,
195 std::string_view end_date,
196 std::string_view modality) {
200 keys.
scheduled_date = std::string(start_date) +
"-" + std::string(end_date);
201 keys.
modality = std::string(modality);
203 return query(assoc, keys);
208 std::string_view patient_id) {
213 return query(assoc, keys);
225 using namespace network::dimse;
228 auto start_time = std::chrono::steady_clock::now();
234 "Association not established");
242 "No accepted presentation context for Modality Worklist");
248 request.set_dataset(query_ds);
250 logger_->debug_fmt(
"Sending streaming MWL C-FIND request (message_id={})",
254 auto send_result = assoc.
send_dimse(*context_id, request);
255 if (send_result.is_err()) {
256 return send_result.error();
261 bool query_complete =
false;
262 bool should_cancel =
false;
264 while (!query_complete) {
266 if (recv_result.is_err()) {
267 return recv_result.error();
270 const auto& [recv_context_id, response] = recv_result.value();
273 if (response.command() != command_field::c_find_rsp) {
276 "Expected C-FIND-RSP but received " +
277 std::string(
to_string(response.command())));
280 auto status = response.status();
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()) {
291 if (!callback(item)) {
292 should_cancel =
true;
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);
308 should_cancel =
false;
313 query_complete =
true;
319 total_items_.fetch_add(count, std::memory_order_relaxed);
321 auto end_time = std::chrono::steady_clock::now();
322 auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
323 end_time - start_time);
325 logger_->debug_fmt(
"Streaming MWL C-FIND completed: {} items in {} ms",
326 count, elapsed.count());
337 uint16_t message_id) {
339 using namespace network::dimse;
345 "No accepted presentation context for cancel");
349 dimse_message cancel_rq{command_field::c_cancel_rq, message_id};
350 return assoc.
send_dimse(*context_id, cancel_rq);
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");
406 using namespace core;
432 item.scheduled_procedure_step_description =
444 using namespace core;
445 using namespace encoding;
auto get_string(dicom_tag tag, std::string_view default_value="") const -> std::string
Get the string value of an element.
bool is_established() const noexcept
Check if association is established and ready for DIMSE.
Result< std::monostate > send_dimse(uint8_t context_id, const dimse::dimse_message &msg)
Send a DIMSE message.
Result< std::pair< uint8_t, dimse::dimse_message > > receive_dimse(duration timeout=default_timeout)
Receive a DIMSE message.
std::optional< uint8_t > accepted_context_id(std::string_view abstract_syntax) const
Get the presentation context ID for an abstract syntax.
void reset_statistics() noexcept
Reset statistics counters to zero.
worklist_scu(std::shared_ptr< di::ILogger > logger=nullptr)
Construct a Worklist SCU with default configuration.
std::atomic< size_t > total_items_
Statistics: total number of items received.
network::Result< worklist_result > query_impl(network::association &assoc, const core::dicom_dataset &query_keys, uint16_t message_id)
Internal query implementation.
std::atomic< uint16_t > message_id_counter_
Message ID counter.
size_t queries_performed() const noexcept
Get the number of queries performed since construction.
std::shared_ptr< di::ILogger > logger_
Logger instance for service logging.
network::Result< worklist_result > query_patient(network::association &assoc, std::string_view patient_id)
Query worklist by patient ID.
network::Result< std::monostate > cancel(network::association &assoc, uint16_t message_id)
Send a C-CANCEL request to stop an ongoing query.
uint16_t next_message_id() noexcept
Get the next message ID for DIMSE operations.
const worklist_scu_config & config() const noexcept
Get the current configuration.
network::Result< worklist_result > query(network::association &assoc, const worklist_query_keys &keys)
Perform a MWL C-FIND query with typed keys.
static std::string get_today_date()
Get today's date in DICOM format (YYYYMMDD)
std::atomic< size_t > queries_performed_
Statistics: number of queries performed.
core::dicom_dataset build_query_dataset(const worklist_query_keys &keys) const
Build query dataset from typed keys.
worklist_scu_config config_
Configuration.
void set_config(const worklist_scu_config &config)
Update the SCU configuration.
size_t total_items() const noexcept
Get the total number of items received since construction.
worklist_item parse_worklist_item(const core::dicom_dataset &ds) const
Parse a worklist item from a response dataset.
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< worklist_result > query_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_result > query_today(network::association &assoc, std::string_view station_ae, std::string_view modality="")
Query today's worklist for a station.
DIMSE command field enumeration.
Compile-time constants for commonly used DICOM tags.
constexpr int no_acceptable_context
constexpr int find_unexpected_command
constexpr int association_not_established
constexpr std::string_view worklist_find_sop_class_uid
Modality Worklist Information Model - FIND SOP Class UID.
std::function< bool(const worklist_item &)> worklist_streaming_callback
Callback type for streaming worklist query results.
auto to_string(mpps_status status) -> std::string_view
Convert mpps_status to DICOM string representation.
Result< T > pacs_error(int code, const std::string &message, const std::string &details="")
Create a PACS error result with module context.
Result<T> type aliases and helpers for PACS system.
Parsed worklist item from MWL query response.
Typed query keys for Modality Worklist queries.
std::string scheduled_station_ae
Scheduled Station AE Title (0040,0001)
std::string institution
Institution Name (0008,0080)
std::string scheduled_date
Scheduled Procedure Step Start Date (0040,0002) - YYYYMMDD or range.
std::string modality
Modality (0008,0060) - e.g., CT, MR, US, XR.
std::string requested_procedure_id
Requested Procedure ID (0040,1001)
std::string patient_sex
Patient's Sex (0010,0040) - M, F, O.
std::string scheduled_physician
Scheduled Performing Physician's Name (0040,0006)
std::string referring_physician
Referring Physician's Name (0008,0090)
std::string accession_number
Accession Number (0008,0050)
std::string requested_procedure_description
Requested Procedure Description (0032,1060)
std::string patient_name
Patient's Name (0010,0010) - supports wildcards (* ?)
std::string patient_id
Patient ID (0010,0020)
std::string scheduled_procedure_step_id
Scheduled Procedure Step ID (0040,0009)
std::string patient_birth_date
Patient's Birth Date (0010,0030)
std::string scheduled_time
Scheduled Procedure Step Start Time (0040,0003) - HHMMSS or range.
Result of a Modality Worklist query operation.
std::vector< worklist_item > items
Parsed worklist items from the query.
std::chrono::milliseconds elapsed
Query execution time.
size_t total_pending
Total pending responses received (may differ from items.size() if max_results was enforced)
uint16_t status
Final DIMSE status code (0x0000 = success)
Configuration for Worklist SCU service.
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.
DICOM Modality Worklist SCP service (MWL C-FIND handler)
DICOM Modality Worklist SCU service (MWL C-FIND sender)