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
Represents a DICOM Dataset (ordered collection of Data Elements)
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.
std::string content_type
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
Query parameters for instance search.
Instance record from the database.
Query parameters for series search.
Series record from the database.
Query parameters for study search.
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.