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

#include <mini_pacs.h>

Collaboration diagram for kcenon::pacs::samples::mini_pacs:
Collaboration graph

Public Member Functions

 mini_pacs (const mini_pacs_config &config)
 Construct Mini PACS with configuration.
 
 ~mini_pacs ()
 Destructor - ensures clean shutdown.
 
 mini_pacs (const mini_pacs &)=delete
 
mini_pacs & operator= (const mini_pacs &)=delete
 
 mini_pacs (mini_pacs &&)=delete
 
mini_pacs & operator= (mini_pacs &&)=delete
 
bool start ()
 Start the PACS server.
 
void stop ()
 Stop the PACS server.
 
void wait ()
 Block until server shutdown.
 
bool is_running () const noexcept
 Check if server is running.
 
const mini_pacs_statistics & statistics () const noexcept
 Get current statistics.
 
void add_worklist_item (const worklist_entry &item)
 Add a worklist item for MWL queries.
 
void clear_worklist ()
 Clear all worklist items.
 
std::vector< worklist_entry > get_worklist_items () const
 Get current worklist items.
 
const mini_pacs_config & config () const noexcept
 Get the configuration.
 

Private Member Functions

void initialize_storage ()
 
void initialize_services ()
 
void configure_server ()
 
void setup_event_handlers ()
 
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)
 
bool update_index (const core::dicom_dataset &dataset, const std::filesystem::path &file_path)
 
std::vector< core::dicom_dataset > handle_query (services::query_level level, const core::dicom_dataset &query_keys, const std::string &calling_ae)
 
std::vector< core::dicom_file > handle_retrieve (const core::dicom_dataset &query_keys)
 
std::optional< std::pair< std::string, uint16_t > > resolve_destination (const std::string &ae_title)
 
std::vector< core::dicom_dataset > handle_worklist_query (const core::dicom_dataset &query_keys, const std::string &calling_ae)
 
core::dicom_dataset create_worklist_response (const worklist_entry &item)
 
network::Result< std::monostate > handle_mpps_create (const services::mpps_instance &instance)
 
network::Result< std::monostate > handle_mpps_set (const std::string &sop_instance_uid, const core::dicom_dataset &modifications, services::mpps_status new_status)
 

Private Attributes

mini_pacs_config config_
 
mini_pacs_statistics stats_
 
std::shared_ptr< storage::file_storage > file_storage_
 
std::shared_ptr< storage::index_database > index_db_
 
std::shared_ptr< services::verification_scp > verification_scp_
 
std::shared_ptr< services::storage_scp > storage_scp_
 
std::shared_ptr< services::query_scp > query_scp_
 
std::shared_ptr< services::retrieve_scp > retrieve_scp_
 
std::shared_ptr< services::worklist_scp > worklist_scp_
 
std::shared_ptr< services::mpps_scp > mpps_scp_
 
std::unique_ptr< network::dicom_server > server_
 
std::atomic< bool > running_ {false}
 
std::mutex worklist_mutex_
 
std::vector< worklist_entry > worklist_items_
 
std::mutex mpps_mutex_
 
std::vector< mpps_entry > mpps_instances_
 

Detailed Description

Definition at line 211 of file mini_pacs.h.

Constructor & Destructor Documentation

◆ mini_pacs() [1/3]

kcenon::pacs::samples::mini_pacs::mini_pacs ( const mini_pacs_config & config)
explicit

Construct Mini PACS with configuration.

Parameters
configConfiguration options

Definition at line 24 of file mini_pacs.cpp.

References configure_server(), initialize_services(), initialize_storage(), and setup_event_handlers().

Here is the call graph for this function:

◆ ~mini_pacs()

kcenon::pacs::samples::mini_pacs::~mini_pacs ( )

Destructor - ensures clean shutdown.

Definition at line 32 of file mini_pacs.cpp.

32 {
33 stop();
34}
void stop()
Stop the PACS server.
Definition mini_pacs.cpp:55

References stop().

Here is the call graph for this function:

◆ mini_pacs() [2/3]

kcenon::pacs::samples::mini_pacs::mini_pacs ( const mini_pacs & )
delete

◆ mini_pacs() [3/3]

kcenon::pacs::samples::mini_pacs::mini_pacs ( mini_pacs && )
delete

Member Function Documentation

◆ add_worklist_item()

void kcenon::pacs::samples::mini_pacs::add_worklist_item ( const worklist_entry & item)

Add a worklist item for MWL queries.

Parameters
itemThe worklist entry to add

Definition at line 88 of file mini_pacs.cpp.

88 {
89 std::lock_guard<std::mutex> lock(worklist_mutex_);
90 worklist_items_.push_back(item);
91}
std::vector< worklist_entry > worklist_items_
Definition mini_pacs.h:391

References worklist_items_, and worklist_mutex_.

◆ clear_worklist()

void kcenon::pacs::samples::mini_pacs::clear_worklist ( )

Clear all worklist items.

Definition at line 93 of file mini_pacs.cpp.

93 {
94 std::lock_guard<std::mutex> lock(worklist_mutex_);
95 worklist_items_.clear();
96}

References worklist_items_, and worklist_mutex_.

◆ config()

const mini_pacs_config & kcenon::pacs::samples::mini_pacs::config ( ) const
nodiscardnoexcept

Get the configuration.

Returns
Reference to configuration

Definition at line 107 of file mini_pacs.cpp.

107 {
108 return config_;
109}

References config_.

◆ configure_server()

void kcenon::pacs::samples::mini_pacs::configure_server ( )
private

Definition at line 187 of file mini_pacs.cpp.

187 {
188 network::server_config server_config;
189 server_config.ae_title = config_.ae_title;
190 server_config.port = config_.port;
191 server_config.max_associations = config_.max_associations;
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}
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::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::shared_ptr< services::storage_scp > storage_scp_
Definition mini_pacs.h:379
std::shared_ptr< services::verification_scp > verification_scp_
Definition mini_pacs.h:378
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
size_t max_associations
Maximum concurrent associations.
Definition mini_pacs.h:62

References kcenon::pacs::samples::mini_pacs_config::ae_title, kcenon::pacs::samples::server_config::ae_title, config_, kcenon::pacs::samples::server_config::idle_timeout, kcenon::pacs::samples::mini_pacs_config::max_associations, kcenon::pacs::samples::server_config::max_associations, mpps_scp_, kcenon::pacs::samples::mini_pacs_config::port, kcenon::pacs::samples::server_config::port, query_scp_, retrieve_scp_, server_, storage_scp_, verification_scp_, and worklist_scp_.

Referenced by mini_pacs().

Here is the caller graph for this function:

◆ create_worklist_response()

core::dicom_dataset kcenon::pacs::samples::mini_pacs::create_worklist_response ( const worklist_entry & item)
private

Definition at line 598 of file mini_pacs.cpp.

598 {
600
601 // Patient level
606
607 // Study level
611
612 // Scheduled Procedure Step Sequence would be set here
613 // For simplicity, we set flat attributes
620
621 return ds;
622}
Represents a DICOM Dataset (ordered collection of Data Elements)
void set_string(dicom_tag tag, encoding::vr_type vr, std::string_view value)
Set a string value for the given tag.
constexpr dicom_tag referring_physician_name
Referring Physician's Name.
constexpr dicom_tag scheduled_procedure_step_description
Scheduled Procedure Step Description.
constexpr dicom_tag patient_id
Patient ID.
constexpr dicom_tag scheduled_procedure_step_start_date
Scheduled Procedure Step Start Date.
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 patient_sex
Patient's Sex.
constexpr dicom_tag study_instance_uid
Study Instance UID.
constexpr dicom_tag item
Item.
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.
@ DA
Date (8 chars, format: YYYYMMDD)
@ 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)

References kcenon::pacs::core::tags::accession_number, kcenon::pacs::encoding::AE, kcenon::pacs::encoding::CS, kcenon::pacs::encoding::DA, kcenon::pacs::encoding::LO, 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::encoding::PN, kcenon::pacs::core::tags::referring_physician_name, 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::dicom_dataset::set_string(), kcenon::pacs::encoding::SH, kcenon::pacs::core::tags::study_instance_uid, kcenon::pacs::encoding::TM, and kcenon::pacs::encoding::UI.

Referenced by handle_worklist_query().

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

◆ get_worklist_items()

std::vector< worklist_entry > kcenon::pacs::samples::mini_pacs::get_worklist_items ( ) const
nodiscard

Get current worklist items.

Returns
Vector of worklist entries

Definition at line 98 of file mini_pacs.cpp.

98 {
99 std::lock_guard<std::mutex> lock(worklist_mutex_);
100 return worklist_items_;
101}

References worklist_items_, and worklist_mutex_.

◆ handle_mpps_create()

network::Result< std::monostate > kcenon::pacs::samples::mini_pacs::handle_mpps_create ( const services::mpps_instance & instance)
private

Definition at line 628 of file mini_pacs.cpp.

629 {
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
649 return network::Result<std::monostate>::ok({});
650}
std::vector< mpps_entry > mpps_instances_
Definition mini_pacs.h:395
mini_pacs_statistics stats_
Definition mini_pacs.h:371
bool verbose_logging
Enable verbose logging.
Definition mini_pacs.h:71
std::atomic< int > mpps_create_count
MPPS N-CREATE processed.
Definition mini_pacs.h:109

References config_, kcenon::pacs::samples::mpps_entry::data, kcenon::pacs::services::mpps_instance::data, kcenon::pacs::samples::mini_pacs_statistics::mpps_create_count, mpps_instances_, mpps_mutex_, kcenon::pacs::samples::mpps_entry::sop_instance_uid, kcenon::pacs::services::mpps_instance::sop_instance_uid, kcenon::pacs::samples::mpps_entry::station_ae, kcenon::pacs::services::mpps_instance::station_ae, stats_, kcenon::pacs::samples::mpps_entry::status, and kcenon::pacs::samples::mini_pacs_config::verbose_logging.

Referenced by initialize_services().

Here is the caller graph for this function:

◆ handle_mpps_set()

network::Result< std::monostate > kcenon::pacs::samples::mini_pacs::handle_mpps_set ( const std::string & sop_instance_uid,
const core::dicom_dataset & modifications,
services::mpps_status new_status )
private

Definition at line 652 of file mini_pacs.cpp.

655 {
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
670 return network::Result<std::monostate>::ok({});
671 }
672 }
673
676 "MPPS instance not found: " + sop_instance_uid);
677}
constexpr dicom_tag sop_instance_uid
SOP Instance UID.
constexpr int instance_not_found
Definition result.h:137
auto to_string(mpps_status status) -> std::string_view
Convert mpps_status to DICOM string representation.
Definition mpps_scp.h:60
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
std::atomic< int > mpps_set_count
MPPS N-SET processed.
Definition mini_pacs.h:112

References config_, kcenon::pacs::error_codes::instance_not_found, mpps_instances_, mpps_mutex_, kcenon::pacs::samples::mini_pacs_statistics::mpps_set_count, kcenon::pacs::pacs_error(), stats_, kcenon::pacs::services::to_string(), and kcenon::pacs::samples::mini_pacs_config::verbose_logging.

Referenced by initialize_services().

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

◆ handle_query()

std::vector< core::dicom_dataset > kcenon::pacs::samples::mini_pacs::handle_query ( services::query_level level,
const core::dicom_dataset & query_keys,
const std::string & calling_ae )
private

Definition at line 358 of file mini_pacs.cpp.

361 {
362
364
365 std::vector<core::dicom_dataset> results;
366
367 switch (level) {
369 storage::patient_query pq;
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
391 storage::study_query sq;
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
420 storage::series_query sq;
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
447 storage::instance_query iq;
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}
void set_numeric(dicom_tag tag, encoding::vr_type vr, T value)
Set a numeric 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.
std::shared_ptr< storage::index_database > index_db_
Definition mini_pacs.h:375
constexpr dicom_tag study_description
Study Description.
constexpr dicom_tag study_time
Study Time.
constexpr dicom_tag station_name
Station Name.
constexpr dicom_tag series_number
Series Number.
constexpr dicom_tag series_description
Series Description.
constexpr dicom_tag sop_class_uid
SOP Class UID.
constexpr dicom_tag study_id
Study ID.
constexpr dicom_tag study_date
Study Date.
constexpr dicom_tag series_instance_uid
Series Instance UID.
constexpr dicom_tag instance_number
Instance Number.
@ IS
Integer String (12 chars max)
@ 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.
std::atomic< int > c_find_count
C-FIND operations processed.
Definition mini_pacs.h:97

References kcenon::pacs::core::tags::accession_number, kcenon::pacs::storage::study_query::accession_number, kcenon::pacs::samples::mini_pacs_statistics::c_find_count, config_, kcenon::pacs::encoding::CS, kcenon::pacs::encoding::DA, kcenon::pacs::core::dicom_dataset::get_string(), kcenon::pacs::services::image, index_db_, kcenon::pacs::core::tags::instance_number, kcenon::pacs::encoding::IS, kcenon::pacs::encoding::LO, kcenon::pacs::core::tags::modality, kcenon::pacs::storage::series_query::modality, kcenon::pacs::services::patient, kcenon::pacs::core::tags::patient_birth_date, kcenon::pacs::core::tags::patient_id, kcenon::pacs::storage::patient_query::patient_id, kcenon::pacs::storage::study_query::patient_id, kcenon::pacs::core::tags::patient_name, kcenon::pacs::storage::patient_query::patient_name, kcenon::pacs::core::tags::patient_sex, kcenon::pacs::encoding::PN, kcenon::pacs::core::tags::referring_physician_name, kcenon::pacs::services::series, kcenon::pacs::core::tags::series_description, kcenon::pacs::core::tags::series_instance_uid, kcenon::pacs::core::tags::series_number, kcenon::pacs::storage::instance_query::series_uid, kcenon::pacs::storage::series_query::series_uid, kcenon::pacs::core::dicom_dataset::set_numeric(), kcenon::pacs::core::dicom_dataset::set_string(), kcenon::pacs::encoding::SH, kcenon::pacs::core::tags::sop_class_uid, kcenon::pacs::core::tags::sop_instance_uid, kcenon::pacs::storage::instance_query::sop_uid, kcenon::pacs::core::tags::station_name, stats_, kcenon::pacs::services::study, kcenon::pacs::core::tags::study_date, kcenon::pacs::storage::study_query::study_date, kcenon::pacs::core::tags::study_description, kcenon::pacs::core::tags::study_id, kcenon::pacs::core::tags::study_instance_uid, kcenon::pacs::core::tags::study_time, kcenon::pacs::storage::series_query::study_uid, kcenon::pacs::storage::study_query::study_uid, kcenon::pacs::encoding::TM, kcenon::pacs::encoding::UI, and kcenon::pacs::samples::mini_pacs_config::verbose_logging.

Referenced by initialize_services().

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

◆ handle_retrieve()

std::vector< core::dicom_file > kcenon::pacs::samples::mini_pacs::handle_retrieve ( const core::dicom_dataset & query_keys)
private

Definition at line 482 of file mini_pacs.cpp.

483 {
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
496 storage::instance_query iq;
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
504 storage::instance_query iq;
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
512 storage::series_query sq;
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()) {
517 storage::instance_query iq;
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}
static auto open(const std::filesystem::path &path) -> kcenon::pacs::Result< dicom_file >
Open and read a DICOM file from disk.
std::atomic< int > c_move_count
C-MOVE operations processed.
Definition mini_pacs.h:100

References kcenon::pacs::samples::mini_pacs_statistics::c_move_count, config_, kcenon::pacs::core::dicom_dataset::get_string(), index_db_, kcenon::pacs::core::dicom_file::open(), kcenon::pacs::core::tags::series_instance_uid, kcenon::pacs::storage::instance_query::series_uid, kcenon::pacs::core::tags::sop_instance_uid, kcenon::pacs::storage::instance_query::sop_uid, stats_, kcenon::pacs::core::tags::study_instance_uid, kcenon::pacs::storage::series_query::study_uid, and kcenon::pacs::samples::mini_pacs_config::verbose_logging.

Referenced by initialize_services().

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

◆ handle_store()

services::storage_status kcenon::pacs::samples::mini_pacs::handle_store ( const core::dicom_dataset & dataset,
const std::string & calling_ae,
const std::string & sop_class_uid,
const std::string & sop_instance_uid )
private

Definition at line 244 of file mini_pacs.cpp.

248 {
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}
std::shared_ptr< storage::file_storage > file_storage_
Definition mini_pacs.h:374
bool update_index(const core::dicom_dataset &dataset, const std::filesystem::path &file_path)
@ storage_error
Failure: Unable to process - storage error (0xC001)
@ success
Success - image stored successfully (0x0000)
std::atomic< int > c_store_count
C-STORE operations processed.
Definition mini_pacs.h:94
std::atomic< size_t > bytes_received
Total bytes received.
Definition mini_pacs.h:115

References kcenon::pacs::samples::mini_pacs_statistics::bytes_received, kcenon::pacs::samples::mini_pacs_statistics::c_store_count, config_, file_storage_, kcenon::pacs::core::dicom_dataset::get_string(), kcenon::pacs::core::tags::modality, kcenon::pacs::core::tags::patient_name, stats_, kcenon::pacs::services::storage_error, kcenon::pacs::services::success, update_index(), and kcenon::pacs::samples::mini_pacs_config::verbose_logging.

Referenced by initialize_services().

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

◆ handle_worklist_query()

std::vector< core::dicom_dataset > kcenon::pacs::samples::mini_pacs::handle_worklist_query ( const core::dicom_dataset & query_keys,
const std::string & calling_ae )
private

Definition at line 561 of file mini_pacs.cpp.

563 {
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}
core::dicom_dataset create_worklist_response(const worklist_entry &item)
std::atomic< int > mwl_count
MWL queries processed.
Definition mini_pacs.h:106

References config_, create_worklist_response(), kcenon::pacs::core::dicom_dataset::get_string(), kcenon::pacs::core::tags::modality, kcenon::pacs::samples::mini_pacs_statistics::mwl_count, kcenon::pacs::core::tags::patient_id, kcenon::pacs::core::tags::scheduled_procedure_step_start_date, stats_, kcenon::pacs::samples::mini_pacs_config::verbose_logging, worklist_items_, and worklist_mutex_.

Referenced by initialize_services().

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

◆ initialize_services()

void kcenon::pacs::samples::mini_pacs::initialize_services ( )
private

Definition at line 139 of file mini_pacs.cpp.

139 {
140 // Verification SCP (C-ECHO)
141 verification_scp_ = std::make_shared<services::verification_scp>();
142
143 // Storage SCP (C-STORE)
144 services::storage_scp_config scp_config;
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}
std::vector< core::dicom_dataset > handle_worklist_query(const core::dicom_dataset &query_keys, const std::string &calling_ae)
std::vector< core::dicom_dataset > handle_query(services::query_level level, const core::dicom_dataset &query_keys, const std::string &calling_ae)
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)
network::Result< std::monostate > handle_mpps_create(const services::mpps_instance &instance)
bool enable_mpps
Enable MPPS service.
Definition mini_pacs.h:68
bool enable_worklist
Enable Modality Worklist service.
Definition mini_pacs.h:65
std::string_view uid

References config_, kcenon::pacs::samples::mini_pacs_config::enable_mpps, kcenon::pacs::samples::mini_pacs_config::enable_worklist, handle_mpps_create(), handle_mpps_set(), handle_query(), handle_retrieve(), handle_store(), handle_worklist_query(), mpps_scp_, query_scp_, resolve_destination(), retrieve_scp_, storage_scp_, uid, verification_scp_, and worklist_scp_.

Referenced by mini_pacs().

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

◆ initialize_storage()

void kcenon::pacs::samples::mini_pacs::initialize_storage ( )
private

Definition at line 115 of file mini_pacs.cpp.

115 {
116 // Create storage directories
117 std::filesystem::create_directories(config_.storage_path);
118
119 // Configure file storage
120 storage::file_storage_config fs_config;
121 fs_config.root_path = config_.storage_path;
123 fs_config.duplicate = storage::duplicate_policy::replace;
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}
static auto open(std::string_view db_path) -> Result< std::unique_ptr< index_database > >
Open or create a database with default configuration.
@ uid_hierarchical
{StudyUID}/{SeriesUID}/{SOPUID}.dcm
@ replace
Overwrite existing instance.
std::filesystem::path storage_path
Root path for DICOM file storage.
Definition mini_pacs.h:59

References config_, kcenon::pacs::storage::file_storage_config::create_directories, kcenon::pacs::storage::file_storage_config::duplicate, kcenon::pacs::storage::file_storage_config::file_extension, file_storage_, index_db_, kcenon::pacs::storage::file_storage_config::naming, kcenon::pacs::storage::index_database::open(), kcenon::pacs::storage::replace, kcenon::pacs::storage::file_storage_config::root_path, kcenon::pacs::samples::mini_pacs_config::storage_path, and kcenon::pacs::storage::uid_hierarchical.

Referenced by mini_pacs().

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

◆ is_running()

bool kcenon::pacs::samples::mini_pacs::is_running ( ) const
nodiscardnoexcept

Check if server is running.

Returns
true if running

Definition at line 72 of file mini_pacs.cpp.

72 {
73 return running_;
74}
std::atomic< bool > running_
Definition mini_pacs.h:387

References running_.

◆ operator=() [1/2]

mini_pacs & kcenon::pacs::samples::mini_pacs::operator= ( const mini_pacs & )
delete

◆ operator=() [2/2]

mini_pacs & kcenon::pacs::samples::mini_pacs::operator= ( mini_pacs && )
delete

◆ resolve_destination()

std::optional< std::pair< std::string, uint16_t > > kcenon::pacs::samples::mini_pacs::resolve_destination ( const std::string & ae_title)
private

Definition at line 546 of file mini_pacs.cpp.

547 {
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}

References kcenon::pacs::samples::mini_pacs_config::ae_title, config_, and kcenon::pacs::samples::mini_pacs_config::port.

Referenced by initialize_services().

Here is the caller graph for this function:

◆ setup_event_handlers()

void kcenon::pacs::samples::mini_pacs::setup_event_handlers ( )
private

Definition at line 213 of file mini_pacs.cpp.

213 {
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}
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

References kcenon::pacs::samples::mini_pacs_statistics::associations_active, kcenon::pacs::samples::mini_pacs_statistics::associations_total, kcenon::pacs::network::association::called_ae(), kcenon::pacs::network::association::calling_ae(), config_, server_, stats_, and kcenon::pacs::samples::mini_pacs_config::verbose_logging.

Referenced by mini_pacs().

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

◆ start()

bool kcenon::pacs::samples::mini_pacs::start ( )
nodiscard

Start the PACS server.

Returns
true if started successfully

Definition at line 40 of file mini_pacs.cpp.

40 {
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}

References running_, and server_.

◆ statistics()

const mini_pacs_statistics & kcenon::pacs::samples::mini_pacs::statistics ( ) const
nodiscardnoexcept

Get current statistics.

Returns
Reference to statistics structure

Definition at line 80 of file mini_pacs.cpp.

80 {
81 return stats_;
82}

References stats_.

◆ stop()

void kcenon::pacs::samples::mini_pacs::stop ( )

Stop the PACS server.

Definition at line 55 of file mini_pacs.cpp.

55 {
56 if (!running_) {
57 return;
58 }
59
60 running_ = false;
61 if (server_) {
62 server_->stop();
63 }
64}

References running_, and server_.

Referenced by ~mini_pacs().

Here is the caller graph for this function:

◆ update_index()

bool kcenon::pacs::samples::mini_pacs::update_index ( const core::dicom_dataset & dataset,
const std::filesystem::path & file_path )
private

Definition at line 278 of file mini_pacs.cpp.

280 {
281
282 // Patient
283 auto patient_result = index_db_->upsert_patient(
284 ds.get_string(tags::patient_id),
285 ds.get_string(tags::patient_name),
286 ds.get_string(tags::patient_birth_date),
287 ds.get_string(tags::patient_sex));
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,
299 ds.get_string(tags::study_instance_uid),
300 ds.get_string(tags::study_id),
301 ds.get_string(tags::study_date),
302 ds.get_string(tags::study_time),
303 ds.get_string(tags::accession_number),
304 ds.get_string(tags::referring_physician_name),
305 ds.get_string(tags::study_description));
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,
317 ds.get_string(tags::series_instance_uid),
318 ds.get_string(tags::modality),
319 ds.get_numeric<int>(tags::series_number),
320 ds.get_string(tags::series_description),
321 "", // body_part_examined
322 ds.get_string(tags::station_name));
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,
338 ds.get_string(tags::sop_instance_uid),
339 ds.get_string(tags::sop_class_uid),
340 file_path.string(),
341 file_size,
342 ds.get_string(tags::transfer_syntax_uid),
343 ds.get_numeric<int>(tags::instance_number));
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}
constexpr dicom_tag transfer_syntax_uid
Transfer Syntax UID.

References kcenon::pacs::core::tags::accession_number, kcenon::pacs::core::dicom_dataset::get_numeric(), kcenon::pacs::core::dicom_dataset::get_string(), index_db_, kcenon::pacs::core::tags::instance_number, 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::series_description, kcenon::pacs::core::tags::series_instance_uid, kcenon::pacs::core::tags::series_number, kcenon::pacs::core::tags::sop_class_uid, kcenon::pacs::core::tags::sop_instance_uid, kcenon::pacs::core::tags::station_name, kcenon::pacs::core::tags::study_date, kcenon::pacs::core::tags::study_description, kcenon::pacs::core::tags::study_id, kcenon::pacs::core::tags::study_instance_uid, kcenon::pacs::core::tags::study_time, and kcenon::pacs::core::tags::transfer_syntax_uid.

Referenced by handle_store().

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

◆ wait()

void kcenon::pacs::samples::mini_pacs::wait ( )

Block until server shutdown.

Definition at line 66 of file mini_pacs.cpp.

66 {
67 if (server_) {
68 server_->wait_for_shutdown();
69 }
70}

References server_.

Member Data Documentation

◆ config_

◆ file_storage_

std::shared_ptr<storage::file_storage> kcenon::pacs::samples::mini_pacs::file_storage_
private

Definition at line 374 of file mini_pacs.h.

Referenced by handle_store(), and initialize_storage().

◆ index_db_

std::shared_ptr<storage::index_database> kcenon::pacs::samples::mini_pacs::index_db_
private

Definition at line 375 of file mini_pacs.h.

Referenced by handle_query(), handle_retrieve(), initialize_storage(), and update_index().

◆ mpps_instances_

std::vector<mpps_entry> kcenon::pacs::samples::mini_pacs::mpps_instances_
private

Definition at line 395 of file mini_pacs.h.

Referenced by handle_mpps_create(), and handle_mpps_set().

◆ mpps_mutex_

std::mutex kcenon::pacs::samples::mini_pacs::mpps_mutex_
mutableprivate

Definition at line 394 of file mini_pacs.h.

Referenced by handle_mpps_create(), and handle_mpps_set().

◆ mpps_scp_

std::shared_ptr<services::mpps_scp> kcenon::pacs::samples::mini_pacs::mpps_scp_
private

Definition at line 383 of file mini_pacs.h.

Referenced by configure_server(), and initialize_services().

◆ query_scp_

std::shared_ptr<services::query_scp> kcenon::pacs::samples::mini_pacs::query_scp_
private

Definition at line 380 of file mini_pacs.h.

Referenced by configure_server(), and initialize_services().

◆ retrieve_scp_

std::shared_ptr<services::retrieve_scp> kcenon::pacs::samples::mini_pacs::retrieve_scp_
private

Definition at line 381 of file mini_pacs.h.

Referenced by configure_server(), and initialize_services().

◆ running_

std::atomic<bool> kcenon::pacs::samples::mini_pacs::running_ {false}
private

Definition at line 387 of file mini_pacs.h.

387{false};

Referenced by is_running(), start(), and stop().

◆ server_

std::unique_ptr<network::dicom_server> kcenon::pacs::samples::mini_pacs::server_
private

Definition at line 386 of file mini_pacs.h.

Referenced by configure_server(), setup_event_handlers(), start(), stop(), and wait().

◆ stats_

◆ storage_scp_

std::shared_ptr<services::storage_scp> kcenon::pacs::samples::mini_pacs::storage_scp_
private

Definition at line 379 of file mini_pacs.h.

Referenced by configure_server(), and initialize_services().

◆ verification_scp_

std::shared_ptr<services::verification_scp> kcenon::pacs::samples::mini_pacs::verification_scp_
private

Definition at line 378 of file mini_pacs.h.

Referenced by configure_server(), and initialize_services().

◆ worklist_items_

std::vector<worklist_entry> kcenon::pacs::samples::mini_pacs::worklist_items_
private

◆ worklist_mutex_

std::mutex kcenon::pacs::samples::mini_pacs::worklist_mutex_
mutableprivate

◆ worklist_scp_

std::shared_ptr<services::worklist_scp> kcenon::pacs::samples::mini_pacs::worklist_scp_
private

Definition at line 382 of file mini_pacs.h.

Referenced by configure_server(), and initialize_services().


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