PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
mini_pacs.cpp
Go to the documentation of this file.
1
6#include "mini_pacs.h"
7
10
11#include <chrono>
12#include <iomanip>
13#include <iostream>
14#include <sstream>
15
17
18namespace tags = kcenon::pacs::core::tags;
19
20// =============================================================================
21// Construction
22// =============================================================================
23
31
35
36// =============================================================================
37// Lifecycle
38// =============================================================================
39
41 if (running_) {
42 return true;
43 }
44
45 auto result = server_->start();
46 if (result.is_err()) {
47 std::cerr << "Failed to start server: " << result.error().message << "\n";
48 return false;
49 }
50
51 running_ = true;
52 return true;
53}
54
56 if (!running_) {
57 return;
58 }
59
60 running_ = false;
61 if (server_) {
62 server_->stop();
63 }
64}
65
67 if (server_) {
68 server_->wait_for_shutdown();
69 }
70}
71
72bool mini_pacs::is_running() const noexcept {
73 return running_;
74}
75
76// =============================================================================
77// Statistics
78// =============================================================================
79
81 return stats_;
82}
83
84// =============================================================================
85// Worklist Management
86// =============================================================================
87
89 std::lock_guard<std::mutex> lock(worklist_mutex_);
90 worklist_items_.push_back(item);
91}
92
94 std::lock_guard<std::mutex> lock(worklist_mutex_);
95 worklist_items_.clear();
96}
97
98std::vector<worklist_entry> mini_pacs::get_worklist_items() const {
99 std::lock_guard<std::mutex> lock(worklist_mutex_);
100 return worklist_items_;
101}
102
103// =============================================================================
104// Configuration Access
105// =============================================================================
106
107const mini_pacs_config& mini_pacs::config() const noexcept {
108 return config_;
109}
110
111// =============================================================================
112// Initialization
113// =============================================================================
114
116 // Create storage directories
117 std::filesystem::create_directories(config_.storage_path);
118
119 // Configure file storage
121 fs_config.root_path = config_.storage_path;
124 fs_config.create_directories = true;
125 fs_config.file_extension = ".dcm";
126
127 file_storage_ = std::make_shared<storage::file_storage>(fs_config);
128
129 // Initialize index database
130 auto db_path = config_.storage_path / "index.db";
131 auto db_result = storage::index_database::open(db_path.string());
132 if (db_result.is_err()) {
133 throw std::runtime_error("Failed to open index database: " +
134 db_result.error().message);
135 }
136 index_db_ = std::move(db_result.value());
137}
138
140 // Verification SCP (C-ECHO)
141 verification_scp_ = std::make_shared<services::verification_scp>();
142
143 // Storage SCP (C-STORE)
145 storage_scp_ = std::make_shared<services::storage_scp>(scp_config);
146 storage_scp_->set_handler(
147 [this](const auto& ds, const auto& ae, const auto& sop_class,
148 const auto& sop_instance) {
149 return handle_store(ds, ae, sop_class, sop_instance);
150 });
151
152 // Query SCP (C-FIND)
153 query_scp_ = std::make_shared<services::query_scp>();
154 query_scp_->set_handler(
155 [this](auto level, const auto& keys, const auto& ae) {
156 return handle_query(level, keys, ae);
157 });
158
159 // Retrieve SCP (C-MOVE/C-GET)
160 retrieve_scp_ = std::make_shared<services::retrieve_scp>();
161 retrieve_scp_->set_retrieve_handler(
162 [this](const auto& keys) { return handle_retrieve(keys); });
163 retrieve_scp_->set_destination_resolver(
164 [this](const auto& ae) { return resolve_destination(ae); });
165
166 // Worklist SCP (MWL C-FIND)
168 worklist_scp_ = std::make_shared<services::worklist_scp>();
169 worklist_scp_->set_handler(
170 [this](const auto& keys, const auto& ae) {
171 return handle_worklist_query(keys, ae);
172 });
173 }
174
175 // MPPS SCP (N-CREATE/N-SET)
176 if (config_.enable_mpps) {
177 mpps_scp_ = std::make_shared<services::mpps_scp>();
178 mpps_scp_->set_create_handler(
179 [this](const auto& inst) { return handle_mpps_create(inst); });
180 mpps_scp_->set_set_handler(
181 [this](const auto& uid, const auto& mods, auto status) {
182 return handle_mpps_set(uid, mods, status);
183 });
184 }
185}
186
192 server_config.idle_timeout = std::chrono::seconds(60);
193 server_config.max_pdu_size = 65536;
194 server_config.implementation_class_uid = "1.2.410.200001.1.4";
195 server_config.implementation_version_name = "MINI_PACS_4.0";
196
197 server_ = std::make_unique<network::dicom_server>(server_config);
198
199 // Register all services
200 server_->register_service(verification_scp_);
201 server_->register_service(storage_scp_);
202 server_->register_service(query_scp_);
203 server_->register_service(retrieve_scp_);
204
205 if (worklist_scp_) {
206 server_->register_service(worklist_scp_);
207 }
208 if (mpps_scp_) {
209 server_->register_service(mpps_scp_);
210 }
211}
212
214 server_->on_association_established(
215 [this](const network::association& assoc) {
219 std::cout << "[CONNECT] " << assoc.calling_ae()
220 << " -> " << assoc.called_ae()
221 << " (active: " << stats_.associations_active << ")\n";
222 }
223 });
224
225 server_->on_association_released(
226 [this](const network::association& assoc) {
229 std::cout << "[RELEASE] " << assoc.calling_ae()
230 << " (active: " << stats_.associations_active << ")\n";
231 }
232 });
233
234 server_->on_error(
235 [this](const std::string& msg) {
236 std::cerr << "[ERROR] " << msg << "\n";
237 });
238}
239
240// =============================================================================
241// Storage Handlers
242// =============================================================================
243
245 const core::dicom_dataset& dataset,
246 const std::string& calling_ae,
247 const std::string& /*sop_class_uid*/,
248 const std::string& sop_instance_uid) {
249
251
253 std::cout << "[C-STORE] From: " << calling_ae
254 << " Patient: " << dataset.get_string(tags::patient_name)
255 << " Modality: " << dataset.get_string(tags::modality) << "\n";
256 }
257
258 // Store to filesystem
259 auto store_result = file_storage_->store(dataset);
260 if (store_result.is_err()) {
261 std::cerr << " Storage failed: " << store_result.error().message << "\n";
263 }
264
265 // Get stored file path and update index
266 auto file_path = file_storage_->get_file_path(sop_instance_uid);
267 if (!update_index(dataset, file_path)) {
269 }
270
271 stats_.bytes_received += std::filesystem::exists(file_path)
272 ? std::filesystem::file_size(file_path)
273 : 0;
274
276}
277
279 const core::dicom_dataset& ds,
280 const std::filesystem::path& file_path) {
281
282 // Patient
283 auto patient_result = index_db_->upsert_patient(
288
289 if (patient_result.is_err()) {
290 std::cerr << "Database error (patient): "
291 << patient_result.error().message << "\n";
292 return false;
293 }
294 int64_t patient_pk = patient_result.value();
295
296 // Study
297 auto study_result = index_db_->upsert_study(
298 patient_pk,
306
307 if (study_result.is_err()) {
308 std::cerr << "Database error (study): "
309 << study_result.error().message << "\n";
310 return false;
311 }
312 int64_t study_pk = study_result.value();
313
314 // Series
315 auto series_result = index_db_->upsert_series(
316 study_pk,
321 "", // body_part_examined
323
324 if (series_result.is_err()) {
325 std::cerr << "Database error (series): "
326 << series_result.error().message << "\n";
327 return false;
328 }
329 int64_t series_pk = series_result.value();
330
331 // Instance
332 auto file_size = std::filesystem::exists(file_path)
333 ? static_cast<int64_t>(std::filesystem::file_size(file_path))
334 : 0;
335
336 auto instance_result = index_db_->upsert_instance(
337 series_pk,
340 file_path.string(),
341 file_size,
344
345 if (instance_result.is_err()) {
346 std::cerr << "Database error (instance): "
347 << instance_result.error().message << "\n";
348 return false;
349 }
350
351 return true;
352}
353
354// =============================================================================
355// Query Handlers
356// =============================================================================
357
358std::vector<core::dicom_dataset> mini_pacs::handle_query(
360 const core::dicom_dataset& query_keys,
361 const std::string& /*calling_ae*/) {
362
364
365 std::vector<core::dicom_dataset> results;
366
367 switch (level) {
370 auto pid = query_keys.get_string(tags::patient_id);
371 auto pname = query_keys.get_string(tags::patient_name);
372 if (!pid.empty()) pq.patient_id = pid;
373 if (!pname.empty()) pq.patient_name = pname;
374
375 auto patients = index_db_->search_patients(pq);
376
377 if (patients.is_ok()) {
378 for (const auto& p : patients.value()) {
384 results.push_back(std::move(ds));
385 }
386 }
387 break;
388 }
389
392 auto pid = query_keys.get_string(tags::patient_id);
393 auto study_uid = query_keys.get_string(tags::study_instance_uid);
394 auto study_date = query_keys.get_string(tags::study_date);
395 auto accession = query_keys.get_string(tags::accession_number);
396 if (!pid.empty()) sq.patient_id = pid;
397 if (!study_uid.empty()) sq.study_uid = study_uid;
398 if (!study_date.empty()) sq.study_date = study_date;
399 if (!accession.empty()) sq.accession_number = accession;
400
401 auto studies = index_db_->search_studies(sq);
402
403 if (studies.is_ok()) {
404 for (const auto& s : studies.value()) {
413 results.push_back(std::move(ds));
414 }
415 }
416 break;
417 }
418
421 auto study_uid = query_keys.get_string(tags::study_instance_uid);
422 auto series_uid = query_keys.get_string(tags::series_instance_uid);
423 auto modality = query_keys.get_string(tags::modality);
424 if (!study_uid.empty()) sq.study_uid = study_uid;
425 if (!series_uid.empty()) sq.series_uid = series_uid;
426 if (!modality.empty()) sq.modality = modality;
427
428 auto series = index_db_->search_series(sq);
429
430 if (series.is_ok()) {
431 for (const auto& ser : series.value()) {
435 if (ser.series_number.has_value()) {
436 ds.set_numeric(tags::series_number, encoding::vr_type::IS, ser.series_number.value());
437 }
438 ds.set_string(tags::series_description, encoding::vr_type::LO, ser.series_description);
440 results.push_back(std::move(ds));
441 }
442 }
443 break;
444 }
445
448 auto series_uid = query_keys.get_string(tags::series_instance_uid);
449 auto sop_uid = query_keys.get_string(tags::sop_instance_uid);
450 if (!series_uid.empty()) iq.series_uid = series_uid;
451 if (!sop_uid.empty()) iq.sop_uid = sop_uid;
452
453 auto instances = index_db_->search_instances(iq);
454
455 if (instances.is_ok()) {
456 for (const auto& inst : instances.value()) {
460 if (inst.instance_number.has_value()) {
461 ds.set_numeric(tags::instance_number, encoding::vr_type::IS, inst.instance_number.value());
462 }
463 results.push_back(std::move(ds));
464 }
465 }
466 break;
467 }
468 }
469
471 std::cout << "[C-FIND] Level: " << to_string(level)
472 << " Results: " << results.size() << "\n";
473 }
474
475 return results;
476}
477
478// =============================================================================
479// Retrieve Handlers
480// =============================================================================
481
482std::vector<core::dicom_file> mini_pacs::handle_retrieve(
483 const core::dicom_dataset& query_keys) {
484
485 std::vector<core::dicom_file> files;
486
487 // Determine query level from keys
488 auto study_uid = query_keys.get_string(tags::study_instance_uid);
489 auto series_uid = query_keys.get_string(tags::series_instance_uid);
490 auto sop_uid = query_keys.get_string(tags::sop_instance_uid);
491
492 std::vector<storage::instance_record> instances;
493
494 if (!sop_uid.empty()) {
495 // Instance level retrieve
497 iq.sop_uid = sop_uid;
498 auto result = index_db_->search_instances(iq);
499 if (result.is_ok()) {
500 instances = result.value();
501 }
502 } else if (!series_uid.empty()) {
503 // Series level retrieve
505 iq.series_uid = series_uid;
506 auto result = index_db_->search_instances(iq);
507 if (result.is_ok()) {
508 instances = result.value();
509 }
510 } else if (!study_uid.empty()) {
511 // Study level retrieve - get all series first
513 sq.study_uid = study_uid;
514 auto series_result = index_db_->search_series(sq);
515 if (series_result.is_ok()) {
516 for (const auto& ser : series_result.value()) {
518 iq.series_uid = ser.series_uid;
519 auto inst_result = index_db_->search_instances(iq);
520 if (inst_result.is_ok()) {
521 for (const auto& inst : inst_result.value()) {
522 instances.push_back(inst);
523 }
524 }
525 }
526 }
527 }
528
529 // Load files from disk
530 for (const auto& inst : instances) {
531 auto file_result = core::dicom_file::open(inst.file_path);
532 if (file_result.is_ok()) {
533 files.push_back(std::move(file_result.value()));
534 }
535 }
536
538
540 std::cout << "[C-MOVE/C-GET] Files: " << files.size() << "\n";
541 }
542
543 return files;
544}
545
546std::optional<std::pair<std::string, uint16_t>> mini_pacs::resolve_destination(
547 const std::string& ae_title) {
548 // Simple in-memory AE resolution
549 // In a production system, this would query a configuration database
550 if (ae_title == config_.ae_title) {
551 return std::make_pair("localhost", config_.port);
552 }
553 // Return nullopt for unknown destinations
554 return std::nullopt;
555}
556
557// =============================================================================
558// Worklist Handlers
559// =============================================================================
560
561std::vector<core::dicom_dataset> mini_pacs::handle_worklist_query(
562 const core::dicom_dataset& query_keys,
563 const std::string& /*calling_ae*/) {
564
566
567 std::vector<core::dicom_dataset> results;
568
569 // Extract filter criteria
570 auto filter_modality = query_keys.get_string(tags::modality);
571 auto filter_date = query_keys.get_string(tags::scheduled_procedure_step_start_date);
572 auto filter_patient_id = query_keys.get_string(tags::patient_id);
573
574 std::lock_guard<std::mutex> lock(worklist_mutex_);
575
576 for (const auto& item : worklist_items_) {
577 // Apply filters
578 if (!filter_modality.empty() && item.modality != filter_modality) {
579 continue;
580 }
581 if (!filter_date.empty() && item.scheduled_date != filter_date) {
582 continue;
583 }
584 if (!filter_patient_id.empty() && item.patient_id != filter_patient_id) {
585 continue;
586 }
587
588 results.push_back(create_worklist_response(item));
589 }
590
592 std::cout << "[MWL] Results: " << results.size() << "\n";
593 }
594
595 return results;
596}
597
600
601 // Patient level
604 ds.set_string(tags::patient_birth_date, encoding::vr_type::DA, item.patient_birth_date);
606
607 // Study level
609 ds.set_string(tags::accession_number, encoding::vr_type::SH, item.accession_number);
611
612 // Scheduled Procedure Step Sequence would be set here
613 // For simplicity, we set flat attributes
620
621 return ds;
622}
623
624// =============================================================================
625// MPPS Handlers
626// =============================================================================
627
629 const services::mpps_instance& instance) {
630
632
633 mpps_entry entry;
634 entry.sop_instance_uid = instance.sop_instance_uid;
635 entry.status = "IN PROGRESS";
636 entry.station_ae = instance.station_ae;
637 entry.data = instance.data;
638
639 {
640 std::lock_guard<std::mutex> lock(mpps_mutex_);
641 mpps_instances_.push_back(std::move(entry));
642 }
643
645 std::cout << "[MPPS N-CREATE] UID: " << instance.sop_instance_uid
646 << " Station: " << instance.station_ae << "\n";
647 }
648
650}
651
653 const std::string& sop_instance_uid,
654 const core::dicom_dataset& /*modifications*/,
655 services::mpps_status new_status) {
656
658
659 std::lock_guard<std::mutex> lock(mpps_mutex_);
660
661 for (auto& entry : mpps_instances_) {
662 if (entry.sop_instance_uid == sop_instance_uid) {
663 entry.status = std::string(services::to_string(new_status));
664
666 std::cout << "[MPPS N-SET] UID: " << sop_instance_uid
667 << " Status: " << entry.status << "\n";
668 }
669
671 }
672 }
673
676 "MPPS instance not found: " + sop_instance_uid);
677}
678
679} // namespace kcenon::pacs::samples
Represents a DICOM Dataset (ordered collection of Data Elements)
void set_numeric(dicom_tag tag, encoding::vr_type vr, T value)
Set a numeric value for the given tag.
auto get_numeric(dicom_tag tag) const -> std::optional< T >
Get the numeric value of an element.
void set_string(dicom_tag tag, encoding::vr_type vr, std::string_view value)
Set a string value for the given tag.
auto get_string(dicom_tag tag, std::string_view default_value="") const -> std::string
Get the string value of an element.
static auto open(const std::filesystem::path &path) -> kcenon::pacs::Result< dicom_file >
Open and read a DICOM file from disk.
DICOM Association management class.
std::string_view calling_ae() const noexcept
Get calling AE title.
std::string_view called_ae() const noexcept
Get called AE title.
std::unique_ptr< network::dicom_server > server_
Definition mini_pacs.h:386
std::shared_ptr< services::retrieve_scp > retrieve_scp_
Definition mini_pacs.h:381
std::shared_ptr< services::mpps_scp > mpps_scp_
Definition mini_pacs.h:383
std::atomic< bool > running_
Definition mini_pacs.h:387
std::shared_ptr< storage::file_storage > file_storage_
Definition mini_pacs.h:374
void stop()
Stop the PACS server.
Definition mini_pacs.cpp:55
std::vector< core::dicom_dataset > handle_worklist_query(const core::dicom_dataset &query_keys, const std::string &calling_ae)
std::shared_ptr< services::query_scp > query_scp_
Definition mini_pacs.h:380
std::shared_ptr< services::worklist_scp > worklist_scp_
Definition mini_pacs.h:382
std::vector< core::dicom_dataset > handle_query(services::query_level level, const core::dicom_dataset &query_keys, const std::string &calling_ae)
const mini_pacs_statistics & statistics() const noexcept
Get current statistics.
Definition mini_pacs.cpp:80
std::shared_ptr< services::storage_scp > storage_scp_
Definition mini_pacs.h:379
std::vector< worklist_entry > worklist_items_
Definition mini_pacs.h:391
core::dicom_dataset create_worklist_response(const worklist_entry &item)
~mini_pacs()
Destructor - ensures clean shutdown.
Definition mini_pacs.cpp:32
bool is_running() const noexcept
Check if server is running.
Definition mini_pacs.cpp:72
std::vector< mpps_entry > mpps_instances_
Definition mini_pacs.h:395
std::vector< worklist_entry > get_worklist_items() const
Get current worklist items.
Definition mini_pacs.cpp:98
bool start()
Start the PACS server.
Definition mini_pacs.cpp:40
services::storage_status handle_store(const core::dicom_dataset &dataset, const std::string &calling_ae, const std::string &sop_class_uid, const std::string &sop_instance_uid)
std::optional< std::pair< std::string, uint16_t > > resolve_destination(const std::string &ae_title)
network::Result< std::monostate > handle_mpps_set(const std::string &sop_instance_uid, const core::dicom_dataset &modifications, services::mpps_status new_status)
std::vector< core::dicom_file > handle_retrieve(const core::dicom_dataset &query_keys)
void wait()
Block until server shutdown.
Definition mini_pacs.cpp:66
const mini_pacs_config & config() const noexcept
Get the configuration.
std::shared_ptr< services::verification_scp > verification_scp_
Definition mini_pacs.h:378
std::shared_ptr< storage::index_database > index_db_
Definition mini_pacs.h:375
network::Result< std::monostate > handle_mpps_create(const services::mpps_instance &instance)
bool update_index(const core::dicom_dataset &dataset, const std::filesystem::path &file_path)
mini_pacs_statistics stats_
Definition mini_pacs.h:371
void clear_worklist()
Clear all worklist items.
Definition mini_pacs.cpp:93
void add_worklist_item(const worklist_entry &item)
Add a worklist item for MWL queries.
Definition mini_pacs.cpp:88
mini_pacs(const mini_pacs_config &config)
Construct Mini PACS with configuration.
Definition mini_pacs.cpp:24
static auto open(std::string_view db_path) -> Result< std::unique_ptr< index_database > >
Open or create a database with default configuration.
Compile-time constants for commonly used DICOM tags.
Mini PACS class integrating all DICOM services.
constexpr dicom_tag referring_physician_name
Referring Physician's Name.
constexpr dicom_tag scheduled_procedure_step_description
Scheduled Procedure Step Description.
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 sop_instance_uid
SOP Instance UID.
constexpr dicom_tag patient_birth_date
Patient's Birth Date.
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 study_time
Study Time.
constexpr dicom_tag patient_sex
Patient's Sex.
constexpr dicom_tag study_instance_uid
Study Instance UID.
constexpr dicom_tag station_name
Station Name.
constexpr dicom_tag series_number
Series Number.
constexpr dicom_tag series_description
Series Description.
constexpr dicom_tag transfer_syntax_uid
Transfer Syntax UID.
constexpr dicom_tag sop_class_uid
SOP Class UID.
constexpr dicom_tag study_id
Study ID.
constexpr dicom_tag patient_name
Patient's Name.
constexpr dicom_tag study_date
Study Date.
constexpr dicom_tag scheduled_procedure_step_start_time
Scheduled Procedure Step Start Time.
constexpr dicom_tag series_instance_uid
Series Instance UID.
constexpr dicom_tag instance_number
Instance Number.
constexpr dicom_tag scheduled_procedure_step_id
Scheduled Procedure Step ID.
@ DA
Date (8 chars, format: YYYYMMDD)
@ IS
Integer String (12 chars max)
@ LO
Long String (64 chars max)
@ UI
Unique Identifier (64 chars max)
@ PN
Person Name (64 chars max per component group)
@ CS
Code String (16 chars max, uppercase + digits + space + underscore)
@ TM
Time (14 chars max, format: HHMMSS.FFFFFF)
@ AE
Application Entity (16 chars max)
@ SH
Short String (16 chars max)
constexpr int instance_not_found
Definition result.h:137
storage_status
Storage operation status codes.
@ storage_error
Failure: Unable to process - storage error (0xC001)
@ success
Success - image stored successfully (0x0000)
mpps_status
MPPS status enumeration.
Definition mpps_scp.h:48
auto to_string(mpps_status status) -> std::string_view
Convert mpps_status to DICOM string representation.
Definition mpps_scp.h:60
query_level
DICOM Query/Retrieve level enumeration.
Definition query_scp.h:63
@ study
Study level - query study information.
@ image
Image (Instance) level - query instance information.
@ patient
Patient level - query patient demographics.
@ series
Series level - query series information.
@ uid_hierarchical
{StudyUID}/{SeriesUID}/{SOPUID}.dcm
@ replace
Overwrite existing instance.
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
Configuration for DICOM server.
Configuration options for Mini PACS.
Definition mini_pacs.h:51
uint16_t port
TCP port to listen on.
Definition mini_pacs.h:56
std::string ae_title
Application Entity title (max 16 characters)
Definition mini_pacs.h:53
bool verbose_logging
Enable verbose logging.
Definition mini_pacs.h:71
bool enable_mpps
Enable MPPS service.
Definition mini_pacs.h:68
std::filesystem::path storage_path
Root path for DICOM file storage.
Definition mini_pacs.h:59
size_t max_associations
Maximum concurrent associations.
Definition mini_pacs.h:62
bool enable_worklist
Enable Modality Worklist service.
Definition mini_pacs.h:65
Runtime statistics for Mini PACS operations.
Definition mini_pacs.h:83
std::atomic< int > c_move_count
C-MOVE operations processed.
Definition mini_pacs.h:100
std::atomic< int > associations_total
Total associations established since start.
Definition mini_pacs.h:85
std::atomic< int > associations_active
Currently active associations.
Definition mini_pacs.h:88
std::atomic< int > c_store_count
C-STORE operations processed.
Definition mini_pacs.h:94
std::atomic< int > mwl_count
MWL queries processed.
Definition mini_pacs.h:106
std::atomic< int > c_find_count
C-FIND operations processed.
Definition mini_pacs.h:97
std::atomic< size_t > bytes_received
Total bytes received.
Definition mini_pacs.h:115
std::atomic< int > mpps_create_count
MPPS N-CREATE processed.
Definition mini_pacs.h:109
std::atomic< int > mpps_set_count
MPPS N-SET processed.
Definition mini_pacs.h:112
MPPS instance for tracking procedure progress.
Definition mini_pacs.h:150
core::dicom_dataset data
Definition mini_pacs.h:154
std::string sop_instance_uid
Definition mini_pacs.h:151
std::string station_ae
Definition mini_pacs.h:153
std::string status
Definition mini_pacs.h:152
DICOM server configuration.
std::uint16_t port
TCP port for DICOM connections.
std::size_t max_associations
Maximum concurrent associations.
std::chrono::seconds idle_timeout
Idle timeout for associations (seconds)
std::string ae_title
Application Entity title (max 16 characters)
Worklist item for MWL queries.
Definition mini_pacs.h:127
MPPS instance data structure.
Definition mpps_scp.h:98
std::string sop_instance_uid
SOP Instance UID - unique identifier for this MPPS.
Definition mpps_scp.h:100
std::string station_ae
Performing station AE Title.
Definition mpps_scp.h:106
core::dicom_dataset data
Complete MPPS dataset from the request.
Definition mpps_scp.h:109
Configuration for Storage SCP service.
Definition storage_scp.h:49
Configuration for file_storage.
std::string file_extension
File extension for DICOM files.
bool create_directories
Create directories automatically if they don't exist.
duplicate_policy duplicate
How to handle duplicate instances.
std::filesystem::path root_path
Root directory for storage.
naming_scheme naming
File organization scheme.
Query parameters for instance search.
std::optional< std::string > series_uid
Series Instance UID for filtering by series (exact match)
std::optional< std::string > sop_uid
SOP Instance UID (exact match)
Query parameters for patient search.
std::optional< std::string > patient_name
Patient name pattern (supports * wildcard)
std::optional< std::string > patient_id
Patient ID pattern (supports * wildcard)
Query parameters for series search.
std::optional< std::string > study_uid
Study Instance UID for filtering by study (exact match)
std::optional< std::string > series_uid
Series Instance UID (exact match)
std::optional< std::string > modality
Modality filter (exact match, e.g., "CT", "MR")
Query parameters for study search.
std::optional< std::string > study_uid
Study Instance UID (exact match)
std::optional< std::string > accession_number
Accession number pattern (supports * wildcard)
std::optional< std::string > study_date
Study date (exact match, format: YYYYMMDD)
std::optional< std::string > patient_id
Patient ID for filtering by patient (exact match or wildcard)
std::string_view uid