PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
production_pacs.cpp
Go to the documentation of this file.
1
6#include "production_pacs.h"
7#include "console_utils.h"
8
10
11#include <fstream>
12#include <iomanip>
13#include <iostream>
14#include <sstream>
15
16namespace kcenon::pacs::samples {
17
18// =============================================================================
19// Construction
20// =============================================================================
21
23 : config_(config) {
24 stats_.start_time = std::chrono::system_clock::now();
25}
26
28 if (running_) {
29 stop();
30 }
31}
32
33// =============================================================================
34// Lifecycle
35// =============================================================================
36
38 if (running_) {
39 return true;
40 }
41
42 try {
43 // Initialize components in order
50
51 // Start Mini PACS
52 if (!pacs_->start()) {
53 std::cerr << "Failed to start Mini PACS\n";
54 return false;
55 }
56
57 // Start REST API if enabled
59 rest_server_->start_async();
60 }
61
62 running_ = true;
63 return true;
64
65 } catch (const std::exception& e) {
66 std::cerr << "Failed to start Production PACS: " << e.what() << "\n";
67 return false;
68 }
69}
70
72 if (!running_) {
73 return;
74 }
75
76 running_ = false;
77
78 // Stop REST API
79 if (rest_server_) {
80 rest_server_->stop();
81 }
82
83 // Stop Mini PACS
84 if (pacs_) {
85 pacs_->stop();
86 }
87
88 // Notify waiting threads
89 shutdown_cv_.notify_all();
90}
91
93 if (!running_) {
94 return;
95 }
96
97 std::unique_lock<std::mutex> lock(shutdown_mutex_);
98 shutdown_cv_.wait(lock, [this] { return !running_.load(); });
99}
100
101bool production_pacs::is_running() const noexcept {
102 return running_;
103}
104
105// =============================================================================
106// Event Handlers
107// =============================================================================
108
110 std::function<void(const events::image_received_event&)> handler) {
111 std::lock_guard<std::mutex> lock(handlers_mutex_);
112 image_handlers_.push_back(std::move(handler));
113}
114
116 std::function<void(const events::query_executed_event&)> handler) {
117 std::lock_guard<std::mutex> lock(handlers_mutex_);
118 query_handlers_.push_back(std::move(handler));
119}
120
122 std::function<void(const events::association_event&)> handler) {
123 std::lock_guard<std::mutex> lock(handlers_mutex_);
124 association_handlers_.push_back(std::move(handler));
125}
126
128 std::function<void(const events::access_denied_event&)> handler) {
129 std::lock_guard<std::mutex> lock(handlers_mutex_);
130 access_denied_handlers_.push_back(std::move(handler));
131}
132
133// =============================================================================
134// Status and Statistics
135// =============================================================================
136
138 auto uptime = stats_.uptime();
139 auto hours = std::chrono::duration_cast<std::chrono::hours>(uptime);
140 auto minutes = std::chrono::duration_cast<std::chrono::minutes>(uptime) % 60;
141 auto seconds = uptime.count() % 60;
142
143 std::ostringstream uptime_str;
144 uptime_str << hours.count() << "h " << minutes.count() << "m " << seconds << "s";
145
146 std::cout << "\n";
147 std::cout << "╔══════════════════════════════════════════════════════════════════╗\n";
148 std::cout << "║ Production PACS Server Status ║\n";
149 std::cout << "╠══════════════════════════════════════════════════════════════════╣\n";
150 std::cout << "║ Server: ║\n";
151 std::cout << "║ AE Title: " << std::setw(48) << std::left
152 << config_.server.ae_title << "║\n";
153 std::cout << "║ DICOM Port: " << std::setw(48) << std::left
154 << config_.server.port << "║\n";
155 std::cout << "║ TLS: " << std::setw(48) << std::left
156 << (config_.server.tls.enabled ? "Enabled" : "Disabled") << "║\n";
157 std::cout << "╠══════════════════════════════════════════════════════════════════╣\n";
158 std::cout << "║ REST API: ║\n";
159 std::cout << "║ Status: " << std::setw(48) << std::left
160 << (config_.rest_api.enabled ? "Enabled" : "Disabled") << "║\n";
162 std::cout << "║ Port: " << std::setw(48) << std::left
163 << config_.rest_api.port << "║\n";
164 }
165 std::cout << "╠══════════════════════════════════════════════════════════════════╣\n";
166 std::cout << "║ Security: ║\n";
167 std::cout << "║ RBAC: " << std::setw(48) << std::left
168 << (config_.security.access_control.enabled ? "Enabled" : "Disabled") << "║\n";
169 std::cout << "║ Auto-Anon: " << std::setw(48) << std::left
170 << (config_.security.anonymization.auto_anonymize ? "Enabled" : "Disabled")
171 << "║\n";
172 std::cout << "╠══════════════════════════════════════════════════════════════════╣\n";
173 std::cout << "║ Statistics: ║\n";
174 std::cout << "║ Uptime: " << std::setw(48) << std::left
175 << uptime_str.str() << "║\n";
176 std::cout << "║ Images: " << std::setw(48) << std::left
177 << stats_.images_stored.load() << "║\n";
178 std::cout << "║ Queries: " << std::setw(48) << std::left
179 << stats_.queries_executed.load() << "║\n";
180 std::cout << "║ Associations:" << std::setw(48) << std::left
181 << stats_.active_associations.load() << "║\n";
182 std::cout << "╚══════════════════════════════════════════════════════════════════╝\n\n";
183}
184
186 return stats_;
187}
188
190 if (health_checker_) {
191 return health_checker_->get_cached_status();
192 }
194}
195
196void production_pacs::export_statistics(const std::filesystem::path& path) const {
197 std::ofstream file(path);
198 if (!file.is_open()) {
199 std::cerr << "Failed to open statistics file: " << path << "\n";
200 return;
201 }
202
203 auto uptime = stats_.uptime();
204
205 file << "{\n";
206 file << " \"uptime_seconds\": " << uptime.count() << ",\n";
207 file << " \"images_stored\": " << stats_.images_stored.load() << ",\n";
208 file << " \"images_anonymized\": " << stats_.images_anonymized.load() << ",\n";
209 file << " \"queries_executed\": " << stats_.queries_executed.load() << ",\n";
210 file << " \"access_denied_count\": " << stats_.access_denied_count.load() << ",\n";
211 file << " \"rest_requests\": " << stats_.rest_requests.load() << ",\n";
212 file << " \"active_associations\": " << stats_.active_associations.load() << ",\n";
213 file << " \"server\": {\n";
214 file << " \"ae_title\": \"" << config_.server.ae_title << "\",\n";
215 file << " \"port\": " << config_.server.port << ",\n";
216 file << " \"tls_enabled\": " << (config_.server.tls.enabled ? "true" : "false") << "\n";
217 file << " },\n";
218 file << " \"rest_api\": {\n";
219 file << " \"enabled\": " << (config_.rest_api.enabled ? "true" : "false") << ",\n";
220 file << " \"port\": " << config_.rest_api.port << "\n";
221 file << " }\n";
222 file << "}\n";
223}
224
226 return config_;
227}
228
229// =============================================================================
230// Initialization
231// =============================================================================
232
234 mini_pacs_config pacs_config;
235 pacs_config.ae_title = config_.server.ae_title;
236 pacs_config.port = config_.server.port;
239 pacs_config.enable_worklist = true;
240 pacs_config.enable_mpps = true;
241 pacs_config.verbose_logging = (config_.logging.level == "debug");
242
243 pacs_ = std::make_unique<mini_pacs>(pacs_config);
244}
245
248 return;
249 }
250
251 access_control_ = std::make_shared<security::access_control_manager>();
252
253 // Register allowed AE titles
254 for (const auto& ae_title : config_.security.allowed_ae_titles) {
255 // For simplicity, map AE titles to users with viewer role
256 access_control_->register_ae_title(ae_title, ae_title);
257 }
258}
259
262 return;
263 }
264
265 anonymizer_ = std::make_unique<security::anonymizer>(
267}
268
270 if (!config_.rest_api.enabled) {
271 return;
272 }
273
274 web::rest_server_config rest_config;
275 rest_config.port = config_.rest_api.port;
277 rest_config.concurrency = 4;
278
279 rest_server_ = std::make_unique<web::rest_server>(rest_config);
280
281 // Integrate with monitoring
282 if (health_checker_) {
283 rest_server_->set_health_checker(health_checker_);
284 }
285 if (metrics_) {
286 rest_server_->set_metrics_provider(metrics_);
287 }
288 if (access_control_) {
289 rest_server_->set_access_control_manager(access_control_);
290 }
291}
292
294 // Setup health checker
297 health_config.cache_duration = std::chrono::seconds{5};
298 health_config.storage_warning_threshold = 80.0;
299 health_config.storage_critical_threshold = 95.0;
300
301 health_checker_ = std::make_shared<monitoring::health_checker>(health_config);
302 health_checker_->set_version(0, 1, 0, "production_pacs");
303
304 // Setup metrics
306 metrics_ = std::make_shared<monitoring::pacs_metrics>();
307 }
308}
309
311 // Note: In a real implementation, we would hook into the Mini PACS
312 // event system. For this sample, we demonstrate the event architecture
313 // without full integration to keep the sample focused.
314}
315
316// =============================================================================
317// Event Dispatch
318// =============================================================================
319
321 std::lock_guard<std::mutex> lock(handlers_mutex_);
322 for (const auto& handler : image_handlers_) {
323 try {
324 handler(event);
325 } catch (const std::exception& e) {
326 std::cerr << "Error in image received handler: " << e.what() << "\n";
327 }
328 }
329}
330
332 std::lock_guard<std::mutex> lock(handlers_mutex_);
333 for (const auto& handler : query_handlers_) {
334 try {
335 handler(event);
336 } catch (const std::exception& e) {
337 std::cerr << "Error in query executed handler: " << e.what() << "\n";
338 }
339 }
340}
341
343 std::lock_guard<std::mutex> lock(handlers_mutex_);
344 for (const auto& handler : association_handlers_) {
345 try {
346 handler(event);
347 } catch (const std::exception& e) {
348 std::cerr << "Error in association event handler: " << e.what() << "\n";
349 }
350 }
351}
352
354 std::lock_guard<std::mutex> lock(handlers_mutex_);
356 for (const auto& handler : access_denied_handlers_) {
357 try {
358 handler(event);
359 } catch (const std::exception& e) {
360 std::cerr << "Error in access denied handler: " << e.what() << "\n";
361 }
362 }
363}
364
365} // namespace kcenon::pacs::samples
bool is_running() const noexcept
Check if server is running.
void dispatch_image_received(const events::image_received_event &event)
void dispatch_query_executed(const events::query_executed_event &event)
void on_access_denied(std::function< void(const events::access_denied_event &)> handler)
Register handler for access denied events.
production_pacs(const production_config &config)
Construct Production PACS with configuration.
std::unique_ptr< web::rest_server > rest_server_
std::shared_ptr< monitoring::pacs_metrics > metrics_
void print_status() const
Print server status to stdout.
~production_pacs()
Destructor - ensures clean shutdown.
void dispatch_access_denied(const events::access_denied_event &event)
std::shared_ptr< monitoring::health_checker > health_checker_
void on_query_executed(std::function< void(const events::query_executed_event &)> handler)
Register handler for query executed events.
void dispatch_association_event(const events::association_event &event)
void wait()
Block until server shutdown.
const production_statistics & statistics() const noexcept
Get current statistics.
std::vector< std::function< void(const events::query_executed_event &)> > query_handlers_
std::vector< std::function< void(const events::association_event &)> > association_handlers_
void on_association_event(std::function< void(const events::association_event &)> handler)
Register handler for association events.
std::unique_ptr< mini_pacs > pacs_
const production_config & config() const noexcept
Get the configuration.
bool start()
Start the production PACS server.
void on_image_received(std::function< void(const events::image_received_event &)> handler)
Register handler for image received events.
std::vector< std::function< void(const events::image_received_event &)> > image_handlers_
std::vector< std::function< void(const events::access_denied_event &)> > access_denied_handlers_
void stop()
Stop the production PACS server.
std::unique_ptr< security::anonymizer > anonymizer_
monitoring::health_status get_health() const
Get health status.
void export_statistics(const std::filesystem::path &path) const
Export statistics to JSON file.
std::shared_ptr< security::access_control_manager > access_control_
Console output utilities for developer samples.
Production-grade PACS with enterprise features.
Configuration for REST API server.
Configuration options for the health checker.
std::chrono::seconds cache_duration
Cache health check results for this duration.
std::chrono::seconds check_interval
Interval between automatic health checks.
double storage_critical_threshold
Storage usage threshold for unhealthy status (percentage)
double storage_warning_threshold
Storage usage threshold for degraded status (percentage)
Comprehensive health status of the PACS system.
bool enabled
Enable access control enforcement.
security::anonymization_profile profile
Anonymization profile to use.
bool auto_anonymize
Automatically anonymize incoming images.
Event fired for association lifecycle events.
Event fired when an image is received and stored.
Event fired when a query is executed.
std::string level
Log level: "debug", "info", "warn", "error".
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
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.
logging_config logging
Logging 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.
Extended statistics for production PACS.
std::atomic< std::uint64_t > images_stored
Total images stored.
std::atomic< std::uint64_t > images_anonymized
Total images anonymized.
std::atomic< std::uint64_t > rest_requests
Total REST API requests.
std::chrono::system_clock::time_point start_time
Server uptime start.
std::atomic< std::uint64_t > queries_executed
Total queries executed.
std::atomic< std::uint64_t > access_denied_count
Total access denied events.
auto uptime() const -> std::chrono::seconds
Get uptime duration.
std::atomic< std::uint32_t > active_associations
Current active DICOM associations.
std::uint16_t port
HTTP port for REST API.
bool cors_enabled
Enable CORS for web clients.
bool enabled
Enable REST API server.
std::vector< std::string > allowed_ae_titles
Allowed AE titles (supports wildcards with *)
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::string ae_title
Application Entity title (max 16 characters)
std::filesystem::path root_path
Root path for DICOM file storage.
bool enabled
Enable TLS for DICOM connections.
Configuration options for the REST server.
Definition rest_config.h:29
bool enable_cors
Enable CORS (Cross-Origin Resource Sharing) headers.
Definition rest_config.h:40
std::uint16_t port
Port to listen on.
Definition rest_config.h:34
std::size_t concurrency
Number of worker threads for handling requests.
Definition rest_config.h:37