Skip to content

Commit 182db60

Browse files
authored
Merge pull request #579 from Enet4/imp/fromimage/encapsulate
[fromimage] Add --encapsulate option
2 parents f054566 + a8b2e9a commit 182db60

File tree

3 files changed

+224
-80
lines changed

3 files changed

+224
-80
lines changed

fromimage/README.md

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,51 @@ This tool is part of the [DICOM-rs](https://github.com/Enet4/dicom-rs) project.
1111
## Usage
1212

1313
```none
14-
dicom-fromimage 0.1.0
15-
Convert and replace a DICOM file's image with another image
14+
Usage: dicom-fromimage [OPTIONS] <DCM_FILE> <IMG_FILE>
15+
16+
Arguments:
17+
<DCM_FILE> Path to the base DICOM file to read
18+
<IMG_FILE> Path to the image file to replace the DICOM file
19+
20+
Options:
21+
-o, --out <OUTPUT>
22+
Path to the output image (default is to replace input extension with `.new.dcm`)
23+
--transfer-syntax <TRANSFER_SYNTAX>
24+
Override the transfer syntax UID
25+
--encapsulate
26+
Encapsulate the image file raw data in a fragment sequence instead of writing native pixel data
27+
--retain-implementation
28+
Retain the implementation class UID and version name from base DICOM
29+
-v, --verbose
30+
Print more information about the image and the output file
31+
-h, --help
32+
Print help
33+
-V, --version
34+
Print version
35+
```
36+
37+
### Example
1638

17-
USAGE:
18-
dicom-fromimage.exe [FLAGS] [OPTIONS] <dcm-file> <img-file>
39+
Given a template DICOM file `base.dcm`,
40+
replace the image data with the image in `image.png`:
1941

20-
FLAGS:
21-
-h, --help Prints help information
22-
-V, --version Prints version information
23-
-v, --verbose Print more information about the image and the output file
42+
```none
43+
dicom-fromimage base.dcm image.png -o image.dcm
44+
```
2445

25-
OPTIONS:
26-
-o, --out <output> Path to the output image (default is to replace input extension with `.new.dcm`)
46+
This will read the image file in the second argument
47+
and save it as native pixel data in Explicit VR Little Endian to `image.dcm`.
2748

28-
ARGS:
29-
<dcm-file> Path to the base DICOM file to read
30-
<img-file> Path to the image file to replace the DICOM file
49+
You can also encapsulate the image file into a pixel data fragment,
50+
without converting to native pixel data.
51+
This allows you to create a DICOM file in JPEG baseline:
52+
53+
```none
54+
dicom-fromimage base.dcm image.jpg --transfer-syntax 1.2.840.10008.1.2.4.50 --encapsulate -o image.dcm
3155
```
56+
57+
**Note:** `--transfer-syntax` is just a UID override,
58+
it will not automatically transcode the pixel data
59+
to conform to the given transfer syntax.
60+
To transcode files between transfer syntaxes,
61+
see [`dicom-transcode`](https://github.com/Enet4/dicom-rs/tree/master/pixeldata).

fromimage/src/main.rs

Lines changed: 154 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,15 @@
1616
use std::path::PathBuf;
1717

1818
use clap::Parser;
19-
use dicom_core::{value::PrimitiveValue, DataElement, VR};
19+
use dicom_core::{
20+
value::{PixelFragmentSequence, PrimitiveValue},
21+
DataElement, DicomValue, VR,
22+
};
2023
use dicom_dictionary_std::tags;
21-
use dicom_object::{open_file, FileMetaTableBuilder};
24+
use dicom_object::{open_file, DefaultDicomObject, FileMetaTableBuilder};
25+
use image::DynamicImage;
26+
27+
type Result<T, E = snafu::Whatever> = std::result::Result<T, E>;
2228

2329
/// Convert and replace a DICOM file's image with another image
2430
#[derive(Debug, Parser)]
@@ -32,6 +38,13 @@ struct App {
3238
/// (default is to replace input extension with `.new.dcm`)
3339
#[arg(short = 'o', long = "out")]
3440
output: Option<PathBuf>,
41+
/// Override the transfer syntax UID (pixel data is not converted)
42+
#[arg(long = "transfer-syntax", alias = "ts")]
43+
transfer_syntax: Option<String>,
44+
/// Encapsulate the image file raw data in a fragment sequence
45+
/// instead of writing native pixel data
46+
#[arg(long)]
47+
encapsulate: bool,
3548
/// Retain the implementation class UID and version name from base DICOM
3649
#[arg(long)]
3750
retain_implementation: bool,
@@ -50,6 +63,8 @@ fn main() {
5063
dcm_file,
5164
img_file,
5265
output,
66+
encapsulate,
67+
transfer_syntax,
5368
retain_implementation,
5469
verbose,
5570
} = App::parse();
@@ -65,11 +80,147 @@ fn main() {
6580
std::process::exit(-1);
6681
});
6782

68-
let img = image::open(img_file).unwrap_or_else(|e| {
83+
if encapsulate {
84+
inject_encapsulated(&mut obj, img_file, verbose)
85+
} else {
86+
inject_image(&mut obj, img_file, verbose)
87+
}
88+
.unwrap_or_else(|e| {
89+
tracing::error!("{}", snafu::Report::from_error(e));
90+
std::process::exit(-2);
91+
});
92+
93+
let class_uid = obj.meta().media_storage_sop_class_uid.clone();
94+
95+
let mut meta_builder = FileMetaTableBuilder::new()
96+
// currently the tool will always decode the image's pixel data,
97+
// so encode it as Explicit VR Little Endian
98+
.transfer_syntax("1.2.840.10008.1.2.1")
99+
.media_storage_sop_class_uid(class_uid);
100+
101+
if let Some(ts) = transfer_syntax {
102+
meta_builder = meta_builder.transfer_syntax(ts);
103+
}
104+
105+
// recover implementation class UID and version name from base object
106+
if retain_implementation {
107+
let implementation_class_uid = &obj.meta().implementation_class_uid;
108+
meta_builder = meta_builder.implementation_class_uid(implementation_class_uid);
109+
110+
if let Some(implementation_version_name) = obj.meta().implementation_version_name.as_ref() {
111+
meta_builder = meta_builder.implementation_version_name(implementation_version_name);
112+
}
113+
}
114+
115+
let obj = obj
116+
.into_inner()
117+
.with_meta(meta_builder)
118+
.unwrap_or_else(|e| {
119+
tracing::error!("{}", snafu::Report::from_error(e));
120+
std::process::exit(-3);
121+
});
122+
123+
obj.write_to_file(&output).unwrap_or_else(|e| {
124+
tracing::error!("{}", snafu::Report::from_error(e));
125+
std::process::exit(-4);
126+
});
127+
128+
if verbose {
129+
println!("DICOM file saved to {}", output.display());
130+
}
131+
}
132+
133+
fn inject_image(obj: &mut DefaultDicomObject, img_file: PathBuf, verbose: bool) -> Result<()> {
134+
let image_reader = image::ImageReader::open(img_file).unwrap_or_else(|e| {
135+
tracing::error!("{}", snafu::Report::from_error(e));
136+
std::process::exit(-1);
137+
});
138+
139+
let img = image_reader.decode().unwrap_or_else(|e| {
140+
tracing::error!("{}", snafu::Report::from_error(e));
141+
std::process::exit(-1);
142+
});
143+
144+
let color = img.color();
145+
146+
let bits_stored: u16 = match color {
147+
image::ColorType::L8 => 8,
148+
image::ColorType::L16 => 16,
149+
image::ColorType::Rgb8 => 8,
150+
image::ColorType::Rgb16 => 16,
151+
_ => {
152+
eprintln!("Unsupported image format {:?}", color);
153+
std::process::exit(-2);
154+
}
155+
};
156+
157+
update_from_img(obj, &img, verbose);
158+
159+
for tag in [
160+
tags::NUMBER_OF_FRAMES,
161+
tags::PIXEL_ASPECT_RATIO,
162+
tags::SMALLEST_IMAGE_PIXEL_VALUE,
163+
tags::LARGEST_IMAGE_PIXEL_VALUE,
164+
tags::PIXEL_PADDING_RANGE_LIMIT,
165+
tags::RED_PALETTE_COLOR_LOOKUP_TABLE_DATA,
166+
tags::RED_PALETTE_COLOR_LOOKUP_TABLE_DESCRIPTOR,
167+
tags::GREEN_PALETTE_COLOR_LOOKUP_TABLE_DATA,
168+
tags::GREEN_PALETTE_COLOR_LOOKUP_TABLE_DESCRIPTOR,
169+
tags::BLUE_PALETTE_COLOR_LOOKUP_TABLE_DATA,
170+
tags::BLUE_PALETTE_COLOR_LOOKUP_TABLE_DESCRIPTOR,
171+
tags::ICC_PROFILE,
172+
tags::COLOR_SPACE,
173+
tags::PIXEL_DATA_PROVIDER_URL,
174+
tags::EXTENDED_OFFSET_TABLE,
175+
tags::EXTENDED_OFFSET_TABLE_LENGTHS,
176+
] {
177+
obj.remove_element(tag);
178+
}
179+
180+
let pixeldata = img.into_bytes();
181+
182+
obj.put(DataElement::new(
183+
tags::PIXEL_DATA,
184+
if bits_stored == 8 { VR::OB } else { VR::OW },
185+
PrimitiveValue::from(pixeldata),
186+
));
187+
188+
Ok(())
189+
}
190+
191+
fn inject_encapsulated(
192+
dcm: &mut DefaultDicomObject,
193+
img_file: PathBuf,
194+
verbose: bool,
195+
) -> Result<()> {
196+
let image_reader = image::ImageReader::open(&img_file).unwrap_or_else(|e| {
69197
tracing::error!("{}", snafu::Report::from_error(e));
70198
std::process::exit(-1);
71199
});
72200

201+
// collect img file data
202+
let all_data = std::fs::read(img_file).unwrap_or_else(|e| {
203+
tracing::error!("{}", snafu::Report::from_error(e));
204+
std::process::exit(-2);
205+
});
206+
207+
if let Ok(img) = image_reader.decode() {
208+
// insert attributes but not pixel data
209+
210+
update_from_img(&mut *dcm, &img, verbose);
211+
}
212+
213+
// insert pixel data in a sequence
214+
dcm.put(DataElement::new(
215+
tags::PIXEL_DATA,
216+
VR::OB,
217+
DicomValue::PixelSequence(PixelFragmentSequence::new_fragments(vec![all_data])),
218+
));
219+
220+
Ok(())
221+
}
222+
223+
fn update_from_img(obj: &mut DefaultDicomObject, img: &DynamicImage, verbose: bool) {
73224
let width = img.width();
74225
let height = img.height();
75226
let color = img.color();
@@ -85,8 +236,6 @@ fn main() {
85236
}
86237
};
87238

88-
let pixeldata = img.into_bytes();
89-
90239
if verbose {
91240
println!("{}x{} {:?} image", width, height, color);
92241
}
@@ -151,68 +300,6 @@ fn main() {
151300
VR::US,
152301
PrimitiveValue::from(0_u16),
153302
));
154-
155-
for tag in [
156-
tags::NUMBER_OF_FRAMES,
157-
tags::PIXEL_ASPECT_RATIO,
158-
tags::SMALLEST_IMAGE_PIXEL_VALUE,
159-
tags::LARGEST_IMAGE_PIXEL_VALUE,
160-
tags::PIXEL_PADDING_RANGE_LIMIT,
161-
tags::RED_PALETTE_COLOR_LOOKUP_TABLE_DATA,
162-
tags::RED_PALETTE_COLOR_LOOKUP_TABLE_DESCRIPTOR,
163-
tags::GREEN_PALETTE_COLOR_LOOKUP_TABLE_DATA,
164-
tags::GREEN_PALETTE_COLOR_LOOKUP_TABLE_DESCRIPTOR,
165-
tags::BLUE_PALETTE_COLOR_LOOKUP_TABLE_DATA,
166-
tags::BLUE_PALETTE_COLOR_LOOKUP_TABLE_DESCRIPTOR,
167-
tags::ICC_PROFILE,
168-
tags::COLOR_SPACE,
169-
tags::PIXEL_DATA_PROVIDER_URL,
170-
tags::EXTENDED_OFFSET_TABLE,
171-
tags::EXTENDED_OFFSET_TABLE_LENGTHS,
172-
] {
173-
obj.remove_element(tag);
174-
}
175-
176-
obj.put(DataElement::new(
177-
tags::PIXEL_DATA,
178-
if bits_stored == 8 { VR::OB } else { VR::OW },
179-
PrimitiveValue::from(pixeldata),
180-
));
181-
182-
let class_uid = obj.meta().media_storage_sop_class_uid.clone();
183-
184-
let mut meta_builder = FileMetaTableBuilder::new()
185-
// currently the tool will always decode the image's pixel data,
186-
// so encode it as Explicit VR Little Endian
187-
.transfer_syntax("1.2.840.10008.1.2.1")
188-
.media_storage_sop_class_uid(class_uid);
189-
190-
// recover implementation class UID and version name from base object
191-
if retain_implementation {
192-
let implementation_class_uid = &obj.meta().implementation_class_uid;
193-
meta_builder = meta_builder.implementation_class_uid(implementation_class_uid);
194-
195-
if let Some(implementation_version_name) = obj.meta().implementation_version_name.as_ref() {
196-
meta_builder = meta_builder.implementation_version_name(implementation_version_name);
197-
}
198-
}
199-
200-
let obj = obj
201-
.into_inner()
202-
.with_meta(meta_builder)
203-
.unwrap_or_else(|e| {
204-
tracing::error!("{}", snafu::Report::from_error(e));
205-
std::process::exit(-3);
206-
});
207-
208-
obj.write_to_file(&output).unwrap_or_else(|e| {
209-
tracing::error!("{}", snafu::Report::from_error(e));
210-
std::process::exit(-4);
211-
});
212-
213-
if verbose {
214-
println!("DICOM file saved to {}", output.display());
215-
}
216303
}
217304

218305
#[cfg(test)]

pixeldata/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,30 @@ and is responsible for decoding pixel data elements
99
into images or multi-dimensional arrays.
1010

1111
This crate is part of the [DICOM-rs](https://github.com/Enet4/dicom-rs) project.
12+
13+
## Binary
14+
15+
`dicom-pixeldata` also offers the `dicom-transcode` command-line tool
16+
(enable Cargo feature `cli`).
17+
You can use it to transcode a DICOM file to another transfer syntax,
18+
transforming pixel data along the way.
19+
20+
```none
21+
Usage: dicom-transcode [OPTIONS] <--ts <TS>|--expl-vr-le|--impl-vr-le|--jpeg-baseline> <FILE>
22+
23+
Arguments:
24+
<FILE>
25+
26+
Options:
27+
-o, --output <OUTPUT> The output file (default is to change the extension to .new.dcm)
28+
--quality <QUALITY> The encoding quality (from 0 to 100)
29+
--effort <EFFORT> The encoding effort (from 0 to 100)
30+
--ts <TS> Transcode to the Transfer Syntax indicated by UID
31+
--expl-vr-le Transcode to Explicit VR Little Endian
32+
--impl-vr-le Transcode to Implicit VR Little Endian
33+
--jpeg-baseline Transcode to JPEG baseline (8-bit)
34+
--retain-implementation Retain the original implementation class UID and version name
35+
-v, --verbose Verbose mode
36+
-h, --help Print help
37+
-V, --version Print version
38+
```

0 commit comments

Comments
 (0)