1793 std::shared_ptr<rest_server_context> ctx) {
1799 CROW_ROUTE(app,
"/dicomweb/studies/<string>")
1800 .methods(crow::HTTPMethod::GET)(
1801 [ctx](
const crow::request& req,
const std::string& study_uid) {
1803 add_cors_headers(res, *ctx);
1806 if (!check_dicomweb_auth(ctx, req, res,
1807 {
"dicomweb.read"})) {
1811 if (!ctx->database) {
1813 res.add_header(
"Content-Type",
"application/json");
1814 res.body = make_error_json(
"DATABASE_UNAVAILABLE",
1815 "Database not configured");
1820 auto accept = req.get_header_value(
"Accept");
1821 auto accept_infos = dicomweb::parse_accept_header(accept);
1824 if (dicomweb::is_acceptable(accept_infos,
1825 dicomweb::media_type::dicom_json)) {
1826 auto files_result = ctx->database->get_study_files(study_uid);
1827 if (!files_result.is_ok()) {
1829 res.add_header(
"Content-Type",
"application/json");
1830 res.body = make_error_json(
"QUERY_ERROR",
1831 files_result.error().message);
1834 std::string bulk_uri =
"/dicomweb/studies/" + study_uid +
1836 return build_metadata_response(files_result.value(), *ctx, bulk_uri);
1840 auto files_result = ctx->database->get_study_files(study_uid);
1841 if (!files_result.is_ok()) {
1843 res.add_header(
"Content-Type",
"application/json");
1844 res.body = make_error_json(
"QUERY_ERROR",
1845 files_result.error().message);
1848 std::string base_uri =
"/dicomweb/studies/" + study_uid;
1849 return build_multipart_dicom_response(files_result.value(), *ctx, base_uri);
1853 CROW_ROUTE(app,
"/dicomweb/studies/<string>/metadata")
1854 .methods(crow::HTTPMethod::GET)(
1855 [ctx](
const crow::request& req,
const std::string& study_uid) {
1857 add_cors_headers(res, *ctx);
1860 if (!check_dicomweb_auth(ctx, req, res,
1861 {
"dicomweb.read"})) {
1865 if (!ctx->database) {
1867 res.add_header(
"Content-Type",
"application/json");
1868 res.body = make_error_json(
"DATABASE_UNAVAILABLE",
1869 "Database not configured");
1873 auto files_result = ctx->database->get_study_files(study_uid);
1874 if (!files_result.is_ok()) {
1876 res.add_header(
"Content-Type",
"application/json");
1877 res.body = make_error_json(
"QUERY_ERROR",
1878 files_result.error().message);
1881 std::string bulk_uri =
"/dicomweb/studies/" + study_uid +
1883 return build_metadata_response(files_result.value(), *ctx, bulk_uri);
1891 CROW_ROUTE(app,
"/dicomweb/studies/<string>/series/<string>")
1892 .methods(crow::HTTPMethod::GET)(
1893 [ctx](
const crow::request& req,
1894 const std::string& study_uid,
1895 const std::string& series_uid) {
1897 add_cors_headers(res, *ctx);
1899 if (!ctx->database) {
1901 res.add_header(
"Content-Type",
"application/json");
1902 res.body = make_error_json(
"DATABASE_UNAVAILABLE",
1903 "Database not configured");
1908 auto study = ctx->database->find_study(study_uid);
1911 res.add_header(
"Content-Type",
"application/json");
1912 res.body = make_error_json(
"NOT_FOUND",
"Study not found");
1917 auto accept = req.get_header_value(
"Accept");
1918 auto accept_infos = dicomweb::parse_accept_header(accept);
1921 if (dicomweb::is_acceptable(accept_infos,
1922 dicomweb::media_type::dicom_json)) {
1923 auto files_result = ctx->database->get_series_files(series_uid);
1924 if (!files_result.is_ok()) {
1926 res.add_header(
"Content-Type",
"application/json");
1927 res.body = make_error_json(
"QUERY_ERROR",
1928 files_result.error().message);
1931 std::string bulk_uri =
"/dicomweb/studies/" + study_uid +
1932 "/series/" + series_uid +
"/bulkdata/";
1933 return build_metadata_response(files_result.value(), *ctx, bulk_uri);
1936 auto files_result = ctx->database->get_series_files(series_uid);
1937 if (!files_result.is_ok()) {
1939 res.add_header(
"Content-Type",
"application/json");
1940 res.body = make_error_json(
"QUERY_ERROR",
1941 files_result.error().message);
1944 std::string base_uri =
"/dicomweb/studies/" + study_uid +
1945 "/series/" + series_uid;
1946 return build_multipart_dicom_response(files_result.value(), *ctx, base_uri);
1950 CROW_ROUTE(app,
"/dicomweb/studies/<string>/series/<string>/metadata")
1951 .methods(crow::HTTPMethod::GET)(
1952 [ctx](
const crow::request& ,
1953 const std::string& study_uid,
1954 const std::string& series_uid) {
1956 add_cors_headers(res, *ctx);
1958 if (!ctx->database) {
1960 res.add_header(
"Content-Type",
"application/json");
1961 res.body = make_error_json(
"DATABASE_UNAVAILABLE",
1962 "Database not configured");
1966 auto files_result = ctx->database->get_series_files(series_uid);
1967 if (!files_result.is_ok()) {
1969 res.add_header(
"Content-Type",
"application/json");
1970 res.body = make_error_json(
"QUERY_ERROR",
1971 files_result.error().message);
1974 std::string bulk_uri =
"/dicomweb/studies/" + study_uid +
1975 "/series/" + series_uid +
"/bulkdata/";
1976 return build_metadata_response(files_result.value(), *ctx, bulk_uri);
1985 "/dicomweb/studies/<string>/series/<string>/instances/<string>")
1986 .methods(crow::HTTPMethod::GET)(
1987 [ctx](
const crow::request& req,
1988 const std::string& study_uid,
1989 const std::string& series_uid,
1990 const std::string& sop_uid) {
1992 add_cors_headers(res, *ctx);
1994 if (!ctx->database) {
1996 res.add_header(
"Content-Type",
"application/json");
1997 res.body = make_error_json(
"DATABASE_UNAVAILABLE",
1998 "Database not configured");
2002 auto file_path_result = ctx->database->get_file_path(sop_uid);
2003 if (!file_path_result.is_ok()) {
2005 res.add_header(
"Content-Type",
"application/json");
2006 res.body = make_error_json(
"QUERY_ERROR",
2007 file_path_result.error().message);
2010 const auto& file_path = file_path_result.value();
2013 res.add_header(
"Content-Type",
"application/json");
2014 res.body = make_error_json(
"NOT_FOUND",
"Instance not found");
2019 auto accept = req.get_header_value(
"Accept");
2020 auto accept_infos = dicomweb::parse_accept_header(accept);
2023 if (dicomweb::is_acceptable(accept_infos,
2024 dicomweb::media_type::dicom_json)) {
2025 std::vector<std::string> files = {*file_path};
2026 std::string bulk_uri =
"/dicomweb/studies/" + study_uid +
2027 "/series/" + series_uid +
2028 "/instances/" + sop_uid +
"/bulkdata/";
2029 return build_metadata_response(files, *ctx, bulk_uri);
2033 auto data = read_file_bytes(*file_path);
2036 res.add_header(
"Content-Type",
"application/json");
2037 res.body = make_error_json(
"READ_ERROR",
2038 "Failed to read DICOM file");
2043 res.add_header(
"Content-Type",
2044 std::string(dicomweb::media_type::dicom));
2045 res.body = std::string(
reinterpret_cast<char*
>(data.data()),
2052 "/dicomweb/studies/<string>/series/<string>/instances/<string>/metadata")
2053 .methods(crow::HTTPMethod::GET)(
2054 [ctx](
const crow::request& ,
2055 const std::string& study_uid,
2056 const std::string& series_uid,
2057 const std::string& sop_uid) {
2059 add_cors_headers(res, *ctx);
2061 if (!ctx->database) {
2063 res.add_header(
"Content-Type",
"application/json");
2064 res.body = make_error_json(
"DATABASE_UNAVAILABLE",
2065 "Database not configured");
2069 auto file_path_result = ctx->database->get_file_path(sop_uid);
2070 if (!file_path_result.is_ok()) {
2072 res.add_header(
"Content-Type",
"application/json");
2073 res.body = make_error_json(
"QUERY_ERROR",
2074 file_path_result.error().message);
2077 const auto& file_path = file_path_result.value();
2080 res.add_header(
"Content-Type",
"application/json");
2081 res.body = make_error_json(
"NOT_FOUND",
"Instance not found");
2085 std::vector<std::string> files = {*file_path};
2086 std::string bulk_uri =
"/dicomweb/studies/" + study_uid +
2087 "/series/" + series_uid +
2088 "/instances/" + sop_uid +
"/bulkdata/";
2089 return build_metadata_response(files, *ctx, bulk_uri);
2098 "/dicomweb/studies/<string>/series/<string>/instances/<string>/frames/<string>")
2099 .methods(crow::HTTPMethod::GET)(
2100 [ctx](
const crow::request& req,
2101 const std::string& study_uid,
2102 const std::string& series_uid,
2103 const std::string& sop_uid,
2104 const std::string& frame_list) {
2106 add_cors_headers(res, *ctx);
2108 if (!ctx->database) {
2110 res.add_header(
"Content-Type",
"application/json");
2111 res.body = make_error_json(
"DATABASE_UNAVAILABLE",
2112 "Database not configured");
2116 auto file_path_result = ctx->database->get_file_path(sop_uid);
2117 if (!file_path_result.is_ok()) {
2119 res.add_header(
"Content-Type",
"application/json");
2120 res.body = make_error_json(
"QUERY_ERROR",
2121 file_path_result.error().message);
2124 const auto& file_path = file_path_result.value();
2127 res.add_header(
"Content-Type",
"application/json");
2128 res.body = make_error_json(
"NOT_FOUND",
"Instance not found");
2133 auto frames = dicomweb::parse_frame_numbers(frame_list);
2134 if (frames.empty()) {
2136 res.add_header(
"Content-Type",
"application/json");
2137 res.body = make_error_json(
"INVALID_FRAME_LIST",
2138 "No valid frame numbers specified");
2143 auto data = read_file_bytes(*file_path);
2146 res.add_header(
"Content-Type",
"application/json");
2147 res.body = make_error_json(
"READ_ERROR",
2148 "Failed to read DICOM file");
2152 auto dicom_result = core::dicom_file::from_bytes(
2153 std::span<const uint8_t>(data.data(), data.size()));
2154 if (dicom_result.is_err()) {
2156 res.add_header(
"Content-Type",
"application/json");
2157 res.body = make_error_json(
"PARSE_ERROR",
2158 "Failed to parse DICOM file");
2162 const auto& dataset = dicom_result.value().dataset();
2165 auto rows_elem = dataset.get(core::tags::rows);
2166 auto cols_elem = dataset.get(core::tags::columns);
2167 auto bits_alloc_elem = dataset.get(core::tags::bits_allocated);
2168 auto samples_elem = dataset.get(core::tags::samples_per_pixel);
2171 auto num_frames_elem = dataset.get(number_of_frames_tag);
2172 auto pixel_data_elem = dataset.get(core::tags::pixel_data);
2174 if (!rows_elem || !cols_elem || !pixel_data_elem) {
2176 res.add_header(
"Content-Type",
"application/json");
2177 res.body = make_error_json(
"NOT_IMAGE",
2178 "Instance does not contain image data");
2182 uint16_t rows = rows_elem->as_numeric<uint16_t>().unwrap_or(0);
2183 uint16_t cols = cols_elem->as_numeric<uint16_t>().unwrap_or(0);
2184 uint16_t bits_allocated = bits_alloc_elem ?
2185 bits_alloc_elem->as_numeric<uint16_t>().unwrap_or(16) : 16;
2186 uint16_t samples_per_pixel = samples_elem ?
2187 samples_elem->as_numeric<uint16_t>().unwrap_or(1) : 1;
2188 uint32_t num_frames = 1;
2189 if (num_frames_elem) {
2191 num_frames = std::stoul(num_frames_elem->as_string().unwrap_or(
"1"));
2196 size_t frame_size =
static_cast<size_t>(rows) * cols *
2197 samples_per_pixel * ((bits_allocated + 7) / 8);
2199 auto pixel_data = pixel_data_elem->raw_data();
2202 auto accept = req.get_header_value(
"Accept");
2206 dicomweb::media_type::octet_stream);
2208 for (uint32_t frame_num : frames) {
2209 if (frame_num > num_frames) {
2214 auto frame_data = dicomweb::extract_frame(
2215 pixel_data, frame_num, frame_size);
2217 if (!frame_data.empty()) {
2218 std::string location =
"/dicomweb/studies/" + study_uid +
2219 "/series/" + series_uid +
2220 "/instances/" + sop_uid +
2221 "/frames/" + std::to_string(frame_num);
2226 if (builder.
empty()) {
2228 res.add_header(
"Content-Type",
"application/json");
2229 res.body = make_error_json(
"NOT_FOUND",
2230 "No valid frames found");
2235 if (builder.
size() == 1) {
2237 auto body = builder.
build();
2239 res.add_header(
"Content-Type",
2240 std::string(dicomweb::media_type::octet_stream));
2242 auto frame_data = dicomweb::extract_frame(
2243 pixel_data, frames[0], frame_size);
2244 res.body = std::string(
2245 reinterpret_cast<char*
>(frame_data.data()),
2251 res.body = builder.
build();
2263 "/dicomweb/studies/<string>/series/<string>/instances/<string>/rendered")
2264 .methods(crow::HTTPMethod::GET)(
2265 [ctx](
const crow::request& req,
2266 const std::string& study_uid,
2267 const std::string& series_uid,
2268 const std::string& sop_uid) {
2270 add_cors_headers(res, *ctx);
2272 if (!ctx->database) {
2274 res.add_header(
"Content-Type",
"application/json");
2275 res.body = make_error_json(
"DATABASE_UNAVAILABLE",
2276 "Database not configured");
2280 auto file_path_result = ctx->database->get_file_path(sop_uid);
2281 if (!file_path_result.is_ok()) {
2283 res.add_header(
"Content-Type",
"application/json");
2284 res.body = make_error_json(
"QUERY_ERROR",
2285 file_path_result.error().message);
2288 const auto& file_path = file_path_result.value();
2291 res.add_header(
"Content-Type",
"application/json");
2292 res.body = make_error_json(
"NOT_FOUND",
"Instance not found");
2297 auto accept = req.get_header_value(
"Accept");
2298 auto params = dicomweb::parse_rendered_params(req.raw_url, accept);
2301 auto result = dicomweb::render_dicom_image(*file_path, params);
2303 if (!result.success) {
2305 res.add_header(
"Content-Type",
"application/json");
2306 res.body = make_error_json(
"RENDER_ERROR", result.error_message);
2311 res.add_header(
"Content-Type", result.content_type);
2312 res.body = std::string(
2313 reinterpret_cast<char*
>(result.data.data()),
2314 result.data.size());
2320 "/dicomweb/studies/<string>/series/<string>/instances/<string>/frames/<string>/rendered")
2321 .methods(crow::HTTPMethod::GET)(
2322 [ctx](
const crow::request& req,
2323 const std::string& study_uid,
2324 const std::string& series_uid,
2325 const std::string& sop_uid,
2326 const std::string& frame_str) {
2328 add_cors_headers(res, *ctx);
2330 if (!ctx->database) {
2332 res.add_header(
"Content-Type",
"application/json");
2333 res.body = make_error_json(
"DATABASE_UNAVAILABLE",
2334 "Database not configured");
2338 auto file_path_result = ctx->database->get_file_path(sop_uid);
2339 if (!file_path_result.is_ok()) {
2341 res.add_header(
"Content-Type",
"application/json");
2342 res.body = make_error_json(
"QUERY_ERROR",
2343 file_path_result.error().message);
2346 const auto& file_path = file_path_result.value();
2349 res.add_header(
"Content-Type",
"application/json");
2350 res.body = make_error_json(
"NOT_FOUND",
"Instance not found");
2355 uint32_t frame_num = 1;
2357 frame_num = std::stoul(frame_str);
2358 if (frame_num == 0) frame_num = 1;
2361 res.add_header(
"Content-Type",
"application/json");
2362 res.body = make_error_json(
"INVALID_FRAME",
2363 "Invalid frame number");
2368 auto accept = req.get_header_value(
"Accept");
2369 auto params = dicomweb::parse_rendered_params(req.raw_url, accept);
2370 params.frame = frame_num;
2373 auto result = dicomweb::render_dicom_image(*file_path, params);
2375 if (!result.success) {
2377 res.add_header(
"Content-Type",
"application/json");
2378 res.body = make_error_json(
"RENDER_ERROR", result.error_message);
2383 res.add_header(
"Content-Type", result.content_type);
2384 res.body = std::string(
2385 reinterpret_cast<char*
>(result.data.data()),
2386 result.data.size());
2395 CROW_ROUTE(app,
"/dicomweb/studies")
2396 .methods(crow::HTTPMethod::POST)(
2397 [ctx](
const crow::request& req) {
2399 add_cors_headers(res, *ctx);
2402 if (!check_dicomweb_auth(ctx, req, res,
2403 {
"dicomweb.write"})) {
2407 if (!ctx->database || !ctx->file_storage) {
2409 res.add_header(
"Content-Type",
"application/json");
2410 res.body = make_error_json(
"SERVICE_UNAVAILABLE",
2412 ?
"Database not configured"
2413 :
"File storage not configured");
2418 auto content_type = req.get_header_value(
"Content-Type");
2419 if (content_type.empty() ||
2420 content_type.find(
"multipart/related") == std::string::npos) {
2422 res.add_header(
"Content-Type",
"application/json");
2423 res.body = make_error_json(
2424 "UNSUPPORTED_MEDIA_TYPE",
2425 "Content-Type must be multipart/related");
2430 auto parse_result = dicomweb::multipart_parser::parse(
2431 content_type, req.body);
2433 if (!parse_result) {
2435 res.add_header(
"Content-Type",
"application/json");
2436 res.body = make_error_json(
2437 parse_result.error->code,
2438 parse_result.error->message);
2442 if (parse_result.parts.empty()) {
2444 res.add_header(
"Content-Type",
"application/json");
2445 res.body = make_error_json(
2447 "No DICOM instances in request body");
2454 for (
const auto& part : parse_result.parts) {
2458 if (part.content_type.find(
"application/dicom") ==
2459 std::string::npos) {
2464 auto dicom_result = core::dicom_file::from_bytes(
2465 std::span<const uint8_t>(part.data.data(), part.data.size()));
2467 if (dicom_result.is_err()) {
2471 store_response.failed_instances.push_back(
2476 const auto& dataset = dicom_result.value().dataset();
2479 auto validation = dicomweb::validate_instance(dataset);
2486 if (
auto elem = dataset.get(core::tags::sop_class_uid)) {
2489 if (
auto elem = dataset.get(core::tags::sop_instance_uid)) {
2493 store_response.failed_instances.push_back(
2499 auto sop_class_elem = dataset.get(core::tags::sop_class_uid);
2500 auto sop_instance_elem = dataset.get(core::tags::sop_instance_uid);
2501 auto study_uid_elem = dataset.get(core::tags::study_instance_uid);
2502 auto series_uid_elem = dataset.get(core::tags::series_instance_uid);
2504 result.
sop_class_uid = sop_class_elem->as_string().unwrap_or(
"");
2507 std::string study_uid = study_uid_elem->as_string().unwrap_or(
"");
2508 std::string series_uid = series_uid_elem->as_string().unwrap_or(
"");
2511 auto existing_result = ctx->database->get_file_path(
2513 if (existing_result.is_ok() && existing_result.value()) {
2517 store_response.failed_instances.push_back(
2523 if (!store_instance_to_storage(ctx, dataset, result)) {
2524 store_response.failed_instances.push_back(
2530 result.
retrieve_url =
"/dicomweb/studies/" + study_uid +
2531 "/series/" + series_uid +
2533 store_response.referenced_instances.push_back(
2538 std::string base_url;
2540 res.add_header(
"Content-Type",
2541 std::string(dicomweb::media_type::dicom_json));
2543 if (store_response.all_failed()) {
2545 }
else if (store_response.partial_success()) {
2551 res.body = dicomweb::build_store_response_json(
2552 store_response, base_url);
2557 CROW_ROUTE(app,
"/dicomweb/studies/<string>")
2558 .methods(crow::HTTPMethod::POST)(
2559 [ctx](
const crow::request& req,
const std::string& target_study_uid) {
2561 add_cors_headers(res, *ctx);
2564 if (!check_dicomweb_auth(ctx, req, res,
2565 {
"dicomweb.write"})) {
2569 if (!ctx->database || !ctx->file_storage) {
2571 res.add_header(
"Content-Type",
"application/json");
2572 res.body = make_error_json(
"SERVICE_UNAVAILABLE",
2574 ?
"Database not configured"
2575 :
"File storage not configured");
2580 auto content_type = req.get_header_value(
"Content-Type");
2581 if (content_type.empty() ||
2582 content_type.find(
"multipart/related") == std::string::npos) {
2584 res.add_header(
"Content-Type",
"application/json");
2585 res.body = make_error_json(
2586 "UNSUPPORTED_MEDIA_TYPE",
2587 "Content-Type must be multipart/related");
2592 auto parse_result = dicomweb::multipart_parser::parse(
2593 content_type, req.body);
2595 if (!parse_result) {
2597 res.add_header(
"Content-Type",
"application/json");
2598 res.body = make_error_json(
2599 parse_result.error->code,
2600 parse_result.error->message);
2604 if (parse_result.parts.empty()) {
2606 res.add_header(
"Content-Type",
"application/json");
2607 res.body = make_error_json(
2609 "No DICOM instances in request body");
2616 for (
const auto& part : parse_result.parts) {
2620 if (part.content_type.find(
"application/dicom") ==
2621 std::string::npos) {
2626 auto dicom_result = core::dicom_file::from_bytes(
2627 std::span<const uint8_t>(part.data.data(), part.data.size()));
2629 if (dicom_result.is_err()) {
2633 store_response.failed_instances.push_back(
2638 const auto& dataset = dicom_result.value().dataset();
2641 auto validation = dicomweb::validate_instance(
2642 dataset, target_study_uid);
2648 if (
auto elem = dataset.get(core::tags::sop_class_uid)) {
2651 if (
auto elem = dataset.get(core::tags::sop_instance_uid)) {
2655 store_response.failed_instances.push_back(
2661 auto sop_class_elem = dataset.get(core::tags::sop_class_uid);
2662 auto sop_instance_elem = dataset.get(core::tags::sop_instance_uid);
2663 auto series_uid_elem = dataset.get(core::tags::series_instance_uid);
2665 result.
sop_class_uid = sop_class_elem->as_string().unwrap_or(
"");
2667 std::string series_uid = series_uid_elem->as_string().unwrap_or(
"");
2670 auto existing_result = ctx->database->get_file_path(
2672 if (existing_result.is_ok() && existing_result.value()) {
2676 store_response.failed_instances.push_back(
2682 if (!store_instance_to_storage(ctx, dataset, result)) {
2683 store_response.failed_instances.push_back(
2689 result.
retrieve_url =
"/dicomweb/studies/" + target_study_uid +
2690 "/series/" + series_uid +
2692 store_response.referenced_instances.push_back(
2697 std::string base_url;
2699 res.add_header(
"Content-Type",
2700 std::string(dicomweb::media_type::dicom_json));
2702 if (store_response.all_failed()) {
2704 }
else if (store_response.partial_success()) {
2710 res.body = dicomweb::build_store_response_json(
2711 store_response, base_url);
2720 CROW_ROUTE(app,
"/dicomweb/studies")
2721 .methods(crow::HTTPMethod::GET)(
2722 [ctx](
const crow::request& req) {
2724 add_cors_headers(res, *ctx);
2725 res.add_header(
"Content-Type",
2726 std::string(dicomweb::media_type::dicom_json));
2729 if (!check_dicomweb_auth(ctx, req, res,
2730 {
"dicomweb.read",
"dicomweb.search"})) {
2734 if (!ctx->database) {
2736 res.body = make_error_json(
"DATABASE_UNAVAILABLE",
2737 "Database not configured");
2742 auto query = dicomweb::parse_study_query_params(req.raw_url);
2745 if (query.limit == 0) {
2750 auto studies_result = ctx->database->search_studies(query);
2751 if (!studies_result.is_ok()) {
2753 res.body = make_error_json(
"QUERY_ERROR",
2754 studies_result.error().message);
2759 std::ostringstream oss;
2763 for (
const auto& study : studies_result.value()) {
2764 if (!first) oss <<
",";
2768 std::string patient_id;
2769 std::string patient_name;
2770 if (
auto patient = ctx->database->find_patient_by_pk(study.patient_pk)) {
2771 patient_id = patient->patient_id;
2772 patient_name = patient->patient_name;
2775 oss << dicomweb::study_record_to_dicom_json(
2776 study, patient_id, patient_name);
2782 res.body = oss.str();
2787 CROW_ROUTE(app,
"/dicomweb/series")
2788 .methods(crow::HTTPMethod::GET)(
2789 [ctx](
const crow::request& req) {
2791 add_cors_headers(res, *ctx);
2792 res.add_header(
"Content-Type",
2793 std::string(dicomweb::media_type::dicom_json));
2795 if (!ctx->database) {
2797 res.body = make_error_json(
"DATABASE_UNAVAILABLE",
2798 "Database not configured");
2803 auto query = dicomweb::parse_series_query_params(req.raw_url);
2806 if (query.limit == 0) {
2811 auto series_list_result = ctx->database->search_series(query);
2812 if (!series_list_result.is_ok()) {
2814 res.body = make_error_json(
"QUERY_ERROR",
2815 series_list_result.error().message);
2820 std::ostringstream oss;
2824 for (
const auto& series : series_list_result.value()) {
2825 if (!first) oss <<
",";
2829 std::string study_uid;
2830 if (
auto study = ctx->database->find_study_by_pk(series.study_pk)) {
2831 study_uid = study->study_uid;
2834 oss << dicomweb::series_record_to_dicom_json(series, study_uid);
2840 res.body = oss.str();
2845 CROW_ROUTE(app,
"/dicomweb/instances")
2846 .methods(crow::HTTPMethod::GET)(
2847 [ctx](
const crow::request& req) {
2849 add_cors_headers(res, *ctx);
2850 res.add_header(
"Content-Type",
2851 std::string(dicomweb::media_type::dicom_json));
2853 if (!ctx->database) {
2855 res.body = make_error_json(
"DATABASE_UNAVAILABLE",
2856 "Database not configured");
2861 auto query = dicomweb::parse_instance_query_params(req.raw_url);
2864 if (query.limit == 0) {
2869 auto instances_result = ctx->database->search_instances(query);
2870 if (!instances_result.is_ok()) {
2872 res.body = make_error_json(
"QUERY_ERROR",
2873 instances_result.error().message);
2878 std::ostringstream oss;
2882 for (
const auto& instance : instances_result.value()) {
2883 if (!first) oss <<
",";
2887 std::string series_uid;
2888 std::string study_uid;
2889 if (
auto series = ctx->database->find_series_by_pk(instance.series_pk)) {
2890 series_uid = series->series_uid;
2891 if (
auto study = ctx->database->find_study_by_pk(series->study_pk)) {
2892 study_uid = study->study_uid;
2896 oss << dicomweb::instance_record_to_dicom_json(
2897 instance, series_uid, study_uid);
2903 res.body = oss.str();
2908 CROW_ROUTE(app,
"/dicomweb/studies/<string>/series")
2909 .methods(crow::HTTPMethod::GET)(
2910 [ctx](
const crow::request& req,
const std::string& study_uid) {
2912 add_cors_headers(res, *ctx);
2913 res.add_header(
"Content-Type",
2914 std::string(dicomweb::media_type::dicom_json));
2917 if (!check_dicomweb_auth(ctx, req, res,
2918 {
"dicomweb.read",
"dicomweb.search"})) {
2922 if (!ctx->database) {
2924 res.body = make_error_json(
"DATABASE_UNAVAILABLE",
2925 "Database not configured");
2930 auto study = ctx->database->find_study(study_uid);
2933 res.body = make_error_json(
"NOT_FOUND",
"Study not found");
2938 auto query = dicomweb::parse_series_query_params(req.raw_url);
2939 query.study_uid = study_uid;
2942 if (query.limit == 0) {
2947 auto series_list_result = ctx->database->search_series(query);
2948 if (!series_list_result.is_ok()) {
2950 res.body = make_error_json(
"QUERY_ERROR",
2951 series_list_result.error().message);
2956 std::ostringstream oss;
2960 for (
const auto& series : series_list_result.value()) {
2961 if (!first) oss <<
",";
2964 oss << dicomweb::series_record_to_dicom_json(series, study_uid);
2970 res.body = oss.str();
2975 CROW_ROUTE(app,
"/dicomweb/studies/<string>/instances")
2976 .methods(crow::HTTPMethod::GET)(
2977 [ctx](
const crow::request& req,
const std::string& study_uid) {
2979 add_cors_headers(res, *ctx);
2980 res.add_header(
"Content-Type",
2981 std::string(dicomweb::media_type::dicom_json));
2984 if (!check_dicomweb_auth(ctx, req, res,
2985 {
"dicomweb.read",
"dicomweb.search"})) {
2989 if (!ctx->database) {
2991 res.body = make_error_json(
"DATABASE_UNAVAILABLE",
2992 "Database not configured");
2997 auto study = ctx->database->find_study(study_uid);
3000 res.body = make_error_json(
"NOT_FOUND",
"Study not found");
3007 auto series_list_result = ctx->database->search_series(series_query);
3008 if (!series_list_result.is_ok()) {
3010 res.body = make_error_json(
"QUERY_ERROR",
3011 series_list_result.error().message);
3016 auto inst_query = dicomweb::parse_instance_query_params(req.raw_url);
3017 if (inst_query.limit == 0) {
3018 inst_query.limit = 100;
3022 std::ostringstream oss;
3029 for (
const auto& series : series_list_result.value()) {
3030 if (count >= inst_query.limit)
break;
3034 query.series_uid = series.series_uid;
3035 if (inst_query.sop_uid.has_value()) {
3036 query.sop_uid = inst_query.sop_uid;
3038 if (inst_query.sop_class_uid.has_value()) {
3039 query.sop_class_uid = inst_query.sop_class_uid;
3041 if (inst_query.instance_number.has_value()) {
3042 query.instance_number = inst_query.instance_number;
3044 query.limit = inst_query.limit - count;
3046 auto instances_result = ctx->database->search_instances(query);
3047 if (!instances_result.is_ok()) {
3050 for (
const auto& instance : instances_result.value()) {
3052 if (skipped < inst_query.offset) {
3057 if (count >= inst_query.limit)
break;
3059 if (!first) oss <<
",";
3062 oss << dicomweb::instance_record_to_dicom_json(
3063 instance, series.series_uid, study_uid);
3071 res.body = oss.str();
3076 CROW_ROUTE(app,
"/dicomweb/studies/<string>/series/<string>/instances")
3077 .methods(crow::HTTPMethod::GET)(
3078 [ctx](
const crow::request& req,
3079 const std::string& study_uid,
3080 const std::string& series_uid) {
3082 add_cors_headers(res, *ctx);
3083 res.add_header(
"Content-Type",
3084 std::string(dicomweb::media_type::dicom_json));
3087 if (!check_dicomweb_auth(ctx, req, res,
3088 {
"dicomweb.read",
"dicomweb.search"})) {
3092 if (!ctx->database) {
3094 res.body = make_error_json(
"DATABASE_UNAVAILABLE",
3095 "Database not configured");
3100 auto study = ctx->database->find_study(study_uid);
3103 res.body = make_error_json(
"NOT_FOUND",
"Study not found");
3108 auto series = ctx->database->find_series(series_uid);
3111 res.body = make_error_json(
"NOT_FOUND",
"Series not found");
3116 auto query = dicomweb::parse_instance_query_params(req.raw_url);
3117 query.series_uid = series_uid;
3120 if (query.limit == 0) {
3125 auto instances_result = ctx->database->search_instances(query);
3126 if (!instances_result.is_ok()) {
3128 res.body = make_error_json(
"QUERY_ERROR",
3129 instances_result.error().message);
3134 std::ostringstream oss;
3138 for (
const auto& instance : instances_result.value()) {
3139 if (!first) oss <<
",";
3142 oss << dicomweb::instance_record_to_dicom_json(
3143 instance, series_uid, study_uid);
3149 res.body = oss.str();
3157 CROW_ROUTE(app,
"/dicomweb/<path>")
3158 .methods(crow::HTTPMethod::OPTIONS)(
3159 [ctx](
const crow::request& ,
const std::string& ) {
3160 crow::response res(204);
3162 res.add_header(
"Access-Control-Allow-Origin",
3163 ctx->config->cors_allowed_origins);
3165 res.add_header(
"Access-Control-Allow-Methods",
3166 "GET, POST, OPTIONS");
3167 res.add_header(
"Access-Control-Allow-Headers",
3168 "Content-Type, Accept, Authorization");
3169 res.add_header(
"Access-Control-Max-Age",
"86400");