79 {
81
82
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
90
91
92
93
94
95
96
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
106
107 if (std::filesystem::exists(config_path)) {
108 std::cout << "Loading configuration from file...\n";
109
111 if (!result.is_ok()) {
112 print_error(
"Failed to load configuration: " + result.error().message);
113 return 1;
114 }
115
116 config = result.value();
118 } else {
119 std::cout << "Configuration file not found, using defaults...\n";
122 }
123
124
127 {
"DICOM Port", std::to_string(config.
server.
port)},
131 });
132
138 });
139
145 });
146
151 });
152
156 });
157
159
160
161
162
163
164
165
166
167
168
169
171
172 std::cout << "Creating Production PACS instance...\n";
173
175
176
177 pacs.on_image_received([](
const events::image_received_event& evt) {
180 << evt.sop_instance_uid
181 << " from " << evt.calling_ae << "\n";
182 });
183
184 pacs.on_query_executed([](
const events::query_executed_event& evt) {
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 }
206 << ": " << evt.calling_ae << " -> " << evt.called_ae << "\n";
207 });
208
209 pacs.on_access_denied([](
const events::access_denied_event& evt) {
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
220
221
223
226 return 1;
227 }
228
230
231
232
233
234
236
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";
247 i < 27; ++i) {
248 std::cout << " ";
249 }
250 std::cout << "║\n";
251 std::cout << "║ ║\n";
252 std::cout << "║ Store Image: ║\n";
254 <<
" localhost " << config.
server.
port <<
" image.dcm";
256 i < 17; ++i) {
257 std::cout << " ";
258 }
259 std::cout << "║\n";
260
262 std::cout << "║ ║\n";
263 std::cout << "║ REST API: ║\n";
264 std::cout << "║ Health: curl http://localhost:"
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:"
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
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;
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
300 << "Graceful shutdown initiated..."
303 });
304
305
307
308
309
310
311
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
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
333 pacs.export_statistics(
"statistics.json");
334 print_info(
"Statistics exported to statistics.json");
335
336
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
358
359 return 0;
360}
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.
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.
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.
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.