PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
kcenon::pacs::core::dicom_file Class Reference

#include <dicom_file.h>

Collaboration diagram for kcenon::pacs::core::dicom_file:
Collaboration graph

Public Member Functions

auto save (const std::filesystem::path &path) const -> kcenon::pacs::VoidResult
 Save the DICOM file to disk.
 
auto to_bytes () const -> std::vector< uint8_t >
 Encode the DICOM file to raw bytes.
 
auto meta_information () const noexcept -> const dicom_dataset &
 Get read-only access to the File Meta Information.
 
auto meta_information () noexcept -> dicom_dataset &
 Get mutable access to the File Meta Information.
 
auto dataset () const noexcept -> const dicom_dataset &
 Get read-only access to the main dataset.
 
auto dataset () noexcept -> dicom_dataset &
 Get mutable access to the main dataset.
 
auto transfer_syntax () const -> encoding::transfer_syntax
 Get the Transfer Syntax of this file.
 
auto sop_class_uid () const -> std::string
 Get the SOP Class UID.
 
auto sop_instance_uid () const -> std::string
 Get the SOP Instance UID.
 
 dicom_file ()=default
 Default constructor - creates an empty file.
 
 dicom_file (const dicom_file &)=default
 Copy constructor.
 
 dicom_file (dicom_file &&) noexcept=default
 Move constructor.
 
auto operator= (const dicom_file &) -> dicom_file &=default
 Copy assignment.
 
auto operator= (dicom_file &&) noexcept -> dicom_file &=default
 Move assignment.
 
 ~dicom_file ()=default
 Destructor.
 

Static Public Member Functions

static auto open (const std::filesystem::path &path) -> kcenon::pacs::Result< dicom_file >
 Open and read a DICOM file from disk.
 
static auto from_bytes (std::span< const uint8_t > data) -> kcenon::pacs::Result< dicom_file >
 Parse a DICOM file from raw bytes.
 
static auto create (dicom_dataset dataset, const encoding::transfer_syntax &ts) -> dicom_file
 Create a new DICOM file from a dataset.
 

Private Member Functions

 dicom_file (dicom_dataset meta_info, dicom_dataset main_dataset)
 Private constructor for internal use.
 

Static Private Member Functions

static auto parse_meta_information (std::span< const uint8_t > data, size_t &bytes_read) -> kcenon::pacs::Result< dicom_dataset >
 Parse file meta information from raw data.
 
static auto generate_meta_information (const dicom_dataset &dataset, const encoding::transfer_syntax &ts) -> dicom_dataset
 Generate File Meta Information for a dataset.
 
static auto encode_explicit_vr_le (const dicom_dataset &dataset) -> std::vector< uint8_t >
 Encode a dataset using Explicit VR Little Endian.
 
static auto decode_explicit_vr_le (std::span< const uint8_t > data, size_t &bytes_read) -> kcenon::pacs::Result< dicom_dataset >
 Decode a dataset from Explicit VR Little Endian format.
 
static auto decode_implicit_vr_le (std::span< const uint8_t > data, size_t &bytes_read) -> kcenon::pacs::Result< dicom_dataset >
 Decode a dataset from Implicit VR Little Endian format.
 
static auto decode_explicit_vr_be (std::span< const uint8_t > data, size_t &bytes_read) -> kcenon::pacs::Result< dicom_dataset >
 Decode a dataset from Explicit VR Big Endian format.
 
static auto encode_implicit_vr_le (const dicom_dataset &dataset) -> std::vector< uint8_t >
 Encode a dataset using Implicit VR Little Endian.
 
static auto encode_explicit_vr_be (const dicom_dataset &dataset) -> std::vector< uint8_t >
 Encode a dataset using Explicit VR Big Endian.
 
static auto decode_dataset (std::span< const uint8_t > data, const encoding::transfer_syntax &ts, size_t &bytes_read) -> kcenon::pacs::Result< dicom_dataset >
 Decode a dataset based on its Transfer Syntax.
 
static auto encode_dataset (const dicom_dataset &dataset, const encoding::transfer_syntax &ts) -> std::vector< uint8_t >
 Encode a dataset based on its Transfer Syntax.
 
static auto parse_undefined_length_sequence (std::span< const uint8_t > data, size_t &bytes_read, bool explicit_vr, bool big_endian) -> kcenon::pacs::Result< std::vector< dicom_dataset > >
 Parse a sequence with undefined length.
 
static auto parse_encapsulated_frames (std::span< const uint8_t > data) -> std::vector< std::vector< uint8_t > >
 Parse encapsulated pixel data frames.
 

Private Attributes

dicom_dataset meta_info_
 File Meta Information (Group 0002)
 
dicom_dataset dataset_
 Main dataset (encoded per Transfer Syntax)
 

Static Private Attributes

static constexpr std::string_view kImplementationClassUid
 Implementation Class UID for this library.
 
static constexpr std::string_view kImplementationVersionName = "PACS_SYS_001"
 Implementation Version Name.
 
static constexpr uint8_t kDicmPrefix [4] = {'D', 'I', 'C', 'M'}
 DICOM magic bytes.
 
static constexpr size_t kPreambleSize = 128
 Preamble size.
 

Detailed Description

Examples
module_example/main.cpp.

Definition at line 67 of file dicom_file.h.

Constructor & Destructor Documentation

◆ dicom_file() [1/4]

kcenon::pacs::core::dicom_file::dicom_file ( )
default

Default constructor - creates an empty file.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/core/dicom_file.h.

◆ dicom_file() [2/4]

kcenon::pacs::core::dicom_file::dicom_file ( const dicom_file & )
default

Copy constructor.

◆ dicom_file() [3/4]

kcenon::pacs::core::dicom_file::dicom_file ( dicom_file && )
defaultnoexcept

Move constructor.

◆ ~dicom_file()

kcenon::pacs::core::dicom_file::~dicom_file ( )
default

◆ dicom_file() [4/4]

kcenon::pacs::core::dicom_file::dicom_file ( dicom_dataset meta_info,
dicom_dataset main_dataset )
private

Private constructor for internal use.

Definition at line 142 of file dicom_file.cpp.

143 : meta_info_(std::move(meta_info)), dataset_(std::move(main_dataset)) {}
dicom_dataset meta_info_
File Meta Information (Group 0002)
Definition dicom_file.h:343
dicom_dataset dataset_
Main dataset (encoded per Transfer Syntax)
Definition dicom_file.h:346

Member Function Documentation

◆ create()

auto kcenon::pacs::core::dicom_file::create ( dicom_dataset dataset,
const encoding::transfer_syntax & ts ) -> dicom_file
staticnodiscard

Create a new DICOM file from a dataset.

Parameters
datasetThe main dataset (SOP Class UID and Instance UID required)
tsThe transfer syntax to use for encoding
Returns
A new dicom_file with auto-generated File Meta Information

The File Meta Information will be automatically generated with:

  • (0002,0001) File Meta Information Version = 0x00, 0x01
  • (0002,0002) Media Storage SOP Class UID from dataset
  • (0002,0003) Media Storage SOP Instance UID from dataset
  • (0002,0010) Transfer Syntax UID
  • (0002,0012) Implementation Class UID
  • (0002,0013) Implementation Version Name
Examples
dcm_dir/main.cpp, get_scu/main.cpp, and retrieve_scu/main.cpp.

Definition at line 232 of file dicom_file.cpp.

234 {
235 auto meta_info = generate_meta_information(dataset, ts);
236 return dicom_file{std::move(meta_info), std::move(dataset)};
237}
auto dataset() const noexcept -> const dicom_dataset &
Get read-only access to the main dataset.
static auto generate_meta_information(const dicom_dataset &dataset, const encoding::transfer_syntax &ts) -> dicom_dataset
Generate File Meta Information for a dataset.
Transfer Syntax UIDs.
Definition main.cpp:78

Referenced by kcenon::pacs::storage::file_storage::store(), kcenon::pacs::storage::azure_blob_storage::store_with_progress(), and kcenon::pacs::storage::s3_storage::store_with_progress().

Here is the caller graph for this function:

◆ dataset() [1/2]

auto kcenon::pacs::core::dicom_file::dataset ( ) const -> const dicom_dataset&
nodiscardnoexcept

Get read-only access to the main dataset.

Returns
Const reference to the main dataset
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/core/dicom_file.h, and module_example/main.cpp.

Definition at line 300 of file dicom_file.cpp.

300 {
301 return dataset_;
302}

References dataset_.

◆ dataset() [2/2]

auto kcenon::pacs::core::dicom_file::dataset ( ) -> dicom_dataset&
nodiscardnoexcept

Get mutable access to the main dataset.

Returns
Reference to the main dataset

Definition at line 304 of file dicom_file.cpp.

304 {
305 return dataset_;
306}

References dataset_.

◆ decode_dataset()

auto kcenon::pacs::core::dicom_file::decode_dataset ( std::span< const uint8_t > data,
const encoding::transfer_syntax & ts,
size_t & bytes_read ) -> kcenon::pacs::Result<dicom_dataset>
staticnodiscardprivate

Decode a dataset based on its Transfer Syntax.

Parameters
dataThe raw byte data
tsThe Transfer Syntax to use for decoding
bytes_readOutput parameter for bytes consumed
Returns
Result containing the decoded dataset or error
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/core/dicom_file.h.

Definition at line 993 of file dicom_file.cpp.

996 {
997
998 // Route to appropriate decoder based on Transfer Syntax properties
999 if (ts.vr_type() == encoding::vr_encoding::implicit) {
1000 // Implicit VR Little Endian (only implicit is always LE)
1001 return decode_implicit_vr_le(data, bytes_read);
1002 }
1003
1004 if (ts.endianness() == encoding::byte_order::big_endian) {
1005 // Explicit VR Big Endian
1006 return decode_explicit_vr_be(data, bytes_read);
1007 }
1008
1009 // Explicit VR Little Endian (default and compressed transfer syntaxes)
1010 // Note: For compressed TS, pixel data is handled specially but
1011 // other elements are still Explicit VR LE
1012 return decode_explicit_vr_le(data, bytes_read);
1013}
static auto decode_explicit_vr_be(std::span< const uint8_t > data, size_t &bytes_read) -> kcenon::pacs::Result< dicom_dataset >
Decode a dataset from Explicit VR Big Endian format.
static auto decode_implicit_vr_le(std::span< const uint8_t > data, size_t &bytes_read) -> kcenon::pacs::Result< dicom_dataset >
Decode a dataset from Implicit VR Little Endian format.
static auto decode_explicit_vr_le(std::span< const uint8_t > data, size_t &bytes_read) -> kcenon::pacs::Result< dicom_dataset >
Decode a dataset from Explicit VR Little Endian format.
@ big_endian
Most significant byte first (legacy, rarely used)
@ implicit
VR determined from data dictionary lookup.

References kcenon::pacs::encoding::big_endian, and kcenon::pacs::encoding::implicit.

◆ decode_explicit_vr_be()

auto kcenon::pacs::core::dicom_file::decode_explicit_vr_be ( std::span< const uint8_t > data,
size_t & bytes_read ) -> kcenon::pacs::Result<dicom_dataset>
staticnodiscardprivate

Decode a dataset from Explicit VR Big Endian format.

Parameters
dataThe raw byte data
bytes_readOutput parameter for bytes consumed
Returns
Result containing the decoded dataset or error
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/core/dicom_file.h.

Definition at line 765 of file dicom_file.cpp.

767 {
768 dicom_dataset dataset;
769 size_t offset = 0;
770
771 while (offset + 8 <= data.size()) {
772 // Read tag (4 bytes, big-endian)
773 const uint16_t group = read_uint16_be(data.subspan(offset, 2));
774 const uint16_t element = read_uint16_be(data.subspan(offset + 2, 2));
775 const dicom_tag tag{group, element};
776
777 // Check for item/sequence delimiters
778 if (group == kItemTagGroup) {
779 break;
780 }
781
782 // Read VR (2 bytes ASCII)
783 if (offset + 6 > data.size()) {
784 break;
785 }
786
787 const char vr_chars[3] = {
788 static_cast<char>(data[offset + 4]),
789 static_cast<char>(data[offset + 5]),
790 '\0'
791 };
792 const auto vr_opt = encoding::from_string(std::string_view(vr_chars, 2));
793 // Unknown VR: fallback to UN per PS3.5 §6.2.2 (uses 4-byte length)
794 const auto vr = vr_opt.value_or(encoding::vr_type::UN);
795
796 // Determine length field size
797 uint32_t length = 0;
798 size_t header_size = 0;
799
801 if (offset + 12 > data.size()) {
802 break;
803 }
804 // Skip 2 reserved bytes, then read 4-byte length (big-endian)
805 length = read_uint32_be(data.subspan(offset + 8, 4));
806 header_size = 12;
807 } else {
808 if (offset + 8 > data.size()) {
809 break;
810 }
811 length = read_uint16_be(data.subspan(offset + 6, 2));
812 header_size = 8;
813 }
814
815 // Handle undefined length
816 if (length == kUndefinedLength) {
817 if (vr == encoding::vr_type::SQ) {
818 // Parse undefined length sequence
819 size_t seq_bytes_read = 0;
820 auto seq_result = parse_undefined_length_sequence(
821 data.subspan(offset + header_size),
822 seq_bytes_read, true, true);
823
824 if (seq_result.is_ok()) {
825 // Create sequence element with parsed items
826 dicom_element seq_elem{tag, vr};
827 for (auto& item : seq_result.value()) {
828 seq_elem.add_sequence_item(std::move(item));
829 }
830 dataset.insert(std::move(seq_elem));
831 }
832 offset += header_size + seq_bytes_read;
833 continue;
834 }
835 else if (tag == tags::pixel_data) {
836 // Encapsulated pixel data (rare for big endian but handle it)
837 size_t pixel_start = offset + header_size;
838 size_t scan_offset = pixel_start;
839
840 while (scan_offset + 8 <= data.size()) {
841 uint16_t g = read_uint16_be(data.subspan(scan_offset, 2));
842 uint16_t e = read_uint16_be(data.subspan(scan_offset + 2, 2));
843
844 if (g == kItemTagGroup && e == kSequenceDelimitationElement) {
845 scan_offset += 8;
846 break;
847 }
848
849 if (g == kItemTagGroup && e == kItemTagElement) {
850 scan_offset += 4;
851 if (scan_offset + 4 > data.size()) break;
852 uint32_t frag_len = read_uint32_be(data.subspan(scan_offset, 4));
853 scan_offset += 4 + frag_len;
854 } else {
855 break;
856 }
857 }
858
859 size_t encap_length = scan_offset - pixel_start;
860 auto pixel_span = data.subspan(pixel_start, encap_length);
861 dicom_element pixel_elem{tag, encoding::vr_type::OB, pixel_span};
862 dataset.insert(std::move(pixel_elem));
863
864 offset = scan_offset;
865 continue;
866 }
867 else {
868 break;
869 }
870 }
871
872 // Validate value bounds
873 if (offset + header_size + length > data.size()) {
874 break;
875 }
876
877 // Read value - need to swap bytes for numeric types
878 const auto value_data = data.subspan(offset + header_size, length);
879 std::vector<uint8_t> swapped_data(value_data.begin(), value_data.end());
880
881 // Swap bytes for numeric VRs
883 const size_t element_size = encoding::fixed_length(vr);
884 if (element_size == 2) {
885 for (size_t i = 0; i + 1 < swapped_data.size(); i += 2) {
886 std::swap(swapped_data[i], swapped_data[i + 1]);
887 }
888 } else if (element_size == 4) {
889 for (size_t i = 0; i + 3 < swapped_data.size(); i += 4) {
890 std::swap(swapped_data[i], swapped_data[i + 3]);
891 std::swap(swapped_data[i + 1], swapped_data[i + 2]);
892 }
893 } else if (element_size == 8) {
894 for (size_t i = 0; i + 7 < swapped_data.size(); i += 8) {
895 std::swap(swapped_data[i], swapped_data[i + 7]);
896 std::swap(swapped_data[i + 1], swapped_data[i + 6]);
897 std::swap(swapped_data[i + 2], swapped_data[i + 5]);
898 std::swap(swapped_data[i + 3], swapped_data[i + 4]);
899 }
900 }
901 }
902
903 dicom_element elem{tag, vr, std::span<const uint8_t>(swapped_data)};
904 dataset.insert(std::move(elem));
905
906 offset += header_size + length;
907 }
908
909 bytes_read = offset;
911}
static auto parse_undefined_length_sequence(std::span< const uint8_t > data, size_t &bytes_read, bool explicit_vr, bool big_endian) -> kcenon::pacs::Result< std::vector< dicom_dataset > >
Parse a sequence with undefined length.
constexpr dicom_tag pixel_data
Pixel Data.
constexpr std::size_t fixed_length(vr_type vr) noexcept
Gets the fixed size of a VR if applicable.
Definition vr_type.h:260
constexpr bool is_numeric_vr(vr_type vr) noexcept
Checks if a VR is a numeric type.
Definition vr_type.h:214
@ OB
Other Byte (variable length)
@ UN
Unknown (variable length)
@ SQ
Sequence of Items (undefined length)
constexpr std::optional< vr_type > from_string(std::string_view str) noexcept
Parses a two-character string to a vr_type.
Definition vr_type.h:132
constexpr bool has_explicit_32bit_length(vr_type vr) noexcept
Checks if a VR requires 32-bit length field in Explicit VR encoding.
Definition vr_type.h:235
@ length
Linear distance measurement.
vr_encoding vr

References kcenon::pacs::encoding::fixed_length(), kcenon::pacs::encoding::from_string(), kcenon::pacs::encoding::has_explicit_32bit_length(), kcenon::pacs::core::dicom_dataset::insert(), kcenon::pacs::encoding::is_numeric_vr(), kcenon::pacs::encoding::OB, kcenon::pacs::core::tags::pixel_data, kcenon::pacs::encoding::SQ, kcenon::pacs::encoding::UN, and vr.

Here is the call graph for this function:

◆ decode_explicit_vr_le()

auto kcenon::pacs::core::dicom_file::decode_explicit_vr_le ( std::span< const uint8_t > data,
size_t & bytes_read ) -> kcenon::pacs::Result<dicom_dataset>
staticnodiscardprivate

Decode a dataset from Explicit VR Little Endian format.

Parameters
dataThe raw byte data
bytes_readOutput parameter for bytes consumed
Returns
Result containing the decoded dataset or error
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/core/dicom_file.h.

Definition at line 500 of file dicom_file.cpp.

502 {
503 dicom_dataset dataset;
504 size_t offset = 0;
505
506 while (offset + 8 <= data.size()) { // Minimum element size
507 // Read tag (4 bytes)
508 const uint16_t group = read_uint16_le(data.subspan(offset, 2));
509 const uint16_t element = read_uint16_le(data.subspan(offset + 2, 2));
510 const dicom_tag tag{group, element};
511
512 // Skip item delimiter tags
513 if (group == 0xFFFE) {
514 break;
515 }
516
517 // Read VR (2 bytes)
518 if (offset + 6 > data.size()) {
519 break;
520 }
521
522 const char vr_chars[3] = {
523 static_cast<char>(data[offset + 4]),
524 static_cast<char>(data[offset + 5]),
525 '\0'
526 };
527 const auto vr_opt = encoding::from_string(std::string_view(vr_chars, 2));
528 // Unknown VR: fallback to UN per PS3.5 §6.2.2 (uses 4-byte length)
529 const auto vr = vr_opt.value_or(encoding::vr_type::UN);
530
531 // Determine length field size
532 uint32_t length = 0;
533 size_t header_size = 0;
534
536 if (offset + 12 > data.size()) {
537 break;
538 }
539 length = read_uint32_le(data.subspan(offset + 8, 4));
540 header_size = 12;
541 } else {
542 if (offset + 8 > data.size()) {
543 break;
544 }
545 length = read_uint16_le(data.subspan(offset + 6, 2));
546 header_size = 8;
547 }
548
549 // Handle undefined length (0xFFFFFFFF)
550 if (length == kUndefinedLength) {
551 if (vr == encoding::vr_type::SQ) {
552 // Parse undefined length sequence
553 size_t seq_bytes_read = 0;
554 auto seq_result = parse_undefined_length_sequence(
555 data.subspan(offset + header_size),
556 seq_bytes_read, true, false);
557
558 if (seq_result.is_ok()) {
559 // Create sequence element with parsed items
560 dicom_element seq_elem{tag, vr};
561 for (auto& item : seq_result.value()) {
562 seq_elem.add_sequence_item(std::move(item));
563 }
564 dataset.insert(std::move(seq_elem));
565 }
566 offset += header_size + seq_bytes_read;
567 continue;
568 }
569 else if (tag == tags::pixel_data) {
570 // Encapsulated pixel data - find sequence delimitation and store raw
571 size_t pixel_start = offset + header_size;
572 size_t scan_offset = pixel_start;
573
574 // Scan for Sequence Delimitation Item
575 while (scan_offset + 8 <= data.size()) {
576 uint16_t g = read_uint16_le(data.subspan(scan_offset, 2));
577 uint16_t e = read_uint16_le(data.subspan(scan_offset + 2, 2));
578
579 if (g == kItemTagGroup && e == kSequenceDelimitationElement) {
580 scan_offset += 8; // Include delimitation item
581 break;
582 }
583
584 // Skip item tag and length
585 if (g == kItemTagGroup && e == kItemTagElement) {
586 scan_offset += 4;
587 if (scan_offset + 4 > data.size()) break;
588 uint32_t frag_len = read_uint32_le(data.subspan(scan_offset, 4));
589 scan_offset += 4 + frag_len;
590 } else {
591 break; // Unexpected tag
592 }
593 }
594
595 // Store encapsulated data (OB VR for compressed pixel data)
596 size_t encap_length = scan_offset - pixel_start;
597 auto pixel_span = data.subspan(pixel_start, encap_length);
598 dicom_element pixel_elem{tag, encoding::vr_type::OB, pixel_span};
599 dataset.insert(std::move(pixel_elem));
600
601 offset = scan_offset;
602 continue;
603 }
604 else {
605 // Unknown undefined length element - try to find delimiter
606 break;
607 }
608 }
609
610 // Validate value bounds
611 if (offset + header_size + length > data.size()) {
612 break;
613 }
614
615 // Read value and create element
616 const auto value_data = data.subspan(offset + header_size, length);
617 dicom_element elem{tag, vr, value_data};
618 dataset.insert(std::move(elem));
619
620 offset += header_size + length;
621 }
622
623 bytes_read = offset;
625}

References kcenon::pacs::encoding::from_string(), kcenon::pacs::encoding::has_explicit_32bit_length(), kcenon::pacs::core::dicom_dataset::insert(), kcenon::pacs::encoding::OB, kcenon::pacs::core::tags::pixel_data, kcenon::pacs::encoding::SQ, kcenon::pacs::encoding::UN, and vr.

Here is the call graph for this function:

◆ decode_implicit_vr_le()

auto kcenon::pacs::core::dicom_file::decode_implicit_vr_le ( std::span< const uint8_t > data,
size_t & bytes_read ) -> kcenon::pacs::Result<dicom_dataset>
staticnodiscardprivate

Decode a dataset from Implicit VR Little Endian format.

Parameters
dataThe raw byte data
bytes_readOutput parameter for bytes consumed
Returns
Result containing the decoded dataset or error

Uses dicom_dictionary for VR lookup since VR is not encoded in data.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/core/dicom_file.h.

Definition at line 627 of file dicom_file.cpp.

629 {
630 dicom_dataset dataset;
631 size_t offset = 0;
632 const auto& dict = dicom_dictionary::instance();
633
634 while (offset + 8 <= data.size()) {
635 // Read tag (4 bytes, little-endian)
636 const uint16_t group = read_uint16_le(data.subspan(offset, 2));
637 const uint16_t element = read_uint16_le(data.subspan(offset + 2, 2));
638 const dicom_tag tag{group, element};
639
640 // Check for item/sequence delimiters
641 if (group == kItemTagGroup) {
642 if (element == kSequenceDelimitationElement ||
643 element == kItemDelimitationElement) {
644 break;
645 }
646 }
647
648 // Read length (4 bytes in Implicit VR)
649 if (offset + 8 > data.size()) {
650 break;
651 }
652 uint32_t length = read_uint32_le(data.subspan(offset + 4, 4));
653
654 // Look up VR from dictionary
655 auto tag_info = dict.find(tag);
657 if (tag_info) {
658 auto vr_opt = encoding::from_string(
659 encoding::to_string(static_cast<encoding::vr_type>(tag_info->vr)));
660 if (vr_opt) {
661 vr = *vr_opt;
662 }
663 }
664
665 // For private data elements without dictionary entry, try creator-based lookup
666 if (vr == encoding::vr_type::UN && tag.is_private_data()) {
667 auto creator = dataset.get_private_creator(tag);
668 if (creator) {
669 auto block_num = tag.private_block_number();
670 if (block_num) {
671 uint8_t elem_offset = static_cast<uint8_t>(
672 tag.element() & 0x00FF);
673 auto def = private_tag_registry::instance().find(
674 *creator, elem_offset);
675 if (def) {
676 vr = def->vr;
677 }
678 }
679 }
680 }
681
682 // Private Creator elements are always LO
683 if (tag.is_private_creator()) {
685 }
686
687 constexpr size_t kImplicitHeaderSize = 8; // 4 bytes tag + 4 bytes length
688
689 // Handle undefined length
690 if (length == kUndefinedLength) {
691 if (vr == encoding::vr_type::SQ) {
692 // Parse undefined length sequence
693 size_t seq_bytes_read = 0;
694 auto seq_result = parse_undefined_length_sequence(
695 data.subspan(offset + kImplicitHeaderSize),
696 seq_bytes_read, false, false);
697
698 if (seq_result.is_ok()) {
699 // Create sequence element with parsed items
700 dicom_element seq_elem{tag, vr};
701 for (auto& item : seq_result.value()) {
702 seq_elem.add_sequence_item(std::move(item));
703 }
704 dataset.insert(std::move(seq_elem));
705 }
706 offset += kImplicitHeaderSize + seq_bytes_read;
707 continue;
708 }
709 else if (tag == tags::pixel_data) {
710 // Encapsulated pixel data - find sequence delimitation and store raw
711 size_t pixel_start = offset + kImplicitHeaderSize;
712 size_t scan_offset = pixel_start;
713
714 // Scan for Sequence Delimitation Item
715 while (scan_offset + 8 <= data.size()) {
716 uint16_t g = read_uint16_le(data.subspan(scan_offset, 2));
717 uint16_t e = read_uint16_le(data.subspan(scan_offset + 2, 2));
718
719 if (g == kItemTagGroup && e == kSequenceDelimitationElement) {
720 scan_offset += 8;
721 break;
722 }
723
724 if (g == kItemTagGroup && e == kItemTagElement) {
725 scan_offset += 4;
726 if (scan_offset + 4 > data.size()) break;
727 uint32_t frag_len = read_uint32_le(data.subspan(scan_offset, 4));
728 scan_offset += 4 + frag_len;
729 } else {
730 break;
731 }
732 }
733
734 size_t encap_length = scan_offset - pixel_start;
735 auto pixel_span = data.subspan(pixel_start, encap_length);
736 dicom_element pixel_elem{tag, encoding::vr_type::OB, pixel_span};
737 dataset.insert(std::move(pixel_elem));
738
739 offset = scan_offset;
740 continue;
741 }
742 else {
743 // Unknown undefined length element
744 break;
745 }
746 }
747
748 // Validate value bounds
749 if (offset + kImplicitHeaderSize + length > data.size()) {
750 break;
751 }
752
753 // Read value and create element
754 const auto value_data = data.subspan(offset + kImplicitHeaderSize, length);
755 dicom_element elem{tag, vr, value_data};
756 dataset.insert(std::move(elem));
757
758 offset += kImplicitHeaderSize + length;
759 }
760
761 bytes_read = offset;
763}
static auto instance() -> dicom_dictionary &
Get the singleton instance.
static auto instance() -> private_tag_registry &
Get the singleton instance.
vr_type
DICOM Value Representation (VR) types.
Definition vr_type.h:29
@ LO
Long String (64 chars max)
constexpr std::string_view to_string(vr_type vr) noexcept
Converts a vr_type to its two-character string representation.
Definition vr_type.h:83

References kcenon::pacs::encoding::from_string(), kcenon::pacs::core::dicom_dataset::get_private_creator(), kcenon::pacs::core::dicom_dataset::insert(), kcenon::pacs::core::dicom_dictionary::instance(), kcenon::pacs::core::private_tag_registry::instance(), kcenon::pacs::encoding::LO, kcenon::pacs::encoding::OB, kcenon::pacs::core::tags::pixel_data, kcenon::pacs::encoding::SQ, kcenon::pacs::encoding::to_string(), kcenon::pacs::encoding::UN, kcenon::pacs::core::tag_info::vr, and vr.

Here is the call graph for this function:

◆ encode_dataset()

auto kcenon::pacs::core::dicom_file::encode_dataset ( const dicom_dataset & dataset,
const encoding::transfer_syntax & ts ) -> std::vector<uint8_t>
staticnodiscardprivate

Encode a dataset based on its Transfer Syntax.

Parameters
datasetThe dataset to encode
tsThe Transfer Syntax to use for encoding
Returns
Encoded byte data
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/core/dicom_file.h.

Definition at line 1015 of file dicom_file.cpp.

1017 {
1018
1019 // Route to appropriate encoder based on Transfer Syntax properties
1020 if (ts.vr_type() == encoding::vr_encoding::implicit) {
1022 }
1023
1024 if (ts.endianness() == encoding::byte_order::big_endian) {
1026 }
1027
1028 // Explicit VR Little Endian (default)
1030}
static auto encode_implicit_vr_le(const dicom_dataset &dataset) -> std::vector< uint8_t >
Encode a dataset using Implicit VR Little Endian.
static auto encode_explicit_vr_le(const dicom_dataset &dataset) -> std::vector< uint8_t >
Encode a dataset using Explicit VR Little Endian.
static auto encode_explicit_vr_be(const dicom_dataset &dataset) -> std::vector< uint8_t >
Encode a dataset using Explicit VR Big Endian.

References kcenon::pacs::encoding::big_endian, and kcenon::pacs::encoding::implicit.

Referenced by to_bytes().

Here is the caller graph for this function:

◆ encode_explicit_vr_be()

auto kcenon::pacs::core::dicom_file::encode_explicit_vr_be ( const dicom_dataset & dataset) -> std::vector<uint8_t>
staticnodiscardprivate

Encode a dataset using Explicit VR Big Endian.

Parameters
datasetThe dataset to encode
Returns
Encoded byte data
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/core/dicom_file.h.

Definition at line 934 of file dicom_file.cpp.

935 {
936 std::vector<uint8_t> result;
937 result.reserve(65536);
938
939 for (const auto& [tag, element] : dataset) {
940 // Write tag (4 bytes, big-endian)
941 write_uint16_be(result, tag.group());
942 write_uint16_be(result, tag.element());
943
944 // Write VR (2 bytes ASCII)
945 const auto vr_str = encoding::to_string(element.vr());
946 result.push_back(static_cast<uint8_t>(vr_str[0]));
947 result.push_back(static_cast<uint8_t>(vr_str[1]));
948
949 const auto& raw_data = element.raw_data();
950 const auto length = static_cast<uint32_t>(raw_data.size());
951
952 if (encoding::has_explicit_32bit_length(element.vr())) {
953 // 2 reserved bytes + 4 byte length (big-endian)
954 result.push_back(0x00);
955 result.push_back(0x00);
956 write_uint32_be(result, length);
957 } else {
958 // 2 byte length (big-endian)
959 write_uint16_be(result, static_cast<uint16_t>(length));
960 }
961
962 // Write value - swap bytes for numeric types
963 if (encoding::is_numeric_vr(element.vr())) {
964 const size_t element_size = encoding::fixed_length(element.vr());
965 std::vector<uint8_t> swapped_data(raw_data.begin(), raw_data.end());
966
967 if (element_size == 2) {
968 for (size_t i = 0; i + 1 < swapped_data.size(); i += 2) {
969 std::swap(swapped_data[i], swapped_data[i + 1]);
970 }
971 } else if (element_size == 4) {
972 for (size_t i = 0; i + 3 < swapped_data.size(); i += 4) {
973 std::swap(swapped_data[i], swapped_data[i + 3]);
974 std::swap(swapped_data[i + 1], swapped_data[i + 2]);
975 }
976 } else if (element_size == 8) {
977 for (size_t i = 0; i + 7 < swapped_data.size(); i += 8) {
978 std::swap(swapped_data[i], swapped_data[i + 7]);
979 std::swap(swapped_data[i + 1], swapped_data[i + 6]);
980 std::swap(swapped_data[i + 2], swapped_data[i + 5]);
981 std::swap(swapped_data[i + 3], swapped_data[i + 4]);
982 }
983 }
984 result.insert(result.end(), swapped_data.begin(), swapped_data.end());
985 } else {
986 result.insert(result.end(), raw_data.begin(), raw_data.end());
987 }
988 }
989
990 return result;
991}

References kcenon::pacs::encoding::fixed_length(), kcenon::pacs::encoding::has_explicit_32bit_length(), kcenon::pacs::encoding::is_numeric_vr(), and kcenon::pacs::encoding::to_string().

Here is the call graph for this function:

◆ encode_explicit_vr_le()

auto kcenon::pacs::core::dicom_file::encode_explicit_vr_le ( const dicom_dataset & dataset) -> std::vector<uint8_t>
staticnodiscardprivate

Encode a dataset using Explicit VR Little Endian.

Parameters
datasetThe dataset to encode
Returns
Encoded byte data
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/core/dicom_file.h.

Definition at line 465 of file dicom_file.cpp.

466 {
467 std::vector<uint8_t> result;
468 result.reserve(65536); // Initial estimate
469
470 for (const auto& [tag, element] : dataset) {
471 // Write tag (4 bytes, little-endian)
472 write_uint16_le(result, tag.group());
473 write_uint16_le(result, tag.element());
474
475 // Write VR (2 bytes)
476 const auto vr_str = encoding::to_string(element.vr());
477 result.push_back(static_cast<uint8_t>(vr_str[0]));
478 result.push_back(static_cast<uint8_t>(vr_str[1]));
479
480 const auto& raw_data = element.raw_data();
481 const auto length = static_cast<uint32_t>(raw_data.size());
482
483 if (encoding::has_explicit_32bit_length(element.vr())) {
484 // 2 reserved bytes + 4 byte length
485 result.push_back(0x00);
486 result.push_back(0x00);
487 write_uint32_le(result, length);
488 } else {
489 // 2 byte length
490 write_uint16_le(result, static_cast<uint16_t>(length));
491 }
492
493 // Write value
494 result.insert(result.end(), raw_data.begin(), raw_data.end());
495 }
496
497 return result;
498}

References kcenon::pacs::encoding::has_explicit_32bit_length(), and kcenon::pacs::encoding::to_string().

Referenced by to_bytes().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ encode_implicit_vr_le()

auto kcenon::pacs::core::dicom_file::encode_implicit_vr_le ( const dicom_dataset & dataset) -> std::vector<uint8_t>
staticnodiscardprivate

Encode a dataset using Implicit VR Little Endian.

Parameters
datasetThe dataset to encode
Returns
Encoded byte data
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/core/dicom_file.h.

Definition at line 913 of file dicom_file.cpp.

914 {
915 std::vector<uint8_t> result;
916 result.reserve(65536);
917
918 for (const auto& [tag, element] : dataset) {
919 // Write tag (4 bytes, little-endian)
920 write_uint16_le(result, tag.group());
921 write_uint16_le(result, tag.element());
922
923 // Write length (4 bytes in Implicit VR)
924 const auto& raw_data = element.raw_data();
925 write_uint32_le(result, static_cast<uint32_t>(raw_data.size()));
926
927 // Write value
928 result.insert(result.end(), raw_data.begin(), raw_data.end());
929 }
930
931 return result;
932}

◆ from_bytes()

auto kcenon::pacs::core::dicom_file::from_bytes ( std::span< const uint8_t > data) -> kcenon::pacs::Result<dicom_file>
staticnodiscard

Parse a DICOM file from raw bytes.

Parameters
dataRaw byte data of the DICOM file
Returns
Result containing the parsed file or an error
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/core/dicom_file.h.

Definition at line 166 of file dicom_file.cpp.

167 {
168 // Minimum size: 128 (preamble) + 4 (DICM) + minimal meta info
169 if (data.size() < kPreambleSize + 4) {
172 "File too small to be valid DICOM Part 10 file");
173 }
174
175 // Check for DICM prefix at offset 128
176 const auto prefix = data.subspan(kPreambleSize, 4);
177 if (std::memcmp(prefix.data(), kDicmPrefix, 4) != 0) {
180 "Missing DICM prefix at offset 128");
181 }
182
183 // Parse File Meta Information (starts after preamble + DICM)
184 const auto meta_start = data.subspan(kPreambleSize + 4);
185 size_t meta_bytes_read = 0;
186
187 auto meta_result = parse_meta_information(meta_start, meta_bytes_read);
188 if (meta_result.is_err()) {
189 return kcenon::pacs::Result<dicom_file>::err(meta_result.error());
190 }
191
192 // Extract Transfer Syntax from meta information
193 const auto* ts_elem = meta_result.value().get(tags::transfer_syntax_uid);
194 if (ts_elem == nullptr) {
197 "Transfer Syntax UID not found in meta information");
198 }
199
200 auto ts_uid_result = ts_elem->as_string();
201 if (ts_uid_result.is_err()) {
204 "Failed to read Transfer Syntax UID");
205 }
206 const auto ts_uid = ts_uid_result.value();
207 encoding::transfer_syntax ts{ts_uid};
208
209 if (!ts.is_valid()) {
212 "Unsupported Transfer Syntax: " + ts_uid);
213 }
214
215 // Parse main dataset using appropriate decoder based on Transfer Syntax
216 const auto dataset_start = meta_start.subspan(meta_bytes_read);
217 size_t dataset_bytes_read = 0;
218
219 auto dataset_result = decode_dataset(dataset_start, ts, dataset_bytes_read);
220 if (dataset_result.is_err()) {
221 return kcenon::pacs::Result<dicom_file>::err(dataset_result.error());
222 }
223
225 dicom_file{std::move(meta_result.value()), std::move(dataset_result.value())});
226}
static constexpr size_t kPreambleSize
Preamble size.
Definition dicom_file.h:359
static auto decode_dataset(std::span< const uint8_t > data, const encoding::transfer_syntax &ts, size_t &bytes_read) -> kcenon::pacs::Result< dicom_dataset >
Decode a dataset based on its Transfer Syntax.
static constexpr uint8_t kDicmPrefix[4]
DICOM magic bytes.
Definition dicom_file.h:356
static auto parse_meta_information(std::span< const uint8_t > data, size_t &bytes_read) -> kcenon::pacs::Result< dicom_dataset >
Parse file meta information from raw data.
constexpr dicom_tag transfer_syntax_uid
Transfer Syntax UID.
constexpr int missing_dicm_prefix
Definition result.h:63
constexpr int value_conversion_error
Definition result.h:70
constexpr int invalid_dicom_file
Definition result.h:62
constexpr int missing_transfer_syntax
Definition result.h:65
constexpr int unsupported_transfer_syntax
Definition result.h:66
Result< T > pacs_error(int code, const std::string &message, const std::string &details="")
Create a PACS error result with module context.
Definition result.h:234

References kcenon::pacs::error_codes::invalid_dicom_file, kcenon::pacs::error_codes::missing_dicm_prefix, kcenon::pacs::error_codes::missing_transfer_syntax, kcenon::pacs::pacs_error(), kcenon::pacs::core::tags::transfer_syntax_uid, kcenon::pacs::error_codes::unsupported_transfer_syntax, and kcenon::pacs::error_codes::value_conversion_error.

Referenced by kcenon::pacs::storage::azure_blob_storage::find(), kcenon::pacs::storage::s3_storage::find(), kcenon::pacs::storage::azure_blob_storage::rebuild_index(), kcenon::pacs::storage::s3_storage::rebuild_index(), kcenon::pacs::web::dicomweb::render_dicom_image(), kcenon::pacs::storage::azure_blob_storage::retrieve_with_progress(), and kcenon::pacs::storage::s3_storage::retrieve_with_progress().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ generate_meta_information()

auto kcenon::pacs::core::dicom_file::generate_meta_information ( const dicom_dataset & dataset,
const encoding::transfer_syntax & ts ) -> dicom_dataset
staticnodiscardprivate

Generate File Meta Information for a dataset.

Parameters
datasetThe main dataset
tsThe transfer syntax
Returns
The generated meta information dataset
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/core/dicom_file.h.

Definition at line 412 of file dicom_file.cpp.

414 {
415 dicom_dataset meta_info;
416
417 // (0002,0001) File Meta Information Version
418 const uint8_t version_bytes[] = {0x00, 0x01};
419 meta_info.insert(dicom_element{
422 std::span<const uint8_t>(version_bytes, 2)
423 });
424
425 // (0002,0002) Media Storage SOP Class UID
426 const auto sop_class = dataset.get_string(tags::sop_class_uid);
427 meta_info.set_string(
430 sop_class
431 );
432
433 // (0002,0003) Media Storage SOP Instance UID
434 const auto sop_instance = dataset.get_string(tags::sop_instance_uid);
435 meta_info.set_string(
438 sop_instance
439 );
440
441 // (0002,0010) Transfer Syntax UID
442 meta_info.set_string(
445 std::string(ts.uid())
446 );
447
448 // (0002,0012) Implementation Class UID
449 meta_info.set_string(
452 std::string(kImplementationClassUid)
453 );
454
455 // (0002,0013) Implementation Version Name
456 meta_info.set_string(
459 std::string(kImplementationVersionName)
460 );
461
462 return meta_info;
463}
static constexpr std::string_view kImplementationVersionName
Implementation Version Name.
Definition dicom_file.h:353
static constexpr std::string_view kImplementationClassUid
Implementation Class UID for this library.
Definition dicom_file.h:349
constexpr dicom_tag implementation_version_name
Implementation Version Name.
constexpr dicom_tag sop_instance_uid
SOP Instance UID.
constexpr dicom_tag file_meta_information_version
File Meta Information Version.
constexpr dicom_tag implementation_class_uid
Implementation Class UID.
constexpr dicom_tag media_storage_sop_class_uid
Media Storage SOP Class UID.
constexpr dicom_tag sop_class_uid
SOP Class UID.
constexpr dicom_tag media_storage_sop_instance_uid
Media Storage SOP Instance UID.
@ UI
Unique Identifier (64 chars max)
@ SH
Short String (16 chars max)

References kcenon::pacs::core::tags::file_meta_information_version, kcenon::pacs::core::tags::implementation_class_uid, kcenon::pacs::core::tags::implementation_version_name, kcenon::pacs::core::dicom_dataset::insert(), kcenon::pacs::core::tags::media_storage_sop_class_uid, kcenon::pacs::core::tags::media_storage_sop_instance_uid, kcenon::pacs::encoding::OB, kcenon::pacs::core::dicom_dataset::set_string(), kcenon::pacs::encoding::SH, kcenon::pacs::core::tags::sop_class_uid, kcenon::pacs::core::tags::sop_instance_uid, kcenon::pacs::core::tags::transfer_syntax_uid, and kcenon::pacs::encoding::UI.

Here is the call graph for this function:

◆ meta_information() [1/2]

auto kcenon::pacs::core::dicom_file::meta_information ( ) const -> const dicom_dataset&
nodiscardnoexcept

Get read-only access to the File Meta Information.

Returns
Const reference to the meta information dataset
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/core/dicom_file.h.

Definition at line 292 of file dicom_file.cpp.

292 {
293 return meta_info_;
294}

References meta_info_.

◆ meta_information() [2/2]

auto kcenon::pacs::core::dicom_file::meta_information ( ) -> dicom_dataset&
nodiscardnoexcept

Get mutable access to the File Meta Information.

Returns
Reference to the meta information dataset

Definition at line 296 of file dicom_file.cpp.

296 {
297 return meta_info_;
298}

References meta_info_.

◆ open()

auto kcenon::pacs::core::dicom_file::open ( const std::filesystem::path & path) -> kcenon::pacs::Result<dicom_file>
staticnodiscard

Open and read a DICOM file from disk.

Parameters
pathPath to the DICOM file
Returns
Result containing the parsed file or an error
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/core/dicom_file.h.

Definition at line 149 of file dicom_file.cpp.

150 {
151 // Try memory-mapped I/O first for better performance
152 auto mmap_result = memory_mapped_file::open(path);
153 if (mmap_result.is_ok()) {
154 return from_bytes(mmap_result.value().as_span());
155 }
156
157 // Fall back to traditional file I/O
158 auto contents = read_file_contents(path);
159 if (contents.is_err()) {
160 return kcenon::pacs::Result<dicom_file>::err(contents.error());
161 }
162
163 return from_bytes(contents.value());
164}
static auto from_bytes(std::span< const uint8_t > data) -> kcenon::pacs::Result< dicom_file >
Parse a DICOM file from raw bytes.
static auto open(const std::filesystem::path &path) -> kcenon::pacs::Result< memory_mapped_file >
Open and memory-map a file for reading.

References kcenon::pacs::core::memory_mapped_file::open().

Referenced by kcenon::pacs::storage::file_storage::find(), kcenon::pacs::web::metadata_service::get_frame_info(), kcenon::pacs::web::metadata_service::get_sorted_instances(), kcenon::pacs::storage::file_storage::get_statistics(), kcenon::pacs::web::metadata_service::get_voi_lut(), kcenon::pacs::example::pacs_server_app::handle_retrieve(), kcenon::pacs::storage::file_storage::import_directory(), kcenon::pacs::web::metadata_service::read_dicom_tags(), kcenon::pacs::storage::file_storage::rebuild_index(), kcenon::pacs::storage::file_storage::retrieve(), kcenon::pacs::services::storage_scu::store_file(), and kcenon::pacs::storage::file_storage::verify_integrity().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ operator=() [1/2]

auto kcenon::pacs::core::dicom_file::operator= ( const dicom_file & ) -> dicom_file &=default
default

◆ operator=() [2/2]

auto kcenon::pacs::core::dicom_file::operator= ( dicom_file && ) -> dicom_file &=default
defaultnoexcept

Move assignment.

◆ parse_encapsulated_frames()

auto kcenon::pacs::core::dicom_file::parse_encapsulated_frames ( std::span< const uint8_t > data) -> std::vector<std::vector<uint8_t>>
staticnodiscardprivate

Parse encapsulated pixel data frames.

Parameters
dataThe raw encapsulated pixel data
Returns
Vector of frame data
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/core/dicom_file.h.

Definition at line 1119 of file dicom_file.cpp.

1120 {
1121
1122 std::vector<std::vector<uint8_t>> frames;
1123 size_t offset = 0;
1124
1125 // First item is Basic Offset Table (may be empty)
1126 if (offset + 8 <= data.size()) {
1127 const uint16_t group = read_uint16_le(data.subspan(offset, 2));
1128 const uint16_t element = read_uint16_le(data.subspan(offset + 2, 2));
1129
1130 if (group == kItemTagGroup && element == kItemTagElement) {
1131 uint32_t bot_length = read_uint32_le(data.subspan(offset + 4, 4));
1132 offset += 8 + bot_length; // Skip BOT
1133 }
1134 }
1135
1136 // Parse fragment items
1137 while (offset + 8 <= data.size()) {
1138 const uint16_t group = read_uint16_le(data.subspan(offset, 2));
1139 const uint16_t element = read_uint16_le(data.subspan(offset + 2, 2));
1140
1141 // Check for sequence delimitation
1142 if (group == kItemTagGroup && element == kSequenceDelimitationElement) {
1143 break;
1144 }
1145
1146 // Must be an item tag
1147 if (group != kItemTagGroup || element != kItemTagElement) {
1148 break;
1149 }
1150
1151 uint32_t fragment_length = read_uint32_le(data.subspan(offset + 4, 4));
1152 offset += 8;
1153
1154 if (offset + fragment_length > data.size()) {
1155 break;
1156 }
1157
1158 // Copy fragment data
1159 std::vector<uint8_t> fragment(
1160 data.begin() + static_cast<std::ptrdiff_t>(offset),
1161 data.begin() + static_cast<std::ptrdiff_t>(offset + fragment_length));
1162 frames.push_back(std::move(fragment));
1163
1164 offset += fragment_length;
1165 }
1166
1167 return frames;
1168}

◆ parse_meta_information()

auto kcenon::pacs::core::dicom_file::parse_meta_information ( std::span< const uint8_t > data,
size_t & bytes_read ) -> kcenon::pacs::Result<dicom_dataset>
staticnodiscardprivate

Parse file meta information from raw data.

Parameters
dataRaw byte data starting at the meta information
bytes_readOutput parameter for bytes consumed
Returns
Result containing the parsed meta info or error
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/core/dicom_file.h.

Definition at line 329 of file dicom_file.cpp.

331 {
332 // File Meta Information is always encoded as Explicit VR Little Endian
333 // First, we need to find the group length to know how much to read
334
335 dicom_dataset meta_info;
336 size_t offset = 0;
337
338 // Parse elements until we leave group 0x0002 or run out of data
339 while (offset + 8 <= data.size()) { // Minimum element size: 8 bytes
340 // Read tag (4 bytes)
341 const uint16_t group = read_uint16_le(data.subspan(offset, 2));
342 const uint16_t element = read_uint16_le(data.subspan(offset + 2, 2));
343 const dicom_tag tag{group, element};
344
345 // Stop if we've left the meta information group
346 if (group != 0x0002) {
347 break;
348 }
349
350 // Read VR (2 bytes)
351 if (offset + 6 > data.size()) {
354 "Unexpected end of data while reading VR");
355 }
356
357 const char vr_chars[3] = {
358 static_cast<char>(data[offset + 4]),
359 static_cast<char>(data[offset + 5]),
360 '\0'
361 };
362 const auto vr_opt = encoding::from_string(std::string_view(vr_chars, 2));
363 if (!vr_opt) {
366 "Invalid VR: " + std::string(vr_chars, 2));
367 }
368 const auto vr = *vr_opt;
369
370 // Determine length field size based on VR
371 uint32_t length = 0;
372 size_t header_size = 0;
373
375 // VR + 2 reserved bytes + 4 byte length
376 if (offset + 12 > data.size()) {
379 "Unexpected end of data while reading length field");
380 }
381 length = read_uint32_le(data.subspan(offset + 8, 4));
382 header_size = 12;
383 } else {
384 // VR + 2 byte length
385 if (offset + 8 > data.size()) {
388 "Unexpected end of data while reading length field");
389 }
390 length = read_uint16_le(data.subspan(offset + 6, 2));
391 header_size = 8;
392 }
393
394 // Validate and read value
395 if (offset + header_size + length > data.size()) {
398 "Value length exceeds available data");
399 }
400
401 const auto value_data = data.subspan(offset + header_size, length);
402 dicom_element elem{tag, vr, value_data};
403 meta_info.insert(std::move(elem));
404
405 offset += header_size + length;
406 }
407
408 bytes_read = offset;
409 return kcenon::pacs::Result<dicom_dataset>::ok(std::move(meta_info));
410}
constexpr int decode_error
Definition result.h:76

References kcenon::pacs::error_codes::decode_error, kcenon::pacs::encoding::from_string(), kcenon::pacs::encoding::has_explicit_32bit_length(), kcenon::pacs::core::dicom_dataset::insert(), kcenon::pacs::pacs_error(), and vr.

Here is the call graph for this function:

◆ parse_undefined_length_sequence()

auto kcenon::pacs::core::dicom_file::parse_undefined_length_sequence ( std::span< const uint8_t > data,
size_t & bytes_read,
bool explicit_vr,
bool big_endian ) -> kcenon::pacs::Result<std::vector<dicom_dataset>>
staticnodiscardprivate

Parse a sequence with undefined length.

Parameters
dataThe raw byte data starting at sequence value
bytes_readOutput parameter for bytes consumed
explicit_vrWhether to use explicit VR parsing
big_endianWhether data is big endian
Returns
Result containing the sequence items or error
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/core/dicom_file.h.

Definition at line 1032 of file dicom_file.cpp.

1035 {
1036
1037 std::vector<dicom_dataset> items;
1038 size_t offset = 0;
1039
1040 // Helper function pointers for reading based on endianness
1041 auto read_u16 = big_endian ? read_uint16_be : read_uint16_le;
1042 auto read_u32 = big_endian ? read_uint32_be : read_uint32_le;
1043
1044 while (offset + 8 <= data.size()) {
1045 // Read item tag
1046 const uint16_t group = read_u16(data.subspan(offset, 2));
1047 const uint16_t element = read_u16(data.subspan(offset + 2, 2));
1048
1049 // Check for sequence delimitation item
1050 if (group == kItemTagGroup && element == kSequenceDelimitationElement) {
1051 // Read length (should be 0)
1052 offset += 8;
1053 break;
1054 }
1055
1056 // Must be an item tag (FFFE,E000)
1057 if (group != kItemTagGroup || element != kItemTagElement) {
1058 break; // Unexpected tag
1059 }
1060
1061 // Read item length
1062 uint32_t item_length = read_u32(data.subspan(offset + 4, 4));
1063 offset += 8;
1064
1065 if (item_length == kUndefinedLength) {
1066 // Item with undefined length - find item delimitation tag
1067 size_t item_end = offset;
1068 while (item_end + 8 <= data.size()) {
1069 uint16_t g = read_u16(data.subspan(item_end, 2));
1070 uint16_t e = read_u16(data.subspan(item_end + 2, 2));
1071 if (g == kItemTagGroup && e == kItemDelimitationElement) {
1072 break;
1073 }
1074 // Simple skip - move forward by element
1075 // This is a simplified approach; full implementation would parse elements
1076 item_end += 4;
1077 if (item_end + 4 > data.size()) break;
1078 uint32_t len = read_u32(data.subspan(item_end, 4));
1079 item_end += 4;
1080 if (len != kUndefinedLength) {
1081 item_end += len;
1082 }
1083 }
1084 // Parse item content
1085 size_t item_bytes_read = 0;
1086 auto item_data = data.subspan(offset, item_end - offset);
1088 ? (big_endian ? decode_explicit_vr_be(item_data, item_bytes_read)
1089 : decode_explicit_vr_le(item_data, item_bytes_read))
1090 : decode_implicit_vr_le(item_data, item_bytes_read);
1091
1092 if (item_result.is_ok()) {
1093 items.push_back(std::move(item_result.value()));
1094 }
1095 offset = item_end + 8; // Skip delimitation item
1096 } else {
1097 // Item with defined length
1098 if (offset + item_length > data.size()) {
1099 break;
1100 }
1101 size_t item_bytes_read = 0;
1102 auto item_data = data.subspan(offset, item_length);
1104 ? (big_endian ? decode_explicit_vr_be(item_data, item_bytes_read)
1105 : decode_explicit_vr_le(item_data, item_bytes_read))
1106 : decode_implicit_vr_le(item_data, item_bytes_read);
1107
1108 if (item_result.is_ok()) {
1109 items.push_back(std::move(item_result.value()));
1110 }
1111 offset += item_length;
1112 }
1113 }
1114
1115 bytes_read = offset;
1116 return kcenon::pacs::Result<std::vector<dicom_dataset>>::ok(std::move(items));
1117}
@ explicit_vr
VR explicitly encoded in the data stream.

◆ save()

auto kcenon::pacs::core::dicom_file::save ( const std::filesystem::path & path) const -> kcenon::pacs::VoidResult
nodiscard

Save the DICOM file to disk.

Parameters
pathDestination file path
Returns
Result indicating success or failure
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/core/dicom_file.h.

Definition at line 243 of file dicom_file.cpp.

244 {
245 auto bytes = to_bytes();
246
247 std::ofstream file(path, std::ios::binary);
248 if (!file) {
251 "Failed to open file for writing: " + path.string());
252 }
253
254 if (!file.write(reinterpret_cast<const char*>(bytes.data()),
255 static_cast<std::streamsize>(bytes.size()))) {
258 "Failed to write to file: " + path.string());
259 }
260
261 return kcenon::pacs::ok();
262}
auto to_bytes() const -> std::vector< uint8_t >
Encode the DICOM file to raw bytes.
constexpr int file_write_error
Definition result.h:61
VoidResult pacs_void_error(int code, const std::string &message, const std::string &details="")
Create a PACS void error result.
Definition result.h:249

References kcenon::pacs::error_codes::file_write_error, and kcenon::pacs::pacs_void_error().

Here is the call graph for this function:

◆ sop_class_uid()

auto kcenon::pacs::core::dicom_file::sop_class_uid ( ) const -> std::string
nodiscard

Get the SOP Class UID.

Returns
The SOP Class UID from the main dataset
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/core/dicom_file.h.

Definition at line 317 of file dicom_file.cpp.

317 {
319}
auto get_string(dicom_tag tag, std::string_view default_value="") const -> std::string
Get the string value of an element.

References dataset_, kcenon::pacs::core::dicom_dataset::get_string(), and kcenon::pacs::core::tags::sop_class_uid.

Here is the call graph for this function:

◆ sop_instance_uid()

auto kcenon::pacs::core::dicom_file::sop_instance_uid ( ) const -> std::string
nodiscard

Get the SOP Instance UID.

Returns
The SOP Instance UID from the main dataset
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/core/dicom_file.h.

Definition at line 321 of file dicom_file.cpp.

321 {
323}

References dataset_, kcenon::pacs::core::dicom_dataset::get_string(), and kcenon::pacs::core::tags::sop_instance_uid.

Here is the call graph for this function:

◆ to_bytes()

auto kcenon::pacs::core::dicom_file::to_bytes ( ) const -> std::vector<uint8_t>
nodiscard

Encode the DICOM file to raw bytes.

Returns
Vector containing the encoded file data
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/core/dicom_file.h.

Definition at line 264 of file dicom_file.cpp.

264 {
265 std::vector<uint8_t> result;
266
267 // Reserve estimated size
268 result.reserve(kPreambleSize + 4 + 512 + 65536);
269
270 // Write 128-byte preamble (zeros)
271 result.resize(kPreambleSize, 0);
272
273 // Write DICM prefix
274 result.insert(result.end(), std::begin(kDicmPrefix), std::end(kDicmPrefix));
275
276 // Encode and write File Meta Information (always Explicit VR LE)
277 auto meta_bytes = encode_explicit_vr_le(meta_info_);
278 result.insert(result.end(), meta_bytes.begin(), meta_bytes.end());
279
280 // Encode and write main dataset using appropriate Transfer Syntax
281 auto ts = transfer_syntax();
282 auto dataset_bytes = encode_dataset(dataset_, ts);
283 result.insert(result.end(), dataset_bytes.begin(), dataset_bytes.end());
284
285 return result;
286}
static auto encode_dataset(const dicom_dataset &dataset, const encoding::transfer_syntax &ts) -> std::vector< uint8_t >
Encode a dataset based on its Transfer Syntax.
auto transfer_syntax() const -> encoding::transfer_syntax
Get the Transfer Syntax of this file.

References dataset_, encode_dataset(), encode_explicit_vr_le(), kDicmPrefix, kPreambleSize, meta_info_, and transfer_syntax().

Here is the call graph for this function:

◆ transfer_syntax()

auto kcenon::pacs::core::dicom_file::transfer_syntax ( ) const -> encoding::transfer_syntax
nodiscard

Get the Transfer Syntax of this file.

Returns
The transfer syntax (from File Meta Information)
Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/core/dicom_file.h.

Definition at line 312 of file dicom_file.cpp.

312 {
314 return encoding::transfer_syntax{ts_uid};
315}

References kcenon::pacs::core::dicom_dataset::get_string(), meta_info_, and kcenon::pacs::core::tags::transfer_syntax_uid.

Referenced by to_bytes().

Here is the call graph for this function:
Here is the caller graph for this function:

Member Data Documentation

◆ dataset_

dicom_dataset kcenon::pacs::core::dicom_file::dataset_
private

Main dataset (encoded per Transfer Syntax)

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/core/dicom_file.h.

Definition at line 346 of file dicom_file.h.

Referenced by dataset(), dataset(), sop_class_uid(), sop_instance_uid(), and to_bytes().

◆ kDicmPrefix

uint8_t kcenon::pacs::core::dicom_file::kDicmPrefix[4] = {'D', 'I', 'C', 'M'}
staticconstexprprivate

DICOM magic bytes.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/core/dicom_file.h.

Definition at line 356 of file dicom_file.h.

356{'D', 'I', 'C', 'M'};

Referenced by to_bytes().

◆ kImplementationClassUid

std::string_view kcenon::pacs::core::dicom_file::kImplementationClassUid
staticconstexprprivate
Initial value:
=
"1.2.826.0.1.3680043.8.1055.1"

Implementation Class UID for this library.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/core/dicom_file.h.

Definition at line 349 of file dicom_file.h.

◆ kImplementationVersionName

std::string_view kcenon::pacs::core::dicom_file::kImplementationVersionName = "PACS_SYS_001"
staticconstexprprivate

Implementation Version Name.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/core/dicom_file.h.

Definition at line 353 of file dicom_file.h.

◆ kPreambleSize

size_t kcenon::pacs::core::dicom_file::kPreambleSize = 128
staticconstexprprivate

Preamble size.

Examples
/home/runner/work/pacs_system/pacs_system/include/kcenon/pacs/core/dicom_file.h.

Definition at line 359 of file dicom_file.h.

Referenced by to_bytes().

◆ meta_info_

dicom_dataset kcenon::pacs::core::dicom_file::meta_info_
private

The documentation for this class was generated from the following files: