PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
thumbnail_endpoints.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2021-2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
14// IMPORTANT: Include Crow FIRST before any PACS headers to avoid forward
15// declaration conflicts
16#include "crow.h"
17
18// Workaround for Windows: DELETE is defined as a macro in <winnt.h>
19// which conflicts with crow::HTTPMethod::DELETE
20#ifdef DELETE
21#undef DELETE
22#endif
23
30
31#include <memory>
32#include <sstream>
33
35
36namespace {
37
41void add_cors_headers(crow::response& res, const rest_server_context& ctx) {
42 if (ctx.config != nullptr && !ctx.config->cors_allowed_origins.empty()) {
43 res.add_header("Access-Control-Allow-Origin",
44 ctx.config->cors_allowed_origins);
45 }
46}
47
53thumbnail_params parse_thumbnail_params(const crow::request& req) {
54 thumbnail_params params;
55
56 // Parse size
57 auto size_param = req.url_params.get("size");
58 if (size_param != nullptr) {
59 try {
60 int size = std::stoi(size_param);
61 if (size == 64 || size == 128 || size == 256 || size == 512) {
62 params.size = static_cast<uint16_t>(size);
63 }
64 } catch (...) {
65 // Use default
66 }
67 }
68
69 // Parse format
70 auto format_param = req.url_params.get("format");
71 if (format_param != nullptr) {
72 std::string format = format_param;
73 if (format == "jpeg" || format == "png") {
74 params.format = format;
75 }
76 }
77
78 // Parse quality
79 auto quality_param = req.url_params.get("quality");
80 if (quality_param != nullptr) {
81 try {
82 int quality = std::stoi(quality_param);
83 if (quality >= 1 && quality <= 100) {
84 params.quality = quality;
85 }
86 } catch (...) {
87 // Use default
88 }
89 }
90
91 // Parse frame
92 auto frame_param = req.url_params.get("frame");
93 if (frame_param != nullptr) {
94 try {
95 int frame = std::stoi(frame_param);
96 if (frame >= 1) {
97 params.frame = static_cast<uint32_t>(frame);
98 }
99 } catch (...) {
100 // Use default
101 }
102 }
103
104 return params;
105}
106
108std::shared_ptr<thumbnail_service> g_thumbnail_service;
109
110} // namespace
111
112// Internal implementation function called from rest_server.cpp
113void register_thumbnail_endpoints_impl(crow::SimpleApp& app,
114 std::shared_ptr<rest_server_context> ctx) {
115 // Initialize thumbnail service if database is available
116 if (ctx->database != nullptr && g_thumbnail_service == nullptr) {
117 g_thumbnail_service =
118 std::make_shared<thumbnail_service>(ctx->database);
119 }
120
121 // GET /api/v1/thumbnails/instances/{sopInstanceUid}
122 CROW_ROUTE(app, "/api/v1/thumbnails/instances/<string>")
123 .methods(crow::HTTPMethod::GET)(
124 [ctx](const crow::request& req, const std::string& sop_uid) {
125 crow::response res;
126 add_cors_headers(res, *ctx);
127
128 if (g_thumbnail_service == nullptr) {
129 res.code = 503;
130 res.add_header("Content-Type", "application/json");
131 res.body = make_error_json("SERVICE_UNAVAILABLE",
132 "Thumbnail service not configured");
133 return res;
134 }
135
136 auto params = parse_thumbnail_params(req);
137 auto result = g_thumbnail_service->get_thumbnail(sop_uid, params);
138
139 if (!result.success) {
140 res.code = 404;
141 res.add_header("Content-Type", "application/json");
142 res.body =
143 make_error_json("NOT_FOUND", result.error_message);
144 return res;
145 }
146
147 res.code = 200;
148 res.add_header("Content-Type", result.entry.content_type);
149 res.add_header("Cache-Control", "max-age=3600");
150 res.body = std::string(
151 reinterpret_cast<const char*>(result.entry.data.data()),
152 result.entry.data.size());
153 return res;
154 });
155
156 // GET /api/v1/thumbnails/series/{seriesUid}
157 CROW_ROUTE(app, "/api/v1/thumbnails/series/<string>")
158 .methods(crow::HTTPMethod::GET)(
159 [ctx](const crow::request& req, const std::string& series_uid) {
160 crow::response res;
161 add_cors_headers(res, *ctx);
162
163 if (g_thumbnail_service == nullptr) {
164 res.code = 503;
165 res.add_header("Content-Type", "application/json");
166 res.body = make_error_json("SERVICE_UNAVAILABLE",
167 "Thumbnail service not configured");
168 return res;
169 }
170
171 auto params = parse_thumbnail_params(req);
172 auto result =
173 g_thumbnail_service->get_series_thumbnail(series_uid, params);
174
175 if (!result.success) {
176 res.code = 404;
177 res.add_header("Content-Type", "application/json");
178 res.body =
179 make_error_json("NOT_FOUND", result.error_message);
180 return res;
181 }
182
183 res.code = 200;
184 res.add_header("Content-Type", result.entry.content_type);
185 res.add_header("Cache-Control", "max-age=3600");
186 res.body = std::string(
187 reinterpret_cast<const char*>(result.entry.data.data()),
188 result.entry.data.size());
189 return res;
190 });
191
192 // GET /api/v1/thumbnails/studies/{studyUid}
193 CROW_ROUTE(app, "/api/v1/thumbnails/studies/<string>")
194 .methods(crow::HTTPMethod::GET)(
195 [ctx](const crow::request& req, const std::string& study_uid) {
196 crow::response res;
197 add_cors_headers(res, *ctx);
198
199 if (g_thumbnail_service == nullptr) {
200 res.code = 503;
201 res.add_header("Content-Type", "application/json");
202 res.body = make_error_json("SERVICE_UNAVAILABLE",
203 "Thumbnail service not configured");
204 return res;
205 }
206
207 auto params = parse_thumbnail_params(req);
208 auto result =
209 g_thumbnail_service->get_study_thumbnail(study_uid, params);
210
211 if (!result.success) {
212 res.code = 404;
213 res.add_header("Content-Type", "application/json");
214 res.body =
215 make_error_json("NOT_FOUND", result.error_message);
216 return res;
217 }
218
219 res.code = 200;
220 res.add_header("Content-Type", result.entry.content_type);
221 res.add_header("Cache-Control", "max-age=3600");
222 res.body = std::string(
223 reinterpret_cast<const char*>(result.entry.data.data()),
224 result.entry.data.size());
225 return res;
226 });
227
228 // DELETE /api/v1/thumbnails/cache - Clear all cached thumbnails
229 CROW_ROUTE(app, "/api/v1/thumbnails/cache")
230 .methods(crow::HTTPMethod::DELETE)([ctx](const crow::request& /*req*/) {
231 crow::response res;
232 res.add_header("Content-Type", "application/json");
233 add_cors_headers(res, *ctx);
234
235 if (g_thumbnail_service == nullptr) {
236 res.code = 503;
237 res.body = make_error_json("SERVICE_UNAVAILABLE",
238 "Thumbnail service not configured");
239 return res;
240 }
241
242 g_thumbnail_service->clear_cache();
243
244 res.code = 200;
245 res.body = make_success_json("Cache cleared successfully");
246 return res;
247 });
248
249 // GET /api/v1/thumbnails/cache/stats - Get cache statistics
250 CROW_ROUTE(app, "/api/v1/thumbnails/cache/stats")
251 .methods(crow::HTTPMethod::GET)([ctx](const crow::request& /*req*/) {
252 crow::response res;
253 res.add_header("Content-Type", "application/json");
254 add_cors_headers(res, *ctx);
255
256 if (g_thumbnail_service == nullptr) {
257 res.code = 503;
258 res.body = make_error_json("SERVICE_UNAVAILABLE",
259 "Thumbnail service not configured");
260 return res;
261 }
262
263 std::ostringstream oss;
264 oss << R"({"cache_size":)" << g_thumbnail_service->cache_size()
265 << R"(,"entry_count":)" << g_thumbnail_service->cache_entry_count()
266 << R"(,"max_size":)" << g_thumbnail_service->max_cache_size()
267 << "}";
268
269 res.code = 200;
270 res.body = oss.str();
271 return res;
272 });
273}
274
275} // namespace kcenon::pacs::web::endpoints
PACS index database for metadata storage and retrieval.
void register_thumbnail_endpoints_impl(crow::SimpleApp &app, std::shared_ptr< rest_server_context > ctx)
std::string make_error_json(std::string_view code, std::string_view message)
Create JSON error response body with details.
Definition rest_types.h:79
std::string make_success_json(std::string_view message="OK")
Create success response with optional message.
Definition rest_types.h:91
Configuration for REST API server.
Common types and utilities for REST API.
System API endpoints for REST server.
Thumbnail REST API endpoints.
Thumbnail generation service for DICOM images.