46 const char* study_uid,
47 const char* series_uid,
48 const char* object_uid,
49 const char* content_type,
50 const char* transfer_syntax,
51 const char* anonymize,
54 const char* window_center,
55 const char* window_width,
56 const char* frame_number) {
60 if (study_uid !=
nullptr) {
63 if (series_uid !=
nullptr) {
66 if (object_uid !=
nullptr) {
69 if (content_type !=
nullptr && content_type[0] !=
'\0') {
72 if (transfer_syntax !=
nullptr && transfer_syntax[0] !=
'\0') {
75 if (anonymize !=
nullptr) {
76 std::string anon_str(anonymize);
77 request.
anonymize = (anon_str ==
"yes" || anon_str ==
"true"
80 if (rows !=
nullptr) {
82 int val = std::stoi(rows);
83 if (val > 0 && val <= 65535) {
84 request.
rows =
static_cast<uint16_t
>(val);
90 if (columns !=
nullptr) {
92 int val = std::stoi(columns);
93 if (val > 0 && val <= 65535) {
94 request.
columns =
static_cast<uint16_t
>(val);
100 if (window_center !=
nullptr) {
107 if (window_width !=
nullptr) {
114 if (frame_number !=
nullptr) {
116 int val = std::stoi(frame_number);
131 400,
"MISSING_PARAMETER",
"studyUID parameter is required");
135 400,
"MISSING_PARAMETER",
"seriesUID parameter is required");
139 400,
"MISSING_PARAMETER",
"objectUID parameter is required");
143 406,
"UNSUPPORTED_MEDIA_TYPE",
150 return content_type ==
"application/dicom"
151 || content_type ==
"image/jpeg"
152 || content_type ==
"image/png"
153 || content_type ==
"application/dicom+json";
165void add_cors_headers(crow::response& res,
const rest_server_context& ctx) {
166 if (ctx.config !=
nullptr && !ctx.config->cors_allowed_origins.empty()) {
167 res.add_header(
"Access-Control-Allow-Origin",
168 ctx.config->cors_allowed_origins);
175std::vector<uint8_t> read_file_bytes(
const std::filesystem::path& path) {
176 std::ifstream file(path, std::ios::binary | std::ios::ate);
181 auto size = file.tellg();
182 file.seekg(0, std::ios::beg);
184 std::vector<uint8_t> buffer(
static_cast<size_t>(size));
185 if (!file.read(
reinterpret_cast<char*
>(buffer.data()), size)) {
195crow::response handle_dicom_response(
196 const std::string& file_path,
197 const rest_server_context& ctx) {
199 add_cors_headers(res, ctx);
201 auto data = read_file_bytes(file_path);
204 res.add_header(
"Content-Type",
"application/json");
210 res.add_header(
"Content-Type",
"application/dicom");
211 res.body = std::string(
reinterpret_cast<char*
>(data.data()), data.size());
218crow::response handle_rendered_response(
219 const std::string& file_path,
220 const wado_uri::wado_uri_request& request,
221 const rest_server_context& ctx) {
223 add_cors_headers(res, ctx);
225 dicomweb::rendered_params params;
227 if (request.content_type ==
"image/png") {
233 params.window_center = request.window_center;
234 params.window_width = request.window_width;
236 if (request.rows.has_value()) {
237 params.viewport_height = request.rows.value();
239 if (request.columns.has_value()) {
240 params.viewport_width = request.columns.value();
242 if (request.frame_number.has_value()) {
243 params.frame = request.frame_number.value();
248 if (!result.success) {
250 res.add_header(
"Content-Type",
"application/json");
256 res.add_header(
"Content-Type", result.content_type);
257 res.body = std::string(
258 reinterpret_cast<char*
>(result.data.data()),
266crow::response handle_dicom_json_response(
267 const std::string& file_path,
268 const rest_server_context& ctx) {
270 add_cors_headers(res, ctx);
272 auto data = read_file_bytes(file_path);
275 res.add_header(
"Content-Type",
"application/json");
281 std::span<const uint8_t>(data.data(), data.size()));
282 if (dicom_result.is_err()) {
284 res.add_header(
"Content-Type",
"application/json");
285 res.body =
make_error_json(
"PARSE_ERROR",
"Failed to parse DICOM file");
290 dicom_result.value().dataset(),
false,
"");
293 res.add_header(
"Content-Type",
"application/dicom+json");
294 res.body =
"[" + json +
"]";
301 crow::SimpleApp& app,
302 std::shared_ptr<rest_server_context> ctx) {
305 CROW_ROUTE(app,
"/wado")
306 .methods(crow::HTTPMethod::GET)(
307 [ctx](
const crow::request& req) {
309 add_cors_headers(res, *ctx);
312 if (ctx->oauth2 && ctx->oauth2->enabled()) {
313 auto auth = ctx->oauth2->authenticate(req, res);
314 if (!auth)
return res;
315 if (!ctx->oauth2->require_any_scope(
317 {
"dicomweb.read"})) {
323 auto request_type = req.url_params.get(
"requestType");
324 if (request_type ==
nullptr
325 || std::string(request_type) !=
"WADO") {
327 res.add_header(
"Content-Type",
"application/json");
329 "INVALID_REQUEST_TYPE",
330 "requestType must be 'WADO'");
335 if (!ctx->database) {
337 res.add_header(
"Content-Type",
"application/json");
339 "DATABASE_UNAVAILABLE",
"Database not configured");
345 req.url_params.get(
"studyUID"),
346 req.url_params.get(
"seriesUID"),
347 req.url_params.get(
"objectUID"),
348 req.url_params.get(
"contentType"),
349 req.url_params.get(
"transferSyntax"),
350 req.url_params.get(
"anonymize"),
351 req.url_params.get(
"rows"),
352 req.url_params.get(
"columns"),
353 req.url_params.get(
"windowCenter"),
354 req.url_params.get(
"windowWidth"),
355 req.url_params.get(
"frameNumber"));
359 if (!validation.valid) {
360 res.code = validation.http_status;
361 res.add_header(
"Content-Type",
"application/json");
363 validation.error_code, validation.error_message);
368 auto file_path_result = ctx->database->get_file_path(
369 wado_request.object_uid);
370 if (!file_path_result.is_ok()) {
372 res.add_header(
"Content-Type",
"application/json");
374 "QUERY_ERROR", file_path_result.error().message);
377 const auto& file_path = file_path_result.value();
380 res.add_header(
"Content-Type",
"application/json");
382 "NOT_FOUND",
"DICOM instance not found");
387 if (wado_request.content_type ==
"application/dicom") {
388 return handle_dicom_response(*file_path, *ctx);
390 if (wado_request.content_type ==
"image/jpeg"
391 || wado_request.content_type ==
"image/png") {
392 return handle_rendered_response(
393 *file_path, wado_request, *ctx);
395 if (wado_request.content_type ==
"application/dicom+json") {
396 return handle_dicom_json_response(*file_path, *ctx);
401 res.add_header(
"Content-Type",
"application/json");
403 "UNSUPPORTED_MEDIA_TYPE",
404 "Unsupported contentType: " + wado_request.content_type);
static auto from_bytes(std::span< const uint8_t > data) -> kcenon::pacs::Result< dicom_file >
Parse a DICOM file from raw bytes.
DICOM Part 10 file handling for reading/writing DICOM files.
DICOMweb (WADO-RS) API endpoints for REST server.
PACS index database for metadata storage and retrieval.
@ jpeg
JPEG format (default)
auto dataset_to_dicom_json(const core::dicom_dataset &dataset, bool include_bulk_data=false, std::string_view bulk_data_uri_prefix="") -> std::string
Convert a DICOM dataset to DicomJSON format.
auto render_dicom_image(std::string_view file_path, const rendered_params ¶ms) -> rendered_result
Render a DICOM image to JPEG or PNG.
void register_wado_uri_endpoints_impl(crow::SimpleApp &app, std::shared_ptr< rest_server_context > ctx)
validation_result validate_wado_uri_request(const wado_uri_request &request)
Validate a WADO-URI request.
wado_uri_request parse_wado_uri_params(const char *study_uid, const char *series_uid, const char *object_uid, const char *content_type, const char *transfer_syntax, const char *anonymize, const char *rows, const char *columns, const char *window_center, const char *window_width, const char *frame_number)
Parse WADO-URI query parameters from an HTTP request.
bool is_supported_content_type(std::string_view content_type)
Check if a content type is supported by WADO-URI.
std::string make_error_json(std::string_view code, std::string_view message)
Create JSON error response body with details.
OAuth 2.0 middleware for DICOMweb endpoint authorization.
Configuration for REST API server.
Common types and utilities for REST API.
Result of WADO-URI request validation.
static validation_result error(int status, std::string code, std::string message)
static validation_result ok()
Parsed WADO-URI request parameters.
std::optional< double > window_center
Window center for rendered images (optional)
std::optional< uint16_t > rows
Output viewport rows (optional, for rendered images)
std::optional< uint16_t > columns
Output viewport columns (optional, for rendered images)
std::string series_uid
Series Instance UID (required)
std::string content_type
Requested content type (default: application/dicom)
bool anonymize
Whether to anonymize the response (optional)
std::string object_uid
SOP Instance UID (required)
std::optional< uint32_t > frame_number
Frame number for multi-frame images (1-based, optional)
std::optional< double > window_width
Window width for rendered images (optional)
std::optional< std::string > transfer_syntax
Transfer Syntax UID for transcoding (optional)
std::string study_uid
Study Instance UID (required)
System API endpoints for REST server.
WADO-URI (Web Access to DICOM Objects — URI-based) API endpoints.