PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1
27#include "production_pacs.h"
28#include "config_loader.h"
29#include "console_utils.h"
30#include "signal_handler.h"
31
32#include <iostream>
33#include <string>
34
35using namespace kcenon::pacs::samples;
36
37namespace {
38
42void print_usage(const char* program) {
43 std::cout << "Usage: " << program << " [config_file]\n\n";
44 std::cout << "Arguments:\n";
45 std::cout << " config_file Path to YAML configuration file\n";
46 std::cout << " (default: config/pacs_config.yaml)\n\n";
47 std::cout << "Examples:\n";
48 std::cout << " " << program << "\n";
49 std::cout << " " << program << " /etc/pacs/production.yaml\n";
50 std::cout << " " << program << " config/development.yaml\n";
51}
52
56std::string profile_to_string(kcenon::pacs::security::anonymization_profile profile) {
57 switch (profile) {
59 return "Basic";
61 return "HIPAA Safe Harbor";
63 return "Retain Longitudinal";
65 return "Retain Patient Characteristics";
67 return "GDPR Compliant";
69 return "Clean Pixel";
71 return "Clean Descriptions";
72 default:
73 return "Unknown";
74 }
75}
76
77} // namespace
78
79int main(int argc, char* argv[]) {
80 print_header("Production PACS - Level 5 Sample");
81
82 // Check for help flag
83 if (argc > 1 && (std::string(argv[1]) == "-h" || std::string(argv[1]) == "--help")) {
84 print_usage(argv[0]);
85 return 0;
86 }
87
88 // ==========================================================================
89 // Part 1: Load Configuration
90 // ==========================================================================
91 // Production PACS uses YAML configuration files for flexible deployment.
92 // This allows separation of configuration from code and supports:
93 // - Different environments (dev, staging, production)
94 // - Container deployments with mounted config files
95 // - Easy configuration updates without recompilation
96
97 print_section("Part 1: Load Configuration");
98
99 std::filesystem::path config_path = argc > 1
100 ? argv[1]
101 : "config/pacs_config.yaml";
102
103 std::cout << "Configuration file: " << config_path << "\n\n";
104
105 production_config config;
106
107 if (std::filesystem::exists(config_path)) {
108 std::cout << "Loading configuration from file...\n";
109
110 auto result = config_loader::load(config_path);
111 if (!result.is_ok()) {
112 print_error("Failed to load configuration: " + result.error().message);
113 return 1;
114 }
115
116 config = result.value();
117 print_success("Configuration loaded successfully!");
118 } else {
119 std::cout << "Configuration file not found, using defaults...\n";
121 print_info("Using default configuration");
122 }
123
124 // Display configuration summary
125 print_table("Server Configuration", {
126 {"AE Title", config.server.ae_title},
127 {"DICOM Port", std::to_string(config.server.port)},
128 {"Max Associations", std::to_string(config.server.max_associations)},
129 {"Idle Timeout", std::to_string(config.server.idle_timeout.count()) + "s"},
130 {"TLS Enabled", config.server.tls.enabled ? "Yes" : "No"}
131 });
132
133 print_table("Storage Configuration", {
134 {"Root Path", config.storage.root_path.string()},
135 {"Naming Scheme", config.storage.naming_scheme},
136 {"Duplicate Policy", config.storage.duplicate_policy},
137 {"Database Path", config.storage.database.path.string()}
138 });
139
140 print_table("Security Configuration", {
141 {"Access Control", config.security.access_control.enabled ? "Enabled" : "Disabled"},
142 {"Default Role", config.security.access_control.default_role},
143 {"Auto-Anonymize", config.security.anonymization.auto_anonymize ? "Yes" : "No"},
144 {"Anon Profile", profile_to_string(config.security.anonymization.profile)}
145 });
146
147 print_table("REST API Configuration", {
148 {"Enabled", config.rest_api.enabled ? "Yes" : "No"},
149 {"Port", std::to_string(config.rest_api.port)},
150 {"CORS", config.rest_api.cors_enabled ? "Enabled" : "Disabled"}
151 });
152
153 print_table("Monitoring Configuration", {
154 {"Health Check Interval", std::to_string(config.monitoring.health_check_interval.count()) + "s"},
155 {"Metrics Enabled", config.monitoring.metrics_enabled ? "Yes" : "No"}
156 });
157
158 print_success("Part 1 complete - Configuration ready!");
159
160 // ==========================================================================
161 // Part 2: Initialize Production PACS
162 // ==========================================================================
163 // The production_pacs class integrates all enterprise features:
164 // - Mini PACS for core DICOM services
165 // - Security manager for access control
166 // - Anonymizer for de-identification
167 // - REST server for web access
168 // - Health checker for monitoring
169
170 print_section("Part 2: Initialize Production PACS");
171
172 std::cout << "Creating Production PACS instance...\n";
173
174 production_pacs pacs(config);
175
176 // Setup event handlers for monitoring
177 pacs.on_image_received([](const events::image_received_event& evt) {
178 std::cout << colors::green << "[EVENT] "
179 << colors::reset << "Image received: "
180 << evt.sop_instance_uid
181 << " from " << evt.calling_ae << "\n";
182 });
183
184 pacs.on_query_executed([](const events::query_executed_event& evt) {
185 std::cout << colors::blue << "[EVENT] "
186 << colors::reset << "Query at " << evt.query_level
187 << " level returned " << evt.result_count
188 << " results (" << evt.duration.count() << "ms)\n";
189 });
190
191 pacs.on_association_event([](const events::association_event& evt) {
192 const char* type_str = "";
193 switch (evt.event_type) {
194 case events::association_event::type::opened:
195 type_str = "opened";
196 break;
197 case events::association_event::type::closed:
198 type_str = "closed";
199 break;
200 case events::association_event::type::rejected:
201 type_str = "rejected";
202 break;
203 }
204 std::cout << colors::cyan << "[EVENT] "
205 << colors::reset << "Association " << type_str
206 << ": " << evt.calling_ae << " -> " << evt.called_ae << "\n";
207 });
208
209 pacs.on_access_denied([](const events::access_denied_event& evt) {
210 std::cout << colors::red << "[SECURITY] "
211 << colors::reset << "Access denied: "
212 << evt.calling_ae << " attempted " << evt.operation
213 << " - " << evt.reason << "\n";
214 });
215
216 print_success("Part 2 complete - Production PACS initialized!");
217
218 // ==========================================================================
219 // Part 3: Start Services
220 // ==========================================================================
221
222 print_section("Part 3: Start Services");
223
224 if (!pacs.start()) {
225 print_error("Failed to start Production PACS");
226 return 1;
227 }
228
229 print_success("Part 3 complete - All services started!");
230
231 // ==========================================================================
232 // Part 4: Display Status and Wait
233 // ==========================================================================
234
235 print_section("Part 4: Running Server");
236
237 pacs.print_status();
238
239 std::cout << "\n";
240 std::cout << "╔══════════════════════════════════════════════════════════════════╗\n";
241 std::cout << "║ Test Commands ║\n";
242 std::cout << "╠══════════════════════════════════════════════════════════════════╣\n";
243 std::cout << "║ DICOM Connectivity: ║\n";
244 std::cout << "║ echoscu -aec " << config.server.ae_title
245 << " localhost " << config.server.port;
246 for (size_t i = config.server.ae_title.length() + std::to_string(config.server.port).length();
247 i < 27; ++i) {
248 std::cout << " ";
249 }
250 std::cout << "║\n";
251 std::cout << "║ ║\n";
252 std::cout << "║ Store Image: ║\n";
253 std::cout << "║ storescu -aec " << config.server.ae_title
254 << " localhost " << config.server.port << " image.dcm";
255 for (size_t i = config.server.ae_title.length() + std::to_string(config.server.port).length();
256 i < 17; ++i) {
257 std::cout << " ";
258 }
259 std::cout << "║\n";
260
261 if (config.rest_api.enabled) {
262 std::cout << "║ ║\n";
263 std::cout << "║ REST API: ║\n";
264 std::cout << "║ Health: curl http://localhost:"
265 << config.rest_api.port << "/api/v1/system/status";
266 for (size_t i = std::to_string(config.rest_api.port).length(); i < 9; ++i) {
267 std::cout << " ";
268 }
269 std::cout << "║\n";
270 std::cout << "║ Metrics: curl http://localhost:"
271 << config.rest_api.port << "/api/v1/system/metrics";
272 for (size_t i = std::to_string(config.rest_api.port).length(); i < 8; ++i) {
273 std::cout << " ";
274 }
275 std::cout << "║\n";
276 }
277
278 if (config.server.tls.enabled) {
279 std::cout << "║ ║\n";
280 std::cout << "║ TLS Connection: ║\n";
281 std::cout << "║ echoscu --tls --add-cert-file ca.pem -aec " << config.server.ae_title;
282 for (size_t i = config.server.ae_title.length(); i < 12; ++i) {
283 std::cout << " ";
284 }
285 std::cout << "║\n";
286 std::cout << "║ localhost " << config.server.port;
287 for (size_t i = std::to_string(config.server.port).length(); i < 47; ++i) {
288 std::cout << " ";
289 }
290 std::cout << "║\n";
291 }
292
293 std::cout << "║ ║\n";
294 std::cout << "║ Press Ctrl+C to stop ║\n";
295 std::cout << "╚══════════════════════════════════════════════════════════════════╝\n\n";
296
297 // Install signal handler for graceful shutdown
298 scoped_signal_handler sig_handler([&pacs]() {
299 std::cout << "\n" << colors::yellow
300 << "Graceful shutdown initiated..."
301 << colors::reset << "\n";
302 pacs.stop();
303 });
304
305 // Wait for shutdown signal
306 sig_handler.wait();
307
308 // ==========================================================================
309 // Part 5: Export Statistics and Cleanup
310 // ==========================================================================
311
312 print_section("Final Statistics");
313
314 const auto& stats = pacs.statistics();
315 auto uptime = stats.uptime();
316 auto hours = std::chrono::duration_cast<std::chrono::hours>(uptime);
317 auto minutes = std::chrono::duration_cast<std::chrono::minutes>(uptime) % 60;
318 auto seconds = uptime.count() % 60;
319
320 std::ostringstream uptime_str;
321 uptime_str << hours.count() << "h " << minutes.count() << "m " << seconds << "s";
322
323 print_table("Session Statistics", {
324 {"Uptime", uptime_str.str()},
325 {"Images Stored", std::to_string(stats.images_stored.load())},
326 {"Images Anonymized", std::to_string(stats.images_anonymized.load())},
327 {"Queries Executed", std::to_string(stats.queries_executed.load())},
328 {"Access Denied", std::to_string(stats.access_denied_count.load())},
329 {"REST Requests", std::to_string(stats.rest_requests.load())}
330 });
331
332 // Export statistics to file
333 pacs.export_statistics("statistics.json");
334 print_info("Statistics exported to statistics.json");
335
336 // Summary
337 print_box({
338 "Congratulations! You have learned:",
339 "",
340 "1. Configuration Management - YAML-based configuration",
341 "2. TLS Security - Secure DICOM connections",
342 "3. Access Control - Role-based permissions (RBAC)",
343 "4. Anonymization - De-identification profiles",
344 "5. REST API - Web-based PACS access",
345 "6. Health Monitoring - Service health checks",
346 "7. Event Architecture - Decoupled event handling",
347 "",
348 "This Production PACS demonstrates enterprise-ready patterns",
349 "for building secure, scalable DICOM infrastructure.",
350 "",
351 "Further reading:",
352 " - DICOM PS3.15: Security and System Management Profiles",
353 " - HIPAA Technical Safeguards",
354 " - GDPR Requirements for Medical Imaging"
355 });
356
357 print_success("Production PACS terminated successfully.");
358
359 return 0;
360}
int main()
Definition main.cpp:34
static auto create_default() -> production_config
Create a default configuration.
static auto load(const std::filesystem::path &path) -> kcenon::common::Result< production_config >
Load configuration from a YAML file.
void wait()
Block until shutdown signal is received.
YAML configuration loader for Production PACS.
Console output utilities for developer samples.
constexpr const char * reset
Reset all formatting.
constexpr const char * red
constexpr const char * blue
constexpr const char * yellow
constexpr const char * green
constexpr const char * cyan
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_info(std::string_view message)
Print an informational message.
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.
anonymization_profile
DICOM de-identification profiles based on PS3.15 Annex E.
@ clean_pixel
Clean Pixel Data - Remove burned-in annotations.
@ hipaa_safe_harbor
HIPAA Safe Harbor - 18 identifier removal.
@ gdpr_compliant
GDPR Compliant - European data protection.
@ retain_longitudinal
Retain Longitudinal - Preserve temporal relationships.
@ retain_patient_characteristics
Retain Patient Characteristics.
@ clean_descriptions
Clean Descriptions - Sanitize text fields.
@ basic
Basic Profile - Remove direct identifiers.
Production-grade PACS with enterprise features.
Cross-platform signal handling for graceful shutdown.
std::string default_role
Default role for unknown AE titles.
bool enabled
Enable access control enforcement.
security::anonymization_profile profile
Anonymization profile to use.
bool auto_anonymize
Automatically anonymize incoming images.
std::filesystem::path path
Path to SQLite database file.
Event fired for association lifecycle events.
Event fired when an image is received and stored.
Event fired when a query is executed.
std::chrono::seconds health_check_interval
Interval between health checks (seconds)
bool metrics_enabled
Enable Prometheus-style metrics endpoint.
Complete production PACS configuration.
monitoring_config monitoring
Monitoring settings.
storage_config storage
Storage settings.
rest_api_config rest_api
REST API settings.
security_config security
Security settings.
server_config server
DICOM server settings.
std::uint16_t port
HTTP port for REST API.
bool cors_enabled
Enable CORS for web clients.
bool enabled
Enable REST API server.
access_control_config access_control
Access control settings.
anonymization_config anonymization
Anonymization settings.
std::uint16_t port
TCP port for DICOM connections.
tls_config tls
TLS configuration.
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)
std::string duplicate_policy
Duplicate handling: "reject", "replace", "rename".
std::string naming_scheme
Naming scheme: "uid_flat", "uid_hierarchical", "date_based".
database_config database
Database configuration.
std::filesystem::path root_path
Root path for DICOM file storage.
bool enabled
Enable TLS for DICOM connections.