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);
51std::string escape_json(
const std::string& str) {
52 std::ostringstream oss;
87metadata_request parse_metadata_request(
const crow::request& req) {
88 metadata_request request;
91 auto tags_param = req.url_params.get(
"tags");
92 if (tags_param !=
nullptr) {
93 std::string tags_str = tags_param;
94 std::istringstream iss(tags_str);
96 while (std::getline(iss, tag,
',')) {
98 size_t start = tag.find_first_not_of(
" \t");
99 size_t end = tag.find_last_not_of(
" \t");
100 if (start != std::string::npos) {
101 request.tags.push_back(tag.substr(start, end - start + 1));
107 auto preset_param = req.url_params.get(
"preset");
108 if (preset_param !=
nullptr) {
113 auto private_param = req.url_params.get(
"include_private");
114 if (private_param !=
nullptr) {
115 std::string val = private_param;
116 request.include_private = (val ==
"true" || val ==
"1");
125std::string metadata_response_to_json(
const metadata_response& resp) {
126 std::ostringstream oss;
127 oss << R
"({"tags":{)";
130 for (
const auto& [tag, value] : resp.tags) {
137 bool is_numeric = !value.empty();
138 for (
char c : value) {
139 if (!std::isdigit(c) && c !=
'.' && c !=
'-' && c !=
'+' &&
140 c !=
'e' && c !=
'E' && c !=
'\\') {
147 if (is_numeric && value.find(
'\\') != std::string::npos) {
149 oss <<
"\"" << tag <<
"\":[";
150 std::istringstream vals(value);
152 bool first_val =
true;
153 while (std::getline(vals, v,
'\\')) {
161 }
else if (is_numeric && value.find(
'\\') == std::string::npos) {
162 oss <<
"\"" << tag <<
"\":" << value;
164 oss <<
"\"" << tag <<
"\":\"" << escape_json(value) <<
"\"";
175std::string sorted_instances_to_json(
const sorted_instances_response& resp) {
176 std::ostringstream oss;
177 oss << R
"({"instances":[)";
180 for (
const auto& inst : resp.instances) {
186 oss <<
"{\"sop_instance_uid\":\"" << inst.sop_instance_uid <<
"\"";
188 if (inst.instance_number.has_value()) {
189 oss <<
",\"instance_number\":" << inst.instance_number.value();
192 if (inst.slice_location.has_value()) {
193 oss <<
",\"slice_location\":" << inst.slice_location.value();
199 oss <<
"],\"total\":" << resp.total <<
"}";
206std::string navigation_info_to_json(
const navigation_info& nav) {
207 std::ostringstream oss;
210 if (!nav.previous.empty()) {
211 oss <<
"\"previous\":\"" << nav.previous <<
"\",";
214 if (!nav.next.empty()) {
215 oss <<
"\"next\":\"" << nav.next <<
"\",";
218 oss <<
"\"index\":" << nav.index <<
",\"total\":" << nav.total
219 <<
",\"first\":\"" << nav.first <<
"\",\"last\":\"" << nav.last <<
"\"}";
227std::string presets_to_json(
const std::vector<window_level_preset>& presets) {
228 std::ostringstream oss;
229 oss << R
"({"presets":[)";
232 for (
const auto& p : presets) {
238 oss <<
"{\"name\":\"" << escape_json(p.name) <<
"\",\"center\":"
239 << p.center <<
",\"width\":" << p.width <<
"}";
249std::string voi_lut_to_json(
const voi_lut_info& info) {
250 std::ostringstream oss;
251 oss <<
"{\"window_center\":[";
253 for (
size_t i = 0; i <
info.window_center.size(); ++i) {
257 oss <<
info.window_center[i];
260 oss <<
"],\"window_width\":[";
261 for (
size_t i = 0; i <
info.window_width.size(); ++i) {
265 oss <<
info.window_width[i];
268 oss <<
"],\"window_explanations\":[";
269 for (
size_t i = 0; i <
info.window_explanations.size(); ++i) {
273 oss <<
"\"" << escape_json(
info.window_explanations[i]) <<
"\"";
276 oss <<
"],\"rescale_slope\":" <<
info.rescale_slope
277 <<
",\"rescale_intercept\":" <<
info.rescale_intercept <<
"}";
285std::string frame_info_to_json(
const frame_info& info) {
286 std::ostringstream oss;
287 oss <<
"{\"total_frames\":" <<
info.total_frames;
289 if (
info.frame_time.has_value()) {
290 oss <<
",\"frame_time\":" <<
info.frame_time.value();
293 if (
info.frame_rate.has_value()) {
294 oss <<
",\"frame_rate\":" <<
info.frame_rate.value();
297 oss <<
",\"rows\":" <<
info.rows <<
",\"columns\":" <<
info.columns <<
"}";
303std::shared_ptr<metadata_service> g_metadata_service;
309 std::shared_ptr<rest_server_context> ctx) {
311 if (ctx->database !=
nullptr && g_metadata_service ==
nullptr) {
312 g_metadata_service = std::make_shared<metadata_service>(ctx->database);
320 CROW_ROUTE(app,
"/api/v1/instances/<string>/metadata")
321 .methods(crow::HTTPMethod::GET)(
322 [ctx](
const crow::request& req,
const std::string& sop_uid) {
324 res.add_header(
"Content-Type",
"application/json");
325 add_cors_headers(res, *ctx);
327 if (g_metadata_service ==
nullptr) {
330 "Metadata service not configured");
334 auto request = parse_metadata_request(req);
335 auto result = g_metadata_service->get_metadata(sop_uid, request);
337 if (!result.success) {
344 res.body = metadata_response_to_json(result);
353 CROW_ROUTE(app,
"/api/v1/series/<string>/instances/sorted")
354 .methods(crow::HTTPMethod::GET)(
355 [ctx](
const crow::request& req,
const std::string& series_uid) {
357 res.add_header(
"Content-Type",
"application/json");
358 add_cors_headers(res, *ctx);
360 if (g_metadata_service ==
nullptr) {
363 "Metadata service not configured");
369 auto sort_param = req.url_params.get(
"sort_by");
370 if (sort_param !=
nullptr) {
372 if (parsed.has_value()) {
373 order = parsed.value();
377 bool ascending =
true;
378 auto dir_param = req.url_params.get(
"direction");
379 if (dir_param !=
nullptr) {
380 std::string dir = dir_param;
381 ascending = (dir !=
"desc");
384 auto result = g_metadata_service->get_sorted_instances(
385 series_uid, order, ascending);
387 if (!result.success) {
394 res.body = sorted_instances_to_json(result);
399 CROW_ROUTE(app,
"/api/v1/instances/<string>/navigation")
400 .methods(crow::HTTPMethod::GET)(
401 [ctx](
const crow::request& ,
const std::string& sop_uid) {
403 res.add_header(
"Content-Type",
"application/json");
404 add_cors_headers(res, *ctx);
406 if (g_metadata_service ==
nullptr) {
409 "Metadata service not configured");
413 auto result = g_metadata_service->get_navigation(sop_uid);
415 if (!result.success) {
422 res.body = navigation_info_to_json(result);
431 CROW_ROUTE(app,
"/api/v1/presets/window-level")
432 .methods(crow::HTTPMethod::GET)([ctx](
const crow::request& req) {
434 res.add_header(
"Content-Type",
"application/json");
435 add_cors_headers(res, *ctx);
437 std::string modality =
"CT";
438 auto modality_param = req.url_params.get(
"modality");
439 if (modality_param !=
nullptr) {
440 modality = modality_param;
446 res.body = presets_to_json(presets);
451 CROW_ROUTE(app,
"/api/v1/instances/<string>/voi-lut")
452 .methods(crow::HTTPMethod::GET)(
453 [ctx](
const crow::request& ,
const std::string& sop_uid) {
455 res.add_header(
"Content-Type",
"application/json");
456 add_cors_headers(res, *ctx);
458 if (g_metadata_service ==
nullptr) {
461 "Metadata service not configured");
465 auto result = g_metadata_service->get_voi_lut(sop_uid);
467 if (!result.success) {
474 res.body = voi_lut_to_json(result);
483 CROW_ROUTE(app,
"/api/v1/instances/<string>/frame-info")
484 .methods(crow::HTTPMethod::GET)(
485 [ctx](
const crow::request& ,
const std::string& sop_uid) {
487 res.add_header(
"Content-Type",
"application/json");
488 add_cors_headers(res, *ctx);
490 if (g_metadata_service ==
nullptr) {
493 "Metadata service not configured");
497 auto result = g_metadata_service->get_frame_info(sop_uid);
499 if (!result.success) {
506 res.body = frame_info_to_json(result);
PACS index database for metadata storage and retrieval.
void register_metadata_endpoints_impl(crow::SimpleApp &app, std::shared_ptr< rest_server_context > ctx)
sort_order
Sort order for series instances.
@ position
Sort by ImagePositionPatient/SliceLocation.
std::string make_error_json(std::string_view code, std::string_view message)
Create JSON error response body with details.
std::optional< sort_order > sort_order_from_string(std::string_view str)
Parse sort order from string.
std::optional< metadata_preset > preset_from_string(std::string_view str)
Parse preset from string.
Configuration for REST API server.
Common types and utilities for REST API.
System API endpoints for REST server.