37std::string generate_uid() {
39 auto now = std::chrono::system_clock::now();
40 auto epoch = now.time_since_epoch();
41 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(epoch).count();
42 return "1.2.410.200001.4." + std::to_string(ms) +
"." + std::to_string(++counter);
48std::string current_date() {
49 auto now = std::chrono::system_clock::now();
50 auto time = std::chrono::system_clock::to_time_t(now);
52 localtime_r(&time, &tm_time);
55 std::strftime(
buffer,
sizeof(
buffer),
"%Y%m%d", &tm_time);
78 pacs.add_worklist_item(wl1);
95 pacs.add_worklist_item(wl2);
112 pacs.add_worklist_item(wl3);
117int main(
int argc,
char* argv[]) {
133 std::cout <<
"Mini PACS provides a complete DICOM server with:\n";
134 std::cout <<
" - Verification SCP: C-ECHO for connectivity\n";
135 std::cout <<
" - Storage SCP: C-STORE for image reception\n";
136 std::cout <<
" - Query SCP: C-FIND at all levels\n";
137 std::cout <<
" - Retrieve SCP: C-MOVE/C-GET for retrieval\n";
138 std::cout <<
" - Worklist SCP: MWL for scheduled procedures\n";
139 std::cout <<
" - MPPS SCP: N-CREATE/N-SET for tracking\n\n";
144 config.
port = argc > 1 ?
static_cast<uint16_t
>(std::stoi(argv[1])) : 11112;
153 {
"Port", std::to_string(config.
port)},
157 {
"MPPS Enabled", config.
enable_mpps ?
"Yes" :
"No"}
176 add_sample_worklist_items(
pacs);
178 std::cout <<
"Added " <<
pacs.get_worklist_items().size()
179 <<
" sample worklist items for MWL testing.\n\n";
195 std::cout <<
"╔══════════════════════════════════════════════════════════════╗\n";
196 std::cout <<
"║ Mini PACS Server Started ║\n";
197 std::cout <<
"╠══════════════════════════════════════════════════════════════╣\n";
198 std::cout <<
"║ AE Title: " << config.
ae_title;
199 for (
size_t i = config.
ae_title.length(); i < 48; ++i) std::cout <<
" ";
201 std::cout <<
"║ Port: " << config.
port;
202 std::string port_str = std::to_string(config.
port);
203 for (
size_t i = port_str.length(); i < 48; ++i) std::cout <<
" ";
205 std::cout <<
"╠══════════════════════════════════════════════════════════════╣\n";
206 std::cout <<
"║ Services: ║\n";
207 std::cout <<
"║ [x] Verification (C-ECHO) ║\n";
208 std::cout <<
"║ [x] Storage (C-STORE) ║\n";
209 std::cout <<
"║ [x] Query (C-FIND Patient/Study/Series/Image) ║\n";
210 std::cout <<
"║ [x] Retrieve (C-MOVE/C-GET) ║\n";
211 std::cout <<
"║ [x] Modality Worklist (MWL C-FIND) ║\n";
212 std::cout <<
"║ [x] MPPS (N-CREATE/N-SET) ║\n";
213 std::cout <<
"╠══════════════════════════════════════════════════════════════╣\n";
214 std::cout <<
"║ Test Commands: ║\n";
215 std::cout <<
"║ ║\n";
216 std::cout <<
"║ Connectivity: ║\n";
217 std::cout <<
"║ echoscu -aec MINI_PACS localhost " << config.
port;
218 for (
size_t i = port_str.length(); i < 22; ++i) std::cout <<
" ";
220 std::cout <<
"║ ║\n";
221 std::cout <<
"║ Store Images: ║\n";
222 std::cout <<
"║ storescu -aec MINI_PACS localhost " << config.
port <<
" *.dcm";
223 for (
size_t i = port_str.length(); i < 16; ++i) std::cout <<
" ";
225 std::cout <<
"║ ║\n";
226 std::cout <<
"║ Query Patient Level: ║\n";
227 std::cout <<
"║ findscu -aec MINI_PACS -P \\ ║\n";
228 std::cout <<
"║ -k QueryRetrieveLevel=PATIENT \\ ║\n";
229 std::cout <<
"║ -k PatientName=\"*\" localhost " << config.
port;
230 for (
size_t i = port_str.length(); i < 27; ++i) std::cout <<
" ";
232 std::cout <<
"║ ║\n";
233 std::cout <<
"║ Query Study Level: ║\n";
234 std::cout <<
"║ findscu -aec MINI_PACS -S \\ ║\n";
235 std::cout <<
"║ -k QueryRetrieveLevel=STUDY \\ ║\n";
236 std::cout <<
"║ -k StudyDate=\"\" localhost " << config.
port;
237 for (
size_t i = port_str.length(); i < 29; ++i) std::cout <<
" ";
239 std::cout <<
"║ ║\n";
240 std::cout <<
"║ Query Worklist: ║\n";
241 std::cout <<
"║ findscu -aec MINI_PACS -W \\ ║\n";
242 std::cout <<
"║ -k ScheduledProcedureStepStartDate=\"\" \\ ║\n";
243 std::cout <<
"║ -k Modality=\"\" localhost " << config.
port;
244 for (
size_t i = port_str.length(); i < 30; ++i) std::cout <<
" ";
246 std::cout <<
"║ ║\n";
247 std::cout <<
"║ Retrieve Study: ║\n";
248 std::cout <<
"║ movescu -aec MINI_PACS -aem DEST \\ ║\n";
249 std::cout <<
"║ -k QueryRetrieveLevel=STUDY \\ ║\n";
250 std::cout <<
"║ -k StudyInstanceUID=\"...\" localhost " << config.
port;
251 for (
size_t i = port_str.length(); i < 20; ++i) std::cout <<
" ";
253 std::cout <<
"╠══════════════════════════════════════════════════════════════╣\n";
254 std::cout <<
"║ Worklist Items: 3 scheduled procedures ║\n";
255 std::cout <<
"║ ║\n";
256 std::cout <<
"║ Press Ctrl+C to stop ║\n";
257 std::cout <<
"╚══════════════════════════════════════════════════════════════╝\n\n";
262 <<
"Shutdown signal received..."
276 const auto& stats =
pacs.statistics();
279 {
"Total Associations", std::to_string(stats.associations_total.load())},
280 {
"Active Associations", std::to_string(stats.associations_active.load())}
284 {
"C-ECHO Count", std::to_string(stats.c_echo_count.load())},
285 {
"C-STORE Count", std::to_string(stats.c_store_count.load())},
286 {
"C-FIND Count", std::to_string(stats.c_find_count.load())},
287 {
"C-MOVE Count", std::to_string(stats.c_move_count.load())},
288 {
"C-GET Count", std::to_string(stats.c_get_count.load())},
289 {
"MWL Queries", std::to_string(stats.mwl_count.load())},
290 {
"MPPS N-CREATE", std::to_string(stats.mpps_create_count.load())},
291 {
"MPPS N-SET", std::to_string(stats.mpps_set_count.load())}
295 {
"Bytes Received", std::to_string(stats.bytes_received.load()) +
" bytes"}
300 "Congratulations! You have learned:",
302 "1. Service Integration - Combining multiple SCPs",
303 "2. Query/Retrieve - C-FIND and C-MOVE operations",
304 "3. Modality Worklist - Scheduled procedure queries",
305 "4. MPPS - Procedure progress tracking",
306 "5. Class-Based Design - Encapsulating PACS functionality",
308 "Query the database:",
309 " sqlite3 ./pacs_data/index.db \"SELECT * FROM patients;\"",
310 " sqlite3 ./pacs_data/index.db \"SELECT * FROM studies;\"",
312 "Next step: Level 5 - Production PACS (TLS, RBAC, REST API)"
void wait()
Block until shutdown signal is received.
Console output utilities for developer samples.
Mini PACS class integrating all DICOM services.
@ counter
Monotonic increasing value.
constexpr const char * reset
Reset all formatting.
constexpr const char * yellow
void print_error(std::string_view message)
Print an error message.
void print_table(std::string_view title, const std::vector< std::pair< std::string, std::string > > &rows)
Print a key-value table.
void print_header(std::string_view title)
Print a header/title with decorative border.
void print_success(std::string_view message)
Print a success message.
void print_section(std::string_view title)
Print a section header.
void print_box(const std::vector< std::string > &lines)
Print text in a decorative box.
Cross-platform signal handling for graceful shutdown.
Configuration options for Mini PACS.
uint16_t port
TCP port to listen on.
std::string ae_title
Application Entity title (max 16 characters)
bool verbose_logging
Enable verbose logging.
bool enable_mpps
Enable MPPS service.
std::filesystem::path storage_path
Root path for DICOM file storage.
size_t max_associations
Maximum concurrent associations.
bool enable_worklist
Enable Modality Worklist service.
Worklist item for MWL queries.
std::string referring_physician
std::string patient_birth_date
std::string scheduled_station_ae
std::string accession_number
std::string scheduled_time
std::string procedure_description
std::string scheduled_date