PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
dicomweb_endpoints.h
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
22#pragma once
23
24#include <cstdint>
25#include <memory>
26#include <optional>
27#include <span>
28#include <string>
29#include <string_view>
30#include <vector>
31
32namespace kcenon::pacs::core {
33class dicom_dataset;
34} // namespace kcenon::pacs::core
35
36namespace kcenon::pacs::storage {
37class index_database;
38struct study_record;
39struct series_record;
40struct instance_record;
41struct study_query;
42struct series_query;
43struct instance_query;
44} // namespace kcenon::pacs::storage
45
46namespace kcenon::pacs::web {
47
48struct rest_server_context;
49
50namespace dicomweb {
51
55struct media_type {
56 static constexpr std::string_view dicom = "application/dicom";
57 static constexpr std::string_view dicom_json = "application/dicom+json";
58 static constexpr std::string_view dicom_xml = "application/dicom+xml";
59 static constexpr std::string_view octet_stream = "application/octet-stream";
60 static constexpr std::string_view jpeg = "image/jpeg";
61 static constexpr std::string_view png = "image/png";
62 static constexpr std::string_view jphc = "image/jphc";
63 static constexpr std::string_view multipart_related = "multipart/related";
64};
65
70 std::string media_type;
71 std::string transfer_syntax;
72 float quality = 1.0f;
73};
74
80[[nodiscard]] auto parse_accept_header(std::string_view accept_header)
81 -> std::vector<accept_info>;
82
89[[nodiscard]] auto is_acceptable(const std::vector<accept_info>& accept_infos,
90 std::string_view media_type) -> bool;
91
99public:
104 explicit multipart_builder(
105 std::string_view content_type = media_type::dicom);
106
112 void add_part(std::vector<uint8_t> data,
113 std::optional<std::string_view> content_type = std::nullopt);
114
122 std::vector<uint8_t> data,
123 std::string_view location,
124 std::optional<std::string_view> content_type = std::nullopt);
125
130 [[nodiscard]] auto build() const -> std::string;
131
136 [[nodiscard]] auto content_type_header() const -> std::string;
137
142 [[nodiscard]] auto boundary() const -> std::string_view;
143
148 [[nodiscard]] auto empty() const noexcept -> bool;
149
154 [[nodiscard]] auto size() const noexcept -> size_t;
155
156private:
157 struct part {
158 std::vector<uint8_t> data;
159 std::string content_type;
160 std::string location;
161 };
162
163 std::string boundary_;
165 std::vector<part> parts_;
166
171 [[nodiscard]] static auto generate_boundary() -> std::string;
172};
173
181[[nodiscard]] auto dataset_to_dicom_json(
182 const core::dicom_dataset& dataset,
183 bool include_bulk_data = false,
184 std::string_view bulk_data_uri_prefix = "") -> std::string;
185
191[[nodiscard]] auto vr_to_string(uint16_t vr_code) -> std::string;
192
198[[nodiscard]] auto is_bulk_data_tag(uint32_t tag) -> bool;
199
200// ============================================================================
201// STOW-RS Support (Store Over the Web)
202// ============================================================================
203
210 std::string content_type;
211 std::string content_location;
212 std::string content_id;
213 std::vector<uint8_t> data;
214};
215
233public:
237 struct parse_error {
238 std::string code;
239 std::string message;
240 };
241
246 std::vector<multipart_part> parts;
247 std::optional<parse_error> error;
248
249 [[nodiscard]] bool success() const noexcept { return !error.has_value(); }
250 [[nodiscard]] explicit operator bool() const noexcept { return success(); }
251 };
252
259 [[nodiscard]] static auto parse(std::string_view content_type,
260 std::string_view body) -> parse_result;
261
267 [[nodiscard]] static auto extract_boundary(std::string_view content_type)
268 -> std::optional<std::string>;
269
275 [[nodiscard]] static auto extract_type(std::string_view content_type)
276 -> std::optional<std::string>;
277
278private:
284 [[nodiscard]] static auto parse_part_headers(std::string_view header_section)
285 -> std::vector<std::pair<std::string, std::string>>;
286};
287
292 bool success = false;
293 std::string sop_class_uid;
294 std::string sop_instance_uid;
295 std::string retrieve_url;
296 std::optional<std::string> error_code;
297 std::optional<std::string> error_message;
298};
299
304 std::vector<store_instance_result> referenced_instances;
305 std::vector<store_instance_result> failed_instances;
306
307 [[nodiscard]] bool all_success() const noexcept {
308 return failed_instances.empty() && !referenced_instances.empty();
309 }
310
311 [[nodiscard]] bool all_failed() const noexcept {
312 return referenced_instances.empty() && !failed_instances.empty();
313 }
314
315 [[nodiscard]] bool partial_success() const noexcept {
316 return !referenced_instances.empty() && !failed_instances.empty();
317 }
318};
319
324 bool valid = true;
325 std::string error_code;
326 std::string error_message;
327
328 [[nodiscard]] explicit operator bool() const noexcept { return valid; }
329
330 static validation_result ok() { return {true, "", ""}; }
331 static validation_result error(std::string code, std::string message) {
332 return {false, std::move(code), std::move(message)};
333 }
334};
335
342[[nodiscard]] auto validate_instance(
343 const core::dicom_dataset& dataset,
344 std::optional<std::string_view> target_study_uid = std::nullopt)
345 -> validation_result;
346
353[[nodiscard]] auto build_store_response_json(
354 const store_response& response,
355 std::string_view base_url) -> std::string;
356
357// ============================================================================
358// QIDO-RS Support (Query based on ID for DICOM Objects)
359// ============================================================================
360
368[[nodiscard]] auto study_record_to_dicom_json(
369 const storage::study_record& record,
370 std::string_view patient_id,
371 std::string_view patient_name) -> std::string;
372
379[[nodiscard]] auto series_record_to_dicom_json(
380 const storage::series_record& record,
381 std::string_view study_uid) -> std::string;
382
390[[nodiscard]] auto instance_record_to_dicom_json(
391 const storage::instance_record& record,
392 std::string_view series_uid,
393 std::string_view study_uid) -> std::string;
394
400[[nodiscard]] auto parse_study_query_params(
401 const std::string& url_params) -> storage::study_query;
402
408[[nodiscard]] auto parse_series_query_params(
409 const std::string& url_params) -> storage::series_query;
410
416[[nodiscard]] auto parse_instance_query_params(
417 const std::string& url_params) -> storage::instance_query;
418
419// ============================================================================
420// Frame Retrieval (WADO-RS)
421// ============================================================================
422
434[[nodiscard]] auto parse_frame_numbers(std::string_view frame_list)
435 -> std::vector<uint32_t>;
436
444[[nodiscard]] auto extract_frame(
445 std::span<const uint8_t> pixel_data,
446 uint32_t frame_number,
447 size_t frame_size) -> std::vector<uint8_t>;
448
449// ============================================================================
450// Rendered Images (WADO-RS)
451// ============================================================================
452
456enum class rendered_format {
457 jpeg,
458 png,
459 jphc
460};
461
468
470 int quality{75};
471
473 std::optional<double> window_center;
474
476 std::optional<double> window_width;
477
479 uint16_t viewport_width{0};
480
482 uint16_t viewport_height{0};
483
485 uint32_t frame{1};
486
488 std::optional<std::string> presentation_state_uid;
489
491 bool burn_annotations{false};
492};
493
500[[nodiscard]] auto parse_rendered_params(
501 std::string_view query_string,
502 std::string_view accept_header) -> rendered_params;
503
508 std::vector<uint8_t> data;
509 std::string content_type;
510 bool success{false};
511 std::string error_message;
512
513 [[nodiscard]] static rendered_result ok(
514 std::vector<uint8_t> d, std::string_view mime_type) {
515 return {std::move(d), std::string(mime_type), true, ""};
516 }
517
518 [[nodiscard]] static rendered_result error(std::string msg) {
519 return {{}, "", false, std::move(msg)};
520 }
521};
522
536[[nodiscard]] auto apply_window_level(
537 std::span<const uint8_t> pixel_data,
538 uint16_t width,
539 uint16_t height,
540 uint16_t bits_stored,
541 bool is_signed,
542 double window_center,
543 double window_width,
544 double rescale_slope = 1.0,
545 double rescale_intercept = 0.0) -> std::vector<uint8_t>;
546
553[[nodiscard]] auto render_dicom_image(
554 std::string_view file_path,
555 const rendered_params& params) -> rendered_result;
556
557} // namespace dicomweb
558
559namespace endpoints {
560
561// Internal function - implementation in cpp file
562// Registers DICOMweb endpoints with the Crow app
563// Called from rest_server.cpp
564
565} // namespace endpoints
566
567} // namespace kcenon::pacs::web
Builder for multipart MIME responses.
auto build() const -> std::string
Build the complete multipart response body.
auto boundary() const -> std::string_view
Get the boundary string.
auto content_type_header() const -> std::string
Get the Content-Type header value for this multipart response.
void add_part(std::vector< uint8_t > data, std::optional< std::string_view > content_type=std::nullopt)
Add a part to the multipart response.
static auto generate_boundary() -> std::string
Generate a unique boundary string.
auto empty() const noexcept -> bool
Check if any parts have been added.
void add_part_with_location(std::vector< uint8_t > data, std::string_view location, std::optional< std::string_view > content_type=std::nullopt)
Add a part with location header.
multipart_builder(std::string_view content_type=media_type::dicom)
Construct a multipart builder.
auto size() const noexcept -> size_t
Get the number of parts.
Parser for multipart/related request bodies.
static auto extract_type(std::string_view content_type) -> std::optional< std::string >
Extract type parameter from Content-Type header.
static auto extract_boundary(std::string_view content_type) -> std::optional< std::string >
Extract boundary from Content-Type header.
static auto parse_part_headers(std::string_view header_section) -> std::vector< std::pair< std::string, std::string > >
Parse headers from a part's header section.
static auto parse(std::string_view content_type, std::string_view body) -> parse_result
Parse a multipart/related request body.
auto study_record_to_dicom_json(const storage::study_record &record, std::string_view patient_id, std::string_view patient_name) -> std::string
Convert a study record to DicomJSON format for QIDO-RS response.
auto extract_frame(std::span< const uint8_t > pixel_data, uint32_t frame_number, size_t frame_size) -> std::vector< uint8_t >
Extract a single frame from pixel data.
auto parse_accept_header(std::string_view accept_header) -> std::vector< accept_info >
Parse Accept header into structured format.
auto series_record_to_dicom_json(const storage::series_record &record, std::string_view study_uid) -> std::string
Convert a series record to DicomJSON format for QIDO-RS response.
auto is_acceptable(const std::vector< accept_info > &accept_infos, std::string_view media_type) -> bool
Check if a media type is acceptable based on Accept header.
auto parse_rendered_params(std::string_view query_string, std::string_view accept_header) -> rendered_params
Parse rendered image parameters from HTTP request.
rendered_format
Rendered image output format.
auto build_store_response_json(const store_response &response, std::string_view base_url) -> std::string
Build STOW-RS response in DicomJSON format.
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 validate_instance(const core::dicom_dataset &dataset, std::optional< std::string_view > target_study_uid=std::nullopt) -> validation_result
Validate a DICOM instance for STOW-RS storage.
auto is_bulk_data_tag(uint32_t tag) -> bool
Check if a DICOM tag contains bulk data.
auto parse_frame_numbers(std::string_view frame_list) -> std::vector< uint32_t >
Parse frame numbers from URL path.
auto parse_instance_query_params(const std::string &url_params) -> storage::instance_query
Parse QIDO-RS instance query parameters from HTTP request.
auto instance_record_to_dicom_json(const storage::instance_record &record, std::string_view series_uid, std::string_view study_uid) -> std::string
Convert an instance record to DicomJSON format for QIDO-RS response.
auto apply_window_level(std::span< const uint8_t > pixel_data, uint16_t width, uint16_t height, uint16_t bits_stored, bool is_signed, double window_center, double window_width, double rescale_slope=1.0, double rescale_intercept=0.0) -> std::vector< uint8_t >
Apply window/level transformation to pixel data.
auto vr_to_string(uint16_t vr_code) -> std::string
Convert a VR type code to DicomJSON VR string.
auto parse_series_query_params(const std::string &url_params) -> storage::series_query
Parse QIDO-RS series query parameters from HTTP request.
auto render_dicom_image(std::string_view file_path, const rendered_params &params) -> rendered_result
Render a DICOM image to JPEG or PNG.
auto parse_study_query_params(const std::string &url_params) -> storage::study_query
Parse QIDO-RS query parameters from HTTP request.
std::string_view code
Instance record from the database.
Series record from the database.
Study record from the database.
Parsed Accept header information.
Media types supported by WADO-RS.
static constexpr std::string_view dicom_json
static constexpr std::string_view dicom
static constexpr std::string_view multipart_related
static constexpr std::string_view png
static constexpr std::string_view dicom_xml
static constexpr std::string_view octet_stream
static constexpr std::string_view jphc
static constexpr std::string_view jpeg
std::string code
Error code (e.g., "INVALID_BOUNDARY")
std::optional< parse_error > error
Error if parsing failed.
std::vector< multipart_part > parts
Parsed parts (empty on error)
Parsed part from multipart request.
std::string content_id
Content-ID header (optional)
std::string content_type
Content-Type of this part.
std::vector< uint8_t > data
Binary data of this part.
std::string content_location
Content-Location header (optional)
Parameters for rendered image requests.
uint16_t viewport_width
Output viewport width (0 = original size)
std::optional< double > window_center
Window center (default: auto from DICOM or calculated)
std::optional< double > window_width
Window width (default: auto from DICOM or calculated)
uint32_t frame
Frame number for multi-frame images (1-based, default 1)
int quality
JPEG quality (1-100, default 75)
bool burn_annotations
Annotation (burned-in or removed)
std::optional< std::string > presentation_state_uid
Presentation state SOP Instance UID (optional)
uint16_t viewport_height
Output viewport height (0 = original size)
Result of rendered image operation.
std::string error_message
Error message if failed.
std::vector< uint8_t > data
Encoded image data.
std::string content_type
MIME type (image/jpeg or image/png)
static rendered_result ok(std::vector< uint8_t > d, std::string_view mime_type)
static rendered_result error(std::string msg)
STOW-RS store result for a single instance.
std::optional< std::string > error_code
Error code if failed.
std::string sop_class_uid
SOP Class UID of the instance.
std::optional< std::string > error_message
Error message if failed.
std::string retrieve_url
URL to retrieve the stored instance.
std::string sop_instance_uid
SOP Instance UID of the instance.
std::vector< store_instance_result > referenced_instances
Successfully stored.
std::vector< store_instance_result > failed_instances
Failed to store.
Validation result for DICOM instance.
std::string error_code
Error code if invalid.
static validation_result error(std::string code, std::string message)
std::string error_message
Error message if invalid.