├── .gitignore ├── tests ├── fixtures │ ├── CT1_UNC │ ├── CT1_UNC.explicit_big_endian.dcm │ ├── CT0012.explicit_little_endian.dcm │ ├── CT1_UNC.explicit_little_endian.dcm │ ├── CT1_UNC.implicit_little_endian.dcm │ ├── IM00001.implicit_little_endian.dcm │ ├── CT0012.fragmented_no_bot_jpeg_ls.80.dcm │ └── CT0012.not_fragmented_bot_jpeg_ls.80.dcm └── test_p10.rs ├── src ├── attribute.rs ├── lib.rs ├── tag.rs ├── p10_parser.rs ├── prefix.rs ├── handler │ ├── cancel.rs │ ├── tee.rs │ └── mod.rs ├── vr.rs ├── value_parser │ ├── mod.rs │ ├── data.rs │ ├── data_undefined_length.rs │ ├── sequence.rs │ ├── encapsulated_pixel_data.rs │ └── sequence_item_data.rs ├── encoding.rs ├── p10.rs ├── test.rs ├── meta_information.rs ├── data_set_parser.rs └── attribute_parser.rs ├── Cargo.toml ├── .vscode ├── tasks.json └── launch.json ├── LICENSE ├── .devcontainer ├── devcontainer.json └── Dockerfile ├── examples ├── sop_instance_identification.rs └── async_file.rs ├── README.md ├── DESIGN.md └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /tests/fixtures/CT1_UNC: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/dicom-parser-rs/HEAD/tests/fixtures/CT1_UNC -------------------------------------------------------------------------------- /tests/fixtures/CT1_UNC.explicit_big_endian.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/dicom-parser-rs/HEAD/tests/fixtures/CT1_UNC.explicit_big_endian.dcm -------------------------------------------------------------------------------- /tests/fixtures/CT0012.explicit_little_endian.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/dicom-parser-rs/HEAD/tests/fixtures/CT0012.explicit_little_endian.dcm -------------------------------------------------------------------------------- /tests/fixtures/CT1_UNC.explicit_little_endian.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/dicom-parser-rs/HEAD/tests/fixtures/CT1_UNC.explicit_little_endian.dcm -------------------------------------------------------------------------------- /tests/fixtures/CT1_UNC.implicit_little_endian.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/dicom-parser-rs/HEAD/tests/fixtures/CT1_UNC.implicit_little_endian.dcm -------------------------------------------------------------------------------- /tests/fixtures/IM00001.implicit_little_endian.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/dicom-parser-rs/HEAD/tests/fixtures/IM00001.implicit_little_endian.dcm -------------------------------------------------------------------------------- /tests/fixtures/CT0012.fragmented_no_bot_jpeg_ls.80.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/dicom-parser-rs/HEAD/tests/fixtures/CT0012.fragmented_no_bot_jpeg_ls.80.dcm -------------------------------------------------------------------------------- /tests/fixtures/CT0012.not_fragmented_bot_jpeg_ls.80.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chafey/dicom-parser-rs/HEAD/tests/fixtures/CT0012.not_fragmented_bot_jpeg_ls.80.dcm -------------------------------------------------------------------------------- /src/attribute.rs: -------------------------------------------------------------------------------- 1 | use crate::tag::Tag; 2 | use crate::vr::VR; 3 | 4 | #[derive(Default, Debug, Clone, Copy)] 5 | pub struct Attribute { 6 | pub tag: Tag, 7 | pub vr: Option, 8 | pub length: usize, 9 | } 10 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //#![no_std] 2 | 3 | pub mod attribute; 4 | pub mod attribute_parser; 5 | pub mod data_set_parser; 6 | pub mod encoding; 7 | pub mod handler; 8 | pub mod meta_information; 9 | pub mod p10; 10 | pub mod p10_parser; 11 | pub mod prefix; 12 | pub mod tag; 13 | pub mod test; 14 | pub mod value_parser; 15 | pub mod vr; 16 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dicom-parser-rs" 3 | version = "0.1.0" 4 | authors = ["Chris Hafey "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [lib] 10 | name = "dicomparser" 11 | path = "./src/lib.rs" 12 | 13 | 14 | [dependencies] 15 | 16 | 17 | [dev-dependencies] 18 | tokio = { version = "0.2", features = ["full"] } 19 | futures = "0.3" -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "cargo test", 8 | "type": "shell", 9 | "command": "cargo", 10 | "args": [ 11 | "test", 12 | "--", 13 | "--nocapture" 14 | ], 15 | "group": { 16 | "kind": "build", 17 | "isDefault": true 18 | }, 19 | "presentation": { 20 | "clear": true 21 | } 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "lldb", 9 | "request": "launch", 10 | "name": "Debug unit tests in library 'image'", 11 | "cargo": { 12 | "args": [ 13 | "test", 14 | "parse_partial_debug", 15 | "--no-run", 16 | "--lib", 17 | "--package=dicom-parser-rs" 18 | ], 19 | "filter": { 20 | "name": "dicomparser", 21 | "kind": "lib" 22 | } 23 | }, 24 | "args": [ 25 | "implicit_little_endian" 26 | ], 27 | "cwd": "${workspaceFolder}" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Chris Hafey 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/vscode-remote/devcontainer.json or this file's README at: 2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.117.1/containers/rust 3 | { 4 | "name": "Rust", 5 | "dockerFile": "Dockerfile", 6 | "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ], 7 | 8 | // Set *default* container specific settings.json values on container create. 9 | "settings": { 10 | "terminal.integrated.shell.linux": "/bin/bash", 11 | "lldb.executable": "/usr/bin/lldb" 12 | }, 13 | 14 | // Add the IDs of extensions you want installed when the container is created. 15 | "extensions": [ 16 | "rust-lang.rust", 17 | "bungcip.better-toml", 18 | "vadimcn.vscode-lldb" 19 | ], 20 | 21 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 22 | // "forwardPorts": [], 23 | 24 | // Use 'postCreateCommand' to run commands after the container is created. 25 | // "postCreateCommand": "rustc --version", 26 | 27 | // Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root. 28 | "remoteUser": "vscode" 29 | } -------------------------------------------------------------------------------- /src/tag.rs: -------------------------------------------------------------------------------- 1 | use crate::encoding::Encoding; 2 | use std::fmt; 3 | 4 | #[derive(Default, PartialEq, Eq, PartialOrd, Clone, Copy)] 5 | pub struct Tag { 6 | pub group: u16, 7 | pub element: u16, 8 | } 9 | impl Tag { 10 | pub fn new(group: u16, element: u16) -> Tag { 11 | Tag { group, element } 12 | } 13 | 14 | pub fn from_bytes(bytes: &[u8]) -> Tag { 15 | let group = T::u16(&bytes[0..2]); 16 | let element = T::u16(&bytes[2..4]); 17 | Tag { group, element } 18 | } 19 | 20 | #[allow(clippy::trivially_copy_pass_by_ref)] 21 | pub fn is_private(&self) -> bool { 22 | (self.group % 2) > 0 23 | } 24 | } 25 | 26 | impl fmt::Debug for Tag { 27 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 28 | f.write_fmt(format_args!("({:04X},{:04X})", self.group, self.element)) 29 | } 30 | } 31 | 32 | pub static ITEM: Tag = Tag { 33 | group: 0xFFFE, 34 | element: 0xE000, 35 | }; 36 | pub static ITEMDELIMITATIONITEM: Tag = Tag { 37 | group: 0xFFFE, 38 | element: 0xE00D, 39 | }; 40 | pub static SEQUENCEDELIMITATIONITEM: Tag = Tag { 41 | group: 0xFFFE, 42 | element: 0xE0DD, 43 | }; 44 | 45 | #[cfg(test)] 46 | mod tests { 47 | use super::Tag; 48 | 49 | #[test] 50 | fn new() { 51 | let tag = Tag::new(8, 10); 52 | assert_eq!(tag.group, 8); 53 | assert_eq!(tag.element, 10); 54 | } 55 | 56 | #[test] 57 | fn tag_is_private_returns_false() { 58 | let tag = Tag::new(8, 10); 59 | assert_eq!(tag.is_private(), false); 60 | } 61 | 62 | #[test] 63 | fn tag_is_private_returns_true() { 64 | let tag = Tag::new(9, 10); 65 | assert_eq!(tag.is_private(), true); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/p10_parser.rs: -------------------------------------------------------------------------------- 1 | /*use crate::data_set_parser::DataSetParser; 2 | use crate::encoding::Encoding; 3 | use crate::encoding::ImplicitLittleEndian; 4 | use crate::handler::Handler; 5 | use crate::meta_information; 6 | use crate::meta_information::MetaInformation; 7 | use crate::value_parser::ParseError; 8 | use crate::value_parser::ParseResult; 9 | 10 | #[derive(Debug, Default)] 11 | pub struct P10Parser { 12 | bytes_consumed: usize, 13 | meta: Option, 14 | parser: Option>>, 15 | } 16 | 17 | impl P10Parser { 18 | pub fn parse<'a, T: Handler>( 19 | &mut self, 20 | handler: &'a mut T, 21 | bytes: &[u8], 22 | ) -> Result { 23 | match self.meta { 24 | None => { 25 | let meta = meta_information::parse(handler, bytes)?; 26 | self.meta = Some(meta); 27 | self.bytes_consumed = meta.end_position; 28 | self.parser = match &meta.transfer_syntax_uid[..] { 29 | "1.2.840.10008.1.2" => { 30 | Some(Box::new(DataSetParser::::default())) 31 | } 32 | } 33 | } 34 | Some(_) => {} 35 | } 36 | 37 | println!("{:?}", self.meta); 38 | 39 | Err(ParseError { 40 | reason: "unexpected EOF", 41 | position: self.bytes_consumed, 42 | }) 43 | } 44 | } 45 | #[cfg(test)] 46 | mod tests { 47 | 48 | use super::P10Parser; 49 | use crate::test::tests::read_file; 50 | use crate::test::tests::TestHandler; 51 | 52 | #[test] 53 | fn explicit_little_endian() { 54 | let bytes = read_file("tests/fixtures/CT1_UNC.explicit_little_endian.dcm"); 55 | let mut handler = TestHandler::default(); 56 | //handler.print = true; 57 | let mut parser = P10Parser::default(); 58 | let result = parser.parse(&mut handler, &bytes); 59 | assert!(result.is_ok()); 60 | assert_eq!(265, handler.attributes.len()); 61 | } 62 | } 63 | */ 64 | -------------------------------------------------------------------------------- /src/prefix.rs: -------------------------------------------------------------------------------- 1 | use crate::value_parser::ParseError; 2 | 3 | /// Detects the presence of a valid DICOM P10 Header Prefix. A valid 4 | /// prefix consists of 132 bytes with the string "DICM" at location 5 | /// 128. The first 128 bytes are usually 0 but do not have to be 6 | pub fn validate(bytes: &[u8]) -> Result<(), ParseError> { 7 | // check length 8 | if bytes.len() < 132 { 9 | return Err(ParseError { 10 | reason: "must have at least 132 bytes to validate", 11 | position: bytes.len(), 12 | }); 13 | } 14 | 15 | // check for DICM 16 | if &bytes[128..132] != b"DICM" { 17 | return Err(ParseError { 18 | reason: "DICOM not found at position 128", 19 | position: 128, 20 | }); 21 | } 22 | 23 | Ok(()) 24 | } 25 | 26 | #[cfg(test)] 27 | mod tests { 28 | use super::validate; 29 | 30 | #[test] 31 | fn zero_preamble_valid_prefix_returns_true() { 32 | let mut bytes: Vec = vec![]; 33 | bytes.resize(134, 0); 34 | bytes[128] = 'D' as u8; 35 | bytes[129] = 'I' as u8; 36 | bytes[130] = 'C' as u8; 37 | bytes[131] = 'M' as u8; 38 | 39 | let result = validate(&bytes); 40 | assert_eq!(result.is_ok(), true); 41 | } 42 | 43 | #[test] 44 | fn non_zero_preamble_valid_prefix_returns_true() { 45 | let mut bytes: Vec = vec![]; 46 | bytes.resize(134, 0); 47 | bytes[0] = 1; 48 | bytes[128] = 'D' as u8; 49 | bytes[129] = 'I' as u8; 50 | bytes[130] = 'C' as u8; 51 | bytes[131] = 'M' as u8; 52 | 53 | let result = validate(&bytes); 54 | assert_eq!(result.is_ok(), true); 55 | } 56 | 57 | #[test] 58 | fn zero_preamble_invalid_prefix_returns_error() { 59 | let mut bytes: Vec = vec![]; 60 | bytes.resize(134, 0); 61 | 62 | let result = validate(&bytes); 63 | assert_eq!(result.is_err(), true); 64 | } 65 | 66 | #[test] 67 | fn short_buffer_returns_error() { 68 | let mut bytes: Vec = vec![]; 69 | bytes.resize(128, 0); 70 | 71 | let result = validate(&bytes); 72 | assert_eq!(result.is_err(), true); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------------------------------------- 2 | # Copyright (c) Microsoft Corporation. All rights reserved. 3 | # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. 4 | #------------------------------------------------------------------------------------------------------------- 5 | 6 | FROM rust:1 7 | 8 | # This Dockerfile adds a non-root user with sudo access. Use the "remoteUser" 9 | # property in devcontainer.json to use it. On Linux, the container user's GID/UIDs 10 | # will be updated to match your local UID/GID (when using the dockerFile property). 11 | # See https://aka.ms/vscode-remote/containers/non-root-user for details. 12 | ARG USERNAME=vscode 13 | ARG USER_UID=1000 14 | ARG USER_GID=$USER_UID 15 | 16 | # Avoid warnings by switching to noninteractive 17 | ENV DEBIAN_FRONTEND=noninteractive 18 | 19 | # Configure apt and install packages 20 | RUN apt-get update \ 21 | && apt-get -y install --no-install-recommends apt-utils dialog 2>&1 \ 22 | # 23 | # Verify git, needed tools installed 24 | && apt-get -y install git openssh-client less iproute2 procps lsb-release \ 25 | # 26 | # Install lldb, vadimcn.vscode-lldb VSCode extension dependencies 27 | && apt-get install -y lldb python3-minimal libpython3.7 \ 28 | # 29 | # Install Rust components 30 | && rustup update 2>&1 \ 31 | && rustup component add rls rust-analysis rust-src rustfmt clippy 2>&1 \ 32 | # 33 | # Create a non-root user to use if preferred - see https://aka.ms/vscode-remote/containers/non-root-user. 34 | && groupadd --gid $USER_GID $USERNAME \ 35 | && useradd -s /bin/bash --uid $USER_UID --gid $USER_GID -m $USERNAME \ 36 | # [Optional] Add sudo support for the non-root user 37 | && apt-get install -y sudo \ 38 | && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME\ 39 | && chmod 0440 /etc/sudoers.d/$USERNAME \ 40 | # 41 | # Clean up 42 | && apt-get autoremove -y \ 43 | && apt-get clean -y \ 44 | && rm -rf /var/lib/apt/lists/* 45 | 46 | # Switch back to dialog for any ad-hoc use of apt-get 47 | ENV DEBIAN_FRONTEND=dialog 48 | -------------------------------------------------------------------------------- /tests/test_p10.rs: -------------------------------------------------------------------------------- 1 | /* 2 | #[cfg(test)] 3 | mod tests { 4 | 5 | use dicomparser::accumulator::Accumulator; 6 | use std::fs::File; 7 | use std::io::Read; 8 | 9 | use dicomparser::condition; 10 | use dicomparser::p10; 11 | 12 | #[allow(dead_code)] 13 | pub fn read_file(filepath: &str) -> Vec { 14 | let mut file = File::open(filepath).unwrap(); 15 | let mut buffer = Vec::new(); 16 | file.read_to_end(&mut buffer).unwrap(); 17 | buffer 18 | } 19 | 20 | #[test] 21 | fn explicit_little_endian() { 22 | let mut bytes = read_file("tests/fixtures/CT1_UNC.explicit_little_endian.dcm"); 23 | let mut accumulator = Accumulator::new(condition::none, condition::none); 24 | accumulator.print = true; 25 | p10::parse(&mut accumulator, &mut bytes).unwrap(); 26 | println!("Parsed {:?} attributes", accumulator.attributes.len()); 27 | //println!("{:?}", accumulator.attributes); 28 | } 29 | #[test] 30 | fn implicit_little_endian() { 31 | let mut bytes = read_file("tests/fixtures/CT1_UNC.implicit_little_endian.dcm"); 32 | let mut accumulator = Accumulator::new(condition::none, condition::none); 33 | accumulator.print = true; 34 | p10::parse(&mut accumulator, &mut bytes).unwrap(); 35 | println!("Parsed {:?} attributes", accumulator.attributes.len()); 36 | //println!("{:?}", accumulator.attributes); 37 | } 38 | #[test] 39 | fn sequences() { 40 | //(0008,9121) @ position 0x376 / 886 41 | let mut bytes = read_file("tests/fixtures/CT0012.fragmented_no_bot_jpeg_ls.80.dcm"); 42 | let mut accumulator = Accumulator::new(condition::none, condition::none); 43 | accumulator.print = true; 44 | p10::parse(&mut accumulator, &mut bytes).unwrap(); 45 | println!("Parsed {:?} attributes", accumulator.attributes.len()); 46 | /*for attr in accumulator.attributes { 47 | println!("{:?}", attr); 48 | }*/ 49 | } 50 | #[test] 51 | fn explicit_big_endian() { 52 | let mut bytes = read_file("tests/fixtures/CT1_UNC.explicit_big_endian.dcm"); 53 | let meta = meta_information::parse(&bytes).unwrap(); 54 | let accumulator = Accumulator::new(condition::none, condition::none); 55 | let mut parser = Parser::new(accumulator, Attribute::ele); 56 | parser.parse(&mut bytes[meta.end_position..]); 57 | println!("Parsed {:?} attributes", parser.callback.attributes.len()); 58 | } 59 | } 60 | */ 61 | -------------------------------------------------------------------------------- /src/handler/cancel.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::Attribute; 2 | use crate::handler::{Handler, HandlerResult}; 3 | 4 | pub type CancelFN = fn(&Attribute) -> bool; 5 | 6 | /// Implements the Handler trait that will cancel the parse if the provided 7 | /// Cancel function returns true or forward/proxy all functions to another 8 | /// Handler implementation. Some use cases do not require parsing the full 9 | /// DICOM DataSet and encapsulating the stop parsing in a function (perhaps 10 | /// a closure) can aid in readability of processing logic. 11 | pub struct CancelHandler<'t> { 12 | /// true if the parse has been canceled, false otherwise 13 | pub canceled: bool, 14 | /// the Handler to forward/proxy function calls to 15 | pub handler: &'t mut dyn Handler, 16 | /// The function to call to see if the parse should be cancelled 17 | pub cancel_fn: CancelFN, 18 | } 19 | 20 | impl<'t> CancelHandler<'t> { 21 | /// Creates a new CancelHanlder given a handler to proxy/forward functions 22 | /// to and a function that returns true when the parse should be canceled 23 | pub fn new(handler: &'t mut dyn Handler, cancel_fn: CancelFN) -> CancelHandler<'t> { 24 | CancelHandler { 25 | canceled: false, 26 | handler, 27 | cancel_fn, 28 | } 29 | } 30 | } 31 | 32 | impl Handler for CancelHandler<'_> { 33 | fn attribute( 34 | &mut self, 35 | attribute: &Attribute, 36 | position: usize, 37 | data_offset: usize, 38 | ) -> HandlerResult { 39 | if (self.cancel_fn)(&attribute) { 40 | self.canceled = true; 41 | return HandlerResult::Cancel; 42 | } 43 | self.handler.attribute(&attribute, position, data_offset) 44 | } 45 | fn data(&mut self, attribute: &Attribute, data: &[u8], complete: bool) { 46 | self.handler.data(&attribute, data, complete) 47 | } 48 | fn start_sequence(&mut self, attribute: &Attribute) { 49 | self.handler.start_sequence(&attribute) 50 | } 51 | fn start_sequence_item(&mut self, attribute: &Attribute) { 52 | self.handler.start_sequence_item(&attribute) 53 | } 54 | fn end_sequence_item(&mut self, attribute: &Attribute) { 55 | self.handler.end_sequence_item(&attribute) 56 | } 57 | fn end_sequence(&mut self, attribute: &Attribute) { 58 | self.handler.end_sequence(&attribute) 59 | } 60 | fn basic_offset_table( 61 | &mut self, 62 | attribute: &Attribute, 63 | data: &[u8], 64 | complete: bool, 65 | ) -> HandlerResult { 66 | self.handler.basic_offset_table(&attribute, data, complete) 67 | } 68 | fn pixel_data_fragment( 69 | &mut self, 70 | attribute: &Attribute, 71 | fragment_number: usize, 72 | data: &[u8], 73 | complete: bool, 74 | ) -> HandlerResult { 75 | self.handler 76 | .pixel_data_fragment(&attribute, fragment_number, data, complete) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/vr.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, Copy, PartialEq)] 2 | pub enum VR { 3 | AE, 4 | AS, 5 | AT, 6 | CS, 7 | DA, 8 | DS, 9 | DT, 10 | FD, 11 | FL, 12 | IS, 13 | LO, 14 | LT, 15 | OB, 16 | OD, 17 | OF, 18 | OL, 19 | OW, 20 | OV, 21 | PN, 22 | SH, 23 | SL, 24 | SQ, 25 | SS, 26 | ST, 27 | SV, 28 | TM, 29 | UC, 30 | UI, 31 | UL, 32 | UN, 33 | UR, 34 | US, 35 | UT, 36 | UV, 37 | Unknown { bytes: [u8; 2] }, 38 | } 39 | 40 | impl VR { 41 | pub fn from_bytes(bytes: &[u8]) -> VR { 42 | match bytes { 43 | b"AE" => VR::AE, 44 | b"AS" => VR::AS, 45 | b"AT" => VR::AT, 46 | b"CS" => VR::CS, 47 | b"DA" => VR::DA, 48 | b"DS" => VR::DS, 49 | b"DT" => VR::DT, 50 | b"FD" => VR::FD, 51 | b"FL" => VR::FL, 52 | b"IS" => VR::IS, 53 | b"LO" => VR::LO, 54 | b"LT" => VR::LT, 55 | b"OB" => VR::OB, 56 | b"OD" => VR::OD, 57 | b"OF" => VR::OF, 58 | b"OL" => VR::OL, 59 | b"OW" => VR::OW, 60 | b"OV" => VR::OV, 61 | b"PN" => VR::PN, 62 | b"SH" => VR::SH, 63 | b"SL" => VR::SL, 64 | b"SQ" => VR::SQ, 65 | b"SS" => VR::SS, 66 | b"ST" => VR::ST, 67 | b"SV" => VR::SV, 68 | b"TM" => VR::TM, 69 | b"UC" => VR::UC, 70 | b"UI" => VR::UI, 71 | b"UL" => VR::UL, 72 | b"UN" => VR::UN, 73 | b"UR" => VR::UR, 74 | b"US" => VR::US, 75 | b"UT" => VR::UT, 76 | b"UV" => VR::UV, 77 | _ => VR::Unknown { 78 | bytes: [bytes[0], bytes[1]], 79 | }, 80 | } 81 | } 82 | 83 | pub fn explicit_length_is_u32(vr: VR) -> bool { 84 | match vr { 85 | VR::OW | VR::OB | VR::SQ | VR::OF | VR::UT | VR::UN => true, 86 | _ => false, 87 | } 88 | } 89 | } 90 | 91 | #[cfg(test)] 92 | mod tests { 93 | use super::VR; 94 | 95 | #[test] 96 | fn from_bytes_returns_cs() { 97 | let vr = VR::from_bytes(b"CS"); 98 | assert_eq!(vr, VR::CS); 99 | } 100 | 101 | #[test] 102 | fn from_bytes_returns_unknown() { 103 | let vr = VR::from_bytes(b"XX"); 104 | assert_eq!( 105 | vr, 106 | VR::Unknown { 107 | bytes: [b'X', b'X'] 108 | } 109 | ); 110 | } 111 | 112 | #[test] 113 | fn explicit_length_is_u32_returns_true() { 114 | assert_eq!(true, VR::explicit_length_is_u32(VR::OW)); 115 | } 116 | #[test] 117 | fn explicit_length_is_u32_returns_false() { 118 | assert_eq!(false, VR::explicit_length_is_u32(VR::CS)); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/value_parser/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::Attribute; 2 | use crate::encoding::Encoding; 3 | use crate::handler::Handler; 4 | 5 | use std::fmt; 6 | 7 | /// Contains information about an error encountered while parsing 8 | pub struct ParseError { 9 | /// A string explaining the reason for the parse failure 10 | pub reason: &'static str, 11 | /// The position relative to the beginning of the stream that the error 12 | /// occured at 13 | pub position: usize, 14 | } 15 | 16 | /// Enum describing the current state of the parser 17 | #[derive(PartialEq, Copy, Clone, Debug)] 18 | pub enum ParseState { 19 | /// The parse was canceled by the Handler 20 | Cancelled, 21 | /// The parse is incomplete due to lack of bytes provided. Parsing 22 | /// can continue once more bytes are available 23 | Incomplete, 24 | /// The Attribute and its value field were fully parsed 25 | Completed, 26 | } 27 | 28 | /// Contains information about the result of a parse 29 | pub struct ParseResult { 30 | /// The number of bytes actually consumed by this function 31 | pub bytes_consumed: usize, 32 | /// The result state of the parser after calling this function 33 | pub state: ParseState, 34 | } 35 | 36 | impl ParseResult { 37 | /// Convenience function to create a Cancelled ParseResult 38 | pub fn cancelled(bytes_consumed: usize) -> ParseResult { 39 | ParseResult { 40 | bytes_consumed, 41 | state: ParseState::Cancelled, 42 | } 43 | } 44 | 45 | /// Convenience function to create an Incomplete ParseResult 46 | pub fn incomplete(bytes_consumed: usize) -> ParseResult { 47 | ParseResult { 48 | bytes_consumed, 49 | state: ParseState::Incomplete, 50 | } 51 | } 52 | 53 | /// Convenience function to create an Completed ParseResult 54 | pub fn completed(bytes_consumed: usize) -> ParseResult { 55 | ParseResult { 56 | bytes_consumed, 57 | state: ParseState::Completed, 58 | } 59 | } 60 | } 61 | 62 | /// This trait defines an interface for parsing the value portion of a DICOM 63 | /// Attribute for a specific Encoding. 64 | pub trait ValueParser { 65 | /// Parses the value field 66 | /// 67 | /// # Arguments 68 | /// 69 | /// * `handler` - The Handler to invoke when parsing the value field 70 | /// * `attribute` - The Attribute associated with this value field 71 | /// * `bytes` - The raw bytes of the value field 72 | /// * `position` - The position since the beginning of the parse stream 73 | /// of the value field. 74 | fn parse( 75 | &mut self, 76 | handler: &mut dyn Handler, 77 | attribute: &Attribute, 78 | bytes: &[u8], 79 | position: usize, 80 | ) -> Result; 81 | } 82 | 83 | pub mod data; 84 | pub mod data_undefined_length; 85 | pub mod encapsulated_pixel_data; 86 | pub mod sequence; 87 | pub mod sequence_item_data; 88 | -------------------------------------------------------------------------------- /src/handler/tee.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::Attribute; 2 | use crate::handler::{Handler, HandlerResult}; 3 | 4 | /// Implements the Handler trait that forward each function call to each 5 | /// handler in its list of handlers 6 | #[derive(Default)] 7 | pub struct TeeHandler<'t> { 8 | /// the Handlers to forward function calls to 9 | pub handlers: Vec<&'t mut dyn Handler>, 10 | } 11 | 12 | impl Handler for TeeHandler<'_> { 13 | fn attribute( 14 | &mut self, 15 | attribute: &Attribute, 16 | position: usize, 17 | data_offset: usize, 18 | ) -> HandlerResult { 19 | if self 20 | .handlers 21 | .iter_mut() 22 | .map(|handler| handler.attribute(attribute, position, data_offset)) 23 | .filter(|handler_result| handler_result == &HandlerResult::Cancel) 24 | .count() 25 | > 0 26 | { 27 | HandlerResult::Cancel 28 | } else { 29 | HandlerResult::Continue 30 | } 31 | } 32 | fn data(&mut self, attribute: &Attribute, data: &[u8], complete: bool) { 33 | self.handlers 34 | .iter_mut() 35 | .for_each(|handler| handler.data(attribute, data, complete)) 36 | } 37 | fn start_sequence(&mut self, attribute: &Attribute) { 38 | self.handlers 39 | .iter_mut() 40 | .for_each(|handler| handler.start_sequence(attribute)) 41 | } 42 | fn start_sequence_item(&mut self, attribute: &Attribute) { 43 | self.handlers 44 | .iter_mut() 45 | .for_each(|handler| handler.start_sequence_item(attribute)) 46 | } 47 | fn end_sequence_item(&mut self, attribute: &Attribute) { 48 | self.handlers 49 | .iter_mut() 50 | .for_each(|handler| handler.end_sequence_item(attribute)) 51 | } 52 | fn end_sequence(&mut self, attribute: &Attribute) { 53 | self.handlers 54 | .iter_mut() 55 | .for_each(|handler| handler.end_sequence(attribute)) 56 | } 57 | fn basic_offset_table( 58 | &mut self, 59 | attribute: &Attribute, 60 | data: &[u8], 61 | complete: bool, 62 | ) -> HandlerResult { 63 | if self 64 | .handlers 65 | .iter_mut() 66 | .map(|handler| handler.basic_offset_table(attribute, data, complete)) 67 | .filter(|handler_result| handler_result == &HandlerResult::Cancel) 68 | .count() 69 | > 0 70 | { 71 | HandlerResult::Cancel 72 | } else { 73 | HandlerResult::Continue 74 | } 75 | } 76 | fn pixel_data_fragment( 77 | &mut self, 78 | attribute: &Attribute, 79 | fragment_number: usize, 80 | data: &[u8], 81 | complete: bool, 82 | ) -> HandlerResult { 83 | if self 84 | .handlers 85 | .iter_mut() 86 | .map(|handler| handler.pixel_data_fragment(attribute, fragment_number, data, complete)) 87 | .filter(|handler_result| handler_result == &HandlerResult::Cancel) 88 | .count() 89 | > 0 90 | { 91 | HandlerResult::Cancel 92 | } else { 93 | HandlerResult::Continue 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /examples/sop_instance_identification.rs: -------------------------------------------------------------------------------- 1 | use dicomparser::attribute::Attribute; 2 | use dicomparser::handler::{Handler, HandlerResult}; 3 | use dicomparser::p10::parse; 4 | use dicomparser::tag::Tag; 5 | use std::env; 6 | use std::fs::File; 7 | use std::io::Read; 8 | use std::str; 9 | 10 | const STUDYINSTANCEUID: Tag = Tag { 11 | group: 0x0020, 12 | element: 0x000D, 13 | }; 14 | 15 | const SERIESINSTANCEUID: Tag = Tag { 16 | group: 0x0020, 17 | element: 0x000E, 18 | }; 19 | 20 | const SOPINSTANCEUID: Tag = Tag { 21 | group: 0x0008, 22 | element: 0x0018, 23 | }; 24 | 25 | #[derive(Default, Debug)] 26 | pub struct SOPInstanceIdentificationHandler { 27 | pub study_instance_uid: String, 28 | pub series_instance_uid: String, 29 | pub sop_instance_uid: String, 30 | // buffer to accumulate data for an attribute 31 | data_buffer: Vec, 32 | } 33 | 34 | impl SOPInstanceIdentificationHandler { 35 | fn is_tag_wanted(tag: Tag) -> bool { 36 | match tag { 37 | STUDYINSTANCEUID => true, 38 | SERIESINSTANCEUID => true, 39 | SOPINSTANCEUID => true, 40 | _ => false, 41 | } 42 | } 43 | } 44 | 45 | impl Handler for SOPInstanceIdentificationHandler { 46 | fn attribute( 47 | &mut self, 48 | attribute: &Attribute, 49 | _position: usize, 50 | _data_offset: usize, 51 | ) -> HandlerResult { 52 | if SOPInstanceIdentificationHandler::is_tag_wanted(attribute.tag) { 53 | self.data_buffer.clear(); 54 | } 55 | if attribute.tag > Tag::new(0x0020, 0x000E) { 56 | HandlerResult::Cancel 57 | } else { 58 | HandlerResult::Continue 59 | } 60 | } 61 | 62 | fn data(&mut self, attribute: &Attribute, data: &[u8], complete: bool) { 63 | if attribute.length == 0 { 64 | return; 65 | } 66 | if SOPInstanceIdentificationHandler::is_tag_wanted(attribute.tag) { 67 | self.data_buffer.extend_from_slice(data); 68 | 69 | if complete { 70 | let bytes = if self.data_buffer[attribute.length - 1] != 0 { 71 | &self.data_buffer 72 | } else { 73 | &self.data_buffer[0..(attribute.length - 1)] 74 | }; 75 | 76 | if attribute.tag == STUDYINSTANCEUID { 77 | self.study_instance_uid = String::from(str::from_utf8(&bytes).unwrap()); 78 | } else if attribute.tag == SERIESINSTANCEUID { 79 | self.series_instance_uid = String::from(str::from_utf8(&bytes).unwrap()); 80 | } else if attribute.tag == SOPINSTANCEUID { 81 | self.sop_instance_uid = String::from(str::from_utf8(&bytes).unwrap()); 82 | } 83 | } 84 | } 85 | } 86 | } 87 | 88 | pub fn read_file(filepath: &str) -> Vec { 89 | let mut file = File::open(filepath).unwrap(); 90 | let mut buffer = Vec::new(); 91 | file.read_to_end(&mut buffer).unwrap(); 92 | buffer 93 | } 94 | 95 | fn main() { 96 | let args: Vec = env::args().collect(); 97 | let mut bytes = if args.len() > 1 { 98 | read_file(&args[1]) 99 | } else { 100 | read_file("tests/fixtures/CT1_UNC.explicit_little_endian.dcm") 101 | }; 102 | let mut handler = SOPInstanceIdentificationHandler::default(); 103 | match parse(&mut handler, &mut bytes) { 104 | Ok(_meta) => println!("{:?}", handler), 105 | Err(_parse_error) => {} 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/value_parser/data.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::Attribute; 2 | use crate::encoding::Encoding; 3 | use crate::handler::Handler; 4 | use crate::value_parser::ParseError; 5 | use crate::value_parser::ParseResult; 6 | use crate::value_parser::ValueParser; 7 | use std::marker::PhantomData; 8 | 9 | /// Parses the value field for an Attribute. 10 | #[derive(Default)] 11 | pub struct DataParser { 12 | pub phantom: PhantomData, 13 | total_bytes_consumed: usize, 14 | } 15 | 16 | impl ValueParser for DataParser { 17 | fn parse( 18 | &mut self, 19 | handler: &mut dyn Handler, 20 | attribute: &Attribute, 21 | bytes: &[u8], 22 | _position: usize, 23 | ) -> Result { 24 | // get slice of bytes based on how many we have already parsed for this value field 25 | let bytes_remaining = if bytes.len() > attribute.length - self.total_bytes_consumed { 26 | &bytes[..attribute.length - self.total_bytes_consumed] 27 | } else { 28 | bytes 29 | }; 30 | 31 | // notify handler of data 32 | let complete = self.total_bytes_consumed + bytes_remaining.len() == attribute.length; 33 | handler.data(attribute, bytes_remaining, complete); 34 | 35 | // update our internal counter of bytes consumed 36 | self.total_bytes_consumed += bytes_remaining.len(); 37 | 38 | // Return complete if we have parsed all bytes for this value or incomplete if not 39 | if self.total_bytes_consumed == attribute.length { 40 | Ok(ParseResult::completed(bytes_remaining.len())) 41 | } else { 42 | Ok(ParseResult::incomplete(bytes_remaining.len())) 43 | } 44 | } 45 | } 46 | 47 | #[cfg(test)] 48 | mod tests { 49 | 50 | use super::DataParser; 51 | use crate::attribute::Attribute; 52 | use crate::encoding::ExplicitLittleEndian; 53 | use crate::test::tests::TestHandler; 54 | use crate::value_parser::ParseState; 55 | use crate::value_parser::ValueParser; 56 | 57 | #[test] 58 | fn data_parser_completes() { 59 | let mut parser = DataParser::::default(); 60 | let mut handler = TestHandler::default(); 61 | let mut attribute = Attribute::default(); 62 | attribute.length = 255; 63 | let bytes = [0; 255]; 64 | match parser.parse(&mut handler, &attribute, &bytes, 0) { 65 | Ok(result) => { 66 | assert_eq!(result.bytes_consumed, bytes.len()); 67 | assert_eq!(result.state, ParseState::Completed); 68 | } 69 | Err(_error) => { 70 | assert!(false); // should not happen 71 | } 72 | }; 73 | } 74 | 75 | #[test] 76 | fn data_parser_streaming_completes() { 77 | let mut parser = DataParser::::default(); 78 | let mut handler = TestHandler::default(); 79 | let mut attribute = Attribute::default(); 80 | attribute.length = 255; 81 | let bytes = [0; 255]; 82 | match parser.parse(&mut handler, &attribute, &bytes[0..100], 0) { 83 | Ok(result) => { 84 | assert_eq!(result.bytes_consumed, 100); 85 | assert_eq!(result.state, ParseState::Incomplete); 86 | } 87 | Err(_error) => { 88 | assert!(false); // should not happen 89 | } 90 | }; 91 | match parser.parse(&mut handler, &attribute, &bytes[100..], 0) { 92 | Ok(result) => { 93 | assert_eq!(result.bytes_consumed, bytes.len() - 100); 94 | assert_eq!(result.state, ParseState::Completed); 95 | } 96 | Err(_error) => { 97 | assert!(false); // should not happen 98 | } 99 | }; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dicom-parser-rs 2 | dicom parser written in Rust 3 | 4 | ## Design Goals 5 | 6 | * Parse all standards compliant DICOM P10 files 7 | * First class support for WebAssembly builds 8 | * Streaming compatible API 9 | * SAX Style callback based parsing 10 | * Does not utilize a DICOM data dictionary 11 | * Modular design enabling flexible re-use of the library functionality 12 | 13 | Read about the [design rationale for this library](DESIGN.md) 14 | 15 | ## Features 16 | 17 | * [X] Callback based parsing 18 | * [X] Cancel Parsing 19 | * [X] DICOM P10 Meta Information 20 | * [X] Explicit Little Endian Transfer Syntax 21 | * [X] Streaming Parser 22 | * [X] Implicit Little Endian Transfer Syntax 23 | * [X] Explicit Big Endian Transfer Syntax 24 | * [X] Encapsulated Pixel Data 25 | * [X] Sequences with known lengths 26 | * [X] Sequences with undefined lengths 27 | * [X] UN with undefined lengths 28 | * [ ] Deflate Transfer Syntax 29 | 30 | ## Status 31 | 32 | Actively being developed (June 20, 2020) 33 | 34 | ## To Do's (before first version release) 35 | 36 | * Add no_std configuration? 37 | * Add example applications 38 | * dump to DICOM JSON format 39 | * dump to text (in DCMTK dcmdump format) - requires data dictionary though 40 | * Test library with large number of images (GDCM test images, etc) 41 | * Would be nice to build an automated regression suite 42 | 43 | ## Performance Benchmarking 44 | 45 | Add performance benchmark to understand parser performance for various use 46 | cases and data variations. Establishing a baseline here will help avoid 47 | performance regressions due to future changes and also serve as a useful 48 | comparsion agains other parsing implementations. 49 | 50 | * Use Cases 51 | * SOP Instance Identification - extract Study,Series and SOPInstanceUID Only 52 | * Basic Image Display - Extract minimum attributes to display image (ImagePixel Module) 53 | * Basic Encoding Validation - make sure the entire dataset can be parsed (ignore data though) 54 | * Basic Metadata - extract basic metadata like patietn demographics, study description, etc 55 | * Metadata ingestion - parse all metadata fields 56 | * Pixel Ingestion - parse all pixel data 57 | * Data Variations 58 | * Transfer Syntaxes (Implicit Little Endian, Explicit Little Endian, Explicit Big Endian) 59 | * Pixel Data (uncompressed, compressed/encapsulated not fragmented, encapsulated fragmented) 60 | * Lengths (defined and undefined) 61 | * Sizes (large multiframe, small single frame) 62 | * Private Attributes (large, small) 63 | * Stream Parsing - full dataset, chunked, single bytes 64 | 65 | ## Possible Future Functionality 66 | 67 | * Consider helpers to convert attribute data into rust types (e.g. strings, numbers, etc) 68 | * Note: meta_information already has functionality to convert to utf8 strings 69 | * Create handler that produces DICOM JSON? 70 | * Consider adding FilterHandler that filters out handler calls for specific attributes. 71 | * Consider adding TagCancelHandler to cancel parsing on specific tag (or tags) 72 | * Consider making a cancelled parse resumable? Should work given that the parser is streaming capable 73 | * Add P10StreamParser which provides a simplified interface for stream parsing by buffering data from 74 | incomplete parses 75 | * Consider adding a Handler that aggregates mutliple data callbacks into a single buffer 76 | * Explore ways to automate mapping from Handler to types in a struct, perhaps using macros? 77 | * would be nice to be able to do something like: !map(0x0020, 0x000D, &self.study_instance_uid); 78 | * could use this for creating the MetaInformation struct rather than a custom Handler 79 | 80 | ## Refactorings 81 | 82 | * Separate undefined length logic from known length logic 83 | * SequenceItemDataParser->SequenceItemDataUndefinedLengthParser 84 | * SequenceParser -> SequenceUndefinedLengthParser 85 | * Refactor parse_tag_and_length() so we don't have two of them - perhaps replace with parse_attribute()? 86 | * replace unit test dependencies on actual P10 files with synthetic data -------------------------------------------------------------------------------- /src/encoding.rs: -------------------------------------------------------------------------------- 1 | use crate::vr::VR; 2 | use std::convert::TryInto; 3 | use std::fmt; 4 | 5 | pub trait Encoding: fmt::Debug + Default { 6 | fn u16(bytes: &[u8]) -> u16; 7 | fn u32(bytes: &[u8]) -> u32; 8 | fn vr_and_length(bytes: &[u8]) -> Result<(Option, usize, usize), ()>; 9 | } 10 | 11 | #[derive(Default)] 12 | pub struct ExplicitLittleEndian {} 13 | 14 | impl fmt::Debug for ExplicitLittleEndian { 15 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 16 | f.debug_struct("ExplicitLittleEndian").finish() 17 | } 18 | } 19 | 20 | impl Encoding for ExplicitLittleEndian { 21 | fn u16(bytes: &[u8]) -> u16 { 22 | u16::from_le_bytes([bytes[0], bytes[1]].try_into().unwrap()) 23 | } 24 | 25 | fn u32(bytes: &[u8]) -> u32 { 26 | u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]].try_into().unwrap()) 27 | } 28 | 29 | fn vr_and_length(bytes: &[u8]) -> Result<(Option, usize, usize), ()> { 30 | let vr = VR::from_bytes(&bytes[4..6]); 31 | if VR::explicit_length_is_u32(vr) { 32 | if bytes.len() < 12 { 33 | return Err(()); 34 | } 35 | 36 | Ok(( 37 | Some(vr), 38 | ExplicitLittleEndian::u32(&bytes[8..12]) as usize, 39 | 12, 40 | )) 41 | } else { 42 | if bytes.len() < 8 { 43 | return Err(()); 44 | } 45 | Ok(( 46 | Some(vr), 47 | ExplicitLittleEndian::u16(&bytes[6..8]) as usize, 48 | 8, 49 | )) 50 | } 51 | } 52 | } 53 | 54 | #[derive(Default)] 55 | pub struct ImplicitLittleEndian {} 56 | 57 | impl fmt::Debug for ImplicitLittleEndian { 58 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 59 | f.debug_struct("ImplicitLittleEndian").finish() 60 | } 61 | } 62 | 63 | impl Encoding for ImplicitLittleEndian { 64 | fn u16(bytes: &[u8]) -> u16 { 65 | u16::from_le_bytes([bytes[0], bytes[1]].try_into().unwrap()) 66 | } 67 | 68 | fn u32(bytes: &[u8]) -> u32 { 69 | u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]].try_into().unwrap()) 70 | } 71 | 72 | fn vr_and_length(bytes: &[u8]) -> Result<(Option, usize, usize), ()> { 73 | if bytes.len() < 8 { 74 | return Err(()); 75 | } 76 | 77 | let length = ImplicitLittleEndian::u32(&bytes[4..8]) as usize; 78 | Ok((None, length, 8)) 79 | } 80 | } 81 | 82 | #[derive(Default)] 83 | pub struct ExplicitBigEndian {} 84 | 85 | impl fmt::Debug for ExplicitBigEndian { 86 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 87 | f.debug_struct("ExplicitBigEndian").finish() 88 | } 89 | } 90 | 91 | impl Encoding for ExplicitBigEndian { 92 | fn u16(bytes: &[u8]) -> u16 { 93 | u16::from_be_bytes([bytes[0], bytes[1]].try_into().unwrap()) 94 | } 95 | 96 | fn u32(bytes: &[u8]) -> u32 { 97 | u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]].try_into().unwrap()) 98 | } 99 | 100 | fn vr_and_length(bytes: &[u8]) -> Result<(Option, usize, usize), ()> { 101 | let vr = VR::from_bytes(&bytes[4..6]); 102 | if VR::explicit_length_is_u32(vr) { 103 | if bytes.len() < 12 { 104 | return Err(()); 105 | } 106 | 107 | Ok((Some(vr), ExplicitBigEndian::u32(&bytes[8..12]) as usize, 12)) 108 | } else { 109 | if bytes.len() < 8 { 110 | return Err(()); 111 | } 112 | Ok((Some(vr), ExplicitBigEndian::u16(&bytes[6..8]) as usize, 8)) 113 | } 114 | } 115 | } 116 | 117 | /* 118 | #[cfg(test)] 119 | mod tests { 120 | use super::le_u32; 121 | 122 | #[test] 123 | fn read_success() { 124 | let bytes = vec![0xfe,0xff,0x00,0xe0]; 125 | let value = le_u32(&bytes); 126 | println!("value = {}", value); 127 | assert_eq!(value, xfffee000); 128 | } 129 | }*/ 130 | -------------------------------------------------------------------------------- /examples/async_file.rs: -------------------------------------------------------------------------------- 1 | use dicomparser::attribute::Attribute; 2 | use dicomparser::data_set_parser::DataSetParser; 3 | use dicomparser::encoding::ExplicitLittleEndian; 4 | use dicomparser::handler::{Handler, HandlerResult}; 5 | use dicomparser::meta_information; 6 | use std::env; 7 | use tokio::fs::File; 8 | use tokio::io::{AsyncReadExt, Error}; 9 | 10 | #[derive(Default)] 11 | pub struct TestHandler { 12 | pub depth: usize, 13 | } 14 | 15 | impl Handler for TestHandler { 16 | fn attribute( 17 | &mut self, 18 | attribute: &Attribute, 19 | _position: usize, 20 | _data_offset: usize, 21 | ) -> HandlerResult { 22 | println!("{:- HandlerResult { 57 | println!("BasicOffsetTable"); 58 | HandlerResult::Continue 59 | } 60 | 61 | fn pixel_data_fragment( 62 | &mut self, 63 | _attribute: &Attribute, 64 | _fragment_number: usize, 65 | data: &[u8], 66 | _complete: bool, 67 | ) -> HandlerResult { 68 | println!("Pixle Data Fragment of length {:?} ", data.len()); 69 | HandlerResult::Continue 70 | } 71 | } 72 | 73 | async fn reader(file_path: &str) -> Result<(), Error> { 74 | let mut file = File::open(file_path).await?; 75 | let mut bytes = vec![0; 1024]; 76 | 77 | let mut handler = TestHandler::default(); 78 | // Read the first 1k bytes which is hopefully enough for 79 | // the p10 header (ugh) 80 | let num_bytes_read = file.read(&mut bytes).await?; 81 | let meta = match meta_information::parse(&mut handler, &bytes[..num_bytes_read]) { 82 | Ok(meta) => meta, 83 | Err(_err) => return Ok(()), 84 | }; 85 | println!("{:?}", meta); 86 | let mut parser = DataSetParser::::default(); 87 | 88 | let mut remaining_bytes = (&bytes[meta.end_position..num_bytes_read]).to_vec(); 89 | let mut bytes_from_beginning = meta.end_position; 90 | //let result = parser.parse(&mut handler, &remaining_bytes, bytes_from_beginning); 91 | let mut content = vec![0; 1024 * 64]; 92 | loop { 93 | //println!("remaining_bytes.len()={:?}", remaining_bytes.len()); 94 | let num_bytes_read2 = file.read(&mut content).await?; 95 | //println!("read {:?} bytes", num_bytes_read2); 96 | if num_bytes_read2 == 0 { 97 | break; 98 | } 99 | 100 | // append to remaining bytes 101 | let concat = [&remaining_bytes, &content[..num_bytes_read2]].concat(); 102 | 103 | //println!("concat.len()={:?}", concat.len()); 104 | 105 | match parser.parse(&mut handler, &concat, bytes_from_beginning) { 106 | Ok(result) => { 107 | //println!("bytes consumed={:?}", result.bytes_consumed); 108 | bytes_from_beginning += result.bytes_consumed; 109 | //println!("bytes from beginning: {}", bytes_from_beginning); 110 | remaining_bytes = (&concat[result.bytes_consumed..]).to_vec(); 111 | } 112 | Err(_) => return Ok(()), 113 | } 114 | } 115 | Ok(()) 116 | } 117 | 118 | #[tokio::main] 119 | async fn main() { 120 | let args: Vec = env::args().collect(); 121 | reader(&args[1]).await.unwrap(); 122 | } 123 | -------------------------------------------------------------------------------- /src/handler/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::Attribute; 2 | 3 | /// Enum returned from Handler implementations to instruct the parser to 4 | /// continue parsing or cancel parsing. 5 | #[derive(PartialEq)] 6 | pub enum HandlerResult { 7 | Continue, // continue (decode the element's data) 8 | Cancel, // stop parsing 9 | } 10 | 11 | /// The Handler trait defines a callback interface that is called when 12 | /// parsing a DICOM DataSet allowing the parsed data to be processed. 13 | /// Note that DICOM DataSet are tree like due to sequences, implementations 14 | /// of this trait must be aware and keep track of this. 15 | pub trait Handler { 16 | /// Invoked every time an Attribute is parsed. Note that the data for the 17 | /// attribute is provided via the data function below and may have not been 18 | /// provided to the parser yet (due to streaming). 19 | /// 20 | /// # Arguments 21 | /// 22 | /// * `_attribute` - The attribute parsed (Tag, VR, Length) 23 | /// * `_position` - The offset from the beginning of the stream of the 24 | /// first byte of the Attribute 25 | /// * `_data_offset` - The offset from _position of the attribute's value 26 | /// field 27 | fn attribute( 28 | &mut self, 29 | _attribute: &Attribute, 30 | _position: usize, 31 | _data_offset: usize, 32 | ) -> HandlerResult { 33 | HandlerResult::Continue 34 | } 35 | /// Invoked after attribute() with the value field/data for the attribute 36 | /// This function may be invoked multiple times for the same attribute 37 | /// due to streaming. Handler implementations are responsible for 38 | /// concatenating the received data in this case 39 | /// 40 | /// # Arguments 41 | /// * `_attribute` - the Attribute corresponding to this data 42 | /// * `_data` - the raw bytes for the value field 43 | /// * `_complete` - true if this is the data is complete, false if not 44 | fn data(&mut self, _attribute: &Attribute, _data: &[u8], _complete: bool) {} 45 | /// Invoked after attribute() for Sequences Attributes instead of data(). 46 | /// A corresponding call to end_sequence() will be made once the value 47 | /// field for the sequence is fully parsed. 48 | fn start_sequence(&mut self, _attribute: &Attribute) {} 49 | /// Invoked for each sequence item parsed in a sequence attribute. A 50 | /// corresponding call to end_sequence_item() will be made once the 51 | /// sequence item is fully parsed. Parsing a sequence item includes 52 | /// zero or more calls to attribute() for each attribute in the sequence 53 | /// item 54 | fn start_sequence_item(&mut self, _attribute: &Attribute) {} 55 | /// Invoked after all attributes in a sequence item are parsed. 56 | /// Corresponds to exactly one prior call to start_sequence_item() 57 | fn end_sequence_item(&mut self, _attribute: &Attribute) {} 58 | /// Invoked once the value field for a sequence is fully parsed. 59 | /// Corresponds to exaclty one prior call to start_sequence 60 | fn end_sequence(&mut self, _attribute: &Attribute) {} 61 | /// Invoked when the basic offset table is parsed in an encaspulated pixel 62 | /// data attribute. Note that basic offset table is not required so may be 63 | /// empty (or zero length) 64 | /// This function may be invoked multiple times for the same attribute 65 | /// due to streaming. Handler implementations are responsible for 66 | /// concatenating the received data in this case 67 | fn basic_offset_table( 68 | &mut self, 69 | _attribute: &Attribute, 70 | _data: &[u8], 71 | _complete: bool, 72 | ) -> HandlerResult { 73 | HandlerResult::Continue 74 | } 75 | /// Invoked for each pixel data fragment parsed in an encapsulated pixel 76 | /// data attribute. Note that a given image frame may consist of multiple 77 | /// fragments (although this may only occur in single frame - need to 78 | /// confirm this) 79 | /// This function may be invoked multiple times for the same attribute 80 | /// due to streaming. Handler implementations are responsible for 81 | /// concatenating the received data in this case 82 | fn pixel_data_fragment( 83 | &mut self, 84 | _attribute: &Attribute, 85 | _fragment_number: usize, 86 | _data: &[u8], 87 | _complete: bool, 88 | ) -> HandlerResult { 89 | HandlerResult::Continue 90 | } 91 | } 92 | 93 | pub mod cancel; 94 | pub mod tee; 95 | -------------------------------------------------------------------------------- /src/p10.rs: -------------------------------------------------------------------------------- 1 | use crate::data_set_parser::parse_full; 2 | use crate::encoding::ExplicitBigEndian; 3 | use crate::encoding::ExplicitLittleEndian; 4 | use crate::encoding::ImplicitLittleEndian; 5 | use crate::handler::Handler; 6 | use crate::meta_information; 7 | use crate::meta_information::MetaInformation; 8 | use crate::value_parser::ParseError; 9 | 10 | /// Parses a DICOM P10 Instance. Returns the corresponding MetaInformation or 11 | /// a ParseError if an error occurs during parse. 12 | /// 13 | /// # Arguments 14 | /// 15 | /// * `handler` - The Handler to invoke when parsing the DataSet 16 | /// * `bytes` - bytes from a DICOM P10 instance. Can be the entire file or 17 | /// the beginning part of the file. If the entire file is not 18 | /// provided and the parse is not Cancelled, an error may be 19 | /// returned 20 | /// 21 | pub fn parse<'a, T: Handler>( 22 | handler: &'a mut T, 23 | bytes: &[u8], 24 | ) -> Result { 25 | let meta = meta_information::parse(handler, &bytes)?; 26 | let remaining_bytes = &bytes[meta.end_position..]; 27 | let result = match &meta.transfer_syntax_uid[..] { 28 | "1.2.840.10008.1.2" => { 29 | // implicit little endian 30 | parse_full::(handler, remaining_bytes, meta.end_position) 31 | } 32 | "1.2.840.10008.1.2.2" => { 33 | // explicit big endian 34 | parse_full::(handler, remaining_bytes, meta.end_position) 35 | } 36 | "1.2.840.10008.1.2.1.99" => panic!("deflated not suported yet"), 37 | _ => { 38 | // explicit little endian 39 | parse_full::(handler, remaining_bytes, meta.end_position) 40 | } 41 | }; 42 | match result { 43 | Ok(_) => Ok(meta), 44 | Err(parse_error) => Err(parse_error), 45 | } 46 | } 47 | 48 | #[cfg(test)] 49 | mod tests { 50 | 51 | use super::parse; 52 | use crate::meta_information::tests::make_p10_header; 53 | use crate::test::tests::read_file; 54 | use crate::test::tests::TestHandler; 55 | 56 | fn make_p10_file() -> Vec { 57 | let mut bytes = make_p10_header(); 58 | bytes.extend_from_slice(&vec![0x08, 0x00, 0x05, 0x00, b'C', b'S', 2, 0, b'I', b'S']); 59 | 60 | bytes 61 | } 62 | 63 | #[test] 64 | fn explicit_little_endian_parses() { 65 | let mut bytes = make_p10_file(); 66 | let mut handler = TestHandler::default(); 67 | let result = parse(&mut handler, &mut bytes); 68 | assert!(result.is_ok()); 69 | assert_eq!(handler.attributes.len(), 7); 70 | } 71 | #[test] 72 | fn explicit_little_endian() { 73 | let mut bytes = read_file("tests/fixtures/CT1_UNC.explicit_little_endian.dcm"); 74 | let mut handler = TestHandler::default(); 75 | //handler.print = true; 76 | let result = parse(&mut handler, &mut bytes); 77 | assert!(result.is_ok()); 78 | assert_eq!(265, handler.attributes.len()); 79 | } 80 | 81 | #[test] 82 | fn implicit_little_endian() { 83 | let mut bytes = read_file("tests/fixtures/CT1_UNC.implicit_little_endian.dcm"); 84 | let mut handler = TestHandler::default(); 85 | //handler.print = true; 86 | let result = parse(&mut handler, &mut bytes); 87 | assert!(result.is_ok()); 88 | assert_eq!(265, handler.attributes.len()); 89 | } 90 | 91 | #[test] 92 | fn explicit_big_endian() { 93 | let mut bytes = read_file("tests/fixtures/CT1_UNC.explicit_big_endian.dcm"); 94 | let mut handler = TestHandler::default(); 95 | //handler.print = true; 96 | let result = parse(&mut handler, &mut bytes); 97 | assert!(result.is_ok()); 98 | assert_eq!(265, handler.attributes.len()); 99 | } 100 | 101 | #[test] 102 | fn ele_sequences_known_lengths() { 103 | //(0008,9121) @ position 0x376 / 886 104 | let mut bytes = read_file("tests/fixtures/CT0012.fragmented_no_bot_jpeg_ls.80.dcm"); 105 | let mut handler = TestHandler::default(); 106 | //handler.print = true; 107 | let result = parse(&mut handler, &mut bytes); 108 | assert!(result.is_ok()); 109 | assert_eq!(165, handler.attributes.len()); 110 | } 111 | 112 | #[test] 113 | fn ile_sequences_undefined_lengths() { 114 | //(0008,9121) @ position 0x376 / 886 115 | let mut bytes = read_file("tests/fixtures/IM00001.implicit_little_endian.dcm"); 116 | let mut handler = TestHandler::default(); 117 | //handler.print = true; 118 | let result = parse(&mut handler, &mut bytes); 119 | assert!(result.is_ok()); 120 | assert_eq!(102, handler.attributes.len()); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/test.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | pub mod tests { 3 | 4 | use crate::attribute::Attribute; 5 | use crate::handler::{Handler, HandlerResult}; 6 | use crate::meta_information; 7 | use crate::meta_information::MetaInformation; 8 | use std::fs::File; 9 | use std::io::Read; 10 | 11 | /// Implementation of Handler that collects the attributes and data from 12 | /// callback functions and has an optional ability to print the output 13 | /// of the parse. This is used by tests and for general debugging 14 | 15 | #[derive(Default)] 16 | pub struct TestHandler { 17 | pub attributes: Vec, 18 | pub data: Vec>, 19 | pub depth: usize, 20 | pub print: bool, 21 | } 22 | 23 | impl Handler for TestHandler { 24 | fn attribute( 25 | &mut self, 26 | attribute: &Attribute, 27 | position: usize, 28 | data_offset: usize, 29 | ) -> HandlerResult { 30 | if self.print { 31 | println!( 32 | "{:- HandlerResult { 91 | if self.print { 92 | println!( 93 | "{:- HandlerResult { 110 | if self.print { 111 | println!( 112 | "{:- Vec { 126 | let mut file = File::open(filepath).unwrap(); 127 | let mut buffer = Vec::new(); 128 | file.read_to_end(&mut buffer).unwrap(); 129 | buffer 130 | } 131 | 132 | pub fn read_data_set_bytes_from_file(filepath: &str) -> (MetaInformation, Vec) { 133 | let bytes = read_file(&filepath); 134 | let mut handler = TestHandler::default(); 135 | 136 | let meta = match meta_information::parse(&mut handler, &bytes) { 137 | Ok(meta) => meta, 138 | Err(_parse_error) => panic!("Let's play Global Thermonuclear War"), 139 | }; 140 | //println!("meta.end_position={}", meta.end_position); 141 | let end_position = meta.end_position; 142 | (meta, (&bytes[end_position..]).to_vec()) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/value_parser/data_undefined_length.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::Attribute; 2 | use crate::encoding::Encoding; 3 | use crate::handler::Handler; 4 | use crate::value_parser::ParseError; 5 | use crate::value_parser::ParseResult; 6 | use crate::value_parser::ValueParser; 7 | use std::marker::PhantomData; 8 | 9 | #[derive(Default)] 10 | pub struct DataUndefinedLengthParser { 11 | pub phantom: PhantomData, 12 | } 13 | 14 | impl ValueParser for DataUndefinedLengthParser { 15 | fn parse( 16 | &mut self, 17 | handler: &mut dyn Handler, 18 | attribute: &Attribute, 19 | bytes: &[u8], 20 | _position: usize, 21 | ) -> Result { 22 | // we return immediately if we don't have at least 8 bytes of data 23 | // since it takes 8 bytes for the end of data marker 24 | if bytes.len() < 8 { 25 | return Ok(ParseResult::incomplete(0)); 26 | } 27 | 28 | // scan for end of marker 29 | let (length, end_of_data_marker_found) = find_end_of_data_marker::(bytes); 30 | 31 | // notify handler of data 32 | handler.data(attribute, &bytes[..length], end_of_data_marker_found); 33 | 34 | if end_of_data_marker_found { 35 | // include the size of the end of data marker in the number of 36 | // bytes consumed 37 | Ok(ParseResult::completed(length + 8)) 38 | } else { 39 | Ok(ParseResult::incomplete(length)) 40 | } 41 | } 42 | } 43 | 44 | fn find_end_of_data_marker(bytes: &[u8]) -> (usize, bool) { 45 | let mut position = 0; 46 | while position <= bytes.len() - 4 { 47 | let group = T::u16(&bytes[position..position + 2]); 48 | position += 2; 49 | if group == 0xFFFE { 50 | let element = T::u16(&bytes[position..position + 2]); 51 | if element == 0xE0DD { 52 | return (position - 2, true); 53 | } 54 | } 55 | } 56 | 57 | // we get here if we don't find the end of data. We don't want 58 | // to send any bytes from the end of data marker and since it 59 | // is possible that bytes currently has some of the end of data 60 | // marker already, we reduce the numnber of bytes to send to 61 | // the handler by the size of the end of data marker (8). 62 | (bytes.len() - 8, false) 63 | } 64 | 65 | #[cfg(test)] 66 | mod tests { 67 | 68 | use super::DataUndefinedLengthParser; 69 | use crate::attribute::Attribute; 70 | use crate::encoding::ExplicitLittleEndian; 71 | use crate::test::tests::TestHandler; 72 | use crate::value_parser::ParseState; 73 | use crate::value_parser::ValueParser; 74 | 75 | fn make_undefined_length_value() -> Vec { 76 | let mut bytes = vec![0; 250]; 77 | // end with sequence item delimeter 78 | bytes.extend_from_slice(&vec![0xFE, 0xFF, 0xDD, 0xE0, 0, 0, 0, 0]); 79 | 80 | bytes 81 | } 82 | 83 | #[test] 84 | fn full_parse_completes() { 85 | let mut parser = DataUndefinedLengthParser::::default(); 86 | let mut handler = TestHandler::default(); 87 | let mut attribute = Attribute::default(); 88 | attribute.length = 0xFFFF_FFFF; 89 | let bytes = make_undefined_length_value(); 90 | match parser.parse(&mut handler, &attribute, &bytes, 0) { 91 | Ok(result) => { 92 | assert_eq!(result.bytes_consumed, bytes.len()); 93 | assert_eq!(result.state, ParseState::Completed); 94 | } 95 | Err(_error) => { 96 | assert!(false); // should not happen 97 | } 98 | }; 99 | } 100 | 101 | #[test] 102 | fn dul_streaming_parse_completes() { 103 | let mut parser = DataUndefinedLengthParser::::default(); 104 | let mut handler = TestHandler::default(); 105 | let mut attribute = Attribute::default(); 106 | attribute.length = 0xFFFF_FFFF; 107 | let bytes = make_undefined_length_value(); 108 | match parser.parse(&mut handler, &attribute, &bytes[0..100], 0) { 109 | Ok(result1) => { 110 | assert_eq!(result1.bytes_consumed, 92); 111 | assert_eq!(result1.state, ParseState::Incomplete); 112 | match parser.parse( 113 | &mut handler, 114 | &attribute, 115 | &bytes[result1.bytes_consumed..], 116 | 0, 117 | ) { 118 | Ok(result2) => { 119 | assert_eq!(result2.bytes_consumed, bytes.len() - result1.bytes_consumed); 120 | assert_eq!(result2.state, ParseState::Completed); 121 | } 122 | Err(_error) => { 123 | assert!(false); // should not happen 124 | } 125 | }; 126 | } 127 | Err(_error) => { 128 | assert!(false); // should not happen 129 | } 130 | }; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/value_parser/sequence.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::Attribute; 2 | use crate::encoding::Encoding; 3 | use crate::handler::Handler; 4 | use crate::tag; 5 | use crate::tag::Tag; 6 | use crate::value_parser::sequence_item_data::SequenceItemDataParser; 7 | use crate::value_parser::ParseError; 8 | use crate::value_parser::ParseResult; 9 | use crate::value_parser::ParseState; 10 | use crate::value_parser::ValueParser; 11 | 12 | #[derive(Default)] 13 | pub struct SequenceParser { 14 | parser: Option>>, 15 | total_bytes_consumed: usize, 16 | } 17 | 18 | impl ValueParser for SequenceParser { 19 | fn parse( 20 | &mut self, 21 | handler: &mut dyn Handler, 22 | attribute: &Attribute, 23 | bytes: &[u8], 24 | position: usize, 25 | ) -> Result { 26 | // if we have a known length, only parse the bytes we know we have 27 | let mut remaining_bytes = if attribute.length == 0xFFFF_FFFF 28 | || bytes.len() < (attribute.length - self.total_bytes_consumed) 29 | { 30 | bytes 31 | } else { 32 | &bytes[0..(attribute.length - self.total_bytes_consumed)] 33 | }; 34 | 35 | let mut bytes_consumed = 0; 36 | 37 | // iterate over remaining bytes 38 | while !remaining_bytes.is_empty() { 39 | match &mut self.parser { 40 | None => { 41 | // read the tag and length or return incomplete if not enough 42 | // bytes 43 | if remaining_bytes.len() < 8 { 44 | return Ok(ParseResult::incomplete(bytes_consumed)); 45 | } 46 | let (tag, length) = parse_tag_and_length::(remaining_bytes); 47 | 48 | // if we have undefined length, return completed if we have a sequence 49 | // delimitation item (which marks the end of the sequence) 50 | if attribute.length == 0xFFFF_FFFF && tag == tag::SEQUENCEDELIMITATIONITEM { 51 | return Ok(ParseResult::completed(bytes_consumed + 8)); 52 | } 53 | 54 | // verify we have a sequence item tag and return error if not 55 | if tag != tag::ITEM { 56 | return Err(ParseError { 57 | reason: "expected Item tag FFFE,E000", 58 | position, 59 | }); 60 | } 61 | 62 | // update internal state 63 | bytes_consumed += 8; 64 | self.total_bytes_consumed += 8; 65 | remaining_bytes = &remaining_bytes[8..]; 66 | 67 | // notify handle that we are starting a new sequence item 68 | handler.start_sequence_item(attribute); 69 | 70 | // create a new SequenceItemDataParser for this sequence item 71 | self.parser = Some(Box::new(SequenceItemDataParser::::new(length))); 72 | } 73 | Some(parser) => { 74 | // we have a parser so forward the remaining bytes to it to be parsed 75 | let parse_result = parser.parse( 76 | handler, 77 | attribute, 78 | remaining_bytes, 79 | position + bytes_consumed, 80 | )?; 81 | 82 | // update internal state 83 | bytes_consumed += parse_result.bytes_consumed; 84 | self.total_bytes_consumed += parse_result.bytes_consumed; 85 | remaining_bytes = &remaining_bytes[parse_result.bytes_consumed..]; 86 | 87 | // handle parse result 88 | match parse_result.state { 89 | ParseState::Cancelled => { 90 | return Ok(ParseResult::cancelled(bytes_consumed)); 91 | } 92 | ParseState::Incomplete => { 93 | return Ok(ParseResult::incomplete(bytes_consumed)); 94 | } 95 | ParseState::Completed => { 96 | handler.end_sequence_item(attribute); 97 | self.parser = None; 98 | continue; // continue if there are more bytes to parse 99 | } 100 | } 101 | } 102 | } 103 | } 104 | 105 | // if we have a known length and have consumed all the bytes, we are complete, 106 | // otherwise we are incomplete 107 | if attribute.length == 0xFFFF_FFFF || self.total_bytes_consumed < attribute.length { 108 | Ok(ParseResult::incomplete(bytes_consumed)) 109 | } else { 110 | handler.end_sequence(attribute); 111 | Ok(ParseResult::completed(bytes_consumed)) 112 | } 113 | } 114 | } 115 | 116 | pub fn parse_sequence_item(bytes: &[u8]) -> Result { 117 | let item_tag = Tag::from_bytes::(&bytes[0..4]); 118 | let length = T::u32(&bytes[4..8]) as usize; 119 | 120 | if item_tag != tag::ITEM { 121 | return Err(()); 122 | } 123 | 124 | Ok(length) 125 | } 126 | 127 | pub fn parse_tag_and_length(bytes: &[u8]) -> (Tag, usize) { 128 | let tag = Tag::from_bytes::(&bytes[0..4]); 129 | let length = T::u32(&bytes[4..8]) as usize; 130 | (tag, length) 131 | } 132 | -------------------------------------------------------------------------------- /src/meta_information.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::Attribute; 2 | use crate::data_set_parser::parse_full; 3 | use crate::encoding::ExplicitLittleEndian; 4 | use crate::handler::cancel::CancelHandler; 5 | use crate::handler::tee::TeeHandler; 6 | use crate::handler::Handler; 7 | use crate::handler::HandlerResult; 8 | use crate::prefix; 9 | use crate::tag::Tag; 10 | use crate::value_parser::ParseError; 11 | use std::str; 12 | 13 | /* 14 | macro_rules! map_string { 15 | ($group:expr, $element:expr, $property:expr) => { 16 | if attribute.tag.group == group && attribute.tag.element == element { 17 | property = str::from_utf8(self.data_buffer).unwrap() 18 | } 19 | }; 20 | }*/ 21 | 22 | /// MetaInformation includes the required attributes from the DICOM P10 Header 23 | #[derive(Debug, Default)] 24 | pub struct MetaInformation { 25 | /// The SOP Class UID 26 | pub media_storage_sop_class_uid: String, 27 | /// The SOP Instance UID 28 | pub media_storage_sop_instance_uid: String, 29 | /// The Transfer Syntax UID 30 | pub transfer_syntax_uid: String, 31 | /// The Implementation Class UID 32 | pub implementation_class_uid: String, 33 | /// The offset from the beginning of the file that the DICOM P10 header 34 | /// ends at 35 | pub end_position: usize, 36 | } 37 | 38 | struct MetaInformationBuilder<'a> { 39 | pub meta_information: &'a mut MetaInformation, 40 | 41 | // buffer to accumulate data for an attribute 42 | data_buffer: Vec, 43 | } 44 | 45 | impl Handler for MetaInformationBuilder<'_> { 46 | fn attribute( 47 | &mut self, 48 | _attribute: &Attribute, 49 | _position: usize, 50 | _data_offset: usize, 51 | ) -> HandlerResult { 52 | self.data_buffer.clear(); 53 | HandlerResult::Continue 54 | } 55 | fn data(&mut self, attribute: &Attribute, data: &[u8], complete: bool) { 56 | if attribute.length == 0 { 57 | return; 58 | } 59 | self.data_buffer.extend_from_slice(data); 60 | 61 | if complete { 62 | let bytes = if self.data_buffer[attribute.length - 1] != 0 { 63 | &self.data_buffer 64 | } else { 65 | &self.data_buffer[0..(attribute.length - 1)] 66 | }; 67 | 68 | if attribute.tag == Tag::new(0x0002, 0x02) { 69 | self.meta_information.media_storage_sop_class_uid = 70 | String::from(str::from_utf8(&bytes).unwrap()); 71 | } else if attribute.tag == Tag::new(0x0002, 0x0003) { 72 | self.meta_information.media_storage_sop_instance_uid = 73 | String::from(str::from_utf8(&bytes).unwrap()); 74 | } else if attribute.tag == Tag::new(0x0002, 0x0010) { 75 | self.meta_information.transfer_syntax_uid = 76 | String::from(str::from_utf8(&bytes).unwrap()); 77 | } else if attribute.tag == Tag::new(0x0002, 0x0012) { 78 | self.meta_information.implementation_class_uid = 79 | String::from(str::from_utf8(&bytes).unwrap()); 80 | } 81 | } 82 | } 83 | } 84 | 85 | /// Parses the DICOM P10 Header and returns it as a MetaInformation instance 86 | /// 87 | /// # Arguments 88 | /// 89 | /// * `bytes` - bytes containg the entire DICOM P10 Header including the 90 | /// preamble 91 | pub fn parse<'a, T: Handler>( 92 | handler: &'a mut T, 93 | bytes: &[u8], 94 | ) -> Result { 95 | // validate that we have a P10 Header Prefix 96 | prefix::validate(bytes)?; 97 | 98 | // Create a MetaInformationBuilder 99 | let mut meta_information = MetaInformation::default(); 100 | let mut builder = MetaInformationBuilder { 101 | meta_information: &mut meta_information, 102 | data_buffer: vec![], 103 | }; 104 | 105 | // Create a TeeHandler that forwards Handler callbacks to 106 | // the user supplied Handler and our MetaInformationBuilder 107 | let mut tee_handler = TeeHandler::default(); 108 | tee_handler.handlers.push(handler); 109 | tee_handler.handlers.push(&mut builder); 110 | 111 | // create a CancelHandler that will cancel the parse when we see an attribute that has a 112 | // tag not in group 2 (All meta information tags are group 2) 113 | let mut handler = CancelHandler::new(&mut tee_handler, |x: &Attribute| x.tag.group != 2); 114 | 115 | // attempt to fully parse the p10 header. _cancelled will typically be true. 116 | // If _cancelled is false, we may not have a complete p10 header - we can't 117 | // distinguish between these two until we validate the contents (which we do 118 | // afterwards) 119 | let (bytes_consumed, _cancelled) = 120 | parse_full::(&mut handler, &bytes[132..], 132)?; 121 | 122 | // calculate the end position of the p10 header by adding the prefix length 123 | // to the number of bytes consumed parsing the meta information 124 | meta_information.end_position = 132 + bytes_consumed; 125 | 126 | Ok(meta_information) 127 | } 128 | 129 | #[cfg(test)] 130 | pub mod tests { 131 | use super::parse; 132 | use crate::test::tests::TestHandler; 133 | 134 | fn make_preamble_and_prefix() -> Vec { 135 | let mut bytes = vec![]; 136 | bytes.resize(132, 0); 137 | bytes[128] = 'D' as u8; 138 | bytes[129] = 'I' as u8; 139 | bytes[130] = 'C' as u8; 140 | bytes[131] = 'M' as u8; 141 | 142 | bytes 143 | } 144 | 145 | pub fn make_p10_header() -> Vec { 146 | let mut bytes = make_preamble_and_prefix(); 147 | bytes.extend_from_slice(&vec![0x02, 0x00, 0x00, 0x00, b'U', b'L', 4, 0, 0, 0, 0, 0]); 148 | bytes.extend_from_slice(&vec![ 149 | 0x02, 0x00, 0x01, 0x00, b'O', b'B', 0, 0, 2, 0, 0, 0, 0, 1, 150 | ]); 151 | bytes.extend_from_slice(&vec![0x02, 0x00, 0x02, 0x00, b'U', b'I', 2, 0, b'1', 0]); 152 | bytes.extend_from_slice(&vec![0x02, 0x00, 0x03, 0x00, b'U', b'I', 2, 0, b'2', 0]); 153 | bytes.extend_from_slice(&vec![0x02, 0x00, 0x10, 0x00, b'U', b'I', 2, 0, b'3', 0]); 154 | bytes.extend_from_slice(&vec![0x02, 0x00, 0x12, 0x00, b'U', b'I', 2, 0, b'4', 0]); 155 | 156 | let length = bytes.len() as u32; 157 | bytes[140] = (length & 0xff) as u8; 158 | bytes[141] = (length >> 8 & 0xff) as u8; 159 | 160 | bytes 161 | } 162 | 163 | #[test] 164 | fn valid_meta_information() { 165 | let bytes = make_p10_header(); 166 | let mut handler = TestHandler::default(); 167 | //handler.print = true; 168 | match parse(&mut handler, &bytes) { 169 | Ok(_meta) => { 170 | //println!("{:?}", _meta); 171 | //assert_eq!(meta.data_set.attributes.len(), 6); 172 | } 173 | Err(_parse_error) => { 174 | assert!(false); 175 | } 176 | }; 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/value_parser/encapsulated_pixel_data.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::Attribute; 2 | use crate::encoding::Encoding; 3 | use crate::handler::Handler; 4 | use crate::tag; 5 | use crate::tag::Tag; 6 | use crate::value_parser::ParseError; 7 | use crate::value_parser::ParseResult; 8 | use crate::value_parser::ValueParser; 9 | use std::marker::PhantomData; 10 | 11 | pub struct EncapsulatedPixelDataParser { 12 | phantom: PhantomData, 13 | total_bytes_consumed: usize, 14 | remaining_byte_count: usize, 15 | item_number: usize, 16 | } 17 | 18 | impl EncapsulatedPixelDataParser { 19 | pub fn default() -> EncapsulatedPixelDataParser { 20 | EncapsulatedPixelDataParser { 21 | phantom: PhantomData, 22 | total_bytes_consumed: 0, 23 | remaining_byte_count: 0, 24 | item_number: 0, 25 | } 26 | } 27 | } 28 | 29 | impl ValueParser for EncapsulatedPixelDataParser { 30 | fn parse( 31 | &mut self, 32 | handler: &mut dyn Handler, 33 | attribute: &Attribute, 34 | bytes: &[u8], 35 | position: usize, 36 | ) -> Result { 37 | let mut remaining_bytes = bytes; 38 | let mut bytes_consumed = 0; 39 | 40 | // iterate over remaining bytes parsing them 41 | while !remaining_bytes.is_empty() { 42 | // if no bytes remaining, we are at a new item so read the 43 | // tag and length 44 | if self.remaining_byte_count == 0 { 45 | // read tag and length 46 | if remaining_bytes.len() < 8 { 47 | return Ok(ParseResult::incomplete(bytes_consumed)); 48 | } 49 | let (tag, length) = parse_tag_and_length::(remaining_bytes); 50 | self.total_bytes_consumed += 8; 51 | bytes_consumed += 8; 52 | remaining_bytes = &remaining_bytes[8..]; 53 | 54 | // if sequence delimtation item, we are done 55 | if tag == tag::SEQUENCEDELIMITATIONITEM { 56 | return Ok(ParseResult::completed(bytes_consumed)); 57 | } 58 | 59 | // make sure we have a sequence item 60 | if tag != tag::ITEM { 61 | return Err(ParseError { 62 | reason: "expected Item tag FFFE,E000", 63 | position: position + bytes_consumed, 64 | }); 65 | } 66 | 67 | // make sure item length is not undefined 68 | if length == 0xFFFF_FFFF { 69 | return Err(ParseError { 70 | reason: "expected defined length", 71 | position: position + bytes_consumed, 72 | }); 73 | } 74 | 75 | // set remaining_byte_count so we know how much data to stream 76 | // to the handler 77 | self.remaining_byte_count = length; 78 | } 79 | 80 | // get slice of bytes based on how many we have already parsed for this value field 81 | let value_bytes = if remaining_bytes.len() > self.remaining_byte_count { 82 | &remaining_bytes[..self.remaining_byte_count] 83 | } else { 84 | remaining_bytes 85 | }; 86 | 87 | // invoke appropriate function on handler (basic offset table is always first) 88 | let complete = self.remaining_byte_count == value_bytes.len(); 89 | if self.item_number == 0 { 90 | handler.basic_offset_table(attribute, value_bytes, complete); 91 | } else { 92 | handler.pixel_data_fragment(attribute, self.item_number, value_bytes, complete); 93 | } 94 | 95 | // update counters 96 | self.total_bytes_consumed += value_bytes.len(); 97 | self.remaining_byte_count -= value_bytes.len(); 98 | bytes_consumed += value_bytes.len(); 99 | remaining_bytes = &remaining_bytes[value_bytes.len()..]; 100 | 101 | // if we finished this item, increment item_number 102 | if self.remaining_byte_count == 0 { 103 | self.item_number += 1; 104 | } 105 | } 106 | 107 | Ok(ParseResult::incomplete(bytes_consumed)) 108 | } 109 | } 110 | 111 | pub fn parse_tag_and_length(bytes: &[u8]) -> (Tag, usize) { 112 | let tag = Tag::from_bytes::(&bytes[0..4]); 113 | let length = T::u32(&bytes[4..8]) as usize; 114 | (tag, length) 115 | } 116 | 117 | #[cfg(test)] 118 | mod tests { 119 | 120 | use super::EncapsulatedPixelDataParser; 121 | use crate::attribute::Attribute; 122 | use crate::encoding::ExplicitLittleEndian; 123 | use crate::tag::Tag; 124 | use crate::test::tests::TestHandler; 125 | use crate::value_parser::ParseState; 126 | use crate::value_parser::ValueParser; 127 | use crate::vr::VR; 128 | 129 | fn make_encapsulated_pixel_data_value_with_empty_bot() -> Vec { 130 | let mut bytes = vec![]; 131 | // Basic Offset Table (Empty) 132 | bytes.extend_from_slice(&vec![0xFE, 0xFF, 0x00, 0xE0, 0, 0, 0, 0]); 133 | // Fragment #1 (250 zeros) 134 | bytes.extend_from_slice(&vec![0xFE, 0xFF, 0x00, 0xE0, 250, 0, 0, 0]); 135 | bytes.extend_from_slice(&vec![0; 250]); 136 | // Fragment #2 (150 zeros) 137 | bytes.extend_from_slice(&vec![0xFE, 0xFF, 0x00, 0xE0, 150, 0, 0, 0]); 138 | bytes.extend_from_slice(&vec![0; 150]); 139 | // end with sequence item delimeter 140 | bytes.extend_from_slice(&vec![0xFE, 0xFF, 0xDD, 0xE0, 0, 0, 0, 0]); 141 | 142 | bytes 143 | } 144 | 145 | #[test] 146 | fn full_parse_completes() { 147 | let mut parser = EncapsulatedPixelDataParser::::default(); 148 | let mut handler = TestHandler::default(); 149 | let bytes = make_encapsulated_pixel_data_value_with_empty_bot(); 150 | let mut attribute = Attribute::default(); 151 | attribute.tag = Tag::new(0x7fe0, 0x0010); 152 | attribute.vr = Some(VR::OB); 153 | attribute.length = 0xFFFF_FFFF; 154 | 155 | match parser.parse(&mut handler, &attribute, &bytes[..], 0) { 156 | Ok(result) => { 157 | assert_eq!(result.bytes_consumed, bytes.len()); 158 | assert_eq!(result.state, ParseState::Completed); 159 | } 160 | Err(_error) => { 161 | assert!(false); // should not happen 162 | } 163 | }; 164 | } 165 | 166 | #[test] 167 | fn streaming_parse_completes() { 168 | let mut parser = EncapsulatedPixelDataParser::::default(); 169 | let mut handler = TestHandler::default(); 170 | //handler.print = true; 171 | let bytes = make_encapsulated_pixel_data_value_with_empty_bot(); 172 | let mut attribute = Attribute::default(); 173 | attribute.tag = Tag::new(0x7fe0, 0x0010); 174 | attribute.vr = Some(VR::OB); 175 | attribute.length = 0xFFFF_FFFF; 176 | 177 | match parser.parse(&mut handler, &attribute, &bytes[0..100], 0) { 178 | Ok(result) => { 179 | assert_eq!(result.bytes_consumed, 100); 180 | assert_eq!(result.state, ParseState::Incomplete); 181 | } 182 | Err(_error) => { 183 | assert!(false); // should not happen 184 | } 185 | }; 186 | match parser.parse(&mut handler, &attribute, &bytes[100..], 0) { 187 | Ok(result) => { 188 | assert_eq!(result.bytes_consumed, bytes.len() - 100); 189 | assert_eq!(result.state, ParseState::Completed); 190 | } 191 | Err(_error) => { 192 | assert!(false); // should not happen 193 | } 194 | }; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/data_set_parser.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute_parser::AttributeParser; 2 | use crate::encoding::Encoding; 3 | use crate::handler::Handler; 4 | use crate::value_parser::ParseError; 5 | use crate::value_parser::ParseResult; 6 | use crate::value_parser::ParseState; 7 | 8 | pub struct DataSetParser { 9 | parser: AttributeParser, 10 | total_bytes_consumed: usize, 11 | } 12 | 13 | impl DataSetParser { 14 | pub fn default() -> DataSetParser { 15 | DataSetParser { 16 | parser: AttributeParser::::default(), 17 | total_bytes_consumed: 0, 18 | } 19 | } 20 | } 21 | 22 | impl DataSetParser { 23 | pub fn parse( 24 | &mut self, 25 | handler: &mut dyn Handler, 26 | bytes: &[u8], 27 | bytes_from_beginning: usize, 28 | ) -> Result { 29 | // initial state 30 | let mut remaining_bytes = bytes; 31 | let mut bytes_consumed = 0; 32 | 33 | // iterate over remaining bytes until empty 34 | while !remaining_bytes.is_empty() { 35 | // initialize position 36 | let position = bytes_from_beginning + bytes_consumed; 37 | 38 | // pares the remaining bytes 39 | let result = self.parser.parse(handler, remaining_bytes, position)?; 40 | 41 | // update internal state 42 | bytes_consumed += result.bytes_consumed; 43 | self.total_bytes_consumed += result.bytes_consumed; 44 | remaining_bytes = &remaining_bytes[result.bytes_consumed..]; 45 | 46 | // handle the parse result state 47 | match result.state { 48 | ParseState::Cancelled => { 49 | return Ok(ParseResult::cancelled(bytes_consumed)); 50 | } 51 | ParseState::Incomplete => { 52 | return Ok(ParseResult::incomplete(bytes_consumed)); 53 | } 54 | ParseState::Completed => { 55 | self.parser = AttributeParser::::default(); 56 | continue; // continue on to next attribute 57 | } 58 | } 59 | } 60 | // All bytes parsed, return completed. Note that we may yet get 61 | // additional bytes to parse if data is being streamed 62 | Ok(ParseResult::completed(bytes_consumed)) 63 | } 64 | } 65 | 66 | // returns 67 | // number of bytes parsed 68 | // true if cancelled, false, if not 69 | pub fn parse_full( 70 | handler: &mut dyn Handler, 71 | bytes: &[u8], 72 | bytes_from_beginning: usize, 73 | ) -> Result<(usize, bool), ParseError> { 74 | let mut parser = DataSetParser::::default(); 75 | match parser.parse(handler, bytes, bytes_from_beginning) { 76 | Ok(parse_result) => match parse_result.state { 77 | ParseState::Cancelled => Ok((parse_result.bytes_consumed, true)), 78 | ParseState::Incomplete => Err(ParseError { 79 | reason: "unexpected EOF", 80 | position: parse_result.bytes_consumed + bytes_from_beginning, 81 | }), 82 | ParseState::Completed => Ok((parse_result.bytes_consumed, false)), 83 | }, 84 | Err(parse_error) => Err(parse_error), 85 | } 86 | } 87 | 88 | #[cfg(test)] 89 | mod tests { 90 | 91 | use super::DataSetParser; 92 | use crate::encoding::{ExplicitLittleEndian, ImplicitLittleEndian}; 93 | use crate::test::tests::read_data_set_bytes_from_file; 94 | use crate::test::tests::TestHandler; 95 | use crate::value_parser::ParseError; 96 | use crate::value_parser::ParseState; 97 | 98 | fn parse_ele_data_set( 99 | bytes: &[u8], 100 | bytes_from_beginning: usize, 101 | ) -> Result<(ParseState, usize), ParseError> { 102 | let mut handler = TestHandler::default(); 103 | let mut parser = DataSetParser::::default(); 104 | let result = parser.parse(&mut handler, bytes, bytes_from_beginning)?; 105 | Ok((result.state, result.bytes_consumed)) 106 | } 107 | #[test] 108 | fn parse_full_ok() { 109 | let (meta, bytes) = 110 | read_data_set_bytes_from_file("tests/fixtures/CT1_UNC.explicit_little_endian.dcm"); 111 | let result = parse_ele_data_set(&bytes[..], meta.end_position); 112 | assert!(result.is_ok()); 113 | } 114 | /* 115 | 116 | fn split_parse(bytes: &[u8], split_position: usize) -> Result<(), ()> { 117 | println!("split_parse @ {} of {} ", split_position, bytes.len()); 118 | let mut handler = DataSetHandler::default(); 119 | //handler.print = true; 120 | let mut parser = DataSetParser::::default(); 121 | let result = parser.parse(&mut handler, &bytes[0..split_position])?; 122 | //println!("bytes_consumed: {:?}", result.bytes_consumed); 123 | //println!("state: {:?}", result.state); 124 | let result2 = parser.parse(&mut handler, &bytes[result.bytes_consumed..])?; 125 | //println!("bytes_consumed: {:?}", result2.bytes_consumed); 126 | //println!("state: {:?}", result2.state); 127 | assert_eq!(result2.bytes_consumed, bytes.len() - result.bytes_consumed); 128 | Ok(()) 129 | } 130 | #[test] 131 | fn parse_partial_debug() { 132 | //let bytes = read_data_set_bytes_from_file("tests/fixtures/CT1_UNC.explicit_little_endian.dcm"); 133 | //let bytes = read_data_set_bytes_from_file("tests/fixtures/CT0012.fragmented_no_bot_jpeg_ls.80.dcm"); 134 | let bytes = 135 | read_data_set_bytes_from_file("tests/fixtures/IM00001.implicit_little_endian.dcm"); // meta ends at 352 136 | 137 | // 3576 + 336 = 3912 xF48 (in VR of Pixel Data Attribute? ) 138 | let result = split_parse(&bytes, 3576); 139 | assert!(result.is_ok()); 140 | //println!("{:?}", result); 141 | } 142 | */ 143 | 144 | #[test] 145 | fn explicit_little_endian_streaming_parse_ok() { 146 | let (meta, bytes) = 147 | read_data_set_bytes_from_file("tests/fixtures/CT0012.fragmented_no_bot_jpeg_ls.80.dcm"); 148 | let mut handler = TestHandler::default(); 149 | //handler.print = true; 150 | let mut parser = DataSetParser::::default(); 151 | let mut offset = 0; 152 | for i in 0..bytes.len() { 153 | match parser.parse(&mut handler, &bytes[offset..i + 1], meta.end_position) { 154 | Ok(parse_result) => { 155 | if parse_result.bytes_consumed != 0 { 156 | //println!("consumed {} bytes", parse_result.bytes_consumed) 157 | } 158 | offset += parse_result.bytes_consumed; 159 | } 160 | Err(_error) => panic!("Let's play Global Thermonuclear War"), 161 | } 162 | } 163 | assert_eq!(157, handler.attributes.len()); 164 | } 165 | 166 | #[test] 167 | fn implicit_little_endian_streaming_parse_ok() { 168 | let (meta, bytes) = 169 | read_data_set_bytes_from_file("tests/fixtures/IM00001.implicit_little_endian.dcm"); 170 | let mut handler = TestHandler::default(); 171 | //handler.print = true; 172 | let mut parser = DataSetParser::::default(); 173 | let mut offset = 0; 174 | for i in 0..bytes.len() { 175 | match parser.parse(&mut handler, &bytes[offset..i + 1], meta.end_position + i) { 176 | Ok(parse_result) => { 177 | if parse_result.bytes_consumed != 0 { 178 | //println!("consumed {} bytes", parse_result.bytes_consumed) 179 | } 180 | offset += parse_result.bytes_consumed; 181 | } 182 | Err(_error) => panic!("Let's play Global Thermonuclear War"), 183 | } 184 | } 185 | assert_eq!(94, handler.attributes.len()); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/value_parser/sequence_item_data.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::Attribute; 2 | use crate::data_set_parser::DataSetParser; 3 | use crate::encoding::Encoding; 4 | use crate::handler::cancel::CancelHandler; 5 | use crate::handler::Handler; 6 | use crate::tag; 7 | use crate::value_parser::ParseError; 8 | use crate::value_parser::ParseResult; 9 | use crate::value_parser::ValueParser; 10 | 11 | pub struct SequenceItemDataParser { 12 | item_length: usize, 13 | parser: DataSetParser, 14 | total_bytes_consumed: usize, 15 | } 16 | 17 | impl SequenceItemDataParser { 18 | pub fn new(item_length: usize) -> SequenceItemDataParser { 19 | SequenceItemDataParser { 20 | item_length, 21 | parser: DataSetParser::::default(), 22 | total_bytes_consumed: 0, 23 | } 24 | } 25 | } 26 | 27 | impl ValueParser for SequenceItemDataParser { 28 | fn parse( 29 | &mut self, 30 | handler: &mut dyn Handler, 31 | _attribute: &Attribute, 32 | bytes: &[u8], 33 | position: usize, 34 | ) -> Result { 35 | // if we have a known length, only parse the bytes we know we have 36 | let remaining_bytes = if self.item_length == 0xFFFF_FFFF 37 | || bytes.len() < (self.item_length - self.total_bytes_consumed) 38 | { 39 | bytes 40 | } else { 41 | &bytes[0..(self.item_length - self.total_bytes_consumed)] 42 | }; 43 | 44 | // setup a cancel handler based on the item delimitation item 45 | let mut sequence_item_handler = 46 | CancelHandler::new(handler, |x: &Attribute| x.tag == tag::ITEMDELIMITATIONITEM); 47 | 48 | // forward bytes to the DataSetParser 49 | let parse_result = 50 | self.parser 51 | .parse(&mut sequence_item_handler, remaining_bytes, position)?; 52 | 53 | // update internal state 54 | self.total_bytes_consumed += parse_result.bytes_consumed; 55 | 56 | // if the parse was cancelled due to the item delimitation item, add 57 | // the size of the item delimitation item to the bytes consumed 58 | if sequence_item_handler.canceled { 59 | self.total_bytes_consumed += 8; 60 | } 61 | 62 | if sequence_item_handler.canceled { 63 | // if the parse was cancelled due to hitting the item delimitation item, 64 | // we are complete 65 | Ok(ParseResult::completed(parse_result.bytes_consumed + 8)) 66 | } else if self.total_bytes_consumed == self.item_length { 67 | // if we have a known length and have consumed all the bytes, we are complete 68 | Ok(ParseResult::completed(parse_result.bytes_consumed)) 69 | } else if self.total_bytes_consumed < self.item_length { 70 | // if we have a known length and have consumed fewer bytes, we are incomplete 71 | Ok(ParseResult::incomplete(parse_result.bytes_consumed)) 72 | } else { 73 | // not sure this can happen 74 | Ok(parse_result) 75 | } 76 | } 77 | } 78 | 79 | #[cfg(test)] 80 | mod tests { 81 | use super::SequenceItemDataParser; 82 | use crate::attribute::Attribute; 83 | use crate::encoding::ExplicitLittleEndian; 84 | use crate::tag::Tag; 85 | use crate::test::tests::TestHandler; 86 | use crate::value_parser::ParseState; 87 | use crate::value_parser::ValueParser; 88 | 89 | fn make_sequence_item_undefined_length() -> Vec { 90 | let mut bytes = vec![]; 91 | bytes.extend_from_slice(&vec![0x02, 0x00, 0x00, 0x00, b'U', b'L', 4, 0, 0, 0, 0, 0]); 92 | bytes.extend_from_slice(&vec![0xFE, 0xFF, 0x0D, 0xE0, 0, 0, 0, 0]); 93 | 94 | bytes 95 | } 96 | 97 | fn make_sequence_item_known_length() -> Vec { 98 | let mut bytes = vec![]; 99 | bytes.extend_from_slice(&vec![0x02, 0x00, 0x00, 0x00, b'U', b'L', 4, 0, 0, 0, 0, 0]); 100 | 101 | bytes 102 | } 103 | 104 | #[test] 105 | fn known_length_completes() { 106 | let bytes = make_sequence_item_known_length(); 107 | let mut parser = SequenceItemDataParser::::new(bytes.len()); 108 | let mut handler = TestHandler::default(); 109 | let attribute = Attribute { 110 | tag: Tag::new(0x0008, 0x0008), 111 | vr: None, 112 | length: 0, 113 | }; 114 | match parser.parse(&mut handler, &attribute, &bytes[..], 0) { 115 | Ok(result) => { 116 | assert_eq!(result.bytes_consumed, 12); 117 | assert_eq!(result.state, ParseState::Completed); 118 | } 119 | Err(_parse_result) => panic!("Let's play Global Thermonuclear War"), 120 | } 121 | } 122 | 123 | #[test] 124 | fn partial_known_length_returns_incomplete() { 125 | let bytes = make_sequence_item_known_length(); 126 | let mut parser = SequenceItemDataParser::::new(bytes.len()); 127 | let mut handler = TestHandler::default(); 128 | let attribute = Attribute { 129 | tag: Tag::new(0x0008, 0x0008), 130 | vr: None, 131 | length: 0, 132 | }; 133 | match parser.parse(&mut handler, &attribute, &bytes[..1], 0) { 134 | Ok(result) => { 135 | assert_eq!(result.bytes_consumed, 0); 136 | assert_eq!(result.state, ParseState::Incomplete); 137 | } 138 | Err(_parse_error) => panic!("Let's play Global Thermonuclear War"), 139 | } 140 | } 141 | 142 | #[test] 143 | fn undefined_length_completes() { 144 | let bytes = make_sequence_item_undefined_length(); 145 | let mut parser = SequenceItemDataParser::::new(0xFFFF_FFFF); 146 | let mut handler = TestHandler::default(); 147 | let attribute = Attribute { 148 | tag: Tag::new(0x0008, 0x0008), 149 | vr: None, 150 | length: 0, 151 | }; 152 | match parser.parse(&mut handler, &attribute, &bytes[..], 0) { 153 | Ok(result) => { 154 | assert_eq!(result.bytes_consumed, 20); 155 | assert_eq!(result.state, ParseState::Completed); 156 | } 157 | Err(_) => panic!("Let's play Global Thermonuclear War"), 158 | }; 159 | } 160 | 161 | #[test] 162 | fn partial_undefined_length_returns_incomplete() { 163 | let bytes = make_sequence_item_undefined_length(); 164 | let mut parser = SequenceItemDataParser::::new(0xFFFF_FFFF); 165 | let mut handler = TestHandler::default(); 166 | let attribute = Attribute { 167 | tag: Tag::new(0x0008, 0x0008), 168 | vr: None, 169 | length: 0, 170 | }; 171 | match parser.parse(&mut handler, &attribute, &bytes[0..1], 0) { 172 | Ok(result) => { 173 | assert_eq!(result.bytes_consumed, 0); 174 | assert_eq!(result.state, ParseState::Incomplete); 175 | } 176 | Err(_error) => { 177 | panic!("Let's play Global Thermonuclear War"); 178 | } 179 | } 180 | } 181 | 182 | #[test] 183 | fn partial_in_item_delimetation_item_undefined_length_returns_incomplete() { 184 | let bytes = make_sequence_item_undefined_length(); 185 | let mut parser = SequenceItemDataParser::::new(0xFFFF_FFFF); 186 | let mut handler = TestHandler::default(); 187 | let attribute = Attribute { 188 | tag: Tag::new(0x0008, 0x0008), 189 | vr: None, 190 | length: 0, 191 | }; 192 | match parser.parse(&mut handler, &attribute, &bytes[0..13], 0) { 193 | Ok(result) => { 194 | assert_eq!(result.bytes_consumed, 12); 195 | assert_eq!(result.state, ParseState::Incomplete); 196 | } 197 | Err(_error) => panic!("Let's play Global Thermonuclear War"), 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/attribute_parser.rs: -------------------------------------------------------------------------------- 1 | use crate::attribute::Attribute; 2 | use crate::encoding::Encoding; 3 | use crate::handler::Handler; 4 | use crate::handler::HandlerResult; 5 | use crate::tag; 6 | use crate::tag::Tag; 7 | use crate::value_parser::data::DataParser; 8 | use crate::value_parser::data_undefined_length::DataUndefinedLengthParser; 9 | use crate::value_parser::encapsulated_pixel_data::EncapsulatedPixelDataParser; 10 | use crate::value_parser::sequence::SequenceParser; 11 | use crate::value_parser::ParseError; 12 | use crate::value_parser::ParseResult; 13 | use crate::value_parser::ValueParser; 14 | use crate::vr::VR; 15 | 16 | #[derive(Default)] 17 | pub struct AttributeParser { 18 | attribute: Attribute, 19 | parser: Option>>, 20 | } 21 | 22 | impl AttributeParser { 23 | pub fn parse( 24 | &mut self, 25 | handler: &mut dyn Handler, 26 | bytes: &[u8], 27 | bytes_from_beginning: usize, 28 | ) -> Result { 29 | match &mut self.parser { 30 | None => { 31 | // try to parse the attribute 32 | let (bytes_consumed, attribute) = match parse_attribute::(bytes) { 33 | Ok((bytes_consumed, attribute)) => (bytes_consumed, attribute), 34 | Err(()) => { 35 | // not enough bytes to parse the attribute so return incomplete 36 | return Ok(ParseResult::incomplete(0)); 37 | } 38 | }; 39 | 40 | self.attribute = attribute; 41 | 42 | // notify the handler of the attribute and return cancelled if the handler 43 | // cancels it 44 | match handler.attribute(&self.attribute, bytes_from_beginning, bytes_consumed) { 45 | HandlerResult::Continue => {} 46 | HandlerResult::Cancel => { 47 | return Ok(ParseResult::cancelled(0)); 48 | } 49 | } 50 | 51 | // update internal state 52 | let data_position = bytes_from_beginning + bytes_consumed; 53 | let remaining_bytes = &bytes[bytes_consumed..]; 54 | 55 | // if we have a known length, just get the value field bytes 56 | let value_bytes = if attribute.length == 0xFFFF_FFFF 57 | || remaining_bytes.len() < attribute.length 58 | { 59 | remaining_bytes 60 | } else { 61 | &remaining_bytes[0..attribute.length] 62 | }; 63 | 64 | // create the appropriate value parser for this attribute. 65 | self.parser = Some(make_parser::(handler, &attribute, value_bytes)); 66 | 67 | // parse the value bytes 68 | let mut parse_result = self.parser.as_mut().unwrap().parse( 69 | handler, 70 | &self.attribute, 71 | value_bytes, 72 | data_position, 73 | )?; 74 | 75 | // add in the size of the attribute tag/vr/length to the bytes consumed and 76 | // return the parse result 77 | parse_result.bytes_consumed += bytes_consumed; 78 | Ok(parse_result) 79 | } 80 | Some(parser) => parser.parse(handler, &self.attribute, bytes, bytes_from_beginning), 81 | } 82 | } 83 | } 84 | 85 | fn make_parser( 86 | handler: &mut dyn Handler, 87 | attribute: &Attribute, 88 | bytes: &[u8], 89 | ) -> Box> { 90 | if attribute.vr == Some(VR::SQ) { 91 | handler.start_sequence(attribute); 92 | Box::new(SequenceParser::::default()) 93 | } else if is_encapsulated_pixel_data(attribute) { 94 | Box::new(EncapsulatedPixelDataParser::default()) 95 | } else if attribute.length == 0xFFFF_FFFF { 96 | // TODO: Consider moving sequence parsing into dataundefinedlengthparser 97 | if is_sequence::(bytes) { 98 | handler.start_sequence(attribute); 99 | Box::new(SequenceParser::::default()) 100 | } else { 101 | Box::new(DataUndefinedLengthParser::::default()) 102 | } 103 | } else { 104 | Box::new(DataParser::::default()) 105 | } 106 | } 107 | 108 | fn parse_attribute(bytes: &[u8]) -> Result<(usize, Attribute), ()> { 109 | if bytes.len() < 6 { 110 | return Err(()); 111 | } 112 | let group = T::u16(&bytes[0..2]); 113 | let element = T::u16(&bytes[2..4]); 114 | let tag = Tag::new(group, element); 115 | 116 | let (vr, length, bytes_consumed) = if is_sequence_tag(tag) { 117 | if bytes.len() < 8 { 118 | return Err(()); 119 | } 120 | let length = T::u32(&bytes[4..8]) as usize; 121 | (None, length, 4) 122 | } else { 123 | T::vr_and_length(&bytes)? 124 | }; 125 | 126 | // if we have undefined length, check to make sure we have an additional 8 bytes 127 | // which will be needed for implicit little endian to detect if this is a sequence 128 | // item or not. This check occurs shortly after this function is called 129 | if length == 0xFFFF_FFFF && bytes.len() < (bytes_consumed + 8) { 130 | return Err(()); 131 | } 132 | 133 | let attribute = Attribute { 134 | tag: Tag::new(group, element), 135 | vr, 136 | length, 137 | }; 138 | Ok((bytes_consumed, attribute)) 139 | } 140 | 141 | fn is_sequence_tag(tag: Tag) -> bool { 142 | tag.group == 0xFFFE && (tag.element == 0xE000 || tag.element == 0xE00D || tag.element == 0xE0DD) 143 | } 144 | 145 | fn is_encapsulated_pixel_data(attribute: &Attribute) -> bool { 146 | attribute.tag == Tag::new(0x7fe0, 0x0010) && attribute.length == 0xffff_ffff 147 | } 148 | 149 | fn is_sequence(bytes: &[u8]) -> bool { 150 | let item_tag = Tag::from_bytes::(&bytes[0..4]); 151 | item_tag == tag::ITEM 152 | } 153 | 154 | #[cfg(test)] 155 | mod tests { 156 | 157 | use super::AttributeParser; 158 | use crate::encoding::ExplicitLittleEndian; 159 | use crate::test::tests::TestHandler; 160 | use crate::value_parser::ParseState; 161 | 162 | fn make_encapsulated_pixel_data_empty_bot() -> Vec { 163 | let mut bytes = vec![]; 164 | // Tag/VR/Length 165 | bytes.extend_from_slice(&vec![ 166 | 0xE0, 0x7F, 0x10, 0x00, b'O', b'B', 0, 0, 0xFF, 0xFF, 0xFF, 0xFF, 167 | ]); 168 | // Basic Offset Table (Empty) 169 | bytes.extend_from_slice(&vec![0xFE, 0xFF, 0x00, 0xE0, 0, 0, 0, 0]); 170 | // Fragment #1 (250 zeros) 171 | bytes.extend_from_slice(&vec![0xFE, 0xFF, 0x00, 0xE0, 250, 0, 0, 0]); 172 | bytes.extend_from_slice(&vec![0; 250]); 173 | // end with sequence item delimeter 174 | bytes.extend_from_slice(&vec![0xFE, 0xFF, 0xDD, 0xE0, 0, 0, 0, 0]); 175 | 176 | bytes 177 | } 178 | 179 | #[test] 180 | fn full_parse_completes() { 181 | let mut parser = AttributeParser::::default(); 182 | let mut handler = TestHandler::default(); 183 | let bytes = make_encapsulated_pixel_data_empty_bot(); 184 | 185 | match parser.parse(&mut handler, &bytes[..], 0) { 186 | Ok(result) => { 187 | assert_eq!(result.bytes_consumed, bytes.len()); 188 | assert_eq!(result.state, ParseState::Completed); 189 | } 190 | Err(_error) => { 191 | assert!(false); // should not happen 192 | } 193 | }; 194 | } 195 | 196 | #[test] 197 | fn streaming_parse_completes() { 198 | let mut parser = AttributeParser::::default(); 199 | let mut handler = TestHandler::default(); 200 | //handler.print = true; 201 | let bytes = make_encapsulated_pixel_data_empty_bot(); 202 | 203 | match parser.parse(&mut handler, &bytes[0..100], 0) { 204 | Ok(result) => { 205 | assert_eq!(result.bytes_consumed, 100); 206 | assert_eq!(result.state, ParseState::Incomplete); 207 | } 208 | Err(_error) => { 209 | assert!(false); // should not happen 210 | } 211 | }; 212 | match parser.parse(&mut handler, &bytes[100..], 0) { 213 | Ok(result) => { 214 | assert_eq!(result.bytes_consumed, bytes.len() - 100); 215 | assert_eq!(result.state, ParseState::Completed); 216 | } 217 | Err(_error) => { 218 | assert!(false); // should not happen 219 | } 220 | }; 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /DESIGN.md: -------------------------------------------------------------------------------- 1 | # Design Rationale for dicom-parser-rs 2 | 3 | ## Scope 4 | 5 | This DICOM parsing library takes a minimalist approach similar to the 6 | CornerstoneJS JavaScript dicom-parser library. The goal of the minimalist 7 | approach is to provide the least ammount of functionality that is still 8 | useful and designed in a way where it can be used as a building block 9 | to do more complex things. This library therefore is focused on doing 10 | one thing and one thing only - parsing DICOM binary byte streams. 11 | This focused piece of functionality is enough to support the needs for 12 | many use cases. Those that need more functionality can layer it on top 13 | of this library. It is not in scope for this library has the right design to be 14 | used as a building block to do more complex things such as writing DICOM 15 | binary byte streams, DICOM JSON, de-identification, image decompression, etc. 16 | 17 | ## Background 18 | 19 | This is the second DICOM parsing library I have written from scratch. The 20 | first was the CornerstoneJS DICOM Parser which started out as a quick and dirty 21 | hack to support my CornerstoneJS development. It grew over time due to my own 22 | needs to use it in a production application as well as others who 23 | provided fixes and feature enhancments. While the original design has held up 24 | fairly well for its intended purpose of parsing an entire DICOM P10 instance, 25 | there are new requirements that I want out of a dicom parser that require 26 | a new design. 27 | 28 | ## Data Dictionary 29 | 30 | This library does not utilize a DICOM Data Dictionary as it isn't needed to 31 | parse the DICOM DataSet binary format. When it comes to DICOM Data 32 | Dictionaries, DICOM parsing use cases generally fall into one of the 33 | following categories: 34 | * Those that don't requre a Data Dictionary at all. Most use cases fall 35 | into this category including viewing of images. 36 | * Those that will make use of a Data Dictionary if available to deliver a 37 | better user experience. For these use cases, the Data Dictionary does 38 | not have to be complete or up to date. An example of this is a feature 39 | to visualize or dump a DICOM Data Set for debugging purposes. If a 40 | given tag is in the DataSet but not in the dictionary, the tool will 41 | still work but it won't have information about that specific tag. 42 | * Those that may fail if they encounter an attribute not in the Data Dictionary. 43 | Having functionality that can fail is rare and generally undesirable - if 44 | this situation occurs, there are usually ways to workaround them (perhaps 45 | with some drawbacks). An example of such a use case is converting the 46 | transfer syntax for a DICOM P10 file from implicit little endian to explicit 47 | little endian. 48 | 49 | ## Streaming 50 | 51 | Full support for streaming was an important requirement in the design of this 52 | library. Streaming provides the following benefits compared to a design 53 | that doesn't support streaming: 54 | 55 | * minimizes resource utilization - specifically memory, but also CPU, network 56 | and disk. Non streaming libraries usually require loading the entire 57 | byte stream into memory before parsing it. Smart designs can leverage 58 | streaming to avoid additional reads/writes of byte streams. 59 | * improves system concurrency - mainly due to reduced resource utilization above 60 | * reduces costs - mainly due to reduced resource utilization above 61 | * improves consistency of perceived user experience - mainly due to reduced 62 | resource utilization above. 63 | 64 | The only drawback to a streaming based design is increased complexity of 65 | the implementation. 66 | 67 | ## Callback 68 | 69 | The Handler trait defines a callback interface that the parser invokes as it 70 | processes the DICOM DataSet byte stream. Using a callback interface provides 71 | the following benefits compared to a non callback interface: 72 | 73 | * Immediate accessibility - each DICOM Attribute is immediately made available 74 | to the Handler as it is parsed. In non callback designs, the attributes 75 | are not available until parsing is complete thus delaying access. 76 | * Efficient processing - different use cases require different processing of 77 | the DICOM DataSet. The Handler abstraction introduces no overhead to the 78 | processing allowing highly efficient and optimized processing 79 | implementations. In non callback designs, the result of a parse is usually 80 | a DataSet type object which the caller can interpret. Efficiencies can be 81 | gained by avoiding the construction of this intermediary DataSet object 82 | and let the processing logic access the underlying data stream directly. 83 | Note that a DataSet parse interface can still be provided on top of a 84 | callback interface by creating a Handler that produces a DataSet type object. 85 | * Flexible control logic. Some use cases do not require full parsing of the 86 | DICOM DataSet byte stream. The Handler trait enables implementations to 87 | implement custom control logic to cancel the parsing at any point which 88 | avoids unnecessary resource utilization. In non callback based designs, the 89 | parser needs to implement different control logics itself which may not be 90 | as flexible as a callback based design. Implementing control logic in the 91 | parser also complicates the parser implementation. 92 | 93 | ## Modularity 94 | 95 | The library is built in a modular way such that it can be easily used to handle 96 | a variety of use cases. For example, the DataSetParser can be used directly 97 | to parse non DICOM P10 byte streams. The MetaInformation class can be used 98 | directly to read the P10 header. 99 | 100 | ## Encapsulating Complexity 101 | 102 | There is a large ammount of condition logic involved with parsing DICOM 103 | DataSets. When I designed the CornerstoneJS dicom parser, the design was 104 | limited by the available language features in JavaScript at that time. The 105 | strategy I used was to break the complexity down into functions for each 106 | major condition branch. For example - explicit vs implicit, big endian vs 107 | little endian, undefined length vs known lengths. This approach made the 108 | code easier to debug and understand, but resulted in a lot of code 109 | duplication. 110 | 111 | Since this library is built on Rust, there are powerful language features such 112 | as generics and traits which can be used to improve the encapsulation, 113 | improve the performance and eliminate code duplication with no trade offs. 114 | Some applications of these features: 115 | 116 | * Encoding trait provides an interface for encapsulating logic for decoding 117 | big endian vs little endian and explict vs implicit. There are three 118 | concrete classes which encapsulate the three variations: 119 | ExplicitLittleEndian, ImplicitLittleEndian, ExplicitBigEndian. 120 | 121 | * ValueParser trait provides an interface for parsing the value field of 122 | a DICOM Attribute for a specific Encoding. The Encoding is provided via 123 | a generic parameter T to ValueParser. 124 | 125 | * Handler trait provides a callback interface for the parser to notify the 126 | consumer/user of what has been parsed. Complex processing logic can 127 | be broken up into multiple Handler implementations which can be chained 128 | together (see the included CancelHandler for an example of this) 129 | 130 | ## Words of Wisdom 131 | 132 | I discovered a few things while implementing this library that I wish I had 133 | known before starting. I wanted to capture them for those that want to 134 | implement their own parser, or if I happen to implement another DICOM parsing 135 | library some day. 136 | 137 | * The most complex part of parsing DICOM is parsing implicit little endian 138 | undefined length attributes. To handle this case properly, you must check 139 | to see if it is a sequence and if so, parse it fully. It is good to 140 | implement this case ASAP as it tends to impact the rest of the design. 141 | 142 | * The variability associated with parsing the value field of an Attribute is 143 | independent of parsing DataSets and Attribute Tag/VR/Length. Initially they 144 | seem similar since the code for parsing a DataSet and Attribute is used 145 | when parsing the value field (e.g. sequences). Intially I implemented 146 | DataSet and Attribute Parsing behind the same trait/interface that I used 147 | for value parsing along with a state pattern which ended up being fairly 148 | brittle. The design became more elegant and the codebase became cleaner 149 | when I used the trait to encapsulate value field parsing only and separated 150 | the DataSetParser and AttributeParser from it. 151 | 152 | * It is tempting to combine parsing for undefined length with known length 153 | as the logic is very similar. When the parsing logic for these two are 154 | combined, changing one tends to break the other. It may be more efficient 155 | to start out implementing these cases separately and then combine them 156 | later once everything is working properly. 157 | 158 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "arc-swap" 5 | version = "0.4.7" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "4d25d88fd6b8041580a654f9d0c581a047baee2b3efee13275f2fc392fc75034" 8 | 9 | [[package]] 10 | name = "bitflags" 11 | version = "1.2.1" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 14 | 15 | [[package]] 16 | name = "bytes" 17 | version = "0.5.5" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "118cf036fbb97d0816e3c34b2d7a1e8cfc60f68fcf63d550ddbe9bd5f59c213b" 20 | dependencies = [ 21 | "loom", 22 | ] 23 | 24 | [[package]] 25 | name = "cc" 26 | version = "1.0.54" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311" 29 | 30 | [[package]] 31 | name = "cfg-if" 32 | version = "0.1.10" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 35 | 36 | [[package]] 37 | name = "dicom-parser-rs" 38 | version = "0.1.0" 39 | dependencies = [ 40 | "futures", 41 | "tokio", 42 | ] 43 | 44 | [[package]] 45 | name = "fnv" 46 | version = "1.0.7" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 49 | 50 | [[package]] 51 | name = "fuchsia-zircon" 52 | version = "0.3.3" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 55 | dependencies = [ 56 | "bitflags", 57 | "fuchsia-zircon-sys", 58 | ] 59 | 60 | [[package]] 61 | name = "fuchsia-zircon-sys" 62 | version = "0.3.3" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 65 | 66 | [[package]] 67 | name = "futures" 68 | version = "0.3.5" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "1e05b85ec287aac0dc34db7d4a569323df697f9c55b99b15d6b4ef8cde49f613" 71 | dependencies = [ 72 | "futures-channel", 73 | "futures-core", 74 | "futures-executor", 75 | "futures-io", 76 | "futures-sink", 77 | "futures-task", 78 | "futures-util", 79 | ] 80 | 81 | [[package]] 82 | name = "futures-channel" 83 | version = "0.3.5" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5" 86 | dependencies = [ 87 | "futures-core", 88 | "futures-sink", 89 | ] 90 | 91 | [[package]] 92 | name = "futures-core" 93 | version = "0.3.5" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" 96 | 97 | [[package]] 98 | name = "futures-executor" 99 | version = "0.3.5" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "10d6bb888be1153d3abeb9006b11b02cf5e9b209fda28693c31ae1e4e012e314" 102 | dependencies = [ 103 | "futures-core", 104 | "futures-task", 105 | "futures-util", 106 | ] 107 | 108 | [[package]] 109 | name = "futures-io" 110 | version = "0.3.5" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789" 113 | 114 | [[package]] 115 | name = "futures-macro" 116 | version = "0.3.5" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39" 119 | dependencies = [ 120 | "proc-macro-hack", 121 | "proc-macro2", 122 | "quote", 123 | "syn", 124 | ] 125 | 126 | [[package]] 127 | name = "futures-sink" 128 | version = "0.3.5" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc" 131 | 132 | [[package]] 133 | name = "futures-task" 134 | version = "0.3.5" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626" 137 | dependencies = [ 138 | "once_cell", 139 | ] 140 | 141 | [[package]] 142 | name = "futures-util" 143 | version = "0.3.5" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6" 146 | dependencies = [ 147 | "futures-channel", 148 | "futures-core", 149 | "futures-io", 150 | "futures-macro", 151 | "futures-sink", 152 | "futures-task", 153 | "memchr", 154 | "pin-project", 155 | "pin-utils", 156 | "proc-macro-hack", 157 | "proc-macro-nested", 158 | "slab", 159 | ] 160 | 161 | [[package]] 162 | name = "generator" 163 | version = "0.6.21" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "add72f17bb81521258fcc8a7a3245b1e184e916bfbe34f0ea89558f440df5c68" 166 | dependencies = [ 167 | "cc", 168 | "libc", 169 | "log", 170 | "rustc_version", 171 | "winapi 0.3.8", 172 | ] 173 | 174 | [[package]] 175 | name = "hermit-abi" 176 | version = "0.1.14" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "b9586eedd4ce6b3c498bc3b4dd92fc9f11166aa908a914071953768066c67909" 179 | dependencies = [ 180 | "libc", 181 | ] 182 | 183 | [[package]] 184 | name = "iovec" 185 | version = "0.1.4" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" 188 | dependencies = [ 189 | "libc", 190 | ] 191 | 192 | [[package]] 193 | name = "kernel32-sys" 194 | version = "0.2.2" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 197 | dependencies = [ 198 | "winapi 0.2.8", 199 | "winapi-build", 200 | ] 201 | 202 | [[package]] 203 | name = "lazy_static" 204 | version = "1.4.0" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 207 | 208 | [[package]] 209 | name = "libc" 210 | version = "0.2.71" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" 213 | 214 | [[package]] 215 | name = "log" 216 | version = "0.4.8" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 219 | dependencies = [ 220 | "cfg-if", 221 | ] 222 | 223 | [[package]] 224 | name = "loom" 225 | version = "0.3.4" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "4ecc775857611e1df29abba5c41355cdf540e7e9d4acfdf0f355eefee82330b7" 228 | dependencies = [ 229 | "cfg-if", 230 | "generator", 231 | "scoped-tls", 232 | ] 233 | 234 | [[package]] 235 | name = "memchr" 236 | version = "2.3.3" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" 239 | 240 | [[package]] 241 | name = "mio" 242 | version = "0.6.22" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" 245 | dependencies = [ 246 | "cfg-if", 247 | "fuchsia-zircon", 248 | "fuchsia-zircon-sys", 249 | "iovec", 250 | "kernel32-sys", 251 | "libc", 252 | "log", 253 | "miow 0.2.1", 254 | "net2", 255 | "slab", 256 | "winapi 0.2.8", 257 | ] 258 | 259 | [[package]] 260 | name = "mio-named-pipes" 261 | version = "0.1.6" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "f5e374eff525ce1c5b7687c4cef63943e7686524a387933ad27ca7ec43779cb3" 264 | dependencies = [ 265 | "log", 266 | "mio", 267 | "miow 0.3.5", 268 | "winapi 0.3.8", 269 | ] 270 | 271 | [[package]] 272 | name = "mio-uds" 273 | version = "0.6.8" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" 276 | dependencies = [ 277 | "iovec", 278 | "libc", 279 | "mio", 280 | ] 281 | 282 | [[package]] 283 | name = "miow" 284 | version = "0.2.1" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" 287 | dependencies = [ 288 | "kernel32-sys", 289 | "net2", 290 | "winapi 0.2.8", 291 | "ws2_32-sys", 292 | ] 293 | 294 | [[package]] 295 | name = "miow" 296 | version = "0.3.5" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "07b88fb9795d4d36d62a012dfbf49a8f5cf12751f36d31a9dbe66d528e58979e" 299 | dependencies = [ 300 | "socket2", 301 | "winapi 0.3.8", 302 | ] 303 | 304 | [[package]] 305 | name = "net2" 306 | version = "0.2.34" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7" 309 | dependencies = [ 310 | "cfg-if", 311 | "libc", 312 | "winapi 0.3.8", 313 | ] 314 | 315 | [[package]] 316 | name = "num_cpus" 317 | version = "1.13.0" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 320 | dependencies = [ 321 | "hermit-abi", 322 | "libc", 323 | ] 324 | 325 | [[package]] 326 | name = "once_cell" 327 | version = "1.4.0" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" 330 | 331 | [[package]] 332 | name = "pin-project" 333 | version = "0.4.22" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "12e3a6cdbfe94a5e4572812a0201f8c0ed98c1c452c7b8563ce2276988ef9c17" 336 | dependencies = [ 337 | "pin-project-internal", 338 | ] 339 | 340 | [[package]] 341 | name = "pin-project-internal" 342 | version = "0.4.22" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "6a0ffd45cf79d88737d7cc85bfd5d2894bee1139b356e616fe85dc389c61aaf7" 345 | dependencies = [ 346 | "proc-macro2", 347 | "quote", 348 | "syn", 349 | ] 350 | 351 | [[package]] 352 | name = "pin-project-lite" 353 | version = "0.1.7" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "282adbf10f2698a7a77f8e983a74b2d18176c19a7fd32a45446139ae7b02b715" 356 | 357 | [[package]] 358 | name = "pin-utils" 359 | version = "0.1.0" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 362 | 363 | [[package]] 364 | name = "proc-macro-hack" 365 | version = "0.5.16" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4" 368 | 369 | [[package]] 370 | name = "proc-macro-nested" 371 | version = "0.1.6" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a" 374 | 375 | [[package]] 376 | name = "proc-macro2" 377 | version = "1.0.18" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" 380 | dependencies = [ 381 | "unicode-xid", 382 | ] 383 | 384 | [[package]] 385 | name = "quote" 386 | version = "1.0.7" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" 389 | dependencies = [ 390 | "proc-macro2", 391 | ] 392 | 393 | [[package]] 394 | name = "redox_syscall" 395 | version = "0.1.56" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" 398 | 399 | [[package]] 400 | name = "rustc_version" 401 | version = "0.2.3" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 404 | dependencies = [ 405 | "semver", 406 | ] 407 | 408 | [[package]] 409 | name = "scoped-tls" 410 | version = "0.1.2" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "332ffa32bf586782a3efaeb58f127980944bbc8c4d6913a86107ac2a5ab24b28" 413 | 414 | [[package]] 415 | name = "semver" 416 | version = "0.9.0" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 419 | dependencies = [ 420 | "semver-parser", 421 | ] 422 | 423 | [[package]] 424 | name = "semver-parser" 425 | version = "0.7.0" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 428 | 429 | [[package]] 430 | name = "signal-hook-registry" 431 | version = "1.2.0" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41" 434 | dependencies = [ 435 | "arc-swap", 436 | "libc", 437 | ] 438 | 439 | [[package]] 440 | name = "slab" 441 | version = "0.4.2" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 444 | 445 | [[package]] 446 | name = "socket2" 447 | version = "0.3.12" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918" 450 | dependencies = [ 451 | "cfg-if", 452 | "libc", 453 | "redox_syscall", 454 | "winapi 0.3.8", 455 | ] 456 | 457 | [[package]] 458 | name = "syn" 459 | version = "1.0.33" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "e8d5d96e8cbb005d6959f119f773bfaebb5684296108fb32600c00cde305b2cd" 462 | dependencies = [ 463 | "proc-macro2", 464 | "quote", 465 | "unicode-xid", 466 | ] 467 | 468 | [[package]] 469 | name = "tokio" 470 | version = "0.2.21" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "d099fa27b9702bed751524694adbe393e18b36b204da91eb1cbbbbb4a5ee2d58" 473 | dependencies = [ 474 | "bytes", 475 | "fnv", 476 | "futures-core", 477 | "iovec", 478 | "lazy_static", 479 | "libc", 480 | "memchr", 481 | "mio", 482 | "mio-named-pipes", 483 | "mio-uds", 484 | "num_cpus", 485 | "pin-project-lite", 486 | "signal-hook-registry", 487 | "slab", 488 | "tokio-macros", 489 | "winapi 0.3.8", 490 | ] 491 | 492 | [[package]] 493 | name = "tokio-macros" 494 | version = "0.2.5" 495 | source = "registry+https://github.com/rust-lang/crates.io-index" 496 | checksum = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389" 497 | dependencies = [ 498 | "proc-macro2", 499 | "quote", 500 | "syn", 501 | ] 502 | 503 | [[package]] 504 | name = "unicode-xid" 505 | version = "0.2.1" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 508 | 509 | [[package]] 510 | name = "winapi" 511 | version = "0.2.8" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 514 | 515 | [[package]] 516 | name = "winapi" 517 | version = "0.3.8" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 520 | dependencies = [ 521 | "winapi-i686-pc-windows-gnu", 522 | "winapi-x86_64-pc-windows-gnu", 523 | ] 524 | 525 | [[package]] 526 | name = "winapi-build" 527 | version = "0.1.1" 528 | source = "registry+https://github.com/rust-lang/crates.io-index" 529 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 530 | 531 | [[package]] 532 | name = "winapi-i686-pc-windows-gnu" 533 | version = "0.4.0" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 536 | 537 | [[package]] 538 | name = "winapi-x86_64-pc-windows-gnu" 539 | version = "0.4.0" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 542 | 543 | [[package]] 544 | name = "ws2_32-sys" 545 | version = "0.2.1" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 548 | dependencies = [ 549 | "winapi 0.2.8", 550 | "winapi-build", 551 | ] 552 | --------------------------------------------------------------------------------